diff --git a/src/ai/default/attack.cpp b/src/ai/default/attack.cpp index d3979e7116697..cdfacb2756f7e 100644 --- a/src/ai/default/attack.cpp +++ b/src/ai/default/attack.cpp @@ -19,6 +19,9 @@ #include "ai/manager.hpp" #include "ai/default/contexts.hpp" +#include "ai/actions.hpp" +#include "ai/formula/ai.hpp" +#include "ai/composite/contexts.hpp" #include "actions/attack.hpp" #include "attack_prediction.hpp" @@ -28,6 +31,8 @@ #include "team.hpp" #include "units/unit.hpp" #include "formula/callable_objects.hpp" // for location_callable +#include "resources.hpp" +#include "game_board.hpp" static lg::log_domain log_ai("ai/attack"); #define LOG_AI LOG_STREAM(info, log_ai) @@ -35,6 +40,8 @@ static lg::log_domain log_ai("ai/attack"); namespace ai { +extern ai_context& get_ai_context(const game_logic::formula_callable* for_fai); + void attack_analysis::analyze(const gamemap& map, unit_map& units, const readonly_context& ai_obj, const move_map& dstsrc, const move_map& srcdst, @@ -400,4 +407,56 @@ void attack_analysis::get_inputs(game_logic::formula_input_vector* inputs) const add_input(inputs, "is_surrounded"); } +variant attack_analysis::execute_self(variant ctxt) { + //If we get an attack analysis back we will do the first attack. + //Then the AI can get run again and re-choose. + if(movements.empty()) { + return variant(false); + } + + unit_map& units = resources::gameboard->units(); + + //make sure that unit which has to attack is at given position and is able to attack + unit_map::const_iterator unit = units.find(movements.front().first); + if(!unit.valid() || unit->attacks_left() == 0) { + return variant(false); + } + + const map_location& move_from = movements.front().first; + const map_location& att_src = movements.front().second; + const map_location& att_dst = target; + + //check if target is still valid + unit = units.find(att_dst); + if(unit == units.end()) { + return variant(new game_logic::safe_call_result(this, attack_result::E_EMPTY_DEFENDER, move_from)); + } + + //check if we need to move + if(move_from != att_src) { + //now check if location to which we want to move is still unoccupied + unit = units.find(att_src); + if(unit != units.end()) { + return variant(new game_logic::safe_call_result(this, move_result::E_NO_UNIT, move_from)); + } + + ai::move_result_ptr result = get_ai_context(ctxt.as_callable()).execute_move_action(move_from, att_src); + if(!result->is_ok()) { + //move part failed + LOG_AI << "ERROR #" << result->get_status() << " while executing 'attack' formula function\n" << std::endl; + return variant(new game_logic::safe_call_result(this, result->get_status(), result->get_unit_location())); + } + } + + if(units.count(att_src)) { + ai::attack_result_ptr result = get_ai_context(ctxt.as_callable()).execute_attack_action(movements.front().second, target, -1); + if(!result->is_ok()) { + //attack failed + LOG_AI << "ERROR #" << result->get_status() << " while executing 'attack' formula function\n" << std::endl; + return variant(new game_logic::safe_call_result(this, result->get_status())); + } + } + return variant(true); +} + } //end of namespace ai diff --git a/src/ai/default/contexts.hpp b/src/ai/default/contexts.hpp index 36af90e2f25a1..34367081425db 100644 --- a/src/ai/default/contexts.hpp +++ b/src/ai/default/contexts.hpp @@ -55,11 +55,11 @@ struct target { }; -class attack_analysis : public game_logic::formula_callable +class attack_analysis : public game_logic::action_callable { public: attack_analysis() : - game_logic::formula_callable(), + game_logic::action_callable(), target(), movements(), target_value(0.0), @@ -138,6 +138,7 @@ class attack_analysis : public game_logic::formula_callable /** Is true if the units involved in this attack sequence are surrounded. */ bool is_surrounded; + variant execute_self(variant ctxt) override; }; diff --git a/src/ai/formula/ai.cpp b/src/ai/formula/ai.cpp index 5cc672d3d3fb7..0ac1272d9a194 100644 --- a/src/ai/formula/ai.cpp +++ b/src/ai/formula/ai.cpp @@ -27,7 +27,6 @@ #include "game_display.hpp" // for game_display #include "log.hpp" // for LOG_STREAM, logger, etc #include "map/map.hpp" // for gamemap -#include "menu_events.hpp" #include "pathfind/pathfind.hpp" // for plain_route, etc #include "pathfind/teleport.hpp" // for get_teleport_locations, etc #include "recall_list_manager.hpp" // for recall_list_manager @@ -106,7 +105,7 @@ formula_ai::formula_ai(readonly_context &context, const config &cfg) cfg_(cfg), recursion_counter_(context.get_recursion_count()), keeps_cache_(), - infinite_loop_guardian_(), +// infinite_loop_guardian_(), vars_(), function_table_(*this) { @@ -169,7 +168,7 @@ std::string formula_ai::evaluate(const std::string& formula_str) const variant v = f.evaluate(callable,nullptr); if (ai_ptr_) { - variant var = execute_variant(v, *ai_ptr_, true ); + variant var = variant(this).execute_variant(v); if ( !var.is_empty() ) { return "Made move: " + var.to_debug_string(); @@ -195,7 +194,7 @@ variant formula_ai::make_action(game_logic::const_formula_ptr formula_, const ga variant res; if (ai_ptr_) { - res = execute_variant(var, *ai_ptr_, false); + res = variant(this).execute_variant(var); } else { ERR_AI << "skipped execution of action because ai context is not set correctly" << std::endl; } @@ -262,296 +261,6 @@ pathfind::teleport_map formula_ai::get_allowed_teleports(unit_map::iterator& uni return pathfind::get_teleport_locations(*unit_it, current_team(), true); } -//commandline=true when we evaluate formula from commandline, false otherwise (default) -variant formula_ai::execute_variant(const variant& var, ai_context &ai_, bool commandline) -{ - std::stack vars; - if(var.is_list()) { - for(size_t n = 1; n <= var.num_elements() ; ++n) { - vars.push(var[ var.num_elements() - n ]); - } - } else { - vars.push(var); - } - - std::vector made_moves; - - variant error; - - unit_map& units = resources::gameboard->units(); - - while( !vars.empty() ) { - - if(vars.top().is_null()) { - vars.pop(); - continue; - } - - variant action = vars.top(); - vars.pop(); - - game_logic::safe_call_callable* safe_call = action.try_convert(); - - if(safe_call) { - action = safe_call->get_main(); - } - - const move_callable* move = action.try_convert(); - const move_partial_callable* move_partial = action.try_convert(); - const attack_callable* attack = action.try_convert(); - const attack_analysis* _attack_analysis = action.try_convert(); - const recruit_callable* recruit_command = action.try_convert(); - const recall_callable* recall_command = action.try_convert(); - const set_var_callable* set_var_command = action.try_convert(); - const set_unit_var_callable* set_unit_var_command = action.try_convert(); - const fallback_callable* fallback_command = action.try_convert(); - - if( move || move_partial ) { - move_result_ptr move_result; - - if(move) - move_result = ai_.execute_move_action(move->src(), move->dst(), true); - else - move_result = ai_.execute_move_action(move_partial->src(), move_partial->dst(), false); - - if ( !move_result->is_ok() ) { - if( move ) { - LOG_AI << "ERROR #" << move_result->get_status() << " while executing 'move' formula function\n" << std::endl; - - if(safe_call) { - //safe_call was called, prepare error information - error = variant(new safe_call_result(move, - move_result->get_status(), move_result->get_unit_location())); - } - } else { - LOG_AI << "ERROR #" << move_result->get_status() << " while executing 'move_partial' formula function\n" << std::endl; - - if(safe_call) { - //safe_call was called, prepare error information - error = variant(new safe_call_result(move_partial, - move_result->get_status(), move_result->get_unit_location())); - } - } - } - - if( move_result->is_gamestate_changed() ) - made_moves.push_back(action); - } else if(attack) { - bool gamestate_changed = false; - move_result_ptr move_result; - - if( attack->move_from() != attack->src() ) { - move_result = ai_.execute_move_action(attack->move_from(), attack->src(), false); - gamestate_changed |= move_result->is_gamestate_changed(); - - if (!move_result->is_ok()) { - //move part failed - LOG_AI << "ERROR #" << move_result->get_status() << " while executing 'attack' formula function\n" << std::endl; - - if(safe_call) { - //safe_call was called, prepare error information - error = variant(new safe_call_result(attack, - move_result->get_status(), move_result->get_unit_location())); - } - } - } - - if (!move_result || move_result->is_ok() ) { - //if move wasn't done at all or was done successfully - attack_result_ptr attack_result = ai_.execute_attack_action(attack->src(), attack->dst(), attack->weapon() ); - gamestate_changed |= attack_result->is_gamestate_changed(); - if (!attack_result->is_ok()) { - //attack failed - - LOG_AI << "ERROR #" << attack_result->get_status() << " while executing 'attack' formula function\n" << std::endl; - - if(safe_call) { - //safe_call was called, prepare error information - error = variant(new safe_call_result(attack, attack_result->get_status())); - } - } - } - - if (gamestate_changed) { - made_moves.push_back(action); - } - } else if(_attack_analysis) { - //If we get an attack analysis back we will do the first attack. - //Then the AI can get run again and re-choose. - assert(_attack_analysis->movements.empty() == false); - - //make sure that unit which has to attack is at given position and is able to attack - unit_map::const_iterator unit = units.find(_attack_analysis->movements.front().first); - if (!unit.valid() || unit->attacks_left() == 0) - continue; - - const map_location& move_from = _attack_analysis->movements.front().first; - const map_location& att_src = _attack_analysis->movements.front().second; - const map_location& att_dst = _attack_analysis->target; - - //check if target is still valid - unit = units.find(att_dst); - if ( unit == units.end() ) - continue; - - //check if we need to move - if( move_from != att_src ) { - //now check if location to which we want to move is still unoccupied - unit = units.find(att_src); - if ( unit != units.end() ) { - continue; - } - - ai_.execute_move_action(move_from, att_src); - } - - if(units.count(att_src)) { - ai_.execute_attack_action(_attack_analysis->movements.front().second,_attack_analysis->target,-1); - } - made_moves.push_back(action); - } else if(recall_command) { - - recall_result_ptr recall_result = ai_.check_recall_action(recall_command->id(), recall_command->loc()); - - if( recall_result->is_ok() ) { - recall_result->execute(); - } - - if (!recall_result->is_ok()) { - - if(safe_call) { - //safe call was called, prepare error information - error = variant(new safe_call_result(recall_command, - recall_result->get_status())); - - LOG_AI << "ERROR #" <get_status() << " while executing 'recall' formula function\n"<get_status() << " while executing 'recall' formula function\n"<is_gamestate_changed() ) { - made_moves.push_back(action); - } - - } else if(recruit_command) { - recruit_result_ptr recruit_result = ai_.check_recruit_action(recruit_command->type(), recruit_command->loc()); - - //is_ok()==true means that the action is successful (eg. no unexpected events) - //is_ok() must be checked or the code will complain :) - if( recruit_result->is_ok() ) - recruit_result->execute(); - - if (!recruit_result->is_ok()) { - - if(safe_call) { - //safe call was called, prepare error information - error = variant(new safe_call_result(recruit_command, - recruit_result->get_status())); - - LOG_AI << "ERROR #" <get_status() << " while executing 'recruit' formula function\n"<get_status() << " while executing 'recruit' formula function\n"<is_gamestate_changed() ) - made_moves.push_back(action); - - } else if(set_var_command) { - if( infinite_loop_guardian_.set_var_check() ) { - LOG_AI << "Setting variable: " << set_var_command->key() << " -> " << set_var_command->value().to_debug_string() << "\n"; - vars_.add(set_var_command->key(), set_var_command->value()); - made_moves.push_back(action); - } else { - //too many calls in a row - possible infinite loop - ERR_AI << "ERROR #" << 5001 << " while executing 'set_var' formula function" << std::endl; - - if( safe_call ) - error = variant(new safe_call_result(set_var_command, 5001)); - } - } else if(set_unit_var_command) { - int status = 0; - unit_map::iterator unit; - - if( !infinite_loop_guardian_.set_unit_var_check() ) { - status = 5001; //exceeded nmber of calls in a row - possible infinite loop - } else if( (unit = units.find(set_unit_var_command->loc())) == units.end() ) { - status = 5002; //unit not found - } else if (unit->side() != get_side()) { - status = 5003;//unit does not belong to our side - } - - if( status == 0 ){ - LOG_AI << "Setting unit variable: " << set_unit_var_command->key() << " -> " << set_unit_var_command->value().to_debug_string() << "\n"; - unit->formula_manager().add_formula_var(set_unit_var_command->key(), set_unit_var_command->value()); - made_moves.push_back(action); - } else { - ERR_AI << "ERROR #" << status << " while executing 'set_unit_var' formula function" << std::endl; - if(safe_call) - error = variant(new safe_call_result(set_unit_var_command, - status)); - } - - } else if( action.is_string() && action.as_string() == "continue") { - if( infinite_loop_guardian_.continue_check() ) { - made_moves.push_back(action); - } else { - //too many calls in a row - possible infinite loop - ERR_AI << "ERROR #" << 5001 << " while executing 'continue' formula keyword" << std::endl; - - if( safe_call ) - error = variant(new safe_call_result(nullptr, 5001)); - } - } else if( action.is_string() && (action.as_string() == "end_turn" || action.as_string() == "end" ) ) { - return variant(); - } else if(fallback_command) { - if(get_recursion_count()get_backup()->evaluate(callable); - - if(backup_result.is_list()) { - for(size_t n = 1; n <= backup_result.num_elements() ; ++n) { - vars.push(backup_result[ backup_result.num_elements() - n ]); - } - } else { - vars.push(backup_result); - } - - //store the result in safe_call_callable case we would like to display it to the user - //for example if this formula was executed from commandline - safe_call->set_backup_result(backup_result); - - error = variant(); - } - } - - return variant(made_moves); -} - void formula_ai::add_formula_function(const std::string& name, const_formula_ptr formula, const_formula_ptr precondition, const std::vector& args) { formula_function_ptr fcn(new user_formula_function(name,formula,precondition,args)); @@ -884,6 +593,10 @@ void formula_ai::get_inputs(std::vector* inputs) const add_input(inputs, "enemy_and_unowned_villages"); } +void formula_ai::set_value(const std::string& key, const variant& value) { + vars_.mutate_value(key, value); +} + variant formula_ai::get_keeps() const { if(keeps_cache_.is_null()) { @@ -976,6 +689,7 @@ bool formula_ai::execute_candidate_action(ca_ptr fai_ca) return !make_action(move_formula, callable).is_empty(); } +#if 0 formula_ai::gamestate_change_observer::gamestate_change_observer() : set_var_counter_(), set_unit_var_counter_(), continue_counter_() { @@ -1016,6 +730,7 @@ bool formula_ai::gamestate_change_observer::continue_check() { continue_counter_++; return true; } +#endif config formula_ai::to_config() const { diff --git a/src/ai/formula/ai.hpp b/src/ai/formula/ai.hpp index 9ac12ea05882c..f4c292443f7ad 100644 --- a/src/ai/formula/ai.hpp +++ b/src/ai/formula/ai.hpp @@ -87,6 +87,7 @@ class formula_ai : public readonly_context_proxy, public game_logic::formula_cal virtual void add_formula_function(const std::string& name, game_logic::const_formula_ptr formula, game_logic::const_formula_ptr precondition, const std::vector& args); +#if 0 //class responsible for looking for possible infinite loops when calling set_var or set_unit_var class gamestate_change_observer : public events::observer { @@ -106,6 +107,7 @@ class formula_ai : public readonly_context_proxy, public game_logic::formula_cal bool continue_check(); }; +#endif typedef game_logic::position_callable::move_map_backup move_map_backup; @@ -157,17 +159,18 @@ class formula_ai : public readonly_context_proxy, public game_logic::formula_cal const config cfg_; recursion_counter recursion_counter_; void display_message(const std::string& msg) const; - variant execute_variant(const variant& var, ai_context &ai_, bool commandline=false); virtual variant get_value(const std::string& key) const; + void set_value(const std::string& key, const variant& value); virtual void get_inputs(game_logic::formula_input_vector* inputs) const; mutable variant keeps_cache_; - gamestate_change_observer infinite_loop_guardian_; +// gamestate_change_observer infinite_loop_guardian_; game_logic::map_formula_callable vars_; game_logic::ai_function_symbol_table function_table_; friend class ai_default; + friend ai_context& get_ai_context(const formula_callable* for_fai); }; } //end of namespace ai diff --git a/src/ai/formula/callable_objects.cpp b/src/ai/formula/callable_objects.cpp index bd05339962d26..58933bad86510 100644 --- a/src/ai/formula/callable_objects.cpp +++ b/src/ai/formula/callable_objects.cpp @@ -16,10 +16,33 @@ #include "attack_prediction.hpp" #include "game_board.hpp" #include "ai/formula/callable_objects.hpp" +#include "ai/composite/contexts.hpp" #include "resources.hpp" #include "map/map.hpp" +#include "ai/game_info.hpp" +#include "ai/actions.hpp" +#include "units/formula_manager.hpp" +#include "log.hpp" +#include "menu_events.hpp" // for fallback_ai_to_human_exception + +static lg::log_domain log_formula_ai("ai/engine/fai"); +#define DBG_AI LOG_STREAM(debug, log_formula_ai) +#define LOG_AI LOG_STREAM(info, log_formula_ai) +#define WRN_AI LOG_STREAM(warn, log_formula_ai) +#define ERR_AI LOG_STREAM(err, log_formula_ai) + +namespace ai { + +ai_context& get_ai_context(const game_logic::formula_callable* for_fai) { + const formula_ai* fai = dynamic_cast(for_fai); + assert(fai != nullptr); // Why not just use dynamic_cast instead then? + return *const_cast(fai)->ai_ptr_; +} + +} namespace game_logic { + using namespace ai; variant move_map_callable::get_value(const std::string& key) const { @@ -63,6 +86,18 @@ int move_callable::do_compare(const formula_callable* callable) const return dst_.do_compare(other_dst); } +variant move_callable::execute_self(variant ctxt) { + ai_context& ai = get_ai_context(ctxt.as_callable()); + move_result_ptr move_result = ai.execute_move_action(src_, dst_, true); + + if(!move_result->is_ok()) { + LOG_AI << "ERROR #" << move_result->get_status() << " while executing 'move' formula function\n" << std::endl; + return variant(new safe_call_result(this, move_result->get_status(), move_result->get_unit_location())); + } + + return variant(move_result->is_gamestate_changed()); +} + int move_partial_callable::do_compare(const formula_callable* callable) const { const move_partial_callable* mv_callable = dynamic_cast(callable); @@ -80,6 +115,18 @@ int move_partial_callable::do_compare(const formula_callable* callable) const return dst_.do_compare(other_dst); } +variant move_partial_callable::execute_self(variant ctxt) { + ai_context& ai = get_ai_context(ctxt.as_callable()); + move_result_ptr move_result = ai.execute_move_action(src_, dst_, false); + + if(!move_result->is_ok()) { + LOG_AI << "ERROR #" << move_result->get_status() << " while executing 'move_partial' formula function\n" << std::endl; + return variant(new safe_call_result(this, move_result->get_status(), move_result->get_unit_location())); + } + + return variant(move_result->is_gamestate_changed()); +} + variant position_callable::get_value(const std::string& key) const { if(key == "chance") { return variant(chance_); @@ -167,6 +214,35 @@ int attack_callable::do_compare(const game_logic::formula_callable* callable) return this->defender_weapon() - other_def_weapon; } +variant attack_callable::execute_self(variant ctxt) { + ai_context& ai = get_ai_context(ctxt.as_callable()); + bool gamestate_changed = false; + move_result_ptr move_result; + + if(move_from_ != src_) { + move_result = ai.execute_move_action(move_from_, src_, false); + gamestate_changed |= move_result->is_gamestate_changed(); + + if(!move_result->is_ok()) { + //move part failed + LOG_AI << "ERROR #" << move_result->get_status() << " while executing 'attack' formula function\n" << std::endl; + return variant(new safe_call_result(this, move_result->get_status(), move_result->get_unit_location())); + } + } + + if(!move_result || move_result->is_ok()) { + //if move wasn't done at all or was done successfully + attack_result_ptr attack_result = ai.execute_attack_action(src_, dst_, weapon()); + gamestate_changed |= attack_result->is_gamestate_changed(); + if(!attack_result->is_ok()) { + //attack failed + LOG_AI << "ERROR #" << attack_result->get_status() << " while executing 'attack' formula function\n" << std::endl; + return variant(new safe_call_result(this, attack_result->get_status())); + } + } + + return variant(gamestate_changed); +} variant attack_map_callable::get_value(const std::string& key) const { if(key == "attacks") { @@ -231,7 +307,19 @@ void recall_callable::get_inputs(formula_input_vector* inputs) const { add_input(inputs, "loc"); } +variant recall_callable::execute_self(variant ctxt) { + ai_context& ai = get_ai_context(ctxt.as_callable()); + recall_result_ptr recall_result = ai.check_recall_action(id_, loc_); + + if(recall_result->is_ok()) { + recall_result->execute(); + } else { + LOG_AI << "ERROR #" << recall_result->get_status() << " while executing 'recall' formula function\n" << std::endl; + return variant(new safe_call_result(this, recall_result->get_status())); + } + return variant(recall_result->is_gamestate_changed()); +} variant recruit_callable::get_value(const std::string& key) const { if( key == "unit_type") @@ -246,23 +334,24 @@ void recruit_callable::get_inputs(formula_input_vector* inputs) const { add_input(inputs, "recruit_loc"); } +variant recruit_callable::execute_self(variant ctxt) { + ai_context& ai = get_ai_context(ctxt.as_callable()); + recruit_result_ptr recruit_result = ai.check_recruit_action(type_, loc_); -variant set_var_callable::get_value(const std::string& key) const { - if(key == "key") - return variant(key_); - - if(key == "value") - return value_; - - return variant(); -} + //is_ok()==true means that the action is successful (eg. no unexpected events) + //is_ok() must be checked or the code will complain :) + if(recruit_result->is_ok()) { + recruit_result->execute(); + } else { + LOG_AI << "ERROR #" << recruit_result->get_status() << " while executing 'recruit' formula function\n" << std::endl; + return variant(new safe_call_result(this, recruit_result->get_status())); + } -void set_var_callable::get_inputs(formula_input_vector* inputs) const { - add_input(inputs, "key"); - add_input(inputs, "value"); + //is_gamestate_changed()==true means that the game state was somehow changed by action. + //it is believed that during a turn, a game state can change only a finite number of times + return variant(recruit_result->is_gamestate_changed()); } - variant set_unit_var_callable::get_value(const std::string& key) const { if(key == "loc") return variant(new location_callable(loc_)); @@ -282,45 +371,35 @@ void set_unit_var_callable::get_inputs(formula_input_vector* inputs) const { add_input(inputs, "value"); } - -variant safe_call_callable::get_value(const std::string& key) const { - if(key == "main") - return variant(main_); - - if(key == "backup") - return variant(backup_); - - return variant(); -} - -void safe_call_callable::get_inputs(formula_input_vector* inputs) const { - add_input(inputs, "main"); - add_input(inputs, "backup"); -} - - -variant safe_call_result::get_value(const std::string& key) const { - if(key == "status") - return variant(status_); - - if(key == "object") { - if( failed_callable_ != nullptr) - return variant(failed_callable_); - else - return variant(); +variant set_unit_var_callable::execute_self(variant ctxt) { + int status = 0; + unit_map::iterator unit; + unit_map& units = resources::gameboard->units(); + +/* if(!infinite_loop_guardian_.set_unit_var_check()) { + status = 5001; //exceeded nmber of calls in a row - possible infinite loop + } else*/ if((unit = units.find(loc_)) == units.end()) { + status = 5002; //unit not found + } else if(unit->side() != get_ai_context(ctxt.as_callable()).get_side()) { + status = 5003;//unit does not belong to our side } - if(key == "current_loc" && current_unit_location_ != map_location()) - return variant(new location_callable(current_unit_location_)); + if(status == 0) { + LOG_AI << "Setting unit variable: " << key_ << " -> " << value_.to_debug_string() << "\n"; + unit->formula_manager().add_formula_var(key_, value_); + return variant(true); + } - return variant(); + ERR_AI << "ERROR #" << status << " while executing 'set_unit_var' formula function" << std::endl; + return variant(new safe_call_result(this, status)); } -void safe_call_result::get_inputs(formula_input_vector* inputs) const { - add_input(inputs, "status"); - add_input(inputs, "object"); - if( current_unit_location_ != map_location() ) - add_input(inputs, "current_loc"); +variant fallback_callable::execute_self(variant) { +// if(get_recursion_count() < recursion_counter::MAX_COUNTER_VALUE) { + //we want give control of the side to human for the rest of this turn + throw fallback_ai_to_human_exception(); +// } + return variant(false); } } diff --git a/src/ai/formula/callable_objects.hpp b/src/ai/formula/callable_objects.hpp index ab75240f9965d..ebbe7ccd7f5b6 100644 --- a/src/ai/formula/callable_objects.hpp +++ b/src/ai/formula/callable_objects.hpp @@ -45,7 +45,7 @@ class attack_map_callable : public formula_callable { void collect_possible_attacks(std::vector& vars, map_location attacker_location, map_location attack_position) const; }; -class attack_callable : public formula_callable { +class attack_callable : public action_callable { map_location move_from_, src_, dst_; battle_context bc_; variant get_value(const std::string& key) const; @@ -65,10 +65,10 @@ class attack_callable : public formula_callable { * (nondeterministic in consequent game runs) if method argument is not * attack_callable */ int do_compare(const game_logic::formula_callable* callable) const; + variant execute_self(variant ctxt) override; }; - -class move_callable : public game_logic::formula_callable { +class move_callable : public action_callable { map_location src_, dst_; variant get_value(const std::string& key) const { if(key == "src") { @@ -94,10 +94,10 @@ class move_callable : public game_logic::formula_callable { const map_location& src() const { return src_; } const map_location& dst() const { return dst_; } + variant execute_self(variant ctxt) override; }; - -class move_partial_callable : public game_logic::formula_callable { +class move_partial_callable : public action_callable { map_location src_, dst_; variant get_value(const std::string& key) const { if(key == "src") { @@ -123,11 +123,10 @@ class move_partial_callable : public game_logic::formula_callable { const map_location& src() const { return src_; } const map_location& dst() const { return dst_; } + variant execute_self(variant ctxt) override; }; - - -class recall_callable : public formula_callable { +class recall_callable : public action_callable { map_location loc_; std::string id_; @@ -141,10 +140,10 @@ class recall_callable : public formula_callable { const map_location& loc() const { return loc_; } const std::string& id() const { return id_; } + variant execute_self(variant ctxt) override; }; - -class recruit_callable : public formula_callable { +class recruit_callable : public action_callable { map_location loc_; std::string type_; @@ -158,26 +157,10 @@ class recruit_callable : public formula_callable { const map_location& loc() const { return loc_; } const std::string& type() const { return type_; } + variant execute_self(variant ctxt) override; }; - -class set_var_callable : public formula_callable { - std::string key_; - variant value_; - variant get_value(const std::string& key) const; - - void get_inputs(formula_input_vector* inputs) const; -public: - set_var_callable(const std::string& key, const variant& value) - : key_(key), value_(value) - {} - - const std::string& key() const { return key_; } - variant value() const { return value_; } -}; - - -class set_unit_var_callable : public formula_callable { +class set_unit_var_callable : public action_callable { std::string key_; variant value_; map_location loc_; @@ -192,56 +175,18 @@ class set_unit_var_callable : public formula_callable { const std::string& key() const { return key_; } variant value() const { return value_; } const map_location loc() const { return loc_; } + variant execute_self(variant ctxt) override; }; -class fallback_callable : public formula_callable { +class fallback_callable : public action_callable { variant get_value(const std::string& /*key*/) const { return variant(); } public: explicit fallback_callable() { } + variant execute_self(variant ctxt) override; }; -class safe_call_callable : public formula_callable { - variant main_; - variant backup_; - expression_ptr backup_formula_; - variant get_value(const std::string& key) const; - - void get_inputs(formula_input_vector* inputs) const; -public: - safe_call_callable(const variant& main, const expression_ptr& backup) - : main_(main) - , backup_() - , backup_formula_(backup) - {} - - const variant& get_main() const { return main_; } - const expression_ptr& get_backup() const { return backup_formula_; } - - void set_backup_result(const variant& v) { - backup_ = v; - } -}; - - -class safe_call_result : public formula_callable { - const formula_callable* failed_callable_; - const map_location current_unit_location_; - const int status_; - - variant get_value(const std::string& key) const; - - void get_inputs(formula_input_vector* inputs) const; - -public: - safe_call_result(const formula_callable* callable, int status, - const map_location& loc = map_location() ) - : failed_callable_(callable), current_unit_location_(loc), status_(status) - {} -}; - - -class move_map_callable : public game_logic::formula_callable { +class move_map_callable : public formula_callable { typedef std::multimap move_map; const move_map& srcdst_; const move_map& dstsrc_; diff --git a/src/formula/callable.hpp b/src/formula/callable.hpp index 13cac3e692f61..64053e84d24f8 100644 --- a/src/formula/callable.hpp +++ b/src/formula/callable.hpp @@ -74,9 +74,11 @@ class formula_callable { serialize_to_string(str); } + bool has_key(const std::string& key) const { return !query_value(key).is_null(); } +protected: template static variant convert_map(const std::map& input_map) { @@ -118,7 +120,6 @@ class formula_callable { inputs->push_back(formula_input(key, access_type)); } -protected: virtual void set_value(const std::string& key, const variant& value); virtual int do_compare(const formula_callable* callable) const { if( type_ < callable->type_ ) @@ -147,6 +148,11 @@ class formula_callable { bool has_self_; }; +class action_callable : public formula_callable { +public: + virtual variant execute_self(variant ctxt) = 0; +}; + class formula_callable_with_backup : public formula_callable { const formula_callable& main_; const formula_callable& backup_; diff --git a/src/formula/callable_objects.cpp b/src/formula/callable_objects.cpp index 56ea27996b737..31b17b4ba3907 100644 --- a/src/formula/callable_objects.cpp +++ b/src/formula/callable_objects.cpp @@ -19,6 +19,13 @@ #include "map/map.hpp" #include "team.hpp" #include "units/formula_manager.hpp" +#include "log.hpp" + +static lg::log_domain log_scripting_formula("scripting/formula"); +#define DBG_SF LOG_STREAM(debug, log_scripting_formula) +#define LOG_SF LOG_STREAM(info, log_scripting_formula) +#define WRN_SF LOG_STREAM(warn, log_scripting_formula) +#define ERR_SF LOG_STREAM(err, log_scripting_formula) namespace game_logic { @@ -663,4 +670,92 @@ variant team_callable::get_value(const std::string& key) const return variant(); } +variant set_var_callable::get_value(const std::string& key) const { + if(key == "key") { + return variant(key_); + } else if(key == "value") { + return value_; + } + return variant(); +} + +void set_var_callable::get_inputs(std::vector* inputs) const { + add_input(inputs, "key"); + add_input(inputs, "value"); +} + +variant set_var_callable::execute_self(variant ctxt) { +// if(infinite_loop_guardian_.set_var_check()) { + if(formula_callable* obj = ctxt.try_convert()) { + LOG_SF << "Setting variable: " << key_ << " -> " << value_.to_debug_string() << "\n"; + obj->mutate_value(key_, value_); + return variant(true); + } +// } + //too many calls in a row - possible infinite loop + ERR_SF << "ERROR #" << 5001 << " while executing 'set_var' formula function" << std::endl; + + return variant(new safe_call_result(this, 5001)); +} + +variant safe_call_callable::get_value(const std::string& key) const { + if(key == "main") { + return variant(main_); + } else if(key == "backup") { + return variant(backup_); + } + return variant(); +} + +void safe_call_callable::get_inputs(std::vector* inputs) const { + add_input(inputs, "main"); + add_input(inputs, "backup"); +} + +variant safe_call_callable::execute_self(variant ctxt) { + variant res; + if(action_callable* action = main_.try_convert()) { + res = action->execute_self(ctxt); + } + + if(safe_call_result* error = res.try_convert()) { + /*if we have safe_call formula and either error occurred, or current action + *was not recognized, then evaluate backup formula from safe_call and execute it + *during the next loop + */ + + game_logic::map_formula_callable callable(ctxt.try_convert()); + callable.add("error", res); + + //store the result in safe_call_callable in case we would like to display it to the user + //for example if this formula was executed from commandline + backup_ = get_backup()->evaluate(callable); + ctxt.execute_variant(backup_); + } + return variant(true); +} + +variant safe_call_result::get_value(const std::string& key) const { + if(key == "status") { + return variant(status_); + } else if(key == "object") { + if(failed_callable_ != nullptr) + return variant(failed_callable_); + else + return variant(); + } else if(key == "current_loc" && current_unit_location_ != map_location()) { + return variant(new location_callable(current_unit_location_)); + } + + return variant(); +} + +void safe_call_result::get_inputs(std::vector* inputs) const { + add_input(inputs, "status"); + add_input(inputs, "object"); + if(current_unit_location_ != map_location()) { + add_input(inputs, "current_loc"); + } +} + } // namespace game_logic diff --git a/src/formula/callable_objects.hpp b/src/formula/callable_objects.hpp index 805724ca88807..0a9925505f1d4 100644 --- a/src/formula/callable_objects.hpp +++ b/src/formula/callable_objects.hpp @@ -16,6 +16,7 @@ #define CALLABLE_OBJECTS_HPP_INCLUDED #include "formula/callable.hpp" +#include "formula/formula.hpp" #include "units/unit.hpp" @@ -173,6 +174,58 @@ class team_callable : public formula_callable const team& team_; }; +class set_var_callable : public action_callable { + std::string key_; + variant value_; + variant get_value(const std::string& key) const; + + void get_inputs(std::vector* inputs) const; +public: + set_var_callable(const std::string& key, const variant& value) + : key_(key), value_(value) {} + + const std::string& key() const { return key_; } + variant value() const { return value_; } + variant execute_self(variant ctxt) override; +}; + +class safe_call_callable : public action_callable { + variant main_; + variant backup_; + expression_ptr backup_formula_; + variant get_value(const std::string& key) const; + + void get_inputs(std::vector* inputs) const; +public: + safe_call_callable(const variant& main, const expression_ptr& backup) + : main_(main) + , backup_() + , backup_formula_(backup) {} + + const variant& get_main() const { return main_; } + const expression_ptr& get_backup() const { return backup_formula_; } + + void set_backup_result(const variant& v) { + backup_ = v; + } + variant execute_self(variant ctxt) override; +}; + +class safe_call_result : public formula_callable { + const formula_callable* failed_callable_; + const map_location current_unit_location_; + const int status_; + + variant get_value(const std::string& key) const; + + void get_inputs(std::vector* inputs) const; + +public: + safe_call_result(const formula_callable* callable, int status, + const map_location& loc = map_location()) + : failed_callable_(callable), current_unit_location_(loc), status_(status) {} +}; + } // namespace game_logic #endif diff --git a/src/formula/variant.cpp b/src/formula/variant.cpp index 68d788cb86a90..3f4545f803a5e 100644 --- a/src/formula/variant.cpp +++ b/src/formula/variant.cpp @@ -12,9 +12,22 @@ See the COPYING file for more details. */ +#include +#include +#include +#include +#include + #include "formatter.hpp" #include "formula/function.hpp" #include "utils/math.hpp" +#include "log.hpp" + +static lg::log_domain log_scripting_formula("scripting/formula"); +#define DBG_SF LOG_STREAM(debug, log_scripting_formula) +#define LOG_SF LOG_STREAM(info, log_scripting_formula) +#define WRN_SF LOG_STREAM(warn, log_scripting_formula) +#define ERR_SF LOG_STREAM(err, log_scripting_formula) #include #include @@ -756,3 +769,50 @@ std::string variant::to_debug_string(bool verbose, game_logic::formula_seen_stac return value_->get_debug_string(*seen, verbose); } + +variant variant::execute_variant(const variant& var) { + std::stack vars; + if(var.is_list()) { + for(size_t n = 1; n <= var.num_elements(); ++n) { + vars.push(var[var.num_elements() - n]); + } + } else { + vars.push(var); + } + + std::vector made_moves; + + while(!vars.empty()) { + + if(vars.top().is_null()) { + vars.pop(); + continue; + } + + if(game_logic::action_callable* action = vars.top().try_convert()) { + variant res = action->execute_self(*this); + if(res.is_int() && res.as_bool()) { + made_moves.push_back(vars.top()); + } + } else if(vars.top().is_string() && vars.top().as_string() == "continue") { +// if(infinite_loop_guardian_.continue_check()) { + made_moves.push_back(vars.top()); +// } else { + //too many calls in a row - possible infinite loop +// ERR_SF << "ERROR #5001 while executing 'continue' formula keyword" << std::endl; + +// if(safe_call) +// error = variant(new game_logic::safe_call_result(nullptr, 5001)); +// } + } else if(vars.top().is_string() && (vars.top().as_string() == "end_turn" || vars.top().as_string() == "end")) { + break; + } else { + //this information is unneeded when evaluating formulas from commandline + ERR_SF << "UNRECOGNIZED MOVE: " << vars.top().to_debug_string() << std::endl; + } + + vars.pop(); + } + + return variant(made_moves); +} diff --git a/src/formula/variant.hpp b/src/formula/variant.hpp index a1622167e3b80..58b21c74e12fc 100644 --- a/src/formula/variant.hpp +++ b/src/formula/variant.hpp @@ -161,6 +161,7 @@ class variant return type().to_string(); } + variant execute_variant(const variant& to_exec); private: template std::shared_ptr value_cast() const