Working on stuff
This commit is contained in:
parent
17d903fd52
commit
07f45ad1e1
@ -109,7 +109,7 @@ std::unique_ptr<Character> Character::createFromState(CharacterState st, Rendere
|
|||||||
return c;
|
return c;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Character::setTarget(const Eigen::Vector3f& target, std::function<void()> onArrived) {
|
void Character::setTarget(const Eigen::Vector3f& target, std::function<void()> onArrived, std::string callbackName) {
|
||||||
Eigen::Vector3f normalizedTarget(target.x(), 0.f, target.z());
|
Eigen::Vector3f normalizedTarget(target.x(), 0.f, target.z());
|
||||||
|
|
||||||
const bool sameRequestedTarget =
|
const bool sameRequestedTarget =
|
||||||
@ -123,8 +123,9 @@ void Character::setTarget(const Eigen::Vector3f& target, std::function<void()> o
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
state.requestedWalkTarget = normalizedTarget;
|
state.requestedWalkTarget = normalizedTarget;
|
||||||
state.onArrivedCallback = std::move(onArrived);
|
state.onArrivedCallback = std::move(onArrived);
|
||||||
|
state.onArrivedCallbackName = std::move(callbackName);
|
||||||
|
|
||||||
if (pathPlanner) {
|
if (pathPlanner) {
|
||||||
state.pathWaypoints = pathPlanner(state.position, normalizedTarget);
|
state.pathWaypoints = pathPlanner(state.position, normalizedTarget);
|
||||||
@ -140,6 +141,7 @@ void Character::setTarget(const Eigen::Vector3f& target, std::function<void()> o
|
|||||||
|
|
||||||
state.walkTarget = Eigen::Vector3f(state.position.x(), 0.f, state.position.z());
|
state.walkTarget = Eigen::Vector3f(state.position.x(), 0.f, state.position.z());
|
||||||
state.onArrivedCallback = nullptr;
|
state.onArrivedCallback = nullptr;
|
||||||
|
state.onArrivedCallbackName.clear();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -178,6 +180,7 @@ void Character::forceReplan() {
|
|||||||
|
|
||||||
state.walkTarget = Eigen::Vector3f(state.position.x(), 0.f, state.position.z());
|
state.walkTarget = Eigen::Vector3f(state.position.x(), 0.f, state.position.z());
|
||||||
state.onArrivedCallback = nullptr;
|
state.onArrivedCallback = nullptr;
|
||||||
|
state.onArrivedCallbackName.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Character::setTexture(std::shared_ptr<ZL::Texture> texture) {
|
void Character::setTexture(std::shared_ptr<ZL::Texture> texture) {
|
||||||
@ -294,6 +297,7 @@ void Character::update(int64_t deltaMs, const CharacterResolver& resolver) {
|
|||||||
if (!hasNextWaypoint && state.onArrivedCallback) {
|
if (!hasNextWaypoint && state.onArrivedCallback) {
|
||||||
auto cb = std::move(state.onArrivedCallback);
|
auto cb = std::move(state.onArrivedCallback);
|
||||||
state.onArrivedCallback = nullptr;
|
state.onArrivedCallback = nullptr;
|
||||||
|
state.onArrivedCallbackName.clear();
|
||||||
cb();
|
cb();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -52,7 +52,9 @@ public:
|
|||||||
void setPathPlanner(PathPlanner planner);
|
void setPathPlanner(PathPlanner planner);
|
||||||
|
|
||||||
// ---- Navigation (path data lives in state; pathPlanner lives here) ----
|
// ---- Navigation (path data lives in state; pathPlanner lives here) ----
|
||||||
void setTarget(const Eigen::Vector3f& target, std::function<void()> onArrived = nullptr);
|
// callbackName, if given, is the global Lua function name backing onArrived —
|
||||||
|
// stored in state.onArrivedCallbackName so it survives save/load (see ScriptEngine::rehydrateNamedCallback).
|
||||||
|
void setTarget(const Eigen::Vector3f& target, std::function<void()> onArrived = nullptr, std::string callbackName = "");
|
||||||
void forceReplan();
|
void forceReplan();
|
||||||
void stopInPlace() { state.stopInPlace(); }
|
void stopInPlace() { state.stopInPlace(); }
|
||||||
|
|
||||||
|
|||||||
@ -21,6 +21,7 @@ void CharacterState::stopInPlace() {
|
|||||||
pathWaypoints.clear();
|
pathWaypoints.clear();
|
||||||
currentWaypointIndex = 0;
|
currentWaypointIndex = 0;
|
||||||
onArrivedCallback = nullptr;
|
onArrivedCallback = nullptr;
|
||||||
|
onArrivedCallbackName.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
void CharacterState::setHp(float newHp) {
|
void CharacterState::setHp(float newHp) {
|
||||||
@ -52,6 +53,8 @@ void CharacterState::save(nlohmann::json& out) const
|
|||||||
out["pathWaypoints"] = std::move(wpArr);
|
out["pathWaypoints"] = std::move(wpArr);
|
||||||
out["currentWaypointIndex"] = currentWaypointIndex;
|
out["currentWaypointIndex"] = currentWaypointIndex;
|
||||||
out["homeDriftCheckTimer"] = homeDriftCheckTimer;
|
out["homeDriftCheckTimer"] = homeDriftCheckTimer;
|
||||||
|
if (!onArrivedCallbackName.empty())
|
||||||
|
out["onArrivedCallbackName"] = onArrivedCallbackName;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CharacterState::load(const nlohmann::json& in)
|
void CharacterState::load(const nlohmann::json& in)
|
||||||
@ -84,6 +87,7 @@ void CharacterState::load(const nlohmann::json& in)
|
|||||||
}
|
}
|
||||||
currentWaypointIndex = in.value("currentWaypointIndex", currentWaypointIndex);
|
currentWaypointIndex = in.value("currentWaypointIndex", currentWaypointIndex);
|
||||||
homeDriftCheckTimer = in.value("homeDriftCheckTimer", homeDriftCheckTimer);
|
homeDriftCheckTimer = in.value("homeDriftCheckTimer", homeDriftCheckTimer);
|
||||||
|
onArrivedCallbackName = in.value("onArrivedCallbackName", std::string());
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace ZL
|
} // namespace ZL
|
||||||
|
|||||||
@ -116,6 +116,11 @@ public:
|
|||||||
std::function<void()> onDeathAnimComplete;
|
std::function<void()> onDeathAnimComplete;
|
||||||
std::function<void(float, float)> onHpChanged;
|
std::function<void(float, float)> onHpChanged;
|
||||||
|
|
||||||
|
// Global Lua function name backing onArrivedCallback, if it was sourced from a
|
||||||
|
// script (see ScriptEngine::rehydrateNamedCallback). Serialized so the callback
|
||||||
|
// can be re-looked-up by name after a save/load; empty if none or non-Lua.
|
||||||
|
std::string onArrivedCallbackName;
|
||||||
|
|
||||||
// --- Asset / creation info (used to reload visuals from a saved state) ---
|
// --- Asset / creation info (used to reload visuals from a saved state) ---
|
||||||
CharacterCreationInfo creationInfo;
|
CharacterCreationInfo creationInfo;
|
||||||
|
|
||||||
|
|||||||
@ -20,6 +20,7 @@
|
|||||||
#include <emscripten.h>
|
#include <emscripten.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
#include "GameConstants.h"
|
#include "GameConstants.h"
|
||||||
|
|
||||||
namespace ZL
|
namespace ZL
|
||||||
|
|||||||
@ -1995,6 +1995,24 @@ namespace ZL
|
|||||||
if (in.contains("scriptGlobals")) {
|
if (in.contains("scriptGlobals")) {
|
||||||
scriptEngine.loadScriptGlobals(in["scriptGlobals"]);
|
scriptEngine.loadScriptGlobals(in["scriptGlobals"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Re-wire in-flight, one-shot Lua callbacks (walk-arrival / animation-complete)
|
||||||
|
// that were serialized by name. Must run after scriptEngine has already run the
|
||||||
|
// location's script (guaranteed: Location::setup() always runs before load())
|
||||||
|
// and after the states above are restored.
|
||||||
|
if (player && !player->state.onArrivedCallbackName.empty()) {
|
||||||
|
scriptEngine.rehydrateNamedCallback(player->state.onArrivedCallbackName, player->state.onArrivedCallback);
|
||||||
|
}
|
||||||
|
for (auto& npc : npcs) {
|
||||||
|
if (npc && !npc->state.onArrivedCallbackName.empty()) {
|
||||||
|
scriptEngine.rehydrateNamedCallback(npc->state.onArrivedCallbackName, npc->state.onArrivedCallback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (auto& intObj : interactiveObjects) {
|
||||||
|
if (intObj.state.isAnimating && intObj.state.animTask && !intObj.state.animTask->onCompleteName.empty()) {
|
||||||
|
scriptEngine.rehydrateNamedCallback(intObj.state.animTask->onCompleteName, intObj.state.animTask->onComplete);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace ZL
|
} // namespace ZL
|
||||||
|
|||||||
@ -11,6 +11,54 @@
|
|||||||
|
|
||||||
namespace ZL {
|
namespace ZL {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
// Wraps a sol::protected_function into a std::function<void()>, invoking it and
|
||||||
|
// logging any Lua-side error to stderr. Shared by every binding that stores a
|
||||||
|
// callback for later, one-shot invocation (npc_walk_to, move_object, etc.) and by
|
||||||
|
// rehydrateNamedCallback, which re-wraps a callback looked up by name after load.
|
||||||
|
std::function<void()> wrapLuaCallback(sol::protected_function fn) {
|
||||||
|
return [fn]() mutable {
|
||||||
|
auto result = fn();
|
||||||
|
if (!result.valid()) {
|
||||||
|
sol::error err = result;
|
||||||
|
std::cerr << "[script] callback error: " << err.what() << "\n";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reverse-looks-up the global name a Lua function is registered under, by
|
||||||
|
// comparing raw function pointers via lua_topointer. Returns "" if fn isn't
|
||||||
|
// (or is no longer) reachable as a plain global — e.g. an anonymous function,
|
||||||
|
// or one stored only in a local/upvalue. This lets callback-accepting bindings
|
||||||
|
// remember a callback "by name" for save/load without changing the Lua-facing
|
||||||
|
// API (scripts still just pass a function reference).
|
||||||
|
std::string findGlobalFunctionName(lua_State* L, const sol::protected_function& fn) {
|
||||||
|
if (!fn.valid()) return {};
|
||||||
|
|
||||||
|
fn.push(L);
|
||||||
|
const void* target = lua_topointer(L, -1);
|
||||||
|
lua_pop(L, 1);
|
||||||
|
if (!target) return {};
|
||||||
|
|
||||||
|
std::string found;
|
||||||
|
lua_pushglobaltable(L); // stack: [_G]
|
||||||
|
lua_pushnil(L); // stack: [_G, nil]
|
||||||
|
while (lua_next(L, -2) != 0) { // stack: [_G, key, value]
|
||||||
|
if (lua_type(L, -2) == LUA_TSTRING && lua_isfunction(L, -1) &&
|
||||||
|
lua_topointer(L, -1) == target) {
|
||||||
|
found = lua_tostring(L, -2);
|
||||||
|
lua_pop(L, 2); // pop value, key
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
lua_pop(L, 1); // pop value, keep key for next iteration
|
||||||
|
}
|
||||||
|
lua_pop(L, 1); // pop _G
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // anonymous namespace
|
||||||
|
|
||||||
struct ScriptEngine::Impl {
|
struct ScriptEngine::Impl {
|
||||||
sol::state lua;
|
sol::state lua;
|
||||||
std::unordered_map<std::string, sol::protected_function> triggerEnterCallbacks;
|
std::unordered_map<std::string, sol::protected_function> triggerEnterCallbacks;
|
||||||
@ -45,7 +93,7 @@ namespace ZL {
|
|||||||
// on_arrived is an optional Lua function called when the NPC reaches the target.
|
// on_arrived is an optional Lua function called when the NPC reaches the target.
|
||||||
// It can call npc_walk_to again (or anything else) to chain behaviour.
|
// It can call npc_walk_to again (or anything else) to chain behaviour.
|
||||||
api.set_function("npc_walk_to",
|
api.set_function("npc_walk_to",
|
||||||
[loc](int index, float x, float y, float z, sol::object on_arrived) {
|
[loc, this_impl = impl.get()](int index, float x, float y, float z, sol::object on_arrived) {
|
||||||
auto& npcs = loc->npcs;
|
auto& npcs = loc->npcs;
|
||||||
if (index < 0 || index >= static_cast<int>(npcs.size())) {
|
if (index < 0 || index >= static_cast<int>(npcs.size())) {
|
||||||
std::cerr << "[script] npc_walk_to: index " << index
|
std::cerr << "[script] npc_walk_to: index " << index
|
||||||
@ -53,18 +101,16 @@ namespace ZL {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
std::function<void()> cb;
|
std::function<void()> cb;
|
||||||
|
std::string cbName;
|
||||||
if (on_arrived.is<sol::protected_function>()) {
|
if (on_arrived.is<sol::protected_function>()) {
|
||||||
sol::protected_function fn = on_arrived.as<sol::protected_function>();
|
sol::protected_function fn = on_arrived.as<sol::protected_function>();
|
||||||
cb = [fn]() mutable {
|
cb = wrapLuaCallback(fn);
|
||||||
auto result = fn();
|
cbName = findGlobalFunctionName(this_impl->lua.lua_state(), fn);
|
||||||
if (!result.valid()) {
|
if (cbName.empty())
|
||||||
sol::error err = result;
|
std::cerr << "[script] npc_walk_to: on_arrived callback has no global name, will not survive save/load\n";
|
||||||
std::cerr << "[script] on_arrived error: " << err.what() << "\n";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
npcs[index]->state.homePosition = Eigen::Vector3f(x, 0.f, z);
|
npcs[index]->state.homePosition = Eigen::Vector3f(x, 0.f, z);
|
||||||
npcs[index]->setTarget(Eigen::Vector3f(x, y, z), std::move(cb));
|
npcs[index]->setTarget(Eigen::Vector3f(x, y, z), std::move(cb), std::move(cbName));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@ -83,21 +129,19 @@ namespace ZL {
|
|||||||
});
|
});
|
||||||
|
|
||||||
api.set_function("player_walk_to",
|
api.set_function("player_walk_to",
|
||||||
[loc](float x, float y, float z, sol::object on_arrived) {
|
[loc, this_impl = impl.get()](float x, float y, float z, sol::object on_arrived) {
|
||||||
|
|
||||||
std::function<void()> cb;
|
std::function<void()> cb;
|
||||||
|
std::string cbName;
|
||||||
if (on_arrived.is<sol::protected_function>()) {
|
if (on_arrived.is<sol::protected_function>()) {
|
||||||
sol::protected_function fn = on_arrived.as<sol::protected_function>();
|
sol::protected_function fn = on_arrived.as<sol::protected_function>();
|
||||||
cb = [fn]() mutable {
|
cb = wrapLuaCallback(fn);
|
||||||
auto result = fn();
|
cbName = findGlobalFunctionName(this_impl->lua.lua_state(), fn);
|
||||||
if (!result.valid()) {
|
if (cbName.empty())
|
||||||
sol::error err = result;
|
std::cerr << "[script] player_walk_to: on_arrived callback has no global name, will not survive save/load\n";
|
||||||
std::cerr << "[script] on_arrived error: " << err.what() << "\n";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
loc->player->state.homePosition = Eigen::Vector3f(x, 0.f, z);
|
loc->player->state.homePosition = Eigen::Vector3f(x, 0.f, z);
|
||||||
loc->player->setTarget(Eigen::Vector3f(x, y, z), std::move(cb));
|
loc->player->setTarget(Eigen::Vector3f(x, y, z), std::move(cb), std::move(cbName));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@ -556,7 +600,7 @@ namespace ZL {
|
|||||||
|
|
||||||
// move_object(name, x, y, z, duration_sec [, on_complete])
|
// move_object(name, x, y, z, duration_sec [, on_complete])
|
||||||
api.set_function("move_object",
|
api.set_function("move_object",
|
||||||
[loc](const std::string& name, float x, float y, float z,
|
[loc, this_impl = impl.get()](const std::string& name, float x, float y, float z,
|
||||||
float durationSec, sol::object onComplete) {
|
float durationSec, sol::object onComplete) {
|
||||||
for (auto& intObj : loc->interactiveObjects) {
|
for (auto& intObj : loc->interactiveObjects) {
|
||||||
if (intObj.loadedObject.name != name) continue;
|
if (intObj.loadedObject.name != name) continue;
|
||||||
@ -565,17 +609,15 @@ namespace ZL {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
std::function<void()> cb;
|
std::function<void()> cb;
|
||||||
|
std::string cbName;
|
||||||
if (onComplete.is<sol::protected_function>()) {
|
if (onComplete.is<sol::protected_function>()) {
|
||||||
sol::protected_function fn = onComplete.as<sol::protected_function>();
|
sol::protected_function fn = onComplete.as<sol::protected_function>();
|
||||||
cb = [fn]() mutable {
|
cb = wrapLuaCallback(fn);
|
||||||
auto res = fn();
|
cbName = findGlobalFunctionName(this_impl->lua.lua_state(), fn);
|
||||||
if (!res.valid()) {
|
if (cbName.empty())
|
||||||
sol::error err = res;
|
std::cerr << "[script] move_object: on_complete callback has no global name, will not survive save/load\n";
|
||||||
std::cerr << "[script] move_object on_complete error: " << err.what() << "\n";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
intObj.moveTo(Eigen::Vector3f(x, y, z), durationSec, std::move(cb));
|
intObj.moveTo(Eigen::Vector3f(x, y, z), durationSec, std::move(cb), std::move(cbName));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
std::cerr << "[script] move_object: object '" << name << "' not found\n";
|
std::cerr << "[script] move_object: object '" << name << "' not found\n";
|
||||||
@ -583,7 +625,7 @@ namespace ZL {
|
|||||||
|
|
||||||
// rotate_object(name, angle_deg, duration_sec [, on_complete])
|
// rotate_object(name, angle_deg, duration_sec [, on_complete])
|
||||||
api.set_function("rotate_object",
|
api.set_function("rotate_object",
|
||||||
[loc](const std::string& name, float angleDeg,
|
[loc, this_impl = impl.get()](const std::string& name, float angleDeg,
|
||||||
float durationSec, sol::object onComplete) {
|
float durationSec, sol::object onComplete) {
|
||||||
for (auto& intObj : loc->interactiveObjects) {
|
for (auto& intObj : loc->interactiveObjects) {
|
||||||
if (intObj.loadedObject.name != name) continue;
|
if (intObj.loadedObject.name != name) continue;
|
||||||
@ -592,18 +634,16 @@ namespace ZL {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
std::function<void()> cb;
|
std::function<void()> cb;
|
||||||
|
std::string cbName;
|
||||||
if (onComplete.is<sol::protected_function>()) {
|
if (onComplete.is<sol::protected_function>()) {
|
||||||
sol::protected_function fn = onComplete.as<sol::protected_function>();
|
sol::protected_function fn = onComplete.as<sol::protected_function>();
|
||||||
cb = [fn]() mutable {
|
cb = wrapLuaCallback(fn);
|
||||||
auto res = fn();
|
cbName = findGlobalFunctionName(this_impl->lua.lua_state(), fn);
|
||||||
if (!res.valid()) {
|
if (cbName.empty())
|
||||||
sol::error err = res;
|
std::cerr << "[script] rotate_object: on_complete callback has no global name, will not survive save/load\n";
|
||||||
std::cerr << "[script] rotate_object on_complete error: " << err.what() << "\n";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
const float angleRad = angleDeg * static_cast<float>(M_PI) / 180.f;
|
const float angleRad = angleDeg * static_cast<float>(M_PI) / 180.f;
|
||||||
intObj.rotateTo(intObj.state.rotationY + angleRad, durationSec, std::move(cb));
|
intObj.rotateTo(intObj.state.rotationY + angleRad, durationSec, std::move(cb), std::move(cbName));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
std::cerr << "[script] rotate_object: object '" << name << "' not found\n";
|
std::cerr << "[script] rotate_object: object '" << name << "' not found\n";
|
||||||
@ -611,7 +651,7 @@ namespace ZL {
|
|||||||
|
|
||||||
// fade_object(name, target_alpha, duration_sec [, on_complete])
|
// fade_object(name, target_alpha, duration_sec [, on_complete])
|
||||||
api.set_function("fade_object",
|
api.set_function("fade_object",
|
||||||
[loc](const std::string& name, float targetAlpha,
|
[loc, this_impl = impl.get()](const std::string& name, float targetAlpha,
|
||||||
float durationSec, sol::object onComplete) {
|
float durationSec, sol::object onComplete) {
|
||||||
for (auto& intObj : loc->interactiveObjects) {
|
for (auto& intObj : loc->interactiveObjects) {
|
||||||
if (intObj.loadedObject.name != name) continue;
|
if (intObj.loadedObject.name != name) continue;
|
||||||
@ -620,17 +660,15 @@ namespace ZL {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
std::function<void()> cb;
|
std::function<void()> cb;
|
||||||
|
std::string cbName;
|
||||||
if (onComplete.is<sol::protected_function>()) {
|
if (onComplete.is<sol::protected_function>()) {
|
||||||
sol::protected_function fn = onComplete.as<sol::protected_function>();
|
sol::protected_function fn = onComplete.as<sol::protected_function>();
|
||||||
cb = [fn]() mutable {
|
cb = wrapLuaCallback(fn);
|
||||||
auto res = fn();
|
cbName = findGlobalFunctionName(this_impl->lua.lua_state(), fn);
|
||||||
if (!res.valid()) {
|
if (cbName.empty())
|
||||||
sol::error err = res;
|
std::cerr << "[script] fade_object: on_complete callback has no global name, will not survive save/load\n";
|
||||||
std::cerr << "[script] fade_object on_complete error: " << err.what() << "\n";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
intObj.fadeTo(targetAlpha, durationSec, std::move(cb));
|
intObj.fadeTo(targetAlpha, durationSec, std::move(cb), std::move(cbName));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
std::cerr << "[script] fade_object: object '" << name << "' not found\n";
|
std::cerr << "[script] fade_object: object '" << name << "' not found\n";
|
||||||
@ -638,7 +676,7 @@ namespace ZL {
|
|||||||
|
|
||||||
// scale_object(name, target_scale, duration_sec [, on_complete])
|
// scale_object(name, target_scale, duration_sec [, on_complete])
|
||||||
api.set_function("scale_object",
|
api.set_function("scale_object",
|
||||||
[loc](const std::string& name, float targetScale,
|
[loc, this_impl = impl.get()](const std::string& name, float targetScale,
|
||||||
float durationSec, sol::object onComplete) {
|
float durationSec, sol::object onComplete) {
|
||||||
for (auto& intObj : loc->interactiveObjects) {
|
for (auto& intObj : loc->interactiveObjects) {
|
||||||
if (intObj.loadedObject.name != name) continue;
|
if (intObj.loadedObject.name != name) continue;
|
||||||
@ -647,17 +685,15 @@ namespace ZL {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
std::function<void()> cb;
|
std::function<void()> cb;
|
||||||
|
std::string cbName;
|
||||||
if (onComplete.is<sol::protected_function>()) {
|
if (onComplete.is<sol::protected_function>()) {
|
||||||
sol::protected_function fn = onComplete.as<sol::protected_function>();
|
sol::protected_function fn = onComplete.as<sol::protected_function>();
|
||||||
cb = [fn]() mutable {
|
cb = wrapLuaCallback(fn);
|
||||||
auto res = fn();
|
cbName = findGlobalFunctionName(this_impl->lua.lua_state(), fn);
|
||||||
if (!res.valid()) {
|
if (cbName.empty())
|
||||||
sol::error err = res;
|
std::cerr << "[script] scale_object: on_complete callback has no global name, will not survive save/load\n";
|
||||||
std::cerr << "[script] scale_object on_complete error: " << err.what() << "\n";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
intObj.scaleTo(targetScale, durationSec, std::move(cb));
|
intObj.scaleTo(targetScale, durationSec, std::move(cb), std::move(cbName));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
std::cerr << "[script] scale_object: object '" << name << "' not found\n";
|
std::cerr << "[script] scale_object: object '" << name << "' not found\n";
|
||||||
@ -1065,4 +1101,18 @@ namespace ZL {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ScriptEngine::rehydrateNamedCallback(const std::string& name, std::function<void()>& outCallback) const
|
||||||
|
{
|
||||||
|
outCallback = nullptr;
|
||||||
|
if (!impl || name.empty()) return;
|
||||||
|
|
||||||
|
sol::protected_function fn = impl->lua[name];
|
||||||
|
if (!fn.valid()) {
|
||||||
|
std::cerr << "[SCRIPT] rehydrateNamedCallback: global function '" << name
|
||||||
|
<< "' not found (script changed since save?)\n";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
outCallback = wrapLuaCallback(fn);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace ZL
|
} // namespace ZL
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
#include <functional>
|
||||||
#include "quest/QuestJournal.h"
|
#include "quest/QuestJournal.h"
|
||||||
#include "external/nlohmann/json.hpp"
|
#include "external/nlohmann/json.hpp"
|
||||||
#include "ISaveable.h"
|
#include "ISaveable.h"
|
||||||
@ -62,6 +63,14 @@ public:
|
|||||||
// has already run and registered all callbacks.
|
// has already run and registered all callbacks.
|
||||||
void loadScriptGlobals(const nlohmann::json& in);
|
void loadScriptGlobals(const nlohmann::json& in);
|
||||||
|
|
||||||
|
// Re-wires an in-flight, one-shot callback (NPC/player walk-arrival, object
|
||||||
|
// animation-complete) that was serialized by name rather than by value.
|
||||||
|
// Looks up `name` as a global Lua function and assigns a wrapped invoker to
|
||||||
|
// outCallback; leaves outCallback empty and logs an error if not found.
|
||||||
|
// Call after init() (the script must already have run) and after the owning
|
||||||
|
// state (CharacterState / InteractiveObjectState) has been loaded.
|
||||||
|
void rehydrateNamedCallback(const std::string& name, std::function<void()>& outCallback) const;
|
||||||
|
|
||||||
void save(nlohmann::json& out) const override { saveScriptGlobals(out); }
|
void save(nlohmann::json& out) const override { saveScriptGlobals(out); }
|
||||||
void load(const nlohmann::json& in) override { loadScriptGlobals(in); }
|
void load(const nlohmann::json& in) override { loadScriptGlobals(in); }
|
||||||
|
|
||||||
|
|||||||
@ -71,7 +71,7 @@ namespace ZL {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void InteractiveObject::moveTo(const Eigen::Vector3f& target, float durationSec, std::function<void()> onComplete) {
|
void InteractiveObject::moveTo(const Eigen::Vector3f& target, float durationSec, std::function<void()> onComplete, std::string callbackName) {
|
||||||
if (state.isAnimating) return;
|
if (state.isAnimating) return;
|
||||||
AnimTask task;
|
AnimTask task;
|
||||||
task.type = AnimTask::Type::Move;
|
task.type = AnimTask::Type::Move;
|
||||||
@ -83,12 +83,13 @@ namespace ZL {
|
|||||||
task.targetScale = state.scale;
|
task.targetScale = state.scale;
|
||||||
task.durationMs = durationSec * 1000.f;
|
task.durationMs = durationSec * 1000.f;
|
||||||
task.elapsedMs = 0.f;
|
task.elapsedMs = 0.f;
|
||||||
task.onComplete = std::move(onComplete);
|
task.onComplete = std::move(onComplete);
|
||||||
|
task.onCompleteName = std::move(callbackName);
|
||||||
state.animTask = std::move(task);
|
state.animTask = std::move(task);
|
||||||
state.isAnimating = true;
|
state.isAnimating = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void InteractiveObject::rotateTo(float targetRotY, float durationSec, std::function<void()> onComplete) {
|
void InteractiveObject::rotateTo(float targetRotY, float durationSec, std::function<void()> onComplete, std::string callbackName) {
|
||||||
if (state.isAnimating) return;
|
if (state.isAnimating) return;
|
||||||
AnimTask task;
|
AnimTask task;
|
||||||
task.type = AnimTask::Type::Rotate;
|
task.type = AnimTask::Type::Rotate;
|
||||||
@ -100,12 +101,13 @@ namespace ZL {
|
|||||||
task.targetScale = state.scale;
|
task.targetScale = state.scale;
|
||||||
task.durationMs = durationSec * 1000.f;
|
task.durationMs = durationSec * 1000.f;
|
||||||
task.elapsedMs = 0.f;
|
task.elapsedMs = 0.f;
|
||||||
task.onComplete = std::move(onComplete);
|
task.onComplete = std::move(onComplete);
|
||||||
|
task.onCompleteName = std::move(callbackName);
|
||||||
state.animTask = std::move(task);
|
state.animTask = std::move(task);
|
||||||
state.isAnimating = true;
|
state.isAnimating = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void InteractiveObject::scaleTo(float targetScale, float durationSec, std::function<void()> onComplete) {
|
void InteractiveObject::scaleTo(float targetScale, float durationSec, std::function<void()> onComplete, std::string callbackName) {
|
||||||
if (state.isAnimating) return;
|
if (state.isAnimating) return;
|
||||||
AnimTask task;
|
AnimTask task;
|
||||||
task.type = AnimTask::Type::Scale;
|
task.type = AnimTask::Type::Scale;
|
||||||
@ -117,12 +119,13 @@ namespace ZL {
|
|||||||
task.targetScale = targetScale;
|
task.targetScale = targetScale;
|
||||||
task.durationMs = durationSec * 1000.f;
|
task.durationMs = durationSec * 1000.f;
|
||||||
task.elapsedMs = 0.f;
|
task.elapsedMs = 0.f;
|
||||||
task.onComplete = std::move(onComplete);
|
task.onComplete = std::move(onComplete);
|
||||||
|
task.onCompleteName = std::move(callbackName);
|
||||||
state.animTask = std::move(task);
|
state.animTask = std::move(task);
|
||||||
state.isAnimating = true;
|
state.isAnimating = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void InteractiveObject::fadeTo(float targetAlpha, float durationSec, std::function<void()> onComplete) {
|
void InteractiveObject::fadeTo(float targetAlpha, float durationSec, std::function<void()> onComplete, std::string callbackName) {
|
||||||
if (state.isAnimating) return;
|
if (state.isAnimating) return;
|
||||||
AnimTask task;
|
AnimTask task;
|
||||||
task.type = AnimTask::Type::Fade;
|
task.type = AnimTask::Type::Fade;
|
||||||
@ -134,7 +137,8 @@ namespace ZL {
|
|||||||
task.targetScale = targetAlpha; // reuse targetScale to hold target alpha
|
task.targetScale = targetAlpha; // reuse targetScale to hold target alpha
|
||||||
task.durationMs = durationSec * 1000.f;
|
task.durationMs = durationSec * 1000.f;
|
||||||
task.elapsedMs = 0.f;
|
task.elapsedMs = 0.f;
|
||||||
task.onComplete = std::move(onComplete);
|
task.onComplete = std::move(onComplete);
|
||||||
|
task.onCompleteName = std::move(callbackName);
|
||||||
state.animTask = std::move(task);
|
state.animTask = std::move(task);
|
||||||
state.isAnimating = true;
|
state.isAnimating = true;
|
||||||
}
|
}
|
||||||
@ -256,6 +260,8 @@ namespace ZL {
|
|||||||
at["targetScale"] = animTask->targetScale;
|
at["targetScale"] = animTask->targetScale;
|
||||||
at["durationMs"] = animTask->durationMs;
|
at["durationMs"] = animTask->durationMs;
|
||||||
at["elapsedMs"] = animTask->elapsedMs;
|
at["elapsedMs"] = animTask->elapsedMs;
|
||||||
|
if (!animTask->onCompleteName.empty())
|
||||||
|
at["onCompleteName"] = animTask->onCompleteName;
|
||||||
out["animTask"] = std::move(at);
|
out["animTask"] = std::move(at);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -284,6 +290,7 @@ namespace ZL {
|
|||||||
task.targetScale = at.value("targetScale", 1.f);
|
task.targetScale = at.value("targetScale", 1.f);
|
||||||
task.durationMs = at.value("durationMs", 1000.f);
|
task.durationMs = at.value("durationMs", 1000.f);
|
||||||
task.elapsedMs = at.value("elapsedMs", 0.f);
|
task.elapsedMs = at.value("elapsedMs", 0.f);
|
||||||
|
task.onCompleteName = at.value("onCompleteName", std::string());
|
||||||
// onComplete is non-serializable; re-wired at runtime by Lua script callbacks.
|
// onComplete is non-serializable; re-wired at runtime by Lua script callbacks.
|
||||||
animTask = std::move(task);
|
animTask = std::move(task);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -31,10 +31,12 @@ namespace ZL {
|
|||||||
Renderer& renderer,
|
Renderer& renderer,
|
||||||
const std::string& zipPath = "");
|
const std::string& zipPath = "");
|
||||||
|
|
||||||
void moveTo(const Eigen::Vector3f& target, float durationSec, std::function<void()> onComplete = {});
|
// callbackName, if given, is the global Lua function name backing onComplete —
|
||||||
void rotateTo(float targetRotY, float durationSec, std::function<void()> onComplete = {});
|
// stored in the AnimTask so it survives save/load (see ScriptEngine::rehydrateNamedCallback).
|
||||||
void scaleTo(float targetScale, float durationSec, std::function<void()> onComplete = {});
|
void moveTo(const Eigen::Vector3f& target, float durationSec, std::function<void()> onComplete = {}, std::string callbackName = "");
|
||||||
void fadeTo(float targetAlpha, float durationSec, std::function<void()> onComplete = {});
|
void rotateTo(float targetRotY, float durationSec, std::function<void()> onComplete = {}, std::string callbackName = "");
|
||||||
|
void scaleTo(float targetScale, float durationSec, std::function<void()> onComplete = {}, std::string callbackName = "");
|
||||||
|
void fadeTo(float targetAlpha, float durationSec, std::function<void()> onComplete = {}, std::string callbackName = "");
|
||||||
void update(int64_t deltaMs);
|
void update(int64_t deltaMs);
|
||||||
void draw(Renderer& renderer) const;
|
void draw(Renderer& renderer) const;
|
||||||
void drawDarklands(Renderer& renderer) const;
|
void drawDarklands(Renderer& renderer) const;
|
||||||
|
|||||||
@ -22,6 +22,10 @@ struct AnimTask {
|
|||||||
float durationMs = 1000.f;
|
float durationMs = 1000.f;
|
||||||
float elapsedMs = 0.f;
|
float elapsedMs = 0.f;
|
||||||
std::function<void()> onComplete; // non-serializable; re-wired at runtime by Lua callbacks
|
std::function<void()> onComplete; // non-serializable; re-wired at runtime by Lua callbacks
|
||||||
|
// Global Lua function name backing onComplete, if it was sourced from a script
|
||||||
|
// (see ScriptEngine::rehydrateNamedCallback). Serialized so the callback can be
|
||||||
|
// re-looked-up by name after a save/load; empty if none or non-Lua.
|
||||||
|
std::string onCompleteName;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Paths and baked mesh transforms needed to recreate a LoadedGameObject from disk.
|
// Paths and baked mesh transforms needed to recreate a LoadedGameObject from disk.
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user