diff --git a/data/ai/lua/ca_high_xp_attack.lua b/data/ai/lua/ca_high_xp_attack.lua index f479a03fb59..9421d61231f 100644 --- a/data/ai/lua/ca_high_xp_attack.lua +++ b/data/ai/lua/ca_high_xp_attack.lua @@ -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 diff --git a/src/ai/composite/aspect.hpp b/src/ai/composite/aspect.hpp index 7d374aa2c37..1333a61fb5f 100644 --- a/src/ai/composite/aspect.hpp +++ b/src/ai/composite/aspect.hpp @@ -439,7 +439,8 @@ public: { this->valid_lua_ = true; this->value_lua_.reset(new lua_object); - handler_->handle(params_, true, this->value_lua_); + const config empty_cfg; + handler_->handle(params_, empty_cfg, true, this->value_lua_); } config to_config() const diff --git a/src/ai/composite/goal.cpp b/src/ai/composite/goal.cpp index e13e46c6a4e..e7ffe3389fe 100644 --- a/src/ai/composite/goal.cpp +++ b/src/ai/composite/goal.cpp @@ -330,7 +330,8 @@ void lua_goal::add_targets(std::back_insert_iterator< std::vector< target >> tar { std::shared_ptr>> l_obj = std::make_shared>>(); 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()); diff --git a/src/ai/composite/rca.cpp b/src/ai/composite/rca.cpp index cce015a4806..cfd2cac3dec 100644 --- a/src/ai/composite/rca.cpp +++ b/src/ai/composite/rca.cpp @@ -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 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; } diff --git a/src/ai/composite/rca.hpp b/src/ai/composite/rca.hpp index f71f0df430b..81e3670d9ac 100644 --- a/src/ai/composite/rca.hpp +++ b/src/ai/composite/rca.hpp @@ -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 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 filter_own_; + + std::string id_; diff --git a/src/ai/default/aspect_attacks.cpp b/src/ai/default/aspect_attacks.cpp index e8e932d816d..7f18ac2ded0 100644 --- a/src/ai/default/aspect_attacks.cpp +++ b/src/ai/default/aspect_attacks.cpp @@ -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); - 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) { diff --git a/src/ai/default/ca.cpp b/src/ai/default/ca.cpp index 75f738a302e..e27df3a2716 100644 --- a/src/ai/default/ca.cpp +++ b/src/ai/default/ca.cpp @@ -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 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()); } } @@ -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::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; } diff --git a/src/ai/default/ca_move_to_targets.cpp b/src/ai/default/ca_move_to_targets.cpp index 8a6bfc974b5..125e15269ba 100644 --- a/src/ai/default/ca_move_to_targets.cpp +++ b/src/ai/default/ca_move_to_targets.cpp @@ -290,7 +290,7 @@ std::pair 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 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; } diff --git a/src/ai/default/recruitment.cpp b/src/ai/default/recruitment.cpp index 9fbcbc5d62e..f5c0fb181e7 100644 --- a/src/ai/default/recruitment.cpp +++ b/src/ai/default/recruitment.cpp @@ -176,6 +176,12 @@ double recruitment::evaluate() { const std::vector 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; diff --git a/src/ai/lua/core.cpp b/src/ai/lua/core.cpp index 2bc6acfa750..c37172d1f08 100644 --- a/src/ai/lua/core.cpp +++ b/src/ai/lua/core.cpp @@ -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); } diff --git a/src/ai/lua/core.hpp b/src/ai/lua/core.hpp index 158636bb90d..cea37182e57 100644 --- a/src/ai/lua/core.hpp +++ b/src/ai/lua/core.hpp @@ -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; }; diff --git a/src/ai/lua/engine_lua.cpp b/src/ai/lua/engine_lua.cpp index 665ac5be0b2..ec95c74643a 100644 --- a/src/ai/lua/engine_lua.cpp +++ b/src/ai/lua/engine_lua.cpp @@ -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()); 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 evaluation_action_handler_; std::shared_ptr 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();