Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Monster behavior tree #39810

Merged
merged 2 commits into from
Apr 27, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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