Compare commits

...

104 Commits

Author SHA1 Message Date
11530dd7fc Dialogue uses arrays 2026-05-03 01:25:25 +03:00
3fd167ebff More events added 2026-05-03 01:11:14 +03:00
5952a96ee6 Fixed fonts sizes 2026-05-03 00:02:24 +03:00
76c3ead4a8 game_start event works 2026-05-02 23:43:48 +03:00
39a053d4ee Game mode API 2026-05-02 20:25:16 +03:00
c5da977857 Dualogue event API doc/samples 2026-05-02 18:25:30 +03:00
3e7b0169d5 Lua behavior tree 2026-05-01 13:54:44 +03:00
f918c5cefb Better handling of lua tasks 2026-05-01 04:02:47 +03:00
976ced3731 Lua-based behavior tree node 2026-05-01 00:31:06 +03:00
0fd8deaf53 direct save/load action database 2026-04-30 20:21:18 +03:00
4d843c18c7 Lua API 2026-04-30 19:07:35 +03:00
0ed83966da Lua action APIs 2026-04-30 10:03:56 +03:00
998984f75a Root motion fixed now 2026-04-29 18:45:37 +03:00
02fa78764a Lua API implemented 2026-04-29 14:13:50 +03:00
abe6eef6b3 Modules update 2026-04-29 12:53:13 +03:00
cca732b41b Fixes for event system 2026-04-27 20:53:04 +03:00
8507a3a501 Event system 2026-04-27 18:45:01 +03:00
b9cce0248a Labels and actuators work perfectly! 2026-04-27 09:03:47 +03:00
fa49bb5005 Actuators 2026-04-27 06:55:05 +03:00
37441aa8fd Motion fixed 2026-04-27 06:04:14 +03:00
a1b74aa2d5 Now Path Following component works 2026-04-27 05:37:41 +03:00
c80d9c96e6 AI motion refactoring 2026-04-27 05:24:45 +03:00
a75db85027 Can disable physics stepping 2026-04-26 22:15:15 +03:00
7563937ab8 Prefab placement at cursor 2026-04-26 20:51:20 +03:00
425bb8411d Fixed crash with entity destruction 2026-04-26 17:56:19 +03:00
9b29b68b33 Prefab editing and window hiding 2026-04-26 17:45:57 +03:00
7557c710fb Added prefabs 2026-04-26 16:43:37 +03:00
ce2f6c1306 Navmesh generation works with cell grids 2026-04-26 14:28:54 +03:00
e0e8e316d4 Fixed navmesh 2026-04-26 00:50:55 +03:00
abd2dc22d3 Now can test smart object action on player character again 2026-04-26 00:00:36 +03:00
a5df60769f Now repeated smart object action works perfectly 2026-04-25 23:08:00 +03:00
75ba39895f Teleport node works 2026-04-25 21:55:21 +03:00
2cff982473 Delays and animation conflicts fixed 2026-04-25 20:59:50 +03:00
3bd2801d1d Smart objects work! 2026-04-25 09:04:12 +03:00
2e358275f0 Path following works great 2026-04-25 01:17:31 +03:00
5ed7552164 Path following 2026-04-25 00:11:21 +03:00
2b3482da88 Normal display tool implemented 2026-04-24 21:12:02 +03:00
1d2c330481 Material fixes, playing with navmesh 2026-04-24 20:29:54 +03:00
a0d2561587 navmesh 2026-04-24 04:37:38 +03:00
e95b904f4e Underwater effect 2026-04-23 01:55:09 +03:00
9d4fad1d10 Water plane 2026-04-23 01:29:53 +03:00
4335a8cb05 Skybox and sun 2026-04-23 00:38:20 +03:00
d55bf970e0 Swim animations 2026-04-22 21:38:33 +03:00
30814ea35a Added animations for swimming 2026-04-22 19:50:14 +03:00
35f50f7f51 Fixed buoyancy 2026-04-22 18:57:33 +03:00
ca5b5b3052 Buoyancy 2026-04-22 17:27:40 +03:00
7e4e8f6638 Color atlas serialization fixes 2026-04-21 11:42:46 +03:00
c6fb3bb463 Camera works now 2026-04-21 03:59:45 +03:00
1411990def Forward/backwards fixed 2026-04-21 03:27:53 +03:00
1488d7d918 Camera change... 2026-04-21 03:04:47 +03:00
ef708fa14a Fixed game mode menu 2026-04-20 22:10:17 +03:00
6d7fcb1157 Not so well working game mode 2026-04-20 20:21:27 +03:00
4313d190f9 Characters are fully functional now 2026-04-20 12:23:31 +03:00
a2173114b9 Some questionable changes 2026-04-19 23:50:00 +03:00
fb6881998c Added character physics but it does not work yet 2026-04-19 23:45:00 +03:00
529476d8cd Animation Tree was implemented 2026-04-19 22:06:46 +03:00
43e9fb330f Character Animation 2026-04-19 19:24:55 +03:00
a392eb0bf9 Character display 2026-04-19 18:19:48 +03:00
e2960d67e4 Added character models and lua scripts to groups 2026-04-18 11:35:38 +03:00
79b6af1fff Furniture vertical offset 2026-04-16 17:11:12 +03:00
863c401230 De-clutter the scene 2026-04-16 05:39:56 +03:00
eec0d8f6f7 Physics works 2026-04-15 23:49:24 +03:00
c2a1db5a65 The furniture is batched now 2026-04-15 04:28:30 +03:00
77f93659d5 Furiture placement works 2026-04-14 21:44:06 +03:00
febeb8ff8d Furniture enabled 2026-04-14 18:54:28 +03:00
611dcd0d46 FPS/batch counter works 2026-04-14 13:55:39 +03:00
e6494936d6 Atlas margin setting support 2026-04-14 13:09:14 +03:00
e3b90e8bba Fixed room connectivity code 2026-04-14 12:35:20 +03:00
7846082220 valid connection doors positions 2026-04-13 11:51:26 +03:00
a955f0b218 Almost good generation of room layout 2026-04-13 04:03:48 +03:00
da4a1a6722 Now camera switching works! 2026-04-12 19:22:18 +03:00
21879c2784 Groups and components 2026-04-12 04:04:40 +03:00
5377d1a75a Material editing fixes 2026-04-12 01:36:58 +03:00
03f72bdd77 roof color editing 2026-04-11 14:59:43 +03:00
3c47a87768 Roof now works 2026-04-08 20:06:40 +03:00
4ba28fe512 Town components indicators 2026-04-07 11:37:43 +03:00
9f2f0be4a3 Adjusted frame geometry 2026-04-07 11:21:01 +03:00
7d64ba30cb CellGrid+Frames done 2026-04-07 01:39:32 +03:00
82c0e8c6ce Fixed doors and windows geometry 2026-04-07 01:18:48 +03:00
3798f227a7 Fixed external door geometry 2026-04-06 23:00:20 +03:00
19e4d80741 Proper walls geometry with frames 2026-04-06 19:31:10 +03:00
d8122e3275 Fixed floor offset 2026-04-06 02:16:45 +03:00
0ebba40867 Corners finally match 2026-04-06 01:45:26 +03:00
64b03abb48 Fixed material for Lot geometry and CellGrid is re-created now 2026-04-05 19:51:22 +03:00
cfd9dde5da Town plaza fixed 2026-04-05 17:23:28 +03:00
07101fcc64 Fixed crash on texture change 2026-04-04 05:36:08 +03:00
b8c61da1f7 TriangleBuffer 2026-04-04 05:13:15 +03:00
b1413d6d00 Procedural texture implementation 2026-04-04 03:42:50 +03:00
f785339852 Added OgreProcedural support 2026-04-04 02:21:18 +03:00
c2cbd0974d Added StaticGeometry support 2026-04-04 02:12:38 +03:00
9e72a48457 Fixed LOD support 2026-04-04 00:42:27 +03:00
ebd875feac Static body check 2026-04-03 21:12:45 +03:00
2a2fd53c4f Camera and Light components 2026-04-03 20:08:47 +03:00
d4061386ec Fixed crashes 2026-04-03 18:43:54 +03:00
ec2695bc6c Now Mesh collider works 2026-04-02 05:54:58 +03:00
d68da8fc04 physics save/load added 2026-04-02 02:40:40 +03:00
2371ba3b19 added grid and angle buttons 2026-04-02 00:18:50 +03:00
bcf9291c03 Load meshes immediately 2026-04-01 20:11:43 +03:00
c31892ac05 Load/Save dialogues implemented 2026-04-01 00:04:06 +03:00
abd961ea0f Camera using CameraMan with working keys 2026-03-31 23:45:59 +03:00
e5f4bbfb90 Keyboard camera navigation works now 2026-03-31 22:47:03 +03:00
3ebb41647e scene save implemented 2026-03-31 12:57:59 +03:00
db15c6a48a Proper scene editor implementation 2026-03-31 02:48:01 +03:00
9c2adbb698 Added demo project to research root motion 2026-03-26 12:19:51 +03:00
591 changed files with 247442 additions and 5569 deletions

View File

@@ -78,6 +78,7 @@ add_subdirectory(assets/blender/buildings/parts)
add_subdirectory(assets/blender/characters)
add_subdirectory(resources)
add_subdirectory(src/text_editor)
add_subdirectory(src/features)
add_executable(Game Game.cpp ${WATER_SRC})
target_include_directories(Game PRIVATE src/gamedata)

View File

@@ -164,9 +164,9 @@ function(blender_import_vrm BLEND VRM EDITABLE RIG)
COMMAND ${BLENDER} -b -Y -P ${CMAKE_SOURCE_DIR}/assets/blender/scripts/import_vrm2.py -- ${VRM_NAME}.vrm ${BLEND} ${EDITABLE} ${RIG}
COMMAND ${CMAKE_COMMAND} -D FILE=${BLEND} -P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/check_file_size.cmake
COMMAND ${CMAKE_COMMAND} -E touch_nocreate ${BLEND}
DEPENDS ${CMAKE_SOURCE_DIR}/assets/blender/scripts/import_vrm2.py
DEPENDS ${CMAKE_SOURCE_DIR}/assets/blender/scripts/import_vrm2.py
${CMAKE_CURRENT_BINARY_DIR}/blender-addons-installed
${VRM}
${VRM} ${CMAKE_BINARY_DIR}/assets/blender/mixamo
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
)
endfunction()

View File

@@ -73,8 +73,6 @@ FileSystem=resources/fonts
[LuaScripts]
FileSystem=lua-scripts
#[Characters]
#FileSystem=./characters
[Audio]
FileSystem=./audio/gui

View File

@@ -13,6 +13,17 @@ foreach(DIR_NAME ${DIRECTORY_LIST})
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/${DIR_NAME})
list(APPEND TARGET_PATHS ${CMAKE_CURRENT_BINARY_DIR}/${DIR_NAME})
endforeach()
set(COPY_FILES main/flare.png)
set(TARGET_FILES)
foreach(PFILE ${COPY_FILES})
add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${PFILE}
COMMAND ${CMAKE_COMMAND} -E copy
${CMAKE_CURRENT_SOURCE_DIR}/${PFILE}
${CMAKE_CURRENT_BINARY_DIR}/${PFILE}
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/${PFILE}
)
list(APPEND TARGET_FILES ${CMAKE_CURRENT_BINARY_DIR}/${PFILE})
endforeach()
#add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/terrain/world_map.png
# COMMAND unzip -o ${CMAKE_CURRENT_SOURCE_DIR}/world_map.kra mergedimage.png -d ${CMAKE_CURRENT_BINARY_DIR}/world_map
@@ -31,4 +42,4 @@ add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/terrain/brushes.png
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/brushes.kra)
list(APPEND TARGET_PATHS ${CMAKE_CURRENT_BINARY_DIR}/terrain/brushes.png)
add_custom_target(stage_resources ALL DEPENDS ${TARGET_PATHS})
add_custom_target(stage_resources ALL DEPENDS ${TARGET_PATHS} ${TARGET_FILES})

View File

@@ -69,3 +69,27 @@ material Debug/Red2
}
}
/**
* Debug material for normal visualization overlay.
* Renders on top of everything (depth_check off, depth_write off)
* Uses vertex colours so each normal line can have its own colour.
* Rendered in overlay queue to appear on top of all geometry.
*/
material Debug/NormalOverlay
{
technique
{
pass
{
lighting off
depth_check off
depth_write off
ambient 1.0 1.0 1.0 1.0
diffuse vertexcolour
specular 0.0 0.0 0.0 1.0
cull_software none
cull_hardware none
scene_blend alpha_blend
}
}
}

BIN
resources/main/flare.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

View File

@@ -0,0 +1,19 @@
material Examples/Flare
{
technique
{
pass
{
lighting off
scene_blend add
depth_write off
diffuse vertexcolour
texture_unit
{
texture flare.png
}
}
}
}

View File

@@ -0,0 +1,4 @@
project(features)
add_subdirectory(characters)
add_subdirectory(sceneEditor)
add_subdirectory(editScene)

View File

@@ -0,0 +1,33 @@
project(characters)
find_package(OGRE REQUIRED COMPONENTS Bites Paging Terrain CONFIG)
find_package(ZLIB)
find_package(SDL2)
find_package(assimp REQUIRED CONFIG)
find_package(OgreProcedural REQUIRED CONFIG)
find_package(pugixml REQUIRED CONFIG)
find_package(flecs REQUIRED CONFIG)
find_package(Tracy REQUIRED CONFIG)
add_executable(demo main.cpp TimeEvents.cpp)
target_link_libraries(demo OgreMain OgreBites)
file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/resources.cfg"
DESTINATION "${CMAKE_CURRENT_BINARY_DIR}")
add_custom_command(TARGET demo POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
"${CMAKE_BINARY_DIR}/resources"
"${CMAKE_CURRENT_BINARY_DIR}/resources"
COMMAND ${CMAKE_COMMAND} -E copy
"${CMAKE_CURRENT_SOURCE_DIR}/resources/main/jaiqua.mesh"
"${CMAKE_CURRENT_BINARY_DIR}/resources/main/jaiqua.mesh"
COMMAND ${CMAKE_COMMAND} -E copy
"${CMAKE_CURRENT_SOURCE_DIR}/resources/main/jaiqua.skeleton"
"${CMAKE_CURRENT_BINARY_DIR}/resources/main/jaiqua.skeleton"
COMMAND ${CMAKE_COMMAND} -E copy
"${CMAKE_CURRENT_SOURCE_DIR}/resources/main/jaiqua.material"
"${CMAKE_CURRENT_BINARY_DIR}/resources/main/jaiqua.material"
COMMAND ${CMAKE_COMMAND} -E copy
"${CMAKE_CURRENT_SOURCE_DIR}/resources/main/blue_jaiqua.jpg"
"${CMAKE_CURRENT_BINARY_DIR}/resources/main/blue_jaiqua.jpg"
DEPENDS "${CMAKE_BINARY_DIR}/resources"
COMMENT "Copying generated resources from root build dir to local build dir"
)

View File

@@ -0,0 +1,207 @@
//
// TimeEvents.cpp
// OGRE
//
// Created by Chilly Willy on 11/29/25.
//
#include "TimeEvents.h"
#include <algorithm>
void TimeEventDispatcher::addEventList(const TimeEventList * list)
{
if (std::find(mEventLists.begin(), mEventLists.end(), list) == mEventLists.end())
{
mEventLists.push_back(list);
}
}
void TimeEventDispatcher::removeEventList(const TimeEventList * list)
{
auto i = std::find(mEventLists.begin(), mEventLists.end(), list);
if (i != mEventLists.end())
{
mEventLists.erase(i);
}
}
void TimeEventDispatcher::addListener(TimeEventListener * listener)
{
if (std::find(mListeners.begin(), mListeners.end(), listener) == mListeners.end())
{
mListeners.push_back(listener);
}
}
void TimeEventDispatcher::removeListener(TimeEventListener * listener)
{
auto i = std::find(mListeners.begin(), mListeners.end(), listener);
if (i != mListeners.end())
{
mListeners.erase(i);
}
}
/*
lastTime, thisTime, n=loops
lastTime is the thisTime from the last frame and thisTime will be the lastTime in the next frame.
Events at those times should only be dispatched once so we should include one and exclude the other.
If we include thisTime and exclude lastTime, then we need to dispatch events before we start playing,
eg when we call setTimePosition(), and we would need to somehow know whether it should be dispatched
as Forward or Backward.
If we include lastTime and exclude thisTime, then we don't need to do anything before playing but
we need to include the length/0 when we loop or finish, which requires special handling anyway.
Looping Forward:
next lastTime
thisTime < length thisTime lastTime <= t < thisTime
thisTime = length 0 lastTime <= t <= length
thisTime > length thisTime - n * length lastTime <= t <= length ... 0 <= t < thisTime
... 0 <= t <= length
Looping Backward:
next lastTime
thisTime > 0 thisTime lastTime >= t > thisTime
thisTime = 0 0 !!! lastTime >= t >= 0
thisTime < 0 thisTime + n * length lastTime >= t >= 0 ... length >= t > thisTime
... length >= t >= 0
Not-Looping Forward:
next lastTime
thisTime < length thisTime lastTime <= t < thisTime
thisTime = length length lastTime <= t <= length
thisTime > length length lastTime <= t <= length
Not-Looping Backward:
next lastTime
thisTime > 0 thisTime lastTime >= t > thisTime
thisTime = 0 0 lastTime >= t >= 0
thisTime < 0 0 lastTime >= t >= 0
*/
void TimeEventDispatcher::dispatch(float lastTime, float thisTime, int loops, float length)
{
if ((loops > 0) || (thisTime > lastTime))
{
while (loops--)
{
dispatchForwardInclusive(lastTime, length);
lastTime = 0.0f;
}
if (thisTime >= length)
{
dispatchForwardInclusive(lastTime, length);
}
else
{
dispatchForwardExclusive(lastTime, thisTime);
}
}
else if ((loops < 0) || (thisTime < lastTime))
{
if (lastTime == 0.0f)
{
/* length mod length = 0 mod length
Either we've already been looping backward and the last frame just happened
to stop on 0, in which case we already triggered its events, or we are just
starting to to loop backward, in which case we should not trigger events at
0 until we do a full backward run through the animation. If we haven't been
looping then we would not get in here because thisTime == lastTime == 0.0f.
For the same reason we also know loops < 0.
*/
lastTime = length;
++loops;
}
while (loops++)
{
dispatchBackwardInclusive(lastTime, 0.0f);
lastTime = length;
}
if (thisTime <= 0.0f)
{
dispatchBackwardInclusive(lastTime, 0.0f);
}
else
{
dispatchBackwardExclusive(lastTime, thisTime);
}
}
else
{
// Nothing doing
}
}
void TimeEventDispatcher::dispatchForwardInclusive(float lastTime, float thisTime)
{
for (const TimeEventList * events : mEventLists)
{
for (auto ie = events->begin(); ie != events->end(); ++ie)
{
if (ie->first >= lastTime && ie->first <= thisTime)
{
dispatchEvent(ie->second, TED_FORWARD);
}
}
}
}
void TimeEventDispatcher::dispatchForwardExclusive(float lastTime, float thisTime)
{
for (const TimeEventList * events : mEventLists)
{
for (auto ie = events->begin(); ie != events->end(); ++ie)
{
if (ie->first >= lastTime && ie->first < thisTime)
{
dispatchEvent(ie->second, TED_FORWARD);
}
}
}
}
void TimeEventDispatcher::dispatchBackwardInclusive(float lastTime, float thisTime)
{
for (const TimeEventList * events : mEventLists)
{
for (auto ie = events->rbegin(); ie != events->rend(); ++ie)
{
if (ie->first <= lastTime && ie->first >= thisTime)
{
dispatchEvent(ie->second, TED_BACKWARD);
}
}
}
}
void TimeEventDispatcher::dispatchBackwardExclusive(float lastTime, float thisTime)
{
for (const TimeEventList * events : mEventLists)
{
for (auto ie = events->rbegin(); ie != events->rend(); ++ie)
{
if (ie->first <= lastTime && ie->first > thisTime)
{
dispatchEvent(ie->second, TED_BACKWARD);
}
}
}
}
void TimeEventDispatcher::dispatchEvent(const std::string & name, TimeEventDirection direction)
{
for (TimeEventListener * listener : mListeners)
{
listener->eventOccurred(name, direction);
}
}

View File

@@ -0,0 +1,58 @@
//
// TimeEvents.h
// OGRE
//
// Created by Chilly Willy on 11/29/25.
//
#ifndef INCLUDE_OGRE_TIME_EVENTS_H
#define INCLUDE_OGRE_TIME_EVENTS_H
#include <map>
#include <vector>
#include <string>
typedef std::multimap<float, std::string> TimeEventList;
enum TimeEventDirection
{
TED_FORWARD,
TED_BACKWARD,
};
class TimeEventListener
{
public:
virtual void eventOccurred(const std::string & name, TimeEventDirection direction) {}
};
class TimeEventDispatcher
{
public:
void addEventList(const TimeEventList * list);
void removeEventList(const TimeEventList * list);
void addListener(TimeEventListener * listener);
void removeListener(TimeEventListener * listener);
void dispatch(float lastTime, float thisTime, int loops, float length);
private:
void dispatchForwardInclusive(float lastTime, float thisTime);
void dispatchForwardExclusive(float lastTime, float thisTime);
void dispatchBackwardInclusive(float lastTime, float thisTime);
void dispatchBackwardExclusive(float lastTime, float thisTime);
void dispatchEvent(const std::string & name, TimeEventDirection direction);
std::vector<const TimeEventList *> mEventLists;
std::vector<TimeEventListener *> mListeners;
};
#endif /* INCLUDE_OGRE_TIME_EVENTS_H */

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,82 @@
# Ogre Core Resources
[OgreInternal]
#FileSystem=./Media/Main
FileSystem=resources/main
FileSystem=resources/shaderlib
#FileSystem=./Media/RTShaderLib
FileSystem=resources/terrain
# Resources required by OgreBites::Trays
[Essential]
#Zip=./Media/packs/SdkTrays.zip
#Zip=./Media/packs/profiler.zip
## this line will end up in the [Essential] group
#FileSystem=./Media/thumbnails
# Common sample resources needed by many of the samples.
# Rarely used resources should be separately loaded by the
# samples which require them.
[General]
FileSystem=skybox
FileSystem=resources/buildings
FileSystem=resources/buildings/parts/pier
FileSystem=resources/buildings/parts/furniture
FileSystem=resources/vehicles
FileSystem=resources/debug
FileSystem=resources/fonts
# PBR media must come before the scripts that reference it
#FileSystem=./Media/PBR
#FileSystem=./Media/PBR/filament
#FileSystem=./Media/materials/programs/GLSL
#FileSystem=./Media/materials/programs/GLSL120
#FileSystem=./Media/materials/programs/GLSL150
#FileSystem=./Media/materials/programs/GLSL400
#FileSystem=./Media/materials/programs/GLSLES
#FileSystem=./Media/materials/programs/SPIRV
#FileSystem=./Media/materials/programs/Cg
#FileSystem=./Media/materials/programs/HLSL
#FileSystem=./Media/materials/programs/HLSL_Cg
#FileSystem=./Media/materials/scripts
#FileSystem=./Media/materials/textures
#FileSystem=./Media/materials/textures/terrain
#FileSystem=./Media/models
#FileSystem=./Media/particle
#FileSystem=./Media/DeferredShadingMedia
#FileSystem=./Media/DeferredShadingMedia/DeferredShading/post
#FileSystem=./Media/PCZAppMedia
#FileSystem=./Media/materials/scripts/SSAO
#FileSystem=./Media/materials/textures/SSAO
#FileSystem=./Media/volumeTerrain
#FileSystem=./Media/CSMShadows
#Zip=./Media/packs/cubemap.zip
#Zip=./Media/packs/cubemapsJS.zip
#Zip=./Media/packs/dragon.zip
#Zip=./Media/packs/fresneldemo.zip
#Zip=./Media/packs/ogredance.zip
#Zip=./Media/packs/Sinbad.zip
#Zip=./Media/packs/skybox.zip
#Zip=./Media/volumeTerrain/volumeTerrainBig.zip
#Zip=./Media/packs/DamagedHelmet.zip
#Zip=./Media/packs/filament_shaders.zip
#[BSPWorld]
#Zip=./Media/packs/oa_rpg3dm2.pk3
#Zip=./Media/packs/ogretestmap.zip
# Materials for visual tests
#[Tests]
#FileSystem=/media/slapin/library/ogre/ogre-sdk/Tests/Media
[LuaScripts]
FileSystem=lua-scripts
#[Characters]
#FileSystem=./characters
[Audio]
FileSystem=./audio/gui
[Water]
FileSystem=water

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

View File

