AI: add [filter_own] to all default AI candidate actions

This allows restricting each CA individually to only a subset of the units. It also means that several instances of the same CA can be used in the same AI configuration, in order to, for example, force the order in which units are dealt with, or to apply different settings to different units.
This commit is contained in:
mattsc 2019-11-17 19:13:27 -08:00
parent a9d9fbaccd
commit 626d7ee381
12 changed files with 106 additions and 29 deletions

View file

@ -30,7 +30,7 @@ local XP_attack
local ca_attack_highxp = {}
function ca_attack_highxp:evaluation(cfg, data)
function ca_attack_highxp:evaluation(cfg, data, filter_own)
-- Note: (most of) the code below is set up to maximize speed. Do not
-- "simplify" this unless you understand exactly what that means
@ -38,7 +38,7 @@ function ca_attack_highxp:evaluation(cfg, data)
local max_unit_level = 0
local units = {}
for _,unit in ipairs(attacks_aspect.own) do
if (unit.attacks_left > 0) and (#unit.attacks > 0) and (not unit.canrecruit) then
if (unit.attacks_left > 0) and (#unit.attacks > 0) and (not unit.canrecruit) and unit:matches(filter_own) then
table.insert(units, unit)
local level = unit.level

View file

@ -439,7 +439,8 @@ public:
{
this->valid_lua_ = true;
this->value_lua_.reset(new lua_object<T>);
handler_->handle(params_, true, this->value_lua_);
const config empty_cfg;
handler_->handle(params_, empty_cfg, true, this->value_lua_);
}
config to_config() const

View file

@ -330,7 +330,8 @@ void lua_goal::add_targets(std::back_insert_iterator< std::vector< target >> tar
{
std::shared_ptr<lua_object<std::vector<target>>> l_obj = std::make_shared<lua_object<std::vector<target>>>();
config c(cfg_.child_or_empty("args"));
handler_->handle(c, true, l_obj);
const config empty_cfg;
handler_->handle(c, empty_cfg, true, l_obj);
try {
std::vector < target > targets = *(l_obj->get());

View file

@ -39,6 +39,11 @@ candidate_action::candidate_action(rca_context &context, const config &cfg):
max_score_(cfg["max_score"].to_double(HIGH_SCORE)),
id_(cfg["id"]), name_(cfg["name"]), type_(cfg["type"]), to_be_removed_(false)
{
if (const config &filter_own = cfg.child("filter_own")) {
vconfig vcfg(filter_own);
vcfg.make_safe();
filter_own_.reset(new unit_filter(vcfg));
}
init_rca_context_proxy(context);
}
@ -80,6 +85,20 @@ double candidate_action::get_max_score() const
return max_score_;
}
std::shared_ptr<unit_filter> candidate_action::get_filter_own() const
{
return filter_own_;
}
bool candidate_action::is_allowed_unit(const unit& u) const
{
if (filter_own_) {
return (*filter_own_)(u);
}
return true;
}
const std::string& candidate_action::get_type() const
{
return type_;
@ -94,6 +113,9 @@ config candidate_action::to_config() const
cfg["name"] = name_;
cfg["score"] = score_;
cfg["max_score"] = max_score_;
if (filter_own_ && !filter_own_->empty()) {
cfg.add_child("filter_own", filter_own_->to_config());
}
cfg["type"] = type_;
return cfg;
}

View file

@ -21,6 +21,7 @@
#include "ai/composite/component.hpp"
#include "ai/composite/contexts.hpp"
#include "units/filter.hpp"
//============================================================================
namespace ai {
@ -80,6 +81,18 @@ public:
*/
double get_max_score() const;
/**
* Get the unit filter for allowed units for this candidate action
*/
std::shared_ptr<unit_filter> get_filter_own() const;
/**
* Flag indicating whether unit may be used by this candidate action
*/
bool is_allowed_unit(const unit& u) const;
/**
* Get the name of the candidate action (useful for debug purposes)
*/
@ -125,6 +138,9 @@ private:
double max_score_;
std::shared_ptr<unit_filter> filter_own_;
std::string id_;

View file

@ -439,7 +439,8 @@ aspect_attacks_lua::aspect_attacks_lua(readonly_context &context, const config &
void aspect_attacks_lua::recalculate() const
{
obj_.reset(new lua_object<aspect_attacks_lua_filter>);
handler_->handle(params_, true, obj_);
const config empty_cfg;
handler_->handle(params_, empty_cfg, true, obj_);
aspect_attacks_lua_filter filt = *obj_->get();
aspect_attacks_base::recalculate();
if(filt.lua) {

View file

@ -85,6 +85,10 @@ double goto_phase::evaluate()
}
// end of passive_leader
if(!is_allowed_unit(*ui)){
continue;
}
const pathfind::shortest_path_calculator calc(*ui, current_team(), resources::gameboard->teams(), resources::gameboard->map());
const pathfind::teleport_map allowed_teleports = pathfind::get_teleport_locations(*ui, current_team());
@ -161,6 +165,7 @@ combat_phase::~combat_phase()
double combat_phase::evaluate()
{
const unit_map &units_ = resources::gameboard->units();
std::vector<std::string> options = get_recruitment_pattern();
choice_rating_ = -1000.0;
@ -193,6 +198,22 @@ double combat_phase::evaluate()
if(skip_num > 0 && ((it - analysis.begin())%skip_num) && it->movements.size() > 1)
continue;
// This is somewhat inefficient. It would be faster to exclude these attacks
// in get_attacks() above, but the CA filter information is not available inside
// the attacks aspect code. Providing the filtering here is only done for consistency
// with other CAs though, the recommended method of filtering attacks is via
// 'filter_own' of the attacks aspect.
bool skip_attack = false;
for(std::size_t i = 0; i != it->movements.size(); ++i) {
const unit_map::const_iterator u = units_.find(it->movements[i].first);
if (!is_allowed_unit(*u)) {
skip_attack = true;
break;
}
}
if (skip_attack)
continue;
const double rating = it->rating(get_aggression(),*this);
LOG_AI_TESTING_AI_DEFAULT << "attack option rated at " << rating << " ("
<< (it->uses_leader ? get_leader_aggression() : get_aggression()) << ")\n";
@ -288,7 +309,7 @@ double move_leader_to_goals_phase::evaluate()
const unit* leader = nullptr;
for (const unit_map::const_iterator& l_itor : leaders) {
if (!l_itor->incapacitated() && l_itor->movement_left() > 0) {
if (!l_itor->incapacitated() && l_itor->movement_left() > 0 && is_allowed_unit(*l_itor)) {
leader = &(*l_itor);
break;
}
@ -413,7 +434,7 @@ double move_leader_to_keep_phase::evaluate()
int shortest_distance = 99999;
for (const unit_map::const_iterator& leader : leaders) {
if (leader->incapacitated() || leader->movement_left() == 0) {
if (leader->incapacitated() || leader->movement_left() == 0 || !is_allowed_unit(*leader)) {
continue;
}
@ -620,7 +641,7 @@ void get_villages_phase::get_villages(
if(u_itor->can_recruit() && get_passive_leader()){
continue;
}
if(u_itor->side() == get_side() && u_itor->movement_left()) {
if(u_itor->side() == get_side() && u_itor->movement_left() && is_allowed_unit(*u_itor)) {
reachmap.emplace(u_itor->get_location(), std::vector<map_location>());
}
}
@ -732,7 +753,7 @@ void get_villages_phase::find_villages(
}
const unit_map::const_iterator u = resources::gameboard->units().find(j->second);
if (u == resources::gameboard->units().end() || u->get_state("guardian")) {
if (u == resources::gameboard->units().end() || u->get_state("guardian") || !is_allowed_unit(*u)) {
continue;
}
@ -1340,7 +1361,7 @@ double get_healing_phase::evaluate()
if(u.side() == get_side() &&
(u.max_hitpoints() - u.hitpoints() >= game_config::poison_amount/2
|| u.get_state(unit::STATE_POISONED)) &&
!u.get_ability_bool("regenerate"))
!u.get_ability_bool("regenerate") && is_allowed_unit(*u_it))
{
// Look for the village which is the least vulnerable to enemy attack.
typedef std::multimap<map_location,map_location>::const_iterator Itor;
@ -1444,7 +1465,7 @@ double retreat_phase::evaluate()
i->movement_left() == i->total_movement() &&
//leaders.find(*i) == leaders.end() && //unit_map::const_iterator(i) != leader &&
std::find(leaders.begin(), leaders.end(), i) == leaders.end() &&
!i->incapacitated())
!i->incapacitated() && is_allowed_unit(*i))
{
// This unit still has movement left, and is a candidate to retreat.
// We see the amount of power of each side on the situation,
@ -1624,7 +1645,7 @@ void leader_shares_keep_phase::execute()
//check for each ai leader if he should move away from his keep
for (unit_map::unit_iterator &ai_leader : ai_leaders) {
if(!ai_leader.valid()) {
if(!ai_leader.valid() || !is_allowed_unit(*ai_leader)) {
//This can happen if wml killed or moved a leader during a movement events of another leader
continue;
}
@ -1693,11 +1714,6 @@ void leader_shares_keep_phase::execute()
}
ai_leader->remove_movement_ai();
}
// re-get the AI leaders, in case an event did something
ai_leaders = resources::gameboard->units().find_leaders(get_side());
for(unit_map::unit_iterator &leader : ai_leaders) {
leader->remove_movement_ai();
}
//ERR_AI_TESTING_AI_DEFAULT << get_name() << ": evaluate - not yet implemented" << std::endl;
}

View file

@ -290,7 +290,7 @@ std::pair<map_location,map_location> move_to_targets_phase::choose_move(std::vec
//now find the first eligible remaining unit
for(u = units_.begin(); u != units_.end(); ++u) {
if (!(u->side() != get_side() || (u->can_recruit() && !get_leader_ignores_keep()) || u->movement_left() <= 0 || u->incapacitated())) {
if (!(u->side() != get_side() || (u->can_recruit() && !get_leader_ignores_keep()) || u->movement_left() <= 0 || u->incapacitated() || !is_allowed_unit(*u))) {
break;
}
}
@ -393,7 +393,7 @@ std::pair<map_location,map_location> move_to_targets_phase::choose_move(std::vec
for(++u; u != units_.end(); ++u) {
if (u->side() != get_side() || (u->can_recruit() && !get_leader_ignores_keep()) ||
u->movement_left() <= 0 || u->get_state("guardian") ||
u->incapacitated())
u->incapacitated() || !is_allowed_unit(*u))
{
continue;
}

View file

@ -176,6 +176,12 @@ double recruitment::evaluate() {
const std::vector<unit_map::const_iterator> leaders = units.find_leaders(get_side());
for (const unit_map::const_iterator& leader : leaders) {
// Need to check this here, otherwise recruiting might be blacklisted
// if no allowed leader is on a keep yet
if (!is_allowed_unit(*leader)) {
continue;
}
if (leader == resources::gameboard->units().end()) {
return BAD_SCORE;
}
@ -215,6 +221,10 @@ void recruitment::execute() {
for (const unit_map::const_iterator& leader : leaders) {
const map_location& keep = leader->get_location();
if (!is_allowed_unit(*leader)) {
LOG_AI_RECRUITMENT << "Leader " << leader->name() << " is not allowed recruiter. \n";
continue;
}
if (!resources::gameboard->map().is_keep(keep)) {
LOG_AI_RECRUITMENT << "Leader " << leader->name() << " is not on keep. \n";
continue;

View file

@ -1104,7 +1104,7 @@ lua_ai_context::~lua_ai_context()
lua_pop(L, 1);
}
void lua_ai_action_handler::handle(const config &cfg, bool read_only, lua_object_ptr l_obj)
void lua_ai_action_handler::handle(const config &cfg, const config &filter_own, bool read_only, lua_object_ptr l_obj)
{
int initial_top = lua_gettop(L);//get the old stack size
@ -1122,8 +1122,14 @@ void lua_ai_action_handler::handle(const config &cfg, bool read_only, lua_object
luaW_pushconfig(L, cfg);
lua_getfield(L, iState, "data");
int num = 3;
if (!filter_own.empty()) {
luaW_pushconfig(L, filter_own);
num=4;
}
// Call the function
luaW_pcall(L, 3, l_obj ? 1 : 0, true);
luaW_pcall(L, num, l_obj ? 1 : 0, true);
if (l_obj) {
l_obj->store(L, -1);
}

View file

@ -78,7 +78,8 @@ private:
static lua_ai_action_handler* create(lua_State *L, char const *code, lua_ai_context &context);
public:
~lua_ai_action_handler();
void handle(const config &cfg, bool read_only, lua_object_ptr l_obj);
//void handle(const config &cfg, bool read_only, lua_object_ptr l_obj);
void handle(const config &cfg, const config &filter_own, bool read_only, lua_object_ptr l_obj);
friend class ::game_lua_kernel;
};

View file

@ -56,7 +56,7 @@ class lua_candidate_action_wrapper_base : public candidate_action {
public:
lua_candidate_action_wrapper_base( rca_context &context, const config &cfg)
: candidate_action(context, cfg),evaluation_action_handler_(),execution_action_handler_(),serialized_evaluation_state_(cfg.child_or_empty("args"))
: candidate_action(context, cfg),evaluation_action_handler_(),execution_action_handler_(),serialized_evaluation_state_(cfg.child_or_empty("args")),serialized_filterown_(cfg.child_or_empty("filter_own"))
{
// do nothing
}
@ -68,7 +68,7 @@ public:
lua_int_obj l_obj(new lua_object<int>());
if (evaluation_action_handler_) {
evaluation_action_handler_->handle(serialized_evaluation_state_, true, l_obj);
evaluation_action_handler_->handle(serialized_evaluation_state_, serialized_filterown_, true, l_obj);
} else {
return BAD_SCORE;
}
@ -82,13 +82,14 @@ public:
virtual void execute() {
if (execution_action_handler_) {
lua_object_ptr nil;
execution_action_handler_->handle(serialized_evaluation_state_, false, nil);
execution_action_handler_->handle(serialized_evaluation_state_, serialized_filterown_, false, nil);
}
}
virtual config to_config() const {
config cfg = candidate_action::to_config();
cfg.add_child("args",serialized_evaluation_state_);
cfg.add_child("filter_own",serialized_filterown_);
return cfg;
}
@ -96,6 +97,7 @@ protected:
std::shared_ptr<lua_ai_action_handler> evaluation_action_handler_;
std::shared_ptr<lua_ai_action_handler> execution_action_handler_;
config serialized_evaluation_state_;
config serialized_filterown_;
};
class lua_candidate_action_wrapper : public lua_candidate_action_wrapper_base {
@ -161,14 +163,14 @@ private:
bool use_parms_;
void generate_code(std::string& eval, std::string& exec) {
std::string preamble = "local self, params, data = ...\n";
std::string preamble = "local self, params, data, filter_own = ...\n";
std::string load = "wesnoth.require(\"" + location_ + "\")";
if (use_parms_) {
eval = preamble + "return " + load + ":evaluation(ai, {" + eval_parms_ + "}, {data = data})";
exec = preamble + load + ":execution(ai, {" + exec_parms_ + "}, {data = data})";
} else {
eval = preamble + "return " + load + ".evaluation(self, params, data)";
exec = preamble + load + ".execution(self, params, data)";
eval = preamble + "return " + load + ".evaluation(self, params, data, filter_own)";
exec = preamble + load + ".execution(self, params, data, filter_own)";
}
}
};
@ -224,7 +226,8 @@ public:
if (action_handler_) {
lua_object_ptr nil;
action_handler_->handle(serialized_evaluation_state_, false, nil);
const config empty_cfg;
action_handler_->handle(serialized_evaluation_state_, empty_cfg, false, nil);
}
return gs_o.is_gamestate_changed();