Updated lots of things
This commit is contained in:
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
|
||||
Reference in New Issue
Block a user