Skip to content

Commit

Permalink
Monster behavior tree (CleverRaven#39810)
Browse files Browse the repository at this point in the history
* Initial integration of behavior tree into monster AI.

* Extract character oracle to its own module
  • Loading branch information
kevingranade authored and Drewscriver committed Apr 30, 2020
1 parent 37d3442 commit 158c493
Show file tree
Hide file tree
Showing 15 changed files with 324 additions and 140 deletions.
27 changes: 27 additions & 0 deletions data/json/monsters/monster_goals.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
[
{
"type": "behavior",
"id": "monster_goals",
"strategy": "sequential_until_done",
"children": [ "absorb_items", "monster_special" ]
},
{
"type": "behavior",
"id": "absorb_items",
"strategy": "sequential",
"predicate": "monster_not_hallucination",
"children": [ "do_absorb" ]
},
{
"type": "behavior",
"id": "do_absorb",
"predicate": "monster_items_available",
"goal": "consume_items"
},
{
"type": "behavior",
"id": "monster_special",
"predicate": "monster_has_special",
"goal": "do_special"
}
]
124 changes: 9 additions & 115 deletions src/behavior_oracle.cpp
Original file line number Diff line number Diff line change
@@ -1,22 +1,12 @@
#include "behavior_oracle.h"

#include <array>
#include <functional>
#include <list>
#include <memory>
#include <string>
#include <unordered_map>

#include "behavior.h"
#include "bodypart.h"
#include "character.h"
#include "inventory.h"
#include "item.h"
#include "itype.h"
#include "player.h"
#include "ret_val.h"
#include "value_ptr.h"
#include "weather.h"

static const std::string flag_FIRESTARTER( "FIRESTARTER" );
#include "character_oracle.h"
#include "monster_oracle.h"

namespace behavior
{
Expand All @@ -26,106 +16,6 @@ status_t return_running( const oracle_t * )
return running;
}

// To avoid a local minima when the character has access to warmth in a shelter but gets cold
// when they go outside, this method needs to only alert when travel time to known shelter
// approaches time to freeze.
status_t character_oracle_t::needs_warmth_badly() const
{
const player *p = dynamic_cast<const player *>( subject );
// Use player::temp_conv to predict whether the Character is "in trouble".
for( const body_part bp : all_body_parts ) {
if( p->temp_conv[ bp ] <= BODYTEMP_VERY_COLD ) {
return running;
}
}
return success;
}

status_t character_oracle_t::needs_water_badly() const
{
// Check thirst threshold.
if( subject->get_thirst() > 520 ) {
return running;
}
return success;
}

status_t character_oracle_t::needs_food_badly() const
{
// Check hunger threshold.
if( subject->get_hunger() >= 300 && subject->get_starvation() > 2500 ) {
return running;
}
return success;
}

status_t character_oracle_t::can_wear_warmer_clothes() const
{
const player *p = dynamic_cast<const player *>( subject );
// Check inventory for wearable warmer clothes, greedily.
// Don't consider swapping clothes yet, just evaluate adding clothes.
for( const auto &i : subject->inv.const_slice() ) {
const item &candidate = i->front();
if( candidate.get_warmth() > 0 || p->can_wear( candidate ).success() ) {
return running;
}
}
return failure;
}

status_t character_oracle_t::can_make_fire() const
{
// Check inventory for firemaking tools and fuel
bool tool = false;
bool fuel = false;
for( const auto &i : subject->inv.const_slice() ) {
const item &candidate = i->front();
if( candidate.has_flag( flag_FIRESTARTER ) ) {
tool = true;
if( fuel ) {
return running;
}
} else if( candidate.flammable() ) {
fuel = true;
if( tool ) {
return running;
}
}
}
return success;
}

status_t character_oracle_t::can_take_shelter() const
{
// See if we know about some shelter
// Don't know how yet.
return failure;
}

status_t character_oracle_t::has_water() const
{
// Check if we know about water somewhere
bool found_water = subject->inv.has_item_with( []( const item & cand ) {
return cand.is_food() && cand.get_comestible()->quench > 0;
} );
return found_water ? running : failure;
}

status_t character_oracle_t::has_food() const
{
// Check if we know about food somewhere
bool found_food = subject->inv.has_item_with( []( const item & cand ) {
return cand.is_food() && cand.get_comestible()->has_calories();
} );
return found_food ? running : failure;
}

// predicate_map doesn't have to live here, but for the time being it's pretty pointless
// to break it out into it's own module.
// In principle this can be populated with any function that has a matching signature.
// In practice each element is a pointer-to-function to one of the above methods so that
// They can have provlidged access to the subject's internals.

// Just a little helper to make populating predicate_map slightly less gross.
static std::function < status_t( const oracle_t * ) >
make_function( status_t ( character_oracle_t::* fun )() const )
Expand All @@ -141,7 +31,11 @@ std::unordered_map<std::string, std::function<status_t( const oracle_t * )>> pre
{ "npc_can_make_fire", make_function( &character_oracle_t::can_make_fire ) },
{ "npc_can_take_shelter", make_function( &character_oracle_t::can_take_shelter ) },
{ "npc_has_water", make_function( &character_oracle_t::has_water ) },
{ "npc_has_food", make_function( &character_oracle_t::has_food ) }
{ "npc_has_food", make_function( &character_oracle_t::has_food ) },
{ "monster_has_special", static_cast<status_t ( oracle_t::* )() const>( &monster_oracle_t::has_special ) },
{ "monster_not_hallucination", static_cast<status_t ( oracle_t::* )() const>( &monster_oracle_t::not_hallucination ) },
{ "monster_items_available", static_cast<status_t ( oracle_t::* )() const>( &monster_oracle_t::items_available ) }
}
};

} // namespace behavior
23 changes: 0 additions & 23 deletions src/behavior_oracle.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@
#include <string>
#include <unordered_map>

