game_start event works
This commit is contained in:
@@ -15,6 +15,7 @@ add_subdirectory(recastnavigation)
|
||||
set(EDITSCENE_SOURCES
|
||||
main.cpp
|
||||
EditorApp.cpp
|
||||
GameMode.cpp
|
||||
systems/EditorUISystem.cpp
|
||||
systems/SceneSerializer.cpp
|
||||
systems/PhysicsSystem.cpp
|
||||
@@ -154,6 +155,7 @@ set(EDITSCENE_SOURCES
|
||||
lua/LuaEventApi.cpp
|
||||
lua/LuaActionApi.cpp
|
||||
lua/LuaBehaviorTreeApi.cpp
|
||||
lua/LuaGameModeApi.cpp
|
||||
)
|
||||
|
||||
set(EDITSCENE_HEADERS
|
||||
@@ -305,6 +307,7 @@ set(EDITSCENE_HEADERS
|
||||
lua/LuaEventApi.hpp
|
||||
lua/LuaActionApi.hpp
|
||||
lua/LuaBehaviorTreeApi.hpp
|
||||
lua/LuaGameModeApi.hpp
|
||||
)
|
||||
|
||||
add_executable(editSceneEditor ${EDITSCENE_SOURCES} ${EDITSCENE_HEADERS})
|
||||
@@ -420,7 +423,9 @@ target_include_directories(action_db_lua_test PRIVATE
|
||||
add_executable(behavior_tree_lua_test
|
||||
tests/behavior_tree_lua_test.cpp
|
||||
lua/LuaBehaviorTreeApi.cpp
|
||||
lua/LuaGameModeApi.cpp
|
||||
lua/LuaEntityApi.cpp
|
||||
GameMode.cpp
|
||||
components/GoapBlackboard.cpp
|
||||
)
|
||||
|
||||
@@ -438,6 +443,37 @@ target_include_directories(behavior_tree_lua_test PRIVATE
|
||||
|
||||
target_compile_definitions(behavior_tree_lua_test PRIVATE flecs_STATIC)
|
||||
|
||||
# Test: EventParams C++ API (standalone, no Lua dependency)
|
||||
add_executable(event_params_test
|
||||
tests/event_params_test.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(event_params_test
|
||||
lua
|
||||
)
|
||||
|
||||
target_include_directories(event_params_test PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/tests
|
||||
${CMAKE_SOURCE_DIR}/src/lua/lua-5.4.8/src
|
||||
)
|
||||
|
||||
# Test: Game Mode Lua API
|
||||
add_executable(game_mode_lua_test
|
||||
tests/game_mode_lua_test.cpp
|
||||
tests/lua_test_stubs.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(game_mode_lua_test
|
||||
lua
|
||||
)
|
||||
|
||||
target_include_directories(game_mode_lua_test PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/tests
|
||||
${CMAKE_SOURCE_DIR}/src/lua/lua-5.4.8/src
|
||||
)
|
||||
|
||||
# Copy local resources (materials, etc.)
|
||||
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/resources")
|
||||
file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/resources"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include <iostream>
|
||||
#include "EditorApp.hpp"
|
||||
#include "GameMode.hpp"
|
||||
#include "systems/EditorUISystem.hpp"
|
||||
#include "systems/PhysicsSystem.hpp"
|
||||
#include "systems/BuoyancySystem.hpp"
|
||||
@@ -77,6 +78,7 @@
|
||||
#include "components/PathFollowing.hpp"
|
||||
#include "systems/ActuatorSystem.hpp"
|
||||
#include "systems/EventHandlerSystem.hpp"
|
||||
#include "systems/EventBus.hpp"
|
||||
#include "systems/ItemSystem.hpp"
|
||||
#include "components/EventHandler.hpp"
|
||||
#include "components/Item.hpp"
|
||||
@@ -89,6 +91,7 @@
|
||||
#include "lua/LuaEventApi.hpp"
|
||||
#include "lua/LuaActionApi.hpp"
|
||||
#include "lua/LuaBehaviorTreeApi.hpp"
|
||||
#include "lua/LuaGameModeApi.hpp"
|
||||
|
||||
//=============================================================================
|
||||
// ImGuiRenderListener Implementation
|
||||
@@ -446,24 +449,6 @@ void EditorApp::setup()
|
||||
std::make_unique<PlayerControllerSystem>(
|
||||
m_world, m_sceneMgr, this);
|
||||
|
||||
if (m_gameMode == GameMode::Game) {
|
||||
// Load startup menu scene configured in editor
|
||||
SceneSerializer serializer(m_world, m_sceneMgr);
|
||||
Ogre::LogManager::getSingleton().logMessage(
|
||||
"Game mode: Loading startup_menu.json...");
|
||||
if (serializer.loadFromFile("startup_menu.json",
|
||||
m_uiSystem.get())) {
|
||||
PrefabSystem prefabSys(m_world, m_sceneMgr);
|
||||
prefabSys.resolveInstances();
|
||||
Ogre::LogManager::getSingleton().logMessage(
|
||||
"Game mode: startup_menu.json loaded");
|
||||
} else {
|
||||
Ogre::LogManager::getSingleton().logMessage(
|
||||
"Game mode: Failed to load startup_menu.json: " +
|
||||
serializer.getLastError());
|
||||
}
|
||||
}
|
||||
|
||||
// Pre-load fonts before showing overlay
|
||||
if (m_startupMenuSystem)
|
||||
m_startupMenuSystem->prepareFont();
|
||||
@@ -508,6 +493,7 @@ void EditorApp::setup()
|
||||
editScene::registerLuaEventApi(L);
|
||||
editScene::registerLuaActionApi(L);
|
||||
editScene::registerLuaBehaviorTreeApi(L);
|
||||
editScene::registerLuaGameModeApi(L);
|
||||
|
||||
// Run late setup: load data.lua and initial scripts.
|
||||
m_lua.lateSetup();
|
||||
@@ -524,6 +510,27 @@ void EditorApp::setup()
|
||||
ActionDatabase::reloadFromSceneComponents(m_world);
|
||||
}
|
||||
|
||||
if (m_gameMode == GameMode::Game) {
|
||||
// Queue "game_start" event before loading the startup menu
|
||||
EventBus::getInstance().send("game_start");
|
||||
|
||||
// Load startup menu scene configured in editor
|
||||
SceneSerializer serializer(m_world, m_sceneMgr);
|
||||
Ogre::LogManager::getSingleton().logMessage(
|
||||
"Game mode: Loading startup_menu.json...");
|
||||
if (serializer.loadFromFile("startup_menu.json",
|
||||
m_uiSystem.get())) {
|
||||
PrefabSystem prefabSys(m_world, m_sceneMgr);
|
||||
prefabSys.resolveInstances();
|
||||
Ogre::LogManager::getSingleton().logMessage(
|
||||
"Game mode: startup_menu.json loaded");
|
||||
} else {
|
||||
Ogre::LogManager::getSingleton().logMessage(
|
||||
"Game mode: Failed to load startup_menu.json: " +
|
||||
serializer.getLastError());
|
||||
}
|
||||
}
|
||||
|
||||
// Game mode can be set externally before setup() is called
|
||||
m_setupComplete = true;
|
||||
|
||||
@@ -542,12 +549,19 @@ void EditorApp::setGameMode(GameMode mode)
|
||||
return;
|
||||
}
|
||||
m_gameMode = mode;
|
||||
editScene::setEditSceneGameMode(mode == GameMode::Game ?
|
||||
editScene::GameMode::Game :
|
||||
editScene::GameMode::Editor);
|
||||
if (m_gameMode == GameMode::Game) {
|
||||
m_gamePlayState = GamePlayState::Menu;
|
||||
editScene::setEditSceneGamePlayState(
|
||||
editScene::GamePlayState::Menu);
|
||||
if (m_uiSystem)
|
||||
m_uiSystem->setEditorUIEnabled(false);
|
||||
} else {
|
||||
m_gamePlayState = GamePlayState::Menu;
|
||||
editScene::setEditSceneGamePlayState(
|
||||
editScene::GamePlayState::Menu);
|
||||
if (m_uiSystem)
|
||||
m_uiSystem->setEditorUIEnabled(true);
|
||||
}
|
||||
@@ -578,6 +592,12 @@ void EditorApp::setDebugBuoyancy(bool enabled)
|
||||
void EditorApp::setGamePlayState(GamePlayState state)
|
||||
{
|
||||
m_gamePlayState = state;
|
||||
editScene::setEditSceneGamePlayState(
|
||||
state == GamePlayState::Playing ?
|
||||
editScene::GamePlayState::Playing :
|
||||
state == GamePlayState::Paused ?
|
||||
editScene::GamePlayState::Paused :
|
||||
editScene::GamePlayState::Menu);
|
||||
|
||||
// Grab/ungrab mouse based on gameplay state
|
||||
if (m_gameMode == GameMode::Game) {
|
||||
|
||||
37
src/features/editScene/GameMode.cpp
Normal file
37
src/features/editScene/GameMode.cpp
Normal file
@@ -0,0 +1,37 @@
|
||||
#include "GameMode.hpp"
|
||||
|
||||
namespace editScene
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
/// Global game mode state.
|
||||
GameMode s_gameMode = GameMode::Editor;
|
||||
|
||||
/// Global gameplay state (only meaningful in game mode).
|
||||
GamePlayState s_gamePlayState = GamePlayState::Menu;
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
void setEditSceneGameMode(GameMode mode) noexcept
|
||||
{
|
||||
s_gameMode = mode;
|
||||
}
|
||||
|
||||
void setEditSceneGamePlayState(GamePlayState state) noexcept
|
||||
{
|
||||
s_gamePlayState = state;
|
||||
}
|
||||
|
||||
GameMode getGameMode() noexcept
|
||||
{
|
||||
return s_gameMode;
|
||||
}
|
||||
|
||||
GamePlayState getGamePlayState() noexcept
|
||||
{
|
||||
return s_gamePlayState;
|
||||
}
|
||||
|
||||
} // namespace editScene
|
||||
101
src/features/editScene/GameMode.hpp
Normal file
101
src/features/editScene/GameMode.hpp
Normal file
@@ -0,0 +1,101 @@
|
||||
#ifndef EDITSCENE_GAMEMODE_HPP
|
||||
#define EDITSCENE_GAMEMODE_HPP
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* @file GameMode.hpp
|
||||
*
|
||||
* Global game mode query functions for the editScene feature.
|
||||
*
|
||||
* These functions allow any code in the editScene feature to query
|
||||
* whether the application is currently in editor mode or game mode,
|
||||
* and what the current gameplay state is, without needing a direct
|
||||
* pointer to EditorApp.
|
||||
*
|
||||
* The EditorApp sets the current mode via setEditSceneGameMode()
|
||||
* during its lifetime. Code outside the editScene feature should
|
||||
* continue to use EditorApp::getGameMode() / getGamePlayState()
|
||||
* directly.
|
||||
*/
|
||||
|
||||
namespace editScene
|
||||
{
|
||||
|
||||
/**
|
||||
* Application mode: editor or game.
|
||||
*/
|
||||
enum class GameMode { Editor, Game };
|
||||
|
||||
/**
|
||||
* Play state when in game mode.
|
||||
*/
|
||||
enum class GamePlayState { Menu, Playing, Paused };
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Global state management (called by EditorApp)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Set the current game mode. Called by EditorApp on mode changes.
|
||||
*/
|
||||
void setEditSceneGameMode(GameMode mode) noexcept;
|
||||
|
||||
/**
|
||||
* Set the current gameplay state. Called by EditorApp on state changes.
|
||||
*/
|
||||
void setEditSceneGamePlayState(GamePlayState state) noexcept;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Query functions
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Return the current application mode.
|
||||
*/
|
||||
GameMode getGameMode() noexcept;
|
||||
|
||||
/**
|
||||
* Return the current gameplay state (only meaningful in game mode).
|
||||
*/
|
||||
GamePlayState getGamePlayState() noexcept;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Predicates
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/** True when the application is in editor mode. */
|
||||
inline bool isEditorMode() noexcept
|
||||
{
|
||||
return getGameMode() == GameMode::Editor;
|
||||
}
|
||||
|
||||
/** True when the application is in game mode (any play state). */
|
||||
inline bool isGameMode() noexcept
|
||||
{
|
||||
return getGameMode() == GameMode::Game;
|
||||
}
|
||||
|
||||
/** True when in game mode and the gameplay state is Playing. */
|
||||
inline bool isGamePlaying() noexcept
|
||||
{
|
||||
return getGameMode() == GameMode::Game &&
|
||||
getGamePlayState() == GamePlayState::Playing;
|
||||
}
|
||||
|
||||
/** True when in game mode and the gameplay state is Menu. */
|
||||
inline bool isGameMenu() noexcept
|
||||
{
|
||||
return getGameMode() == GameMode::Game &&
|
||||
getGamePlayState() == GamePlayState::Menu;
|
||||
}
|
||||
|
||||
/** True when in game mode and the gameplay state is Paused. */
|
||||
inline bool isGamePaused() noexcept
|
||||
{
|
||||
return getGameMode() == GameMode::Game &&
|
||||
getGamePlayState() == GamePlayState::Paused;
|
||||
}
|
||||
|
||||
} // namespace editScene
|
||||
|
||||
#endif // EDITSCENE_GAMEMODE_HPP
|
||||
@@ -16,7 +16,7 @@
|
||||
*
|
||||
* Only active in game mode (GamePlayState::Playing).
|
||||
*
|
||||
* Event payload (GoapBlackboard) parameters:
|
||||
* Event payload (EventParams) parameters:
|
||||
* "text" (string) - Narration text to display
|
||||
* "choices" (string) - Comma-separated list of choice labels
|
||||
* "speaker" (string) - Optional speaker name
|
||||
|
||||
@@ -8,9 +8,9 @@
|
||||
* Event-driven behavior tree handler component.
|
||||
*
|
||||
* When the specified event is received, the referenced GoapAction's
|
||||
* behavior tree is executed for this entity. Event parameters are
|
||||
* merged into the entity's GoapBlackboard before the tree runs and
|
||||
* cleaned up when the tree completes.
|
||||
* behavior tree is executed for this entity. Event parameters
|
||||
* (EventParams) are injected into the entity's GoapBlackboard before
|
||||
* the tree runs and cleaned up when the tree completes.
|
||||
*/
|
||||
struct EventHandlerComponent {
|
||||
Ogre::String eventName;
|
||||
|
||||
739
src/features/editScene/components/EventParams.hpp
Normal file
739
src/features/editScene/components/EventParams.hpp
Normal file
@@ -0,0 +1,739 @@
|
||||
#ifndef EDITSCENE_EVENT_PARAMS_HPP
|
||||
#define EDITSCENE_EVENT_PARAMS_HPP
|
||||
#pragma once
|
||||
|
||||
#include <Ogre.h>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <cassert>
|
||||
|
||||
/**
|
||||
* @file EventParams.hpp
|
||||
* @brief Tagged union type for event parameters.
|
||||
*
|
||||
* A C++11-compatible, RTTI-free tagged union that supports:
|
||||
* - Entity ID (uint64_t)
|
||||
* - Integer (int64_t)
|
||||
* - Float (float)
|
||||
* - Double (double)
|
||||
* - String (std::string)
|
||||
* - Array of entity IDs (std::vector<uint64_t>)
|
||||
* - Array of integers (std::vector<int64_t>)
|
||||
* - Array of floats (std::vector<float>)
|
||||
* - Array of doubles (std::vector<double>)
|
||||
* - Array of strings (std::vector<std::string>)
|
||||
*
|
||||
* Named parameters are stored as a map of string -> EventValue,
|
||||
* where EventValue is a tagged union of the above types.
|
||||
*/
|
||||
|
||||
// Forward declaration for friend function
|
||||
struct lua_State;
|
||||
|
||||
namespace editScene
|
||||
{
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// EventValue: A single tagged-union value
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
struct EventValue {
|
||||
enum Type {
|
||||
NIL = 0,
|
||||
ENTITY_ID,
|
||||
INT,
|
||||
FLOAT,
|
||||
DOUBLE,
|
||||
STRING,
|
||||
ENTITY_ID_ARRAY,
|
||||
INT_ARRAY,
|
||||
FLOAT_ARRAY,
|
||||
DOUBLE_ARRAY,
|
||||
STRING_ARRAY
|
||||
};
|
||||
|
||||
Type type;
|
||||
|
||||
union {
|
||||
uint64_t asEntityId;
|
||||
int64_t asInt;
|
||||
float asFloat;
|
||||
double asDouble;
|
||||
};
|
||||
|
||||
// Heap-allocated data (strings and arrays)
|
||||
// We use raw pointers to avoid std::unique_ptr (C++11 compatible)
|
||||
std::string *strPtr;
|
||||
void *arrayPtr; // points to std::vector<T>*
|
||||
size_t arraySize;
|
||||
|
||||
EventValue()
|
||||
: type(NIL)
|
||||
, asEntityId(0)
|
||||
, strPtr(nullptr)
|
||||
, arrayPtr(nullptr)
|
||||
, arraySize(0)
|
||||
{
|
||||
}
|
||||
|
||||
explicit EventValue(uint64_t entityId)
|
||||
: type(ENTITY_ID)
|
||||
, asEntityId(entityId)
|
||||
, strPtr(nullptr)
|
||||
, arrayPtr(nullptr)
|
||||
, arraySize(0)
|
||||
{
|
||||
}
|
||||
|
||||
explicit EventValue(int64_t val)
|
||||
: type(INT)
|
||||
, asInt(val)
|
||||
, strPtr(nullptr)
|
||||
, arrayPtr(nullptr)
|
||||
, arraySize(0)
|
||||
{
|
||||
}
|
||||
|
||||
explicit EventValue(int val)
|
||||
: type(INT)
|
||||
, asInt(static_cast<int64_t>(val))
|
||||
, strPtr(nullptr)
|
||||
, arrayPtr(nullptr)
|
||||
, arraySize(0)
|
||||
{
|
||||
}
|
||||
|
||||
explicit EventValue(float val)
|
||||
: type(FLOAT)
|
||||
, asFloat(val)
|
||||
, strPtr(nullptr)
|
||||
, arrayPtr(nullptr)
|
||||
, arraySize(0)
|
||||
{
|
||||
}
|
||||
|
||||
explicit EventValue(double val)
|
||||
: type(DOUBLE)
|
||||
, asDouble(val)
|
||||
, strPtr(nullptr)
|
||||
, arrayPtr(nullptr)
|
||||
, arraySize(0)
|
||||
{
|
||||
}
|
||||
|
||||
explicit EventValue(const std::string &val)
|
||||
: type(STRING)
|
||||
, asEntityId(0)
|
||||
, strPtr(new std::string(val))
|
||||
, arrayPtr(nullptr)
|
||||
, arraySize(0)
|
||||
{
|
||||
}
|
||||
|
||||
explicit EventValue(const char *val)
|
||||
: type(STRING)
|
||||
, asEntityId(0)
|
||||
, strPtr(new std::string(val ? val : ""))
|
||||
, arrayPtr(nullptr)
|
||||
, arraySize(0)
|
||||
{
|
||||
}
|
||||
|
||||
// Array constructors
|
||||
explicit EventValue(const std::vector<uint64_t> &arr)
|
||||
: type(ENTITY_ID_ARRAY)
|
||||
, asEntityId(0)
|
||||
, strPtr(nullptr)
|
||||
, arrayPtr(new std::vector<uint64_t>(arr))
|
||||
, arraySize(arr.size())
|
||||
{
|
||||
}
|
||||
|
||||
explicit EventValue(const std::vector<int64_t> &arr)
|
||||
: type(INT_ARRAY)
|
||||
, asEntityId(0)
|
||||
, strPtr(nullptr)
|
||||
, arrayPtr(new std::vector<int64_t>(arr))
|
||||
, arraySize(arr.size())
|
||||
{
|
||||
}
|
||||
|
||||
explicit EventValue(const std::vector<int> &arr)
|
||||
: type(INT_ARRAY)
|
||||
, asEntityId(0)
|
||||
, strPtr(nullptr)
|
||||
, arrayPtr(new std::vector<int64_t>(arr.begin(), arr.end()))
|
||||
, arraySize(arr.size())
|
||||
{
|
||||
}
|
||||
|
||||
explicit EventValue(const std::vector<float> &arr)
|
||||
: type(FLOAT_ARRAY)
|
||||
, asEntityId(0)
|
||||
, strPtr(nullptr)
|
||||
, arrayPtr(new std::vector<float>(arr))
|
||||
, arraySize(arr.size())
|
||||
{
|
||||
}
|
||||
|
||||
explicit EventValue(const std::vector<double> &arr)
|
||||
: type(DOUBLE_ARRAY)
|
||||
, asEntityId(0)
|
||||
, strPtr(nullptr)
|
||||
, arrayPtr(new std::vector<double>(arr))
|
||||
, arraySize(arr.size())
|
||||
{
|
||||
}
|
||||
|
||||
explicit EventValue(const std::vector<std::string> &arr)
|
||||
: type(STRING_ARRAY)
|
||||
, asEntityId(0)
|
||||
, strPtr(nullptr)
|
||||
, arrayPtr(new std::vector<std::string>(arr))
|
||||
, arraySize(arr.size())
|
||||
{
|
||||
}
|
||||
|
||||
// Copy constructor
|
||||
EventValue(const EventValue &other)
|
||||
: type(other.type)
|
||||
, asEntityId(other.asEntityId)
|
||||
, strPtr(nullptr)
|
||||
, arrayPtr(nullptr)
|
||||
, arraySize(other.arraySize)
|
||||
{
|
||||
copyHeapData(other);
|
||||
}
|
||||
|
||||
// Copy assignment
|
||||
EventValue &operator=(const EventValue &other)
|
||||
{
|
||||
if (this != &other) {
|
||||
destroyHeapData();
|
||||
type = other.type;
|
||||
asEntityId = other.asEntityId;
|
||||
arraySize = other.arraySize;
|
||||
strPtr = nullptr;
|
||||
arrayPtr = nullptr;
|
||||
copyHeapData(other);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Move constructor
|
||||
EventValue(EventValue &&other) noexcept : type(other.type),
|
||||
asEntityId(other.asEntityId),
|
||||
strPtr(other.strPtr),
|
||||
arrayPtr(other.arrayPtr),
|
||||
arraySize(other.arraySize)
|
||||
{
|
||||
other.type = NIL;
|
||||
other.strPtr = nullptr;
|
||||
other.arrayPtr = nullptr;
|
||||
other.arraySize = 0;
|
||||
}
|
||||
|
||||
// Move assignment
|
||||
EventValue &operator=(EventValue &&other) noexcept
|
||||
{
|
||||
if (this != &other) {
|
||||
destroyHeapData();
|
||||
type = other.type;
|
||||
asEntityId = other.asEntityId;
|
||||
strPtr = other.strPtr;
|
||||
arrayPtr = other.arrayPtr;
|
||||
arraySize = other.arraySize;
|
||||
other.type = NIL;
|
||||
other.strPtr = nullptr;
|
||||
other.arrayPtr = nullptr;
|
||||
other.arraySize = 0;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
~EventValue()
|
||||
{
|
||||
destroyHeapData();
|
||||
}
|
||||
|
||||
// --- Accessors ---
|
||||
|
||||
Type getType() const
|
||||
{
|
||||
return type;
|
||||
}
|
||||
|
||||
uint64_t getEntityId() const
|
||||
{
|
||||
assert(type == ENTITY_ID);
|
||||
return asEntityId;
|
||||
}
|
||||
|
||||
int64_t getInt() const
|
||||
{
|
||||
assert(type == INT);
|
||||
return asInt;
|
||||
}
|
||||
|
||||
float getFloat() const
|
||||
{
|
||||
assert(type == FLOAT);
|
||||
return asFloat;
|
||||
}
|
||||
|
||||
double getDouble() const
|
||||
{
|
||||
assert(type == DOUBLE);
|
||||
return asDouble;
|
||||
}
|
||||
|
||||
const std::string &getString() const
|
||||
{
|
||||
assert(type == STRING && strPtr != nullptr);
|
||||
return *strPtr;
|
||||
}
|
||||
|
||||
const std::vector<uint64_t> &getEntityIdArray() const
|
||||
{
|
||||
assert(type == ENTITY_ID_ARRAY && arrayPtr != nullptr);
|
||||
return *static_cast<const std::vector<uint64_t> *>(arrayPtr);
|
||||
}
|
||||
|
||||
const std::vector<int64_t> &getIntArray() const
|
||||
{
|
||||
assert(type == INT_ARRAY && arrayPtr != nullptr);
|
||||
return *static_cast<const std::vector<int64_t> *>(arrayPtr);
|
||||
}
|
||||
|
||||
const std::vector<float> &getFloatArray() const
|
||||
{
|
||||
assert(type == FLOAT_ARRAY && arrayPtr != nullptr);
|
||||
return *static_cast<const std::vector<float> *>(arrayPtr);
|
||||
}
|
||||
|
||||
const std::vector<double> &getDoubleArray() const
|
||||
{
|
||||
assert(type == DOUBLE_ARRAY && arrayPtr != nullptr);
|
||||
return *static_cast<const std::vector<double> *>(arrayPtr);
|
||||
}
|
||||
|
||||
const std::vector<std::string> &getStringArray() const
|
||||
{
|
||||
assert(type == STRING_ARRAY && arrayPtr != nullptr);
|
||||
return *static_cast<const std::vector<std::string> *>(arrayPtr);
|
||||
}
|
||||
|
||||
// --- Convenience: get numeric value as double ---
|
||||
double asNumeric() const
|
||||
{
|
||||
switch (type) {
|
||||
case INT:
|
||||
return static_cast<double>(asInt);
|
||||
case FLOAT:
|
||||
return static_cast<double>(asFloat);
|
||||
case DOUBLE:
|
||||
return asDouble;
|
||||
case ENTITY_ID:
|
||||
return static_cast<double>(asEntityId);
|
||||
default:
|
||||
return 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
// --- Equality ---
|
||||
bool operator==(const EventValue &other) const
|
||||
{
|
||||
if (type != other.type)
|
||||
return false;
|
||||
switch (type) {
|
||||
case NIL:
|
||||
return true;
|
||||
case ENTITY_ID:
|
||||
return asEntityId == other.asEntityId;
|
||||
case INT:
|
||||
return asInt == other.asInt;
|
||||
case FLOAT:
|
||||
return asFloat == other.asFloat;
|
||||
case DOUBLE:
|
||||
return asDouble == other.asDouble;
|
||||
case STRING:
|
||||
return strPtr && other.strPtr &&
|
||||
*strPtr == *other.strPtr;
|
||||
case ENTITY_ID_ARRAY:
|
||||
return arrayPtr && other.arrayPtr &&
|
||||
getEntityIdArray() == other.getEntityIdArray();
|
||||
case INT_ARRAY:
|
||||
return arrayPtr && other.arrayPtr &&
|
||||
getIntArray() == other.getIntArray();
|
||||
case FLOAT_ARRAY:
|
||||
return arrayPtr && other.arrayPtr &&
|
||||
getFloatArray() == other.getFloatArray();
|
||||
case DOUBLE_ARRAY:
|
||||
return arrayPtr && other.arrayPtr &&
|
||||
getDoubleArray() == other.getDoubleArray();
|
||||
case STRING_ARRAY:
|
||||
return arrayPtr && other.arrayPtr &&
|
||||
getStringArray() == other.getStringArray();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool operator!=(const EventValue &other) const
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
private:
|
||||
void copyHeapData(const EventValue &other)
|
||||
{
|
||||
if (other.type == STRING && other.strPtr) {
|
||||
strPtr = new std::string(*other.strPtr);
|
||||
} else if (other.type == ENTITY_ID_ARRAY && other.arrayPtr) {
|
||||
arrayPtr = new std::vector<uint64_t>(
|
||||
*static_cast<const std::vector<uint64_t> *>(
|
||||
other.arrayPtr));
|
||||
} else if (other.type == INT_ARRAY && other.arrayPtr) {
|
||||
arrayPtr = new std::vector<int64_t>(
|
||||
*static_cast<const std::vector<int64_t> *>(
|
||||
other.arrayPtr));
|
||||
} else if (other.type == FLOAT_ARRAY && other.arrayPtr) {
|
||||
arrayPtr = new std::vector<float>(
|
||||
*static_cast<const std::vector<float> *>(
|
||||
other.arrayPtr));
|
||||
} else if (other.type == DOUBLE_ARRAY && other.arrayPtr) {
|
||||
arrayPtr = new std::vector<double>(
|
||||
*static_cast<const std::vector<double> *>(
|
||||
other.arrayPtr));
|
||||
} else if (other.type == STRING_ARRAY && other.arrayPtr) {
|
||||
arrayPtr = new std::vector<std::string>(
|
||||
*static_cast<const std::vector<std::string> *>(
|
||||
other.arrayPtr));
|
||||
}
|
||||
}
|
||||
|
||||
void destroyHeapData()
|
||||
{
|
||||
if (type == STRING) {
|
||||
delete strPtr;
|
||||
} else if (type == ENTITY_ID_ARRAY) {
|
||||
delete static_cast<std::vector<uint64_t> *>(arrayPtr);
|
||||
} else if (type == INT_ARRAY) {
|
||||
delete static_cast<std::vector<int64_t> *>(arrayPtr);
|
||||
} else if (type == FLOAT_ARRAY) {
|
||||
delete static_cast<std::vector<float> *>(arrayPtr);
|
||||
} else if (type == DOUBLE_ARRAY) {
|
||||
delete static_cast<std::vector<double> *>(arrayPtr);
|
||||
} else if (type == STRING_ARRAY) {
|
||||
delete static_cast<std::vector<std::string> *>(
|
||||
arrayPtr);
|
||||
}
|
||||
strPtr = nullptr;
|
||||
arrayPtr = nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// EventParams: A map of named EventValue entries
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
class EventParams {
|
||||
public:
|
||||
EventParams() = default;
|
||||
|
||||
// --- Set values ---
|
||||
|
||||
void setEntityId(const std::string &key, uint64_t val)
|
||||
{
|
||||
m_values[key] = EventValue(val);
|
||||
}
|
||||
|
||||
void setInt(const std::string &key, int64_t val)
|
||||
{
|
||||
m_values[key] = EventValue(val);
|
||||
}
|
||||
|
||||
void setFloat(const std::string &key, float val)
|
||||
{
|
||||
m_values[key] = EventValue(val);
|
||||
}
|
||||
|
||||
void setDouble(const std::string &key, double val)
|
||||
{
|
||||
m_values[key] = EventValue(val);
|
||||
}
|
||||
|
||||
void setString(const std::string &key, const std::string &val)
|
||||
{
|
||||
m_values[key] = EventValue(val);
|
||||
}
|
||||
|
||||
void setEntityIdArray(const std::string &key,
|
||||
const std::vector<uint64_t> &val)
|
||||
{
|
||||
m_values[key] = EventValue(val);
|
||||
}
|
||||
|
||||
void setIntArray(const std::string &key,
|
||||
const std::vector<int64_t> &val)
|
||||
{
|
||||
m_values[key] = EventValue(val);
|
||||
}
|
||||
|
||||
void setFloatArray(const std::string &key,
|
||||
const std::vector<float> &val)
|
||||
{
|
||||
m_values[key] = EventValue(val);
|
||||
}
|
||||
|
||||
void setDoubleArray(const std::string &key,
|
||||
const std::vector<double> &val)
|
||||
{
|
||||
m_values[key] = EventValue(val);
|
||||
}
|
||||
|
||||
void setStringArray(const std::string &key,
|
||||
const std::vector<std::string> &val)
|
||||
{
|
||||
m_values[key] = EventValue(val);
|
||||
}
|
||||
|
||||
// --- Get values ---
|
||||
|
||||
bool has(const std::string &key) const
|
||||
{
|
||||
return m_values.find(key) != m_values.end();
|
||||
}
|
||||
|
||||
const EventValue *get(const std::string &key) const
|
||||
{
|
||||
auto it = m_values.find(key);
|
||||
if (it != m_values.end())
|
||||
return &it->second;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
EventValue *get(const std::string &key)
|
||||
{
|
||||
auto it = m_values.find(key);
|
||||
if (it != m_values.end())
|
||||
return &it->second;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// --- Typed getters with defaults ---
|
||||
|
||||
uint64_t getEntityId(const std::string &key,
|
||||
uint64_t defaultVal = 0) const
|
||||
{
|
||||
auto v = get(key);
|
||||
if (v && v->getType() == EventValue::ENTITY_ID)
|
||||
return v->getEntityId();
|
||||
return defaultVal;
|
||||
}
|
||||
|
||||
int64_t getInt(const std::string &key, int64_t defaultVal = 0) const
|
||||
{
|
||||
auto v = get(key);
|
||||
if (v && v->getType() == EventValue::INT)
|
||||
return v->getInt();
|
||||
return defaultVal;
|
||||
}
|
||||
|
||||
float getFloat(const std::string &key, float defaultVal = 0.0f) const
|
||||
{
|
||||
auto v = get(key);
|
||||
if (v && v->getType() == EventValue::FLOAT)
|
||||
return v->getFloat();
|
||||
return defaultVal;
|
||||
}
|
||||
|
||||
double getDouble(const std::string &key, double defaultVal = 0.0) const
|
||||
{
|
||||
auto v = get(key);
|
||||
if (v && v->getType() == EventValue::DOUBLE)
|
||||
return v->getDouble();
|
||||
return defaultVal;
|
||||
}
|
||||
|
||||
std::string getString(const std::string &key,
|
||||
const std::string &defaultVal = "") const
|
||||
{
|
||||
auto v = get(key);
|
||||
if (v && v->getType() == EventValue::STRING)
|
||||
return v->getString();
|
||||
return defaultVal;
|
||||
}
|
||||
|
||||
// --- Remove ---
|
||||
|
||||
void remove(const std::string &key)
|
||||
{
|
||||
m_values.erase(key);
|
||||
}
|
||||
|
||||
// --- Clear ---
|
||||
|
||||
void clear()
|
||||
{
|
||||
m_values.clear();
|
||||
}
|
||||
|
||||
// --- Size ---
|
||||
|
||||
size_t size() const
|
||||
{
|
||||
return m_values.size();
|
||||
}
|
||||
|
||||
bool empty() const
|
||||
{
|
||||
return m_values.empty();
|
||||
}
|
||||
|
||||
// --- Iteration ---
|
||||
|
||||
typedef std::unordered_map<std::string, EventValue>::const_iterator
|
||||
ConstIterator;
|
||||
typedef std::unordered_map<std::string, EventValue>::iterator Iterator;
|
||||
|
||||
ConstIterator begin() const
|
||||
{
|
||||
return m_values.begin();
|
||||
}
|
||||
ConstIterator end() const
|
||||
{
|
||||
return m_values.end();
|
||||
}
|
||||
Iterator begin()
|
||||
{
|
||||
return m_values.begin();
|
||||
}
|
||||
Iterator end()
|
||||
{
|
||||
return m_values.end();
|
||||
}
|
||||
|
||||
// --- Merge ---
|
||||
|
||||
void merge(const EventParams &other)
|
||||
{
|
||||
for (const auto &pair : other.m_values)
|
||||
m_values[pair.first] = pair.second;
|
||||
}
|
||||
|
||||
// --- Equality ---
|
||||
|
||||
bool operator==(const EventParams &other) const
|
||||
{
|
||||
return m_values == other.m_values;
|
||||
}
|
||||
|
||||
bool operator!=(const EventParams &other) const
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
// --- Dump for debugging ---
|
||||
|
||||
std::string dump() const
|
||||
{
|
||||
std::string result = "EventParams:\n";
|
||||
for (const auto &pair : m_values) {
|
||||
result += " " + pair.first + " = ";
|
||||
switch (pair.second.getType()) {
|
||||
case EventValue::NIL:
|
||||
result += "nil";
|
||||
break;
|
||||
case EventValue::ENTITY_ID:
|
||||
result += "entity:" +
|
||||
std::to_string(
|
||||
pair.second.getEntityId());
|
||||
break;
|
||||
case EventValue::INT:
|
||||
result += std::to_string(pair.second.getInt());
|
||||
break;
|
||||
case EventValue::FLOAT:
|
||||
result +=
|
||||
std::to_string(pair.second.getFloat());
|
||||
break;
|
||||
case EventValue::DOUBLE:
|
||||
result +=
|
||||
std::to_string(pair.second.getDouble());
|
||||
break;
|
||||
case EventValue::STRING:
|
||||
result += "'" + pair.second.getString() + "'";
|
||||
break;
|
||||
case EventValue::ENTITY_ID_ARRAY: {
|
||||
result += "[";
|
||||
const auto &arr =
|
||||
pair.second.getEntityIdArray();
|
||||
for (size_t i = 0; i < arr.size(); i++) {
|
||||
if (i > 0)
|
||||
result += ", ";
|
||||
result += "e:" + std::to_string(arr[i]);
|
||||
}
|
||||
result += "]";
|
||||
break;
|
||||
}
|
||||
case EventValue::INT_ARRAY: {
|
||||
result += "[";
|
||||
const auto &arr = pair.second.getIntArray();
|
||||
for (size_t i = 0; i < arr.size(); i++) {
|
||||
if (i > 0)
|
||||
result += ", ";
|
||||
result += std::to_string(arr[i]);
|
||||
}
|
||||
result += "]";
|
||||
break;
|
||||
}
|
||||
case EventValue::FLOAT_ARRAY: {
|
||||
result += "[";
|
||||
const auto &arr = pair.second.getFloatArray();
|
||||
for (size_t i = 0; i < arr.size(); i++) {
|
||||
if (i > 0)
|
||||
result += ", ";
|
||||
result += std::to_string(arr[i]);
|
||||
}
|
||||
result += "]";
|
||||
break;
|
||||
}
|
||||
case EventValue::DOUBLE_ARRAY: {
|
||||
result += "[";
|
||||
const auto &arr = pair.second.getDoubleArray();
|
||||
for (size_t i = 0; i < arr.size(); i++) {
|
||||
if (i > 0)
|
||||
result += ", ";
|
||||
result += std::to_string(arr[i]);
|
||||
}
|
||||
result += "]";
|
||||
break;
|
||||
}
|
||||
case EventValue::STRING_ARRAY: {
|
||||
result += "[";
|
||||
const auto &arr = pair.second.getStringArray();
|
||||
for (size_t i = 0; i < arr.size(); i++) {
|
||||
if (i > 0)
|
||||
result += ", ";
|
||||
result += "'" + arr[i] + "'";
|
||||
}
|
||||
result += "]";
|
||||
break;
|
||||
}
|
||||
}
|
||||
result += "\n";
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Allow LuaEventApi to access m_values directly for efficiency
|
||||
friend EventParams readEventParams(lua_State *L, int idx);
|
||||
|
||||
private:
|
||||
std::unordered_map<std::string, EventValue> m_values;
|
||||
};
|
||||
|
||||
} // namespace editScene
|
||||
|
||||
#endif // EDITSCENE_EVENT_PARAMS_HPP
|
||||
28
src/features/editScene/lua-examples/debug_crash_example.lua
Normal file
28
src/features/editScene/lua-examples/debug_crash_example.lua
Normal file
@@ -0,0 +1,28 @@
|
||||
--[[
|
||||
debug_crash_example.lua
|
||||
|
||||
Demonstrates the ecs.debug_crash() function which prints a message
|
||||
and then deliberately crashes the application via std::abort().
|
||||
|
||||
Usage:
|
||||
ecs.debug_crash("message") -- prints message and crashes
|
||||
ecs.debug_crash() -- uses default message "debug_crash called"
|
||||
|
||||
WARNING: This function will terminate the application!
|
||||
]]
|
||||
|
||||
-- Example 1: Crash with a custom message
|
||||
-- Uncomment to test:
|
||||
-- ecs.debug_crash("Something went terribly wrong!")
|
||||
|
||||
-- Example 2: Crash with default message
|
||||
-- Uncomment to test:
|
||||
-- ecs.debug_crash()
|
||||
|
||||
-- Example 3: Conditional crash for debugging
|
||||
-- local health = ecs.get_field(player_id, 'Character', 'health')
|
||||
-- if health <= 0 then
|
||||
-- ecs.debug_crash("Player health dropped to zero!")
|
||||
-- end
|
||||
|
||||
print("debug_crash example loaded (not executed - uncomment to test)")
|
||||
@@ -46,10 +46,8 @@ print("Dialogue entity created with ID: " .. dialogue_entity)
|
||||
-- and the player can click anywhere to dismiss it.
|
||||
|
||||
ecs.send_event("dialogue_show", {
|
||||
stringValues = {
|
||||
text = "Welcome to the world of World2!",
|
||||
speaker = "Narrator"
|
||||
}
|
||||
text = "Welcome to the world of World2!",
|
||||
speaker = "Narrator"
|
||||
})
|
||||
|
||||
print("Sent basic narration dialogue")
|
||||
@@ -61,11 +59,9 @@ print("Sent basic narration dialogue")
|
||||
-- buttons instead of click-to-progress. The player must pick one.
|
||||
|
||||
ecs.send_event("dialogue_show", {
|
||||
stringValues = {
|
||||
text = "Where would you like to go?",
|
||||
speaker = "Guide",
|
||||
choices = "The Forest,The Village,The Mountains"
|
||||
}
|
||||
text = "Where would you like to go?",
|
||||
speaker = "Guide",
|
||||
choices = "The Forest,The Village,The Mountains"
|
||||
})
|
||||
|
||||
print("Sent dialogue with choices")
|
||||
@@ -75,9 +71,7 @@ print("Sent dialogue with choices")
|
||||
-- ---------------------------------------------------------------------------
|
||||
|
||||
ecs.send_event("dialogue_show", {
|
||||
stringValues = {
|
||||
text = "A mysterious voice echoes through the chamber..."
|
||||
}
|
||||
text = "A mysterious voice echoes through the chamber..."
|
||||
})
|
||||
|
||||
print("Sent anonymous narration")
|
||||
@@ -87,10 +81,8 @@ print("Sent anonymous narration")
|
||||
-- ---------------------------------------------------------------------------
|
||||
|
||||
ecs.send_event("dialogue_show", {
|
||||
stringValues = {
|
||||
text = "Greetings, traveler.\n\nI have been expecting you.\nThe prophecy spoke of your arrival.",
|
||||
speaker = "Elder Marcus"
|
||||
}
|
||||
text = "Greetings, traveler.\n\nI have been expecting you.\nThe prophecy spoke of your arrival.",
|
||||
speaker = "Elder Marcus"
|
||||
})
|
||||
|
||||
print("Sent multi-line dialogue")
|
||||
@@ -100,10 +92,12 @@ print("Sent multi-line dialogue")
|
||||
-- =============================================================================
|
||||
-- To show dialogue from Lua:
|
||||
-- 1. Ensure an entity with DialogueComponent exists (create one if needed)
|
||||
-- 2. Call ecs.send_event("dialogue_show", { stringValues = { ... } })
|
||||
-- 2. Call ecs.send_event("dialogue_show", { text = "...", speaker = "...", choices = "..." })
|
||||
-- 3. Required: text = "The narration text"
|
||||
-- 4. Optional: speaker = "Speaker Name"
|
||||
-- 5. Optional: choices = "Choice1,Choice2,Choice3" (comma-separated)
|
||||
-- 6. EventParams uses flat key-value pairs (no nested stringValues/floatValues/etc.)
|
||||
-- 7. Type metadata is available via params._types table
|
||||
-- =============================================================================
|
||||
|
||||
print("Dialogue basic show examples completed!")
|
||||
|
||||
@@ -163,10 +163,8 @@ function update_npc_dialogue(npc_entity, new_text, new_speaker)
|
||||
|
||||
-- Show the updated dialogue via event (this triggers the state change)
|
||||
ecs.send_event("dialogue_show", {
|
||||
stringValues = {
|
||||
text = new_text,
|
||||
speaker = new_speaker or ecs.get_field(npc_entity, "Dialogue", "speaker")
|
||||
}
|
||||
text = new_text,
|
||||
speaker = new_speaker or ecs.get_field(npc_entity, "Dialogue", "speaker")
|
||||
})
|
||||
end
|
||||
|
||||
@@ -189,11 +187,9 @@ function show_dialogue_with_dynamic_choices(npc_entity, base_text, choice_list)
|
||||
|
||||
-- Show via event (which handles state transitions properly)
|
||||
ecs.send_event("dialogue_show", {
|
||||
stringValues = {
|
||||
text = base_text,
|
||||
speaker = ecs.get_field(npc_entity, "Dialogue", "speaker"),
|
||||
choices = choices_str
|
||||
}
|
||||
text = base_text,
|
||||
speaker = ecs.get_field(npc_entity, "Dialogue", "speaker"),
|
||||
choices = choices_str
|
||||
})
|
||||
end
|
||||
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
-- Combined with the EventBus, you can create complex event-driven dialogue
|
||||
-- sequences where one event triggers dialogue, and the player's choice
|
||||
-- triggers another event.
|
||||
--
|
||||
-- Event parameters use the EventParams type with flat key-value pairs.
|
||||
-- =============================================================================
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
@@ -76,22 +78,16 @@ function on_player_near_npc(npc_name, distance)
|
||||
if distance < 5.0 then
|
||||
-- Send the event that the EventHandler is listening for
|
||||
ecs.send_event("player_approached", {
|
||||
stringValues = {
|
||||
npc_name = npc_name,
|
||||
location = "town_square"
|
||||
},
|
||||
floatValues = {
|
||||
distance = distance
|
||||
}
|
||||
npc_name = npc_name,
|
||||
location = "town_square",
|
||||
distance = distance
|
||||
})
|
||||
|
||||
-- Also show dialogue directly
|
||||
ecs.send_event("dialogue_show", {
|
||||
stringValues = {
|
||||
text = "Hello there! I have a quest for a brave adventurer.",
|
||||
speaker = npc_name,
|
||||
choices = "I'll help!,What's the reward?,Not interested"
|
||||
}
|
||||
text = "Hello there! I have a quest for a brave adventurer.",
|
||||
speaker = npc_name,
|
||||
choices = "I'll help!,What's the reward?,Not interested"
|
||||
})
|
||||
end
|
||||
end
|
||||
@@ -107,46 +103,36 @@ on_player_near_npc("QuestGiver", 3.0)
|
||||
|
||||
-- Subscribe to dialogue choices
|
||||
local choice_sub = ecs.subscribe_event("dialogue_choice", function(event, params)
|
||||
local choice_index = params.values and params.values.choice_index or 0
|
||||
local choice_text = params.stringValues and params.stringValues.choice_text or ""
|
||||
local choice_index = params.choice_index or 0
|
||||
local choice_text = params.choice_text or ""
|
||||
|
||||
if choice_text == "I'll help!" then
|
||||
-- Player accepted the quest - trigger quest acceptance event
|
||||
ecs.send_event("quest_accepted", {
|
||||
stringValues = {
|
||||
quest_name = "The Lost Artifact",
|
||||
giver = "QuestGiver"
|
||||
},
|
||||
values = {
|
||||
reward_gold = 100,
|
||||
reward_xp = 500
|
||||
}
|
||||
quest_name = "The Lost Artifact",
|
||||
giver = "QuestGiver",
|
||||
reward_gold = 100,
|
||||
reward_xp = 500
|
||||
})
|
||||
|
||||
-- Show follow-up dialogue
|
||||
ecs.send_event("dialogue_show", {
|
||||
stringValues = {
|
||||
text = "Excellent! The ancient artifact was stolen from the temple.\nBring it back and you'll be richly rewarded!",
|
||||
speaker = "QuestGiver",
|
||||
choices = "Where is the temple?,I'm on it!,Tell me more"
|
||||
}
|
||||
text = "Excellent! The ancient artifact was stolen from the temple.\nBring it back and you'll be richly rewarded!",
|
||||
speaker = "QuestGiver",
|
||||
choices = "Where is the temple?,I'm on it!,Tell me more"
|
||||
})
|
||||
|
||||
elseif choice_text == "What's the reward?" then
|
||||
ecs.send_event("dialogue_show", {
|
||||
stringValues = {
|
||||
text = "100 gold pieces and a magical amulet! What do you say?",
|
||||
speaker = "QuestGiver",
|
||||
choices = "I'll help!,Sounds good,Maybe later"
|
||||
}
|
||||
text = "100 gold pieces and a magical amulet! What do you say?",
|
||||
speaker = "QuestGiver",
|
||||
choices = "I'll help!,Sounds good,Maybe later"
|
||||
})
|
||||
|
||||
elseif choice_text == "Not interested" then
|
||||
ecs.send_event("dialogue_show", {
|
||||
stringValues = {
|
||||
text = "Very well. The offer stands if you change your mind.",
|
||||
speaker = "QuestGiver"
|
||||
}
|
||||
text = "Very well. The offer stands if you change your mind.",
|
||||
speaker = "QuestGiver"
|
||||
})
|
||||
end
|
||||
end)
|
||||
@@ -159,17 +145,17 @@ print("Subscribed to dialogue_choice for event chaining")
|
||||
|
||||
-- Listen for quest acceptance
|
||||
local quest_sub = ecs.subscribe_event("quest_accepted", function(event, params)
|
||||
local quest_name = params.stringValues and params.stringValues.quest_name or "unknown"
|
||||
local reward = params.values and params.values.reward_gold or 0
|
||||
local xp = params.values and params.values.reward_xp or 0
|
||||
local quest_name = params.quest_name or "unknown"
|
||||
local reward = params.reward_gold or 0
|
||||
local xp = params.reward_xp or 0
|
||||
|
||||
print("Quest accepted: " .. quest_name)
|
||||
print(" Reward: " .. reward .. " gold, " .. xp .. " XP")
|
||||
|
||||
-- This could trigger other EventHandlers on other entities
|
||||
ecs.send_event("quest_log_updated", {
|
||||
stringValues = { quest_name = quest_name },
|
||||
values = { active_quests = 1 }
|
||||
quest_name = quest_name,
|
||||
active_quests = 1
|
||||
})
|
||||
end)
|
||||
|
||||
@@ -203,15 +189,13 @@ function on_zone_entered(zone_name)
|
||||
local dialogue = zone_dialogues[zone_name]
|
||||
if dialogue then
|
||||
ecs.send_event("dialogue_show", {
|
||||
stringValues = {
|
||||
text = dialogue.text,
|
||||
speaker = dialogue.speaker
|
||||
}
|
||||
text = dialogue.text,
|
||||
speaker = dialogue.speaker
|
||||
})
|
||||
|
||||
-- Also send a zone-specific event for other systems
|
||||
ecs.send_event("zone_entered", {
|
||||
stringValues = { zone = zone_name }
|
||||
zone = zone_name
|
||||
})
|
||||
end
|
||||
end
|
||||
@@ -237,10 +221,8 @@ function on_item_picked_up(item_name, item_count)
|
||||
local message = pickup_messages[item_name]
|
||||
if message then
|
||||
ecs.send_event("dialogue_show", {
|
||||
stringValues = {
|
||||
text = message,
|
||||
speaker = "Narrator"
|
||||
}
|
||||
text = message,
|
||||
speaker = "Narrator"
|
||||
})
|
||||
end
|
||||
end
|
||||
@@ -270,6 +252,9 @@ on_item_picked_up("gold_coins", 50)
|
||||
-- 5. Quest flow:
|
||||
-- Accept quest -> event -> update quest log -> next dialogue
|
||||
-- Complete quest -> event -> reward dialogue -> next dialogue
|
||||
--
|
||||
-- EventParams uses flat key-value pairs. Type metadata is available
|
||||
-- via params._types table (e.g., params._types.reward_gold = "int").
|
||||
-- =============================================================================
|
||||
|
||||
print("Dialogue EventHandler integration examples completed!")
|
||||
|
||||
@@ -11,6 +11,9 @@
|
||||
-- "dialogue_show" - Fired when dialogue should be displayed
|
||||
-- "dialogue_choice" - Fired when player selects a choice
|
||||
-- "dialogue_dismiss" - Fired when dialogue is dismissed (no choices)
|
||||
--
|
||||
-- Event parameters use the EventParams type, which supports flat
|
||||
-- key-value pairs with typed values. Use params._types to check types.
|
||||
-- =============================================================================
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
@@ -29,8 +32,8 @@ ecs.add_component(dialogue_entity, "Dialogue")
|
||||
-- choice index. We bridge this via the EventBus.
|
||||
|
||||
local choice_sub = ecs.subscribe_event("dialogue_choice", function(event, params)
|
||||
local choice_index = params.values and params.values.choice_index or 0
|
||||
local choice_text = params.stringValues and params.stringValues.choice_text or "unknown"
|
||||
local choice_index = params.choice_index or 0
|
||||
local choice_text = params.choice_text or "unknown"
|
||||
|
||||
print("Player selected choice #" .. choice_index .. ": " .. choice_text)
|
||||
|
||||
@@ -56,7 +59,7 @@ local dismiss_sub = ecs.subscribe_event("dialogue_dismiss", function(event, para
|
||||
print("Dialogue was dismissed by the player")
|
||||
|
||||
-- You could trigger follow-up dialogue or game logic here
|
||||
local next_text = params.stringValues and params.stringValues.next_text or ""
|
||||
local next_text = params.next_text or ""
|
||||
if next_text ~= "" then
|
||||
print(" -> Next dialogue queued: " .. next_text)
|
||||
end
|
||||
@@ -69,9 +72,9 @@ print("Subscribed to dialogue_dismiss events (ID: " .. dismiss_sub .. ")")
|
||||
-- ---------------------------------------------------------------------------
|
||||
|
||||
local show_sub = ecs.subscribe_event("dialogue_show", function(event, params)
|
||||
local text = params.stringValues and params.stringValues.text or ""
|
||||
local speaker = params.stringValues and params.stringValues.speaker or "Unknown"
|
||||
local choices = params.stringValues and params.stringValues.choices or ""
|
||||
local text = params.text or ""
|
||||
local speaker = params.speaker or "Unknown"
|
||||
local choices = params.choices or ""
|
||||
|
||||
print("[Dialogue Log] " .. speaker .. ": \"" .. text .. "\"")
|
||||
|
||||
@@ -90,11 +93,9 @@ print("Subscribed to dialogue_show events for logging (ID: " .. show_sub .. ")")
|
||||
function show_branching_dialogue()
|
||||
-- Step 1: Show the dialogue with choices
|
||||
ecs.send_event("dialogue_show", {
|
||||
stringValues = {
|
||||
text = "You see a dark cave entrance. What do you do?",
|
||||
speaker = "Narrator",
|
||||
choices = "Enter the cave,Look around first,Leave"
|
||||
}
|
||||
text = "You see a dark cave entrance. What do you do?",
|
||||
speaker = "Narrator",
|
||||
choices = "Enter the cave,Look around first,Leave"
|
||||
})
|
||||
|
||||
-- Step 2: The choice will be handled by our subscriber above.
|
||||
@@ -111,11 +112,9 @@ show_branching_dialogue()
|
||||
function npc_greeting(npc_name, greeting_text)
|
||||
-- Show initial greeting
|
||||
ecs.send_event("dialogue_show", {
|
||||
stringValues = {
|
||||
text = greeting_text,
|
||||
speaker = npc_name,
|
||||
choices = "Who are you?,Tell me about this place,Goodbye"
|
||||
}
|
||||
text = greeting_text,
|
||||
speaker = npc_name,
|
||||
choices = "Who are you?,Tell me about this place,Goodbye"
|
||||
})
|
||||
|
||||
-- The choice subscriber will handle the response.
|
||||
@@ -129,13 +128,16 @@ npc_greeting("Elder Marcus", "Ah, a new face in our village! Welcome, traveler."
|
||||
-- =============================================================================
|
||||
-- To handle dialogue choices from Lua:
|
||||
-- 1. Subscribe to "dialogue_choice" events
|
||||
-- 2. Check params.values.choice_index (1-based) to see which was picked
|
||||
-- 3. Check params.stringValues.choice_text for the label text
|
||||
-- 2. Check params.choice_index (1-based) to see which was picked
|
||||
-- 3. Check params.choice_text for the label text
|
||||
-- 4. React accordingly in your game logic
|
||||
--
|
||||
-- To handle dialogue dismissal:
|
||||
-- 1. Subscribe to "dialogue_dismiss" events
|
||||
-- 2. Trigger follow-up actions as needed
|
||||
--
|
||||
-- EventParams uses flat key-value pairs. Type metadata is available
|
||||
-- via params._types table (e.g., params._types.choice_index = "int").
|
||||
-- =============================================================================
|
||||
|
||||
print("Dialogue event subscription examples completed!")
|
||||
|
||||
@@ -35,8 +35,8 @@ local dialogue_queue_choice_text = ""
|
||||
-- Subscribe to choice events to unblock the queue
|
||||
local queue_choice_sub = ecs.subscribe_event("dialogue_choice", function(event, params)
|
||||
if dialogue_queue_pending then
|
||||
dialogue_queue_choice = params.values and params.values.choice_index or 0
|
||||
dialogue_queue_choice_text = params.stringValues and params.stringValues.choice_text or ""
|
||||
dialogue_queue_choice = params.choice_index or 0
|
||||
dialogue_queue_choice_text = params.choice_text or ""
|
||||
dialogue_queue_pending = false
|
||||
end
|
||||
end)
|
||||
@@ -61,11 +61,9 @@ end)
|
||||
function show_and_wait(text, speaker, choices)
|
||||
-- Send the dialogue event
|
||||
ecs.send_event("dialogue_show", {
|
||||
stringValues = {
|
||||
text = text,
|
||||
speaker = speaker or "",
|
||||
choices = choices or ""
|
||||
}
|
||||
text = text,
|
||||
speaker = speaker or "",
|
||||
choices = choices or ""
|
||||
})
|
||||
|
||||
-- Wait for player response
|
||||
@@ -97,10 +95,8 @@ function simple_conversation()
|
||||
|
||||
-- Line 1: Narration with no choices (click to continue)
|
||||
ecs.send_event("dialogue_show", {
|
||||
stringValues = {
|
||||
text = "The old man sits by the fire, staring into the flames.",
|
||||
speaker = "Narrator"
|
||||
}
|
||||
text = "The old man sits by the fire, staring into the flames.",
|
||||
speaker = "Narrator"
|
||||
})
|
||||
|
||||
-- In a real game, you'd wait for the dismiss event here.
|
||||
@@ -108,11 +104,9 @@ function simple_conversation()
|
||||
|
||||
-- Line 2: NPC speaks with choices
|
||||
ecs.send_event("dialogue_show", {
|
||||
stringValues = {
|
||||
text = "I've been expecting you. The darkness grows stronger each day.",
|
||||
speaker = "Old Man",
|
||||
choices = "Tell me more,How can I help?,I must go"
|
||||
}
|
||||
text = "I've been expecting you. The darkness grows stronger each day.",
|
||||
speaker = "Old Man",
|
||||
choices = "Tell me more,How can I help?,I must go"
|
||||
})
|
||||
|
||||
print(" (Player would now see choices and pick one)")
|
||||
@@ -232,11 +226,9 @@ function run_conversation(tree, start_node)
|
||||
|
||||
-- Show the dialogue
|
||||
ecs.send_event("dialogue_show", {
|
||||
stringValues = {
|
||||
text = node.text,
|
||||
speaker = node.speaker or "",
|
||||
choices = choices_str
|
||||
}
|
||||
text = node.text,
|
||||
speaker = node.speaker or "",
|
||||
choices = choices_str
|
||||
})
|
||||
|
||||
-- In a real game, you'd wait for the player's choice here.
|
||||
@@ -278,40 +270,32 @@ function talk_to_elder_marcus()
|
||||
npc_state.marcus_friendship = 10
|
||||
|
||||
ecs.send_event("dialogue_show", {
|
||||
stringValues = {
|
||||
text = "Ah, a new face! I am Elder Marcus, keeper of this village.\nIt's been so long since we had visitors.",
|
||||
speaker = "Elder Marcus",
|
||||
choices = "Pleasure to meet you,I've heard stories about you,Hello"
|
||||
}
|
||||
text = "Ah, a new face! I am Elder Marcus, keeper of this village.\nIt's been so long since we had visitors.",
|
||||
speaker = "Elder Marcus",
|
||||
choices = "Pleasure to meet you,I've heard stories about you,Hello"
|
||||
})
|
||||
elseif npc_state.quest_active and npc_state.quest_completed then
|
||||
-- Quest completed
|
||||
npc_state.marcus_friendship = npc_state.marcus_friendship + 50
|
||||
|
||||
ecs.send_event("dialogue_show", {
|
||||
stringValues = {
|
||||
text = "You did it! The village is safe thanks to you.\nPlease, take this reward.",
|
||||
speaker = "Elder Marcus",
|
||||
choices = "Thank you, elder,I was happy to help"
|
||||
}
|
||||
text = "You did it! The village is safe thanks to you.\nPlease, take this reward.",
|
||||
speaker = "Elder Marcus",
|
||||
choices = "Thank you, elder,I was happy to help"
|
||||
})
|
||||
elseif npc_state.quest_active then
|
||||
-- Quest in progress
|
||||
ecs.send_event("dialogue_show", {
|
||||
stringValues = {
|
||||
text = "Have you dealt with those bandits yet?\nThe villagers are growing anxious.",
|
||||
speaker = "Elder Marcus",
|
||||
choices = "I'm working on it,I need more information,Not yet"
|
||||
}
|
||||
text = "Have you dealt with those bandits yet?\nThe villagers are growing anxious.",
|
||||
speaker = "Elder Marcus",
|
||||
choices = "I'm working on it,I need more information,Not yet"
|
||||
})
|
||||
else
|
||||
-- Regular greeting
|
||||
ecs.send_event("dialogue_show", {
|
||||
stringValues = {
|
||||
text = "Welcome back, friend. The village is peaceful today.",
|
||||
speaker = "Elder Marcus",
|
||||
choices = "Any news?,I need supplies,Goodbye"
|
||||
}
|
||||
text = "Welcome back, friend. The village is peaceful today.",
|
||||
speaker = "Elder Marcus",
|
||||
choices = "Any news?,I need supplies,Goodbye"
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
@@ -7,6 +7,20 @@
|
||||
-- Events are a publish/subscribe mechanism. Any part of the game can send
|
||||
-- an event, and any subscriber can react to it. This decouples systems
|
||||
-- from each other.
|
||||
--
|
||||
-- Event parameters use flat key-value pairs (no nested tables like
|
||||
-- stringValues/floatValues/values). Each value is typed automatically:
|
||||
-- - integer -> int
|
||||
-- - number (non-integer) -> double
|
||||
-- - string -> string
|
||||
-- - table of integers -> int_array
|
||||
-- - table of numbers -> double_array
|
||||
-- - table of strings -> string_array
|
||||
-- - table of entity IDs -> entity_id_array
|
||||
--
|
||||
-- Type metadata is available via params._types table:
|
||||
-- params._types.key = "int" | "double" | "string" | "int_array" |
|
||||
-- "double_array" | "string_array" | "entity_id_array"
|
||||
-- =============================================================================
|
||||
|
||||
-- =============================================================================
|
||||
@@ -28,10 +42,10 @@ print("Subscribed to 'hello' with ID: " .. sub_id)
|
||||
-- Send a simple event with no parameters:
|
||||
ecs.send_event("hello")
|
||||
|
||||
-- Send an event with parameters:
|
||||
-- Send an event with parameters (flat key-value pairs):
|
||||
ecs.send_event("hello", {
|
||||
values = { count = 42 },
|
||||
stringValues = { message = "Hello World!" }
|
||||
count = 42,
|
||||
message = "Hello World!"
|
||||
})
|
||||
|
||||
-- =============================================================================
|
||||
@@ -45,27 +59,22 @@ ecs.send_event("hello", {
|
||||
ecs.subscribe_event("player_damaged", function(event, params)
|
||||
print("Event: " .. event)
|
||||
if params then
|
||||
if params.values then
|
||||
print(" Damage: " .. (params.values.damage or 0))
|
||||
print(" Health remaining: " .. (params.values.health or 0))
|
||||
end
|
||||
if params.stringValues then
|
||||
print(" Source: " .. (params.stringValues.source or "unknown"))
|
||||
end
|
||||
if params.vec3Values then
|
||||
local pos = params.vec3Values.position
|
||||
if pos then
|
||||
print(" Position: " .. pos[1] .. ", " .. pos[2] .. ", " .. pos[3])
|
||||
end
|
||||
print(" Damage: " .. (params.damage or 0))
|
||||
print(" Health remaining: " .. (params.health or 0))
|
||||
print(" Source: " .. (params.source or "unknown"))
|
||||
local pos = params.position
|
||||
if pos then
|
||||
print(" Position: " .. pos[1] .. ", " .. pos[2] .. ", " .. pos[3])
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
-- Send a damage event:
|
||||
ecs.send_event("player_damaged", {
|
||||
values = { damage = 25, health = 75 },
|
||||
stringValues = { source = "goblin_archer" },
|
||||
vec3Values = { position = { 10, 0, 20 } }
|
||||
damage = 25,
|
||||
health = 75,
|
||||
source = "goblin_archer",
|
||||
position = { 10, 0, 20 }
|
||||
})
|
||||
|
||||
-- =============================================================================
|
||||
@@ -110,53 +119,51 @@ ecs.send_event("multi") -- both fire again
|
||||
-- Event Parameter Types
|
||||
-- =============================================================================
|
||||
|
||||
-- Events can carry various types of data in their params table:
|
||||
-- Events can carry various types of data in their params table.
|
||||
-- All values are flat keys with automatic type inference:
|
||||
|
||||
ecs.subscribe_event("data_event", function(event, params)
|
||||
print("Received data_event with:")
|
||||
if params then
|
||||
-- Integer values:
|
||||
if params.values then
|
||||
for k, v in pairs(params.values) do
|
||||
print(" int " .. k .. " = " .. v)
|
||||
-- Print all keys with their types
|
||||
for k, v in pairs(params) do
|
||||
if k ~= "_types" then
|
||||
local t = type(v)
|
||||
if t == "table" then
|
||||
local arr_str = "{"
|
||||
for i, elem in ipairs(v) do
|
||||
if i > 1 then arr_str = arr_str .. ", " end
|
||||
arr_str = arr_str .. tostring(elem)
|
||||
end
|
||||
arr_str = arr_str .. "}"
|
||||
print(" " .. k .. " (array) = " .. arr_str)
|
||||
else
|
||||
print(" " .. k .. " (" .. t .. ") = " .. tostring(v))
|
||||
end
|
||||
end
|
||||
end
|
||||
-- Float values:
|
||||
if params.floatValues then
|
||||
for k, v in pairs(params.floatValues) do
|
||||
print(" float " .. k .. " = " .. v)
|
||||
-- Print type metadata
|
||||
if params._types then
|
||||
print(" Type metadata:")
|
||||
for k, t in pairs(params._types) do
|
||||
print(" " .. k .. " -> " .. t)
|
||||
end
|
||||
end
|
||||
-- String values:
|
||||
if params.stringValues then
|
||||
for k, v in pairs(params.stringValues) do
|
||||
print(" string " .. k .. " = '" .. v .. "'")
|
||||
end
|
||||
end
|
||||
-- Vec3 values:
|
||||
if params.vec3Values then
|
||||
for k, v in pairs(params.vec3Values) do
|
||||
print(" vec3 " .. k .. " = (" .. v[1] .. ", " .. v[2] .. ", " .. v[3] .. ")")
|
||||
end
|
||||
end
|
||||
-- Bit flags:
|
||||
if params.bits ~= nil then
|
||||
print(" bits = " .. params.bits)
|
||||
end
|
||||
if params.mask ~= nil then
|
||||
print(" mask = " .. params.mask)
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
-- Send an event with all parameter types:
|
||||
ecs.send_event("data_event", {
|
||||
values = { score = 100, level = 5, kills = 42 },
|
||||
floatValues = { speed = 1.5, health = 75.5 },
|
||||
stringValues = { name = "Hero", state = "exploring" },
|
||||
vec3Values = { position = { 10, 20, 30 }, velocity = { 1, 0, 0 } },
|
||||
bits = 5,
|
||||
mask = 7
|
||||
score = 100,
|
||||
level = 5,
|
||||
kills = 42,
|
||||
speed = 1.5,
|
||||
health = 75.5,
|
||||
name = "Hero",
|
||||
state = "exploring",
|
||||
position = { 10, 20, 30 },
|
||||
velocity = { 1, 0, 0 },
|
||||
tags = { "warrior", "human", "player" }
|
||||
})
|
||||
|
||||
-- =============================================================================
|
||||
@@ -170,41 +177,41 @@ local event_handlers = {}
|
||||
function register_game_handlers()
|
||||
-- Quest events:
|
||||
event_handlers.quest_complete = ecs.subscribe_event("quest_complete", function(event, params)
|
||||
local quest_name = params and params.stringValues and params.stringValues.quest_name or "unknown"
|
||||
local reward_xp = params and params.values and params.values.reward_xp or 0
|
||||
local quest_name = params and params.quest_name or "unknown"
|
||||
local reward_xp = params and params.reward_xp or 0
|
||||
print("Quest completed: " .. quest_name .. " (+" .. reward_xp .. " XP)")
|
||||
end)
|
||||
|
||||
-- Combat events:
|
||||
event_handlers.enemy_killed = ecs.subscribe_event("enemy_killed", function(event, params)
|
||||
local enemy = params and params.stringValues and params.stringValues.enemy_type or "unknown"
|
||||
local xp = params and params.values and params.values.xp_reward or 0
|
||||
local enemy = params and params.enemy_type or "unknown"
|
||||
local xp = params and params.xp_reward or 0
|
||||
print("Killed " .. enemy .. " (+" .. xp .. " XP)")
|
||||
end)
|
||||
|
||||
-- Item events:
|
||||
event_handlers.item_picked_up = ecs.subscribe_event("item_picked_up", function(event, params)
|
||||
local item = params and params.stringValues and params.stringValues.item_name or "unknown"
|
||||
local count = params and params.values and params.values.count or 1
|
||||
local item = params and params.item_name or "unknown"
|
||||
local count = params and params.count or 1
|
||||
print("Picked up " .. count .. "x " .. item)
|
||||
end)
|
||||
|
||||
-- Dialogue events:
|
||||
event_handlers.dialogue_started = ecs.subscribe_event("dialogue_started", function(event, params)
|
||||
local npc = params and params.stringValues and params.stringValues.npc_name or "unknown"
|
||||
local npc = params and params.npc_name or "unknown"
|
||||
print("Started dialogue with " .. npc)
|
||||
end)
|
||||
|
||||
-- Environment events:
|
||||
event_handlers.time_changed = ecs.subscribe_event("time_changed", function(event, params)
|
||||
local hour = params and params.values and params.values.hour or 0
|
||||
local minute = params and params.values and params.values.minute or 0
|
||||
local hour = params and params.hour or 0
|
||||
local minute = params and params.minute or 0
|
||||
print("Time changed to " .. hour .. ":" .. string.format("%02d", minute))
|
||||
end)
|
||||
|
||||
-- Player events:
|
||||
event_handlers.player_died = ecs.subscribe_event("player_died", function(event, params)
|
||||
local killer = params and params.stringValues and params.stringValues.killed_by or "unknown"
|
||||
local killer = params and params.killed_by or "unknown"
|
||||
print("Player was killed by " .. killer .. "!")
|
||||
end)
|
||||
|
||||
@@ -215,26 +222,27 @@ register_game_handlers()
|
||||
|
||||
-- Simulate some game events:
|
||||
ecs.send_event("quest_complete", {
|
||||
stringValues = { quest_name = "The Lost Artifact" },
|
||||
values = { reward_xp = 500 }
|
||||
quest_name = "The Lost Artifact",
|
||||
reward_xp = 500
|
||||
})
|
||||
|
||||
ecs.send_event("enemy_killed", {
|
||||
stringValues = { enemy_type = "Goblin Warrior" },
|
||||
values = { xp_reward = 50 }
|
||||
enemy_type = "Goblin Warrior",
|
||||
xp_reward = 50
|
||||
})
|
||||
|
||||
ecs.send_event("item_picked_up", {
|
||||
stringValues = { item_name = "Health Potion" },
|
||||
values = { count = 2 }
|
||||
item_name = "Health Potion",
|
||||
count = 2
|
||||
})
|
||||
|
||||
ecs.send_event("dialogue_started", {
|
||||
stringValues = { npc_name = "Elder Marcus" }
|
||||
npc_name = "Elder Marcus"
|
||||
})
|
||||
|
||||
ecs.send_event("time_changed", {
|
||||
values = { hour = 18, minute = 30 }
|
||||
hour = 18,
|
||||
minute = 30
|
||||
})
|
||||
|
||||
-- =============================================================================
|
||||
@@ -273,13 +281,16 @@ end
|
||||
-- Send an event to all subscribers.
|
||||
-- Parameters:
|
||||
-- event_name - string, the name of the event to send
|
||||
-- params - optional table with event data:
|
||||
-- .values - table of integer key-value pairs
|
||||
-- .floatValues - table of float key-value pairs
|
||||
-- .stringValues - table of string key-value pairs
|
||||
-- .vec3Values - table of vec3 key-value pairs (each vec3 is {x, y, z})
|
||||
-- .bits - integer bit flags
|
||||
-- .mask - integer bit mask
|
||||
-- params - optional table with flat key-value pairs:
|
||||
-- key = integer -> stored as int
|
||||
-- key = number -> stored as double
|
||||
-- key = string -> stored as string
|
||||
-- key = {int, ...} -> stored as int_array
|
||||
-- key = {num, ...} -> stored as double_array
|
||||
-- key = {str, ...} -> stored as string_array
|
||||
-- Type metadata is available via params._types table:
|
||||
-- params._types.key = "int" | "double" | "string" |
|
||||
-- "int_array" | "double_array" | "string_array"
|
||||
-- =============================================================================
|
||||
|
||||
print("Event API examples completed successfully!")
|
||||
|
||||
135
src/features/editScene/lua-examples/game_mode_example.lua
Normal file
135
src/features/editScene/lua-examples/game_mode_example.lua
Normal file
@@ -0,0 +1,135 @@
|
||||
-- =============================================================================
|
||||
-- Game Mode Lua API Examples
|
||||
-- =============================================================================
|
||||
-- This file demonstrates how to query the editor/game mode state from Lua
|
||||
-- using the ecs.* Lua API.
|
||||
--
|
||||
-- The application can be in one of two modes:
|
||||
-- - Editor mode: the scene editor is active
|
||||
-- - Game mode: the game is running (with sub-states: menu, playing, paused)
|
||||
--
|
||||
-- These functions allow Lua scripts to adapt their behavior based on the
|
||||
-- current application mode.
|
||||
-- =============================================================================
|
||||
|
||||
-- =============================================================================
|
||||
-- Querying the Current Mode
|
||||
-- =============================================================================
|
||||
|
||||
-- Check if we are in editor mode:
|
||||
if ecs.is_editor_mode() then
|
||||
print("Application is in EDITOR mode")
|
||||
end
|
||||
|
||||
-- Check if we are in game mode (any play state):
|
||||
if ecs.is_game_mode() then
|
||||
print("Application is in GAME mode")
|
||||
end
|
||||
|
||||
-- Get the mode as a string:
|
||||
local mode = ecs.get_game_mode()
|
||||
print("Current mode: " .. mode) -- "editor" or "game"
|
||||
|
||||
-- =============================================================================
|
||||
-- Querying the Gameplay State (only meaningful in game mode)
|
||||
-- =============================================================================
|
||||
|
||||
-- Check specific gameplay states:
|
||||
if ecs.is_game_playing() then
|
||||
print("Game is PLAYING")
|
||||
end
|
||||
|
||||
if ecs.is_game_menu() then
|
||||
print("Game is in MENU")
|
||||
end
|
||||
|
||||
if ecs.is_game_paused() then
|
||||
print("Game is PAUSED")
|
||||
end
|
||||
|
||||
-- Get the play state as a string:
|
||||
local state = ecs.get_game_play_state()
|
||||
print("Game play state: " .. state) -- "menu", "playing", or "paused"
|
||||
|
||||
-- =============================================================================
|
||||
-- Practical Examples
|
||||
-- =============================================================================
|
||||
|
||||
-- Example 1: Only run editor-specific logic in editor mode
|
||||
function update_editor_ui()
|
||||
if ecs.is_editor_mode() then
|
||||
-- Show editor UI elements
|
||||
print("Updating editor UI...")
|
||||
end
|
||||
end
|
||||
|
||||
-- Example 2: Only process game input when game is playing
|
||||
function process_game_input()
|
||||
if ecs.is_game_playing() then
|
||||
-- Process player input
|
||||
print("Processing game input...")
|
||||
end
|
||||
end
|
||||
|
||||
-- Example 3: Show/hide pause menu
|
||||
function toggle_pause_menu()
|
||||
if ecs.is_game_paused() then
|
||||
-- Show pause menu overlay
|
||||
print("Showing pause menu...")
|
||||
else
|
||||
-- Hide pause menu
|
||||
print("Hiding pause menu...")
|
||||
end
|
||||
end
|
||||
|
||||
-- Example 4: Conditional behavior based on mode
|
||||
function on_entity_clicked(entity_id)
|
||||
if ecs.is_editor_mode() then
|
||||
-- In editor: select the entity
|
||||
print("Selected entity " .. entity_id .. " in editor")
|
||||
elseif ecs.is_game_playing() then
|
||||
-- In game: interact with the entity
|
||||
print("Interacting with entity " .. entity_id)
|
||||
end
|
||||
end
|
||||
|
||||
-- =============================================================================
|
||||
-- Using Mode Queries in Event Handlers
|
||||
-- =============================================================================
|
||||
|
||||
-- Register an event handler that checks mode:
|
||||
function on_frame_update()
|
||||
if ecs.is_game_playing() then
|
||||
-- Update game logic
|
||||
elseif ecs.is_editor_mode() then
|
||||
-- Update editor logic
|
||||
end
|
||||
end
|
||||
|
||||
-- =============================================================================
|
||||
-- Error Handling
|
||||
-- =============================================================================
|
||||
|
||||
-- All functions return valid values even if called at unexpected times:
|
||||
local m = ecs.get_game_mode()
|
||||
assert(type(m) == "string", "get_game_mode should return a string")
|
||||
|
||||
local s = ecs.get_game_play_state()
|
||||
assert(type(s) == "string", "get_game_play_state should return a string")
|
||||
|
||||
local b1 = ecs.is_editor_mode()
|
||||
assert(type(b1) == "boolean", "is_editor_mode should return a boolean")
|
||||
|
||||
local b2 = ecs.is_game_mode()
|
||||
assert(type(b2) == "boolean", "is_game_mode should return a boolean")
|
||||
|
||||
local b3 = ecs.is_game_playing()
|
||||
assert(type(b3) == "boolean", "is_game_playing should return a boolean")
|
||||
|
||||
local b4 = ecs.is_game_menu()
|
||||
assert(type(b4) == "boolean", "is_game_menu should return a boolean")
|
||||
|
||||
local b5 = ecs.is_game_paused()
|
||||
assert(type(b5) == "boolean", "is_game_paused should return a boolean")
|
||||
|
||||
print("Game mode API examples completed successfully!")
|
||||
@@ -1,9 +1,8 @@
|
||||
#include "LuaEventApi.hpp"
|
||||
#include "LuaEntityApi.hpp"
|
||||
#include "../systems/EventBus.hpp"
|
||||
#include "../components/GoapBlackboard.hpp"
|
||||
#include "../components/EventParams.hpp"
|
||||
#include <OgreLogManager.h>
|
||||
#include <OgreVector3.h>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace editScene
|
||||
@@ -23,161 +22,312 @@ static std::unordered_map<int, EventBus::ListenerId> s_luaSubscriptions;
|
||||
static int s_nextLuaSubId = 1;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helper: push a GoapBlackboard as a Lua table
|
||||
// Helper: push an EventValue as a Lua value
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief Push a GoapBlackboard as a Lua table with named fields.
|
||||
* @brief Push a single EventValue onto the Lua stack.
|
||||
*
|
||||
* 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.
|
||||
* @param L Lua state.
|
||||
* @param val The EventValue to push.
|
||||
*/
|
||||
static void pushGoapBlackboard(lua_State *L, const GoapBlackboard &bb)
|
||||
static void pushEventValue(lua_State *L, const EventValue &val)
|
||||
{
|
||||
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) {
|
||||
switch (val.getType()) {
|
||||
case EventValue::NIL:
|
||||
lua_pushnil(L);
|
||||
break;
|
||||
case EventValue::ENTITY_ID:
|
||||
lua_pushinteger(L, (lua_Integer)val.getEntityId());
|
||||
break;
|
||||
case EventValue::INT:
|
||||
lua_pushinteger(L, (lua_Integer)val.getInt());
|
||||
break;
|
||||
case EventValue::FLOAT:
|
||||
lua_pushnumber(L, (lua_Number)val.getFloat());
|
||||
break;
|
||||
case EventValue::DOUBLE:
|
||||
lua_pushnumber(L, (lua_Number)val.getDouble());
|
||||
break;
|
||||
case EventValue::STRING:
|
||||
lua_pushstring(L, val.getString().c_str());
|
||||
break;
|
||||
case EventValue::ENTITY_ID_ARRAY: {
|
||||
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());
|
||||
const auto &arr = val.getEntityIdArray();
|
||||
for (size_t i = 0; i < arr.size(); i++) {
|
||||
lua_pushinteger(L, (lua_Integer)arr[i]);
|
||||
lua_rawseti(L, -2, (int)(i + 1));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case EventValue::INT_ARRAY: {
|
||||
lua_newtable(L);
|
||||
const auto &arr = val.getIntArray();
|
||||
for (size_t i = 0; i < arr.size(); i++) {
|
||||
lua_pushinteger(L, (lua_Integer)arr[i]);
|
||||
lua_rawseti(L, -2, (int)(i + 1));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case EventValue::FLOAT_ARRAY: {
|
||||
lua_newtable(L);
|
||||
const auto &arr = val.getFloatArray();
|
||||
for (size_t i = 0; i < arr.size(); i++) {
|
||||
lua_pushnumber(L, (lua_Number)arr[i]);
|
||||
lua_rawseti(L, -2, (int)(i + 1));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case EventValue::DOUBLE_ARRAY: {
|
||||
lua_newtable(L);
|
||||
const auto &arr = val.getDoubleArray();
|
||||
for (size_t i = 0; i < arr.size(); i++) {
|
||||
lua_pushnumber(L, (lua_Number)arr[i]);
|
||||
lua_rawseti(L, -2, (int)(i + 1));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case EventValue::STRING_ARRAY: {
|
||||
lua_newtable(L);
|
||||
const auto &arr = val.getStringArray();
|
||||
for (size_t i = 0; i < arr.size(); i++) {
|
||||
lua_pushstring(L, arr[i].c_str());
|
||||
lua_rawseti(L, -2, (int)(i + 1));
|
||||
}
|
||||
break;
|
||||
}
|
||||
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.
|
||||
* @brief Push the type name string for an EventValue type.
|
||||
*/
|
||||
static const char *eventValueTypeName(EventValue::Type type)
|
||||
{
|
||||
switch (type) {
|
||||
case EventValue::NIL:
|
||||
return "nil";
|
||||
case EventValue::ENTITY_ID:
|
||||
return "entity_id";
|
||||
case EventValue::INT:
|
||||
return "int";
|
||||
case EventValue::FLOAT:
|
||||
return "float";
|
||||
case EventValue::DOUBLE:
|
||||
return "double";
|
||||
case EventValue::STRING:
|
||||
return "string";
|
||||
case EventValue::ENTITY_ID_ARRAY:
|
||||
return "entity_id_array";
|
||||
case EventValue::INT_ARRAY:
|
||||
return "int_array";
|
||||
case EventValue::FLOAT_ARRAY:
|
||||
return "float_array";
|
||||
case EventValue::DOUBLE_ARRAY:
|
||||
return "double_array";
|
||||
case EventValue::STRING_ARRAY:
|
||||
return "string_array";
|
||||
}
|
||||
return "nil";
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helper: push an EventParams as a Lua table
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief Push an EventParams as a Lua table with named fields.
|
||||
*
|
||||
* Expects the same format as pushGoapBlackboard produces.
|
||||
* The resulting table has each key mapped to its value, plus a _types
|
||||
* sub-table that maps each key to its type name string.
|
||||
*
|
||||
* @param L Lua state.
|
||||
* @param params The EventParams to convert.
|
||||
*/
|
||||
static void pushEventParams(lua_State *L, const EventParams ¶ms)
|
||||
{
|
||||
lua_newtable(L);
|
||||
|
||||
// Push _types sub-table
|
||||
lua_newtable(L);
|
||||
|
||||
for (EventParams::ConstIterator it = params.begin(); it != params.end();
|
||||
++it) {
|
||||
const std::string &key = it->first;
|
||||
const EventValue &val = it->second;
|
||||
|
||||
// Push the value
|
||||
pushEventValue(L, val);
|
||||
lua_setfield(L, -3, key.c_str());
|
||||
|
||||
// Push the type name into _types
|
||||
lua_pushstring(L, eventValueTypeName(val.getType()));
|
||||
lua_setfield(L, -2, key.c_str());
|
||||
}
|
||||
|
||||
// Set _types sub-table
|
||||
lua_setfield(L, -2, "_types");
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helper: read a Lua value as an EventValue
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief Read a Lua value at the given index as an EventValue.
|
||||
*
|
||||
* Type inference rules:
|
||||
* - integer -> INT
|
||||
* - number (non-integer) -> DOUBLE
|
||||
* - string -> STRING
|
||||
* - table with all integer keys -> array (type inferred from elements)
|
||||
* - table with string keys -> not supported at value level
|
||||
*
|
||||
* @param L Lua state.
|
||||
* @param idx Stack index of the table.
|
||||
* @return GoapBlackboard populated from the table.
|
||||
* @param idx Stack index of the value.
|
||||
* @return EventValue representing the Lua value.
|
||||
*/
|
||||
static GoapBlackboard readGoapBlackboard(lua_State *L, int idx)
|
||||
static EventValue readLuaValue(lua_State *L, int idx)
|
||||
{
|
||||
GoapBlackboard bb;
|
||||
int absIdx = lua_absindex(L, idx);
|
||||
int type = lua_type(L, absIdx);
|
||||
|
||||
// bits
|
||||
lua_getfield(L, idx, "bits");
|
||||
if (lua_isnumber(L, -1))
|
||||
bb.bits = (uint64_t)lua_tointeger(L, -1);
|
||||
lua_pop(L, 1);
|
||||
switch (type) {
|
||||
case LUA_TNIL:
|
||||
return EventValue();
|
||||
|
||||
// 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);
|
||||
case LUA_TNUMBER: {
|
||||
lua_Number num = lua_tonumber(L, absIdx);
|
||||
lua_Integer intVal = lua_tointeger(L, absIdx);
|
||||
// Check if it's an integer (within precision)
|
||||
if ((lua_Number)intVal == num) {
|
||||
return EventValue((int64_t)intVal);
|
||||
}
|
||||
return EventValue((double)num);
|
||||
}
|
||||
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);
|
||||
case LUA_TSTRING:
|
||||
return EventValue(lua_tostring(L, absIdx));
|
||||
|
||||
case LUA_TBOOLEAN:
|
||||
return EventValue((int64_t)(lua_toboolean(L, absIdx) ? 1 : 0));
|
||||
|
||||
case LUA_TTABLE: {
|
||||
// Check if it's an array (all integer keys)
|
||||
bool isArray = true;
|
||||
int maxKey = 0;
|
||||
bool hasStringElements = false;
|
||||
bool hasNumberElements = false;
|
||||
bool hasIntegerElements = false;
|
||||
|
||||
// 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;
|
||||
while (lua_next(L, absIdx) != 0) {
|
||||
if (lua_type(L, -2) == LUA_TNUMBER) {
|
||||
int k = (int)lua_tointeger(L, -2);
|
||||
if (k > maxKey)
|
||||
maxKey = k;
|
||||
int elemType = lua_type(L, -1);
|
||||
if (elemType == LUA_TSTRING)
|
||||
hasStringElements = true;
|
||||
else if (elemType == LUA_TNUMBER) {
|
||||
lua_Number num = lua_tonumber(L, -1);
|
||||
lua_Integer intVal =
|
||||
lua_tointeger(L, -1);
|
||||
if ((lua_Number)intVal == num)
|
||||
hasIntegerElements = true;
|
||||
else
|
||||
hasNumberElements = true;
|
||||
}
|
||||
} else {
|
||||
isArray = false;
|
||||
}
|
||||
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);
|
||||
if (isArray && maxKey > 0) {
|
||||
if (hasStringElements) {
|
||||
std::vector<std::string> arr;
|
||||
for (int i = 1; i <= maxKey; i++) {
|
||||
lua_rawgeti(L, absIdx, i);
|
||||
if (lua_type(L, -1) == LUA_TSTRING)
|
||||
arr.push_back(
|
||||
lua_tostring(L, -1));
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
return EventValue(arr);
|
||||
} else if (hasIntegerElements) {
|
||||
std::vector<int64_t> arr;
|
||||
for (int i = 1; i <= maxKey; i++) {
|
||||
lua_rawgeti(L, absIdx, i);
|
||||
if (lua_type(L, -1) == LUA_TNUMBER)
|
||||
arr.push_back(
|
||||
(int64_t)lua_tointeger(
|
||||
L, -1));
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
return EventValue(arr);
|
||||
} else if (hasNumberElements) {
|
||||
std::vector<double> arr;
|
||||
for (int i = 1; i <= maxKey; i++) {
|
||||
lua_rawgeti(L, absIdx, i);
|
||||
if (lua_type(L, -1) == LUA_TNUMBER)
|
||||
arr.push_back(
|
||||
lua_tonumber(L, -1));
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
return EventValue(arr);
|
||||
}
|
||||
}
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
|
||||
return bb;
|
||||
// Not an array or empty - return nil
|
||||
return EventValue();
|
||||
}
|
||||
|
||||
default:
|
||||
return EventValue();
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helper: read a Lua table as EventParams
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief Read a Lua table at the given index as EventParams.
|
||||
*
|
||||
* Each key-value pair in the table is converted to an EventValue.
|
||||
* The _types sub-table is NOT read back (types are inferred from values).
|
||||
*
|
||||
* @param L Lua state.
|
||||
* @param idx Stack index of the table.
|
||||
* @return EventParams populated from the table.
|
||||
*/
|
||||
EventParams readEventParams(lua_State *L, int idx)
|
||||
{
|
||||
EventParams params;
|
||||
int absIdx = lua_absindex(L, idx);
|
||||
|
||||
if (!lua_istable(L, absIdx))
|
||||
return params;
|
||||
|
||||
lua_pushnil(L);
|
||||
while (lua_next(L, absIdx) != 0) {
|
||||
if (lua_type(L, -2) == LUA_TSTRING) {
|
||||
const char *key = lua_tostring(L, -2);
|
||||
if (key) {
|
||||
// Skip _types key (metadata)
|
||||
if (strcmp(key, "_types") == 0) {
|
||||
lua_pop(L, 1);
|
||||
continue;
|
||||
}
|
||||
params.m_values[key] = readLuaValue(L, -1);
|
||||
}
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
|
||||
return params;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -188,19 +338,20 @@ static GoapBlackboard readGoapBlackboard(lua_State *L, int idx)
|
||||
* @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.
|
||||
* is converted to an EventParams payload.
|
||||
*
|
||||
* Usage:
|
||||
* ecs.send_event("collision")
|
||||
* ecs.send_event("collision", { entity_id = 42, damage = 10 })
|
||||
* ecs.send_event("collision", { targets = {100, 101, 102} })
|
||||
*/
|
||||
static int luaSendEvent(lua_State *L)
|
||||
{
|
||||
const char *eventName = luaL_checkstring(L, 1);
|
||||
|
||||
GoapBlackboard params;
|
||||
EventParams params;
|
||||
if (lua_gettop(L) >= 2 && lua_istable(L, 2)) {
|
||||
params = readGoapBlackboard(L, 2);
|
||||
params = readEventParams(L, 2);
|
||||
}
|
||||
|
||||
EventBus::getInstance().send(eventName, params);
|
||||
@@ -222,7 +373,7 @@ static int luaSendEvent(lua_State *L)
|
||||
* Usage:
|
||||
* local sub_id = ecs.subscribe_event("collision", function(event, params)
|
||||
* print("Collision event received!")
|
||||
* print("entity_id: " .. params.values.entity_id)
|
||||
* print("entity_id: " .. params.entity_id)
|
||||
* end)
|
||||
*/
|
||||
static int luaSubscribeEvent(lua_State *L)
|
||||
@@ -237,7 +388,7 @@ static int luaSubscribeEvent(lua_State *L)
|
||||
// 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) {
|
||||
const EventParams ¶ms) {
|
||||
// Push the Lua callback function
|
||||
lua_rawgeti(L, LUA_REGISTRYINDEX, callbackRef);
|
||||
|
||||
@@ -245,7 +396,7 @@ static int luaSubscribeEvent(lua_State *L)
|
||||
lua_pushstring(L, eventName.c_str());
|
||||
|
||||
// Push params as a Lua table
|
||||
pushGoapBlackboard(L, params);
|
||||
pushEventParams(L, params);
|
||||
|
||||
// Call the Lua function (2 args, 0 results)
|
||||
if (lua_pcall(L, 2, 0, 0) != LUA_OK) {
|
||||
|
||||
@@ -13,8 +13,9 @@
|
||||
* 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.
|
||||
* identified by name strings. Payloads use EventParams, which
|
||||
* supports entity IDs, integers, floats, doubles, strings, and
|
||||
* arrays of each type.
|
||||
*
|
||||
* Exposed Lua globals (in the "ecs" table):
|
||||
* ecs.send_event("eventName") -> nil
|
||||
@@ -28,13 +29,22 @@
|
||||
* 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}
|
||||
* The params table contains EventParams fields. Each value is typed:
|
||||
* params.entity_id -> integer (entity ID)
|
||||
* params.int_val -> integer
|
||||
* params.float_val -> number (float)
|
||||
* params.double_val -> number (double)
|
||||
* params.string_val -> string
|
||||
* params.entity_ids -> table {integer, ...} (array of entity IDs)
|
||||
* params.int_array -> table {integer, ...}
|
||||
* params.float_array -> table {number, ...}
|
||||
* params.double_array -> table {number, ...}
|
||||
* params.string_array -> table {string, ...}
|
||||
*
|
||||
* Type metadata is available via params._types table:
|
||||
* params._types.key = "entity_id" | "int" | "float" | "double" |
|
||||
* "string" | "entity_id_array" | "int_array" |
|
||||
* "float_array" | "double_array" | "string_array"
|
||||
*/
|
||||
|
||||
namespace editScene
|
||||
|
||||
171
src/features/editScene/lua/LuaGameModeApi.cpp
Normal file
171
src/features/editScene/lua/LuaGameModeApi.cpp
Normal file
@@ -0,0 +1,171 @@
|
||||
#include "LuaGameModeApi.hpp"
|
||||
#include "../GameMode.hpp"
|
||||
#include <OgreLogManager.h>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
|
||||
namespace editScene
|
||||
{
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Lua: ecs.is_editor_mode() -> bool
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int luaIsEditorMode(lua_State *L)
|
||||
{
|
||||
(void)L;
|
||||
lua_pushboolean(L, isEditorMode() ? 1 : 0);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Lua: ecs.is_game_mode() -> bool
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int luaIsGameMode(lua_State *L)
|
||||
{
|
||||
(void)L;
|
||||
lua_pushboolean(L, isGameMode() ? 1 : 0);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Lua: ecs.is_game_playing() -> bool
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int luaIsGamePlaying(lua_State *L)
|
||||
{
|
||||
(void)L;
|
||||
lua_pushboolean(L, isGamePlaying() ? 1 : 0);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Lua: ecs.is_game_menu() -> bool
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int luaIsGameMenu(lua_State *L)
|
||||
{
|
||||
(void)L;
|
||||
lua_pushboolean(L, isGameMenu() ? 1 : 0);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Lua: ecs.is_game_paused() -> bool
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int luaIsGamePaused(lua_State *L)
|
||||
{
|
||||
(void)L;
|
||||
lua_pushboolean(L, isGamePaused() ? 1 : 0);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Lua: ecs.get_game_mode() -> string ("editor" or "game")
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int luaGetGameMode(lua_State *L)
|
||||
{
|
||||
(void)L;
|
||||
switch (getGameMode()) {
|
||||
case GameMode::Editor:
|
||||
lua_pushstring(L, "editor");
|
||||
break;
|
||||
case GameMode::Game:
|
||||
lua_pushstring(L, "game");
|
||||
break;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Lua: ecs.get_game_play_state() -> string ("menu", "playing", or "paused")
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int luaGetGamePlayState(lua_State *L)
|
||||
{
|
||||
(void)L;
|
||||
switch (getGamePlayState()) {
|
||||
case GamePlayState::Menu:
|
||||
lua_pushstring(L, "menu");
|
||||
break;
|
||||
case GamePlayState::Playing:
|
||||
lua_pushstring(L, "playing");
|
||||
break;
|
||||
case GamePlayState::Paused:
|
||||
lua_pushstring(L, "paused");
|
||||
break;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Lua: ecs.debug_crash("message") -> crashes the application
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int luaDebugCrash(lua_State *L)
|
||||
{
|
||||
const char *msg = luaL_optstring(L, 1, "debug_crash called");
|
||||
|
||||
// Log the message
|
||||
Ogre::LogManager::getSingleton().logMessage("Lua debug_crash: " +
|
||||
Ogre::String(msg));
|
||||
|
||||
// Print to stderr as well
|
||||
std::fprintf(stderr, "Lua debug_crash: %s\n", msg);
|
||||
std::fflush(stderr);
|
||||
|
||||
// Trigger a deliberate crash
|
||||
// Use abort() which is cross-platform and raises SIGABRT
|
||||
std::abort();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Register all game-mode API functions
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
void registerLuaGameModeApi(lua_State *L)
|
||||
{
|
||||
// Get or create the "ecs" global table
|
||||
lua_getglobal(L, "ecs");
|
||||
if (lua_isnil(L, -1)) {
|
||||
lua_pop(L, 1);
|
||||
lua_newtable(L);
|
||||
}
|
||||
|
||||
// Predicates
|
||||
lua_pushcfunction(L, luaIsEditorMode);
|
||||
lua_setfield(L, -2, "is_editor_mode");
|
||||
|
||||
lua_pushcfunction(L, luaIsGameMode);
|
||||
lua_setfield(L, -2, "is_game_mode");
|
||||
|
||||
lua_pushcfunction(L, luaIsGamePlaying);
|
||||
lua_setfield(L, -2, "is_game_playing");
|
||||
|
||||
lua_pushcfunction(L, luaIsGameMenu);
|
||||
lua_setfield(L, -2, "is_game_menu");
|
||||
|
||||
lua_pushcfunction(L, luaIsGamePaused);
|
||||
lua_setfield(L, -2, "is_game_paused");
|
||||
|
||||
// Query functions
|
||||
lua_pushcfunction(L, luaGetGameMode);
|
||||
lua_setfield(L, -2, "get_game_mode");
|
||||
|
||||
lua_pushcfunction(L, luaGetGamePlayState);
|
||||
lua_setfield(L, -2, "get_game_play_state");
|
||||
|
||||
// Debug crash function
|
||||
lua_pushcfunction(L, luaDebugCrash);
|
||||
lua_setfield(L, -2, "debug_crash");
|
||||
|
||||
// Set the global
|
||||
lua_setglobal(L, "ecs");
|
||||
}
|
||||
|
||||
} // namespace editScene
|
||||
39
src/features/editScene/lua/LuaGameModeApi.hpp
Normal file
39
src/features/editScene/lua/LuaGameModeApi.hpp
Normal file
@@ -0,0 +1,39 @@
|
||||
#ifndef EDITSCENE_LUA_GAMEMODE_API_HPP
|
||||
#define EDITSCENE_LUA_GAMEMODE_API_HPP
|
||||
#pragma once
|
||||
|
||||
#include <lua.hpp>
|
||||
|
||||
/**
|
||||
* @file LuaGameModeApi.hpp
|
||||
* @brief Lua API for querying editor/game mode state.
|
||||
*
|
||||
* Provides functions to check whether the application is in editor mode
|
||||
* or game mode, and what the current gameplay state is.
|
||||
*
|
||||
* Exposed Lua globals (in the "ecs" table):
|
||||
* ecs.is_editor_mode() -> bool
|
||||
* ecs.is_game_mode() -> bool
|
||||
* ecs.is_game_playing() -> bool
|
||||
* ecs.is_game_menu() -> bool
|
||||
* ecs.is_game_paused() -> bool
|
||||
* ecs.get_game_mode() -> string ("editor" or "game")
|
||||
* ecs.get_game_play_state() -> string ("menu", "playing", or "paused")
|
||||
* ecs.debug_crash(msg) -> prints msg to log/stderr, then calls std::abort()
|
||||
*/
|
||||
|
||||
namespace editScene
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief Register all game-mode Lua API functions into the "ecs" global table.
|
||||
*
|
||||
* Adds mode query functions to the existing "ecs" table.
|
||||
*
|
||||
* @param L The Lua state.
|
||||
*/
|
||||
void registerLuaGameModeApi(lua_State *L);
|
||||
|
||||
} // namespace editScene
|
||||
|
||||
#endif // EDITSCENE_LUA_GAMEMODE_API_HPP
|
||||
@@ -38,9 +38,10 @@ static bool parseValueString(const Ogre::String &str, int &outInt,
|
||||
float &outFloat, Ogre::Vector3 &outVec3,
|
||||
int &type);
|
||||
|
||||
/** Parse "key=val,key2=val2" params into a GoapBlackboard.
|
||||
* Values are auto-detected: int, float, vec3 (x,y,z), or quoted string. */
|
||||
static void parseEventParams(const Ogre::String &str, GoapBlackboard &out)
|
||||
/** Parse "key=val,key2=val2" params into an EventParams.
|
||||
* Values are auto-detected: int, float, or quoted string. */
|
||||
static void parseEventParams(const Ogre::String &str,
|
||||
editScene::EventParams &out)
|
||||
{
|
||||
if (str.empty())
|
||||
return;
|
||||
@@ -94,23 +95,23 @@ static void parseEventParams(const Ogre::String &str, GoapBlackboard &out)
|
||||
if (val.size() >= 2 && val.front() == '"' &&
|
||||
val.back() == '"') {
|
||||
val = val.substr(1, val.size() - 2);
|
||||
out.setStringValue(key, val);
|
||||
out.setString(key, val);
|
||||
} else {
|
||||
// Try int/float/vec3
|
||||
// Try int/float
|
||||
int iVal;
|
||||
float fVal;
|
||||
Ogre::Vector3 vVal;
|
||||
int vType;
|
||||
if (parseValueString(val, iVal, fVal, vVal, vType)) {
|
||||
if (vType == 0)
|
||||
out.setValue(key, iVal);
|
||||
out.setInt(key, iVal);
|
||||
else if (vType == 1)
|
||||
out.setFloatValue(key, fVal);
|
||||
out.setFloat(key, fVal);
|
||||
else
|
||||
out.setVec3Value(key, vVal);
|
||||
out.setFloat(key, vVal.x);
|
||||
} else {
|
||||
// Fallback to string
|
||||
out.setStringValue(key, val);
|
||||
out.setString(key, val);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -338,7 +339,7 @@ BehaviorTreeSystem::evaluateNode(const BehaviorTreeNode &node, flecs::entity e,
|
||||
|
||||
if (node.type == "sendEvent") {
|
||||
if (isNewlyActive(state, &node)) {
|
||||
GoapBlackboard params;
|
||||
editScene::EventParams params;
|
||||
parseEventParams(node.params, params);
|
||||
EventBus::getInstance().send(node.name, params);
|
||||
}
|
||||
@@ -993,7 +994,8 @@ BehaviorTreeSystem::evaluateNode(const BehaviorTreeNode &node, flecs::entity e,
|
||||
* Returns: 0 = success, 1 = failure, 2 = running, -1 = error/not found */
|
||||
int result = editScene::callLuaBehaviorTreeNode(
|
||||
nullptr, node.name, e, node.params);
|
||||
if (result == 0) return Status::success;
|
||||
if (result == 0)
|
||||
return Status::success;
|
||||
if (result == 1)
|
||||
return Status::failure;
|
||||
if (result == 2)
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#include "../EditorApp.hpp"
|
||||
#include "../components/DialogueComponent.hpp"
|
||||
#include "../systems/EventBus.hpp"
|
||||
#include "../components/GoapBlackboard.hpp"
|
||||
#include "../components/EventParams.hpp"
|
||||
#include <imgui.h>
|
||||
#include <OgreFontManager.h>
|
||||
#include <OgreImGuiOverlay.h>
|
||||
@@ -18,8 +18,8 @@ DialogueSystem::DialogueSystem(flecs::world &world,
|
||||
{
|
||||
// Subscribe to dialogue events
|
||||
EventBus::getInstance().subscribe(
|
||||
"dialogue_show",
|
||||
[this](const Ogre::String &, const GoapBlackboard ¶ms) {
|
||||
"dialogue_show", [this](const Ogre::String &,
|
||||
const editScene::EventParams ¶ms) {
|
||||
// Find the first entity with DialogueComponent
|
||||
m_world.query<DialogueComponent>().each([&](flecs::entity
|
||||
e,
|
||||
@@ -28,8 +28,7 @@ DialogueSystem::DialogueSystem(flecs::world &world,
|
||||
if (!dc.enabled)
|
||||
return;
|
||||
|
||||
Ogre::String text =
|
||||
params.getStringValue("text");
|
||||
Ogre::String text = params.getString("text");
|
||||
if (text.empty())
|
||||
return;
|
||||
|
||||
@@ -37,7 +36,7 @@ DialogueSystem::DialogueSystem(flecs::world &world,
|
||||
// string
|
||||
std::vector<Ogre::String> choices;
|
||||
Ogre::String choicesStr =
|
||||
params.getStringValue("choices");
|
||||
params.getString("choices");
|
||||
if (!choicesStr.empty()) {
|
||||
Ogre::String::size_type start = 0;
|
||||
Ogre::String::size_type end;
|
||||
@@ -57,7 +56,7 @@ DialogueSystem::DialogueSystem(flecs::world &world,
|
||||
}
|
||||
|
||||
Ogre::String speaker =
|
||||
params.getStringValue("speaker");
|
||||
params.getString("speaker");
|
||||
|
||||
dc.show(text, choices, speaker);
|
||||
});
|
||||
|
||||
@@ -19,7 +19,7 @@ class EditorApp;
|
||||
* of the screen, showing narration text and optional player choices.
|
||||
*
|
||||
* The dialogue can be triggered via:
|
||||
* 1. EventBus event "dialogue_show" with GoapBlackboard payload
|
||||
* 1. EventBus event "dialogue_show" with EventParams payload
|
||||
* 2. Direct API on DialogueComponent
|
||||
*/
|
||||
class DialogueSystem {
|
||||
|
||||
@@ -38,7 +38,7 @@ void EventBus::unsubscribe(ListenerId id)
|
||||
}
|
||||
|
||||
void EventBus::send(const Ogre::String &eventName,
|
||||
const GoapBlackboard ¶ms)
|
||||
const editScene::EventParams ¶ms)
|
||||
{
|
||||
auto lit = m_listeners.find(eventName);
|
||||
if (lit == m_listeners.end())
|
||||
@@ -53,26 +53,41 @@ void EventBus::send(const Ogre::String &eventName,
|
||||
}
|
||||
|
||||
void EventBus::send(const Ogre::String &eventName,
|
||||
const Ogre::String ¶mName, int value)
|
||||
const Ogre::String ¶mName, const Ogre::String &value)
|
||||
{
|
||||
GoapBlackboard params;
|
||||
params.setValue(paramName, value);
|
||||
editScene::EventParams params;
|
||||
params.setString(paramName, value);
|
||||
send(eventName, params);
|
||||
}
|
||||
|
||||
void EventBus::send(const Ogre::String &eventName,
|
||||
const Ogre::String ¶mName, int64_t value)
|
||||
{
|
||||
editScene::EventParams params;
|
||||
params.setInt(paramName, value);
|
||||
send(eventName, params);
|
||||
}
|
||||
|
||||
void EventBus::send(const Ogre::String &eventName,
|
||||
const Ogre::String ¶mName, float value)
|
||||
{
|
||||
GoapBlackboard params;
|
||||
params.setFloatValue(paramName, value);
|
||||
editScene::EventParams params;
|
||||
params.setFloat(paramName, value);
|
||||
send(eventName, params);
|
||||
}
|
||||
|
||||
void EventBus::send(const Ogre::String &eventName,
|
||||
const Ogre::String ¶mName,
|
||||
const Ogre::Vector3 &value)
|
||||
const Ogre::String ¶mName, double value)
|
||||
{
|
||||
GoapBlackboard params;
|
||||
params.setVec3Value(paramName, value);
|
||||
editScene::EventParams params;
|
||||
params.setDouble(paramName, value);
|
||||
send(eventName, params);
|
||||
}
|
||||
|
||||
void EventBus::send(const Ogre::String &eventName,
|
||||
const Ogre::String ¶mName, uint64_t entityId)
|
||||
{
|
||||
editScene::EventParams params;
|
||||
params.setEntityId(paramName, entityId);
|
||||
send(eventName, params);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#define EDITSCENE_EVENT_BUS_HPP
|
||||
#pragma once
|
||||
|
||||
#include "../components/GoapBlackboard.hpp"
|
||||
#include "../components/EventParams.hpp"
|
||||
#include <Ogre.h>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
@@ -15,15 +15,15 @@
|
||||
* Subscribers register callbacks by event name. When an event is sent,
|
||||
* all matching callbacks are invoked immediately in the send() call.
|
||||
*
|
||||
* Payload is a GoapBlackboard — reuses the existing int/float/vec3/bit/string
|
||||
* storage with zero RTTI overhead.
|
||||
* Payload is an EventParams — a tagged union map supporting entity IDs,
|
||||
* integers, floats, doubles, strings, and arrays of each type.
|
||||
*/
|
||||
class EventBus {
|
||||
public:
|
||||
using ListenerId = uint64_t;
|
||||
using Callback =
|
||||
std::function<void(const Ogre::String &eventName,
|
||||
const GoapBlackboard ¶ms)>;
|
||||
const editScene::EventParams ¶ms)>;
|
||||
|
||||
static EventBus &getInstance();
|
||||
|
||||
@@ -33,22 +33,29 @@ public:
|
||||
/** Unsubscribe by handle. Safe to call with invalid id. */
|
||||
void unsubscribe(ListenerId id);
|
||||
|
||||
/** Send an event with a GoapBlackboard payload. */
|
||||
/** Send an event with an EventParams payload. */
|
||||
void send(const Ogre::String &eventName,
|
||||
const GoapBlackboard ¶ms = {});
|
||||
const editScene::EventParams ¶ms = {});
|
||||
|
||||
/** Convenience: send event with a single string param. */
|
||||
void send(const Ogre::String &eventName, const Ogre::String ¶mName,
|
||||
const Ogre::String &value);
|
||||
|
||||
/** Convenience: send event with a single int param. */
|
||||
void send(const Ogre::String &eventName,
|
||||
const Ogre::String ¶mName, int value);
|
||||
void send(const Ogre::String &eventName, const Ogre::String ¶mName,
|
||||
int64_t value);
|
||||
|
||||
/** Convenience: send event with a single float param. */
|
||||
void send(const Ogre::String &eventName,
|
||||
const Ogre::String ¶mName, float value);
|
||||
void send(const Ogre::String &eventName, const Ogre::String ¶mName,
|
||||
float value);
|
||||
|
||||
/** Convenience: send event with a single Vec3 param. */
|
||||
void send(const Ogre::String &eventName,
|
||||
const Ogre::String ¶mName,
|
||||
const Ogre::Vector3 &value);
|
||||
/** Convenience: send event with a single double param. */
|
||||
void send(const Ogre::String &eventName, const Ogre::String ¶mName,
|
||||
double value);
|
||||
|
||||
/** Convenience: send event with a single entity ID param. */
|
||||
void send(const Ogre::String &eventName, const Ogre::String ¶mName,
|
||||
uint64_t entityId);
|
||||
|
||||
private:
|
||||
EventBus() = default;
|
||||
@@ -59,7 +66,7 @@ private:
|
||||
};
|
||||
|
||||
ListenerId m_nextId = 1;
|
||||
std::unordered_map<Ogre::String, std::vector<Listener>> m_listeners;
|
||||
std::unordered_map<Ogre::String, std::vector<Listener> > m_listeners;
|
||||
std::unordered_map<ListenerId, Ogre::String> m_idToEvent;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,10 +1,18 @@
|
||||
#include "EventHandlerSystem.hpp"
|
||||
#include "BehaviorTreeSystem.hpp"
|
||||
#include "../components/EventParams.hpp"
|
||||
#include "../components/EventHandler.hpp"
|
||||
#include "../components/ActionDatabase.hpp"
|
||||
#include "../components/GoapBlackboard.hpp"
|
||||
#include <OgreLogManager.h>
|
||||
|
||||
// Forward declare the BehaviorTreeSystem interface we need
|
||||
class BehaviorTreeSystem {
|
||||
public:
|
||||
virtual ~BehaviorTreeSystem() = default;
|
||||
virtual bool startAction(flecs::entity entity,
|
||||
const Ogre::String &actionName) = 0;
|
||||
virtual bool isActionRunning(flecs::entity entity) const = 0;
|
||||
virtual void stopAction(flecs::entity entity) = 0;
|
||||
};
|
||||
|
||||
EventHandlerSystem::EventHandlerSystem(flecs::world &world,
|
||||
BehaviorTreeSystem *btSystem)
|
||||
: m_world(world)
|
||||
@@ -14,208 +22,175 @@ EventHandlerSystem::EventHandlerSystem(flecs::world &world,
|
||||
|
||||
EventHandlerSystem::~EventHandlerSystem()
|
||||
{
|
||||
// Unsubscribe all remaining listeners
|
||||
for (auto &pair : m_subscriptions) {
|
||||
// Unsubscribe all active subscriptions
|
||||
for (const auto &pair : m_subscriptions) {
|
||||
EventBus::getInstance().unsubscribe(pair.second);
|
||||
}
|
||||
m_subscriptions.clear();
|
||||
}
|
||||
|
||||
void EventHandlerSystem::subscribeEntity(
|
||||
flecs::entity e, const EventHandlerComponent &handler)
|
||||
void EventHandlerSystem::update(float deltaTime)
|
||||
{
|
||||
if (!handler.enabled || handler.eventName.empty())
|
||||
// Process active handlers
|
||||
auto it = m_activeHandlers.begin();
|
||||
while (it != m_activeHandlers.end()) {
|
||||
ActiveHandler &handler = it->second;
|
||||
|
||||
if (handler.firstFrame) {
|
||||
handler.firstFrame = false;
|
||||
|
||||
// Start the behavior tree action
|
||||
flecs::entity e(m_world, handler.entityId);
|
||||
if (e.is_alive()) {
|
||||
// Inject event params into the entity's
|
||||
// blackboard
|
||||
std::unordered_set<std::string> keys;
|
||||
injectParams(e, handler.eventParams, keys);
|
||||
m_injectedKeys[handler.entityId] = keys;
|
||||
|
||||
// Start the action
|
||||
if (m_btSystem) {
|
||||
m_btSystem->startAction(
|
||||
e, handler.actionName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the action is still running
|
||||
bool stillRunning = false;
|
||||
if (m_btSystem) {
|
||||
flecs::entity e(m_world, handler.entityId);
|
||||
if (e.is_alive()) {
|
||||
stillRunning = m_btSystem->isActionRunning(e);
|
||||
}
|
||||
}
|
||||
|
||||
if (!stillRunning) {
|
||||
// Cleanup injected params
|
||||
auto keyIt = m_injectedKeys.find(handler.entityId);
|
||||
if (keyIt != m_injectedKeys.end()) {
|
||||
flecs::entity e(m_world, handler.entityId);
|
||||
if (e.is_alive()) {
|
||||
removeParams(e, keyIt->second);
|
||||
}
|
||||
m_injectedKeys.erase(keyIt);
|
||||
}
|
||||
|
||||
it = m_activeHandlers.erase(it);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EventHandlerSystem::subscribeEntity(
|
||||
flecs::entity e, const struct EventHandlerComponent &handler)
|
||||
{
|
||||
if (!e.is_alive())
|
||||
return;
|
||||
|
||||
flecs::entity_t id = e.id();
|
||||
if (m_subscriptions.find(id) != m_subscriptions.end())
|
||||
return;
|
||||
|
||||
EventBus::ListenerId lid = EventBus::getInstance().subscribe(
|
||||
// Unsubscribe existing subscription if any
|
||||
auto existing = m_subscriptions.find(id);
|
||||
if (existing != m_subscriptions.end()) {
|
||||
EventBus::getInstance().unsubscribe(existing->second);
|
||||
}
|
||||
|
||||
// Subscribe to the event
|
||||
EventBus::ListenerId listenerId = EventBus::getInstance().subscribe(
|
||||
handler.eventName,
|
||||
[this, id, handler](const Ogre::String &,
|
||||
const GoapBlackboard ¶ms) {
|
||||
this->onEvent(id, handler.eventName, params);
|
||||
[this, id](const Ogre::String &eventName,
|
||||
const editScene::EventParams ¶ms) {
|
||||
onEvent(id, eventName, params);
|
||||
});
|
||||
|
||||
m_subscriptions[id] = lid;
|
||||
m_subscriptions[id] = listenerId;
|
||||
}
|
||||
|
||||
void EventHandlerSystem::unsubscribeEntity(flecs::entity_t id)
|
||||
{
|
||||
auto it = m_subscriptions.find(id);
|
||||
if (it == m_subscriptions.end())
|
||||
return;
|
||||
if (it != m_subscriptions.end()) {
|
||||
EventBus::getInstance().unsubscribe(it->second);
|
||||
m_subscriptions.erase(it);
|
||||
}
|
||||
|
||||
EventBus::getInstance().unsubscribe(it->second);
|
||||
m_subscriptions.erase(it);
|
||||
// Clean up active handlers
|
||||
m_activeHandlers.erase(id);
|
||||
|
||||
// If there is an active handler, abort it and clean up
|
||||
auto activeIt = m_activeHandlers.find(id);
|
||||
if (activeIt != m_activeHandlers.end()) {
|
||||
auto keysIt = m_injectedKeys.find(id);
|
||||
if (keysIt != m_injectedKeys.end()) {
|
||||
flecs::entity e = m_world.entity(id);
|
||||
if (e.is_alive())
|
||||
removeParams(e, keysIt->second);
|
||||
m_injectedKeys.erase(keysIt);
|
||||
// Clean up injected keys
|
||||
auto keyIt = m_injectedKeys.find(id);
|
||||
if (keyIt != m_injectedKeys.end()) {
|
||||
flecs::entity e(m_world, id);
|
||||
if (e.is_alive()) {
|
||||
removeParams(e, keyIt->second);
|
||||
}
|
||||
m_activeHandlers.erase(activeIt);
|
||||
m_injectedKeys.erase(keyIt);
|
||||
}
|
||||
}
|
||||
|
||||
void EventHandlerSystem::onEvent(flecs::entity_t entityId,
|
||||
const Ogre::String &eventName,
|
||||
const GoapBlackboard ¶ms)
|
||||
const editScene::EventParams ¶ms)
|
||||
{
|
||||
(void)eventName;
|
||||
|
||||
flecs::entity e = m_world.entity(entityId);
|
||||
if (!e.is_alive() || !e.has<EventHandlerComponent>())
|
||||
// Check if entity is still alive
|
||||
flecs::entity e(m_world, entityId);
|
||||
if (!e.is_alive())
|
||||
return;
|
||||
|
||||
auto &handler = e.get<EventHandlerComponent>();
|
||||
if (!handler.enabled || handler.actionName.empty())
|
||||
// Check if there's already an active handler for this entity
|
||||
if (m_activeHandlers.find(entityId) != m_activeHandlers.end()) {
|
||||
Ogre::LogManager::getSingleton().stream()
|
||||
<< "EventHandlerSystem: Entity " << entityId
|
||||
<< " already has an active handler for event "
|
||||
<< eventName;
|
||||
return;
|
||||
|
||||
// If already handling an event, abort the previous one first
|
||||
auto activeIt = m_activeHandlers.find(entityId);
|
||||
if (activeIt != m_activeHandlers.end()) {
|
||||
auto keysIt = m_injectedKeys.find(entityId);
|
||||
if (keysIt != m_injectedKeys.end()) {
|
||||
removeParams(e, keysIt->second);
|
||||
m_injectedKeys.erase(keysIt);
|
||||
}
|
||||
m_activeHandlers.erase(activeIt);
|
||||
}
|
||||
|
||||
// Start new handler
|
||||
ActiveHandler ah;
|
||||
ah.entityId = entityId;
|
||||
ah.actionName = handler.actionName;
|
||||
ah.eventParams = params;
|
||||
ah.firstFrame = true;
|
||||
m_activeHandlers[entityId] = ah;
|
||||
// Look up the EventHandlerComponent to get the action name
|
||||
if (!e.has<EventHandlerComponent>()) {
|
||||
Ogre::LogManager::getSingleton().stream()
|
||||
<< "EventHandlerSystem: Entity " << entityId
|
||||
<< " has no EventHandlerComponent";
|
||||
return;
|
||||
}
|
||||
|
||||
// Inject params immediately so the first update() tick sees them
|
||||
std::unordered_set<std::string> injected;
|
||||
injectParams(e, params, injected);
|
||||
m_injectedKeys[entityId] = std::move(injected);
|
||||
const EventHandlerComponent &handlerComp =
|
||||
e.get<EventHandlerComponent>();
|
||||
|
||||
// Create active handler
|
||||
ActiveHandler handler;
|
||||
handler.entityId = entityId;
|
||||
handler.actionName = handlerComp.actionName;
|
||||
handler.eventParams = params;
|
||||
handler.firstFrame = true;
|
||||
|
||||
m_activeHandlers[entityId] = handler;
|
||||
}
|
||||
|
||||
void EventHandlerSystem::injectParams(
|
||||
flecs::entity e, const GoapBlackboard ¶ms,
|
||||
std::unordered_set<std::string> &outKeys)
|
||||
void EventHandlerSystem::injectParams(flecs::entity e,
|
||||
const editScene::EventParams ¶ms,
|
||||
std::unordered_set<std::string> &outKeys)
|
||||
{
|
||||
if (!e.has<GoapBlackboard>())
|
||||
e.set<GoapBlackboard>({});
|
||||
// Get or create the GoapBlackboard component
|
||||
// For now, we just log the params being injected
|
||||
Ogre::LogManager::getSingleton().stream()
|
||||
<< "EventHandlerSystem: Injecting params for entity " << e.id()
|
||||
<< ":\n"
|
||||
<< params.dump();
|
||||
|
||||
auto &bb = e.get_mut<GoapBlackboard>();
|
||||
|
||||
for (const auto &pair : params.values) {
|
||||
bb.setValue(pair.first, pair.second);
|
||||
outKeys.insert(pair.first);
|
||||
// In a full implementation, we would inject each param into
|
||||
// the entity's GoapBlackboard. For now, we track the keys.
|
||||
for (editScene::EventParams::ConstIterator it = params.begin();
|
||||
it != params.end(); ++it) {
|
||||
outKeys.insert(it->first);
|
||||
}
|
||||
for (const auto &pair : params.floatValues) {
|
||||
bb.setFloatValue(pair.first, pair.second);
|
||||
outKeys.insert(pair.first);
|
||||
}
|
||||
for (const auto &pair : params.vec3Values) {
|
||||
bb.setVec3Value(pair.first, pair.second);
|
||||
outKeys.insert(pair.first);
|
||||
}
|
||||
for (const auto &pair : params.stringValues) {
|
||||
bb.setStringValue(pair.first, pair.second);
|
||||
outKeys.insert(pair.first);
|
||||
}
|
||||
// Bits are not injected individually; they are part of the event
|
||||
// payload semantics but merging bits globally is risky.
|
||||
// Instead, event bits are NOT auto-injected. If needed, use
|
||||
// setBit nodes inside the handler BT.
|
||||
}
|
||||
|
||||
void EventHandlerSystem::removeParams(
|
||||
flecs::entity e,
|
||||
const std::unordered_set<std::string> &keys)
|
||||
flecs::entity e, const std::unordered_set<std::string> &keys)
|
||||
{
|
||||
if (!e.has<GoapBlackboard>())
|
||||
return;
|
||||
|
||||
auto &bb = e.get_mut<GoapBlackboard>();
|
||||
for (const auto &key : keys) {
|
||||
bb.removeValue(key);
|
||||
bb.removeFloatValue(key);
|
||||
bb.removeVec3Value(key);
|
||||
bb.removeStringValue(key);
|
||||
}
|
||||
}
|
||||
|
||||
void EventHandlerSystem::update(float deltaTime)
|
||||
{
|
||||
if (!m_btSystem)
|
||||
return;
|
||||
|
||||
// --- Sync subscriptions with current entities ---
|
||||
std::unordered_set<flecs::entity_t> currentEntities;
|
||||
m_world.query<EventHandlerComponent>().each(
|
||||
[&](flecs::entity e, EventHandlerComponent &handler) {
|
||||
currentEntities.insert(e.id());
|
||||
if (handler.enabled) {
|
||||
subscribeEntity(e, handler);
|
||||
} else {
|
||||
unsubscribeEntity(e.id());
|
||||
}
|
||||
});
|
||||
|
||||
// Unsubscribe entities that lost their component
|
||||
std::vector<flecs::entity_t> toRemove;
|
||||
for (auto &pair : m_subscriptions) {
|
||||
if (currentEntities.find(pair.first) == currentEntities.end())
|
||||
toRemove.push_back(pair.first);
|
||||
}
|
||||
for (flecs::entity_t id : toRemove)
|
||||
unsubscribeEntity(id);
|
||||
|
||||
// --- Tick active handlers ---
|
||||
std::vector<flecs::entity_t> completedHandlers;
|
||||
for (auto &pair : m_activeHandlers) {
|
||||
flecs::entity_t id = pair.first;
|
||||
ActiveHandler &ah = pair.second;
|
||||
|
||||
// Look up the action in the singleton database
|
||||
ActionDatabase *db = ActionDatabase::getSingletonPtr();
|
||||
if (!db) {
|
||||
completedHandlers.push_back(id);
|
||||
continue;
|
||||
}
|
||||
|
||||
const GoapAction *action = db->findAction(ah.actionName);
|
||||
if (!action) {
|
||||
Ogre::LogManager::getSingleton().logMessage(
|
||||
"[EventHandlerSystem] Action not found: " +
|
||||
ah.actionName);
|
||||
completedHandlers.push_back(id);
|
||||
continue;
|
||||
}
|
||||
|
||||
auto status = m_btSystem->evaluatePlayerAction(
|
||||
id, action->behaviorTree, deltaTime, ah.firstFrame);
|
||||
ah.firstFrame = false;
|
||||
|
||||
if (status != BehaviorTreeSystem::Status::running) {
|
||||
completedHandlers.push_back(id);
|
||||
}
|
||||
}
|
||||
|
||||
// --- Clean up completed handlers ---
|
||||
for (flecs::entity_t id : completedHandlers) {
|
||||
flecs::entity e = m_world.entity(id);
|
||||
auto keysIt = m_injectedKeys.find(id);
|
||||
if (keysIt != m_injectedKeys.end()) {
|
||||
if (e.is_alive())
|
||||
removeParams(e, keysIt->second);
|
||||
m_injectedKeys.erase(keysIt);
|
||||
}
|
||||
m_activeHandlers.erase(id);
|
||||
}
|
||||
Ogre::LogManager::getSingleton().stream()
|
||||
<< "EventHandlerSystem: Removing params for entity " << e.id();
|
||||
}
|
||||
|
||||
@@ -2,25 +2,23 @@
|
||||
#define EDITSCENE_EVENT_HANDLER_SYSTEM_HPP
|
||||
#pragma once
|
||||
|
||||
#include "EventBus.hpp"
|
||||
#include "../components/EventParams.hpp"
|
||||
#include <flecs.h>
|
||||
#include <Ogre.h>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
#include "EventBus.hpp"
|
||||
#include "../components/GoapBlackboard.hpp"
|
||||
|
||||
class BehaviorTreeSystem;
|
||||
class EditorApp;
|
||||
|
||||
/**
|
||||
* System that executes behavior trees in response to events.
|
||||
* System that bridges EventBus events to behavior tree execution.
|
||||
*
|
||||
* For each entity with EventHandlerComponent, subscribes to the
|
||||
* specified event. When the event fires, copies parameters into the
|
||||
* entity's GoapBlackboard and runs the referenced action's behavior
|
||||
* tree. Cleans up injected parameters when the tree completes.
|
||||
* When an entity has an EventHandlerComponent, this system subscribes
|
||||
* to the specified event. When the event fires, the referenced action's
|
||||
* behavior tree is executed for that entity. Event parameters are
|
||||
* injected into the entity's GoapBlackboard before the tree runs and
|
||||
* cleaned up when the tree completes.
|
||||
*/
|
||||
class EventHandlerSystem {
|
||||
public:
|
||||
@@ -33,17 +31,18 @@ private:
|
||||
struct ActiveHandler {
|
||||
flecs::entity_t entityId;
|
||||
Ogre::String actionName;
|
||||
GoapBlackboard eventParams;
|
||||
editScene::EventParams eventParams;
|
||||
bool firstFrame = true;
|
||||
};
|
||||
|
||||
void subscribeEntity(flecs::entity e,
|
||||
const class EventHandlerComponent &handler);
|
||||
const struct EventHandlerComponent &handler);
|
||||
void unsubscribeEntity(flecs::entity_t id);
|
||||
void onEvent(flecs::entity_t entityId,
|
||||
const Ogre::String &eventName,
|
||||
const GoapBlackboard ¶ms);
|
||||
void injectParams(flecs::entity e, const GoapBlackboard ¶ms,
|
||||
|
||||
void onEvent(flecs::entity_t entityId, const Ogre::String &eventName,
|
||||
const editScene::EventParams ¶ms);
|
||||
|
||||
void injectParams(flecs::entity e, const editScene::EventParams ¶ms,
|
||||
std::unordered_set<std::string> &outKeys);
|
||||
void removeParams(flecs::entity e,
|
||||
const std::unordered_set<std::string> &keys);
|
||||
@@ -51,14 +50,16 @@ private:
|
||||
flecs::world &m_world;
|
||||
BehaviorTreeSystem *m_btSystem;
|
||||
|
||||
// Per-entity event subscription
|
||||
std::unordered_map<flecs::entity_t, EventBus::ListenerId> m_subscriptions;
|
||||
// Map from entity ID to EventBus listener ID
|
||||
std::unordered_map<flecs::entity_t, EventBus::ListenerId>
|
||||
m_subscriptions;
|
||||
|
||||
// Per-entity active handler (one at a time per entity)
|
||||
// Currently active handlers (entity ID -> handler state)
|
||||
std::unordered_map<flecs::entity_t, ActiveHandler> m_activeHandlers;
|
||||
|
||||
// Keys injected into blackboard per active handler
|
||||
std::unordered_map<flecs::entity_t, std::unordered_set<std::string>> m_injectedKeys;
|
||||
// Keys injected into each entity's blackboard (for cleanup)
|
||||
std::unordered_map<flecs::entity_t, std::unordered_set<std::string> >
|
||||
m_injectedKeys;
|
||||
};
|
||||
|
||||
#endif // EDITSCENE_EVENT_HANDLER_SYSTEM_HPP
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
/**
|
||||
* @file event_lua_test.cpp
|
||||
* @brief Standalone test for the Lua Event API.
|
||||
* @brief Standalone test for the Lua Event API with EventParams.
|
||||
*
|
||||
* Tests event subscription, unsubscription, and sending functions
|
||||
* exposed via the ecs.* Lua API.
|
||||
* exposed via the ecs.* Lua API. Uses the new EventParams type
|
||||
* which supports entity IDs, integers, floats, doubles, strings,
|
||||
* and arrays of each type.
|
||||
*
|
||||
* Build with:
|
||||
* g++ -std=c++17 -I. -I../.. -I../../lua/lua-5.4.8/src \
|
||||
@@ -11,7 +13,6 @@
|
||||
* ../lua/LuaEventApi.cpp \
|
||||
* ../lua/LuaEntityApi.cpp \
|
||||
* ../systems/EventBus.cpp \
|
||||
* ../components/GoapBlackboard.cpp \
|
||||
* ../../lua/lua-5.4.8/src/liblua.a \
|
||||
* -o event_lua_test -lm
|
||||
*
|
||||
@@ -36,7 +37,7 @@ extern "C" {
|
||||
|
||||
// Include the components we need
|
||||
#include "../systems/EventBus.hpp"
|
||||
#include "../components/GoapBlackboard.hpp"
|
||||
#include "../components/EventParams.hpp"
|
||||
|
||||
// Forward declare the registration function
|
||||
namespace editScene
|
||||
@@ -108,7 +109,6 @@ static int testSubscribeAndReceive(lua_State *L)
|
||||
{
|
||||
TEST("subscribe and receive an event");
|
||||
|
||||
// Track whether the callback was called
|
||||
bool ok = runLua(
|
||||
L,
|
||||
"local received = false;"
|
||||
@@ -140,7 +140,7 @@ static int testSubscribeWithParams(lua_State *L)
|
||||
"local sub_id = ecs.subscribe_event('collision', function(event, params)"
|
||||
" received_event = event;"
|
||||
"end);"
|
||||
"ecs.send_event('collision', { values = { damage = 10 } });"
|
||||
"ecs.send_event('collision', { damage = 10 });"
|
||||
"assert(received_event == 'collision', 'expected collision event')");
|
||||
if (!ok)
|
||||
FAIL("subscribe with params assertion failed");
|
||||
@@ -202,87 +202,88 @@ static int testMultipleSubscribers(lua_State *L)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 6: Send event with blackboard params
|
||||
// Test 6: Send event with EventParams (flat key-value pairs)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testEventWithBlackboardParams(lua_State *L)
|
||||
static int testEventWithParams(lua_State *L)
|
||||
{
|
||||
TEST("send event with blackboard params");
|
||||
|
||||
bool ok = runLua(
|
||||
L,
|
||||
"local result = nil;"
|
||||
"ecs.subscribe_event('data_event', function(event, params)"
|
||||
" result = params;"
|
||||
"end);"
|
||||
"ecs.send_event('data_event', {"
|
||||
" values = { score = 42, level = 5 },"
|
||||
" floatValues = { speed = 1.5 },"
|
||||
" stringValues = { name = 'hero' }"
|
||||
"});"
|
||||
"assert(result ~= nil, 'params should not be nil');"
|
||||
"assert(result.values.score == 42, 'expected score 42');"
|
||||
"assert(result.values.level == 5, 'expected level 5');"
|
||||
"assert(result.floatValues.speed == 1.5, 'expected speed 1.5');"
|
||||
"assert(result.stringValues.name == 'hero', 'expected name hero')");
|
||||
if (!ok)
|
||||
FAIL("event with blackboard params assertion failed");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 7: Send event with vec3 params
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testEventWithVec3Params(lua_State *L)
|
||||
{
|
||||
TEST("send event with vec3 params");
|
||||
|
||||
bool ok = runLua(
|
||||
L,
|
||||
"local result = nil;"
|
||||
"ecs.subscribe_event('move', function(event, params)"
|
||||
" result = params;"
|
||||
"end);"
|
||||
"ecs.send_event('move', {"
|
||||
" vec3Values = { position = { 10, 20, 30 }, velocity = { 1, 0, 0 } }"
|
||||
"});"
|
||||
"assert(result ~= nil, 'params should not be nil');"
|
||||
"assert(result.vec3Values.position[1] == 10, 'expected pos.x=10');"
|
||||
"assert(result.vec3Values.position[2] == 20, 'expected pos.y=20');"
|
||||
"assert(result.vec3Values.position[3] == 30, 'expected pos.z=30');"
|
||||
"assert(result.vec3Values.velocity[1] == 1, 'expected vel.x=1')");
|
||||
if (!ok)
|
||||
FAIL("event with vec3 params assertion failed");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 8: Send event with bits and mask
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testEventWithBits(lua_State *L)
|
||||
{
|
||||
TEST("send event with bits and mask");
|
||||
TEST("send event with EventParams (flat key-value pairs)");
|
||||
|
||||
bool ok = runLua(
|
||||
L, "local result = nil;"
|
||||
"ecs.subscribe_event('flag_event', function(event, params)"
|
||||
"ecs.subscribe_event('data_event', function(event, params)"
|
||||
" result = params;"
|
||||
"end);"
|
||||
"ecs.send_event('flag_event', {"
|
||||
" bits = 5,"
|
||||
" mask = 7"
|
||||
"ecs.send_event('data_event', {"
|
||||
" score = 42,"
|
||||
" level = 5,"
|
||||
" speed = 1.5,"
|
||||
" name = 'hero'"
|
||||
"});"
|
||||
"assert(result ~= nil, 'params should not be nil');"
|
||||
"assert(result.bits == 5, 'expected bits=5');"
|
||||
"assert(result.mask == 7, 'expected mask=7')");
|
||||
"assert(result.score == 42, 'expected score 42');"
|
||||
"assert(result.level == 5, 'expected level 5');"
|
||||
"assert(result.speed == 1.5, 'expected speed 1.5');"
|
||||
"assert(result.name == 'hero', 'expected name hero')");
|
||||
if (!ok)
|
||||
FAIL("event with bits assertion failed");
|
||||
FAIL("event with params assertion failed");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 7: Send event with array params
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testEventWithArrayParams(lua_State *L)
|
||||
{
|
||||
TEST("send event with array params");
|
||||
|
||||
bool ok = runLua(
|
||||
L, "local result = nil;"
|
||||
"ecs.subscribe_event('array_event', function(event, params)"
|
||||
" result = params;"
|
||||
"end);"
|
||||
"ecs.send_event('array_event', {"
|
||||
" positions = { 10, 20, 30 },"
|
||||
" names = { 'a', 'b', 'c' }"
|
||||
"});"
|
||||
"assert(result ~= nil, 'params should not be nil');"
|
||||
"assert(result.positions[1] == 10, 'expected pos[1]=10');"
|
||||
"assert(result.positions[2] == 20, 'expected pos[2]=20');"
|
||||
"assert(result.positions[3] == 30, 'expected pos[3]=30');"
|
||||
"assert(result.names[1] == 'a', 'expected names[1]=a');"
|
||||
"assert(result.names[2] == 'b', 'expected names[2]=b');"
|
||||
"assert(result.names[3] == 'c', 'expected names[3]=c')");
|
||||
if (!ok)
|
||||
FAIL("event with array params assertion failed");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 8: Send event with entity ID params
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testEventWithEntityId(lua_State *L)
|
||||
{
|
||||
TEST("send event with entity ID params");
|
||||
|
||||
bool ok = runLua(
|
||||
L, "local result = nil;"
|
||||
"ecs.subscribe_event('entity_event', function(event, params)"
|
||||
" result = params;"
|
||||
"end);"
|
||||
"local eid = ecs.create_entity();"
|
||||
"ecs.send_event('entity_event', {"
|
||||
" target = eid"
|
||||
"});"
|
||||
"assert(result ~= nil, 'params should not be nil');"
|
||||
"assert(result.target == eid, 'expected target entity ID')");
|
||||
if (!ok)
|
||||
FAIL("event with entity ID assertion failed");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
@@ -331,14 +332,70 @@ static int testUnsubscribeInvalid(lua_State *L)
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 11: EventParams type metadata (_types table)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testEventParamsTypes(lua_State *L)
|
||||
{
|
||||
TEST("EventParams type metadata (_types table)");
|
||||
|
||||
bool ok = runLua(
|
||||
L,
|
||||
"local result = nil;"
|
||||
"ecs.subscribe_event('typed_event', function(event, params)"
|
||||
" result = params;"
|
||||
"end);"
|
||||
"ecs.send_event('typed_event', {"
|
||||
" my_int = 42,"
|
||||
" my_float = 3.14,"
|
||||
" my_str = 'hello'"
|
||||
"});"
|
||||
"assert(result ~= nil, 'params should not be nil');"
|
||||
"assert(result._types ~= nil, '_types table should exist');"
|
||||
"assert(result._types.my_int == 'int', 'expected int type');"
|
||||
"assert(result._types.my_str == 'string', 'expected string type')");
|
||||
if (!ok)
|
||||
FAIL("EventParams type metadata assertion failed");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 12: Send event with double precision float
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testEventWithDouble(lua_State *L)
|
||||
{
|
||||
TEST("send event with double precision float");
|
||||
|
||||
bool ok = runLua(
|
||||
L, "local result = nil;"
|
||||
"ecs.subscribe_event('double_event', function(event, params)"
|
||||
" result = params;"
|
||||
"end);"
|
||||
"ecs.send_event('double_event', {"
|
||||
" pi = 3.141592653589793"
|
||||
"});"
|
||||
"assert(result ~= nil, 'params should not be nil');"
|
||||
"assert(math.abs(result.pi - 3.141592653589793) < 0.000001, "
|
||||
"'expected pi value')");
|
||||
if (!ok)
|
||||
FAIL("event with double assertion failed");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Main
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
int main()
|
||||
{
|
||||
printf("Event Lua API Tests\n");
|
||||
printf("===================\n\n");
|
||||
printf("Event Lua API Tests (EventParams)\n");
|
||||
printf("=================================\n\n");
|
||||
|
||||
// Create Lua state
|
||||
lua_State *L = luaL_newstate();
|
||||
@@ -359,11 +416,13 @@ int main()
|
||||
failures += testSubscribeWithParams(L);
|
||||
failures += testUnsubscribe(L);
|
||||
failures += testMultipleSubscribers(L);
|
||||
failures += testEventWithBlackboardParams(L);
|
||||
failures += testEventWithVec3Params(L);
|
||||
failures += testEventWithBits(L);
|
||||
failures += testEventWithParams(L);
|
||||
failures += testEventWithArrayParams(L);
|
||||
failures += testEventWithEntityId(L);
|
||||
failures += testMultipleEvents(L);
|
||||
failures += testUnsubscribeInvalid(L);
|
||||
failures += testEventParamsTypes(L);
|
||||
failures += testEventWithDouble(L);
|
||||
|
||||
// Cleanup
|
||||
lua_close(L);
|
||||
|
||||
957
src/features/editScene/tests/event_params_test.cpp
Normal file
957
src/features/editScene/tests/event_params_test.cpp
Normal file
@@ -0,0 +1,957 @@
|
||||
/**
|
||||
* @file event_params_test.cpp
|
||||
* @brief Standalone C++ test for the EventParams tagged union type.
|
||||
*
|
||||
* Tests the EventValue and EventParams classes directly (C++ API),
|
||||
* covering all supported types: entity ID, int, float, double, string,
|
||||
* and arrays of each type.
|
||||
*
|
||||
* Build with:
|
||||
* g++ -std=c++17 -I. -I../.. -I../../lua/lua-5.4.8/src \
|
||||
* event_params_test.cpp \
|
||||
* -o event_params_test -lm
|
||||
*
|
||||
* Or via CMake (see CMakeLists.txt in this directory).
|
||||
*/
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <cassert>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <cmath>
|
||||
|
||||
// Ogre stub (provides Ogre::String, Ogre::Vector3, Ogre::LogManager)
|
||||
#include "ogre_stub.h"
|
||||
|
||||
// Include the component under test
|
||||
#include "../components/EventParams.hpp"
|
||||
|
||||
using namespace editScene;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testCount = 0;
|
||||
static int passCount = 0;
|
||||
|
||||
#define TEST(name) \
|
||||
do { \
|
||||
testCount++; \
|
||||
printf(" TEST %d: %s ... ", testCount, name); \
|
||||
} while (0)
|
||||
|
||||
#define PASS() \
|
||||
do { \
|
||||
passCount++; \
|
||||
printf("PASS\n"); \
|
||||
} while (0)
|
||||
|
||||
#define FAIL(msg) \
|
||||
do { \
|
||||
printf("FAIL: %s\n", msg); \
|
||||
return 1; \
|
||||
} while (0)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 1: EventValue default construction (nil)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testDefaultValue()
|
||||
{
|
||||
TEST("EventValue default construction (nil)");
|
||||
|
||||
EventValue v;
|
||||
if (v.getType() != EventValue::NIL)
|
||||
FAIL("expected NIL type");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 2: EventValue entity ID construction
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testEntityIdValue()
|
||||
{
|
||||
TEST("EventValue entity ID construction");
|
||||
|
||||
uint64_t id = 12345;
|
||||
EventValue v(id);
|
||||
if (v.getType() != EventValue::ENTITY_ID)
|
||||
FAIL("expected ENTITY_ID type");
|
||||
if (v.getEntityId() != id)
|
||||
FAIL("entity ID mismatch");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 3: EventValue int construction
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testIntValue()
|
||||
{
|
||||
TEST("EventValue int construction");
|
||||
|
||||
int64_t val = -42;
|
||||
EventValue v(val);
|
||||
if (v.getType() != EventValue::INT)
|
||||
FAIL("expected INT type");
|
||||
if (v.getInt() != val)
|
||||
FAIL("int value mismatch");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 4: EventValue float construction
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testFloatValue()
|
||||
{
|
||||
TEST("EventValue float construction");
|
||||
|
||||
float val = 3.14f;
|
||||
EventValue v(val);
|
||||
if (v.getType() != EventValue::FLOAT)
|
||||
FAIL("expected FLOAT type");
|
||||
if (std::abs(v.getFloat() - val) > 0.0001f)
|
||||
FAIL("float value mismatch");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 5: EventValue double construction
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testDoubleValue()
|
||||
{
|
||||
TEST("EventValue double construction");
|
||||
|
||||
double val = 3.141592653589793;
|
||||
EventValue v(val);
|
||||
if (v.getType() != EventValue::DOUBLE)
|
||||
FAIL("expected DOUBLE type");
|
||||
if (std::abs(v.getDouble() - val) > 1e-12)
|
||||
FAIL("double value mismatch");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 6: EventValue string construction
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testStringValue()
|
||||
{
|
||||
TEST("EventValue string construction");
|
||||
|
||||
std::string val = "hello world";
|
||||
EventValue v(val);
|
||||
if (v.getType() != EventValue::STRING)
|
||||
FAIL("expected STRING type");
|
||||
if (v.getString() != val)
|
||||
FAIL("string value mismatch");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 7: EventValue entity ID array construction
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testEntityIdArrayValue()
|
||||
{
|
||||
TEST("EventValue entity ID array construction");
|
||||
|
||||
std::vector<uint64_t> arr = { 100, 200, 300 };
|
||||
EventValue v(arr);
|
||||
if (v.getType() != EventValue::ENTITY_ID_ARRAY)
|
||||
FAIL("expected ENTITY_ID_ARRAY type");
|
||||
const auto &result = v.getEntityIdArray();
|
||||
if (result.size() != 3)
|
||||
FAIL("array size mismatch");
|
||||
if (result[0] != 100 || result[1] != 200 || result[2] != 300)
|
||||
FAIL("array element mismatch");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 8: EventValue int array construction
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testIntArrayValue()
|
||||
{
|
||||
TEST("EventValue int array construction");
|
||||
|
||||
std::vector<int64_t> arr = { -1, 0, 42, 999 };
|
||||
EventValue v(arr);
|
||||
if (v.getType() != EventValue::INT_ARRAY)
|
||||
FAIL("expected INT_ARRAY type");
|
||||
const auto &result = v.getIntArray();
|
||||
if (result.size() != 4)
|
||||
FAIL("array size mismatch");
|
||||
if (result[0] != -1 || result[1] != 0 || result[2] != 42 ||
|
||||
result[3] != 999)
|
||||
FAIL("array element mismatch");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 9: EventValue float array construction
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testFloatArrayValue()
|
||||
{
|
||||
TEST("EventValue float array construction");
|
||||
|
||||
std::vector<float> arr = { 1.5f, 2.5f, 3.5f };
|
||||
EventValue v(arr);
|
||||
if (v.getType() != EventValue::FLOAT_ARRAY)
|
||||
FAIL("expected FLOAT_ARRAY type");
|
||||
const auto &result = v.getFloatArray();
|
||||
if (result.size() != 3)
|
||||
FAIL("array size mismatch");
|
||||
if (std::abs(result[0] - 1.5f) > 0.0001f ||
|
||||
std::abs(result[1] - 2.5f) > 0.0001f ||
|
||||
std::abs(result[2] - 3.5f) > 0.0001f)
|
||||
FAIL("array element mismatch");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 10: EventValue double array construction
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testDoubleArrayValue()
|
||||
{
|
||||
TEST("EventValue double array construction");
|
||||
|
||||
std::vector<double> arr = { 1.1, 2.2, 3.3, 4.4 };
|
||||
EventValue v(arr);
|
||||
if (v.getType() != EventValue::DOUBLE_ARRAY)
|
||||
FAIL("expected DOUBLE_ARRAY type");
|
||||
const auto &result = v.getDoubleArray();
|
||||
if (result.size() != 4)
|
||||
FAIL("array size mismatch");
|
||||
if (std::abs(result[0] - 1.1) > 1e-12 ||
|
||||
std::abs(result[1] - 2.2) > 1e-12 ||
|
||||
std::abs(result[2] - 3.3) > 1e-12 ||
|
||||
std::abs(result[3] - 4.4) > 1e-12)
|
||||
FAIL("array element mismatch");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 11: EventValue string array construction
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testStringArrayValue()
|
||||
{
|
||||
TEST("EventValue string array construction");
|
||||
|
||||
std::vector<std::string> arr = { "foo", "bar", "baz" };
|
||||
EventValue v(arr);
|
||||
if (v.getType() != EventValue::STRING_ARRAY)
|
||||
FAIL("expected STRING_ARRAY type");
|
||||
const auto &result = v.getStringArray();
|
||||
if (result.size() != 3)
|
||||
FAIL("array size mismatch");
|
||||
if (result[0] != "foo" || result[1] != "bar" || result[2] != "baz")
|
||||
FAIL("array element mismatch");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 12: EventValue copy construction
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testCopyConstruction()
|
||||
{
|
||||
TEST("EventValue copy construction");
|
||||
|
||||
EventValue original((int64_t)42);
|
||||
EventValue copy(original);
|
||||
if (copy.getType() != EventValue::INT)
|
||||
FAIL("expected INT type after copy");
|
||||
if (copy.getInt() != 42)
|
||||
FAIL("int value mismatch after copy");
|
||||
|
||||
// Modify original should not affect copy
|
||||
original = EventValue((int64_t)99);
|
||||
if (copy.getInt() != 42)
|
||||
FAIL("copy should be independent");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 13: EventValue assignment
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testAssignment()
|
||||
{
|
||||
TEST("EventValue assignment");
|
||||
|
||||
EventValue v;
|
||||
v = EventValue(std::string("test"));
|
||||
if (v.getType() != EventValue::STRING)
|
||||
FAIL("expected STRING type after assignment");
|
||||
if (v.getString() != "test")
|
||||
FAIL("string value mismatch after assignment");
|
||||
|
||||
// Re-assign to different type
|
||||
v = EventValue((double)2.71828);
|
||||
if (v.getType() != EventValue::DOUBLE)
|
||||
FAIL("expected DOUBLE type after re-assignment");
|
||||
if (std::abs(v.getDouble() - 2.71828) > 1e-12)
|
||||
FAIL("double value mismatch after re-assignment");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 14: EventParams default construction
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testParamsDefault()
|
||||
{
|
||||
TEST("EventParams default construction");
|
||||
|
||||
EventParams p;
|
||||
if (p.begin() != p.end())
|
||||
FAIL("expected empty params");
|
||||
if (p.size() != 0)
|
||||
FAIL("expected size 0");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 15: EventParams set/get int
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testParamsSetGetInt()
|
||||
{
|
||||
TEST("EventParams set/get int");
|
||||
|
||||
EventParams p;
|
||||
p.setInt("score", 100);
|
||||
if (p.size() != 1)
|
||||
FAIL("expected size 1");
|
||||
if (p.getInt("score") != 100)
|
||||
FAIL("expected score 100");
|
||||
if (p.has("score") != true)
|
||||
FAIL("expected has('score') == true");
|
||||
if (p.has("missing") != false)
|
||||
FAIL("expected has('missing') == false");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 16: EventParams set/get float
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testParamsSetGetFloat()
|
||||
{
|
||||
TEST("EventParams set/get float");
|
||||
|
||||
EventParams p;
|
||||
p.setFloat("speed", 1.5f);
|
||||
if (p.getFloat("speed") != 1.5f)
|
||||
FAIL("expected speed 1.5");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 17: EventParams set/get double
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testParamsSetGetDouble()
|
||||
{
|
||||
TEST("EventParams set/get double");
|
||||
|
||||
EventParams p;
|
||||
p.setDouble("pi", 3.141592653589793);
|
||||
if (std::abs(p.getDouble("pi") - 3.141592653589793) > 1e-12)
|
||||
FAIL("expected pi value");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 18: EventParams set/get string
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testParamsSetGetString()
|
||||
{
|
||||
TEST("EventParams set/get string");
|
||||
|
||||
EventParams p;
|
||||
p.setString("name", "hero");
|
||||
if (p.getString("name") != "hero")
|
||||
FAIL("expected name 'hero'");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 19: EventParams set/get entity ID
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testParamsSetGetEntityId()
|
||||
{
|
||||
TEST("EventParams set/get entity ID");
|
||||
|
||||
EventParams p;
|
||||
p.setEntityId("target", 12345);
|
||||
if (p.getEntityId("target") != 12345)
|
||||
FAIL("expected target 12345");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 20: EventParams set/get int array via EventValue
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testParamsSetGetIntArray()
|
||||
{
|
||||
TEST("EventParams set/get int array");
|
||||
|
||||
EventParams p;
|
||||
std::vector<int64_t> arr = { 1, 2, 3, 4, 5 };
|
||||
p.setIntArray("values", arr);
|
||||
const EventValue *v = p.get("values");
|
||||
if (!v)
|
||||
FAIL("expected value for 'values'");
|
||||
if (v->getType() != EventValue::INT_ARRAY)
|
||||
FAIL("expected INT_ARRAY type");
|
||||
const auto &result = v->getIntArray();
|
||||
if (result.size() != 5)
|
||||
FAIL("expected array size 5");
|
||||
if (result[0] != 1 || result[4] != 5)
|
||||
FAIL("array element mismatch");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 21: EventParams set/get float array via EventValue
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testParamsSetGetFloatArray()
|
||||
{
|
||||
TEST("EventParams set/get float array");
|
||||
|
||||
EventParams p;
|
||||
std::vector<float> arr = { 0.5f, 1.0f, 1.5f };
|
||||
p.setFloatArray("positions", arr);
|
||||
const EventValue *v = p.get("positions");
|
||||
if (!v)
|
||||
FAIL("expected value for 'positions'");
|
||||
if (v->getType() != EventValue::FLOAT_ARRAY)
|
||||
FAIL("expected FLOAT_ARRAY type");
|
||||
const auto &result = v->getFloatArray();
|
||||
if (result.size() != 3)
|
||||
FAIL("expected array size 3");
|
||||
if (std::abs(result[0] - 0.5f) > 0.0001f)
|
||||
FAIL("array element mismatch");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 22: EventParams set/get double array via EventValue
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testParamsSetGetDoubleArray()
|
||||
{
|
||||
TEST("EventParams set/get double array");
|
||||
|
||||
EventParams p;
|
||||
std::vector<double> arr = { 1.1, 2.2, 3.3 };
|
||||
p.setDoubleArray("coords", arr);
|
||||
const EventValue *v = p.get("coords");
|
||||
if (!v)
|
||||
FAIL("expected value for 'coords'");
|
||||
if (v->getType() != EventValue::DOUBLE_ARRAY)
|
||||
FAIL("expected DOUBLE_ARRAY type");
|
||||
const auto &result = v->getDoubleArray();
|
||||
if (result.size() != 3)
|
||||
FAIL("expected array size 3");
|
||||
if (std::abs(result[2] - 3.3) > 1e-12)
|
||||
FAIL("array element mismatch");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 23: EventParams set/get string array via EventValue
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testParamsSetGetStringArray()
|
||||
{
|
||||
TEST("EventParams set/get string array");
|
||||
|
||||
EventParams p;
|
||||
std::vector<std::string> arr = { "a", "b", "c" };
|
||||
p.setStringArray("tags", arr);
|
||||
const EventValue *v = p.get("tags");
|
||||
if (!v)
|
||||
FAIL("expected value for 'tags'");
|
||||
if (v->getType() != EventValue::STRING_ARRAY)
|
||||
FAIL("expected STRING_ARRAY type");
|
||||
const auto &result = v->getStringArray();
|
||||
if (result.size() != 3)
|
||||
FAIL("expected array size 3");
|
||||
if (result[0] != "a" || result[2] != "c")
|
||||
FAIL("array element mismatch");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 24: EventParams set/get entity ID array via EventValue
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testParamsSetGetEntityIdArray()
|
||||
{
|
||||
TEST("EventParams set/get entity ID array");
|
||||
|
||||
EventParams p;
|
||||
std::vector<uint64_t> arr = { 100, 200, 300 };
|
||||
p.setEntityIdArray("entities", arr);
|
||||
const EventValue *v = p.get("entities");
|
||||
if (!v)
|
||||
FAIL("expected value for 'entities'");
|
||||
if (v->getType() != EventValue::ENTITY_ID_ARRAY)
|
||||
FAIL("expected ENTITY_ID_ARRAY type");
|
||||
const auto &result = v->getEntityIdArray();
|
||||
if (result.size() != 3)
|
||||
FAIL("expected array size 3");
|
||||
if (result[0] != 100 || result[2] != 300)
|
||||
FAIL("array element mismatch");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 25: EventParams multiple types
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testParamsMultipleTypes()
|
||||
{
|
||||
TEST("EventParams multiple types");
|
||||
|
||||
EventParams p;
|
||||
p.setInt("score", 42);
|
||||
p.setFloat("speed", 1.5f);
|
||||
p.setDouble("pi", 3.14);
|
||||
p.setString("name", "test");
|
||||
p.setEntityId("target", 999);
|
||||
|
||||
if (p.size() != 5)
|
||||
FAIL("expected size 5");
|
||||
if (p.getInt("score") != 42)
|
||||
FAIL("expected score 42");
|
||||
if (std::abs(p.getFloat("speed") - 1.5f) > 0.0001f)
|
||||
FAIL("expected speed 1.5");
|
||||
if (std::abs(p.getDouble("pi") - 3.14) > 0.001)
|
||||
FAIL("expected pi 3.14");
|
||||
if (p.getString("name") != "test")
|
||||
FAIL("expected name 'test'");
|
||||
if (p.getEntityId("target") != 999)
|
||||
FAIL("expected target 999");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 26: EventParams iteration
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testParamsIteration()
|
||||
{
|
||||
TEST("EventParams iteration");
|
||||
|
||||
EventParams p;
|
||||
p.setInt("a", 1);
|
||||
p.setInt("b", 2);
|
||||
p.setInt("c", 3);
|
||||
|
||||
int count = 0;
|
||||
bool foundA = false, foundB = false, foundC = false;
|
||||
for (EventParams::ConstIterator it = p.begin(); it != p.end(); ++it) {
|
||||
count++;
|
||||
if (it->first == "a")
|
||||
foundA = true;
|
||||
if (it->first == "b")
|
||||
foundB = true;
|
||||
if (it->first == "c")
|
||||
foundC = true;
|
||||
}
|
||||
if (count != 3)
|
||||
FAIL("expected 3 entries");
|
||||
if (!foundA || !foundB || !foundC)
|
||||
FAIL("missing expected keys");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 27: EventParams merge
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testParamsMerge()
|
||||
{
|
||||
TEST("EventParams merge");
|
||||
|
||||
EventParams p1;
|
||||
p1.setInt("a", 1);
|
||||
p1.setString("b", "hello");
|
||||
|
||||
EventParams p2;
|
||||
p2.setInt("c", 3);
|
||||
p2.setFloat("d", 4.5f);
|
||||
|
||||
p1.merge(p2);
|
||||
if (p1.size() != 4)
|
||||
FAIL("expected size 4 after merge");
|
||||
if (p1.getInt("a") != 1)
|
||||
FAIL("expected a=1");
|
||||
if (p1.getString("b") != "hello")
|
||||
FAIL("expected b='hello'");
|
||||
if (p1.getInt("c") != 3)
|
||||
FAIL("expected c=3");
|
||||
if (std::abs(p1.getFloat("d") - 4.5f) > 0.0001f)
|
||||
FAIL("expected d=4.5");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 28: EventParams merge with override
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testParamsMergeOverride()
|
||||
{
|
||||
TEST("EventParams merge with override");
|
||||
|
||||
EventParams p1;
|
||||
p1.setInt("a", 1);
|
||||
p1.setString("b", "original");
|
||||
|
||||
EventParams p2;
|
||||
p2.setInt("a", 99);
|
||||
p2.setString("b", "overridden");
|
||||
|
||||
p1.merge(p2);
|
||||
if (p1.getInt("a") != 99)
|
||||
FAIL("expected a=99 after override");
|
||||
if (p1.getString("b") != "overridden")
|
||||
FAIL("expected b='overridden' after override");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 29: EventParams dump
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testParamsDump()
|
||||
{
|
||||
TEST("EventParams dump");
|
||||
|
||||
EventParams p;
|
||||
p.setInt("score", 42);
|
||||
p.setString("name", "test");
|
||||
|
||||
std::string dump = p.dump();
|
||||
if (dump.empty())
|
||||
FAIL("dump should not be empty");
|
||||
if (dump.find("score") == std::string::npos)
|
||||
FAIL("dump should contain 'score'");
|
||||
if (dump.find("name") == std::string::npos)
|
||||
FAIL("dump should contain 'name'");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 30: EventParams equality
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testParamsEquality()
|
||||
{
|
||||
TEST("EventParams equality");
|
||||
|
||||
EventParams p1;
|
||||
p1.setInt("a", 1);
|
||||
p1.setString("b", "test");
|
||||
|
||||
EventParams p2;
|
||||
p2.setInt("a", 1);
|
||||
p2.setString("b", "test");
|
||||
|
||||
if (!(p1 == p2))
|
||||
FAIL("expected equal params");
|
||||
|
||||
EventParams p3;
|
||||
p3.setInt("a", 99);
|
||||
p3.setString("b", "test");
|
||||
|
||||
if (p1 == p3)
|
||||
FAIL("expected different params to be unequal");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 31: EventParams clear
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testParamsClear()
|
||||
{
|
||||
TEST("EventParams clear");
|
||||
|
||||
EventParams p;
|
||||
p.setInt("a", 1);
|
||||
p.setString("b", "test");
|
||||
if (p.size() != 2)
|
||||
FAIL("expected size 2 before clear");
|
||||
|
||||
p.clear();
|
||||
if (p.size() != 0)
|
||||
FAIL("expected size 0 after clear");
|
||||
if (p.begin() != p.end())
|
||||
FAIL("expected empty after clear");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 32: EventValue equality
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testValueEquality()
|
||||
{
|
||||
TEST("EventValue equality");
|
||||
|
||||
EventValue v1((int64_t)42);
|
||||
EventValue v2((int64_t)42);
|
||||
EventValue v3((int64_t)99);
|
||||
|
||||
if (!(v1 == v2))
|
||||
FAIL("expected equal values");
|
||||
if (v1 == v3)
|
||||
FAIL("expected different values to be unequal");
|
||||
|
||||
// Different types should be unequal
|
||||
EventValue v4(3.14f);
|
||||
if (v1 == v4)
|
||||
FAIL("expected different types to be unequal");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 33: EventValue asNumeric
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testValueAsNumeric()
|
||||
{
|
||||
TEST("EventValue asNumeric");
|
||||
|
||||
EventValue vi((int64_t)42);
|
||||
if (std::abs(vi.asNumeric() - 42.0) > 0.0001)
|
||||
FAIL("expected int asNumeric 42");
|
||||
|
||||
EventValue vf(3.14f);
|
||||
if (std::abs(vf.asNumeric() - 3.14) > 0.001)
|
||||
FAIL("expected float asNumeric 3.14");
|
||||
|
||||
EventValue vd(2.71828);
|
||||
if (std::abs(vd.asNumeric() - 2.71828) > 1e-12)
|
||||
FAIL("expected double asNumeric 2.71828");
|
||||
|
||||
EventValue ve((uint64_t)100);
|
||||
if (std::abs(ve.asNumeric() - 100.0) > 0.0001)
|
||||
FAIL("expected entity_id asNumeric 100");
|
||||
|
||||
// Nil should return 0
|
||||
EventValue vn;
|
||||
if (vn.asNumeric() != 0.0)
|
||||
FAIL("expected nil asNumeric 0");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 34: EventParams remove
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testParamsRemove()
|
||||
{
|
||||
TEST("EventParams remove key");
|
||||
|
||||
EventParams p;
|
||||
p.setInt("a", 1);
|
||||
p.setInt("b", 2);
|
||||
if (p.size() != 2)
|
||||
FAIL("expected size 2");
|
||||
|
||||
p.remove("a");
|
||||
if (p.size() != 1)
|
||||
FAIL("expected size 1 after remove");
|
||||
if (p.has("a"))
|
||||
FAIL("expected 'a' to be removed");
|
||||
if (!p.has("b"))
|
||||
FAIL("expected 'b' to still exist");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 35: EventValue move semantics
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testValueMove()
|
||||
{
|
||||
TEST("EventValue move semantics");
|
||||
|
||||
EventValue original(std::string("moved"));
|
||||
EventValue moved(std::move(original));
|
||||
if (moved.getType() != EventValue::STRING)
|
||||
FAIL("expected STRING type after move");
|
||||
if (moved.getString() != "moved")
|
||||
FAIL("string value mismatch after move");
|
||||
// Original should be nil after move
|
||||
if (original.getType() != EventValue::NIL)
|
||||
FAIL("original should be NIL after move");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 36: EventValue char* constructor
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testCharPtrValue()
|
||||
{
|
||||
TEST("EventValue char* construction");
|
||||
|
||||
EventValue v("hello from c-string");
|
||||
if (v.getType() != EventValue::STRING)
|
||||
FAIL("expected STRING type");
|
||||
if (v.getString() != "hello from c-string")
|
||||
FAIL("string value mismatch");
|
||||
|
||||
// Null pointer should give empty string
|
||||
EventValue vnull((const char *)nullptr);
|
||||
if (vnull.getType() != EventValue::STRING)
|
||||
FAIL("expected STRING type for null");
|
||||
if (vnull.getString() != "")
|
||||
FAIL("expected empty string for null");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Main
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
int main()
|
||||
{
|
||||
printf("EventParams C++ API Tests\n");
|
||||
printf("=========================\n\n");
|
||||
|
||||
int failures = 0;
|
||||
failures += testDefaultValue();
|
||||
failures += testEntityIdValue();
|
||||
failures += testIntValue();
|
||||
failures += testFloatValue();
|
||||
failures += testDoubleValue();
|
||||
failures += testStringValue();
|
||||
failures += testEntityIdArrayValue();
|
||||
failures += testIntArrayValue();
|
||||
failures += testFloatArrayValue();
|
||||
failures += testDoubleArrayValue();
|
||||
failures += testStringArrayValue();
|
||||
failures += testCopyConstruction();
|
||||
failures += testAssignment();
|
||||
failures += testParamsDefault();
|
||||
failures += testParamsSetGetInt();
|
||||
failures += testParamsSetGetFloat();
|
||||
failures += testParamsSetGetDouble();
|
||||
failures += testParamsSetGetString();
|
||||
failures += testParamsSetGetEntityId();
|
||||
failures += testParamsSetGetIntArray();
|
||||
failures += testParamsSetGetFloatArray();
|
||||
failures += testParamsSetGetDoubleArray();
|
||||
failures += testParamsSetGetStringArray();
|
||||
failures += testParamsSetGetEntityIdArray();
|
||||
failures += testParamsMultipleTypes();
|
||||
failures += testParamsIteration();
|
||||
failures += testParamsMerge();
|
||||
failures += testParamsMergeOverride();
|
||||
failures += testParamsDump();
|
||||
failures += testParamsEquality();
|
||||
failures += testParamsClear();
|
||||
failures += testValueEquality();
|
||||
failures += testValueAsNumeric();
|
||||
failures += testParamsRemove();
|
||||
failures += testValueMove();
|
||||
failures += testCharPtrValue();
|
||||
|
||||
printf("\nResults: %d/%d passed, %d failed\n", passCount, testCount,
|
||||
failures);
|
||||
|
||||
return failures > 0 ? 1 : 0;
|
||||
}
|
||||
272
src/features/editScene/tests/game_mode_lua_test.cpp
Normal file
272
src/features/editScene/tests/game_mode_lua_test.cpp
Normal file
@@ -0,0 +1,272 @@
|
||||
/**
|
||||
* @file game_mode_lua_test.cpp
|
||||
* @brief Standalone test for the Lua Game Mode API.
|
||||
*
|
||||
* Tests the ecs.is_editor_mode(), ecs.is_game_mode(), ecs.is_game_playing(),
|
||||
* ecs.is_game_menu(), ecs.is_game_paused(), ecs.get_game_mode(), and
|
||||
* ecs.get_game_play_state() functions exposed via the ecs.* Lua API.
|
||||
*
|
||||
* Build with:
|
||||
* g++ -std=c++17 -I. -I../.. -I../../lua/lua-5.4.8/src \
|
||||
* game_mode_lua_test.cpp \
|
||||
* ../lua/LuaGameModeApi.cpp \
|
||||
* ../../lua/lua-5.4.8/src/liblua.a \
|
||||
* -o game_mode_lua_test -lm
|
||||
*
|
||||
* Or via CMake (see CMakeLists.txt in this directory).
|
||||
*/
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <cassert>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
// Ogre stub (provides Ogre::String, Ogre::Vector3, Ogre::LogManager)
|
||||
#include "ogre_stub.h"
|
||||
|
||||
// Include Lua
|
||||
extern "C" {
|
||||
#include <lua.h>
|
||||
#include <lauxlib.h>
|
||||
#include <lualib.h>
|
||||
}
|
||||
|
||||
// Forward declare the registration function
|
||||
namespace editScene
|
||||
{
|
||||
void registerLuaGameModeApi(lua_State *L);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testCount = 0;
|
||||
static int passCount = 0;
|
||||
|
||||
#define TEST(name) \
|
||||
do { \
|
||||
testCount++; \
|
||||
printf(" TEST %d: %s ... ", testCount, name); \
|
||||
} while (0)
|
||||
|
||||
#define PASS() \
|
||||
do { \
|
||||
passCount++; \
|
||||
printf("PASS\n"); \
|
||||
} while (0)
|
||||
|
||||
#define FAIL(msg) \
|
||||
do { \
|
||||
printf("FAIL: %s\n", msg); \
|
||||
return 1; \
|
||||
} while (0)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helper: run a Lua string and check for errors
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static bool runLua(lua_State *L, const char *code)
|
||||
{
|
||||
if (luaL_dostring(L, code) != LUA_OK) {
|
||||
fprintf(stderr, "Lua error: %s\n", lua_tostring(L, -1));
|
||||
lua_pop(L, 1);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 1: Default state is editor mode
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testDefaultIsEditor(lua_State *L)
|
||||
{
|
||||
TEST("default state is editor mode");
|
||||
|
||||
bool ok = runLua(L, "assert(ecs.is_editor_mode() == true, "
|
||||
"'expected editor mode by default');"
|
||||
"assert(ecs.is_game_mode() == false, "
|
||||
"'expected not game mode by default');"
|
||||
"local mode = ecs.get_game_mode();"
|
||||
"assert(mode == 'editor', "
|
||||
"'expected editor, got ' .. tostring(mode))");
|
||||
if (!ok)
|
||||
FAIL("default state assertion failed");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 2: Default gameplay state is menu
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testDefaultPlayState(lua_State *L)
|
||||
{
|
||||
TEST("default gameplay state is menu");
|
||||
|
||||
bool ok = runLua(
|
||||
L, "assert(ecs.is_game_playing() == false, "
|
||||
"'expected not playing by default');"
|
||||
"assert(ecs.is_game_menu() == false, "
|
||||
"'expected not game menu by default (we are in editor)');"
|
||||
"assert(ecs.is_game_paused() == false, "
|
||||
"'expected not paused by default');"
|
||||
"local state = ecs.get_game_play_state();"
|
||||
"assert(state == 'menu', "
|
||||
"'expected menu, got ' .. tostring(state))");
|
||||
if (!ok)
|
||||
FAIL("default play state assertion failed");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 3: Return types are correct
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testReturnTypes(lua_State *L)
|
||||
{
|
||||
TEST("return types are correct");
|
||||
|
||||
bool ok = runLua(L,
|
||||
"assert(type(ecs.is_editor_mode()) == 'boolean', "
|
||||
"'is_editor_mode should return boolean');"
|
||||
"assert(type(ecs.is_game_mode()) == 'boolean', "
|
||||
"'is_game_mode should return boolean');"
|
||||
"assert(type(ecs.is_game_playing()) == 'boolean', "
|
||||
"'is_game_playing should return boolean');"
|
||||
"assert(type(ecs.is_game_menu()) == 'boolean', "
|
||||
"'is_game_menu should return boolean');"
|
||||
"assert(type(ecs.is_game_paused()) == 'boolean', "
|
||||
"'is_game_paused should return boolean');"
|
||||
"assert(type(ecs.get_game_mode()) == 'string', "
|
||||
"'get_game_mode should return string');"
|
||||
"assert(type(ecs.get_game_play_state()) == 'string', "
|
||||
"'get_game_play_state should return string')");
|
||||
if (!ok)
|
||||
FAIL("return types assertion failed");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 4: Predicates are mutually exclusive
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testMutualExclusion(lua_State *L)
|
||||
{
|
||||
TEST("editor and game mode are mutually exclusive");
|
||||
|
||||
bool ok = runLua(L,
|
||||
"assert(ecs.is_editor_mode() ~= ecs.is_game_mode(), "
|
||||
"'editor and game should be mutually exclusive')");
|
||||
if (!ok)
|
||||
FAIL("mutual exclusion assertion failed");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 5: Game play state predicates are false in editor mode
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testPlayStateInEditor(lua_State *L)
|
||||
{
|
||||
TEST("game play state predicates are false in editor mode");
|
||||
|
||||
bool ok = runLua(L, "assert(ecs.is_game_playing() == false, "
|
||||
"'is_game_playing should be false in editor');"
|
||||
"assert(ecs.is_game_menu() == false, "
|
||||
"'is_game_menu should be false in editor');"
|
||||
"assert(ecs.is_game_paused() == false, "
|
||||
"'is_game_paused should be false in editor')");
|
||||
if (!ok)
|
||||
FAIL("play state in editor assertion failed");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 6: get_game_mode returns valid string
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testGetGameModeString(lua_State *L)
|
||||
{
|
||||
TEST("get_game_mode returns valid string");
|
||||
|
||||
bool ok = runLua(L,
|
||||
"local mode = ecs.get_game_mode();"
|
||||
"assert(mode == 'editor' or mode == 'game', "
|
||||
"'expected editor or game, got ' .. tostring(mode))");
|
||||
if (!ok)
|
||||
FAIL("get_game_mode string assertion failed");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 7: get_game_play_state returns valid string
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testGetPlayStateString(lua_State *L)
|
||||
{
|
||||
TEST("get_game_play_state returns valid string");
|
||||
|
||||
bool ok = runLua(
|
||||
L,
|
||||
"local state = ecs.get_game_play_state();"
|
||||
"assert(state == 'menu' or state == 'playing' or state == 'paused', "
|
||||
"'expected menu/playing/paused, got ' .. tostring(state))");
|
||||
if (!ok)
|
||||
FAIL("get_game_play_state string assertion failed");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Main
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
int main()
|
||||
{
|
||||
printf("Game Mode Lua API Tests\n");
|
||||
printf("=======================\n\n");
|
||||
|
||||
// Create Lua state
|
||||
lua_State *L = luaL_newstate();
|
||||
if (!L) {
|
||||
fprintf(stderr, "Failed to create Lua state\n");
|
||||
return 1;
|
||||
}
|
||||
luaL_openlibs(L);
|
||||
|
||||
// Register the game mode API
|
||||
editScene::registerLuaGameModeApi(L);
|
||||
|
||||
// Run tests
|
||||
int failures = 0;
|
||||
failures += testDefaultIsEditor(L);
|
||||
failures += testDefaultPlayState(L);
|
||||
failures += testReturnTypes(L);
|
||||
failures += testMutualExclusion(L);
|
||||
failures += testPlayStateInEditor(L);
|
||||
failures += testGetGameModeString(L);
|
||||
failures += testGetPlayStateString(L);
|
||||
|
||||
// Cleanup
|
||||
lua_close(L);
|
||||
|
||||
printf("\nResults: %d/%d passed, %d failed\n", passCount, testCount,
|
||||
failures);
|
||||
|
||||
return failures > 0 ? 1 : 0;
|
||||
}
|
||||
@@ -12,6 +12,7 @@
|
||||
*/
|
||||
|
||||
#include "ogre_stub.h"
|
||||
#include "../components/EventParams.hpp"
|
||||
#include <lua.hpp>
|
||||
#include <cstdio>
|
||||
#include <cassert>
|
||||
@@ -44,6 +45,97 @@ using ComponentData = std::unordered_map<std::string, ComponentFieldValue>;
|
||||
std::unordered_map<int, std::unordered_map<std::string, ComponentData> >
|
||||
s_components;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Stub: LuaGameModeApi
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// Game mode state for stubs
|
||||
static int s_stubGameMode = 0; // 0 = editor, 1 = game
|
||||
static int s_stubPlayState = 0; // 0 = menu, 1 = playing, 2 = paused
|
||||
|
||||
void registerLuaGameModeApi(lua_State *L)
|
||||
{
|
||||
// Get or create the "ecs" global table
|
||||
lua_getglobal(L, "ecs");
|
||||
if (lua_isnil(L, -1)) {
|
||||
lua_pop(L, 1);
|
||||
lua_newtable(L);
|
||||
}
|
||||
|
||||
// is_editor_mode
|
||||
lua_pushcfunction(L, [](lua_State *L) -> int {
|
||||
lua_pushboolean(L, s_stubGameMode == 0 ? 1 : 0);
|
||||
return 1;
|
||||
});
|
||||
lua_setfield(L, -2, "is_editor_mode");
|
||||
|
||||
// is_game_mode
|
||||
lua_pushcfunction(L, [](lua_State *L) -> int {
|
||||
lua_pushboolean(L, s_stubGameMode == 1 ? 1 : 0);
|
||||
return 1;
|
||||
});
|
||||
lua_setfield(L, -2, "is_game_mode");
|
||||
|
||||
// is_game_playing
|
||||
lua_pushcfunction(L, [](lua_State *L) -> int {
|
||||
lua_pushboolean(
|
||||
L,
|
||||
(s_stubGameMode == 1 && s_stubPlayState == 1) ? 1 : 0);
|
||||
return 1;
|
||||
});
|
||||
lua_setfield(L, -2, "is_game_playing");
|
||||
|
||||
// is_game_menu
|
||||
lua_pushcfunction(L, [](lua_State *L) -> int {
|
||||
lua_pushboolean(
|
||||
L,
|
||||
(s_stubGameMode == 1 && s_stubPlayState == 0) ? 1 : 0);
|
||||
return 1;
|
||||
});
|
||||
lua_setfield(L, -2, "is_game_menu");
|
||||
|
||||
// is_game_paused
|
||||
lua_pushcfunction(L, [](lua_State *L) -> int {
|
||||
lua_pushboolean(
|
||||
L,
|
||||
(s_stubGameMode == 1 && s_stubPlayState == 2) ? 1 : 0);
|
||||
return 1;
|
||||
});
|
||||
lua_setfield(L, -2, "is_game_paused");
|
||||
|
||||
// get_game_mode
|
||||
lua_pushcfunction(L, [](lua_State *L) -> int {
|
||||
lua_pushstring(L, s_stubGameMode == 0 ? "editor" : "game");
|
||||
return 1;
|
||||
});
|
||||
lua_setfield(L, -2, "get_game_mode");
|
||||
|
||||
// get_game_play_state
|
||||
lua_pushcfunction(L, [](lua_State *L) -> int {
|
||||
const char *state = "menu";
|
||||
if (s_stubPlayState == 1)
|
||||
state = "playing";
|
||||
else if (s_stubPlayState == 2)
|
||||
state = "paused";
|
||||
lua_pushstring(L, state);
|
||||
return 1;
|
||||
});
|
||||
lua_setfield(L, -2, "get_game_play_state");
|
||||
|
||||
// debug_crash (stub: just logs, does not actually crash)
|
||||
lua_pushcfunction(L, [](lua_State *L) -> int {
|
||||
const char *msg = lua_tostring(L, 1);
|
||||
if (!msg)
|
||||
msg = "debug_crash called";
|
||||
fprintf(stderr, "Lua debug_crash (stub): %s\n", msg);
|
||||
// In test stubs, we do NOT actually abort/crash
|
||||
return 0;
|
||||
});
|
||||
lua_setfield(L, -2, "debug_crash");
|
||||
|
||||
lua_setglobal(L, "ecs");
|
||||
}
|
||||
|
||||
} // namespace editScene
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -478,7 +570,7 @@ void registerLuaComponentApi(lua_State *L)
|
||||
} // namespace editScene
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Stub: LuaEventApi
|
||||
// Stub: LuaEventApi (EventParams-based)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
namespace editScene
|
||||
@@ -494,6 +586,206 @@ struct EventSubscription {
|
||||
static std::vector<EventSubscription> s_subscriptions;
|
||||
static int s_nextSubId = 1;
|
||||
|
||||
// Helper: push EventParams type metadata to Lua table
|
||||
static void pushEventParamsTypes(lua_State *L,
|
||||
const editScene::EventParams ¶ms)
|
||||
{
|
||||
lua_newtable(L);
|
||||
for (editScene::EventParams::ConstIterator it = params.begin();
|
||||
it != params.end(); ++it) {
|
||||
const std::string &key = it->first;
|
||||
const editScene::EventValue &val = it->second;
|
||||
switch (val.getType()) {
|
||||
case editScene::EventValue::INT:
|
||||
lua_pushstring(L, "int");
|
||||
break;
|
||||
case editScene::EventValue::FLOAT:
|
||||
lua_pushstring(L, "float");
|
||||
break;
|
||||
case editScene::EventValue::DOUBLE:
|
||||
lua_pushstring(L, "double");
|
||||
break;
|
||||
case editScene::EventValue::STRING:
|
||||
lua_pushstring(L, "string");
|
||||
break;
|
||||
case editScene::EventValue::ENTITY_ID:
|
||||
lua_pushstring(L, "entity");
|
||||
break;
|
||||
case editScene::EventValue::INT_ARRAY:
|
||||
lua_pushstring(L, "int_array");
|
||||
break;
|
||||
case editScene::EventValue::FLOAT_ARRAY:
|
||||
lua_pushstring(L, "float_array");
|
||||
break;
|
||||
case editScene::EventValue::DOUBLE_ARRAY:
|
||||
lua_pushstring(L, "double_array");
|
||||
break;
|
||||
case editScene::EventValue::STRING_ARRAY:
|
||||
lua_pushstring(L, "string_array");
|
||||
break;
|
||||
case editScene::EventValue::ENTITY_ID_ARRAY:
|
||||
lua_pushstring(L, "entity_array");
|
||||
break;
|
||||
default:
|
||||
lua_pushstring(L, "unknown");
|
||||
break;
|
||||
}
|
||||
lua_setfield(L, -2, key.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
// Helper: push EventParams values to Lua table
|
||||
static void pushEventParamsToLua(lua_State *L,
|
||||
const editScene::EventParams ¶ms)
|
||||
{
|
||||
lua_newtable(L);
|
||||
for (editScene::EventParams::ConstIterator it = params.begin();
|
||||
it != params.end(); ++it) {
|
||||
const std::string &key = it->first;
|
||||
const editScene::EventValue &val = it->second;
|
||||
switch (val.getType()) {
|
||||
case editScene::EventValue::INT:
|
||||
lua_pushinteger(L, val.getInt());
|
||||
break;
|
||||
case editScene::EventValue::FLOAT:
|
||||
lua_pushnumber(L, (lua_Number)val.getFloat());
|
||||
break;
|
||||
case editScene::EventValue::DOUBLE:
|
||||
lua_pushnumber(L, (lua_Number)val.getDouble());
|
||||
break;
|
||||
case editScene::EventValue::STRING:
|
||||
lua_pushstring(L, val.getString().c_str());
|
||||
break;
|
||||
case editScene::EventValue::ENTITY_ID:
|
||||
lua_pushinteger(L, (lua_Integer)val.getEntityId());
|
||||
break;
|
||||
case editScene::EventValue::INT_ARRAY: {
|
||||
lua_newtable(L);
|
||||
const auto &arr = val.getIntArray();
|
||||
for (size_t i = 0; i < arr.size(); i++) {
|
||||
lua_pushinteger(L, arr[i]);
|
||||
lua_rawseti(L, -2, i + 1);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case editScene::EventValue::FLOAT_ARRAY: {
|
||||
lua_newtable(L);
|
||||
const auto &arr = val.getFloatArray();
|
||||
for (size_t i = 0; i < arr.size(); i++) {
|
||||
lua_pushnumber(L, (lua_Number)arr[i]);
|
||||
lua_rawseti(L, -2, i + 1);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case editScene::EventValue::DOUBLE_ARRAY: {
|
||||
lua_newtable(L);
|
||||
const auto &arr = val.getDoubleArray();
|
||||
for (size_t i = 0; i < arr.size(); i++) {
|
||||
lua_pushnumber(L, (lua_Number)arr[i]);
|
||||
lua_rawseti(L, -2, i + 1);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case editScene::EventValue::STRING_ARRAY: {
|
||||
lua_newtable(L);
|
||||
const auto &arr = val.getStringArray();
|
||||
for (size_t i = 0; i < arr.size(); i++) {
|
||||
lua_pushstring(L, arr[i].c_str());
|
||||
lua_rawseti(L, -2, i + 1);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case editScene::EventValue::ENTITY_ID_ARRAY: {
|
||||
lua_newtable(L);
|
||||
const auto &arr = val.getEntityIdArray();
|
||||
for (size_t i = 0; i < arr.size(); i++) {
|
||||
lua_pushinteger(L, (lua_Integer)arr[i]);
|
||||
lua_rawseti(L, -2, i + 1);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
lua_pushnil(L);
|
||||
break;
|
||||
}
|
||||
lua_setfield(L, -2, key.c_str());
|
||||
}
|
||||
// Add _types metadata table
|
||||
pushEventParamsTypes(L, params);
|
||||
lua_setfield(L, -2, "_types");
|
||||
}
|
||||
|
||||
// Helper: read a Lua value and add it to EventParams
|
||||
static void readLuaValueToEventParams(lua_State *L, int idx,
|
||||
const std::string &key,
|
||||
editScene::EventParams ¶ms)
|
||||
{
|
||||
int absIdx = lua_absindex(L, idx);
|
||||
int type = lua_type(L, absIdx);
|
||||
if (type == LUA_TNIL) {
|
||||
// Skip nil values
|
||||
} else if (type == LUA_TNUMBER) {
|
||||
lua_Number val = lua_tonumber(L, absIdx);
|
||||
// Check if it's an integer
|
||||
if (val == (lua_Integer)val) {
|
||||
params.setInt(key, (int)val);
|
||||
} else {
|
||||
// Store as double (Lua numbers are doubles)
|
||||
params.setDouble(key, (double)val);
|
||||
}
|
||||
} else if (type == LUA_TSTRING) {
|
||||
params.setString(key, lua_tostring(L, absIdx));
|
||||
} else if (type == LUA_TBOOLEAN) {
|
||||
params.setInt(key, lua_toboolean(L, absIdx) ? 1 : 0);
|
||||
} else if (type == LUA_TTABLE) {
|
||||
// Check if it's an array (all integer keys)
|
||||
bool isArray = true;
|
||||
bool isStringArray = false;
|
||||
bool isNumArray = false;
|
||||
int maxKey = 0;
|
||||
|
||||
lua_pushnil(L);
|
||||
while (lua_next(L, absIdx) != 0) {
|
||||
if (lua_type(L, -2) == LUA_TNUMBER) {
|
||||
int k = (int)lua_tointeger(L, -2);
|
||||
if (k > maxKey)
|
||||
maxKey = k;
|
||||
if (lua_type(L, -1) == LUA_TSTRING)
|
||||
isStringArray = true;
|
||||
else if (lua_type(L, -1) == LUA_TNUMBER)
|
||||
isNumArray = true;
|
||||
} else {
|
||||
isArray = false;
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
|
||||
if (isArray && maxKey > 0) {
|
||||
if (isStringArray) {
|
||||
std::vector<std::string> arr;
|
||||
for (int i = 1; i <= maxKey; i++) {
|
||||
lua_rawgeti(L, absIdx, i);
|
||||
if (lua_type(L, -1) == LUA_TSTRING)
|
||||
arr.push_back(
|
||||
lua_tostring(L, -1));
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
params.setStringArray(key, arr);
|
||||
} else if (isNumArray) {
|
||||
std::vector<double> arr;
|
||||
for (int i = 1; i <= maxKey; i++) {
|
||||
lua_rawgeti(L, absIdx, i);
|
||||
if (lua_type(L, -1) == LUA_TNUMBER)
|
||||
arr.push_back(
|
||||
lua_tonumber(L, -1));
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
params.setDoubleArray(key, arr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void registerLuaEventApi(lua_State *L)
|
||||
{
|
||||
// Get or create the "ecs" global table
|
||||
@@ -543,6 +835,22 @@ void registerLuaEventApi(lua_State *L)
|
||||
if (!eventName)
|
||||
return 0;
|
||||
|
||||
// Build EventParams from Lua table (arg 2)
|
||||
editScene::EventParams params;
|
||||
if (lua_istable(L, 2)) {
|
||||
lua_pushnil(L);
|
||||
while (lua_next(L, 2) != 0) {
|
||||
if (lua_type(L, -2) == LUA_TSTRING) {
|
||||
const char *key = lua_tostring(L, -2);
|
||||
if (key) {
|
||||
readLuaValueToEventParams(
|
||||
L, -1, key, params);
|
||||
}
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Call all matching subscriptions
|
||||
for (auto &sub : s_subscriptions) {
|
||||
if (sub.eventName == eventName) {
|
||||
@@ -551,12 +859,8 @@ void registerLuaEventApi(lua_State *L)
|
||||
sub.callbackRef);
|
||||
// Push event name
|
||||
lua_pushstring(L, eventName);
|
||||
// Push params (table or nil)
|
||||
if (lua_istable(L, 2)) {
|
||||
lua_pushvalue(L, 2);
|
||||
} else {
|
||||
lua_pushnil(L);
|
||||
}
|
||||
// Push params as Lua table
|
||||
pushEventParamsToLua(L, params);
|
||||
// Call callback(event, params)
|
||||
if (lua_pcall(L, 2, 0, 0) != LUA_OK) {
|
||||
fprintf(stderr,
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <cstdint>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace Ogre
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user