@@ -0,0 +1,24 @@
material jaiqua
{
technique
{
pass
{
texture_unit
{
texture blue_jaiqua.jpg
tex_address_mode clamp
}
rtshader_system
{
// In case the system uses the RTSS, the following line will ensure
// that hardware animation is used.
// Alternatively, you can derive this information programatically via
// HardwareSkinningFactory::prepareEntityForSkinning
hardware_skinning 38 2 dual_quaternion true false
}
}
}
}

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,502 @@
project(editScene)
set(CMAKE_CXX_STANDARD 17)
find_package(OGRE REQUIRED COMPONENTS Bites Overlay MeshLodGenerator CONFIG)
find_package(flecs REQUIRED CONFIG)
find_package(nlohmann_json REQUIRED)
find_package(SDL2 REQUIRED)
find_package(Jolt REQUIRED)
find_package(OgreProcedural REQUIRED CONFIG)
# Build RecastNavigation from copied source (RTTI-compatible)
add_subdirectory(recastnavigation)
# Collect all source files
set(EDITSCENE_SOURCES
main.cpp
EditorApp.cpp
GameMode.cpp
systems/EditorUISystem.cpp
systems/SceneSerializer.cpp
systems/PhysicsSystem.cpp
systems/BuoyancySystem.cpp
systems/EditorSunSystem.cpp
systems/EditorSkyboxSystem.cpp
systems/EditorWaterPlaneSystem.cpp
systems/LightSystem.cpp
systems/CameraSystem.cpp
systems/LodSystem.cpp
systems/StaticGeometrySystem.cpp
systems/ProceduralTextureSystem.cpp
systems/ProceduralMaterialSystem.cpp
systems/ProceduralMeshSystem.cpp
systems/CellGridSystem.cpp
systems/NormalDebugSystem.cpp
systems/RoomLayoutSystem.cpp
systems/FurnitureLibrary.cpp
systems/StartupMenuSystem.cpp
systems/PlayerControllerSystem.cpp
systems/CharacterSlotSystem.cpp
systems/AnimationTreeSystem.cpp
systems/BehaviorTreeSystem.cpp
systems/NavMeshSystem.cpp
recast/TileCacheNavMesh.cpp
recast/PartitionedMesh.cpp
recast/fastlz.c
systems/CharacterSystem.cpp
systems/SmartObjectSystem.cpp
components/SmartObjectModule.cpp
ui/SmartObjectEditor.cpp
components/GoapPlannerModule.cpp
systems/GoapRunnerSystem.cpp
systems/PathFollowingSystem.cpp
systems/GoapPlannerSystem.cpp
components/GoapRunnerModule.cpp
components/PathFollowingModule.cpp
ui/GoapRunnerEditor.cpp
ui/PathFollowingEditor.cpp
ui/GoapPlannerEditor.cpp
systems/ActuatorSystem.cpp
ui/ActuatorEditor.cpp
components/ActuatorModule.cpp
systems/EventBus.cpp
systems/EventHandlerSystem.cpp
ui/EventHandlerEditor.cpp
components/EventHandlerModule.cpp
systems/PrefabSystem.cpp
ui/PrefabInstanceEditor.cpp
systems/ItemSystem.cpp
components/ItemModule.cpp
components/InventoryModule.cpp
ui/ItemEditor.cpp
ui/InventoryEditor.cpp
ui/TransformEditor.cpp
ui/RenderableEditor.cpp
ui/PhysicsColliderEditor.cpp
ui/RigidBodyEditor.cpp
ui/LightEditor.cpp
ui/CameraEditor.cpp
ui/LodEditor.cpp
ui/LodSettingsEditor.cpp
ui/StaticGeometryEditor.cpp
ui/StaticGeometryMemberEditor.cpp
ui/ProceduralTextureEditor.cpp
ui/ProceduralMaterialEditor.cpp
ui/PrimitiveEditor.cpp
ui/TriangleBufferEditor.cpp
ui/CharacterSlotsEditor.cpp
ui/AnimationTreeEditor.cpp
ui/AnimationTreeTemplateEditor.cpp
ui/CharacterEditor.cpp
ui/CellGridEditor.cpp
ui/LotEditor.cpp
ui/DistrictEditor.cpp
ui/TownEditor.cpp
ui/RoofEditor.cpp
ui/RoomEditor.cpp
ui/ClearAreaEditor.cpp
ui/FurnitureTemplateEditor.cpp
ui/StartupMenuEditor.cpp
ui/PlayerControllerEditor.cpp
ui/BuoyancyInfoEditor.cpp
ui/GoapBlackboardEditor.cpp
ui/BehaviorTreeEditor.cpp
ui/InlineBehaviorTreeEditor.cpp
ui/NavMeshEditor.cpp
ui/ActionDatabaseEditor.cpp
ui/ActionDatabaseSingletonEditor.cpp
ui/ActionDebugEditor.cpp
ui/ComponentRegistration.cpp
components/GoapBlackboard.cpp
components/GoapExpression.cpp
components/GoapGoal.cpp
components/ActionDatabase.cpp
components/ActionDatabaseModule.cpp
components/ActionDebugModule.cpp
components/GoapBlackboardModule.cpp
components/NavMeshModule.cpp
components/LightModule.cpp
components/CameraModule.cpp
components/LodModule.cpp
components/StaticGeometryModule.cpp
components/ProceduralTextureModule.cpp
components/ProceduralMaterialModule.cpp
components/PrimitiveModule.cpp
components/TriangleBufferModule.cpp
components/CharacterSlotsModule.cpp
components/AnimationTreeModule.cpp
components/AnimationTreeTemplateModule.cpp
components/AnimationTree.cpp
components/CharacterModule.cpp
components/CellGridModule.cpp
components/CellGridEditorsModule.cpp
components/CellGrid.cpp
components/StartupMenuModule.cpp
components/PlayerControllerModule.cpp
components/DialogueComponentModule.cpp
systems/DialogueSystem.cpp
ui/DialogueEditor.cpp
components/BuoyancyInfoModule.cpp
components/WaterPhysicsModule.cpp
components/WaterPlaneModule.cpp
components/SunModule.cpp
components/SkyboxModule.cpp
camera/EditorCamera.cpp
gizmo/Gizmo.cpp
gizmo/Cursor3D.cpp
physics/physics.cpp
lua/LuaState.cpp
lua/LuaEntityApi.cpp
lua/LuaComponentApi.cpp
lua/LuaEventApi.cpp
lua/LuaActionApi.cpp
lua/LuaBehaviorTreeApi.cpp
lua/LuaGameModeApi.cpp
)
set(EDITSCENE_HEADERS
EditorApp.hpp
components/Transform.hpp
components/Renderable.hpp
components/EntityName.hpp
components/Relationship.hpp
components/PhysicsCollider.hpp
components/RigidBody.hpp
components/BuoyancyInfo.hpp
components/WaterPhysics.hpp
components/WaterPlane.hpp
components/Sun.hpp
components/Skybox.hpp
components/Light.hpp
components/Camera.hpp
components/Lod.hpp
components/LodSettings.hpp
components/StaticGeometry.hpp
components/StaticGeometryMember.hpp
components/ProceduralTexture.hpp
components/ProceduralMaterial.hpp
components/Primitive.hpp
components/TriangleBuffer.hpp
components/CharacterSlots.hpp
components/AnimationTree.hpp
components/Character.hpp
components/CellGrid.hpp
components/StartupMenu.hpp
components/PlayerController.hpp
components/DialogueComponent.hpp
systems/DialogueSystem.hpp
ui/DialogueEditor.hpp
systems/StartupMenuSystem.hpp
systems/PlayerControllerSystem.hpp
systems/EditorUISystem.hpp
systems/CellGridSystem.hpp
systems/NormalDebugSystem.hpp
systems/RoomLayoutSystem.hpp
systems/FurnitureLibrary.hpp
systems/ProceduralMaterialSystem.hpp
systems/ProceduralMeshSystem.hpp
systems/CharacterSlotSystem.hpp
systems/AnimationTreeSystem.hpp
systems/BehaviorTreeSystem.hpp
systems/NavMeshSystem.hpp
recast/TileCacheNavMesh.hpp
recast/PartitionedMesh.hpp
recast/fastlz.h
systems/CharacterSystem.hpp
systems/SmartObjectSystem.hpp
components/SmartObject.hpp
ui/SmartObjectEditor.hpp
components/GoapPlanner.hpp
components/PathFollowing.hpp
components/GoapRunner.hpp
ui/GoapPlannerEditor.hpp
ui/GoapRunnerEditor.hpp
ui/PathFollowingEditor.hpp
systems/PrefabSystem.hpp
systems/GoapRunnerSystem.hpp
systems/PathFollowingSystem.hpp
systems/GoapPlannerSystem.hpp
components/Actuator.hpp
ui/ActuatorEditor.hpp
systems/EventBus.hpp
components/EventHandler.hpp
systems/EventHandlerSystem.hpp
ui/EventHandlerEditor.hpp
components/PrefabInstance.hpp
ui/PrefabInstanceEditor.hpp
systems/ItemSystem.hpp
components/Item.hpp
components/Inventory.hpp
ui/ItemEditor.hpp
ui/InventoryEditor.hpp
systems/ProceduralTextureSystem.hpp
systems/StaticGeometrySystem.hpp
systems/SceneSerializer.hpp
systems/PhysicsSystem.hpp
systems/BuoyancySystem.hpp
systems/EditorSunSystem.hpp
systems/EditorSkyboxSystem.hpp
systems/EditorWaterPlaneSystem.hpp
systems/LightSystem.hpp
systems/CameraSystem.hpp
systems/LodSystem.hpp
ui/ComponentEditor.hpp
ui/ComponentRegistry.hpp
ui/ComponentRegistration.hpp
ui/TransformEditor.hpp
ui/RenderableEditor.hpp
ui/PhysicsColliderEditor.hpp
ui/RigidBodyEditor.hpp
ui/LightEditor.hpp
ui/CameraEditor.hpp
ui/LodEditor.hpp
ui/LodSettingsEditor.hpp
ui/StaticGeometryEditor.hpp
ui/StaticGeometryMemberEditor.hpp
ui/ProceduralTextureEditor.hpp
ui/ProceduralMaterialEditor.hpp
ui/PrimitiveEditor.hpp
ui/TriangleBufferEditor.hpp
ui/CharacterSlotsEditor.hpp
ui/AnimationTreeEditor.hpp
ui/AnimationTreeTemplateEditor.hpp
ui/CharacterEditor.hpp
ui/CellGridEditor.hpp
ui/LotEditor.hpp
ui/DistrictEditor.hpp
ui/TownEditor.hpp
ui/RoofEditor.hpp
ui/RoomEditor.hpp
ui/ClearAreaEditor.hpp
ui/FurnitureTemplateEditor.hpp
ui/StartupMenuEditor.hpp
ui/PlayerControllerEditor.hpp
ui/BuoyancyInfoEditor.hpp
ui/GoapBlackboardEditor.hpp
ui/GoapBlackboardComponentEditor.hpp
ui/BehaviorTreeEditor.hpp
ui/InlineBehaviorTreeEditor.hpp
ui/NavMeshEditor.hpp
ui/NavMeshGeometrySourceEditor.hpp
ui/ActionDatabaseEditor.hpp
ui/ActionDatabaseSingletonEditor.hpp
ui/ActionDebugEditor.hpp
components/GoapBlackboard.hpp
components/GoapExpression.hpp
components/NavMesh.hpp
components/BehaviorTree.hpp
components/GoapAction.hpp
components/GoapGoal.hpp
components/ActionDatabase.hpp
components/ActionDebug.hpp
camera/EditorCamera.hpp
gizmo/Gizmo.hpp
gizmo/Cursor3D.hpp
physics/physics.h
lua/LuaState.hpp
lua/LuaEntityApi.hpp
lua/LuaComponentApi.hpp
lua/LuaEventApi.hpp
lua/LuaActionApi.hpp
lua/LuaBehaviorTreeApi.hpp
lua/LuaGameModeApi.hpp
)
add_executable(editSceneEditor ${EDITSCENE_SOURCES} ${EDITSCENE_HEADERS})
# Define JPH_DEBUG_RENDERER for physics debug drawing
target_compile_definitions(editSceneEditor PRIVATE JPH_DEBUG_RENDERER)
target_link_libraries(editSceneEditor
OgreMain
OgreBites
OgreOverlay
OgreMeshLodGenerator
flecs::flecs_static
nlohmann_json::nlohmann_json
Jolt::Jolt
OgreProcedural::OgreProcedural
RecastNavigation::Recast
RecastNavigation::Detour
RecastNavigation::DetourTileCache
RecastNavigation::DetourCrowd
RecastNavigation::DebugUtils
lua
)
target_include_directories(editSceneEditor PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/recastnavigation/Recast/Include
${CMAKE_CURRENT_SOURCE_DIR}/recastnavigation/Detour/Include
${CMAKE_CURRENT_SOURCE_DIR}/recastnavigation/DetourTileCache/Include
${CMAKE_CURRENT_SOURCE_DIR}/recastnavigation/DetourCrowd/Include
${CMAKE_CURRENT_SOURCE_DIR}/recastnavigation/DebugUtils/Include
${CMAKE_SOURCE_DIR}/src/lua/lua-5.4.8/src
${CMAKE_SOURCE_DIR}/src/lua/lpeg-1.1.0
)
# ---------------------------------------------------------------------------
# Tests: Lua API standalone tests
# ---------------------------------------------------------------------------
# These standalone tests verify the Lua API functions work correctly.
# They do not require OGRE or Flecs - only Lua and the core component types.
# Test: Entity Lua API
add_executable(entity_lua_test
tests/entity_lua_test.cpp
tests/lua_test_stubs.cpp
)
target_link_libraries(entity_lua_test
lua
)
target_include_directories(entity_lua_test PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/tests
${CMAKE_SOURCE_DIR}/src/lua/lua-5.4.8/src
)
# Test: Component Lua API
add_executable(component_lua_test
tests/component_lua_test.cpp
tests/lua_test_stubs.cpp
)
target_link_libraries(component_lua_test
lua
)
target_include_directories(component_lua_test PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/tests
${CMAKE_SOURCE_DIR}/src/lua/lua-5.4.8/src
)
# Test: Event Lua API
add_executable(event_lua_test
tests/event_lua_test.cpp
tests/lua_test_stubs.cpp
)
target_link_libraries(event_lua_test
lua
)
target_include_directories(event_lua_test PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/tests
${CMAKE_SOURCE_DIR}/src/lua/lua-5.4.8/src
)
# Test: ActionDatabase Lua API
add_executable(action_db_lua_test
tests/action_db_lua_test.cpp
components/ActionDatabase.cpp
components/GoapBlackboard.cpp
components/GoapGoal.cpp
components/GoapExpression.cpp
lua/LuaActionApi.cpp
)
target_link_libraries(action_db_lua_test
lua
flecs::flecs_static
nlohmann_json::nlohmann_json
)
target_include_directories(action_db_lua_test PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/tests
${CMAKE_SOURCE_DIR}/src/lua/lua-5.4.8/src
)
# Test: Behavior Tree Lua API
add_executable(behavior_tree_lua_test
tests/behavior_tree_lua_test.cpp
lua/LuaBehaviorTreeApi.cpp
lua/LuaGameModeApi.cpp
lua/LuaEntityApi.cpp
GameMode.cpp
components/GoapBlackboard.cpp
)
target_link_libraries(behavior_tree_lua_test
lua
flecs::flecs_static
OgreMain
)
target_include_directories(behavior_tree_lua_test PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/tests
${CMAKE_SOURCE_DIR}/src/lua/lua-5.4.8/src
)
target_compile_definitions(behavior_tree_lua_test PRIVATE flecs_STATIC)
# Test: EventParams C++ API (standalone, no Lua dependency)
add_executable(event_params_test
tests/event_params_test.cpp
)
target_link_libraries(event_params_test
lua
)
target_include_directories(event_params_test PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/tests
${CMAKE_SOURCE_DIR}/src/lua/lua-5.4.8/src
)
# Test: Game Mode Lua API
add_executable(game_mode_lua_test
tests/game_mode_lua_test.cpp
tests/lua_test_stubs.cpp
)
target_link_libraries(game_mode_lua_test
lua
)
target_include_directories(game_mode_lua_test PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/tests
${CMAKE_SOURCE_DIR}/src/lua/lua-5.4.8/src
)
# Copy local resources (materials, etc.)
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/resources")
file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/resources"
DESTINATION "${CMAKE_CURRENT_BINARY_DIR}")
endif()
# Copy resources from main build
add_custom_command(TARGET editSceneEditor POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
"${CMAKE_BINARY_DIR}/resources"
"${CMAKE_CURRENT_BINARY_DIR}/resources"
COMMAND ${CMAKE_COMMAND} -E copy_directory
"${CMAKE_BINARY_DIR}/characters"
"${CMAKE_CURRENT_BINARY_DIR}/characters"
COMMAND ${CMAKE_COMMAND} -E copy_directory
"${CMAKE_BINARY_DIR}/lua-scripts"
"${CMAKE_CURRENT_BINARY_DIR}/lua-scripts"
COMMAND ${CMAKE_COMMAND} -E copy
"${CMAKE_CURRENT_SOURCE_DIR}/resources.cfg"
"${CMAKE_CURRENT_BINARY_DIR}/resources.cfg"
# Re-copy editScene-specific resources so they aren't overwritten
COMMAND ${CMAKE_COMMAND} -E copy_directory
"${CMAKE_CURRENT_SOURCE_DIR}/resources"
"${CMAKE_CURRENT_BINARY_DIR}/resources"
COMMENT "Copying resources to editSceneEditor build directory"
)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,273 @@
#ifndef EDITSCENE_EDITORAPP_HPP
#define EDITSCENE_EDITORAPP_HPP
#pragma once
#include <Ogre.h>
#include <OgreApplicationContext.h>
#include <OgreInput.h>
#include <OgreOverlaySystem.h>
#include <OgreImGuiOverlay.h>
#include <OgreRenderTargetListener.h>
#include <flecs.h>
#include <memory>
#include "lua/LuaState.hpp"
// Forward declarations
class EditorUISystem;
class EditorCamera;
class EditorPhysicsSystem;
class EditorLightSystem;
class EditorCameraSystem;
class EditorLodSystem;
class StaticGeometrySystem;
class ProceduralTextureSystem;
class ProceduralMaterialSystem;
class ProceduralMeshSystem;
class CharacterSlotSystem;
class AnimationTreeSystem;
class BehaviorTreeSystem;
class NavMeshSystem;
class CharacterSystem;
class CellGridSystem;
class RoomLayoutSystem;
class StartupMenuSystem;
class DialogueSystem;
class PlayerControllerSystem;
class BuoyancySystem;
class EditorSunSystem;
class EditorSkyboxSystem;
class EditorWaterPlaneSystem;
class NormalDebugSystem;
class SmartObjectSystem;
class GoapRunnerSystem;
class PathFollowingSystem;
class GoapPlannerSystem;
class ActuatorSystem;
class EventHandlerSystem;
class ItemSystem;
class EditorApp;
/**
* Shared input state for game mode
*/
struct GameInputState {
bool w = false;
bool a = false;
bool s = false;
bool d = false;
bool shift = false;
bool e = false;
bool f = false;
bool ePressed = false;
bool fPressed = false;
float mouseDeltaX = 0.0f;
float mouseDeltaY = 0.0f;
bool mouseMoved = false;
void resetPerFrame()
{
mouseMoved = false;
mouseDeltaX = 0.0f;
mouseDeltaY = 0.0f;
ePressed = false;
fPressed = false;
}
};
/**
* RenderTargetListener for ImGui frame management
* Handles NewFrame() in preViewportUpdate and EndFrame() in postViewportUpdate
*/
class ImGuiRenderListener : public Ogre::RenderTargetListener {
public:
ImGuiRenderListener(Ogre::ImGuiOverlay *imguiOverlay,
EditorUISystem *uiSystem,
Ogre::RenderWindow *renderWindow,
EditorApp *editorApp);
void
preViewportUpdate(const Ogre::RenderTargetViewportEvent &evt) override;
void
postViewportUpdate(const Ogre::RenderTargetViewportEvent &evt) override;
private:
Ogre::ImGuiOverlay *m_imguiOverlay;
EditorUISystem *m_uiSystem;
Ogre::RenderWindow *m_renderWindow;
EditorApp *m_editorApp;
// Timer for delta time calculation
Ogre::Timer m_timer;
unsigned long m_lastTime = 0;
float m_deltaTime = 0.0f;
// Frame stats (updated in postViewportUpdate)
int m_lastBatchCount = 0;
};
/**
* Main application class for the scene editor / game
*/
class EditorApp : public OgreBites::ApplicationContext,
public OgreBites::InputListener {
public:
enum class GameMode { Editor, Game };
enum class GamePlayState { Menu, Playing, Paused };
EditorApp();
virtual ~EditorApp();
// OgreBites::ApplicationContext overrides
void setup() override;
bool frameRenderingQueued(const Ogre::FrameEvent &evt) override;
void locateResources() override;
// OgreBites::InputListener overrides
bool mouseMoved(const OgreBites::MouseMotionEvent &evt) override;
bool mousePressed(const OgreBites::MouseButtonEvent &evt) override;
bool mouseReleased(const OgreBites::MouseButtonEvent &evt) override;
bool keyPressed(const OgreBites::KeyboardEvent &evt) override;
bool keyReleased(const OgreBites::KeyboardEvent &evt) override;
// Scene setup
void setupLights();
void createGrid();
void createAxes();
// ECS setup
void setupECS();
void createDefaultEntities();
// Game mode management
void setGameMode(GameMode mode);
GameMode getGameMode() const
{
return m_gameMode;
}
// Debug buoyancy
void setDebugBuoyancy(bool enabled);
bool getDebugBuoyancy() const
{
return m_debugBuoyancy;
}
GamePlayState getGamePlayState() const
{
return m_gamePlayState;
}
void setGamePlayState(GamePlayState state);
void startNewGame(const Ogre::String &scenePath);
void clearScene();
// Input access
GameInputState &getGameInputState()
{
return m_gameInput;
}
// Getters
flecs::entity getSelectedEntity() const;
Ogre::SceneManager *getSceneManager() const
{
return m_sceneMgr;
}
flecs::world *getWorld()
{
return &m_world;
}
EditorCamera *getEditorCamera() const
{
return m_camera.get();
}
AnimationTreeSystem *getAnimationTreeSystem() const
{
return m_animationTreeSystem.get();
}
CharacterSlotSystem *getCharacterSlotSystem() const
{
return m_characterSlotSystem.get();
}
StartupMenuSystem *getStartupMenuSystem() const
{
return m_startupMenuSystem.get();
}
DialogueSystem *getDialogueSystem() const
{
return m_dialogueSystem.get();
}
ActuatorSystem *getActuatorSystem() const
{
return m_actuatorSystem.get();
}
EventHandlerSystem *getEventHandlerSystem() const
{
return m_eventHandlerSystem.get();
}
Ogre::ImGuiOverlay *getImGuiOverlay() const
{
return m_imguiOverlay;
}
private:
// Ogre objects
Ogre::SceneManager *m_sceneMgr;
Ogre::OverlaySystem *m_overlaySystem;
Ogre::ImGuiOverlay *m_imguiOverlay;
// ECS
flecs::world m_world;
std::vector<flecs::entity> m_defaultEntities;
// Editor systems
std::unique_ptr<EditorUISystem> m_uiSystem;
std::unique_ptr<EditorCamera> m_camera;
std::unique_ptr<ImGuiRenderListener> m_imguiListener;
std::unique_ptr<EditorPhysicsSystem> m_physicsSystem;
std::unique_ptr<BuoyancySystem> m_buoyancySystem;
std::unique_ptr<EditorSunSystem> m_sunSystem;
std::unique_ptr<EditorSkyboxSystem> m_skyboxSystem;
std::unique_ptr<EditorWaterPlaneSystem> m_waterPlaneSystem;
std::unique_ptr<EditorLightSystem> m_lightSystem;
std::unique_ptr<EditorCameraSystem> m_cameraSystem;
std::unique_ptr<EditorLodSystem> m_lodSystem;
std::unique_ptr<StaticGeometrySystem> m_staticGeometrySystem;
std::unique_ptr<ProceduralTextureSystem> m_proceduralTextureSystem;
std::unique_ptr<ProceduralMaterialSystem> m_proceduralMaterialSystem;
std::unique_ptr<ProceduralMeshSystem> m_proceduralMeshSystem;
std::unique_ptr<CharacterSlotSystem> m_characterSlotSystem;
std::unique_ptr<AnimationTreeSystem> m_animationTreeSystem;
std::unique_ptr<BehaviorTreeSystem> m_behaviorTreeSystem;
std::unique_ptr<NavMeshSystem> m_navMeshSystem;
std::unique_ptr<CharacterSystem> m_characterSystem;
std::unique_ptr<CellGridSystem> m_cellGridSystem;
std::unique_ptr<NormalDebugSystem> m_normalDebugSystem;
std::unique_ptr<RoomLayoutSystem> m_roomLayoutSystem;
std::unique_ptr<SmartObjectSystem> m_smartObjectSystem;
std::unique_ptr<GoapRunnerSystem> m_goapRunnerSystem;
std::unique_ptr<PathFollowingSystem> m_pathFollowingSystem;
std::unique_ptr<GoapPlannerSystem> m_goapPlannerSystem;
std::unique_ptr<ActuatorSystem> m_actuatorSystem;
std::unique_ptr<EventHandlerSystem> m_eventHandlerSystem;
std::unique_ptr<ItemSystem> m_itemSystem;
// Game systems
std::unique_ptr<StartupMenuSystem> m_startupMenuSystem;
std::unique_ptr<DialogueSystem> m_dialogueSystem;
std::unique_ptr<PlayerControllerSystem> m_playerControllerSystem;
// State
uint16_t m_currentModifiers;
GameMode m_gameMode = GameMode::Editor;
GamePlayState m_gamePlayState = GamePlayState::Menu;
GameInputState m_gameInput;
bool m_setupComplete = false;
bool m_debugBuoyancy = false;
// Lua scripting
editScene::LuaState m_lua;
// Editor visualization nodes
Ogre::SceneNode *m_gridNode = nullptr;
Ogre::SceneNode *m_axisNode = nullptr;
};
#endif // EDITSCENE_EDITORAPP_HPP

View File

@@ -0,0 +1,37 @@
#include "GameMode.hpp"
namespace editScene
{
namespace
{
/// Global game mode state.
GameMode s_gameMode = GameMode::Editor;
/// Global gameplay state (only meaningful in game mode).
GamePlayState s_gamePlayState = GamePlayState::Menu;
} // anonymous namespace
void setEditSceneGameMode(GameMode mode) noexcept
{
s_gameMode = mode;
}
void setEditSceneGamePlayState(GamePlayState state) noexcept
{
s_gamePlayState = state;
}
GameMode getGameMode() noexcept
{
return s_gameMode;
}
GamePlayState getGamePlayState() noexcept
{
return s_gamePlayState;
}
} // namespace editScene

View File

@@ -0,0 +1,101 @@
#ifndef EDITSCENE_GAMEMODE_HPP
#define EDITSCENE_GAMEMODE_HPP
#pragma once
/**
* @file GameMode.hpp
*
* Global game mode query functions for the editScene feature.
*
* These functions allow any code in the editScene feature to query
* whether the application is currently in editor mode or game mode,
* and what the current gameplay state is, without needing a direct
* pointer to EditorApp.
*
* The EditorApp sets the current mode via setEditSceneGameMode()
* during its lifetime. Code outside the editScene feature should
* continue to use EditorApp::getGameMode() / getGamePlayState()
* directly.
*/
namespace editScene
{
/**
* Application mode: editor or game.
*/
enum class GameMode { Editor, Game };
/**
* Play state when in game mode.
*/
enum class GamePlayState { Menu, Playing, Paused };
// ---------------------------------------------------------------------------
// Global state management (called by EditorApp)
// ---------------------------------------------------------------------------
/**
* Set the current game mode. Called by EditorApp on mode changes.
*/
void setEditSceneGameMode(GameMode mode) noexcept;
/**
* Set the current gameplay state. Called by EditorApp on state changes.
*/
void setEditSceneGamePlayState(GamePlayState state) noexcept;
// ---------------------------------------------------------------------------
// Query functions
// ---------------------------------------------------------------------------
/**
* Return the current application mode.
*/
GameMode getGameMode() noexcept;
/**
* Return the current gameplay state (only meaningful in game mode).
*/
GamePlayState getGamePlayState() noexcept;
// ---------------------------------------------------------------------------
// Predicates
// ---------------------------------------------------------------------------
/** True when the application is in editor mode. */
inline bool isEditorMode() noexcept
{
return getGameMode() == GameMode::Editor;
}
/** True when the application is in game mode (any play state). */
inline bool isGameMode() noexcept
{
return getGameMode() == GameMode::Game;
}
/** True when in game mode and the gameplay state is Playing. */
inline bool isGamePlaying() noexcept
{
return getGameMode() == GameMode::Game &&
getGamePlayState() == GamePlayState::Playing;
}
/** True when in game mode and the gameplay state is Menu. */
inline bool isGameMenu() noexcept
{
return getGameMode() == GameMode::Game &&
getGamePlayState() == GamePlayState::Menu;
}
/** True when in game mode and the gameplay state is Paused. */
inline bool isGamePaused() noexcept
{
return getGameMode() == GameMode::Game &&
getGamePlayState() == GamePlayState::Paused;
}
} // namespace editScene
#endif // EDITSCENE_GAMEMODE_HPP

View File

@@ -0,0 +1,237 @@
#include "EditorCamera.hpp"
#include <OgreViewport.h>
EditorCamera::EditorCamera(Ogre::SceneManager *sceneMgr,
Ogre::RenderWindow *window)
: m_sceneMgr(sceneMgr)
, m_camera(nullptr)
, m_cameraNode(nullptr)
, m_targetNode(nullptr)
, m_position(0, 5, 15)
, m_target(0, 0, 0)
, m_distance(15.0f)
, m_yaw(0.0f)
, m_pitch(-20.0f)
, m_rotating(false)
, m_panning(false)
, m_fpsMode(false)
, m_lastMouseX(0)
, m_lastMouseY(0)
, m_keyW(false)
, m_keyS(false)
, m_keyA(false)
, m_keyD(false)
, m_keyQ(false)
, m_keyE(false)
{
// Create camera
m_camera = sceneMgr->createCamera("EditorCamera");
m_camera->setNearClipDistance(0.1f);
m_camera->setFarClipDistance(1000.0f);
m_camera->setAutoAspectRatio(true);
// Create camera node
m_cameraNode = sceneMgr->getRootSceneNode()->createChildSceneNode(
"EditorCameraNode");
m_cameraNode->attachObject(m_camera);
// Create target node
m_targetNode = sceneMgr->getRootSceneNode()->createChildSceneNode(
"EditorCameraTarget");
// Setup viewport
Ogre::Viewport *vp = window->addViewport(m_camera);
vp->setBackgroundColour(Ogre::ColourValue(0.1f, 0.1f, 0.1f));
// Setup CameraMan for proper camera control (no roll)
m_cameraMan = std::make_unique<OgreBites::CameraMan>(m_cameraNode);
m_cameraMan->setStyle(OgreBites::CS_ORBIT);
m_cameraMan->setTarget(m_targetNode);
m_cameraMan->setYawPitchDist(Ogre::Degree(m_yaw), Ogre::Degree(m_pitch),
m_distance);
updateCameraPosition();
}
EditorCamera::~EditorCamera() = default;
void EditorCamera::update(float deltaTime)
{
if (m_fpsMode) {
updateFPSMovement(deltaTime);
}
// CameraMan handles the positioning
m_cameraMan->setYawPitchDist(Ogre::Degree(m_yaw), Ogre::Degree(m_pitch),
m_distance);
m_cameraMan->frameRendered(Ogre::FrameEvent{ deltaTime });
}
void EditorCamera::updateFPSMovement(float deltaTime)
{
// Get camera's forward and right vectors from CameraMan's derived orientation
Ogre::Quaternion orientation = m_camera->getDerivedOrientation();
Ogre::Vector3 forward = orientation * Ogre::Vector3::UNIT_Z;
Ogre::Vector3 right = orientation * Ogre::Vector3::UNIT_X;
Ogre::Vector3 up = Ogre::Vector3::UNIT_Y; // World up for Q/E
// Flatten forward vector to horizontal plane for WSAD movement
Ogre::Vector3 forwardHorizontal =
Ogre::Vector3(forward.x, 0, forward.z);
if (forwardHorizontal.squaredLength() > 0.0001f) {
forwardHorizontal.normalise();
}
Ogre::Vector3 movement = Ogre::Vector3::ZERO;
// WSAD movement (horizontal plane)
if (m_keyW)
movement -= forwardHorizontal;
if (m_keyS)
movement += forwardHorizontal;
if (m_keyA)
movement -= right;
if (m_keyD)
movement += right;
// Q/E for vertical movement
if (m_keyQ)
movement -= up;
if (m_keyE)
movement += up;
// Apply movement
if (movement.squaredLength() > 0.0001f) {
movement.normalise();
m_target += movement * FPS_SPEED * deltaTime;
m_targetNode->setPosition(m_target);
m_cameraMan->setTarget(m_targetNode);
}
}
void EditorCamera::handleMouseMove(const OgreBites::MouseMotionEvent &evt)
{
int dx = evt.x - m_lastMouseX;
int dy = evt.y - m_lastMouseY;
m_lastMouseX = evt.x;
m_lastMouseY = evt.y;
if (m_rotating) {
m_yaw -= dx * ROTATION_SPEED;
m_pitch -= dy * ROTATION_SPEED;
// Clamp pitch
if (m_pitch > 89.0f)
m_pitch = 89.0f;
if (m_pitch < -89.0f)
m_pitch = -89.0f;
}
if (m_panning) {
// Get right and up vectors from camera's orientation
Ogre::Quaternion orientation =
m_camera->getDerivedOrientation();
Ogre::Vector3 right = orientation * Ogre::Vector3::UNIT_X;
Ogre::Vector3 up = orientation * Ogre::Vector3::UNIT_Y;
Ogre::Vector3 pan = right * (-dx * PAN_SPEED * m_distance) +
up * (dy * PAN_SPEED * m_distance);
m_target += pan;
m_targetNode->setPosition(m_target);
m_cameraMan->setTarget(m_targetNode);
}
}
void EditorCamera::handleMousePress(const OgreBites::MouseButtonEvent &evt)
{
m_lastMouseX = evt.x;
m_lastMouseY = evt.y;
if (evt.button == OgreBites::BUTTON_RIGHT) {
m_rotating = true;
m_fpsMode = true; // Enable FPS mode when right mouse is held
} else if (evt.button == OgreBites::BUTTON_MIDDLE) {
m_panning = true;
}
}
void EditorCamera::handleMouseRelease(const OgreBites::MouseButtonEvent &evt)
{
if (evt.button == OgreBites::BUTTON_RIGHT) {
m_rotating = false;
m_fpsMode =
false; // Disable FPS mode when right mouse is released
// Reset key states
m_keyW = m_keyS = m_keyA = m_keyD = m_keyQ = m_keyE = false;
} else if (evt.button == OgreBites::BUTTON_MIDDLE) {
m_panning = false;
}
}
void EditorCamera::handleKeyboard(const OgreBites::KeyboardEvent &evt)
{
// Track key states for FPS movement
bool pressed = (evt.type == OgreBites::KEYDOWN);
switch (evt.keysym.sym) {
case 'w':
case 'W':
m_keyW = pressed;
break;
case 's':
case 'S':
m_keyS = pressed;
break;
case 'a':
case 'A':
m_keyA = pressed;
break;
case 'd':
case 'D':
m_keyD = pressed;
break;
case 'q':
case 'Q':
m_keyQ = pressed;
break;
case 'e':
case 'E':
m_keyE = pressed;
break;
}
}
void EditorCamera::focusOn(const Ogre::Vector3 &point)
{
m_target = point;
m_targetNode->setPosition(m_target);
m_cameraMan->setTarget(m_targetNode);
}
void EditorCamera::setPosition(const Ogre::Vector3 &pos)
{
m_target = pos;
m_targetNode->setPosition(m_target);
m_cameraMan->setTarget(m_targetNode);
}
Ogre::Ray EditorCamera::getMouseRay(float screenX, float screenY) const
{
// Convert pixel coordinates to normalized viewport coordinates (0-1)
Ogre::Viewport *viewport = m_camera->getViewport();
if (!viewport)
return Ogre::Ray();
float normX = screenX / viewport->getActualWidth();
float normY = screenY / viewport->getActualHeight();
return m_camera->getCameraToViewportRay(normX, normY);
}
void EditorCamera::updateCameraPosition()
{
// CameraMan handles the positioning
m_targetNode->setPosition(m_target);
m_cameraMan->setYawPitchDist(Ogre::Degree(m_yaw), Ogre::Degree(m_pitch),
m_distance);
}