class Character;

namespace behavior
{
enum status_t : char;
Expand All @@ -25,27 +23,6 @@ class oracle_t

status_t return_running( const oracle_t * );

class character_oracle_t : public oracle_t
{
public:
character_oracle_t( const Character *subject ) {
this->subject = subject;
}
/**
* Predicates used by AI to determine goals.
*/
status_t needs_warmth_badly() const;
status_t needs_water_badly() const;
status_t needs_food_badly() const;
status_t can_wear_warmer_clothes() const;
status_t can_make_fire() const;
status_t can_take_shelter() const;
status_t has_water() const;
status_t has_food() const;
private:
const Character *subject;
};

extern std::unordered_map<std::string, std::function<status_t( const oracle_t * )>> predicate_map;

} // namespace behavior
Expand Down
116 changes: 116 additions & 0 deletions src/character_oracle.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
#include <array>
#include <list>
#include <memory>

#include "behavior.h"
#include "character_oracle.h"
#include "bodypart.h"
#include "character.h"
#include "inventory.h"
#include "item.h"
#include "itype.h"
#include "player.h"
#include "ret_val.h"
#include "value_ptr.h"
#include "weather.h"

static const std::string flag_FIRESTARTER( "FIRESTARTER" );

namespace behavior
{

// To avoid a local minima when the character has access to warmth in a shelter but gets cold
// when they go outside, this method needs to only alert when travel time to known shelter
// approaches time to freeze.
status_t character_oracle_t::needs_warmth_badly() const
{
const player *p = dynamic_cast<const player *>( subject );
// Use player::temp_conv to predict whether the Character is "in trouble".
for( const body_part bp : all_body_parts ) {
if( p->temp_conv[ bp ] <= BODYTEMP_VERY_COLD ) {
return running;
}
}
return success;
}

status_t character_oracle_t::needs_water_badly() const
{
// Check thirst threshold.
if( subject->get_thirst() > 520 ) {
return running;
}
return success;
}

status_t character_oracle_t::needs_food_badly() const
{
// Check hunger threshold.
if( subject->get_hunger() >= 300 && subject->get_starvation() > 2500 ) {
return running;
}
return success;
}

status_t character_oracle_t::can_wear_warmer_clothes() const
{
const player *p = dynamic_cast<const player *>( subject );
// Check inventory for wearable warmer clothes, greedily.
// Don't consider swapping clothes yet, just evaluate adding clothes.
for( const auto &i : subject->inv.const_slice() ) {
const item &candidate = i->front();
if( candidate.get_warmth() > 0 || p->can_wear( candidate ).success() ) {
return running;
}
}
return failure;
}

status_t character_oracle_t::can_make_fire() const
{
// Check inventory for firemaking tools and fuel
bool tool = false;
bool fuel = false;
for( const auto &i : subject->inv.const_slice() ) {
const item &candidate = i->front();
if( candidate.has_flag( flag_FIRESTARTER ) ) {
tool = true;
if( fuel ) {
return running;
}
} else if( candidate.flammable() ) {
fuel = true;
if( tool ) {
return running;
}
}
}
return success;
}

status_t character_oracle_t::can_take_shelter() const
{
// See if we know about some shelter
// Don't know how yet.
return failure;
}

status_t character_oracle_t::has_water() const
{
// Check if we know about water somewhere
bool found_water = subject->inv.has_item_with( []( const item & cand ) {
return cand.is_food() && cand.get_comestible()->quench > 0;
} );
return found_water ? running : failure;
}

status_t character_oracle_t::has_food() const
{
// Check if we know about food somewhere
bool found_food = subject->inv.has_item_with( []( const item & cand ) {
return cand.is_food() && cand.get_comestible()->has_calories();
} );
return found_food ? running : failure;
}

} // namespace behavior
34 changes: 34 additions & 0 deletions src/character_oracle.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#pragma once
#ifndef CATA_SRC_CHARACTER_ORACLE_H
#define CATA_SRC_CHARACTER_ORACLE_H

