police chase
This commit is contained in:
parent
8e0936836e
commit
25bb79fb42
@ -273,7 +273,7 @@
|
|||||||
"type": "Line",
|
"type": "Line",
|
||||||
"speaker": "Hero",
|
"speaker": "Hero",
|
||||||
"portrait": "resources/w/gg/gg2_s_podsvetkoy5.png",
|
"portrait": "resources/w/gg/gg2_s_podsvetkoy5.png",
|
||||||
"text": "Phone 1 У нас бензин кончается.",
|
"text": "[Телефон звонит]",
|
||||||
"next": "line_2"
|
"next": "line_2"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -281,7 +281,7 @@
|
|||||||
"type": "Line",
|
"type": "Line",
|
||||||
"speaker": "Hero",
|
"speaker": "Hero",
|
||||||
"portrait": "resources/w/gg/gg2_s_podsvetkoy5.png",
|
"portrait": "resources/w/gg/gg2_s_podsvetkoy5.png",
|
||||||
"text": "Надо заправиться.",
|
"text": "Да, слушаю.",
|
||||||
"next": "line_3"
|
"next": "line_3"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -289,7 +289,7 @@
|
|||||||
"type": "Line",
|
"type": "Line",
|
||||||
"speaker": "Ghost",
|
"speaker": "Ghost",
|
||||||
"portrait": "resources/ghost_avatar.png",
|
"portrait": "resources/ghost_avatar.png",
|
||||||
"text": "Хорошо, только давай быстро.",
|
"text": "Алексей, это Нурланбай на связи.",
|
||||||
"next": "line_4"
|
"next": "line_4"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -297,7 +297,105 @@
|
|||||||
"type": "Line",
|
"type": "Line",
|
||||||
"speaker": "Ghost",
|
"speaker": "Ghost",
|
||||||
"portrait": "resources/ghost_avatar.png",
|
"portrait": "resources/ghost_avatar.png",
|
||||||
"text": "Мне как-то не по себе.",
|
"text": "Мои ребята сообщили, что Алтынай видели на заправке с тобой.",
|
||||||
|
"next": "line_5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "line_5",
|
||||||
|
"type": "Line",
|
||||||
|
"speaker": "Ghost",
|
||||||
|
"portrait": "resources/ghost_avatar.png",
|
||||||
|
"text": "Как вы узнали мой номер?",
|
||||||
|
"next": "line_6"
|
||||||
|
}, {
|
||||||
|
"id": "line_6",
|
||||||
|
"type": "Line",
|
||||||
|
"speaker": "Ghost",
|
||||||
|
"portrait": "resources/ghost_avatar.png",
|
||||||
|
"text": "О, это было нетрудно. У тебя слишком заметная машина.",
|
||||||
|
"next": "line_7"
|
||||||
|
}, {
|
||||||
|
"id": "line_7",
|
||||||
|
"type": "Line",
|
||||||
|
"speaker": "Ghost",
|
||||||
|
"portrait": "resources/ghost_avatar.png",
|
||||||
|
"text": "Мои ребята уже поехали за тобой.",
|
||||||
|
"next": "line_8"
|
||||||
|
}, {
|
||||||
|
"id": "line_8",
|
||||||
|
"type": "Line",
|
||||||
|
"speaker": "Ghost",
|
||||||
|
"portrait": "resources/ghost_avatar.png",
|
||||||
|
"text": "Предлагаю тебе не усложнять ничего.",
|
||||||
|
"next": "line_9"
|
||||||
|
}, {
|
||||||
|
"id": "line_9",
|
||||||
|
"type": "Line",
|
||||||
|
"speaker": "Ghost",
|
||||||
|
"portrait": "resources/ghost_avatar.png",
|
||||||
|
"text": "Остановись на трассе, отдай нам Алтынай.",
|
||||||
|
"next": "line_10"
|
||||||
|
}, {
|
||||||
|
"id": "line_10",
|
||||||
|
"type": "Line",
|
||||||
|
"speaker": "Ghost",
|
||||||
|
"portrait": "resources/ghost_avatar.png",
|
||||||
|
"text": "И можешь ехать дальше спокойно.",
|
||||||
|
"next": "line_11"
|
||||||
|
}, {
|
||||||
|
"id": "line_11",
|
||||||
|
"type": "Line",
|
||||||
|
"speaker": "Ghost",
|
||||||
|
"portrait": "resources/ghost_avatar.png",
|
||||||
|
"text": "Никогда!",
|
||||||
|
"next": "line_12"
|
||||||
|
},{
|
||||||
|
"id": "line_12",
|
||||||
|
"type": "Line",
|
||||||
|
"speaker": "Ghost",
|
||||||
|
"portrait": "resources/ghost_avatar.png",
|
||||||
|
"text": "Какой ты смелый парень.",
|
||||||
|
"next": "line_13"
|
||||||
|
},{
|
||||||
|
"id": "line_13",
|
||||||
|
"type": "Line",
|
||||||
|
"speaker": "Ghost",
|
||||||
|
"portrait": "resources/ghost_avatar.png",
|
||||||
|
"text": "Ну ничего, скоро увидимся. Давай, бывай.",
|
||||||
|
"next": "line_14"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "line_14",
|
||||||
|
"type": "Line",
|
||||||
|
"speaker": "Ghost",
|
||||||
|
"portrait": "resources/ghost_avatar.png",
|
||||||
|
"text": "[Гудки]",
|
||||||
|
"next": "end_1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "end_1",
|
||||||
|
"type": "End"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "dialogue_police1",
|
||||||
|
"start": "line_1",
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"id": "line_1",
|
||||||
|
"type": "Line",
|
||||||
|
"speaker": "Ghost",
|
||||||
|
"portrait": "resources/ghost_avatar.png",
|
||||||
|
"text": "Старший лейтенант Каримов, отдел милиции Чуйской области.",
|
||||||
|
"next": "line_2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "line_2",
|
||||||
|
"type": "Line",
|
||||||
|
"speaker": "Hero",
|
||||||
|
"portrait": "resources/w/gg/gg2_s_podsvetkoy5.png",
|
||||||
|
"text": "Я задержан?",
|
||||||
"next": "end_1"
|
"next": "end_1"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@ -1001,14 +1001,20 @@ void Location::setup()
|
|||||||
if (player) {
|
if (player) {
|
||||||
if (!inCar) {
|
if (!inCar) {
|
||||||
player->targetFacingAngle = cameraAzimuth;
|
player->targetFacingAngle = cameraAzimuth;
|
||||||
if (keyForward) {
|
if (playerFrozen) {
|
||||||
|
player->clearPath();
|
||||||
|
wasKeyForward = false;
|
||||||
|
} else if (keyForward) {
|
||||||
player->attackTarget = nullptr;
|
player->attackTarget = nullptr;
|
||||||
Eigen::Vector3f forward(std::sin(cameraAzimuth), 0.f, -std::cos(cameraAzimuth));
|
Eigen::Vector3f forward(std::sin(cameraAzimuth), 0.f, -std::cos(cameraAzimuth));
|
||||||
player->setDirectWalkTarget(player->position + forward * 5.0f);
|
player->setDirectWalkTarget(player->position + forward * 5.0f);
|
||||||
} else if (wasKeyForward) {
|
wasKeyForward = true;
|
||||||
|
} else {
|
||||||
|
if (wasKeyForward) {
|
||||||
player->clearPath();
|
player->clearPath();
|
||||||
}
|
}
|
||||||
wasKeyForward = keyForward;
|
wasKeyForward = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
player->update(delta);
|
player->update(delta);
|
||||||
dialogueSystem.update(static_cast<int>(delta), player->position);
|
dialogueSystem.update(static_cast<int>(delta), player->position);
|
||||||
@ -1050,7 +1056,10 @@ void Location::setup()
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (salesperson) pushOutOfNpcCarFootprint(salesperson->position);
|
if (salesperson) pushOutOfNpcCarFootprint(salesperson->position);
|
||||||
if (police) pushOutOfNpcCarFootprint(police->position);
|
if (police) {
|
||||||
|
police->update(delta);
|
||||||
|
pushOutOfNpcCarFootprint(police->position);
|
||||||
|
}
|
||||||
if (bandit) pushOutOfNpcCarFootprint(bandit->position);
|
if (bandit) pushOutOfNpcCarFootprint(bandit->position);
|
||||||
|
|
||||||
for (auto& npc : npcs) {
|
for (auto& npc : npcs) {
|
||||||
@ -1159,6 +1168,17 @@ void Location::setup()
|
|||||||
{
|
{
|
||||||
policeFollow = true;
|
policeFollow = true;
|
||||||
npcCar.mode = NpcCar::Mode::FOLLOW_PLAYER;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -1237,6 +1257,61 @@ void Location::setup()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
policeEncounterStage = PoliceEncounterStage::Done;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Phone rings once the player drives far from the gas station — fires once,
|
// Phone rings once the player drives far from the gas station — fires once,
|
||||||
// and only after the gas-station sale.
|
// and only after the gas-station sale.
|
||||||
if (inCar && dialoguePlayedGas1 && !dialoguePlayedPhone1 && !dialogueSystem.isActive()) {
|
if (inCar && dialoguePlayedGas1 && !dialoguePlayedPhone1 && !dialogueSystem.isActive()) {
|
||||||
@ -1572,6 +1647,7 @@ void Location::setup()
|
|||||||
case SDLK_l:
|
case SDLK_l:
|
||||||
{
|
{
|
||||||
if (!player) break;
|
if (!player) break;
|
||||||
|
if (playerFrozen) break;
|
||||||
if (!inCar) {
|
if (!inCar) {
|
||||||
const Eigen::Vector3f diff(
|
const Eigen::Vector3f diff(
|
||||||
carPosition.x() - player->position.x(), 0.f,
|
carPosition.x() - player->position.x(), 0.f,
|
||||||
|
|||||||
@ -116,6 +116,11 @@ namespace ZL
|
|||||||
|
|
||||||
bool policeFollow = false;
|
bool policeFollow = false;
|
||||||
|
|
||||||
|
enum class PoliceEncounterStage { Idle, Approaching, Returning, Done };
|
||||||
|
PoliceEncounterStage policeEncounterStage = PoliceEncounterStage::Idle;
|
||||||
|
bool playerFrozen = false;
|
||||||
|
float policeDrivingDialogueTimer = 8.0f;
|
||||||
|
|
||||||
ScriptEngine scriptEngine;
|
ScriptEngine scriptEngine;
|
||||||
Dialogue::DialogueSystem dialogueSystem;
|
Dialogue::DialogueSystem dialogueSystem;
|
||||||
|
|
||||||
|
|||||||
@ -10,7 +10,7 @@ void DialogueRuntime::setDatabase(const DialogueDatabase* value) {
|
|||||||
database = value;
|
database = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DialogueRuntime::startDialogue(const std::string& dialogueId) {
|
bool DialogueRuntime::startDialogue(const std::string& dialogueId, std::function<void()> onFinished) {
|
||||||
if (!database) {
|
if (!database) {
|
||||||
std::cerr << "[dialogue] No database assigned to runtime\n";
|
std::cerr << "[dialogue] No database assigned to runtime\n";
|
||||||
return false;
|
return false;
|
||||||
@ -35,6 +35,7 @@ bool DialogueRuntime::startDialogue(const std::string& dialogueId) {
|
|||||||
cutsceneTotalDurationMs = 0;
|
cutsceneTotalDurationMs = 0;
|
||||||
presentation = {};
|
presentation = {};
|
||||||
presentation.dialogueId = dialogue->id;
|
presentation.dialogueId = dialogue->id;
|
||||||
|
onFinishedCallback = std::move(onFinished);
|
||||||
|
|
||||||
return enterNode(dialogue->startNode);
|
return enterNode(dialogue->startNode);
|
||||||
}
|
}
|
||||||
@ -53,6 +54,12 @@ void DialogueRuntime::stop() {
|
|||||||
cutsceneTotalDurationMs = 0;
|
cutsceneTotalDurationMs = 0;
|
||||||
mode = Mode::Inactive;
|
mode = Mode::Inactive;
|
||||||
presentation = {};
|
presentation = {};
|
||||||
|
|
||||||
|
// Move the callback out before firing so it can safely start a new dialogue
|
||||||
|
// (which would otherwise overwrite the member mid-call).
|
||||||
|
std::function<void()> cb = std::move(onFinishedCallback);
|
||||||
|
onFinishedCallback = nullptr;
|
||||||
|
if (cb) cb();
|
||||||
}
|
}
|
||||||
|
|
||||||
void DialogueRuntime::update(int deltaMs) {
|
void DialogueRuntime::update(int deltaMs) {
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include "dialogue/DialogueDatabase.h"
|
#include "dialogue/DialogueDatabase.h"
|
||||||
#include "external/nlohmann/json.hpp"
|
#include "external/nlohmann/json.hpp"
|
||||||
|
#include <functional>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <unordered_set>
|
#include <unordered_set>
|
||||||
@ -15,7 +16,7 @@ public:
|
|||||||
|
|
||||||
void setDatabase(const DialogueDatabase* value);
|
void setDatabase(const DialogueDatabase* value);
|
||||||
|
|
||||||
bool startDialogue(const std::string& dialogueId);
|
bool startDialogue(const std::string& dialogueId, std::function<void()> onFinished = nullptr);
|
||||||
void stop();
|
void stop();
|
||||||
|
|
||||||
void update(int deltaMs);
|
void update(int deltaMs);
|
||||||
@ -50,6 +51,7 @@ private:
|
|||||||
|
|
||||||
std::unordered_map<std::string, int> flags;
|
std::unordered_map<std::string, int> flags;
|
||||||
std::unordered_set<std::string> consumedChoices;
|
std::unordered_set<std::string> consumedChoices;
|
||||||
|
std::function<void()> onFinishedCallback;
|
||||||
|
|
||||||
std::string currentNodeId;
|
std::string currentNodeId;
|
||||||
std::string pendingNodeAfterCutscene;
|
std::string pendingNodeAfterCutscene;
|
||||||
|
|||||||
@ -106,8 +106,8 @@ bool DialogueSystem::handlePointerReleased(float x, float y) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DialogueSystem::startDialogue(const std::string& dialogueId) {
|
bool DialogueSystem::startDialogue(const std::string& dialogueId, std::function<void()> onFinished) {
|
||||||
return runtime.startDialogue(dialogueId);
|
return runtime.startDialogue(dialogueId, std::move(onFinished));
|
||||||
}
|
}
|
||||||
|
|
||||||
void DialogueSystem::stopDialogue() {
|
void DialogueSystem::stopDialogue() {
|
||||||
|
|||||||
@ -4,6 +4,7 @@
|
|||||||
#include "dialogue/DialogueRuntime.h"
|
#include "dialogue/DialogueRuntime.h"
|
||||||
#include <Eigen/Dense>
|
#include <Eigen/Dense>
|
||||||
#include <SDL.h>
|
#include <SDL.h>
|
||||||
|
#include <functional>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
@ -31,7 +32,7 @@ public:
|
|||||||
void handlePointerMoved(float x, float y);
|
void handlePointerMoved(float x, float y);
|
||||||
bool handlePointerReleased(float x, float y);
|
bool handlePointerReleased(float x, float y);
|
||||||
|
|
||||||
bool startDialogue(const std::string& dialogueId);
|
bool startDialogue(const std::string& dialogueId, std::function<void()> onFinished = nullptr);
|
||||||
void stopDialogue();
|
void stopDialogue();
|
||||||
|
|
||||||
bool isActive() const { return runtime.isActive(); }
|
bool isActive() const { return runtime.isActive(); }
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user