View File

@@ -0,0 +1,113 @@
#ifndef EDITSCENE_EDITORCAMERA_HPP
#define EDITSCENE_EDITORCAMERA_HPP
#pragma once
#include <memory>
#include <Ogre.h>
#include <OgreCamera.h>
#include <OgreSceneManager.h>
#include <OgreRenderWindow.h>
#include <OgreInput.h>
#include <OgreCameraMan.h>
/**
* Editor camera controller using OgreBites::CameraMan
* Supports orbit (default) and FPS modes
*/
class EditorCamera {
public:
EditorCamera(Ogre::SceneManager *sceneMgr, Ogre::RenderWindow *window);
~EditorCamera();
/**
* Update camera (called each frame)
*/
void update(float deltaTime);
/**
* Handle input events
*/
void handleMouseMove(const OgreBites::MouseMotionEvent &evt);
void handleMousePress(const OgreBites::MouseButtonEvent &evt);
void handleMouseRelease(const OgreBites::MouseButtonEvent &evt);
void handleKeyboard(const OgreBites::KeyboardEvent &evt);
/**
* Get the camera
*/
Ogre::Camera *getCamera() const
{
return m_camera;
}
/**
* Focus camera on a point
*/
void focusOn(const Ogre::Vector3 &point);
/**
* Set camera position
*/
void setPosition(const Ogre::Vector3 &pos);
/**
* Get camera position
*/
Ogre::Vector3 getPosition() const
{
return m_position;
}
/**
* Get ray from mouse position
*/
Ogre::Ray getMouseRay(float screenX, float screenY) const;
/**
* Check if in FPS mode
*/
bool isFPSMode() const
{
return m_fpsMode;
}
private:
void updateCameraPosition();
void updateFPSMovement(float deltaTime);
Ogre::SceneManager *m_sceneMgr;
Ogre::Camera *m_camera;
Ogre::SceneNode *m_cameraNode;
Ogre::SceneNode *m_targetNode;
// Use OgreBites::CameraMan for proper camera control
std::unique_ptr<OgreBites::CameraMan> m_cameraMan;
// Camera state
Ogre::Vector3 m_position;
Ogre::Vector3 m_target;
float m_distance;
float m_yaw;
float m_pitch;
// Input state
bool m_rotating;
bool m_panning;
bool m_fpsMode;
int m_lastMouseX;
int m_lastMouseY;
// Keyboard state for FPS movement
bool m_keyW;
bool m_keyS;
bool m_keyA;
bool m_keyD;
bool m_keyQ;
bool m_keyE;
// Movement speeds
static constexpr float ROTATION_SPEED = 0.3f;
static constexpr float PAN_SPEED = 0.01f;
static constexpr float FPS_SPEED = 10.0f;
};
#endif // EDITSCENE_EDITORCAMERA_HPP

View File

@@ -0,0 +1,515 @@
#include "ActionDatabase.hpp"
#ifndef OGRE_STUB_H
#include <OgreLogManager.h>
#include <OgreResourceGroupManager.h>
#endif
#include <flecs.h>
#include <nlohmann/json.hpp>
#include <fstream>
#include <filesystem>
// ---------------------------------------------------------------------------
// Singleton
// ---------------------------------------------------------------------------
ActionDatabase &ActionDatabase::getSingleton()
{
static ActionDatabase instance;
return instance;
}
ActionDatabase *ActionDatabase::getSingletonPtr()
{
return &getSingleton();
}
// ---------------------------------------------------------------------------
// Find methods
// ---------------------------------------------------------------------------
const GoapAction *ActionDatabase::findAction(const Ogre::String &name) const
{
for (const auto &action : actions) {
if (action.name == name)
return &action;
}
return nullptr;
}
GoapAction *ActionDatabase::findAction(const Ogre::String &name)
{
for (auto &action : actions) {
if (action.name == name)
return &action;
}
return nullptr;
}
const GoapGoal *ActionDatabase::findGoal(const Ogre::String &name) const
{
for (const auto &goal : goals) {
if (goal.name == name)
return &goal;
}
return nullptr;
}
GoapGoal *ActionDatabase::findGoal(const Ogre::String &name)
{
for (auto &goal : goals) {
if (goal.name == name)
return &goal;
}
return nullptr;
}
// ---------------------------------------------------------------------------
// Add or replace
// ---------------------------------------------------------------------------
void ActionDatabase::addOrReplaceAction(const GoapAction &action)
{
for (auto &a : actions) {
if (a.name == action.name) {
a = action;
return;
}
}
actions.push_back(action);
}
void ActionDatabase::addOrReplaceGoal(const GoapGoal &goal)
{
for (auto &g : goals) {
if (g.name == goal.name) {
g = goal;
return;
}
}
goals.push_back(goal);
}
// ---------------------------------------------------------------------------
// Remove methods
// ---------------------------------------------------------------------------
bool ActionDatabase::removeAction(const Ogre::String &name)
{
for (auto it = actions.begin(); it != actions.end(); ++it) {
if (it->name == name) {
actions.erase(it);
return true;
}
}
return false;
}
bool ActionDatabase::removeGoal(const Ogre::String &name)
{
for (auto it = goals.begin(); it != goals.end(); ++it) {
if (it->name == name) {
goals.erase(it);
return true;
}
}
return false;
}
// ---------------------------------------------------------------------------
// Selection / validation
// ---------------------------------------------------------------------------
const GoapGoal *
ActionDatabase::selectBestGoal(const GoapBlackboard &blackboard) const
{
const GoapGoal *best = nullptr;
int bestPriority = -1;
for (const auto &goal : goals) {
if (!goal.isValid(blackboard))
continue;
if (goal.priority > bestPriority) {
bestPriority = goal.priority;
best = &goal;
}
}
return best;
}
std::vector<const GoapAction *>
ActionDatabase::getValidActions(const GoapBlackboard &blackboard) const
{
std::vector<const GoapAction *> result;
for (const auto &action : actions) {
if (action.canRun(blackboard))
result.push_back(&action);
}
return result;
}
// ---------------------------------------------------------------------------
// Clear
// ---------------------------------------------------------------------------
void ActionDatabase::clear()
{
actions.clear();
goals.clear();
}
// ---------------------------------------------------------------------------
// ActionDatabaseComponent
// ---------------------------------------------------------------------------
void ActionDatabaseComponent::syncToSingleton() const
{
auto &db = ActionDatabase::getSingleton();
db.clear();
for (const auto &action : actions)
db.addOrReplaceAction(action);
for (const auto &goal : goals)
db.addOrReplaceGoal(goal);
}
// ---------------------------------------------------------------------------
// JSON serialization helpers (local to this translation unit)
// ---------------------------------------------------------------------------
static nlohmann::json serializeGoapBlackboard(const GoapBlackboard &bb)
{
nlohmann::json json;
json["bits"] = (uint64_t)bb.bits;
json["mask"] = (uint64_t)bb.mask;
if (bb.bitmask != ~0ULL)
json["bitmask"] = (uint64_t)bb.bitmask;
if (!bb.values.empty()) {
json["values"] = nlohmann::json::object();
for (const auto &pair : bb.values)
json["values"][pair.first] = pair.second;
}
if (!bb.floatValues.empty()) {
json["floatValues"] = nlohmann::json::object();
for (const auto &pair : bb.floatValues)
json["floatValues"][pair.first] = pair.second;
}
if (!bb.vec3Values.empty()) {
json["vec3Values"] = nlohmann::json::object();
for (const auto &pair : bb.vec3Values) {
nlohmann::json v;
v.push_back(pair.second.x);
v.push_back(pair.second.y);
v.push_back(pair.second.z);
json["vec3Values"][pair.first] = v;
}
}
return json;
}
static void deserializeGoapBlackboard(GoapBlackboard &bb,
const nlohmann::json &json)
{
bb.bits = json.value("bits", (uint64_t)0);
bb.mask = json.value("mask", (uint64_t)0);
bb.bitmask = json.value("bitmask", ~0ULL);
bb.values.clear();
if (json.contains("values") && json["values"].is_object()) {
for (auto &[key, val] : json["values"].items())
bb.values[key] = val.get<int>();
}
bb.floatValues.clear();
if (json.contains("floatValues") && json["floatValues"].is_object()) {
for (auto &[key, val] : json["floatValues"].items())
bb.floatValues[key] = val.get<float>();
}
bb.vec3Values.clear();
if (json.contains("vec3Values") && json["vec3Values"].is_object()) {
for (auto &[key, val] : json["vec3Values"].items()) {
if (val.is_array() && val.size() >= 3)
bb.vec3Values[key] =
Ogre::Vector3(val[0].get<float>(),
val[1].get<float>(),
val[2].get<float>());
}
}
}
static nlohmann::json serializeBehaviorTreeNode(const BehaviorTreeNode &node)
{
nlohmann::json json;
json["type"] = node.type;
if (!node.name.empty())
json["name"] = node.name;
if (!node.params.empty())
json["params"] = node.params;
if (!node.children.empty()) {
json["children"] = nlohmann::json::array();
for (const auto &child : node.children)
json["children"].push_back(
serializeBehaviorTreeNode(child));
}
return json;
}
static void deserializeBehaviorTreeNode(BehaviorTreeNode &node,
const nlohmann::json &json)
{
node.type = json.value("type", "task");
node.name = json.value("name", "");
node.params = json.value("params", "");
node.children.clear();
if (json.contains("children") && json["children"].is_array()) {
for (const auto &childJson : json["children"]) {
BehaviorTreeNode child;
deserializeBehaviorTreeNode(child, childJson);
node.children.push_back(child);
}
}
}
static nlohmann::json serializeGoapAction(const GoapAction &action)
{
nlohmann::json json;
json["name"] = action.name;
json["cost"] = action.cost;
json["preconditions"] = serializeGoapBlackboard(action.preconditions);
json["effects"] = serializeGoapBlackboard(action.effects);
if (action.preconditionMask != ~0ULL)
json["preconditionMask"] = action.preconditionMask;
json["behaviorTree"] = serializeBehaviorTreeNode(action.behaviorTree);
if (!action.behaviorTreeName.empty())
json["behaviorTreeName"] = action.behaviorTreeName;
return json;
}
static void deserializeGoapAction(GoapAction &action,
const nlohmann::json &json)
{
action.name = json.value("name", "Unnamed");
action.cost = json.value("cost", 1);
if (json.contains("preconditions"))
deserializeGoapBlackboard(action.preconditions,
json["preconditions"]);
if (json.contains("effects"))
deserializeGoapBlackboard(action.effects, json["effects"]);
action.preconditionMask = json.value("preconditionMask", ~0ULL);
if (json.contains("behaviorTree"))
deserializeBehaviorTreeNode(action.behaviorTree,
json["behaviorTree"]);
action.behaviorTreeName = json.value("behaviorTreeName", "");
}
static nlohmann::json serializeGoapGoal(const GoapGoal &goal)
{
nlohmann::json json;
json["name"] = goal.name;
json["priority"] = goal.priority;
json["target"] = serializeGoapBlackboard(goal.target);
if (!goal.condition.empty())
json["condition"] = goal.condition;
return json;
}
static void deserializeGoapGoal(GoapGoal &goal, const nlohmann::json &json)
{
goal.name = json.value("name", "Unnamed");
goal.priority = json.value("priority", 1);
if (json.contains("target"))
deserializeGoapBlackboard(goal.target, json["target"]);
goal.condition = json.value("condition", "");
}
// ---------------------------------------------------------------------------
// saveToJson / loadFromJson
// ---------------------------------------------------------------------------
bool ActionDatabase::saveToJson(const std::string &filename)
{
try {
// Resolve the filesystem path from the "General" resource group
Ogre::ResourceGroupManager &rgm =
Ogre::ResourceGroupManager::getSingleton();
const Ogre::ResourceGroupManager::LocationList &locations =
rgm.getResourceLocationList("General");
if (locations.empty()) {
Ogre::LogManager::getSingleton().logMessage(
"ActionDatabase::saveToJson: "
"no resource locations for group 'General'");
return false;
}
// Use the first location's path
std::string dir = locations.begin()->archive->getName();
std::string filepath = dir + "/" + filename;
// Backup existing file
if (std::filesystem::exists(filepath)) {
std::string backup = filepath + ".bak";
try {
std::filesystem::copy_file(
filepath, backup,
std::filesystem::copy_options::
overwrite_existing);
} catch (const std::exception &e) {
Ogre::LogManager::getSingleton().logMessage(
"ActionDatabase::saveToJson: "
"backup failed: " +
Ogre::String(e.what()));
}
}
const ActionDatabase &db = getSingleton();
nlohmann::json root;
root["actions"] = nlohmann::json::array();
for (const auto &action : db.actions)
root["actions"].push_back(serializeGoapAction(action));
root["goals"] = nlohmann::json::array();
for (const auto &goal : db.goals)
root["goals"].push_back(serializeGoapGoal(goal));
// Save bit names
nlohmann::json bitNames = nlohmann::json::array();
for (int i = 0; i < 64; i++) {
const char *name = GoapBlackboard::getBitName(i);
if (name) {
nlohmann::json entry;
entry["index"] = i;
entry["name"] = name;
bitNames.push_back(entry);
}
}
if (!bitNames.empty())
root["bitNames"] = bitNames;
std::ofstream file(filepath);
if (!file.is_open()) {
Ogre::LogManager::getSingleton().logMessage(
"ActionDatabase::saveToJson: "
"failed to open " +
filepath);
return false;
}
file << root.dump(4);
file.close();
Ogre::LogManager::getSingleton().logMessage(
"ActionDatabase saved to " + filepath);
return true;
} catch (const std::exception &e) {
Ogre::LogManager::getSingleton().logMessage(
"ActionDatabase::saveToJson error: " +
Ogre::String(e.what()));
return false;
}
}
bool ActionDatabase::loadFromJson(const std::string &filename)
{
try {
// Resolve the filesystem path from the "General" resource group
Ogre::ResourceGroupManager &rgm =
Ogre::ResourceGroupManager::getSingleton();
const Ogre::ResourceGroupManager::LocationList &locations =
rgm.getResourceLocationList("General");
if (locations.empty()) {
Ogre::LogManager::getSingleton().logMessage(
"ActionDatabase::loadFromJson: "
"no resource locations for group 'General'");
return false;
}
std::string dir = locations.begin()->archive->getName();
std::string filepath = dir + "/" + filename;
if (!std::filesystem::exists(filepath)) {
Ogre::LogManager::getSingleton().logMessage(
"ActionDatabase::loadFromJson: "
"file not found: " +
filepath);
return false;
}
std::ifstream file(filepath);
if (!file.is_open()) {
Ogre::LogManager::getSingleton().logMessage(
"ActionDatabase::loadFromJson: "
"failed to open " +
filepath);
return false;
}
nlohmann::json root;
try {
file >> root;
} catch (const nlohmann::json::parse_error &e) {
Ogre::LogManager::getSingleton().logMessage(
"ActionDatabase::loadFromJson: "
"JSON parse error in " +
filepath + ": " + Ogre::String(e.what()));
return false;
}
file.close();
ActionDatabase &db = getSingleton();
// Load actions (add/replace)
if (root.contains("actions") && root["actions"].is_array()) {
for (const auto &actionJson : root["actions"]) {
GoapAction action;
deserializeGoapAction(action, actionJson);
db.addOrReplaceAction(action);
}
}
// Load goals (add/replace)
if (root.contains("goals") && root["goals"].is_array()) {
for (const auto &goalJson : root["goals"]) {
GoapGoal goal;
deserializeGoapGoal(goal, goalJson);
db.addOrReplaceGoal(goal);
}
}
// Load bit names
if (root.contains("bitNames") && root["bitNames"].is_array()) {
for (const auto &entry : root["bitNames"]) {
if (entry.contains("index") &&
entry.contains("name"))
GoapBlackboard::setBitName(
entry["index"].get<int>(),
entry["name"]
.get<std::string>());
}
}
Ogre::LogManager::getSingleton().logMessage(
"ActionDatabase loaded from " + filepath);
return true;
} catch (const std::exception &e) {
Ogre::LogManager::getSingleton().logMessage(
"ActionDatabase::loadFromJson error: " +
Ogre::String(e.what()));
return false;
}
}
// ---------------------------------------------------------------------------
// reloadFromSceneComponents
// ---------------------------------------------------------------------------
void ActionDatabase::reloadFromSceneComponents(flecs::world &world)
{
// First, load from file (if available) — this is done by the caller
// before calling this function. Here we just re-sync from scene
// entities so that scene-defined actions are applied on top.
// Iterate all entities with ActionDatabaseComponent
world.each([](flecs::entity e, ActionDatabaseComponent &dbComp) {
(void)e;
dbComp.syncToSingleton();
});
}

View File

@@ -0,0 +1,114 @@
#ifndef EDITSCENE_ACTION_DATABASE_HPP
#define EDITSCENE_ACTION_DATABASE_HPP
#pragma once
#include "GoapAction.hpp"
#include "GoapGoal.hpp"
#include <vector>
#include <unordered_map>
#include <string>
// Forward declaration for reloadFromSceneComponents
namespace flecs
{
class world;
}
/**
* Global action database singleton.
*
* Holds the master list of GOAP actions and goals that characters can use.
* This is a singleton accessible from anywhere in the codebase.
* The ActionDatabaseComponent on a scene entity stores the actions/goals
* and syncs them to the singleton on scene load.
*/
class ActionDatabase {
public:
/** Get the singleton instance */
static ActionDatabase &getSingleton();
static ActionDatabase *getSingletonPtr();
std::vector<GoapAction> actions;
std::vector<GoapGoal> goals;
// Find an action by name
const GoapAction *findAction(const Ogre::String &name) const;
GoapAction *findAction(const Ogre::String &name);
// Find a goal by name
const GoapGoal *findGoal(const Ogre::String &name) const;
GoapGoal *findGoal(const Ogre::String &name);
// Add or replace an action by name
void addOrReplaceAction(const GoapAction &action);
// Add or replace a goal by name
void addOrReplaceGoal(const GoapGoal &goal);
// Remove an action by name
bool removeAction(const Ogre::String &name);
// Remove a goal by name
bool removeGoal(const Ogre::String &name);
// Select the best valid goal for a given blackboard
// Returns nullptr if no valid goal exists
const GoapGoal *selectBestGoal(const GoapBlackboard &blackboard) const;
// Build a list of actions that can run from a given blackboard state
std::vector<const GoapAction *>
getValidActions(const GoapBlackboard &blackboard) const;
// Clear all actions and goals
void clear();
/**
* Save the action database to a JSON file.
* Creates a backup of the existing file (if any) by appending ".bak".
* The file is written to the filesystem path resolved from the
* "General" resource group.
*
* @param filename The filename (e.g. "actions.json").
* @return true on success.
*/
static bool saveToJson(const std::string &filename);
/**
* Load the action database from a JSON file.
* The file is located via the "General" resource group.
* On failure (file not found, parse error) the error is logged
* and the database is left unchanged.
*
* @param filename The filename (e.g. "actions.json").
* @return true on success.
*/
static bool loadFromJson(const std::string &filename);
/**
* Re-process all ActionDatabaseComponent entities in the given
* Flecs world: clear the singleton and re-sync from every entity
* that carries the component. This is used after a reload so
* scene-defined actions are re-applied on top of the file.
*/
static void reloadFromSceneComponents(flecs::world &world);
private:
ActionDatabase() = default;
~ActionDatabase() = default;
ActionDatabase(const ActionDatabase &) = delete;
ActionDatabase &operator=(const ActionDatabase &) = delete;
};
/**
* Flecs component that stores action database data on a scene entity.
* When set on an entity, it syncs its contents to the ActionDatabase singleton.
*/
struct ActionDatabaseComponent {
std::vector<GoapAction> actions;
std::vector<GoapGoal> goals;
/** Sync this component's data to the ActionDatabase singleton */
void syncToSingleton() const;
};
#endif // EDITSCENE_ACTION_DATABASE_HPP

View File

@@ -0,0 +1,47 @@
#include "ActionDatabase.hpp"
#include "ActionDebug.hpp"
#include "BehaviorTree.hpp"
#include "GoapAction.hpp"
#include "GoapGoal.hpp"
#include "GoapBlackboard.hpp"
#include "../ui/ComponentRegistration.hpp"
#include "../ui/ActionDatabaseEditor.hpp"
#include "../ui/BehaviorTreeEditor.hpp"
REGISTER_COMPONENT_GROUP("Action Database", "AI", ActionDatabaseComponent,
ActionDatabaseEditor)
{
registry.registerComponent<ActionDatabaseComponent>(
"Action Database", "AI",
std::make_unique<ActionDatabaseEditor>(),
// Adder
[](flecs::entity e) {
if (!e.has<ActionDatabaseComponent>())
e.set<ActionDatabaseComponent>({});
},
// Remover
[](flecs::entity e) {
if (e.has<ActionDatabaseComponent>())
e.remove<ActionDatabaseComponent>();
});
}
REGISTER_COMPONENT_GROUP("Behavior Tree", "AI", BehaviorTreeComponent,
BehaviorTreeEditor)
{
registry.registerComponent<BehaviorTreeComponent>(
"Behavior Tree", "AI", std::make_unique<BehaviorTreeEditor>(),
// Adder
[](flecs::entity e) {
if (!e.has<BehaviorTreeComponent>()) {
BehaviorTreeComponent bt;
bt.root.type = "sequence";
e.set<BehaviorTreeComponent>(bt);
}
},
// Remover
[](flecs::entity e) {
if (e.has<BehaviorTreeComponent>())
e.remove<BehaviorTreeComponent>();
});
}

View File

@@ -0,0 +1,39 @@
#ifndef EDITSCENE_ACTION_DEBUG_HPP
#define EDITSCENE_ACTION_DEBUG_HPP
#pragma once
#include "GoapBlackboard.hpp"
#include <Ogre.h>
#include <vector>
#include <unordered_map>
/**
* Per-character action debug component.
*
* Allows test-running individual actions and inspecting the character's
* local blackboard state. Used for debugging AI behavior in the editor.
*
* Path following animation states have been moved to PathFollowingComponent.
*/
struct ActionDebug {
// Character's local GOAP blackboard
GoapBlackboard blackboard;
// Currently selected action for test-running
Ogre::String selectedActionName;
// Currently selected goal for testing
Ogre::String selectedGoalName;
// Test-run state
bool isRunning = false;
float runTimer = 0.0f;
Ogre::String currentActionName;
// Debug output
Ogre::String lastResult;
ActionDebug() = default;
};
#endif // EDITSCENE_ACTION_DEBUG_HPP

View File

@@ -0,0 +1,21 @@
#include "ActionDebug.hpp"
#include "../ui/ComponentRegistration.hpp"
#include "../ui/ActionDebugEditor.hpp"
REGISTER_COMPONENT_GROUP("Action Debug", "AI", ActionDebug,
ActionDebugEditor)
{
registry.registerComponent<ActionDebug>(
"Action Debug", "AI",
std::make_unique<ActionDebugEditor>(),
// Adder
[](flecs::entity e) {
if (!e.has<ActionDebug>())
e.set<ActionDebug>({});
},
// Remover
[](flecs::entity e) {
if (e.has<ActionDebug>())
e.remove<ActionDebug>();
});
}

View File

@@ -0,0 +1,44 @@
#ifndef EDITSCENE_ACTUATOR_HPP
#define EDITSCENE_ACTUATOR_HPP
#pragma once
#include <Ogre.h>
#include <vector>
#include <string>
/**
* Actuator component.
*
* An interactive object visible only to the player character.
* When the player is within radius + height range, an on-screen
* prompt appears and the action can be triggered with the action key.
*
* Unlike SmartObject, Actuators do not use pathfinding or path
* following — they are instantaneous interactions.
*/
struct ActuatorComponent {
// Interaction radius in XZ plane
float radius = 1.5f;
// Maximum height difference for interaction
float height = 1.8f;
// Names of GOAP actions (from ActionDatabase) that this actuator provides
std::vector<Ogre::String> actionNames;
// Runtime: cooldown timer (seconds remaining)
float cooldownTimer = 0.0f;
// Runtime: currently executing an action
bool isExecuting = false;
ActuatorComponent() = default;
explicit ActuatorComponent(float radius_, float height_)
: radius(radius_)
, height(height_)
{
}
};
#endif // EDITSCENE_ACTUATOR_HPP

View File

@@ -0,0 +1,19 @@
#include "Actuator.hpp"
#include "../ui/ComponentRegistration.hpp"
#include "../ui/ActuatorEditor.hpp"
REGISTER_COMPONENT_GROUP("Actuator", "Game", ActuatorComponent, ActuatorEditor)
{
registry.registerComponent<ActuatorComponent>(
"Actuator", "Game", std::make_unique<ActuatorEditor>(),
// Adder
[](flecs::entity e) {
if (!e.has<ActuatorComponent>())
e.set<ActuatorComponent>({});
},
// Remover
[](flecs::entity e) {
if (e.has<ActuatorComponent>())
e.remove<ActuatorComponent>();
});
}

View File

@@ -0,0 +1,9 @@
#include "AnimationTree.hpp"
AnimationTreeComponent::AnimationTreeComponent()
: root()
, enabled(true)
, useRootMotion(false)
, dirty(true)
{
}

View File

