Skip to content

Commit

Permalink
Initial integration of behavior tree into monster AI.
Browse files Browse the repository at this point in the history
  • Loading branch information
kevingranade committed Apr 23, 2020
1 parent 31dcaec commit 10f474d
Show file tree
Hide file tree
Showing 12 changed files with 165 additions and 3 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"
}
]
5 changes: 4 additions & 1 deletion src/behavior_oracle.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,10 @@ 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 ) }
}
};

Expand Down
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
13 changes: 13 additions & 0 deletions src/monster.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2014,6 +2014,19 @@ void monster::disable_special( const std::string &special_name )
special_attacks.at( special_name ).enabled = false;
}

int monster::shortest_special_cooldown() const
{
int countdown = std::numeric_limits<int>::max();
for( const std::pair<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;
}

void monster::normalize_ammo( const int old_ammo )
{
int total_ammo = 0;
Expand Down
2 changes: 2 additions & 0 deletions src/monster.h
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,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;

void process_turn() override;
/** Resets the value of all bonus fields to 0, clears special effect flags. */
Expand Down
33 changes: 33 additions & 0 deletions src/monster_oracle.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#include <string>

#include "behavior.h"
#include "game.h"
#include "map.h"
#include "monster.h"
#include "monster_oracle.h"

namespace behavior
{

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

status_t monster_oracle_t::not_hallucination() const
{
return subject->is_hallucination() ? failure : running;
}

status_t monster_oracle_t::items_available() const
{
if( !g->m.has_flag( TFLAG_SEALED, subject->pos() ) && g->m.has_items( subject->pos() ) ) {
return running;
}
return failure;
}

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

#include "behavior_oracle.h"

class monster;

namespace behavior
{

class monster_oracle_t : public oracle_t
{
public:
monster_oracle_t( const monster *subject ) {
this->subject = subject;
}
/**
* Predicates used by AI to determine goals.
*/
status_t has_special() const;
status_t not_hallucination() const;
status_t items_available() const;
private:
const monster *subject;
};

} // namespace behavior
#endif // CATA_SRC_MONSTER_ORACLE_H
9 changes: 9 additions & 0 deletions src/monstergenerator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,14 @@ void load_monster_adjustment( const JsonObject &jsobj )
adjustments.push_back( adj );
}

static void build_behavior_tree( mtype &type )
{
type.set_strategy();
if( type.has_flag( MF_ABSORBS ) || type.has_flag( MF_ABSORBS_SPLITS ) ) {
type.add_goal( "absorb_items" );
}
}

void MonsterGenerator::finalize_mtypes()
{
mon_templates->finalize();
Expand Down Expand Up @@ -368,6 +376,7 @@ void MonsterGenerator::finalize_mtypes()
// Lower bound for hp scaling
mon.hp = std::max( mon.hp, 1 );

build_behavior_tree( mon );
finalize_pathfinding_settings( mon );
}

Expand Down
16 changes: 16 additions & 0 deletions src/mtype.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include <algorithm>
#include <cmath>

#include "behavior_strategy.h"
#include "creature.h"
#include "field_type.h"
#include "item.h"
Expand Down Expand Up @@ -235,3 +236,18 @@ std::string mtype::get_footsteps() const
}
return _( "footsteps." );
}

void mtype::set_strategy()
{
goals.set_strategy( behavior::strategy_map[ "sequential_until_done" ] );
}

void mtype::add_goal( const std::string &goal_id )
{
goals.add_child( &string_id<behavior::node_t>( goal_id ).obj() );
}

const behavior::node_t *mtype::get_goals() const
{
return &goals;
}
6 changes: 6 additions & 0 deletions src/mtype.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include <string>
#include <vector>

#include "behavior.h"
#include "calendar.h"
#include "color.h"
#include "damage.h"
Expand Down Expand Up @@ -213,6 +214,8 @@ struct mtype {
enum_bitset<mon_trigger> fear;
enum_bitset<mon_trigger> placate;

behavior::node_t goals;

void add_special_attacks( const JsonObject &jo, const std::string &member_name,
const std::string &src );
void remove_special_attacks( const JsonObject &jo, const std::string &member_name,
Expand Down Expand Up @@ -385,6 +388,9 @@ struct mtype {
int get_meat_chunks_count() const;
std::string get_description() const;
std::string get_footsteps() const;
void set_strategy();
void add_goal( const std::string &goal_id );
const behavior::node_t *get_goals() const;

// Historically located in monstergenerator.cpp
void load( const JsonObject &jo, const std::string &src );
Expand Down
1 change: 1 addition & 0 deletions src/panels.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include "cata_utility.h"
#include "catacharset.h"
#include "character.h"
#include "character_oracle.h"
#include "character_martial_arts.h"
#include "color.h"
#include "compatibility.h"
Expand Down
17 changes: 17 additions & 0 deletions tests/behavior_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@
#include "behavior_oracle.h"
#include "behavior_strategy.h"
#include "catch/catch.hpp"
#include "character_oracle.h"
#include "game.h"
#include "item.h"
#include "item_location.h"
#include "monster_oracle.h"
#include "npc.h"
#include "player.h"
#include "map_helpers.h"
Expand Down Expand Up @@ -176,3 +178,18 @@ TEST_CASE( "check_npc_behavior_tree", "[npc][behavior]" )
CHECK( npc_needs.tick( &oracle ) == "idle" );
}
}

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 } );
behavior::monster_oracle_t oracle( &test_monster );
CHECK( monster_goals.tick( &oracle ) == "idle" );
SECTION( "Special Attack" ) {
test_monster.set_special( "bite", 0 );
CHECK( monster_goals.tick( &oracle ) == "do_special" );
test_monster.set_special( "bite", 1 );
CHECK( monster_goals.tick( &oracle ) == "idle" );
}
}

0 comments on commit 10f474d

Please sign in to comment.