shadow-over-bishkek001/src/ScriptEngine.cpp
2026-05-14 21:28:41 +03:00

394 lines
17 KiB
C++

#include "ScriptEngine.h"
#include "Game.h"
#include <iostream>
#include <stdexcept>
#include <unordered_map>
#include "Location.h"
#include "items/ItemRegistry.h"
#define SOL_ALL_SAFETIES_ON 1
#include <sol/sol.hpp>
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;
std::unordered_map<std::string, sol::protected_function> cutsceneCompleteCallbacks;
};
ScriptEngine::ScriptEngine() = default;
ScriptEngine::~ScriptEngine() = default;
void ScriptEngine::init(Location* game, Inventory* inventory, const std::string& scriptPath) {
impl = std::make_unique<Impl>();
sol::state& lua = impl->lua;
lua.open_libraries(sol::lib::base, sol::lib::math, sol::lib::string, sol::lib::table);
auto api = lua.create_named_table("game_api");
// npc_walk_to(index, x, y, z [, on_arrived])
// 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.
api.set_function("npc_walk_to",
[game](int index, float x, float y, float z, sol::object on_arrived) {
auto& npcs = game->npcs;
if (index < 0 || index >= static_cast<int>(npcs.size())) {
std::cerr << "[script] npc_walk_to: index " << index
<< " out of range (0.." << npcs.size() - 1 << ")\n";
return;
}
std::function<void()> cb;
if (on_arrived.is<sol::protected_function>()) {
sol::protected_function fn = on_arrived.as<sol::protected_function>();
cb = [fn]() mutable {
auto result = fn();
if (!result.valid()) {
sol::error err = result;
std::cerr << "[script] on_arrived error: " << err.what() << "\n";
}
};
}
npcs[index]->setTarget(Eigen::Vector3f(x, y, z), std::move(cb));
});
// pickup_item(item_id)
api.set_function("pickup_item", [inventory](const std::string& itemId) {
const Item* item = ItemRegistry::instance().findById(itemId);
if (item) {
inventory->addItem(*item);
std::cout << "[script] pickup_item: " << item->name << std::endl;
} else {
std::cerr << "[script] pickup_item: item '" << itemId << "' not found in ItemRegistry\n";
}
});
// remove_item(item_id)
api.set_function("remove_item", [game, inventory](const std::string& id) {
std::cout << "[script] remove_item: " << id << std::endl;
inventory->removeItem(id);
});
// deactivate_interactive_object(object_name)
api.set_function("deactivate_interactive_object", [game](const std::string& objectName) {
for (auto& intObj : game->interactiveObjects) {
if (intObj.loadedObject.name == objectName) {
intObj.isActive = false;
std::cout << "[script] deactivate_interactive_object: " << objectName << std::endl;
return;
}
}
std::cerr << "[script] deactivate_interactive_object: not found: " << objectName << std::endl;
});
// activate_interactive_object(object_name)
api.set_function("activate_interactive_object", [game](const std::string& objectName) {
for (auto& intObj : game->interactiveObjects) {
if (intObj.loadedObject.name == objectName) {
intObj.isActive = true;
std::cout << "[script] activate_interactive_object: " << objectName << std::endl;
return;
}
}
std::cerr << "[script] activate_interactive_object: not found: " << objectName << std::endl;
});
// get_inventory_count()
api.set_function("get_inventory_count", [inventory]() {
return inventory->getCount();
});
// has_item(item_id)
api.set_function("has_item", [inventory](const std::string& id) {
return inventory->hasItem(id);
});
api.set_function("start_dialogue",
[game](const std::string& dialogueId) {
if (!game->requestDialogueStart(dialogueId)) {
std::cerr << "[script] start_dialogue failed for id: " << dialogueId << "\n";
}
});
// start_cutscene(cutscene_id)
api.set_function("start_cutscene",
[game](const std::string& cutsceneId) {
if (!game->requestCutsceneStart(cutsceneId))
std::cerr << "[script] start_cutscene failed for id: " << cutsceneId << "\n";
});
// set_cutscene_callback(cutscene_id, on_complete)
// on_complete() is called with no arguments when the named cutscene finishes.
api.set_function("set_cutscene_callback",
[this_impl = impl.get()](const std::string& cutsceneId, sol::object onComplete) {
if (onComplete.is<sol::protected_function>())
this_impl->cutsceneCompleteCallbacks[cutsceneId] = onComplete.as<sol::protected_function>();
});
api.set_function("set_dialogue_flag",
[game](const std::string& flag, int value) {
game->setDialogueFlag(flag, value);
});
api.set_function("get_dialogue_flag",
[game](const std::string& flag) {
return game->getDialogueFlag(flag);
});
api.set_function("set_navigation_area_available",
[game](const std::string& areaName, bool available) {
if (!game->setNavigationAreaAvailable(areaName, available)) {
std::cerr << "[script] set_navigation_area_available: area not found: "
<< areaName << "\n";
}
});
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>();
});
api.set_function("set_trigger_zone_enabled",
[game](int index, bool value) {
auto& triggerZones = game->triggerZones;
if (index < 0 || index >= static_cast<int>(triggerZones.size())) {
std::cerr << "[script] set_trigger_zone_enabled: index " << index << " out of range\n";
return;
}
triggerZones[index].enabled = value;
});
// set_npc_enabled(index, enabled)
api.set_function("set_npc_enabled",
[game](int index, bool value) {
auto& npcs = game->npcs;
if (index < 0 || index >= static_cast<int>(npcs.size())) {
std::cerr << "[script] set_npc_enabled: index " << index << " out of range\n";
return;
}
npcs[index]->enabled = value;
});
// move_object(name, x, y, z, duration_sec [, on_complete])
api.set_function("move_object",
[game](const std::string& name, float x, float y, float z,
float durationSec, sol::object onComplete) {
for (auto& intObj : game->interactiveObjects) {
if (intObj.loadedObject.name != name) continue;
if (intObj.isAnimating) {
std::cerr << "[script] move_object: '" << name << "' is already animating\n";
return;
}
std::function<void()> cb;
if (onComplete.is<sol::protected_function>()) {
sol::protected_function fn = onComplete.as<sol::protected_function>();
cb = [fn]() mutable {
auto res = fn();
if (!res.valid()) {
sol::error err = res;
std::cerr << "[script] move_object on_complete error: " << err.what() << "\n";
}
};
}
intObj.moveTo(Eigen::Vector3f(x, y, z), durationSec, std::move(cb));
return;
}
std::cerr << "[script] move_object: object '" << name << "' not found\n";
});
// rotate_object(name, angle_deg, duration_sec [, on_complete])
api.set_function("rotate_object",
[game](const std::string& name, float angleDeg,
float durationSec, sol::object onComplete) {
for (auto& intObj : game->interactiveObjects) {
if (intObj.loadedObject.name != name) continue;
if (intObj.isAnimating) {
std::cerr << "[script] rotate_object: '" << name << "' is already animating\n";
return;
}
std::function<void()> cb;
if (onComplete.is<sol::protected_function>()) {
sol::protected_function fn = onComplete.as<sol::protected_function>();
cb = [fn]() mutable {
auto res = fn();
if (!res.valid()) {
sol::error err = res;
std::cerr << "[script] rotate_object on_complete error: " << err.what() << "\n";
}
};
}
const float angleRad = angleDeg * static_cast<float>(M_PI) / 180.f;
intObj.rotateTo(intObj.rotationY + angleRad, durationSec, std::move(cb));
return;
}
std::cerr << "[script] rotate_object: object '" << name << "' not found\n";
});
// scale_object(name, target_scale, duration_sec [, on_complete])
api.set_function("scale_object",
[game](const std::string& name, float targetScale,
float durationSec, sol::object onComplete) {
for (auto& intObj : game->interactiveObjects) {
if (intObj.loadedObject.name != name) continue;
if (intObj.isAnimating) {
std::cerr << "[script] scale_object: '" << name << "' is already animating\n";
return;
}
std::function<void()> cb;
if (onComplete.is<sol::protected_function>()) {
sol::protected_function fn = onComplete.as<sol::protected_function>();
cb = [fn]() mutable {
auto res = fn();
if (!res.valid()) {
sol::error err = res;
std::cerr << "[script] scale_object on_complete error: " << err.what() << "\n";
}
};
}
intObj.scaleTo(targetScale, durationSec, std::move(cb));
return;
}
std::cerr << "[script] scale_object: object '" << name << "' not found\n";
});
lua.script_file(scriptPath);
}
void ScriptEngine::callNpcInteractCallback(int npcIndex) {
if (!impl) {
std::cerr << "[SCRIPT] Engine not initialized!" << std::endl;
return;
}
sol::state& lua = impl->lua;
sol::function fn = lua["on_npc_interact"];
if (fn.valid()) {
auto result = fn(npcIndex);
if (!result.valid()) {
sol::error err = result;
std::cerr << "[SCRIPT] on_npc_interact error: " << err.what() << "\n";
}
else {
std::cout << "[SCRIPT] on_npc_interact called with index " << npcIndex << std::endl;
}
}
else {
std::cerr << "[SCRIPT] Lua function 'on_npc_interact' not found!" << std::endl;
}
}
void ScriptEngine::runScript(const std::string& path) {
auto result = impl->lua.safe_script_file(path, sol::script_pass_on_error);
if (!result.valid()) {
sol::error err = result;
std::cerr << "[script] Error in " << path << ": " << err.what() << "\n";
}
}
void ScriptEngine::callActivateFunction(const std::string& functionName) {
if (!impl) {
throw std::runtime_error("[SCRIPT] Engine not initialized!");
}
if (functionName.empty()) {
throw std::runtime_error("[SCRIPT] Activate function name is empty!");
}
sol::state& lua = impl->lua;
std::cout << "[SCRIPT] Looking for activate function: " << functionName << std::endl;
sol::function activateFunc = lua[functionName];
if (!activateFunc.valid()) {
throw std::runtime_error("[SCRIPT] Lua function not found: " + functionName);
}
std::cout << "[SCRIPT] Found function! Calling: " << functionName << std::endl;
auto result = activateFunc();
if (!result.valid()) {
sol::error err = result;
throw std::runtime_error("[SCRIPT] Error executing " + functionName + ": " + std::string(err.what()));
}
std::cout << "[SCRIPT] Function executed successfully!" << std::endl;
}
void ScriptEngine::callItemPickupCallback(const std::string& objectName) {
if (!impl) {
std::cerr << "[SCRIPT] impl is null!" << std::endl;
return;
}
sol::state& lua = impl->lua;
// Try to find custom activate function first
sol::function activateFunc = lua["on_item_pickup"];
if (activateFunc.valid()) {
std::cout << "[SCRIPT] Callback found! Calling with argument: " << objectName << std::endl;
auto result = activateFunc(objectName);
if (!result.valid()) {
sol::error err = result;
std::cerr << "[SCRIPT] on_item_pickup callback error: " << err.what() << "\n";
}
else {
std::cout << "[SCRIPT] Callback executed successfully!" << std::endl;
}
}
else {
std::cout << "[SCRIPT] Fallback: on_item_pickup not found" << std::endl;
}
}
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";
}
}
void ScriptEngine::callCutsceneCompleteCallback(const std::string& cutsceneId) {
if (!impl) return;
auto it = impl->cutsceneCompleteCallbacks.find(cutsceneId);
if (it == impl->cutsceneCompleteCallbacks.end()) return;
auto result = it->second();
if (!result.valid()) {
sol::error err = result;
std::cerr << "[SCRIPT] cutscene complete callback error for '"
<< cutsceneId << "': " << err.what() << "\n";
}
}
} // namespace ZL