@@ -0,0 +1,165 @@
#ifndef EDITSCENE_ANIMATIONTREE_HPP
#define EDITSCENE_ANIMATIONTREE_HPP
#pragma once
#include <Ogre.h>
#include <vector>
#include <unordered_map>
/**
* A node in the animation tree.
*
* Node types:
* "output" - Root output node, optional speed multiplier (1 child)
* "stateMachine" - Cross-fades between child states, named for lookup
* "state" - A named state within a state machine (1 child)
* "speed" - Playback speed multiplier (1 child)
* "animation" - Leaf referencing an Ogre animation by name
*/
struct AnimationTreeNode {
Ogre::String type = "animation";
Ogre::String name;
Ogre::String animationName;
float speed = 1.0f;
float fadeSpeed = 7.5f;
std::vector<AnimationTreeNode> children;
/* For stateMachine nodes: auto-transition when animation ends */
std::unordered_map<Ogre::String, Ogre::String> endTransitions;
AnimationTreeNode() = default;
AnimationTreeNode *findChild(const Ogre::String &childName)
{
for (auto &child : children) {
if (child.name == childName)
return &child;
}
return nullptr;
}
const AnimationTreeNode *findChild(
const Ogre::String &childName) const
{
for (const auto &child : children) {
if (child.name == childName)
return &child;
}
return nullptr;
}
AnimationTreeNode *findStateMachine(const Ogre::String &smName)
{
if (type == "stateMachine" && name == smName)
return this;
for (auto &child : children) {
auto *found = child.findStateMachine(smName);
if (found)
return found;
}
return nullptr;
}
const AnimationTreeNode *findStateMachine(
const Ogre::String &smName) const
{
if (type == "stateMachine" && name == smName)
return this;
for (const auto &child : children) {
auto *found = child.findStateMachine(smName);
if (found)
return found;
}
return nullptr;
}
AnimationTreeNode *findState(const Ogre::String &stateName)
{
if (type == "state" && name == stateName)
return this;
for (auto &child : children) {
auto *found = child.findState(stateName);
if (found)
return found;
}
return nullptr;
}
const AnimationTreeNode *findState(
const Ogre::String &stateName) const
{
if (type == "state" && name == stateName)
return this;
for (const auto &child : children) {
auto *found = child.findState(stateName);
if (found)
return found;
}
return nullptr;
}
AnimationTreeNode *findAnimationLeaf()
{
if (type == "animation")
return this;
for (auto &child : children) {
auto *found = child.findAnimationLeaf();
if (found)
return found;
}
return nullptr;
}
const AnimationTreeNode *findAnimationLeaf() const
{
if (type == "animation")
return this;
for (const auto &child : children) {
auto *found = child.findAnimationLeaf();
if (found)
return found;
}
return nullptr;
}
void collectStateMachines(std::vector<AnimationTreeNode *> &out)
{
if (type == "stateMachine")
out.push_back(this);
for (auto &child : children)
child.collectStateMachines(out);
}
void collectStateMachines(
std::vector<const AnimationTreeNode *> &out) const
{
if (type == "stateMachine")
out.push_back(this);
for (const auto &child : children)
child.collectStateMachines(out);
}
};
/**
* Animation tree component for hierarchical state-machine-based animation.
*
* The tree is evaluated each frame by AnimationTreeSystem to determine
* which Ogre AnimationStates are active and their blend weights.
*/
struct AnimationTreeComponent {
AnimationTreeNode root;
bool enabled = true;
bool useRootMotion = false;
bool dirty = true;
/* If set, the tree root is copied from the named template */
Ogre::String templateName;
/* Runtime: last copied template version (not serialized) */
uint64_t templateVersion = 0;
/* Runtime: current state of each state machine (not serialized) */
std::unordered_map<Ogre::String, Ogre::String> currentStates;
AnimationTreeComponent();
};
#endif // EDITSCENE_ANIMATIONTREE_HPP

View File

@@ -0,0 +1,57 @@
#include "AnimationTree.hpp"
#include "../ui/ComponentRegistration.hpp"
#include "../ui/AnimationTreeEditor.hpp"
#include "Transform.hpp"
static AnimationTreeComponent createDefaultTree()
{
AnimationTreeComponent at;
at.root.type = "output";
at.root.speed = 1.0f;
AnimationTreeNode sm;
sm.type = "stateMachine";
sm.name = "main";
sm.fadeSpeed = 7.5f;
AnimationTreeNode state;
state.type = "state";
state.name = "idle";
AnimationTreeNode anim;
anim.type = "animation";
anim.animationName = "idle";
state.children.push_back(anim);
sm.children.push_back(state);
at.root.children.push_back(sm);
return at;
}
REGISTER_COMPONENT_GROUP("Animation Tree", "Rendering",
AnimationTreeComponent, AnimationTreeEditor)
{
registry.registerComponent<AnimationTreeComponent>(
"Animation Tree", "Rendering",
std::make_unique<AnimationTreeEditor>(sceneMgr),
/* Adder */
[sceneMgr](flecs::entity e) {
if (!e.has<TransformComponent>()) {
TransformComponent transform;
transform.node =
sceneMgr->getRootSceneNode()
->createChildSceneNode();
e.set<TransformComponent>(transform);
}
AnimationTreeComponent at = createDefaultTree();
at.dirty = true;
e.set<AnimationTreeComponent>(at);
},
/* Remover */
[sceneMgr](flecs::entity e) {
(void)sceneMgr;
e.remove<AnimationTreeComponent>();
});
}

View File

@@ -0,0 +1,20 @@
#ifndef EDITSCENE_ANIMATIONTREETEMPLATE_HPP
#define EDITSCENE_ANIMATIONTREETEMPLATE_HPP
#pragma once
#include <Ogre.h>
/**
* Template marker for reusable animation trees.
*
* Entities with this component serve as shared animation tree templates.
* They should also have an AnimationTreeComponent for editing the tree.
* Other entities reference the template by name via
* AnimationTreeComponent::templateName.
*/
struct AnimationTreeTemplate {
Ogre::String name;
uint64_t version = 1;
};
#endif // EDITSCENE_ANIMATIONTREETEMPLATE_HPP

View File

@@ -0,0 +1,23 @@
#include "AnimationTreeTemplate.hpp"
#include "../ui/ComponentRegistration.hpp"
#include "../ui/AnimationTreeTemplateEditor.hpp"
REGISTER_COMPONENT_GROUP("Animation Tree Template", "Animation",
AnimationTreeTemplate, AnimationTreeTemplateEditor)
{
registry.registerComponent<AnimationTreeTemplate>(
AnimationTreeTemplate_name, AnimationTreeTemplate_group,
std::make_unique<AnimationTreeTemplateEditor>(),
// Adder
[](flecs::entity e) {
if (!e.has<AnimationTreeTemplate>()) {
e.set<AnimationTreeTemplate>({});
}
},
// Remover
[](flecs::entity e) {
if (e.has<AnimationTreeTemplate>()) {
e.remove<AnimationTreeTemplate>();
}
});
}

View File

@@ -0,0 +1,130 @@
#ifndef EDITSCENE_BEHAVIOR_TREE_HPP
#define EDITSCENE_BEHAVIOR_TREE_HPP
#pragma once
#include <Ogre.h>
#include <vector>
/**
* Data-driven behavior tree node for AI action execution.
*
* Node types:
* "sequence" - Execute children in order until one fails
* "selector" - Execute children in order until one succeeds
* "invert" - Invert the result of a single child
* "task" - Leaf action (references a named task)
* "check" - Leaf condition (references a named check)
* "debugPrint" - Leaf: prints 'name' to console once when active
* "setAnimationState"- Leaf: sets animation state (name="SM/State")
* "isAnimationEnded" - Leaf check: true if anim in state machine ended
* "setBit" - Leaf: sets blackboard bit (name=bit, params=0/1)
* "checkBit" - Leaf check: true if blackboard bit is set
* "setValue" - Leaf: sets blackboard value (name=key, params=val)
* "checkValue" - Leaf check: blackboard comparison (name=key, params="op val")
* "blackboardDump" - Leaf: dumps entire blackboard to log
* "delay" - Leaf: waits for N seconds (params=seconds as float)
* "teleportToChild" - Leaf: teleports character to a named child entity
* of the Smart Object being interacted with.
* name = child entity name to teleport to.
* The character is positioned at the child's absolute
* world transform (position + orientation).
* "disablePhysics" - Leaf: removes character's JPH::BodyID from physics
* system so physics no longer interferes with animation.
* "enablePhysics" - Leaf: re-adds character's JPH::BodyID to physics
* system to restore physics simulation.
*
* --- Item / Inventory nodes ---
* "hasItem" - Leaf check: true if character's inventory has an item
* matching the given itemId (name=itemId).
* "hasItemByName" - Leaf check: true if character's inventory has an item
* matching the given itemName (name=itemName).
* "countItem" - Leaf check: true if character's inventory has at least
* N of itemId (name=itemId, params=count as int).
* "pickupItem" - Leaf: picks up the nearest ItemComponent entity within
* range into the character's inventory.
* name=itemId filter (optional, empty = any).
* "dropItem" - Leaf: drops an item from inventory into the world.
* name=itemId, params=count (optional, default 1).
* "useItem" - Leaf: uses an item from inventory (executes its
* useAction behavior tree). name=itemId.
* "addItemToInventory"- Leaf: adds an item directly to character's inventory
* (for quest rewards, etc.).
* params="itemId,itemName,itemType,count,weight,value"
*
* --- Lua node ---
* "luaTask" - Leaf: calls a registered Lua function.
* name = registered node handler name.
* params = "key=val,key2=val2" passed to the Lua function.
* The Lua function receives (entity_id, params_table)
* and must return "success", "failure", or "running".
* Register handlers via:
* ecs.behavior_tree.register_node("name", function)
*/
struct BehaviorTreeNode {
Ogre::String type = "task";
Ogre::String name; // Action/condition name, or message, or SM/State
Ogre::String params; // Optional extra parameters
std::vector<BehaviorTreeNode> children;
BehaviorTreeNode() = default;
BehaviorTreeNode *findChild(const Ogre::String &childName)
{
for (auto &child : children) {
if (child.name == childName)
return &child;
}
return nullptr;
}
const BehaviorTreeNode *findChild(const Ogre::String &childName) const
{
for (const auto &child : children) {
if (child.name == childName)
return &child;
}
return nullptr;
}
bool canHaveChildren() const
{
return type == "sequence" || type == "selector" ||
type == "invert";
}
bool isLeaf() const
{
return type == "task" || type == "check" ||
type == "debugPrint" || type == "setAnimationState" ||
type == "isAnimationEnded" || type == "setBit" ||
type == "checkBit" || type == "setValue" ||
type == "checkValue" || type == "blackboardDump" ||
type == "delay" || type == "teleportToChild" ||
type == "disablePhysics" || type == "enablePhysics" ||
type == "sendEvent" || type == "hasItem" ||
type == "hasItemByName" || type == "countItem" ||
type == "pickupItem" || type == "dropItem" ||
type == "useItem" || type == "addItemToInventory" ||
type == "luaTask";
}
};
/**
* Behavior tree asset component.
*
* Can be attached to an entity to define a reusable behavior tree,
* or referenced by name from a GoapAction.
*/
struct BehaviorTreeComponent {
BehaviorTreeNode root;
Ogre::String treeName;
bool enabled = true;
bool dirty = true;
void markDirty()
{
dirty = true;
}
};
#endif // EDITSCENE_BEHAVIOR_TREE_HPP

View File

@@ -0,0 +1,46 @@
#ifndef EDITSCENE_BUOYANCYINFO_HPP
#define EDITSCENE_BUOYANCYINFO_HPP
#pragma once
#include <Ogre.h>
/**
* BuoyancyInfo component
* Provides per-entity buoyancy settings for water physics
* If an entity has this component, it will use these settings
* Otherwise, default settings from the buoyancy system will be used
*/
struct BuoyancyInfo {
// Enable/disable buoyancy for this entity
bool enabled = true;
// Buoyancy strength (0 = no buoyancy, 1 = neutral buoyancy, >1 = floats)
float buoyancy = 1.0f;
// Linear drag when submerged (0 = no drag, 1 = full drag)
float linearDrag = 0.1f;
// Angular drag when submerged (0 = no drag, 1 = full drag)
float angularDrag = 0.05f;
// Water surface Y level for this entity (world space)
// If not set (0), uses global water level from buoyancy system
float waterSurfaceY = 0.0f;
// Submergedness threshold (0-1) - how much of the body must be submerged
// before buoyancy is applied (0 = any contact, 1 = fully submerged)
float submergedThreshold = 0.3f;
// Use custom water surface level (if false, uses global water level)
bool useCustomWaterLevel = false;
// Mark component as dirty (needs update)
bool dirty = true;
void markDirty()
{
dirty = true;
}
};
#endif // EDITSCENE_BUOYANCYINFO_HPP

View File

@@ -0,0 +1,23 @@
#include "BuoyancyInfo.hpp"
#include "Transform.hpp"
#include "../ui/ComponentRegistration.hpp"
#include "../ui/BuoyancyInfoEditor.hpp"
// Register BuoyancyInfo component
REGISTER_COMPONENT("Buoyancy Info", BuoyancyInfo, BuoyancyInfoEditor)
{
registry.registerComponent<BuoyancyInfo>(
"Buoyancy Info", std::make_unique<BuoyancyInfoEditor>(),
// Adder
[](flecs::entity e) {
if (!e.has<BuoyancyInfo>()) {
e.set<BuoyancyInfo>({});
}
},
// Remover
[](flecs::entity e) {
if (e.has<BuoyancyInfo>()) {
e.remove<BuoyancyInfo>();
}
});
}

View File

@@ -0,0 +1,37 @@
#ifndef EDITSCENE_CAMERA_HPP
#define EDITSCENE_CAMERA_HPP
#pragma once
#include <Ogre.h>
/**
* Camera component - attaches an Ogre::Camera to the entity's SceneNode
*/
struct CameraComponent {
// Camera properties
float fovY = 45.0f; // Vertical field of view in degrees
float nearClip = 0.1f; // Near clip distance
float farClip = 1000.0f; // Far clip distance
float aspectRatio = 16.0f / 9.0f; // Aspect ratio (width/height)
// For orthographic camera
bool orthographic = false;
float orthoWidth = 10.0f; // Width for orthographic view
float orthoHeight = 10.0f; // Height for orthographic view
// The Ogre camera object (created by CameraSystem)
Ogre::Camera* camera = nullptr;
// For preview (optional RTT - created on demand)
Ogre::RenderTarget* previewTarget = nullptr;
Ogre::TexturePtr previewTexture;
bool showPreview = false;
int previewWidth = 400;
int previewHeight = 300;
void markDirty() { needsRebuild = true; }
bool needsRebuild = true;
bool needsPreviewUpdate = false;
};
#endif // EDITSCENE_CAMERA_HPP

View File

@@ -0,0 +1,53 @@
#include "Camera.hpp"
#include "Transform.hpp"
#include "../ui/ComponentRegistration.hpp"
#include "../ui/CameraEditor.hpp"
#include "../systems/CameraSystem.hpp"
// Register Camera component
REGISTER_COMPONENT("Camera", CameraComponent, CameraEditor)
{
registry.registerComponent<CameraComponent>(
"Camera",
std::make_unique<CameraEditor>(sceneMgr, renderWindow),
// Adder
[sceneMgr](flecs::entity e) {
if (!e.has<CameraComponent>()) {
// Camera requires Transform
if (!e.has<TransformComponent>()) {
// Auto-add transform if missing
TransformComponent transform;
transform.node = sceneMgr->getRootSceneNode()->createChildSceneNode();
e.set<TransformComponent>(transform);
}
e.set<CameraComponent>({});
}
},
// Remover
[sceneMgr](flecs::entity e) {
if (e.has<CameraComponent>()) {
auto& camera = e.get_mut<CameraComponent>();
// Clean up Ogre camera and preview resources
if (camera.camera) {
// Clean up preview
if (camera.previewTarget) {
camera.previewTarget->removeAllListeners();
}
if (camera.previewTexture) {
Ogre::TextureManager::getSingleton().remove(
camera.previewTexture->getName());
}
// Detach and destroy camera
Ogre::SceneNode* parent = camera.camera->getParentSceneNode();
if (parent) {
parent->detachObject(camera.camera);
}
sceneMgr->destroyCamera(camera.camera);
camera.camera = nullptr;
}
e.remove<CameraComponent>();
}
}
);
}

View File

@@ -0,0 +1,178 @@
#include "CellGrid.hpp"
#include <algorithm>
Cell* CellGridComponent::findCell(int x, int y, int z)
{
int64_t key = ((int64_t)x + 2048 * (int64_t)y + 2048 * 2048 * (int64_t)z);
for (auto& cell : cells) {
if (cell.getKey() == key) {
return &cell;
}
}
return nullptr;
}
const Cell* CellGridComponent::findCell(int x, int y, int z) const
{
int64_t key = ((int64_t)x + 2048 * (int64_t)y + 2048 * 2048 * (int64_t)z);
for (const auto& cell : cells) {
if (cell.getKey() == key) {
return &cell;
}
}
return nullptr;
}
Cell& CellGridComponent::getOrCreateCell(int x, int y, int z)
{
Cell* existing = findCell(x, y, z);
if (existing) {
return *existing;
}
Cell newCell;
newCell.x = x;
newCell.y = y;
newCell.z = z;
cells.push_back(newCell);
return cells.back();
}
void CellGridComponent::removeCell(int x, int y, int z)
{
int64_t key = ((int64_t)x + 2048 * (int64_t)y + 2048 * 2048 * (int64_t)z);
auto it = std::remove_if(cells.begin(), cells.end(),
[key](const Cell& c) { return c.getKey() == key; });
cells.erase(it, cells.end());
}
FurnitureCell* CellGridComponent::findFurnitureCell(int x, int y, int z)
{
int64_t key = ((int64_t)x + 2048 * (int64_t)y + 2048 * 2048 * (int64_t)z);
for (auto& cell : furnitureCells) {
if (cell.getKey() == key) {
return &cell;
}
}
return nullptr;
}
FurnitureCell& CellGridComponent::getOrCreateFurnitureCell(int x, int y, int z)
{
FurnitureCell* existing = findFurnitureCell(x, y, z);
if (existing) {
return *existing;
}
FurnitureCell newCell;
newCell.x = x;
newCell.y = y;
newCell.z = z;
furnitureCells.push_back(newCell);
return furnitureCells.back();
}
void CellGridComponent::removeFurnitureCell(int x, int y, int z)
{
int64_t key = ((int64_t)x + 2048 * (int64_t)y + 2048 * 2048 * (int64_t)z);
auto it = std::remove_if(furnitureCells.begin(), furnitureCells.end(),
[key](const FurnitureCell& c) { return c.getKey() == key; });
furnitureCells.erase(it, furnitureCells.end());
}
void CellGridComponent::clear()
{
cells.clear();
furnitureCells.clear();
markDirty();
}
Ogre::Vector3 CellGridComponent::cellToWorld(int x, int y, int z) const
{
return Ogre::Vector3(
x * cellSize,
y * cellHeight,
z * cellSize
);
}
void CellGridComponent::worldToCell(const Ogre::Vector3& worldPos, int& x, int& y, int& z) const
{
x = Ogre::Math::Floor(worldPos.x / cellSize);
y = Ogre::Math::Floor(worldPos.y / cellHeight);
z = Ogre::Math::Floor(worldPos.z / cellSize);
}
const char* CellGridComponent::getFlagName(uint64_t flag)
{
switch (flag) {
case CellFlags::Floor: return "Floor";
case CellFlags::Ceiling: return "Ceiling";
case CellFlags::WallXNeg: return "Ext Wall X-";
case CellFlags::WallXPos: return "Ext Wall X+";
case CellFlags::WallZPos: return "Ext Wall Z+";
case CellFlags::WallZNeg: return "Ext Wall Z-";
case CellFlags::DoorXNeg: return "Ext Door X-";
case CellFlags::DoorXPos: return "Ext Door X+";
case CellFlags::DoorZPos: return "Ext Door Z+";
case CellFlags::DoorZNeg: return "Ext Door Z-";
case CellFlags::WindowXNeg: return "Ext Window X-";
case CellFlags::WindowXPos: return "Ext Window X+";
case CellFlags::WindowZPos: return "Ext Window Z+";
case CellFlags::WindowZNeg: return "Ext Window Z-";
case CellFlags::IntWallXNeg: return "Int Wall X-";
case CellFlags::IntWallXPos: return "Int Wall X+";
case CellFlags::IntWallZPos: return "Int Wall Z+";
case CellFlags::IntWallZNeg: return "Int Wall Z-";
case CellFlags::IntDoorXNeg: return "Int Door X-";
case CellFlags::IntDoorXPos: return "Int Door X+";
case CellFlags::IntDoorZPos: return "Int Door Z+";
case CellFlags::IntDoorZNeg: return "Int Door Z-";
case CellFlags::IntWindowXNeg: return "Int Window X-";
case CellFlags::IntWindowXPos: return "Int Window X+";
case CellFlags::IntWindowZPos: return "Int Window Z+";
case CellFlags::IntWindowZNeg: return "Int Window Z-";
default: return "Unknown";
}
}
uint64_t CellGridComponent::getFlagByName(const std::string& name)
{
auto flags = getAllFlags();
for (const auto& [flag, flagName] : flags) {
if (name == flagName) {
return flag;
}
}
return 0;
}
std::vector<std::pair<uint64_t, const char*>> CellGridComponent::getAllFlags()
{
return {
{ CellFlags::Floor, "Floor" },
{ CellFlags::Ceiling, "Ceiling" },
{ CellFlags::WallXNeg, "Ext Wall X-" },
{ CellFlags::WallXPos, "Ext Wall X+" },
{ CellFlags::WallZPos, "Ext Wall Z+" },
{ CellFlags::WallZNeg, "Ext Wall Z-" },
{ CellFlags::DoorXNeg, "Ext Door X-" },
{ CellFlags::DoorXPos, "Ext Door X+" },
{ CellFlags::DoorZPos, "Ext Door Z+" },
{ CellFlags::DoorZNeg, "Ext Door Z-" },
{ CellFlags::WindowXNeg, "Ext Window X-" },
{ CellFlags::WindowXPos, "Ext Window X+" },
{ CellFlags::WindowZPos, "Ext Window Z+" },
{ CellFlags::WindowZNeg, "Ext Window Z-" },
{ CellFlags::IntWallXNeg, "Int Wall X-" },
{ CellFlags::IntWallXPos, "Int Wall X+" },
{ CellFlags::IntWallZPos, "Int Wall Z+" },
{ CellFlags::IntWallZNeg, "Int Wall Z-" },
{ CellFlags::IntDoorXNeg, "Int Door X-" },
{ CellFlags::IntDoorXPos, "Int Door X+" },
{ CellFlags::IntDoorZPos, "Int Door Z+" },
{ CellFlags::IntDoorZNeg, "Int Door Z-" },
{ CellFlags::IntWindowXNeg, "Int Window X-" },
{ CellFlags::IntWindowXPos, "Int Window X+" },
{ CellFlags::IntWindowZPos, "Int Window Z+" },
{ CellFlags::IntWindowZNeg, "Int Window Z-" },
};
}

View File

