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

Behavior tree special attack integration #40555

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
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 @@ -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
Loading