mirror of
https://github.com/wesnoth/wesnoth
synced 2025-05-21 13:56:17 +00:00
785 lines
24 KiB
C++
785 lines
24 KiB
C++
/*
|
|
Copyright (C) 2009 - 2024
|
|
by Yurii Chernyi <terraninfo@terraninfo.net>
|
|
Part of the Battle for Wesnoth Project https://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 lifecycle and interface for the rest of Wesnoth
|
|
* @file
|
|
*/
|
|
|
|
#include "ai/manager.hpp"
|
|
|
|
#include "config.hpp" // for config, etc
|
|
#include "game_events/pump.hpp"
|
|
#include "log.hpp"
|
|
#include "map/location.hpp" // for map_location
|
|
#include "resources.hpp"
|
|
#include "serialization/string_utils.hpp"
|
|
#include "tod_manager.hpp"
|
|
|
|
#include "ai/composite/ai.hpp" // for ai_composite
|
|
#include "ai/composite/component.hpp" // for component_manager
|
|
#include "ai/composite/engine.hpp" // for engine
|
|
#include "ai/composite/value_translator.hpp"
|
|
#include "ai/configuration.hpp" // for configuration
|
|
#include "ai/contexts.hpp" // for readonly_context, etc
|
|
#include "ai/default/contexts.hpp" // for default_ai_context, etc
|
|
#include "ai/game_info.hpp" // for side_number, engine_ptr, etc
|
|
#include "game_config.hpp" // for debug
|
|
#include "ai/lua/aspect_advancements.hpp"
|
|
#include "ai/registry.hpp" // for init
|
|
#include "ai/lua/engine_lua.hpp"
|
|
|
|
#include <algorithm> // for min
|
|
#include <cassert> // for assert
|
|
#include <iterator> // for reverse_iterator, etc
|
|
#include <map> // for _Rb_tree_iterator, etc
|
|
#include <set> // for set
|
|
#include <stack> // for stack
|
|
#include <utility> // for pair, make_pair
|
|
#include <vector> // for vector, allocator, etc
|
|
|
|
#include <SDL2/SDL_timer.h>
|
|
|
|
namespace ai {
|
|
|
|
const std::string manager::AI_TYPE_COMPOSITE_AI = "composite_ai";
|
|
const std::string manager::AI_TYPE_SAMPLE_AI = "sample_ai";
|
|
const std::string manager::AI_TYPE_IDLE_AI = "idle_ai";
|
|
const std::string manager::AI_TYPE_FORMULA_AI = "formula_ai";
|
|
const std::string manager::AI_TYPE_DEFAULT = "default";
|
|
|
|
static lg::log_domain log_ai_manager("ai/manager");
|
|
#define DBG_AI_MANAGER LOG_STREAM(debug, log_ai_manager)
|
|
#define LOG_AI_MANAGER LOG_STREAM(info, log_ai_manager)
|
|
#define ERR_AI_MANAGER LOG_STREAM(err, log_ai_manager)
|
|
|
|
static lg::log_domain log_ai_mod("ai/mod");
|
|
#define DBG_AI_MOD LOG_STREAM(debug, log_ai_mod)
|
|
#define LOG_AI_MOD LOG_STREAM(info, log_ai_mod)
|
|
#define WRN_AI_MOD LOG_STREAM(warn, log_ai_mod)
|
|
#define ERR_AI_MOD LOG_STREAM(err, log_ai_mod)
|
|
|
|
holder::holder( side_number side, const config &cfg )
|
|
: ai_(), side_context_(nullptr), readonly_context_(nullptr), readwrite_context_(nullptr), default_ai_context_(nullptr), side_(side), cfg_(cfg)
|
|
{
|
|
DBG_AI_MANAGER << describe_ai() << "Preparing new AI holder";
|
|
}
|
|
|
|
void holder::init( side_number side )
|
|
{
|
|
if (side_context_ == nullptr) {
|
|
side_context_.reset(new side_context_impl(side,cfg_));
|
|
} else {
|
|
side_context_->set_side(side);
|
|
}
|
|
if (readonly_context_ == nullptr){
|
|
readonly_context_.reset(new readonly_context_impl(*side_context_,cfg_));
|
|
readonly_context_->on_readonly_context_create();
|
|
}
|
|
if (readwrite_context_ == nullptr){
|
|
readwrite_context_.reset(new readwrite_context_impl(*readonly_context_,cfg_));
|
|
}
|
|
if (default_ai_context_ == nullptr){
|
|
default_ai_context_.reset(new default_ai_context_impl(*readwrite_context_,cfg_));
|
|
}
|
|
if (!ai_){
|
|
ai_.reset(new ai_composite(*default_ai_context_,cfg_));
|
|
}
|
|
|
|
if (ai_) {
|
|
ai_->on_create();
|
|
for (config &mod_ai : cfg_.child_range("modify_ai")) {
|
|
if (!mod_ai.has_attribute("side")) {
|
|
mod_ai["side"] = side;
|
|
}
|
|
modify_ai(mod_ai);
|
|
}
|
|
for(config& micro : cfg_.child_range("micro_ai")) {
|
|
micro["side"] = side;
|
|
micro["action"] = "add";
|
|
micro_ai(micro);
|
|
}
|
|
cfg_.clear_children("modify_ai", "micro_ai");
|
|
|
|
std::vector<engine_ptr> engines = ai_->get_engines();
|
|
for (std::vector<engine_ptr>::iterator it = engines.begin(); it != engines.end(); ++it)
|
|
{
|
|
(*it)->set_ai_context(&(ai_->get_ai_context()));
|
|
}
|
|
|
|
} else {
|
|
ERR_AI_MANAGER << describe_ai()<<"AI lazy initialization error!";
|
|
}
|
|
|
|
}
|
|
|
|
holder::~holder()
|
|
{
|
|
try {
|
|
if (ai_) {
|
|
LOG_AI_MANAGER << describe_ai() << "Managed AI will be deleted";
|
|
}
|
|
} catch (...) {}
|
|
}
|
|
|
|
ai_composite& holder::get_ai_ref()
|
|
{
|
|
if (!ai_) {
|
|
init(side_);
|
|
}
|
|
assert(ai_);
|
|
|
|
return *ai_;
|
|
}
|
|
|
|
void holder::micro_ai(const config& cfg)
|
|
{
|
|
if (!ai_) {
|
|
init(side_);
|
|
}
|
|
assert(ai_);
|
|
|
|
auto engine = ai_->get_engine_by_cfg(config{"engine", "lua"});
|
|
if(auto lua = std::dynamic_pointer_cast<engine_lua>(engine)) {
|
|
lua->apply_micro_ai(cfg);
|
|
}
|
|
}
|
|
|
|
void holder::modify_ai(const config &cfg)
|
|
{
|
|
if (!ai_) {
|
|
// if not initialized, initialize now.
|
|
get_ai_ref();
|
|
}
|
|
const std::string &act = cfg["action"];
|
|
LOG_AI_MOD << "side "<< side_ << " "<<act<<"_ai_component \""<<cfg["path"]<<"\"";
|
|
DBG_AI_MOD << std::endl << cfg;
|
|
DBG_AI_MOD << "side "<< side_ << " before "<<act<<"_ai_component"<<std::endl << to_config();
|
|
bool res = false;
|
|
if (act == "add") {
|
|
res = component_manager::add_component(&*ai_,cfg["path"],cfg);
|
|
} else if (act == "change") {
|
|
res = component_manager::change_component(&*ai_,cfg["path"],cfg);
|
|
} else if (act == "delete") {
|
|
res = component_manager::delete_component(&*ai_,cfg["path"]);
|
|
} else {
|
|
ERR_AI_MOD << "modify_ai tag has invalid 'action' attribute " << act;
|
|
}
|
|
DBG_AI_MOD << "side "<< side_ << " after [modify_ai]"<<act<<std::endl << to_config();
|
|
if (!res) {
|
|
LOG_AI_MOD << act << "_ai_component failed";
|
|
} else {
|
|
LOG_AI_MOD << act << "_ai_component success";
|
|
}
|
|
|
|
}
|
|
|
|
void holder::append_ai(const config& cfg)
|
|
{
|
|
if(!ai_) {
|
|
get_ai_ref();
|
|
}
|
|
for(const config& aspect : cfg.child_range("aspect")) {
|
|
const std::string& id = aspect["id"];
|
|
for(const config& facet : aspect.child_range("facet")) {
|
|
ai_->add_facet(id, facet);
|
|
}
|
|
}
|
|
for(const config& goal : cfg.child_range("goal")) {
|
|
ai_->add_goal(goal);
|
|
}
|
|
for(const config& stage : cfg.child_range("stage")) {
|
|
if(stage["name"] != "empty") {
|
|
ai_->add_stage(stage);
|
|
}
|
|
}
|
|
for(config mod : cfg.child_range("modify_ai")) {
|
|
if (!mod.has_attribute("side")) {
|
|
mod["side"] = side_context_->get_side();
|
|
}
|
|
modify_ai(mod);
|
|
}
|
|
for(config micro : cfg.child_range("micro_ai")) {
|
|
micro["side"] = side_context_->get_side();
|
|
micro["action"] = "add";
|
|
micro_ai(micro);
|
|
}
|
|
}
|
|
|
|
config holder::to_config() const
|
|
{
|
|
if (!ai_) {
|
|
return cfg_;
|
|
} else {
|
|
config cfg = ai_->to_config();
|
|
if (side_context_!=nullptr) {
|
|
cfg.merge_with(side_context_->to_side_context_config());
|
|
}
|
|
if (readonly_context_!=nullptr) {
|
|
cfg.merge_with(readonly_context_->to_readonly_context_config());
|
|
}
|
|
if (readwrite_context_!=nullptr) {
|
|
cfg.merge_with(readwrite_context_->to_readwrite_context_config());
|
|
}
|
|
if (default_ai_context_!=nullptr) {
|
|
cfg.merge_with(default_ai_context_->to_default_ai_context_config());
|
|
}
|
|
|
|
return cfg;
|
|
}
|
|
}
|
|
|
|
std::string holder::describe_ai() const
|
|
{
|
|
if(ai_) {
|
|
return formatter() << ai_->describe_self() << " for side " << side_ << " : ";
|
|
} else {
|
|
return formatter() << "not initialized ai with id=[" << cfg_["id"] << "] for side " << side_ << " : ";
|
|
}
|
|
}
|
|
|
|
std::string holder::get_ai_overview()
|
|
{
|
|
if (!ai_) {
|
|
get_ai_ref();
|
|
}
|
|
// These assignments are necessary because the code will otherwise not compile on some platforms with an lvalue/rvalue mismatch error
|
|
auto lik = ai_->get_leader_ignores_keep();
|
|
auto pl = ai_->get_passive_leader();
|
|
auto plsk = ai_->get_passive_leader_shares_keep();
|
|
// In order to display booleans as yes/no rather than 1/0 or true/false
|
|
config cfg;
|
|
cfg["allow_ally_villages"] = ai_->get_allow_ally_villages();
|
|
cfg["simple_targeting"] = ai_->get_simple_targeting();
|
|
cfg["support_villages"] = ai_->get_support_villages();
|
|
std::stringstream s;
|
|
s << "advancements: " << ai_->get_advancements().get_value() << std::endl;
|
|
s << "aggression: " << ai_->get_aggression() << std::endl;
|
|
s << "allow_ally_villages: " << cfg["allow_ally_villages"] << std::endl;
|
|
s << "caution: " << ai_->get_caution() << std::endl;
|
|
s << "grouping: " << ai_->get_grouping() << std::endl;
|
|
s << "leader_aggression: " << ai_->get_leader_aggression() << std::endl;
|
|
s << "leader_ignores_keep: " << utils::visit(leader_aspects_visitor(), lik) << std::endl;
|
|
s << "leader_value: " << ai_->get_leader_value() << std::endl;
|
|
s << "passive_leader: " << utils::visit(leader_aspects_visitor(), pl) << std::endl;
|
|
s << "passive_leader_shares_keep: " << utils::visit(leader_aspects_visitor(), plsk) << std::endl;
|
|
s << "recruitment_diversity: " << ai_->get_recruitment_diversity() << std::endl;
|
|
s << "recruitment_instructions: " << std::endl << "----config begin----" << std::endl;
|
|
s << ai_->get_recruitment_instructions() << "-----config end-----" << std::endl;
|
|
s << "recruitment_more: " << utils::join(ai_->get_recruitment_more()) << std::endl;
|
|
s << "recruitment_pattern: " << utils::join(ai_->get_recruitment_pattern()) << std::endl;
|
|
s << "recruitment_randomness: " << ai_->get_recruitment_randomness() << std::endl;
|
|
s << "recruitment_save_gold: " << std::endl << "----config begin----" << std::endl;
|
|
s << ai_->get_recruitment_save_gold() << "-----config end-----" << std::endl;
|
|
s << "retreat_enemy_weight: " << ai_->get_retreat_enemy_weight() << std::endl;
|
|
s << "retreat_factor: " << ai_->get_retreat_factor() << std::endl;
|
|
s << "scout_village_targeting: " << ai_->get_scout_village_targeting() << std::endl;
|
|
s << "simple_targeting: " << cfg["simple_targeting"] << std::endl;
|
|
s << "support_villages: " << cfg["support_villages"] << std::endl;
|
|
s << "village_value: " << ai_->get_village_value() << std::endl;
|
|
s << "villages_per_scout: " << ai_->get_villages_per_scout() << std::endl;
|
|
|
|
return s.str();
|
|
}
|
|
|
|
std::string holder::get_ai_structure()
|
|
{
|
|
if (!ai_) {
|
|
get_ai_ref();
|
|
}
|
|
return component_manager::print_component_tree(&*ai_,"");
|
|
}
|
|
|
|
std::string holder::get_ai_identifier() const
|
|
{
|
|
return cfg_["id"];
|
|
}
|
|
|
|
component* holder::get_component(component *root, const std::string &path) {
|
|
if (!game_config::debug) // Debug guard
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
if (root == nullptr) // Return root component(ai_)
|
|
{
|
|
if (!ai_) {
|
|
init(side_);
|
|
}
|
|
assert(ai_);
|
|
|
|
return &*ai_;
|
|
}
|
|
|
|
return component_manager::get_component(root, path);
|
|
}
|
|
|
|
// =======================================================================
|
|
// LIFECYCLE
|
|
// =======================================================================
|
|
|
|
manager::manager()
|
|
: history_()
|
|
, history_item_counter_(0)
|
|
, ai_info_()
|
|
, map_changed_("ai_map_changed")
|
|
, recruit_list_changed_("ai_recruit_list_changed")
|
|
, user_interact_("ai_user_interact")
|
|
, sync_network_("ai_sync_network")
|
|
, tod_changed_("ai_tod_changed")
|
|
, gamestate_changed_("ai_gamestate_changed")
|
|
, turn_started_("ai_turn_started")
|
|
, last_interact_(0)
|
|
, num_interact_(0)
|
|
{
|
|
registry::init();
|
|
singleton_ = this;
|
|
}
|
|
|
|
manager* manager::singleton_ = nullptr;
|
|
|
|
void manager::add_observer( events::observer* event_observer){
|
|
user_interact_.attach_handler(event_observer);
|
|
sync_network_.attach_handler(event_observer);
|
|
turn_started_.attach_handler(event_observer);
|
|
gamestate_changed_.attach_handler(event_observer);
|
|
}
|
|
|
|
void manager::remove_observer(events::observer* event_observer){
|
|
user_interact_.detach_handler(event_observer);
|
|
sync_network_.detach_handler(event_observer);
|
|
turn_started_.detach_handler(event_observer);
|
|
gamestate_changed_.detach_handler(event_observer);
|
|
}
|
|
|
|
void manager::add_gamestate_observer( events::observer* event_observer){
|
|
gamestate_changed_.attach_handler(event_observer);
|
|
turn_started_.attach_handler(event_observer);
|
|
map_changed_.attach_handler(event_observer);
|
|
}
|
|
|
|
void manager::remove_gamestate_observer(events::observer* event_observer){
|
|
gamestate_changed_.detach_handler(event_observer);
|
|
turn_started_.detach_handler(event_observer);
|
|
map_changed_.detach_handler(event_observer);
|
|
}
|
|
|
|
void manager::add_tod_changed_observer( events::observer* event_observer){
|
|
tod_changed_.attach_handler(event_observer);
|
|
}
|
|
|
|
void manager::remove_tod_changed_observer(events::observer* event_observer){
|
|
tod_changed_.detach_handler(event_observer);
|
|
}
|
|
|
|
void manager::add_map_changed_observer( events::observer* event_observer )
|
|
{
|
|
map_changed_.attach_handler(event_observer);
|
|
}
|
|
|
|
void manager::add_recruit_list_changed_observer( events::observer* event_observer )
|
|
{
|
|
recruit_list_changed_.attach_handler(event_observer);
|
|
}
|
|
|
|
void manager::add_turn_started_observer( events::observer* event_observer )
|
|
{
|
|
turn_started_.attach_handler(event_observer);
|
|
}
|
|
|
|
void manager::remove_recruit_list_changed_observer( events::observer* event_observer )
|
|
{
|
|
recruit_list_changed_.detach_handler(event_observer);
|
|
}
|
|
|
|
void manager::remove_map_changed_observer( events::observer* event_observer )
|
|
{
|
|
map_changed_.detach_handler(event_observer);
|
|
}
|
|
|
|
void manager::remove_turn_started_observer( events::observer* event_observer )
|
|
{
|
|
turn_started_.detach_handler(event_observer);
|
|
}
|
|
|
|
void manager::raise_user_interact() {
|
|
if(resources::simulation_){
|
|
return;
|
|
}
|
|
|
|
const int interact_time = 30;
|
|
const int time_since_interact = SDL_GetTicks() - last_interact_;
|
|
if(time_since_interact < interact_time) {
|
|
return;
|
|
}
|
|
|
|
++num_interact_;
|
|
user_interact_.notify_observers();
|
|
|
|
last_interact_ = SDL_GetTicks();
|
|
|
|
}
|
|
|
|
void manager::raise_sync_network() {
|
|
sync_network_.notify_observers();
|
|
}
|
|
|
|
void manager::raise_gamestate_changed() {
|
|
gamestate_changed_.notify_observers();
|
|
}
|
|
|
|
void manager::raise_tod_changed() {
|
|
tod_changed_.notify_observers();
|
|
}
|
|
|
|
void manager::raise_turn_started() {
|
|
turn_started_.notify_observers();
|
|
}
|
|
|
|
void manager::raise_recruit_list_changed() {
|
|
recruit_list_changed_.notify_observers();
|
|
}
|
|
|
|
void manager::raise_map_changed() {
|
|
map_changed_.notify_observers();
|
|
}
|
|
|
|
// =======================================================================
|
|
// EVALUATION
|
|
// =======================================================================
|
|
|
|
const std::string manager::evaluate_command( side_number side, const std::string& str )
|
|
{
|
|
//insert new command into history
|
|
history_.emplace_back(history_item_counter_++,str);
|
|
|
|
//prune history - erase 1/2 of it if it grows too large
|
|
if (history_.size()>MAX_HISTORY_SIZE){
|
|
history_.erase(history_.begin(),history_.begin()+MAX_HISTORY_SIZE/2);
|
|
LOG_AI_MANAGER << "AI MANAGER: pruned history";
|
|
}
|
|
|
|
if (!should_intercept(str)){
|
|
ai_composite& ai = get_active_ai_for_side(side);
|
|
raise_gamestate_changed();
|
|
return ai.evaluate(str);
|
|
}
|
|
|
|
return internal_evaluate_command(side,str);
|
|
}
|
|
|
|
bool manager::should_intercept( const std::string& str ) const
|
|
{
|
|
if (str.length()<1) {
|
|
return false;
|
|
}
|
|
if (str.at(0)=='!'){
|
|
return true;
|
|
}
|
|
if (str.at(0)=='?'){
|
|
return true;
|
|
}
|
|
return false;
|
|
|
|
}
|
|
|
|
// this is stub code to allow testing of basic 'history', 'repeat-last-command', 'add/remove/replace ai' capabilities.
|
|
// yes, it doesn't look nice. but it is usable.
|
|
// to be refactored at earliest opportunity
|
|
// TODO: extract to separate class which will use fai or lua parser
|
|
const std::string manager::internal_evaluate_command( side_number side, const std::string& str ){
|
|
const int MAX_HISTORY_VISIBLE = 30;
|
|
|
|
//repeat last command
|
|
if (str=="!") {
|
|
//this command should not be recorded in history
|
|
if (!history_.empty()){
|
|
history_.pop_back();
|
|
history_item_counter_--;
|
|
}
|
|
|
|
if (history_.empty()){
|
|
return "AI MANAGER: empty history";
|
|
}
|
|
return evaluate_command(side, history_.back().get_command());//no infinite loop since '!' commands are not present in history
|
|
};
|
|
//show last command
|
|
if (str=="?") {
|
|
//this command should not be recorded in history
|
|
if (!history_.empty()){
|
|
history_.pop_back();
|
|
history_item_counter_--;
|
|
}
|
|
|
|
if (history_.empty()){
|
|
return "AI MANAGER: History is empty";
|
|
}
|
|
|
|
int n = std::min<int>( MAX_HISTORY_VISIBLE, history_.size() );
|
|
std::stringstream strstream;
|
|
strstream << "AI MANAGER: History - last "<< n <<" commands:\n";
|
|
std::deque< command_history_item >::reverse_iterator j = history_.rbegin();
|
|
|
|
for (int cmd_id=n; cmd_id>0; --cmd_id){
|
|
strstream << j->get_number() << " :" << j->get_command() << '\n';
|
|
++j;//this is *reverse* iterator
|
|
}
|
|
|
|
return strstream.str();
|
|
};
|
|
|
|
std::vector< std::string > cmd = utils::parenthetical_split(str, ' ',"'","'");
|
|
|
|
if (cmd.size()==3){
|
|
// add_ai side file
|
|
if (cmd.at(0)=="!add_ai"){
|
|
side = std::stoi(cmd.at(1));
|
|
std::string file = cmd.at(2);
|
|
if (add_ai_for_side_from_file(side,file,false)){
|
|
return std::string("AI MANAGER: added [")+manager::get_active_ai_identifier_for_side(side)+std::string("] AI for side ")+std::to_string(side)+std::string(" from file ")+file;
|
|
} else {
|
|
return std::string("AI MANAGER: failed attempt to add AI for side ")+std::to_string(side)+std::string(" from file ")+file;
|
|
}
|
|
}
|
|
// replace_ai side file
|
|
if (cmd.at(0)=="!replace_ai"){
|
|
side = std::stoi(cmd.at(1));
|
|
std::string file = cmd.at(2);
|
|
if (add_ai_for_side_from_file(side,file,true)){
|
|
return std::string("AI MANAGER: added [")+manager::get_active_ai_identifier_for_side(side)+std::string("] AI for side ")+std::to_string(side)+std::string(" from file ")+file;
|
|
} else {
|
|
return std::string("AI MANAGER: failed attempt to add AI for side ")+std::to_string(side)+std::string(" from file ")+file;
|
|
}
|
|
}
|
|
|
|
} else if (cmd.size()==2){
|
|
// remove_ai side
|
|
if (cmd.at(0)=="!remove_ai"){
|
|
side = std::stoi(cmd.at(1));
|
|
remove_ai_for_side(side);
|
|
return std::string("AI MANAGER: made an attempt to remove AI for side ")+std::to_string(side);
|
|
}
|
|
if (cmd.at(0)=="!"){
|
|
//this command should not be recorded in history
|
|
if (!history_.empty()){
|
|
history_.pop_back();
|
|
history_item_counter_--;
|
|
}
|
|
|
|
int command = std::stoi(cmd.at(1));
|
|
std::deque< command_history_item >::reverse_iterator j = history_.rbegin();
|
|
//yes, the iterator could be precisely positioned (since command numbers go 1,2,3,4,..). will do it later.
|
|
while ( (j!=history_.rend()) && (j->get_number()!=command) ){
|
|
++j;// this is *reverse* iterator
|
|
}
|
|
if (j!=history_.rend()){
|
|
return evaluate_command(side,j->get_command());//no infinite loop since '!' commands are not present in history
|
|
}
|
|
return "AI MANAGER: no command with requested number found";
|
|
}
|
|
} else if (cmd.size()==1){
|
|
if (cmd.at(0)=="!help") {
|
|
return
|
|
"known commands:\n"
|
|
"! - repeat last command (? and ! do not count)\n"
|
|
"! NUMBER - repeat numbered command\n"
|
|
"? - show a history list\n"
|
|
"!add_ai TEAM FILE - add a AI to side (0 - command AI, N - AI for side #N) from file\n"
|
|
"!remove_ai TEAM - remove AI from side (0 - command AI, N - AI for side #N)\n"
|
|
"!replace_ai TEAM FILE - replace AI of side (0 - command AI, N - AI for side #N) from file\n"
|
|
"!help - show this help message";
|
|
}
|
|
}
|
|
|
|
return "AI MANAGER: nothing to do";
|
|
}
|
|
|
|
// =======================================================================
|
|
// ADD, CREATE AIs, OR LIST AI TYPES
|
|
// =======================================================================
|
|
|
|
bool manager::add_ai_for_side_from_file( side_number side, const std::string& file, bool replace )
|
|
{
|
|
config cfg;
|
|
if (!configuration::get_side_config_from_file(file,cfg)){
|
|
ERR_AI_MANAGER << " unable to read [SIDE] config for side "<< side << "from file [" << file <<"]";
|
|
return false;
|
|
}
|
|
return add_ai_for_side_from_config(side,cfg,replace);
|
|
}
|
|
|
|
bool manager::add_ai_for_side_from_config( side_number side, const config& cfg, bool replace ){
|
|
config parsed_cfg;
|
|
configuration::parse_side_config(side, cfg, parsed_cfg);
|
|
|
|
if (replace) {
|
|
remove_ai_for_side(side);
|
|
}
|
|
|
|
std::stack<holder>& ai_stack_for_specific_side = get_or_create_ai_stack_for_side(side);
|
|
ai_stack_for_specific_side.emplace(side, parsed_cfg);
|
|
return true;
|
|
}
|
|
|
|
// =======================================================================
|
|
// REMOVE
|
|
// =======================================================================
|
|
|
|
void manager::remove_ai_for_side( side_number side )
|
|
{
|
|
std::stack<holder>& ai_stack_for_specific_side = get_or_create_ai_stack_for_side(side);
|
|
if (!ai_stack_for_specific_side.empty()){
|
|
ai_stack_for_specific_side.pop();
|
|
}
|
|
}
|
|
|
|
void manager::remove_all_ais_for_side( side_number side )
|
|
{
|
|
std::stack<holder>& ai_stack_for_specific_side = get_or_create_ai_stack_for_side(side);
|
|
|
|
//clear the stack. std::stack doesn't have a '.clear()' method to do it
|
|
while (!ai_stack_for_specific_side.empty()){
|
|
ai_stack_for_specific_side.pop();
|
|
}
|
|
}
|
|
|
|
void manager::clear_ais()
|
|
{
|
|
ai_map_.clear();
|
|
}
|
|
|
|
void manager::modify_active_ai_for_side ( side_number side, const config &cfg )
|
|
{
|
|
get_active_ai_holder_for_side(side).modify_ai(cfg);
|
|
}
|
|
|
|
void manager::append_active_ai_for_side(side_number side, const config& cfg)
|
|
{
|
|
get_active_ai_holder_for_side(side).append_ai(cfg);
|
|
}
|
|
|
|
std::string manager::get_active_ai_overview_for_side( side_number side )
|
|
{
|
|
return get_active_ai_holder_for_side(side).get_ai_overview();
|
|
}
|
|
|
|
std::string manager::get_active_ai_structure_for_side( side_number side )
|
|
{
|
|
return get_active_ai_holder_for_side(side).get_ai_structure();
|
|
}
|
|
|
|
std::string manager::get_active_ai_identifier_for_side( side_number side )
|
|
{
|
|
return get_active_ai_holder_for_side(side).get_ai_identifier();
|
|
}
|
|
|
|
ai::holder& manager::get_active_ai_holder_for_side_dbg( side_number side )
|
|
{
|
|
if (!game_config::debug)
|
|
{
|
|
static ai::holder empty_holder(side, config());
|
|
return empty_holder;
|
|
}
|
|
return get_active_ai_holder_for_side(side);
|
|
}
|
|
|
|
config manager::to_config( side_number side )
|
|
{
|
|
return get_active_ai_holder_for_side(side).to_config();
|
|
}
|
|
|
|
game_info& manager::get_active_ai_info_for_side( side_number /*side*/ )
|
|
{
|
|
return ai_info_;
|
|
}
|
|
|
|
game_info& manager::get_ai_info()
|
|
{
|
|
return ai_info_;
|
|
}
|
|
|
|
const ai::unit_advancements_aspect& manager::get_advancement_aspect_for_side(side_number side)
|
|
{
|
|
return get_active_ai_holder_for_side(side).get_ai_ref().get_advancements();
|
|
}
|
|
|
|
// =======================================================================
|
|
// PROXY
|
|
// =======================================================================
|
|
|
|
void manager::play_turn( side_number side ){
|
|
last_interact_ = 0;
|
|
num_interact_ = 0;
|
|
const int turn_start_time = SDL_GetTicks();
|
|
get_ai_info().recent_attacks.clear();
|
|
ai_composite& ai_obj = get_active_ai_for_side(side);
|
|
resources::game_events->pump().fire("ai_turn");
|
|
raise_turn_started();
|
|
if (resources::tod_manager->has_tod_bonus_changed()) {
|
|
raise_tod_changed();
|
|
}
|
|
ai_obj.new_turn();
|
|
ai_obj.play_turn();
|
|
const int turn_end_time= SDL_GetTicks();
|
|
DBG_AI_MANAGER << "side " << side << ": number of user interactions: "<<num_interact_;
|
|
DBG_AI_MANAGER << "side " << side << ": total turn time: "<<turn_end_time - turn_start_time << " ms ";
|
|
}
|
|
|
|
// =======================================================================
|
|
// PRIVATE
|
|
// =======================================================================
|
|
// =======================================================================
|
|
// AI STACKS
|
|
// =======================================================================
|
|
std::stack<holder>& manager::get_or_create_ai_stack_for_side( side_number side )
|
|
{
|
|
AI_map_of_stacks::iterator iter = ai_map_.find(side);
|
|
if (iter!=ai_map_.end()){
|
|
return iter->second;
|
|
}
|
|
return ai_map_.emplace(side, std::stack<holder>()).first->second;
|
|
}
|
|
|
|
// =======================================================================
|
|
// AI HOLDERS
|
|
// =======================================================================
|
|
holder& manager::get_active_ai_holder_for_side( side_number side )
|
|
{
|
|
std::stack<holder>& ai_stack_for_specific_side = get_or_create_ai_stack_for_side(side);
|
|
|
|
if (!ai_stack_for_specific_side.empty()){
|
|
return ai_stack_for_specific_side.top();
|
|
} else {
|
|
config cfg = configuration::get_default_ai_parameters();
|
|
ai_stack_for_specific_side.emplace(side, cfg);
|
|
return ai_stack_for_specific_side.top();
|
|
}
|
|
}
|
|
|
|
// =======================================================================
|
|
// AI POINTERS
|
|
// =======================================================================
|
|
|
|
ai_composite& manager::get_active_ai_for_side( side_number side )
|
|
{
|
|
return get_active_ai_holder_for_side(side).get_ai_ref();
|
|
}
|
|
|
|
// =======================================================================
|
|
// MISC
|
|
// =======================================================================
|
|
|
|
} //end of namespace ai
|