Updated lots of things

This commit is contained in:
2025-10-22 16:39:19 +03:00
parent 9c4bea5983
commit 3f0484e87c
56 changed files with 4497 additions and 160 deletions

View File

@@ -43,12 +43,32 @@ BoatModule::BoatModule(flecs::world &ecs)
b2e.entities[body.body] = e;
} else if (type.resourceName.find(".scene") !=
std::string::npos) {
int i;
std::vector<Ogre::SceneNode *> colliderTarget;
boat.mNode =
ECS::get<EngineData>()
.mScnMgr->getRootSceneNode()
->createChildSceneNode(
type.position,
type.orientation);
auto pnodes = boat.mNode->getChildren();
for (i = 0; i < pnodes.size(); i++) {
Ogre::SceneNode *pnode =
static_cast<Ogre::SceneNode *>(
pnodes[i]);
Ogre::Any any =
pnode->getUserObjectBindings()
.getUserAny("type");
if (!any.has_value())
continue;
Ogre::String obj_type =
Ogre::any_cast<Ogre::String>(
any);
if (obj_type == "hull-collider") {
/* FIXME */
}
}
Ogre::SceneNode *attachment =
eng.mScnMgr->getRootSceneNode()
->createChildSceneNode();
@@ -63,14 +83,15 @@ BoatModule::BoatModule(flecs::world &ecs)
// attachment->loadChildren(type.resourceName);
std::vector<Ogre::Node *> v =
attachment->getChildren();
int i;
OgreAssert(v.size() == 1, "Bad nodes count");
OgreAssert(v.size() == 1,
"Bad root nodes count in " +
type.resourceName);
Ogre::Any any =
static_cast<Ogre::SceneNode *>(v[0])
->getUserObjectBindings()
.getUserAny("type");
OgreAssert(any.has_value(),
"bas node type costom prop");
"no \"type\" costom prop");
Ogre::String obj_type =
Ogre::any_cast<Ogre::String>(any);
std::cout << "type: " << obj_type << std::endl;
@@ -89,7 +110,7 @@ BoatModule::BoatModule(flecs::world &ecs)
ECS::get<EngineData>()
.mWorld->addRigidBody(
0, boat.mEnt,
Ogre::Bullet::CT_HULL,
Ogre::Bullet::CT_TRIMESH,
nullptr, 2, 0x7fffffff);
b2e.entities[body.body] = e;
std::vector<Ogre::Node *> slots =
@@ -149,4 +170,4 @@ BoatModule::BoatModule(flecs::world &ecs)
e.modified<BoatBody>();
});
}
}
}

View File

@@ -1,6 +1,9 @@
project(gamedata)
find_package(OGRE REQUIRED COMPONENTS Bites Bullet Paging Terrain Overlay CONFIG)
find_package(Bullet REQUIRED)
add_library(GameData STATIC GameData.cpp CharacterModule.cpp WaterModule.cpp SunModule.cpp TerrainModule.cpp GUIModule.cpp LuaData.cpp WorldMapModule.cpp
BoatModule.cpp EventTriggerModule.cpp CharacterAnimationModule.cpp)
target_link_libraries(GameData PUBLIC OgreMain OgreBites OgreBullet OgrePaging OgreTerrain OgreOverlay flecs::flecs_static lua PRIVATE sceneloader)
target_include_directories(GameData PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
BoatModule.cpp EventTriggerModule.cpp CharacterAnimationModule.cpp SmartObject.cpp goap.cpp)
target_link_libraries(GameData PUBLIC OgreMain OgreBites OgreBullet
OgrePaging OgreTerrain OgreOverlay flecs::flecs_static
lua ${BULLET_LIBRARIES} PRIVATE sceneloader world-build)
target_include_directories(GameData PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} ${BULLET_INCLUDE_DIR})

View File

@@ -3,6 +3,7 @@
#include "EventTriggerModule.h"
#include "CharacterModule.h"
#include "CharacterAnimationModule.h"
#include "world-build.h"
namespace ECS
{
CharacterAnimationModule::CharacterAnimationModule(flecs::world &ecs)
@@ -29,7 +30,9 @@ CharacterAnimationModule::CharacterAnimationModule(flecs::world &ecs)
"swimming-edge-climb",
"character-talk",
"pass-character",
"idle-act"
"idle-act",
"sitting-chair",
"sitting-ground"
};
int state_count = sizeof(animNames) /
sizeof(animNames[0]);
@@ -107,6 +110,12 @@ CharacterAnimationModule::CharacterAnimationModule(flecs::world &ecs)
->state("character-talk")
->animation("character-talk")
->end()
->state("sitting")
->animation("sitting-chair")
->end()
->state("sitting-ground")
->animation("sitting-ground")
->end()
->transition_end("swimming-edge-climb", "idle")
->transition_end("hanging-climb", "idle")
->transition_end("pass-character", "idle")
@@ -646,5 +655,47 @@ CharacterAnimationModule::CharacterAnimationModule(flecs::world &ecs)
result.m_collisionObject)));
}
});
struct AnimationSetCommand : public GameWorld::Command {
int operator()(const std::vector<GameWorld::Parameter *> &args)
override
{
GameWorld::ValueParameter<flecs::entity> *param_e =
static_cast<GameWorld::ValueParameter<
flecs::entity> *>(args[0]);
OgreAssert(param_e->get().is_valid(), "bad entity");
GameWorld::ValueParameter<std::string> *param_node =
static_cast<GameWorld::ValueParameter<
std::string> *>(args[1]);
GameWorld::ValueParameter<std::string> *param_state =
static_cast<GameWorld::ValueParameter<
std::string> *>(args[2]);
if (param_e->get().has<AnimationControl>() &&
param_e->get().get<AnimationControl>().configured ==
true) {
const AnimationControl &control =
param_e->get().get<AnimationControl>();
AnimationNodeStateMachine *sm =
control.mAnimationSystem
->get<AnimationNodeStateMachine>(
param_node->get());
bool reset = false;
if (args.size() == 4) {
GameWorld::ValueParameter<bool>
*param_reset = static_cast<
GameWorld::ValueParameter<
bool> *>(
args[3]);
reset = param_reset->get();
}
sm->setAnimation(param_state->get(), reset);
std::cout << "animation switch: "
<< param_node->get() << " "
<< param_state->get() << std::endl;
}
return 0;
}
};
ECS::get_mut<GameWorld>().add_command<AnimationSetCommand>(
"set_animation_state");
}
}

View File

@@ -893,6 +893,9 @@ struct AnimationControl {
bool configured;
AnimationSystem *mAnimationSystem;
};
struct DefaultAnimation {
std::vector<std::pair<std::string, std::string> > animations;
};
struct CharacterAnimationModule {
CharacterAnimationModule(flecs::world &ecs);
};

View File

