Updated lots of things
This commit is contained in:
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