wesnoth/src/ai/configuration.cpp
flix 4959db0952 Add aspects for new recruitment CA.
The aspects are:
- recruitment_diversity
- recruitment_instructions
- recruitment_more
- recruitment_randomness
- recruitment_save_gold

All aspects will only work with the new recruitment CA (not committed yet).
See http://wiki.wesnoth.org/AI_Recruitment
2013-09-23 17:09:26 +02:00

546 lines
19 KiB
C++

/*
Copyright (C) 2009 - 2013 by Yurii Chernyi <terraninfo@terraninfo.net>
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.
*/
/**
* Managing the AI configuration
* @file
*/
#include "configuration.hpp"
#include "../filesystem.hpp"
#include "../log.hpp"
#include "../serialization/parser.hpp"
#include "../serialization/preprocessor.hpp"
#include "../team.hpp"
#include "wml_exception.hpp"
#include <boost/foreach.hpp>
#include <vector>
namespace ai {
static lg::log_domain log_ai_configuration("ai/config");
#define DBG_AI_CONFIGURATION LOG_STREAM(debug, log_ai_configuration)
#define LOG_AI_CONFIGURATION LOG_STREAM(info, log_ai_configuration)
#define WRN_AI_CONFIGURATION LOG_STREAM(warn, log_ai_configuration)
#define ERR_AI_CONFIGURATION LOG_STREAM(err, log_ai_configuration)
class well_known_aspect {
public:
well_known_aspect(const std::string &name, bool attr = true)
: name_(name),was_an_attribute_(attr)
{
}
virtual ~well_known_aspect() {};
std::string name_;
bool was_an_attribute_;
};
std::vector<well_known_aspect> well_known_aspects;
void configuration::init(const config &game_config)
{
ai_configurations_.clear();
era_ai_configurations_.clear();
mod_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"));
well_known_aspects.push_back(well_known_aspect("avoid",false));
well_known_aspects.push_back(well_known_aspect("caution"));
well_known_aspects.push_back(well_known_aspect("grouping"));
well_known_aspects.push_back(well_known_aspect("leader_aggression"));
well_known_aspects.push_back(well_known_aspect("leader_goal",false));
well_known_aspects.push_back(well_known_aspect("leader_ignores_keep"));
well_known_aspects.push_back(well_known_aspect("leader_value"));
well_known_aspects.push_back(well_known_aspect("number_of_possible_recruits_to_force_recruit"));
well_known_aspects.push_back(well_known_aspect("passive_leader"));
well_known_aspects.push_back(well_known_aspect("passive_leader_shares_keep"));
well_known_aspects.push_back(well_known_aspect("recruitment"));
well_known_aspects.push_back(well_known_aspect("recruitment_diversity"));
well_known_aspects.push_back(well_known_aspect("recruitment_ignore_bad_combat"));
well_known_aspects.push_back(well_known_aspect("recruitment_ignore_bad_movement"));
well_known_aspects.push_back(well_known_aspect("recruitment_instructions"));
well_known_aspects.push_back(well_known_aspect("recruitment_more"));
well_known_aspects.push_back(well_known_aspect("recruitment_pattern"));
well_known_aspects.push_back(well_known_aspect("recruitment_randomness"));
well_known_aspects.push_back(well_known_aspect("recruitment_save_gold"));
well_known_aspects.push_back(well_known_aspect("scout_village_targeting"));
well_known_aspects.push_back(well_known_aspect("simple_targeting"));
well_known_aspects.push_back(well_known_aspect("support_villages"));
well_known_aspects.push_back(well_known_aspect("village_value"));
well_known_aspects.push_back(well_known_aspect("villages_per_scout"));
const config &ais = game_config.child("ais");
default_config_ = ais.child("default_config");
if (!default_config_) {
ERR_AI_CONFIGURATION << "Missing AI [default_config]. Therefore, default_config_ set to empty." << std::endl;
default_config_ = config();
}
BOOST_FOREACH(const config &ai_configuration, ais.child_range("ai")) {
const std::string &id = ai_configuration["id"];
if (id.empty()){
ERR_AI_CONFIGURATION << "skipped AI config due to missing id" << ". Config contains:"<< std::endl << ai_configuration << std::endl;
continue;
}
if (ai_configurations_.count(id)>0){
ERR_AI_CONFIGURATION << "skipped AI config due to duplicate id [" << id << "]. Config contains:"<< std::endl << ai_configuration << std::endl;
continue;
}
description desc;
desc.id=id;
desc.text = ai_configuration["description"].str();
desc.cfg=ai_configuration;
ai_configurations_.insert(std::make_pair(id,desc));
LOG_AI_CONFIGURATION << "loaded AI config: " << ai_configuration["description"] << std::endl;
}
}
namespace {
void extract_ai_configurations(std::map<std::string, description> &storage, const config &input)
{
BOOST_FOREACH(const config &ai_configuration, input.child_range("ai")) {
const std::string &id = ai_configuration["id"];
if (id.empty()){
ERR_AI_CONFIGURATION << "skipped AI config due to missing id" << ". Config contains:"<< std::endl << ai_configuration << std::endl;
continue;
}
if (storage.count(id)>0){
ERR_AI_CONFIGURATION << "skipped AI config due to duplicate id [" << id << "]. Config contains:"<< std::endl << ai_configuration << std::endl;
continue;
}
description desc;
desc.id=id;
desc.text = ai_configuration["description"].str();
desc.cfg=ai_configuration;
storage.insert(std::make_pair(id,desc));
LOG_AI_CONFIGURATION << "loaded AI config: " << ai_configuration["description"] << std::endl;
}
}
}
void configuration::add_era_ai_from_config(const config &era)
{
era_ai_configurations_.clear();
extract_ai_configurations(era_ai_configurations_, era);
}
void configuration::add_mod_ai_from_config(config::const_child_itors mods)
{
mod_ai_configurations_.clear();
BOOST_FOREACH(const config &mod, mods) {
extract_ai_configurations(mod_ai_configurations_, mod);
}
}
std::vector<description*> configuration::get_available_ais(){
std::vector<description*> ais_list;
for(description_map::iterator desc = ai_configurations_.begin(); desc!=ai_configurations_.end(); ++desc) {
ais_list.push_back(&desc->second);
DBG_AI_CONFIGURATION << "has ai with config: "<< std::endl << desc->second.cfg<< std::endl;
}
for(description_map::iterator desc = era_ai_configurations_.begin(); desc!=era_ai_configurations_.end(); ++desc) {
ais_list.push_back(&desc->second);
DBG_AI_CONFIGURATION << "has ai with config: "<< std::endl << desc->second.cfg<< std::endl;
}
for(description_map::iterator desc = mod_ai_configurations_.begin(); desc!=mod_ai_configurations_.end(); ++desc) {
ais_list.push_back(&desc->second);
DBG_AI_CONFIGURATION << "has ai with config: "<< std::endl << desc->second.cfg<< std::endl;
}
return ais_list;
}
const config& configuration::get_ai_config_for(const std::string &id)
{
description_map::iterator cfg_it = ai_configurations_.find(id);
if (cfg_it==ai_configurations_.end()){
description_map::iterator era_cfg_it = era_ai_configurations_.find(id);
if (era_cfg_it==era_ai_configurations_.end()){
description_map::iterator mod_cfg_it = mod_ai_configurations_.find(id);
if (mod_cfg_it==mod_ai_configurations_.end()) {
return default_config_;
} else {
return mod_cfg_it->second.cfg;
}
} else {
return era_cfg_it->second.cfg;
}
}
return cfg_it->second.cfg;
}
configuration::description_map configuration::ai_configurations_ = configuration::description_map();
configuration::description_map configuration::era_ai_configurations_ = configuration::description_map();
configuration::description_map configuration::mod_ai_configurations_ = configuration::description_map();
config configuration::default_config_ = config();
bool configuration::get_side_config_from_file(const std::string& file, config& cfg ){
try {
scoped_istream stream = preprocess_file(get_wml_location(file));
read(cfg, *stream);
LOG_AI_CONFIGURATION << "Reading AI configuration from file '" << file << "'" << std::endl;
} catch(config::error &) {
ERR_AI_CONFIGURATION << "Error while reading AI configuration from file '" << file << "'" << std::endl;
return false;
}
LOG_AI_CONFIGURATION << "Successfully read AI configuration from file '" << file << "'" << std::endl;
return true;
}
const config& configuration::get_default_ai_parameters()
{
return default_config_;
}
bool configuration::upgrade_aspect_config_from_1_07_02_to_1_07_03(side_number side, const config& cfg, config& parsed_cfg, const std::string &id, bool aspect_was_attribute)
{
config aspect_config;
aspect_config["id"] = id;
BOOST_FOREACH(const config &aiparam, cfg.child_range("ai")) {
const config &_aspect = aiparam.find_child("aspect","id",id);
if (_aspect) {
aspect_config.append(_aspect);
}
if (aspect_was_attribute && !aiparam.has_attribute(id)) {
continue;//we are looking for an attribute but it isn't present
}
if (!aspect_was_attribute && !aiparam.child(id)) {
continue;//we are looking for a child but it isn't present
}
config facet_config;
facet_config["engine"] = "cpp";
facet_config["name"] = "standard_aspect";
if (aspect_was_attribute) {
facet_config["value"] = aiparam[id];
} else {
BOOST_FOREACH(const config &value, aiparam.child_range(id)) {
facet_config.add_child("value",value);
}
}
if (const config::attribute_value *v = aiparam.get("turns")) {
facet_config["turns"] = *v;
}
if (const config::attribute_value *v = aiparam.get("time_of_day")) {
facet_config["time_of_day"] = *v;
}
aspect_config.add_child("facet",facet_config);
}
DBG_AI_CONFIGURATION << "side "<< side << " aspect[" << id << "] config :\n"<< cfg << "\n";
parsed_cfg.add_child("aspect",aspect_config);
return true;
}
bool configuration::parse_side_config(side_number side, const config& original_cfg, config &cfg )
{
LOG_AI_CONFIGURATION << "side "<< side <<": parsing AI configuration from config" << std::endl;
//leave only the [ai] children
cfg = config();
BOOST_FOREACH(const config &aiparam, original_cfg.child_range("ai")) {
cfg.add_child("ai",aiparam);
}
//backward-compatibility hack: put ai_algorithm if it is present
if (const config::attribute_value *v = original_cfg.get("ai_algorithm")) {
config ai_a;
ai_a["ai_algorithm"] = *v;
cfg.add_child("ai",ai_a);
}
DBG_AI_CONFIGURATION << "side " << side << ": config contains:"<< std::endl << cfg << std::endl;
//insert default config at the beginning
if (default_config_) {
DBG_AI_CONFIGURATION << "side "<< side <<": applying default configuration" << std::endl;
cfg.add_child_at("ai",default_config_,0);
} else {
ERR_AI_CONFIGURATION << "side "<< side <<": default configuration is not available, do not applying it" << std::endl;
}
//find version
int version = 10600;
BOOST_FOREACH(const config &aiparam, cfg.child_range("ai")) {
if (const config::attribute_value *a = aiparam.get("version")){
int v = a->to_int(version);
if (version<v) {
version = v;
}
}
}
if (version<10703) {
if (!upgrade_side_config_from_1_07_02_to_1_07_03(side, cfg)) {
return false;
}
version = 10703;
}
if (version<10710) {
version = 10710;
}
//construct new-style integrated config
LOG_AI_CONFIGURATION << "side "<< side << ": doing final operations on AI config"<< std::endl;
config parsed_cfg = config();
LOG_AI_CONFIGURATION << "side "<< side <<": merging AI configurations"<< std::endl;
BOOST_FOREACH(const config &aiparam, cfg.child_range("ai")) {
parsed_cfg.append(aiparam);
}
LOG_AI_CONFIGURATION << "side "<< side <<": setting config version to "<< version << std::endl;
parsed_cfg["version"] = version;
LOG_AI_CONFIGURATION << "side "<< side <<": merging AI aspect with the same id"<< std::endl;
parsed_cfg.merge_children_by_attribute("aspect","id");
LOG_AI_CONFIGURATION << "side "<< side <<": removing duplicate [default] tags from aspects"<< std::endl;
BOOST_FOREACH(config &aspect_cfg, parsed_cfg.child_range("aspect")) {
if (!aspect_cfg.child("default")) {
WRN_AI_CONFIGURATION << "side "<< side <<": aspect with id=["<<aspect_cfg["id"]<<"] lacks default config facet!" <<std::endl;
continue;
}
config c = aspect_cfg.child("default");
aspect_cfg.clear_children("default");
aspect_cfg.add_child("default",c);
}
DBG_AI_CONFIGURATION << "side "<< side <<": done parsing side config, it contains:"<< std::endl << parsed_cfg << std::endl;
LOG_AI_CONFIGURATION << "side "<< side <<": done parsing side config"<< std::endl;
cfg = parsed_cfg;
return true;
}
static void transfer_turns_and_time_of_day_data(const config &src, config &dst)
{
if (const config::attribute_value *turns = src.get("turns")) {
dst["turns"] = *turns;
}
if (const config::attribute_value *time_of_day = src.get("time_of_day")) {
dst["time_of_day"] = *time_of_day;
}
}
bool configuration::upgrade_side_config_from_1_07_02_to_1_07_03(side_number side, config &cfg)
{
LOG_AI_CONFIGURATION << "side "<< side <<": upgrading ai config version from version 1.7.2 to 1.7.3"<< std::endl;
config parsed_cfg;
bool is_idle_ai = false;
if (cfg["ai_algorithm"]=="idle_ai") {
is_idle_ai = true;
} else {
BOOST_FOREACH(config &aiparam, cfg.child_range("ai")) {
if (aiparam["ai_algorithm"]=="idle_ai") {
is_idle_ai = true;
break;
}
}
}
if (!is_idle_ai) {
parsed_cfg = get_ai_config_for("ai_default_rca");
}
//get values of all aspects
upgrade_aspect_configs_from_1_07_02_to_1_07_03(side, cfg.child_range("ai"), parsed_cfg);
//dump the rest of the config into fallback stage
config fallback_stage_cfg_ai;
BOOST_FOREACH(config &aiparam, cfg.child_range("ai")) {
BOOST_FOREACH(const well_known_aspect &wka, well_known_aspects) {
if (wka.was_an_attribute_) {
aiparam.remove_attribute(wka.name_);
} else {
aiparam.clear_children(wka.name_);
}
}
BOOST_FOREACH(const config &aitarget, aiparam.child_range("target")) {
lg::wml_error << deprecate_wml_key_warning("target", "1.12.0") << "\n";
config aigoal;
transfer_turns_and_time_of_day_data(aiparam,aigoal);
if (const config::attribute_value *v = aitarget.get("value")) {
aigoal["value"] = *v;
} else {
aigoal["value"] = 0;
}
config &aigoalcriteria = aigoal.add_child("criteria",aitarget);
aigoalcriteria.remove_attribute("value");
parsed_cfg.add_child("goal",aigoal);
}
aiparam.clear_children("target");
BOOST_FOREACH(config &ai_protect_unit, aiparam.child_range("protect_unit")) {
lg::wml_error << deprecate_wml_key_warning("protect_unit", "1.12.0") << "\n";
transfer_turns_and_time_of_day_data(aiparam,ai_protect_unit);
upgrade_protect_goal_config_from_1_07_02_to_1_07_03(side,ai_protect_unit,parsed_cfg,true);
}
aiparam.clear_children("protect_unit");
BOOST_FOREACH(config &ai_protect_location, aiparam.child_range("protect_location")) {
lg::wml_error << deprecate_wml_key_warning("protect_location", "1.12.0") << "\n";
transfer_turns_and_time_of_day_data(aiparam,ai_protect_location);
upgrade_protect_goal_config_from_1_07_02_to_1_07_03(side,ai_protect_location,parsed_cfg,false);
}
aiparam.clear_children("protect_location");
if (const config::attribute_value *v = aiparam.get("protect_leader"))
{
lg::wml_error << deprecate_wml_key_warning("protect_leader", "1.12.0") << "\n";
config c;
c["value"] = *v;
c["canrecruit"] = true;
c["side_number"] = side;
transfer_turns_and_time_of_day_data(aiparam,c);
if (const config::attribute_value *v = aiparam.get("protect_leader_radius")) {
lg::wml_error << deprecate_wml_key_warning("protect_leader_radius", "1.12.0") << "\n";
c["radius"] = *v;
}
upgrade_protect_goal_config_from_1_07_02_to_1_07_03(side,c,parsed_cfg,true);
}
if (!aiparam.has_attribute("turns") && !aiparam.has_attribute("time_of_day")) {
fallback_stage_cfg_ai.append(aiparam);
} else {
// Move [goal]s to root of the config, adding turns and time_of_day.
BOOST_FOREACH(const config &aigoal, aiparam.child_range("goal")) {
config updated_goal_config = aigoal;
transfer_turns_and_time_of_day_data(aiparam, updated_goal_config);
parsed_cfg.add_child("goal", updated_goal_config);
}
}
}
fallback_stage_cfg_ai.clear_children("aspect");
//move [stage]s to root of the config
BOOST_FOREACH(const config &aistage, fallback_stage_cfg_ai.child_range("stage")) {
parsed_cfg.add_child("stage",aistage);
}
fallback_stage_cfg_ai.clear_children("stage");
//move [goal]s to root of the config
BOOST_FOREACH(const config &aigoal, fallback_stage_cfg_ai.child_range("goal")) {
parsed_cfg.add_child("goal",aigoal);
}
fallback_stage_cfg_ai.clear_children("goal");
//move [modify_ai]'s to root of the config
BOOST_FOREACH(const config &aimodifyai, fallback_stage_cfg_ai.child_range("modify_ai")) {
parsed_cfg.add_child("modify_ai",aimodifyai);
}
fallback_stage_cfg_ai.clear_children("modify_ai");
//nothing useful should be at fallback_stage_cfg_ai at this point
cfg = config();
cfg.add_child("ai",parsed_cfg);
DBG_AI_CONFIGURATION << "side "<< side <<": after upgrade to 1.7.3 syntax, config contains:"<< std::endl << cfg << std::endl;
return true;
}
void configuration::upgrade_aspect_configs_from_1_07_02_to_1_07_03(side_number side, const config::const_child_itors &ai_parameters, config &parsed_cfg)
{
config cfg;
BOOST_FOREACH(const config &aiparam, ai_parameters) {
cfg.add_child("ai",aiparam);
}
DBG_AI_CONFIGURATION << "side "<< side <<": upgrading aspects from syntax of 1.7.2 to 1.7.3, old-style config is:" << std::endl << cfg << std::endl;
BOOST_FOREACH(const well_known_aspect &wka, well_known_aspects) {
upgrade_aspect_config_from_1_07_02_to_1_07_03(side, cfg,parsed_cfg,wka.name_,wka.was_an_attribute_);
}
}
void configuration::upgrade_protect_goal_config_from_1_07_02_to_1_07_03(side_number side, const config &protect_cfg, config &parsed_cfg, bool add_filter)
{
config aigoal;
aigoal["name"] = "protect";
transfer_turns_and_time_of_day_data(protect_cfg,aigoal);
if (const config::attribute_value *v = protect_cfg.get("value")) {
aigoal["value"] = *v;
} else {
aigoal["value"] = 1; //old default value
}
//note: 'radius' attribute is renamed to avoid confusion with SLF's radius
if (const config::attribute_value *v = protect_cfg.get("radius")) {
aigoal["protect_radius"] = *v;
} else {
aigoal["protect_radius"] = 20; //old default value
}
DBG_AI_CONFIGURATION << "side "<< side <<": upgrading protect goal from syntax of 1.7.2 to 1.7.3, old-style config is:" << std::endl << protect_cfg << std::endl;
if (add_filter) {
config &aigoal_criteria = aigoal.add_child("criteria",config());
config &aigoal_criteria_filter = aigoal_criteria.add_child("filter",protect_cfg);
aigoal_criteria_filter.remove_attribute("value");
aigoal_criteria_filter.remove_attribute("radius");
} else {
config &aigoal_criteria = aigoal.add_child("criteria",protect_cfg);
aigoal_criteria.remove_attribute("value");
aigoal_criteria.remove_attribute("radius");
}
parsed_cfg.add_child("goal",aigoal);
DBG_AI_CONFIGURATION << "side "<< side <<": after upgrade of protect goal from syntax of 1.7.2 to 1.7.3, new-style config is:" << std::endl << aigoal << std::endl;
}
} //end of namespace ai