Updated lots of things
This commit is contained in:
@@ -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>();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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})
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
@@ -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>();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
11
src/gamedata/EventSystem.h
Normal file
11
src/gamedata/EventSystem.h
Normal 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
|
||||
@@ -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)
|
||||
|
||||
@@ -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>();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
10
src/gamedata/SmartObject.cpp
Normal file
10
src/gamedata/SmartObject.cpp
Normal 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>();
|
||||
}
|
||||
}
|
||||
19
src/gamedata/SmartObject.h
Normal file
19
src/gamedata/SmartObject.h
Normal 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
38
src/gamedata/goap.cpp
Normal 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
349
src/gamedata/goap.h
Normal 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
|
||||
@@ -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
10
src/tests/CMakeLists.txt
Normal 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
54
src/tests/check_uv.cpp
Normal 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;
|
||||
}
|
||||
83
src/tests/ogre_check_uv.cpp
Normal file
83
src/tests/ogre_check_uv.cpp
Normal 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
18
src/world/CMakeLists.txt
Normal 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
2
src/world/action.cpp
Normal file
@@ -0,0 +1,2 @@
|
||||
#include <goap/goap.hpp>
|
||||
#include "action.h"
|
||||
2
src/world/action.h
Normal file
2
src/world/action.h
Normal file
@@ -0,0 +1,2 @@
|
||||
class CharacterAction {};
|
||||
class WorldAction {};
|
||||
320
src/world/aitoolkit/behtree.hpp
Normal file
320
src/world/aitoolkit/behtree.hpp
Normal 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
318
src/world/aitoolkit/fsm.hpp
Normal 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;
|
||||
};
|
||||
}
|
||||
383
src/world/aitoolkit/goap.hpp
Normal file
383
src/world/aitoolkit/goap.hpp
Normal 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>();
|
||||
}
|
||||
}
|
||||
203
src/world/aitoolkit/utility.hpp
Normal file
203
src/world/aitoolkit/utility.hpp
Normal 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
21
src/world/goap/LICENSE
Normal 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
193
src/world/goap/goap.hpp
Normal 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
|
||||
93
src/world/goap/goap_internals.hpp
Normal file
93
src/world/goap/goap_internals.hpp
Normal 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
283
src/world/mark_harbors.cpp
Normal 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
218
src/world/test.cpp
Normal 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
495
src/world/test2.cpp
Normal 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
23
src/world/world-build.cpp
Normal 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
115
src/world/world-build.h
Normal 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
|
||||
Reference in New Issue
Block a user