Add new aspect 'advancements'

As part of a GSoC proposal I added a new aspect so a scenario editor can control advancements in two ways:
1. Define a aspect with a string-value like "Swordsman, Knight", so the units of interesst will always advance to this
2. Use the LUA-Engine and return a function of the form advance(x, y) which will itself return a string-value
    like "Swordsman, Knight". Everytime a ai-unit advances advance(x, y) will be called.
The corresponding wikipage (http://wiki.wesnoth.org/AiWML) is going to be updated soon.
This commit is contained in:
flix/Felix Bauer 2013-04-22 16:18:46 +03:00 committed by flix
parent f9a231f09d
commit 29e8584738
21 changed files with 374 additions and 17 deletions

View file

@ -22,6 +22,10 @@ Version 1.11.4+dev:
* Added a new playlist FULL_MUSIC_PLAYLIST, which contains all Wesnoth tracks
in alphabetical order
* Added -Wno-null-conversion to the CMake pedantic flags.
* WML engine:
* Added new aspect 'advancements' which with lua engine can handle a
function return type of the form f(x, y) -> String. 'advancements'
tells the AI to what unit a given unit should advance to.
Version 1.11.4:
* AI:

View file

@ -40,6 +40,7 @@
[/aspect]
#enddef
{DEFAULT_ASPECT_EMPTY advancements}
{DEFAULT_ASPECT_VALUE aggression 0.4}
{DEFAULT_ASPECT_VALUE attack_depth 5}
[aspect]

View file

@ -989,6 +989,9 @@
[entry]
name = "Fedor Khod'kov (teddy/fkhodkov)"
[/entry]
[entry]
name = "Felix Bauer (flix)"
[/entry]
[entry]
name = "Francesco Gigli (Jaramir)"
[/entry]

View file

@ -864,7 +864,7 @@ def local_sanity_check(filename, nav, key, prefix, value, comment):
"recruitment_ignore_bad_combat",
"recruitment_pattern",
"villages_per_scout", "leader_value", "village_value",
"aggression", "caution", "attack_depth", "grouping"):
"aggression", "caution", "attack_depth", "grouping", "advancements"):
print errlead + key + " outside [ai] scope"
# Bad [recruit] attribute
if parent in ("[allow_recruit]", "[disallow_recruit]") and key == "recruit":

View file

@ -3050,6 +3050,62 @@
RelativePath="..\..\src\ai\lua\lua_object.hpp"
>
</File>
<File
RelativePath="..\..\src\ai\lua\unit_advancements_aspect.cpp"
>
<FileConfiguration
Name="Debug|Win32"
>
<Tool
Name="VCCLCompilerTool"
ObjectFile="$(IntDir)\ai\lua\"
/>
</FileConfiguration>
<FileConfiguration
Name="Release|Win32"
>
<Tool
Name="VCCLCompilerTool"
ObjectFile="$(IntDir)\ai\lua\"
/>
</FileConfiguration>
<FileConfiguration
Name="Debug_with_VLD|Win32"
>
<Tool
Name="VCCLCompilerTool"
ObjectFile="$(IntDir)\ai\lua\"
/>
</FileConfiguration>
<FileConfiguration
Name="Test_Debug|Win32"
>
<Tool
Name="VCCLCompilerTool"
ObjectFile="$(IntDir)\ai\lua\"
/>
</FileConfiguration>
<FileConfiguration
Name="Test_Release|Win32"
>
<Tool
Name="VCCLCompilerTool"
ObjectFile="$(IntDir)\ai\lua\"
/>
</FileConfiguration>
<FileConfiguration
Name="ReleaseDEBUG|Win32"
>
<Tool
Name="VCCLCompilerTool"
ObjectFile="$(IntDir)\ai\lua\"
/>
</FileConfiguration>
</File>
<File
RelativePath="..\..\src\ai\lua\unit_advancements_aspect.hpp"
>
</File>
</Filter>
<Filter
Name="akihara"

View file

