Working on location exterior

This commit is contained in:
Vladislav Khorev committed 2026-05-21 20:30:49 +03:00
1 parent 3a73f3fbde
commit ffd5bd01a4
35 files changed
+72176 -124

No files matched your search

+11 -2
View File
@@ -16,7 +16,6 @@
#ifdef EMSCRIPTEN
#include <emscripten.h>
#endif
@@ -676,7 +675,17 @@ namespace ZL
break;
case SDLK_j:
menuManager.toggleQuestJournal();
if (navigationEditorMode && currentLocation) {
currentLocation->gameObjectEditorPlaceTree();
} else {
menuManager.toggleQuestJournal();
}
break;
case SDLK_v:
if (navigationEditorMode && currentLocation) {
currentLocation->gameObjectEditorSave();
}
break;
case SDLK_RETURN:
+114
View File
@@ -8,6 +8,7 @@
#include <cmath>
#include <algorithm>
#include <filesystem>
#include <fstream>
#include <functional>
#include <memory>
#include <cfloat>
@@ -499,6 +500,82 @@ namespace ZL
std::cout << "[NAV_EDITOR] Reloaded navigation maps (" << navigationMapPaths.size() << " maps)\n";
}
void Location::gameObjectEditorPlaceTree()
{
if (!player) return;
static std::mt19937 rng(std::random_device{}());
std::uniform_real_distribution<float> scaleDist(0.8f, 1.2f);
std::uniform_real_distribution<float> rotDist(0.0f, 360.0f);
GameObjectData data;
data.name = "editor_tree_" + std::to_string(++editorPlacedObjectCounter);
data.texturePath = "resources/w/exterior/tree001.png";
data.meshPath = "resources/w/exterior/tree003.txt";
data.rotationX = 0.0f;
data.rotationY = rotDist(rng);
data.rotationZ = 0.0f;
data.positionX = player->position.x();
data.positionY = player->position.y();
data.positionZ = player->position.z();
data.scale = scaleDist(rng);
LoadedGameObject obj = GameObjectLoader::buildLoadedObject(data, renderer, CONST_ZIP_FILE);
obj.mesh.data.Move({ data.positionX, data.positionY, data.positionZ });
obj.mesh.RefreshVBO();
gameObjects[data.name] = std::move(obj);
editorPlacedObjects.push_back(data);
std::cout << "[GAME_EDITOR] Placed '" << data.name << "' at ("
<< data.positionX << ", " << data.positionZ
<< ") scale=" << data.scale << " rotY=" << data.rotationY << "\n";
}
void Location::gameObjectEditorSave()
{
if (editorPlacedObjects.empty()) {
std::cout << "[GAME_EDITOR] No editor-placed objects to save\n";
return;
}
std::string baseName;
for (int i = 1; ; ++i) {
char buf[32];
snprintf(buf, sizeof(buf), "saved_objects%03d", i);
baseName = buf;
if (!std::filesystem::exists(baseName + ".json")) break;
}
using json = nlohmann::json;
json j;
j["objects"] = json::array();
for (const auto& d : editorPlacedObjects) {
json obj;
obj["name"] = d.name;
obj["texturePath"] = d.texturePath;
obj["meshPath"] = d.meshPath;
obj["rotationX"] = d.rotationX;
obj["rotationY"] = d.rotationY;
obj["rotationZ"] = d.rotationZ;
obj["positionX"] = d.positionX;
obj["positionY"] = d.positionY;
obj["positionZ"] = d.positionZ;
obj["scale"] = d.scale;
j["objects"].push_back(obj);
}
const std::string filename = baseName + ".json";
std::ofstream out(filename);
if (out.is_open()) {
out << j.dump(4);
std::cout << "[GAME_EDITOR] Saved " << editorPlacedObjects.size()
<< " object(s) to " << filename << "\n";
} else {
std::cerr << "[GAME_EDITOR] Failed to open " << filename << " for writing\n";
}
}
InteractiveObject* Location::raycastInteractiveObjects(const Eigen::Vector3f& rayOrigin, const Eigen::Vector3f& rayDir) {
if (interactiveObjects.empty()) {
@@ -983,6 +1060,37 @@ namespace ZL
return navigation->setAreaAvailable(areaName, available);
}
int Location::findNpcIndex(const Character* npc) const
{
for (int i = 0; i < static_cast<int>(npcs.size()); ++i) {
if (npcs[i].get() == npc) return i;
}
return -1;
}
void Location::fireBumpCallbacks(Character* mover, Character* standing)
{
if (mover->isPlayer) {
// Player walked into a standing NPC
const int idx = findNpcIndex(standing);
if (idx < 0) return;
auto& cooldown = npcBumpedByPlayerCooldown[idx];
if (cooldown <= 0.f) {
cooldown = NPC_BUMP_CALLBACK_COOLDOWN;
scriptEngine.callNpcBumpedByPlayerCallback(idx);
}
} else if (standing->isPlayer) {
// Moving NPC walked into the standing player
const int idx = findNpcIndex(mover);
if (idx < 0) return;
auto& cooldown = npcBumpsPlayerCooldown[idx];
if (cooldown <= 0.f) {
cooldown = NPC_BUMP_CALLBACK_COOLDOWN;
scriptEngine.callNpcBumpsPlayerCallback(idx);
}
}
}
void Location::nudgeCharacterAside(Character* standing, const Eigen::Vector3f& awayFrom)
{
if (!standing || standing->isPlayer) return;
@@ -1093,8 +1201,10 @@ namespace ZL
if (aWasMoving && !bWasMoving) {
nudgeCharacterAside(b, a->position);
fireBumpCallbacks(a, b);
} else if (bWasMoving && !aWasMoving) {
nudgeCharacterAside(a, b->position);
fireBumpCallbacks(b, a);
}
}
}
@@ -1188,6 +1298,10 @@ namespace ZL
resolveCharacterCollisions();
updateDynamicReplans(delta);
const float deltaS = static_cast<float>(delta) / 1000.f;
for (auto& [idx, t] : npcBumpedByPlayerCooldown) t -= deltaS;
for (auto& [idx, t] : npcBumpsPlayerCooldown) t -= deltaS;
// Check if player reached target interactive object
if (targetInteractiveObject && player && !targetInteractiveObject->isAnimating) {
float distToObject = (player->position - targetInteractiveObject->position).norm();
+12
View File
@@ -109,6 +109,12 @@ namespace ZL
void navigationEditorSave();
void navigationEditorReload();
void gameObjectEditorPlaceTree();
void gameObjectEditorSave();
std::vector<GameObjectData> editorPlacedObjects;
int editorPlacedObjectCounter = 0;
// Set by Game when the user's primary pointer (left mouse / single touch)
// has crossed the tap-vs-drag threshold and is now rotating the camera.
bool cameraDragging = false;
@@ -152,9 +158,15 @@ namespace ZL
void loadTriggerZones(const std::string& jsonPath, const char* zipFile);
void updateTriggerZones(const Eigen::Vector3f& playerPos);
void nudgeCharacterAside(Character* standing, const Eigen::Vector3f& awayFrom);
int findNpcIndex(const Character* npc) const;
void fireBumpCallbacks(Character* mover, Character* standing);
std::unordered_map<Character*, Eigen::Vector3f> lastCharacterPositions;
std::unordered_map<Character*, int64_t> replanCooldownRemainingMs;
static constexpr float NPC_BUMP_CALLBACK_COOLDOWN = 5.0f;
std::unordered_map<int, float> npcBumpedByPlayerCooldown;
std::unordered_map<int, float> npcBumpsPlayerCooldown;
};
} // namespace ZL
+55
View File
@@ -16,6 +16,8 @@ namespace ZL {
std::unordered_map<std::string, sol::protected_function> triggerEnterCallbacks;
std::unordered_map<std::string, sol::protected_function> triggerExitCallbacks;
std::unordered_map<std::string, sol::protected_function> cutsceneCompleteCallbacks;
std::unordered_map<int, sol::protected_function> npcBumpedByPlayerCallbacks;
std::unordered_map<int, sol::protected_function> npcBumpsPlayerCallbacks;
std::unordered_map<std::string, int>* globalInts = nullptr;
std::unordered_map<std::string, float>* globalFloats = nullptr;
sol::protected_function locationEnterCallback;
@@ -458,6 +460,37 @@ namespace ZL {
std::cerr << "[script] scale_object: object '" << name << "' not found\n";
});
// player_stop() — cancels the player's current walk target and stops them immediately.
api.set_function("player_stop",
[game]() {
if (game->player) game->player->stopInPlace();
});
// npc_stop(index) — cancels an NPC's current walk target and stops them immediately.
api.set_function("npc_stop",
[game](int index) {
auto& npcs = game->npcs;
if (index < 0 || index >= static_cast<int>(npcs.size())) {
std::cerr << "[script] npc_stop: index " << index
<< " out of range (0.." << npcs.size() - 1 << ")\n";
return;
}
npcs[index]->stopInPlace();
});
// set_npc_bump_callbacks(npc_index, on_player_bumps_npc, on_npc_bumps_player)
// Both callbacks are optional (pass nil to omit).
// on_player_bumps_npc() — player was moving and walked into the NPC.
// on_npc_bumps_player() — the NPC was moving and walked into the standing player.
// Each callback fires at most once every 5 seconds per NPC (cooldown managed by Location).
api.set_function("set_npc_bump_callbacks",
[this_impl = impl.get()](int index, sol::object onPlayerBumpsNpc, sol::object onNpcBumpsPlayer) {
if (onPlayerBumpsNpc.is<sol::protected_function>())
this_impl->npcBumpedByPlayerCallbacks[index] = onPlayerBumpsNpc.as<sol::protected_function>();
if (onNpcBumpsPlayer.is<sol::protected_function>())
this_impl->npcBumpsPlayerCallbacks[index] = onNpcBumpsPlayer.as<sol::protected_function>();
});
lua.script_file(scriptPath);
}
@@ -625,4 +658,26 @@ namespace ZL {
}
}
void ScriptEngine::callNpcBumpedByPlayerCallback(int npcIndex) {
if (!impl) return;
auto it = impl->npcBumpedByPlayerCallbacks.find(npcIndex);
if (it == impl->npcBumpedByPlayerCallbacks.end()) return;
auto result = it->second();
if (!result.valid()) {
sol::error err = result;
std::cerr << "[SCRIPT] on_player_bumps_npc error for NPC " << npcIndex << ": " << err.what() << "\n";
}
}
void ScriptEngine::callNpcBumpsPlayerCallback(int npcIndex) {
if (!impl) return;
auto it = impl->npcBumpsPlayerCallbacks.find(npcIndex);
if (it == impl->npcBumpsPlayerCallbacks.end()) return;
auto result = it->second();
if (!result.valid()) {
sol::error err = result;
std::cerr << "[SCRIPT] on_npc_bumps_player error for NPC " << npcIndex << ": " << err.what() << "\n";
}
}
} // namespace ZL
+5
View File
@@ -28,6 +28,11 @@ public:
void callTriggerEnterCallback(const std::string& zoneId);
void callTriggerExitCallback(const std::string& zoneId);
// Fired when the player (moving) physically bumps into a standing NPC.
void callNpcBumpedByPlayerCallback(int npcIndex);
// Fired when a moving NPC physically bumps into the standing player.
void callNpcBumpsPlayerCallback(int npcIndex);
void setGlobalStore(std::unordered_map<std::string, int>* store);
void setGlobalFloatStore(std::unordered_map<std::string, float>* store);
+2 -1
View File
@@ -86,11 +86,12 @@ namespace ZL {
static std::vector<NpcData> loadNpcsFromJson(const std::string& jsonPath, const std::string& zipPath = "");
static LoadedGameObject buildLoadedObject(const GameObjectData& data, Renderer& renderer, const std::string& zipPath);
private:
static nlohmann::json loadJson(const std::string& jsonPath, const std::string& zipPath);
static std::vector<GameObjectData> loadFromJson(const std::string& jsonPath, const std::string& zipPath);
static std::vector<InteractiveObjectData> loadInteractiveFromJson(const std::string& jsonPath, const std::string& zipPath);
static LoadedGameObject buildLoadedObject(const GameObjectData& data, Renderer& renderer, const std::string& zipPath);
};
} // namespace ZL