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

+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)
{