@@ -0,0 +1,521 @@
#pragma once
#include <cstdint>
#include <vector>
#include <string>
#include <unordered_map>
#include <chrono>
#include <flecs.h>
#include <Ogre.h>
/**
* @brief Cell flags for building geometry
*
* Each cell in the 3D grid can have these features:
* - Floor/Ceiling
* - External walls (4 directions)
* - External doors/windows (4 directions each)
* - Internal walls (4 directions)
* - Internal doors/windows (4 directions each)
*/
namespace CellFlags {
constexpr uint64_t Floor = 1ULL << 0;
constexpr uint64_t Ceiling = 1ULL << 1;
// External walls
constexpr uint64_t WallXNeg = 1ULL << 2; // Wall on -X side
constexpr uint64_t WallXPos = 1ULL << 3; // Wall on +X side
constexpr uint64_t WallZPos = 1ULL << 4; // Wall on +Z side
constexpr uint64_t WallZNeg = 1ULL << 5; // Wall on -Z side
// External doors
constexpr uint64_t DoorXNeg = 1ULL << 6;
constexpr uint64_t DoorXPos = 1ULL << 7;
constexpr uint64_t DoorZPos = 1ULL << 8;
constexpr uint64_t DoorZNeg = 1ULL << 9;
// External windows
constexpr uint64_t WindowXNeg = 1ULL << 10;
constexpr uint64_t WindowXPos = 1ULL << 11;
constexpr uint64_t WindowZPos = 1ULL << 12;
constexpr uint64_t WindowZNeg = 1ULL << 13;
// Internal walls
constexpr uint64_t IntWallXNeg = 1ULL << 14;
constexpr uint64_t IntWallXPos = 1ULL << 15;
constexpr uint64_t IntWallZPos = 1ULL << 16;
constexpr uint64_t IntWallZNeg = 1ULL << 17;
// Internal doors
constexpr uint64_t IntDoorXNeg = 1ULL << 18;
constexpr uint64_t IntDoorXPos = 1ULL << 19;
constexpr uint64_t IntDoorZPos = 1ULL << 20;
constexpr uint64_t IntDoorZNeg = 1ULL << 21;
// Internal windows
constexpr uint64_t IntWindowXNeg = 1ULL << 22;
constexpr uint64_t IntWindowXPos = 1ULL << 23;
constexpr uint64_t IntWindowZPos = 1ULL << 24;
constexpr uint64_t IntWindowZNeg = 1ULL << 25;
// Masks
constexpr uint64_t AllExternalWalls = WallXNeg | WallXPos | WallZPos | WallZNeg;
constexpr uint64_t AllInternalWalls = IntWallXNeg | IntWallXPos | IntWallZPos | IntWallZNeg;
constexpr uint64_t AllWalls = AllExternalWalls | AllInternalWalls;
constexpr uint64_t AllDoors = DoorXNeg | DoorXPos | DoorZPos | DoorZNeg |
IntDoorXNeg | IntDoorXPos | IntDoorZPos | IntDoorZNeg;
constexpr uint64_t AllWindows = WindowXNeg | WindowXPos | WindowZPos | WindowZNeg |
IntWindowXNeg | IntWindowXPos | IntWindowZPos | IntWindowZNeg;
// Combined masks for corners (walls + doors + windows in each direction)
constexpr uint64_t AllXNeg = WallXNeg | DoorXNeg | WindowXNeg;
constexpr uint64_t AllXPos = WallXPos | DoorXPos | WindowXPos;
constexpr uint64_t AllZPos = WallZPos | DoorZPos | WindowZPos;
constexpr uint64_t AllZNeg = WallZNeg | DoorZNeg | WindowZNeg;
constexpr uint64_t AllIntXNeg = IntWallXNeg | IntDoorXNeg | IntWindowXNeg;
constexpr uint64_t AllIntXPos = IntWallXPos | IntDoorXPos | IntWindowXPos;
constexpr uint64_t AllIntZPos = IntWallZPos | IntDoorZPos | IntWindowZPos;
constexpr uint64_t AllIntZNeg = IntWallZNeg | IntDoorZNeg | IntWindowZNeg;
}
/**
* @brief A single cell in the 3D grid
*/
struct Cell {
int x = 0, y = 0, z = 0;
uint64_t flags = 0;
// Generate unique key for this cell
int64_t getKey() const {
// Support -1024 to 1024 range for each axis
return (int64_t)x + 2048 * (int64_t)y + 2048 * 2048 * (int64_t)z;
}
bool hasFlag(uint64_t flag) const { return (flags & flag) != 0; }
void setFlag(uint64_t flag) { flags |= flag; }
void clearFlag(uint64_t flag) { flags &= ~flag; }
void toggleFlag(uint64_t flag) { flags ^= flag; }
};
/**
* @brief Furniture placement in a cell
*/
struct FurnitureCell {
int x = 0, y = 0, z = 0;
std::vector<std::string> tags;
std::string furnitureType;
int rotation = 0; // 0-3, representing 0, 90, 180, 270 degrees
int64_t getKey() const {
return (int64_t)x + 2048 * (int64_t)y + 2048 * 2048 * (int64_t)z;
}
};
/**
* @brief Cell grid for procedural building generation
*
* This component stores a sparse 3D grid of cells that define
* building geometry (walls, floors, doors, windows).
*
* Used by: House/Lot generation, dungeon generation
*/
struct CellGridComponent {
// Grid dimensions (in cells)
int width = 10; // X dimension
int height = 1; // Y dimension (floors)
int depth = 10; // Z dimension
// Cell size in world units
float cellSize = 4.0f;
float cellHeight = 4.0f;
// The cell data (sparse storage)
std::vector<Cell> cells;
std::vector<FurnitureCell> furnitureCells;
// Generation script (Lua or custom format)
std::string generationScript;
// Texture rectangle names for different parts (from ProceduralTexture)
std::string floorRectName;
std::string ceilingRectName;
std::string extWallRectName;
std::string intWallRectName;
// Frame texture rectangles
std::string extDoorFrameRectName;
std::string intDoorFrameRectName;
std::string extWindowFrameRectName;
std::string intWindowFrameRectName;
// Roof texture rectangles
std::string roofTopRectName;
std::string roofSideRectName;
// Physics properties for generated colliders
float friction = 0.5f;
// Dirty flag - triggers rebuild
bool dirty = true;
unsigned int version = 0;
// Find cell at position (returns nullptr if not found)
Cell* findCell(int x, int y, int z);
const Cell* findCell(int x, int y, int z) const;
// Get or create cell at position
Cell& getOrCreateCell(int x, int y, int z);
// Remove cell at position
void removeCell(int x, int y, int z);
// Find furniture cell
FurnitureCell* findFurnitureCell(int x, int y, int z);
// Get or create furniture cell
FurnitureCell& getOrCreateFurnitureCell(int x, int y, int z);
// Remove furniture cell
void removeFurnitureCell(int x, int y, int z);
// Clear all cells
void clear();
// Mark for rebuild
void markDirty() { dirty = true; version++; }
// Convert local cell position to world position
Ogre::Vector3 cellToWorld(int x, int y, int z) const;
// Convert world position to cell coordinates
void worldToCell(const Ogre::Vector3& worldPos, int& x, int& y, int& z) const;
// Get bit name for editor display
static const char* getFlagName(uint64_t flag);
static uint64_t getFlagByName(const std::string& name);
static std::vector<std::pair<uint64_t, const char*>> getAllFlags();
};
/**
* @brief Room definition within a cell grid
*
* Rooms are rectangular areas that can be connected by doors.
* This is the ECS version of the Lua room() function.
*/
struct RoomComponent {
// Room bounds (in cell coordinates)
// A room from (minX, minZ) to (maxX-1, maxZ-1) inclusive
int minX = 0, minY = 0, minZ = 0;
int maxX = 1, maxY = 1, maxZ = 1; // exclusive max (size = max - min)
// Room name/tag for furniture placement rules
std::string roomType; // e.g., "bedroom", "kitchen", "hallway"
std::vector<std::string> tags;
// Generation flags
bool createFloor = true; // Create floor cells
bool createCeiling = true; // Create ceiling cells
bool createInteriorWalls = true; // Create iwallx-/+, iwallz-/+ around the room
bool createWindows = false; // Convert exterior-facing walls to windows
bool fillRoomWithFurniture = false; // Automatically place furniture based on tags
unsigned int furnitureSeed = 42; // Seed for deterministic furniture placement
float furnitureYOffset = 0.05f; // Y offset for all furniture in this room
// Dirty flag - triggers regeneration of cell grid
bool dirty = true;
void markDirty() { dirty = true; }
// Helper to get size
int getSizeX() const { return maxX - minX; }
int getSizeY() const { return maxY - minY; }
int getSizeZ() const { return maxZ - minZ; }
// Check if a cell position is inside this room
bool contains(int x, int y, int z) const {
return x >= minX && x < maxX &&
y >= minY && y < maxY &&
z >= minZ && z < maxZ;
}
// Check if a cell is on the room edge (for wall placement)
bool isOnEdge(int x, int z) const {
return x == minX || x == maxX - 1 || z == minZ || z == maxZ - 1;
}
// Get the side of the room this cell is on (0=Z-, 1=Z+, 2=X-, 3=X+, -1=not on edge)
int getEdgeSide(int x, int z) const {
if (!isOnEdge(x, z)) return -1;
if (z == minZ) return 0; // Z- (north)
if (z == maxZ - 1) return 1; // Z+ (south)
if (x == minX) return 2; // X- (west)
if (x == maxX - 1) return 3; // X+ (east)
return -1;
}
// Persistent unique ID for this room (survives save/load)
// If empty, will be auto-generated during serialization
std::string persistentId;
// Connected room persistent IDs (bidirectional connections)
// Using persistent IDs instead of entity IDs because entity IDs change on save/load
std::vector<std::string> connectedRoomIds;
// Exit doors for outside-facing walls (0=Z-, 1=Z+, 2=X-, 3=X+)
// An exit is always a door placed on a wall that faces outside (not connected to another room)
bool exits[4] = { false, false, false, false }; // Z-, Z+, X-, X+
// Generate a persistent ID if one doesn't exist
void ensurePersistentId(flecs::entity_t entityId = 0) {
if (persistentId.empty()) {
// Generate unique ID based on timestamp + random + optional entity ID
auto now = std::chrono::high_resolution_clock::now();
auto nanos = std::chrono::duration_cast<std::chrono::nanoseconds>(
now.time_since_epoch()).count();
persistentId = "room_" + std::to_string(nanos);
if (entityId != 0) {
persistentId += "_" + std::to_string(entityId);
}
}
}
// Add a connection to another room (bidirectional, by persistent ID)
void addConnection(const std::string& otherRoomId) {
// Check if not already connected
for (const auto& id : connectedRoomIds) {
if (id == otherRoomId) return;
}
connectedRoomIds.push_back(otherRoomId);
}
// Remove a connection to another room
void removeConnection(const std::string& otherRoomId) {
connectedRoomIds.erase(
std::remove(connectedRoomIds.begin(), connectedRoomIds.end(), otherRoomId),
connectedRoomIds.end()
);
}
// Check if connected to a room (by persistent ID)
bool isConnectedTo(const std::string& otherRoomId) const {
for (const auto& id : connectedRoomIds) {
if (id == otherRoomId) return true;
}
return false;
}
// Helper to set all exits at once
void setAllExits(bool value) {
for (int i = 0; i < 4; i++) {
exits[i] = value;
}
}
// Helper to set a specific exit
void setExit(int side, bool value) {
if (side >= 0 && side < 4) {
exits[side] = value;
}
}
};
/**
* @brief Room exits - defines external exits from a room
*
* This replaces the Lua create_exit0/1/2/3() functions.
* Creates external walls/doors/windows on the room edges that face outside.
*/
/**
* @brief Clear Area - clears cells in a region before room generation
*
* This replaces the Lua clear_area() and clear_furniture_area() functions.
* It clears all cells within the specified bounds before any room generation happens.
* This component is processed first in the RoomLayoutSystem.
*/
struct ClearAreaComponent {
// Area bounds (in cell coordinates, inclusive min, exclusive max)
int minX = 0, minY = 0, minZ = 0;
int maxX = 1, maxY = 1, maxZ = 1;
// What to clear
bool clearCells = true; // Clear cell flags (walls, floors, etc.)
bool clearFurniture = true; // Clear furniture placements
bool clearRoofs = false; // Clear roof definitions (children with RoofComponent)
bool clearRooms = false; // Remove existing room entities (children with RoomComponent)
// Processed flag - cleared each time dirty is set
bool processed = false;
// Dirty flag - clear again when true
bool dirty = true;
void markDirty() { dirty = true; }
// Helper to set bounds
void setBounds(int x1, int z1, int x2, int z2, int y = 0) {
minX = x1; minZ = z1; minY = y;
maxX = x2; maxZ = z2; maxY = y + 1;
}
// Helper to get size
int getSizeX() const { return maxX - minX; }
int getSizeY() const { return maxY - minY; }
int getSizeZ() const { return maxZ - minZ; }
};
/**
* @brief Roof definition for a lot
*/
struct RoofComponent {
enum Type {
Flat = 0,
Normal = 1,
Normal2 = 2,
Cone = 3,
Cylinder = 4
};
Type type = Flat;
// Position (in cell coordinates)
int posX = 0, posY = 0, posZ = 0;
// Size (in cells)
int sizeX = 1, sizeZ = 1;
// Offset from cell position (world units)
float offsetX = 0.0f, offsetY = 0.0f, offsetZ = 0.0f;
// Height parameters
float baseHeight = 0.5f;
float maxHeight = 0.5f;
// Dirty flag - triggers rebuild when changed
bool dirty = true;
void markDirty() { dirty = true; }
};
/**
* @brief Lot component - represents a single house/building lot
*
* A lot contains:
* - Cell grid (walls, floors, etc.)
* - Room definitions
* - Roof definitions
* - Furniture placement rules
*/
struct LotComponent {
// Lot dimensions (in cells)
int width = 10;
int depth = 10;
// Elevation offset from district
float elevation = 0.0f;
// Rotation around district center (degrees)
float angle = 0.0f;
// Position offset (for manual placement)
float offsetX = 0.0f, offsetZ = 0.0f;
// Template name if this lot was created from a template
std::string templateName;
// Procedural material for lot base (optional - falls back to District, then Town)
flecs::entity proceduralMaterialEntity;
std::string proceduralMaterialEntityId; // For serialization
// Texture rectangle name from ProceduralTexture for UV mapping
std::string textureRectName;
// Physics properties for generated colliders
float friction = 0.5f;
// Dirty flag
bool dirty = true;
void markDirty() { dirty = true; }
};
/**
* @brief District component - contains multiple lots arranged in a circle
*/
struct DistrictComponent {
// District radius (distance from center to lots)
float radius = 50.0f;
// Elevation offset
float elevation = 0.0f;
// Height of district base (for plaza)
float height = 0.2f;
// Is this a plaza (circular base)?
bool isPlaza = false;
// Lot templates for this district
std::vector<std::string> lotTemplateNames;
// Procedural material for plaza (references entity with ProceduralMaterialComponent)
flecs::entity proceduralMaterialEntity;
std::string proceduralMaterialEntityId; // For serialization
// Texture rectangle name from ProceduralTexture for UV mapping
std::string textureRectName;
// Physics properties for generated colliders
float friction = 0.5f;
// Dirty flag
bool dirty = true;
void markDirty() { dirty = true; }
};
/**
* @brief Town component - top-level container
*/
struct TownComponent {
std::string townName = "New Town";
// Color rectangles for texture atlas
struct ColorRect {
float left = 0.0f, top = 0.0f, right = 1.0f, bottom = 1.0f;
Ogre::ColourValue color = Ogre::ColourValue::White;
};
std::unordered_map<std::string, ColorRect> colorRects;
// Material name (created from color rects)
std::string materialName;
// Procedural material for town (used by districts and lots)
flecs::entity proceduralMaterialEntity;
std::string proceduralMaterialEntityId; // For serialization
// Texture rectangle name from ProceduralTexture for UV mapping
std::string textureRectName;
// Dirty flag
bool dirty = true;
bool materialDirty = true;
void markDirty() { dirty = true; }
void markMaterialDirty() { materialDirty = true; }
};
/**
* @brief Furniture template - defines a furniture item
*/
struct FurnitureTemplateComponent {
std::string templateName;
std::vector<std::string> tags; // e.g., "bed", "essential", "bedroom"
// Mesh/model info
std::string meshName;
std::string materialName;
// Placement rules
bool requiresWall = false;
bool requiresFloor = true;
bool blocksPath = true;
// Offset from cell center
float offsetX = 0.0f, offsetY = 0.0f, offsetZ = 0.0f;
// Random selection weight
float weight = 1.0f;
};

View File

@@ -0,0 +1,167 @@
#include "CellGrid.hpp"
#include "../ui/ComponentRegistration.hpp"
#include "../ui/CellGridEditor.hpp"
#include "../ui/LotEditor.hpp"
#include "../ui/DistrictEditor.hpp"
#include "../ui/TownEditor.hpp"
#include "../ui/RoofEditor.hpp"
#include "../ui/RoomEditor.hpp"
#include "../ui/ClearAreaEditor.hpp"
#include "../ui/FurnitureTemplateEditor.hpp"
// Register CellGrid component
REGISTER_COMPONENT_GROUP("Cell Grid", "Cell Grid", CellGridComponent, CellGridEditor)
{
registry.registerComponent<CellGridComponent>(
"Cell Grid", "Cell Grid",
std::make_unique<CellGridEditor>(),
[sceneMgr](flecs::entity e) {
if (!e.has<CellGridComponent>()) {
e.set<CellGridComponent>({});
}
},
[sceneMgr](flecs::entity e) {
if (e.has<CellGridComponent>()) {
e.remove<CellGridComponent>();
}
}
);
}
// Register Lot component
REGISTER_COMPONENT_GROUP("Lot", "Cell Grid", LotComponent, LotEditor)
{
registry.registerComponent<LotComponent>(
"Lot", "Cell Grid",
std::make_unique<LotEditor>(),
[sceneMgr](flecs::entity e) {
if (!e.has<LotComponent>()) {
e.set<LotComponent>({});
}
},
[sceneMgr](flecs::entity e) {
if (e.has<LotComponent>()) {
e.remove<LotComponent>();
}
}
);
}
// Register District component
REGISTER_COMPONENT_GROUP("District", "Cell Grid", DistrictComponent, DistrictEditor)
{
registry.registerComponent<DistrictComponent>(
"District", "Cell Grid",
std::make_unique<DistrictEditor>(),
[sceneMgr](flecs::entity e) {
if (!e.has<DistrictComponent>()) {
e.set<DistrictComponent>({});
}
},
[sceneMgr](flecs::entity e) {
if (e.has<DistrictComponent>()) {
e.remove<DistrictComponent>();
}
}
);
}
// Register Town component
REGISTER_COMPONENT_GROUP("Town", "Cell Grid", TownComponent, TownEditor)
{
registry.registerComponent<TownComponent>(
"Town", "Cell Grid",
std::make_unique<TownEditor>(),
[sceneMgr](flecs::entity e) {
if (!e.has<TownComponent>()) {
e.set<TownComponent>({});
}
},
[sceneMgr](flecs::entity e) {
if (e.has<TownComponent>()) {
e.remove<TownComponent>();
}
}
);
}
// Register Roof component
REGISTER_COMPONENT_GROUP("Roof", "Cell Grid", RoofComponent, RoofEditor)
{
registry.registerComponent<RoofComponent>(
"Roof", "Cell Grid",
std::make_unique<RoofEditor>(),
[sceneMgr](flecs::entity e) {
if (!e.has<RoofComponent>()) {
e.set<RoofComponent>({});
}
},
[sceneMgr](flecs::entity e) {
if (e.has<RoofComponent>()) {
e.remove<RoofComponent>();
}
}
);
}
// Register Room component (in Room Layout group)
REGISTER_COMPONENT_GROUP("Room", "Room Layout", RoomComponent, RoomEditor)
{
registry.registerComponent<RoomComponent>(
"Room", "Room Layout",
std::make_unique<RoomEditor>(),
[sceneMgr](flecs::entity e) {
if (!e.has<RoomComponent>()) {
e.set<RoomComponent>({});
}
},
[sceneMgr](flecs::entity e) {
if (e.has<RoomComponent>()) {
e.remove<RoomComponent>();
}
}
);
}
// Register FurnitureTemplate component
REGISTER_COMPONENT_GROUP("Furniture Template", "Cell Grid", FurnitureTemplateComponent, FurnitureTemplateEditor)
{
registry.registerComponent<FurnitureTemplateComponent>(
"Furniture Template", "Cell Grid",
std::make_unique<FurnitureTemplateEditor>(),
[sceneMgr](flecs::entity e) {
if (!e.has<FurnitureTemplateComponent>()) {
e.set<FurnitureTemplateComponent>({});
}
},
[sceneMgr](flecs::entity e) {
if (e.has<FurnitureTemplateComponent>()) {
e.remove<FurnitureTemplateComponent>();
}
}
);
}
// Register ClearArea component (in Room Layout group)
REGISTER_COMPONENT_GROUP("Clear Area", "Room Layout", ClearAreaComponent, ClearAreaEditor)
{
registry.registerComponent<ClearAreaComponent>(
"Clear Area", "Room Layout",
std::make_unique<ClearAreaEditor>(),
[sceneMgr](flecs::entity e) {
if (!e.has<ClearAreaComponent>()) {
e.set<ClearAreaComponent>({});
}
},
[sceneMgr](flecs::entity e) {
if (e.has<ClearAreaComponent>()) {
e.remove<ClearAreaComponent>();
}
}
);
}
// Note: ExteriorGenerationComponent has been removed.
// Exterior generation now automatically runs at the end of the RoomLayoutSystem pipeline.
// This mirrors the original Lua behavior where create_exterior() was called after all rooms were defined.

View File

@@ -0,0 +1,48 @@
#include "CellGridModule.hpp"
#include "CellGrid.hpp"
#include "GeneratedPhysicsTag.hpp"
#include <nlohmann/json.hpp>
#include <Ogre.h>
namespace CellGridModule {
using json = nlohmann::json;
void registerComponents(flecs::world& world)
{
// Register components without .member<> for complex types (std::string, std::vector)
// Flecs doesn't have built-in reflection for these, but JSON serialization handles them
// CellGridComponent
world.component<CellGridComponent>();
// Cell struct
world.component<Cell>();
// FurnitureCell struct
world.component<FurnitureCell>();
// LotComponent
world.component<LotComponent>();
// DistrictComponent
world.component<DistrictComponent>();
// TownComponent
world.component<TownComponent>();
// TownComponent::ColorRect
world.component<TownComponent::ColorRect>();
// RoomComponent
world.component<RoomComponent>();
// RoofComponent
world.component<RoofComponent>();
// FurnitureTemplateComponent
world.component<FurnitureTemplateComponent>();
world.component<GeneratedPhysicsTag>();
}
} // namespace CellGridModule

View File

@@ -0,0 +1,7 @@
#pragma once
#include <flecs.h>
namespace CellGridModule {
void registerComponents(flecs::world& world);
}

View File

@@ -0,0 +1,61 @@
#ifndef EDITSCENE_CHARACTER_HPP
#define EDITSCENE_CHARACTER_HPP
#pragma once
#include <Ogre.h>
/**
* Character physics component
*
* Attaches a Jolt JPH::Character (kinematic capsule) to the entity.
* The entity may also have CharacterSlotsComponent; the character
* physics lives on the same entity as the visual character.
*
* Child entities can add extra collision shapes via PhysicsColliderComponent.
*/
struct CharacterComponent {
/* Capsule dimensions */
float radius = 0.3f;
float height = 1.8f; /* cylinder height (excluding spherical caps) */
/* Offset from the entity's scene node */
Ogre::Vector3 offset = Ogre::Vector3::ZERO;
/* Current linear velocity (m/s), applied each frame by CharacterSystem */
Ogre::Vector3 linearVelocity = Ogre::Vector3::ZERO;
/* Enable/disable physics character */
bool enabled = true;
/* Physics was explicitly disabled (e.g. by behavior tree node).
* When true, the character's JPH::BodyID is removed from the physics
* system but the JPH::Character object is kept alive so it can be
* re-added later. This is separate from 'enabled' which controls
* whether the character system processes this entity at all. */
bool physicsDisabled = false;
/* Dirty flag — triggers rebuild of the Jolt character */
bool dirty = true;
/* When true, the scene node position is driven by root motion
* (AnimationTreeSystem), not by physics. The physics character
* position is synced to match the scene node each frame, and
* physics does NOT write its position back to the scene node. */
bool useRootMotion = false;
/* Floor detection: raycast downward to find ground before enabling gravity */
bool hasFloor = false;
float floorCheckDistance = 2.0f;
bool useGravity = true;
float getHalfHeight() const
{
return height * 0.5f;
}
float getTotalHeight() const
{
return height + 2.0f * radius;
}
};
#endif // EDITSCENE_CHARACTER_HPP

View File

@@ -0,0 +1,20 @@
#include "../ui/ComponentRegistration.hpp"
#include "Character.hpp"
#include "../ui/CharacterEditor.hpp"
REGISTER_COMPONENT_GROUP("Character Physics", "Physics",
CharacterComponent, CharacterEditor)
{
registry.registerComponent<CharacterComponent>(
"Character Physics", "Physics", std::make_unique<CharacterEditor>(),
// Adder
[](flecs::entity e) {
if (!e.has<CharacterComponent>())
e.set<CharacterComponent>({});
},
// Remover
[](flecs::entity e) {
if (e.has<CharacterComponent>())
e.remove<CharacterComponent>();
});
}

View File

@@ -0,0 +1,30 @@
#ifndef EDITSCENE_CHARACTERSLOTS_HPP
#define EDITSCENE_CHARACTERSLOTS_HPP
#pragma once
#include <Ogre.h>
#include <unordered_map>
/**
* Multi-slot mesh component for character parts sharing a skeleton.
* The "face" slot (or first available slot) serves as the master skeleton.
*/
struct CharacterSlotsComponent {
Ogre::String age = "adult";
Ogre::String sex = "male";
std::unordered_map<Ogre::String, Ogre::String> slots;
bool dirty = true;
/* Runtime: master entity with shared skeleton (set by CharacterSlotSystem) */
Ogre::Entity *masterEntity = nullptr;
/**
* Front-facing axis for this character model.
* Most models face -Z (NEGATIVE_UNIT_Z), but some face +Z.
* This is used by path following to rotate the character correctly.
*/
Ogre::Vector3 frontAxis = Ogre::Vector3::NEGATIVE_UNIT_Z;
CharacterSlotsComponent() = default;
};
#endif // EDITSCENE_CHARACTERSLOTS_HPP

View File

@@ -0,0 +1,35 @@
#include "CharacterSlots.hpp"
#include "../ui/ComponentRegistration.hpp"
#include "../ui/CharacterSlotsEditor.hpp"
#include "../systems/CharacterSlotSystem.hpp"
#include "Transform.hpp"
REGISTER_COMPONENT_GROUP("Character Slots", "Rendering",
CharacterSlotsComponent, CharacterSlotsEditor)
{
CharacterSlotSystem::loadCatalog();
registry.registerComponent<CharacterSlotsComponent>(
"Character Slots",
"Rendering",
std::make_unique<CharacterSlotsEditor>(sceneMgr),
/* Adder */
[sceneMgr](flecs::entity e) {
if (!e.has<TransformComponent>()) {
TransformComponent transform;
transform.node =
sceneMgr->getRootSceneNode()
->createChildSceneNode();
e.set<TransformComponent>(transform);
}
CharacterSlotsComponent cs;
cs.dirty = true;
e.set<CharacterSlotsComponent>(cs);
},
/* Remover */
[sceneMgr](flecs::entity e) {
(void)sceneMgr;
e.remove<CharacterSlotsComponent>();
}
);
}

View File

@@ -0,0 +1,131 @@
#ifndef EDITSCENE_DIALOGUE_COMPONENT_HPP
#define EDITSCENE_DIALOGUE_COMPONENT_HPP
#pragma once
#include <Ogre.h>
#include <functional>
#include <string>
#include <vector>
/**
* Visual-novel style dialogue box component.
*
* Displays a narration text box at the bottom of the screen with optional
* player choices. The dialogue can be driven via the EventBus system
* (using "dialogue_show" event) or directly via the component API.
*
* Only active in game mode (GamePlayState::Playing).
*
* Event payload (EventParams) parameters:
* "text" (string) - Narration text to display
* "choices" (string_array) - Array of choice label strings (Lua table)
* "speaker" (string) - Optional speaker name
* "auto_progress" (int) - If 1, clicking anywhere progresses (no choices)
*
* Component state transitions:
* Idle -> Showing (on show() or event)
* Showing -> AwaitingChoice (if choices provided)
* Showing -> Idle (if no choices, on click progress)
* AwaitingChoice -> Idle (on choice selected)
*/
struct DialogueComponent {
/** Current state of the dialogue box */
enum class State {
Idle, ///< No dialogue active
Showing, ///< Text is being displayed
AwaitingChoice ///< Waiting for player to pick a choice
};
State state = State::Idle;
/** The narration text to display */
Ogre::String text;
/** Optional speaker name (displayed above the text) */
Ogre::String speaker;
/** Player choice labels (empty = no choices, click to progress) */
std::vector<Ogre::String> choices;
/** Font configuration */
Ogre::String fontName = "Jupiteroid-Regular.ttf";
float fontSize = 24.0f;
/** Speaker name font size (slightly smaller) */
float speakerFontSize = 20.0f;
/** Background opacity (0.0 - 1.0) */
float backgroundOpacity = 0.85f;
/** Height of the dialogue box as fraction of screen height (0.0 - 1.0) */
float boxHeightFraction = 0.25f;
/** Vertical position as fraction from top (0.0 = top, 0.75 = bottom quarter) */
float boxPositionFraction = 0.75f;
/** Whether the dialogue box is enabled (can be toggled) */
bool enabled = true;
/** Callback invoked when a choice is selected (choice index, 1-based) */
std::function<void(int)> onChoiceSelected;
/** Callback invoked when dialogue is dismissed (no choices mode) */
std::function<void()> onDismissed;
/** Callback invoked when dialogue starts showing */
std::function<void()> onShow;
/* --- API --- */
/** Show dialogue with given text and optional choices */
void show(const Ogre::String &narrationText,
const std::vector<Ogre::String> &choiceLabels = {},
const Ogre::String &speakerName = "")
{
text = narrationText;
choices = choiceLabels;
speaker = speakerName;
state = choices.empty() ? State::Showing :
State::AwaitingChoice;
if (onShow)
onShow();
}
/** Progress the dialogue (click-through when no choices) */
void progress()
{
if (state == State::Showing && choices.empty()) {
state = State::Idle;
if (onDismissed)
onDismissed();
}
}
/** Select a choice by 1-based index */
void selectChoice(int index)
{
if (state == State::AwaitingChoice && index >= 1 &&
index <= (int)choices.size()) {
state = State::Idle;
if (onChoiceSelected)
onChoiceSelected(index);
}
}
/** Check if dialogue is currently active */
bool isActive() const
{
return state != State::Idle;
}
/** Reset dialogue to idle state */
void reset()
{
state = State::Idle;
text.clear();
choices.clear();
speaker.clear();
}
};
#endif // EDITSCENE_DIALOGUE_COMPONENT_HPP

View File

@@ -0,0 +1,23 @@
#include "../ui/ComponentRegistration.hpp"
#include "../ui/DialogueEditor.hpp"
#include "DialogueComponent.hpp"
REGISTER_COMPONENT_GROUP("Dialogue Box", "Game", DialogueComponent,
DialogueEditor)
{
registry.registerComponent<DialogueComponent>(
DialogueComponent_name, DialogueComponent_group,
std::make_unique<DialogueEditor>(),
// Adder
[](flecs::entity e) {
if (!e.has<DialogueComponent>()) {
e.set<DialogueComponent>({});
}
},
// Remover
[](flecs::entity e) {
if (e.has<DialogueComponent>()) {
e.remove<DialogueComponent>();
}
});
}

View File

@@ -0,0 +1,13 @@
#ifndef EDITSCENE_EDITORMARKER_HPP
#define EDITSCENE_EDITORMARKER_HPP
#pragma once
/**
* Marker component for entities created in the editor
* Used to filter out flecs internal entities (components, systems, etc.)
*/
struct EditorMarkerComponent {
// Empty marker component
};
#endif // EDITSCENE_EDITORMARKER_HPP

View File

@@ -0,0 +1,21 @@
#ifndef EDITSCENE_ENTITYNAME_HPP
#define EDITSCENE_ENTITYNAME_HPP
#pragma once
#include <Ogre.h>
/**
* Name component for Flecs entities
* Used for display in the hierarchy window
*/
struct EntityNameComponent {
Ogre::String name;
EntityNameComponent() = default;
explicit EntityNameComponent(const Ogre::String &n)
: name(n)
{
}
};
#endif // EDITSCENE_ENTITYNAME_HPP

View File

@@ -0,0 +1,21 @@
#ifndef EDITSCENE_EVENT_HANDLER_HPP
#define EDITSCENE_EVENT_HANDLER_HPP
#pragma once
#include <Ogre.h>
/**
* Event-driven behavior tree handler component.
*
* When the specified event is received, the referenced GoapAction's
* behavior tree is executed for this entity. Event parameters
* (EventParams) are injected into the entity's GoapBlackboard before
* the tree runs and cleaned up when the tree completes.
*/
struct EventHandlerComponent {
Ogre::String eventName;
Ogre::String actionName;
bool enabled = true;
};
#endif // EDITSCENE_EVENT_HANDLER_HPP

View File

@@ -0,0 +1,21 @@
#include "EventHandler.hpp"
#include "../ui/ComponentRegistration.hpp"
#include "../ui/EventHandlerEditor.hpp"
REGISTER_COMPONENT_GROUP("Event Handler", "Game", EventHandlerComponent,
EventHandlerEditor)
{
registry.registerComponent<EventHandlerComponent>(
"Event Handler", "Game",
std::make_unique<EventHandlerEditor>(),
// Adder
[](flecs::entity e) {
if (!e.has<EventHandlerComponent>())
e.set<EventHandlerComponent>({});
},
// Remover
[](flecs::entity e) {
if (e.has<EventHandlerComponent>())
e.remove<EventHandlerComponent>();
});
}

View File