@@ -7,6 +7,7 @@
#include "Components.h"
#include "CharacterAnimationModule.h"
#include "CharacterModule.h"
#include "goap.h"
namespace ECS
{
CharacterModule::CharacterModule(flecs::world &ecs)
@@ -21,6 +22,13 @@ CharacterModule::CharacterModule(flecs::world &ecs)
ecs.component<CharacterDisablePhysics>();
ecs.component<CharacterUpdatePhysicsState>();
ecs.component<CharacterInActuator>();
ecs.component<Blackboard>();
ecs.component<ActionTarget>();
ecs.component<Plan>();
ecs.component<Male>();
ecs.component<Female>();
ecs.component<Planner>().add(flecs::Singleton);
ecs.system<EngineData, CharacterBase>("UpdateTimer")
.kind(flecs::OnUpdate)
.each([this](EngineData &eng, CharacterBase &ch) {
@@ -540,4 +548,182 @@ void CharacterModule::updateCameraGoal(Camera &camera, Ogre::Real deltaYaw,
camera.mCameraGoal->translate(0, 0, distChange,
Ogre::Node::TS_LOCAL);
}
CharacterAIModule::CharacterAIModule(flecs::world &ecs)
{
ecs.module<CharacterAIModule>();
ecs.system<Blackboard>("UpdateCharacters")
.kind(flecs::OnUpdate)
.each([&](flecs::entity e, Blackboard &bb) {
bb.flags &=
~(Blackboard::LOW_HEALTH |
Blackboard::FULL_HEALTH |
Blackboard::LOW_STAMINA |
Blackboard::FULL_STAMINA |
Blackboard::LOW_LUST | Blackboard::HIGH_LUST |
Blackboard::FULL_LUST);
if (bb.health < 5)
bb.flags |= Blackboard::LOW_HEALTH;
else if (bb.health >= 100)
bb.flags |= Blackboard::FULL_HEALTH;
if (bb.stamina < 5)
bb.flags |= Blackboard::LOW_STAMINA;
if (bb.stamina >= 100)
bb.flags |= Blackboard::FULL_STAMINA;
if (bb.lust >= 100)
bb.flags |= Blackboard::FULL_LUST;
if (bb.lust > 10)
bb.flags |= Blackboard::HIGH_LUST;
if (bb.lust < 5)
bb.flags |= Blackboard::LOW_LUST;
if (bb.stamina < 0)
bb.stamina = 0;
else if (bb.stamina > 100)
bb.stamina = 100;
if (bb.lust < 0)
bb.lust = 0;
else if (bb.lust > 100)
bb.lust = 100;
});
ecs.system<Blackboard, Plan>("UpdateCharactersPlan2")
.kind(flecs::OnUpdate)
.each([&](flecs::entity e, Blackboard &bb, Plan &p) {
int i;
bool ok_plan = true;
for (i = p.position; i < p.length; i++) {
if (!p.actions[i]->can_run(bb, true)) {
ok_plan = false;
break;
}
int ret = p.actions[i]->execute(bb);
p.position = i;
if (ret == BaseAction::BUSY)
break;
else if (ret == BaseAction::ABORT) {
ok_plan = false;
break;
}
if (ret == BaseAction::OK && i == p.length - 1)
ok_plan = false;
// stop_this = true;
}
if (!ok_plan) {
std::cout << e.name() << ": invalidated plan"
<< " step: " << i << std::endl;
for (i = 0; i < p.length; i++)
p.actions[i]->stop(bb);
e.remove<Plan>();
}
});
ecs.system<Blackboard, Planner>("UpdateCharacters2")
.kind(flecs::OnUpdate)
.without<Plan>()
.each([&](flecs::entity e, Blackboard &bb, Planner &planner) {
int i;
std::vector<BaseAction *> actions;
actions.insert(actions.end(), planner.actions.begin(),
planner.actions.end());
e.world()
.query_builder<const ActionTarget>()
.build()
.each([&](flecs::entity me,
const ActionTarget &t) {
actions.insert(actions.end(),
t.actions.begin(),
t.actions.end());
#if 0
auto it = t.actions.begin();
while (it != t.actions.end()) {
if (me != bb.me)
actions.push_back(*it);
// std::cout << (*it)->get_name()
// << std::endl;
it++;
}
#endif
});
#if 0
for (i = 0; i < actions.size(); i++)
std::cout << "action: " << i << " "
<< actions[i]->get_name()
<< std::endl;
#endif
if (actions.size() == 0)
return;
int len = -1;
OgreAssert(
bb.stamina < 100 ||
bb.stamina >= 100 &&
!bb.get_flag(
Blackboard::LOW_STAMINA),
"bad thing");
for (i = 0; i < planner.goals.size(); i++) {
if (planner.goals[i]->distance_to(bb) > 1000000)
continue;
len = planner.planner.plan(
bb, *planner.goals[i], actions.data(),
actions.size(), planner.path.data(),
100);
std::cout << bb.me.name() << ": goal: " << len
<< " " << planner.goals[i]->get_name()
<< std::endl;
if (len > 0)
break;
}
// std::cout << "plan length: " << len << std::endl;
#if 0
if (len > 0)
stop_this = true;
if (len < 0)
stop_this = true;
#endif
if (len > 0) {
Plan &p = e.ensure<Plan>();
p.actions = planner.path;
p.position = 0;
p.length = len;
for (i = 0; i < len; i++)
std::cout
<< i << " "
<< planner.path[i]->get_name()
<< " "
<< planner.path[i]->get_cost(bb)
<< std::endl;
bool ok_plan = true;
for (i = 0; i < len; i++) {
if (!planner.path[i]->can_run(bb,
true)) {
ok_plan = false;
break;
}
int ret = planner.path[i]->execute(bb);
p.position = i;
std::cout << "exec: " << i << " "
<< planner.path[i]->get_name()
<< std::endl;
if (ret == BaseAction::BUSY)
break;
else if (ret == BaseAction::ABORT) {
ok_plan = false;
} else if (ret == BaseAction::OK)
std::cout
<< "exec: complete "
<< i << " "
<< planner.path[i]
->get_name()
<< std::endl;
}
e.modified<Plan>();
if (!ok_plan) {
std::cout << e.name()
<< ": invalidate plan"
<< " step: " << i
<< std::endl;
for (i = 0; i < len; i++)
planner.path[i]->stop(bb);
e.remove<Plan>();
}
}
});
}
}

View File

@@ -1,6 +1,7 @@
#ifndef CHARACTER_MODULE_H_
#define CHARACTER_MODULE_H_
#include <flecs.h>
#include "goap.h"
namespace ECS
{
struct Camera;
@@ -11,6 +12,9 @@ struct CharacterGravity {};
struct CharacterBuoyancy {};
struct CharacterDisablePhysics {};
struct CharacterUpdatePhysicsState {};
struct Male {};
struct Female {};
struct CharacterBase {
Ogre::String type;
float mTimer;
@@ -45,10 +49,28 @@ struct CharacterInActuator {
Ogre::String animationState;
Vector3 prevMotion;
};
struct ActionTarget {
std::vector<BaseAction *> actions;
};
struct Plan {
std::vector<BaseAction *> actions;
int position;
int length;
};
struct Planner {
BasePlanner<ECS::Blackboard, BaseAction> planner;
std::vector<BaseAction *> path;
std::vector<BasePlanner<ECS::Blackboard, BaseAction>::BaseGoal *> goals;
std::vector<BaseAction *> actions;
};
struct CharacterModule {
CharacterModule(flecs::world &ecs);
void updateCameraGoal(Camera &camera, Ogre::Real deltaYaw,
Ogre::Real deltaPitch, Ogre::Real deltaZoom);
};
struct CharacterAIModule {
CharacterAIModule(flecs::world &ecs);
};
}
#endif

View File

@@ -0,0 +1,11 @@
#ifndef EVENT_SYSTEM_H_
#define EVENT_SYSTEM_H_
struct EventSystem {
struct CommonEvent {
int event_type;
};
void send_event(const EventSystem &event)
{
}
};
#endif

View File

@@ -127,11 +127,13 @@ ECS::EventTriggerModule::EventTriggerModule(flecs::world &ecs)
body.mSceneNode->getUserObjectBindings().setUserAny(
"BtCollisionObject", objWrapper);
});
ecs.component<EventTrigger>().on_set(
[](flecs::entity e, EventTrigger &ev) {
e.add<TriggerBody>();
e.set<EventTriggerData>({});
});
ecs.component<EventTrigger>().on_set([](flecs::entity e,
EventTrigger &ev) {
e.add<TriggerBody>();
e.set<EventTriggerData>({});
ECS::get<LuaBase>().mLua->call_handler("actuator_created", e,
e);
});
ecs.system<const EngineData, const EventTrigger, TriggerBody,
EventTriggerData>("CheckCollisions")
.kind(flecs::OnUpdate)

View File

@@ -12,20 +12,19 @@
#include "BoatModule.h"
#include "EventTriggerModule.h"
#include "CharacterAnimationModule.h"
#include "world-build.h"
namespace ECS
{
static flecs::world ecs;
flecs::entity player;
void setup(Ogre::SceneManager *scnMgr, Ogre::Bullet::DynamicsWorld *world,
Ogre::SceneNode *cameraNode, Ogre::Camera *camera,
Ogre::RenderWindow *window)
void setup_minimal()
{
std::cout << "Setup GameData\n";
ecs.component<EngineData>().add(flecs::Singleton);
ecs.component<GameData>().add(flecs::Singleton);
ecs.component<Input>().add(flecs::Singleton);
ecs.component<Camera>().add(flecs::Singleton);
ecs.import <GameWorldModule>();
ecs.component<InWater>();
ecs.component<WaterReady>().add(flecs::Singleton);
ecs.component<GroundCheckReady>().add(flecs::Singleton);
@@ -41,6 +40,13 @@ void setup(Ogre::SceneManager *scnMgr, Ogre::Bullet::DynamicsWorld *world,
ecs.component<ParentSlot>();
ecs.component<ObjectSlots>();
ecs.component<Body2Entity>().add(flecs::Singleton);
}
void setup(Ogre::SceneManager *scnMgr, Ogre::Bullet::DynamicsWorld *world,
Ogre::SceneNode *cameraNode, Ogre::Camera *camera,
Ogre::RenderWindow *window)
{
std::cout << "Setup GameData\n";
setup_minimal();
ecs.import <WaterModule>();
ecs.import <CharacterModule>();
ecs.import <TerrainModule>();

View File

@@ -5,6 +5,7 @@
namespace ECS
{
extern flecs::entity player;
void setup_minimal();
void setup(Ogre::SceneManager *scnMgr, Ogre::Bullet::DynamicsWorld *world,
Ogre::SceneNode *cameraNode, Ogre::Camera *camera,
Ogre::RenderWindow *window);

View File

@@ -3,8 +3,10 @@
#include "Components.h"
#include "GUIModule.h"
#include "CharacterModule.h"
#include "CharacterAnimationModule.h"
#include "BoatModule.h"
#include "EventTriggerModule.h"
#include "world-build.h"
#include "LuaData.h"
extern "C" {
@@ -258,35 +260,6 @@ LuaData::LuaData()
return 0;
});
lua_setglobal(L, "main_menu");
lua_pushcfunction(L, [](lua_State *L) -> int {
OgreAssert(lua_gettop(L) == 3, "Invalid parameters");
luaL_checktype(L, 1, LUA_TNUMBER);
luaL_checktype(L, 2, LUA_TSTRING);
luaL_checktype(L, 3, LUA_TBOOLEAN);
bool enable = lua_toboolean(L, 3);
flecs::entity e = idmap.get_entity(lua_tointeger(L, 1));
Ogre::String what = lua_tostring(L, 2);
OgreAssert(e.is_valid(), "Invalid character");
OgreAssert(e.has<Character>(), "Not a character");
if (what == "gravity") {
/* clear momentum */
e.get_mut<CharacterVelocity>().gvelocity.y = 0.0f;
e.get_mut<CharacterVelocity>().velocity.y = 0.0f;
e.modified<CharacterVelocity>();
if (enable)
e.add<CharacterGravity>();
else
e.remove<CharacterGravity>();
} else if (what == "buoyancy") {
if (enable)
e.add<CharacterBuoyancy>();
else
e.remove<CharacterBuoyancy>();
} else
OgreAssert(false, "Bad parameter " + what);
return 0;
});
lua_setglobal(L, "ecs_character_params_set");
lua_pushcfunction(L, [](lua_State *L) -> int {
luaL_checktype(L, 1, LUA_TSTRING);
luaL_checktype(L, 2, LUA_TNUMBER);
@@ -476,44 +449,114 @@ LuaData::LuaData()
});
lua_setglobal(L, "ecs_set_debug_drawing");
lua_pushcfunction(L, [](lua_State *L) -> int {
OgreAssert(lua_gettop(L) == 2, "Bad parameters");
luaL_checktype(L, 1, LUA_TNUMBER); // object
luaL_checktype(L, 2, LUA_TBOOLEAN);
int object = lua_tointeger(L, 1);
flecs::entity object_e = idmap.get_entity(object);
bool enable = lua_toboolean(L, 2);
if (enable)
object_e.remove<CharacterDisablePhysics>();
else
object_e.add<CharacterDisablePhysics>();
object_e.add<CharacterUpdatePhysicsState>();
return 0;
});
lua_setglobal(L, "ecs_character_physics_control");
lua_pushcfunction(L, [](lua_State *L) -> int {
luaL_checktype(L, 1, LUA_TNUMBER); // object
int object = lua_tointeger(L, 1);
flecs::entity object_e = idmap.get_entity(object);
lua_pushboolean(L, object_e.has<Player>());
return 1;
});
lua_setglobal(L, "ecs_character_is_player");
lua_pushcfunction(L, [](lua_State *L) -> int {
luaL_checktype(L, 1, LUA_TNUMBER); // object
luaL_checktype(L, 2, LUA_TSTRING); // animation
Ogre::String animation = lua_tostring(L, 2);
OgreAssert(lua_gettop(L) >= 1, "Bad parameters");
luaL_checktype(L, 1, LUA_TSTRING);
Ogre::String command = lua_tostring(L, 1);
if (command == "physics-control") {
OgreAssert(lua_gettop(L) == 3, "Bad parameters");
luaL_checktype(L, 2, LUA_TNUMBER); // object
luaL_checktype(L, 3, LUA_TBOOLEAN);
int object = lua_tointeger(L, 2);
flecs::entity object_e = idmap.get_entity(object);
bool enable = lua_toboolean(L, 3);
if (enable)
object_e.remove<CharacterDisablePhysics>();
else
object_e.add<CharacterDisablePhysics>();
object_e.add<CharacterUpdatePhysicsState>();
return 0;
} else if (command == "is-player") {
OgreAssert(lua_gettop(L) == 2, "Bad parameters");
luaL_checktype(L, 2, LUA_TNUMBER); // object
int object = lua_tointeger(L, 2);
flecs::entity object_e = idmap.get_entity(object);
lua_pushboolean(L, object_e.has<Player>());
return 1;
} else if (command == "set-actuator") {
OgreAssert(lua_gettop(L) == 3, "Bad parameters");
luaL_checktype(L, 2, LUA_TNUMBER); // object
luaL_checktype(L, 3, LUA_TSTRING); // animation
Ogre::String animation = lua_tostring(L, 3);
int object = lua_tointeger(L, 1);
flecs::entity object_e = idmap.get_entity(object);
object_e.set<CharacterVelocity>({ { 0, 0, 0 }, { 0, 0, 0 } });
if (animation.length() > 0)
object_e.set<CharacterInActuator>(
{ animation, { 0, 0, 0 } });
else
object_e.remove<CharacterInActuator>();
return 0;
int object = lua_tointeger(L, 2);
flecs::entity object_e = idmap.get_entity(object);
object_e.set<CharacterVelocity>(
{ { 0, 0, 0 }, { 0, 0, 0 } });
if (animation.length() > 0)
object_e.set<CharacterInActuator>(
{ animation, { 0, 0, 0 } });
else
object_e.remove<CharacterInActuator>();
return 0;
} else if (command == "animation-state") {
OgreAssert(lua_gettop(L) >= 4, "Bad parameters");
luaL_checktype(L, 2, LUA_TNUMBER); // object
luaL_checktype(L, 3, LUA_TSTRING); // node
luaL_checktype(L, 4, LUA_TSTRING); // state
if (lua_gettop(L) == 5)
luaL_checktype(L, 5, LUA_TBOOLEAN); // reset
int object = lua_tointeger(L, 2);
flecs::entity object_e = idmap.get_entity(object);
Ogre::String nodeName = lua_tostring(L, 3);
Ogre::String stateName = lua_tostring(L, 4);
bool reset = false;
if (lua_gettop(L) == 5)
reset = lua_toboolean(L, 5);
GameWorld::ValueParameter<flecs::entity> *obj =
object_e.world()
.get_mut<GameWorld>()
.allocate_entity(object_e);
GameWorld::ValueParameter<std::string> *obj_node =
object_e.world()
.get_mut<GameWorld>()
.allocate_string(nodeName);
GameWorld::ValueParameter<std::string> *obj_state =
object_e.world()
.get_mut<GameWorld>()
.allocate_string(stateName);
ECS::get_mut<GameWorld>().command("set_animation_state",
{ obj, obj_node,
obj_state });
ECS::modified<GameWorld>();
return 0;
} else if (command == "params-set") {
OgreAssert(lua_gettop(L) == 4, "Invalid parameters");
luaL_checktype(L, 2, LUA_TNUMBER);
luaL_checktype(L, 3, LUA_TSTRING);
luaL_checktype(L, 4, LUA_TBOOLEAN);
bool enable = lua_toboolean(L, 4);
flecs::entity e = idmap.get_entity(lua_tointeger(L, 2));
Ogre::String what = lua_tostring(L, 3);
OgreAssert(e.is_valid(), "Invalid character");
OgreAssert(e.has<Character>(), "Not a character");
if (what == "gravity") {
/* clear momentum */
if (e.has<CharacterVelocity>()) {
e.get_mut<CharacterVelocity>()
.gvelocity.y = 0.0f;
e.get_mut<CharacterVelocity>()
.velocity.y = 0.0f;
e.modified<CharacterVelocity>();
}
if (enable)
e.add<CharacterGravity>();
else
e.remove<CharacterGravity>();
} else if (what == "buoyancy") {
if (enable)
e.add<CharacterBuoyancy>();
else
e.remove<CharacterBuoyancy>();
} else
OgreAssert(false, "Bad parameter " + what);
return 0;
} else {
OgreAssert(false, "bad argument " + command);
return 0;
}
});
lua_setglobal(L, "ecs_character_set_actuator");
lua_setglobal(L, "ecs_character");
lua_pushcfunction(L, [](lua_State *L) -> int {
OgreAssert(lua_gettop(L) == 3, "Bad parameters");
luaL_checktype(L, 1, LUA_TNUMBER); // parent
@@ -693,6 +736,7 @@ void LuaData::lateSetup()
LuaModule::LuaModule(flecs::world &ecs)
{
ecs.module<LuaModule>();
ecs.component<LuaChildEventTrigger>();
ecs.component<LuaBase>()
.on_add([](LuaBase &lua) {

View File

@@ -0,0 +1,10 @@
#include "SmartObject.h"
namespace ECS
{
SmartObjectModule::SmartObjectModule(flecs::world &ecs)
{
ecs.module<SmartObjectModule>();
ecs.component<SmartObjectManager>().add(flecs::Singleton);
ecs.component<SmartObject>();
}
}

View File

@@ -0,0 +1,19 @@
#ifndef SMART_OBJECT_H_
#define SMART_OBJECT_H_
#include <Ogre.h>
#include <flecs.h>
namespace ECS
{
struct SmartObject {
Ogre::String enterNodeName;
Ogre::String exitNodeName;
Ogre::String scenario;
};
struct SmartObjectManager {
std::vector<flecs::entity> targets;
};
struct SmartObjectModule {
SmartObjectModule(flecs::world &ecs);
};
}
#endif

38
src/gamedata/goap.cpp Normal file
View File

@@ -0,0 +1,38 @@
#include <set>
#include "goap.h"
bool BaseAction::can_run(const ECS::Blackboard &state, bool debug)
{
return m_exec->_can_run(state, debug);
}
void BaseAction::_plan_effects(ECS::Blackboard &state)
{
state.clear_flag(m_exec->m_clear_bits);
state.set_flag(m_exec->m_set_bits);
}
bool BaseAction::is_active(const ECS::Blackboard &state)
{
return m_exec->is_active(state);
}
bool BaseAction::stop(ECS::Blackboard &state)
{
if (!is_active(state))
return false;
m_exec->_exit(state);
state._active.erase(m_exec.get());
return true;
}
int BaseAction::execute(ECS::Blackboard &state)
{
if (!is_active(state)) {
state._active.insert(m_exec.get());
m_exec->_enter(state);
}
int ret = m_exec->_execute(state);
if (ret == OK)
stop(state);
return ret;
}
int BaseAction::get_cost(const ECS::Blackboard &state) const
{
return m_exec->_get_cost(state);
}

349
src/gamedata/goap.h Normal file
View File

@@ -0,0 +1,349 @@
#ifndef H_GOAP_H_
#define H_GOAP_H_
#include <string>
#include <memory>
#include <flecs.h>
namespace ECS
{
struct Blackboard;
}
struct BaseAction;
struct BaseActionExec;
namespace ECS
{
struct Blackboard {
enum {
HIGH_LUST = (1 << 0),
LOW_LUST = (1 << 1),
FULL_LUST = (1 << 2),
LOW_HEALTH = (1 << 3),
FULL_HEALTH = (1 << 4),
HAS_TARGET = (1 << 5),
LOW_STAMINA = (1 << 6),
FULL_STAMINA = (1 << 7),
REACHED_TARGET = (1 << 8),
};
flecs::entity me;
int health;
int stamina;
int lust;
uint32_t flags;
std::set<BaseActionExec *> _active;
bool operator==(const Blackboard &other)
{
return flags == other.flags;
}
void set_flag(int flag)
{
flags |= flag;
}
void clear_flag(int flag)
{
flags &= ~flag;
}
bool get_flag(int flag) const
{
return flags & flag;
}
bool check_flag(int flag) const
{
return (flags & flag) == flag;
}
};
}
struct BaseAction {
std::string m_name;
public:
enum { OK = 0, BUSY, ABORT };
private:
std::unique_ptr<BaseActionExec> m_exec;
public:
BaseAction(const std::string &name, BaseActionExec *exec)
: m_name(name)
, m_exec(exec)
{
}
const std::string &get_name() const
{
return m_name;
}
void plan_effects(ECS::Blackboard &state)
{
// std::cout << m_name << " pre: " << &state << " " << state.flags
// << std::endl;
_plan_effects(state);
// std::cout << m_name << " post: " << &state << " " << state.flags
// << std::endl;
}
virtual bool can_run(const ECS::Blackboard &state, bool debug = false);
virtual void _plan_effects(ECS::Blackboard &state);
bool is_active(const ECS::Blackboard &state);
bool stop(ECS::Blackboard &state);
int execute(ECS::Blackboard &state);
virtual int get_cost(const ECS::Blackboard &state) const;
};
struct BaseActionExec {
enum {
OK = BaseAction::OK,
BUSY = BaseAction::BUSY,
ABORT = BaseAction::ABORT
};
BaseActionExec(int set_bits, int clear_bits)
: m_set_bits(set_bits)
, m_clear_bits(clear_bits)
{
}
bool is_active(const ECS::Blackboard &state)
{
return state._active.find(this) != state._active.end();
}
virtual int _execute(ECS::Blackboard &state) = 0;
virtual void _enter(ECS::Blackboard &state) = 0;
virtual void _exit(ECS::Blackboard &state) = 0;
virtual int _get_cost(const ECS::Blackboard &state) const
{
return 1;
}
virtual bool _can_run(const ECS::Blackboard &state, bool debug = false)
{
return true;
}
int m_set_bits, m_clear_bits;
};
template <typename State, typename Act, int N = 100> class BasePlanner {
struct VisitedState {
int priority;
int cost;
State state;
// Used to reconstruct the path
VisitedState *parent;
Act *action;
// Only used for linked list management
VisitedState *next;
};
VisitedState nodes[N];
void visited_states_array_to_list(VisitedState *nodes, int len)
{
for (auto i = 0; i < len - 1; i++) {
nodes[i].next = &nodes[i + 1];
}
nodes[len - 1].next = nullptr;
}
VisitedState *priority_list_pop(VisitedState *&list)
{
VisitedState *min_elem = nullptr;
auto min_prio = std::numeric_limits<int>::max();
// Find the element with the lowest priority
for (auto p = list; p != nullptr; p = p->next) {
if (p->priority < min_prio) {
min_elem = p;
min_prio = p->priority;
}
}
// Remove it from the list
if (min_elem == list) {
list = list->next;
} else {
for (auto p = list; p != nullptr; p = p->next) {
if (p->next == min_elem) {
p->next = p->next->next;
min_elem->next = nullptr;
}
}
}
return min_elem;
}
VisitedState *list_pop_head(VisitedState *&head)
{
auto ret = head;
head = head->next;
ret->next = nullptr;
return ret;
}
void list_push_head(VisitedState *&head, VisitedState *elem)
{
elem->next = head;
head = elem;
}
void update_queued_state(VisitedState *previous,
const VisitedState *current)
{
if (previous->cost > current->cost) {
previous->priority = current->cost;
previous->priority = current->priority;
previous->parent = current->parent;
previous->action = current->action;
}
}
public:
struct BaseGoal {
std::string m_name;
public:
BaseGoal(const std::string &name)
: m_name(name)
{
}
/** Checks if the goal is reached for the given state. */
virtual bool is_reached(const State &state) const
{
return distance_to(state) == 0;
}
/** Computes the distance from state to goal. */
virtual int distance_to(const State &state) const = 0;
virtual ~BaseGoal() = default;
const std::string &get_name() const
{
return m_name;
}
};
/** Finds a plan from state to goal and returns its length.
*
* If path is given, then the found path is stored there.
*/
int plan(const State &state, BaseGoal &goal, Act *actions[],
unsigned action_count, Act **path = nullptr, int path_len = 10)
{
visited_states_array_to_list(nodes, N);
auto free_nodes = &nodes[0];
VisitedState *open = list_pop_head(free_nodes);
VisitedState *close = nullptr;
open->state = state;
open->cost = 0;
open->priority = 0;
open->parent = nullptr;
open->action = nullptr;
while (open) {
VisitedState *current = priority_list_pop(open);
list_push_head(close, current);
if (goal.is_reached(current->state)) {
auto len = 0;
for (auto p = current->parent; p;
p = p->parent) {
len++;
}
if (len > path_len) {
return -1;
}
if (path) {
auto i = len - 1;
for (auto p = current; p->parent;
p = p->parent, i--) {
path[i] = p->action;
}
}
return len;
}
for (auto i = 0u; i < action_count; i++) {
auto action = actions[i];
if (action->can_run(current->state)) {
// Cannot allocate a new node, abort
if (free_nodes == nullptr) {
// Garbage collect the node that is most unlikely to be
// visited (i.e. lowest priority)
VisitedState *gc,
*gc_prev = nullptr;
for (gc = open; gc && gc->next;
gc = gc->next) {
gc_prev = gc;
}
if (!gc) {
return -2 /* OOM */;
}
if (gc_prev) {
gc_prev->next = nullptr;
}
free_nodes = gc;
}
auto neighbor =
list_pop_head(free_nodes);
neighbor->state = current->state;
action->plan_effects(neighbor->state);
neighbor->cost =
current->cost + 1 +
action->get_cost(
neighbor->state);
neighbor->priority =
current->priority + 1 +
goal.distance_to(
neighbor->state);
neighbor->parent = current;
neighbor->action = action;
bool should_insert = true;
// Check if the node is already in the list of nodes
// scheduled to be visited
for (auto p = open; p; p = p->next) {
if (p->state ==
neighbor->state) {
should_insert = false;
update_queued_state(
p, neighbor);
}
}
// Check if the state is in the list of already visited
// state
for (auto p = close; p; p = p->next) {
if (p->state ==
neighbor->state) {
should_insert = false;
update_queued_state(
p, neighbor);
}
}
if (should_insert) {
list_push_head(open, neighbor);
} else {
list_push_head(free_nodes,
neighbor);
}
}
}
}
// Reaching here means we did not find a path
return -1 /* No path */;
}
};
template <class RunnerType> struct DeclareAction : public BaseAction {
RunnerType runner;
template <class... Args>
DeclareAction(const std::string &name, Args... args)
: runner(args...)
, BaseAction(name, &runner)
{
}
};
#endif

View File

@@ -461,10 +461,18 @@ void SceneLoader::processNode(pugi::xml_node &XMLNode, SceneNode *pParent)
// Provide the name
if (pParent)
pNode = pParent->createChildSceneNode(
mNamePrefix + "/" + name);
mNamePrefix + name);
else
pNode = mAttachNode->createChildSceneNode(
mNamePrefix + "/" + name);
mNamePrefix + name);
LogManager::getSingleton().logMessage(
"[SceneLoader] Processing Node: " +
mNamePrefix + name);
LogManager::getSingleton().logMessage(
"[SceneLoader] Processing Node: prefix: " +
mNamePrefix);
LogManager::getSingleton().logMessage(
"[SceneLoader] Processing Node: name: " + name);
} else {
// Let Ogre choose the name
Ogre::String pname = mNamePrefix += name;

10
src/tests/CMakeLists.txt Normal file
View File

@@ -0,0 +1,10 @@
project(tests)
find_package(pugixml CONFIG)
find_package(assimp REQUIRED CONFIG)
find_package(OGRE REQUIRED COMPONENTS Bites Bullet Paging Terrain CONFIG)
add_executable(check_uv check_uv.cpp)
target_link_libraries(check_uv ${ASSIMP_LIBRARIES})
add_executable(ogre_check_uv ogre_check_uv.cpp)
target_link_libraries(ogre_check_uv OgreBites OgreMain)

54
src/tests/check_uv.cpp Normal file
View File

@@ -0,0 +1,54 @@
#include <iostream>
#include <list>
#include <assimp/version.h>
#include <assimp/scene.h>
#include <assimp/postprocess.h>
#include <assimp/Importer.hpp>
#include <assimp/IOStream.hpp>
#include <assimp/IOSystem.hpp>
int main(int argc, char *argv[])
{
int i;
Assimp::Importer importer;
uint32_t flags = aiProcessPreset_TargetRealtime_Fast | aiProcess_TransformUVCoords | aiProcess_FlipUVs;
flags &= ~(aiProcess_JoinIdenticalVertices | aiProcess_CalcTangentSpace); // optimize for fast loading
if((flags & (aiProcess_GenSmoothNormals | aiProcess_GenNormals)) != aiProcess_GenNormals)
flags &= ~aiProcess_GenNormals; // prefer smooth normals
float maxEdgeAngle = 0.75f;
importer.SetPropertyFloat("PP_GSN_MAX_SMOOTHING_ANGLE", maxEdgeAngle);
importer.SetPropertyBool(AI_CONFIG_IMPORT_FBX_EMBEDDED_TEXTURES_LEGACY_NAMING, true);
if (argc < 2) {
std::cerr << "path name needed" << std::endl;
return 1;
}
const char *pname = argv[1];
const aiScene* scene = importer.ReadFile(pname,
flags);
std::list<aiNode *> queue;
if (!scene) {
std::cerr << "could not process file" << std::endl;
return 1;
}
queue.push_back(scene->mRootNode);
while(!queue.empty()) {
aiNode *node = queue.front();
queue.pop_front();
for (i = 0; i < node->mNumMeshes; i++) {
aiMesh* mesh = scene->mMeshes[node->mMeshes[i]];
int uv_count = 0;
for (int uvindex = 0; uvindex < AI_MAX_NUMBER_OF_TEXTURECOORDS; uvindex++) {
aiVector3D* uv = mesh->mTextureCoords[uvindex];
if (!uv)
break;
uv_count++;
}
std::cout << "node: " << node->mName.C_Str() << " mesh: " << i << " mesh uv count: " << uv_count << std::endl;
}
for (i = 0; i < node->mNumChildren; i++)
queue.push_back(node->mChildren[i]);
}
return 0;
}

View File

@@ -0,0 +1,83 @@
#include <iostream>
#include <Ogre.h>
#include <OgreCodec.h>
#include <OgreFileSystem.h>
#include <OgreFileSystemLayer.h>
#include <OgreMaterialManager.h>
#include <OgreShaderGenerator.h>
static void getSubmeshUVs(const Ogre::Mesh *mesh, const Ogre::SubMesh *submesh,
std::vector<Ogre::Vector2> &uvs, int index)
{
int j;
float *pReal;
Ogre::HardwareVertexBufferSharedPtr vbuf;
Ogre::VertexData *vertex_data = submesh->useSharedVertices ?
mesh->sharedVertexData :
submesh->vertexData;
const Ogre::VertexElement *uvElem =
vertex_data->vertexDeclaration->findElementBySemantic(
Ogre::VES_TEXTURE_COORDINATES, index);
int vertex_count = 0;
if (submesh->useSharedVertices)
vertex_count += mesh->sharedVertexData->vertexCount;
else
vertex_count += submesh->vertexData->vertexCount;
if (!uvElem)
return;
OgreAssert(uvs.size() == 0 || uvs.size() == vertex_count,
"bad vertex count");
uvs.resize(vertex_count);
vbuf = vertex_data->vertexBufferBinding->getBuffer(uvElem->getSource());
unsigned char *uv = static_cast<unsigned char *>(
vbuf->lock(Ogre::HardwareBuffer::HBL_READ_ONLY));
for (j = 0; j < vertex_data->vertexCount; ++j) {
uvElem->baseVertexPointerToElement(uv, &pReal);
uvs[j] = Ogre::Vector2(pReal[0], pReal[1]);
uv += vbuf->getVertexSize();
}
vbuf->unlock();
}
int main()
{
Ogre::LogManager logMgr;
logMgr.createLog("messages.log", true, true, true);
Ogre::DefaultHardwareBufferManager bufferManager; // needed because we don't have a rendersystem
Ogre::Root *ogre = new Ogre::Root("", "", "");
Ogre::ConfigFile pluginsCfg;
Ogre::FileSystemLayer fsLayer("Ogre3D");
pluginsCfg.load(fsLayer.getConfigFilePath("plugins.cfg"));
auto pluginDir = Ogre::FileSystemLayer::resolveBundlePath(pluginsCfg.getSetting("PluginFolder")+"/");
ogre->loadPlugin(pluginDir + "/Codec_Assimp");
Ogre::MaterialManager::getSingleton().initialise();
Ogre::RTShader::ShaderGenerator::initialize();
Ogre::DefaultTextureManager texMgr;
auto& shadergen = Ogre::RTShader::ShaderGenerator::getSingleton();
shadergen.setTargetLanguage("glsl"); // must be valid, but otherwise arbitrary
shadergen.getRenderState(Ogre::MSN_SHADERGEN)->setLightCountAutoUpdate(false);
shadergen.validateScheme(Ogre::MSN_SHADERGEN);
auto codec = Ogre::Codec::getCodec("glb");
Ogre::ResourceGroupManager::getSingleton().createResourceGroup(
"Characters", true);
Ogre::ResourceGroupManager::getSingleton().addResourceLocation(
"characters", "FileSystem", "Characters", true, true);
Ogre::ResourceGroupManager::getSingleton().addResourceLocation(
"../characters", "FileSystem", "Characters", true, true);
Ogre::ResourceGroupManager::getSingleton().addResourceLocation(
"../../characters", "FileSystem", "Characters", true, true);
Ogre::DataStreamPtr meshData = Ogre::ResourceGroupManager::getSingleton().openResource("shapes/male/edited-normal-male-base.glb", "Characters");
Ogre::MeshPtr mesh = Ogre::MeshManager::getSingleton().createManual("shapes/male/edited-normal-male-base.glb", "Characters");
codec->decode(meshData, mesh.get());
std::vector<Ogre::Vector2> uvs;
getSubmeshUVs(mesh.get(), mesh->getSubMesh(0), uvs, 0);
std::cout << "UV0: " << uvs.size() << std::endl;
uvs.clear();
getSubmeshUVs(mesh.get(), mesh->getSubMesh(0), uvs, 1);
std::cout << "UV1: " << uvs.size() << std::endl;
return 0;
}

18
src/world/CMakeLists.txt Normal file
View File

@@ -0,0 +1,18 @@
project(world)
find_package(OGRE REQUIRED COMPONENTS Bites Bullet Paging Terrain Overlay CONFIG)
add_library(action action.cpp)
target_link_libraries(action PUBLIC GameData OgreBullet)
target_include_directories(action PUBLIC .)
add_library(world-build world-build.cpp)
target_link_libraries(world-build PUBLIC GameData OgreBullet)
target_include_directories(world-build PUBLIC .)
add_executable(test test.cpp)
target_link_libraries(test PRIVATE OgreMain OgreBullet action world-build lua)
add_executable(test2 test2.cpp)
target_link_libraries(test2 PRIVATE OgreMain OgreBullet action world-build lua)
add_executable(mark_harbors mark_harbors.cpp)
target_link_libraries(mark_harbors PRIVATE OgreMain OgreBullet lua)
add_custom_target(world ALL DEPENDS test test2)

2
src/world/action.cpp Normal file
View File

@@ -0,0 +1,2 @@
#include <goap/goap.hpp>
#include "action.h"

2
src/world/action.h Normal file
View File

@@ -0,0 +1,2 @@
class CharacterAction {};
class WorldAction {};

View File

@@ -0,0 +1,320 @@
#pragma once
/**
@defgroup behtree Behavior Tree
## Introduction
A behavior tree is a tree structure where each node represents a behavior. The
tree is evaluated from the root node to the leaf nodes. The leaf nodes are
either tasks or checks. A task is a node that performs an action and returns
either success or failure. A check is a node that returns either success or
failure based on some condition.
<center><pre class="mermaid">
flowchart TD
root{{Selector}} --> seq1
root --> seq2
root --> seq3
seq1{{Sequence}} --> seq1a1
seq1 --> seq1a2
seq2{{Sequence}} --> seq2a1
seq2 --> seq2a2
seq3{{Sequence}} --> seq3a1
seq3 --> seq3a2
seq1a1[Check enemy in attack range]
seq1a2[[Destroy enemy]]
seq2a1[Check enemy in sight]
seq2a2[[Move towards enemy]]
seq3a1[[Move towards waypoint]]
seq3a2[[Select next waypoint]]
style root fill:darkred
style seq1 fill:darkgreen
style seq2 fill:darkgreen
style seq3 fill:darkgreen
</pre></center>
## Usage
First, include the header file:
```cpp
#include <aitoolkit/behtree.hpp>
```
Then, create a blackboard class that will hold the state of the tree:
```cpp
struct blackboard_type {
glm::vec2 agent_position;
glm::vec2 enemy_position;
float attack_range;
float sight_range;
size_t current_waypoint;
std::vector<glm::vec2> waypoints;
};
```
Next, create the tree:
```cpp
using namespace aitoolkit::bt;
auto tree = sel<blackboard_type>(
node_list<blackboard_type>(
seq<blackboard_type>(
node_list<blackboard_type>(
check<blackboard_type>([](const blackboard_type& bb) {
auto distance = glm::distance(bb.agent_position, bb.enemy_position);
return distance <= bb.attack_range;
}),
task<blackboard_type>([](blackboard_type& bb) {
// Destroy enemy
return execution_state::success;
})
)
),
seq<blackboard_type>(
node_list<blackboard_type>(
check<blackboard_type>([](const blackboard_type& bb) {
auto distance = glm::distance(bb.agent_position, bb.enemy_position);
return distance <= bb.sight_range;
}),
task<blackboard_type>([](blackboard_type& bb) {
// Move towards enemy
return execution_state::success;
})
)
),
seq<blackboard_type>::make({
task<blackboard_type>([](blackboard_type& bb) {
// Move towards waypoint
return execution_state::success;
}),
task<blackboard_type>([](blackboard_type& bb) {
// Select next waypoint
return execution_state::success;
})
})
)
);
```
Finally, evaluate the tree:
```cpp
auto bb = blackboard_type{
.agent_position = { 0.0f, 0.0f },
.enemy_position = { 1.0f, 1.0f },
.attack_range = 0.5f,
.sight_range = 1.0f
};
while (true) {
auto state = tree.evaluate(bb);
if (state == execution_state::success) {
break;
}
}
```
*/
#include <functional>
#include <memory>
#include <vector>
#include <type_traits>
#include <concepts>
namespace aitoolkit::bt {
/**
* @ingroup behtree
* @enum execution_state
* @brief Represent the state of a node
*/
enum class execution_state {
success, /**< The node was successful */
failure, /**< The node failed */
running /**< The node is still running */
};
/**
* @ingroup behtree
* @class node
* @brief Base abstract class for all nodes
*/
template <class T>
class node {
public:
node() = default;
node(const node&) = delete;
node(node&& other) {
m_children = std::move(other.m_children);
}
virtual ~node() = default;
virtual execution_state evaluate(T& blackboard) const = 0;
protected:
std::vector<std::unique_ptr<node<T>>> m_children;
};
/**
* @ingroup behtree
* @brief Heap-allocated pointer to node
*/
template <class T>
using node_ptr = std::unique_ptr<node<T>>;
template <typename N, class T>
concept node_trait = std::derived_from<N, node<T>>;
/**
* @ingroup behtree
* @brief Helper function to create a list of nodes
*/
template <typename T, node_trait<T> ...Children>
std::vector<node_ptr<T>> node_list(Children&&... children) {
auto nodes = std::vector<node_ptr<T>>{};
nodes.reserve(sizeof...(children));
(nodes.push_back(std::make_unique<Children>(std::move(children))), ...);
return nodes;
}
/**
* @ingroup behtree
* @class seq
* @brief Sequence node, will execute all children in order until one fails
*/
template <class T>
class seq final : public node<T> {
public:
seq(std::vector<node_ptr<T>> children) {
this->m_children = std::move(children);
}
virtual execution_state evaluate(T& blackboard) const override {
for (auto& child : this->m_children) {
auto state = child->evaluate(blackboard);
if (state != execution_state::success) {
return state;
}
}
return execution_state::success;
}
};
/**
* @ingroup behtree
* @class sel
* @brief Selector node, will execute all children in order until one succeeds
*/
template <class T>
class sel final : public node<T> {
public:
sel(std::vector<node_ptr<T>> children) {
this->m_children = std::move(children);
}
virtual execution_state evaluate(T& blackboard) const override {
for (auto& child : this->m_children) {
auto state = child->evaluate(blackboard);
if (state != execution_state::failure) {
return state;
}
}
return execution_state::failure;
}
};
/**
* @ingroup behtree
* @class neg
* @brief Negate node, will return the opposite of the child node
*/
template <class T>
class neg final : public node<T> {
public:
template <node_trait<T> N>
neg(N&& child) {
this->m_children.reserve(1);
this->m_children.push_back(std::make_unique<N>(std::move(child)));
}
virtual execution_state evaluate(T& blackboard) const override {
if (this->m_children.size() != 1) {
return execution_state::failure;
}
auto& child = this->m_children.front();
auto state = child->evaluate(blackboard);
if (state == execution_state::success) {
return execution_state::failure;
} else if (state == execution_state::failure) {
return execution_state::success;
}
return state;
}
};
/**
* @ingroup behtree
* @class check
* @brief Check node, will return success if the callback returns true
*/
template <class T>
class check final : public node<T> {
public:
using callback_type = std::function<bool(const T&)>;
public:
check(callback_type fn) : m_fn(fn) {}
virtual execution_state evaluate(T& blackboard) const override {
if (m_fn(blackboard)) {
return execution_state::success;
}
return execution_state::failure;
}
private:
callback_type m_fn;
};
/**
* @ingroup behtree
* @class task
* @brief Task node, will execute the callback and return the result
*/
template <class T>
class task final : public node<T> {
public:
using callback_type = std::function<execution_state(T&)>;
public:
task(callback_type fn) : m_fn(fn) {}
virtual execution_state evaluate(T& blackboard) const override {
return m_fn(blackboard);
}
private:
callback_type m_fn;
};
}

318
src/world/aitoolkit/fsm.hpp Normal file
View File

@@ -0,0 +1,318 @@
#pragma once
/**
@defgroup fsm Finite State Machine
## Introduction
A finite state machine (FSM) is a mathematical model of computation. It is an
abstract machine that can be in exactly one of a finite number of states at any
given time. The FSM can change from one state to another in response to some
external inputs; the change from one state to another is called a transition.
This library provides 2 types of FSMs: a simple machiine and a stack machine.
The simple machine is the simplest form of FSM, it can only be in one state at a
time. The stack machine is a more complex form of FSM, it can be in multiple
states at a time.
## Usage
First, include the header:
```cpp
#include <aitoolkit/fsm.hpp>
```
Then, create a blackboard type:
```cpp
struct blackboard_type {
// ...
};
```
Then, create a state type for each of your states:
```cpp
class state_dummy final : public state<blackboard_type> {
public:
virtual void enter(blackboard_type& blackboard) override {
// ...
}
virtual void exit(blackboard_type& blackboard) override {
// ...
}
virtual void pause(blackboard_type& blackboard) override {
// ...
}
virtual void resume(blackboard_type& blackboard) override {
// ...
}
virtual void update(blackboard_type& blackboard) override {
// ...
}
};
```
### Simple state machine
Create an instance of the FSM:
```cpp
using namespace aitoolkit::fsm;
auto machine = simple_machine<blackboard_type>{};
```
To transition to a new state, call `set_state()`:
```cpp
machine.set_state(state_dummy{}, blackboard);
```
> **NB:**
>
> - this will call the `exit` method of the current state (if any) and the
> `enter` method of the new state
> - if the machine is paused while transitioning to a new state, the new state
> will be paused as well
To pause the machine, call `pause()`:
```cpp
machine.pause(blackboard);
```
> **NB:** This will call the `pause` method of the current state (if any).
To resume the machine, call `resume()`:
```cpp
machine.resume(blackboard);
```
> **NB:** This will call the `resume` method of the current state (if any).
To update the machine, call `update()`:
```cpp
machine.update(blackboard);
```
> **NB:**
>
> - this will call the `update` method of the current state (if any)
> - if the machine is paused, calling `update()` will do nothing
To clear any state, call `clear_state()`:
```cpp
machine.clear_state(blackboard);
```
> **NB:** This will call the `exit` method of the current state (if any).
### Stack state machine
Create an instance of the FSM:
```cpp
using namespace aitoolkit::fsm;
auto machine = stack_machine<blackboard_type>{};
```
To push a new state onto the stack, call `push_state()`:
```cpp
machine.push_state(state_dummy{}, blackboard);
```
> **NB:** This will call the `pause` method of the current state (if any) and
> the `enter` method of the new state.
To pop the top state off the stack, call `pop_state()`:
```cpp
machine.pop_state(blackboard);
```
> **NB:** This will call the `exit` method of the current state (if any) and
> the `resume` method of the new top state (if any).
To update the machine, call `update()`:
```cpp
machine.update(blackboard);
```
> **NB:** This will call the `update` method of the top state (if any).
*/
#include <memory>
#include <vector>
#include <type_traits>
#include <concepts>
namespace aitoolkit::fsm {
/**
* @ingroup fsm
* @class state
* @brief A state of the FSM.
*/
template <typename T>
class state {
public:
virtual ~state() = default;
virtual void enter(T& blackboard) {};
virtual void exit(T& blackboard) {};
virtual void pause(T& blackboard) {};
virtual void resume(T& blackboard) {};
virtual void update(T& blackboard) {};
};
/**
* @ingroup fsm
* @brief Heap-allocated pointer to a state.
*/
template <typename T>
using state_ptr = std::unique_ptr<state<T>>;
template <typename S, typename T>
concept state_trait = std::derived_from<S, state<T>>;
/**
* @ingroup fsm
* @class simple_machine
* @brief A simple FSM.
*/
template <typename T>
class simple_machine {
public:
/**
* @brief Enters in a new state, exiting the previous one (if any).
*/
template <state_trait<T> S>
void set_state(S state, T& blackboard) {
if (m_current_state) {
m_current_state->exit(blackboard);
}
m_current_state = std::make_unique<S>(state);
m_current_state->enter(blackboard);
if (m_paused) {
m_current_state->pause(blackboard);
}
}
/**
* @brief Clear the current state.
*/
void clear_state(T& blackboard) {
if (m_current_state) {
m_current_state->exit(blackboard);
m_current_state = nullptr;
}
}
/**
* @brief Pause the machine.
*/
void pause(T& blackboard) {
m_paused = true;
if (m_current_state) {
m_current_state->pause(blackboard);
}
}
/**
* @brief Resume the machine.
*/
void resume(T& blackboard) {
m_paused = false;
if (m_current_state) {
m_current_state->resume(blackboard);
}
}
/**
* @brief Update the machine.
*/
void update(T& blackboard) {
if (m_paused) {
return;
}
if (m_current_state) {
m_current_state->update(blackboard);
}
}
private:
state_ptr<T> m_current_state{nullptr};
bool m_paused{false};
};
/**
* @ingroup fsm
* @class stack_machine
* @brief A stack FSM.
*/
template <typename T>
class stack_machine {
public:
/**
* @brief Enters in a new state, pausing the previous one (if any).
*/
template <state_trait<T> S>
void push_state(S state, T& blackboard) {
if (!m_state_stack.empty()) {
auto& current_state = m_state_stack.back();
current_state->pause(blackboard);
}
state.enter(blackboard);
m_state_stack.push_back(std::make_unique<S>(state));
}
/**
* @brief Exits the current state, resuming the previous one (if any).
*/
void pop_state(T& blackboard) {
if (!m_state_stack.empty()) {
auto& current_state = m_state_stack.back();
current_state->exit(blackboard);
m_state_stack.pop_back();
}
if (!m_state_stack.empty()) {
auto& current_state = m_state_stack.back();
current_state->resume(blackboard);
}
}
/**
* @brief Update the machine.
*/
void update(T& blackboard) {
if (!m_state_stack.empty()) {
auto& current_state = m_state_stack.back();
current_state->update(blackboard);
}
}
private:
std::vector<state_ptr<T>> m_state_stack;
};
}

View File

@@ -0,0 +1,383 @@
#pragma once
/**
@defgroup goap Goal Oriented Action Planning
## Introduction
Goal Oriented Action Planning (GOAP) is a planning algorithm that can be used
to find a sequence of actions that will lead to a goal state. The algorithm
works by searching through a graph of possible actions and their effects. The
algorithm is guaranteed to find a solution if one exists, but it is not
guaranteed to find the optimal solution.
<center><pre class="mermaid">
graph LR
start[Start] --> action1
action1[Get axe] --> action2
action2[Chop tree] --> goal
goal[Goal]
action3[Get pickaxe] --> action4
action3 --> action5
action4[Mine gold]
action5[Mine stone]
style start fill:darkred
style goal fill:darkgreen
</pre></center>
## Usage
First, include the header file:
```cpp
#include <aitoolkit/goap.hpp>
```
Then, create a blackboard class that will hold the state of the planner:
```cpp
struct blackboard_type {
bool has_axe{false};
bool has_pickaxe{false};
int wood{0};
int gold{0};
int stone{0};
};
```
> **NB:** The blackboard needs to be comparable (`a == b`) and hashable.
Next, create a class for each action that you want to be able to perform:
```cpp
using namespace aitoolkit::goap;
class get_axe final : public action<blackboard_type> {
public:
virtual float cost(const blackboard_type& blackboard) const override {
return 1.0f;
}
virtual bool check_preconditions(const blackboard_type& blackboard) const override {
return !blackboard.has_axe;
}
virtual void apply_effects(blackboard_type& blackboard, bool dry_run) const override {
blackboard.has_axe = true;
}
};
class get_pickaxe final : public action<blackboard_type> {
public:
virtual float cost(const blackboard_type& blackboard) const override {
return 1.0f;
}
virtual bool check_preconditions(const blackboard_type& blackboard) const override {
return !blackboard.has_pickaxe;
}
virtual void apply_effects(blackboard_type& blackboard, bool dry_run) const override {
blackboard.has_pickaxe = true;
}
};
class chop_tree final : public action<blackboard_type> {
public:
virtual float cost(const blackboard_type& blackboard) const override {
return 1.0f;
}
virtual bool check_preconditions(const blackboard_type& blackboard) const override {
return blackboard.has_axe;
}
virtual void apply_effects(blackboard_type& blackboard, bool dry_run) const override {
blackboard.wood += 1;
}
};
class mine_gold final : public action<blackboard_type> {
public:
virtual float cost(const blackboard_type& blackboard) const override {
return 1.0f;
}
virtual bool check_preconditions(const blackboard_type& blackboard) const override {
return blackboard.has_pickaxe;
}
virtual void apply_effects(blackboard_type& blackboard, bool dry_run) const override {
blackboard.gold += 1;
}
};
class mine_stone final : public action<blackboard_type> {
public:
virtual float cost(const blackboard_type& blackboard) const override {
return 1.0f;
}
virtual bool check_preconditions(const blackboard_type& blackboard) const override {
return blackboard.has_pickaxe;
}
virtual void apply_effects(blackboard_type& blackboard, bool dry_run) const override {
blackboard.stone += 1;
}
};
```
Finally, create a plan and run it:
```cpp
auto initial = blackboard_type{};
auto goal = blackboard_type{
.has_axe = true,
.has_pickaxe = true,
.wood = 1,
.gold = 1,
.stone = 1
};
auto p = planner<blackboard_type>(
action_list<blackboard_type>(
get_axe{},
get_pickaxe{},
chop_tree{},
mine_gold{},
mine_stone{}
)
initial,
goal
);
auto blackboard = initial;
while (p) {
p.run_next(blackboard); // will mutate the blackboard
}
```
*/
#include <unordered_set>
#include <functional>
#include <coroutine>
#include <optional>
#include <memory>
#include <vector>
#include <queue>
#include <stack>
#include <type_traits>
#include <concepts>
namespace aitoolkit::goap {
template <typename T>
concept blackboard_trait = requires(const T &a, const T &b) {
{ a == b } -> std::convertible_to<bool>;
{ std::hash<T>{}(a) } -> std::convertible_to<size_t>;
};
/**
* @ingroup goap
* @class action
* @brief An action that can be performed on a blackboard.
*
* An action hae a cost, which is used during planning, if 2 actions have a
* similar effect, the one with the lowest cost will be chosen.
*
* An action also has preconditions, which are used to determine if an action
* can be performed on a blackboard. If the preconditions are not met, the
* action will not be considered during planning.
*
* Finally, an action has effects, which are applied to the blackboard when
* the action is performed.
*/
template <blackboard_trait T>
class action {
public:
virtual ~action() = default;
/**
* @brief The cost of performing this action.
*/
virtual float cost(const T& blackboard) const = 0;
/**
* @brief Check if the preconditions for this action are met.
*/
virtual bool check_preconditions(const T& blackboard) const = 0;
/**
* @brief Apply the effects of this action to the blackboard.
*/
virtual void apply_effects(T& blackboard, bool dry_run) const = 0;
};
/**
* @brief Heeap allocated pointer to an action.
*/
template <blackboard_trait T>
using action_ptr = std::unique_ptr<action<T>>;
template <typename A, typename T>
concept action_trait = std::derived_from<A, action<T>>;
template <blackboard_trait T, action_trait<T>... Actions>
std::vector<action_ptr<T>> action_list(Actions... actions) {
auto action_list = std::vector<action_ptr<T>>{};
action_list.reserve(sizeof...(Actions));
(action_list.push_back(std::make_unique<Actions>(std::move(actions))), ...);
return action_list;
}
/**
* @ingroup goap
* @class plan
* @brief A plan is a sequence of actions that will lead to a goal state.
*/
template <blackboard_trait T>
class plan {
public:
plan() = default;
/**
* @brief Get the number of actions in the plan.
*/
size_t size() const {
return m_plan.size();
}
/**
* @brief Check if the plan is empty.
*/
operator bool() const {
return !m_plan.empty();
}
/**
* @brief Execute the next planned action.
*/
void run_next(T& blackboard) {
if (!m_plan.empty()) {
auto action_idx = m_plan.top();
m_plan.pop();
auto& action = m_actions[action_idx];
action->apply_effects(blackboard, false);
}
}
private:
std::stack<size_t> m_plan;
std::vector<action_ptr<T>> m_actions;
friend plan<T> planner<T>(
std::vector<action_ptr<T>> actions,
T initital_blackboard,
T goal_blackboard,
size_t max_iterations
);
};
/**
* @ingroup goap
* @brief Create a plan.
*
* The plan is created by providing a list of actions, an initial blackboard
* state, and a goal blackboard state. The plan will then find a sequence of
* actions that will lead to the goal state.
*
* @param actions The list of actions that can be performed.
* @param initital_blackboard The initial state of the blackboard.
* @param goal_blackboard The goal state of the blackboard.
* @param max_iterations The maximum number of iterations to perform
* before giving up. If 0, the plan will run until it finds a solution.
* @return A plan that can be executed.
*/
template <blackboard_trait T>
plan<T> planner(
std::vector<action_ptr<T>> actions,
T initital_blackboard,
T goal_blackboard,
size_t max_iterations = 0
) {
struct node_type {
T blackboard;
float cost;
std::optional<size_t> action_taken_idx;
std::shared_ptr<node_type> parent;
};
using node_ptr = std::shared_ptr<node_type>;
struct node_compare {
bool operator()(const node_ptr& a, const node_ptr& b) const {
return a->cost > b->cost;
}
};
std::priority_queue<node_ptr, std::vector<node_ptr>, node_compare> open_set;
std::unordered_set<T> closed_set;
open_set.push(std::make_shared<node_type>(node_type{
.blackboard = initital_blackboard,
.cost = 0.0f,
.action_taken_idx = std::nullopt,
.parent = nullptr
}));
for (
size_t iteration = 0;
!open_set.empty() && (max_iterations == 0 || iteration < max_iterations);
++iteration
) {
auto current_node = open_set.top();
open_set.pop();
if (current_node->blackboard == goal_blackboard) {
auto p = plan<T>();
p.m_actions = std::move(actions);
while (current_node->parent != nullptr) {
auto action_idx = current_node->action_taken_idx.value();
p.m_plan.push(action_idx);
current_node = current_node->parent;
}
return p;
}
if (!closed_set.contains(current_node->blackboard)) {
closed_set.insert(current_node->blackboard);
for (size_t action_idx = 0; action_idx < actions.size(); action_idx++) {
auto& action = actions[action_idx];
if (action->check_preconditions(current_node->blackboard)) {
auto next_blackboard = current_node->blackboard;
action->apply_effects(next_blackboard, true);
auto next_cost = current_node->cost + action->cost(current_node->blackboard);
if (!closed_set.contains(next_blackboard)) {
open_set.push(std::make_shared<node_type>(node_type{
.blackboard = next_blackboard,
.cost = next_cost,
.action_taken_idx = action_idx,
.parent = current_node
}));
}
}
}
}
}
return plan<T>();
}
}

View File

@@ -0,0 +1,203 @@
#pragma once
/**
@defgroup utility Utility AI
## Introduction
Utility AI is a planning algorithm that can be used to find the best action to
perform in a given situation. The algorithm works by assigning a score to each
action based on how well it will achieve the goal. The algorithm is guaranteed
to find a solution.
<center><pre class="mermaid">
flowchart TD
a1[Collect food\nscore: +50]
a2[Collect wood\nscore: +150]
a3[Collect stone\nscore: -10]
a4[Collect gold\nscore: +75]
style a1 color:darkred
style a2 color:darkgreen
style a3 color:darkred
style a4 color:darkred
</pre></center>
## Usage
First, include the header file:
```cpp
#include <aitoolkit/utility.hpp>
```
Then, create a blackboard type:
```cpp
struct blackboard_type {
int food{0};
int wood{0};
int stone{0};
int gold{0};
};
```
Next, create a class for each action that you want to be able to perform:
```cpp
using namespace aitoolkit::utility;
class collect_food final : public action<blackboard_type> {
public:
virtual float score(const blackboard_type& blackboard) const override {
return 50.0f;
}
virtual void apply(blackboard_type& blackboard) const override {
blackboard.food += 1;
}
};
class collect_wood final : public action<blackboard_type> {
public:
virtual float score(const blackboard_type& blackboard) const override {
return 150.0f;
}
virtual void apply(blackboard_type& blackboard) const override {
blackboard.wood += 1;
}
};
class collect_stone final : public action<blackboard_type> {
public:
virtual float score(const blackboard_type& blackboard) const override {
return -10.0f;
}
virtual void apply(blackboard_type& blackboard) const override {
blackboard.stone += 1;
}
};
class collect_gold final : public action<blackboard_type> {
public:
virtual float score(const blackboard_type& blackboard) const override {
return 75.0f;
}
virtual void apply(blackboard_type& blackboard) const override {
blackboard.gold += 1;
}
};
```
Finally, create an evaluator and run it:
```cpp
auto evaluator = evaluator<blackboard_type>(
action_list<blackboard_type>(
collect_food{},
collect_wood{},
collect_stone{},
collect_gol{}
)
};
auto blackboard = blackboard_type{};
evaluator.run(blackboard);
```
*/
#include <memory>
#include <vector>
#include <limits>
#include <type_traits>
#include <concepts>
namespace aitoolkit::utility {
/**
* @ingroup utility
* @class action
* @brief Base abstract class for all actions
*/
template <typename T>
class action {
public:
virtual ~action() = default;
/**
* @brief Return the score of the action
*/
virtual float score(const T& blackboard) const = 0;
/**
* @brief Apply the action to the blackboard
*/
virtual void apply(T& blackboard) const = 0;
};
/**
* @ingroup utility
* @brief Heap allocated pointer to an action.
*/
template <typename T>
using action_ptr = std::unique_ptr<action<T>>;
template <typename A, typename T>
concept action_trait = std::derived_from<A, action<T>>;
/**
* @ingroup utility
* @brief Helper function to create a list of actions
*/
template <typename T, action_trait<T> ...Actions>
std::vector<action_ptr<T>> action_list(Actions&&... actions) {
auto actions_list = std::vector<action_ptr<T>>{};
actions_list.reserve(sizeof...(Actions));
(actions_list.push_back(std::make_unique<Actions>(std::move(actions))), ...);
return actions_list;
}
/**
* @ingroup utility
* @class evaluator
* @brief Evaluate a set of actions and apply the best one.
*/
template <typename T>
class evaluator {
public:
/**
* @brief Construct an evaluator from a list of actions
*/
evaluator(std::vector<action_ptr<T>> actions) : m_actions(std::move(actions)) {}
/**
* @brief Find the best action and apply it to the blackboard
*/
void run(T& blackboard) const {
if (m_actions.empty()) {
return;
}
auto best_score = std::numeric_limits<float>::min();
auto best_action = m_actions.front().get();
for (auto& action : m_actions) {
auto score = action->score(blackboard);
if (score > best_score) {
best_score = score;
best_action = action.get();
}
}
best_action->apply(blackboard);
}
private:
std::vector<action_ptr<T>> m_actions;
};
}

21
src/world/goap/LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2016 Club Vaudois de Robotique Autonome
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

193
src/world/goap/goap.hpp Normal file
View File

@@ -0,0 +1,193 @@
/** Action planner for basic AI purposes
*
* This file implements the technique presented in "Three States and a Plan:
* The A.I. of F.E.A.R." by Jeff Orkin.
*/
#ifndef GOAP_HPP
#define GOAP_HPP
#include <cstdint>
#include <cstdlib>
#include <goap/goap_internals.hpp>
#include <cstring>
namespace goap {
const int kErrorNoPathFound = -1;
const int kErrorNotEnoughMemory = -2;
template <typename State>
class Action {
public:
/** Checks if the given state allows this action to proceed. */
virtual bool can_run(const State& state) = 0;
/** Plans the effects of this action on the state */
virtual void plan_effects(State& state) = 0;
/** Tries to execute the task and returns true if it suceeded. */
virtual bool execute(State& state) = 0;
virtual ~Action() = default;
};
template <typename State>
class Goal {
public:
/** Checks if the goal is reached for the given state. */
virtual bool is_reached(const State& state) const
{
return distance_to(state) == 0;
}
/** Computes the distance from state to goal. */
virtual int distance_to(const State& state) const = 0;
virtual ~Goal() = default;
};
template <typename State, int N = 100>
class Planner {
VisitedState<State> nodes[N];
public:
/** Finds a plan from state to goal and returns its length.
*
* If path is given, then the found path is stored there.
*/
int plan(const State& state, Goal<State>& goal, Action<State>* actions[], unsigned action_count, Action<State>** path = nullptr, int path_len = 10)
{
visited_states_array_to_list(nodes, N);
auto free_nodes = &nodes[0];
auto open = list_pop_head(free_nodes);
VisitedState<State>* close = nullptr;
open->state = state;
open->cost = 0;
open->priority = 0;
open->parent = nullptr;
open->action = nullptr;
while (open) {
auto current = priority_list_pop(open);
list_push_head(close, current);
if (goal.is_reached(current->state)) {
auto len = 0;
for (auto p = current->parent; p; p = p->parent) {
len++;
}
if (len > path_len) {
return -1;
}
if (path) {
auto i = len - 1;
for (auto p = current; p->parent; p = p->parent, i--) {
path[i] = p->action;
}
}
return len;
}
for (auto i = 0u; i < action_count; i++) {
auto action = actions[i];
if (action->can_run(current->state)) {
// Cannot allocate a new node, abort
if (free_nodes == nullptr) {
// Garbage collect the node that is most unlikely to be
// visited (i.e. lowest priority)
VisitedState<State>*gc, *gc_prev = nullptr;
for (gc = open; gc && gc->next; gc = gc->next) {
gc_prev = gc;
}
if (!gc) {
return kErrorNotEnoughMemory;
}
if (gc_prev) {
gc_prev->next = nullptr;
}
free_nodes = gc;
}
auto neighbor = list_pop_head(free_nodes);
neighbor->state = current->state;
action->plan_effects(neighbor->state);
neighbor->cost = current->cost + 1;
neighbor->priority = current->priority + 1 + goal.distance_to(neighbor->state);
neighbor->parent = current;
neighbor->action = action;
bool should_insert = true;
// Check if the node is already in the list of nodes
// scheduled to be visited
for (auto p = open; p; p = p->next) {
if (p->state == neighbor->state) {
should_insert = false;
update_queued_state(p, neighbor);
}
}
// Check if the state is in the list of already visited
// state
for (auto p = close; p; p = p->next) {
if (p->state == neighbor->state) {
should_insert = false;
update_queued_state(p, neighbor);
}
}
if (should_insert) {
list_push_head(open, neighbor);
} else {
list_push_head(free_nodes, neighbor);
}
}
}
}
// Reaching here means we did not find a path
return kErrorNoPathFound;
}
};
// Distance class, used to build distance metrics that read easily
class Distance {
int distance;
public:
Distance shouldBeTrue(bool var)
{
distance += var ? 0 : 1;
return *this;
}
Distance shouldBeFalse(bool var)
{
distance += var ? 1 : 0;
return *this;
}
Distance shouldBeEqual(int target, int var)
{
distance += abs(var - target);
return *this;
}
operator int()
{
return distance;
}
};
}; // namespace goap
#endif

View File

@@ -0,0 +1,93 @@
#ifndef GOAP_INTERNALS_HPP
#define GOAP_INTERNALS_HPP
#include <limits>
namespace goap {
template <typename State>
class Action;
template <typename State>
struct VisitedState {
int priority;
int cost;
State state;
// Used to reconstruct the path
VisitedState<State>* parent;
Action<State>* action;
// Only used for linked list management
VisitedState<State>* next;
};
template <typename State>
void visited_states_array_to_list(VisitedState<State>* nodes, int len)
{
for (auto i = 0; i < len - 1; i++) {
nodes[i].next = &nodes[i + 1];
}
nodes[len - 1].next = nullptr;
}
/** Returns the node element in the list with the minimal priority */
template <typename State>
VisitedState<State>* priority_list_pop(VisitedState<State>*& list)
{
VisitedState<State>* min_elem = nullptr;
auto min_prio = std::numeric_limits<int>::max();
// Find the element with the lowest priority
for (auto p = list; p != nullptr; p = p->next) {
if (p->priority < min_prio) {
min_elem = p;
min_prio = p->priority;
}
}
// Remove it from the list
if (min_elem == list) {
list = list->next;
} else {
for (auto p = list; p != nullptr; p = p->next) {
if (p->next == min_elem) {
p->next = p->next->next;
min_elem->next = nullptr;
}
}
}
return min_elem;
}
template <typename State>
VisitedState<State>* list_pop_head(VisitedState<State>*& head)
{
auto ret = head;
head = head->next;
ret->next = nullptr;
return ret;
}
template <typename State>
void list_push_head(VisitedState<State>*& head, VisitedState<State>* elem)
{
elem->next = head;
head = elem;
}
template <typename State>
void update_queued_state(VisitedState<State>* previous, const VisitedState<State>* current)
{
if (previous->cost > current->cost) {
previous->priority = current->cost;
previous->priority = current->priority;
previous->parent = current->parent;
previous->action = current->action;
}
}
} // namespace goap
#endif

283
src/world/mark_harbors.cpp Normal file
View File

@@ -0,0 +1,283 @@
#include <iostream>
#include <Ogre.h>
#include <OgreCodec.h>
#include <OgreFileSystem.h>
#include <OgreFileSystemLayer.h>
#include <OgreMaterialManager.h>
#include <OgreShaderGenerator.h>
int main()
{
Ogre::LogManager logMgr;
Ogre::ConfigFile pluginsCfg;
Ogre::FileSystemLayer fsLayer("Ogre3D");
pluginsCfg.load(fsLayer.getConfigFilePath("plugins.cfg"));
logMgr.createLog("messages.log", true, true, true);
Ogre::DefaultHardwareBufferManager
bufferManager; // needed because we don't have a rendersystem
Ogre::Root *ogre = new Ogre::Root("", "", "");
auto pluginDir = Ogre::FileSystemLayer::resolveBundlePath(
pluginsCfg.getSetting("PluginFolder") + "/");
ogre->loadPlugin(pluginDir + "/Codec_STBI");
auto codec = Ogre::Codec::getCodec("png");
Ogre::ResourceGroupManager::getSingleton().addResourceLocation(
"./resources/terrain", "FileSystem",
Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, true,
true);
Ogre::ResourceGroupManager::getSingleton().addResourceLocation(
"../resources/terrain", "FileSystem",
Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, true,
true);
Ogre::ResourceGroupManager::getSingleton().addResourceLocation(
"../../resources/terrain", "FileSystem",
Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, true,
true);
struct harbour {
Ogre::Vector2i position;
std::vector<int> neighbors;
};
std::vector<struct harbour> harbours;
Ogre::Image map_image;
map_image.load("world_map.png",
Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
Ogre::Image output_image(map_image);
int pos_x = map_image.getWidth() / 2;
int pos_y = map_image.getHeight() / 2 - 1;
int seed = 1345;
long long int step_count = 1000000000;
int max_stuck = 10000000;
long long int step = 0;
int directions[18] = {};
int i, j;
for (i = 0; i < map_image.getHeight(); i++)
for (j = 0; j < map_image.getWidth(); j++) {
Ogre::ColourValue c = map_image.getColourAt(j, i, 0);
c.g = 0;
c.b = 0;
output_image.setColourAt(c, j, i, 0);
}
for (i = -1; i < 2; i++)
for (j = -1; j < 2; j++) {
int index = (i + 1) * 3 + (j + 1);
directions[index * 2 + 0] = i;
directions[index * 2 + 1] = j;
std::cout << index << " " << i << " " << j << std::endl;
}
Ogre::ColourValue mc = map_image.getColourAt(pos_x, pos_y, 0);
std::cout << "base h: " << mc.r << std::endl;
mc.g = 1.0;
mc.b = 0.0;
output_image.setColourAt(mc, pos_x, pos_y, 0);
float l = 0.1f;
int dir = seed % 8;
if (dir >= 4)
dir++;
int state = 0;
int stuck = 0;
int backtrack = 0;
int dir_x, dir_y;
Ogre::ColourValue c, rc;
std::list<std::pair<int, int> > queue;
queue.push_back({ pos_x, pos_y });
std::set<uint64_t> harbour_keys;
while (!queue.empty()) {
int i;
std::pair<int, int> item = queue.front();
queue.pop_front();
c = map_image.getColourAt(item.first, item.second, 0);
rc = output_image.getColourAt(item.first, item.second, 0);
// std::cout << "item: " << item.first << " " << item.second
// << " color: " << c.r << " " << rc.b << std::endl;
if (c.r < 0.5f && rc.b < 0.04f) {
// std::cout << "using " << item.first << " "
// << item.second << std::endl;
Ogre::ColourValue mc(c.r, 0, 1, 1);
output_image.setColourAt(mc, item.first, item.second,
0);
for (dir = 0; dir < 9; dir++) {
bool bad_position = false;
dir_x = directions[dir * 2];
dir_y = directions[dir * 2 + 1];
if (dir_x == 0 && dir_y == 0)
continue;
OgreAssert(dir_x > -2 && dir_x < 2,
"bad dir_x");
OgreAssert(dir_y > -2 && dir_y < 2,
"bad dir_x");
if (item.first + dir_x < 0 ||
item.first + dir_x >= map_image.getWidth())
bad_position = true;
if (item.second + dir_y < 0 ||
item.second + dir_y >=
map_image.getHeight())
bad_position = true;
if (bad_position)
continue;
mc = map_image.getColourAt(item.first + dir_x,
item.second + dir_y,
0);
if (mc.r > 0.5f) {
mc.g = 1.0f;
mc.b = 1.0f;
uint64_t harbour_key =
((item.first >> 3) & 0xffff) |
(((item.second >> 3) & 0xffff)
<< 16);
seed = seed * 1103515245 + 1234513;
seed = seed / 65536 % 32768;
int have_harbour = seed % 18 == 0;
if (have_harbour &&
harbour_keys.find(harbour_key) ==
harbour_keys.end()) {
output_image.setColourAt(
mc, item.first + dir_x,
item.second + dir_y, 0);
harbour_keys.insert(
harbour_key);
harbours.push_back(
{ { item.first + dir_x,
item.second +
dir_y },
{} });
}
} else
queue.push_back(
{ item.first + dir_x,
item.second + dir_y });
}
}
}
for (i = 0; i < map_image.getHeight(); i++)
for (j = 0; j < map_image.getWidth(); j++) {
Ogre::ColourValue c = map_image.getColourAt(j, i, 0);
Ogre::ColourValue mc =
output_image.getColourAt(j, i, 0);
mc.r = c.r;
mc.b = 0;
output_image.setColourAt(mc, j, i, 0);
}
std::cout << "harbours count: " << harbours.size() << std::endl;
for (i = 0; i < harbours.size(); i++) {
int x = harbours[i].position[0];
int y = harbours[i].position[1];
queue.clear();
queue.push_back({ x, y });
std::cout << " looking for neighbors... " << i << std::endl;
while (!queue.empty()) {
std::pair<int, int> item = queue.front();
queue.pop_front();
c = map_image.getColourAt(item.first, item.second, 0);
rc = output_image.getColourAt(item.first, item.second,
0);
// std::cout << "item: " << c.r << " " << c.b << std::endl;
if (c.r > 0.5f && rc.b < 0.04f) {
Ogre::ColourValue mc(rc.r, rc.g, 1, 1);
bool neighbor_found = false;
if (rc.g >= 0.9f) {
if (item.first != x ||
item.second != y) {
std::cout << "neighbor found\n";
std::cout << "\titem: " << rc.r
<< " " << rc.b << " "
<< item.first << " "
<< item.second
<< std::endl;
// output_image.setColourAt(
// mc, item.first,
// item.second, 0);
neighbor_found = true;
}
if (neighbor_found) {
int neighbor_id = -1;
for (j = 0; j < harbours.size();
j++)
if (harbours[j].position
[0] ==
item.first &&
harbours[j].position
[1] ==
item.second) {
neighbor_id = j;
break;
}
OgreAssert(
neighbor_id >= 0,
"neighbor not found");
std::cout << "neighbor_id: "
<< neighbor_id
<< std::endl;
if (std::find(harbours[i]
.neighbors
.begin(),
harbours[i]
.neighbors
.end(),
neighbor_id) ==
harbours[i].neighbors.end())
harbours[i].neighbors.push_back(
neighbor_id);
}
}
output_image.setColourAt(mc, item.first,
item.second, 0);
for (dir = 0; dir < 9; dir++) {
bool bad_position = false;
dir_x = directions[dir * 2];
dir_y = directions[dir * 2 + 1];
if (dir_x == 0 && dir_y == 0)
continue;
OgreAssert(dir_x > -2 && dir_x < 2,
"bad dir_x");
OgreAssert(dir_y > -2 && dir_y < 2,
"bad dir_x");
if (item.first + dir_x < 0 ||
item.first + dir_x >=
map_image.getWidth())
bad_position = true;
if (item.second + dir_y < 0 ||
item.second + dir_y >=
map_image.getHeight())
bad_position = true;
if (bad_position)
continue;
mc = output_image.getColourAt(
item.first + dir_x,
item.second + dir_y, 0);
if (mc.r > 0.5f && mc.b < 0.04f) {
queue.push_back(
{ item.first + dir_x,
item.second +
dir_y });
}
}
}
// std::cout << "size: " << queue.size() << std::endl;
}
std::cout << "neighbors: " << i << " "
<< harbours[i].neighbors.size() << std::endl;
int k;
for (k = 0; k < map_image.getHeight(); k++)
for (j = 0; j < map_image.getWidth(); j++) {
Ogre::ColourValue c =
map_image.getColourAt(j, k, 0);
Ogre::ColourValue mc =
output_image.getColourAt(j, k, 0);
mc.r = c.r;
mc.b = 0;
output_image.setColourAt(mc, j, k, 0);
}
}
#if 0
for (i = 0; i < map_image.getHeight(); i++)
for (j = 0; j < map_image.getWidth(); j++) {
Ogre::ColourValue c = map_image.getColourAt(j, i, 0);
Ogre::ColourValue mc =
output_image.getColourAt(j, i, 0);
mc.r = c.r;
mc.b = c.b;
output_image.setColourAt(mc, j, i, 0);
}
#endif
output_image.save("world_map_marked.png");
return 0;
}

218
src/world/test.cpp Normal file
View File

@@ -0,0 +1,218 @@
#include <iostream>
#include <Ogre.h>
#include <OgreFileSystemLayer.h>
#include <goap/goap.hpp>
#include "GameData.h"
#include "Components.h"
#include "LuaData.h"
#include "world-build.h"
struct Blackboard {
enum {
HIGH_LUST = 1,
LOW_HEALTH = (1 << 1),
HAS_TARGET = (1 << 2),
};
flecs::entity me;
int health;
int stamina;
int lust;
uint32_t flags;
bool operator==(const Blackboard &other)
{
return flags != other.flags;
}
};
struct CopulationGoal : goap::Goal<Blackboard> {
int distance_to(const Blackboard &state) const override
{
if (state.flags & Blackboard::HIGH_LUST)
return 1;
else
return 0;
}
};
class BaseAction : public goap::Action<Blackboard> {
std::string m_name;
public:
BaseAction(const std::string &name)
: goap::Action<Blackboard>()
, m_name(name)
{
}
BaseAction(const char *name)
: goap::Action<Blackboard>()
, m_name(name)
{
}
const std::string &get_name() const
{
return m_name;
}
virtual bool can_run(const Blackboard &state) = 0;
virtual void plan_effects(Blackboard &state) = 0;
virtual bool execute(Blackboard &state) = 0;
};
class find_target : public BaseAction {
public:
find_target(const std::string &name)
: BaseAction(name)
{
}
bool can_run(const Blackboard &state) override
{
bool can = !(state.flags & Blackboard::HAS_TARGET);
std::cout << "find can run " << can << std::endl;
return true;
}
void plan_effects(Blackboard &state) override
{
std::cout << "fuck: " << &state << " " << state.flags
<< std::endl;
// std::cout << "find plan_effects\n";
state.flags |= Blackboard::HAS_TARGET;
}
bool execute(Blackboard &state)
{
std::cout << "find_target" << std::endl;
return false;
}
};
class fuck_target : public BaseAction {
public:
fuck_target(const std::string &name)
: BaseAction(name)
{
}
bool can_run(const Blackboard &state) override
{
std::cout << "fuck: " << &state << " " << state.flags
<< std::endl;
bool can = (state.flags & Blackboard::HAS_TARGET);
std::cout << "fuck can run " << can << std::endl;
return can;
}
void plan_effects(Blackboard &state) override
{
// std::cout << "fuck plan_effects\n";
state.lust = 0;
state.flags &= ~Blackboard::HIGH_LUST;
}
bool execute(Blackboard &state)
{
std::cout << "fuck_target" << std::endl;
return false;
}
};
int main()
{
// new Ogre::LogManager;
// new Ogre::ArchiveManager;
// Ogre::FileSystemLayer fsLayer("Ogre3D");
// new Ogre::ResourceGroupManager;
Ogre::Root *ogre = new Ogre::Root("", "");
Ogre::ResourceGroupManager::getSingleton().createResourceGroup(
"Characters", true);
Ogre::ResourceGroupManager::getSingleton().addResourceLocation(
"./characters", "FileSystem", "Characters", true, true);
Ogre::ResourceGroupManager::getSingleton().addResourceLocation(
"../characters", "FileSystem", "Characters", true, true);
Ogre::ResourceGroupManager::getSingleton().addResourceLocation(
"../../characters", "FileSystem", "Characters", true, true);
Ogre::ResourceGroupManager::getSingleton().createResourceGroup(
"LuaScripts", true);
Ogre::ResourceGroupManager::getSingleton().addResourceLocation(
"./lua-scripts", "FileSystem", "LuaScripts", true, true);
Ogre::ResourceGroupManager::getSingleton().addResourceLocation(
"../lua-scripts", "FileSystem", "LuaScripts", true, true);
Ogre::ResourceGroupManager::getSingleton().addResourceLocation(
"../../lua-scripts", "FileSystem", "LuaScripts", true, true);
// Ogre::ResourceGroupManager &mgr =
// Ogre::ResourceGroupManager::getSingleton();
flecs::world ecs = ECS::get();
ECS::setup_minimal();
ecs.import <ECS::LuaModule>();
ecs.import <ECS::GameWorldModule>();
struct Male {};
struct Female {};
struct Position {
Ogre::Vector2 position;
};
struct Target {
flecs::entity target;
};
ecs.component<Blackboard>();
ecs.component<Male>();
ecs.component<Female>();
ecs.component<Position>();
ecs.component<Target>();
ecs.set<ECS::EngineData>({ nullptr, nullptr, 0.0f, 5.0f, 0, 0, false });
flecs::entity e1 = ecs.entity("e1");
e1.set<Blackboard>({ e1, 100, 100, 100, 0 });
e1.add<Male>();
e1.set<Position>({ { -100, -100 } });
flecs::entity e2 = ecs.entity("e2");
e2.set<Blackboard>({ e2, 100, 100, 100, 0 });
e2.add<Female>();
e2.set<Position>({ { 100, 100 } });
std::vector<goap::Action<Blackboard> *> actions = {
new find_target("find_target"), new fuck_target("fuck_target")
};
goap::Planner<Blackboard> planner;
goap::Action<Blackboard> *path[100];
CopulationGoal goal;
bool stop_this = false;
ecs.system<Blackboard>("UpdateCharacters")
.kind(flecs::OnUpdate)
.each([&](flecs::entity e, Blackboard &bb) {
bb.flags = 0;
bb.lust += 1;
if (bb.health < 5)
bb.flags |= Blackboard::LOW_HEALTH;
if (bb.lust > 1)
bb.flags |= Blackboard::HIGH_LUST;
});
ecs.system<Blackboard>("UpdateCharacters2")
.kind(flecs::OnUpdate)
.each([&](flecs::entity e, Blackboard &bb) {
int i;
int len = planner.plan(
bb, goal,
static_cast<goap::Action<Blackboard> **>(
actions.data()),
actions.size(), path, 100);
std::cout << "plan length: " << len << std::endl;
if (len > 0)
stop_this = true;
if (len < 0)
stop_this = true;
for (i = 0; i < len; i++)
std::cout << i << " "
<< static_cast<BaseAction *>(path[i])
->get_name()
<< std::endl;
for (i = 0; i < len; i++) {
if (!path[i]->execute(bb))
break;
}
});
while (true) {
#if 0
if (ecs.get<ECS::LuaBase>().setup_called ||
ecs.get<ECS::LuaBase>().startup_called)
break;
#endif
ecs.progress(0.16f);
if (stop_this)
break;
}
return 0;
}

495
src/world/test2.cpp Normal file
View File

@@ -0,0 +1,495 @@
#include <iostream>
#include <Ogre.h>
#include <OgreFileSystemLayer.h>
#include "GameData.h"
#include "Components.h"
#include "CharacterModule.h"
#include "LuaData.h"
#include "goap.h"
#include "world-build.h"
struct CopulationGoal
: public BasePlanner<ECS::Blackboard, BaseAction>::BaseGoal {
CopulationGoal()
: BasePlanner<ECS::Blackboard, BaseAction>::BaseGoal(
"copulation")
{
}
int distance_to(const ECS::Blackboard &state) const override
{
if (state.get_flag(ECS::Blackboard::LOW_LUST))
return 0;
return 1;
}
bool is_reached(const ECS::Blackboard &state) const override
{
return state.get_flag(ECS::Blackboard::LOW_LUST);
}
};
struct SustainabilityGoal
: public BasePlanner<ECS::Blackboard, BaseAction>::BaseGoal {
SustainabilityGoal()
: BasePlanner<ECS::Blackboard, BaseAction>::BaseGoal(
"sustainability")
{
}
int distance_to(const ECS::Blackboard &state) const override
{
bool healthy = !state.get_flag(ECS::Blackboard::LOW_HEALTH);
bool able = !state.get_flag(ECS::Blackboard::LOW_STAMINA);
bool lusty = !state.get_flag(ECS::Blackboard::LOW_LUST);
if (healthy && able && lusty)
return 0;
else if (healthy && able)
return 1;
else if (able && lusty)
return 1;
else if (healthy && lusty)
return 1;
else if (healthy || able || lusty)
return 2;
else
return 3;
}
bool is_reached(const ECS::Blackboard &state) const override
{
bool healthy = !state.get_flag(ECS::Blackboard::LOW_HEALTH);
bool able = !state.get_flag(ECS::Blackboard::LOW_STAMINA);
bool lusty = !state.get_flag(ECS::Blackboard::LOW_LUST);
return healthy && able && lusty;
}
};
struct Position {
Ogre::Vector2 position;
};
struct Velocity {
Ogre::Vector2 velocity;
};
struct Locked {
flecs::entity e;
};
struct rest_lust_runner : public BaseActionExec {
rest_lust_runner()
: BaseActionExec(ECS::Blackboard::HIGH_LUST |
ECS::Blackboard::FULL_LUST,
ECS::Blackboard::LOW_LUST)
{
}
void _enter(ECS::Blackboard &state) override
{
}
void _exit(ECS::Blackboard &state) override
{
}
int _execute(ECS::Blackboard &state)
{
state.lust += 1;
if (state.lust > 100)
state.lust = 100;
std::cout << ">>> " << state.me.name() << " restored some lust"
<< std::endl;
bool ok = state.get_flag(ECS::Blackboard::HIGH_LUST) ||
state.get_flag(ECS::Blackboard::FULL_LUST);
if (ok)
return BaseAction::OK;
else
return BaseAction::BUSY;
}
};
class rest_lust : public BaseAction {
public:
rest_lust_runner runner;
rest_lust(const std::string &name)
: runner(rest_lust_runner())
, BaseAction(name, &runner)
{
}
#if 0
bool can_run(const ECS::Blackboard &state, bool debug = false) override
{
return true;
}
void _plan_effects(ECS::Blackboard &state) override
{
state.clear_flag(ECS::Blackboard::LOW_LUST);
state.set_flag(ECS::Blackboard::HIGH_LUST);
state.set_flag(ECS::Blackboard::FULL_LUST);
}
#endif
};
struct rest_runner : public BaseActionExec {
rest_runner()
: BaseActionExec(0, ECS::Blackboard::LOW_HEALTH |
ECS::Blackboard::LOW_STAMINA)
{
}
int _execute(ECS::Blackboard &state)
{
state.health += 10;
state.stamina += 10;
if (state.health > 100)
state.health = 100;
if (state.stamina > 100)
state.stamina = 100;
std::cout << ">>> " << state.me.name() << " is resting"
<< std::endl;
bool ok = !state.get_flag(ECS::Blackboard::LOW_HEALTH) &&
!state.get_flag(ECS::Blackboard::LOW_STAMINA);
if (ok)
return BaseAction::OK;
else
return BaseAction::BUSY;
}
void _enter(ECS::Blackboard &state) override
{
}
void _exit(ECS::Blackboard &state) override
{
}
bool _can_run(const ECS::Blackboard &state, bool debug = false) override
{
bool ret = !state.get_flag(ECS::Blackboard::FULL_HEALTH) ||
!state.get_flag(ECS::Blackboard::FULL_STAMINA);
return ret;
}
};
class rest : public BaseAction {
rest_runner runner;
public:
rest(const std::string &name)
: runner(rest_runner())
, BaseAction(name, &runner)
{
}
#if 0
bool can_run(const ECS::Blackboard &state, bool debug = false) override
{
bool ret = !state.get_flag(ECS::Blackboard::FULL_HEALTH) ||
!state.get_flag(ECS::Blackboard::FULL_STAMINA);
return ret;
}
void _plan_effects(ECS::Blackboard &state) override
{
state.clear_flag(ECS::Blackboard::LOW_HEALTH);
state.clear_flag(ECS::Blackboard::LOW_STAMINA);
}
int get_cost(const ECS::Blackboard &state) const override
{
return 1;
}
#endif
};
struct fuck_target_runner : public BaseActionExec {
flecs::entity e;
std::string m_name;
fuck_target_runner(const std::string &name, flecs::entity e)
: BaseActionExec(ECS::Blackboard::LOW_LUST |
ECS::Blackboard::LOW_STAMINA,
ECS::Blackboard::HIGH_LUST |
ECS::Blackboard::FULL_LUST)
, e(e)
, m_name(name)
{
}
const std::string &get_name()
{
return m_name;
}
bool has_target(const ECS::Blackboard &state)
{
return state.get_flag(ECS::Blackboard::HAS_TARGET) &&
e.has<Locked>() && e.get<Locked>().e == state.me;
}
bool lock_target(ECS::Blackboard &state)
{
if (e.has<Locked>() && e.get<Locked>().e != state.me)
return false;
if (e.has<Locked>() && e.get<Locked>().e == state.me)
return true;
e.set<Locked>({ state.me });
state.set_flag(ECS::Blackboard::HAS_TARGET);
return true;
}
bool reach_target(ECS::Blackboard &state, float distance)
{
float d = state.me.get<Position>().position.squaredDistance(
e.get<Position>().position);
if (d < distance) {
state.me.remove<Velocity>();
state.set_flag(ECS::Blackboard::REACHED_TARGET);
return true;
}
Ogre::Vector2 v = e.get<Position>().position -
state.me.get<Position>().position;
state.me.ensure<Velocity>().velocity =
v.normalisedCopy() * 0.5f;
state.me.modified<Velocity>();
return false;
}
bool unlock_target(ECS::Blackboard &state)
{
if (e.has<Locked>() && e.get<Locked>().e != state.me)
return false;
if (!e.has<Locked>())
return false;
e.remove<Locked>();
return true;
}
int _execute(ECS::Blackboard &state)
{
if (!has_target(state)) {
if (!lock_target(state))
return BaseAction::ABORT;
std::cout << ">>> " << e.name() << " is locked by "
<< state.me.name() << std::endl;
return BaseAction::BUSY;
}
if (state.stamina <= 0)
return BaseAction::ABORT;
if (!reach_target(state, 0.04f)) {
if (state.stamina > 0)
state.stamina -= 1;
return BaseAction::BUSY;
}
ECS::get_mut<ECS::GameWorld>().command(
"fuck", std::vector<ECS::GameWorld::Parameter *>());
if (state.lust >= 5)
state.lust -= 5;
if (state.stamina >= 5)
state.stamina -= 5;
if (e.has<ECS::Blackboard>()) {
if (e.get_mut<ECS::Blackboard>().lust > 0) {
e.get_mut<ECS::Blackboard>().lust -= 5;
if (e.get_mut<ECS::Blackboard>().stamina > 0)
e.get_mut<ECS::Blackboard>().stamina -=
5;
e.modified<ECS::Blackboard>();
}
}
std::cout << ">>> " << e.name() << " was fucked by "
<< state.me.name() << std::endl;
std::cout << ">>> " << state.me.name() << " lust is "
<< state.lust << std::endl;
if (state.lust <= 0) {
unlock_target(state);
std::cout << ">>> " << get_name() << " completed by "
<< state.me.name() << std::endl;
return BaseAction::OK;
}
return BaseAction::BUSY;
}
void _enter(ECS::Blackboard &state) override
{
std::cout << ">>> " << get_name() << " entered by "
<< state.me.name() << std::endl;
}
void _exit(ECS::Blackboard &state) override
{
unlock_target(state);
std::cout << ">>> " << get_name() << " exited by "
<< state.me.name() << std::endl;
}
int _get_cost(const ECS::Blackboard &state) const override
{
float d = state.me.get<Position>().position.squaredDistance(
e.get<Position>().position);
int cost = d;
cost += state.lust;
if (e.has<Locked>() && e.get<Locked>().e != state.me)
cost += 1000000;
return cost + 1;
}
bool _can_run(const ECS::Blackboard &state, bool debug = false) override
{
bool mighty = !state.get_flag(ECS::Blackboard::LOW_STAMINA);
bool healthy = !state.get_flag(ECS::Blackboard::LOW_HEALTH);
bool has_target = state.get_flag(ECS::Blackboard::HAS_TARGET);
bool lusty = !state.get_flag(ECS::Blackboard::LOW_LUST);
if (is_active(state)) {
if (e.has<Locked>() && e.get<Locked>().e == state.me)
return mighty && healthy && has_target && lusty;
return false;
}
bool reached_target =
state.get_flag(ECS::Blackboard::REACHED_TARGET);
bool is_male = state.me.has<ECS::Male>();
bool ret = is_male && mighty && healthy && lusty;
if (debug) {
if (!ret && is_male) {
std::cout << state.me.name() << ": ";
if (!mighty)
std::cout << " low stamina "
<< state.stamina;
if (!healthy)
std::cout << " low health ";
if (!has_target)
std::cout << " no target ";
if (!reached_target)
std::cout << " target is not reached ";
if (!is_male)
std::cout << " not male ";
}
if (!ret && is_male)
std::cout << std::endl;
}
if (!ret && is_male && debug) {
if (!mighty)
std::cout << " low stamina " << state.stamina;
if (!healthy)
std::cout << " low health ";
if (!lusty)
std::cout << " low lust ";
std::cout << " " << get_name() << ":" << state.me.name()
<< ": " << ret << std::endl;
}
return ret;
}
};
flecs::entity create_npc(flecs::world ecs, const char *name, bool male,
const Ogre::Vector2 &position)
{
flecs::entity e = ecs.entity(name);
e.set<ECS::Blackboard>({ e, 100, 100, 100, 0 });
if (male)
e.add<ECS::Male>();
else
e.add<ECS::Female>();
e.set<Position>({ position });
return e;
}
namespace ECS
{
struct CharacterAIExtraModule {
CharacterAIExtraModule(flecs::world &ecs)
{
ecs.module<CharacterAIExtraModule>();
ecs.component<Position>();
ecs.component<Locked>();
ecs.system<Blackboard, Position, const Velocity>(
"UpdateCharacterPosition")
.kind(flecs::OnUpdate)
.each([&](flecs::entity e, Blackboard &bb, Position &p,
const Velocity &v) {
p.position +=
v.velocity * e.world().delta_time();
std::cout << e.name()
<< " moved: " << p.position
<< std::endl;
});
ecs.system<Blackboard>("UpdateBaseCharacters")
.kind(flecs::OnUpdate)
.without<ActionTarget>()
.each([&](flecs::entity e, Blackboard &bb) {
ActionTarget &ac = e.ensure<ActionTarget>();
if (e.has<Female>()) {
ac.actions.push_back(
new DeclareAction<
fuck_target_runner>(
"fuck_target",
"fuck_target", e));
}
std::cout << "created targets for: " << e.name()
<< std::endl;
});
ecs.system<Blackboard>("DisplayCharacterData")
.kind(flecs::OnUpdate)
.rate(10)
.each([&](flecs::entity e, Blackboard &bb) {
std::cout << e.name()
<< " health: " << bb.health
<< " stamina: " << bb.stamina
<< std::endl;
});
}
};
}
int main()
{
// new Ogre::LogManager;
// new Ogre::ArchiveManager;
// Ogre::FileSystemLayer fsLayer("Ogre3D");
// new Ogre::ResourceGroupManager;
Ogre::Root *ogre = new Ogre::Root("", "");
Ogre::ResourceGroupManager::getSingleton().createResourceGroup(
"Characters", true);
Ogre::ResourceGroupManager::getSingleton().addResourceLocation(
"./characters", "FileSystem", "Characters", true, true);
Ogre::ResourceGroupManager::getSingleton().addResourceLocation(
"../characters", "FileSystem", "Characters", true, true);
Ogre::ResourceGroupManager::getSingleton().addResourceLocation(
"../../characters", "FileSystem", "Characters", true, true);
Ogre::ResourceGroupManager::getSingleton().createResourceGroup(
"LuaScripts", true);
Ogre::ResourceGroupManager::getSingleton().addResourceLocation(
"./lua-scripts", "FileSystem", "LuaScripts", true, true);
Ogre::ResourceGroupManager::getSingleton().addResourceLocation(
"../lua-scripts", "FileSystem", "LuaScripts", true, true);
Ogre::ResourceGroupManager::getSingleton().addResourceLocation(
"../../lua-scripts", "FileSystem", "LuaScripts", true, true);
// Ogre::ResourceGroupManager &mgr =
// Ogre::ResourceGroupManager::getSingleton();
flecs::world ecs = ECS::get();
ECS::setup_minimal();
ecs.import <ECS::LuaModule>();
ecs.import <ECS::GameWorldModule>();
ecs.import <ECS::CharacterModule>();
ecs.import <ECS::CharacterAIModule>();
ecs.import <ECS::CharacterAIExtraModule>();
ecs.import <ECS::GameWorldModule>();
ecs.set<ECS::EngineData>({ nullptr, nullptr, 0.0f, 5.0f, 0, 0, false });
CopulationGoal goal_copulation;
SustainabilityGoal goal_sustainability;
ECS::Planner &planner = ecs.ensure<ECS::Planner>();
struct FuckCommand : public ECS::GameWorld::Command {
int operator()(const std::vector<ECS::GameWorld::Parameter *>
&args) override
{
OgreAssert(false, "fuck called");
return 0;
}
};
FuckCommand fc;
ecs.get_mut<ECS::GameWorld>().add_command<FuckCommand>("fuck");
ecs.modified<ECS::GameWorld>();
#if 0
ecs.get_mut<ECS::GameWorld>().command(
"fuck", std::vector<ECS::GameWorld::Parameter *>());
#endif
planner.path.resize(100);
planner.goals.push_back(&goal_copulation);
planner.goals.push_back(&goal_sustainability);
planner.actions.push_back(new DeclareAction<rest_runner>("rest"));
planner.actions.push_back(
new DeclareAction<rest_lust_runner>("rest_lust"));
std::vector<flecs::entity> entities = {
create_npc(ecs, "m1", true, { -100, -100 }),
create_npc(ecs, "m2", true, { 0, -100 }),
create_npc(ecs, "m3", true, { 100, -100 }),
create_npc(ecs, "f1", false, { -100, 100 }),
create_npc(ecs, "f2", false, { 0, 100 }),
create_npc(ecs, "f3", false, { -100, 100 }),
create_npc(ecs, "f4", false, { 0, 50 })
};
bool stop_this = false;
// AI stuff
while (true) {
#if 0
if (ecs.get<ECS::LuaBase>().setup_called ||
ecs.get<ECS::LuaBase>().startup_called)
break;
#endif
ecs.progress(0.16f);
if (stop_this)
break;
}
return 0;
}

23
src/world/world-build.cpp Normal file
View File

@@ -0,0 +1,23 @@
#include <vector>
#include <goap/goap.hpp>
#include "world-build.h"
namespace ECS
{
GameWorldModule::GameWorldModule(flecs::world &ecs)
{
ecs.module<GameWorldModule>();
ecs.component<GameWorld>().add(flecs::Singleton);
ecs.set<GameWorld>({ nullptr });
ecs.system<GameWorld>("RunUpdateQueue")
.kind(flecs::OnUpdate)
.rate(5)
.each([](GameWorld &gw) {
while (!gw.queue.empty()) {
GameWorld::QueueTask task = gw.queue.front();
gw.queue.pop_front();
(*task.command)(task.args);
}
});
}
}

115
src/world/world-build.h Normal file
View File

@@ -0,0 +1,115 @@
#ifndef WORLD_BUILD_H
#define WORLD_BUILD_H
#include <flecs.h>
#include <string>
#include <list>
#include <map>
namespace ECS
{
struct GameWorldData {
std::vector<flecs::entity> npcs;
std::vector<flecs::entity> vehicles;
std::vector<flecs::entity> settlements;
};
struct GameWorld {
struct GameWorldData *data;
struct Parameter {
int type;
};
struct IntegerParameter : public Parameter {
int value;
public:
IntegerParameter(int value)
: value(value)
{
}
int get()
{
return value;
}
};
template <class T> struct ValueParameter : public Parameter {
T value;
public:
ValueParameter(const T &value)
: value(value)
{
}
T get()
{
return value;
}
const T &get() const
{
return value;
}
};
struct Command {
virtual int
operator()(const std::vector<Parameter *> &args) = 0;
};
struct QueueTask {
Command *command;
std::vector<Parameter *> args;
};
std::map<std::string, Command *> commands;
std::list<QueueTask> queue;
int command(const std::string &cmd,
const std::vector<Parameter *> &args)
{
if (commands.find(cmd) != commands.end())
return (*commands.at(cmd))(args);
return -1;
}
void queue_command(const std::string &cmd,
const std::vector<Parameter *> &args)
{
if (commands.find(cmd) != commands.end()) {
QueueTask task{ commands.at(cmd), args };
queue.push_back(task);
}
}
void queue_command(Command *cmd, const std::vector<Parameter *> &args)
{
QueueTask task{ cmd, args };
queue.push_back(task);
}
template <class CommandType> void add_command(const std::string &name)
{
CommandType *cmd = new CommandType();
commands[name] = cmd;
}
ValueParameter<int> *allocate_int(int value)
{
ValueParameter<int> *ret = new ValueParameter<int>(value);
ret->type = 0;
return ret;
}
ValueParameter<std::string> *allocate_string(const std::string &value)
{
ValueParameter<std::string> *ret =
new ValueParameter<std::string>(value);
ret->type = 1;
return ret;
}
ValueParameter<flecs::entity> *allocate_entity(flecs::entity value)
{
ValueParameter<flecs::entity> *ret =
new ValueParameter<flecs::entity>(value);
ret->type = 2;
return ret;
}
ValueParameter<bool> *allocate_boolean(bool value)
{
ValueParameter<bool> *ret = new ValueParameter<bool>(value);
ret->type = 3;
return ret;
}
};
struct GameWorldModule {
GameWorldModule(flecs::world &ecs);
};
}
#endif