Working on location exterior
This commit is contained in:
1 parent
3a73f3fbde
commit
ffd5bd01a4
35 files changed
+72176
-124
No files matched your search
+11
-2
@@ -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:
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
|
||||
Reference in new issue
Block a user