@@ -0,0 +1,739 @@
#ifndef EDITSCENE_EVENT_PARAMS_HPP
#define EDITSCENE_EVENT_PARAMS_HPP
#pragma once
#include <Ogre.h>
#include <cstdint>
#include <cstring>
#include <string>
#include <vector>
#include <cassert>
/**
* @file EventParams.hpp
* @brief Tagged union type for event parameters.
*
* A C++11-compatible, RTTI-free tagged union that supports:
* - Entity ID (uint64_t)
* - Integer (int64_t)
* - Float (float)
* - Double (double)
* - String (std::string)
* - Array of entity IDs (std::vector<uint64_t>)
* - Array of integers (std::vector<int64_t>)
* - Array of floats (std::vector<float>)
* - Array of doubles (std::vector<double>)
* - Array of strings (std::vector<std::string>)
*
* Named parameters are stored as a map of string -> EventValue,
* where EventValue is a tagged union of the above types.
*/
// Forward declaration for friend function
struct lua_State;
namespace editScene
{
// ---------------------------------------------------------------------------
// EventValue: A single tagged-union value
// ---------------------------------------------------------------------------
struct EventValue {
enum Type {
NIL = 0,
ENTITY_ID,
INT,
FLOAT,
DOUBLE,
STRING,
ENTITY_ID_ARRAY,
INT_ARRAY,
FLOAT_ARRAY,
DOUBLE_ARRAY,
STRING_ARRAY
};
Type type;
union {
uint64_t asEntityId;
int64_t asInt;
float asFloat;
double asDouble;
};
// Heap-allocated data (strings and arrays)
// We use raw pointers to avoid std::unique_ptr (C++11 compatible)
std::string *strPtr;
void *arrayPtr; // points to std::vector<T>*
size_t arraySize;
EventValue()
: type(NIL)
, asEntityId(0)
, strPtr(nullptr)
, arrayPtr(nullptr)
, arraySize(0)
{
}
explicit EventValue(uint64_t entityId)
: type(ENTITY_ID)
, asEntityId(entityId)
, strPtr(nullptr)
, arrayPtr(nullptr)
, arraySize(0)
{
}
explicit EventValue(int64_t val)
: type(INT)
, asInt(val)
, strPtr(nullptr)
, arrayPtr(nullptr)
, arraySize(0)
{
}
explicit EventValue(int val)
: type(INT)
, asInt(static_cast<int64_t>(val))
, strPtr(nullptr)
, arrayPtr(nullptr)
, arraySize(0)
{
}
explicit EventValue(float val)
: type(FLOAT)
, asFloat(val)
, strPtr(nullptr)
, arrayPtr(nullptr)
, arraySize(0)
{
}
explicit EventValue(double val)
: type(DOUBLE)
, asDouble(val)
, strPtr(nullptr)
, arrayPtr(nullptr)
, arraySize(0)
{
}
explicit EventValue(const std::string &val)
: type(STRING)
, asEntityId(0)
, strPtr(new std::string(val))
, arrayPtr(nullptr)
, arraySize(0)
{
}
explicit EventValue(const char *val)
: type(STRING)
, asEntityId(0)
, strPtr(new std::string(val ? val : ""))
, arrayPtr(nullptr)
, arraySize(0)
{
}
// Array constructors
explicit EventValue(const std::vector<uint64_t> &arr)
: type(ENTITY_ID_ARRAY)
, asEntityId(0)
, strPtr(nullptr)
, arrayPtr(new std::vector<uint64_t>(arr))
, arraySize(arr.size())
{
}
explicit EventValue(const std::vector<int64_t> &arr)
: type(INT_ARRAY)
, asEntityId(0)
, strPtr(nullptr)
, arrayPtr(new std::vector<int64_t>(arr))
, arraySize(arr.size())
{
}
explicit EventValue(const std::vector<int> &arr)
: type(INT_ARRAY)
, asEntityId(0)
, strPtr(nullptr)
, arrayPtr(new std::vector<int64_t>(arr.begin(), arr.end()))
, arraySize(arr.size())
{
}
explicit EventValue(const std::vector<float> &arr)
: type(FLOAT_ARRAY)
, asEntityId(0)
, strPtr(nullptr)
, arrayPtr(new std::vector<float>(arr))
, arraySize(arr.size())
{
}
explicit EventValue(const std::vector<double> &arr)
: type(DOUBLE_ARRAY)
, asEntityId(0)
, strPtr(nullptr)
, arrayPtr(new std::vector<double>(arr))
, arraySize(arr.size())
{
}
explicit EventValue(const std::vector<std::string> &arr)
: type(STRING_ARRAY)
, asEntityId(0)
, strPtr(nullptr)
, arrayPtr(new std::vector<std::string>(arr))
, arraySize(arr.size())
{
}
// Copy constructor
EventValue(const EventValue &other)
: type(other.type)
, asEntityId(other.asEntityId)
, strPtr(nullptr)
, arrayPtr(nullptr)
, arraySize(other.arraySize)
{
copyHeapData(other);
}
// Copy assignment
EventValue &operator=(const EventValue &other)
{
if (this != &other) {
destroyHeapData();
type = other.type;
asEntityId = other.asEntityId;
arraySize = other.arraySize;
strPtr = nullptr;
arrayPtr = nullptr;
copyHeapData(other);
}
return *this;
}
// Move constructor
EventValue(EventValue &&other) noexcept : type(other.type),
asEntityId(other.asEntityId),
strPtr(other.strPtr),
arrayPtr(other.arrayPtr),
arraySize(other.arraySize)
{
other.type = NIL;
other.strPtr = nullptr;
other.arrayPtr = nullptr;
other.arraySize = 0;
}
// Move assignment
EventValue &operator=(EventValue &&other) noexcept
{
if (this != &other) {
destroyHeapData();
type = other.type;
asEntityId = other.asEntityId;
strPtr = other.strPtr;
arrayPtr = other.arrayPtr;
arraySize = other.arraySize;
other.type = NIL;
other.strPtr = nullptr;
other.arrayPtr = nullptr;
other.arraySize = 0;
}
return *this;
}
~EventValue()
{
destroyHeapData();
}
// --- Accessors ---
Type getType() const
{
return type;
}
uint64_t getEntityId() const
{
assert(type == ENTITY_ID);
return asEntityId;
}
int64_t getInt() const
{
assert(type == INT);
return asInt;
}
float getFloat() const
{
assert(type == FLOAT);
return asFloat;
}
double getDouble() const
{
assert(type == DOUBLE);
return asDouble;
}
const std::string &getString() const
{
assert(type == STRING && strPtr != nullptr);
return *strPtr;
}
const std::vector<uint64_t> &getEntityIdArray() const
{
assert(type == ENTITY_ID_ARRAY && arrayPtr != nullptr);
return *static_cast<const std::vector<uint64_t> *>(arrayPtr);
}
const std::vector<int64_t> &getIntArray() const
{
assert(type == INT_ARRAY && arrayPtr != nullptr);
return *static_cast<const std::vector<int64_t> *>(arrayPtr);
}
const std::vector<float> &getFloatArray() const
{
assert(type == FLOAT_ARRAY && arrayPtr != nullptr);
return *static_cast<const std::vector<float> *>(arrayPtr);
}
const std::vector<double> &getDoubleArray() const
{
assert(type == DOUBLE_ARRAY && arrayPtr != nullptr);
return *static_cast<const std::vector<double> *>(arrayPtr);
}
const std::vector<std::string> &getStringArray() const
{
assert(type == STRING_ARRAY && arrayPtr != nullptr);
return *static_cast<const std::vector<std::string> *>(arrayPtr);
}
// --- Convenience: get numeric value as double ---
double asNumeric() const
{
switch (type) {
case INT:
return static_cast<double>(asInt);
case FLOAT:
return static_cast<double>(asFloat);
case DOUBLE:
return asDouble;
case ENTITY_ID:
return static_cast<double>(asEntityId);
default:
return 0.0;
}
}
// --- Equality ---
bool operator==(const EventValue &other) const
{
if (type != other.type)
return false;
switch (type) {
case NIL:
return true;
case ENTITY_ID:
return asEntityId == other.asEntityId;
case INT:
return asInt == other.asInt;
case FLOAT:
return asFloat == other.asFloat;
case DOUBLE:
return asDouble == other.asDouble;
case STRING:
return strPtr && other.strPtr &&
*strPtr == *other.strPtr;
case ENTITY_ID_ARRAY:
return arrayPtr && other.arrayPtr &&
getEntityIdArray() == other.getEntityIdArray();
case INT_ARRAY:
return arrayPtr && other.arrayPtr &&
getIntArray() == other.getIntArray();
case FLOAT_ARRAY:
return arrayPtr && other.arrayPtr &&
getFloatArray() == other.getFloatArray();
case DOUBLE_ARRAY:
return arrayPtr && other.arrayPtr &&
getDoubleArray() == other.getDoubleArray();
case STRING_ARRAY:
return arrayPtr && other.arrayPtr &&
getStringArray() == other.getStringArray();
}
return false;
}
bool operator!=(const EventValue &other) const
{
return !(*this == other);
}
private:
void copyHeapData(const EventValue &other)
{
if (other.type == STRING && other.strPtr) {
strPtr = new std::string(*other.strPtr);
} else if (other.type == ENTITY_ID_ARRAY && other.arrayPtr) {
arrayPtr = new std::vector<uint64_t>(
*static_cast<const std::vector<uint64_t> *>(
other.arrayPtr));
} else if (other.type == INT_ARRAY && other.arrayPtr) {
arrayPtr = new std::vector<int64_t>(
*static_cast<const std::vector<int64_t> *>(
other.arrayPtr));
} else if (other.type == FLOAT_ARRAY && other.arrayPtr) {
arrayPtr = new std::vector<float>(
*static_cast<const std::vector<float> *>(
other.arrayPtr));
} else if (other.type == DOUBLE_ARRAY && other.arrayPtr) {
arrayPtr = new std::vector<double>(
*static_cast<const std::vector<double> *>(
other.arrayPtr));
} else if (other.type == STRING_ARRAY && other.arrayPtr) {
arrayPtr = new std::vector<std::string>(
*static_cast<const std::vector<std::string> *>(
other.arrayPtr));
}
}
void destroyHeapData()
{
if (type == STRING) {
delete strPtr;
} else if (type == ENTITY_ID_ARRAY) {
delete static_cast<std::vector<uint64_t> *>(arrayPtr);
} else if (type == INT_ARRAY) {
delete static_cast<std::vector<int64_t> *>(arrayPtr);
} else if (type == FLOAT_ARRAY) {
delete static_cast<std::vector<float> *>(arrayPtr);
} else if (type == DOUBLE_ARRAY) {
delete static_cast<std::vector<double> *>(arrayPtr);
} else if (type == STRING_ARRAY) {
delete static_cast<std::vector<std::string> *>(
arrayPtr);
}
strPtr = nullptr;
arrayPtr = nullptr;
}
};
// ---------------------------------------------------------------------------
// EventParams: A map of named EventValue entries
// ---------------------------------------------------------------------------
class EventParams {
public:
EventParams() = default;
// --- Set values ---
void setEntityId(const std::string &key, uint64_t val)
{
m_values[key] = EventValue(val);
}
void setInt(const std::string &key, int64_t val)
{
m_values[key] = EventValue(val);
}
void setFloat(const std::string &key, float val)
{
m_values[key] = EventValue(val);
}
void setDouble(const std::string &key, double val)
{
m_values[key] = EventValue(val);
}
void setString(const std::string &key, const std::string &val)
{
m_values[key] = EventValue(val);
}
void setEntityIdArray(const std::string &key,
const std::vector<uint64_t> &val)
{
m_values[key] = EventValue(val);
}
void setIntArray(const std::string &key,
const std::vector<int64_t> &val)
{
m_values[key] = EventValue(val);
}
void setFloatArray(const std::string &key,
const std::vector<float> &val)
{
m_values[key] = EventValue(val);
}
void setDoubleArray(const std::string &key,
const std::vector<double> &val)
{
m_values[key] = EventValue(val);
}
void setStringArray(const std::string &key,
const std::vector<std::string> &val)
{
m_values[key] = EventValue(val);
}
// --- Get values ---
bool has(const std::string &key) const
{
return m_values.find(key) != m_values.end();
}
const EventValue *get(const std::string &key) const
{
auto it = m_values.find(key);
if (it != m_values.end())
return &it->second;
return nullptr;
}
EventValue *get(const std::string &key)
{
auto it = m_values.find(key);
if (it != m_values.end())
return &it->second;
return nullptr;
}
// --- Typed getters with defaults ---
uint64_t getEntityId(const std::string &key,
uint64_t defaultVal = 0) const
{
auto v = get(key);
if (v && v->getType() == EventValue::ENTITY_ID)
return v->getEntityId();
return defaultVal;
}
int64_t getInt(const std::string &key, int64_t defaultVal = 0) const
{
auto v = get(key);
if (v && v->getType() == EventValue::INT)
return v->getInt();
return defaultVal;
}
float getFloat(const std::string &key, float defaultVal = 0.0f) const
{
auto v = get(key);
if (v && v->getType() == EventValue::FLOAT)
return v->getFloat();
return defaultVal;
}
double getDouble(const std::string &key, double defaultVal = 0.0) const
{
auto v = get(key);
if (v && v->getType() == EventValue::DOUBLE)
return v->getDouble();
return defaultVal;
}
std::string getString(const std::string &key,
const std::string &defaultVal = "") const
{
auto v = get(key);
if (v && v->getType() == EventValue::STRING)
return v->getString();
return defaultVal;
}
// --- Remove ---
void remove(const std::string &key)
{
m_values.erase(key);
}
// --- Clear ---
void clear()
{
m_values.clear();
}
// --- Size ---
size_t size() const
{
return m_values.size();
}
bool empty() const
{
return m_values.empty();
}
// --- Iteration ---
typedef std::unordered_map<std::string, EventValue>::const_iterator
ConstIterator;
typedef std::unordered_map<std::string, EventValue>::iterator Iterator;
ConstIterator begin() const
{
return m_values.begin();
}
ConstIterator end() const
{
return m_values.end();
}
Iterator begin()
{
return m_values.begin();
}
Iterator end()
{
return m_values.end();
}
// --- Merge ---
void merge(const EventParams &other)
{
for (const auto &pair : other.m_values)
m_values[pair.first] = pair.second;
}
// --- Equality ---
bool operator==(const EventParams &other) const
{
return m_values == other.m_values;
}
bool operator!=(const EventParams &other) const
{
return !(*this == other);
}
// --- Dump for debugging ---
std::string dump() const
{
std::string result = "EventParams:\n";
for (const auto &pair : m_values) {
result += " " + pair.first + " = ";
switch (pair.second.getType()) {
case EventValue::NIL:
result += "nil";
break;
case EventValue::ENTITY_ID:
result += "entity:" +
std::to_string(
pair.second.getEntityId());
break;
case EventValue::INT:
result += std::to_string(pair.second.getInt());
break;
case EventValue::FLOAT:
result +=
std::to_string(pair.second.getFloat());
break;
case EventValue::DOUBLE:
result +=
std::to_string(pair.second.getDouble());
break;
case EventValue::STRING:
result += "'" + pair.second.getString() + "'";
break;
case EventValue::ENTITY_ID_ARRAY: {
result += "[";
const auto &arr =
pair.second.getEntityIdArray();
for (size_t i = 0; i < arr.size(); i++) {
if (i > 0)
result += ", ";
result += "e:" + std::to_string(arr[i]);
}
result += "]";
break;
}
case EventValue::INT_ARRAY: {
result += "[";
const auto &arr = pair.second.getIntArray();
for (size_t i = 0; i < arr.size(); i++) {
if (i > 0)
result += ", ";
result += std::to_string(arr[i]);
}
result += "]";
break;
}
case EventValue::FLOAT_ARRAY: {
result += "[";
const auto &arr = pair.second.getFloatArray();
for (size_t i = 0; i < arr.size(); i++) {
if (i > 0)
result += ", ";
result += std::to_string(arr[i]);
}
result += "]";
break;
}
case EventValue::DOUBLE_ARRAY: {
result += "[";
const auto &arr = pair.second.getDoubleArray();
for (size_t i = 0; i < arr.size(); i++) {
if (i > 0)
result += ", ";
result += std::to_string(arr[i]);
}
result += "]";
break;
}
case EventValue::STRING_ARRAY: {
result += "[";
const auto &arr = pair.second.getStringArray();
for (size_t i = 0; i < arr.size(); i++) {
if (i > 0)
result += ", ";
result += "'" + arr[i] + "'";
}
result += "]";
break;
}
}
result += "\n";
}
return result;
}
// Allow LuaEventApi to access m_values directly for efficiency
friend EventParams readEventParams(lua_State *L, int idx);
private:
std::unordered_map<std::string, EventValue> m_values;
};
} // namespace editScene
#endif // EDITSCENE_EVENT_PARAMS_HPP

View File

@@ -0,0 +1,11 @@
#ifndef EDITSCENE_GENERATEDPHYSICSTAG_HPP
#define EDITSCENE_GENERATEDPHYSICSTAG_HPP
#pragma once
/**
* Marker component for entities auto-generated by systems (e.g. physics colliders).
* Entities with this tag are hidden from the editor hierarchy and not serialized.
*/
struct GeneratedPhysicsTag {};
#endif // EDITSCENE_GENERATEDPHYSICSTAG_HPP

View File

@@ -0,0 +1,74 @@
#ifndef EDITSCENE_GOAP_ACTION_HPP
#define EDITSCENE_GOAP_ACTION_HPP
#pragma once
#include "GoapBlackboard.hpp"
#include "BehaviorTree.hpp"
#include <Ogre.h>
/**
* A GOAP action definition.
*
* Actions live in the ActionDatabase and can be executed by any character.
* Each action has preconditions (required blackboard state),
* effects (resulting blackboard state), a cost, and a behavior tree.
*/
struct GoapAction {
Ogre::String name;
int cost = 1;
// GOAP preconditions and effects
GoapBlackboard preconditions;
GoapBlackboard effects;
// Bitmask for precondition checking. Only bits set here are compared.
// Defaults to all 1s (check all bits).
uint64_t preconditionMask = ~0ULL;
// Behavior tree to execute when this action is selected
BehaviorTreeNode behaviorTree;
// Optional: reference to a named behavior tree asset
Ogre::String behaviorTreeName;
GoapAction() = default;
explicit GoapAction(const Ogre::String &name_, int cost_ = 1)
: name(name_)
, cost(cost_)
{
}
// Check if the given blackboard satisfies this action's preconditions.
// Only bits in preconditionMask are compared.
bool canRun(const GoapBlackboard &blackboard) const
{
// Fast-path: if mask covers all set bits, use standard check
if (preconditionMask == ~0ULL)
return blackboard.satisfies(preconditions);
// Masked check: only compare bits in preconditionMask
uint64_t relevantBits = preconditionMask;
uint64_t bbBits = blackboard.bits & relevantBits;
uint64_t preBits = preconditions.bits & relevantBits;
uint64_t bbMask = blackboard.mask & relevantBits;
uint64_t preMask = preconditions.mask & relevantBits;
// All precondition bits must be present in blackboard
if ((bbMask & preMask) != preMask)
return false;
if ((bbBits & preMask) != preBits)
return false;
// Check integer values
for (const auto &kv : preconditions.values) {
if (!blackboard.hasValue(kv.first))
return false;
if (blackboard.getValue(kv.first) != kv.second)
return false;
}
return true;
}
};
#endif // EDITSCENE_GOAP_ACTION_HPP

View File

@@ -0,0 +1,227 @@
#include "GoapBlackboard.hpp"
#include <cstdlib>
std::array<std::string, 64> &GoapBlackboard::getBitNameRegistry()
{
static std::array<std::string, 64> registry;
static bool initialized = false;
if (!initialized) {
for (auto &s : registry)
s.clear();
initialized = true;
}
return registry;
}
void GoapBlackboard::setBitName(int index, const std::string &name)
{
if (index < 0 || index >= 64)
return;
getBitNameRegistry()[index] = name;
}
const char *GoapBlackboard::getBitName(int index)
{
if (index < 0 || index >= 64)
return nullptr;
const auto &name = getBitNameRegistry()[index];
return name.empty() ? nullptr : name.c_str();
}
int GoapBlackboard::findBitByName(const std::string &name)
{
if (name.empty())
return -1;
const auto &registry = getBitNameRegistry();
for (int i = 0; i < 64; i++) {
if (registry[i] == name)
return i;
}
return -1;
}
bool GoapBlackboard::satisfies(const GoapBlackboard &other) const
{
// Only compare bits that both sides consider relevant
uint64_t relevantMask = bitmask & other.bitmask;
// Check bits: for every bit set in other's mask (within relevant mask),
// our bit must match
uint64_t commonMask = mask & other.mask & relevantMask;
if ((bits & commonMask) != (other.bits & commonMask))
return false;
// Also check bits that other has set but we don't (within relevant mask)
uint64_t missingMask = other.mask & ~mask & relevantMask;
if (missingMask) {
// Other requires bits we don't have set -> fail
// But only if other has those bits set to 1
if ((other.bits & missingMask) != 0)
return false;
}
// Check values: for every key in other, our value must match
for (const auto &pair : other.values) {
auto it = values.find(pair.first);
if (it == values.end()) {
// If we don't have the key, only satisfy if target is 0
if (pair.second != 0)
return false;
} else if (it->second != pair.second) {
return false;
}
}
return true;
}
void GoapBlackboard::apply(const GoapBlackboard &other)
{
// Apply bit effects
bits = (bits & ~other.mask) | (other.bits & other.mask);
mask |= other.mask;
// Apply value effects
for (const auto &pair : other.values)
values[pair.first] = pair.second;
}
bool GoapBlackboard::getScalarValue(const std::string &key,
float &out) const
{
auto itf = floatValues.find(key);
if (itf != floatValues.end()) {
out = itf->second;
return true;
}
auto iti = values.find(key);
if (iti != values.end()) {
out = static_cast<float>(iti->second);
return true;
}
return false;
}
int GoapBlackboard::distanceTo(const GoapBlackboard &target,
bool ignoreValues) const
{
int distance = 0;
// Only compare bits that both sides consider relevant
uint64_t relevantMask = bitmask & target.bitmask;
// Bit differences (within relevant mask)
uint64_t commonMask = mask & target.mask & relevantMask;
distance += __builtin_popcountll((bits ^ target.bits) & commonMask);
// Bits target cares about but we don't have (within relevant mask)
uint64_t missingInUs = target.mask & ~mask & relevantMask;
distance += __builtin_popcountll(target.bits & missingInUs);
// Bits we care about but target doesn't (within relevant mask)
uint64_t missingInTarget = mask & ~target.mask & relevantMask;
distance += __builtin_popcountll(bits & missingInTarget);
if (ignoreValues)
return distance;
// Value differences (int only — planner ignores float/vec3)
for (const auto &pair : target.values) {
auto it = values.find(pair.first);
if (it == values.end())
distance += std::abs(pair.second);
else
distance += std::abs(it->second - pair.second);
}
// Values we have that target doesn't (may need to be cleared)
for (const auto &pair : values) {
if (target.values.find(pair.first) == target.values.end())
distance += std::abs(pair.second);
}
return distance;
}
Ogre::String GoapBlackboard::dump() const
{
Ogre::String result = "Blackboard:\n";
result += " Bits:\n";
for (int i = 0; i < 64; i++) {
if (hasBit(i)) {
const char *name = getBitName(i);
if (name)
result += " " + Ogre::String(name) + " = " +
(getBit(i) ? "true" : "false") + "\n";
else
result += " bit[" +
Ogre::StringConverter::toString(i) + "] = " +
(getBit(i) ? "true" : "false") + "\n";
}
}
if (!values.empty()) {
result += " Int values:\n";
for (const auto &pair : values)
result += " " + pair.first + " = " +
Ogre::StringConverter::toString(pair.second) +
"\n";
}
if (!floatValues.empty()) {
result += " Float values:\n";
for (const auto &pair : floatValues)
result += " " + pair.first + " = " +
Ogre::StringConverter::toString(pair.second) +
"\n";
}
if (!vec3Values.empty()) {
result += " Vec3 values:\n";
for (const auto &pair : vec3Values)
result += " " + pair.first + " = (" +
Ogre::StringConverter::toString(pair.second.x) +
", " +
Ogre::StringConverter::toString(pair.second.y) +
", " +
Ogre::StringConverter::toString(pair.second.z) +
")\n";
}
if (!stringValues.empty()) {
result += " String values:\n";
for (const auto &pair : stringValues)
result += " " + pair.first + " = " + pair.second + "\n";
}
return result;
}
void GoapBlackboard::merge(const GoapBlackboard &other)
{
// Merge bits
bits = (bits & ~other.mask) | (other.bits & other.mask);
mask |= other.mask;
// Merge values
for (const auto &pair : other.values)
values[pair.first] = pair.second;
for (const auto &pair : other.floatValues)
floatValues[pair.first] = pair.second;
for (const auto &pair : other.vec3Values)
vec3Values[pair.first] = pair.second;
for (const auto &pair : other.stringValues)
stringValues[pair.first] = pair.second;
}
std::vector<int> GoapBlackboard::getSetBits() const
{
std::vector<int> result;
uint64_t m = mask;
while (m) {
int bit = __builtin_ctzll(m);
result.push_back(bit);
m &= m - 1;
}
return result;
}

View File

@@ -0,0 +1,236 @@
#ifndef EDITSCENE_GOAP_BLACKBOARD_HPP
#define EDITSCENE_GOAP_BLACKBOARD_HPP
#pragma once
#include <Ogre.h>
#include <array>
#include <cstdint>
#include <string>
#include <unordered_map>
#include <vector>
/**
* Lightweight GOAP blackboard for action preconditions, effects,
* and per-character runtime state.
*
* Uses a 64-bit bitfield for fast boolean flag checks (used by GOAP planner).
* Supports int, float, and Vector3 values (int is used for preconditions/effects;
* float/vec3 are for behavior-tree-driven character state).
*/
struct GoapBlackboard {
// Boolean flags: 64 bits available. These are used by the GOAP planner.
uint64_t bits = 0;
uint64_t mask = 0; // which bits are actually set
// Bitmask for comparison: only bits set here are compared.
// Defaults to all 1s (compare all bits).
uint64_t bitmask = ~0ULL;
// Named integer values (health, hunger, etc.) — used by preconditions/effects
std::unordered_map<std::string, int> values;
// Named float values — runtime character state
std::unordered_map<std::string, float> floatValues;
// Named Vector3 values — runtime character state
std::unordered_map<std::string, Ogre::Vector3> vec3Values;
// Named string values — event params, tags, etc.
std::unordered_map<std::string, Ogre::String> stringValues;
GoapBlackboard() = default;
/* --- Bit accessors --- */
void setBit(int index, bool value)
{
if (index < 0 || index >= 64)
return;
uint64_t bit = 1ULL << index;
mask |= bit;
if (value)
bits |= bit;
else
bits &= ~bit;
}
bool getBit(int index) const
{
if (index < 0 || index >= 64)
return false;
return (bits >> index) & 1ULL;
}
bool hasBit(int index) const
{
if (index < 0 || index >= 64)
return false;
return (mask >> index) & 1ULL;
}
void clearBit(int index)
{
if (index < 0 || index >= 64)
return;
uint64_t bit = 1ULL << index;
mask &= ~bit;
bits &= ~bit;
}
/* --- Integer value accessors (backward compat) --- */
void setValue(const std::string &key, int value)
{
values[key] = value;
}
int getValue(const std::string &key, int defaultValue = 0) const
{
auto it = values.find(key);
if (it != values.end())
return it->second;
return defaultValue;
}
bool hasValue(const std::string &key) const
{
return values.find(key) != values.end();
}
void removeValue(const std::string &key)
{
values.erase(key);
}
/* --- Float value accessors --- */
void setFloatValue(const std::string &key, float value)
{
floatValues[key] = value;
}
float getFloatValue(const std::string &key,
float defaultValue = 0.0f) const
{
auto it = floatValues.find(key);
if (it != floatValues.end())
return it->second;
return defaultValue;
}
bool hasFloatValue(const std::string &key) const
{
return floatValues.find(key) != floatValues.end();
}
void removeFloatValue(const std::string &key)
{
floatValues.erase(key);
}
/* --- Vector3 value accessors --- */
void setVec3Value(const std::string &key, const Ogre::Vector3 &value)
{
vec3Values[key] = value;
}
Ogre::Vector3 getVec3Value(
const std::string &key,
const Ogre::Vector3 &defaultValue = Ogre::Vector3::ZERO) const
{
auto it = vec3Values.find(key);
if (it != vec3Values.end())
return it->second;
return defaultValue;
}
bool hasVec3Value(const std::string &key) const
{
return vec3Values.find(key) != vec3Values.end();
}
void removeVec3Value(const std::string &key)
{
vec3Values.erase(key);
}
/* --- String value accessors --- */
void setStringValue(const std::string &key, const Ogre::String &value)
{
stringValues[key] = value;
}
Ogre::String getStringValue(const std::string &key,
const Ogre::String &defaultValue = "") const
{
auto it = stringValues.find(key);
if (it != stringValues.end())
return it->second;
return defaultValue;
}
bool hasStringValue(const std::string &key) const
{
return stringValues.find(key) != stringValues.end();
}
void removeStringValue(const std::string &key)
{
stringValues.erase(key);
}
/* --- Merge another blackboard into this one --- */
void merge(const GoapBlackboard &other);
/* --- Generic scalar lookup (tries int then float) --- */
bool getScalarValue(const std::string &key, float &out) const;
/* --- GOAP methods --- */
bool satisfies(const GoapBlackboard &other) const;
void apply(const GoapBlackboard &other);
int distanceTo(const GoapBlackboard &target,
bool ignoreValues = false) const;
/* --- Utility --- */
bool isValid() const
{
return mask != 0 || !values.empty() || !floatValues.empty() ||
!vec3Values.empty();
}
void clear()
{
bits = 0;
mask = 0;
values.clear();
floatValues.clear();
vec3Values.clear();
stringValues.clear();
}
Ogre::String dump() const;
// Bit naming: global registry for human-readable bit names
static std::array<std::string, 64> &getBitNameRegistry();
static void setBitName(int index, const std::string &name);
static const char *getBitName(int index);
static int findBitByName(const std::string &name);
// List all set bit indices
std::vector<int> getSetBits() const;
// Equality
bool operator==(const GoapBlackboard &other) const
{
return bits == other.bits && mask == other.mask &&
bitmask == other.bitmask &&
values == other.values &&
floatValues == other.floatValues &&
vec3Values == other.vec3Values &&
stringValues == other.stringValues;
}
bool operator!=(const GoapBlackboard &other) const
{
return !(*this == other);
}
};
#endif // EDITSCENE_GOAP_BLACKBOARD_HPP

View File

@@ -0,0 +1,21 @@
#include "GoapBlackboard.hpp"
#include "../ui/ComponentRegistration.hpp"
#include "../ui/GoapBlackboardComponentEditor.hpp"
REGISTER_COMPONENT_GROUP("Blackboard", "AI", GoapBlackboard,
GoapBlackboardComponentEditor)
{
registry.registerComponent<GoapBlackboard>(
"Blackboard", "AI",
std::make_unique<GoapBlackboardComponentEditor>(),
// Adder
[](flecs::entity e) {
if (!e.has<GoapBlackboard>())
e.set<GoapBlackboard>({});
},
// Remover
[](flecs::entity e) {
if (e.has<GoapBlackboard>())
e.remove<GoapBlackboard>();
});
}