@ -631,6 +631,7 @@ set(wesnoth-main_SRC
ai/interface.cpp
ai/lua/core.cpp
ai/lua/lua_object.cpp
ai/lua/unit_advancements_aspect.cpp
ai/manager.cpp
ai/registry.cpp
ai/testing.cpp

View file

@ -203,6 +203,7 @@ wesnoth_sources = Split("""
ai/interface.cpp
ai/lua/core.cpp
ai/lua/lua_object.cpp
ai/lua/unit_advancements_aspect.cpp
ai/manager.cpp
ai/registry.cpp
ai/testing.cpp

View file

@ -179,8 +179,8 @@ team& action_result::get_my_team() const
// attack_result
attack_result::attack_result( side_number side, const map_location& attacker_loc, const map_location& defender_loc, int attacker_weapon, double aggression)
: action_result(side), attacker_loc_(attacker_loc), defender_loc_(defender_loc), attacker_weapon_(attacker_weapon), aggression_(aggression){
attack_result::attack_result( side_number side, const map_location& attacker_loc, const map_location& defender_loc, int attacker_weapon, double aggression, const unit_advancements_aspect& advancements)
: action_result(side), attacker_loc_(attacker_loc), defender_loc_(defender_loc), attacker_weapon_(attacker_weapon), aggression_(aggression), advancements_(advancements){
}
@ -302,7 +302,8 @@ void attack_result::do_execute()
}
recorder.add_seed("attack", rand_rng::get_last_seed());
attack_unit(attacker_loc_, defender_loc_, attacker_weapon, defender_weapon);
dialogs::advance_unit(attacker_loc_, true);
dialogs::advance_unit(attacker_loc_, true, false, advancements_);
const unit_map::const_iterator defender = resources::units->find(defender_loc_);
if(defender != resources::units->end()) {
@ -922,9 +923,10 @@ attack_result_ptr actions::execute_attack_action( side_number side,
const map_location& attacker_loc,
const map_location& defender_loc,
int attacker_weapon,
double aggression)
double aggression,
const unit_advancements_aspect& advancements)
{
attack_result_ptr action(new attack_result(side,attacker_loc,defender_loc,attacker_weapon,aggression));
attack_result_ptr action(new attack_result(side,attacker_loc,defender_loc,attacker_weapon,aggression,advancements));
execute ? action->execute() : action->check_before();
return action;
}

View file

@ -23,6 +23,7 @@
#include "game_info.hpp"
#include "../actions/move.hpp"
#include "lua/unit_advancements_aspect.hpp"
namespace pathfind {
struct plain_route;
@ -130,7 +131,8 @@ public:
const map_location& attacker_loc,
const map_location& defender_loc,
int attacker_weapon,
double aggression );
double aggression,
const unit_advancements_aspect& advancements = unit_advancements_aspect());
enum tresult {
E_EMPTY_ATTACKER = 1001,
@ -156,6 +158,7 @@ private:
const map_location& defender_loc_;
int attacker_weapon_;
double aggression_;
const unit_advancements_aspect& advancements_;
};
class move_result : public action_result {
@ -314,7 +317,8 @@ static attack_result_ptr execute_attack_action( side_number side,
const map_location& attacker_loc,
const map_location& defender_loc,
int attacker_weapon,
double aggression );
double aggression,
const unit_advancements_aspect& advancements = unit_advancements_aspect());
/**

View file

@ -27,6 +27,7 @@
#include "../../util.hpp"
#include "../../serialization/string_utils.hpp"
#include "../../resources.hpp"
#include "../lua/unit_advancements_aspect.hpp"
namespace ai {
@ -202,6 +203,35 @@ public:
}
};
template<>
class config_value_translator<unit_advancements_aspect> {
public:
static unit_advancements_aspect cfg_to_value(const config &cfg)
{
return unit_advancements_aspect(cfg["value"]);
}
static void cfg_to_value(const config &cfg, unit_advancements_aspect &value)
{
value = cfg_to_value(cfg);
}
static void value_to_cfg(const unit_advancements_aspect &value, config &cfg)
{
cfg["value"] = value.get_value();
}
static config value_to_cfg(const unit_advancements_aspect &value)
{
config cfg;
value_to_cfg(value,cfg);
return cfg;
}
};
// variant value translator

View file

@ -59,6 +59,7 @@ void configuration::init(const config &game_config)
ai_configurations_.clear();
era_ai_configurations_.clear();
well_known_aspects.clear();
well_known_aspects.push_back(well_known_aspect("advancements"));
well_known_aspects.push_back(well_known_aspect("aggression"));
well_known_aspects.push_back(well_known_aspect("attack_depth"));
well_known_aspects.push_back(well_known_aspect("attacks"));

View file

@ -90,14 +90,16 @@ team& readwrite_context_impl::current_team_w()
attack_result_ptr readwrite_context_impl::execute_attack_action(const map_location& attacker_loc, const map_location& defender_loc, int attacker_weapon){
unit_map::iterator i = resources::units->find(attacker_loc);
double m_aggression = i.valid() && i->can_recruit() ? get_leader_aggression() : get_aggression();
return actions::execute_attack_action(get_side(),true,attacker_loc,defender_loc,attacker_weapon, m_aggression);
const unit_advancements_aspect& m_advancements = get_advancements();
return actions::execute_attack_action(get_side(),true,attacker_loc,defender_loc,attacker_weapon, m_aggression, m_advancements);
}
attack_result_ptr readonly_context_impl::check_attack_action(const map_location& attacker_loc, const map_location& defender_loc, int attacker_weapon){
unit_map::iterator i = resources::units->find(attacker_loc);
double m_aggression = i.valid() && i->can_recruit() ? get_leader_aggression() : get_aggression();
return actions::execute_attack_action(get_side(),false,attacker_loc,defender_loc,attacker_weapon, m_aggression);
const unit_advancements_aspect& m_advancements = get_advancements();
return actions::execute_attack_action(get_side(),false,attacker_loc,defender_loc,attacker_weapon, m_aggression, m_advancements);
}
@ -152,6 +154,7 @@ readonly_context_impl::readonly_context_impl(side_context &context, const config
: cfg_(cfg),
engines_(),
known_aspects_(),
advancements_(),
aggression_(),
attack_depth_(),
aspects_(),
@ -195,6 +198,7 @@ readonly_context_impl::readonly_context_impl(side_context &context, const config
init_side_context_proxy(context);
manager::add_gamestate_observer(this);
add_known_aspect("advancements", advancements_);
add_known_aspect("aggression",aggression_);
add_known_aspect("attack_depth",attack_depth_);
add_known_aspect("attacks",attacks_);
@ -491,6 +495,17 @@ std::map<map_location,defensive_position>& readonly_context_impl::defensive_posi
}
const unit_advancements_aspect& readonly_context_impl::get_advancements() const
{
if (advancements_) {
return advancements_->get();
}
static unit_advancements_aspect uaa = unit_advancements_aspect();
return uaa;
}
double readonly_context_impl::get_aggression() const
{
if (aggression_) {
@ -842,6 +857,7 @@ int readonly_context_impl::get_villages_per_scout() const
return 0;
}
bool readonly_context_impl::is_dst_src_valid_lua() const
{
return dst_src_valid_lua_;

View file

@ -24,6 +24,7 @@
#include "game_info.hpp"
#include "../generic_event.hpp"
#include "../config.hpp"
#include "lua/unit_advancements_aspect.hpp"
//#include "../unit.hpp"
@ -207,6 +208,9 @@ public:
virtual std::map<map_location,defensive_position>& defensive_position_cache() const = 0;
virtual const unit_advancements_aspect& get_advancements() const = 0;
virtual double get_aggression() const = 0;
@ -326,6 +330,7 @@ public:
virtual int get_villages_per_scout() const = 0;
virtual bool is_active(const std::string &time_of_day, const std::string &turns) const = 0;
virtual bool is_dst_src_valid_lua() const = 0;
@ -607,6 +612,12 @@ public:
}
virtual const unit_advancements_aspect& get_advancements() const
{
return target_->get_advancements();
}
virtual double get_aggression() const
{
return target_->get_aggression();
@ -1264,6 +1275,9 @@ public:
virtual std::map<map_location,defensive_position>& defensive_position_cache() const;
virtual const unit_advancements_aspect& get_advancements() const;
virtual double get_aggression() const;
@ -1449,6 +1463,7 @@ private:
known_aspect_map known_aspects_;
aspect_type< unit_advancements_aspect >::typesafe_ptr advancements_;
aspect_type<double>::typesafe_ptr aggression_;
aspect_type<int>::typesafe_ptr attack_depth_;
aspect_map aspects_;

View file

@ -31,6 +31,7 @@
#include "../default/contexts.hpp"
#include "terrain_filter.hpp"
#include "resources.hpp"
#include "unit_advancements_aspect.hpp"
namespace ai {
@ -190,6 +191,12 @@ inline boost::shared_ptr<std::vector<target> > lua_object< std::vector<target> >
return targets;
}
template <>
inline boost::shared_ptr<unit_advancements_aspect> lua_object<unit_advancements_aspect>::to_type(lua_State *L, int n)
{
boost::shared_ptr<unit_advancements_aspect> uaa = boost::shared_ptr<unit_advancements_aspect>(new unit_advancements_aspect(L, n));
return uaa;
}
} // end of namespace ai

View file

@ -0,0 +1,130 @@
/*
Copyright (C) 2013 by Felix Bauer <fehlxb+wesnoth@gmail.com>
Part of the Battle for Wesnoth Project http://www.wesnoth.org/
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.
See the COPYING file for more details.
*/
#include "unit_advancements_aspect.hpp"
#include "../../log.hpp"
#include "lua/lualib.h"
#include "lua/lauxlib.h"
#include <string>
#include <vector>
#include "scripting/lua.hpp"
#include "scripting/lua_api.hpp"
#include "global.hpp"
#include "../../unit.hpp"
#include "../../map.hpp"
static lg::log_domain log_ai_engine_lua("ai/engine/lua");
#define LOG_LUA LOG_STREAM(info, log_ai_engine_lua)
#define ERR_LUA LOG_STREAM(err, log_ai_engine_lua)
namespace ai{
unit_advancements_aspect::unit_advancements_aspect():
val_(), L_(),ref_()
{
}
unit_advancements_aspect::unit_advancements_aspect(lua_State* L, int n)
{
val_ = "Lua Function";
L_ = L;
lua_settop(L, n);
//on the top of the Lua-Stack is now the pointer to the function. Save it:
ref_ = luaL_ref(L, LUA_REGISTRYINDEX);
}
unit_advancements_aspect::unit_advancements_aspect(const std::string& val): val_(val), L_(), ref_()
{
}
const std::vector<std::string> unit_advancements_aspect::get_advancements(const unit_map::const_iterator& unit) const
{
if(!unit.valid())
{
return std::vector<std::string>();
}
const std::string& unit_id = (*unit).id();
const int unit_x = (*unit).get_location().x + 1;
const int unit_y = (*unit).get_location().y + 1;
LOG_LUA << "Entering unit_advancements_aspect::get_advancements() in instance " << this << " with unit " << unit_id << " on (x,y) = (" << unit_x << ", " << unit_y << ")\n";
if(L_ == NULL || ref_ == LUA_REFNIL)
{
//If we end up here, most likely the aspect don't use the lua-engine.
//Just to make sure:
if (val_ == "Lua Function")
{
return std::vector<std::string>();
}
return utils::split(val_);
}
//put the Pointer back on the Stack
lua_rawgeti(L_, LUA_REGISTRYINDEX, ref_);
if(lua_isstring(L_, -1))
{
return utils::split(lua_tostring(L_, -1));
}
if(!lua_isfunction(L_, -1))
{
ERR_LUA << "Can't evaluate advancement aspect: Value is neither a string nor a function.\n";
return std::vector<std::string>();
}
//push parameter to the stack
lua_pushinteger(L_, unit_x);
lua_pushinteger(L_, unit_y);
//To make unit_id a Parameter of the Lua function:
//lua_pushfstring(L_, unit_id.c_str());
//call function
if(lua_pcall(L_, 2, 1, 0) != 0)
{
ERR_LUA << "LUA Error while evaluating advancements_aspect: " << lua_tostring(L_, -1) << "\n";
return std::vector<std::string>();
}
if (!lua_isstring(L_, -1))
{
ERR_LUA << "LUA Error while evaluating advancements_aspect: Function must return String \n";
return std::vector<std::string>();
}
//get result from Lua-Stack
const std::string retval = std::string(lua_tostring(L_, -1));
lua_pop(L_, 1);
LOG_LUA << "Called Lua advancement function. Result was: \"" << retval << "\".\n";
return utils::split(retval);
}
const std::string unit_advancements_aspect::get_value() const
{
return val_;
}
}

View file

@ -0,0 +1,46 @@
/*
Copyright (C) 2013 by Felix Bauer <fehlxb+wesnoth@gmail.com>
Part of the Battle for Wesnoth Project http://www.wesnoth.org/
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.
See the COPYING file for more details.
*/
#ifndef UNIT_ADVANCEMENT_ACPECT_H_INCLUDED
#define UNIT_ADVANCEMENT_ACPECT_H_INCLUDED
#include <string>
#include <vector>
#include "lua/lualib.h"
#include "../../unit_map.hpp"
#include "../../config.hpp"
namespace ai {
class unit_advancements_aspect
{
public:
unit_advancements_aspect();
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()
{
}
const std::string get_value() const;
private:
std::string val_;
lua_State * L_;
int ref_;
};
}
#endif

View file

@ -234,6 +234,7 @@ const std::string holder::get_ai_overview()
get_ai_ref();
}
std::stringstream s;
s << "advancements: " << this->ai_->get_advancements().get_value() << std::endl;
s << "aggression: " << this->ai_->get_aggression() << std::endl;
s << "attack_depth: " << this->ai_->get_attack_depth() << std::endl;
s << "caution: " << this->ai_->get_caution() << std::endl;
@ -259,6 +260,7 @@ const std::string holder::get_ai_overview()
s << "support_villages: " << this->ai_->get_support_villages() << std::endl;
s << "village_value: " << this->ai_->get_village_value() << std::endl;
s << "villages_per_scout: " << this->ai_->get_villages_per_scout() << std::endl;
return s.str();
}

View file

@ -34,6 +34,7 @@
#include "testing/stage_rca.hpp"
#include "testing/stage_fallback.hpp"
#include "akihara/recruitment.hpp"
#include "lua/unit_advancements_aspect.hpp"
namespace ai {
// =======================================================================
@ -224,6 +225,10 @@ static register_goal_factory<lua_goal>
// =======================================================================
//name=composite_aspect
static register_aspect_factory< composite_aspect< unit_advancements_aspect > >
advancements__composite_aspect_factory("advancements*composite_aspect");
static register_aspect_factory< composite_aspect<double> >
aggression__composite_aspect_factory("aggression*composite_aspect");
@ -292,6 +297,9 @@ static register_aspect_factory< composite_aspect<int> >
//name=standard_aspect
static register_aspect_factory< standard_aspect< unit_advancements_aspect > >
advancements__standard_aspect_factory("advancements*standard_aspect");
static register_aspect_factory< standard_aspect<double> >
aggression__standard_aspect_factory("aggression*standard_aspect");
@ -358,11 +366,15 @@ static register_aspect_factory< standard_aspect<double> >
static register_aspect_factory< standard_aspect<int> >
villages_per_scout__standard_aspect_factory("villages_per_scout*standard_aspect");
// Also keep the old syntax
static register_aspect_factory< testing_ai_default::aspect_attacks >
old_attacks__testing_ai_default_aspect_attacks_factory("attacks*testing_ai_default::aspect_attacks");
//name = default
static register_aspect_factory< standard_aspect< unit_advancements_aspect > >
advancements__standard_aspect_factory2("advancements*");
static register_aspect_factory< standard_aspect<double> >
aggression__standard_aspect_factory2("aggression*");
@ -429,7 +441,11 @@ static register_aspect_factory< standard_aspect<double> >
static register_aspect_factory< standard_aspect<int> >
villages_per_scout__standard_aspect_factory2("villages_per_scout*");
//name = lua
static register_lua_aspect_factory< lua_aspect< unit_advancements_aspect > >
advancements__lua_aspect_factory("advancements*lua_aspect");
static register_lua_aspect_factory< lua_aspect<double> >
aggression__lua_aspect_factory("aggression*lua_aspect");

View file

@ -213,6 +213,7 @@ void recruitment_phase::execute()
analyze_potential_recruit_combat();
std::vector<std::string> options = get_recruitment_pattern();
if (std::count(options.begin(), options.end(), "scout") > 0) {
size_t neutral_villages = 0;
@ -527,6 +528,8 @@ combat_phase::~combat_phase()
double combat_phase::evaluate()
{
std::vector<std::string> options = get_recruitment_pattern();
choice_rating_ = -1000.0;
int ticks = SDL_GetTicks();

View file

@ -50,6 +50,7 @@
#include "formula_string_utils.hpp"
#include "gui/dialogs/game_save.hpp"
#include "gui/dialogs/transient_message.hpp"
#include "ai/lua/unit_advancements_aspect.hpp"
#include <boost/foreach.hpp>
@ -206,7 +207,7 @@ int advance_unit_dialog(const map_location &loc)
return 0;
}
void advance_unit(const map_location &loc, bool random_choice, bool add_replay_event)
void advance_unit(const map_location &loc, bool automatic, bool add_replay_event, const ai::unit_advancements_aspect& advancements)
{
unit_map::iterator u = resources::units->find(loc);
if(!unit_helper::will_certainly_advance(u)) {
@ -218,12 +219,24 @@ void advance_unit(const map_location &loc, bool random_choice, bool add_replay_e
int res;
if (random_choice) {
if (automatic) {
//if the advancements are empty or don't match any option
//choose random instead.
res = rand() % unit_helper::number_of_possible_advances(*u);
const std::vector<std::string>& options = u->advances_to();
const std::vector<std::string>& allowed = advancements.get_advancements(u);
for(std::vector<std::string>::const_iterator a = options.begin(); a != options.end(); ++a) {
if (std::find(allowed.begin(), allowed.end(), *a) != allowed.end()){
res = a - options.begin();
break;
}
}
} else {
res = advance_unit_dialog(loc);
}
if(add_replay_event) {
recorder.add_advancement(loc);
}
@ -244,7 +257,7 @@ void advance_unit(const map_location &loc, bool random_choice, bool add_replay_e
if (u->experience() < 81) {
// For all leveling up we have to add advancement to replay here because replay
// doesn't handle cascading advancement since it just calls animate_unit_advancement().
advance_unit(loc, random_choice, true);
advance_unit(loc, automatic, true, advancements);
} else {
ERR_CF << "Unit has too many (" << u->experience()
<< ") XP left; cascade leveling disabled.\n";

View file

@ -25,13 +25,19 @@ class unit_type;
#include "map_location.hpp"
#include "construct_dialog.hpp"
#include "network.hpp"
#include "ai/lua/unit_advancements_aspect.hpp"
namespace dialogs {
/**
* Function to handle an advancing unit. If there is only one choice to advance
* to, the unit will be automatically advanced. If there is a choice, and
* 'random_choice' is true, then a unit will be selected at random. Otherwise,
* to, the unit will be automatically advanced.
* If 'automatic' is true,
* a unit will be selected at by given 'advancements' or at random when
* 'advancements' is empty or don't match any possible advancement options.
*
* If 'automatic' is false,
* a dialog will be displayed asking the user what to advance to.
*
* Note that 'loc' is not a reference, because deleting an item from the units
@ -42,7 +48,7 @@ namespace dialogs {
* cause for advancement is different (eg unstore_unit) this routine
* should _not_ be used.
*/
void advance_unit(const map_location &loc, bool random_choice = false, bool add_replay_event = false);
void advance_unit(const map_location &loc, bool automatic = false, bool add_replay_event = false, const ai::unit_advancements_aspect& advancements = ai::unit_advancements_aspect());
/**
* Lets the user to select a unit advancement. This should always be used