From 02fa78764acc33c4162934d7cbdd5fd2f27527ee Mon Sep 17 00:00:00 2001 From: Sergey Lapin Date: Wed, 29 Apr 2026 14:13:50 +0300 Subject: [PATCH] Lua API implemented --- src/features/editScene/CMakeLists.txt | 11 +- src/features/editScene/EditorApp.cpp | 25 + src/features/editScene/EditorApp.hpp | 4 + .../editScene/lua/LuaComponentApi.cpp | 1931 +++++++++++++++++ .../editScene/lua/LuaComponentApi.hpp | 55 + src/features/editScene/lua/LuaEntityApi.cpp | 236 ++ src/features/editScene/lua/LuaEntityApi.hpp | 126 ++ src/features/editScene/lua/LuaEventApi.cpp | 320 +++ src/features/editScene/lua/LuaEventApi.hpp | 55 + src/features/editScene/lua/LuaState.cpp | 187 ++ src/features/editScene/lua/LuaState.hpp | 137 ++ 11 files changed, 3084 insertions(+), 3 deletions(-) create mode 100644 src/features/editScene/lua/LuaComponentApi.cpp create mode 100644 src/features/editScene/lua/LuaComponentApi.hpp create mode 100644 src/features/editScene/lua/LuaEntityApi.cpp create mode 100644 src/features/editScene/lua/LuaEntityApi.hpp create mode 100644 src/features/editScene/lua/LuaEventApi.cpp create mode 100644 src/features/editScene/lua/LuaEventApi.hpp create mode 100644 src/features/editScene/lua/LuaState.cpp create mode 100644 src/features/editScene/lua/LuaState.hpp diff --git a/src/features/editScene/CMakeLists.txt b/src/features/editScene/CMakeLists.txt index cfd6442..231c16f 100644 --- a/src/features/editScene/CMakeLists.txt +++ b/src/features/editScene/CMakeLists.txt @@ -65,8 +65,6 @@ set(EDITSCENE_SOURCES systems/PrefabSystem.cpp ui/PrefabInstanceEditor.cpp - LuaScripting.cpp - systems/ItemSystem.cpp components/ItemModule.cpp components/InventoryModule.cpp @@ -149,6 +147,10 @@ set(EDITSCENE_SOURCES gizmo/Gizmo.cpp gizmo/Cursor3D.cpp physics/physics.cpp + lua/LuaState.cpp + lua/LuaEntityApi.cpp + lua/LuaComponentApi.cpp + lua/LuaEventApi.cpp ) set(EDITSCENE_HEADERS @@ -293,7 +295,10 @@ set(EDITSCENE_HEADERS gizmo/Gizmo.hpp gizmo/Cursor3D.hpp physics/physics.h - LuaScripting.hpp + lua/LuaState.hpp + lua/LuaEntityApi.hpp + lua/LuaComponentApi.hpp + lua/LuaEventApi.hpp ) add_executable(editSceneEditor ${EDITSCENE_SOURCES} ${EDITSCENE_HEADERS}) diff --git a/src/features/editScene/EditorApp.cpp b/src/features/editScene/EditorApp.cpp index 3555766..74e7202 100644 --- a/src/features/editScene/EditorApp.cpp +++ b/src/features/editScene/EditorApp.cpp @@ -84,6 +84,9 @@ #include #include +#include "lua/LuaEntityApi.hpp" +#include "lua/LuaComponentApi.hpp" +#include "lua/LuaEventApi.hpp" //============================================================================= // ImGuiRenderListener Implementation @@ -484,6 +487,28 @@ void EditorApp::setup() addInputListener(this); addInputListener(getImGuiInputListener()); + // Initialize Lua scripting + { + lua_State *L = m_lua.getState(); + + // Store the Flecs world pointer in the Lua registry + // so Lua API functions can access it. + flecs::world *worldPtr = &m_world; + lua_pushlightuserdata(L, worldPtr); + lua_setfield(L, LUA_REGISTRYINDEX, + "EditSceneFlecsWorld"); + + // Register all Lua API modules. + // Order matters: Entity API creates the "ecs" table, + // Component and Event APIs add to it. + editScene::registerLuaEntityApi(L); + editScene::registerLuaComponentApi(L); + editScene::registerLuaEventApi(L); + + // Run late setup: load data.lua and initial scripts. + m_lua.lateSetup(); + } + // Game mode can be set externally before setup() is called m_setupComplete = true; diff --git a/src/features/editScene/EditorApp.hpp b/src/features/editScene/EditorApp.hpp index 6613b55..dc36b85 100644 --- a/src/features/editScene/EditorApp.hpp +++ b/src/features/editScene/EditorApp.hpp @@ -9,6 +9,7 @@ #include #include #include +#include "lua/LuaState.hpp" // Forward declarations class EditorUISystem; @@ -261,6 +262,9 @@ private: bool m_setupComplete = false; bool m_debugBuoyancy = false; + // Lua scripting + editScene::LuaState m_lua; + // Editor visualization nodes Ogre::SceneNode *m_gridNode = nullptr; Ogre::SceneNode *m_axisNode = nullptr; diff --git a/src/features/editScene/lua/LuaComponentApi.cpp b/src/features/editScene/lua/LuaComponentApi.cpp new file mode 100644 index 0000000..2e57e96 --- /dev/null +++ b/src/features/editScene/lua/LuaComponentApi.cpp @@ -0,0 +1,1931 @@ +#include "LuaComponentApi.hpp" +#include "LuaEntityApi.hpp" +#include +#include +#include +#include +#include +#include +#include +#include + +// Include all component headers +#include "components/EntityName.hpp" +#include "components/Transform.hpp" +#include "components/Renderable.hpp" +#include "components/EditorMarker.hpp" +#include "components/PhysicsCollider.hpp" +#include "components/RigidBody.hpp" +#include "components/BuoyancyInfo.hpp" +#include "components/InWater.hpp" +#include "components/WaterPhysics.hpp" +#include "components/WaterPlane.hpp" +#include "components/Sun.hpp" +#include "components/Skybox.hpp" +#include "components/Light.hpp" +#include "components/Camera.hpp" +#include "components/Lod.hpp" +#include "components/LodSettings.hpp" +#include "components/StaticGeometry.hpp" +#include "components/StaticGeometryMember.hpp" +#include "components/ProceduralTexture.hpp" +#include "components/ProceduralMaterial.hpp" +#include "components/Primitive.hpp" +#include "components/TriangleBuffer.hpp" +#include "components/CharacterSlots.hpp" +#include "components/AnimationTree.hpp" +#include "components/AnimationTreeTemplate.hpp" +#include "components/Character.hpp" +#include "components/StartupMenu.hpp" +#include "components/DialogueComponent.hpp" +#include "components/PlayerController.hpp" +#include "components/CellGrid.hpp" +#include "components/ActionDatabase.hpp" +#include "components/ActionDebug.hpp" +#include "components/BehaviorTree.hpp" +#include "components/GoapBlackboard.hpp" +#include "components/PrefabInstance.hpp" +#include "components/NavMesh.hpp" +#include "components/SmartObject.hpp" +#include "components/Actuator.hpp" +#include "components/GoapPlanner.hpp" +#include "components/GoapRunner.hpp" +#include "components/PathFollowing.hpp" +#include "components/EventHandler.hpp" +#include "components/Item.hpp" +#include "components/Inventory.hpp" + +namespace editScene +{ + +// --------------------------------------------------------------------------- +// Helper: push a Lua table from a component's fields +// --------------------------------------------------------------------------- + +/** + * @brief Push an Ogre::Vector3 as a Lua table {x, y, z}. + */ +static void pushVector3(lua_State *L, const Ogre::Vector3 &v) +{ + lua_newtable(L); + lua_pushnumber(L, v.x); + lua_rawseti(L, -2, 1); + lua_pushnumber(L, v.y); + lua_rawseti(L, -2, 2); + lua_pushnumber(L, v.z); + lua_rawseti(L, -2, 3); +} + +/** + * @brief Read a Lua table at the given index as an Ogre::Vector3. + * Expects {x, y, z} or {1=x, 2=y, 3=z}. + */ +static Ogre::Vector3 readVector3(lua_State *L, int idx) +{ + Ogre::Vector3 v; + lua_rawgeti(L, idx, 1); + v.x = (float)lua_tonumber(L, -1); + lua_pop(L, 1); + lua_rawgeti(L, idx, 2); + v.y = (float)lua_tonumber(L, -1); + lua_pop(L, 1); + lua_rawgeti(L, idx, 3); + v.z = (float)lua_tonumber(L, -1); + lua_pop(L, 1); + return v; +} + +/** + * @brief Push an Ogre::Quaternion as a Lua table {w, x, y, z}. + */ +static void pushQuaternion(lua_State *L, const Ogre::Quaternion &q) +{ + lua_newtable(L); + lua_pushnumber(L, q.w); + lua_rawseti(L, -2, 1); + lua_pushnumber(L, q.x); + lua_rawseti(L, -2, 2); + lua_pushnumber(L, q.y); + lua_rawseti(L, -2, 3); + lua_pushnumber(L, q.z); + lua_rawseti(L, -2, 4); +} + +/** + * @brief Read a Lua table at the given index as an Ogre::Quaternion. + * Expects {w, x, y, z}. + */ +static Ogre::Quaternion readQuaternion(lua_State *L, int idx) +{ + Ogre::Quaternion q; + lua_rawgeti(L, idx, 1); + q.w = (float)lua_tonumber(L, -1); + lua_pop(L, 1); + lua_rawgeti(L, idx, 2); + q.x = (float)lua_tonumber(L, -1); + lua_pop(L, 1); + lua_rawgeti(L, idx, 3); + q.y = (float)lua_tonumber(L, -1); + lua_pop(L, 1); + lua_rawgeti(L, idx, 4); + q.z = (float)lua_tonumber(L, -1); + lua_pop(L, 1); + return q; +} + +/** + * @brief Push an Ogre::ColourValue as a Lua table {r, g, b, a}. + */ +static void pushColourValue(lua_State *L, const Ogre::ColourValue &c) +{ + lua_newtable(L); + lua_pushnumber(L, c.r); + lua_rawseti(L, -2, 1); + lua_pushnumber(L, c.g); + lua_rawseti(L, -2, 2); + lua_pushnumber(L, c.b); + lua_rawseti(L, -2, 3); + lua_pushnumber(L, c.a); + lua_rawseti(L, -2, 4); +} + +/** + * @brief Read a Lua table at the given index as an Ogre::ColourValue. + * Expects {r, g, b, a}. + */ +static Ogre::ColourValue readColourValue(lua_State *L, int idx) +{ + Ogre::ColourValue c; + lua_rawgeti(L, idx, 1); + c.r = (float)lua_tonumber(L, -1); + lua_pop(L, 1); + lua_rawgeti(L, idx, 2); + c.g = (float)lua_tonumber(L, -1); + lua_pop(L, 1); + lua_rawgeti(L, idx, 3); + c.b = (float)lua_tonumber(L, -1); + lua_pop(L, 1); + lua_rawgeti(L, idx, 4); + c.a = (float)lua_tonumber(L, -1); + lua_pop(L, 1); + return c; +} + +/** + * @brief Push a string vector as a Lua table. + */ +static void pushStringVector(lua_State *L, const std::vector &vec) +{ + lua_newtable(L); + for (size_t i = 0; i < vec.size(); i++) { + lua_pushstring(L, vec[i].c_str()); + lua_rawseti(L, -2, (int)(i + 1)); + } +} + +/** + * @brief Read a Lua table as a string vector. + */ +static std::vector readStringVector(lua_State *L, int idx) +{ + std::vector result; + lua_pushnil(L); + while (lua_next(L, idx) != 0) { + if (lua_isstring(L, -1)) + result.push_back(lua_tostring(L, -1)); + lua_pop(L, 1); + } + return result; +} + +// --------------------------------------------------------------------------- +// Helper: get the Flecs world from the Lua registry +// --------------------------------------------------------------------------- + +static flecs::world getWorld(lua_State *L) +{ + lua_getfield(L, LUA_REGISTRYINDEX, "EditSceneFlecsWorld"); + OgreAssert(lua_islightuserdata(L, -1), "Flecs world not registered"); + flecs::world *world = + static_cast(lua_touserdata(L, -1)); + lua_pop(L, 1); + return *world; +} + +// --------------------------------------------------------------------------- +// Component descriptor: maps component name to add/get/set/has/remove ops +// --------------------------------------------------------------------------- + +struct ComponentDescriptor { + const char *name; + std::function pushFields; + std::function readFields; +}; + +static std::unordered_map s_components; + +// --------------------------------------------------------------------------- +// Registration helper macro +// --------------------------------------------------------------------------- + +#define REGISTER_COMPONENT(CompType, NameStr, PushBody, ReadBody) \ + do { \ + s_components[NameStr] = { \ + NameStr, \ + [](lua_State *L, flecs::entity e) { \ + if (!e.has()) { \ + lua_pushnil(L); \ + return; \ + } \ + const auto &c = e.get(); \ + lua_newtable(L); \ + PushBody; \ + }, \ + [](lua_State *L, flecs::entity e, int idx) { \ + CompType c; \ + if (e.has()) \ + c = e.get(); \ + ReadBody; \ + e.set(c); \ + } \ + }; \ + } while (0) + +// --------------------------------------------------------------------------- +// Register all components +// --------------------------------------------------------------------------- + +static void registerAllComponents() +{ + if (!s_components.empty()) + return; + + // --- EntityName --- + REGISTER_COMPONENT(EntityNameComponent, "EntityName", + lua_pushstring(L, c.name.c_str()); + lua_setfield(L, -2, "name"); + , if (lua_getfield(L, idx, "name"), + lua_isstring(L, -1)) + c.name = lua_tostring(L, -1); + lua_pop(L, 1);); + + // --- Transform --- + REGISTER_COMPONENT( + TransformComponent, "Transform", pushVector3(L, c.position); + lua_setfield(L, -2, "position"); pushQuaternion(L, c.rotation); + lua_setfield(L, -2, "rotation"); pushVector3(L, c.scale); + lua_setfield(L, -2, "scale"); + , if (lua_getfield(L, idx, "position"), lua_istable(L, -1)) + c.position = readVector3(L, lua_gettop(L)); + lua_pop(L, 1); + if (lua_getfield(L, idx, "rotation"), lua_istable(L, -1)) + c.rotation = readQuaternion(L, lua_gettop(L)); + lua_pop(L, 1); + if (lua_getfield(L, idx, "scale"), lua_istable(L, -1)) + c.scale = readVector3(L, lua_gettop(L)); + lua_pop(L, 1);); + + // --- Renderable --- + REGISTER_COMPONENT( + RenderableComponent, "Renderable", + lua_pushstring(L, c.meshName.c_str()); + lua_setfield(L, -2, "meshName"); + lua_pushboolean(L, c.visible ? 1 : 0); + lua_setfield(L, -2, "visible"); + , if (lua_getfield(L, idx, "meshName"), lua_isstring(L, -1)) + c.meshName = lua_tostring(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "visible"), lua_isboolean(L, -1)) + c.visible = lua_toboolean(L, -1) != 0; + lua_pop(L, 1);); + + // --- EditorMarker (tag, no fields) --- + s_components["EditorMarker"] = { + "EditorMarker", + [](lua_State *L, flecs::entity e) { + lua_pushboolean(L, + e.has() ? 1 : 0); + }, + [](lua_State *L, flecs::entity e, int idx) { + (void)idx; + if (lua_toboolean(L, -1)) + e.add(); + else + e.remove(); + } + }; + + // --- Light --- + REGISTER_COMPONENT( + LightComponent, "Light", lua_pushinteger(L, (int)c.lightType); + lua_setfield(L, -2, "lightType"); + pushColourValue(L, c.diffuseColor); + lua_setfield(L, -2, "diffuseColor"); + pushColourValue(L, c.specularColor); + lua_setfield(L, -2, "specularColor"); + lua_pushnumber(L, c.intensity); + lua_setfield(L, -2, "intensity"); lua_pushnumber(L, c.range); + lua_setfield(L, -2, "range"); + lua_pushnumber(L, c.spotlightInnerAngle); + lua_setfield(L, -2, "spotlightInnerAngle"); + lua_pushnumber(L, c.spotlightOuterAngle); + lua_setfield(L, -2, "spotlightOuterAngle"); + pushVector3(L, c.direction); lua_setfield(L, -2, "direction"); + lua_pushboolean(L, c.castShadows ? 1 : 0); + lua_setfield(L, -2, "castShadows"); + , if (lua_getfield(L, idx, "lightType"), lua_isnumber(L, -1)) + c.lightType = + (LightComponent::LightType)lua_tointeger(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "diffuseColor"), lua_istable(L, -1)) + c.diffuseColor = readColourValue(L, lua_gettop(L)); + lua_pop(L, 1); + if (lua_getfield(L, idx, "specularColor"), lua_istable(L, -1)) + c.specularColor = readColourValue(L, lua_gettop(L)); + lua_pop(L, 1); + if (lua_getfield(L, idx, "intensity"), lua_isnumber(L, -1)) + c.intensity = (float)lua_tonumber(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "range"), lua_isnumber(L, -1)) + c.range = (float)lua_tonumber(L, -1); + lua_pop(L, 1); if (lua_getfield(L, idx, "spotlightInnerAngle"), + lua_isnumber(L, -1)) c.spotlightInnerAngle = + (float)lua_tonumber(L, -1); + lua_pop(L, 1); if (lua_getfield(L, idx, "spotlightOuterAngle"), + lua_isnumber(L, -1)) c.spotlightOuterAngle = + (float)lua_tonumber(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "direction"), lua_istable(L, -1)) + c.direction = readVector3(L, lua_gettop(L)); + lua_pop(L, 1); + if (lua_getfield(L, idx, "castShadows"), lua_isboolean(L, -1)) + c.castShadows = lua_toboolean(L, -1) != 0; + lua_pop(L, 1);); + + // --- Camera --- + REGISTER_COMPONENT( + CameraComponent, "Camera", lua_pushnumber(L, c.fovY); + lua_setfield(L, -2, "fovY"); lua_pushnumber(L, c.nearClip); + lua_setfield(L, -2, "nearClip"); lua_pushnumber(L, c.farClip); + lua_setfield(L, -2, "farClip"); + lua_pushboolean(L, c.orthographic ? 1 : 0); + lua_setfield(L, -2, "orthographic"); + lua_pushnumber(L, c.orthoWidth); + lua_setfield(L, -2, "orthoWidth"); + lua_pushnumber(L, c.orthoHeight); + lua_setfield(L, -2, "orthoHeight"); + , if (lua_getfield(L, idx, "fovY"), lua_isnumber(L, -1)) + c.fovY = (float)lua_tonumber(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "nearClip"), lua_isnumber(L, -1)) + c.nearClip = (float)lua_tonumber(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "farClip"), lua_isnumber(L, -1)) + c.farClip = (float)lua_tonumber(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "orthographic"), lua_isboolean(L, -1)) + c.orthographic = lua_toboolean(L, -1) != 0; + lua_pop(L, 1); + if (lua_getfield(L, idx, "orthoWidth"), lua_isnumber(L, -1)) + c.orthoWidth = (float)lua_tonumber(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "orthoHeight"), lua_isnumber(L, -1)) + c.orthoHeight = (float)lua_tonumber(L, -1); + lua_pop(L, 1);); + + // --- RigidBody --- + REGISTER_COMPONENT( + RigidBodyComponent, "RigidBody", + lua_pushinteger(L, (int)c.bodyType); + lua_setfield(L, -2, "bodyType"); lua_pushnumber(L, c.mass); + lua_setfield(L, -2, "mass"); lua_pushnumber(L, c.friction); + lua_setfield(L, -2, "friction"); + lua_pushnumber(L, c.restitution); + lua_setfield(L, -2, "restitution"); + lua_pushboolean(L, c.isSensor ? 1 : 0); + lua_setfield(L, -2, "isSensor"); + lua_pushboolean(L, c.enabled ? 1 : 0); + lua_setfield(L, -2, "enabled"); + , if (lua_getfield(L, idx, "bodyType"), lua_isnumber(L, -1)) + c.bodyType = + (RigidBodyComponent::BodyType)lua_tointeger(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "mass"), lua_isnumber(L, -1)) + c.mass = (float)lua_tonumber(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "friction"), lua_isnumber(L, -1)) + c.friction = (float)lua_tonumber(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "restitution"), lua_isnumber(L, -1)) + c.restitution = (float)lua_tonumber(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "isSensor"), lua_isboolean(L, -1)) + c.isSensor = lua_toboolean(L, -1) != 0; + lua_pop(L, 1); + if (lua_getfield(L, idx, "enabled"), lua_isboolean(L, -1)) + c.enabled = lua_toboolean(L, -1) != 0; + lua_pop(L, 1);); + + // --- PhysicsCollider --- + REGISTER_COMPONENT( + PhysicsColliderComponent, "PhysicsCollider", + lua_pushinteger(L, (int)c.shapeType); + lua_setfield(L, -2, "shapeType"); pushVector3(L, c.parameters); + lua_setfield(L, -2, "parameters"); lua_pushnumber(L, c.radius); + lua_setfield(L, -2, "radius"); lua_pushnumber(L, c.halfHeight); + lua_setfield(L, -2, "halfHeight"); + lua_pushstring(L, c.meshName.c_str()); + lua_setfield(L, -2, "meshName"); pushVector3(L, c.offset); + lua_setfield(L, -2, "offset"); + , if (lua_getfield(L, idx, "shapeType"), lua_isnumber(L, -1)) + c.shapeType = (PhysicsColliderComponent::ShapeType) + lua_tointeger(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "parameters"), lua_istable(L, -1)) + c.parameters = readVector3(L, lua_gettop(L)); + lua_pop(L, 1); + if (lua_getfield(L, idx, "radius"), lua_isnumber(L, -1)) + c.radius = (float)lua_tonumber(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "halfHeight"), lua_isnumber(L, -1)) + c.halfHeight = (float)lua_tonumber(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "meshName"), lua_isstring(L, -1)) + c.meshName = lua_tostring(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "offset"), lua_istable(L, -1)) + c.offset = readVector3(L, lua_gettop(L)); + lua_pop(L, 1);); + + // --- Character --- + REGISTER_COMPONENT( + CharacterComponent, "Character", lua_pushnumber(L, c.radius); + lua_setfield(L, -2, "radius"); lua_pushnumber(L, c.height); + lua_setfield(L, -2, "height"); pushVector3(L, c.offset); + lua_setfield(L, -2, "offset"); pushVector3(L, c.linearVelocity); + lua_setfield(L, -2, "linearVelocity"); + lua_pushboolean(L, c.enabled ? 1 : 0); + lua_setfield(L, -2, "enabled"); + lua_pushboolean(L, c.useGravity ? 1 : 0); + lua_setfield(L, -2, "useGravity"); + , if (lua_getfield(L, idx, "radius"), lua_isnumber(L, -1)) + c.radius = (float)lua_tonumber(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "height"), lua_isnumber(L, -1)) + c.height = (float)lua_tonumber(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "offset"), lua_istable(L, -1)) + c.offset = readVector3(L, lua_gettop(L)); + lua_pop(L, 1); + if (lua_getfield(L, idx, "linearVelocity"), lua_istable(L, -1)) + c.linearVelocity = readVector3(L, lua_gettop(L)); + lua_pop(L, 1); + if (lua_getfield(L, idx, "enabled"), lua_isboolean(L, -1)) + c.enabled = lua_toboolean(L, -1) != 0; + lua_pop(L, 1); + if (lua_getfield(L, idx, "useGravity"), lua_isboolean(L, -1)) + c.useGravity = lua_toboolean(L, -1) != 0; + lua_pop(L, 1);); + + // --- CharacterSlots --- + REGISTER_COMPONENT( + CharacterSlotsComponent, "CharacterSlots", + lua_pushstring(L, c.age.c_str()); + lua_setfield(L, -2, "age"); lua_pushstring(L, c.sex.c_str()); + lua_setfield(L, -2, "sex"); + // slots map: push as table + lua_newtable(L); for (auto &kv : c.slots) { + lua_pushstring(L, kv.second.c_str()); + lua_setfield(L, -2, kv.first.c_str()); + } lua_setfield(L, -2, "slots"); + pushVector3(L, c.frontAxis); lua_setfield(L, -2, "frontAxis"); + , if (lua_getfield(L, idx, "age"), lua_isstring(L, -1)) + c.age = lua_tostring(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "sex"), lua_isstring(L, -1)) + c.sex = lua_tostring(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "slots"), lua_istable(L, -1)) { + c.slots.clear(); + lua_pushnil(L); + while (lua_next(L, -2) != 0) { + if (lua_isstring(L, -2) && lua_isstring(L, -1)) + c.slots[lua_tostring(L, -2)] = + lua_tostring(L, -1); + lua_pop(L, 1); + } + } lua_pop(L, 1); + if (lua_getfield(L, idx, "frontAxis"), lua_istable(L, -1)) + c.frontAxis = readVector3(L, lua_gettop(L)); + lua_pop(L, 1);); + + // --- AnimationTree --- + REGISTER_COMPONENT( + AnimationTreeComponent, "AnimationTree", + lua_pushboolean(L, c.enabled ? 1 : 0); + lua_setfield(L, -2, "enabled"); + lua_pushboolean(L, c.useRootMotion ? 1 : 0); + lua_setfield(L, -2, "useRootMotion"); + lua_pushstring(L, c.templateName.c_str()); + lua_setfield(L, -2, "templateName"); + , if (lua_getfield(L, idx, "enabled"), lua_isboolean(L, -1)) + c.enabled = lua_toboolean(L, -1) != 0; + lua_pop(L, 1); + if (lua_getfield(L, idx, "useRootMotion"), lua_isboolean(L, -1)) + c.useRootMotion = lua_toboolean(L, -1) != 0; + lua_pop(L, 1); + if (lua_getfield(L, idx, "templateName"), lua_isstring(L, -1)) + c.templateName = lua_tostring(L, -1); + lua_pop(L, 1);); + + // --- AnimationTreeTemplate --- + REGISTER_COMPONENT(AnimationTreeTemplate, "AnimationTreeTemplate", + lua_pushstring(L, c.name.c_str()); + lua_setfield(L, -2, "name"); + , if (lua_getfield(L, idx, "name"), + lua_isstring(L, -1)) + c.name = lua_tostring(L, -1); + lua_pop(L, 1);); + + // --- BehaviorTree --- + REGISTER_COMPONENT( + BehaviorTreeComponent, "BehaviorTree", + lua_pushstring(L, c.treeName.c_str()); + lua_setfield(L, -2, "treeName"); + lua_pushboolean(L, c.enabled ? 1 : 0); + lua_setfield(L, -2, "enabled"); + , if (lua_getfield(L, idx, "treeName"), lua_isstring(L, -1)) + c.treeName = lua_tostring(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "enabled"), lua_isboolean(L, -1)) + c.enabled = lua_toboolean(L, -1) != 0; + lua_pop(L, 1);); + + // --- GoapBlackboard --- + REGISTER_COMPONENT( + GoapBlackboard, "GoapBlackboard", + lua_pushinteger(L, (lua_Integer)c.bits); + lua_setfield(L, -2, "bits"); + lua_pushinteger(L, (lua_Integer)c.mask); + lua_setfield(L, -2, "mask"); + // values map + lua_newtable(L); for (auto &kv : c.values) { + lua_pushinteger(L, kv.second); + lua_setfield(L, -2, kv.first.c_str()); + } lua_setfield(L, -2, "values"); + // floatValues map + lua_newtable(L); for (auto &kv : c.floatValues) { + lua_pushnumber(L, kv.second); + lua_setfield(L, -2, kv.first.c_str()); + } lua_setfield(L, -2, "floatValues"); + // stringValues map + lua_newtable(L); for (auto &kv : c.stringValues) { + lua_pushstring(L, kv.second.c_str()); + lua_setfield(L, -2, kv.first.c_str()); + } lua_setfield(L, -2, "stringValues"); + , if (lua_getfield(L, idx, "bits"), lua_isnumber(L, -1)) + c.bits = (uint64_t)lua_tointeger(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "mask"), lua_isnumber(L, -1)) + c.mask = (uint64_t)lua_tointeger(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "values"), lua_istable(L, -1)) { + c.values.clear(); + lua_pushnil(L); + while (lua_next(L, -2) != 0) { + if (lua_isstring(L, -2) && lua_isnumber(L, -1)) + c.values[lua_tostring(L, -2)] = + (int)lua_tointeger(L, -1); + lua_pop(L, 1); + } + } lua_pop(L, 1); + if (lua_getfield(L, idx, "floatValues"), lua_istable(L, -1)) { + c.floatValues.clear(); + lua_pushnil(L); + while (lua_next(L, -2) != 0) { + if (lua_isstring(L, -2) && lua_isnumber(L, -1)) + c.floatValues[lua_tostring(L, -2)] = + (float)lua_tonumber(L, -1); + lua_pop(L, 1); + } + } lua_pop(L, 1); + if (lua_getfield(L, idx, "stringValues"), lua_istable(L, -1)) { + c.stringValues.clear(); + lua_pushnil(L); + while (lua_next(L, -2) != 0) { + if (lua_isstring(L, -2) && lua_isstring(L, -1)) + c.stringValues[lua_tostring(L, -2)] = + lua_tostring(L, -1); + lua_pop(L, 1); + } + } lua_pop(L, 1);); + + // --- SmartObject --- + REGISTER_COMPONENT( + SmartObjectComponent, "SmartObject", + lua_pushnumber(L, c.radius); + lua_setfield(L, -2, "radius"); lua_pushnumber(L, c.height); + lua_setfield(L, -2, "height"); + pushStringVector(L, c.actionNames); + lua_setfield(L, -2, "actionNames"); + , if (lua_getfield(L, idx, "radius"), lua_isnumber(L, -1)) + c.radius = (float)lua_tonumber(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "height"), lua_isnumber(L, -1)) + c.height = (float)lua_tonumber(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "actionNames"), lua_istable(L, -1)) + c.actionNames = readStringVector(L, lua_gettop(L)); + lua_pop(L, 1);); + + // --- Actuator --- + REGISTER_COMPONENT( + ActuatorComponent, "Actuator", lua_pushnumber(L, c.radius); + lua_setfield(L, -2, "radius"); lua_pushnumber(L, c.height); + lua_setfield(L, -2, "height"); + pushStringVector(L, c.actionNames); + lua_setfield(L, -2, "actionNames"); + , if (lua_getfield(L, idx, "radius"), lua_isnumber(L, -1)) + c.radius = (float)lua_tonumber(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "height"), lua_isnumber(L, -1)) + c.height = (float)lua_tonumber(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "actionNames"), lua_istable(L, -1)) + c.actionNames = readStringVector(L, lua_gettop(L)); + lua_pop(L, 1);); + + // --- EventHandler --- + REGISTER_COMPONENT( + EventHandlerComponent, "EventHandler", + lua_pushstring(L, c.eventName.c_str()); + lua_setfield(L, -2, "eventName"); + lua_pushstring(L, c.actionName.c_str()); + lua_setfield(L, -2, "actionName"); + lua_pushboolean(L, c.enabled ? 1 : 0); + lua_setfield(L, -2, "enabled"); + , if (lua_getfield(L, idx, "eventName"), lua_isstring(L, -1)) + c.eventName = lua_tostring(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "actionName"), lua_isstring(L, -1)) + c.actionName = lua_tostring(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "enabled"), lua_isboolean(L, -1)) + c.enabled = lua_toboolean(L, -1) != 0; + lua_pop(L, 1);); + + // --- PathFollowing --- + REGISTER_COMPONENT( + PathFollowingComponent, "PathFollowing", + lua_pushnumber(L, c.walkSpeed); + lua_setfield(L, -2, "walkSpeed"); lua_pushnumber(L, c.runSpeed); + lua_setfield(L, -2, "runSpeed"); + lua_pushboolean(L, c.useRootMotion ? 1 : 0); + lua_setfield(L, -2, "useRootMotion"); + lua_pushstring(L, c.currentLocomotionState.c_str()); + lua_setfield(L, -2, "currentLocomotionState"); + , if (lua_getfield(L, idx, "walkSpeed"), lua_isnumber(L, -1)) + c.walkSpeed = (float)lua_tonumber(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "runSpeed"), lua_isnumber(L, -1)) + c.runSpeed = (float)lua_tonumber(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "useRootMotion"), lua_isboolean(L, -1)) + c.useRootMotion = lua_toboolean(L, -1) != 0; + lua_pop(L, 1); + if (lua_getfield(L, idx, "currentLocomotionState"), + lua_isstring(L, -1)) + c.currentLocomotionState = lua_tostring(L, -1); + lua_pop(L, 1);); + + // --- NavMesh --- + REGISTER_COMPONENT( + NavMeshComponent, "NavMesh", lua_pushnumber(L, c.cellSize); + lua_setfield(L, -2, "cellSize"); + lua_pushnumber(L, c.cellHeight); + lua_setfield(L, -2, "cellHeight"); + lua_pushnumber(L, c.agentHeight); + lua_setfield(L, -2, "agentHeight"); + lua_pushnumber(L, c.agentRadius); + lua_setfield(L, -2, "agentRadius"); + lua_pushnumber(L, c.agentMaxClimb); + lua_setfield(L, -2, "agentMaxClimb"); + lua_pushnumber(L, c.agentMaxSlope); + lua_setfield(L, -2, "agentMaxSlope"); + lua_pushboolean(L, c.enabled ? 1 : 0); + lua_setfield(L, -2, "enabled"); + lua_pushboolean(L, c.debugDraw ? 1 : 0); + lua_setfield(L, -2, "debugDraw"); + , if (lua_getfield(L, idx, "cellSize"), lua_isnumber(L, -1)) + c.cellSize = (float)lua_tonumber(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "cellHeight"), lua_isnumber(L, -1)) + c.cellHeight = (float)lua_tonumber(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "agentHeight"), lua_isnumber(L, -1)) + c.agentHeight = (float)lua_tonumber(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "agentRadius"), lua_isnumber(L, -1)) + c.agentRadius = (float)lua_tonumber(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "agentMaxClimb"), lua_isnumber(L, -1)) + c.agentMaxClimb = (float)lua_tonumber(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "agentMaxSlope"), lua_isnumber(L, -1)) + c.agentMaxSlope = (float)lua_tonumber(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "enabled"), lua_isboolean(L, -1)) + c.enabled = lua_toboolean(L, -1) != 0; + lua_pop(L, 1); + if (lua_getfield(L, idx, "debugDraw"), lua_isboolean(L, -1)) + c.debugDraw = lua_toboolean(L, -1) != 0; + lua_pop(L, 1);); + + // --- NavMeshAgent --- + REGISTER_COMPONENT( + NavMeshAgent, "NavMeshAgent", pushVector3(L, c.targetPos); + lua_setfield(L, -2, "targetPos"); + lua_pushboolean(L, c.hasTarget ? 1 : 0); + lua_setfield(L, -2, "hasTarget"); + , if (lua_getfield(L, idx, "targetPos"), lua_istable(L, -1)) + c.targetPos = readVector3(L, lua_gettop(L)); + lua_pop(L, 1); + if (lua_getfield(L, idx, "hasTarget"), lua_isboolean(L, -1)) + c.hasTarget = lua_toboolean(L, -1) != 0; + lua_pop(L, 1);); + + // --- NavMeshGeometrySource --- + s_components["NavMeshGeometrySource"] = { + "NavMeshGeometrySource", + [](lua_State *L, flecs::entity e) { + lua_pushboolean(L, + e.has() ? 1 : 0); + }, + [](lua_State *L, flecs::entity e, int idx) { + (void)idx; + if (lua_toboolean(L, -1)) + e.add(); + else + e.remove(); + } + }; + + // --- Item --- + REGISTER_COMPONENT( + ItemComponent, "Item", lua_pushstring(L, c.itemName.c_str()); + lua_setfield(L, -2, "itemName"); + lua_pushstring(L, c.itemType.c_str()); + lua_setfield(L, -2, "itemType"); + lua_pushstring(L, c.itemId.c_str()); + lua_setfield(L, -2, "itemId"); lua_pushinteger(L, c.stackSize); + lua_setfield(L, -2, "stackSize"); + lua_pushinteger(L, c.maxStackSize); + lua_setfield(L, -2, "maxStackSize"); + lua_pushnumber(L, c.weight); lua_setfield(L, -2, "weight"); + lua_pushinteger(L, c.value); lua_setfield(L, -2, "value"); + lua_pushstring(L, c.useActionName.c_str()); + lua_setfield(L, -2, "useActionName"); + , if (lua_getfield(L, idx, "itemName"), lua_isstring(L, -1)) + c.itemName = lua_tostring(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "itemType"), lua_isstring(L, -1)) + c.itemType = lua_tostring(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "itemId"), lua_isstring(L, -1)) + c.itemId = lua_tostring(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "stackSize"), lua_isnumber(L, -1)) + c.stackSize = (int)lua_tointeger(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "maxStackSize"), lua_isnumber(L, -1)) + c.maxStackSize = (int)lua_tointeger(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "weight"), lua_isnumber(L, -1)) + c.weight = (float)lua_tonumber(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "value"), lua_isnumber(L, -1)) + c.value = (int)lua_tointeger(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "useActionName"), lua_isstring(L, -1)) + c.useActionName = lua_tostring(L, -1); + lua_pop(L, 1);); + + // --- Inventory --- + REGISTER_COMPONENT( + InventoryComponent, "Inventory", lua_pushinteger(L, c.maxSlots); + lua_setfield(L, -2, "maxSlots"); lua_pushnumber(L, c.maxWeight); + lua_setfield(L, -2, "maxWeight"); + lua_pushboolean(L, c.isContainer ? 1 : 0); + lua_setfield(L, -2, "isContainer"); + , if (lua_getfield(L, idx, "maxSlots"), lua_isnumber(L, -1)) + c.maxSlots = (int)lua_tointeger(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "maxWeight"), lua_isnumber(L, -1)) + c.maxWeight = (float)lua_tonumber(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "isContainer"), lua_isboolean(L, -1)) + c.isContainer = lua_toboolean(L, -1) != 0; + lua_pop(L, 1);); + + // --- Lod --- + REGISTER_COMPONENT( + LodComponent, "Lod", lua_pushstring(L, c.settingsId.c_str()); + lua_setfield(L, -2, "settingsId"); + lua_pushnumber(L, c.distanceMultiplier); + lua_setfield(L, -2, "distanceMultiplier"); + , if (lua_getfield(L, idx, "settingsId"), lua_isstring(L, -1)) + c.settingsId = lua_tostring(L, -1); + lua_pop(L, 1); if (lua_getfield(L, idx, "distanceMultiplier"), + lua_isnumber(L, -1)) c.distanceMultiplier = + (float)lua_tonumber(L, -1); + lua_pop(L, 1);); + + // --- LodSettings --- + REGISTER_COMPONENT( + LodSettingsComponent, "LodSettings", + lua_pushstring(L, c.settingsId.c_str()); + lua_setfield(L, -2, "settingsId"); + lua_pushinteger(L, (int)c.strategy); + lua_setfield(L, -2, "strategy"); + lua_pushboolean(L, c.useCompression ? 1 : 0); + lua_setfield(L, -2, "useCompression"); + lua_pushboolean(L, c.useVertexNormals ? 1 : 0); + lua_setfield(L, -2, "useVertexNormals"); + , if (lua_getfield(L, idx, "settingsId"), lua_isstring(L, -1)) + c.settingsId = lua_tostring(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "strategy"), lua_isnumber(L, -1)) + c.strategy = + (LodSettingsComponent::Strategy)lua_tointeger(L, -1); + lua_pop(L, 1); if (lua_getfield(L, idx, "useCompression"), + lua_isboolean(L, -1)) c.useCompression = + lua_toboolean(L, -1) != 0; + lua_pop(L, 1); if (lua_getfield(L, idx, "useVertexNormals"), + lua_isboolean(L, -1)) c.useVertexNormals = + lua_toboolean(L, -1) != 0; + lua_pop(L, 1);); + + // --- StaticGeometry --- + REGISTER_COMPONENT( + StaticGeometryComponent, "StaticGeometry", + lua_pushstring(L, c.regionId.c_str()); + lua_setfield(L, -2, "regionId"); + lua_pushstring(L, c.regionName.c_str()); + lua_setfield(L, -2, "regionName"); + lua_pushnumber(L, c.renderingDistance); + lua_setfield(L, -2, "renderingDistance"); + lua_pushboolean(L, c.castShadows ? 1 : 0); + lua_setfield(L, -2, "castShadows"); + lua_pushnumber(L, c.regionDimensions); + lua_setfield(L, -2, "regionDimensions"); + , if (lua_getfield(L, idx, "regionId"), lua_isstring(L, -1)) + c.regionId = lua_tostring(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "regionName"), lua_isstring(L, -1)) + c.regionName = lua_tostring(L, -1); + lua_pop(L, 1); if (lua_getfield(L, idx, "renderingDistance"), + lua_isnumber(L, -1)) c.renderingDistance = + (float)lua_tonumber(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "castShadows"), lua_isboolean(L, -1)) + c.castShadows = lua_toboolean(L, -1) != 0; + lua_pop(L, 1); if (lua_getfield(L, idx, "regionDimensions"), + lua_isnumber(L, -1)) c.regionDimensions = + (float)lua_tonumber(L, -1); + lua_pop(L, 1);); + + // --- StaticGeometryMember --- + REGISTER_COMPONENT( + StaticGeometryMemberComponent, "StaticGeometryMember", + lua_pushstring(L, c.meshName.c_str()); + lua_setfield(L, -2, "meshName"); + lua_pushstring(L, c.materialName.c_str()); + lua_setfield(L, -2, "materialName"); + lua_pushstring(L, c.regionId.c_str()); + lua_setfield(L, -2, "regionId"); + lua_pushboolean(L, c.castShadows ? 1 : 0); + lua_setfield(L, -2, "castShadows"); + lua_pushboolean(L, c.visible ? 1 : 0); + lua_setfield(L, -2, "visible"); + , if (lua_getfield(L, idx, "meshName"), lua_isstring(L, -1)) + c.meshName = lua_tostring(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "materialName"), lua_isstring(L, -1)) + c.materialName = lua_tostring(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "regionId"), lua_isstring(L, -1)) + c.regionId = lua_tostring(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "castShadows"), lua_isboolean(L, -1)) + c.castShadows = lua_toboolean(L, -1) != 0; + lua_pop(L, 1); + if (lua_getfield(L, idx, "visible"), lua_isboolean(L, -1)) + c.visible = lua_toboolean(L, -1) != 0; + lua_pop(L, 1);); + + // --- ProceduralTexture --- + REGISTER_COMPONENT( + ProceduralTextureComponent, "ProceduralTexture", + lua_pushstring(L, c.textureId.c_str()); + lua_setfield(L, -2, "textureId"); + lua_pushstring(L, c.textureName.c_str()); + lua_setfield(L, -2, "textureName"); + lua_pushinteger(L, c.textureSize); + lua_setfield(L, -2, "textureSize"); + lua_pushnumber(L, c.uvMargin); lua_setfield(L, -2, "uvMargin"); + , if (lua_getfield(L, idx, "textureId"), lua_isstring(L, -1)) + c.textureId = lua_tostring(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "textureName"), lua_isstring(L, -1)) + c.textureName = lua_tostring(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "textureSize"), lua_isnumber(L, -1)) + c.textureSize = (int)lua_tointeger(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "uvMargin"), lua_isnumber(L, -1)) + c.uvMargin = (float)lua_tonumber(L, -1); + lua_pop(L, 1);); + + // --- ProceduralMaterial --- + REGISTER_COMPONENT( + ProceduralMaterialComponent, "ProceduralMaterial", + lua_pushstring(L, c.materialId.c_str()); + lua_setfield(L, -2, "materialId"); + lua_pushstring(L, c.materialName.c_str()); + lua_setfield(L, -2, "materialName"); + lua_pushstring(L, c.diffuseTextureId.c_str()); + lua_setfield(L, -2, "diffuseTextureId"); + lua_pushnumber(L, c.shininess); + lua_setfield(L, -2, "shininess"); + lua_pushnumber(L, c.roughness); + lua_setfield(L, -2, "roughness"); + , if (lua_getfield(L, idx, "materialId"), lua_isstring(L, -1)) + c.materialId = lua_tostring(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "materialName"), lua_isstring(L, -1)) + c.materialName = lua_tostring(L, -1); + lua_pop(L, 1); if (lua_getfield(L, idx, "diffuseTextureId"), + lua_isstring(L, -1)) + c.diffuseTextureId = lua_tostring(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "shininess"), lua_isnumber(L, -1)) + c.shininess = (float)lua_tonumber(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "roughness"), lua_isnumber(L, -1)) + c.roughness = (float)lua_tonumber(L, -1); + lua_pop(L, 1);); + + // --- Primitive --- + REGISTER_COMPONENT( + PrimitiveComponent, "Primitive", + lua_pushinteger(L, (int)c.type); + lua_setfield(L, -2, "type"); lua_pushnumber(L, c.boxSizeX); + lua_setfield(L, -2, "boxSizeX"); lua_pushnumber(L, c.boxSizeY); + lua_setfield(L, -2, "boxSizeY"); lua_pushnumber(L, c.boxSizeZ); + lua_setfield(L, -2, "boxSizeZ"); + lua_pushnumber(L, c.planeSizeX); + lua_setfield(L, -2, "planeSizeX"); + lua_pushnumber(L, c.planeSizeY); + lua_setfield(L, -2, "planeSizeY"); + , + if (lua_getfield(L, idx, "type"), lua_isnumber(L, -1)) + c.type = (PrimitiveComponent::Type)lua_tointeger(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "boxSizeX"), lua_isnumber(L, -1)) + c.boxSizeX = (float)lua_tonumber(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "boxSizeY"), lua_isnumber(L, -1)) + c.boxSizeY = (float)lua_tonumber(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "boxSizeZ"), lua_isnumber(L, -1)) + c.boxSizeZ = (float)lua_tonumber(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "planeSizeX"), lua_isnumber(L, -1)) + c.planeSizeX = (float)lua_tonumber(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "planeSizeY"), lua_isnumber(L, -1)) + c.planeSizeY = (float)lua_tonumber(L, -1); + lua_pop(L, 1);); + + // --- TriangleBuffer --- + REGISTER_COMPONENT( + TriangleBufferComponent, "TriangleBuffer", + lua_pushstring(L, c.meshName.c_str()); + lua_setfield(L, -2, "meshName"); + lua_pushstring(L, c.textureRectName.c_str()); + lua_setfield(L, -2, "textureRectName"); + lua_pushboolean(L, c.useStaticGeometry ? 1 : 0); + lua_setfield(L, -2, "useStaticGeometry"); + , if (lua_getfield(L, idx, "meshName"), lua_isstring(L, -1)) + c.meshName = lua_tostring(L, -1); + lua_pop(L, 1); if (lua_getfield(L, idx, "textureRectName"), + lua_isstring(L, -1)) + c.textureRectName = lua_tostring(L, -1); + lua_pop(L, 1); if (lua_getfield(L, idx, "useStaticGeometry"), + lua_isboolean(L, -1)) c.useStaticGeometry = + lua_toboolean(L, -1) != 0; + lua_pop(L, 1);); + + // --- Sun --- + REGISTER_COMPONENT( + SunComponent, "Sun", lua_pushboolean(L, c.enabled ? 1 : 0); + lua_setfield(L, -2, "enabled"); lua_pushnumber(L, c.timeOfDay); + lua_setfield(L, -2, "timeOfDay"); + lua_pushnumber(L, c.timeSpeed); + lua_setfield(L, -2, "timeSpeed"); + pushColourValue(L, c.sunColor); lua_setfield(L, -2, "sunColor"); + pushColourValue(L, c.moonColor); + lua_setfield(L, -2, "moonColor"); + lua_pushnumber(L, c.intensity); + lua_setfield(L, -2, "intensity"); + lua_pushboolean(L, c.castShadows ? 1 : 0); + lua_setfield(L, -2, "castShadows"); + , if (lua_getfield(L, idx, "enabled"), lua_isboolean(L, -1)) + c.enabled = lua_toboolean(L, -1) != 0; + lua_pop(L, 1); + if (lua_getfield(L, idx, "timeOfDay"), lua_isnumber(L, -1)) + c.timeOfDay = (float)lua_tonumber(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "timeSpeed"), lua_isnumber(L, -1)) + c.timeSpeed = (float)lua_tonumber(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "sunColor"), lua_istable(L, -1)) + c.sunColor = readColourValue(L, lua_gettop(L)); + lua_pop(L, 1); + if (lua_getfield(L, idx, "moonColor"), lua_istable(L, -1)) + c.moonColor = readColourValue(L, lua_gettop(L)); + lua_pop(L, 1); + if (lua_getfield(L, idx, "intensity"), lua_isnumber(L, -1)) + c.intensity = (float)lua_tonumber(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "castShadows"), lua_isboolean(L, -1)) + c.castShadows = lua_toboolean(L, -1) != 0; + lua_pop(L, 1);); + + // --- Skybox --- + REGISTER_COMPONENT( + SkyboxComponent, "Skybox", + lua_pushboolean(L, c.enabled ? 1 : 0); + lua_setfield(L, -2, "enabled"); lua_pushnumber(L, c.size); + lua_setfield(L, -2, "size"); pushColourValue(L, c.dayTopColor); + lua_setfield(L, -2, "dayTopColor"); + pushColourValue(L, c.dayBottomColor); + lua_setfield(L, -2, "dayBottomColor"); + pushColourValue(L, c.nightTopColor); + lua_setfield(L, -2, "nightTopColor"); + pushColourValue(L, c.nightBottomColor); + lua_setfield(L, -2, "nightBottomColor"); + lua_pushboolean(L, c.starsEnabled ? 1 : 0); + lua_setfield(L, -2, "starsEnabled"); + , if (lua_getfield(L, idx, "enabled"), lua_isboolean(L, -1)) + c.enabled = lua_toboolean(L, -1) != 0; + lua_pop(L, 1); + if (lua_getfield(L, idx, "size"), lua_isnumber(L, -1)) + c.size = (float)lua_tonumber(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "dayTopColor"), lua_istable(L, -1)) + c.dayTopColor = readColourValue(L, lua_gettop(L)); + lua_pop(L, 1); + if (lua_getfield(L, idx, "dayBottomColor"), lua_istable(L, -1)) + c.dayBottomColor = readColourValue(L, lua_gettop(L)); + lua_pop(L, 1); + if (lua_getfield(L, idx, "nightTopColor"), lua_istable(L, -1)) + c.nightTopColor = readColourValue(L, lua_gettop(L)); + lua_pop(L, 1); if (lua_getfield(L, idx, "nightBottomColor"), + lua_istable(L, -1)) c.nightBottomColor = + readColourValue(L, lua_gettop(L)); + lua_pop(L, 1); + if (lua_getfield(L, idx, "starsEnabled"), lua_isboolean(L, -1)) + c.starsEnabled = lua_toboolean(L, -1) != 0; + lua_pop(L, 1);); + + // --- WaterPlane --- + REGISTER_COMPONENT( + WaterPlane, "WaterPlane", lua_pushboolean(L, c.enabled ? 1 : 0); + lua_setfield(L, -2, "enabled"); + lua_pushnumber(L, c.waterSurfaceY); + lua_setfield(L, -2, "waterSurfaceY"); + lua_pushnumber(L, c.planeSize); + lua_setfield(L, -2, "planeSize"); + pushColourValue(L, c.waterColor); + lua_setfield(L, -2, "waterColor"); + lua_pushnumber(L, c.reflectivity); + lua_setfield(L, -2, "reflectivity"); + lua_pushnumber(L, c.waveSpeed); + lua_setfield(L, -2, "waveSpeed"); + , if (lua_getfield(L, idx, "enabled"), lua_isboolean(L, -1)) + c.enabled = lua_toboolean(L, -1) != 0; + lua_pop(L, 1); + if (lua_getfield(L, idx, "waterSurfaceY"), lua_isnumber(L, -1)) + c.waterSurfaceY = (float)lua_tonumber(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "planeSize"), lua_isnumber(L, -1)) + c.planeSize = (float)lua_tonumber(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "waterColor"), lua_istable(L, -1)) + c.waterColor = readColourValue(L, lua_gettop(L)); + lua_pop(L, 1); + if (lua_getfield(L, idx, "reflectivity"), lua_isnumber(L, -1)) + c.reflectivity = (float)lua_tonumber(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "waveSpeed"), lua_isnumber(L, -1)) + c.waveSpeed = (float)lua_tonumber(L, -1); + lua_pop(L, 1);); + + // --- WaterPhysics --- + REGISTER_COMPONENT( + WaterPhysics, "WaterPhysics", + lua_pushnumber(L, c.waterSurfaceY); + lua_setfield(L, -2, "waterSurfaceY"); + lua_pushnumber(L, c.defaultBuoyancy); + lua_setfield(L, -2, "defaultBuoyancy"); + lua_pushnumber(L, c.defaultLinearDrag); + lua_setfield(L, -2, "defaultLinearDrag"); + lua_pushnumber(L, c.defaultAngularDrag); + lua_setfield(L, -2, "defaultAngularDrag"); + lua_pushboolean(L, c.enabled ? 1 : 0); + lua_setfield(L, -2, "enabled"); + , + if (lua_getfield(L, idx, "waterSurfaceY"), lua_isnumber(L, -1)) + c.waterSurfaceY = (float)lua_tonumber(L, -1); + lua_pop(L, 1); if (lua_getfield(L, idx, "defaultBuoyancy"), + lua_isnumber(L, -1)) c.defaultBuoyancy = + (float)lua_tonumber(L, -1); + lua_pop(L, 1); if (lua_getfield(L, idx, "defaultLinearDrag"), + lua_isnumber(L, -1)) c.defaultLinearDrag = + (float)lua_tonumber(L, -1); + lua_pop(L, 1); if (lua_getfield(L, idx, "defaultAngularDrag"), + lua_isnumber(L, -1)) c.defaultAngularDrag = + (float)lua_tonumber(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "enabled"), lua_isboolean(L, -1)) + c.enabled = lua_toboolean(L, -1) != 0; + lua_pop(L, 1);); + + // --- BuoyancyInfo --- + REGISTER_COMPONENT( + BuoyancyInfo, "BuoyancyInfo", + lua_pushboolean(L, c.enabled ? 1 : 0); + lua_setfield(L, -2, "enabled"); lua_pushnumber(L, c.buoyancy); + lua_setfield(L, -2, "buoyancy"); + lua_pushnumber(L, c.linearDrag); + lua_setfield(L, -2, "linearDrag"); + lua_pushnumber(L, c.angularDrag); + lua_setfield(L, -2, "angularDrag"); + lua_pushnumber(L, c.waterSurfaceY); + lua_setfield(L, -2, "waterSurfaceY"); + lua_pushnumber(L, c.submergedThreshold); + lua_setfield(L, -2, "submergedThreshold"); + , if (lua_getfield(L, idx, "enabled"), lua_isboolean(L, -1)) + c.enabled = lua_toboolean(L, -1) != 0; + lua_pop(L, 1); + if (lua_getfield(L, idx, "buoyancy"), lua_isnumber(L, -1)) + c.buoyancy = (float)lua_tonumber(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "linearDrag"), lua_isnumber(L, -1)) + c.linearDrag = (float)lua_tonumber(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "angularDrag"), lua_isnumber(L, -1)) + c.angularDrag = (float)lua_tonumber(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "waterSurfaceY"), lua_isnumber(L, -1)) + c.waterSurfaceY = (float)lua_tonumber(L, -1); + lua_pop(L, 1); if (lua_getfield(L, idx, "submergedThreshold"), + lua_isnumber(L, -1)) c.submergedThreshold = + (float)lua_tonumber(L, -1); + lua_pop(L, 1);); + + // --- InWater (tag) --- + s_components["InWater"] = { + "InWater", + [](lua_State *L, flecs::entity e) { + lua_pushboolean(L, e.has() ? 1 : 0); + }, + [](lua_State *L, flecs::entity e, int idx) { + (void)idx; + if (lua_toboolean(L, -1)) + e.add(); + else + e.remove(); + } + }; + + // --- StartupMenu --- + REGISTER_COMPONENT( + StartupMenuComponent, "StartupMenu", + lua_pushstring(L, c.fontName.c_str()); + lua_setfield(L, -2, "fontName"); lua_pushnumber(L, c.fontSize); + lua_setfield(L, -2, "fontSize"); + lua_pushstring(L, c.newGameScene.c_str()); + lua_setfield(L, -2, "newGameScene"); + lua_pushboolean(L, c.showLoadGame ? 1 : 0); + lua_setfield(L, -2, "showLoadGame"); + lua_pushboolean(L, c.showOptions ? 1 : 0); + lua_setfield(L, -2, "showOptions"); + lua_pushboolean(L, c.showQuit ? 1 : 0); + lua_setfield(L, -2, "showQuit"); + , if (lua_getfield(L, idx, "fontName"), lua_isstring(L, -1)) + c.fontName = lua_tostring(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "fontSize"), lua_isnumber(L, -1)) + c.fontSize = (float)lua_tonumber(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "newGameScene"), lua_isstring(L, -1)) + c.newGameScene = lua_tostring(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "showLoadGame"), lua_isboolean(L, -1)) + c.showLoadGame = lua_toboolean(L, -1) != 0; + lua_pop(L, 1); + if (lua_getfield(L, idx, "showOptions"), lua_isboolean(L, -1)) + c.showOptions = lua_toboolean(L, -1) != 0; + lua_pop(L, 1); + if (lua_getfield(L, idx, "showQuit"), lua_isboolean(L, -1)) + c.showQuit = lua_toboolean(L, -1) != 0; + lua_pop(L, 1);); + + // --- Dialogue --- + REGISTER_COMPONENT( + DialogueComponent, "Dialogue", + lua_pushstring(L, c.text.c_str()); + lua_setfield(L, -2, "text"); + lua_pushstring(L, c.speaker.c_str()); + lua_setfield(L, -2, "speaker"); + lua_pushstring(L, c.fontName.c_str()); + lua_setfield(L, -2, "fontName"); lua_pushnumber(L, c.fontSize); + lua_setfield(L, -2, "fontSize"); + lua_pushnumber(L, c.backgroundOpacity); + lua_setfield(L, -2, "backgroundOpacity"); + lua_pushnumber(L, c.boxHeightFraction); + lua_setfield(L, -2, "boxHeightFraction"); + lua_pushnumber(L, c.boxPositionFraction); + lua_setfield(L, -2, "boxPositionFraction"); + lua_pushboolean(L, c.enabled ? 1 : 0); + lua_setfield(L, -2, "enabled"); + , if (lua_getfield(L, idx, "text"), lua_isstring(L, -1)) + c.text = lua_tostring(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "speaker"), lua_isstring(L, -1)) + c.speaker = lua_tostring(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "fontName"), lua_isstring(L, -1)) + c.fontName = lua_tostring(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "fontSize"), lua_isnumber(L, -1)) + c.fontSize = (float)lua_tonumber(L, -1); + lua_pop(L, 1); if (lua_getfield(L, idx, "backgroundOpacity"), + lua_isnumber(L, -1)) c.backgroundOpacity = + (float)lua_tonumber(L, -1); + lua_pop(L, 1); if (lua_getfield(L, idx, "boxHeightFraction"), + lua_isnumber(L, -1)) c.boxHeightFraction = + (float)lua_tonumber(L, -1); + lua_pop(L, 1); if (lua_getfield(L, idx, "boxPositionFraction"), + lua_isnumber(L, -1)) c.boxPositionFraction = + (float)lua_tonumber(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "enabled"), lua_isboolean(L, -1)) + c.enabled = lua_toboolean(L, -1) != 0; + lua_pop(L, 1);); + + // --- PlayerController --- + REGISTER_COMPONENT( + PlayerControllerComponent, "PlayerController", + lua_pushinteger(L, c.cameraMode); + lua_setfield(L, -2, "cameraMode"); + lua_pushstring(L, c.targetCharacterName.c_str()); + lua_setfield(L, -2, "targetCharacterName"); + lua_pushnumber(L, c.tpsDistance); + lua_setfield(L, -2, "tpsDistance"); + lua_pushnumber(L, c.tpsHeight); + lua_setfield(L, -2, "tpsHeight"); + lua_pushnumber(L, c.mouseSensitivity); + lua_setfield(L, -2, "mouseSensitivity"); + lua_pushnumber(L, c.actuatorDistance); + lua_setfield(L, -2, "actuatorDistance"); + lua_pushnumber(L, c.actuatorCooldown); + lua_setfield(L, -2, "actuatorCooldown"); + , if (lua_getfield(L, idx, "cameraMode"), lua_isnumber(L, -1)) + c.cameraMode = (int)lua_tointeger(L, -1); + lua_pop(L, 1); if (lua_getfield(L, idx, "targetCharacterName"), + lua_isstring(L, -1)) c.targetCharacterName = + lua_tostring(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "tpsDistance"), lua_isnumber(L, -1)) + c.tpsDistance = (float)lua_tonumber(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "tpsHeight"), lua_isnumber(L, -1)) + c.tpsHeight = (float)lua_tonumber(L, -1); + lua_pop(L, 1); if (lua_getfield(L, idx, "mouseSensitivity"), + lua_isnumber(L, -1)) c.mouseSensitivity = + (float)lua_tonumber(L, -1); + lua_pop(L, 1); if (lua_getfield(L, idx, "actuatorDistance"), + lua_isnumber(L, -1)) c.actuatorDistance = + (float)lua_tonumber(L, -1); + lua_pop(L, 1); if (lua_getfield(L, idx, "actuatorCooldown"), + lua_isnumber(L, -1)) c.actuatorCooldown = + (float)lua_tonumber(L, -1); + lua_pop(L, 1);); + + // --- CellGrid --- + REGISTER_COMPONENT( + CellGridComponent, "CellGrid", lua_pushinteger(L, c.width); + lua_setfield(L, -2, "width"); lua_pushinteger(L, c.height); + lua_setfield(L, -2, "height"); lua_pushinteger(L, c.depth); + lua_setfield(L, -2, "depth"); lua_pushnumber(L, c.cellSize); + lua_setfield(L, -2, "cellSize"); + lua_pushnumber(L, c.cellHeight); + lua_setfield(L, -2, "cellHeight"); + , if (lua_getfield(L, idx, "width"), lua_isnumber(L, -1)) + c.width = (int)lua_tointeger(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "height"), lua_isnumber(L, -1)) + c.height = (int)lua_tointeger(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "depth"), lua_isnumber(L, -1)) + c.depth = (int)lua_tointeger(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "cellSize"), lua_isnumber(L, -1)) + c.cellSize = (float)lua_tonumber(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "cellHeight"), lua_isnumber(L, -1)) + c.cellHeight = (float)lua_tonumber(L, -1); + lua_pop(L, 1);); + + // --- Room --- + REGISTER_COMPONENT( + RoomComponent, "Room", lua_pushinteger(L, c.minX); + lua_setfield(L, -2, "minX"); lua_pushinteger(L, c.minY); + lua_setfield(L, -2, "minY"); lua_pushinteger(L, c.minZ); + lua_setfield(L, -2, "minZ"); lua_pushinteger(L, c.maxX); + lua_setfield(L, -2, "maxX"); lua_pushinteger(L, c.maxY); + lua_setfield(L, -2, "maxY"); lua_pushinteger(L, c.maxZ); + lua_setfield(L, -2, "maxZ"); + lua_pushstring(L, c.roomType.c_str()); + lua_setfield(L, -2, "roomType"); + lua_pushboolean(L, c.createFloor ? 1 : 0); + lua_setfield(L, -2, "createFloor"); + lua_pushboolean(L, c.createCeiling ? 1 : 0); + lua_setfield(L, -2, "createCeiling"); + lua_pushboolean(L, c.createInteriorWalls ? 1 : 0); + lua_setfield(L, -2, "createInteriorWalls"); + lua_pushboolean(L, c.createWindows ? 1 : 0); + lua_setfield(L, -2, "createWindows"); + , if (lua_getfield(L, idx, "minX"), lua_isnumber(L, -1)) + c.minX = (int)lua_tointeger(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "minY"), lua_isnumber(L, -1)) + c.minY = (int)lua_tointeger(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "minZ"), lua_isnumber(L, -1)) + c.minZ = (int)lua_tointeger(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "maxX"), lua_isnumber(L, -1)) + c.maxX = (int)lua_tointeger(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "maxY"), lua_isnumber(L, -1)) + c.maxY = (int)lua_tointeger(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "maxZ"), lua_isnumber(L, -1)) + c.maxZ = (int)lua_tointeger(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "roomType"), lua_isstring(L, -1)) + c.roomType = lua_tostring(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "createFloor"), lua_isboolean(L, -1)) + c.createFloor = lua_toboolean(L, -1) != 0; + lua_pop(L, 1); + if (lua_getfield(L, idx, "createCeiling"), lua_isboolean(L, -1)) + c.createCeiling = lua_toboolean(L, -1) != 0; + lua_pop(L, 1); if (lua_getfield(L, idx, "createInteriorWalls"), + lua_isboolean(L, -1)) c.createInteriorWalls = + lua_toboolean(L, -1) != 0; + lua_pop(L, 1); + if (lua_getfield(L, idx, "createWindows"), lua_isboolean(L, -1)) + c.createWindows = lua_toboolean(L, -1) != 0; + lua_pop(L, 1);); + + // --- ClearArea --- + REGISTER_COMPONENT( + ClearAreaComponent, "ClearArea", lua_pushinteger(L, c.minX); + lua_setfield(L, -2, "minX"); lua_pushinteger(L, c.minY); + lua_setfield(L, -2, "minY"); lua_pushinteger(L, c.minZ); + lua_setfield(L, -2, "minZ"); lua_pushinteger(L, c.maxX); + lua_setfield(L, -2, "maxX"); lua_pushinteger(L, c.maxY); + lua_setfield(L, -2, "maxY"); lua_pushinteger(L, c.maxZ); + lua_setfield(L, -2, "maxZ"); + lua_pushboolean(L, c.clearCells ? 1 : 0); + lua_setfield(L, -2, "clearCells"); + lua_pushboolean(L, c.clearFurniture ? 1 : 0); + lua_setfield(L, -2, "clearFurniture"); + , if (lua_getfield(L, idx, "minX"), lua_isnumber(L, -1)) + c.minX = (int)lua_tointeger(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "minY"), lua_isnumber(L, -1)) + c.minY = (int)lua_tointeger(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "minZ"), lua_isnumber(L, -1)) + c.minZ = (int)lua_tointeger(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "maxX"), lua_isnumber(L, -1)) + c.maxX = (int)lua_tointeger(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "maxY"), lua_isnumber(L, -1)) + c.maxY = (int)lua_tointeger(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "maxZ"), lua_isnumber(L, -1)) + c.maxZ = (int)lua_tointeger(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "clearCells"), lua_isboolean(L, -1)) + c.clearCells = lua_toboolean(L, -1) != 0; + lua_pop(L, 1); if (lua_getfield(L, idx, "clearFurniture"), + lua_isboolean(L, -1)) c.clearFurniture = + lua_toboolean(L, -1) != 0; + lua_pop(L, 1);); + + // --- Roof --- + REGISTER_COMPONENT( + RoofComponent, "Roof", lua_pushinteger(L, (int)c.type); + lua_setfield(L, -2, "type"); lua_pushinteger(L, c.posX); + lua_setfield(L, -2, "posX"); lua_pushinteger(L, c.posY); + lua_setfield(L, -2, "posY"); lua_pushinteger(L, c.posZ); + lua_setfield(L, -2, "posZ"); lua_pushinteger(L, c.sizeX); + lua_setfield(L, -2, "sizeX"); lua_pushinteger(L, c.sizeZ); + lua_setfield(L, -2, "sizeZ"); lua_pushnumber(L, c.baseHeight); + lua_setfield(L, -2, "baseHeight"); + lua_pushnumber(L, c.maxHeight); + lua_setfield(L, -2, "maxHeight"); + , if (lua_getfield(L, idx, "type"), lua_isnumber(L, -1)) + c.type = (RoofComponent::Type)lua_tointeger(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "posX"), lua_isnumber(L, -1)) + c.posX = (int)lua_tointeger(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "posY"), lua_isnumber(L, -1)) + c.posY = (int)lua_tointeger(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "posZ"), lua_isnumber(L, -1)) + c.posZ = (int)lua_tointeger(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "sizeX"), lua_isnumber(L, -1)) + c.sizeX = (int)lua_tointeger(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "sizeZ"), lua_isnumber(L, -1)) + c.sizeZ = (int)lua_tointeger(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "baseHeight"), lua_isnumber(L, -1)) + c.baseHeight = (float)lua_tonumber(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "maxHeight"), lua_isnumber(L, -1)) + c.maxHeight = (float)lua_tonumber(L, -1); + lua_pop(L, 1);); + + // --- Lot --- + REGISTER_COMPONENT( + LotComponent, "Lot", lua_pushinteger(L, c.width); + lua_setfield(L, -2, "width"); lua_pushinteger(L, c.depth); + lua_setfield(L, -2, "depth"); lua_pushnumber(L, c.elevation); + lua_setfield(L, -2, "elevation"); lua_pushnumber(L, c.angle); + lua_setfield(L, -2, "angle"); + lua_pushstring(L, c.templateName.c_str()); + lua_setfield(L, -2, "templateName"); + , if (lua_getfield(L, idx, "width"), lua_isnumber(L, -1)) + c.width = (int)lua_tointeger(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "depth"), lua_isnumber(L, -1)) + c.depth = (int)lua_tointeger(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "elevation"), lua_isnumber(L, -1)) + c.elevation = (float)lua_tonumber(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "angle"), lua_isnumber(L, -1)) + c.angle = (float)lua_tonumber(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "templateName"), lua_isstring(L, -1)) + c.templateName = lua_tostring(L, -1); + lua_pop(L, 1);); + + // --- District --- + REGISTER_COMPONENT( + DistrictComponent, "District", lua_pushnumber(L, c.radius); + lua_setfield(L, -2, "radius"); lua_pushnumber(L, c.elevation); + lua_setfield(L, -2, "elevation"); lua_pushnumber(L, c.height); + lua_setfield(L, -2, "height"); + lua_pushboolean(L, c.isPlaza ? 1 : 0); + lua_setfield(L, -2, "isPlaza"); + , if (lua_getfield(L, idx, "radius"), lua_isnumber(L, -1)) + c.radius = (float)lua_tonumber(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "elevation"), lua_isnumber(L, -1)) + c.elevation = (float)lua_tonumber(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "height"), lua_isnumber(L, -1)) + c.height = (float)lua_tonumber(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "isPlaza"), lua_isboolean(L, -1)) + c.isPlaza = lua_toboolean(L, -1) != 0; + lua_pop(L, 1);); + + // --- Town --- + REGISTER_COMPONENT( + TownComponent, "Town", lua_pushstring(L, c.townName.c_str()); + lua_setfield(L, -2, "townName"); + , if (lua_getfield(L, idx, "townName"), lua_isstring(L, -1)) + c.townName = lua_tostring(L, -1); + lua_pop(L, 1);); + + // --- FurnitureTemplate --- + REGISTER_COMPONENT( + FurnitureTemplateComponent, "FurnitureTemplate", + lua_pushstring(L, c.templateName.c_str()); + lua_setfield(L, -2, "templateName"); + lua_pushstring(L, c.meshName.c_str()); + lua_setfield(L, -2, "meshName"); + lua_pushstring(L, c.materialName.c_str()); + lua_setfield(L, -2, "materialName"); + lua_pushboolean(L, c.requiresWall ? 1 : 0); + lua_setfield(L, -2, "requiresWall"); + lua_pushboolean(L, c.requiresFloor ? 1 : 0); + lua_setfield(L, -2, "requiresFloor"); + lua_pushboolean(L, c.blocksPath ? 1 : 0); + lua_setfield(L, -2, "blocksPath"); lua_pushnumber(L, c.weight); + lua_setfield(L, -2, "weight"); + , if (lua_getfield(L, idx, "templateName"), lua_isstring(L, -1)) + c.templateName = lua_tostring(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "meshName"), lua_isstring(L, -1)) + c.meshName = lua_tostring(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "materialName"), lua_isstring(L, -1)) + c.materialName = lua_tostring(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "requiresWall"), lua_isboolean(L, -1)) + c.requiresWall = lua_toboolean(L, -1) != 0; + lua_pop(L, 1); + if (lua_getfield(L, idx, "requiresFloor"), lua_isboolean(L, -1)) + c.requiresFloor = lua_toboolean(L, -1) != 0; + lua_pop(L, 1); + if (lua_getfield(L, idx, "blocksPath"), lua_isboolean(L, -1)) + c.blocksPath = lua_toboolean(L, -1) != 0; + lua_pop(L, 1); + if (lua_getfield(L, idx, "weight"), lua_isnumber(L, -1)) + c.weight = (float)lua_tonumber(L, -1); + lua_pop(L, 1);); + + // --- PrefabInstance --- + REGISTER_COMPONENT( + PrefabInstanceComponent, "PrefabInstance", + lua_pushstring(L, c.prefabPath.c_str()); + lua_setfield(L, -2, "prefabPath"); + lua_pushboolean(L, c.instantiated ? 1 : 0); + lua_setfield(L, -2, "instantiated"); + , if (lua_getfield(L, idx, "prefabPath"), lua_isstring(L, -1)) + c.prefabPath = lua_tostring(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "instantiated"), lua_isboolean(L, -1)) + c.instantiated = lua_toboolean(L, -1) != 0; + lua_pop(L, 1);); + + // --- GoapPlanner --- + REGISTER_COMPONENT( + GoapPlannerComponent, "GoapPlanner", + pushStringVector(L, c.actionNames); + lua_setfield(L, -2, "actionNames"); + pushStringVector(L, c.goalNames); + lua_setfield(L, -2, "goalNames"); + lua_pushnumber(L, c.smartObjectDistance); + lua_setfield(L, -2, "smartObjectDistance"); + lua_pushboolean(L, c.includeSmartObjects ? 1 : 0); + lua_setfield(L, -2, "includeSmartObjects"); + lua_pushstring(L, c.actionDatabaseRef.c_str()); + lua_setfield(L, -2, "actionDatabaseRef"); + lua_pushboolean(L, c.planDirty ? 1 : 0); + lua_setfield(L, -2, "planDirty"); + lua_pushinteger(L, c.maxPlans); lua_setfield(L, -2, "maxPlans"); + , if (lua_getfield(L, idx, "actionNames"), lua_istable(L, -1)) + c.actionNames = readStringVector(L, lua_gettop(L)); + lua_pop(L, 1); + if (lua_getfield(L, idx, "goalNames"), lua_istable(L, -1)) + c.goalNames = readStringVector(L, lua_gettop(L)); + lua_pop(L, 1); if (lua_getfield(L, idx, "smartObjectDistance"), + lua_isnumber(L, -1)) c.smartObjectDistance = + (float)lua_tonumber(L, -1); + lua_pop(L, 1); if (lua_getfield(L, idx, "includeSmartObjects"), + lua_isboolean(L, -1)) c.includeSmartObjects = + lua_toboolean(L, -1) != 0; + lua_pop(L, 1); if (lua_getfield(L, idx, "actionDatabaseRef"), + lua_isstring(L, -1)) c.actionDatabaseRef = + lua_tostring(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "planDirty"), lua_isboolean(L, -1)) + c.planDirty = lua_toboolean(L, -1) != 0; + lua_pop(L, 1); + if (lua_getfield(L, idx, "maxPlans"), lua_isnumber(L, -1)) + c.maxPlans = (int)lua_tointeger(L, -1); + lua_pop(L, 1);); + + // --- GoapRunner --- + REGISTER_COMPONENT( + GoapRunnerComponent, "GoapRunner", + lua_pushinteger(L, (int)c.state); + lua_setfield(L, -2, "state"); + lua_pushinteger(L, c.currentActionIndex); + lua_setfield(L, -2, "currentActionIndex"); + lua_pushstring(L, c.currentActionName.c_str()); + lua_setfield(L, -2, "currentActionName"); + lua_pushnumber(L, c.actionTimer); + lua_setfield(L, -2, "actionTimer"); + lua_pushboolean(L, c.autoReplan ? 1 : 0); + lua_setfield(L, -2, "autoReplan"); + , if (lua_getfield(L, idx, "state"), lua_isnumber(L, -1)) + c.state = + (GoapRunnerComponent::State)lua_tointeger(L, -1); + lua_pop(L, 1); if (lua_getfield(L, idx, "currentActionIndex"), + lua_isnumber(L, -1)) c.currentActionIndex = + (int)lua_tointeger(L, -1); + lua_pop(L, 1); if (lua_getfield(L, idx, "currentActionName"), + lua_isstring(L, -1)) c.currentActionName = + lua_tostring(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "actionTimer"), lua_isnumber(L, -1)) + c.actionTimer = (float)lua_tonumber(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "autoReplan"), lua_isboolean(L, -1)) + c.autoReplan = lua_toboolean(L, -1) != 0; + lua_pop(L, 1);); + + // --- ActionDatabase --- + REGISTER_COMPONENT(ActionDatabase, "ActionDatabase", + lua_pushinteger(L, (int)c.actions.size()); + lua_setfield(L, -2, "numActions"); + lua_pushinteger(L, (int)c.goals.size()); + lua_setfield(L, -2, "numGoals"); + /* end PushBody */ + , (void)idx;); + + // --- ActionDebug --- + REGISTER_COMPONENT( + ActionDebug, "ActionDebug", + lua_pushboolean(L, c.isRunning ? 1 : 0); + lua_setfield(L, -2, "isRunning"); + lua_pushstring(L, c.selectedActionName.c_str()); + lua_setfield(L, -2, "selectedActionName"); + lua_pushstring(L, c.selectedGoalName.c_str()); + lua_setfield(L, -2, "selectedGoalName"); + lua_pushstring(L, c.currentActionName.c_str()); + lua_setfield(L, -2, "currentActionName"); + lua_pushstring(L, c.lastResult.c_str()); + lua_setfield(L, -2, "lastResult"); + , if (lua_getfield(L, idx, "isRunning"), lua_isboolean(L, -1)) + c.isRunning = lua_toboolean(L, -1) != 0; + lua_pop(L, 1); if (lua_getfield(L, idx, "selectedActionName"), + lua_isstring(L, -1)) c.selectedActionName = + lua_tostring(L, -1); + lua_pop(L, 1); if (lua_getfield(L, idx, "selectedGoalName"), + lua_isstring(L, -1)) + c.selectedGoalName = lua_tostring(L, -1); + lua_pop(L, 1); if (lua_getfield(L, idx, "currentActionName"), + lua_isstring(L, -1)) c.currentActionName = + lua_tostring(L, -1); + lua_pop(L, 1); + if (lua_getfield(L, idx, "lastResult"), lua_isstring(L, -1)) + c.lastResult = lua_tostring(L, -1); + lua_pop(L, 1);); +} + +// --------------------------------------------------------------------------- +// Helper: get a Flecs entity from a Lua integer ID on the stack +// --------------------------------------------------------------------------- + +/** + * @brief Read a Lua integer entity ID from the stack and return the + * corresponding Flecs entity. + * + * The first argument on the Lua stack must be an integer entity ID + * that was previously returned by ecs.create_entity(). + * + * @param L Lua state. + * @return flecs::entity The corresponding Flecs entity. + */ +static flecs::entity getEntity(lua_State *L) +{ + int id = (int)luaL_checkinteger(L, 1); + return luaIdToEntity(id); +} + +// --------------------------------------------------------------------------- +// Lua C-callable functions +// --------------------------------------------------------------------------- + +/** + * @brief Lua: ecs.add_component(entity_id, "ComponentName") + * + * Adds a component to an entity. If the component has fields, they are + * initialized to defaults. + */ +static int luaAddComponent(lua_State *L) +{ + flecs::entity e = getEntity(L); + const char *name = luaL_checkstring(L, 2); + + auto it = s_components.find(name); + if (it == s_components.end()) { + luaL_error(L, "Unknown component: %s", name); + return 0; + } + + // Add the component by calling readFields with an empty table + // to set defaults, then pushFields to get the component + it->second.readFields(L, e, -1); + return 0; +} + +/** + * @brief Lua: ecs.remove_component(entity_id, "ComponentName") + * + * Removes a component from an entity. + */ +static int luaRemoveComponent(lua_State *L) +{ + flecs::entity e = getEntity(L); + const char *name = luaL_checkstring(L, 2); + + auto it = s_components.find(name); + if (it == s_components.end()) { + luaL_error(L, "Unknown component: %s", name); + return 0; + } + + // Remove by calling readFields with nil to clear + lua_pushnil(L); + it->second.readFields(L, e, lua_gettop(L)); + lua_pop(L, 1); + return 0; +} + +/** + * @brief Lua: ecs.has_component(entity_id, "ComponentName") -> bool + * + * Checks if an entity has a component. + */ +static int luaHasComponent(lua_State *L) +{ + flecs::entity e = getEntity(L); + const char *name = luaL_checkstring(L, 2); + + auto it = s_components.find(name); + if (it == s_components.end()) { + lua_pushboolean(L, 0); + return 1; + } + + it->second.pushFields(L, e); + // pushFields pushes nil if not present, or a table if present + bool has = !lua_isnil(L, -1); + lua_pop(L, 1); + lua_pushboolean(L, has ? 1 : 0); + return 1; +} + +/** + * @brief Lua: ecs.get_component(entity_id, "ComponentName") -> table + * + * Returns a table of all fields for a component, or nil if not present. + */ +static int luaGetComponent(lua_State *L) +{ + flecs::entity e = getEntity(L); + const char *name = luaL_checkstring(L, 2); + + auto it = s_components.find(name); + if (it == s_components.end()) { + lua_pushnil(L); + return 1; + } + + it->second.pushFields(L, e); + return 1; +} + +/** + * @brief Lua: ecs.set_component(entity_id, "ComponentName", table) + * + * Sets all fields of a component from a Lua table. + */ +static int luaSetComponent(lua_State *L) +{ + flecs::entity e = getEntity(L); + const char *name = luaL_checkstring(L, 2); + luaL_checktype(L, 3, LUA_TTABLE); + + auto it = s_components.find(name); + if (it == s_components.end()) { + luaL_error(L, "Unknown component: %s", name); + return 0; + } + + it->second.readFields(L, e, 3); + return 0; +} + +/** + * @brief Lua: ecs.get_field(entity_id, "ComponentName", "fieldName") -> value + * + * Gets a single field value from a component. + */ +static int luaGetField(lua_State *L) +{ + flecs::entity e = getEntity(L); + const char *name = luaL_checkstring(L, 2); + const char *field = luaL_checkstring(L, 3); + + auto it = s_components.find(name); + if (it == s_components.end()) { + lua_pushnil(L); + return 1; + } + + // Push the component table, then get the field + it->second.pushFields(L, e); + if (lua_isnil(L, -1)) { + return 1; + } + lua_getfield(L, -1, field); + // Remove the component table, keep the field value + lua_remove(L, -2); + return 1; +} + +/** + * @brief Lua: ecs.set_field(entity_id, "ComponentName", "fieldName", value) + * + * Sets a single field value on a component. + */ +static int luaSetField(lua_State *L) +{ + flecs::entity e = getEntity(L); + const char *name = luaL_checkstring(L, 2); + const char *field = luaL_checkstring(L, 3); + + auto it = s_components.find(name); + if (it == s_components.end()) { + luaL_error(L, "Unknown component: %s", name); + return 0; + } + + // Get current component as table, set the field, write back + it->second.pushFields(L, e); + if (lua_isnil(L, -1)) { + // Component doesn't exist yet, create a new table + lua_newtable(L); + } + // Copy value from stack position 4 into the table + lua_pushvalue(L, 4); + lua_setfield(L, -2, field); + // Write back + it->second.readFields(L, e, lua_gettop(L)); + lua_pop(L, 1); + return 0; +} + +// --------------------------------------------------------------------------- +// Public registration function +// --------------------------------------------------------------------------- + +void registerLuaComponentApi(lua_State *L) +{ + registerAllComponents(); + + // Get or create the "ecs" global table + lua_getglobal(L, "ecs"); + if (lua_isnil(L, -1)) { + lua_newtable(L); + lua_setglobal(L, "ecs"); + lua_getglobal(L, "ecs"); + } + + // Register functions + lua_pushcfunction(L, luaAddComponent); + lua_setfield(L, -2, "add_component"); + + lua_pushcfunction(L, luaRemoveComponent); + lua_setfield(L, -2, "remove_component"); + + lua_pushcfunction(L, luaHasComponent); + lua_setfield(L, -2, "has_component"); + + lua_pushcfunction(L, luaGetComponent); + lua_setfield(L, -2, "get_component"); + + lua_pushcfunction(L, luaSetComponent); + lua_setfield(L, -2, "set_component"); + + lua_pushcfunction(L, luaGetField); + lua_setfield(L, -2, "get_field"); + + lua_pushcfunction(L, luaSetField); + lua_setfield(L, -2, "set_field"); + + // Pop the ecs table + lua_pop(L, 1); +} + +} // namespace editScene diff --git a/src/features/editScene/lua/LuaComponentApi.hpp b/src/features/editScene/lua/LuaComponentApi.hpp new file mode 100644 index 0000000..8357ec9 --- /dev/null +++ b/src/features/editScene/lua/LuaComponentApi.hpp @@ -0,0 +1,55 @@ +#ifndef EDITSCENE_LUA_COMPONENT_API_HPP +#define EDITSCENE_LUA_COMPONENT_API_HPP +#pragma once + +#include + +/** + * @file LuaComponentApi.hpp + * @brief Lua API for adding, removing, and modifying ECS components. + * + * Provides a generic component API where Lua scripts can add, get, set, + * and remove components on entities. Each component type is registered + * with a name string and a set of field accessors. + * + * Exposed Lua globals (in the "ecs" table): + * ecs.add_component(id, "ComponentName") -> nil + * ecs.remove_component(id, "ComponentName") -> nil + * ecs.has_component(id, "ComponentName") -> bool + * ecs.get_component(id, "ComponentName") -> table (field -> value) + * ecs.set_component(id, "ComponentName", table) -> nil + * ecs.get_field(id, "ComponentName", "fieldName") -> value + * ecs.set_field(id, "ComponentName", "fieldName", value) -> nil + * + * Supported component names (case-sensitive): + * "EntityName", "Transform", "Renderable", "Light", "Camera", + * "RigidBody", "PhysicsCollider", "Character", "CharacterSlots", + * "AnimationTree", "AnimationTreeTemplate", "BehaviorTree", + * "GoapBlackboard", "GoapAction", "GoapGoal", "ActionDatabase", + * "ActionDebug", "SmartObject", "Actuator", "EventHandler", + * "GoapPlanner", "GoapRunner", "PathFollowing", "NavMesh", + * "NavMeshGeometrySource", "NavMeshAgent", "Item", "Inventory", + * "Lod", "LodSettings", "StaticGeometry", "StaticGeometryMember", + * "ProceduralTexture", "ProceduralMaterial", "Primitive", + * "TriangleBuffer", "Sun", "Skybox", "WaterPlane", "WaterPhysics", + * "BuoyancyInfo", "StartupMenu", "Dialogue", "PlayerController", + * "CellGrid", "Room", "ClearArea", "Roof", "Lot", "District", + * "Town", "FurnitureTemplate", "PrefabInstance", "EditorMarker" + */ + +namespace editScene +{ + +/** + * @brief Register all component Lua API functions into the "ecs" global table. + * + * Adds functions for component manipulation (add, remove, has, get, set, + * get_field, set_field) to the existing "ecs" table. + * + * @param L The Lua state. + */ +void registerLuaComponentApi(lua_State *L); + +} // namespace editScene + +#endif // EDITSCENE_LUA_COMPONENT_API_HPP diff --git a/src/features/editScene/lua/LuaEntityApi.cpp b/src/features/editScene/lua/LuaEntityApi.cpp new file mode 100644 index 0000000..de17566 --- /dev/null +++ b/src/features/editScene/lua/LuaEntityApi.cpp @@ -0,0 +1,236 @@ +#include "LuaEntityApi.hpp" +#include "components/EditorMarker.hpp" +#include +#include + +namespace editScene +{ + +// Global entity ID map +LuaEntityIdMap g_luaEntityIdMap; + +int luaEntityToId(flecs::entity e) +{ + if (!e.is_valid()) + return -1; + return g_luaEntityIdMap.addEntity(e); +} + +flecs::entity luaIdToEntity(int id) +{ + return g_luaEntityIdMap.getEntity(id); +} + +// --------------------------------------------------------------------------- +// Helper: get the Flecs world from the Lua registry +// --------------------------------------------------------------------------- + +static flecs::world getWorld(lua_State *L) +{ + lua_getfield(L, LUA_REGISTRYINDEX, "EditSceneFlecsWorld"); + OgreAssert(lua_islightuserdata(L, -1), "Flecs world not registered"); + flecs::world *world = + static_cast(lua_touserdata(L, -1)); + lua_pop(L, 1); + return *world; +} + +// --------------------------------------------------------------------------- +// Lua: ecs.create_entity() -> int (entity ID) +// Creates a new entity with EditorMarkerComponent and returns its Lua ID. +// --------------------------------------------------------------------------- + +static int luaCreateEntity(lua_State *L) +{ + flecs::world world = getWorld(L); + flecs::entity e = world.entity(); + // Add EditorMarkerComponent so it appears in the editor hierarchy + // (the component is forward-declared; we use a tag approach) + e.add(); + int id = g_luaEntityIdMap.addEntity(e); + lua_pushinteger(L, id); + return 1; +} + +// --------------------------------------------------------------------------- +// Lua: ecs.destroy_entity(id) -> nil +// Destroys an entity and removes it from the ID map. +// --------------------------------------------------------------------------- + +static int luaDestroyEntity(lua_State *L) +{ + luaL_checktype(L, 1, LUA_TNUMBER); + int id = lua_tointeger(L, 1); + flecs::entity e = g_luaEntityIdMap.getEntity(id); + if (e.is_alive()) { + g_luaEntityIdMap.removeEntity(e); + e.destruct(); + } + return 0; +} + +// --------------------------------------------------------------------------- +// Lua: ecs.entity_exists(id) -> bool +// --------------------------------------------------------------------------- + +static int luaEntityExists(lua_State *L) +{ + luaL_checktype(L, 1, LUA_TNUMBER); + int id = lua_tointeger(L, 1); + lua_pushboolean(L, g_luaEntityIdMap.hasId(id) ? 1 : 0); + return 1; +} + +// --------------------------------------------------------------------------- +// Lua: ecs.get_player_entity() -> int (entity ID) or nil +// Looks up the entity named "player" in the Flecs world. +// --------------------------------------------------------------------------- + +static int luaGetPlayerEntity(lua_State *L) +{ + flecs::world world = getWorld(L); + flecs::entity e = world.lookup("player"); + if (e.is_valid()) { + int id = g_luaEntityIdMap.addEntity(e); + lua_pushinteger(L, id); + } else { + lua_pushnil(L); + } + return 1; +} + +// --------------------------------------------------------------------------- +// Lua: ecs.get_entity_by_name(name) -> int (entity ID) or nil +// --------------------------------------------------------------------------- + +static int luaGetEntityByName(lua_State *L) +{ + luaL_checktype(L, 1, LUA_TSTRING); + const char *name = lua_tostring(L, 1); + flecs::world world = getWorld(L); + flecs::entity e = world.lookup(name); + if (e.is_valid()) { + int id = g_luaEntityIdMap.addEntity(e); + lua_pushinteger(L, id); + } else { + lua_pushnil(L); + } + return 1; +} + +// --------------------------------------------------------------------------- +// Lua: ecs.set_entity_name(id, name) -> nil +// --------------------------------------------------------------------------- + +static int luaSetEntityName(lua_State *L) +{ + luaL_checktype(L, 1, LUA_TNUMBER); + luaL_checktype(L, 2, LUA_TSTRING); + int id = lua_tointeger(L, 1); + const char *name = lua_tostring(L, 2); + flecs::entity e = g_luaEntityIdMap.getEntity(id); + e.set_name(name); + return 0; +} + +// --------------------------------------------------------------------------- +// Lua: ecs.get_entity_name(id) -> string or nil +// --------------------------------------------------------------------------- + +static int luaGetEntityName(lua_State *L) +{ + luaL_checktype(L, 1, LUA_TNUMBER); + int id = lua_tointeger(L, 1); + flecs::entity e = g_luaEntityIdMap.getEntity(id); + const char *name = e.name(); + if (name && name[0]) { + lua_pushstring(L, name); + } else { + lua_pushnil(L); + } + return 1; +} + +// --------------------------------------------------------------------------- +// Lua: ecs.parent(id) -> int (parent ID) or nil +// --------------------------------------------------------------------------- + +static int luaGetParent(lua_State *L) +{ + luaL_checktype(L, 1, LUA_TNUMBER); + int id = lua_tointeger(L, 1); + flecs::entity e = g_luaEntityIdMap.getEntity(id); + flecs::entity parent = e.parent(); + if (parent.is_valid()) { + int parentId = g_luaEntityIdMap.addEntity(parent); + lua_pushinteger(L, parentId); + } else { + lua_pushnil(L); + } + return 1; +} + +// --------------------------------------------------------------------------- +// Lua: ecs.children(id) -> table of child IDs +// --------------------------------------------------------------------------- + +static int luaGetChildren(lua_State *L) +{ + luaL_checktype(L, 1, LUA_TNUMBER); + int id = lua_tointeger(L, 1); + flecs::entity e = g_luaEntityIdMap.getEntity(id); + + lua_newtable(L); // result table + int index = 1; + e.children([&](flecs::entity child) { + int childId = g_luaEntityIdMap.addEntity(child); + lua_pushinteger(L, childId); + lua_rawseti(L, -2, index); + index++; + }); + return 1; +} + +// --------------------------------------------------------------------------- +// Register all entity API functions +// --------------------------------------------------------------------------- + +void registerLuaEntityApi(lua_State *L) +{ + // Create the "ecs" global table + lua_newtable(L); + + // Entity management + lua_pushcfunction(L, luaCreateEntity); + lua_setfield(L, -2, "create_entity"); + + lua_pushcfunction(L, luaDestroyEntity); + lua_setfield(L, -2, "destroy_entity"); + + lua_pushcfunction(L, luaEntityExists); + lua_setfield(L, -2, "entity_exists"); + + lua_pushcfunction(L, luaGetPlayerEntity); + lua_setfield(L, -2, "get_player_entity"); + + lua_pushcfunction(L, luaGetEntityByName); + lua_setfield(L, -2, "get_entity_by_name"); + + lua_pushcfunction(L, luaSetEntityName); + lua_setfield(L, -2, "set_entity_name"); + + lua_pushcfunction(L, luaGetEntityName); + lua_setfield(L, -2, "get_entity_name"); + + // Hierarchy + lua_pushcfunction(L, luaGetParent); + lua_setfield(L, -2, "parent"); + + lua_pushcfunction(L, luaGetChildren); + lua_setfield(L, -2, "children"); + + // Set the global + lua_setglobal(L, "ecs"); +} + +} // namespace editScene diff --git a/src/features/editScene/lua/LuaEntityApi.hpp b/src/features/editScene/lua/LuaEntityApi.hpp new file mode 100644 index 0000000..e6a72d8 --- /dev/null +++ b/src/features/editScene/lua/LuaEntityApi.hpp @@ -0,0 +1,126 @@ +#ifndef EDITSCENE_LUA_ENTITY_API_HPP +#define EDITSCENE_LUA_ENTITY_API_HPP +#pragma once + +#include +#include +#include +#include + +/** + * @file LuaEntityApi.hpp + * @brief Lua API for entity creation, destruction, and ID mapping. + * + * Provides a bidirectional mapping between Lua integer IDs and + * Flecs entity handles. Lua scripts reference entities by integer + * IDs rather than raw Flecs handles for safety and simplicity. + * + * Exposed Lua globals: + * ecs.create_entity() -> int (entity ID) + * ecs.destroy_entity(id) -> nil + * ecs.entity_exists(id) -> bool + * ecs.get_player_entity() -> int (entity ID) + * ecs.get_entity_by_name(name) -> int (entity ID) or nil + * ecs.set_entity_name(id, name)-> nil + * ecs.get_entity_name(id) -> string or nil + * ecs.parent(id) -> int (parent ID) or nil + * ecs.children(id) -> table of child IDs + */ + +namespace editScene +{ + +/** + * @brief Global bidirectional mapping between Lua integer IDs and Flecs entities. + * + * This is a singleton-like global so that all Lua API functions can + * access it without needing to pass it through every closure. + */ +struct LuaEntityIdMap { + std::unordered_map id2entity; + std::unordered_map entity2id; + int nextId = 0; + + /** @brief Get the next available integer ID. */ + int getNextId() + { + nextId++; + return nextId; + } + + /** + * @brief Add an entity to the map, returning its integer ID. + * If the entity is already mapped, returns the existing ID. + */ + int addEntity(flecs::entity e) + { + if (entity2id.find(e.id()) != entity2id.end()) + return entity2id[e.id()]; + int id = getNextId(); + id2entity[id] = e; + entity2id[e.id()] = id; + return id; + } + + /** + * @brief Get the Flecs entity for an integer ID. + * Asserts if the ID is not found or the entity is invalid. + */ + flecs::entity getEntity(int id) + { + auto it = id2entity.find(id); + OgreAssert(it != id2entity.end(), "Invalid entity ID"); + OgreAssert(it->second.is_valid(), "Entity is no longer valid"); + return it->second; + } + + /** + * @brief Remove an entity from the map. + */ + void removeEntity(flecs::entity e) + { + auto it = entity2id.find(e.id()); + if (it != entity2id.end()) { + id2entity.erase(it->second); + entity2id.erase(it); + } + } + + /** + * @brief Check if an integer ID is valid. + */ + bool hasId(int id) const + { + auto it = id2entity.find(id); + return it != id2entity.end() && it->second.is_valid(); + } +}; + +/** @brief Global entity ID map instance. */ +extern LuaEntityIdMap g_luaEntityIdMap; + +/** + * @brief Convert a Flecs entity to a Lua integer ID. + * @return The integer ID, or -1 if the entity is invalid. + */ +int luaEntityToId(flecs::entity e); + +/** + * @brief Convert a Lua integer ID to a Flecs entity. + * Asserts if the ID is invalid. + */ +flecs::entity luaIdToEntity(int id); + +/** + * @brief Register all entity-related Lua API functions into the global table. + * + * Creates the "ecs" global table (or adds to it) with entity management + * functions. Must be called after LuaState is constructed. + * + * @param L The Lua state. + */ +void registerLuaEntityApi(lua_State *L); + +} // namespace editScene + +#endif // EDITSCENE_LUA_ENTITY_API_HPP diff --git a/src/features/editScene/lua/LuaEventApi.cpp b/src/features/editScene/lua/LuaEventApi.cpp new file mode 100644 index 0000000..8bdec8a --- /dev/null +++ b/src/features/editScene/lua/LuaEventApi.cpp @@ -0,0 +1,320 @@ +#include "LuaEventApi.hpp" +#include "LuaEntityApi.hpp" +#include "../systems/EventBus.hpp" +#include "../components/GoapBlackboard.hpp" +#include +#include +#include + +namespace editScene +{ + +// --------------------------------------------------------------------------- +// Internal: subscription ID tracking for Lua-managed subscriptions +// --------------------------------------------------------------------------- + +/** + * @brief Map from Lua subscription IDs to EventBus ListenerIds. + * + * When Lua subscribes to an event, we store the mapping so that + * unsubscribe_event() can remove the correct listener. + */ +static std::unordered_map s_luaSubscriptions; +static int s_nextLuaSubId = 1; + +// --------------------------------------------------------------------------- +// Helper: push a GoapBlackboard as a Lua table +// --------------------------------------------------------------------------- + +/** + * @brief Push a GoapBlackboard as a Lua table with named fields. + * + * The resulting table has: + * .bits -> integer + * .mask -> integer + * .values -> {string -> int} + * .floatValues -> {string -> float} + * .vec3Values -> {string -> {x, y, z}} + * .stringValues -> {string -> string} + * + * @param L Lua state. + * @param bb The GoapBlackboard to convert. + */ +static void pushGoapBlackboard(lua_State *L, const GoapBlackboard &bb) +{ + lua_newtable(L); + + // bits and mask + lua_pushinteger(L, (lua_Integer)bb.bits); + lua_setfield(L, -2, "bits"); + + lua_pushinteger(L, (lua_Integer)bb.mask); + lua_setfield(L, -2, "mask"); + + // values (int map) + lua_newtable(L); + for (auto &kv : bb.values) { + lua_pushinteger(L, kv.second); + lua_setfield(L, -2, kv.first.c_str()); + } + lua_setfield(L, -2, "values"); + + // floatValues + lua_newtable(L); + for (auto &kv : bb.floatValues) { + lua_pushnumber(L, kv.second); + lua_setfield(L, -2, kv.first.c_str()); + } + lua_setfield(L, -2, "floatValues"); + + // vec3Values + lua_newtable(L); + for (auto &kv : bb.vec3Values) { + lua_newtable(L); + lua_pushnumber(L, kv.second.x); + lua_rawseti(L, -2, 1); + lua_pushnumber(L, kv.second.y); + lua_rawseti(L, -2, 2); + lua_pushnumber(L, kv.second.z); + lua_rawseti(L, -2, 3); + lua_setfield(L, -2, kv.first.c_str()); + } + lua_setfield(L, -2, "vec3Values"); + + // stringValues + lua_newtable(L); + for (auto &kv : bb.stringValues) { + lua_pushstring(L, kv.second.c_str()); + lua_setfield(L, -2, kv.first.c_str()); + } + lua_setfield(L, -2, "stringValues"); +} + +/** + * @brief Read a Lua table at the given index as a GoapBlackboard. + * + * Expects the same format as pushGoapBlackboard produces. + * + * @param L Lua state. + * @param idx Stack index of the table. + * @return GoapBlackboard populated from the table. + */ +static GoapBlackboard readGoapBlackboard(lua_State *L, int idx) +{ + GoapBlackboard bb; + + // bits + lua_getfield(L, idx, "bits"); + if (lua_isnumber(L, -1)) + bb.bits = (uint64_t)lua_tointeger(L, -1); + lua_pop(L, 1); + + // mask + lua_getfield(L, idx, "mask"); + if (lua_isnumber(L, -1)) + bb.mask = (uint64_t)lua_tointeger(L, -1); + lua_pop(L, 1); + + // values (int map) + lua_getfield(L, idx, "values"); + if (lua_istable(L, -1)) { + lua_pushnil(L); + while (lua_next(L, -2) != 0) { + if (lua_isstring(L, -2) && lua_isnumber(L, -1)) + bb.values[lua_tostring(L, -2)] = + (int)lua_tointeger(L, -1); + lua_pop(L, 1); + } + } + lua_pop(L, 1); + + // floatValues + lua_getfield(L, idx, "floatValues"); + if (lua_istable(L, -1)) { + lua_pushnil(L); + while (lua_next(L, -2) != 0) { + if (lua_isstring(L, -2) && lua_isnumber(L, -1)) + bb.floatValues[lua_tostring(L, -2)] = + (float)lua_tonumber(L, -1); + lua_pop(L, 1); + } + } + lua_pop(L, 1); + + // vec3Values + lua_getfield(L, idx, "vec3Values"); + if (lua_istable(L, -1)) { + lua_pushnil(L); + while (lua_next(L, -2) != 0) { + if (lua_isstring(L, -2) && lua_istable(L, -1)) { + Ogre::Vector3 v; + lua_rawgeti(L, -1, 1); + v.x = (float)lua_tonumber(L, -1); + lua_pop(L, 1); + lua_rawgeti(L, -1, 2); + v.y = (float)lua_tonumber(L, -1); + lua_pop(L, 1); + lua_rawgeti(L, -1, 3); + v.z = (float)lua_tonumber(L, -1); + lua_pop(L, 1); + bb.vec3Values[lua_tostring(L, -2)] = v; + } + lua_pop(L, 1); + } + } + lua_pop(L, 1); + + // stringValues + lua_getfield(L, idx, "stringValues"); + if (lua_istable(L, -1)) { + lua_pushnil(L); + while (lua_next(L, -2) != 0) { + if (lua_isstring(L, -2) && lua_isstring(L, -1)) + bb.stringValues[lua_tostring(L, -2)] = + lua_tostring(L, -1); + lua_pop(L, 1); + } + } + lua_pop(L, 1); + + return bb; +} + +// --------------------------------------------------------------------------- +// Lua: ecs.send_event("eventName", [params_table]) +// --------------------------------------------------------------------------- + +/** + * @brief Lua: ecs.send_event("eventName", [params_table]) -> nil + * + * Sends an event through the global EventBus. The optional params table + * is converted to a GoapBlackboard payload. + * + * Usage: + * ecs.send_event("collision") + * ecs.send_event("collision", { entity_id = 42, damage = 10 }) + */ +static int luaSendEvent(lua_State *L) +{ + const char *eventName = luaL_checkstring(L, 1); + + GoapBlackboard params; + if (lua_gettop(L) >= 2 && lua_istable(L, 2)) { + params = readGoapBlackboard(L, 2); + } + + EventBus::getInstance().send(eventName, params); + return 0; +} + +// --------------------------------------------------------------------------- +// Lua: ecs.subscribe_event("eventName", callback_fn) -> int (subscription id) +// --------------------------------------------------------------------------- + +/** + * @brief Lua: ecs.subscribe_event("eventName", callback_fn) -> int + * + * Subscribes a Lua function to an event. The callback receives: + * function(event_name, params_table) + * + * Returns a subscription ID that can be used with unsubscribe_event(). + * + * Usage: + * local sub_id = ecs.subscribe_event("collision", function(event, params) + * print("Collision event received!") + * print("entity_id: " .. params.values.entity_id) + * end) + */ +static int luaSubscribeEvent(lua_State *L) +{ + const char *eventName = luaL_checkstring(L, 1); + luaL_checktype(L, 2, LUA_TFUNCTION); + + // Create a reference to the Lua callback function + lua_pushvalue(L, 2); + int callbackRef = luaL_ref(L, LUA_REGISTRYINDEX); + + // Subscribe to the EventBus with a C++ lambda that calls the Lua function + EventBus::ListenerId listenerId = EventBus::getInstance().subscribe( + eventName, [L, callbackRef](const Ogre::String &eventName, + const GoapBlackboard ¶ms) { + // Push the Lua callback function + lua_rawgeti(L, LUA_REGISTRYINDEX, callbackRef); + + // Push event name + lua_pushstring(L, eventName.c_str()); + + // Push params as a Lua table + pushGoapBlackboard(L, params); + + // Call the Lua function (2 args, 0 results) + if (lua_pcall(L, 2, 0, 0) != LUA_OK) { + Ogre::LogManager::getSingleton().stream() + << "Lua event callback error: " + << lua_tostring(L, -1); + lua_pop(L, 1); + } + }); + + // Store the mapping from Lua subscription ID to EventBus listener ID + int luaSubId = s_nextLuaSubId++; + s_luaSubscriptions[luaSubId] = listenerId; + + lua_pushinteger(L, luaSubId); + return 1; +} + +// --------------------------------------------------------------------------- +// Lua: ecs.unsubscribe_event(subscription_id) -> nil +// --------------------------------------------------------------------------- + +/** + * @brief Lua: ecs.unsubscribe_event(subscription_id) -> nil + * + * Unsubscribes a previously registered event subscription. + * + * Usage: + * ecs.unsubscribe_event(sub_id) + */ +static int luaUnsubscribeEvent(lua_State *L) +{ + int luaSubId = (int)luaL_checkinteger(L, 1); + + auto it = s_luaSubscriptions.find(luaSubId); + if (it != s_luaSubscriptions.end()) { + EventBus::getInstance().unsubscribe(it->second); + s_luaSubscriptions.erase(it); + } + + return 0; +} + +// --------------------------------------------------------------------------- +// Public registration function +// --------------------------------------------------------------------------- + +void registerLuaEventApi(lua_State *L) +{ + // Get or create the "ecs" global table + lua_getglobal(L, "ecs"); + if (lua_isnil(L, -1)) { + lua_newtable(L); + lua_setglobal(L, "ecs"); + lua_getglobal(L, "ecs"); + } + + // Register event functions + lua_pushcfunction(L, luaSendEvent); + lua_setfield(L, -2, "send_event"); + + lua_pushcfunction(L, luaSubscribeEvent); + lua_setfield(L, -2, "subscribe_event"); + + lua_pushcfunction(L, luaUnsubscribeEvent); + lua_setfield(L, -2, "unsubscribe_event"); + + // Pop the ecs table + lua_pop(L, 1); +} + +} // namespace editScene diff --git a/src/features/editScene/lua/LuaEventApi.hpp b/src/features/editScene/lua/LuaEventApi.hpp new file mode 100644 index 0000000..9c276e8 --- /dev/null +++ b/src/features/editScene/lua/LuaEventApi.hpp @@ -0,0 +1,55 @@ +#ifndef EDITSCENE_LUA_EVENT_API_HPP +#define EDITSCENE_LUA_EVENT_API_HPP +#pragma once + +#include + +/** + * @file LuaEventApi.hpp + * @brief Lua API for the EventBus system. + * + * Provides Lua bindings for the global EventBus singleton, allowing + * Lua scripts to subscribe to events, send events, and manage + * event subscriptions. + * + * The EventBus is a synchronous publish/subscribe system. Events are + * identified by name strings. Payloads use GoapBlackboard, which + * supports int, float, Vector3, and string values. + * + * Exposed Lua globals (in the "ecs" table): + * ecs.send_event("eventName") -> nil + * ecs.send_event("eventName", {key=value, ...}) -> nil + * ecs.subscribe_event("eventName", callback_fn) -> int (subscription id) + * ecs.unsubscribe_event(subscription_id) -> nil + * + * The callback function receives (event_name, params_table): + * ecs.subscribe_event("collision", function(event, params) + * print(event .. " occurred") + * print("entity_id: " .. params.entity_id) + * end) + * + * The params table contains the GoapBlackboard fields: + * params.bits -> integer (bitfield) + * params.mask -> integer (bitmask) + * params.values -> table {string -> int} + * params.floatValues -> table {string -> float} + * params.vec3Values -> table {string -> {x, y, z}} + * params.stringValues -> table {string -> string} + */ + +namespace editScene +{ + +/** + * @brief Register all event-related Lua API functions into the "ecs" table. + * + * Adds functions for event subscription, unsubscription, and sending. + * Must be called after LuaState is constructed and the "ecs" table exists. + * + * @param L The Lua state. + */ +void registerLuaEventApi(lua_State *L); + +} // namespace editScene + +#endif // EDITSCENE_LUA_EVENT_API_HPP diff --git a/src/features/editScene/lua/LuaState.cpp b/src/features/editScene/lua/LuaState.cpp new file mode 100644 index 0000000..dce55eb --- /dev/null +++ b/src/features/editScene/lua/LuaState.cpp @@ -0,0 +1,187 @@ +#include "LuaState.hpp" +#include +#include +#include +#include + +extern "C" { +int luaopen_lpeg(lua_State *L); +} + +namespace editScene +{ + +// --------------------------------------------------------------------------- +// Custom library loader: loads .lua files from OGRE resource groups +// --------------------------------------------------------------------------- + +int LuaState::luaLibraryLoader(lua_State *L) +{ + if (!lua_isstring(L, 1)) { + luaL_error( + L, + "luaLibraryLoader: Expected string for first parameter"); + } + + std::string libraryFile = lua_tostring(L, 1); + + // Translate '.' to '/' for OGRE resource path compatibility + while (libraryFile.find('.') != std::string::npos) + libraryFile.replace(libraryFile.find('.'), 1, "/"); + + libraryFile += ".lua"; + + Ogre::DataStreamPtr stream = + Ogre::ResourceGroupManager::getSingleton().openResource( + libraryFile, "LuaScripts"); + Ogre::String script = stream->getAsString(); + if (luaL_loadbuffer(L, script.c_str(), script.length(), + libraryFile.c_str())) { + luaL_error( + L, + "Error loading library '%s' from resource archive.\n%s", + libraryFile.c_str(), lua_tostring(L, -1)); + } + return 1; +} + +// --------------------------------------------------------------------------- +// Install the custom loader into package.searchers +// --------------------------------------------------------------------------- + +void LuaState::installLibraryLoader() +{ + lua_getglobal(L, "table"); + lua_getfield(L, -1, "insert"); + lua_remove(L, -2); // table + lua_getglobal(L, "package"); + lua_getfield(L, -1, "searchers"); + lua_remove(L, -2); // package + lua_pushnumber(L, 1); // insert at position 1 (highest priority) + lua_pushcfunction(L, luaLibraryLoader); + if (lua_pcall(L, 3, 0, 0)) + Ogre::LogManager::getSingleton().stream() << lua_tostring(L, 1); +} + +// --------------------------------------------------------------------------- +// Constructor: create Lua state and open standard libraries +// --------------------------------------------------------------------------- + +LuaState::LuaState() + : L(luaL_newstate()) +{ + luaopen_base(L); + luaopen_table(L); + luaopen_package(L); + luaL_requiref(L, "table", luaopen_table, 1); + lua_pop(L, 1); + luaL_requiref(L, "math", luaopen_math, 1); + lua_pop(L, 1); + luaL_requiref(L, "package", luaopen_package, 1); + lua_pop(L, 1); + luaL_requiref(L, "string", luaopen_string, 1); + lua_pop(L, 1); + luaL_requiref(L, "io", luaopen_io, 1); + lua_pop(L, 1); + luaL_requiref(L, "lpeg", luaopen_lpeg, 1); + lua_pop(L, 1); + + installLibraryLoader(); + lua_pop(L, 1); +} + +// --------------------------------------------------------------------------- +// Destructor +// --------------------------------------------------------------------------- + +LuaState::~LuaState() +{ + lua_close(L); +} + +// --------------------------------------------------------------------------- +// Handler registration +// --------------------------------------------------------------------------- + +int LuaState::setupHandler() +{ + luaL_checktype(L, 1, LUA_TFUNCTION); + lua_pushvalue(L, 1); + int ref = luaL_ref(L, LUA_REGISTRYINDEX); + setupHandlers.push_back(ref); + return 0; +} + +// --------------------------------------------------------------------------- +// Call all handlers with an event name (no entities) +// --------------------------------------------------------------------------- + +int LuaState::callHandler(const Ogre::String &event) +{ + for (int ref : setupHandlers) { + lua_rawgeti(L, LUA_REGISTRYINDEX, ref); + lua_pushstring(L, event.c_str()); + lua_pushinteger(L, -1); + lua_pushinteger(L, -1); + if (lua_pcall(L, 3, 0, 0) != LUA_OK) { + Ogre::LogManager::getSingleton().stream() + << lua_tostring(L, -1); + OgreAssert(false, "Lua error"); + } + } + return 0; +} + +// --------------------------------------------------------------------------- +// Call all handlers with an event name and two entities +// --------------------------------------------------------------------------- + +int LuaState::callHandler(const Ogre::String &event, flecs::entity e1, + flecs::entity e2) +{ + // Entity IDs are mapped through the global idmap (see LuaEntityApi.cpp) + extern int luaEntityToId(flecs::entity e); + extern flecs::entity luaIdToEntity(int id); + + for (int ref : setupHandlers) { + lua_rawgeti(L, LUA_REGISTRYINDEX, ref); + lua_pushstring(L, event.c_str()); + lua_pushinteger(L, luaEntityToId(e1)); + lua_pushinteger(L, luaEntityToId(e2)); + if (lua_pcall(L, 3, 0, 0) != LUA_OK) { + Ogre::LogManager::getSingleton().stream() + << lua_tostring(L, -1); + OgreAssert(false, "Lua error"); + } + } + return 0; +} + +// --------------------------------------------------------------------------- +// Late setup: load data.lua and run initialisation +// --------------------------------------------------------------------------- + +void LuaState::lateSetup() +{ + Ogre::DataStreamPtr stream = + Ogre::ResourceGroupManager::getSingleton().openResource( + "data.lua", "LuaScripts"); + std::cout << "stream: " << stream->getAsString() << "\n"; + if (luaL_dostring(L, stream->getAsString().c_str()) != LUA_OK) { + std::cout << "error: " << lua_tostring(L, -1) << "\n"; + OgreAssert(false, "Script failure"); + } + + const char *lua_code = "\n\ + function stuff()\n\ + return 4\n\ + end\n\ + x = stuff()\n\ + "; + luaL_dostring(L, lua_code); + lua_getglobal(L, "x"); + int x = lua_tonumber(L, 1); + std::cout << "lua: " << x << "\n"; +} + +} // namespace editScene diff --git a/src/features/editScene/lua/LuaState.hpp b/src/features/editScene/lua/LuaState.hpp new file mode 100644 index 0000000..4de1490 --- /dev/null +++ b/src/features/editScene/lua/LuaState.hpp @@ -0,0 +1,137 @@ +#ifndef EDITSCENE_LUA_STATE_HPP +#define EDITSCENE_LUA_STATE_HPP +#pragma once + +#include +#include +#include +#include +#include + +/** + * @file LuaState.hpp + * @brief Lua state management for the editScene editor. + * + * Manages the Lua virtual machine instance, library loading, + * script loading from OGRE resource groups, and the custom + * package.searchers entry that loads Lua modules from + * the "LuaScripts" resource group. + * + * Usage: + * LuaState lua; + * lua.installLibraryLoader(); // Register custom searcher + * lua.lateSetup(); // Load data.lua and run startup + * lua.callHandler("event_name", e1, e2); + */ + +namespace editScene +{ + +/** + * @brief Manages a single lua_State instance. + * + * Opens standard Lua libraries (base, table, math, package, string, io) + * plus LPEG. Installs a custom package.searchers entry that loads + * .lua files from OGRE's "LuaScripts" resource group. + * + * Provides a handler/callback system: Lua scripts can register + * callback functions via setup_handler(), and C++ code can invoke + * them with event names and optional entity parameters. + */ +class LuaState { +public: + /** + * @brief Construct a new Lua state. + * + * Opens all standard libraries and installs the custom + * library loader for OGRE resource-based Lua modules. + */ + LuaState(); + + /** + * @brief Destroy the Lua state and close the VM. + */ + ~LuaState(); + + /** + * @brief Get the underlying lua_State pointer. + */ + lua_State *getState() const + { + return L; + } + + /** + * @brief Install the custom library loader into package.searchers. + * + * Inserts luaLibraryLoader at position 1 of the searchers table + * so it takes precedence over the default file-system loader. + * This allows Lua's require() to load modules from OGRE resource + * archives. + */ + void installLibraryLoader(); + + /** + * @brief Late setup: load data.lua and run initialisation. + * + * Opens "data.lua" from the "LuaScripts" resource group and + * executes it. Also runs a simple inline Lua test snippet. + * Called once after the ECS world is fully initialised. + */ + void lateSetup(); + + /** + * @brief Register a Lua callback function for event handling. + * + * Expects a Lua function on the stack (at index 1). + * Stores a reference to it in the registry so it can be + * called later via callHandler(). + * + * @return int Reference index (stored internally). + */ + int setupHandler(); + + /** + * @brief Call all registered handlers with an event name. + * + * @param event The event name string. + * @return int 0 on success. + */ + int callHandler(const Ogre::String &event); + + /** + * @brief Call all registered handlers with an event name and + * two entity IDs. + * + * Entity IDs are mapped through the global idmap (see LuaEntityApi). + * + * @param event The event name string. + * @param e1 First entity (e.g. subject). + * @param e2 Second entity (e.g. object). + * @return int 0 on success. + */ + int callHandler(const Ogre::String &event, flecs::entity e1, + flecs::entity e2); + +private: + lua_State *L; + + /** Registry references for registered Lua callback functions. */ + std::vector setupHandlers; + + /** + * @brief Custom Lua loader function for OGRE resource-based modules. + * + * Registered in package.searchers. Translates dots to path + * separators, appends ".lua", and loads the file from the + * "LuaScripts" OGRE resource group. + * + * @param L Lua state. + * @return int 1 (pushes loaded chunk onto stack) or error. + */ + static int luaLibraryLoader(lua_State *L); +}; + +} // namespace editScene + +#endif // EDITSCENE_LUA_STATE_HPP