View File

@@ -0,0 +1,283 @@
#include "GoapExpression.hpp"
#include <cctype>
#include <cstdlib>
#include <cstring>
void GoapExpression::skipWhitespace()
{
while (*m_pos == ' ' || *m_pos == '\t' || *m_pos == '\n' ||
*m_pos == '\r')
m_pos++;
}
bool GoapExpression::match(const char *s)
{
skipWhitespace();
size_t len = strlen(s);
if (strncmp(m_pos, s, len) == 0) {
m_pos += len;
return true;
}
return false;
}
GoapExpression::Node *GoapExpression::parsePrimary()
{
skipWhitespace();
// Parenthesized expression
if (match("(")) {
Node *node = parseExpression();
if (!node)
return nullptr;
if (!match(")")) {
setError("Expected ')'");
delete node;
return nullptr;
}
return node;
}
// Integer literal
if (isdigit(*m_pos) || (*m_pos == '-' && isdigit(m_pos[1]))) {
bool negative = false;
if (*m_pos == '-') {
negative = true;
m_pos++;
}
int value = 0;
while (isdigit(*m_pos)) {
value = value * 10 + (*m_pos - '0');
m_pos++;
}
Node *node = new Node(Node::Value);
node->value = negative ? -value : value;
return node;
}
// Variable name
if (isalpha(*m_pos) || *m_pos == '_') {
std::string name;
while (isalnum(*m_pos) || *m_pos == '_') {
name += *m_pos;
m_pos++;
}
Node *node = new Node(Node::Variable);
node->name = name;
return node;
}
setError("Unexpected character in expression");
return nullptr;
}
GoapExpression::Node *GoapExpression::parseComparison()
{
Node *left = parsePrimary();
if (!left)
return nullptr;
skipWhitespace();
if (match("==")) {
Node *right = parsePrimary();
if (!right) {
delete left;
return nullptr;
}
Node *node = new Node(Node::Equal);
node->left = left;
node->right = right;
return node;
} else if (match("!=")) {
Node *right = parsePrimary();
if (!right) {
delete left;
return nullptr;
}
Node *node = new Node(Node::NotEqual);
node->left = left;
node->right = right;
return node;
} else if (match("<=")) {
Node *right = parsePrimary();
if (!right) {
delete left;
return nullptr;
}
Node *node = new Node(Node::LessEqual);
node->left = left;
node->right = right;
return node;
} else if (match(">=")) {
Node *right = parsePrimary();
if (!right) {
delete left;
return nullptr;
}
Node *node = new Node(Node::GreaterEqual);
node->left = left;
node->right = right;
return node;
} else if (match("<")) {
Node *right = parsePrimary();
if (!right) {
delete left;
return nullptr;
}
Node *node = new Node(Node::Less);
node->left = left;
node->right = right;
return node;
} else if (match(">")) {
Node *right = parsePrimary();
if (!right) {
delete left;
return nullptr;
}
Node *node = new Node(Node::Greater);
node->left = left;
node->right = right;
return node;
}
return left;
}
GoapExpression::Node *GoapExpression::parseNot()
{
skipWhitespace();
if (match("!")) {
Node *child = parseNot();
if (!child)
return nullptr;
Node *node = new Node(Node::Not);
node->left = child;
return node;
}
return parseComparison();
}
GoapExpression::Node *GoapExpression::parseAnd()
{
Node *left = parseNot();
if (!left)
return nullptr;
while (true) {
if (match("&&")) {
Node *right = parseNot();
if (!right) {
delete left;
return nullptr;
}
Node *node = new Node(Node::And);
node->left = left;
node->right = right;
left = node;
} else {
break;
}
}
return left;
}
GoapExpression::Node *GoapExpression::parseExpression()
{
Node *left = parseAnd();
if (!left)
return nullptr;
while (true) {
if (match("||")) {
Node *right = parseAnd();
if (!right) {
delete left;
return nullptr;
}
Node *node = new Node(Node::Or);
node->left = left;
node->right = right;
left = node;
} else {
break;
}
}
return left;
}
bool GoapExpression::parse(const char *expr)
{
clear();
m_expr = expr;
m_pos = expr;
m_root = parseExpression();
if (!m_root)
return false;
skipWhitespace();
if (*m_pos != '\0') {
setError("Unexpected trailing characters");
clear();
return false;
}
return true;
}
int GoapExpression::evalNode(Node *node, const GoapBlackboard &bb) const
{
if (!node)
return 0;
switch (node->type) {
case Node::Value:
return node->value;
case Node::Variable:
return bb.getValue(node->name, 0);
case Node::Equal:
return evalNode(node->left, bb) == evalNode(node->right, bb) ? 1 : 0;
case Node::NotEqual:
return evalNode(node->left, bb) != evalNode(node->right, bb) ? 1 : 0;
case Node::Less:
return evalNode(node->left, bb) < evalNode(node->right, bb) ? 1 : 0;
case Node::Greater:
return evalNode(node->left, bb) > evalNode(node->right, bb) ? 1 : 0;
case Node::LessEqual:
return evalNode(node->left, bb) <= evalNode(node->right, bb) ? 1 : 0;
case Node::GreaterEqual:
return evalNode(node->left, bb) >= evalNode(node->right, bb) ? 1 : 0;
case Node::And:
return evalNode(node->left, bb) && evalNode(node->right, bb) ? 1 : 0;
case Node::Or:
return evalNode(node->left, bb) || evalNode(node->right, bb) ? 1 : 0;
case Node::Not:
return !evalNode(node->left, bb) ? 1 : 0;
}
return 0;
}
bool GoapExpression::evaluate(const GoapBlackboard &blackboard) const
{
if (!m_root)
return false;
return evalNode(m_root, blackboard) != 0;
}
void GoapExpression::setError(const char *msg)
{
m_error = msg;
if (m_pos && m_expr) {
m_error += " at position ";
m_error += std::to_string(m_pos - m_expr);
m_error += " near \"";
m_error += std::string(m_pos, strnlen(m_pos, 20));
m_error += "\"";
}
}
void GoapExpression::clear()
{
delete m_root;
m_root = nullptr;
m_expr = nullptr;
m_pos = nullptr;
m_error.clear();
}

View File

@@ -0,0 +1,85 @@
#ifndef EDITSCENE_GOAP_EXPRESSION_HPP
#define EDITSCENE_GOAP_EXPRESSION_HPP
#pragma once
#include "GoapBlackboard.hpp"
#include <string>
/**
* Simple expression evaluator for GOAP goal conditions.
*
* Supports:
* - Variable names (looked up in blackboard values, default 0)
* - Integer literals
* - Comparisons: ==, !=, <, >, <=, >=
* - Boolean operators: &&, ||
* - Parentheses for grouping
* - Unary negation: !
*
* Example: "health > 20 && (hunger > 50 || have_food == 1)"
*/
class GoapExpression {
public:
GoapExpression() = default;
// Parse an expression string. Returns true on success.
bool parse(const char *expr);
// Evaluate the parsed expression against a blackboard.
// Returns false if expression was not parsed successfully.
bool evaluate(const GoapBlackboard &blackboard) const;
// Get last error message
const std::string &getError() const { return m_error; }
private:
struct Node {
enum Type {
Value, // integer literal
Variable, // blackboard variable name
Equal,
NotEqual,
Less,
Greater,
LessEqual,
GreaterEqual,
And,
Or,
Not
} type;
int value = 0; // for Value
std::string name; // for Variable
Node *left = nullptr;
Node *right = nullptr;
Node(Type t)
: type(t)
{
}
~Node()
{
delete left;
delete right;
}
};
const char *m_expr = nullptr;
const char *m_pos = nullptr;
std::string m_error;
Node *m_root = nullptr;
void skipWhitespace();
bool match(const char *s);
Node *parseExpression(); // ||
Node *parseAnd(); // &&
Node *parseNot(); // !
Node *parseComparison(); // ==, !=, <, >, <=, >=
Node *parsePrimary(); // value, variable, (expr)
int evalNode(Node *node, const GoapBlackboard &bb) const;
void setError(const char *msg);
void clear();
};
#endif // EDITSCENE_GOAP_EXPRESSION_HPP

View File

@@ -0,0 +1,24 @@
#include "GoapGoal.hpp"
#include "GoapExpression.hpp"
bool GoapGoal::isSatisfied(const GoapBlackboard &blackboard) const
{
return blackboard.satisfies(target);
}
bool GoapGoal::isValid(const GoapBlackboard &blackboard) const
{
// If already satisfied, not a valid goal to pursue
if (isSatisfied(blackboard))
return false;
// Evaluate condition if present
if (!condition.empty()) {
GoapExpression expr;
if (expr.parse(condition.c_str())) {
return expr.evaluate(blackboard);
}
}
return true;
}

View File

@@ -0,0 +1,43 @@
#ifndef EDITSCENE_GOAP_GOAL_HPP
#define EDITSCENE_GOAP_GOAL_HPP
#pragma once
#include "GoapBlackboard.hpp"
#include <Ogre.h>
/**
* A GOAP goal definition.
*
* Goals are selected based on priority and validity.
* The target blackboard defines the desired world state.
* The condition string provides additional runtime validity checks
* using a simple expression language against blackboard values.
*/
struct GoapGoal {
Ogre::String name;
int priority = 1;
// Target blackboard state to achieve
GoapBlackboard target;
// Optional condition expression (e.g. "health > 20 && hunger > 50")
// If empty, the goal is always considered for validity checking
Ogre::String condition;
GoapGoal() = default;
explicit GoapGoal(const Ogre::String &name_, int priority_ = 1)
: name(name_)
, priority(priority_)
{
}
// Check if the goal is already satisfied by the given blackboard
bool isSatisfied(const GoapBlackboard &blackboard) const;
// Check if the goal is valid for the given blackboard
// (condition evaluates to true and goal is not already satisfied)
bool isValid(const GoapBlackboard &blackboard) const;
};
#endif // EDITSCENE_GOAP_GOAL_HPP

View File

@@ -0,0 +1,99 @@
#ifndef EDITSCENE_GOAP_PLANNER_HPP
#define EDITSCENE_GOAP_PLANNER_HPP
#pragma once
#include <Ogre.h>
#include <vector>
#include <string>
#include <queue>
/**
* GOAP Planner component.
*
* Holds a curated list of action and goal names from an ActionDatabase,
* plus configuration for smart-object action discovery.
* The planner resolves names against an ActionDatabase at runtime.
*
* The actionNames and goalNames lists act as external references:
* prefabs can store them even when the ActionDatabase is not
* part of the prefab itself.
*/
struct GoapPlannerComponent {
// Selected action names from ActionDatabase
std::vector<Ogre::String> actionNames;
// Selected goal names from ActionDatabase
std::vector<Ogre::String> goalNames;
// Maximum distance to search for smart objects with matching actions
float smartObjectDistance = 50.0f;
// Whether to include smart object actions in planning
bool includeSmartObjects = true;
// Optional reference to an external ActionDatabase entity by name.
Ogre::String actionDatabaseRef;
// -----------------------------------------------------------------
// Runtime plan queue (not serialized)
// -----------------------------------------------------------------
struct Plan {
std::vector<Ogre::String> actions;
int totalCost = 0;
Ogre::String goalName;
};
std::vector<Plan> planQueue;
// Planner status
enum class Status {
Idle, // No planning requested
Planning, // Currently planning
PlansAvailable, // One or more plans in queue
NoPlanFound // Planning finished but no valid plan found
};
Status status = Status::Idle;
// Goal name that was used for the current plan batch
Ogre::String currentGoalName;
// Planning control
bool planDirty = true;
int maxPlans = 3; // stop after generating this many plans
// Planning progress (for status display)
int plansGenerated = 0;
int nodesExplored = 0;
GoapPlannerComponent() = default;
void clearPlans()
{
planQueue.clear();
plansGenerated = 0;
status = Status::Idle;
}
// Pop the cheapest plan from the queue
Plan popCheapestPlan()
{
if (planQueue.empty())
return Plan();
size_t bestIdx = 0;
for (size_t i = 1; i < planQueue.size(); i++) {
if (planQueue[i].totalCost < planQueue[bestIdx].totalCost)
bestIdx = i;
}
Plan result = std::move(planQueue[bestIdx]);
planQueue.erase(planQueue.begin() + bestIdx);
if (planQueue.empty() && status == Status::PlansAvailable)
status = Status::Idle;
return result;
}
bool hasPlans() const
{
return !planQueue.empty();
}
};
#endif // EDITSCENE_GOAP_PLANNER_HPP

View File

@@ -0,0 +1,21 @@
#include "GoapPlanner.hpp"
#include "../ui/ComponentRegistration.hpp"
#include "../ui/GoapPlannerEditor.hpp"
REGISTER_COMPONENT_GROUP("GOAP Planner", "AI", GoapPlannerComponent,
GoapPlannerEditor)
{
registry.registerComponent<GoapPlannerComponent>(
"GOAP Planner", "AI",
std::make_unique<GoapPlannerEditor>(),
// Adder
[](flecs::entity e) {
if (!e.has<GoapPlannerComponent>())
e.set<GoapPlannerComponent>({});
},
// Remover
[](flecs::entity e) {
if (e.has<GoapPlannerComponent>())
e.remove<GoapPlannerComponent>();
});
}

View File

@@ -0,0 +1,50 @@
#ifndef EDITSCENE_GOAP_RUNNER_HPP
#define EDITSCENE_GOAP_RUNNER_HPP
#pragma once
#include <Ogre.h>
#include <string>
#include <vector>
/**
* GOAP Runner component.
*
* Executes plans from GoapPlannerComponent.
* For normal actions: runs the action's behavior tree.
* For smart object actions: pathfinds to the smart object and executes.
* After plan completion, marks the planner dirty for replanning.
*/
struct GoapRunnerComponent {
// Current plan execution state
enum class State {
Idle, // No plan running
RunningAction, // Executing a normal action
MovingToSmartObject, // Pathfinding to a smart object
ExecutingSmartObject, // Executing smart object action
PlanComplete // Plan finished, waiting for replan
};
State state = State::Idle;
// Index of current action in the plan
int currentActionIndex = 0;
// Name of the currently executing action
Ogre::String currentActionName;
// Timer for action execution
float actionTimer = 0.0f;
// Entity ID of target smart object (if applicable)
uint64_t targetSmartObjectId = 0;
// Active plan actions (copied from planner when plan starts)
std::vector<Ogre::String> planActions;
// Whether to auto-replan after completion
bool autoReplan = true;
GoapRunnerComponent() = default;
};
#endif // EDITSCENE_GOAP_RUNNER_HPP

View File

@@ -0,0 +1,21 @@
#include "GoapRunner.hpp"
#include "../ui/ComponentRegistration.hpp"
#include "../ui/GoapRunnerEditor.hpp"
REGISTER_COMPONENT_GROUP("GOAP Runner", "AI", GoapRunnerComponent,
GoapRunnerEditor)
{
registry.registerComponent<GoapRunnerComponent>(
"GOAP Runner", "AI",
std::make_unique<GoapRunnerEditor>(),
// Adder
[](flecs::entity e) {
if (!e.has<GoapRunnerComponent>())
e.set<GoapRunnerComponent>({});
},
// Remover
[](flecs::entity e) {
if (e.has<GoapRunnerComponent>())
e.remove<GoapRunnerComponent>();
});
}

View File

@@ -0,0 +1,12 @@
#ifndef EDITSCENE_INWATER_HPP
#define EDITSCENE_INWATER_HPP
#pragma once
/**
* Tag component indicating the entity is currently in water.
* Automatically added/removed by BuoyancySystem.
*/
struct InWater {
};
#endif // EDITSCENE_INWATER_HPP

View File

@@ -0,0 +1,165 @@
#ifndef EDITSCENE_INVENTORY_HPP
#define EDITSCENE_INVENTORY_HPP
#pragma once
#include <Ogre.h>
#include <flecs.h>
#include <vector>
#include <string>
#include <cstdint>
/**
* A single slot in an inventory.
* Stores a reference to an item entity (if the item is a world entity)
* or stores item data directly for items that exist only in inventory.
*/
struct InventorySlot {
// Flecs entity ID of the item (0 if slot is empty)
flecs::entity_t itemEntity = 0;
// Item data for items that exist only in inventory (no world entity)
Ogre::String itemId;
Ogre::String itemName;
Ogre::String itemType;
int stackSize = 0;
int maxStackSize = 99;
float weight = 0.1f;
int value = 1;
Ogre::String useActionName;
bool isEmpty() const
{
return itemEntity == 0 && stackSize <= 0;
}
void clear()
{
itemEntity = 0;
itemId.clear();
itemName.clear();
itemType.clear();
stackSize = 0;
maxStackSize = 99;
weight = 0.1f;
value = 1;
useActionName.clear();
}
};
/**
* Inventory component.
*
* Attached to a character entity to hold items.
* Can also be attached to container entities (chests, barrels, etc.)
* to define their contents.
*
* The inventory stores items as InventorySlot entries, each of which
* may reference a world ItemComponent entity or hold item data directly.
*/
struct InventoryComponent {
// Maximum number of slots
int maxSlots = 20;
// Current slots
std::vector<InventorySlot> slots;
// Total weight of all items (computed)
float totalWeight = 0.0f;
// Maximum weight capacity (0 = unlimited)
float maxWeight = 50.0f;
// Whether this inventory is a container (chest, barrel, etc.)
// Containers can be opened by characters to transfer items.
bool isContainer = false;
// Whether this inventory is currently open (for containers being browsed)
bool isOpen = false;
InventoryComponent() = default;
explicit InventoryComponent(int maxSlots_)
: maxSlots(maxSlots_)
{
slots.reserve(maxSlots_);
}
/** Find the first empty slot index, or -1 if full. */
int findEmptySlot() const
{
for (int i = 0; i < maxSlots; i++) {
if (i >= (int)slots.size())
return i;
if (slots[i].isEmpty())
return i;
}
return -1;
}
/** Find a slot containing an item with the given itemId. */
int findItem(const Ogre::String &itemId) const
{
for (int i = 0; i < (int)slots.size(); i++) {
if (!slots[i].isEmpty() && slots[i].itemId == itemId)
return i;
}
return -1;
}
/** Find a slot containing an item with the given itemName. */
int findItemByName(const Ogre::String &itemName) const
{
for (int i = 0; i < (int)slots.size(); i++) {
if (!slots[i].isEmpty() &&
slots[i].itemName == itemName)
return i;
}
return -1;
}
/** Count total number of items (sum of stack sizes). */
int countItems() const
{
int count = 0;
for (const auto &slot : slots) {
if (!slot.isEmpty())
count += slot.stackSize;
}
return count;
}
/** Count how many of a specific itemId are in the inventory. */
int countItem(const Ogre::String &itemId) const
{
int count = 0;
for (const auto &slot : slots) {
if (!slot.isEmpty() && slot.itemId == itemId)
count += slot.stackSize;
}
return count;
}
/** Check if inventory has at least one of a specific itemId. */
bool hasItem(const Ogre::String &itemId) const
{
return findItem(itemId) >= 0;
}
/** Check if inventory has at least one of a specific itemName. */
bool hasItemByName(const Ogre::String &itemName) const
{
return findItemByName(itemName) >= 0;
}
/** Recalculate total weight. */
void recalculateWeight()
{
totalWeight = 0.0f;
for (const auto &slot : slots) {
if (!slot.isEmpty())
totalWeight += slot.weight * slot.stackSize;
}
}
};
#endif // EDITSCENE_INVENTORY_HPP

View File

@@ -0,0 +1,20 @@
#include "Inventory.hpp"
#include "../ui/ComponentRegistration.hpp"
#include "../ui/InventoryEditor.hpp"
REGISTER_COMPONENT_GROUP("Inventory", "Game", InventoryComponent,
InventoryEditor)
{
registry.registerComponent<InventoryComponent>(
"Inventory", "Game", std::make_unique<InventoryEditor>(),
// Adder
[](flecs::entity e) {
if (!e.has<InventoryComponent>())
e.set<InventoryComponent>({});
},
// Remover
[](flecs::entity e) {
if (e.has<InventoryComponent>())
e.remove<InventoryComponent>();
});
}

View File

@@ -0,0 +1,59 @@
#ifndef EDITSCENE_ITEM_HPP
#define EDITSCENE_ITEM_HPP
#pragma once
#include <Ogre.h>
#include <string>
#include <vector>
/**
* Item definition component.
*
* Attached to a world entity that represents a pickable item.
* The ActuatorSystem detects items (entities with ItemComponent)
* and shows "E - Pick up [ItemName]" prompts to the player.
*
* Items can also be placed in containers (chests, etc.) which
* have an InventoryComponent.
*
* For AI characters, behavior tree nodes (hasItem, pickupItem,
* dropItem, useItem, addItemToInventory) provide inventory access.
*/
struct ItemComponent {
// Display name of the item (e.g. "Apple", "Sword", "Key")
Ogre::String itemName = "Item";
// Item type for categorization (e.g. "food", "weapon", "key", "quest")
Ogre::String itemType = "misc";
// Unique identifier for this item definition
// Multiple entities can share the same itemId (e.g. multiple coins)
Ogre::String itemId;
// Stack size: how many of this item are in this stack
int stackSize = 1;
// Maximum stack size (0 = no stacking)
int maxStackSize = 99;
// Weight per unit (for encumbrance calculations)
float weight = 0.1f;
// Value (for trading)
int value = 1;
// Name of the GOAP action to execute when "using" this item
// (e.g. "eat", "equip", "read"). Empty = no use action.
Ogre::String useActionName;
ItemComponent() = default;
explicit ItemComponent(const Ogre::String &name,
const Ogre::String &type = "misc")
: itemName(name)
, itemType(type)
{
}
};
#endif // EDITSCENE_ITEM_HPP

View File

@@ -0,0 +1,19 @@
#include "Item.hpp"
#include "../ui/ComponentRegistration.hpp"
#include "../ui/ItemEditor.hpp"
REGISTER_COMPONENT_GROUP("Item", "Game", ItemComponent, ItemEditor)
{
registry.registerComponent<ItemComponent>(
"Item", "Game", std::make_unique<ItemEditor>(),
// Adder
[](flecs::entity e) {
if (!e.has<ItemComponent>())
e.set<ItemComponent>({});
},
// Remover
[](flecs::entity e) {
if (e.has<ItemComponent>())
e.remove<ItemComponent>();
});
}

View File

@@ -0,0 +1,48 @@
#ifndef EDITSCENE_LIGHT_HPP
#define EDITSCENE_LIGHT_HPP
#pragma once
#include <Ogre.h>
/**
* Light component - attaches an Ogre::Light to the entity's SceneNode
*/
struct LightComponent {
enum class LightType {
Point, // Omnidirectional light
Directional, // Parallel rays (sun/moon)
Spotlight // Cone-shaped light
};
LightType lightType = LightType::Point;
// Common properties
Ogre::ColourValue diffuseColor{0.8f, 0.8f, 0.8f};
Ogre::ColourValue specularColor{0.5f, 0.5f, 0.5f};
float intensity = 1.0f;
// Attenuation (for Point and Spot)
float range = 100.0f;
float constantAttenuation = 1.0f;
float linearAttenuation = 0.0f;
float quadraticAttenuation = 0.0f;
// Spotlight specific
float spotlightInnerAngle = 30.0f; // Degrees
float spotlightOuterAngle = 45.0f; // Degrees
float spotlightFalloff = 1.0f;
// Direction (for Directional and Spot, relative to node orientation)
Ogre::Vector3 direction{0, -1, 0};
// Cast shadows
bool castShadows = true;
// The Ogre light object (created by LightSystem)
Ogre::Light* light = nullptr;
void markDirty() { needsRebuild = true; }
bool needsRebuild = true;
};
#endif // EDITSCENE_LIGHT_HPP

View File

@@ -0,0 +1,43 @@
#include "Light.hpp"
#include "Transform.hpp"
#include "../ui/ComponentRegistration.hpp"
#include "../ui/LightEditor.hpp"
#include "../systems/LightSystem.hpp"
// Register Light component
REGISTER_COMPONENT("Light", LightComponent, LightEditor)
{
registry.registerComponent<LightComponent>(
"Light",
std::make_unique<LightEditor>(),
// Adder
[sceneMgr](flecs::entity e) {
if (!e.has<LightComponent>()) {
// Light requires Transform
if (!e.has<TransformComponent>()) {
// Auto-add transform if missing
TransformComponent transform;
transform.node = sceneMgr->getRootSceneNode()->createChildSceneNode();
e.set<TransformComponent>(transform);
}
e.set<LightComponent>({});
}
},
// Remover
[sceneMgr](flecs::entity e) {
if (e.has<LightComponent>()) {
auto& light = e.get_mut<LightComponent>();
// Clean up Ogre light
if (light.light) {
Ogre::SceneNode* parent = light.light->getParentSceneNode();
if (parent) {
parent->detachObject(light.light);
}
sceneMgr->destroyLight(light.light);
light.light = nullptr;
}
e.remove<LightComponent>();
}
}
);
}

View File

@@ -0,0 +1,47 @@
#ifndef EDITSCENE_LOD_HPP
#define EDITSCENE_LOD_HPP
#pragma once
#include <Ogre.h>
#include <flecs.h>
#include <string>
#include "LodSettings.hpp"
/**
* LodComponent - Per-entity LOD configuration
*
* This component references a LodSettingsComponent by its settingsId
* for persistent identification across scene loads.
*/
struct LodComponent {
// Reference to the settings by ID (persistent across scene loads)
std::string settingsId;
// Runtime entity reference (resolved from settingsId)
flecs::entity settingsEntity = flecs::entity::null();
// Per-entity distance multiplier (scales all LOD distances)
float distanceMultiplier = 1.0f;
// Whether LOD has been applied to the mesh
bool lodApplied = false;
// Last settings version that was applied
uint32_t appliedVersion = 0;
// Dirty flag for regenerating LOD
bool dirty = true;
void markDirty() { dirty = true; }
// Helper to check if LOD needs to be regenerated
bool needsUpdate() const { return dirty; }
// Helper to check if settings reference is valid
bool hasValidSettings() const {
return settingsEntity.is_alive() &&
settingsEntity.has<LodSettingsComponent>();
}
};
#endif // EDITSCENE_LOD_HPP

View File

@@ -0,0 +1,56 @@
#include "Lod.hpp"
#include "LodSettings.hpp"
#include "Renderable.hpp"
#include "../ui/ComponentRegistration.hpp"
#include "../ui/LodEditor.hpp"
#include "../ui/LodSettingsEditor.hpp"
// Register LodSettings component
REGISTER_COMPONENT("LOD Settings", LodSettingsComponent, LodSettingsEditor)
{
registry.registerComponent<LodSettingsComponent>(
"LOD Settings",
std::make_unique<LodSettingsEditor>(),
// Adder
[sceneMgr](flecs::entity e) {
if (!e.has<LodSettingsComponent>()) {
auto settings = LodSettingsComponent();
// Create default LOD levels
settings.createDefaultLevels();
e.set<LodSettingsComponent>(settings);
}
},
// Remover
[sceneMgr](flecs::entity e) {
if (e.has<LodSettingsComponent>()) {
e.remove<LodSettingsComponent>();
}
}
);
}
// Register Lod component
REGISTER_COMPONENT("LOD", LodComponent, LodEditor)
{
registry.registerComponent<LodComponent>(
"LOD",
std::make_unique<LodEditor>(),
// Adder
[sceneMgr](flecs::entity e) {
if (!e.has<LodComponent>()) {
// LOD requires Renderable
if (!e.has<RenderableComponent>()) {
// Auto-add renderable if missing (will need mesh assignment later)
e.set<RenderableComponent>({});
}
e.set<LodComponent>({});
}
},
// Remover
[sceneMgr](flecs::entity e) {
if (e.has<LodComponent>()) {
e.remove<LodComponent>();
}
}
);
}

View File

