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 15, 2020
1 parent 1a4a2c3 commit 73275e9
Show file tree
Hide file tree
Showing 12 changed files with 83 additions and 39 deletions.
22 changes: 18 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,22 @@
},
{
"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",
"children": [ "do_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
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 @@ -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
5 changes: 5 additions & 0 deletions src/monstergenerator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,11 @@ 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( const std::pair<const std::string, mtype_special_attack> &attack : type.special_attacks ) {
if( string_id<behavior::node_t>( attack.first ).is_valid() ) {
type.add_goal( attack.first );
} /* TODO: Make this an error once all the special attacks are migrated. */
}
}

void MonsterGenerator::finalize_mtypes()
Expand Down
23 changes: 17 additions & 6 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 @@ -183,18 +185,27 @@ TEST_CASE( "check_npc_behavior_tree", "[npc][behavior][!mayfail]" )

TEST_CASE( "check_monster_behavior_tree", "[monster][behavior]" )
{
const tripoint monster_location( 5, 5, 0 );
monster &test_monster = spawn_test_monster( "mon_locust", monster_location );

behavior::monster_oracle_t oracle( &test_monster );
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 } );
monster_goals.add( test_monster.type->get_goals() );

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 73275e9

Please sign in to comment.