space-game001/src/Location.cpp
Vladislav Khorev 60a1422232 Adding quests
2026-04-19 09:28:28 +03:00

1832 lines
65 KiB
C++

#include "Location.h"
#include "utils/Utils.h"
#include "render/OpenGlExtensions.h"
#include <iostream>
#include "render/TextureManager.h"
#include "TextModel.h"
#include <random>
#include <cmath>
#include <algorithm>
#include <functional>
#include <memory>
#include <cfloat>
#include "GameConstants.h"
namespace ZL
{
extern float x;
extern const char* CONST_ZIP_FILE;
static constexpr float CAMERA_FOV_Y = 1.0f / 1.5f;
Location::Location(Renderer& iRenderer, Inventory& iInventory, const std::string& locId)
: renderer(iRenderer)
, inventory(iInventory)
, locationId(locId)
, roofObjectKey("")
, azsRoofObjectKey("")
{
}
void Location::setup()
{
tileMesh.data = LoadFromTextFile02("resources/e/land/land003.txt", CONST_ZIP_FILE);
tileMesh.RefreshVBO();
roadTexture = std::make_shared<Texture>(CreateTextureDataFromPng("resources/e/land/Gemini_Generated_Image_eq858beq858beq85.png", CONST_ZIP_FILE));
grassTexture = std::make_shared<Texture>(CreateTextureDataFromPng("resources/e/land/Gemini_Generated_Image_321v9q321v9q321v.png", CONST_ZIP_FILE));
auto playerTexture0 = std::make_shared<Texture>(CreateTextureDataFromPng("resources/e/male_packed0_diffuse.png", CONST_ZIP_FILE));
auto playerTexture1 = std::make_shared<Texture>(CreateTextureDataFromPng("resources/e/male_packed1_diffuse.png", CONST_ZIP_FILE));
auto playerTexture2 = std::make_shared<Texture>(CreateTextureDataFromPng("resources/e/male_packed2_diffuse.png", CONST_ZIP_FILE));
player = std::make_unique<Character>();
player->loadBinaryAnimation(AnimationState::STAND, "resources/e/gg_stand_idle003.anim2", CONST_ZIP_FILE);
player->loadBinaryAnimation(AnimationState::WALK, "resources/e/gg_run003.anim2", CONST_ZIP_FILE);
player->setTexture("Body", playerTexture0);
player->setTexture("Bottoms", playerTexture1);
player->setTexture("Eyelashes", playerTexture0);
player->setTexture("Eyes", playerTexture0);
player->setTexture("Eyewear", playerTexture0);
player->setTexture("Gloves", playerTexture1);
player->setTexture("Hair", playerTexture0);
player->setTexture("Shoes", playerTexture1);
player->setTexture("Tops", playerTexture1);
player->walkSpeed = 3.0f;
player->rotationSpeed = 8.0f;
player->modelScale = 0.01f;
player->modelCorrectionRotation = Eigen::Quaternionf(Eigen::AngleAxisf(M_PI, Eigen::Vector3f::UnitY()));
player->canAttack = true;
player->isPlayer = true;
//player->position = { 9.43527, 0, 0.952688 };
//player->setTarget(player->position);
auto girlfriendTexture0 = std::make_shared<Texture>(CreateTextureDataFromPng("resources/e/female_packed0_diffuse.png", CONST_ZIP_FILE));
auto girlfriendTexture1 = std::make_shared<Texture>(CreateTextureDataFromPng("resources/e/female_packed1_diffuse.png", CONST_ZIP_FILE));
auto girlfriendTexture2 = std::make_shared<Texture>(CreateTextureDataFromPng("resources/e/female_packed2_diffuse.png", CONST_ZIP_FILE));
auto girlfriendTexture3 = std::make_shared<Texture>(CreateTextureDataFromPng("resources/e/female_packed3_diffuse.png", CONST_ZIP_FILE));
girlfriend = std::make_unique<Character>();
girlfriend->loadBinaryAnimation(AnimationState::STAND, "resources/e/woman_stand_idle003.anim2", CONST_ZIP_FILE);
girlfriend->loadBinaryAnimation(AnimationState::WALK, "resources/e/woman_run003.anim2", CONST_ZIP_FILE);
girlfriend->setTexture("Body", girlfriendTexture0);
girlfriend->setTexture("Bottoms", girlfriendTexture3);
girlfriend->setTexture("Eyelashes", girlfriendTexture0);
girlfriend->setTexture("Eyes", girlfriendTexture0);
girlfriend->setTexture("Hair", girlfriendTexture2);
girlfriend->setTexture("Shoes", girlfriendTexture1);
girlfriend->setTexture("Tops", girlfriendTexture2);
girlfriend->walkSpeed = 3.0f;
girlfriend->rotationSpeed = 8.0f;
girlfriend->modelScale = 0.01f;
girlfriend->modelCorrectionRotation = Eigen::Quaternionf(Eigen::AngleAxisf(M_PI, Eigen::Vector3f::UnitY()));
//girlfriend->position = Vector3f{ -10, -0, -10 };
//girlfriend->setTarget(girlfriend->position);
auto salesPersonTexture0 = std::make_shared<Texture>(CreateTextureDataFromPng("resources/e/Salesperson_packed0_diffuse.png", CONST_ZIP_FILE));
auto salesPersonTexture1 = std::make_shared<Texture>(CreateTextureDataFromPng("resources/e/Salesperson_packed1_diffuse.png", CONST_ZIP_FILE));
auto salesPersonTexture2 = std::make_shared<Texture>(CreateTextureDataFromPng("resources/e/Salesperson_packed2_diffuse.png", CONST_ZIP_FILE));
salesperson = std::make_unique<Character>();
salesperson->loadBinaryAnimation(AnimationState::STAND, "resources/e/salesperson_stand_idle003.anim2", CONST_ZIP_FILE);
salesperson->loadBinaryAnimation(AnimationState::WALK, "resources/e/salesperson_stand_idle003.anim2", CONST_ZIP_FILE);
salesperson->setTexture("Body", salesPersonTexture0);
salesperson->setTexture("Bottoms", salesPersonTexture1);
salesperson->setTexture("Eyelashes", salesPersonTexture0);
salesperson->setTexture("Eyes", salesPersonTexture0);
salesperson->setTexture("Hair", salesPersonTexture1);
salesperson->setTexture("Shoes", salesPersonTexture1);
salesperson->setTexture("Tops", salesPersonTexture1);
salesperson->walkSpeed = 3.0f;
salesperson->rotationSpeed = 8.0f;
salesperson->modelScale = 0.01f;
salesperson->modelCorrectionRotation = Eigen::Quaternionf(Eigen::AngleAxisf(0, Eigen::Vector3f::UnitY()));
salesperson->position = Vector3f{ -8.31099, -0, -3.56868 };
salesperson->setTarget(salesperson->position);
auto policeTexture0 = std::make_shared<Texture>(CreateTextureDataFromPng("resources/e/police_packed0_diffuse.png", CONST_ZIP_FILE));
auto policeTexture1 = std::make_shared<Texture>(CreateTextureDataFromPng("resources/e/police_packed1_diffuse.png", CONST_ZIP_FILE));
police = std::make_unique<Character>();
police->loadBinaryAnimation(AnimationState::STAND, "resources/e/police_stand_idle003.anim2", CONST_ZIP_FILE);
police->loadBinaryAnimation(AnimationState::WALK, "resources/e/police_stand_idle003.anim2", CONST_ZIP_FILE);
police->setTexture("Body", policeTexture0);
police->setTexture("Bottoms", policeTexture1);
police->setTexture("Eyelashes", policeTexture0);
police->setTexture("Eyes", policeTexture0);
police->setTexture("Hair", policeTexture1);
police->setTexture("Shoes", policeTexture1);
police->setTexture("Tops", policeTexture1);
police->walkSpeed = 3.0f;
police->rotationSpeed = 8.0f;
police->modelScale = 0.01f;
police->modelCorrectionRotation = Eigen::Quaternionf(Eigen::AngleAxisf(M_PI, Eigen::Vector3f::UnitY()));
police->position = Vector3f{ 1000, -0, 10 };
police->setTarget(police->position);
auto banditTexture0 = std::make_shared<Texture>(CreateTextureDataFromPng("resources/e/bandit_packed0_diffuse.png", CONST_ZIP_FILE));
auto banditTexture1 = std::make_shared<Texture>(CreateTextureDataFromPng("resources/e/bandit_packed1_diffuse.png", CONST_ZIP_FILE));
auto banditTexture2 = std::make_shared<Texture>(CreateTextureDataFromPng("resources/e/bandit_packed2_diffuse.png", CONST_ZIP_FILE));
bandit = std::make_unique<Character>();
bandit->loadBinaryAnimation(AnimationState::STAND, "resources/e/bandit_stand_idle003.anim2", CONST_ZIP_FILE);
bandit->loadBinaryAnimation(AnimationState::WALK, "resources/e/bandit_stand_idle003.anim2", CONST_ZIP_FILE);
bandit->setTexture("Body", banditTexture0);
bandit->setTexture("Bottoms", banditTexture1);
bandit->setTexture("Eyelashes", banditTexture0);
bandit->setTexture("Eyes", policeTexture0);
bandit->setTexture("Hair", banditTexture1);
bandit->setTexture("Shoes", banditTexture2);
bandit->setTexture("Tops", banditTexture2);
bandit->walkSpeed = 3.0f;
bandit->rotationSpeed = 8.0f;
bandit->modelScale = 0.01f;
bandit->modelCorrectionRotation = Eigen::Quaternionf(Eigen::AngleAxisf(M_PI, Eigen::Vector3f::UnitY()));
bandit->position = Vector3f{ 6, 0, 6 };
bandit->setTarget(bandit->position);
//girlfriend->canAttack = true;
//player->isPlayer = true;
struct ModelAsset {
const char* meshPath;
const char* texPath;
Eigen::Vector3f position;
float scale;
};
if (locationId == "forest")
{
std::cout << "[LOCATION] Setting up FOREST location (custom models only)" << std::endl;
carPosition ={ 7, 0, -7 + 200 };
npcCar.position = Vector3f(9, 0, -335) + Vector3f(1000,0,0);
girlfriend->position = Vector3f{ 5, 0, 0.9 + 200 };
girlfriend->setTarget(girlfriend->position);
player->position = { 9.5, 0, 0.95 + 200 };
player->setTarget(player->position);
/* gameObjects.clear();
interactiveObjects.clear();
npcs.clear();
debugNavMeshes.clear();
roomMesh.data = VertexDataStruct();
//roomMesh.data = LoadFromTextFile02("resources/w/road001.txt", CONST_ZIP_FILE);
//roomMesh.RefreshVBO();
roomTexture = std::make_unique<Texture>(CreateTextureDataFromPng("resources/w/room005.png", CONST_ZIP_FILE));
*/
std::vector<ModelAsset> models = {
{"resources/out/AzsBase001.txt", "resources/ghost_avatar.png", {0.0f, 2.0f, 0.0f}, 2.0f},
{"resources/out/AzsRoof001.txt", "resources/ghost_avatar.png", {0.0f, 2.0f, 0.0f}, 2.0f},
{"resources/out/floor001.txt", "resources/ghost_avatar.png", {0.0f, -1.97f, 0.0f}, 2.0f},
{"resources/out/roof001.txt", "resources/ghost_avatar.png", {0.0f, 2.0f, 0.0f}, 2.0f},
{"resources/out/Walls001.txt", "resources/ghost_avatar.png", {0.0f, 2.0f, 0.0f}, 2.0f},
{"resources/out/Price001.txt", "resources/w/blue.png", {0.0f, 1.0f, 5.0f}, 2.0f}
};
for (size_t i = 0; i < models.size(); ++i) {
const auto& m = models[i];
LoadedGameObject obj;
obj.mesh.data = LoadFromTextFile02(m.meshPath, CONST_ZIP_FILE);
obj.mesh.data.Scale(m.scale);
obj.mesh.data.Move(m.position);
obj.mesh.RefreshVBO();
obj.texture = std::make_shared<Texture>(CreateTextureDataFromPng(m.texPath, CONST_ZIP_FILE));
if (std::string(m.meshPath) == "resources/out/AzsRoof001.txt") {
gameObjects["azsRoof"] = std::move(obj);
azsRoofObjectKey = "azsRoof";
azsRoofHideZones.clear();
azsRoofHideZones.emplace_back(Eigen::Vector2f(-7.0f, -14.0f), Eigen::Vector2f(0.1f, -8.5f));
}
else if (std::string(m.meshPath) == "resources/out/roof001.txt") {
gameObjects["roof"] = std::move(obj);
roofObjectKey = "roof";
roofHideZones.clear();
roofHideZones.emplace_back(Eigen::Vector2f(-9.3f, -5.0f), Eigen::Vector2f(2.5f, 1.7f));
}
else {
gameObjects["forest_model_" + std::to_string(i)] = std::move(obj);
}
}
shadowMap = std::make_unique<ShadowMap>(2048, 40.0f, 0.1f, 100.0f);
shadowMap->setLightDirection(Eigen::Vector3f(-0.5f, -1.0f, -0.3f));
setupNavigation();
scriptEngine.init(this, &inventory);
dialogueSystem.init(renderer, CONST_ZIP_FILE);
dialogueSystem.loadDatabase("resources/dialogue/sample_dialogues.json");
std::cout << "[FOREST] Setup complete, loaded " << gameObjects.size() << " custom models" << std::endl;
}
else // default
{
carPosition = { 5.4005, 0, -3.811283 };
npcCar.position = Vector3f(9, 0, -100);
player->position = { 9.43527, 0, 0.952688 };
player->setTarget(player->position);
girlfriend->position = Vector3f{ -10, -0, -10 };
girlfriend->setTarget(girlfriend->position);
std::vector<ModelAsset> models = {
{"resources/out/main/birch-1.txt", "resources/ghost_avatar.png", {0.0f, 0.0f, 0.0f}, 2.0f},
{"resources/out/main/birch-11.txt", "resources/ghost_avatar.png", {0.0f, 0.0f, 0.0f}, 2.0f},
{"resources/out/main/birch-2.txt", "resources/ghost_avatar.png", {0.0f, 0.0f, 0.0f}, 2.0f},
{"resources/out/main/birch-22.txt", "resources/ghost_avatar.png", {0.0f, 0.0f, 0.0f}, 2.0f},
{"resources/out/main/birch-3.txt", "resources/ghost_avatar.png", {0.0f, 0.0f, 0.0f}, 2.0f},
{"resources/out/main/birch-33.txt", "resources/w/blue.png", {0.0f, 0.0f, 0.0f}, 2.0f},
{"resources/out/main/birch-4.txt", "resources/ghost_avatar.png", {0.0f, 0.0f, 0.0f}, 2.0f},
{"resources/out/main/birch-44.txt", "resources/ghost_avatar.png", {0.0f, 0.0f, 0.0f}, 2.0f},
{"resources/out/main/birch-5.txt", "resources/ghost_avatar.png", {0.0f, 0.0f, 0.0f}, 2.0f},
{"resources/out/main/birch-55.txt", "resources/ghost_avatar.png", {0.0f, 0.0f, 0.0f}, 2.0f},
{"resources/out/main/birch-6.txt", "resources/ghost_avatar.png", {0.0f, 0.0f, 0.0f}, 2.0f},
{"resources/out/main/birch-66.txt", "resources/w/blue.png", {0.0f, 0.0f, 0.0f}, 2.0f},
{"resources/out/main/cube.txt", "resources/ghost_avatar.png", {0.0f, 0.0f, 0.0f}, 2.0f},
{"resources/out/main/cube1.txt", "resources/ghost_avatar.png", {0.0f, 0.0f, 0.0f}, 2.0f},
{"resources/out/main/cube2.txt", "resources/ghost_avatar.png", {0.0f, 0.0f, 0.0f}, 2.0f},
{"resources/out/main/cube3.txt", "resources/ghost_avatar.png", {0.0f, 0.0f, 0.0f}, 2.0f},
{"resources/out/main/cube4.txt", "resources/ghost_avatar.png", {0.0f, 0.0f, 0.0f}, 2.0f},
{"resources/out/main/cube5.txt", "resources/w/blue.png", {0.0f, 0.0f, 0.0f}, 2.0f},
{"resources/out/main/cube6.txt", "resources/ghost_avatar.png", {0.0f, 0.0f, 0.0f}, 2.0f},
{"resources/out/main/cube7.txt", "resources/ghost_avatar.png", {0.0f, 0.0f, 0.0f}, 2.0f},
{"resources/out/main/cube8.txt", "resources/ghost_avatar.png", {0.0f, 0.0f, 0.0f}, 2.0f},
{"resources/out/main/cube9.txt", "resources/ghost_avatar.png", {0.0f, 0.0f, 0.0f}, 2.0f},
{"resources/out/main/cube10.txt", "resources/ghost_avatar.png", {0.0f, 0.0f, 0.0f}, 2.0f},
{"resources/out/main/cube11.txt", "resources/w/blue.png", {0.0f, 0.0f, 0.0f}, 2.0f},
{"resources/out/main/cube12.txt", "resources/ghost_avatar.png", {0.0f, 0.0f, 0.0f}, 2.0f},
{"resources/out/main/cube13.txt", "resources/ghost_avatar.png", {0.0f, 0.0f, 0.0f}, 2.0f},
{"resources/out/main/cube14.txt", "resources/ghost_avatar.png", {0.0f, 0.0f, 0.0f}, 2.0f},
{"resources/out/main/cube15.txt", "resources/w/blue.png", {0.0f, 0.0f, 0.0f}, 2.0f}
};
for (size_t i = 0; i < models.size(); ++i) {
const auto& m = models[i];
LoadedGameObject obj;
obj.mesh.data = LoadFromTextFile02(m.meshPath, CONST_ZIP_FILE);
obj.mesh.data.Scale(m.scale);
obj.mesh.data.Move(m.position);
obj.mesh.RefreshVBO();
obj.texture = std::make_shared<Texture>(CreateTextureDataFromPng(m.texPath, CONST_ZIP_FILE));
gameObjects["default_model_" + std::to_string(i)] = std::move(obj);
}
/*std::cout << "[LOCATION] Setting up DEFAULT location" << std::endl;
std::string roomTexPath = "resources/w/room005.png";
std::string roomMeshPath = "resources/w/room001.txt";
std::string gameObjectsPath = "resources/config2/gameobjects.json";
std::string interactiveObjectsPath = "resources/config2/gameobjects.json";
std::string npcsPath = "resources/config2/npcs.json";
roomTexture = std::make_unique<Texture>(CreateTextureDataFromPng(roomTexPath.c_str(), CONST_ZIP_FILE));
roomMesh.data = LoadFromTextFile02(roomMeshPath.c_str(), CONST_ZIP_FILE);
roomMesh.data.RotateByMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(-M_PI * 0.5, Eigen::Vector3f::UnitY())).toRotationMatrix());
roomMesh.RefreshVBO();
gameObjects = GameObjectLoader::loadAndCreateGameObjects(gameObjectsPath, renderer, CONST_ZIP_FILE);
interactiveObjects = GameObjectLoader::loadAndCreateInteractiveObjects(interactiveObjectsPath, renderer, CONST_ZIP_FILE);
npcs = GameObjectLoader::loadAndCreateNpcs(npcsPath, CONST_ZIP_FILE);
auto playerTexture = std::make_shared<Texture>(CreateTextureDataFromPng("resources/w/gg/IMG_20260413_182354_992.png", CONST_ZIP_FILE));
player = std::make_unique<Character>();
player->loadAnimation(AnimationState::STAND, "resources/e/woman_stand_idle003.txt", CONST_ZIP_FILE);
player->loadAnimation(AnimationState::WALK, "resources/e/woman_stand_idle003.txt", CONST_ZIP_FILE);
player->setTexture("Body", playerTexture1);
player->setTexture("Bottoms", playerTexture4);
player->setTexture("Eyelashes", playerTexture1);
player->setTexture("Eyes", playerTexture1);
player->setTexture("Hair", playerTexture3);
player->setTexture("Shoes", playerTexture1);
player->setTexture("Tops", playerTexture3);
player->walkSpeed = 3.0f;
player->rotationSpeed = 8.0f;
player->modelScale = 0.01f;
player->modelCorrectionRotation = Eigen::Quaternionf(Eigen::AngleAxisf(M_PI, Eigen::Vector3f::UnitY()));
player->canAttack = true;
player->isPlayer = true;
*/
/*
auto ghostTexture = std::make_shared<Texture>(CreateTextureDataFromPng("resources/w/ghost_skin001.png", CONST_ZIP_FILE));
auto npc02 = std::make_unique<Character>();
npc02->loadBinaryAnimation(AnimationState::STAND, "resources/w/default_float001.anim", CONST_ZIP_FILE);
npc02->loadBinaryAnimation(AnimationState::WALK, "resources/w/default_float001.anim", CONST_ZIP_FILE);
npc02->loadBinaryAnimation(AnimationState::ACTION_IDLE, "resources/w/float_attack003_cut.anim", CONST_ZIP_FILE);
npc02->loadBinaryAnimation(AnimationState::ACTION_ATTACK, "resources/w/float_attack003.anim", CONST_ZIP_FILE);
npc02->loadBinaryAnimation(AnimationState::STAND_TO_ACTION, "resources/w/default_float001_cut.anim", CONST_ZIP_FILE);
npc02->loadBinaryAnimation(AnimationState::ACTION_TO_STAND, "resources/w/default_float001_cut.anim", CONST_ZIP_FILE);
npc02->setTexture(ghostTexture);
npc02->walkSpeed = 1.5f;
npc02->rotationSpeed = 8.0f;
npc02->modelScale = 0.01f;
npc02->modelCorrectionRotation = Eigen::Quaternionf(Eigen::AngleAxisf(M_PI, Eigen::Vector3f::UnitY()));
npc02->position = Eigen::Vector3f(0.f, 0.f, -20.f);
npc02->setTarget(npc02->position);
npc02->canAttack = true;
npc02->attackTarget = player.get();
npcs.push_back(std::move(npc02));
*/
shadowMap = std::make_unique<ShadowMap>(2048, 40.0f, 0.1f, 100.0f);
shadowMap->setLightDirection(Eigen::Vector3f(-0.5f, -1.0f, -0.3f));
//setupNavigation();
scriptEngine.init(this, &inventory);
dialogueSystem.init(renderer, CONST_ZIP_FILE);
dialogueSystem.loadDatabase("resources/dialogue/sample_dialogues.json");
}
carTexture = std::make_unique<Texture>(CreateTextureDataFromPng("resources/e/car002.png", CONST_ZIP_FILE));
carMesh.data = LoadFromTextFile02("resources/e/car001.txt", CONST_ZIP_FILE);
carMesh.data.RotateByMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(M_PI * 0.5, Eigen::Vector3f::UnitY())).toRotationMatrix());
carMesh.RefreshVBO();
carWheelTexture = std::make_unique<Texture>(CreateTextureDataFromPng("resources/e/Wheel_base002.png", CONST_ZIP_FILE));
carWheelMesh.data = LoadFromTextFile02("resources/e/car_wheel001.txt", CONST_ZIP_FILE);
carWheelMesh.data.RotateByMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(M_PI * 0.5, Eigen::Vector3f::UnitY())).toRotationMatrix());
carWheelMesh.RefreshVBO();
npcCar.texture = std::make_shared<Texture>(CreateTextureDataFromPng("resources/e/car_black001.png", CONST_ZIP_FILE));
//npcCar.position = carPosition + Eigen::Vector3f(0, 0.f, 14.f);//Eigen::Vector3f(-12.f, 0.f, 8.f);
npcCar.rotation = 0.f;
npcCar.mode = NpcCar::Mode::NONE;
/*npcCar.mode = NpcCar::Mode::FOLLOW_WAYPOINTS;
npcCar.waypoints = {
Eigen::Vector3f(-12.f, 0.f, 8.f),
Eigen::Vector3f( 12.f, 0.f, 8.f),
Eigen::Vector3f( 12.f, 0.f, -8.f),
Eigen::Vector3f(-12.f, 0.f, -8.f),
};*/
//npcCar.currentWaypoint = 0;
}
void Location::setupNavigation()
{
std::vector<PathFinder::ObstacleMesh> obstacles;
if (locationId == "forest") {
navigation.build(obstacles, "resources/config2/navigation2.json", CONST_ZIP_FILE);
}
else {
navigation.build(obstacles, "resources/config2/navigation.json", CONST_ZIP_FILE);
}
//#ifdef SHOW_PATH
buildDebugNavMeshes();
buildDebugForbiddenMeshes();
//#endif
auto planner = [this](const Eigen::Vector3f& start, const Eigen::Vector3f& end) {
return navigation.findPath(start, end);
};
if (player) {
player->setPathPlanner(planner);
}
for (auto& npc : npcs) {
if (npc) {
npc->setPathPlanner(planner);
}
}
}
//#ifdef SHOW_PATH
void Location::buildDebugNavMeshes()
{
debugNavMeshes.clear();
const auto& areas = navigation.getAreas();
float y = navigation.getFloorY() + 0.02f;
Eigen::Vector3f red(1.0f, 0.0f, 0.0f);
for (const auto& area : areas) {
if (area.polygon.size() < 3) continue;
VertexRenderStruct mesh;
mesh.data = CreatePolygonFloor(area.polygon, y, red);
mesh.RefreshVBO();
debugNavMeshes.push_back(std::move(mesh));
}
}
void Location::drawDebugNavigation()
{
renderer.shaderManager.PushShader("defaultColor");
renderer.SetMatrix();
for (const auto& mesh : debugNavMeshes) {
renderer.DrawVertexRenderStruct(mesh);
}
renderer.shaderManager.PopShader();
renderer.SetMatrix();
}
void Location::buildDebugForbiddenMeshes()
{
debugForbiddenMeshes.clear();
const auto& areas = navigation.getAreas();
float y = navigation.getFloorY() + 0.2f;
Eigen::Vector3f blue(0.0f, 0.0f, 1.0f);
for (const auto& area : areas) {
if (area.forbidden && area.polygon.size() >= 3) {
VertexRenderStruct mesh;
mesh.data = CreatePolygonFloor(area.polygon, y, blue);
mesh.RefreshVBO();
debugForbiddenMeshes.push_back(std::move(mesh));
}
}
}
void Location::drawDebugForbidden()
{
if (debugForbiddenMeshes.empty()) return;
renderer.shaderManager.PushShader("defaultColor");
renderer.SetMatrix();
for (const auto& mesh : debugForbiddenMeshes) {
renderer.DrawVertexRenderStruct(mesh);
}
renderer.shaderManager.PopShader();
renderer.SetMatrix();
}
//#endif
InteractiveObject* Location::raycastInteractiveObjects(const Eigen::Vector3f& rayOrigin, const Eigen::Vector3f& rayDir) {
if (interactiveObjects.empty()) {
std::cout << "[RAYCAST] No interactive objects to check" << std::endl;
return nullptr;
}
std::cout << "[RAYCAST] Starting raycast with " << interactiveObjects.size() << " objects" << std::endl;
std::cout << "[RAYCAST] Ray origin: (" << rayOrigin.x() << ", " << rayOrigin.y() << ", " << rayOrigin.z() << ")" << std::endl;
std::cout << "[RAYCAST] Ray dir: (" << rayDir.x() << ", " << rayDir.y() << ", " << rayDir.z() << ")" << std::endl;
float closestDistance = FLT_MAX;
InteractiveObject* closestObject = nullptr;
for (auto& intObj : interactiveObjects) {
std::cout << "[RAYCAST] Checking object: " << intObj.name << " (active: " << intObj.isActive << ")" << std::endl;
if (!intObj.isActive) {
std::cout << "[RAYCAST] -> Object inactive, skipping" << std::endl;
continue;
}
std::cout << "[RAYCAST] Position: (" << intObj.position.x() << ", " << intObj.position.y() << ", "
<< intObj.position.z() << "), Radius: " << intObj.interactionRadius << std::endl;
Eigen::Vector3f toObject = intObj.position - rayOrigin;
std::cout << "[RAYCAST] Vector to object: (" << toObject.x() << ", " << toObject.y() << ", " << toObject.z() << ")" << std::endl;
float distanceAlongRay = toObject.dot(rayDir);
std::cout << "[RAYCAST] Distance along ray: " << distanceAlongRay << std::endl;
if (distanceAlongRay < 0.1f) {
std::cout << "[RAYCAST] -> Object behind camera, skipping" << std::endl;
continue;
}
Eigen::Vector3f closestPointOnRay = rayOrigin + rayDir * distanceAlongRay;
float distToObject = (closestPointOnRay - intObj.position).norm();
std::cout << "[RAYCAST] Distance to object: " << distToObject
<< " (interaction radius: " << intObj.interactionRadius << ")" << std::endl;
if (distToObject <= intObj.interactionRadius && distanceAlongRay < closestDistance) {
std::cout << "[RAYCAST] *** HIT DETECTED! ***" << std::endl;
closestDistance = distanceAlongRay;
closestObject = &intObj;
}
}
if (closestObject) {
std::cout << "[RAYCAST] *** RAYCAST SUCCESS: Found object " << closestObject->name << " ***" << std::endl;
}
else {
std::cout << "[RAYCAST] No objects hit" << std::endl;
}
return closestObject;
}
Character* Location::raycastNpcs(const Eigen::Vector3f& rayOrigin, const Eigen::Vector3f& rayDir, float maxDistance) {
Character* closestNpc = nullptr;
float closestDist = maxDistance;
for (auto& npc : npcs) {
Eigen::Vector3f toNpc = npc->position - rayOrigin;
float distAlongRay = toNpc.dot(rayDir);
if (distAlongRay < 0.1f) continue;
Eigen::Vector3f closestPoint = rayOrigin + rayDir * distAlongRay;
float distToNpc = (closestPoint - npc->position).norm();
float radius = npc->modelScale * 50.0f;
if (distToNpc <= radius && distAlongRay < closestDist) {
closestDist = distAlongRay;
closestNpc = npc.get();
}
}
return closestNpc;
}
void Location::drawGame()
{
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
renderer.shaderManager.PushShader(defaultShaderName);
renderer.RenderUniform1i(textureUniformName, 0);
renderer.PushPerspectiveProjectionMatrix(1.0 / 1.5,
static_cast<float>(Environment::width) / static_cast<float>(Environment::height),
Environment::CONST_Z_NEAR, Environment::CONST_Z_FAR);
renderer.PushMatrix();
renderer.LoadIdentity();
renderer.TranslateMatrix({ 0,0, -1.0f * Environment::zoom });
renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(cameraInclination, Eigen::Vector3f::UnitX())).toRotationMatrix());
renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(cameraAzimuth, Eigen::Vector3f::UnitY())).toRotationMatrix());
Eigen::Vector3f camTarget = inCar ? carPosition : (player ? player->position : Eigen::Vector3f::Zero());
renderer.TranslateMatrix({ -camTarget.x(), -camTarget.y(), -camTarget.z() });
renderer.TranslateMatrix({ 0, -1.3f, 0 });
for (int i = -7; i <= 7; i++)
{
for (int j = -7; j <= 7; j++)
{
renderer.PushMatrix();
renderer.TranslateMatrix({ i * 100.0f, 0, j * 100.0f });
if (i == 0)
{
glBindTexture(GL_TEXTURE_2D, roadTexture->getTexID());
}
else
{
glBindTexture(GL_TEXTURE_2D, grassTexture->getTexID());
}
renderer.DrawVertexRenderStruct(tileMesh);
renderer.PopMatrix();
}
}
if (roomMesh.data.PositionData.size() > 0)
{
glBindTexture(GL_TEXTURE_2D, roomTexture->getTexID());
renderer.DrawVertexRenderStruct(roomMesh);
}
for (auto& [name, gameObj] : gameObjects) {
if (name == roofObjectKey && !roofVisible) {
continue;
}
if (name == azsRoofObjectKey && !azsRoofVisible) {
continue;
}
glBindTexture(GL_TEXTURE_2D, gameObj.texture->getTexID());
renderer.DrawVertexRenderStruct(gameObj.mesh);
}
for (auto& intObj : interactiveObjects) {
if (intObj.isActive) {
intObj.draw(renderer);
}
}
renderer.PushMatrix();
//renderer.LoadIdentity();
renderer.TranslateMatrix(carPosition);
renderer.TranslateMatrix({ 0, 0.7, 0 });
renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(carRotation, Eigen::Vector3f::UnitY())).toRotationMatrix());
glBindTexture(GL_TEXTURE_2D, carTexture->getTexID());
renderer.DrawVertexRenderStruct(carMesh);
if (carWheelTexture) {
constexpr float track_width = 1.28f;
constexpr float wheel_base = 2.25f;
constexpr float shift = 0.6f;
const Eigen::Vector3f wheelPositions[4] = {
Eigen::Vector3f( track_width, 0.f - 0.21, -(wheel_base + shift)+1.25), // front right
Eigen::Vector3f(-track_width, 0.f - 0.21, -(wheel_base + shift)+1.25), // front left
Eigen::Vector3f( track_width, 0.f - 0.21, (wheel_base - shift)+1.1), // rear right
Eigen::Vector3f(-track_width, 0.f - 0.21, (wheel_base - shift)+1.1) // rear left
};
const bool isFront[4] = { true, true, false, false };
glBindTexture(GL_TEXTURE_2D, carWheelTexture->getTexID());
for (int i = 0; i < 4; ++i) {
renderer.PushMatrix();
renderer.TranslateMatrix(wheelPositions[i]);
if (isFront[i]) {
renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(carSteeringAngle, Eigen::Vector3f::UnitY())).toRotationMatrix());
}
renderer.DrawVertexRenderStruct(carWheelMesh);
renderer.PopMatrix();
}
}
renderer.PopMatrix();
drawNpcCar();
if (player && !inCar) player->draw(renderer);
if (girlfriend && !girlfriendInCar)
{
girlfriend->draw(renderer);
}
if (salesperson)
{
salesperson->draw(renderer);
}
if (police)
{
police->draw(renderer);
}
if (bandit)
{
bandit->draw(renderer);
}
for (auto& npc : npcs) npc->draw(renderer);
//#ifdef SHOW_PATH
drawDebugNavigation();
drawDebugForbidden();
//#endif
renderer.PopMatrix();
renderer.PopProjectionMatrix();
renderer.shaderManager.PopShader();
}
void Location::drawShadowDepthPass()
{
if (!shadowMap) return;
const Eigen::Vector3f& sceneCenter = player ? player->position : Eigen::Vector3f::Zero();
shadowMap->updateLightSpaceMatrix(sceneCenter);
shadowMap->bind();
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LESS);
// Use front-face culling during depth pass to reduce shadow acne on lit faces
//glCullFace(GL_FRONT);
glEnable(GL_CULL_FACE);
renderer.shaderManager.PushShader("shadow_depth");
// Set up light's orthographic projection
const Eigen::Matrix4f& lightProj = shadowMap->getLightProjectionMatrix();
const Eigen::Matrix4f& lightView = shadowMap->getLightViewMatrix();
// Push the light's projection matrix via the 6-param ortho overload won't
// match our pre-computed matrix. Instead, use the raw stack approach:
// push a dummy projection then overwrite via PushSpecialMatrix-style.
// Simpler: push ortho then push the light view as modelview.
renderer.PushProjectionMatrix(
-40.0f, 40.0f,
-40.0f, 40.0f,
0.1f, 100.0f);
const Eigen::Vector3f& lightDir = shadowMap->getLightDirection();
Eigen::Vector3f lightPos = sceneCenter - lightDir * 50.0f;
Eigen::Vector3f up(0.0f, 1.0f, 0.0f);
if (std::abs(lightDir.dot(up)) > 0.99f) {
up = Eigen::Vector3f(0.0f, 0.0f, 1.0f);
}
// Build the light view matrix and push it
renderer.PushSpecialMatrix(lightView);
// Draw static geometry
renderer.DrawVertexRenderStruct(roomMesh);
for (auto& [name, gameObj] : gameObjects) {
if (name == roofObjectKey && !roofVisible) {
continue;
}
if (name == azsRoofObjectKey && !azsRoofVisible) {
continue;
}
renderer.DrawVertexRenderStruct(gameObj.mesh);
}
for (auto& intObj : interactiveObjects) {
if (intObj.isActive && intObj.texture) {
renderer.PushMatrix();
renderer.TranslateMatrix(intObj.position);
renderer.DrawVertexRenderStruct(intObj.mesh);
renderer.PopMatrix();
}
}
// Draw characters (they handle their own skinning shader switch internally)
if (player) player->drawShadowDepth(renderer);
if (girlfriend && !girlfriendInCar)
{
girlfriend->drawShadowDepth(renderer);
}
for (auto& npc : npcs) npc->drawShadowDepth(renderer);
renderer.PopMatrix(); // light view
renderer.PopProjectionMatrix();
renderer.shaderManager.PopShader();
//glCullFace(GL_BACK);
glDisable(GL_CULL_FACE);
shadowMap->unbind();
}
void Location::drawGameWithShadows()
{
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
#ifdef DEBUG_LIGHT
// Debug mode: render from the light's point of view using the plain
// textured shader so we can see what the shadow map "sees".
renderer.shaderManager.PushShader(defaultShaderName);
renderer.RenderUniform1i(textureUniformName, 0);
renderer.PushProjectionMatrix(
-40.0f, 40.0f,
-40.0f, 40.0f,
0.1f, 1000.0f);
renderer.PushSpecialMatrix(shadowMap->getLightViewMatrix());
#else
static const std::string shadowShaderName = "default_shadow";
renderer.shaderManager.PushShader(shadowShaderName);
renderer.RenderUniform1i(textureUniformName, 0);
renderer.RenderUniform1i("uShadowMap", 1);
// Bind shadow map texture to unit 1
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, shadowMap->getDepthTexture());
glActiveTexture(GL_TEXTURE0);
renderer.PushPerspectiveProjectionMatrix(1.0 / 1.5,
static_cast<float>(Environment::width) / static_cast<float>(Environment::height),
Environment::CONST_Z_NEAR, Environment::CONST_Z_FAR);
renderer.PushMatrix();
renderer.LoadIdentity();
renderer.TranslateMatrix({ 0,0, -1.0f * Environment::zoom });
renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(cameraInclination, Eigen::Vector3f::UnitX())).toRotationMatrix());
renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(cameraAzimuth, Eigen::Vector3f::UnitY())).toRotationMatrix());
const Eigen::Vector3f& camTarget = player ? player->position : Eigen::Vector3f::Zero();
renderer.TranslateMatrix({ -camTarget.x(), -camTarget.y(), -camTarget.z() });
// Capture the camera view matrix and compute uLightFromCamera
cameraViewMatrix = renderer.GetCurrentModelViewMatrix();
Eigen::Matrix4f cameraViewInverse = cameraViewMatrix.inverse();
Eigen::Matrix4f lightFromCamera = shadowMap->getLightSpaceMatrix() * cameraViewInverse;
renderer.RenderUniformMatrix4fv("uLightFromCamera", false, lightFromCamera.data());
// Light direction in camera space for diffuse lighting
Eigen::Vector3f lightDirCamera = cameraViewMatrix.block<3, 3>(0, 0) * shadowMap->getLightDirection();
renderer.RenderUniform3fv("uLightDir", lightDirCamera.data());
#endif
CheckGlError(__FILE__, __LINE__);
glBindTexture(GL_TEXTURE_2D, roomTexture->getTexID());
renderer.DrawVertexRenderStruct(roomMesh);
CheckGlError(__FILE__, __LINE__);
for (auto& [name, gameObj] : gameObjects) {
if (name == roofObjectKey && !roofVisible) {
continue;
}
if (name == azsRoofObjectKey && !azsRoofVisible) {
continue;
}
glBindTexture(GL_TEXTURE_2D, gameObj.texture->getTexID());
renderer.DrawVertexRenderStruct(gameObj.mesh);
}
CheckGlError(__FILE__, __LINE__);
for (auto& intObj : interactiveObjects) {
if (intObj.isActive) {
intObj.draw(renderer);
}
}
CheckGlError(__FILE__, __LINE__);
#ifdef DEBUG_LIGHT
// In debug-light mode characters use the plain shaders (draw normally
// but from the light's viewpoint — projection/view already on stack).
if (player) player->draw(renderer);
for (auto& npc : npcs) npc->draw(renderer);
#else
// Characters use their own shadow-aware shaders
CheckGlError(__FILE__, __LINE__);
if (player) player->drawWithShadow(renderer, lightFromCamera, shadowMap->getDepthTexture(), lightDirCamera);
CheckGlError(__FILE__, __LINE__);
for (auto& npc : npcs) npc->drawWithShadow(renderer, lightFromCamera, shadowMap->getDepthTexture(), lightDirCamera);
#endif
CheckGlError(__FILE__, __LINE__);
drawDebugNavigation();
drawDebugForbidden();
renderer.PopMatrix();
renderer.PopProjectionMatrix();
renderer.shaderManager.PopShader();
}
bool Location::setNavigationAreaAvailable(const std::string& areaName, bool available)
{
return navigation.setAreaAvailable(areaName, available);
}
bool Location::isPointInCarFootprint(const Eigen::Vector3f& point, const Eigen::Vector3f& center, float rotation) const
{
constexpr float carLength = 7.3f;
constexpr float carWidth = 2.8f;
const Eigen::Vector3f forward(-std::sin(rotation), 0.f, -std::cos(rotation));
const Eigen::Vector3f right(std::cos(rotation), 0.f, -std::sin(rotation));
Eigen::Vector3f delta = point - center;
delta.y() = 0.f;
const float alongForward = delta.dot(forward);
const float alongRight = delta.dot(right);
return std::abs(alongForward) <= carLength * 0.5f && std::abs(alongRight) <= carWidth * 0.5f;
}
void Location::pushOutOfNpcCarFootprint(Eigen::Vector3f& position) const
{
if (!npcCar.texture) return;
constexpr float carLength = 7.3f;
constexpr float carWidth = 2.8f;
constexpr float halfLength = carLength * 0.5f;
constexpr float halfWidth = carWidth * 0.5f;
constexpr float skin = 0.05f;
const Eigen::Vector3f forward(-std::sin(npcCar.rotation), 0.f, -std::cos(npcCar.rotation));
const Eigen::Vector3f right(std::cos(npcCar.rotation), 0.f, -std::sin(npcCar.rotation));
Eigen::Vector3f delta = position - npcCar.position;
delta.y() = 0.f;
float alongForward = delta.dot(forward);
float alongRight = delta.dot(right);
if (std::abs(alongForward) >= halfLength || std::abs(alongRight) >= halfWidth) {
return;
}
const float penForward = halfLength - std::abs(alongForward);
const float penRight = halfWidth - std::abs(alongRight);
if (penRight <= penForward) {
const float sign = alongRight >= 0.f ? 1.f : -1.f;
alongRight = sign * (halfWidth + skin);
} else {
const float sign = alongForward >= 0.f ? 1.f : -1.f;
alongForward = sign * (halfLength + skin);
}
const float originalY = position.y();
Eigen::Vector3f resolved = npcCar.position + forward * alongForward + right * alongRight;
resolved.y() = originalY;
position = resolved;
}
bool Location::doesPlayerCarCollideWithNpcCar(const Eigen::Vector3f& center, float rotation) const
{
if (!npcCar.texture) return false;
constexpr float carLength = 7.3f;
constexpr float carWidth = 2.8f;
constexpr float sampleSpacing = 0.5f;
const Eigen::Vector3f delta = center - npcCar.position;
const float approxRadius = std::sqrt(carLength * carLength + carWidth * carWidth);
if (delta.squaredNorm() > approxRadius * approxRadius) {
return false;
}
const float halfLength = carLength * 0.5f;
const float halfWidth = carWidth * 0.5f;
const int longSteps = max(1, static_cast<int>(std::ceil(carLength / sampleSpacing)));
const int widthSteps = max(1, static_cast<int>(std::ceil(carWidth / sampleSpacing)));
const Eigen::Vector3f forward(-std::sin(rotation), 0.f, -std::cos(rotation));
const Eigen::Vector3f right(std::cos(rotation), 0.f, -std::sin(rotation));
for (int i = 0; i <= longSteps; ++i) {
const float fl = (static_cast<float>(i) / static_cast<float>(longSteps)) * 2.0f - 1.0f;
const Eigen::Vector3f lineOffset = forward * (fl * halfLength);
for (int j = 0; j <= widthSteps; ++j) {
const float fw = (static_cast<float>(j) / static_cast<float>(widthSteps)) * 2.0f - 1.0f;
const Eigen::Vector3f sample = center + lineOffset + right * (fw * halfWidth);
if (isPointInCarFootprint(sample, npcCar.position, npcCar.rotation)) {
return true;
}
}
}
return false;
}
bool Location::isCarFootprintWalkable(const Eigen::Vector3f& center, float rotation) const
{
if (!navigation.isReady()) return true;
constexpr float carLength = 7.3f;
constexpr float carWidth = 2.8f;
constexpr float sampleSpacing = 0.35f;
const float halfLength = carLength * 0.5f;
const float halfWidth = carWidth * 0.5f;
const int longSteps = max(1, static_cast<int>(std::ceil(carLength / sampleSpacing)));
const int widthSteps = max(1, static_cast<int>(std::ceil(carWidth / sampleSpacing)));
const Eigen::Vector3f forward(-std::sin(rotation), 0.f, -std::cos(rotation));
const Eigen::Vector3f right(std::cos(rotation), 0.f, -std::sin(rotation));
for (int i = 0; i <= longSteps; ++i) {
const float fl = (static_cast<float>(i) / static_cast<float>(longSteps)) * 2.0f - 1.0f;
const Eigen::Vector3f lineOffset = forward * (fl * halfLength);
for (int j = 0; j <= widthSteps; ++j) {
const float fw = (static_cast<float>(j) / static_cast<float>(widthSteps)) * 2.0f - 1.0f;
const Eigen::Vector3f sample = center + lineOffset + right * (fw * halfWidth);
if (!navigation.isWalkable(sample)) {
return false;
}
}
}
return true;
}
void Location::update(int64_t delta)
{
const Eigen::Vector3f playerPosBefore = player ? player->position : Eigen::Vector3f::Zero();
if (player) {
if (!inCar) {
player->targetFacingAngle = cameraAzimuth;
if (playerFrozen) {
player->clearPath();
wasKeyForward = false;
} else if (keyForward) {
player->attackTarget = nullptr;
Eigen::Vector3f forward(std::sin(cameraAzimuth), 0.f, -std::cos(cameraAzimuth));
player->setDirectWalkTarget(player->position + forward * 5.0f);
wasKeyForward = true;
} else {
if (wasKeyForward) {
player->clearPath();
}
wasKeyForward = false;
}
}
player->update(delta);
dialogueSystem.update(static_cast<int>(delta), player->position);
if (!roofObjectKey.empty()) {
bool insideAnyZone = false;
const Eigen::Vector2f playerPos2d(player->position.x(), player->position.z());
for (const auto& zone : roofHideZones) {
if (zone.contains(playerPos2d)) {
insideAnyZone = true;
break;
}
}
const float maxHeightForHide = 3.0f;
roofVisible = !(insideAnyZone && player->position.y() < maxHeightForHide);
}
if (!azsRoofObjectKey.empty()) {
bool insideAnyZone = false;
const Eigen::Vector2f playerPos2d(player->position.x(), player->position.z());
for (const auto& zone : azsRoofHideZones) {
if (zone.contains(playerPos2d)) {
insideAnyZone = true;
break;
}
}
const float maxHeightForHide = 3.0f;
azsRoofVisible = !(insideAnyZone && player->position.y() < maxHeightForHide);
}
}
if (girlfriend)
{
updateGirlfriendFollow();
girlfriend->update(delta);
if (!girlfriendInCar) {
pushOutOfNpcCarFootprint(girlfriend->position);
}
}
if (salesperson) pushOutOfNpcCarFootprint(salesperson->position);
if (police) {
police->update(delta);
pushOutOfNpcCarFootprint(police->position);
}
if (bandit) pushOutOfNpcCarFootprint(bandit->position);
for (auto& npc : npcs) {
npc->update(delta);
pushOutOfNpcCarFootprint(npc->position);
}
if (inCar) {
float dt = static_cast<float>(delta) / 1000.0f;
constexpr float roadHalfWidth = 10.0f;
constexpr float offRoadFrictionMultiplier = 4.0f;
const bool offRoad = std::abs(carPosition.x()) > roadHalfWidth;
if (!offRoad && !carOutOfGas) {
if (keyForward) {
carVelocity += carAcceleration * dt;
}
if (keyBackward) {
carVelocity -= carAcceleration * dt;
}
}
const bool applyThrottleFriction = offRoad || (!keyForward && !keyBackward);
if (applyThrottleFriction) {
const float frictionRate = offRoad ? (carFriction * offRoadFrictionMultiplier) : carFriction;
const float resistance = frictionRate * dt;
if (carVelocity > 0.f) {
carVelocity = max(0.f, carVelocity - resistance);
} else if (carVelocity < 0.f) {
carVelocity = min(0.f, carVelocity + resistance);
}
}
carVelocity = max(-carMaxReverseSpeed, min(carMaxSpeed, carVelocity));
const float oldRotation = carRotation;
if (std::abs(carVelocity) > 0.01f) {
float speedFactor = carVelocity / carMaxSpeed;
if (keyLeft) carRotation += carTurnRate * dt * speedFactor;
if (keyRight) carRotation -= carTurnRate * dt * speedFactor;
}
if (navigation.isReady() && !isCarFootprintWalkable(carPosition, carRotation)) {
carRotation = oldRotation;
}
if (doesPlayerCarCollideWithNpcCar(carPosition, carRotation)) {
carRotation = oldRotation;
}
float targetSteer = (keyLeft ? 1.f : 0.f) - (keyRight ? 1.f : 0.f);
targetSteer *= carMaxSteerAngle;
float steerLerp = min(1.f, dt * 8.f);
carSteeringAngle += (targetSteer - carSteeringAngle) * steerLerp;
const Eigen::Vector3f forward(-std::sin(carRotation), 0.f, -std::cos(carRotation));
const Eigen::Vector3f totalDelta = forward * carVelocity * dt;
const float totalDist = totalDelta.norm();
const float maxSubstep = 0.2f;
const int substeps = max(1, static_cast<int>(std::ceil(totalDist / maxSubstep)));
const Eigen::Vector3f stepDelta = totalDelta / static_cast<float>(substeps);
for (int s = 0; s < substeps; ++s) {
Eigen::Vector3f candidate = carPosition + stepDelta;
if (navigation.isReady() && !isCarFootprintWalkable(candidate, carRotation)) {
carVelocity = 0.f;
break;
}
if (doesPlayerCarCollideWithNpcCar(candidate, carRotation)) {
carVelocity = 0.f;
if (!dialoguePlayedCrash && !dialogueSystem.isActive()) {
if (dialogueSystem.startDialogue("driving_dialogue_crash")) {
dialoguePlayedCrash = true;
}
}
break;
}
carPosition = candidate;
}
if (offRoad && std::abs(carVelocity) < 0.1f && (keyForward || keyBackward)) {
if (!dialoguePlayedOffroad && !dialogueSystem.isActive()) {
if (dialogueSystem.startDialogue("driving_dialogue_offroad")) {
dialoguePlayedOffroad = true;
}
}
}
if (dialoguePlayedCrash && carVelocity > 3.0)
{
dialoguePlayedCrash = false;
}
if (player) {
player->position = carPosition;
}
if (girlfriendInCar)
{
if (!dialoguePlayedDrivingGas1 && !dialogueSystem.isActive()) {
if (dialogueSystem.startDialogue("driving_dialogue_gas1")) {
dialoguePlayedDrivingGas1 = true;
}
}
}
// Ran out of gas: far south without having heard the girlfriend's gas
// reminder. Latches the stall, then plays the out-of-gas line once.
if (!carOutOfGas && !dialoguePlayedGas1 && player->position.z() < -300.f) {
carOutOfGas = true;
}
if (carOutOfGas && !dialoguePlayedDrivingGasOut && !dialogueSystem.isActive()) {
if (dialogueSystem.startDialogue("driving_dialogue_gas_out")) {
dialoguePlayedDrivingGasOut = true;
}
}
if (!policeFollow &&
player->position.z() - npcCar.position.z() > -10.f
&& player->position.z() < npcCar.position.z()
&& npcCar.position.x()<900
&& policeEncounterStage != PoliceEncounterStage::Done)
{
policeFollow = true;
npcCar.mode = NpcCar::Mode::FOLLOW_PLAYER;
policeDrivingDialogueTimer = 8.0f;
}
// While police follows and player is driving, the cop yells every 8s.
if (policeFollow && policeEncounterStage == PoliceEncounterStage::Idle) {
policeDrivingDialogueTimer -= static_cast<float>(delta) / 1000.f;
if (policeDrivingDialogueTimer <= 0.f && !dialogueSystem.isActive()) {
if (dialogueSystem.startDialogue("driving_dialogue1")) {
policeDrivingDialogueTimer = 8.0f;
}
}
}
}
if (player && !inCar) {
auto blocked = [&](const Eigen::Vector3f& p) {
if (navigation.isReady() && !navigation.isWalkable(p)) return true;
if (isPointInCarFootprint(p, carPosition, carRotation)) return true;
if (isPointInCarFootprint(p, npcCar.position, npcCar.rotation)) return true;
return false;
};
if (blocked(player->position)) {
const Eigen::Vector3f slideX(player->position.x(), playerPosBefore.y(), playerPosBefore.z());
const Eigen::Vector3f slideZ(playerPosBefore.x(), playerPosBefore.y(), player->position.z());
Eigen::Vector3f resolved;
if (!blocked(slideX)) {
resolved = slideX;
} else if (!blocked(slideZ)) {
resolved = slideZ;
} else {
resolved = playerPosBefore;
}
player->position = resolved;
}
}
updateNpcCar(delta);
// Track remaining driving distance toward -Z. Computed from this frame's
// real movement (captured before the tile-shift below), so the teleport
// never contributes to the count.
if (player) {
distanceRemaining += player->position.z() - playerPosBefore.z();
}
if (!dialoguePlayedDistance7000 && distanceRemaining < 7000.f/5.f && !dialogueSystem.isActive()) {
if (dialogueSystem.startDialogue("driving_dialogue_distance7000")) {
dialoguePlayedDistance7000 = true;
}
}
if (!dialoguePlayedDistance5000 && distanceRemaining < 5000.f/5.f && !dialogueSystem.isActive()) {
if (dialogueSystem.startDialogue("driving_dialogue_distance5000")) {
dialoguePlayedDistance5000 = true;
}
}
if (!dialoguePlayedDistance2000 && distanceRemaining < 2000.f/5.f && !dialogueSystem.isActive()) {
if (dialogueSystem.startDialogue("driving_dialogue_distance2000")) {
dialoguePlayedDistance2000 = true;
}
}
if (!dialoguePlayedDistance0 && distanceRemaining < 0.f && !dialogueSystem.isActive()) {
if (dialogueSystem.startDialogue("driving_dialogue_distance0")) {
dialoguePlayedDistance0 = true;
}
}
{
constexpr float tileSize = 100.0f;
constexpr float teleportThreshold = tileSize * 3.5f;
const Eigen::Vector3f anchor = inCar
? carPosition
: (player ? player->position : Eigen::Vector3f::Zero());
Eigen::Vector3f shift = Eigen::Vector3f::Zero();
if (anchor.x() > teleportThreshold) shift.x() = -tileSize;
else if (anchor.x() < -teleportThreshold) shift.x() = tileSize;
if (anchor.z() > teleportThreshold) shift.z() = -tileSize;
else if (anchor.z() < -teleportThreshold) shift.z() = tileSize;
if (shift.squaredNorm() > 0.f) {
if (inCar) {
carPosition += shift;
if (player) player->position = carPosition;
}
else if (player) {
player->position += shift;
player->setTarget(player->position);
player->clearPath();
}
if (npcCar.mode == NpcCar::Mode::FOLLOW_PLAYER || npcCar.mode == NpcCar::Mode::NONE_STAY) {
npcCar.position += shift;
}
}
}
/*
// Check if player reached target interactive object
if (targetInteractiveObject && player) {
}*/
// After the gas-station sale, once the player has walked away from the
// salesperson, the girlfriend chimes in — fires once.
if (!dialoguePlayedGirlfriend1 && dialoguePlayedGas1 && salesperson && player && !dialogueSystem.isActive()) {
constexpr float minDistance = 8.0f;
const float dx = player->position.x() - salesperson->position.x();
const float dz = player->position.z() - salesperson->position.z();
if (std::hypot(dx, dz) > minDistance) {
if (dialogueSystem.startDialogue("dialogue_girlfriend1")) {
dialoguePlayedGirlfriend1 = true;
}
}
}
// Police encounter: once the player gets out of the car while being
// followed, the officer walks over from the NPC car, delivers his line,
// then walks back and despawns. Happens exactly once.
if (player && policeFollow && !inCar && policeEncounterStage == PoliceEncounterStage::Idle && !dialogueSystem.isActive() && police) {
Eigen::Vector3f toCar = npcCar.position - player->position;
toCar.y() = 0.f;
const float toCarDist = toCar.norm();
// Spawn right next to the NPC car, on its left side.
const Eigen::Vector3f carLeft(-std::cos(npcCar.rotation), 0.f, std::sin(npcCar.rotation));
constexpr float carHalfWidth = 2.8f * 0.5f;
police->position = npcCar.position + carLeft * (carHalfWidth + 1.5f);
police->position.y() = 0.f;
police->clearPath();
// Walk to a spot 1.5 m short of the player, approaching from the car's side.
Eigen::Vector3f approachTarget = player->position;
if (toCarDist > 1e-4f) {
approachTarget = player->position + (toCar / toCarDist) * 1.5f;
}
approachTarget.y() = 0.f;
police->setTarget(approachTarget);
if (dialogueSystem.startDialogue("dialogue_police1")) {
playerFrozen = true;
policeEncounterStage = PoliceEncounterStage::Approaching;
}
}
if (policeEncounterStage == PoliceEncounterStage::Approaching) {
if (!dialogueSystem.isActive()) {
// Dialogue finished — unfreeze player and send officer back to the car.
playerFrozen = false;
if (police) {
police->setTarget(npcCar.position);
}
policeEncounterStage = PoliceEncounterStage::Returning;
}
}
if (policeEncounterStage == PoliceEncounterStage::Returning) {
if (police) {
const float dx = police->position.x() - npcCar.position.x();
const float dz = police->position.z() - npcCar.position.z();
if (std::hypot(dx, dz) <= 8.0f) {
police.reset();
}
}
if (!police) {
policeFollow = false;
npcCar.mode = NpcCar::Mode::NONE_STAY;
policeEncounterStage = PoliceEncounterStage::Done;
}
}
// Phone rings once the player drives far from the gas station — fires once,
// and only after the gas-station sale.
if (inCar && dialoguePlayedGas1 && !dialoguePlayedPhone1 && !dialogueSystem.isActive()) {
constexpr float minDistance = 50.0f;
const Eigen::Vector3f gasStationPos(-3.f, 0.f, -11.f);
const float dx = carPosition.x() - gasStationPos.x();
const float dz = carPosition.z() - gasStationPos.z();
if (std::hypot(dx, dz) > minDistance) {
if (dialogueSystem.startDialogue("dialogue_phone1", [this]() {
dialoguePhone1Finished = true;
})) {
dialoguePlayedPhone1 = true;
}
}
}
// After the phone dialogue, once the player's car enters the ambush zone,
// spawn the NPC (police) car at its staged position. Fires once.
if (dialoguePhone1Finished && !npcCarSpawnedAfterPhone) {
if (carPosition.z() > -350.f && carPosition.z() < -340.f) {
npcCar.position = Eigen::Vector3f(9.f, 0.f, -325.f);
npcCarSpawnedAfterPhone = true;
}
}
}
void Location::updateNpcCar(int64_t deltaMs)
{
const float dt = static_cast<float>(deltaMs) / 1000.0f;
Eigen::Vector3f target = npcCar.position;
float throttle = 0.f;
bool hasTarget = false;
if (npcCar.mode == NpcCar::Mode::FOLLOW_WAYPOINTS) {
if (!npcCar.waypoints.empty()) {
if (npcCar.currentWaypoint >= npcCar.waypoints.size()) {
npcCar.currentWaypoint = 0;
}
Eigen::Vector3f toTarget = npcCar.waypoints[npcCar.currentWaypoint] - npcCar.position;
toTarget.y() = 0.f;
if (toTarget.norm() < npcCar.waypointReachRadius) {
npcCar.currentWaypoint = (npcCar.currentWaypoint + 1) % npcCar.waypoints.size();
}
target = npcCar.waypoints[npcCar.currentWaypoint];
throttle = 1.0f;
hasTarget = true;
}
}
else if (npcCar.mode == NpcCar::Mode::FOLLOW_PLAYER) {
//if (x > 0.5)
{
target = carPosition;
Eigen::Vector3f toTarget = target - npcCar.position;
toTarget.y() = 0.f;
const float dist = toTarget.norm();
const float targetDist = npcCar.followMinDistance;
const float coastBuffer = 1.5f;
if (dist > npcCar.followMaxDistance) {
throttle = 1.0f;
}
else if (dist > targetDist + coastBuffer) {
throttle = 0.3f;
}
else if (dist > targetDist - coastBuffer) {
throttle = 0.f;
}
else {
const float underBy = (targetDist - coastBuffer) - dist;
const float brakeRamp = min(1.0f, underBy / 3.0f);
throttle = -(1.0f + 1.5f * brakeRamp);
}
hasTarget = dist > 0.5f;
}
}
else
{
throttle = 0.f;
}
float targetHeading = npcCar.rotation;
if (hasTarget) {
Eigen::Vector3f toTarget = target - npcCar.position;
toTarget.y() = 0.f;
if (toTarget.squaredNorm() > 1e-4f) {
targetHeading = std::atan2(-toTarget.x(), -toTarget.z());
}
}
float angleDiff = targetHeading - npcCar.rotation;
while (angleDiff > M_PI) angleDiff -= 2.0f * static_cast<float>(M_PI);
while (angleDiff < -M_PI) angleDiff += 2.0f * static_cast<float>(M_PI);
const float turnIntent = max(-1.0f, min(1.0f, angleDiff * 2.0f));
const float targetSteer = turnIntent * npcCar.maxSteerAngle;
const float steerLerp = min(1.f, dt * 8.f);
npcCar.steeringAngle += (targetSteer - npcCar.steeringAngle) * steerLerp;
if (throttle > 0.f) {
npcCar.velocity += npcCar.acceleration * dt * throttle;
}
else if (throttle < 0.f) {
const float brakeForce = npcCar.acceleration * dt * (-throttle);
if (npcCar.velocity > 0.f) {
npcCar.velocity = max(0.f, npcCar.velocity - brakeForce);
}
else {
npcCar.velocity = min(0.f, npcCar.velocity + brakeForce);
}
}
else {
const float resistance = npcCar.friction * dt;
if (npcCar.velocity > 0.f) {
npcCar.velocity = max(0.f, npcCar.velocity - resistance);
}
else if (npcCar.velocity < 0.f) {
npcCar.velocity = min(0.f, npcCar.velocity + resistance);
}
}
npcCar.velocity = max(-npcCar.maxReverseSpeed, min(npcCar.maxSpeed, npcCar.velocity));
const float oldRotation = npcCar.rotation;
if (std::abs(npcCar.velocity) > 0.01f) {
const float speedFactor = npcCar.velocity / npcCar.maxSpeed;
npcCar.rotation += turnIntent * npcCar.turnRate * dt * speedFactor;
}
if (navigation.isReady() && !isCarFootprintWalkable(npcCar.position, npcCar.rotation)) {
npcCar.rotation = oldRotation;
}
const Eigen::Vector3f forward(-std::sin(npcCar.rotation), 0.f, -std::cos(npcCar.rotation));
const Eigen::Vector3f totalDelta = forward * npcCar.velocity * dt;
const float totalDist = totalDelta.norm();
const float maxSubstep = 0.2f;
const int substeps = max(1, static_cast<int>(std::ceil(totalDist / maxSubstep)));
const Eigen::Vector3f stepDelta = totalDelta / static_cast<float>(substeps);
for (int s = 0; s < substeps; ++s) {
Eigen::Vector3f candidate = npcCar.position + stepDelta;
if (navigation.isReady() && !isCarFootprintWalkable(candidate, npcCar.rotation)) {
npcCar.velocity = 0.f;
break;
}
npcCar.position = candidate;
}
}
void Location::updateGirlfriendFollow()
{
if (!girlfriend || !player) return;
constexpr float retargetThreshold = 0.1f;
constexpr float stopDistance = 1.0f;
constexpr float carEnterRadius = 3.0f;
// Player just exited the car while girlfriend was inside — she steps out
// right next to him, as if coming out of the rear door (same lateral side,
// offset back along the car's length).
if (girlfriendInCar && !inCar) {
const Eigen::Vector3f forward(-std::sin(carRotation), 0.f, -std::cos(carRotation));
Eigen::Vector3f exitPos = player->position - forward * 1.5f;
exitPos.y() = 0.f;
girlfriend->position = exitPos;
girlfriend->clearPath();
girlfriendInCar = false;
girlfriendLastFollowTargetValid = false;
}
// While she is riding, her position is glued to the car and no pathing needed.
if (girlfriendInCar) {
girlfriend->position = carPosition;
girlfriend->position.y() = 0.f;
girlfriend->clearPath();
return;
}
// Player is in car and girlfriend has caught up — she jumps in.
if (inCar) {
Eigen::Vector3f diff = girlfriend->position - carPosition;
diff.y() = 0.f;
if (diff.norm() <= carEnterRadius) {
girlfriendInCar = true;
girlfriend->position = carPosition;
girlfriend->position.y() = 0.f;
girlfriend->clearPath();
girlfriendLastFollowTargetValid = false;
return;
}
}
// Follow anchor: the car when player is driving, otherwise a point stopDistance
// away from the player along the line to the girlfriend (so she stops near him).
Eigen::Vector3f anchor;
if (inCar) {
anchor = carPosition;
} else {
Eigen::Vector3f toPlayer = player->position - girlfriend->position;
toPlayer.y() = 0.f;
const float distToPlayer = toPlayer.norm();
if (distToPlayer <= stopDistance) {
girlfriend->clearPath();
girlfriendLastFollowTargetValid = false;
return;
}
const Eigen::Vector3f fromPlayer = -toPlayer / distToPlayer;
anchor = player->position + fromPlayer * stopDistance;
}
anchor.y() = 0.f;
const bool shouldRetarget = !girlfriendLastFollowTargetValid ||
(anchor - girlfriendLastFollowTarget).norm() > retargetThreshold;
if (shouldRetarget) {
girlfriend->setTarget(anchor);
girlfriendLastFollowTarget = anchor;
girlfriendLastFollowTargetValid = true;
}
}
void Location::drawNpcCar()
{
if (!npcCar.texture) return;
renderer.PushMatrix();
renderer.TranslateMatrix(npcCar.position);
renderer.TranslateMatrix({ 0, 0.7f, 0 });
renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(npcCar.rotation, Eigen::Vector3f::UnitY())).toRotationMatrix());
glBindTexture(GL_TEXTURE_2D, npcCar.texture->getTexID());
renderer.DrawVertexRenderStruct(carMesh);
if (carWheelTexture) {
constexpr float track_width = 1.28f;
constexpr float wheel_base = 2.25f;
constexpr float shift = 0.6f;
const Eigen::Vector3f wheelPositions[4] = {
Eigen::Vector3f( track_width, 0.f - 0.21f, -(wheel_base + shift) + 1.25f),
Eigen::Vector3f(-track_width, 0.f - 0.21f, -(wheel_base + shift) + 1.25f),
Eigen::Vector3f( track_width, 0.f - 0.21f, (wheel_base - shift) + 1.1f),
Eigen::Vector3f(-track_width, 0.f - 0.21f, (wheel_base - shift) + 1.1f)
};
const bool isFront[4] = { true, true, false, false };
glBindTexture(GL_TEXTURE_2D, carWheelTexture->getTexID());
for (int i = 0; i < 4; ++i) {
renderer.PushMatrix();
renderer.TranslateMatrix(wheelPositions[i]);
if (isFront[i]) {
renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(npcCar.steeringAngle, Eigen::Vector3f::UnitY())).toRotationMatrix());
}
renderer.DrawVertexRenderStruct(carWheelMesh);
renderer.PopMatrix();
}
}
renderer.PopMatrix();
}
void Location::handleDown(int64_t fingerId, int eventX, int eventY, int mx, int my)
{
// Calculate ray for picking
if (dialogueSystem.blocksGameplayInput()) {
dialogueSystem.handlePointerReleased(static_cast<float>(mx), Environment::projectionHeight - static_cast<float>(my));
return;
}
if (inCar) {
return;
}
if (salesperson && player && !dialogueSystem.isActive()) {
constexpr float maxTalkDistance = 4.0f;
// cos(45°) — salesperson must lie within the ±45° half-cone in front
// of the player (90° total field of view).
constexpr float coneCosThreshold = 0.7071067f;
Eigen::Vector3f toSales = salesperson->position - player->position;
toSales.y() = 0.f;
const float dist = toSales.norm();
if (dist > 1e-4f && dist <= maxTalkDistance) {
const Eigen::Vector3f playerForward(std::sin(cameraAzimuth), 0.f, -std::cos(cameraAzimuth));
const Eigen::Vector3f toSalesDir = toSales / dist;
if (playerForward.dot(toSalesDir) >= coneCosThreshold) {
const Eigen::Vector3f gasStationPos(-3.f, 0.f, -11.f);
const float carDistToStation = std::hypot(
carPosition.x() - gasStationPos.x(),
carPosition.z() - gasStationPos.z());
std::string dialogueId;
if (dialoguePlayedGas1) {
dialogueId = "dialogue_gas3";
} else if (carDistToStation <= 10.f) {
dialogueId = "dialogue_gas1";
} else {
dialogueId = "dialogue_gas2";
}
if (dialogueSystem.startDialogue(dialogueId)) {
// Face each other. cameraAzimuth also drives the player's
// targetFacingAngle, so updating it rotates the player toward
// the salesperson.
cameraAzimuth = std::atan2(toSales.x(), -toSales.z());
salesperson->targetFacingAngle = std::atan2(-toSales.x(), toSales.z());
if (dialogueId == "dialogue_gas1") {
dialoguePlayedGas1 = true;
}
}
}
}
}
}
void Location::handleUp(int64_t fingerId, int mx, int my)
{
}
void Location::handleMotion(int64_t fingerId, int dx, int dy, int mx, int my)
{
/*if (dialogueSystem.blocksGameplayInput()) {
dialogueSystem.handlePointerMoved(
static_cast<float>(mx),
Environment::projectionHeight - static_cast<float>(my)
);
return;
}*/
const float sensitivity = 0.005f;
const float inclinationSign = invertCameraY ? -1.0f : 1.0f;
cameraAzimuth += dx * sensitivity;
cameraInclination += inclinationSign * dy * sensitivity;
const float minInclination = M_PI * 10.f / 180.f;
const float maxInclination = M_PI * 0.5f;
cameraInclination = max(minInclination, min(maxInclination, cameraInclination));
}
void Location::handleKeyDown(int sdlKey)
{
switch (sdlKey) {
case SDLK_l:
{
if (!player) break;
if (playerFrozen) break;
if (!inCar) {
const Eigen::Vector3f diff(
carPosition.x() - player->position.x(), 0.f,
carPosition.z() - player->position.z());
const float distToCar = diff.norm();
const float enterMaxDistance = 4.0f;
if (distToCar <= enterMaxDistance) {
inCar = true;
carVelocity = 0.f;
} else {
std::cout << "[CAR] Too far to enter: " << distToCar << std::endl;
}
} else {
const Eigen::Vector3f left(-std::cos(carRotation), 0.f, std::sin(carRotation));
const float halfWidth = 2.8f * 0.5f;
const float exitBuffers[] = { 1.0f, 1.5f, 2.0f, 3.0f };
Eigen::Vector3f exitPos = carPosition;
bool foundExit = false;
for (float buffer : exitBuffers) {
Eigen::Vector3f candidate = carPosition + left * (halfWidth + buffer);
candidate.y() = 0.f;
if (!navigation.isReady() || navigation.isWalkable(candidate)) {
exitPos = candidate;
foundExit = true;
break;
}
}
if (foundExit) {
player->position = exitPos;
player->clearPath();
inCar = false;
carVelocity = 0.f;
keyForward = false;
} else {
std::cout << "[CAR] No walkable exit spot" << std::endl;
}
}
break;
}
case SDLK_w: keyForward = true; break;
case SDLK_s: keyBackward = true; break;
case SDLK_a: keyLeft = true; break;
case SDLK_d: keyRight = true; break;
case SDLK_i: invertCameraY = !invertCameraY; break;
case SDLK_u:
std::cout << player->position << std::endl;
//girlfriend->setTarget(player->position);
break;
case SDLK_m:
npcCar.mode = (npcCar.mode == NpcCar::Mode::FOLLOW_WAYPOINTS)
? NpcCar::Mode::FOLLOW_PLAYER
: NpcCar::Mode::FOLLOW_WAYPOINTS;
std::cout << "[NPC_CAR] Mode: "
<< (npcCar.mode == NpcCar::Mode::FOLLOW_WAYPOINTS ? "FOLLOW_WAYPOINTS" : "FOLLOW_PLAYER")
<< std::endl;
break;
default: break;
}
}
void Location::handleKeyUp(int sdlKey)
{
switch (sdlKey) {
case SDLK_w: keyForward = false; break;
case SDLK_s: keyBackward = false; break;
case SDLK_a: keyLeft = false; break;
case SDLK_d: keyRight = false; break;
default: break;
}
}
bool Location::requestDialogueStart(const std::string& dialogueId)
{
return dialogueSystem.startDialogue(dialogueId);
}
void Location::setDialogueFlag(const std::string& flag, int value)
{
dialogueSystem.setFlag(flag, value);
}
int Location::getDialogueFlag(const std::string& flag) const
{
return dialogueSystem.getFlag(flag);
}
void Location::cleanup()
{
std::cout << "[LOCATION] Starting cleanup..." << std::endl;
npcs.clear();
interactiveObjects.clear();
gameObjects.clear();
player.reset();
roomTexture.reset();
npcCar.texture.reset();
//dialogueSystem.dialogueDatabase.clear();
navigation = PathFinder();
cameraAzimuth = 0.0f;
cameraInclination = M_PI * 30.f / 180.f;
targetInteractiveObject = nullptr;
rightMouseDown = false;
lastMouseX = 0;
lastMouseY = 0;
dialoguePlayedOffroad = false;
dialoguePlayedCrash = false;
std::cout << "[LOCATION] Cleanup complete" << std::endl;
}
void Location::unload()
{
std::cout << "[LOCATION] Starting unload..." << std::endl;
cleanup();
std::cout << "[LOCATION] Unload complete" << std::endl;
}
} // namespace ZL