Added startup scripts

This commit is contained in:
Vladislav Khorev committed 2026-05-09 14:14:04 +03:00
1 parent a479fa4ac1
commit 13ef92160d
26 files changed
+117164 -150

No files matched your search

+9 -8
View File
@@ -164,8 +164,9 @@ namespace ZL
params1.gameObjectsJsonPath = "resources/config2/gameobjects.json";
params1.npcsJsonPath = "resources/config2/npcs.json";
params1.dialoguesJsonPath = "resources/dialogue/sample_dialogues.json";
params1.navigationJsonPath = "resources/config2/navigation.json";
params1.navigationJsonPaths = {"resources/config2/navigation.json"};
params1.teleportsJsonPath = "resources/config2/teleports.json";
params1.triggerZonesJsonPath = "resources/config2/trigger_zones.json";
params1.scriptPath = "resources/start.lua";
params1.playerPosition = Eigen::Vector3f(0.942694, 0, -9.63104);
@@ -176,8 +177,9 @@ namespace ZL
params2.roomMeshPath = "resources/w/exterior/Segmented_Plane002.txt";
params2.roomTexturePath = "resources/w/exterior/Segmented_Plane002.png";
params2.gameObjectsJsonPath = "resources/config2/gameobjects2.json";
params2.navigationJsonPath = "resources/config2/navigation2.json";
params2.navigationJsonPaths = {"resources/config2/navigation2.json"};
params2.teleportsJsonPath = "resources/config2/teleports2.json";
params2.triggerZonesJsonPath = "resources/config2/trigger_zones2.json";
params2.scriptPath = "resources/start2.lua";
params2.playerPosition = Eigen::Vector3f(5, 0, -18.4);
params2.npcsJsonPath = "resources/config2/npcs2.json";
@@ -191,11 +193,11 @@ namespace ZL
params_dorm.gameObjectsJsonPath = "resources/config2/gameobjects_dorm.json";
params_dorm.npcsJsonPath = "resources/config2/npcs_dorm.json";
params_dorm.dialoguesJsonPath = "resources/dialogue/dorm_dialogues.json";
params_dorm.navigationJsonPath = "resources/config2/navigation_dorm.json";
params_dorm.navigationJsonPaths = {"resources/config2/navigation_dorm.json", "resources/config2/navigation_dorm_tutorial001.json" };
params_dorm.teleportsJsonPath = "resources/config2/teleports_dorm.json";
params_dorm.triggerZonesJsonPath = "resources/config2/trigger_zones_dorm.json";
params_dorm.scriptPath = "resources/start_dorm.lua";
params_dorm.playerPosition = Eigen::Vector3f(6.0357, 0, -16.0581);
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);
@@ -534,15 +536,14 @@ namespace ZL
case SDLK_o:
//y = y + 0.002;
//currentLocation->player->hp = 200;
currentLocation->npcs[0]->walkSpeed += 0.02f;
currentLocation->npcs[0]->walkSpeed += 0.01f;
std::cout << "Walk speed: " << currentLocation->npcs[0]->walkSpeed << std::endl;
break;
case SDLK_k:
//y = y - 0.002;
std::cout << "Player pos: " << currentLocation->player->position.transpose() << std::endl;
currentLocation->npcs[0]->walkSpeed -= 0.02f;
currentLocation->npcs[0]->walkSpeed -= 0.01f;
std::cout << "Walk speed: " << currentLocation->npcs[0]->walkSpeed << std::endl;
break;
+120 -22
View File
@@ -137,6 +137,7 @@ namespace ZL
}
loadTeleportZones(params.teleportsJsonPath, CONST_ZIP_FILE);
loadTriggerZones(params.triggerZonesJsonPath, CONST_ZIP_FILE);
#ifndef EMSCRIPTEN
// Create shadow map (2048x2048, ortho size 40, near 0.1, far 100)
@@ -145,7 +146,7 @@ namespace ZL
std::cout << "Shadow map initialized" << std::endl;
#endif
setupNavigation(params.navigationJsonPath);
setupNavigation(params.navigationJsonPaths);
dialogueSystem.init(renderer, CONST_ZIP_FILE);
dialogueSystem.loadDatabase(params.dialoguesJsonPath);
@@ -165,7 +166,7 @@ namespace ZL
false
});*/
navigationEditorConfigPath = params.navigationJsonPath;
// navigationMapPaths is already populated by setupNavigation
scriptEngine.init(this, &inventory, params.scriptPath);
@@ -231,13 +232,84 @@ namespace ZL
std::cout << "[TELEPORT] Loaded " << teleportZones.size() << " teleport(s) from " << jsonPath << std::endl;
}
void Location::setupNavigation(const std::string& navigationJsonPath)
void Location::loadTriggerZones(const std::string& jsonPath, const char* zipFile)
{
// Static navigation blockers are defined in the navigation JSON as polygons.
// NPC + player are handled as dynamic obstacles, so they are intentionally NOT in JSON.
navigation.build({}, navigationJsonPath, CONST_ZIP_FILE);
if (jsonPath.empty()) return;
if (navigationEditorMode) {
std::string content;
try {
if (!zipFile || zipFile[0] == '\0') {
content = readTextFile(jsonPath);
} else {
auto buf = readFileFromZIP(jsonPath, zipFile);
if (buf.empty()) {
std::cerr << "[TRIGGER] Failed to read " << jsonPath << " from zip" << std::endl;
return;
}
content.assign(buf.begin(), buf.end());
}
} catch (const std::exception& e) {
std::cerr << "[TRIGGER] Failed to open " << jsonPath << ": " << e.what() << std::endl;
return;
}
using json = nlohmann::json;
json j;
try {
j = json::parse(content);
} catch (const std::exception& e) {
std::cerr << "[TRIGGER] JSON parse error in " << jsonPath << ": " << e.what() << std::endl;
return;
}
if (!j.contains("trigger_zones") || !j["trigger_zones"].is_array()) return;
for (const auto& item : j["trigger_zones"]) {
TriggerZone tz;
tz.id = item.value("id", "");
tz.enabled = item.value("enabled", true);
tz.radius = item.value("radius", 1.5f);
tz.hysteresis = item.value("hysteresis", 0.3f);
tz.position = Eigen::Vector3f(
item.value("positionX", 0.0f),
item.value("positionY", 0.0f),
item.value("positionZ", 0.0f)
);
triggerZones.push_back(std::move(tz));
}
std::cout << "[TRIGGER] Loaded " << triggerZones.size() << " trigger zone(s) from " << jsonPath << std::endl;
}
void Location::updateTriggerZones(const Eigen::Vector3f& playerPos)
{
for (auto& tz : triggerZones) {
if (!tz.enabled) continue;
const float dist = (playerPos - tz.position).norm();
if (!tz.playerInside && dist <= tz.radius) {
tz.playerInside = true;
scriptEngine.callTriggerEnterCallback(tz.id);
} else if (tz.playerInside && dist > tz.radius + tz.hysteresis) {
tz.playerInside = false;
scriptEngine.callTriggerExitCallback(tz.id);
}
}
}
void Location::setupNavigation(const std::vector<std::string>& paths)
{
navigationMapPaths = paths;
navigationMaps.clear();
navigationMaps.resize(paths.size());
for (size_t i = 0; i < paths.size(); ++i) {
navigationMaps[i].build({}, paths[i], CONST_ZIP_FILE);
}
activeNavigationIndex = 0;
navigation = navigationMaps.empty() ? nullptr : &navigationMaps[0];
if (navigationEditorMode && navigation) {
navigationEditorBuildNavMeshes();
}
@@ -245,6 +317,7 @@ namespace ZL
auto makePlanner = [this](const Character* self) {
return [this, self](const Eigen::Vector3f& start, const Eigen::Vector3f& end) {
if (!navigation) return std::vector<Eigen::Vector3f>{};
std::vector<PathFinder::DynamicObstacle> dynamicObstacles;
dynamicObstacles.reserve(npcs.size() + 1);
@@ -257,7 +330,7 @@ namespace ZL
}
PathFinder::DynamicObstacle obs;
obs.position = Eigen::Vector3f(other->position.x(), navigation.getFloorY(), other->position.z());
obs.position = Eigen::Vector3f(other->position.x(), navigation->getFloorY(), other->position.z());
obs.radius = (std::max)(0.0f, other->collisionRadius);
dynamicObstacles.push_back(obs);
};
@@ -267,7 +340,7 @@ namespace ZL
addCharacter(npc.get());
}
return navigation.findPath(start, end, dynamicObstacles);
return navigation->findPath(start, end, dynamicObstacles);
};
};
@@ -282,12 +355,35 @@ namespace ZL
}
}
bool Location::switchNavigation(int index)
{
if (index < 0 || index >= static_cast<int>(navigationMaps.size())) {
std::cerr << "[NAV] switchNavigation: index " << index << " out of range\n";
return false;
}
activeNavigationIndex = index;
navigation = &navigationMaps[index];
// Force all characters to replan their paths against the new nav map.
if (player) player->forceReplan();
for (auto& npc : npcs) {
if (npc) npc->forceReplan();
}
if (navigationEditorMode) {
navigationEditorBuildNavMeshes();
}
std::cout << "[NAV] Switched to navigation map " << index << "\n";
return true;
}
void Location::navigationEditorBuildNavMeshes()
{
navigationEditorNavMeshes.clear();
const float y = navigation.getFloorY() + 0.02f;
if (!navigation) return;
const float y = navigation->getFloorY() + 0.02f;
const Eigen::Vector3f red(1.0f, 0.0f, 0.0f);
for (const auto& obs : navigation.getObstaclePolygons()) {
for (const auto& obs : navigation->getObstaclePolygons()) {
if (obs.polygon.size() < 3) continue;
VertexRenderStruct mesh;
mesh.data = CreatePolygonFloor(obs.polygon, y, red);
@@ -311,7 +407,7 @@ namespace ZL
{
VertexDataStruct data;
const Eigen::Vector3f yellow(1.0f, 1.0f, 0.0f);
const float y = navigation.getFloorY() + 0.05f;
const float y = navigation ? navigation->getFloorY() + 0.05f : 0.05f;
const float s = 0.2f;
for (const auto& pt : navigationEditorPoints) {
@@ -381,13 +477,13 @@ namespace ZL
poly.polygon.emplace_back(pt.x(), pt.z());
}
navigation.addObstaclePolygon(poly);
if (navigation) navigation->addObstaclePolygon(poly);
std::cout << "[NAV_EDITOR] Added obstacle '" << poly.name << "' with "
<< poly.polygon.size() << " vertices\n";
// Add a red mesh for the new obstacle polygon so it's visible immediately.
{
const float y = navigation.getFloorY() + 0.02f;
const float y = navigation ? navigation->getFloorY() + 0.02f : 0.02f;
const Eigen::Vector3f red(1.0f, 0.0f, 0.0f);
VertexRenderStruct mesh;
mesh.data = CreatePolygonFloor(poly.polygon, y, red);
@@ -409,7 +505,7 @@ namespace ZL
if (!std::filesystem::exists(filename)) break;
}
if (navigation.saveConfig(filename)) {
if (navigation && navigation->saveConfig(filename)) {
std::cout << "[NAV_EDITOR] Saved navigation config to: " << filename << "\n";
} else {
std::cerr << "[NAV_EDITOR] Failed to save to: " << filename << "\n";
@@ -418,8 +514,8 @@ namespace ZL
void Location::navigationEditorReload()
{
setupNavigation(navigationEditorConfigPath);
std::cout << "[NAV_EDITOR] Reloaded navigation from: " << navigationEditorConfigPath << "\n";
setupNavigation(navigationMapPaths);
std::cout << "[NAV_EDITOR] Reloaded navigation maps (" << navigationMapPaths.size() << " maps)\n";
}
@@ -821,7 +917,8 @@ namespace ZL
bool Location::setNavigationAreaAvailable(const std::string& areaName, bool available)
{
return navigation.setAreaAvailable(areaName, available);
if (!navigation) return false;
return navigation->setAreaAvailable(areaName, available);
}
void Location::resolveCharacterCollisions()
@@ -882,9 +979,9 @@ namespace ZL
newB.z() += normal.y() * push;
newB.y() = 0.f;
if (navigation.isReady()) {
const bool aOk = navigation.isWalkable(newA);
const bool bOk = navigation.isWalkable(newB);
if (navigation && navigation->isReady()) {
const bool aOk = navigation->isWalkable(newA);
const bool bOk = navigation->isWalkable(newB);
if (aOk && bOk) {
a->position = newA;
@@ -978,7 +1075,8 @@ namespace ZL
{
if (player) {
player->update(delta);
dialogueSystem.update(static_cast<int>(delta), player->position);
dialogueSystem.update(static_cast<int>(delta));
updateTriggerZones(player->position);
}
for (auto& npc : npcs)
{
+22 -4
View File
@@ -19,6 +19,16 @@
namespace ZL
{
struct TriggerZone
{
std::string id;
Eigen::Vector3f position = Eigen::Vector3f::Zero();
float radius = 1.5f;
float hysteresis = 0.3f; // exit fires at radius + hysteresis to prevent flickering
bool enabled = true;
bool playerInside = false; // runtime tracking state, not serialized
};
struct LocationSetup
{
std::string roomMeshPath;
@@ -26,9 +36,10 @@ namespace ZL
std::string gameObjectsJsonPath;
std::string npcsJsonPath;
std::string dialoguesJsonPath;
std::string navigationJsonPath;
std::vector<std::string> navigationJsonPaths;
std::string scriptPath;
std::string teleportsJsonPath;
std::string triggerZonesJsonPath;
Eigen::Vector3f playerPosition = Eigen::Vector3f::Zero();
};
@@ -52,7 +63,9 @@ namespace ZL
float cameraAzimuth = 0.0f;
float cameraInclination = M_PI * 30.f / 180.f;
PathFinder navigation;
std::vector<PathFinder> navigationMaps;
PathFinder* navigation = nullptr;
int activeNavigationIndex = 0;
std::unique_ptr<ShadowMap> shadowMap;
Eigen::Matrix4f cameraViewMatrix = Eigen::Matrix4f::Identity();
@@ -73,13 +86,15 @@ namespace ZL
TeleportZone* targetTeleportZone = nullptr;
std::function<void(const std::string&, const Eigen::Vector3f&, float)> onTeleport;
std::vector<TriggerZone> triggerZones;
// Navigation editor — toggle with 'N', save with 'B', right-click to finalize polygon
bool navigationEditorMode = false;
std::vector<Eigen::Vector3f> navigationEditorPoints;
VertexRenderStruct navigationEditorPointsMesh;
std::vector<VertexRenderStruct> navigationEditorNavMeshes;
std::string navigationEditorConfigPath;
std::vector<std::string> navigationMapPaths;
int navigationEditorObstacleCounter = 0;
void navigationEditorBuildNavMeshes();
@@ -99,7 +114,8 @@ namespace ZL
void setup(const LocationSetup& params);
void setupNavigation(const std::string& navigationJsonPath);
void setupNavigation(const std::vector<std::string>& paths);
bool switchNavigation(int index);
InteractiveObject* raycastInteractiveObjects(const Eigen::Vector3f& rayOrigin, const Eigen::Vector3f& rayDir);
Character* raycastNpcs(const Eigen::Vector3f& rayOrigin, const Eigen::Vector3f& rayDir, float maxDistance = 100.0f);
@@ -127,6 +143,8 @@ namespace ZL
void resolveCharacterCollisions();
void updateDynamicReplans(int64_t deltaMs);
void loadTeleportZones(const std::string& jsonPath, const char* zipFile);
void loadTriggerZones(const std::string& jsonPath, const char* zipFile);
void updateTriggerZones(const Eigen::Vector3f& playerPos);
std::unordered_map<Character*, Eigen::Vector3f> lastCharacterPositions;
std::unordered_map<Character*, int64_t> replanCooldownRemainingMs;
+43
View File
@@ -2,6 +2,7 @@
#include "Game.h"
#include <iostream>
#include <stdexcept>
#include <unordered_map>
#include "Location.h"
#define SOL_ALL_SAFETIES_ON 1
@@ -11,6 +12,8 @@ namespace ZL {
struct ScriptEngine::Impl {
sol::state lua;
std::unordered_map<std::string, sol::protected_function> triggerEnterCallbacks;
std::unordered_map<std::string, sol::protected_function> triggerExitCallbacks;
};
ScriptEngine::ScriptEngine() = default;
@@ -133,6 +136,24 @@ namespace ZL {
}
});
api.set_function("switch_navigation",
[game](int index) {
if (!game->switchNavigation(index)) {
std::cerr << "[script] switch_navigation: index " << index << " out of range\n";
}
});
// set_trigger_zone_callbacks(zone_id, on_enter, on_exit)
// on_enter and on_exit are optional Lua functions (pass nil to omit).
// Called when the player enters or exits the named trigger zone.
api.set_function("set_trigger_zone_callbacks",
[this_impl = impl.get()](const std::string& zoneId, sol::object onEnter, sol::object onExit) {
if (onEnter.is<sol::protected_function>())
this_impl->triggerEnterCallbacks[zoneId] = onEnter.as<sol::protected_function>();
if (onExit.is<sol::protected_function>())
this_impl->triggerExitCallbacks[zoneId] = onExit.as<sol::protected_function>();
});
// receive_npc_gift(npc_index)
api.set_function("receive_npc_gift", [game, inventory](int npcIndex) {
std::cout << "[script] receive_npc_gift: npc index " << npcIndex << std::endl;
@@ -254,4 +275,26 @@ namespace ZL {
}
}
void ScriptEngine::callTriggerEnterCallback(const std::string& zoneId) {
if (!impl) return;
auto it = impl->triggerEnterCallbacks.find(zoneId);
if (it == impl->triggerEnterCallbacks.end()) return;
auto result = it->second();
if (!result.valid()) {
sol::error err = result;
std::cerr << "[SCRIPT] trigger enter callback error for '" << zoneId << "': " << err.what() << "\n";
}
}
void ScriptEngine::callTriggerExitCallback(const std::string& zoneId) {
if (!impl) return;
auto it = impl->triggerExitCallbacks.find(zoneId);
if (it == impl->triggerExitCallbacks.end()) return;
auto result = it->second();
if (!result.valid()) {
sol::error err = result;
std::cerr << "[SCRIPT] trigger exit callback error for '" << zoneId << "': " << err.what() << "\n";
}
}
} // namespace ZL
+3
View File
@@ -24,6 +24,9 @@ public:
void callNpcInteractCallback(int npcIndex);
void callTriggerEnterCallback(const std::string& zoneId);
void callTriggerExitCallback(const std::string& zoneId);
private:
struct Impl;
std::unique_ptr<Impl> impl;
+1 -25
View File
@@ -11,23 +11,7 @@ bool DialogueSystem::loadDatabase(const std::string& path) {
return database.loadFromFile(path);
}
void DialogueSystem::update(int deltaMs, const Eigen::Vector3f& playerPosition) {
if (!runtime.isActive()) {
for (TriggerZone& zone : triggerZones) {
if (zone.once && zone.triggered) {
continue;
}
const Eigen::Vector3f diff = playerPosition - zone.center;
if (diff.norm() <= zone.radius) {
if (startDialogue(zone.dialogueId)) {
zone.triggered = true;
break;
}
}
}
}
void DialogueSystem::update(int deltaMs) {
runtime.update(deltaMs);
overlay.update(runtime.getPresentation(), deltaMs);
if (overlay.consumeSkipRequested()) {
@@ -139,12 +123,4 @@ void DialogueSystem::stopDialogue() {
runtime.stop();
}
void DialogueSystem::addTriggerZone(const TriggerZone& zone) {
triggerZones.push_back(zone);
}
void DialogueSystem::clearTriggerZones() {
triggerZones.clear();
}
} // namespace ZL::Dialogue
+1 -16
View File
@@ -2,28 +2,17 @@
#include "dialogue/DialogueOverlay.h"
#include "dialogue/DialogueRuntime.h"
#include <Eigen/Dense>
#include <SDL.h>
#include <string>
#include <vector>
namespace ZL::Dialogue {
struct TriggerZone {
std::string id;
std::string dialogueId;
Eigen::Vector3f center = Eigen::Vector3f::Zero();
float radius = 1.5f;
bool once = true;
bool triggered = false;
};
class DialogueSystem {
public:
bool init(Renderer& renderer, const std::string& zipFile = "");
bool loadDatabase(const std::string& path);
void update(int deltaMs, const Eigen::Vector3f& playerPosition);
void update(int deltaMs);
void draw(Renderer& renderer);
bool handleKeyDown(SDL_Keycode key);
@@ -40,14 +29,10 @@ public:
void setFlag(const std::string& name, int value) { runtime.setFlag(name, value); }
int getFlag(const std::string& name) const { return runtime.getFlag(name); }
void addTriggerZone(const TriggerZone& zone);
void clearTriggerZones();
private:
DialogueDatabase database;
DialogueRuntime runtime;
DialogueOverlay overlay;
std::vector<TriggerZone> triggerZones;
};
} // namespace ZL::Dialogue