Pregnancy and birth
This commit is contained in:
@@ -39,6 +39,7 @@ set(EDITSCENE_SOURCES
|
||||
systems/CharacterSlotSystem.cpp
|
||||
systems/CharacterRegistry.cpp
|
||||
systems/MarkovNameGenerator.cpp
|
||||
systems/PregnancySystem.cpp
|
||||
systems/AnimationTreeSystem.cpp
|
||||
systems/BehaviorTreeSystem.cpp
|
||||
systems/NavMeshSystem.cpp
|
||||
@@ -163,6 +164,7 @@ set(EDITSCENE_SOURCES
|
||||
lua/LuaBehaviorTreeApi.cpp
|
||||
lua/LuaGameModeApi.cpp
|
||||
lua/LuaCharacterClassApi.cpp
|
||||
lua/LuaCharacterApi.cpp
|
||||
)
|
||||
|
||||
set(EDITSCENE_HEADERS
|
||||
@@ -213,6 +215,7 @@ set(EDITSCENE_HEADERS
|
||||
systems/ProceduralMeshSystem.hpp
|
||||
systems/CharacterSlotSystem.hpp
|
||||
systems/CharacterRegistry.hpp
|
||||
systems/PregnancySystem.hpp
|
||||
systems/AnimationTreeSystem.hpp
|
||||
systems/BehaviorTreeSystem.hpp
|
||||
systems/NavMeshSystem.hpp
|
||||
@@ -323,6 +326,7 @@ set(EDITSCENE_HEADERS
|
||||
lua/LuaBehaviorTreeApi.hpp
|
||||
lua/LuaGameModeApi.hpp
|
||||
lua/LuaCharacterClassApi.hpp
|
||||
lua/LuaCharacterApi.hpp
|
||||
)
|
||||
|
||||
add_executable(editSceneEditor ${EDITSCENE_SOURCES} ${EDITSCENE_HEADERS})
|
||||
@@ -489,6 +493,22 @@ target_include_directories(game_mode_lua_test PRIVATE
|
||||
${CMAKE_SOURCE_DIR}/src/lua/lua-5.4.8/src
|
||||
)
|
||||
|
||||
# Test: Character Lua API
|
||||
add_executable(character_lua_test
|
||||
tests/character_lua_test.cpp
|
||||
tests/lua_test_stubs.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(character_lua_test
|
||||
lua
|
||||
)
|
||||
|
||||
target_include_directories(character_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"
|
||||
|
||||
@@ -31,7 +31,9 @@
|
||||
#include "systems/StartupMenuSystem.hpp"
|
||||
#include "systems/DialogueSystem.hpp"
|
||||
#include "systems/CharacterClassSystem.hpp"
|
||||
#include "systems/PregnancySystem.hpp"
|
||||
#include "components/CharacterClassDatabase.hpp"
|
||||
#include "lua/LuaCharacterApi.hpp"
|
||||
#include "systems/PlayerControllerSystem.hpp"
|
||||
#include "systems/SceneSerializer.hpp"
|
||||
#include "camera/EditorCamera.hpp"
|
||||
@@ -157,6 +159,11 @@ void ImGuiRenderListener::preViewportUpdate(
|
||||
m_editorApp->getCharacterClassSystem()->update(m_deltaTime);
|
||||
m_editorApp->getCharacterClassSystem()->renderDialogs();
|
||||
}
|
||||
|
||||
// Pregnancy system (advances pregnancies, triggers birth)
|
||||
if (m_editorApp && m_editorApp->getPregnancySystem()) {
|
||||
m_editorApp->getPregnancySystem()->update(m_deltaTime);
|
||||
}
|
||||
}
|
||||
|
||||
void ImGuiRenderListener::postViewportUpdate(
|
||||
@@ -452,6 +459,8 @@ void EditorApp::setup()
|
||||
m_characterClassSystem =
|
||||
std::make_unique<CharacterClassSystem>(
|
||||
m_world, this);
|
||||
m_pregnancySystem =
|
||||
std::make_unique<PregnancySystem>(m_world);
|
||||
CharacterClassDatabase::loadFromJson(
|
||||
"character_class.json");
|
||||
|
||||
@@ -527,6 +536,7 @@ void EditorApp::setup()
|
||||
editScene::registerLuaBehaviorTreeApi(L);
|
||||
editScene::registerLuaGameModeApi(L);
|
||||
editScene::registerLuaCharacterClassApi(L);
|
||||
editScene::registerLuaCharacterApi(L);
|
||||
editScene::registerLuaDialogueApi(L);
|
||||
|
||||
// Run late setup: load data.lua and initial scripts.
|
||||
|
||||
@@ -45,6 +45,7 @@ class ActuatorSystem;
|
||||
class EventHandlerSystem;
|
||||
class ItemSystem;
|
||||
class CharacterClassSystem;
|
||||
class PregnancySystem;
|
||||
class EditorApp;
|
||||
|
||||
/**
|
||||
@@ -204,6 +205,10 @@ public:
|
||||
{
|
||||
return m_characterClassSystem.get();
|
||||
}
|
||||
PregnancySystem *getPregnancySystem() const
|
||||
{
|
||||
return m_pregnancySystem.get();
|
||||
}
|
||||
Ogre::ImGuiOverlay *getImGuiOverlay() const
|
||||
{
|
||||
return m_imguiOverlay;
|
||||
@@ -251,6 +256,7 @@ private:
|
||||
std::unique_ptr<EventHandlerSystem> m_eventHandlerSystem;
|
||||
std::unique_ptr<ItemSystem> m_itemSystem;
|
||||
std::unique_ptr<CharacterClassSystem> m_characterClassSystem;
|
||||
std::unique_ptr<PregnancySystem> m_pregnancySystem;
|
||||
|
||||
// Game systems
|
||||
std::unique_ptr<StartupMenuSystem> m_startupMenuSystem;
|
||||
|
||||
@@ -0,0 +1,322 @@
|
||||
#include "LuaCharacterApi.hpp"
|
||||
#include "LuaEntityApi.hpp"
|
||||
#include "../systems/CharacterRegistry.hpp"
|
||||
#include "../components/CharacterIdentity.hpp"
|
||||
#include <OgreLogManager.h>
|
||||
|
||||
namespace editScene
|
||||
{
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helper: get the Flecs world from the Lua registry
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static flecs::world getWorld(lua_State *L)
|
||||
{
|
||||
lua_getfield(L, LUA_REGISTRYINDEX, "EditSceneFlecsWorld");
|
||||
OgreAssert(lua_islightuserdata(L, -1), "Flecs world not registered");
|
||||
flecs::world *world =
|
||||
static_cast<flecs::world *>(lua_touserdata(L, -1));
|
||||
lua_pop(L, 1);
|
||||
return *world;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helper: push uint64_t vector as Lua table
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static void pushUint64Vector(lua_State *L, const std::vector<uint64_t> &vec)
|
||||
{
|
||||
lua_newtable(L);
|
||||
for (size_t i = 0; i < vec.size(); i++) {
|
||||
lua_pushinteger(L, static_cast<lua_Integer>(vec[i]));
|
||||
lua_rawseti(L, -2, static_cast<int>(i + 1));
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Character creation & management
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int luaCharacterCreate(lua_State *L)
|
||||
{
|
||||
const char *firstName = luaL_checkstring(L, 1);
|
||||
const char *lastName = luaL_checkstring(L, 2);
|
||||
const char *templatePath = lua_tostring(L, 3);
|
||||
bool persistent = true;
|
||||
if (lua_gettop(L) >= 4)
|
||||
persistent = lua_toboolean(L, 4) != 0;
|
||||
|
||||
uint64_t id = CharacterRegistry::getSingleton().createCharacter(
|
||||
firstName, lastName, templatePath ? templatePath : "",
|
||||
persistent);
|
||||
lua_pushinteger(L, static_cast<lua_Integer>(id));
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int luaCharacterDelete(lua_State *L)
|
||||
{
|
||||
uint64_t id = static_cast<uint64_t>(luaL_checkinteger(L, 1));
|
||||
CharacterRegistry::getSingleton().deleteCharacter(id);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int luaCharacterFind(lua_State *L)
|
||||
{
|
||||
uint64_t id = static_cast<uint64_t>(luaL_checkinteger(L, 1));
|
||||
const CharacterRegistry::CharacterRecord *c =
|
||||
CharacterRegistry::getSingleton().findCharacter(id);
|
||||
if (!c) {
|
||||
lua_pushnil(L);
|
||||
return 1;
|
||||
}
|
||||
|
||||
lua_newtable(L);
|
||||
lua_pushinteger(L, static_cast<lua_Integer>(c->id));
|
||||
lua_setfield(L, -2, "id");
|
||||
lua_pushstring(L, c->firstName.c_str());
|
||||
lua_setfield(L, -2, "firstName");
|
||||
lua_pushstring(L, c->lastName.c_str());
|
||||
lua_setfield(L, -2, "lastName");
|
||||
lua_pushstring(L, c->className.c_str());
|
||||
lua_setfield(L, -2, "className");
|
||||
lua_pushinteger(L, c->level);
|
||||
lua_setfield(L, -2, "level");
|
||||
lua_pushinteger(L, c->ageYears);
|
||||
lua_setfield(L, -2, "ageYears");
|
||||
lua_pushstring(L, c->inlineSex.c_str());
|
||||
lua_setfield(L, -2, "sex");
|
||||
lua_pushboolean(L, c->persistent ? 1 : 0);
|
||||
lua_setfield(L, -2, "persistent");
|
||||
lua_pushinteger(L,
|
||||
static_cast<lua_Integer>(c->pregnantByFatherId));
|
||||
lua_setfield(L, -2, "pregnantByFatherId");
|
||||
lua_pushnumber(L, c->pregnancyProgress);
|
||||
lua_setfield(L, -2, "pregnancyProgress");
|
||||
lua_pushnumber(L, c->pregnancyMaxProgress);
|
||||
lua_setfield(L, -2, "pregnancyMaxProgress");
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int luaCharacterGetAll(lua_State *L)
|
||||
{
|
||||
const auto &chars =
|
||||
CharacterRegistry::getSingleton().getCharacters();
|
||||
lua_newtable(L);
|
||||
int idx = 1;
|
||||
for (const auto &pair : chars) {
|
||||
lua_pushinteger(L, static_cast<lua_Integer>(pair.first));
|
||||
lua_rawseti(L, -2, idx++);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int luaCharacterSetName(lua_State *L)
|
||||
{
|
||||
uint64_t id = static_cast<uint64_t>(luaL_checkinteger(L, 1));
|
||||
const char *firstName = luaL_checkstring(L, 2);
|
||||
const char *lastName = luaL_checkstring(L, 3);
|
||||
|
||||
CharacterRegistry::CharacterRecord *c =
|
||||
CharacterRegistry::getSingleton().findCharacter(id);
|
||||
if (c) {
|
||||
c->firstName = firstName;
|
||||
c->lastName = lastName;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Spawn / despawn
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int luaCharacterSpawn(lua_State *L)
|
||||
{
|
||||
uint64_t id = static_cast<uint64_t>(luaL_checkinteger(L, 1));
|
||||
flecs::entity e =
|
||||
CharacterRegistry::getSingleton().spawnCharacter(id);
|
||||
if (!e.is_alive()) {
|
||||
lua_pushnil(L);
|
||||
return 1;
|
||||
}
|
||||
int luaId = g_luaEntityIdMap.addEntity(e);
|
||||
lua_pushinteger(L, luaId);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int luaCharacterDespawn(lua_State *L)
|
||||
{
|
||||
uint64_t id = static_cast<uint64_t>(luaL_checkinteger(L, 1));
|
||||
bool ok = CharacterRegistry::getSingleton().despawnCharacter(id);
|
||||
lua_pushboolean(L, ok ? 1 : 0);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int luaCharacterIsSpawned(lua_State *L)
|
||||
{
|
||||
uint64_t id = static_cast<uint64_t>(luaL_checkinteger(L, 1));
|
||||
flecs::entity e =
|
||||
CharacterRegistry::getSingleton().findSpawnedEntity(id);
|
||||
lua_pushboolean(L, e.is_alive() ? 1 : 0);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Pregnancy
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int luaCharacterConceive(lua_State *L)
|
||||
{
|
||||
uint64_t motherId =
|
||||
static_cast<uint64_t>(luaL_checkinteger(L, 1));
|
||||
uint64_t fatherId =
|
||||
static_cast<uint64_t>(luaL_checkinteger(L, 2));
|
||||
bool ok = CharacterRegistry::getSingleton().conceive(motherId,
|
||||
fatherId);
|
||||
lua_pushboolean(L, ok ? 1 : 0);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int luaCharacterAbortPregnancy(lua_State *L)
|
||||
{
|
||||
uint64_t motherId =
|
||||
static_cast<uint64_t>(luaL_checkinteger(L, 1));
|
||||
CharacterRegistry::getSingleton().abortPregnancy(motherId);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int luaCharacterIsPregnant(lua_State *L)
|
||||
{
|
||||
uint64_t motherId =
|
||||
static_cast<uint64_t>(luaL_checkinteger(L, 1));
|
||||
bool pregnant = CharacterRegistry::getSingleton().isPregnant(
|
||||
motherId);
|
||||
lua_pushboolean(L, pregnant ? 1 : 0);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int luaCharacterGetPregnancyProgress(lua_State *L)
|
||||
{
|
||||
uint64_t motherId =
|
||||
static_cast<uint64_t>(luaL_checkinteger(L, 1));
|
||||
const CharacterRegistry::CharacterRecord *c =
|
||||
CharacterRegistry::getSingleton().findCharacter(motherId);
|
||||
if (!c || c->pregnantByFatherId == 0) {
|
||||
lua_pushnil(L);
|
||||
return 1;
|
||||
}
|
||||
lua_newtable(L);
|
||||
lua_pushnumber(L, c->pregnancyProgress);
|
||||
lua_setfield(L, -2, "progress");
|
||||
lua_pushnumber(L, c->pregnancyMaxProgress);
|
||||
lua_setfield(L, -2, "maxProgress");
|
||||
lua_pushnumber(L,
|
||||
c->pregnancyMaxProgress > 0.0f ?
|
||||
c->pregnancyProgress /
|
||||
c->pregnancyMaxProgress :
|
||||
0.0f);
|
||||
lua_setfield(L, -2, "ratio");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Birth & lineage
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int luaCharacterCreateChild(lua_State *L)
|
||||
{
|
||||
uint64_t parentA =
|
||||
static_cast<uint64_t>(luaL_checkinteger(L, 1));
|
||||
uint64_t parentB =
|
||||
static_cast<uint64_t>(luaL_checkinteger(L, 2));
|
||||
uint64_t childId =
|
||||
CharacterRegistry::getSingleton().createChild(parentA,
|
||||
parentB);
|
||||
lua_pushinteger(L, static_cast<lua_Integer>(childId));
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int luaCharacterGetParents(lua_State *L)
|
||||
{
|
||||
uint64_t childId =
|
||||
static_cast<uint64_t>(luaL_checkinteger(L, 1));
|
||||
auto parents = CharacterRegistry::getSingleton().getParents(
|
||||
childId);
|
||||
pushUint64Vector(L, parents);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int luaCharacterGetChildren(lua_State *L)
|
||||
{
|
||||
uint64_t parentId =
|
||||
static_cast<uint64_t>(luaL_checkinteger(L, 1));
|
||||
auto children = CharacterRegistry::getSingleton().getChildren(
|
||||
parentId);
|
||||
pushUint64Vector(L, children);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Registration
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
void registerLuaCharacterApi(lua_State *L)
|
||||
{
|
||||
lua_getglobal(L, "ecs");
|
||||
if (lua_isnil(L, -1)) {
|
||||
lua_pop(L, 1);
|
||||
lua_newtable(L);
|
||||
}
|
||||
|
||||
lua_newtable(L); // ecs.character
|
||||
|
||||
lua_pushcfunction(L, luaCharacterCreate);
|
||||
lua_setfield(L, -2, "create");
|
||||
|
||||
lua_pushcfunction(L, luaCharacterDelete);
|
||||
lua_setfield(L, -2, "delete");
|
||||
|
||||
lua_pushcfunction(L, luaCharacterFind);
|
||||
lua_setfield(L, -2, "find");
|
||||
|
||||
lua_pushcfunction(L, luaCharacterGetAll);
|
||||
lua_setfield(L, -2, "get_all");
|
||||
|
||||
lua_pushcfunction(L, luaCharacterSetName);
|
||||
lua_setfield(L, -2, "set_name");
|
||||
|
||||
lua_pushcfunction(L, luaCharacterSpawn);
|
||||
lua_setfield(L, -2, "spawn");
|
||||
|
||||
lua_pushcfunction(L, luaCharacterDespawn);
|
||||
lua_setfield(L, -2, "despawn");
|
||||
|
||||
lua_pushcfunction(L, luaCharacterIsSpawned);
|
||||
lua_setfield(L, -2, "is_spawned");
|
||||
|
||||
lua_pushcfunction(L, luaCharacterConceive);
|
||||
lua_setfield(L, -2, "conceive");
|
||||
|
||||
lua_pushcfunction(L, luaCharacterAbortPregnancy);
|
||||
lua_setfield(L, -2, "abort_pregnancy");
|
||||
|
||||
lua_pushcfunction(L, luaCharacterIsPregnant);
|
||||
lua_setfield(L, -2, "is_pregnant");
|
||||
|
||||
lua_pushcfunction(L, luaCharacterGetPregnancyProgress);
|
||||
lua_setfield(L, -2, "get_pregnancy_progress");
|
||||
|
||||
lua_pushcfunction(L, luaCharacterCreateChild);
|
||||
lua_setfield(L, -2, "create_child");
|
||||
|
||||
lua_pushcfunction(L, luaCharacterGetParents);
|
||||
lua_setfield(L, -2, "get_parents");
|
||||
|
||||
lua_pushcfunction(L, luaCharacterGetChildren);
|
||||
lua_setfield(L, -2, "get_children");
|
||||
|
||||
lua_setfield(L, -2, "character"); // ecs.character = { ... }
|
||||
lua_setglobal(L, "ecs");
|
||||
}
|
||||
|
||||
} // namespace editScene
|
||||
@@ -0,0 +1,11 @@
|
||||
#ifndef EDITSCENE_LUA_CHARACTER_API_HPP
|
||||
#define EDITSCENE_LUA_CHARACTER_API_HPP
|
||||
#pragma once
|
||||
|
||||
#include <lua.hpp>
|
||||
|
||||
namespace editScene {
|
||||
void registerLuaCharacterApi(lua_State *L);
|
||||
}
|
||||
|
||||
#endif // EDITSCENE_LUA_CHARACTER_API_HPP
|
||||
File diff suppressed because it is too large
Load Diff
@@ -12,6 +12,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include "MarkovNameGenerator.hpp"
|
||||
#include "../components/CharacterSlots.hpp"
|
||||
|
||||
class EditorUISystem;
|
||||
|
||||
@@ -63,6 +64,25 @@ public:
|
||||
std::unordered_map<std::string, int> currentPools;
|
||||
bool levelUpPending = false;
|
||||
|
||||
/* Age in years (runtime progression) */
|
||||
int ageYears = 0;
|
||||
|
||||
/* Inline appearance (used when no prefab file exists) */
|
||||
std::string inlineAge = "adult";
|
||||
std::string inlineSex = "male";
|
||||
int inlineOutfitLevel = 2;
|
||||
std::unordered_map<std::string, SlotSelection> inlineSlotSelections;
|
||||
std::unordered_map<std::string, float> inlineShapeKeyWeights;
|
||||
|
||||
/* Runtime-only characters are NOT saved to character_registry.json */
|
||||
bool persistent = true;
|
||||
|
||||
/* Pregnancy state (progresses whether spawned or not) */
|
||||
uint64_t pregnantByFatherId = 0;
|
||||
float pregnancyProgress = 0.0f;
|
||||
float pregnancyMaxProgress = 0.0f;
|
||||
float basePregnancyDuration = 0.0f; /* 0 = use global */
|
||||
|
||||
/* Tags */
|
||||
std::vector<std::string> tags;
|
||||
|
||||
@@ -123,7 +143,8 @@ public:
|
||||
/* ------------------------------------------------------------------ */
|
||||
uint64_t createCharacter(const std::string &firstName,
|
||||
const std::string &lastName,
|
||||
const std::string &templatePath = "");
|
||||
const std::string &templatePath = "",
|
||||
bool persistent = true);
|
||||
void deleteCharacter(uint64_t id);
|
||||
CharacterRecord *findCharacter(uint64_t id);
|
||||
const CharacterRecord *findCharacter(uint64_t id) const;
|
||||
@@ -221,14 +242,75 @@ public:
|
||||
/* Name Generation */
|
||||
/* ------------------------------------------------------------------ */
|
||||
void learnNamesFromRegistry();
|
||||
std::string generateFirstName() const;
|
||||
std::string generateFirstName(const std::string &sex = "male") const;
|
||||
std::string generateLastName() const;
|
||||
|
||||
const MarkovNameGenerator &getMaleFirstNameGen() const
|
||||
{
|
||||
return m_maleFirstNameGen;
|
||||
}
|
||||
const MarkovNameGenerator &getFemaleFirstNameGen() const
|
||||
{
|
||||
return m_femaleFirstNameGen;
|
||||
}
|
||||
|
||||
std::vector<std::string> &getMaleFirstNameSeeds()
|
||||
{
|
||||
return m_maleFirstNameSeeds;
|
||||
}
|
||||
std::vector<std::string> &getFemaleFirstNameSeeds()
|
||||
{
|
||||
return m_femaleFirstNameSeeds;
|
||||
}
|
||||
std::vector<std::string> &getLastNameSeeds()
|
||||
{
|
||||
return m_lastNameSeeds;
|
||||
}
|
||||
|
||||
/* Family / Birth */
|
||||
/* ------------------------------------------------------------------ */
|
||||
std::vector<uint64_t> getParents(uint64_t childId) const;
|
||||
std::vector<uint64_t> getChildren(uint64_t parentId) const;
|
||||
uint64_t createChild(uint64_t parentA, uint64_t parentB);
|
||||
|
||||
/* Pregnancy helpers */
|
||||
bool conceive(uint64_t motherId, uint64_t fatherId);
|
||||
void abortPregnancy(uint64_t motherId);
|
||||
bool isPregnant(uint64_t motherId) const;
|
||||
|
||||
float getBasePregnancyDuration() const
|
||||
{
|
||||
return m_basePregnancyDuration;
|
||||
}
|
||||
void setBasePregnancyDuration(float v)
|
||||
{
|
||||
m_basePregnancyDuration = v;
|
||||
}
|
||||
float getPregnancyTimeScale() const
|
||||
{
|
||||
return m_pregnancyTimeScale;
|
||||
}
|
||||
void setPregnancyTimeScale(float v)
|
||||
{
|
||||
m_pregnancyTimeScale = v;
|
||||
}
|
||||
|
||||
std::vector<std::string> &getBirthRandomizableShapeKeys()
|
||||
{
|
||||
return m_birthRandomizableShapeKeys;
|
||||
}
|
||||
std::vector<std::string> &getBirthExcludedShapeKeys()
|
||||
{
|
||||
return m_birthExcludedShapeKeys;
|
||||
}
|
||||
|
||||
/* Spawn / Save */
|
||||
/* ------------------------------------------------------------------ */
|
||||
flecs::entity findSpawnedEntity(uint64_t id) const;
|
||||
bool despawnCharacter(uint64_t id);
|
||||
flecs::entity spawnCharacter(uint64_t id);
|
||||
flecs::entity spawnInlineCharacter(const CharacterRecord &c,
|
||||
const Ogre::Vector3 &pos);
|
||||
bool savePrefabForCharacter(uint64_t id);
|
||||
|
||||
/**
|
||||
@@ -258,6 +340,7 @@ public:
|
||||
|
||||
private:
|
||||
uint64_t m_nextId = 1;
|
||||
uint64_t m_nextRuntimeId = 1ull << 32;
|
||||
|
||||
std::unordered_map<uint64_t, CharacterRecord> m_characters;
|
||||
std::unordered_map<uint64_t, GroupRecord> m_groups;
|
||||
@@ -274,9 +357,23 @@ private:
|
||||
std::string m_autoSavePath;
|
||||
std::vector<std::string> m_templates;
|
||||
|
||||
MarkovNameGenerator m_firstNameGen;
|
||||
MarkovNameGenerator m_maleFirstNameGen;
|
||||
MarkovNameGenerator m_femaleFirstNameGen;
|
||||
MarkovNameGenerator m_lastNameGen;
|
||||
|
||||
std::vector<std::string> m_maleFirstNameSeeds;
|
||||
std::vector<std::string> m_femaleFirstNameSeeds;
|
||||
std::vector<std::string> m_lastNameSeeds;
|
||||
|
||||
std::vector<std::string> m_birthRandomizableShapeKeys;
|
||||
std::vector<std::string> m_birthExcludedShapeKeys;
|
||||
|
||||
/* Global pregnancy config */
|
||||
float m_basePregnancyDuration = 300.0f;
|
||||
float m_pregnancyTimeScale = 1.0f;
|
||||
|
||||
void rebuildNameGenerators();
|
||||
|
||||
flecs::world *m_world = nullptr;
|
||||
Ogre::SceneManager *m_sceneMgr = nullptr;
|
||||
EditorUISystem *m_uiSystem = nullptr;
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
#include "PregnancySystem.hpp"
|
||||
#include "CharacterRegistry.hpp"
|
||||
#include "EventBus.hpp"
|
||||
#include "../components/EventParams.hpp"
|
||||
#include <OgreLogManager.h>
|
||||
|
||||
PregnancySystem::PregnancySystem(flecs::world &world)
|
||||
: m_world(world)
|
||||
{
|
||||
}
|
||||
|
||||
PregnancySystem::~PregnancySystem() = default;
|
||||
|
||||
void PregnancySystem::update(float deltaTime)
|
||||
{
|
||||
advancePregnancies(deltaTime);
|
||||
checkBirths();
|
||||
}
|
||||
|
||||
void PregnancySystem::advancePregnancies(float dt)
|
||||
{
|
||||
CharacterRegistry ® = CharacterRegistry::getSingleton();
|
||||
float timeScale = reg.getPregnancyTimeScale();
|
||||
if (timeScale <= 0.0f)
|
||||
return;
|
||||
|
||||
for (const auto &pair : reg.getCharacters()) {
|
||||
uint64_t id = pair.first;
|
||||
if (pair.second.pregnantByFatherId == 0)
|
||||
continue;
|
||||
CharacterRegistry::CharacterRecord *c =
|
||||
reg.findCharacter(id);
|
||||
if (c)
|
||||
c->pregnancyProgress += dt * timeScale;
|
||||
}
|
||||
}
|
||||
|
||||
void PregnancySystem::checkBirths()
|
||||
{
|
||||
CharacterRegistry ® = CharacterRegistry::getSingleton();
|
||||
std::vector<uint64_t> toBirth;
|
||||
|
||||
for (const auto &pair : reg.getCharacters()) {
|
||||
const CharacterRegistry::CharacterRecord &c = pair.second;
|
||||
if (c.pregnantByFatherId == 0)
|
||||
continue;
|
||||
if (c.pregnancyMaxProgress > 0.0f &&
|
||||
c.pregnancyProgress >= c.pregnancyMaxProgress)
|
||||
toBirth.push_back(c.id);
|
||||
}
|
||||
|
||||
for (uint64_t motherId : toBirth) {
|
||||
CharacterRegistry::CharacterRecord *mother =
|
||||
reg.findCharacter(motherId);
|
||||
if (!mother)
|
||||
continue;
|
||||
uint64_t fatherId = mother->pregnantByFatherId;
|
||||
|
||||
uint64_t childId = reg.createChild(fatherId, motherId);
|
||||
|
||||
/* Fire birth event */
|
||||
editScene::EventParams params;
|
||||
params.setEntityId("mother", motherId);
|
||||
params.setEntityId("father", fatherId);
|
||||
params.setEntityId("child", childId);
|
||||
EventBus::getInstance().send("birth", params);
|
||||
|
||||
/* Reset pregnancy */
|
||||
mother->pregnantByFatherId = 0;
|
||||
mother->pregnancyProgress = 0.0f;
|
||||
mother->pregnancyMaxProgress = 0.0f;
|
||||
|
||||
Ogre::LogManager::getSingleton().logMessage(
|
||||
"PregnancySystem: birth — mother=" +
|
||||
Ogre::StringConverter::toString(motherId) +
|
||||
" father=" + Ogre::StringConverter::toString(fatherId) +
|
||||
" child=" + Ogre::StringConverter::toString(childId));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
#ifndef EDITSCENE_PREGNANCY_SYSTEM_HPP
|
||||
#define EDITSCENE_PREGNANCY_SYSTEM_HPP
|
||||
#pragma once
|
||||
|
||||
#include <flecs.h>
|
||||
|
||||
class CharacterRegistry;
|
||||
|
||||
/**
|
||||
* System that advances pregnancy progress for all female characters
|
||||
* in the CharacterRegistry and triggers birth when progression completes.
|
||||
*/
|
||||
class PregnancySystem {
|
||||
public:
|
||||
PregnancySystem(flecs::world &world);
|
||||
~PregnancySystem();
|
||||
|
||||
/** Call every frame. Advances pregnancies and triggers births. */
|
||||
void update(float deltaTime);
|
||||
|
||||
private:
|
||||
void advancePregnancies(float dt);
|
||||
void checkBirths();
|
||||
|
||||
flecs::world &m_world;
|
||||
};
|
||||
|
||||
#endif // EDITSCENE_PREGNANCY_SYSTEM_HPP
|
||||
@@ -0,0 +1,526 @@
|
||||
/**
|
||||
* @file character_lua_test.cpp
|
||||
* @brief Standalone test for the Lua Character API.
|
||||
*
|
||||
* Tests runtime character creation, pregnancy, birth, lineage,
|
||||
* spawn/despawn, and birth-event tracking via the ecs.character.*
|
||||
* Lua API.
|
||||
*
|
||||
* Examples included in test comments show how to use each API
|
||||
* from Lua game scripts.
|
||||
*
|
||||
* Build with:
|
||||
* g++ -std=c++17 -I. -I../.. -I../../lua/lua-5.4.8/src \
|
||||
* character_lua_test.cpp \
|
||||
* ../lua/LuaEntityApi.cpp \
|
||||
* ../lua/LuaEventApi.cpp \
|
||||
* ../lua/LuaCharacterApi.cpp \
|
||||
* ../../lua/lua-5.4.8/src/liblua.a \
|
||||
* -o character_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>
|
||||
}
|
||||
|
||||
// Flecs stub for standalone testing
|
||||
namespace flecs
|
||||
{
|
||||
|
||||
struct entity {
|
||||
uint64_t m_id = 0;
|
||||
bool m_valid = false;
|
||||
|
||||
entity()
|
||||
: m_id(0)
|
||||
, m_valid(false)
|
||||
{
|
||||
}
|
||||
explicit entity(uint64_t id)
|
||||
: m_id(id)
|
||||
, m_valid(true)
|
||||
{
|
||||
}
|
||||
|
||||
uint64_t id() const
|
||||
{
|
||||
return m_id;
|
||||
}
|
||||
bool is_valid() const
|
||||
{
|
||||
return m_valid;
|
||||
}
|
||||
bool is_alive() const
|
||||
{
|
||||
return m_valid;
|
||||
}
|
||||
const char *name() const
|
||||
{
|
||||
return "";
|
||||
}
|
||||
void set_name(const char *)
|
||||
{
|
||||
}
|
||||
void destruct()
|
||||
{
|
||||
m_valid = false;
|
||||
}
|
||||
|
||||
entity parent() const
|
||||
{
|
||||
return entity();
|
||||
}
|
||||
|
||||
template <typename Func> void children(Func) const
|
||||
{
|
||||
}
|
||||
|
||||
template <typename T> void add()
|
||||
{
|
||||
}
|
||||
|
||||
template <typename T> bool has() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename T> const T *get() const
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
template <typename T> void set(const T &)
|
||||
{
|
||||
}
|
||||
|
||||
bool operator==(const entity &other) const
|
||||
{
|
||||
return m_id == other.m_id;
|
||||
}
|
||||
};
|
||||
|
||||
using entity_t = uint64_t;
|
||||
|
||||
struct world {
|
||||
entity make_entity()
|
||||
{
|
||||
return entity(nextId++);
|
||||
}
|
||||
entity lookup(const char *)
|
||||
{
|
||||
return entity();
|
||||
}
|
||||
|
||||
static world &get()
|
||||
{
|
||||
static world w;
|
||||
return w;
|
||||
}
|
||||
|
||||
private:
|
||||
uint64_t nextId = 1000;
|
||||
};
|
||||
|
||||
} // namespace flecs
|
||||
|
||||
// Forward declare registration functions
|
||||
namespace editScene
|
||||
{
|
||||
void registerLuaEntityApi(lua_State *L);
|
||||
void registerLuaEventApi(lua_State *L);
|
||||
void registerLuaCharacterApi(lua_State *L);
|
||||
void clearStubCharacters();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 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)
|
||||
|
||||
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: Create persistent and runtime characters
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testCreateCharacters(lua_State *L)
|
||||
{
|
||||
TEST("create persistent and runtime characters");
|
||||
|
||||
/*
|
||||
* Example: Create a persistent roster character
|
||||
* local id = ecs.character.create("Alice", "Smith", "", true)
|
||||
*
|
||||
* Example: Create a runtime-only (temporary) character
|
||||
* local id = ecs.character.create("Bandit", "", "", false)
|
||||
*/
|
||||
|
||||
bool ok = runLua(
|
||||
L,
|
||||
"local p = ecs.character.create('Alice', 'Smith', '', true);"
|
||||
"assert(type(p) == 'number', 'persistent id should be number');"
|
||||
"assert(p > 0, 'persistent id should be positive');"
|
||||
"local r = ecs.character.create('Bandit', '', '', false);"
|
||||
"assert(type(r) == 'number', 'runtime id should be number');"
|
||||
"assert(r > p, 'runtime id should be larger than persistent');"
|
||||
"local c = ecs.character.find(p);"
|
||||
"assert(c ~= nil, 'should find persistent char');"
|
||||
"assert(c.firstName == 'Alice', 'name mismatch');"
|
||||
"assert(c.persistent == true, 'should be persistent');"
|
||||
"local c2 = ecs.character.find(r);"
|
||||
"assert(c2.persistent == false, 'should be runtime');");
|
||||
if (!ok)
|
||||
FAIL("create characters assertion failed");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 2: Character listing and name changes
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testCharacterListingAndNames(lua_State *L)
|
||||
{
|
||||
TEST("list characters and change names");
|
||||
|
||||
/*
|
||||
* Example: List all characters
|
||||
* local all = ecs.character.get_all()
|
||||
* for i, id in ipairs(all) do ... end
|
||||
*
|
||||
* Example: Rename a character
|
||||
* ecs.character.set_name(id, "NewFirst", "NewLast")
|
||||
*/
|
||||
|
||||
bool ok = runLua(
|
||||
L,
|
||||
"ecs.character.create('Bob', 'Jones', '', true);"
|
||||
"ecs.character.create('Carol', 'Dane', '', true);"
|
||||
"local all = ecs.character.get_all();"
|
||||
"assert(#all >= 2, 'should have at least 2 chars');"
|
||||
"local first = all[1];"
|
||||
"ecs.character.set_name(first, 'Robert', 'Jones-Junior');"
|
||||
"local c = ecs.character.find(first);"
|
||||
"assert(c.firstName == 'Robert', 'first name not updated');"
|
||||
"assert(c.lastName == 'Jones-Junior', 'last name not updated');");
|
||||
if (!ok)
|
||||
FAIL("listing/names assertion failed");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 3: Spawn and despawn
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testSpawnDespawn(lua_State *L)
|
||||
{
|
||||
TEST("spawn and despawn characters");
|
||||
|
||||
/*
|
||||
* Example: Spawn a character into the world
|
||||
* local entityId = ecs.character.spawn(charId)
|
||||
* if entityId ~= nil then ... end
|
||||
*
|
||||
* Example: Check if spawned
|
||||
* if ecs.character.is_spawned(charId) then ... end
|
||||
*
|
||||
* Example: Despawn
|
||||
* ecs.character.despawn(charId)
|
||||
*/
|
||||
|
||||
bool ok = runLua(
|
||||
L,
|
||||
"local id = ecs.character.create('Dave', 'Miller', '', true);"
|
||||
"assert(ecs.character.is_spawned(id) == false, 'should not be spawned');"
|
||||
"local eid = ecs.character.spawn(id);"
|
||||
"assert(eid ~= nil, 'spawn should return entity id');"
|
||||
"assert(type(eid) == 'number', 'entity id should be number');"
|
||||
"assert(ecs.character.is_spawned(id) == true, 'should be spawned');"
|
||||
"local ok = ecs.character.despawn(id);"
|
||||
"assert(ok == true, 'despawn should succeed');"
|
||||
"assert(ecs.character.is_spawned(id) == false, 'should not be spawned after despawn');");
|
||||
if (!ok)
|
||||
FAIL("spawn/despawn assertion failed");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 4: Conceive and pregnancy state
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testConceiveAndPregnancy(lua_State *L)
|
||||
{
|
||||
TEST("conceive and query pregnancy state");
|
||||
|
||||
/*
|
||||
* Example: Make a character pregnant
|
||||
* local ok = ecs.character.conceive(motherId, fatherId)
|
||||
* if ok then ... end
|
||||
*
|
||||
* Example: Check pregnancy
|
||||
* if ecs.character.is_pregnant(motherId) then ... end
|
||||
*
|
||||
* Example: Get progress
|
||||
* local prog = ecs.character.get_pregnancy_progress(motherId)
|
||||
* print(prog.progress .. " / " .. prog.maxProgress)
|
||||
*/
|
||||
|
||||
bool ok = runLua(
|
||||
L,
|
||||
"local mother = ecs.character.create('Eve', 'Adams', '', true);"
|
||||
"local father = ecs.character.create('Adam', 'Adams', '', true);"
|
||||
"assert(ecs.character.is_pregnant(mother) == false, 'should not be pregnant initially');"
|
||||
"local ok = ecs.character.conceive(mother, father);"
|
||||
"assert(ok == true, 'conceive should succeed');"
|
||||
"assert(ecs.character.is_pregnant(mother) == true, 'should be pregnant');"
|
||||
"local prog = ecs.character.get_pregnancy_progress(mother);"
|
||||
"assert(prog ~= nil, 'progress should not be nil');"
|
||||
"assert(prog.progress == 0, 'initial progress should be 0');"
|
||||
"assert(prog.maxProgress > 0, 'maxProgress should be positive');"
|
||||
"assert(prog.ratio == 0, 'initial ratio should be 0');");
|
||||
if (!ok)
|
||||
FAIL("conceive/pregnancy assertion failed");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 5: Abort pregnancy
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testAbortPregnancy(lua_State *L)
|
||||
{
|
||||
TEST("abort pregnancy");
|
||||
|
||||
/*
|
||||
* Example: Abort a pregnancy
|
||||
* ecs.character.abort_pregnancy(motherId)
|
||||
*/
|
||||
|
||||
bool ok = runLua(
|
||||
L,
|
||||
"local mother = ecs.character.create('Fay', 'Wray', '', true);"
|
||||
"local father = ecs.character.create('King', 'Kong', '', true);"
|
||||
"ecs.character.conceive(mother, father);"
|
||||
"assert(ecs.character.is_pregnant(mother) == true);"
|
||||
"ecs.character.abort_pregnancy(mother);"
|
||||
"assert(ecs.character.is_pregnant(mother) == false, 'should not be pregnant after abort');"
|
||||
"local prog = ecs.character.get_pregnancy_progress(mother);"
|
||||
"assert(prog == nil, 'progress should be nil after abort');");
|
||||
if (!ok)
|
||||
FAIL("abort pregnancy assertion failed");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 6: Create child and lineage
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testCreateChildAndLineage(lua_State *L)
|
||||
{
|
||||
TEST("create child and query lineage");
|
||||
|
||||
/*
|
||||
* Example: Create a child from two parents
|
||||
* local childId = ecs.character.create_child(fatherId, motherId)
|
||||
*
|
||||
* Example: Get parents of a child
|
||||
* local parents = ecs.character.get_parents(childId)
|
||||
*
|
||||
* Example: Get children of a parent
|
||||
* local kids = ecs.character.get_children(parentId)
|
||||
*/
|
||||
|
||||
bool ok = runLua(
|
||||
L,
|
||||
"local dad = ecs.character.create('Homer', 'Simpson', '', true);"
|
||||
"local mom = ecs.character.create('Marge', 'Simpson', '', true);"
|
||||
"local child = ecs.character.create_child(dad, mom);"
|
||||
"assert(type(child) == 'number', 'child id should be number');"
|
||||
"local parents = ecs.character.get_parents(child);"
|
||||
"assert(#parents == 2, 'should have 2 parents');"
|
||||
"local dadKids = ecs.character.get_children(dad);"
|
||||
"assert(#dadKids == 1, 'dad should have 1 child');"
|
||||
"assert(dadKids[1] == child, 'dad child should match');"
|
||||
"local momKids = ecs.character.get_children(mom);"
|
||||
"assert(#momKids == 1, 'mom should have 1 child');"
|
||||
"assert(momKids[1] == child, 'mom child should match');");
|
||||
if (!ok)
|
||||
FAIL("child/lineage assertion failed");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 7: Birth event tracking via EventBus
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testBirthEvent(lua_State *L)
|
||||
{
|
||||
TEST("subscribe to birth event and receive it");
|
||||
|
||||
/*
|
||||
* Example: Listen for birth events
|
||||
* ecs.subscribe_event('birth', function(event, params)
|
||||
* print('Birth! Mother=' .. params.mother)
|
||||
* print('Father=' .. params.father)
|
||||
* print('Child=' .. params.child)
|
||||
* end)
|
||||
*
|
||||
* Example: Manually send a birth event (for scripted births)
|
||||
* ecs.send_event('birth', { mother = momId, father = dadId, child = babyId })
|
||||
*/
|
||||
|
||||
bool ok = runLua(
|
||||
L,
|
||||
"local birthData = nil;"
|
||||
"local sub = ecs.subscribe_event('birth', function(event, params)\n"
|
||||
" birthData = params;\n"
|
||||
"end);"
|
||||
"local mom = ecs.character.create('Sarah', 'Connor', '', true);"
|
||||
"local dad = ecs.character.create('Kyle', 'Reese', '', true);"
|
||||
"local baby = ecs.character.create_child(dad, mom);"
|
||||
"ecs.send_event('birth', { mother = mom, father = dad, child = baby });"
|
||||
"assert(birthData ~= nil, 'birth event should have been received');"
|
||||
"assert(birthData.mother == mom, 'mother id mismatch');"
|
||||
"assert(birthData.father == dad, 'father id mismatch');"
|
||||
"assert(birthData.child == baby, 'child id mismatch');");
|
||||
if (!ok)
|
||||
FAIL("birth event assertion failed");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 8: Delete character
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testDeleteCharacter(lua_State *L)
|
||||
{
|
||||
TEST("delete character");
|
||||
|
||||
/*
|
||||
* Example: Delete a character permanently
|
||||
* ecs.character.delete(charId)
|
||||
*/
|
||||
|
||||
bool ok = runLua(
|
||||
L,
|
||||
"local id = ecs.character.create('Goner', 'Dead', '', true);"
|
||||
"assert(ecs.character.find(id) ~= nil, 'should exist');"
|
||||
"ecs.character.delete(id);"
|
||||
"assert(ecs.character.find(id) == nil, 'should not exist after delete');");
|
||||
if (!ok)
|
||||
FAIL("delete character assertion failed");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Main
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
int main()
|
||||
{
|
||||
printf("Character Lua API Tests\n");
|
||||
printf("========================\n\n");
|
||||
|
||||
lua_State *L = luaL_newstate();
|
||||
if (!L) {
|
||||
fprintf(stderr, "Failed to create Lua state\n");
|
||||
return 1;
|
||||
}
|
||||
luaL_openlibs(L);
|
||||
|
||||
// Register APIs
|
||||
editScene::registerLuaEntityApi(L);
|
||||
editScene::registerLuaEventApi(L);
|
||||
editScene::registerLuaCharacterApi(L);
|
||||
|
||||
// Run tests (clear stub state between each for isolation)
|
||||
int failures = 0;
|
||||
|
||||
editScene::clearStubCharacters();
|
||||
failures += testCreateCharacters(L);
|
||||
|
||||
editScene::clearStubCharacters();
|
||||
failures += testCharacterListingAndNames(L);
|
||||
|
||||
editScene::clearStubCharacters();
|
||||
failures += testSpawnDespawn(L);
|
||||
|
||||
editScene::clearStubCharacters();
|
||||
failures += testConceiveAndPregnancy(L);
|
||||
|
||||
editScene::clearStubCharacters();
|
||||
failures += testAbortPregnancy(L);
|
||||
|
||||
editScene::clearStubCharacters();
|
||||
failures += testCreateChildAndLineage(L);
|
||||
|
||||
editScene::clearStubCharacters();
|
||||
failures += testBirthEvent(L);
|
||||
|
||||
editScene::clearStubCharacters();
|
||||
failures += testDeleteCharacter(L);
|
||||
|
||||
lua_close(L);
|
||||
|
||||
printf("\nResults: %d/%d passed, %d failed\n", passCount, testCount,
|
||||
failures);
|
||||
|
||||
return failures > 0 ? 1 : 0;
|
||||
}
|
||||
@@ -224,6 +224,324 @@ void registerLuaDialogueApi(lua_State *L)
|
||||
|
||||
} // namespace editScene
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Stub: LuaCharacterApi
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
namespace editScene
|
||||
{
|
||||
|
||||
struct StubCharacter {
|
||||
uint64_t id = 0;
|
||||
std::string firstName;
|
||||
std::string lastName;
|
||||
std::string sex = "male";
|
||||
bool persistent = true;
|
||||
uint64_t pregnantByFatherId = 0;
|
||||
float pregnancyProgress = 0.0f;
|
||||
float pregnancyMaxProgress = 0.0f;
|
||||
bool spawned = false;
|
||||
int entityId = -1;
|
||||
};
|
||||
|
||||
static std::unordered_map<uint64_t, StubCharacter> s_stubCharacters;
|
||||
static std::unordered_map<uint64_t, std::vector<uint64_t> > s_stubParents;
|
||||
static std::unordered_map<uint64_t, std::vector<uint64_t> > s_stubChildren;
|
||||
static uint64_t s_stubNextCharId = 1;
|
||||
static int s_stubNextEntityId = 5000;
|
||||
|
||||
void clearStubCharacters()
|
||||
{
|
||||
s_stubCharacters.clear();
|
||||
s_stubParents.clear();
|
||||
s_stubChildren.clear();
|
||||
s_stubNextCharId = 1;
|
||||
s_stubNextEntityId = 5000;
|
||||
}
|
||||
|
||||
void registerLuaCharacterApi(lua_State *L)
|
||||
{
|
||||
lua_getglobal(L, "ecs");
|
||||
if (lua_isnil(L, -1)) {
|
||||
lua_pop(L, 1);
|
||||
lua_newtable(L);
|
||||
}
|
||||
|
||||
lua_newtable(L); // ecs.character
|
||||
|
||||
// create(firstName, lastName, templatePath, persistent)
|
||||
lua_pushcfunction(L, [](lua_State *L) -> int {
|
||||
const char *fn = lua_tostring(L, 1);
|
||||
const char *ln = lua_tostring(L, 2);
|
||||
bool persistent = true;
|
||||
if (lua_gettop(L) >= 4)
|
||||
persistent = lua_toboolean(L, 4) != 0;
|
||||
uint64_t id = s_stubNextCharId++;
|
||||
StubCharacter c;
|
||||
c.id = id;
|
||||
c.firstName = fn ? fn : "";
|
||||
c.lastName = ln ? ln : "";
|
||||
c.persistent = persistent;
|
||||
s_stubCharacters[id] = c;
|
||||
lua_pushinteger(L, static_cast<lua_Integer>(id));
|
||||
return 1;
|
||||
});
|
||||
lua_setfield(L, -2, "create");
|
||||
|
||||
// delete(id)
|
||||
lua_pushcfunction(L, [](lua_State *L) -> int {
|
||||
uint64_t id = static_cast<uint64_t>(lua_tointeger(L, 1));
|
||||
s_stubCharacters.erase(id);
|
||||
return 0;
|
||||
});
|
||||
lua_setfield(L, -2, "delete");
|
||||
|
||||
// find(id)
|
||||
lua_pushcfunction(L, [](lua_State *L) -> int {
|
||||
uint64_t id = static_cast<uint64_t>(lua_tointeger(L, 1));
|
||||
auto it = s_stubCharacters.find(id);
|
||||
if (it == s_stubCharacters.end()) {
|
||||
lua_pushnil(L);
|
||||
return 1;
|
||||
}
|
||||
lua_newtable(L);
|
||||
lua_pushinteger(L, static_cast<lua_Integer>(it->second.id));
|
||||
lua_setfield(L, -2, "id");
|
||||
lua_pushstring(L, it->second.firstName.c_str());
|
||||
lua_setfield(L, -2, "firstName");
|
||||
lua_pushstring(L, it->second.lastName.c_str());
|
||||
lua_setfield(L, -2, "lastName");
|
||||
lua_pushstring(L, it->second.sex.c_str());
|
||||
lua_setfield(L, -2, "sex");
|
||||
lua_pushboolean(L, it->second.persistent ? 1 : 0);
|
||||
lua_setfield(L, -2, "persistent");
|
||||
lua_pushinteger(L,
|
||||
static_cast<lua_Integer>(
|
||||
it->second.pregnantByFatherId));
|
||||
lua_setfield(L, -2, "pregnantByFatherId");
|
||||
lua_pushnumber(L, it->second.pregnancyProgress);
|
||||
lua_setfield(L, -2, "pregnancyProgress");
|
||||
lua_pushnumber(L, it->second.pregnancyMaxProgress);
|
||||
lua_setfield(L, -2, "pregnancyMaxProgress");
|
||||
return 1;
|
||||
});
|
||||
lua_setfield(L, -2, "find");
|
||||
|
||||
// get_all()
|
||||
lua_pushcfunction(L, [](lua_State *L) -> int {
|
||||
lua_newtable(L);
|
||||
int idx = 1;
|
||||
for (auto &pair : s_stubCharacters) {
|
||||
lua_pushinteger(L,
|
||||
static_cast<lua_Integer>(pair.first));
|
||||
lua_rawseti(L, -2, idx++);
|
||||
}
|
||||
return 1;
|
||||
});
|
||||
lua_setfield(L, -2, "get_all");
|
||||
|
||||
// set_name(id, firstName, lastName)
|
||||
lua_pushcfunction(L, [](lua_State *L) -> int {
|
||||
uint64_t id = static_cast<uint64_t>(lua_tointeger(L, 1));
|
||||
auto it = s_stubCharacters.find(id);
|
||||
if (it != s_stubCharacters.end()) {
|
||||
const char *fn = lua_tostring(L, 2);
|
||||
const char *ln = lua_tostring(L, 3);
|
||||
if (fn)
|
||||
it->second.firstName = fn;
|
||||
if (ln)
|
||||
it->second.lastName = ln;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
lua_setfield(L, -2, "set_name");
|
||||
|
||||
// spawn(id)
|
||||
lua_pushcfunction(L, [](lua_State *L) -> int {
|
||||
uint64_t id = static_cast<uint64_t>(lua_tointeger(L, 1));
|
||||
auto it = s_stubCharacters.find(id);
|
||||
if (it == s_stubCharacters.end()) {
|
||||
lua_pushnil(L);
|
||||
return 1;
|
||||
}
|
||||
it->second.spawned = true;
|
||||
it->second.entityId = s_stubNextEntityId++;
|
||||
lua_pushinteger(L, it->second.entityId);
|
||||
return 1;
|
||||
});
|
||||
lua_setfield(L, -2, "spawn");
|
||||
|
||||
// despawn(id)
|
||||
lua_pushcfunction(L, [](lua_State *L) -> int {
|
||||
uint64_t id = static_cast<uint64_t>(lua_tointeger(L, 1));
|
||||
auto it = s_stubCharacters.find(id);
|
||||
if (it != s_stubCharacters.end()) {
|
||||
it->second.spawned = false;
|
||||
lua_pushboolean(L, 1);
|
||||
} else {
|
||||
lua_pushboolean(L, 0);
|
||||
}
|
||||
return 1;
|
||||
});
|
||||
lua_setfield(L, -2, "despawn");
|
||||
|
||||
// is_spawned(id)
|
||||
lua_pushcfunction(L, [](lua_State *L) -> int {
|
||||
uint64_t id = static_cast<uint64_t>(lua_tointeger(L, 1));
|
||||
auto it = s_stubCharacters.find(id);
|
||||
lua_pushboolean(L,
|
||||
(it != s_stubCharacters.end() &&
|
||||
it->second.spawned) ?
|
||||
1 :
|
||||
0);
|
||||
return 1;
|
||||
});
|
||||
lua_setfield(L, -2, "is_spawned");
|
||||
|
||||
// conceive(motherId, fatherId)
|
||||
lua_pushcfunction(L, [](lua_State *L) -> int {
|
||||
uint64_t motherId =
|
||||
static_cast<uint64_t>(lua_tointeger(L, 1));
|
||||
uint64_t fatherId =
|
||||
static_cast<uint64_t>(lua_tointeger(L, 2));
|
||||
auto it = s_stubCharacters.find(motherId);
|
||||
if (it == s_stubCharacters.end()) {
|
||||
lua_pushboolean(L, 0);
|
||||
return 1;
|
||||
}
|
||||
it->second.pregnantByFatherId = fatherId;
|
||||
it->second.pregnancyProgress = 0.0f;
|
||||
it->second.pregnancyMaxProgress = 300.0f;
|
||||
lua_pushboolean(L, 1);
|
||||
return 1;
|
||||
});
|
||||
lua_setfield(L, -2, "conceive");
|
||||
|
||||
// abort_pregnancy(motherId)
|
||||
lua_pushcfunction(L, [](lua_State *L) -> int {
|
||||
uint64_t motherId =
|
||||
static_cast<uint64_t>(lua_tointeger(L, 1));
|
||||
auto it = s_stubCharacters.find(motherId);
|
||||
if (it != s_stubCharacters.end()) {
|
||||
it->second.pregnantByFatherId = 0;
|
||||
it->second.pregnancyProgress = 0.0f;
|
||||
it->second.pregnancyMaxProgress = 0.0f;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
lua_setfield(L, -2, "abort_pregnancy");
|
||||
|
||||
// is_pregnant(motherId)
|
||||
lua_pushcfunction(L, [](lua_State *L) -> int {
|
||||
uint64_t motherId =
|
||||
static_cast<uint64_t>(lua_tointeger(L, 1));
|
||||
auto it = s_stubCharacters.find(motherId);
|
||||
lua_pushboolean(L,
|
||||
(it != s_stubCharacters.end() &&
|
||||
it->second.pregnantByFatherId != 0) ?
|
||||
1 :
|
||||
0);
|
||||
return 1;
|
||||
});
|
||||
lua_setfield(L, -2, "is_pregnant");
|
||||
|
||||
// get_pregnancy_progress(motherId)
|
||||
lua_pushcfunction(L, [](lua_State *L) -> int {
|
||||
uint64_t motherId =
|
||||
static_cast<uint64_t>(lua_tointeger(L, 1));
|
||||
auto it = s_stubCharacters.find(motherId);
|
||||
if (it == s_stubCharacters.end() ||
|
||||
it->second.pregnantByFatherId == 0) {
|
||||
lua_pushnil(L);
|
||||
return 1;
|
||||
}
|
||||
lua_newtable(L);
|
||||
lua_pushnumber(L, it->second.pregnancyProgress);
|
||||
lua_setfield(L, -2, "progress");
|
||||
lua_pushnumber(L, it->second.pregnancyMaxProgress);
|
||||
lua_setfield(L, -2, "maxProgress");
|
||||
lua_pushnumber(L,
|
||||
it->second.pregnancyMaxProgress > 0.0f ?
|
||||
it->second.pregnancyProgress /
|
||||
it->second
|
||||
.pregnancyMaxProgress :
|
||||
0.0f);
|
||||
lua_setfield(L, -2, "ratio");
|
||||
return 1;
|
||||
});
|
||||
lua_setfield(L, -2, "get_pregnancy_progress");
|
||||
|
||||
// create_child(parentA, parentB)
|
||||
lua_pushcfunction(L, [](lua_State *L) -> int {
|
||||
uint64_t parentA =
|
||||
static_cast<uint64_t>(lua_tointeger(L, 1));
|
||||
uint64_t parentB =
|
||||
static_cast<uint64_t>(lua_tointeger(L, 2));
|
||||
uint64_t childId = s_stubNextCharId++;
|
||||
StubCharacter c;
|
||||
c.id = childId;
|
||||
c.firstName = "Baby";
|
||||
c.lastName = "Smith";
|
||||
c.sex = "female";
|
||||
c.persistent = false;
|
||||
s_stubCharacters[childId] = c;
|
||||
auto &parents = s_stubParents[childId];
|
||||
parents.clear();
|
||||
parents.push_back(parentA);
|
||||
parents.push_back(parentB);
|
||||
s_stubChildren[parentA].push_back(childId);
|
||||
s_stubChildren[parentB].push_back(childId);
|
||||
lua_pushinteger(L, static_cast<lua_Integer>(childId));
|
||||
return 1;
|
||||
});
|
||||
lua_setfield(L, -2, "create_child");
|
||||
|
||||
// get_parents(childId)
|
||||
lua_pushcfunction(L, [](lua_State *L) -> int {
|
||||
uint64_t childId =
|
||||
static_cast<uint64_t>(lua_tointeger(L, 1));
|
||||
lua_newtable(L);
|
||||
auto it = s_stubParents.find(childId);
|
||||
if (it != s_stubParents.end()) {
|
||||
for (size_t i = 0; i < it->second.size(); i++) {
|
||||
lua_pushinteger(
|
||||
L,
|
||||
static_cast<lua_Integer>(
|
||||
it->second[i]));
|
||||
lua_rawseti(L, -2,
|
||||
static_cast<int>(i + 1));
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
});
|
||||
lua_setfield(L, -2, "get_parents");
|
||||
|
||||
// get_children(parentId)
|
||||
lua_pushcfunction(L, [](lua_State *L) -> int {
|
||||
uint64_t parentId =
|
||||
static_cast<uint64_t>(lua_tointeger(L, 1));
|
||||
lua_newtable(L);
|
||||
auto it = s_stubChildren.find(parentId);
|
||||
if (it != s_stubChildren.end()) {
|
||||
for (size_t i = 0; i < it->second.size(); i++) {
|
||||
lua_pushinteger(
|
||||
L,
|
||||
static_cast<lua_Integer>(
|
||||
it->second[i]));
|
||||
lua_rawseti(L, -2,
|
||||
static_cast<int>(i + 1));
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
});
|
||||
lua_setfield(L, -2, "get_children");
|
||||
|
||||
lua_setfield(L, -2, "character"); // ecs.character = { ... }
|
||||
lua_setglobal(L, "ecs");
|
||||
}
|
||||
|
||||
} // namespace editScene
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Stub: LuaEntityApi
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user