police chase
This commit is contained in:
parent
8e0936836e
commit
25bb79fb42
@ -273,7 +273,7 @@
|
||||
"type": "Line",
|
||||
"speaker": "Hero",
|
||||
"portrait": "resources/w/gg/gg2_s_podsvetkoy5.png",
|
||||
"text": "Phone 1 У нас бензин кончается.",
|
||||
"text": "[Телефон звонит]",
|
||||
"next": "line_2"
|
||||
},
|
||||
{
|
||||
@ -281,7 +281,7 @@
|
||||
"type": "Line",
|
||||
"speaker": "Hero",
|
||||
"portrait": "resources/w/gg/gg2_s_podsvetkoy5.png",
|
||||
"text": "Надо заправиться.",
|
||||
"text": "Да, слушаю.",
|
||||
"next": "line_3"
|
||||
},
|
||||
{
|
||||
@ -289,7 +289,7 @@
|
||||
"type": "Line",
|
||||
"speaker": "Ghost",
|
||||
"portrait": "resources/ghost_avatar.png",
|
||||
"text": "Хорошо, только давай быстро.",
|
||||
"text": "Алексей, это Нурланбай на связи.",
|
||||
"next": "line_4"
|
||||
},
|
||||
{
|
||||
@ -297,7 +297,105 @@
|
||||
"type": "Line",
|
||||
"speaker": "Ghost",
|
||||
"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"
|
||||
},
|
||||
{
|
||||
|
||||
@ -1001,14 +1001,20 @@ void Location::setup()
|
||||
if (player) {
|
||||
if (!inCar) {
|
||||
player->targetFacingAngle = cameraAzimuth;
|
||||
if (keyForward) {
|
||||
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);
|
||||
} else if (wasKeyForward) {
|
||||
player->clearPath();
|
||||
wasKeyForward = true;
|
||||
} else {
|
||||
if (wasKeyForward) {
|
||||
player->clearPath();
|
||||
}
|
||||
wasKeyForward = false;
|
||||
}
|
||||
wasKeyForward = keyForward;
|
||||
}
|
||||
player->update(delta);
|
||||
dialogueSystem.update(static_cast<int>(delta), player->position);
|
||||
@ -1050,7 +1056,10 @@ void Location::setup()
|
||||
}
|
||||
|
||||
if (salesperson) pushOutOfNpcCarFootprint(salesperson->position);
|
||||
if (police) pushOutOfNpcCarFootprint(police->position);
|
||||
if (police) {
|
||||
police->update(delta);
|
||||
pushOutOfNpcCarFootprint(police->position);
|
||||
}
|
||||
if (bandit) pushOutOfNpcCarFootprint(bandit->position);
|
||||
|
||||
for (auto& npc : npcs) {
|
||||
@ -1159,6 +1168,17 @@ void Location::setup()
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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,
|
||||
// and only after the gas-station sale.
|
||||
if (inCar && dialoguePlayedGas1 && !dialoguePlayedPhone1 && !dialogueSystem.isActive()) {
|
||||
@ -1572,6 +1647,7 @@ void Location::setup()
|
||||
case SDLK_l:
|
||||
{
|
||||
if (!player) break;
|
||||
if (playerFrozen) break;
|
||||
if (!inCar) {
|
||||
const Eigen::Vector3f diff(
|
||||
carPosition.x() - player->position.x(), 0.f,
|
||||
|
||||
@ -116,6 +116,11 @@ namespace ZL
|
||||
|
||||
bool policeFollow = false;
|
||||
|
||||
enum class PoliceEncounterStage { Idle, Approaching, Returning, Done };
|
||||
PoliceEncounterStage policeEncounterStage = PoliceEncounterStage::Idle;
|
||||
bool playerFrozen = false;
|
||||
float policeDrivingDialogueTimer = 8.0f;
|
||||
|
||||
ScriptEngine scriptEngine;
|
||||
Dialogue::DialogueSystem dialogueSystem;
|
||||
|
||||
|
||||
@ -10,7 +10,7 @@ void DialogueRuntime::setDatabase(const DialogueDatabase* value) {
|
||||
database = value;
|
||||
}
|
||||
|
||||
bool DialogueRuntime::startDialogue(const std::string& dialogueId) {
|
||||
bool DialogueRuntime::startDialogue(const std::string& dialogueId, std::function<void()> onFinished) {
|
||||
if (!database) {
|
||||
std::cerr << "[dialogue] No database assigned to runtime\n";
|
||||
return false;
|
||||
@ -35,6 +35,7 @@ bool DialogueRuntime::startDialogue(const std::string& dialogueId) {
|
||||
cutsceneTotalDurationMs = 0;
|
||||
presentation = {};
|
||||
presentation.dialogueId = dialogue->id;
|
||||
onFinishedCallback = std::move(onFinished);
|
||||
|
||||
return enterNode(dialogue->startNode);
|
||||
}
|
||||
@ -53,6 +54,12 @@ void DialogueRuntime::stop() {
|
||||
cutsceneTotalDurationMs = 0;
|
||||
mode = Mode::Inactive;
|
||||
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) {
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
|
||||
#include "dialogue/DialogueDatabase.h"
|
||||
#include "external/nlohmann/json.hpp"
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
@ -15,7 +16,7 @@ public:
|
||||
|
||||
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 update(int deltaMs);
|
||||
@ -50,6 +51,7 @@ private:
|
||||
|
||||
std::unordered_map<std::string, int> flags;
|
||||
std::unordered_set<std::string> consumedChoices;
|
||||
std::function<void()> onFinishedCallback;
|
||||
|
||||
std::string currentNodeId;
|
||||
std::string pendingNodeAfterCutscene;
|
||||
|
||||
@ -106,8 +106,8 @@ bool DialogueSystem::handlePointerReleased(float x, float y) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DialogueSystem::startDialogue(const std::string& dialogueId) {
|
||||
return runtime.startDialogue(dialogueId);
|
||||
bool DialogueSystem::startDialogue(const std::string& dialogueId, std::function<void()> onFinished) {
|
||||
return runtime.startDialogue(dialogueId, std::move(onFinished));
|
||||
}
|
||||
|
||||
void DialogueSystem::stopDialogue() {
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
#include "dialogue/DialogueRuntime.h"
|
||||
#include <Eigen/Dense>
|
||||
#include <SDL.h>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
@ -31,7 +32,7 @@ public:
|
||||
void handlePointerMoved(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();
|
||||
|
||||
bool isActive() const { return runtime.isActive(); }
|
||||
|
||||
Loading…
Reference in New Issue
Block a user