@@ -0,0 +1,107 @@
#ifndef EDITSCENE_LODSETTINGS_HPP
#define EDITSCENE_LODSETTINGS_HPP
#pragma once
#include <Ogre.h>
#include <vector>
#include <string>
/**
* LOD Level configuration - represents one LOD level
*/
struct LodLevelConfig {
// Distance at which to switch to this LOD level
float distance = 100.0f;
// Reduction method
enum class ReductionMethod {
Proportional, // 0.0-1.0 percentage of vertices to remove
Constant, // Exact vertex count to remove
CollapseCost // Collapse cost threshold
};
ReductionMethod reductionMethod = ReductionMethod::Proportional;
// Reduction value (meaning depends on method)
float reductionValue = 0.5f; // 50% reduction by default
// Optional: manual mesh name (if using manual LOD)
std::string manualMeshName;
bool useManualMesh = false;
};
/**
* LodSettingsComponent - Shareable LOD configuration
*
* This component can be attached to a "settings" entity and referenced
* by multiple entities with LodComponent for shared LOD settings.
*/
struct LodSettingsComponent {
// Unique identifier for this LOD settings (for referencing across scenes)
std::string settingsId;
// Strategy for determining LOD level based on distance
enum class Strategy {
Distance, // Distance from camera
PixelCount, // Screen-space pixel count
EdgePixelCount // Screen-space edge pixel count
};
Strategy strategy = Strategy::Distance;
// LOD levels (from highest quality to lowest)
// Level 0 is always the original mesh
std::vector<LodLevelConfig> lodLevels;
// Advanced options
bool useCompression = true; // Compress index buffers
bool useVertexNormals = true; // Use normals for quality
bool preventPunchingHoles = false; // Prevent destroying triangles
bool preventBreakingLines = false; // Prevent destroying lines
bool useBackgroundQueue = false; // Generate LODs in background
float outsideWeight = 0.0f; // Weight for outside faces (0 = disabled)
float outsideWalkAngle = 0.0f; // Angle for outside walking (-1 to 1)
// Flag to track if settings have been modified
bool dirty = true;
// Version counter for tracking changes
uint32_t version = 1;
void markDirty() {
dirty = true;
version++;
}
// Helper to add a proportional reduction level
void addProportionalLevel(float distance, float reductionPercent) {
LodLevelConfig level;
level.distance = distance;
level.reductionMethod = LodLevelConfig::ReductionMethod::Proportional;
level.reductionValue = reductionPercent;
lodLevels.push_back(level);
markDirty();
}
// Helper to add a constant reduction level
void addConstantLevel(float distance, int verticesToRemove) {
LodLevelConfig level;
level.distance = distance;
level.reductionMethod = LodLevelConfig::ReductionMethod::Constant;
level.reductionValue = static_cast<float>(verticesToRemove);
lodLevels.push_back(level);
markDirty();
}
// Helper to create default LOD levels for a mesh
void createDefaultLevels(float baseDistance = 100.0f) {
lodLevels.clear();
// LOD 1: 50% reduction at base distance
addProportionalLevel(baseDistance, 0.5f);
// LOD 2: 75% reduction at 2x distance
addProportionalLevel(baseDistance * 2.0f, 0.75f);
markDirty();
}
};
#endif // EDITSCENE_LODSETTINGS_HPP

View File

@@ -0,0 +1,59 @@
#ifndef EDITSCENE_NAVMESH_HPP
#define EDITSCENE_NAVMESH_HPP
#pragma once
#include <Ogre.h>
#include <cstdint>
/**
* Navigation mesh component.
*
* Attached to a single "manager" entity that owns the tiled navmesh
* for the entire scene. The NavMeshSystem collects geometry from
* static rigid bodies, StaticGeometry members, and entities with
* NavMeshGeometrySource to build the mesh.
*/
struct NavMeshComponent {
// Recast build parameters
float cellSize = 0.3f;
float cellHeight = 0.2f;
float agentHeight = 2.5f;
float agentRadius = 0.5f;
float agentMaxClimb = 1.0f;
float agentMaxSlope = 20.0f;
float edgeMaxLen = 12.0f;
float edgeMaxError = 1.3f;
float regionMinSize = 50.0f;
float regionMergeSize = 20.0f;
int tileSize = 48; // cells per tile
// Runtime flags
bool enabled = true;
bool debugDraw = false;
bool dirty = true;
// Partial rebuild tracking (not serialized)
bool needsPartialRebuild = false;
Ogre::AxisAlignedBox rebuildArea;
};
/**
* Component that forces an entity's geometry to be included in
* navmesh generation regardless of physics state.
*/
struct NavMeshGeometrySource {
bool include = true;
};
/**
* Component for entities that want pathfinding on this navmesh.
*/
struct NavMeshAgent {
Ogre::Vector3 targetPos = Ogre::Vector3::ZERO;
bool hasTarget = false;
bool pathPending = false;
std::vector<Ogre::Vector3> currentPath;
int pathIndex = 0;
};
#endif // EDITSCENE_NAVMESH_HPP

View File

@@ -0,0 +1,38 @@
#include "NavMesh.hpp"
#include "../ui/ComponentRegistration.hpp"
#include "../ui/NavMeshEditor.hpp"
#include "../ui/NavMeshGeometrySourceEditor.hpp"
REGISTER_COMPONENT_GROUP("NavMesh", "Navigation", NavMeshComponent,
NavMeshEditor)
{
registry.registerComponent<NavMeshComponent>(
"NavMesh", "Navigation",
std::make_unique<NavMeshEditor>(),
// Adder
[](flecs::entity e) {
if (!e.has<NavMeshComponent>())
e.set<NavMeshComponent>({});
},
// Remover
[](flecs::entity e) {
if (e.has<NavMeshComponent>())
e.remove<NavMeshComponent>();
});
}
REGISTER_COMPONENT_GROUP("NavMesh Geometry Source", "Navigation",
NavMeshGeometrySource, NavMeshGeometrySourceEditor)
{
registry.registerComponent<NavMeshGeometrySource>(
"NavMesh Geometry Source", "Navigation",
std::make_unique<NavMeshGeometrySourceEditor>(),
[](flecs::entity e) {
if (!e.has<NavMeshGeometrySource>())
e.set<NavMeshGeometrySource>({});
},
[](flecs::entity e) {
if (e.has<NavMeshGeometrySource>())
e.remove<NavMeshGeometrySource>();
});
}

View File

@@ -0,0 +1,77 @@
#ifndef EDITSCENE_PATH_FOLLOWING_HPP
#define EDITSCENE_PATH_FOLLOWING_HPP
#pragma once
#include <Ogre.h>
#include <vector>
#include <string>
#include <utility>
/**
* Animation state configuration for path following.
*
* Each state (e.g. "idle", "walk", "run") consists of unlimited
* state-machine-name -> state-name pairs applied via AnimationTreeSystem.
*/
struct PathFollowingState {
Ogre::String name;
std::vector<std::pair<Ogre::String, Ogre::String> > stateMachineStates;
PathFollowingState() = default;
explicit PathFollowingState(const Ogre::String &name_)
: name(name_)
{
}
};
/**
* Path Following component.
*
* Configures animation states for locomotion (idle/walk/run)
* and stores the current locomotion state. Used by GoapRunner
* to animate characters during plan execution.
*/
struct PathFollowingComponent {
// Animation state configurations
std::vector<PathFollowingState> pathFollowingStates = {
PathFollowingState("idle"),
PathFollowingState("walk"),
PathFollowingState("run"),
};
// Current locomotion state name
Ogre::String currentLocomotionState = "idle";
// Walk speed (m/s) for root motion scaling
float walkSpeed = 2.5f;
// Run speed (m/s) for root motion scaling
float runSpeed = 5.0f;
// Whether to use root motion
bool useRootMotion = true;
// Target position for path following (set by GoapRunner)
Ogre::Vector3 targetPosition = Ogre::Vector3::ZERO;
// Whether we have an active target
bool hasTarget = false;
// Path waypoints (set by GoapRunner, followed by PathFollowingSystem)
std::vector<Ogre::Vector3> path;
int pathIndex = 0;
float pathRecalcTimer = 0.0f;
PathFollowingComponent() = default;
const PathFollowingState *findState(const Ogre::String &name) const
{
for (const auto &state : pathFollowingStates) {
if (state.name == name)
return &state;
}
return nullptr;
}
};
#endif // EDITSCENE_PATH_FOLLOWING_HPP

View File

@@ -0,0 +1,21 @@
#include "PathFollowing.hpp"
#include "../ui/ComponentRegistration.hpp"
#include "../ui/PathFollowingEditor.hpp"
REGISTER_COMPONENT_GROUP("Path Following", "AI", PathFollowingComponent,
PathFollowingEditor)
{
registry.registerComponent<PathFollowingComponent>(
"Path Following", "AI",
std::make_unique<PathFollowingEditor>(),
// Adder
[](flecs::entity e) {
if (!e.has<PathFollowingComponent>())
e.set<PathFollowingComponent>({});
},
// Remover
[](flecs::entity e) {
if (e.has<PathFollowingComponent>())
e.remove<PathFollowingComponent>();
});
}

View File

@@ -0,0 +1,50 @@
#ifndef EDITSCENE_PHYSICSCOLLIDER_HPP
#define EDITSCENE_PHYSICSCOLLIDER_HPP
#pragma once
#include <Ogre.h>
#include <Jolt/Jolt.h>
#include <Jolt/Physics/Collision/Shape/Shape.h>
#include <memory>
/**
* Physics Collider component
* Defines a collision shape for use in rigid bodies
* Multiple colliders can be attached to a single rigid body (as children)
*/
struct PhysicsColliderComponent {
// Collider shape types
enum class ShapeType {
Box,
Sphere,
Capsule,
Cylinder,
Mesh,
ConvexHull
};
ShapeType shapeType = ShapeType::Box;
// Shape parameters
// Box: half extents
// Sphere: radius
// Capsule/Cylinder: halfHeight, radius
Ogre::Vector3 parameters = Ogre::Vector3(0.5f, 0.5f, 0.5f); // For box: half extents
float radius = 0.5f; // For sphere, capsule, cylinder
float halfHeight = 1.0f; // For capsule, cylinder
// For mesh/convex hull colliders
std::string meshName;
// Offset from parent transform
Ogre::Vector3 offset = Ogre::Vector3::ZERO;
Ogre::Quaternion rotationOffset = Ogre::Quaternion::IDENTITY;
// The actual Jolt shape (created when body is built)
JPH::ShapeRefC shape;
bool shapeDirty = true; // Needs rebuild
void markDirty() { shapeDirty = true; }
};
#endif // EDITSCENE_PHYSICSCOLLIDER_HPP

View File

@@ -0,0 +1,42 @@
#ifndef EDITSCENE_PLAYERCONTROLLER_HPP
#define EDITSCENE_PLAYERCONTROLLER_HPP
#pragma once
#include <Ogre.h>
/**
* Player controller component.
* Only active in game mode. Editable in editor mode.
*/
struct PlayerControllerComponent {
enum CameraMode { TPS = 0, FPS = 1 };
int cameraMode = TPS;
Ogre::String targetCharacterName = "";
Ogre::String fpsBoneName = "Head";
float tpsDistance = 3.0f;
float tpsHeight = 2.0f;
float mouseSensitivity = 0.2f;
/* Animation state machine configuration */
Ogre::String locomotionStateMachine = "locomotion";
Ogre::String idleState = "idle";
Ogre::String walkState = "walking";
Ogre::String runState = "running";
/* Swim animation states (used when character is in water) */
Ogre::String swimIdleState = "swim-idle";
Ogre::String swimState = "swim";
Ogre::String swimFastState = "swim-fast";
/* Actuator interaction settings */
float actuatorDistance = 25.0f;
float actuatorCooldown = 1.5f;
Ogre::Vector3 actuatorColor = Ogre::Vector3(0.0f, 0.4f, 1.0f);
float distantCircleRadius = 8.0f;
float nearCircleRadius = 14.0f;
float actuatorLabelFontSize = 14.0f;
/* Runtime: set by ActuatorSystem while executing an action */
bool inputLocked = false;
};
#endif // EDITSCENE_PLAYERCONTROLLER_HPP

View File

@@ -0,0 +1,24 @@
#include "../ui/ComponentRegistration.hpp"
#include "../ui/PlayerControllerEditor.hpp"
#include "PlayerController.hpp"
REGISTER_COMPONENT_GROUP("Player Controller", "Game",
PlayerControllerComponent, PlayerControllerEditor)
{
registry.registerComponent<PlayerControllerComponent>(
PlayerControllerComponent_name,
PlayerControllerComponent_group,
std::make_unique<PlayerControllerEditor>(),
// Adder
[](flecs::entity e) {
if (!e.has<PlayerControllerComponent>()) {
e.set<PlayerControllerComponent>({});
}
},
// Remover
[](flecs::entity e) {
if (e.has<PlayerControllerComponent>()) {
e.remove<PlayerControllerComponent>();
}
});
}

View File

@@ -0,0 +1,24 @@
#ifndef EDITSCENE_PREFABINSTANCE_HPP
#define EDITSCENE_PREFABINSTANCE_HPP
#pragma once
#include <string>
/**
* @brief Marks an entity as an instance of a prefab asset.
*
* The entity's TransformComponent acts as the world-space override
* for the prefab root. All other components (and the child subtree)
* are loaded from the prefab file at runtime and are NOT serialized
* with the main scene.
*/
struct PrefabInstanceComponent {
/** Path to the prefab JSON file (relative to working dir) */
std::string prefabPath;
/** Set to true once the prefab has been instantiated.
* Prevents double-instantiation on repeated resolve calls.
*/
bool instantiated = false;
};
#endif // EDITSCENE_PREFABINSTANCE_HPP

View File

@@ -0,0 +1,37 @@
#pragma once
#include <string>
#include <flecs.h>
/**
* @brief Base component for procedural primitives that feed into TriangleBuffer
*/
struct PrimitiveComponent {
// Type of primitive
enum class Type {
None,
Box,
Plane
};
Type type = Type::None;
// Box parameters
float boxSizeX = 1.0f;
float boxSizeY = 1.0f;
float boxSizeZ = 1.0f;
// Plane parameters
float planeSizeX = 1.0f;
float planeSizeY = 1.0f;
int planeSegmentsX = 1;
int planeSegmentsY = 1;
// Dirty flag - triggers TriangleBuffer rebuild
bool dirty = true;
// Track last seen transform version to detect gizmo movements
unsigned int lastTransformVersion = 0;
void markDirty() { dirty = true; }
};

View File

@@ -0,0 +1,24 @@
#include "Primitive.hpp"
#include "../ui/ComponentRegistration.hpp"
#include "../ui/PrimitiveEditor.hpp"
// Register Primitive component
REGISTER_COMPONENT("Primitive", PrimitiveComponent, PrimitiveEditor)
{
registry.registerComponent<PrimitiveComponent>(
"Primitive",
std::make_unique<PrimitiveEditor>(),
// Adder
[sceneMgr](flecs::entity e) {
if (!e.has<PrimitiveComponent>()) {
e.set<PrimitiveComponent>({});
}
},
// Remover
[sceneMgr](flecs::entity e) {
if (e.has<PrimitiveComponent>()) {
e.remove<PrimitiveComponent>();
}
}
);
}

View File

@@ -0,0 +1,58 @@
#pragma once
#include <string>
#include <Ogre.h>
#include <flecs.h>
#include "ProceduralTexture.hpp"
/**
* @brief Component for creating procedural Ogre materials
*
* Creates a named material that uses ProceduralTexture as diffuse/albedo map.
* The material can be referenced by name for use with meshes/entities.
*/
struct ProceduralMaterialComponent {
// Unique identifier for persistent referencing across scene loads
std::string materialId;
// Material name for Ogre resource
std::string materialName;
// Persistent reference to texture by ID
std::string diffuseTextureId;
// Runtime reference to entity with ProceduralTextureComponent (for diffuse)
flecs::entity diffuseTextureEntity = flecs::entity::null();
// Whether the material needs regeneration
bool dirty = true;
// Whether the material has been created
bool created = false;
// Pointer to the created Ogre material
Ogre::MaterialPtr ogreMaterial;
// Track texture version for automatic rebuild when texture changes
unsigned int textureVersion = 0;
// Material properties
float ambient[3] = { 0.2f, 0.2f, 0.2f }; // Ambient color (RGB 0-1)
float diffuse[3] = { 1.0f, 1.0f,
1.0f }; // Diffuse color multiplier (RGB 0-1)
float specular[3] = { 0.0f, 0.0f, 0.0f }; // Specular color (RGB 0-1)
float shininess = 32.0f; // Specular shininess
float roughness = 0.5f; // Roughness (0-1, for PBR-style)
void markDirty()
{
dirty = true;
}
// Helper to check if texture reference is valid
bool hasValidTexture() const
{
return diffuseTextureEntity.is_alive() &&
diffuseTextureEntity.has<ProceduralTextureComponent>();
}
};

View File

@@ -0,0 +1,42 @@
#include "ProceduralMaterial.hpp"
#include "../ui/ComponentRegistration.hpp"
#include "../ui/ProceduralMaterialEditor.hpp"
#include <OgreMaterialManager.h>
#include <OgreRTShaderSystem.h>
// Register ProceduralMaterial component
REGISTER_COMPONENT_GROUP("Procedural Material", "Material", ProceduralMaterialComponent, ProceduralMaterialEditor)
{
registry.registerComponent<ProceduralMaterialComponent>(
"Procedural Material", "Material",
std::make_unique<ProceduralMaterialEditor>(sceneMgr),
// Adder
[sceneMgr](flecs::entity e) {
if (!e.has<ProceduralMaterialComponent>()) {
e.set<ProceduralMaterialComponent>({});
}
},
// Remover
[sceneMgr](flecs::entity e) {
if (e.has<ProceduralMaterialComponent>()) {
auto& material = e.get_mut<ProceduralMaterialComponent>();
// Clean up Ogre material - wrap in try/catch since MaterialManager may be shutting down
if (material.ogreMaterial) {
try {
Ogre::RTShader::ShaderGenerator *shaderGen =
Ogre::RTShader::ShaderGenerator::getSingletonPtr();
if (shaderGen) {
shaderGen->removeAllShaderBasedTechniques(
*material.ogreMaterial);
}
if (Ogre::MaterialManager::getSingletonPtr()) {
Ogre::MaterialManager::getSingleton().remove(material.ogreMaterial);
}
} catch (...) {}
material.ogreMaterial.reset();
}
e.remove<ProceduralMaterialComponent>();
}
}
);
}

View File

@@ -0,0 +1,183 @@
#pragma once
#include <string>
#include <array>
#include <vector>
#include <map>
#include <Ogre.h>
/**
* @brief Information about a named rectangle in the texture atlas
*/
struct TextureRectInfo {
std::string name;
float u1, v1; // Top-left UV (0-1 range)
float u2, v2; // Bottom-right UV (0-1 range)
TextureRectInfo()
: u1(0)
, v1(0)
, u2(1)
, v2(1)
{
}
TextureRectInfo(const std::string &n, float left, float top,
float right, float bottom)
: name(n)
, u1(left)
, v1(top)
, u2(right)
, v2(bottom)
{
}
};
/**
* @brief Component for procedural texture generation with 10x10 colored rectangles
*
* Uses OgreProcedural to generate a texture with a grid of colored rectangles.
* Each rectangle color is individually selectable.
* Also supports named rectangles for texture atlas/UV mapping.
*/
struct ProceduralTextureComponent {
// Unique identifier for persistent referencing across scene loads
std::string textureId;
// Texture name for Ogre resource
std::string textureName;
// Grid dimensions (default 10x10)
static constexpr int GRID_SIZE = 10;
static constexpr int RECT_COUNT = GRID_SIZE * GRID_SIZE;
// Colors for each rectangle (stored as float4: r, g, b, a)
std::array<float, RECT_COUNT * 4> colors;
// Named rectangles for texture atlas (name -> rect info)
std::map<std::string, TextureRectInfo> namedRects;
// Texture size (default 512x512)
int textureSize = 512;
// UV margin for texture mapping (default 0.01, range 0.01-0.025)
// Used to prevent color bleeding by adding padding around UV coordinates
float uvMargin = 0.01f;
// Whether the texture needs regeneration
bool dirty = true;
// Whether the texture has been generated
bool generated = false;
// Version counter - increments each time texture is regenerated
// Used by dependent systems to detect texture changes
unsigned int version = 0;
// Pointer to the generated Ogre texture
Ogre::TexturePtr ogreTexture;
ProceduralTextureComponent()
{
// Initialize with default colors (checkerboard pattern)
for (int i = 0; i < RECT_COUNT; ++i) {
int row = i / GRID_SIZE;
int col = i % GRID_SIZE;
bool isWhite = (row + col) % 2 == 0;
colors[i * 4 + 0] = isWhite ? 1.0f : 0.0f; // R
colors[i * 4 + 1] = isWhite ? 1.0f : 0.0f; // G
colors[i * 4 + 2] = isWhite ? 1.0f : 0.0f; // B
colors[i * 4 + 3] = 1.0f; // A
}
}
// Get color for a specific rectangle
Ogre::ColourValue getColor(int index) const
{
if (index < 0 || index >= RECT_COUNT)
return Ogre::ColourValue::White;
return Ogre::ColourValue(colors[index * 4 + 0],
colors[index * 4 + 1],
colors[index * 4 + 2],
colors[index * 4 + 3]);
}
// Set color for a specific rectangle
void setColor(int index, const Ogre::ColourValue &color)
{
if (index < 0 || index >= RECT_COUNT)
return;
colors[index * 4 + 0] = color.r;
colors[index * 4 + 1] = color.g;
colors[index * 4 + 2] = color.b;
colors[index * 4 + 3] = color.a;
dirty = true;
}
// Calculate UV coordinates for a rectangle index
void getRectUVs(int index, float &u1, float &v1, float &u2,
float &v2) const
{
int row = index / GRID_SIZE;
int col = index % GRID_SIZE;
float cellSize = 1.0f / GRID_SIZE;
u1 = col * cellSize;
v1 = row * cellSize;
u2 = (col + 1) * cellSize;
v2 = (row + 1) * cellSize;
}
// Add a named rectangle
bool addNamedRect(const std::string &name, int rectIndex)
{
if (name.empty() || rectIndex < 0 || rectIndex >= RECT_COUNT)
return false;
float u1, v1, u2, v2;
getRectUVs(rectIndex, u1, v1, u2, v2);
namedRects[name] = TextureRectInfo(name, u1, v1, u2, v2);
dirty = true; // Mark texture as dirty since rect atlas changed
return true;
}
// Remove a named rectangle
bool removeNamedRect(const std::string &name)
{
auto it = namedRects.find(name);
if (it != namedRects.end()) {
namedRects.erase(it);
dirty = true; // Mark texture as dirty since rect atlas changed
return true;
}
return false;
}
// Get named rectangle info
const TextureRectInfo *getNamedRect(const std::string &name) const
{
auto it = namedRects.find(name);
if (it != namedRects.end()) {
return &it->second;
}
return nullptr;
}
// Check if a name exists
bool hasNamedRect(const std::string &name) const
{
return namedRects.find(name) != namedRects.end();
}
// Get all named rectangles
const std::map<std::string, TextureRectInfo> &getAllNamedRects() const
{
return namedRects;
}
void markDirty()
{
dirty = true;
}
};

View File

@@ -0,0 +1,34 @@
#include "ProceduralTexture.hpp"
#include "../ui/ComponentRegistration.hpp"
#include "../ui/ProceduralTextureEditor.hpp"
// Register ProceduralTexture component
REGISTER_COMPONENT_GROUP("Procedural Texture", "Material", ProceduralTextureComponent, ProceduralTextureEditor)
{
registry.registerComponent<ProceduralTextureComponent>(
"Procedural Texture", "Material",
std::make_unique<ProceduralTextureEditor>(),
// Adder
[sceneMgr](flecs::entity e) {
if (!e.has<ProceduralTextureComponent>()) {
e.set<ProceduralTextureComponent>({});
}
},
// Remover
[sceneMgr](flecs::entity e) {
if (e.has<ProceduralTextureComponent>()) {
auto& texture = e.get_mut<ProceduralTextureComponent>();
// Clean up Ogre texture - wrap in try/catch since TextureManager may be shutting down
if (texture.ogreTexture) {
try {
if (Ogre::TextureManager::getSingletonPtr()) {
Ogre::TextureManager::getSingleton().remove(texture.ogreTexture);
}
} catch (...) {}
texture.ogreTexture.reset();
}
e.remove<ProceduralTextureComponent>();
}
}
);
}

View File

@@ -0,0 +1,21 @@
#ifndef EDITSCENE_RELATIONSHIP_HPP
#define EDITSCENE_RELATIONSHIP_HPP
#pragma once
#include <flecs.h>
/**
* Parent-child relationship component
* Tracks the parent entity for hierarchy
*/
struct ParentComponent {
flecs::entity parent;
};
/**
* Component to mark an entity as modified (for UI updates)
*/
struct ModifiedComponent {
bool modified = true;
};
#endif // EDITSCENE_RELATIONSHIP_HPP

View File

@@ -0,0 +1,23 @@
#ifndef EDITSCENE_RENDERABLE_HPP
#define EDITSCENE_RENDERABLE_HPP
#pragma once
#include <Ogre.h>
/**
* Renderable component for Flecs entities
* Links a mesh to an entity
*/
struct RenderableComponent {
Ogre::Entity *entity = nullptr;
Ogre::String meshName;
bool visible = true;
RenderableComponent() = default;
explicit RenderableComponent(const Ogre::String &mesh)
: meshName(mesh)
{
}
};
#endif // EDITSCENE_RENDERABLE_HPP

View File

@@ -0,0 +1,43 @@
#ifndef EDITSCENE_RIGIDBODY_HPP
#define EDITSCENE_RIGIDBODY_HPP
#pragma once
#include <Ogre.h>
#include <Jolt/Jolt.h>
#include <Jolt/Physics/Body/BodyID.h>
/**
* RigidBody component
* Represents a physics body that can be static or dynamic
* Uses children's Collider components for collision shapes
*/
struct RigidBodyComponent {
// Body type
enum class BodyType {
Static, // Non-moving (infinite mass)
Dynamic, // Affected by forces and collisions
Kinematic // Controlled by code, not physics
};
BodyType bodyType = BodyType::Static;
// Mass (only used for dynamic bodies)
float mass = 1.0f;
// Physics properties
float friction = 0.5f;
float restitution = 0.0f; // Bounciness
bool isSensor = false; // Detects collisions but doesn't respond
// Enable/disable physics body
bool enabled = true; // If false, body is removed from physics world
// The Jolt body ID
JPH::BodyID bodyID;
bool bodyCreated = false;
bool bodyDirty = true; // Needs rebuild
void markDirty() { bodyDirty = true; }
};
#endif // EDITSCENE_RIGIDBODY_HPP

View File

@@ -0,0 +1,58 @@
#ifndef EDITSCENE_SKYBOX_HPP
#define EDITSCENE_SKYBOX_HPP
#pragma once
#include <Ogre.h>
/**
* Skybox component - procedural sky rendered as a large cube
* with a fragment shader that creates dynamic day/night/sunset
* sky gradients.
*
* Designed to work alongside SunComponent on the same entity.
* If no SunComponent is present, uses default noon lighting.
*/
struct SkyboxComponent {
// Enable/disable skybox
bool enabled = true;
// Size of the skybox cube (default 500)
float size = 500.0f;
// Day sky colors
Ogre::ColourValue dayTopColor = Ogre::ColourValue(0.2f, 0.5f, 1.0f);
Ogre::ColourValue dayBottomColor = Ogre::ColourValue(0.6f, 0.8f, 1.0f);
// Night sky colors
Ogre::ColourValue nightTopColor = Ogre::ColourValue(0.0f, 0.0f, 0.05f);
Ogre::ColourValue nightBottomColor = Ogre::ColourValue(0.05f, 0.05f, 0.15f);
// Horizon glow colors
Ogre::ColourValue sunriseColor = Ogre::ColourValue(1.0f, 0.5f, 0.2f);
Ogre::ColourValue sunsetColor = Ogre::ColourValue(1.0f, 0.3f, 0.1f);
// Angular size of sun/moon discs in the sky shader (0.01 - 0.2)
float sunSize = 0.05f;
float moonSize = 0.03f;
// Enable simple stars at night
bool starsEnabled = true;
// Cloud coverage (0.0 = clear, 1.0 = overcast)
// Not yet implemented in shader but reserved for future
float cloudiness = 0.0f;
// Runtime objects (managed by EditorSkyboxSystem)
Ogre::SceneNode *sceneNode = nullptr;
Ogre::ManualObject *manualObject = nullptr;
// Dirty flag - triggers rebuild
bool dirty = true;
void markDirty()
{
dirty = true;
}
};
#endif // EDITSCENE_SKYBOX_HPP

View File

@@ -0,0 +1,24 @@
#include "Skybox.hpp"
#include "Transform.hpp"
#include "EditorMarker.hpp"
#include "EntityName.hpp"
#include "../ui/ComponentRegistration.hpp"
#include "../ui/SkyboxEditor.hpp"
REGISTER_COMPONENT_GROUP("Skybox", "Environment", SkyboxComponent, SkyboxEditor)
{
registry.registerComponent<SkyboxComponent>(
"Skybox", "Environment", std::make_unique<SkyboxEditor>(),
// Adder
[](flecs::entity e) {
if (!e.has<SkyboxComponent>()) {
e.set<SkyboxComponent>({});
}
},
// Remover
[](flecs::entity e) {
if (e.has<SkyboxComponent>()) {
e.remove<SkyboxComponent>();
}
});
}

View File

@@ -0,0 +1,38 @@
#ifndef EDITSCENE_SMART_OBJECT_HPP
#define EDITSCENE_SMART_OBJECT_HPP
#pragma once
#include <Ogre.h>
#include <vector>
#include <string>
/**
* Smart Object component.
*
* Defines an interactive object in the world that characters can
* navigate to and perform GOAP actions on.
*
* The entity's TransformComponent defines the position/orientation.
* Characters will pathfind to within `radius` distance in XZ plane
* and `height` difference in Y, then execute the selected action.
*/
struct SmartObjectComponent {
// Interaction radius in XZ plane
float radius = 1.0f;
// Maximum height difference for interaction
float height = 1.8f;
// Names of GOAP actions (from ActionDatabase) that this object provides
std::vector<Ogre::String> actionNames;
SmartObjectComponent() = default;
explicit SmartObjectComponent(float radius_, float height_)
: radius(radius_)
, height(height_)
{
}
};
#endif // EDITSCENE_SMART_OBJECT_HPP

Some files were not shown because too many files have changed in this diff Show More