Skip to content

Commit

Permalink
Support for selecting a special attack via behavior tree and integrat…
Browse files Browse the repository at this point in the history
…e with first special
  • Loading branch information
kevingranade committed May 14, 2020
1 parent 8bc6cc6 commit 1fbde55
Show file tree
Hide file tree
Showing 11 changed files with 84 additions and 37 deletions.
21 changes: 17 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,21 @@
},
{
"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_crops", "do_eat_crop" ]
},
{
"type": "behavior",
"id": "ready_to_eat_crop",
"predicate": "monster_special_available",
"predicate_argument": "EAT_CROP"
},
{
"type": "behavior",
"id": "do_eat_crop",
"predicate": "monster_adjacent_plants",
"goal": "eat_crop"
}
]
7 changes: 7 additions & 0 deletions src/behavior.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -85,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
4 changes: 2 additions & 2 deletions src/behavior_oracle.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,10 @@ predicate_map = {{
{ "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", make_function( &monster_oracle_t::has_special ) },
{ "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_adjacent_plants", make_function( &monster_oracle_t::adjacent_plants ) },
{ "monster_special_available", make_function( &monster_oracle_t::special_available ) }
}
};

Expand Down
14 changes: 10 additions & 4 deletions src/monattack.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -301,13 +301,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 @@ -662,6 +662,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 @@ -2032,17 +2032,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
16 changes: 7 additions & 9 deletions src/monster_oracle.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,13 @@
#include "behavior.h"
#include "game.h"
#include "map.h"
#include "map_iterator.h"
#include "monster.h"
#include "monster_oracle.h"

namespace behavior
{

status_t monster_oracle_t::has_special( const std::string & ) const
{
if( subject->shortest_special_cooldown() == 0 ) {
return running;
}
return failure;
}

status_t monster_oracle_t::not_hallucination( const std::string & ) const
{
return subject->is_hallucination() ? failure : running;
Expand All @@ -33,12 +26,17 @@ status_t monster_oracle_t::items_available( const std::string & ) const
// TODO: Have it select a target and stash it somewhere.
status_t monster_oracle_t::adjacent_plants( const std::string & ) const
{
for( const auto &p : g->m.points_in_radius( subject->pos(), 1 ) ) {
for( const tripoint &p : g->m.points_in_radius( subject->pos(), 1 ) ) {
if( g->m.has_flag( "PLANT", p ) ) {
return running;
}
}
return failure;
}

status_t monster_oracle_t::special_available( const std::string &special_name ) const
{
return subject->special_available( special_name ) ? running : failure;
}

} // namespace behavior
2 changes: 1 addition & 1 deletion src/monster_oracle.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@ class monster_oracle_t : public oracle_t
/**
* Predicates used by AI to determine goals.
*/
status_t has_special( const std::string & ) const;
status_t not_hallucination( const std::string & ) const;
status_t items_available( const std::string & ) const;
status_t adjacent_plants( const std::string & ) const;
status_t special_available( const std::string &special_name ) const;
private:
const monster *subject;
};
Expand Down
11 changes: 11 additions & 0 deletions src/monstergenerator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,17 @@ static void build_behavior_tree( mtype &type )
if( type.has_flag( MF_ABSORBS ) || type.has_flag( MF_ABSORBS_SPLITS ) ) {
type.add_goal( "absorb_items" );
}
for( std::pair<std::string, mtype_special_attack> attack : type.special_attacks ) {
std::string goal_name;
// TODO: Lowercase all the hardcoded attack ids.
std::transform( attack.first.begin(), attack.first.end(), goal_name.begin(),
[]( char c ) {
return std::tolower( c );
} );
if( string_id<behavior::node_t>( goal_name ).is_valid() ) {
type.add_goal( goal_name );
}
}
}

void MonsterGenerator::finalize_mtypes()
Expand Down
19 changes: 14 additions & 5 deletions tests/behavior_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
#include "game.h"
#include "item.h"
#include "item_location.h"
#include "map.h"
#include "map_iterator.h"
#include "monster_oracle.h"
#include "mtype.h"
#include "npc.h"
Expand Down Expand Up @@ -184,17 +186,24 @@ TEST_CASE( "check_npc_behavior_tree", "[npc][behavior][!mayfail]" )
TEST_CASE( "check_monster_behavior_tree", "[monster][behavior]" )
{
behavior::tree monster_goals;
monster_goals.add( &string_id<behavior::node_t>( "monster_special" ).obj() );
monster &test_monster = spawn_test_monster( "mon_zombie", { 5, 5, 0 } );
const tripoint monster_location( 5, 5, 0 );
monster_goals.add( &string_id<behavior::node_t>( "eat_crop" ).obj() );
monster &test_monster = spawn_test_monster( "mon_locust", monster_location );
for( const std::string &special_name : test_monster.type->special_attacks_names ) {
test_monster.reset_special( special_name );
}
behavior::monster_oracle_t oracle( &test_monster );
CHECK( monster_goals.tick( &oracle ) == "idle" );
for( const tripoint &near : g->m.points_in_radius( monster_location, 1 ) ) {
g->m.ter_set( near, ter_id( "t_grass" ) );
g->m.furn_set( near, furn_id( "f_null" ) );
}
SECTION( "Special Attack" ) {
test_monster.set_special( "bite", 0 );
CHECK( monster_goals.tick( &oracle ) == "do_special" );
test_monster.set_special( "bite", 1 );
test_monster.set_special( "EAT_CROP", 0 );
CHECK( monster_goals.tick( &oracle ) == "idle" );
g->m.furn_set( monster_location, furn_id( "f_plant_seedling" ) );
CHECK( monster_goals.tick( &oracle ) == "eat_crop" );
test_monster.set_special( "EAT_CROP", 1 );
CHECK( monster_goals.tick( &oracle ) == "idle" );
}
}

0 comments on commit 1fbde55

Please sign in to comment.