Updated APIs and tests
This commit is contained in:
@@ -38,6 +38,7 @@ set(EDITSCENE_SOURCES
|
||||
systems/PauseMenuSystem.cpp
|
||||
systems/ItemRegistry.cpp
|
||||
systems/ContainerStateRegistry.cpp
|
||||
systems/ItemStateRegistry.cpp
|
||||
systems/PlayerControllerSystem.cpp
|
||||
systems/CharacterSlotSystem.cpp
|
||||
systems/CharacterRegistry.cpp
|
||||
@@ -213,6 +214,7 @@ set(EDITSCENE_HEADERS
|
||||
systems/PauseMenuSystem.hpp
|
||||
systems/ItemRegistry.hpp
|
||||
systems/ContainerStateRegistry.hpp
|
||||
systems/ItemStateRegistry.hpp
|
||||
systems/PlayerControllerSystem.hpp
|
||||
systems/EditorUISystem.hpp
|
||||
systems/CellGridSystem.hpp
|
||||
@@ -533,6 +535,38 @@ target_include_directories(character_lua_test PRIVATE
|
||||
${CMAKE_SOURCE_DIR}/src/lua/lua-5.4.8/src
|
||||
)
|
||||
|
||||
# Test: Dialogue Lua API
|
||||
add_executable(dialogue_lua_test
|
||||
tests/dialogue_lua_test.cpp
|
||||
tests/lua_test_stubs.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(dialogue_lua_test
|
||||
lua
|
||||
)
|
||||
|
||||
target_include_directories(dialogue_lua_test PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/tests
|
||||
${CMAKE_SOURCE_DIR}/src/lua/lua-5.4.8/src
|
||||
)
|
||||
|
||||
# Test: Character Class Lua API
|
||||
add_executable(character_class_lua_test
|
||||
tests/character_class_lua_test.cpp
|
||||
tests/lua_test_stubs.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(character_class_lua_test
|
||||
lua
|
||||
)
|
||||
|
||||
target_include_directories(character_class_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"
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
#include "systems/DialogueSystem.hpp"
|
||||
#include "systems/ItemRegistry.hpp"
|
||||
#include "systems/ContainerStateRegistry.hpp"
|
||||
#include "systems/ItemStateRegistry.hpp"
|
||||
#include "systems/CharacterClassSystem.hpp"
|
||||
#include "systems/PregnancySystem.hpp"
|
||||
#include "components/CharacterClassDatabase.hpp"
|
||||
@@ -469,6 +470,8 @@ void EditorApp::setup()
|
||||
PauseMenuSystem::getInstance().init(this);
|
||||
static ItemRegistry s_itemRegistry;
|
||||
ItemRegistry::getSingleton().initialize();
|
||||
ItemStateRegistry::getInstance().loadFromFile(
|
||||
"item_state.json");
|
||||
ContainerStateRegistry::getInstance().loadFromFile(
|
||||
"container_state.json");
|
||||
|
||||
|
||||
@@ -8,9 +8,14 @@
|
||||
/**
|
||||
* Item reference component.
|
||||
*
|
||||
* Attached to a world entity that represents a pickable item.
|
||||
* Attached to a world entity that represents a pickable / interactable item.
|
||||
* All item properties (name, type, weight, etc.) are stored in
|
||||
* the ItemRegistry singleton and looked up by itemId.
|
||||
*
|
||||
* action: Optional GOAP action name to execute on interact (E key).
|
||||
* If empty, the item is picked up into inventory.
|
||||
* instanceId: Optional unique ID for global state tracking (save/load).
|
||||
* disabled: Set to true when the item has been picked up / consumed.
|
||||
*/
|
||||
struct ItemComponent {
|
||||
// Registry key for this item definition
|
||||
@@ -19,6 +24,15 @@ struct ItemComponent {
|
||||
// Stack size for this world entity
|
||||
int stackSize = 1;
|
||||
|
||||
// Optional action to execute on interact (instead of pickup)
|
||||
Ogre::String action;
|
||||
|
||||
// Unique instance ID for global state tracking
|
||||
Ogre::String instanceId;
|
||||
|
||||
// True when item has been picked up or consumed
|
||||
bool disabled = false;
|
||||
|
||||
ItemComponent() = default;
|
||||
|
||||
explicit ItemComponent(const Ogre::String &id, int stack = 1)
|
||||
|
||||
@@ -0,0 +1,262 @@
|
||||
-- =============================================================================
|
||||
-- Character Class Lua API Examples
|
||||
-- =============================================================================
|
||||
-- This file demonstrates how to use the ecs.character_class API for
|
||||
-- querying character class definitions and managing per-entity character
|
||||
-- stats, skills, needs, and resource pools.
|
||||
--
|
||||
-- The character class system provides:
|
||||
-- - Database queries for class definitions, stats, skills, and needs
|
||||
-- - Per-entity runtime stats (level, XP, stats, skills, needs)
|
||||
-- - Resource pool management (health, mana, stamina, etc.)
|
||||
-- =============================================================================
|
||||
|
||||
-- =============================================================================
|
||||
-- Database Queries
|
||||
-- =============================================================================
|
||||
|
||||
-- List all registered class names
|
||||
local class_names = ecs.character_class.get_class_names()
|
||||
print("Registered classes (" .. #class_names .. "):")
|
||||
for _, name in ipairs(class_names) do
|
||||
print(" - " .. name)
|
||||
end
|
||||
|
||||
-- List all stat names
|
||||
local stat_names = ecs.character_class.get_stat_names()
|
||||
print("Stats (" .. #stat_names .. "):")
|
||||
for _, name in ipairs(stat_names) do
|
||||
print(" - " .. name)
|
||||
end
|
||||
|
||||
-- List all skill names
|
||||
local skill_names = ecs.character_class.get_skill_names()
|
||||
print("Skills (" .. #skill_names .. "):")
|
||||
for _, name in ipairs(skill_names) do
|
||||
print(" - " .. name)
|
||||
end
|
||||
|
||||
-- List all need names
|
||||
local need_names = ecs.character_class.get_need_names()
|
||||
print("Needs (" .. #need_names .. "):")
|
||||
for _, name in ipairs(need_names) do
|
||||
print(" - " .. name)
|
||||
end
|
||||
|
||||
-- =============================================================================
|
||||
-- Querying Class Definitions
|
||||
-- =============================================================================
|
||||
|
||||
-- Get a specific class definition
|
||||
local warrior_class = ecs.character_class.get_class("warrior")
|
||||
if warrior_class then
|
||||
print("Warrior class:")
|
||||
print(" Name: " .. warrior_class.name)
|
||||
print(" Description: " .. warrior_class.description)
|
||||
print(" Primary stats (" .. #warrior_class.primary_stats .. "):")
|
||||
for _, stat in ipairs(warrior_class.primary_stats) do
|
||||
print(" - " .. stat)
|
||||
end
|
||||
else
|
||||
print("Warrior class not found (stub database may be empty)")
|
||||
end
|
||||
|
||||
-- =============================================================================
|
||||
-- Stat Kind Queries
|
||||
-- =============================================================================
|
||||
|
||||
-- Check the kind of a stat (attribute vs resource_pool)
|
||||
local strength_kind = ecs.character_class.get_stat_kind("strength")
|
||||
print("Strength stat kind: " .. strength_kind)
|
||||
|
||||
local health_kind = ecs.character_class.get_stat_kind("health")
|
||||
print("Health stat kind: " .. health_kind)
|
||||
|
||||
-- Unknown stat returns "unknown"
|
||||
local unknown_kind = ecs.character_class.get_stat_kind("nonexistent")
|
||||
print("Unknown stat kind: " .. unknown_kind)
|
||||
|
||||
-- =============================================================================
|
||||
-- Per-Entity Character Stats
|
||||
-- =============================================================================
|
||||
|
||||
-- Create a player entity with character identity
|
||||
local player = ecs.create_entity()
|
||||
ecs.set_entity_name(player, "hero")
|
||||
ecs.set_component(player, "CharacterIdentity", {
|
||||
registryId = 1
|
||||
})
|
||||
|
||||
print("Created player entity (ID: " .. player .. ")")
|
||||
|
||||
-- Get character level
|
||||
local level = ecs.character_class.get_level(player)
|
||||
print("Player level: " .. level)
|
||||
|
||||
-- Get current XP
|
||||
local xp = ecs.character_class.get_xp(player)
|
||||
print("Player XP: " .. xp)
|
||||
|
||||
-- Add XP to the player
|
||||
local xp_added = ecs.character_class.add_xp(player, 150)
|
||||
print("XP added: " .. tostring(xp_added))
|
||||
|
||||
-- Verify XP was added
|
||||
local new_xp = ecs.character_class.get_xp(player)
|
||||
print("Player XP after adding: " .. new_xp)
|
||||
|
||||
-- =============================================================================
|
||||
-- Stat, Skill, and Need Queries
|
||||
-- =============================================================================
|
||||
|
||||
-- Get a specific stat value
|
||||
local strength = ecs.character_class.get_stat(player, "strength")
|
||||
print("Player strength: " .. strength)
|
||||
|
||||
-- Get a specific skill value
|
||||
local swordsmanship = ecs.character_class.get_skill(player, "swordsmanship")
|
||||
print("Player swordsmanship: " .. swordsmanship)
|
||||
|
||||
-- Get a specific need value
|
||||
local hunger = ecs.character_class.get_need(player, "hunger")
|
||||
print("Player hunger: " .. hunger)
|
||||
|
||||
-- Get available attribute/skill points
|
||||
local available_points = ecs.character_class.get_available_points(player)
|
||||
print("Available points: " .. available_points)
|
||||
|
||||
-- =============================================================================
|
||||
-- Setting Needs
|
||||
-- =============================================================================
|
||||
|
||||
-- Set a need value (e.g., after eating)
|
||||
ecs.character_class.set_need(player, "hunger", 0)
|
||||
print("Set hunger to 0")
|
||||
|
||||
-- Verify the change
|
||||
local new_hunger = ecs.character_class.get_need(player, "hunger")
|
||||
print("Hunger after setting: " .. new_hunger)
|
||||
|
||||
-- =============================================================================
|
||||
-- Resource Pool Management
|
||||
-- =============================================================================
|
||||
|
||||
-- Get current pool value (e.g., health)
|
||||
local current_health = ecs.character_class.get_pool_current(player, "health")
|
||||
print("Current health: " .. current_health)
|
||||
|
||||
-- Get maximum pool value
|
||||
local max_health = ecs.character_class.get_pool_max(player, "health")
|
||||
print("Max health: " .. max_health)
|
||||
|
||||
-- Set current pool value (e.g., after taking damage)
|
||||
local pool_set = ecs.character_class.set_pool_current(player, "health", 75)
|
||||
print("Health set to 75: " .. tostring(pool_set))
|
||||
|
||||
-- Verify the change
|
||||
local new_health = ecs.character_class.get_pool_current(player, "health")
|
||||
print("Health after setting: " .. new_health)
|
||||
|
||||
-- =============================================================================
|
||||
-- Practical: Character Level Up
|
||||
-- =============================================================================
|
||||
|
||||
function level_up(entity, xp_gained)
|
||||
print("=== Level Up ===")
|
||||
|
||||
-- Add XP
|
||||
ecs.character_class.add_xp(entity, xp_gained)
|
||||
|
||||
-- Get updated level
|
||||
local new_level = ecs.character_class.get_level(entity)
|
||||
print("New level: " .. new_level)
|
||||
|
||||
-- Get available points
|
||||
local points = ecs.character_class.get_available_points(entity)
|
||||
print("Points to spend: " .. points)
|
||||
|
||||
-- Increase a stat
|
||||
local current_str = ecs.character_class.get_stat(entity, "strength")
|
||||
print("Strength was: " .. current_str)
|
||||
|
||||
-- Note: Stats are typically increased via the character system,
|
||||
-- not directly through this API. This example shows querying.
|
||||
|
||||
-- Restore health on level up
|
||||
local max_hp = ecs.character_class.get_pool_max(entity, "health")
|
||||
ecs.character_class.set_pool_current(entity, "health", max_hp)
|
||||
print("Health restored to max: " .. max_hp)
|
||||
|
||||
print("=== Level Up Complete ===")
|
||||
end
|
||||
|
||||
level_up(player, 500)
|
||||
|
||||
-- =============================================================================
|
||||
-- Practical: Character Status Report
|
||||
-- =============================================================================
|
||||
|
||||
function print_character_status(entity)
|
||||
print("=== Character Status ===")
|
||||
print("Level: " .. ecs.character_class.get_level(entity))
|
||||
print("XP: " .. ecs.character_class.get_xp(entity))
|
||||
print("Available Points: " .. ecs.character_class.get_available_points(entity))
|
||||
|
||||
print("Stats:")
|
||||
for _, name in ipairs(stat_names) do
|
||||
local val = ecs.character_class.get_stat(entity, name)
|
||||
local kind = ecs.character_class.get_stat_kind(name)
|
||||
if kind == "resource_pool" then
|
||||
local current = ecs.character_class.get_pool_current(entity, name)
|
||||
local max = ecs.character_class.get_pool_max(entity, name)
|
||||
print(" " .. name .. ": " .. current .. "/" .. max)
|
||||
else
|
||||
print(" " .. name .. ": " .. val)
|
||||
end
|
||||
end
|
||||
|
||||
print("Skills:")
|
||||
for _, name in ipairs(skill_names) do
|
||||
local val = ecs.character_class.get_skill(entity, name)
|
||||
print(" " .. name .. ": " .. val)
|
||||
end
|
||||
|
||||
print("Needs:")
|
||||
for _, name in ipairs(need_names) do
|
||||
local val = ecs.character_class.get_need(entity, name)
|
||||
print(" " .. name .. ": " .. val)
|
||||
end
|
||||
print("=== End Status ===")
|
||||
end
|
||||
|
||||
print_character_status(player)
|
||||
|
||||
-- =============================================================================
|
||||
-- API Reference
|
||||
-- =============================================================================
|
||||
--
|
||||
-- Database Queries:
|
||||
-- ecs.character_class.get_class_names() -> table of strings
|
||||
-- ecs.character_class.get_stat_names() -> table of strings
|
||||
-- ecs.character_class.get_skill_names() -> table of strings
|
||||
-- ecs.character_class.get_need_names() -> table of strings
|
||||
-- ecs.character_class.get_class(name) -> table or nil
|
||||
-- Returns { name, description, primary_stats }
|
||||
-- ecs.character_class.get_stat_kind(name) -> string
|
||||
-- Returns "attribute", "resource_pool", or "unknown"
|
||||
--
|
||||
-- Per-Entity Runtime API:
|
||||
-- ecs.character_class.get_level(entity_id) -> int
|
||||
-- ecs.character_class.get_xp(entity_id) -> int
|
||||
-- ecs.character_class.add_xp(entity_id, amount) -> bool
|
||||
-- ecs.character_class.get_stat(entity_id, stat_name) -> int
|
||||
-- ecs.character_class.get_skill(entity_id, skill_name) -> int
|
||||
-- ecs.character_class.get_need(entity_id, need_name) -> int
|
||||
-- ecs.character_class.get_available_points(entity_id) -> int
|
||||
-- ecs.character_class.set_need(entity_id, need_name, value) -> nil
|
||||
-- ecs.character_class.get_pool_current(entity_id, pool_name) -> int
|
||||
-- ecs.character_class.get_pool_max(entity_id, pool_name) -> int
|
||||
-- ecs.character_class.set_pool_current(entity_id, pool_name, value) -> bool
|
||||
-- =============================================================================
|
||||
|
||||
print("Character class examples completed successfully!")
|
||||
@@ -0,0 +1,284 @@
|
||||
-- =============================================================================
|
||||
-- Character Lua API Examples
|
||||
-- =============================================================================
|
||||
-- This file demonstrates how to use the ecs.character API for managing
|
||||
-- character records in the CharacterRegistry.
|
||||
--
|
||||
-- The character system provides:
|
||||
-- - Character creation, deletion, and lookup
|
||||
-- - Spawning/despawning characters as ECS entities
|
||||
-- - Pregnancy management (conceive, abort, check progress)
|
||||
-- - Lineage tracking (parents, children, create_child)
|
||||
-- - Name management
|
||||
-- =============================================================================
|
||||
|
||||
-- =============================================================================
|
||||
-- Character Creation
|
||||
-- =============================================================================
|
||||
|
||||
-- Create a character with first name, last name, template path, and persistence
|
||||
local hero = ecs.character.create("Arthur", "Pendragon", "characters/knight", true)
|
||||
print("Created hero character (ID: " .. hero .. ")")
|
||||
|
||||
-- Create a non-persistent character (e.g., a temporary NPC)
|
||||
local npc = ecs.character.create("Merlin", "the Wise", "characters/wizard", false)
|
||||
print("Created NPC character (ID: " .. npc .. ")")
|
||||
|
||||
-- Create a character without a template path
|
||||
local villager = ecs.character.create("John", "Smith", "", true)
|
||||
print("Created villager character (ID: " .. villager .. ")")
|
||||
|
||||
-- =============================================================================
|
||||
-- Character Lookup
|
||||
-- =============================================================================
|
||||
|
||||
-- Find a character by ID
|
||||
local found = ecs.character.find(hero)
|
||||
if found then
|
||||
print("Found character:")
|
||||
print(" ID: " .. found.id)
|
||||
print(" Name: " .. found.firstName .. " " .. found.lastName)
|
||||
print(" Class: " .. found.className)
|
||||
print(" Level: " .. found.level)
|
||||
print(" Age: " .. found.ageYears)
|
||||
print(" Sex: " .. found.sex)
|
||||
print(" Persistent: " .. tostring(found.persistent))
|
||||
print(" Pregnant by: " .. found.pregnantByFatherId)
|
||||
print(" Pregnancy progress: " .. found.pregnancyProgress)
|
||||
print(" Pregnancy max progress: " .. found.pregnancyMaxProgress)
|
||||
end
|
||||
|
||||
-- Find a non-existent character returns nil
|
||||
local missing = ecs.character.find(99999)
|
||||
print("Non-existent character: " .. tostring(missing))
|
||||
|
||||
-- =============================================================================
|
||||
-- Listing All Characters
|
||||
-- =============================================================================
|
||||
|
||||
-- Get all character IDs
|
||||
local all_chars = ecs.character.get_all()
|
||||
print("All characters (" .. #all_chars .. "):")
|
||||
for _, id in ipairs(all_chars) do
|
||||
local c = ecs.character.find(id)
|
||||
if c then
|
||||
print(" [" .. id .. "] " .. c.firstName .. " " .. c.lastName)
|
||||
end
|
||||
end
|
||||
|
||||
-- =============================================================================
|
||||
-- Character Name Management
|
||||
-- =============================================================================
|
||||
|
||||
-- Change a character's name
|
||||
ecs.character.set_name(hero, "Arthur", "the Great")
|
||||
print("Renamed hero")
|
||||
|
||||
-- Verify the name change
|
||||
local renamed = ecs.character.find(hero)
|
||||
print("New name: " .. renamed.firstName .. " " .. renamed.lastName)
|
||||
|
||||
-- =============================================================================
|
||||
-- Spawning and Despawning
|
||||
-- =============================================================================
|
||||
|
||||
-- Spawn a character as an ECS entity (returns entity ID)
|
||||
local entity_id = ecs.character.spawn(hero)
|
||||
if entity_id then
|
||||
print("Spawned hero as entity (ID: " .. entity_id .. ")")
|
||||
else
|
||||
print("Failed to spawn hero")
|
||||
end
|
||||
|
||||
-- Check if a character is spawned
|
||||
local is_spawned = ecs.character.is_spawned(hero)
|
||||
print("Hero is spawned: " .. tostring(is_spawned))
|
||||
|
||||
-- Despawn a character
|
||||
local despawned = ecs.character.despawn(hero)
|
||||
print("Hero despawned: " .. tostring(despawned))
|
||||
|
||||
-- Verify despawn
|
||||
local still_spawned = ecs.character.is_spawned(hero)
|
||||
print("Hero still spawned: " .. tostring(still_spawned))
|
||||
|
||||
-- =============================================================================
|
||||
-- Pregnancy Management
|
||||
-- =============================================================================
|
||||
|
||||
-- Create two characters for pregnancy demo
|
||||
local mother = ecs.character.create("Guinevere", "Pendragon", "", true)
|
||||
local father = ecs.character.create("Lancelot", "du Lac", "", true)
|
||||
|
||||
print("Created mother (ID: " .. mother .. ") and father (ID: " .. father .. ")")
|
||||
|
||||
-- Conceive a child
|
||||
local conceived = ecs.character.conceive(mother, father)
|
||||
print("Conceived: " .. tostring(conceived))
|
||||
|
||||
-- Check if pregnant
|
||||
local pregnant = ecs.character.is_pregnant(mother)
|
||||
print("Is pregnant: " .. tostring(pregnant))
|
||||
|
||||
-- Get pregnancy progress
|
||||
local progress = ecs.character.get_pregnancy_progress(mother)
|
||||
if progress then
|
||||
print("Pregnancy progress:")
|
||||
print(" Progress: " .. progress.progress)
|
||||
print(" Max progress: " .. progress.maxProgress)
|
||||
print(" Ratio: " .. progress.ratio)
|
||||
end
|
||||
|
||||
-- Abort pregnancy
|
||||
ecs.character.abort_pregnancy(mother)
|
||||
print("Pregnancy aborted")
|
||||
|
||||
-- Verify abortion
|
||||
local still_pregnant = ecs.character.is_pregnant(mother)
|
||||
print("Still pregnant: " .. tostring(still_pregnant))
|
||||
|
||||
-- =============================================================================
|
||||
-- Lineage: Creating Children
|
||||
-- =============================================================================
|
||||
|
||||
-- Create a child from two parents
|
||||
local child = ecs.character.create_child(mother, father)
|
||||
print("Created child (ID: " .. child .. ")")
|
||||
|
||||
-- Get the child's info
|
||||
local child_info = ecs.character.find(child)
|
||||
print("Child name: " .. child_info.firstName .. " " .. child_info.lastName)
|
||||
|
||||
-- Get parents of the child
|
||||
local parents = ecs.character.get_parents(child)
|
||||
print("Parents of child (" .. #parents .. "):")
|
||||
for _, parent_id in ipairs(parents) do
|
||||
local p = ecs.character.find(parent_id)
|
||||
if p then
|
||||
print(" [" .. parent_id .. "] " .. p.firstName .. " " .. p.lastName)
|
||||
end
|
||||
end
|
||||
|
||||
-- Get children of a parent
|
||||
local children = ecs.character.get_children(mother)
|
||||
print("Children of mother (" .. #children .. "):")
|
||||
for _, child_id in ipairs(children) do
|
||||
local c = ecs.character.find(child_id)
|
||||
if c then
|
||||
print(" [" .. child_id .. "] " .. c.firstName .. " " .. c.lastName)
|
||||
end
|
||||
end
|
||||
|
||||
-- =============================================================================
|
||||
-- Practical: Family Tree
|
||||
-- =============================================================================
|
||||
|
||||
function print_family_tree(character_id, indent)
|
||||
indent = indent or ""
|
||||
local c = ecs.character.find(character_id)
|
||||
if not c then
|
||||
print(indent .. "[Unknown character]")
|
||||
return
|
||||
end
|
||||
|
||||
print(indent .. c.firstName .. " " .. c.lastName .. " (ID: " .. c.id .. ")")
|
||||
|
||||
-- Print children
|
||||
local kids = ecs.character.get_children(character_id)
|
||||
for _, kid_id in ipairs(kids) do
|
||||
print_family_tree(kid_id, indent .. " ")
|
||||
end
|
||||
end
|
||||
|
||||
print("=== Family Tree ===")
|
||||
print_family_tree(mother)
|
||||
|
||||
-- =============================================================================
|
||||
-- Practical: Character Lifecycle
|
||||
-- =============================================================================
|
||||
|
||||
function character_lifecycle_demo()
|
||||
print("=== Character Lifecycle Demo ===")
|
||||
|
||||
-- 1. Create
|
||||
local sim = ecs.character.create("Sim", "One", "", true)
|
||||
print("1. Created character: " .. sim)
|
||||
|
||||
-- 2. Spawn
|
||||
local eid = ecs.character.spawn(sim)
|
||||
print("2. Spawned as entity: " .. tostring(eid))
|
||||
|
||||
-- 3. Rename
|
||||
ecs.character.set_name(sim, "Simantha", "One")
|
||||
print("3. Renamed to: Simantha One")
|
||||
|
||||
-- 4. Despawn
|
||||
ecs.character.despawn(sim)
|
||||
print("4. Despawned")
|
||||
|
||||
-- 5. Delete
|
||||
ecs.character.delete(sim)
|
||||
print("5. Deleted")
|
||||
|
||||
-- Verify deletion
|
||||
local gone = ecs.character.find(sim)
|
||||
print("6. After deletion: " .. tostring(gone))
|
||||
|
||||
print("=== Lifecycle Demo Complete ===")
|
||||
end
|
||||
|
||||
character_lifecycle_demo()
|
||||
|
||||
-- =============================================================================
|
||||
-- API Reference
|
||||
-- =============================================================================
|
||||
--
|
||||
-- ecs.character.create(firstName, lastName, templatePath, persistent) -> int
|
||||
-- Creates a new character record. Returns the character ID.
|
||||
--
|
||||
-- ecs.character.delete(id) -> nil
|
||||
-- Deletes a character record.
|
||||
--
|
||||
-- ecs.character.find(id) -> table or nil
|
||||
-- Returns character info: id, firstName, lastName, className, level,
|
||||
-- ageYears, sex, persistent, pregnantByFatherId, pregnancyProgress,
|
||||
-- pregnancyMaxProgress.
|
||||
--
|
||||
-- ecs.character.get_all() -> table of ints
|
||||
-- Returns all character IDs.
|
||||
--
|
||||
-- ecs.character.set_name(id, firstName, lastName) -> nil
|
||||
-- Updates a character's name.
|
||||
--
|
||||
-- ecs.character.spawn(id) -> int or nil
|
||||
-- Spawns the character as an ECS entity. Returns the entity ID.
|
||||
--
|
||||
-- ecs.character.despawn(id) -> bool
|
||||
-- Despawns the character's entity.
|
||||
--
|
||||
-- ecs.character.is_spawned(id) -> bool
|
||||
-- Returns true if the character is currently spawned.
|
||||
--
|
||||
-- ecs.character.conceive(motherId, fatherId) -> bool
|
||||
-- Initiates pregnancy for the mother.
|
||||
--
|
||||
-- ecs.character.abort_pregnancy(motherId) -> nil
|
||||
-- Aborts the mother's pregnancy.
|
||||
--
|
||||
-- ecs.character.is_pregnant(motherId) -> bool
|
||||
-- Returns true if the mother is pregnant.
|
||||
--
|
||||
-- ecs.character.get_pregnancy_progress(motherId) -> table or nil
|
||||
-- Returns { progress, maxProgress, ratio } or nil if not pregnant.
|
||||
--
|
||||
-- ecs.character.create_child(parentA, parentB) -> int
|
||||
-- Creates a child character from two parents.
|
||||
--
|
||||
-- ecs.character.get_parents(childId) -> table of ints
|
||||
-- Returns the parent IDs of a child.
|
||||
--
|
||||
-- ecs.character.get_children(parentId) -> table of ints
|
||||
-- Returns the child IDs of a parent.
|
||||
-- =============================================================================
|
||||
|
||||
print("Character examples completed successfully!")
|
||||
@@ -0,0 +1,199 @@
|
||||
-- =============================================================================
|
||||
-- Container State Lua API Examples
|
||||
-- =============================================================================
|
||||
-- This file demonstrates how to manage persistent container states using
|
||||
-- the ecs.container API. Container states are stored in the
|
||||
-- ContainerStateRegistry singleton, separate from entity inventories.
|
||||
--
|
||||
-- Use cases:
|
||||
-- - Persistent chests that remember their contents across sessions
|
||||
-- - Shop inventories that reset on a timer
|
||||
-- - Quest containers that change state based on story progress
|
||||
-- - Lootable containers that can be emptied permanently
|
||||
-- =============================================================================
|
||||
|
||||
-- =============================================================================
|
||||
-- Setting Container State
|
||||
-- =============================================================================
|
||||
|
||||
-- Define the contents of a treasure chest
|
||||
ecs.container.set_state("treasure_chest_001", {
|
||||
{ itemId = "sword_iron", stackSize = 1 },
|
||||
{ itemId = "potion_health", stackSize = 3 },
|
||||
{ itemId = "gold_coin", stackSize = 100 }
|
||||
})
|
||||
|
||||
print("Set state for treasure_chest_001")
|
||||
|
||||
-- Define a shop inventory
|
||||
ecs.container.set_state("blacksmith_shop", {
|
||||
{ itemId = "sword_iron", stackSize = 3 },
|
||||
{ itemId = "bow_wood", stackSize = 2 },
|
||||
{ itemId = "arrow", stackSize = 50 },
|
||||
{ itemId = "potion_health", stackSize = 5 }
|
||||
})
|
||||
|
||||
print("Set state for blacksmith_shop")
|
||||
|
||||
-- =============================================================================
|
||||
-- Getting Container State
|
||||
-- =============================================================================
|
||||
|
||||
-- Retrieve the contents of a container
|
||||
local chest_contents = ecs.container.get_state("treasure_chest_001")
|
||||
print("Treasure chest contents (" .. #chest_contents .. " items):")
|
||||
for i, slot in ipairs(chest_contents) do
|
||||
print(" [" .. i .. "] " .. slot.itemId .. " x" .. slot.stackSize)
|
||||
end
|
||||
|
||||
-- =============================================================================
|
||||
-- Updating Container State
|
||||
-- =============================================================================
|
||||
|
||||
-- Simulate looting the chest: remove items and update state
|
||||
local function loot_container(container_id, item_id, count)
|
||||
local contents = ecs.container.get_state(container_id)
|
||||
local remaining = count
|
||||
|
||||
-- Build new contents minus the looted items
|
||||
local new_contents = {}
|
||||
for _, slot in ipairs(contents) do
|
||||
if slot.itemId == item_id and remaining > 0 then
|
||||
local to_remove = math.min(remaining, slot.stackSize)
|
||||
slot.stackSize = slot.stackSize - to_remove
|
||||
remaining = remaining - to_remove
|
||||
end
|
||||
if slot.stackSize > 0 then
|
||||
table.insert(new_contents, slot)
|
||||
end
|
||||
end
|
||||
|
||||
ecs.container.set_state(container_id, new_contents)
|
||||
print("Looted " .. (count - remaining) .. " " .. item_id .. " from " .. container_id)
|
||||
return count - remaining
|
||||
end
|
||||
|
||||
-- Player loots 2 health potions from the chest
|
||||
loot_container("treasure_chest_001", "potion_health", 2)
|
||||
|
||||
-- Check what's left
|
||||
local remaining = ecs.container.get_state("treasure_chest_001")
|
||||
print("Remaining in treasure_chest_001:")
|
||||
for i, slot in ipairs(remaining) do
|
||||
print(" [" .. i .. "] " .. slot.itemId .. " x" .. slot.stackSize)
|
||||
end
|
||||
|
||||
-- =============================================================================
|
||||
-- Clearing Container State
|
||||
-- =============================================================================
|
||||
|
||||
-- Clear a container's state (e.g., after it's been fully looted)
|
||||
ecs.container.clear_state("treasure_chest_001")
|
||||
print("Cleared state for treasure_chest_001")
|
||||
|
||||
-- Verify it's empty
|
||||
local empty = ecs.container.get_state("treasure_chest_001")
|
||||
print("After clear, contents count: " .. #empty)
|
||||
|
||||
-- =============================================================================
|
||||
-- Practical: Shop Restocking
|
||||
-- =============================================================================
|
||||
|
||||
-- Restock a shop's inventory (reset to initial state)
|
||||
function restock_shop(shop_id)
|
||||
local stock = {
|
||||
blacksmith_shop = {
|
||||
{ itemId = "sword_iron", stackSize = 3 },
|
||||
{ itemId = "bow_wood", stackSize = 2 },
|
||||
{ itemId = "arrow", stackSize = 50 },
|
||||
{ itemId = "potion_health", stackSize = 5 }
|
||||
},
|
||||
alchemist_shop = {
|
||||
{ itemId = "potion_health", stackSize = 10 },
|
||||
{ itemId = "potion_stamina", stackSize = 10 }
|
||||
}
|
||||
}
|
||||
|
||||
if stock[shop_id] then
|
||||
ecs.container.set_state(shop_id, stock[shop_id])
|
||||
print("Restocked " .. shop_id)
|
||||
else
|
||||
print("Unknown shop: " .. shop_id)
|
||||
end
|
||||
end
|
||||
|
||||
-- Simulate a purchase: remove items from shop
|
||||
local function buy_from_shop(shop_id, item_id, count)
|
||||
local contents = ecs.container.get_state(shop_id)
|
||||
local available = 0
|
||||
for _, slot in ipairs(contents) do
|
||||
if slot.itemId == item_id then
|
||||
available = slot.stackSize
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if available >= count then
|
||||
loot_container(shop_id, item_id, count)
|
||||
print("Purchased " .. count .. " " .. item_id .. " from " .. shop_id)
|
||||
return true
|
||||
else
|
||||
print("Not enough stock in " .. shop_id .. " (have " .. available .. ", need " .. count .. ")")
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
-- Player buys from blacksmith
|
||||
buy_from_shop("blacksmith_shop", "sword_iron", 1)
|
||||
buy_from_shop("blacksmith_shop", "arrow", 10)
|
||||
|
||||
-- Restock the shop
|
||||
restock_shop("blacksmith_shop")
|
||||
|
||||
-- =============================================================================
|
||||
-- Practical: Quest Container with Conditional State
|
||||
-- =============================================================================
|
||||
|
||||
-- A quest container that changes based on story progress
|
||||
function setup_quest_container(quest_stage)
|
||||
if quest_stage == "not_started" then
|
||||
ecs.container.set_state("ancient_tomb", {
|
||||
{ itemId = "potion_health", stackSize = 2 },
|
||||
{ itemId = "gold_coin", stackSize = 50 }
|
||||
})
|
||||
elseif quest_stage == "in_progress" then
|
||||
ecs.container.set_state("ancient_tomb", {
|
||||
{ itemId = "potion_health", stackSize = 2 },
|
||||
{ itemId = "gold_coin", stackSize = 50 },
|
||||
{ itemId = "amulet_legendary", stackSize = 1 }
|
||||
})
|
||||
elseif quest_stage == "completed" then
|
||||
ecs.container.clear_state("ancient_tomb")
|
||||
end
|
||||
print("Quest container 'ancient_tomb' set to stage: " .. quest_stage)
|
||||
end
|
||||
|
||||
setup_quest_container("not_started")
|
||||
setup_quest_container("in_progress")
|
||||
setup_quest_container("completed")
|
||||
|
||||
-- =============================================================================
|
||||
-- API Reference
|
||||
-- =============================================================================
|
||||
--
|
||||
-- ecs.container.set_state(container_id, slots_table) -> nil
|
||||
-- Sets the persistent state of a container.
|
||||
-- Each slot: { itemId = "...", stackSize = N }
|
||||
--
|
||||
-- ecs.container.get_state(container_id) -> table of { itemId, stackSize }
|
||||
-- Returns the current state of a container.
|
||||
--
|
||||
-- ecs.container.clear_state(container_id) -> nil
|
||||
-- Clears all items from a container's state.
|
||||
--
|
||||
-- Container state is separate from entity inventories (InventoryComponent).
|
||||
-- Use containers for persistent world objects like chests, shops, and
|
||||
-- quest lootables that need to remember their state across sessions.
|
||||
-- =============================================================================
|
||||
|
||||
print("Container examples completed successfully!")
|
||||
@@ -0,0 +1,211 @@
|
||||
-- =============================================================================
|
||||
-- Dialogue Lua API Examples
|
||||
-- =============================================================================
|
||||
-- This file demonstrates how to use the ecs.dialogue API for managing
|
||||
-- in-game dialogue boxes with text, choices, speaker names, and settings.
|
||||
--
|
||||
-- The dialogue system provides:
|
||||
-- - Show/hide dialogue boxes with text and optional choices
|
||||
-- - Speaker name display
|
||||
-- - Choice selection and progression
|
||||
-- - Settings management (font, opacity, positioning)
|
||||
-- - Settings persistence (save/load to JSON)
|
||||
-- =============================================================================
|
||||
|
||||
-- =============================================================================
|
||||
-- Basic Dialogue Display
|
||||
-- =============================================================================
|
||||
|
||||
-- Show a simple dialogue with text only
|
||||
ecs.dialogue.show("Hello, traveler! Welcome to our village.")
|
||||
|
||||
print("Showed basic dialogue")
|
||||
|
||||
-- Show dialogue with speaker name
|
||||
ecs.dialogue.show("I have a quest for you.", {}, "Elder Marcus")
|
||||
|
||||
print("Showed dialogue with speaker")
|
||||
|
||||
-- =============================================================================
|
||||
-- Dialogue with Choices
|
||||
-- =============================================================================
|
||||
|
||||
-- Show dialogue with multiple choices
|
||||
ecs.dialogue.show("What would you like to do?", {
|
||||
"Ask about the quest",
|
||||
"Browse his wares",
|
||||
"Say goodbye"
|
||||
}, "Shopkeeper")
|
||||
|
||||
print("Showed dialogue with choices")
|
||||
|
||||
-- Select a choice (simulates player clicking option 1)
|
||||
ecs.dialogue.select_choice(1)
|
||||
|
||||
print("Selected choice 1")
|
||||
|
||||
-- =============================================================================
|
||||
-- Hiding Dialogue
|
||||
-- =============================================================================
|
||||
|
||||
-- Hide the current dialogue
|
||||
ecs.dialogue.hide()
|
||||
|
||||
print("Dialogue hidden")
|
||||
|
||||
-- =============================================================================
|
||||
-- Checking Dialogue State
|
||||
-- =============================================================================
|
||||
|
||||
-- Check if dialogue is currently active
|
||||
local active = ecs.dialogue.is_active()
|
||||
print("Dialogue active: " .. tostring(active))
|
||||
|
||||
-- =============================================================================
|
||||
-- Progressing Through Dialogue
|
||||
-- =============================================================================
|
||||
|
||||
-- Show a multi-line narration and progress through it
|
||||
ecs.dialogue.show("The sun sets over the horizon...")
|
||||
ecs.dialogue.progress()
|
||||
|
||||
ecs.dialogue.show("A cool breeze sweeps through the valley...")
|
||||
ecs.dialogue.progress()
|
||||
|
||||
ecs.dialogue.show("You hear footsteps in the distance.")
|
||||
ecs.dialogue.progress()
|
||||
|
||||
print("Progressed through narration")
|
||||
|
||||
-- =============================================================================
|
||||
-- Dialogue Settings Management
|
||||
-- =============================================================================
|
||||
|
||||
-- Get current settings
|
||||
local settings = ecs.dialogue.get_settings()
|
||||
print("Current settings:")
|
||||
print(" Font: " .. settings.font_name)
|
||||
print(" Font size: " .. settings.font_size)
|
||||
print(" Speaker font size: " .. settings.speaker_font_size)
|
||||
print(" Background opacity: " .. settings.background_opacity)
|
||||
print(" Box height fraction: " .. settings.box_height_fraction)
|
||||
print(" Box position fraction: " .. settings.box_position_fraction)
|
||||
|
||||
-- Modify settings
|
||||
ecs.dialogue.set_settings({
|
||||
font_name = "Jupiteroid-Regular.ttf",
|
||||
font_size = 20.0,
|
||||
speaker_font_size = 18.0,
|
||||
background_opacity = 0.85,
|
||||
box_height_fraction = 0.25,
|
||||
box_position_fraction = 0.75
|
||||
})
|
||||
|
||||
print("Updated dialogue settings")
|
||||
|
||||
-- Verify the changes
|
||||
local updated = ecs.dialogue.get_settings()
|
||||
print("Updated font size: " .. updated.font_size)
|
||||
|
||||
-- =============================================================================
|
||||
-- Saving and Loading Settings
|
||||
-- =============================================================================
|
||||
|
||||
-- Save settings to default path (dialogue.json)
|
||||
local saved = ecs.dialogue.save_settings()
|
||||
print("Settings saved: " .. tostring(saved))
|
||||
|
||||
-- Save settings to a custom path
|
||||
local saved_custom = ecs.dialogue.save_settings("my_dialogue_config.json")
|
||||
print("Settings saved to custom path: " .. tostring(saved_custom))
|
||||
|
||||
-- Load settings from default path
|
||||
local loaded = ecs.dialogue.load_settings()
|
||||
print("Settings loaded: " .. tostring(loaded))
|
||||
|
||||
-- Load settings from custom path
|
||||
local loaded_custom = ecs.dialogue.load_settings("my_dialogue_config.json")
|
||||
print("Settings loaded from custom path: " .. tostring(loaded_custom))
|
||||
|
||||
-- =============================================================================
|
||||
-- Practical: Dialogue Sequence with Choices
|
||||
-- =============================================================================
|
||||
|
||||
-- A simple dialogue tree simulation
|
||||
function run_dialogue_tree()
|
||||
-- Node 1: Greeting
|
||||
ecs.dialogue.show("Greetings, adventurer! How can I help you?", {
|
||||
"Tell me about the local area",
|
||||
"I need supplies",
|
||||
"I'm just passing through"
|
||||
}, "Innkeeper")
|
||||
|
||||
-- Simulate player choosing option 1
|
||||
ecs.dialogue.select_choice(1)
|
||||
|
||||
-- Node 2: Response to choice 1
|
||||
ecs.dialogue.show("Ah, you must be new here! This town has a rich history. "
|
||||
.. "To the north lies the ancient forest, and to the east, "
|
||||
.. "the old ruins.", {}, "Innkeeper")
|
||||
|
||||
ecs.dialogue.progress()
|
||||
|
||||
-- Node 3: Offer quest
|
||||
ecs.dialogue.show("If you're looking for adventure, I heard the ruins "
|
||||
.. "hold a legendary treasure. But beware of the traps!", {
|
||||
"I'll check it out!",
|
||||
"Sounds too dangerous"
|
||||
}, "Innkeeper")
|
||||
|
||||
-- Simulate player choosing option 1
|
||||
ecs.dialogue.select_choice(1)
|
||||
|
||||
-- Node 4: Final response
|
||||
ecs.dialogue.show("Excellent! Good luck on your journey, adventurer!", {}, "Innkeeper")
|
||||
ecs.dialogue.progress()
|
||||
|
||||
-- End dialogue
|
||||
ecs.dialogue.hide()
|
||||
|
||||
print("Dialogue tree completed")
|
||||
end
|
||||
|
||||
run_dialogue_tree()
|
||||
|
||||
-- =============================================================================
|
||||
-- API Reference
|
||||
-- =============================================================================
|
||||
--
|
||||
-- ecs.dialogue.show(text, choices?, speaker?)
|
||||
-- text - string, the dialogue text to display
|
||||
-- choices - optional table of strings, player response options
|
||||
-- speaker - optional string, name of the speaking character
|
||||
--
|
||||
-- ecs.dialogue.hide()
|
||||
-- Hides the current dialogue box.
|
||||
--
|
||||
-- ecs.dialogue.is_active() -> bool
|
||||
-- Returns true if a dialogue is currently being displayed.
|
||||
--
|
||||
-- ecs.dialogue.select_choice(index)
|
||||
-- Simulates selecting a choice by its 1-based index.
|
||||
--
|
||||
-- ecs.dialogue.progress()
|
||||
-- Advances to the next line of dialogue (click-to-dismiss).
|
||||
--
|
||||
-- ecs.dialogue.get_settings() -> table
|
||||
-- Returns a table with fields:
|
||||
-- font_name, font_size, speaker_font_size,
|
||||
-- background_opacity, box_height_fraction, box_position_fraction
|
||||
--
|
||||
-- ecs.dialogue.set_settings(settings_table)
|
||||
-- Updates dialogue display settings. Partial tables are accepted.
|
||||
--
|
||||
-- ecs.dialogue.save_settings(path?) -> bool
|
||||
-- Saves current settings to JSON. Default path: "dialogue.json"
|
||||
--
|
||||
-- ecs.dialogue.load_settings(path?) -> bool
|
||||
-- Loads settings from JSON. Default path: "dialogue.json"
|
||||
-- =============================================================================
|
||||
|
||||
print("Dialogue examples completed successfully!")
|
||||
@@ -0,0 +1,198 @@
|
||||
-- =============================================================================
|
||||
-- Inventory Lua API Examples
|
||||
-- =============================================================================
|
||||
-- This file demonstrates how to manage entity inventories using the
|
||||
-- ecs.inventory API. Inventories are stored as InventoryComponent on
|
||||
-- ECS entities.
|
||||
--
|
||||
-- Prerequisites:
|
||||
-- - Items must be registered via ecs.items.register() first
|
||||
-- - Entities must have an InventoryComponent (added via ecs.add_component)
|
||||
-- =============================================================================
|
||||
|
||||
-- =============================================================================
|
||||
-- Setup: Create an entity with an inventory
|
||||
-- =============================================================================
|
||||
|
||||
-- Create a player entity
|
||||
local player = ecs.create_entity()
|
||||
ecs.set_entity_name(player, "player")
|
||||
|
||||
-- Add an InventoryComponent with 20 slots and 50.0 max weight
|
||||
ecs.set_component(player, "Inventory", {
|
||||
maxSlots = 20,
|
||||
maxWeight = 50.0,
|
||||
isContainer = false,
|
||||
containerId = ""
|
||||
})
|
||||
|
||||
print("Created player entity with inventory (ID: " .. player .. ")")
|
||||
|
||||
-- =============================================================================
|
||||
-- Adding Items to Inventory
|
||||
-- =============================================================================
|
||||
|
||||
-- Add 5 health potions
|
||||
local added = ecs.inventory.add(player, "potion_health", 5)
|
||||
print("Added 5 health potions: " .. tostring(added))
|
||||
|
||||
-- Add 3 arrows
|
||||
ecs.inventory.add(player, "arrow", 3)
|
||||
print("Added 3 arrows")
|
||||
|
||||
-- Add 1 iron sword
|
||||
ecs.inventory.add(player, "sword_iron", 1)
|
||||
print("Added 1 iron sword")
|
||||
|
||||
-- Add 50 gold coins
|
||||
ecs.inventory.add(player, "gold_coin", 50)
|
||||
print("Added 50 gold coins")
|
||||
|
||||
-- =============================================================================
|
||||
-- Checking Inventory Contents
|
||||
-- =============================================================================
|
||||
|
||||
-- Check if the player has a specific item
|
||||
if ecs.inventory.has(player, "potion_health") then
|
||||
print("Player has health potions")
|
||||
end
|
||||
|
||||
-- Count how many of a specific item
|
||||
local potion_count = ecs.inventory.count(player, "potion_health")
|
||||
print("Health potion count: " .. potion_count)
|
||||
|
||||
local gold_count = ecs.inventory.count(player, "gold_coin")
|
||||
print("Gold coin count: " .. gold_count)
|
||||
|
||||
-- =============================================================================
|
||||
-- Listing Inventory Slots
|
||||
-- =============================================================================
|
||||
|
||||
-- Get all non-empty slots
|
||||
local slots = ecs.inventory.get_slots(player)
|
||||
print("Inventory slots (" .. #slots .. " non-empty):")
|
||||
for i, slot in ipairs(slots) do
|
||||
print(" [" .. i .. "] " .. slot.itemId .. " x" .. slot.stackSize)
|
||||
end
|
||||
|
||||
-- =============================================================================
|
||||
-- Removing Items from Inventory
|
||||
-- =============================================================================
|
||||
|
||||
-- Remove 2 health potions
|
||||
local removed = ecs.inventory.remove(player, "potion_health", 2)
|
||||
print("Removed " .. removed .. " health potions")
|
||||
|
||||
-- Check remaining count
|
||||
local remaining = ecs.inventory.count(player, "potion_health")
|
||||
print("Remaining health potions: " .. remaining)
|
||||
|
||||
-- =============================================================================
|
||||
-- Setting Inventory Slots Directly
|
||||
-- =============================================================================
|
||||
|
||||
-- Replace the entire inventory contents
|
||||
ecs.inventory.set_slots(player, {
|
||||
{ itemId = "potion_health", stackSize = 3 },
|
||||
{ itemId = "sword_iron", stackSize = 1 },
|
||||
{ itemId = "gold_coin", stackSize = 100 }
|
||||
})
|
||||
|
||||
print("Inventory slots replaced via set_slots")
|
||||
|
||||
-- Verify the new contents
|
||||
local new_slots = ecs.inventory.get_slots(player)
|
||||
print("New inventory slots (" .. #new_slots .. "):")
|
||||
for i, slot in ipairs(new_slots) do
|
||||
print(" [" .. i .. "] " .. slot.itemId .. " x" .. slot.stackSize)
|
||||
end
|
||||
|
||||
-- =============================================================================
|
||||
-- Practical: Gold Management
|
||||
-- =============================================================================
|
||||
|
||||
function add_gold(entity, amount)
|
||||
local current = ecs.inventory.count(entity, "gold_coin")
|
||||
ecs.inventory.add(entity, "gold_coin", amount)
|
||||
print("Gold: " .. current .. " -> " .. (current + amount))
|
||||
end
|
||||
|
||||
function remove_gold(entity, amount)
|
||||
local current = ecs.inventory.count(entity, "gold_coin")
|
||||
if current >= amount then
|
||||
ecs.inventory.remove(entity, "gold_coin", amount)
|
||||
print("Gold: " .. current .. " -> " .. (current - amount))
|
||||
return true
|
||||
else
|
||||
print("Not enough gold! Have " .. current .. ", need " .. amount)
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
add_gold(player, 50)
|
||||
remove_gold(player, 30)
|
||||
remove_gold(player, 200) -- Should fail
|
||||
|
||||
-- =============================================================================
|
||||
-- Practical: Item Transfer Between Entities
|
||||
-- =============================================================================
|
||||
|
||||
-- Create a chest entity
|
||||
local chest = ecs.create_entity()
|
||||
ecs.set_entity_name(chest, "chest_wooden")
|
||||
ecs.set_component(chest, "Inventory", {
|
||||
maxSlots = 10,
|
||||
maxWeight = 100.0,
|
||||
isContainer = true,
|
||||
containerId = "chest_wooden_001"
|
||||
})
|
||||
|
||||
-- Add items to chest
|
||||
ecs.inventory.add(chest, "bow_wood", 1)
|
||||
ecs.inventory.add(chest, "arrow", 20)
|
||||
ecs.inventory.add(chest, "potion_health", 2)
|
||||
|
||||
-- Transfer function: move items from one entity to another
|
||||
function transfer_item(from_entity, to_entity, item_id, count)
|
||||
local available = ecs.inventory.count(from_entity, item_id)
|
||||
if available < count then
|
||||
print("Not enough " .. item_id .. " to transfer (have " .. available .. ")")
|
||||
return false
|
||||
end
|
||||
|
||||
ecs.inventory.remove(from_entity, item_id, count)
|
||||
ecs.inventory.add(to_entity, item_id, count)
|
||||
print("Transferred " .. count .. " " .. item_id .. " from " ..
|
||||
ecs.get_entity_name(from_entity) .. " to " ..
|
||||
ecs.get_entity_name(to_entity))
|
||||
return true
|
||||
end
|
||||
|
||||
-- Transfer arrows from chest to player
|
||||
transfer_item(chest, player, "arrow", 10)
|
||||
|
||||
-- =============================================================================
|
||||
-- API Reference
|
||||
-- =============================================================================
|
||||
--
|
||||
-- ecs.inventory.add(entity_id, item_id, count) -> bool
|
||||
-- Adds items to the entity's inventory. Returns true if successful.
|
||||
--
|
||||
-- ecs.inventory.remove(entity_id, item_id, count) -> int
|
||||
-- Removes items and returns the number actually removed.
|
||||
--
|
||||
-- ecs.inventory.has(entity_id, item_id) -> bool
|
||||
-- Returns true if the entity has at least one of the item.
|
||||
--
|
||||
-- ecs.inventory.count(entity_id, item_id) -> int
|
||||
-- Returns the total count of the item across all slots.
|
||||
--
|
||||
-- ecs.inventory.get_slots(entity_id) -> table of { itemId, stackSize }
|
||||
-- Returns all non-empty inventory slots.
|
||||
--
|
||||
-- ecs.inventory.set_slots(entity_id, slots_table) -> nil
|
||||
-- Replaces the entire inventory with the given slots.
|
||||
-- Each slot: { itemId = "...", stackSize = N }
|
||||
-- =============================================================================
|
||||
|
||||
print("Inventory examples completed successfully!")
|
||||
@@ -0,0 +1,144 @@
|
||||
-- =============================================================================
|
||||
-- Item Registry Lua API Examples
|
||||
-- =============================================================================
|
||||
-- This file demonstrates how to register item definitions using the
|
||||
-- ecs.items API. Run this from data.lua or any other Lua entry point
|
||||
-- to populate the global item registry.
|
||||
--
|
||||
-- The ItemRegistry is a global singleton. Items defined here are
|
||||
-- immediately available for use in inventories, containers, and quests.
|
||||
-- =============================================================================
|
||||
|
||||
-- =============================================================================
|
||||
-- Registering Items
|
||||
-- =============================================================================
|
||||
|
||||
-- Consumables
|
||||
ecs.items.register("potion_health", {
|
||||
itemName = "Health Potion",
|
||||
itemType = "consumable",
|
||||
maxStackSize = 10,
|
||||
weight = 0.5,
|
||||
value = 25,
|
||||
useActionName = "drink_potion",
|
||||
unique = false
|
||||
})
|
||||
|
||||
ecs.items.register("potion_stamina", {
|
||||
itemName = "Stamina Potion",
|
||||
itemType = "consumable",
|
||||
maxStackSize = 10,
|
||||
weight = 0.5,
|
||||
value = 20,
|
||||
useActionName = "drink_potion",
|
||||
unique = false
|
||||
})
|
||||
|
||||
-- Weapons
|
||||
ecs.items.register("sword_iron", {
|
||||
itemName = "Iron Sword",
|
||||
itemType = "weapon",
|
||||
maxStackSize = 1,
|
||||
weight = 3.5,
|
||||
value = 100,
|
||||
useActionName = "equip_weapon",
|
||||
unique = false
|
||||
})
|
||||
|
||||
ecs.items.register("bow_wood", {
|
||||
itemName = "Wooden Bow",
|
||||
itemType = "weapon",
|
||||
maxStackSize = 1,
|
||||
weight = 2.0,
|
||||
value = 75,
|
||||
useActionName = "equip_weapon",
|
||||
unique = false
|
||||
})
|
||||
|
||||
-- Ammo
|
||||
ecs.items.register("arrow", {
|
||||
itemName = "Arrow",
|
||||
itemType = "ammo",
|
||||
maxStackSize = 99,
|
||||
weight = 0.1,
|
||||
value = 2,
|
||||
useActionName = "",
|
||||
unique = false
|
||||
})
|
||||
|
||||
-- Unique quest item
|
||||
ecs.items.register("amulet_legendary", {
|
||||
itemName = "Amulet of the Ancients",
|
||||
itemType = "quest",
|
||||
maxStackSize = 1,
|
||||
weight = 0.2,
|
||||
value = 5000,
|
||||
useActionName = "inspect_amulet",
|
||||
unique = true
|
||||
})
|
||||
|
||||
-- Currency
|
||||
ecs.items.register("gold_coin", {
|
||||
itemName = "Gold Coin",
|
||||
itemType = "currency",
|
||||
maxStackSize = 999,
|
||||
weight = 0.01,
|
||||
value = 1,
|
||||
useActionName = "",
|
||||
unique = false
|
||||
})
|
||||
|
||||
-- =============================================================================
|
||||
-- Querying the Item Registry
|
||||
-- =============================================================================
|
||||
|
||||
-- Find an item by ID:
|
||||
local potion = ecs.items.find("potion_health")
|
||||
if potion then
|
||||
print("Found item: " .. potion.itemName .. " (" .. potion.itemType .. ")")
|
||||
print(" Max stack: " .. potion.maxStackSize)
|
||||
print(" Weight: " .. potion.weight)
|
||||
print(" Value: " .. potion.value)
|
||||
print(" Unique: " .. tostring(potion.unique))
|
||||
end
|
||||
|
||||
-- Check if an item is unique:
|
||||
if ecs.items.is_unique("amulet_legendary") then
|
||||
print("Amulet of the Ancients is a unique item")
|
||||
end
|
||||
|
||||
-- List all registered items:
|
||||
print("Registered items:")
|
||||
local all_items = ecs.items.list()
|
||||
for _, id in ipairs(all_items) do
|
||||
local def = ecs.items.find(id)
|
||||
print(" " .. id .. " - " .. def.itemName .. " (" .. def.itemType .. ")")
|
||||
end
|
||||
|
||||
-- =============================================================================
|
||||
-- Item Definition Fields Reference
|
||||
-- =============================================================================
|
||||
--
|
||||
-- ecs.items.register(itemId, definition)
|
||||
-- itemId - string, unique identifier for the item
|
||||
-- definition - table with fields:
|
||||
-- itemName - string, display name
|
||||
-- itemType - string, category ("consumable", "weapon", "ammo",
|
||||
-- "quest", "currency", "material", "armor", etc.)
|
||||
-- maxStackSize - integer, maximum items per inventory slot
|
||||
-- weight - number, weight per item
|
||||
-- value - integer, base value in gold
|
||||
-- useActionName - string, GOAP action name when used (empty = no action)
|
||||
-- unique - boolean, true if only one instance allowed per character
|
||||
--
|
||||
-- ecs.items.find(itemId) -> table or nil
|
||||
-- Returns the item definition table with all fields above.
|
||||
--
|
||||
-- ecs.items.list() -> table of item ID strings
|
||||
-- Returns all registered item IDs.
|
||||
--
|
||||
-- ecs.items.is_unique(itemId) -> boolean
|
||||
-- Returns true if the item is marked as unique.
|
||||
-- =============================================================================
|
||||
|
||||
print("Item registry examples completed successfully!")
|
||||
@@ -0,0 +1,248 @@
|
||||
-- =============================================================================
|
||||
-- Quest Reward Lua API Examples
|
||||
-- =============================================================================
|
||||
-- This file demonstrates how to use the inventory and container APIs
|
||||
-- together to implement quest reward systems.
|
||||
--
|
||||
-- This combines:
|
||||
-- - ecs.inventory.* for player inventory management
|
||||
-- - ecs.container.* for quest container state
|
||||
-- - ecs.items.* for item registry queries
|
||||
-- - ecs.send_event / ecs.subscribe_event for event-driven quest flow
|
||||
-- =============================================================================
|
||||
|
||||
-- =============================================================================
|
||||
-- Setup: Create player and quest-related entities
|
||||
-- =============================================================================
|
||||
|
||||
-- Create the player entity with an inventory
|
||||
local player = ecs.create_entity()
|
||||
ecs.set_entity_name(player, "player")
|
||||
ecs.set_component(player, "Inventory", {
|
||||
maxSlots = 30,
|
||||
maxWeight = 100.0,
|
||||
isContainer = false,
|
||||
containerId = ""
|
||||
})
|
||||
|
||||
print("Created player entity (ID: " .. player .. ")")
|
||||
|
||||
-- Create a quest giver NPC
|
||||
local quest_giver = ecs.create_entity()
|
||||
ecs.set_entity_name(quest_giver, "quest_giver_elder")
|
||||
ecs.set_component(quest_giver, "Inventory", {
|
||||
maxSlots = 10,
|
||||
maxWeight = 50.0,
|
||||
isContainer = false,
|
||||
containerId = ""
|
||||
})
|
||||
|
||||
print("Created quest giver entity (ID: " .. quest_giver .. ")")
|
||||
|
||||
-- =============================================================================
|
||||
-- Quest Reward Distribution
|
||||
-- =============================================================================
|
||||
|
||||
-- Give the player starting equipment
|
||||
ecs.inventory.add(player, "sword_iron", 1)
|
||||
ecs.inventory.add(player, "potion_health", 3)
|
||||
ecs.inventory.add(player, "gold_coin", 25)
|
||||
|
||||
print("Player starting equipment added")
|
||||
|
||||
-- Give the quest giver some reward items
|
||||
ecs.inventory.add(quest_giver, "gold_coin", 200)
|
||||
ecs.inventory.add(quest_giver, "potion_health", 2)
|
||||
ecs.inventory.add(quest_giver, "amulet_legendary", 1)
|
||||
|
||||
print("Quest giver reward items added")
|
||||
|
||||
-- =============================================================================
|
||||
-- Quest Completion: Reward Player
|
||||
-- =============================================================================
|
||||
|
||||
-- Complete a quest and give the player rewards
|
||||
function complete_quest(quest_name, reward_items, xp_reward)
|
||||
print("=== Quest Completed: " .. quest_name .. " ===")
|
||||
print("XP Reward: " .. xp_reward)
|
||||
|
||||
-- Give each reward item to the player
|
||||
for _, reward in ipairs(reward_items) do
|
||||
local item_def = ecs.items.find(reward.itemId)
|
||||
if item_def then
|
||||
ecs.inventory.add(player, reward.itemId, reward.count)
|
||||
print(" Received: " .. reward.count .. "x " .. item_def.itemName)
|
||||
else
|
||||
print(" WARNING: Unknown item '" .. reward.itemId .. "'")
|
||||
end
|
||||
end
|
||||
|
||||
-- Send a quest completion event
|
||||
ecs.send_event("quest_completed", {
|
||||
quest_name = quest_name,
|
||||
player_id = player,
|
||||
xp_rewarded = xp_reward
|
||||
})
|
||||
|
||||
print("Quest completion event sent")
|
||||
end
|
||||
|
||||
-- Complete "The Lost Artifact" quest
|
||||
complete_quest("The Lost Artifact", {
|
||||
{ itemId = "gold_coin", count = 100 },
|
||||
{ itemId = "potion_health", count = 2 }
|
||||
}, 500)
|
||||
|
||||
-- =============================================================================
|
||||
-- Quest Item Requirement Check
|
||||
-- =============================================================================
|
||||
|
||||
-- Check if the player has the required items for a quest
|
||||
function has_quest_items(quest_requirements)
|
||||
for _, req in ipairs(quest_requirements) do
|
||||
local count = ecs.inventory.count(player, req.itemId)
|
||||
if count < req.count then
|
||||
local def = ecs.items.find(req.itemId)
|
||||
local name = def and def.itemName or req.itemId
|
||||
print(" Missing: " .. (req.count - count) .. " more " .. name)
|
||||
return false
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
-- Remove quest items from the player's inventory (turn in)
|
||||
function remove_quest_items(quest_requirements)
|
||||
for _, req in ipairs(quest_requirements) do
|
||||
ecs.inventory.remove(player, req.itemId, req.count)
|
||||
local def = ecs.items.find(req.itemId)
|
||||
local name = def and def.itemName or req.itemId
|
||||
print(" Removed: " .. req.count .. "x " .. name)
|
||||
end
|
||||
end
|
||||
|
||||
-- Define a quest that requires items
|
||||
local bandit_quest = {
|
||||
name = "Bandit Menace",
|
||||
requirements = {
|
||||
{ itemId = "sword_iron", count = 1 },
|
||||
{ itemId = "potion_health", count = 2 }
|
||||
},
|
||||
rewards = {
|
||||
{ itemId = "gold_coin", count = 150 },
|
||||
{ itemId = "bow_wood", count = 1 }
|
||||
},
|
||||
xp = 750
|
||||
}
|
||||
|
||||
-- Check if player can start the quest
|
||||
print("Checking quest requirements for '" .. bandit_quest.name .. "':")
|
||||
if has_quest_items(bandit_quest.requirements) then
|
||||
print("Player has all required items!")
|
||||
remove_quest_items(bandit_quest.requirements)
|
||||
complete_quest(bandit_quest.name, bandit_quest.rewards, bandit_quest.xp)
|
||||
else
|
||||
print("Player does not meet quest requirements")
|
||||
end
|
||||
|
||||
-- =============================================================================
|
||||
-- Quest Container Loot
|
||||
-- =============================================================================
|
||||
|
||||
-- Set up a quest-related container (e.g., a treasure chest at the quest location)
|
||||
ecs.container.set_state("bandit_hideout_chest", {
|
||||
{ itemId = "gold_coin", stackSize = 200 },
|
||||
{ itemId = "potion_health", stackSize = 2 },
|
||||
{ itemId = "potion_stamina", stackSize = 1 }
|
||||
})
|
||||
|
||||
print("Bandit hideout chest populated with loot")
|
||||
|
||||
-- Player loots the chest
|
||||
local chest_loot = ecs.container.get_state("bandit_hideout_chest")
|
||||
print("Looting bandit hideout chest:")
|
||||
for _, slot in ipairs(chest_loot) do
|
||||
ecs.inventory.add(player, slot.itemId, slot.stackSize)
|
||||
local def = ecs.items.find(slot.itemId)
|
||||
local name = def and def.itemName or slot.itemId
|
||||
print(" Acquired: " .. slot.stackSize .. "x " .. name)
|
||||
end
|
||||
|
||||
-- Clear the chest after looting
|
||||
ecs.container.clear_state("bandit_hideout_chest")
|
||||
print("Chest cleared after looting")
|
||||
|
||||
-- =============================================================================
|
||||
-- Quest Progression with Event Subscriptions
|
||||
-- =============================================================================
|
||||
|
||||
-- Track quest state
|
||||
local quest_state = {
|
||||
active_quests = {},
|
||||
completed_quests = {}
|
||||
}
|
||||
|
||||
-- Subscribe to quest completion events
|
||||
local quest_sub = ecs.subscribe_event("quest_completed", function(event, params)
|
||||
local quest_name = params.quest_name or "unknown"
|
||||
local xp = params.xp_rewarded or 0
|
||||
|
||||
-- Track completed quests
|
||||
table.insert(quest_state.completed_quests, quest_name)
|
||||
|
||||
print("[Quest Log] Completed: " .. quest_name .. " (+" .. xp .. " XP)")
|
||||
print("[Quest Log] Total completed: " .. #quest_state.completed_quests)
|
||||
end)
|
||||
|
||||
print("Subscribed to quest_completed events (ID: " .. quest_sub .. ")")
|
||||
|
||||
-- =============================================================================
|
||||
-- Practical: Full Quest Flow
|
||||
-- =============================================================================
|
||||
|
||||
function run_quest_flow(quest_name, requirements, rewards, xp)
|
||||
print("\n=== Starting Quest: " .. quest_name .. " ===")
|
||||
|
||||
-- 1. Check requirements
|
||||
if not has_quest_items(requirements) then
|
||||
print("Cannot start quest - missing items")
|
||||
return false
|
||||
end
|
||||
|
||||
-- 2. Remove quest items
|
||||
print("Turning in quest items:")
|
||||
remove_quest_items(requirements)
|
||||
|
||||
-- 3. Complete quest and give rewards
|
||||
complete_quest(quest_name, rewards, xp)
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
-- Run a second quest
|
||||
run_quest_flow(
|
||||
"The Blacksmith's Request",
|
||||
{
|
||||
{ itemId = "gold_coin", count = 50 }
|
||||
},
|
||||
{
|
||||
{ itemId = "sword_iron", count = 1 },
|
||||
{ itemId = "gold_coin", count = 75 }
|
||||
},
|
||||
300
|
||||
)
|
||||
|
||||
-- =============================================================================
|
||||
-- Summary
|
||||
-- =============================================================================
|
||||
-- Quest reward patterns demonstrated:
|
||||
--
|
||||
-- 1. Direct reward: ecs.inventory.add() to give items to the player
|
||||
-- 2. Requirement check: ecs.inventory.count() to verify quest items
|
||||
-- 3. Item removal: ecs.inventory.remove() to consume quest items
|
||||
-- 4. Container loot: ecs.container.get_state() + ecs.inventory.add()
|
||||
-- 5. Event-driven: ecs.send_event() + ecs.subscribe_event() for quest flow
|
||||
-- 6. Item lookup: ecs.items.find() for display names and metadata
|
||||
-- =============================================================================
|
||||
|
||||
print("Quest reward examples completed successfully!")
|
||||
@@ -833,11 +833,26 @@ static void registerAllComponents()
|
||||
lua_setfield(L, -2, "itemId");
|
||||
lua_pushinteger(L, c.stackSize);
|
||||
lua_setfield(L, -2, "stackSize");
|
||||
lua_pushstring(L, c.action.c_str());
|
||||
lua_setfield(L, -2, "action");
|
||||
lua_pushstring(L, c.instanceId.c_str());
|
||||
lua_setfield(L, -2, "instanceId");
|
||||
lua_pushboolean(L, c.disabled ? 1 : 0);
|
||||
lua_setfield(L, -2, "disabled");
|
||||
, if (lua_getfield(L, idx, "itemId"), lua_isstring(L, -1))
|
||||
c.itemId = lua_tostring(L, -1);
|
||||
lua_pop(L, 1);
|
||||
if (lua_getfield(L, idx, "stackSize"), lua_isnumber(L, -1))
|
||||
c.stackSize = (int)lua_tointeger(L, -1);
|
||||
lua_pop(L, 1);
|
||||
if (lua_getfield(L, idx, "action"), lua_isstring(L, -1))
|
||||
c.action = lua_tostring(L, -1);
|
||||
lua_pop(L, 1);
|
||||
if (lua_getfield(L, idx, "instanceId"), lua_isstring(L, -1))
|
||||
c.instanceId = lua_tostring(L, -1);
|
||||
lua_pop(L, 1);
|
||||
if (lua_getfield(L, idx, "disabled"), lua_isboolean(L, -1))
|
||||
c.disabled = lua_toboolean(L, -1) != 0;
|
||||
lua_pop(L, 1););
|
||||
|
||||
// --- Inventory ---
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "BehaviorTreeSystem.hpp"
|
||||
#include "ItemSystem.hpp"
|
||||
#include "ItemRegistry.hpp"
|
||||
#include "ItemStateRegistry.hpp"
|
||||
#include "../components/Actuator.hpp"
|
||||
#include "../components/Item.hpp"
|
||||
#include "../components/Inventory.hpp"
|
||||
@@ -111,6 +112,28 @@ void ActuatorSystem::executeAction(flecs::entity character,
|
||||
"[ActuatorSystem] Executing action: " + actionName);
|
||||
}
|
||||
|
||||
void ActuatorSystem::executeItemAction(flecs::entity character,
|
||||
flecs::entity itemEntity,
|
||||
const Ogre::String &actionName)
|
||||
{
|
||||
if (!character.is_alive() || !itemEntity.is_alive())
|
||||
return;
|
||||
|
||||
if (!itemEntity.has<ItemComponent>())
|
||||
return;
|
||||
|
||||
m_executingActuatorId = itemEntity.id();
|
||||
m_executingCharacterId = character.id();
|
||||
m_executingActionName = actionName;
|
||||
m_actionFirstFrame = true;
|
||||
|
||||
// Lock player input while action executes
|
||||
setPlayerInputLocked(true);
|
||||
|
||||
Ogre::LogManager::getSingleton().logMessage(
|
||||
"[ActuatorSystem] Executing item action: " + actionName);
|
||||
}
|
||||
|
||||
bool ActuatorSystem::isActionComplete(flecs::entity character, float deltaTime)
|
||||
{
|
||||
if (!m_btSystem || !character.is_alive())
|
||||
@@ -468,10 +491,38 @@ void ActuatorSystem::update(float deltaTime)
|
||||
}
|
||||
} else if (targetEntity.has<ItemComponent>() &&
|
||||
input.ePressed) {
|
||||
// Pick up item
|
||||
if (m_itemSystem) {
|
||||
m_itemSystem->pickupItem(playerCharacter,
|
||||
targetEntity);
|
||||
auto &item = targetEntity.get_mut<ItemComponent>();
|
||||
|
||||
if (!item.action.empty()) {
|
||||
// Execute the item's action via behavior tree
|
||||
executeItemAction(playerCharacter,
|
||||
targetEntity,
|
||||
item.action);
|
||||
} else {
|
||||
// Pick up item
|
||||
if (m_itemSystem) {
|
||||
m_itemSystem->pickupItem(
|
||||
playerCharacter,
|
||||
targetEntity);
|
||||
}
|
||||
// Disable the item and persist state
|
||||
item.disabled = true;
|
||||
if (!item.instanceId.empty()) {
|
||||
ItemStateRegistry::getInstance()
|
||||
.setDisabled(item.instanceId,
|
||||
true);
|
||||
}
|
||||
// Hide the entity in the scene
|
||||
if (targetEntity.has<
|
||||
TransformComponent>()) {
|
||||
auto &trans = targetEntity
|
||||
.get_mut<
|
||||
TransformComponent>();
|
||||
if (trans.node) {
|
||||
trans.node->setVisible(
|
||||
false);
|
||||
}
|
||||
}
|
||||
}
|
||||
m_eHoldTime = 0.0f;
|
||||
}
|
||||
|
||||
@@ -62,6 +62,9 @@ private:
|
||||
void executeAction(flecs::entity character,
|
||||
flecs::entity actuatorEntity,
|
||||
const Ogre::String &actionName);
|
||||
void executeItemAction(flecs::entity character,
|
||||
flecs::entity itemEntity,
|
||||
const Ogre::String &actionName);
|
||||
bool isActionComplete(flecs::entity character, float deltaTime);
|
||||
void drawActionMenu(flecs::entity actuatorEntity);
|
||||
void setPlayerInputLocked(bool locked);
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
#include "CharacterSystem.hpp"
|
||||
#include "SmartObjectSystem.hpp"
|
||||
#include "ItemSystem.hpp"
|
||||
#include "ItemRegistry.hpp"
|
||||
#include "ItemStateRegistry.hpp"
|
||||
#include "EventBus.hpp"
|
||||
#include "../components/BehaviorTree.hpp"
|
||||
#include "../components/ActionDatabase.hpp"
|
||||
@@ -946,6 +948,51 @@ BehaviorTreeSystem::evaluateNode(const BehaviorTreeNode &node, flecs::entity e,
|
||||
return Status::success;
|
||||
}
|
||||
|
||||
/* --- Disable item entity (for pickup / consumption) --- */
|
||||
if (node.type == "disableItem") {
|
||||
if (isNewlyActive(state, &node)) {
|
||||
// node.name can specify an instanceId override,
|
||||
// otherwise we look for an ItemComponent on this entity
|
||||
flecs::entity targetEntity = e;
|
||||
if (!node.name.empty()) {
|
||||
m_world.query<ItemComponent>().each(
|
||||
[&](flecs::entity itemEntity,
|
||||
ItemComponent &itemComp) {
|
||||
if (itemComp.instanceId ==
|
||||
node.name.c_str() &&
|
||||
!targetEntity.is_alive()) {
|
||||
targetEntity = itemEntity;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (!targetEntity.is_alive() ||
|
||||
!targetEntity.has<ItemComponent>()) {
|
||||
std::cout << "[BT] disableItem: no item entity"
|
||||
<< std::endl;
|
||||
return Status::failure;
|
||||
}
|
||||
|
||||
auto &item = targetEntity.get_mut<ItemComponent>();
|
||||
item.disabled = true;
|
||||
if (!item.instanceId.empty()) {
|
||||
ItemStateRegistry::getInstance().setDisabled(
|
||||
item.instanceId, true);
|
||||
}
|
||||
if (targetEntity.has<TransformComponent>()) {
|
||||
auto &trans = targetEntity.get_mut<
|
||||
TransformComponent>();
|
||||
if (trans.node)
|
||||
trans.node->setVisible(false);
|
||||
}
|
||||
|
||||
std::cout << "[BT] disableItem: disabled item "
|
||||
<< targetEntity.id() << std::endl;
|
||||
return Status::success;
|
||||
}
|
||||
return Status::success;
|
||||
}
|
||||
|
||||
/* --- Lua behavior tree node --- */
|
||||
if (node.type == "luaTask") {
|
||||
/* Call the Lua function via the forward-declared API.
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
#include "ItemStateRegistry.hpp"
|
||||
#include <OgreLogManager.h>
|
||||
#include <fstream>
|
||||
|
||||
ItemStateRegistry &ItemStateRegistry::getInstance()
|
||||
{
|
||||
static ItemStateRegistry instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
ItemStateRegistry::ItemStateRegistry()
|
||||
{
|
||||
m_autoSavePath = "item_state.json";
|
||||
}
|
||||
|
||||
void ItemStateRegistry::setDisabled(const std::string &instanceId,
|
||||
bool disabled)
|
||||
{
|
||||
if (instanceId.empty())
|
||||
return;
|
||||
ItemState &state = m_states[instanceId];
|
||||
state.instanceId = instanceId;
|
||||
state.disabled = disabled;
|
||||
autoSave();
|
||||
}
|
||||
|
||||
bool ItemStateRegistry::isDisabled(const std::string &instanceId) const
|
||||
{
|
||||
if (instanceId.empty())
|
||||
return false;
|
||||
auto it = m_states.find(instanceId);
|
||||
if (it != m_states.end())
|
||||
return it->second.disabled;
|
||||
return false;
|
||||
}
|
||||
|
||||
void ItemStateRegistry::clearState(const std::string &instanceId)
|
||||
{
|
||||
m_states.erase(instanceId);
|
||||
autoSave();
|
||||
}
|
||||
|
||||
nlohmann::json ItemStateRegistry::serialize() const
|
||||
{
|
||||
nlohmann::json j;
|
||||
j["version"] = "1.0";
|
||||
for (const auto &pair : m_states) {
|
||||
const ItemState &state = pair.second;
|
||||
nlohmann::json stateJson;
|
||||
stateJson["instanceId"] = state.instanceId;
|
||||
stateJson["disabled"] = state.disabled;
|
||||
j["states"].push_back(stateJson);
|
||||
}
|
||||
return j;
|
||||
}
|
||||
|
||||
void ItemStateRegistry::deserialize(const nlohmann::json &j)
|
||||
{
|
||||
m_states.clear();
|
||||
if (!j.contains("states"))
|
||||
return;
|
||||
for (const auto &stateJson : j["states"]) {
|
||||
ItemState state;
|
||||
state.instanceId = stateJson.value("instanceId", "");
|
||||
state.disabled = stateJson.value("disabled", false);
|
||||
if (!state.instanceId.empty())
|
||||
m_states[state.instanceId] = state;
|
||||
}
|
||||
}
|
||||
|
||||
bool ItemStateRegistry::saveToFile(const std::string &filepath)
|
||||
{
|
||||
try {
|
||||
std::ofstream file(filepath);
|
||||
if (!file.is_open()) {
|
||||
m_lastError = "Cannot open " + filepath;
|
||||
return false;
|
||||
}
|
||||
file << serialize().dump(4);
|
||||
return true;
|
||||
} catch (const std::exception &e) {
|
||||
m_lastError = std::string("Save error: ") + e.what();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool ItemStateRegistry::loadFromFile(const std::string &filepath)
|
||||
{
|
||||
try {
|
||||
std::ifstream file(filepath);
|
||||
if (!file.is_open()) {
|
||||
m_lastError = "Cannot open " + filepath;
|
||||
return false;
|
||||
}
|
||||
nlohmann::json j;
|
||||
file >> j;
|
||||
deserialize(j);
|
||||
return true;
|
||||
} catch (const std::exception &e) {
|
||||
m_lastError = std::string("Load error: ") + e.what();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void ItemStateRegistry::autoSave()
|
||||
{
|
||||
if (!m_autoSavePath.empty())
|
||||
saveToFile(m_autoSavePath);
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
#ifndef EDITSCENE_ITEMSTATEREGISTRY_HPP
|
||||
#define EDITSCENE_ITEMSTATEREGISTRY_HPP
|
||||
#pragma once
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
/**
|
||||
* Global item state registry.
|
||||
*
|
||||
* Tracks per-instance item state keyed by instanceId.
|
||||
* Used to persist whether world items have been picked up / disabled
|
||||
* across scene reloads and game sessions.
|
||||
*
|
||||
* Saves to item_state.json.
|
||||
*/
|
||||
class ItemStateRegistry {
|
||||
public:
|
||||
static ItemStateRegistry &getInstance();
|
||||
|
||||
struct ItemState {
|
||||
std::string instanceId;
|
||||
bool disabled = false;
|
||||
};
|
||||
|
||||
void setDisabled(const std::string &instanceId, bool disabled);
|
||||
bool isDisabled(const std::string &instanceId) const;
|
||||
void clearState(const std::string &instanceId);
|
||||
|
||||
nlohmann::json serialize() const;
|
||||
void deserialize(const nlohmann::json &j);
|
||||
|
||||
bool saveToFile(const std::string &filepath);
|
||||
bool loadFromFile(const std::string &filepath);
|
||||
const std::string &getLastError() const
|
||||
{
|
||||
return m_lastError;
|
||||
}
|
||||
|
||||
void autoSave();
|
||||
|
||||
private:
|
||||
ItemStateRegistry();
|
||||
~ItemStateRegistry() = default;
|
||||
|
||||
ItemStateRegistry(const ItemStateRegistry &) = delete;
|
||||
ItemStateRegistry &operator=(const ItemStateRegistry &) = delete;
|
||||
|
||||
std::unordered_map<std::string, ItemState> m_states;
|
||||
mutable std::string m_lastError;
|
||||
std::string m_autoSavePath;
|
||||
};
|
||||
|
||||
#endif // EDITSCENE_ITEMSTATEREGISTRY_HPP
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "SceneSerializer.hpp"
|
||||
#include "ItemRegistry.hpp"
|
||||
#include "ContainerStateRegistry.hpp"
|
||||
#include "ItemStateRegistry.hpp"
|
||||
#include "../components/Transform.hpp"
|
||||
#include "../components/Renderable.hpp"
|
||||
#include "../components/EntityName.hpp"
|
||||
@@ -3831,6 +3832,12 @@ nlohmann::json SceneSerializer::serializeItem(flecs::entity entity)
|
||||
nlohmann::json json;
|
||||
json["itemId"] = item.itemId;
|
||||
json["stackSize"] = item.stackSize;
|
||||
if (!item.action.empty())
|
||||
json["action"] = item.action;
|
||||
if (!item.instanceId.empty())
|
||||
json["instanceId"] = item.instanceId;
|
||||
if (item.disabled)
|
||||
json["disabled"] = true;
|
||||
return json;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,656 @@
|
||||
/**
|
||||
* @file character_class_lua_test.cpp
|
||||
* @brief Standalone test for the Lua Character Class API.
|
||||
*
|
||||
* Tests the ecs.character_class.* functions exposed via the ecs.* Lua API.
|
||||
* Tests database queries (get_class_names, get_stat_names, get_skill_names,
|
||||
* get_need_names, get_class, get_stat_kind) and per-entity runtime API
|
||||
* (get_level, get_xp, add_xp, get_stat, get_skill, get_need,
|
||||
* get_available_points, set_need, get_pool_current, get_pool_max,
|
||||
* set_pool_current).
|
||||
*
|
||||
* Build with:
|
||||
* g++ -std=c++17 -I. -I../.. -I../../lua/lua-5.4.8/src \
|
||||
* character_class_lua_test.cpp \
|
||||
* lua_test_stubs.cpp \
|
||||
* ../../lua/lua-5.4.8/src/liblua.a \
|
||||
* -o character_class_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 registerLuaCharacterClassApi(lua_State *L);
|
||||
void registerLuaEntityApi(lua_State *L);
|
||||
void registerLuaComponentApi(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: get_class_names returns a table
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testGetClassNames(lua_State *L)
|
||||
{
|
||||
TEST("get_class_names returns a table");
|
||||
|
||||
bool ok = runLua(L,
|
||||
"local names = ecs.character_class.get_class_names();"
|
||||
"assert(type(names) == 'table', "
|
||||
"'get_class_names should return a table')");
|
||||
if (!ok)
|
||||
FAIL("get_class_names assertion failed");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 2: get_stat_names returns a table
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testGetStatNames(lua_State *L)
|
||||
{
|
||||
TEST("get_stat_names returns a table");
|
||||
|
||||
bool ok = runLua(L,
|
||||
"local names = ecs.character_class.get_stat_names();"
|
||||
"assert(type(names) == 'table', "
|
||||
"'get_stat_names should return a table')");
|
||||
if (!ok)
|
||||
FAIL("get_stat_names assertion failed");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 3: get_skill_names returns a table
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testGetSkillNames(lua_State *L)
|
||||
{
|
||||
TEST("get_skill_names returns a table");
|
||||
|
||||
bool ok = runLua(L,
|
||||
"local names = ecs.character_class.get_skill_names();"
|
||||
"assert(type(names) == 'table', "
|
||||
"'get_skill_names should return a table')");
|
||||
if (!ok)
|
||||
FAIL("get_skill_names assertion failed");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 4: get_need_names returns a table
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testGetNeedNames(lua_State *L)
|
||||
{
|
||||
TEST("get_need_names returns a table");
|
||||
|
||||
bool ok = runLua(L,
|
||||
"local names = ecs.character_class.get_need_names();"
|
||||
"assert(type(names) == 'table', "
|
||||
"'get_need_names should return a table')");
|
||||
if (!ok)
|
||||
FAIL("get_need_names assertion failed");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 5: get_class returns nil for unknown class
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testGetClassUnknown(lua_State *L)
|
||||
{
|
||||
TEST("get_class returns nil for unknown class");
|
||||
|
||||
bool ok = runLua(
|
||||
L, "local cls = ecs.character_class.get_class('nonexistent');"
|
||||
"assert(cls == nil, "
|
||||
"'get_class should return nil for unknown class')");
|
||||
if (!ok)
|
||||
FAIL("get_class unknown assertion failed");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 6: get_stat_kind returns 'unknown' for unknown stat
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testGetStatKindUnknown(lua_State *L)
|
||||
{
|
||||
TEST("get_stat_kind returns 'unknown' for unknown stat");
|
||||
|
||||
bool ok = runLua(
|
||||
L,
|
||||
"local kind = ecs.character_class.get_stat_kind('nonexistent');"
|
||||
"assert(kind == 'unknown', "
|
||||
"'get_stat_kind should return unknown for unknown stat')");
|
||||
if (!ok)
|
||||
FAIL("get_stat_kind unknown assertion failed");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 7: get_stat_kind returns 'unknown' for nil
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testGetStatKindNil(lua_State *L)
|
||||
{
|
||||
TEST("get_stat_kind returns 'unknown' for nil");
|
||||
|
||||
bool ok = runLua(L,
|
||||
"local kind = ecs.character_class.get_stat_kind(nil);"
|
||||
"assert(kind == 'unknown', "
|
||||
"'get_stat_kind should return unknown for nil')");
|
||||
if (!ok)
|
||||
FAIL("get_stat_kind nil assertion failed");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 8: get_level returns 0 for non-existent entity
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testGetLevelNoEntity(lua_State *L)
|
||||
{
|
||||
TEST("get_level returns 0 for non-existent entity");
|
||||
|
||||
bool ok = runLua(
|
||||
L, "local level = ecs.character_class.get_level(99999);"
|
||||
"assert(level == 0, "
|
||||
"'get_level should return 0 for non-existent entity')");
|
||||
if (!ok)
|
||||
FAIL("get_level no entity assertion failed");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 9: get_xp returns 0 for non-existent entity
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testGetXPNoEntity(lua_State *L)
|
||||
{
|
||||
TEST("get_xp returns 0 for non-existent entity");
|
||||
|
||||
bool ok = runLua(L,
|
||||
"local xp = ecs.character_class.get_xp(99999);"
|
||||
"assert(xp == 0, "
|
||||
"'get_xp should return 0 for non-existent entity')");
|
||||
if (!ok)
|
||||
FAIL("get_xp no entity assertion failed");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 10: add_xp returns false for non-existent entity
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testAddXPNoEntity(lua_State *L)
|
||||
{
|
||||
TEST("add_xp returns false for non-existent entity");
|
||||
|
||||
bool ok = runLua(
|
||||
L, "local ok = ecs.character_class.add_xp(99999, 100);"
|
||||
"assert(ok == false, "
|
||||
"'add_xp should return false for non-existent entity')");
|
||||
if (!ok)
|
||||
FAIL("add_xp no entity assertion failed");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 11: get_stat returns 0 for non-existent entity
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testGetStatNoEntity(lua_State *L)
|
||||
{
|
||||
TEST("get_stat returns 0 for non-existent entity");
|
||||
|
||||
bool ok = runLua(
|
||||
L,
|
||||
"local val = ecs.character_class.get_stat(99999, 'strength');"
|
||||
"assert(val == 0, "
|
||||
"'get_stat should return 0 for non-existent entity')");
|
||||
if (!ok)
|
||||
FAIL("get_stat no entity assertion failed");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 12: get_skill returns 0 for non-existent entity
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testGetSkillNoEntity(lua_State *L)
|
||||
{
|
||||
TEST("get_skill returns 0 for non-existent entity");
|
||||
|
||||
bool ok = runLua(
|
||||
L,
|
||||
"local val = ecs.character_class.get_skill(99999, 'swordsmanship');"
|
||||
"assert(val == 0, "
|
||||
"'get_skill should return 0 for non-existent entity')");
|
||||
if (!ok)
|
||||
FAIL("get_skill no entity assertion failed");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 13: get_need returns 0 for non-existent entity
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testGetNeedNoEntity(lua_State *L)
|
||||
{
|
||||
TEST("get_need returns 0 for non-existent entity");
|
||||
|
||||
bool ok = runLua(
|
||||
L, "local val = ecs.character_class.get_need(99999, 'hunger');"
|
||||
"assert(val == 0, "
|
||||
"'get_need should return 0 for non-existent entity')");
|
||||
if (!ok)
|
||||
FAIL("get_need no entity assertion failed");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 14: get_available_points returns 0 for non-existent entity
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testGetAvailablePointsNoEntity(lua_State *L)
|
||||
{
|
||||
TEST("get_available_points returns 0 for non-existent entity");
|
||||
|
||||
bool ok = runLua(
|
||||
L,
|
||||
"local pts = ecs.character_class.get_available_points(99999);"
|
||||
"assert(pts == 0, "
|
||||
"'get_available_points should return 0 for non-existent entity')");
|
||||
if (!ok)
|
||||
FAIL("get_available_points no entity assertion failed");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 15: set_need does not crash for non-existent entity
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testSetNeedNoEntity(lua_State *L)
|
||||
{
|
||||
TEST("set_need does not crash for non-existent entity");
|
||||
|
||||
bool ok =
|
||||
runLua(L, "ecs.character_class.set_need(99999, 'hunger', 50);");
|
||||
if (!ok)
|
||||
FAIL("set_need no entity assertion failed");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 16: get_pool_current returns 0 for non-existent entity
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testGetPoolCurrentNoEntity(lua_State *L)
|
||||
{
|
||||
TEST("get_pool_current returns 0 for non-existent entity");
|
||||
|
||||
bool ok = runLua(
|
||||
L,
|
||||
"local val = ecs.character_class.get_pool_current(99999, 'health');"
|
||||
"assert(val == 0, "
|
||||
"'get_pool_current should return 0 for non-existent entity')");
|
||||
if (!ok)
|
||||
FAIL("get_pool_current no entity assertion failed");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 17: get_pool_max returns 0 for non-existent entity
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testGetPoolMaxNoEntity(lua_State *L)
|
||||
{
|
||||
TEST("get_pool_max returns 0 for non-existent entity");
|
||||
|
||||
bool ok = runLua(
|
||||
L,
|
||||
"local val = ecs.character_class.get_pool_max(99999, 'health');"
|
||||
"assert(val == 0, "
|
||||
"'get_pool_max should return 0 for non-existent entity')");
|
||||
if (!ok)
|
||||
FAIL("get_pool_max no entity assertion failed");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 18: set_pool_current returns false for non-existent entity
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testSetPoolCurrentNoEntity(lua_State *L)
|
||||
{
|
||||
TEST("set_pool_current returns false for non-existent entity");
|
||||
|
||||
bool ok = runLua(
|
||||
L,
|
||||
"local ok = ecs.character_class.set_pool_current(99999, 'health', 50);"
|
||||
"assert(ok == false, "
|
||||
"'set_pool_current should return false for non-existent entity')");
|
||||
if (!ok)
|
||||
FAIL("set_pool_current no entity assertion failed");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 19: All functions exist and are callable
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testAllFunctionsExist(lua_State *L)
|
||||
{
|
||||
TEST("all functions exist and are callable");
|
||||
|
||||
bool ok = runLua(
|
||||
L,
|
||||
"assert(type(ecs.character_class.get_class_names) == 'function', "
|
||||
"'get_class_names should be a function');"
|
||||
"assert(type(ecs.character_class.get_stat_names) == 'function', "
|
||||
"'get_stat_names should be a function');"
|
||||
"assert(type(ecs.character_class.get_skill_names) == 'function', "
|
||||
"'get_skill_names should be a function');"
|
||||
"assert(type(ecs.character_class.get_need_names) == 'function', "
|
||||
"'get_need_names should be a function');"
|
||||
"assert(type(ecs.character_class.get_class) == 'function', "
|
||||
"'get_class should be a function');"
|
||||
"assert(type(ecs.character_class.get_stat_kind) == 'function', "
|
||||
"'get_stat_kind should be a function');"
|
||||
"assert(type(ecs.character_class.get_level) == 'function', "
|
||||
"'get_level should be a function');"
|
||||
"assert(type(ecs.character_class.get_xp) == 'function', "
|
||||
"'get_xp should be a function');"
|
||||
"assert(type(ecs.character_class.add_xp) == 'function', "
|
||||
"'add_xp should be a function');"
|
||||
"assert(type(ecs.character_class.get_stat) == 'function', "
|
||||
"'get_stat should be a function');"
|
||||
"assert(type(ecs.character_class.get_skill) == 'function', "
|
||||
"'get_skill should be a function');"
|
||||
"assert(type(ecs.character_class.get_need) == 'function', "
|
||||
"'get_need should be a function');"
|
||||
"assert(type(ecs.character_class.get_available_points) == 'function', "
|
||||
"'get_available_points should be a function');"
|
||||
"assert(type(ecs.character_class.set_need) == 'function', "
|
||||
"'set_need should be a function');"
|
||||
"assert(type(ecs.character_class.get_pool_current) == 'function', "
|
||||
"'get_pool_current should be a function');"
|
||||
"assert(type(ecs.character_class.get_pool_max) == 'function', "
|
||||
"'get_pool_max should be a function');"
|
||||
"assert(type(ecs.character_class.set_pool_current) == 'function', "
|
||||
"'set_pool_current should be a function')");
|
||||
if (!ok)
|
||||
FAIL("all functions exist assertion failed");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 20: get_stat returns 0 for nil stat name
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testGetStatNilName(lua_State *L)
|
||||
{
|
||||
TEST("get_stat returns 0 for nil stat name");
|
||||
|
||||
bool ok = runLua(L,
|
||||
"local val = ecs.character_class.get_stat(99999, nil);"
|
||||
"assert(val == 0, "
|
||||
"'get_stat should return 0 for nil stat name')");
|
||||
if (!ok)
|
||||
FAIL("get_stat nil name assertion failed");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 21: get_skill returns 0 for nil skill name
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testGetSkillNilName(lua_State *L)
|
||||
{
|
||||
TEST("get_skill returns 0 for nil skill name");
|
||||
|
||||
bool ok = runLua(
|
||||
L, "local val = ecs.character_class.get_skill(99999, nil);"
|
||||
"assert(val == 0, "
|
||||
"'get_skill should return 0 for nil skill name')");
|
||||
if (!ok)
|
||||
FAIL("get_skill nil name assertion failed");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 22: get_need returns 0 for nil need name
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testGetNeedNilName(lua_State *L)
|
||||
{
|
||||
TEST("get_need returns 0 for nil need name");
|
||||
|
||||
bool ok = runLua(L,
|
||||
"local val = ecs.character_class.get_need(99999, nil);"
|
||||
"assert(val == 0, "
|
||||
"'get_need should return 0 for nil need name')");
|
||||
if (!ok)
|
||||
FAIL("get_need nil name assertion failed");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 23: get_pool_current returns 0 for nil pool name
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testGetPoolCurrentNilName(lua_State *L)
|
||||
{
|
||||
TEST("get_pool_current returns 0 for nil pool name");
|
||||
|
||||
bool ok = runLua(
|
||||
L,
|
||||
"local val = ecs.character_class.get_pool_current(99999, nil);"
|
||||
"assert(val == 0, "
|
||||
"'get_pool_current should return 0 for nil pool name')");
|
||||
if (!ok)
|
||||
FAIL("get_pool_current nil name assertion failed");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 24: get_pool_max returns 0 for nil pool name
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testGetPoolMaxNilName(lua_State *L)
|
||||
{
|
||||
TEST("get_pool_max returns 0 for nil pool name");
|
||||
|
||||
bool ok = runLua(
|
||||
L, "local val = ecs.character_class.get_pool_max(99999, nil);"
|
||||
"assert(val == 0, "
|
||||
"'get_pool_max should return 0 for nil pool name')");
|
||||
if (!ok)
|
||||
FAIL("get_pool_max nil name assertion failed");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 25: set_pool_current returns false for nil pool name
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testSetPoolCurrentNilName(lua_State *L)
|
||||
{
|
||||
TEST("set_pool_current returns false for nil pool name");
|
||||
|
||||
bool ok = runLua(
|
||||
L,
|
||||
"local ok = ecs.character_class.set_pool_current(99999, nil, 50);"
|
||||
"assert(ok == false, "
|
||||
"'set_pool_current should return false for nil pool name')");
|
||||
if (!ok)
|
||||
FAIL("set_pool_current nil name assertion failed");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Main
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
int main()
|
||||
{
|
||||
printf("Character Class 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 required APIs
|
||||
editScene::registerLuaEntityApi(L);
|
||||
editScene::registerLuaComponentApi(L);
|
||||
editScene::registerLuaCharacterClassApi(L);
|
||||
|
||||
// Run tests
|
||||
int failures = 0;
|
||||
failures += testGetClassNames(L);
|
||||
failures += testGetStatNames(L);
|
||||
failures += testGetSkillNames(L);
|
||||
failures += testGetNeedNames(L);
|
||||
failures += testGetClassUnknown(L);
|
||||
failures += testGetStatKindUnknown(L);
|
||||
failures += testGetStatKindNil(L);
|
||||
failures += testGetLevelNoEntity(L);
|
||||
failures += testGetXPNoEntity(L);
|
||||
failures += testAddXPNoEntity(L);
|
||||
failures += testGetStatNoEntity(L);
|
||||
failures += testGetSkillNoEntity(L);
|
||||
failures += testGetNeedNoEntity(L);
|
||||
failures += testGetAvailablePointsNoEntity(L);
|
||||
failures += testSetNeedNoEntity(L);
|
||||
failures += testGetPoolCurrentNoEntity(L);
|
||||
failures += testGetPoolMaxNoEntity(L);
|
||||
failures += testSetPoolCurrentNoEntity(L);
|
||||
failures += testAllFunctionsExist(L);
|
||||
failures += testGetStatNilName(L);
|
||||
failures += testGetSkillNilName(L);
|
||||
failures += testGetNeedNilName(L);
|
||||
failures += testGetPoolCurrentNilName(L);
|
||||
failures += testGetPoolMaxNilName(L);
|
||||
failures += testSetPoolCurrentNilName(L);
|
||||
|
||||
// Cleanup
|
||||
lua_close(L);
|
||||
|
||||
printf("\nResults: %d/%d passed, %d failed\n", passCount, testCount,
|
||||
failures);
|
||||
|
||||
return failures > 0 ? 1 : 0;
|
||||
}
|
||||
@@ -566,15 +566,21 @@ static int testItemComponent(lua_State *L)
|
||||
|
||||
bool ok = runLua(
|
||||
L,
|
||||
"local id = ecs.create_entity();"
|
||||
"local id = ecs.create_entity(); "
|
||||
"ecs.set_component(id, 'Item', {"
|
||||
" itemId = 'potion_health',"
|
||||
" stackSize = 5"
|
||||
"});"
|
||||
"local item = ecs.get_component(id, 'Item');"
|
||||
"assert(item ~= nil, 'Item should exist');"
|
||||
"assert(item.itemId == 'potion_health', 'wrong id');"
|
||||
"assert(item.stackSize == 5, 'wrong stackSize')");
|
||||
" stackSize = 5,"
|
||||
" action = 'drink_potion',"
|
||||
" instanceId = 'potion_001',"
|
||||
" disabled = true"
|
||||
"}); "
|
||||
"local item = ecs.get_component(id, 'Item'); "
|
||||
"assert(item ~= nil, 'Item should exist'); "
|
||||
"assert(item.itemId == 'potion_health', 'wrong id'); "
|
||||
"assert(item.stackSize == 5, 'wrong stackSize'); "
|
||||
"assert(item.action == 'drink_potion', 'wrong action'); "
|
||||
"assert(item.instanceId == 'potion_001', 'wrong instanceId'); "
|
||||
"assert(item.disabled == true, 'wrong disabled')");
|
||||
if (!ok)
|
||||
FAIL("Item component assertion failed");
|
||||
|
||||
|
||||
@@ -0,0 +1,432 @@
|
||||
/**
|
||||
* @file dialogue_lua_test.cpp
|
||||
* @brief Standalone test for the Lua Dialogue API.
|
||||
*
|
||||
* Tests the ecs.dialogue.* functions exposed via the ecs.* Lua API.
|
||||
* Tests show/hide, is_active, select_choice, progress, settings,
|
||||
* and save/load settings.
|
||||
*
|
||||
* Build with:
|
||||
* g++ -std=c++17 -I. -I../.. -I../../lua/lua-5.4.8/src \
|
||||
* dialogue_lua_test.cpp \
|
||||
* lua_test_stubs.cpp \
|
||||
* ../../lua/lua-5.4.8/src/liblua.a \
|
||||
* -o dialogue_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 registerLuaDialogueApi(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: Show dialogue with text only
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testShowTextOnly(lua_State *L)
|
||||
{
|
||||
TEST("show dialogue with text only");
|
||||
|
||||
bool ok = runLua(L, "ecs.dialogue.show('Hello world');"
|
||||
"assert(ecs.dialogue.is_active() == false, "
|
||||
"'stub returns false for is_active')");
|
||||
if (!ok)
|
||||
FAIL("show text only assertion failed");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 2: Show dialogue with text and speaker
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testShowWithSpeaker(lua_State *L)
|
||||
{
|
||||
TEST("show dialogue with text and speaker");
|
||||
|
||||
bool ok = runLua(L, "ecs.dialogue.show('Welcome!', {}, 'Narrator');"
|
||||
"assert(ecs.dialogue.is_active() == false, "
|
||||
"'stub returns false for is_active')");
|
||||
if (!ok)
|
||||
FAIL("show with speaker assertion failed");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 3: Show dialogue with choices
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testShowWithChoices(lua_State *L)
|
||||
{
|
||||
TEST("show dialogue with choices");
|
||||
|
||||
bool ok = runLua(L, "ecs.dialogue.show('Choose:', "
|
||||
"{ 'Option A', 'Option B', 'Option C' }, 'Guide');"
|
||||
"assert(ecs.dialogue.is_active() == false, "
|
||||
"'stub returns false for is_active')");
|
||||
if (!ok)
|
||||
FAIL("show with choices assertion failed");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 4: Hide dialogue
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testHide(lua_State *L)
|
||||
{
|
||||
TEST("hide dialogue");
|
||||
|
||||
bool ok = runLua(L, "ecs.dialogue.show('Test');"
|
||||
"ecs.dialogue.hide();"
|
||||
"assert(ecs.dialogue.is_active() == false, "
|
||||
"'should be inactive after hide')");
|
||||
if (!ok)
|
||||
FAIL("hide assertion failed");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 5: Select choice
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testSelectChoice(lua_State *L)
|
||||
{
|
||||
TEST("select choice");
|
||||
|
||||
bool ok = runLua(L, "ecs.dialogue.show('Pick one:', "
|
||||
"{ 'A', 'B' }, 'Test');"
|
||||
"ecs.dialogue.select_choice(1);"
|
||||
"ecs.dialogue.select_choice(2);"
|
||||
"ecs.dialogue.select_choice(0);");
|
||||
if (!ok)
|
||||
FAIL("select choice assertion failed");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 6: Progress (click-to-dismiss)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testProgress(lua_State *L)
|
||||
{
|
||||
TEST("progress (click-to-dismiss)");
|
||||
|
||||
bool ok = runLua(L, "ecs.dialogue.show('Narration...');"
|
||||
"ecs.dialogue.progress();");
|
||||
if (!ok)
|
||||
FAIL("progress assertion failed");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 7: Get settings returns a table with expected fields
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testGetSettings(lua_State *L)
|
||||
{
|
||||
TEST("get_settings returns table with expected fields");
|
||||
|
||||
bool ok = runLua(
|
||||
L, "local s = ecs.dialogue.get_settings();"
|
||||
"assert(type(s) == 'table', 'settings should be a table');"
|
||||
"assert(type(s.font_name) == 'string', "
|
||||
"'font_name should be string');"
|
||||
"assert(type(s.font_size) == 'number', "
|
||||
"'font_size should be number');"
|
||||
"assert(type(s.speaker_font_size) == 'number', "
|
||||
"'speaker_font_size should be number');"
|
||||
"assert(type(s.background_opacity) == 'number', "
|
||||
"'background_opacity should be number');"
|
||||
"assert(type(s.box_height_fraction) == 'number', "
|
||||
"'box_height_fraction should be number');"
|
||||
"assert(type(s.box_position_fraction) == 'number', "
|
||||
"'box_position_fraction should be number');");
|
||||
if (!ok)
|
||||
FAIL("get_settings assertion failed");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 8: Set settings
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testSetSettings(lua_State *L)
|
||||
{
|
||||
TEST("set_settings");
|
||||
|
||||
bool ok = runLua(L, "ecs.dialogue.set_settings({"
|
||||
" font_name = 'Custom.ttf',"
|
||||
" font_size = 18.0,"
|
||||
" speaker_font_size = 16.0,"
|
||||
" background_opacity = 0.9,"
|
||||
" box_height_fraction = 0.3,"
|
||||
" box_position_fraction = 0.7"
|
||||
"});");
|
||||
if (!ok)
|
||||
FAIL("set_settings assertion failed");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 9: Save settings returns boolean
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testSaveSettings(lua_State *L)
|
||||
{
|
||||
TEST("save_settings returns boolean");
|
||||
|
||||
bool ok = runLua(
|
||||
L, "local ok = ecs.dialogue.save_settings();"
|
||||
"assert(type(ok) == 'boolean', "
|
||||
"'save_settings should return boolean');"
|
||||
"local ok2 = ecs.dialogue.save_settings('custom.json');"
|
||||
"assert(type(ok2) == 'boolean', "
|
||||
"'save_settings with path should return boolean')");
|
||||
if (!ok)
|
||||
FAIL("save_settings assertion failed");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 10: Load settings returns boolean
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testLoadSettings(lua_State *L)
|
||||
{
|
||||
TEST("load_settings returns boolean");
|
||||
|
||||
bool ok = runLua(
|
||||
L, "local ok = ecs.dialogue.load_settings();"
|
||||
"assert(type(ok) == 'boolean', "
|
||||
"'load_settings should return boolean');"
|
||||
"local ok2 = ecs.dialogue.load_settings('custom.json');"
|
||||
"assert(type(ok2) == 'boolean', "
|
||||
"'load_settings with path should return boolean')");
|
||||
if (!ok)
|
||||
FAIL("load_settings assertion failed");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 11: Return types are correct for all functions
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testReturnTypes(lua_State *L)
|
||||
{
|
||||
TEST("return types are correct");
|
||||
|
||||
bool ok = runLua(L, "assert(ecs.dialogue.show == nil or "
|
||||
"type(ecs.dialogue.show) == 'function', "
|
||||
"'show should be a function');"
|
||||
"assert(ecs.dialogue.hide == nil or "
|
||||
"type(ecs.dialogue.hide) == 'function', "
|
||||
"'hide should be a function');"
|
||||
"assert(ecs.dialogue.is_active == nil or "
|
||||
"type(ecs.dialogue.is_active) == 'function', "
|
||||
"'is_active should be a function');"
|
||||
"assert(ecs.dialogue.select_choice == nil or "
|
||||
"type(ecs.dialogue.select_choice) == 'function', "
|
||||
"'select_choice should be a function');"
|
||||
"assert(ecs.dialogue.progress == nil or "
|
||||
"type(ecs.dialogue.progress) == 'function', "
|
||||
"'progress should be a function');"
|
||||
"assert(ecs.dialogue.get_settings == nil or "
|
||||
"type(ecs.dialogue.get_settings) == 'function', "
|
||||
"'get_settings should be a function');"
|
||||
"assert(ecs.dialogue.set_settings == nil or "
|
||||
"type(ecs.dialogue.set_settings) == 'function', "
|
||||
"'set_settings should be a function');"
|
||||
"assert(ecs.dialogue.save_settings == nil or "
|
||||
"type(ecs.dialogue.save_settings) == 'function', "
|
||||
"'save_settings should be a function');"
|
||||
"assert(ecs.dialogue.load_settings == nil or "
|
||||
"type(ecs.dialogue.load_settings) == 'function', "
|
||||
"'load_settings should be a function')");
|
||||
if (!ok)
|
||||
FAIL("return types assertion failed");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 12: Show with empty choices table
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testShowEmptyChoices(lua_State *L)
|
||||
{
|
||||
TEST("show with empty choices table");
|
||||
|
||||
bool ok = runLua(L, "ecs.dialogue.show('No choices', {}, 'Speaker');"
|
||||
"assert(ecs.dialogue.is_active() == false, "
|
||||
"'stub returns false for is_active')");
|
||||
if (!ok)
|
||||
FAIL("show empty choices assertion failed");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 13: Show with nil speaker
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testShowNilSpeaker(lua_State *L)
|
||||
{
|
||||
TEST("show with nil speaker");
|
||||
|
||||
bool ok = runLua(L, "ecs.dialogue.show('No speaker');"
|
||||
"assert(ecs.dialogue.is_active() == false, "
|
||||
"'stub returns false for is_active')");
|
||||
if (!ok)
|
||||
FAIL("show nil speaker assertion failed");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 14: Settings round-trip (get -> modify -> set -> get)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testSettingsRoundTrip(lua_State *L)
|
||||
{
|
||||
TEST("settings round-trip");
|
||||
|
||||
bool ok = runLua(L, "local s = ecs.dialogue.get_settings();"
|
||||
"s.font_size = 30.0;"
|
||||
"s.background_opacity = 0.5;"
|
||||
"ecs.dialogue.set_settings(s);"
|
||||
"local s2 = ecs.dialogue.get_settings();"
|
||||
"assert(type(s2) == 'table', "
|
||||
"'settings after set should be a table')");
|
||||
if (!ok)
|
||||
FAIL("settings round-trip assertion failed");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Main
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
int main()
|
||||
{
|
||||
printf("Dialogue 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 dialogue API
|
||||
editScene::registerLuaDialogueApi(L);
|
||||
|
||||
// Run tests
|
||||
int failures = 0;
|
||||
failures += testShowTextOnly(L);
|
||||
failures += testShowWithSpeaker(L);
|
||||
failures += testShowWithChoices(L);
|
||||
failures += testHide(L);
|
||||
failures += testSelectChoice(L);
|
||||
failures += testProgress(L);
|
||||
failures += testGetSettings(L);
|
||||
failures += testSetSettings(L);
|
||||
failures += testSaveSettings(L);
|
||||
failures += testLoadSettings(L);
|
||||
failures += testReturnTypes(L);
|
||||
failures += testShowEmptyChoices(L);
|
||||
failures += testShowNilSpeaker(L);
|
||||
failures += testSettingsRoundTrip(L);
|
||||
|
||||
// Cleanup
|
||||
lua_close(L);
|
||||
|
||||
printf("\nResults: %d/%d passed, %d failed\n", passCount, testCount,
|
||||
failures);
|
||||
|
||||
return failures > 0 ? 1 : 0;
|
||||
}
|
||||
@@ -149,15 +149,11 @@ void registerLuaDialogueApi(lua_State *L)
|
||||
lua_newtable(L);
|
||||
|
||||
// show(text, choices, speaker)
|
||||
lua_pushcfunction(L, [](lua_State *L) -> int {
|
||||
return 0;
|
||||
});
|
||||
lua_pushcfunction(L, [](lua_State *L) -> int { return 0; });
|
||||
lua_setfield(L, -2, "show");
|
||||
|
||||
// hide()
|
||||
lua_pushcfunction(L, [](lua_State *L) -> int {
|
||||
return 0;
|
||||
});
|
||||
lua_pushcfunction(L, [](lua_State *L) -> int { return 0; });
|
||||
lua_setfield(L, -2, "hide");
|
||||
|
||||
// is_active()
|
||||
@@ -168,15 +164,11 @@ void registerLuaDialogueApi(lua_State *L)
|
||||
lua_setfield(L, -2, "is_active");
|
||||
|
||||
// select_choice(index)
|
||||
lua_pushcfunction(L, [](lua_State *L) -> int {
|
||||
return 0;
|
||||
});
|
||||
lua_pushcfunction(L, [](lua_State *L) -> int { return 0; });
|
||||
lua_setfield(L, -2, "select_choice");
|
||||
|
||||
// progress()
|
||||
lua_pushcfunction(L, [](lua_State *L) -> int {
|
||||
return 0;
|
||||
});
|
||||
lua_pushcfunction(L, [](lua_State *L) -> int { return 0; });
|
||||
lua_setfield(L, -2, "progress");
|
||||
|
||||
// get_settings()
|
||||
@@ -199,9 +191,7 @@ void registerLuaDialogueApi(lua_State *L)
|
||||
lua_setfield(L, -2, "get_settings");
|
||||
|
||||
// set_settings(table)
|
||||
lua_pushcfunction(L, [](lua_State *L) -> int {
|
||||
return 0;
|
||||
});
|
||||
lua_pushcfunction(L, [](lua_State *L) -> int { return 0; });
|
||||
lua_setfield(L, -2, "set_settings");
|
||||
|
||||
// save_settings(path)
|
||||
@@ -223,6 +213,143 @@ void registerLuaDialogueApi(lua_State *L)
|
||||
lua_setglobal(L, "ecs");
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Stub: LuaCharacterClassApi
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
void registerLuaCharacterClassApi(lua_State *L)
|
||||
{
|
||||
lua_getglobal(L, "ecs");
|
||||
if (lua_isnil(L, -1)) {
|
||||
lua_pop(L, 1);
|
||||
lua_newtable(L);
|
||||
}
|
||||
|
||||
lua_newtable(L);
|
||||
|
||||
// get_class_names()
|
||||
lua_pushcfunction(L, [](lua_State *L) -> int {
|
||||
lua_newtable(L);
|
||||
return 1;
|
||||
});
|
||||
lua_setfield(L, -2, "get_class_names");
|
||||
|
||||
// get_stat_names()
|
||||
lua_pushcfunction(L, [](lua_State *L) -> int {
|
||||
lua_newtable(L);
|
||||
return 1;
|
||||
});
|
||||
lua_setfield(L, -2, "get_stat_names");
|
||||
|
||||
// get_skill_names()
|
||||
lua_pushcfunction(L, [](lua_State *L) -> int {
|
||||
lua_newtable(L);
|
||||
return 1;
|
||||
});
|
||||
lua_setfield(L, -2, "get_skill_names");
|
||||
|
||||
// get_need_names()
|
||||
lua_pushcfunction(L, [](lua_State *L) -> int {
|
||||
lua_newtable(L);
|
||||
return 1;
|
||||
});
|
||||
lua_setfield(L, -2, "get_need_names");
|
||||
|
||||
// get_class(name)
|
||||
lua_pushcfunction(L, [](lua_State *L) -> int { return 0; });
|
||||
lua_setfield(L, -2, "get_class");
|
||||
|
||||
// get_stat_kind(name)
|
||||
lua_pushcfunction(L, [](lua_State *L) -> int {
|
||||
const char *name = lua_tostring(L, 1);
|
||||
if (!name) {
|
||||
lua_pushstring(L, "unknown");
|
||||
return 1;
|
||||
}
|
||||
lua_pushstring(L, "unknown");
|
||||
return 1;
|
||||
});
|
||||
lua_setfield(L, -2, "get_stat_kind");
|
||||
|
||||
// get_level(entity_id)
|
||||
lua_pushcfunction(L, [](lua_State *L) -> int {
|
||||
lua_pushinteger(L, 0);
|
||||
return 1;
|
||||
});
|
||||
lua_setfield(L, -2, "get_level");
|
||||
|
||||
// get_xp(entity_id)
|
||||
lua_pushcfunction(L, [](lua_State *L) -> int {
|
||||
lua_pushinteger(L, 0);
|
||||
return 1;
|
||||
});
|
||||
lua_setfield(L, -2, "get_xp");
|
||||
|
||||
// add_xp(entity_id, amount)
|
||||
lua_pushcfunction(L, [](lua_State *L) -> int {
|
||||
lua_pushboolean(L, 0);
|
||||
return 1;
|
||||
});
|
||||
lua_setfield(L, -2, "add_xp");
|
||||
|
||||
// get_stat(entity_id, stat_name)
|
||||
lua_pushcfunction(L, [](lua_State *L) -> int {
|
||||
lua_pushinteger(L, 0);
|
||||
return 1;
|
||||
});
|
||||
lua_setfield(L, -2, "get_stat");
|
||||
|
||||
// get_skill(entity_id, skill_name)
|
||||
lua_pushcfunction(L, [](lua_State *L) -> int {
|
||||
lua_pushinteger(L, 0);
|
||||
return 1;
|
||||
});
|
||||
lua_setfield(L, -2, "get_skill");
|
||||
|
||||
// get_need(entity_id, need_name)
|
||||
lua_pushcfunction(L, [](lua_State *L) -> int {
|
||||
lua_pushinteger(L, 0);
|
||||
return 1;
|
||||
});
|
||||
lua_setfield(L, -2, "get_need");
|
||||
|
||||
// get_available_points(entity_id)
|
||||
lua_pushcfunction(L, [](lua_State *L) -> int {
|
||||
lua_pushinteger(L, 0);
|
||||
return 1;
|
||||
});
|
||||
lua_setfield(L, -2, "get_available_points");
|
||||
|
||||
// set_need(entity_id, need_name, value)
|
||||
lua_pushcfunction(L, [](lua_State *L) -> int { return 0; });
|
||||
lua_setfield(L, -2, "set_need");
|
||||
|
||||
// get_pool_current(entity_id, pool_name)
|
||||
lua_pushcfunction(L, [](lua_State *L) -> int {
|
||||
lua_pushinteger(L, 0);
|
||||
return 1;
|
||||
});
|
||||
lua_setfield(L, -2, "get_pool_current");
|
||||
|
||||
// get_pool_max(entity_id, pool_name)
|
||||
lua_pushcfunction(L, [](lua_State *L) -> int {
|
||||
lua_pushinteger(L, 0);
|
||||
return 1;
|
||||
});
|
||||
lua_setfield(L, -2, "get_pool_max");
|
||||
|
||||
// set_pool_current(entity_id, pool_name, value)
|
||||
lua_pushcfunction(L, [](lua_State *L) -> int {
|
||||
lua_pushboolean(L, 0);
|
||||
return 1;
|
||||
});
|
||||
lua_setfield(L, -2, "set_pool_current");
|
||||
|
||||
lua_setfield(L, -2, "character_class");
|
||||
|
||||
lua_setglobal(L, "ecs");
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Stub: LuaItemApi
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -246,7 +373,8 @@ struct StubInvSlot {
|
||||
};
|
||||
|
||||
static std::unordered_map<int, std::vector<StubInvSlot> > s_stubInventories;
|
||||
static std::unordered_map<std::string, std::vector<StubInvSlot> > s_stubContainerStates;
|
||||
static std::unordered_map<std::string, std::vector<StubInvSlot> >
|
||||
s_stubContainerStates;
|
||||
|
||||
void registerLuaItemApi(lua_State *L)
|
||||
{
|
||||
@@ -261,7 +389,8 @@ void registerLuaItemApi(lua_State *L)
|
||||
|
||||
// items.register
|
||||
lua_pushcfunction(L, [](lua_State *L) -> int {
|
||||
if (lua_gettop(L) < 2 || !lua_isstring(L, 1) || !lua_istable(L, 2))
|
||||
if (lua_gettop(L) < 2 || !lua_isstring(L, 1) ||
|
||||
!lua_istable(L, 2))
|
||||
return 0;
|
||||
std::string itemId = lua_tostring(L, 1);
|
||||
StubItemDef def;
|
||||
@@ -337,10 +466,9 @@ void registerLuaItemApi(lua_State *L)
|
||||
if (lua_gettop(L) < 1 || !lua_isstring(L, 1))
|
||||
return 0;
|
||||
auto it = s_stubItems.find(lua_tostring(L, 1));
|
||||
lua_pushboolean(L,
|
||||
(it != s_stubItems.end() && it->second.unique) ?
|
||||
1 :
|
||||
0);
|
||||
lua_pushboolean(
|
||||
L,
|
||||
(it != s_stubItems.end() && it->second.unique) ? 1 : 0);
|
||||
return 1;
|
||||
});
|
||||
lua_setfield(L, -2, "is_unique");
|
||||
@@ -387,14 +515,16 @@ void registerLuaItemApi(lua_State *L)
|
||||
if (it != s_stubInventories.end()) {
|
||||
for (auto &slot : it->second) {
|
||||
if (slot.itemId == itemId) {
|
||||
int rem = std::min(count, slot.stackSize);
|
||||
int rem =
|
||||
std::min(count, slot.stackSize);
|
||||
slot.stackSize -= rem;
|
||||
count -= rem;
|
||||
removed += rem;
|
||||
}
|
||||
}
|
||||
it->second.erase(
|
||||
std::remove_if(it->second.begin(), it->second.end(),
|
||||
std::remove_if(it->second.begin(),
|
||||
it->second.end(),
|
||||
[](const StubInvSlot &s) {
|
||||
return s.stackSize <= 0;
|
||||
}),
|
||||
@@ -416,7 +546,8 @@ void registerLuaItemApi(lua_State *L)
|
||||
auto it = s_stubInventories.find(entityId);
|
||||
if (it != s_stubInventories.end()) {
|
||||
for (const auto &slot : it->second) {
|
||||
if (slot.itemId == itemId && slot.stackSize > 0) {
|
||||
if (slot.itemId == itemId &&
|
||||
slot.stackSize > 0) {
|
||||
has = true;
|
||||
break;
|
||||
}
|
||||
@@ -483,11 +614,14 @@ void registerLuaItemApi(lua_State *L)
|
||||
lua_rawgeti(L, 2, i);
|
||||
if (lua_istable(L, -1)) {
|
||||
StubInvSlot slot;
|
||||
if (lua_getfield(L, -1, "itemId"), lua_isstring(L, -1))
|
||||
if (lua_getfield(L, -1, "itemId"),
|
||||
lua_isstring(L, -1))
|
||||
slot.itemId = lua_tostring(L, -1);
|
||||
lua_pop(L, 1);
|
||||
if (lua_getfield(L, -1, "stackSize"), lua_isnumber(L, -1))
|
||||
slot.stackSize = (int)lua_tointeger(L, -1);
|
||||
if (lua_getfield(L, -1, "stackSize"),
|
||||
lua_isnumber(L, -1))
|
||||
slot.stackSize =
|
||||
(int)lua_tointeger(L, -1);
|
||||
lua_pop(L, 1);
|
||||
if (!slot.itemId.empty())
|
||||
slots.push_back(slot);
|
||||
@@ -538,11 +672,14 @@ void registerLuaItemApi(lua_State *L)
|
||||
lua_rawgeti(L, 2, i);
|
||||
if (lua_istable(L, -1)) {
|
||||
StubInvSlot slot;
|
||||
if (lua_getfield(L, -1, "itemId"), lua_isstring(L, -1))
|
||||
if (lua_getfield(L, -1, "itemId"),
|
||||
lua_isstring(L, -1))
|
||||
slot.itemId = lua_tostring(L, -1);
|
||||
lua_pop(L, 1);
|
||||
if (lua_getfield(L, -1, "stackSize"), lua_isnumber(L, -1))
|
||||
slot.stackSize = (int)lua_tointeger(L, -1);
|
||||
if (lua_getfield(L, -1, "stackSize"),
|
||||
lua_isnumber(L, -1))
|
||||
slot.stackSize =
|
||||
(int)lua_tointeger(L, -1);
|
||||
lua_pop(L, 1);
|
||||
if (!slot.itemId.empty())
|
||||
slots.push_back(slot);
|
||||
@@ -661,9 +798,8 @@ void registerLuaCharacterApi(lua_State *L)
|
||||
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_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");
|
||||
@@ -735,21 +871,18 @@ void registerLuaCharacterApi(lua_State *L)
|
||||
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);
|
||||
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));
|
||||
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);
|
||||
@@ -765,8 +898,7 @@ void registerLuaCharacterApi(lua_State *L)
|
||||
|
||||
// abort_pregnancy(motherId)
|
||||
lua_pushcfunction(L, [](lua_State *L) -> int {
|
||||
uint64_t motherId =
|
||||
static_cast<uint64_t>(lua_tointeger(L, 1));
|
||||
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;
|
||||
@@ -779,22 +911,19 @@ void registerLuaCharacterApi(lua_State *L)
|
||||
|
||||
// is_pregnant(motherId)
|
||||
lua_pushcfunction(L, [](lua_State *L) -> int {
|
||||
uint64_t motherId =
|
||||
static_cast<uint64_t>(lua_tointeger(L, 1));
|
||||
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);
|
||||
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));
|
||||
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) {
|
||||
@@ -807,11 +936,10 @@ void registerLuaCharacterApi(lua_State *L)
|
||||
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);
|
||||
it->second.pregnancyMaxProgress > 0.0f ?
|
||||
it->second.pregnancyProgress /
|
||||
it->second.pregnancyMaxProgress :
|
||||
0.0f);
|
||||
lua_setfield(L, -2, "ratio");
|
||||
return 1;
|
||||
});
|
||||
@@ -819,10 +947,8 @@ void registerLuaCharacterApi(lua_State *L)
|
||||
|
||||
// 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 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;
|
||||
@@ -844,18 +970,14 @@ void registerLuaCharacterApi(lua_State *L)
|
||||
|
||||
// get_parents(childId)
|
||||
lua_pushcfunction(L, [](lua_State *L) -> int {
|
||||
uint64_t childId =
|
||||
static_cast<uint64_t>(lua_tointeger(L, 1));
|
||||
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));
|
||||
lua_pushinteger(L, static_cast<lua_Integer>(
|
||||
it->second[i]));
|
||||
lua_rawseti(L, -2, static_cast<int>(i + 1));
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
@@ -864,18 +986,14 @@ void registerLuaCharacterApi(lua_State *L)
|
||||
|
||||
// get_children(parentId)
|
||||
lua_pushcfunction(L, [](lua_State *L) -> int {
|
||||
uint64_t parentId =
|
||||
static_cast<uint64_t>(lua_tointeger(L, 1));
|
||||
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));
|
||||
lua_pushinteger(L, static_cast<lua_Integer>(
|
||||
it->second[i]));
|
||||
lua_rawseti(L, -2, static_cast<int>(i + 1));
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
|
||||
@@ -77,6 +77,48 @@ bool ItemEditor::renderComponent(flecs::entity entity, ItemComponent &item)
|
||||
modified = true;
|
||||
}
|
||||
|
||||
// Action (GOAP action name executed on interact)
|
||||
char actionBuf[256];
|
||||
strncpy(actionBuf, item.action.c_str(), sizeof(actionBuf));
|
||||
actionBuf[sizeof(actionBuf) - 1] = '\0';
|
||||
if (ImGui::InputText("Action", actionBuf, sizeof(actionBuf))) {
|
||||
item.action = actionBuf;
|
||||
modified = true;
|
||||
}
|
||||
ImGui::SameLine();
|
||||
ImGui::TextDisabled("(?)");
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip(
|
||||
"Optional GOAP action name.\n"
|
||||
"If set, pressing E executes this action instead of picking up.");
|
||||
}
|
||||
|
||||
// Instance ID (for global state tracking)
|
||||
char instanceBuf[256];
|
||||
strncpy(instanceBuf, item.instanceId.c_str(), sizeof(instanceBuf));
|
||||
instanceBuf[sizeof(instanceBuf) - 1] = '\0';
|
||||
if (ImGui::InputText("Instance ID", instanceBuf, sizeof(instanceBuf))) {
|
||||
item.instanceId = instanceBuf;
|
||||
modified = true;
|
||||
}
|
||||
ImGui::SameLine();
|
||||
ImGui::TextDisabled("(?)");
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip(
|
||||
"Unique ID for save/load tracking.\n"
|
||||
"If set, disabled state persists across sessions.");
|
||||
}
|
||||
|
||||
// Disabled state
|
||||
if (ImGui::Checkbox("Disabled", &item.disabled)) {
|
||||
modified = true;
|
||||
}
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip(
|
||||
"Item has been picked up / consumed.\n"
|
||||
"Disabled items are hidden in game mode.");
|
||||
}
|
||||
|
||||
ImGui::PopID();
|
||||
return modified;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user