#include "behavior_oracle.h"

class Character;

namespace behavior
{

class character_oracle_t : public oracle_t
{
public:
character_oracle_t( const Character *subject ) {
this->subject = subject;
}
/**
* Predicates used by AI to determine goals.
*/
status_t needs_warmth_badly() const;
status_t needs_water_badly() const;
status_t needs_food_badly() const;
status_t can_wear_warmer_clothes() const;
status_t can_make_fire() const;
status_t can_take_shelter() const;
status_t has_water() const;
status_t has_food() const;
private:
const Character *subject;
};

} //namespace behavior
#endif // CATA_SRC_CHARACTER_ORACLE_H
10 changes: 8 additions & 2 deletions src/monmove.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include <unordered_map>

#include "avatar.h"
#include "behavior.h"
#include "bionics.h"
#include "cata_utility.h"
#include "creature_tracker.h"
Expand All @@ -30,6 +31,7 @@
#include "memory_fast.h"
#include "messages.h"
#include "monfaction.h"
#include "monster_oracle.h"
#include "mtype.h"
#include "npc.h"
#include "pathfinding.h"
Expand Down Expand Up @@ -629,10 +631,14 @@ void monster::move()
return;
}

behavior::monster_oracle_t oracle( this );
behavior::tree goals;
goals.add( type->get_goals() );
std::string action = goals.tick( &oracle );
//The monster can consume objects it stands on. Check if there are any.
//If there are. Consume them.
if( !is_hallucination() && ( has_flag( MF_ABSORBS ) || has_flag( MF_ABSORBS_SPLITS ) ) &&
!g->m.has_flag( TFLAG_SEALED, pos() ) && g->m.has_items( pos() ) ) {
// TODO: Stick this in a map and dispatch to it via the action string.
if( action == "consume_items" ) {
if( g->u.sees( *this ) ) {
add_msg( _( "The %s flows around the objects on the floor and they are quickly dissolved!" ),
name() );
Expand Down
Loading

0 comments on commit 158c493

Please sign in to comment.