Allow the attacks aspect to be implemented with Lua

Semi-related changes also included:
- Unit filter now has empty() and to_config() methods
- Advancements aspect now unrefs its function on destruction
- Const correctness for ai.get_attacks()
This commit is contained in:
Celtic Minstrel 2016-03-27 23:22:43 -04:00
parent 678444133e
commit 97b33cfdfc
11 changed files with 256 additions and 36 deletions

View file

@ -147,6 +147,10 @@ Version 1.13.4+dev:
* New wesnoth.micro_ais table contains the loaders for all Micro AIs.
New loaders can easily be installed by add-ons. See any built-in
micro AI (in ai/micro_ais/mai-defs/) for an example of how to do this.
* The attacks aspect can now be defined as a Lua aspect. The code
should return a table with keys "own" and "enemy", each of which may
be either a unit filter table or a function which takes a unit as a
parameter and returns true or false.
* Wesnoth formula engine:
* Formulas in unit filters can now access nearly all unit attributes
* New syntax features:

View file

@ -30,6 +30,8 @@
#include "units/unit.hpp"
#include "pathfind/pathfind.hpp"
#include "units/filter.hpp"
#include "scripting/lua_api.hpp"
#include "lauxlib.h"
namespace ai {
@ -40,30 +42,35 @@ static lg::log_domain log_ai_testing_aspect_attacks("ai/aspect/attacks");
#define LOG_AI LOG_STREAM(info, log_ai_testing_aspect_attacks)
#define ERR_AI LOG_STREAM(err, log_ai_testing_aspect_attacks)
aspect_attacks::aspect_attacks(readonly_context &context, const config &cfg, const std::string &id)
aspect_attacks_base::aspect_attacks_base(readonly_context &context, const config &cfg, const std::string &id)
: typesafe_aspect<attacks_vector>(context,cfg,id)
{
}
aspect_attacks::aspect_attacks(readonly_context &context, const config &cfg, const std::string &id)
: aspect_attacks_base(context,cfg,id)
, filter_own_()
, filter_enemy_()
{
if (const config &filter_own = cfg.child("filter_own")) {
filter_own_ = filter_own;
vconfig vcfg(filter_own);
vcfg.make_safe();
filter_own_.reset(new unit_filter(vcfg, resources::filter_con));
}
if (const config &filter_enemy = cfg.child("filter_enemy")) {
filter_enemy_ = filter_enemy;
vconfig vcfg(filter_enemy);
vcfg.make_safe();
filter_enemy_.reset(new unit_filter(vcfg, resources::filter_con));
}
}
aspect_attacks::~aspect_attacks()
{
}
void aspect_attacks::recalculate() const
void aspect_attacks_base::recalculate() const
{
this->value_ = analyze_targets();
this->valid_ = true;
}
boost::shared_ptr<attacks_vector> aspect_attacks::analyze_targets() const
boost::shared_ptr<attacks_vector> aspect_attacks_base::analyze_targets() const
{
const move_map& srcdst = get_srcdst();
const move_map& dstsrc = get_dstsrc();
@ -74,10 +81,9 @@ boost::shared_ptr<attacks_vector> aspect_attacks::analyze_targets() const
unit_map& units_ = *resources::units;
std::vector<map_location> unit_locs;
const unit_filter filt_own(vconfig(filter_own_), resources::filter_con);
for(unit_map::const_iterator i = units_.begin(); i != units_.end(); ++i) {
if (i->side() == get_side() && i->attacks_left() && !(i->can_recruit() && get_passive_leader())) {
if (!filt_own(*i)) {
if (!is_allowed_attacker(*i)) {
continue;
}
unit_locs.push_back(i->get_location());
@ -93,7 +99,6 @@ boost::shared_ptr<attacks_vector> aspect_attacks::analyze_targets() const
unit_stats_cache().clear();
const unit_filter filt_en(vconfig(filter_enemy_), resources::filter_con);
for(unit_map::const_iterator j = units_.begin(); j != units_.end(); ++j) {
// Attack anyone who is on the enemy side,
@ -101,7 +106,7 @@ boost::shared_ptr<attacks_vector> aspect_attacks::analyze_targets() const
if (current_team().is_enemy(j->side()) && !j->incapacitated() &&
!j->invisible(j->get_location()))
{
if (!filt_en( *j)) {
if (!is_allowed_enemy(*j)) {
continue;
}
map_location adjacent[6];
@ -120,7 +125,7 @@ boost::shared_ptr<attacks_vector> aspect_attacks::analyze_targets() const
void aspect_attacks::do_attack_analysis(
void aspect_attacks_base::do_attack_analysis(
const map_location& loc,
const move_map& srcdst, const move_map& dstsrc,
const move_map& fullmove_srcdst, const move_map& fullmove_dstsrc,
@ -361,7 +366,7 @@ void aspect_attacks::do_attack_analysis(
}
}
int aspect_attacks::rate_terrain(const unit& u, const map_location& loc)
int aspect_attacks_base::rate_terrain(const unit& u, const map_location& loc)
{
const gamemap &map_ = resources::gameboard->map();
const t_translation::t_terrain terrain = map_.get_terrain(loc);
@ -396,15 +401,110 @@ int aspect_attacks::rate_terrain(const unit& u, const map_location& loc)
config aspect_attacks::to_config() const
{
config cfg = typesafe_aspect<attacks_vector>::to_config();
if (!filter_own_.empty()) {
cfg.add_child("filter_own",filter_own_);
if (filter_own_ && !filter_own_->empty()) {
cfg.add_child("filter_own", filter_own_->to_config());
}
if (!filter_enemy_.empty()) {
cfg.add_child("filter_enemy",filter_enemy_);
if (filter_enemy_ && !filter_enemy_->empty()) {
cfg.add_child("filter_enemy", filter_enemy_->to_config());
}
return cfg;
}
bool aspect_attacks::is_allowed_attacker(const unit& u) const
{
return (*filter_own_)(u);
}
bool aspect_attacks::is_allowed_enemy(const unit& u) const
{
return (*filter_enemy_)(u);
}
} // end of namespace testing_ai_default
aspect_attacks_lua::aspect_attacks_lua(readonly_context &context, const config &cfg, const std::string &id, boost::shared_ptr<lua_ai_context>& l_ctx)
: aspect_attacks_base(context, cfg, id)
, handler_(), code_(), params_(cfg.child_or_empty("args"))
{
this->name_ = "lua_aspect";
if (cfg.has_attribute("code"))
{
code_ = cfg["code"].str();
}
else if (cfg.has_attribute("value"))
{
code_ = "return " + cfg["value"].apply_visitor(lua_aspect_visitor());
}
else
{
// error
return;
}
handler_ = boost::shared_ptr<lua_ai_action_handler>(resources::lua_kernel->create_lua_ai_action_handler(code_.c_str(), *l_ctx));
}
void aspect_attacks_lua::recalculate() const
{
obj_.reset(new lua_object<aspect_attacks_lua_filter>);
handler_->handle(params_, true, obj_);
aspect_attacks_lua_filter filt = *obj_->get();
aspect_attacks_base::recalculate();
if(filt.lua) {
if(filt.ref_own_ != -1) {
luaL_unref(filt.lua, LUA_REGISTRYINDEX, filt.ref_own_);
}
if(filt.ref_enemy_ != -1) {
luaL_unref(filt.lua, LUA_REGISTRYINDEX, filt.ref_enemy_);
}
}
obj_.reset();
}
config aspect_attacks_lua::to_config() const
{
config cfg = aspect::to_config();
cfg["code"] = code_;
if (!params_.empty()) {
cfg.add_child("args", params_);
}
return cfg;
}
static bool call_lua_filter_fcn(lua_State* L, const unit& u, int idx)
{
lua_rawgeti(L, LUA_REGISTRYINDEX, idx);
new(lua_newuserdata(L, sizeof(lua_unit))) lua_unit(u.underlying_id());
lua_pushlightuserdata(L, getunitKey);
lua_rawget(L, LUA_REGISTRYINDEX);
lua_setmetatable(L, -2);
luaW_pcall(L, 1, 1);
bool result = luaW_toboolean(L, -1);
lua_pop(L, 1);
return result;
}
bool aspect_attacks_lua::is_allowed_attacker(const unit& u) const
{
const aspect_attacks_lua_filter& filt = *obj_->get();
if(filt.lua && filt.ref_own_ != -1) {
return call_lua_filter_fcn(filt.lua, u, filt.ref_own_);
} else if(filt.filter_own_) {
return (*filt.filter_own_)(u);
} else {
return true;
}
}
bool aspect_attacks_lua::is_allowed_enemy(const unit& u) const
{
const aspect_attacks_lua_filter& filt = *obj_->get();
if(filt.lua && filt.ref_enemy_ != -1) {
return call_lua_filter_fcn(filt.lua, u, filt.ref_enemy_);
} else if(filt.filter_enemy_) {
return (*filt.filter_enemy_)(u);
} else {
return true;
}
}
} // end of namespace ai

View file

@ -21,6 +21,7 @@
#define AI_TESTING_ASPECT_ATTACKS_HPP_INCLUDED
#include "ai/composite/aspect.hpp"
#include "units/filter.hpp"
#ifdef _MSC_VER
#pragma warning(push)
@ -33,18 +34,19 @@ namespace ai {
namespace ai_default_rca {
class aspect_attacks: public typesafe_aspect<attacks_vector> {
class aspect_attacks_base : public typesafe_aspect<attacks_vector> {
public:
aspect_attacks(readonly_context &context, const config &cfg, const std::string &id);
aspect_attacks_base(readonly_context &context, const config &cfg, const std::string &id);
virtual ~aspect_attacks();
virtual ~aspect_attacks_base() {}
virtual void recalculate() const;
virtual config to_config() const;
virtual bool is_allowed_attacker(const unit& u) const = 0;
virtual bool is_allowed_enemy(const unit& u) const = 0;
protected:
@ -60,14 +62,44 @@ protected:
attack_analysis& cur_analysis,
const team &current_team) const;
static int rate_terrain(const unit& u, const map_location& loc);
config filter_own_;
config filter_enemy_;
};
class aspect_attacks : public aspect_attacks_base {
public:
aspect_attacks(readonly_context &context, const config &cfg, const std::string &id);
virtual ~aspect_attacks() {}
virtual bool is_allowed_attacker(const unit& u) const;
virtual bool is_allowed_enemy(const unit& u) const;
virtual config to_config() const;
private:
boost::shared_ptr<unit_filter> filter_own_, filter_enemy_;
};
} // end of namespace testing_ai_default
struct aspect_attacks_lua_filter {
lua_State* lua;
boost::shared_ptr<unit_filter> filter_own_, filter_enemy_;
int ref_own_, ref_enemy_;
};
class aspect_attacks_lua : public ai_default_rca::aspect_attacks_base {
public:
aspect_attacks_lua(readonly_context &context, const config &cfg, const std::string &id, boost::shared_ptr<lua_ai_context>& l_ctx);
virtual ~aspect_attacks_lua() {}
virtual bool is_allowed_attacker(const unit& u) const;
virtual bool is_allowed_enemy(const unit& u) const;
virtual config to_config() const;
virtual void recalculate() const;
private:
boost::shared_ptr<lua_ai_action_handler> handler_;
mutable boost::shared_ptr<lua_object<aspect_attacks_lua_filter> > obj_;
std::string code_;
const config params_;
};
} // end of namespace ai
#ifdef _MSC_VER

View file

@ -59,6 +59,14 @@ unit_advancements_aspect::unit_advancements_aspect(const std::string& val): val
{
}
unit_advancements_aspect::~unit_advancements_aspect()
{
if(L_) {
// Remove the function from the registry
luaL_unref(L_, LUA_REGISTRYINDEX, ref_);
}
}
const std::vector<std::string> unit_advancements_aspect::get_advancements(const unit_map::const_iterator& unit) const
{

View file

@ -31,9 +31,7 @@ public:
unit_advancements_aspect(lua_State* L, int n);
unit_advancements_aspect(const std::string& val);
const std::vector<std::string> get_advancements(const unit_map::const_iterator& unit) const;
virtual ~unit_advancements_aspect()
{
}
virtual ~unit_advancements_aspect();
const std::string get_value() const;
private:

View file

@ -54,7 +54,7 @@ static char const aisKey = 0;
namespace ai {
static void push_attack_analysis(lua_State *L, attack_analysis&);
static void push_attack_analysis(lua_State *L, const attack_analysis&);
void lua_ai_context::init(lua_State *L)
{
@ -410,11 +410,11 @@ static int cfun_ai_get_attack_depth(lua_State *L)
static int cfun_ai_get_attacks(lua_State *L)
{
ai::attacks_vector attacks = get_readonly_context(L).get_attacks();
const ai::attacks_vector& attacks = get_readonly_context(L).get_attacks();
lua_createtable(L, attacks.size(), 0);
int table_index = lua_gettop(L);
ai::attacks_vector::iterator it = attacks.begin();
ai::attacks_vector::const_iterator it = attacks.begin();
for (int i = 1; it != attacks.end(); ++it, ++i)
{
push_attack_analysis(L, *it);
@ -546,7 +546,7 @@ static int cfun_attack_rating(lua_State *L)
// the attack_analysis table should be on top of the stack
lua_getfield(L, -1, "att_ptr"); // [-2: attack_analysis; -1: pointer to attack_analysis object in c++]
// now the pointer to our attack_analysis C++ object is on top
attack_analysis* aa_ptr = static_cast< attack_analysis * >(lua_touserdata(L, -1));
const attack_analysis* aa_ptr = static_cast< attack_analysis * >(lua_touserdata(L, -1));
//[-2: attack_analysis; -1: pointer to attack_analysis object in c++]
@ -586,13 +586,13 @@ static void push_movements(lua_State *L, const std::vector< std::pair < map_loca
}
static void push_attack_analysis(lua_State *L, attack_analysis& aa)
static void push_attack_analysis(lua_State *L, const attack_analysis& aa)
{
lua_newtable(L);
// Pushing a pointer to the current object
lua_pushstring(L, "att_ptr");
lua_pushlightuserdata(L, &aa);
lua_pushlightuserdata(L, const_cast<attack_analysis*>(&aa));
lua_rawset(L, -3);
// Registering callback function for the rating method

View file

@ -18,7 +18,15 @@
*/
#include "lua_object.hpp"
#include "ai/lua/lua_object.hpp"
#include "ai/lua/engine_lua.hpp"
#include "ai/default/aspect_attacks.hpp"
#include "scripting/lua_types.hpp"
#include "scripting/lua_common.hpp"
#include "resources.hpp"
#include <boost/smart_ptr/make_shared_object.hpp>
#include "lauxlib.h"
namespace ai {
@ -26,5 +34,42 @@ namespace ai {
{
// empty
}
template <>
boost::shared_ptr<aspect_attacks_lua_filter> lua_object<aspect_attacks_lua_filter>::to_type(lua_State *L, int n)
{
boost::shared_ptr<aspect_attacks_lua_filter> att(new aspect_attacks_lua_filter);
att->lua = NULL;
att->ref_own_ = att->ref_enemy_ = -1;
if(!lua_istable(L, n)) {
return att;
}
lua_getfield(L, n, "own");
if(lua_istable(L, -1)) {
config cfg;
vconfig vcfg(cfg, true);
if(luaW_tovconfig(L, -1, vcfg)) {
att->filter_own_.reset(new unit_filter(vcfg, resources::filter_con));
}
} else if(lua_isfunction(L, -1)) {
att->lua = L;
att->ref_own_ = luaL_ref(L, LUA_REGISTRYINDEX);
assert(att->ref_own_ != -1);
}
lua_getfield(L, n, "enemy");
if(lua_istable(L, -1)) {
config cfg;
vconfig vcfg(cfg, true);
if(luaW_tovconfig(L, -1, vcfg)) {
att->filter_enemy_.reset(new unit_filter(vcfg, resources::filter_con));
}
} else if(lua_isfunction(L, -1)) {
att->lua = L;
att->ref_enemy_ = luaL_ref(L, LUA_REGISTRYINDEX);
assert(att->ref_enemy_ != -1);
}
lua_pop(L, 2);
return att;
}
} //end of namespace ai

View file

@ -69,7 +69,7 @@ public:
void store(lua_State* L, int n)
{
this->value_ = boost::shared_ptr<T>(to_type(L, n));
this->value_ = boost::shared_ptr<T>(to_type(L, lua_absindex(L, n)));
}
protected:
@ -200,6 +200,11 @@ inline boost::shared_ptr<unit_advancements_aspect> lua_object<unit_advancements_
boost::shared_ptr<unit_advancements_aspect> uaa = boost::shared_ptr<unit_advancements_aspect>(new unit_advancements_aspect(L, n));
return uaa;
}
// This one is too complex to define in the header.
struct aspect_attacks_lua_filter;
template <>
boost::shared_ptr<aspect_attacks_lua_filter> lua_object<aspect_attacks_lua_filter>::to_type(lua_State *L, int n);
} // end of namespace ai

View file

@ -419,6 +419,9 @@ static register_lua_aspect_factory< lua_aspect<double> >
static register_lua_aspect_factory< lua_aspect<int> >
attack_depth__lua_aspect_factory("attack_depth*lua_aspect");
static register_lua_aspect_factory< aspect_attacks_lua >
attacks__lua_aspect_factory("attacks*lua_aspect");
static register_lua_aspect_factory< lua_aspect<terrain_filter> >
avoid__lua_aspect_factory("avoid*lua_aspect");

View file

@ -48,6 +48,11 @@ static lg::log_domain log_config("config");
#define ERR_CF LOG_STREAM(err, log_config)
#define DBG_CF LOG_STREAM(debug, log_config)
// Defined out of line to avoid including config in unit_filter.hpp
config unit_filter::to_config() const {
return impl_->to_config();
}
///Defined out of line to prevent including unit at unit_filter.hpp
bool unit_filter::matches(const unit & u) const {
return matches (u, u.get_location());
@ -95,6 +100,14 @@ public:
virtual ~null_unit_filter_impl() {}
config to_config() const {
return config();
}
bool empty() const {
return true;
}
private:
const filter_context & fc_;
@ -159,6 +172,9 @@ public:
virtual bool matches(const unit & u, const map_location & loc, const unit * u2) const;
virtual std::vector<const unit *> all_matches_on_map(unsigned max_matches) const;
virtual unit_const_ptr first_match_on_map() const;
config to_config() const {
return vcfg.get_config();
}
virtual ~basic_unit_filter_impl() {}
private:

View file

@ -33,6 +33,7 @@
class filter_context;
class unit;
class config;
class vconfig;
struct map_location;
@ -41,6 +42,8 @@ public:
virtual bool matches(const unit & u, const map_location & loc, const unit * u2 = NULL) const = 0;
virtual std::vector<const unit*> all_matches_on_map(unsigned max_matches) const = 0;
virtual unit_const_ptr first_match_on_map() const = 0;
virtual config to_config() const = 0;
virtual bool empty() const {return false;}
virtual ~unit_filter_abstract_impl() {}
};
@ -95,6 +98,12 @@ public:
unit_const_ptr first_match_on_map() const {
return impl_->first_match_on_map();
}
config to_config() const;
bool empty() const {
return impl_->empty();
}
private:
boost::shared_ptr<unit_filter_abstract_impl> impl_;
unsigned max_matches_;