Skip to content

Commit

Permalink
Behavior tree special attack integration (#40555)
Browse files Browse the repository at this point in the history
* add an argument to behavior tree predicates

* Support for selecting a special attack via behavior tree and integrate with first special
  • Loading branch information
kevingranade authored May 25, 2020
1 parent 93deb96 commit 1813bfb
Show file tree
Hide file tree
Showing 16 changed files with 151 additions and 73 deletions.
23 changes: 19 additions & 4 deletions data/json/monsters/monster_goals.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"type": "behavior",
"id": "monster_goals",
"strategy": "sequential_until_done",
"children": [ "absorb_items", "monster_special" ]
"children": [ "absorb_items" ]
},
{
"type": "behavior",
Expand All @@ -20,8 +20,23 @@
},
{
"type": "behavior",
"id": "monster_special",
"predicate": "monster_has_special",
"goal": "do_special"
"id": "EAT_CROP",
"strategy": "sequential",
"predicate": "monster_not_hallucination",
"children": [ "ready_to_eat_crop" ]
},
{
"type": "behavior",
"id": "ready_to_eat_crop",
"predicate": "monster_special_available",
"predicate_argument": "EAT_CROP",
"strategy": "sequential",
"children": [ "do_eat_crop" ]
},
{
"type": "behavior",
"id": "do_eat_crop",
"predicate": "monster_adjacent_plants",
"goal": "EAT_CROP"
}
]
20 changes: 16 additions & 4 deletions src/behavior.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@ void node_t::set_strategy( const strategy_t *new_strategy )
{
strategy = new_strategy;
}
void node_t::set_predicate( std::function<status_t ( const oracle_t * )> new_predicate )
void node_t::set_predicate( std::function<status_t ( const oracle_t *, const std::string & )>
new_predicate, const std::string &argument )
{
predicate = new_predicate;
predicate_argument = argument;
}
void node_t::set_goal( const std::string &new_goal )
{
Expand All @@ -35,10 +37,10 @@ behavior_return node_t::tick( const oracle_t *subject ) const
{
assert( predicate );
if( children.empty() ) {
return { predicate( subject ), this };
return { predicate( subject, predicate_argument ), this };
} else {
assert( strategy != nullptr );
status_t result = predicate( subject );
status_t result = predicate( subject, predicate_argument );
if( result == running ) {
return strategy->evaluate( subject, children );
} else {
Expand Down Expand Up @@ -83,6 +85,13 @@ generic_factory<behavior::node_t> behavior_factory( "behavior" );
std::list<node_data> temp_node_data;
} // namespace

/** @relates string_id */
template<>
bool string_id<node_t>::is_valid() const
{
return behavior_factory.is_valid( *this );
}

template<>
const node_t &string_id<node_t>::obj() const
{
Expand Down Expand Up @@ -127,7 +136,10 @@ void node_t::load( const JsonObject &jo, const std::string & )
} else {
debugmsg( "While loading %s, failed to find predicate %s.",
id.str(), jo.get_string( "predicate" ) );
jo.throw_error( "Invalid strategy in behavior." );
jo.throw_error( "Invalid predicate in behavior." );
}
if( jo.has_string( "predicate_argument" ) ) {
predicate_argument = jo.get_string( "predicate_argument" );
}
}
optional( jo, was_loaded, "goal", _goal );
Expand Down
8 changes: 5 additions & 3 deletions src/behavior.h
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,8 @@ class node_t

// Interface to construct a node.
void set_strategy( const strategy_t *new_strategy );
void set_predicate( std::function < status_t ( const oracle_t * )> new_predicate );
void set_predicate( std::function < status_t ( const oracle_t *, const std::string & )>
new_predicate, const std::string &argument = "" );
void set_goal( const std::string &new_goal );
void add_child( const node_t *new_child );

Expand All @@ -73,8 +74,9 @@ class node_t
bool was_loaded = false;
private:
std::vector<const node_t *> children;
const strategy_t *strategy;
std::function<status_t( const oracle_t * )> predicate;
const strategy_t *strategy = nullptr;
std::function<status_t( const oracle_t *, const std::string & )> predicate;
std::string predicate_argument;
// TODO: make into an ID?
std::string _goal;
};
Expand Down
24 changes: 16 additions & 8 deletions src/behavior_oracle.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,26 @@
namespace behavior
{

status_t return_running( const oracle_t * )
status_t return_running( const oracle_t *, const std::string & )
{
return running;
}

// 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 )
static std::function < status_t( const oracle_t *, const std::string & ) >
make_function( status_t ( character_oracle_t::* fun )( const std::string & ) const )
{
return static_cast<status_t ( oracle_t::* )() const>( fun );
return static_cast<status_t ( oracle_t::* )( const std::string & ) const>( fun );
}

std::unordered_map<std::string, std::function<status_t( const oracle_t * )>> predicate_map = {{
static std::function < status_t( const oracle_t *, const std::string & ) >
make_function( status_t ( monster_oracle_t::* fun )( const std::string & ) const )
{
return static_cast<status_t ( oracle_t::* )( const std::string & ) const>( fun );
}

std::unordered_map<std::string, std::function<status_t( const oracle_t *, const std::string & ) >>
predicate_map = {{
{ "npc_needs_warmth_badly", make_function( &character_oracle_t::needs_warmth_badly ) },
{ "npc_needs_water_badly", make_function( &character_oracle_t::needs_water_badly ) },
{ "npc_needs_food_badly", make_function( &character_oracle_t::needs_food_badly ) },
Expand All @@ -32,9 +39,10 @@ std::unordered_map<std::string, std::function<status_t( const oracle_t * )>> pre
{ "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 ) },
{ "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 ) }
{ "monster_not_hallucination", make_function( &monster_oracle_t::not_hallucination ) },
{ "monster_items_available", make_function( &monster_oracle_t::items_available ) },
{ "monster_adjacent_plants", make_function( &monster_oracle_t::adjacent_plants ) },
{ "monster_special_available", make_function( &monster_oracle_t::special_available ) }
}
};

Expand Down
5 changes: 3 additions & 2 deletions src/behavior_oracle.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,10 @@ class oracle_t
{
};

status_t return_running( const oracle_t * );
status_t return_running( const oracle_t *, const std::string & );

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

} // namespace behavior
#endif // CATA_SRC_BEHAVIOR_ORACLE_H
16 changes: 8 additions & 8 deletions src/character_oracle.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ 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
status_t character_oracle_t::needs_warmth_badly( const std::string & ) const
{
const player *p = dynamic_cast<const player *>( subject );
// Use player::temp_conv to predict whether the Character is "in trouble".
Expand All @@ -34,7 +34,7 @@ status_t character_oracle_t::needs_warmth_badly() const
return success;
}

status_t character_oracle_t::needs_water_badly() const
status_t character_oracle_t::needs_water_badly( const std::string & ) const
{
// Check thirst threshold.
if( subject->get_thirst() > 520 ) {
Expand All @@ -43,7 +43,7 @@ status_t character_oracle_t::needs_water_badly() const
return success;
}

status_t character_oracle_t::needs_food_badly() const
status_t character_oracle_t::needs_food_badly( const std::string & ) const
{
// Check hunger threshold.
if( subject->get_hunger() >= 300 && subject->get_starvation() > 2500 ) {
Expand All @@ -52,7 +52,7 @@ status_t character_oracle_t::needs_food_badly() const
return success;
}

status_t character_oracle_t::can_wear_warmer_clothes() const
status_t character_oracle_t::can_wear_warmer_clothes( const std::string & ) const
{
const player *p = dynamic_cast<const player *>( subject );
// Check inventory for wearable warmer clothes, greedily.
Expand All @@ -66,7 +66,7 @@ status_t character_oracle_t::can_wear_warmer_clothes() const
return failure;
}

status_t character_oracle_t::can_make_fire() const
status_t character_oracle_t::can_make_fire( const std::string & ) const
{
// Check inventory for firemaking tools and fuel
bool tool = false;
Expand All @@ -88,14 +88,14 @@ status_t character_oracle_t::can_make_fire() const
return success;
}

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

status_t character_oracle_t::has_water() const
status_t character_oracle_t::has_water( const std::string & ) const
{
// Check if we know about water somewhere
bool found_water = subject->inv.has_item_with( []( const item & cand ) {
Expand All @@ -104,7 +104,7 @@ status_t character_oracle_t::has_water() const
return found_water ? running : failure;
}

status_t character_oracle_t::has_food() const
status_t character_oracle_t::has_food( const std::string & ) const
{
// Check if we know about food somewhere
bool found_food = subject->inv.has_item_with( []( const item & cand ) {
Expand Down
18 changes: 10 additions & 8 deletions src/character_oracle.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
#ifndef CATA_SRC_CHARACTER_ORACLE_H
#define CATA_SRC_CHARACTER_ORACLE_H

#include <string>

#include "behavior_oracle.h"

class Character;
Expand All @@ -18,14 +20,14 @@ class character_oracle_t : public oracle_t
/**
* 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;
status_t needs_warmth_badly( const std::string & ) const;
status_t needs_water_badly( const std::string & ) const;
status_t needs_food_badly( const std::string & ) const;
status_t can_wear_warmer_clothes( const std::string & ) const;
status_t can_make_fire( const std::string & ) const;
status_t can_take_shelter( const std::string & ) const;
status_t has_water( const std::string & ) const;
status_t has_food( const std::string & ) const;
private:
const Character *subject;
};
Expand Down
2 changes: 1 addition & 1 deletion src/init.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -593,6 +593,7 @@ void DynamicDataLoader::finalize_loaded_data( loading_ui &ui )
{ _( "Start locations" ), &start_locations::finalize_all },
{ _( "Vehicle prototypes" ), &vehicle_prototype::finalize },
{ _( "Mapgen weights" ), &calculate_mapgen_weights },
{ _( "Behaviors" ), &behavior::finalize },
{
_( "Monster types" ), []()
{
Expand All @@ -608,7 +609,6 @@ void DynamicDataLoader::finalize_loaded_data( loading_ui &ui )
{ _( "Martial arts" ), &finialize_martial_arts },
{ _( "NPC classes" ), &npc_class::finalize_all },
{ _( "Missions" ), &mission_type::finalize },
{ _( "Behaviors" ), &behavior::finalize },
{ _( "Harvest lists" ), &harvest_list::finalize_all },
{ _( "Anatomies" ), &anatomy::finalize_all },
{ _( "Mutations" ), &mutation_branch::finalize },
Expand Down
14 changes: 10 additions & 4 deletions src/monattack.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -323,13 +323,19 @@ bool mattack::none( monster * )

bool mattack::eat_crop( monster *z )
{
cata::optional<tripoint> target;
int num_targets = 1;
for( const auto &p : g->m.points_in_radius( z->pos(), 1 ) ) {
if( g->m.has_flag( "PLANT", p ) && one_in( 4 ) ) {
g->m.furn_set( p, furn_str_id( g->m.furn( p )->plant->base ) );
g->m.i_clear( p );
return true;
if( g->m.has_flag( "PLANT", p ) && one_in( num_targets ) ) {
num_targets++;
target = p;
}
}
if( target ) {
g->m.furn_set( *target, furn_str_id( g->m.furn( *target )->plant->base ) );
g->m.i_clear( *target );
return true;
}
return true;
}

Expand Down
9 changes: 9 additions & 0 deletions src/monmove.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -664,6 +664,15 @@ void monster::move()
}
}
g->m.i_clear( pos() );
} else if( action == "eat_crop" ) {
// TODO: Create a special attacks whitelist unordered map instead of an if chain.
std::map<std::string, mtype_special_attack>::const_iterator attack =
type->special_attacks.find( action );
if( attack != type->special_attacks.end() && attack->second->call( *this ) ) {
if( special_attacks.count( action ) != 0 ) {
reset_special( action );
}
}
}
// record position before moving to put the player there if we're dragging
tripoint drag_to = g->m.getabs( pos() );
Expand Down
14 changes: 4 additions & 10 deletions src/monster.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2036,17 +2036,11 @@ void monster::disable_special( const std::string &special_name )
special_attacks.at( special_name ).enabled = false;
}

int monster::shortest_special_cooldown() const
bool monster::special_available( const std::string &special_name ) const
{
int countdown = std::numeric_limits<int>::max();
for( const std::pair<const std::string, mon_special_attack> &sp_type : special_attacks ) {
const mon_special_attack &local_attack_data = sp_type.second;
if( !local_attack_data.enabled ) {
continue;
}
countdown = std::min( countdown, local_attack_data.cooldown );
}
return countdown;
std::map<std::string, mon_special_attack>::const_iterator iter = special_attacks.find(
special_name );
return iter != special_attacks.end() && iter->second.enabled && iter->second.cooldown == 0;
}

void monster::normalize_ammo( const int old_ammo )
Expand Down
4 changes: 2 additions & 2 deletions src/monster.h
Original file line number Diff line number Diff line change
Expand Up @@ -392,8 +392,8 @@ class monster : public Creature
void set_special( const std::string &special_name, int time );
/** Sets the enabled flag for the given special to false */
void disable_special( const std::string &special_name );
/** Return the lowest cooldown for an enabled special */
int shortest_special_cooldown() const;
/** Test whether the specified special is ready. */
bool special_available( const std::string &special_name ) const;

void process_turn() override;
/** Resets the value of all bonus fields to 0, clears special effect flags. */
Expand Down
Loading

0 comments on commit 1813bfb

Please sign in to comment.