local fcns = wesnoth.new_function_table() --[=[ class calculate_map_ownership_function : public function_expression { public: calculate_map_ownership_function(const args_list& args, const formula_ai& ai) : function_expression("calculate_map_ownership", args, 2, 5), ai_(ai) { } private: struct indexer { int w, h; indexer(int a, int b) : w(a), h(b) { } int operator()(const map_location& loc) const { return loc.y * w + loc.x; } }; struct node { int movement_cost_; map_location loc_; /** * If equal to search_counter, the node is off the list. * If equal to search_counter + 1, the node is on the list. * Otherwise it is outdated. */ unsigned in; node(int moves, const map_location &loc) : movement_cost_(moves) , loc_(loc) , in(0) { } node() : movement_cost_(0) , loc_() , in(0) { } bool operator<(const node& o) const { return movement_cost_ < o.movement_cost_; } }; struct comp { const std::vector& nodes; comp(const std::vector& n) : nodes(n) { } bool operator()(int l, int r) const { return nodes[r] < nodes[l]; } }; void find_movemap(const unit_adapter& u, const map_location& loc, std::vector& scores, bool allow_teleport) const { const std::set& teleports = allow_teleport ? ai_.current_team().villages() : std::set(); const gamemap& map = resources::gameboard->map(); std::vector locs(6 + teleports.size()); std::copy(teleports.begin(), teleports.end(), locs.begin() + 6); search_counter += 2; if (search_counter == 0) search_counter = 2; static std::vector nodes; nodes.resize(map.w() * map.h()); indexer index(map.w(), map.h()); comp node_comp(nodes); nodes[index(loc)] = node(0, loc); std::vector pq; pq.push_back(index(loc)); while (!pq.empty()) { node& n = nodes[pq.front()]; std::pop_heap(pq.begin(), pq.end(), node_comp); pq.pop_back(); n.in = search_counter; get_adjacent_tiles(n.loc_, &locs[0]); for (int i = teleports.count(n.loc_) ? locs.size() : 6; i-- > 0; ) { if (!locs[i].valid(map.w(), map.h())) continue; node& next = nodes[index(locs[i])]; bool next_visited = next.in - search_counter <= 1u; // test if the current path to locs[i] is better than this one could possibly be. // we do this a couple more times below if (next_visited && !(n < next) ) continue; const int move_cost = u.movement_cost(map[locs[i]]); node t = node(n.movement_cost_ + move_cost, locs[i]); if (next_visited && !(t < next) ) continue; bool in_list = next.in == search_counter + 1; t.in = search_counter + 1; next = t; // if already in the priority queue then we just update it, else push it. if (in_list) { std::push_heap(pq.begin(), std::find(pq.begin(), pq.end(), index(locs[i])) + 1, node_comp); } else { pq.push_back(index(locs[i])); std::push_heap(pq.begin(), pq.end(), node_comp); } } } for (int x = 0; x < map.w(); ++x) { for (int y = 0; y < map.h(); ++y) { int i = y * map.w() + x; const node &n = nodes[i]; scores[i] = scores[i] + n.movement_cost_; //std::cout << x << "," << y << ":" << n.movement_cost << std::endl; } } } variant execute(const formula_callable& variables, formula_debugger *fdb) const { int w = resources::gameboard->map().w(); int h = resources::gameboard->map().h(); const variant units_input = args()[0]->evaluate(variables,fdb); const variant leaders_input = args()[1]->evaluate(variables,fdb); int enemy_tollerancy = 3; if( args().size() > 2 ) enemy_tollerancy = args()[2]->evaluate(variables,fdb).as_int(); int enemy_border_tollerancy = 5; if( args().size() > 3 ) enemy_border_tollerancy = args()[3]->evaluate(variables,fdb).as_int(); int ally_tollerancy = 3; if( args().size() > 4 ) ally_tollerancy = args()[4]->evaluate(variables,fdb).as_int(); if( !units_input.is_list() ) return variant(); size_t number_of_teams = units_input.num_elements(); std::vector< std::vector > scores( number_of_teams ); for( size_t i = 0; i< number_of_teams; ++i) scores[i].resize(w*h); // for(unit_map::const_iterator i = resources::units->begin(); i != resources::units->end(); ++i) { // unit_counter[i->second.side()-1]++; // unit_adapter unit(i->second); // find_movemap( resources::gameboard->map(), *resources::units, unit, i->first, scores[i->second.side()-1], ai_.*resources::teams , true ); // } for(size_t side = 0 ; side < units_input.num_elements() ; ++side) { if( leaders_input[side].is_empty() ) continue; const map_location loc = convert_variant(leaders_input[side][0])->loc(); const variant units_of_side = units_input[side]; for(size_t unit_it = 0 ; unit_it < units_of_side.num_elements() ; ++unit_it) { unit_adapter unit(units_of_side[unit_it]); find_movemap( unit, loc, scores[side], true ); } } size_t index = 0; for( std::vector< std::vector >::iterator i = scores.begin() ; i != scores.end() ; ++i) { for( std::vector::iterator j = i->begin() ; j != i->end() ; ++j ) { if(units_input[index].num_elements() != 0) { *j /= units_input[index].num_elements(); } else { *j = 0; } } ++index; } //std::vector res; std::map res; size_t current_side = ai_.get_side() - 1 ; std::vector enemies; std::vector allies; for(size_t side = 0 ; side < units_input.num_elements() ; ++side) { if( side == current_side) continue; if( ai_.current_team().is_enemy(side+1) ) { if( !leaders_input[side].is_empty() ) enemies.push_back(side); } else { if( !leaders_input[side].is_empty() ) allies.push_back(side); } } //calculate_map_ownership( recruits_of_side, map(units_of_side, 'units', map( filter(units, leader), loc) ) ) //map(, debug_label(key,value)) for (int x = 0; x < w; ++x) { for (int y = 0; y < h; ++y) { int i = y * w + x; bool valid = true; bool enemy_border = false; if( scores[current_side][i] > 98 ) continue; for (int side : enemies) { int diff = scores[current_side][i] - scores[side][i]; if ( diff > enemy_tollerancy) { valid = false; break; } else if( abs(diff) < enemy_border_tollerancy ) enemy_border = true; } if( valid ) { for (int side : allies) { if ( scores[current_side][i] - scores[side][i] > ally_tollerancy ) { valid = false; break; } } } if( valid ) { if( enemy_border ) res.insert( std::pair(variant(new location_callable(map_location(x, y))), variant(scores[0][i] + 10000) )); else res.insert( std::pair(variant(new location_callable(map_location(x, y))), variant(scores[0][i] ) )); } } } return variant(&res); } const formula_ai& ai_; }; class nearest_loc_function : public function_expression { public: nearest_loc_function(const args_list& args) : function_expression("nearest_loc", args, 2, 2) { } private: variant execute(const formula_callable& variables, formula_debugger *fdb) const { const map_location loc = convert_variant(args()[0]->evaluate(variables,add_debug_info(fdb,0,"nearest_loc:location")))->loc(); variant items = args()[1]->evaluate(variables,add_debug_info(fdb,1,"nearest_loc:locations")); int best = 1000000; int best_i = -1; for(size_t i = 0; i < items.num_elements(); ++i) { const map_location move_loc = convert_variant(items[i])->loc(); int distance = distance_between(loc, move_loc); if(distance < best) { best = distance; best_i = i; } } if( best_i != -1) return variant(new location_callable(convert_variant(items[best_i])->loc())); else return variant(); } }; class adjacent_locs_function : public function_expression { public: adjacent_locs_function(const args_list& args) : function_expression("adjacent_locs", args, 1, 1) { } private: variant execute(const formula_callable& variables, formula_debugger *fdb) const { const map_location loc = convert_variant(args()[0]->evaluate(variables,add_debug_info(fdb,0,"adjacent_locs:location")))->loc(); map_location adj[6]; get_adjacent_tiles(loc, adj); std::vector v; for(int n = 0; n != 6; ++n) { if (resources::gameboard->map().on_board(adj[n]) ) v.push_back(variant(new location_callable(adj[n]))); } return variant(&v); } }; class locations_in_radius_function : public function_expression { public: locations_in_radius_function(const args_list& args) : function_expression("locations_in_radius", args, 2, 2) { } private: variant execute(const formula_callable& variables, formula_debugger *fdb) const { const map_location loc = convert_variant(args()[0]->evaluate(variables,fdb))->loc(); int range = args()[1]->evaluate(variables,fdb).as_int(); if( range < 0 ) return variant(); if(!range) return variant(new location_callable(loc)); std::vector res; get_tiles_in_radius( loc, range, res); std::vector v; v.reserve(res.size()+1); v.push_back(variant(new location_callable(loc))); for(size_t n = 0; n != res.size(); ++n) { if (resources::gameboard->map().on_board(res[n]) ) v.push_back(variant(new location_callable(res[n]))); } return variant(&v); } }; /** FormulaAI function to run fai script from file. Usable from in-game console. * arguments[0] - required file name, follows the usual wml convention */ class run_file_function : public function_expression { public: explicit run_file_function(const args_list& args, formula_ai& ai) : function_expression("run_file", args, 1, 1), ai_(ai) {} private: variant execute(const formula_callable& variables, formula_debugger *fdb) const { const args_list& arguments = args(); const variant var0 = arguments[0]->evaluate(variables,add_debug_info(fdb,0,"run_file:file")); const std::string filename = var0.string_cast(); //NOTE: get_wml_location also filters file path to ensure it doesn't contain things like "../../top/secret" std::string path = filesystem::get_wml_location(filename); if(path.empty()) { ERR_AI << "run_file : not found [" << filename <<"]"<< std::endl; return variant(); //no suitable file } std::string formula_string = filesystem::read_file(path); //need to get function_table from somewhere or delegate to someone who has access to it formula_ptr parsed_formula = ai_.create_optional_formula(formula_string); if(parsed_formula == game_logic::formula_ptr()) { ERR_AI << "run_file : unable to create formula"<< std::endl; return variant(); //was unable to create a formula from file } return parsed_formula->evaluate(variables,add_debug_info(fdb,-1,"run_file:formula_from_file")); } formula_ai& ai_; }; class castle_locs_function : public function_expression { public: castle_locs_function(const args_list& args) : function_expression("castle_locs", args, 1, 1) { } private: variant execute(const formula_callable& variables, formula_debugger *fdb) const { const map_location starting_loc = convert_variant(args()[0]->evaluate(variables,add_debug_info(fdb,0,"castle_locs:location")))->loc(); //looks like reimplementing a generic graph search algorithm to me std::set< map_location > visited_locs; std::queue< map_location > queued_locs; queued_locs.push(starting_loc); while( !queued_locs.empty() ) { const map_location loc = queued_locs.front(); queued_locs.pop(); if ( visited_locs.find( loc ) != visited_locs.end() ) continue; visited_locs.insert(loc); map_location adj[6]; get_adjacent_tiles(loc, adj); for(int n = 0; n != 6; ++n) { if (resources::gameboard->map().on_board(adj[n]) && visited_locs.find( adj[n] ) == visited_locs.end() ) { if (resources::gameboard->map().get_terrain_info(adj[n]).is_keep() || resources::gameboard->map().get_terrain_info(adj[n]).is_castle() ) { queued_locs.push(adj[n]); } } } } if ( !resources::gameboard->map().get_terrain_info(starting_loc).is_keep() && !resources::gameboard->map().get_terrain_info(starting_loc).is_castle() ) visited_locs.erase(starting_loc); std::vector res; for (const map_location& ml : visited_locs) { res.push_back( variant(new location_callable( ml ) ) ); } return variant(&res); } }; /** * timeofday_modifer formula function. Returns combat modifier, taking * alignment, illuminate, time of day and fearless trait into account. * 'leadership' and 'slowed' are not taken into account. * arguments[0] - unit * arguments[1] - location (optional, defaults to unit's current location. */ class timeofday_modifier_function : public function_expression { public: timeofday_modifier_function(const args_list& args) : function_expression("timeofday_modifier", args, 1, 2) { } private: variant execute(const formula_callable& variables, formula_debugger *fdb) const { variant u = args()[0]->evaluate(variables,add_debug_info(fdb,0,"timeofday_modifier:unit")); if( u.is_null() ) { return variant(); } const unit_callable* u_call = try_convert_variant(u); if (u_call == nullptr) { return variant(); } const unit& un = u_call->get_unit(); map_location const* loc = nullptr; if(args().size()==2) { loc = &convert_variant(args()[1]->evaluate(variables,add_debug_info(fdb,1,"timeofday_modifier:location")))->loc(); } if (loc == nullptr) { loc = &u_call->get_location(); } return variant(combat_modifier(resources::gameboard->units(), resources::gameboard->map(),*loc, un.alignment(), un.is_fearless())); } }; class nearest_keep_function : public function_expression { public: nearest_keep_function(const args_list& args, const formula_ai& ai) : function_expression("nearest_keep", args, 1, 1), ai_(ai) { } private: variant execute(const formula_callable& variables, formula_debugger *fdb) const { const map_location loc = convert_variant(args()[0]->evaluate(variables,add_debug_info(fdb,0,"nearest_keep:location")))->loc(); int best = 1000000; int best_i = -1; ai_.get_keeps(); int size = ai_.get_keeps_cache().num_elements(); for( int i = 0 ; i < size; ++i) { int distance = distance_between(loc, convert_variant(ai_.get_keeps_cache()[i])->loc() ); if(distance < best) { best = distance; best_i = i; } } if( best_i != -1) return variant(new location_callable(convert_variant(ai_.get_keeps_cache()[best_i])->loc())); else return variant(); } const formula_ai& ai_; }; /** * Find suitable keep for unit at location * arguments[0] - location for unit on which the suitable keep is to be found */ class suitable_keep_function : public function_expression { public: suitable_keep_function(const args_list& args, formula_ai& ai) : function_expression("suitable_keep", args, 1, 1), ai_(ai) { } private: variant execute(const formula_callable& variables, formula_debugger *fdb) const { const map_location loc = convert_variant(args()[0]->evaluate(variables,add_debug_info(fdb,0,"suitable_keep:location")))->loc(); const unit_map& units = *resources::units; const unit_map::const_iterator u = units.find(loc); if (u == units.end()){ return variant(); } const pathfind::paths unit_paths(*u, false, true, ai_.current_team()); return variant(new location_callable(ai_.suitable_keep(loc,unit_paths))); } formula_ai& ai_; }; class find_shroud_function : public function_expression { public: find_shroud_function(const args_list& args, const formula_ai& ai) : function_expression("find_shroud", args, 0, 1), ai_(ai) { } private: variant execute(const formula_callable& variables, formula_debugger *fdb) const { std::vector vars; int w,h; if(args().size()==1) { const gamemap& m = convert_variant(args()[0]->evaluate(variables,add_debug_info(fdb,0,"find_shroud:gamemap")))->get_gamemap(); w = m.w(); h = m.h(); } else { w = resources::gameboard->map().w(); h = resources::gameboard->map().h(); } for(int i = 0; i < w; ++i) for(int j = 0; j < h; ++j) { if(ai_.current_team().shrouded(map_location(i,j))) vars.push_back(variant(new location_callable(i,j))); } return variant(&vars); } const formula_ai& ai_; }; class close_enemies_function : public function_expression { public: close_enemies_function(const args_list& args, const formula_ai& ai) : function_expression("close_enemies", args, 2, 2), ai_(ai) { } private: variant execute(const formula_callable& variables, formula_debugger *fdb) const { std::vector vars; const map_location loc = convert_variant(args()[0]->evaluate(variables,add_debug_info(fdb,0,"close_enemies:location")))->loc(); int range_s = args()[1]->evaluate(variables,add_debug_info(fdb,1,"close_enemies:distance")).as_int(); if (range_s < 0) { WRN_AI << "close_enemies_function: range is negative (" << range_s << ")" << std::endl; range_s = 0; } size_t range = static_cast(range_s); unit_map::const_iterator un = resources::units->begin(); unit_map::const_iterator end = resources::units->end(); while (un != end) { if (distance_between(loc, un->get_location()) <= range) { if (un->side() != ai_.get_side()) {//fixme: ignores allied units vars.push_back(variant(new unit_callable(*un))); } } ++un; } return variant(&vars); } const formula_ai& ai_; }; class calculate_outcome_function : public function_expression { public: calculate_outcome_function(const args_list& args) : function_expression( "calculate_outcome", args, 3, 4) { } private: variant execute(const formula_callable& variables, formula_debugger *fdb) const { std::vector vars; int weapon; if (args().size() > 3) weapon = args()[3]->evaluate(variables,add_debug_info(fdb,3,"calculate_outcome:weapon")).as_int(); else weapon = -1; const unit_map& units = *resources::units; map_location attacker_location = convert_variant(args()[0]->evaluate(variables,add_debug_info(fdb,0,"calculate_outcome:attacker_current_location")))->loc(); if(units.count(attacker_location) == 0) { ERR_AI << "Performing calculate_outcome() with non-existent attacker at (" << attacker_location.x+1 << "," << attacker_location.y+1 << ")\n"; return variant(); } map_location defender_location = convert_variant(args()[2]->evaluate(variables,add_debug_info(fdb,2,"calculate_outcome:defender_location")))->loc(); if(units.count(defender_location) == 0) { ERR_AI << "Performing calculate_outcome() with non-existent defender at (" << defender_location.x+1 << "," << defender_location.y+1 << ")\n"; return variant(); } battle_context bc(units, convert_variant(args()[1]->evaluate(variables,add_debug_info(fdb,1,"calculate_outcome:attacker_attack_location")))->loc(), defender_location, weapon, -1, 1.0, nullptr, &*units.find(attacker_location)); std::vector hp_dist = bc.get_attacker_combatant().hp_dist; std::vector::iterator it = hp_dist.begin(); int i = 0; std::vector hitLeft; std::vector prob; while (it != hp_dist.end()) { if (*it != 0) { hitLeft.push_back(variant(i)); prob.push_back(variant(int(*it*10000))); } ++it; ++i; } std::vector status; if (bc.get_attacker_combatant().poisoned != 0) status.push_back(variant("Poisoned")); if (bc.get_attacker_combatant().slowed != 0) status.push_back(variant("Slowed")); if (bc.get_defender_stats().petrifies && static_cast(hitLeft[0].as_int()) != bc.get_attacker_stats().hp) status.push_back(variant("Stoned")); if (bc.get_defender_stats().plagues && hitLeft[0].as_int() == 0) status.push_back(variant("Zombiefied")); vars.push_back(variant(new outcome_callable(hitLeft, prob, status))); hitLeft.clear(); prob.clear(); status.clear(); hp_dist = bc.get_defender_combatant().hp_dist; it = hp_dist.begin(); i = 0; while (it != hp_dist.end()) { if (*it != 0) { hitLeft.push_back(variant(i)); prob.push_back(variant(int(*it*10000))); } ++it; ++i; } if (bc.get_defender_combatant().poisoned != 0) status.push_back(variant("Poisoned")); if (bc.get_defender_combatant().slowed != 0) status.push_back(variant("Slowed")); if (bc.get_attacker_stats().petrifies && static_cast(hitLeft[0].as_int()) != bc.get_defender_stats().hp) status.push_back(variant("Stoned")); if (bc.get_attacker_stats().plagues && hitLeft[0].as_int() == 0) status.push_back(variant("Zombiefied")); vars.push_back(variant(new outcome_callable(hitLeft, prob, status))); return variant(&vars); } }; class outcomes_function : public function_expression { public: outcomes_function(const args_list& args) : function_expression("outcomes", args, 1, 1) { } private: variant execute(const formula_callable& variables, formula_debugger *fdb) const { variant attack = args()[0]->evaluate(variables,add_debug_info(fdb,0,"outcomes:attack")); ai::attack_analysis* analysis = convert_variant(attack); //unit_map units_with_moves(*resources::units); //typedef std::pair mv; //for(const mv &m : analysis->movements) { // units_with_moves.move(m.first, m.second); //} std::vector vars; if(analysis->chance_to_kill > 0.0) { //unit_map units(units_with_moves); //units.erase(analysis->target); vars.push_back(variant(new position_callable(/*&units,*/ static_cast(analysis->chance_to_kill*100)))); } if(analysis->chance_to_kill < 1.0) { //unit_map units(units_with_moves); vars.push_back(variant(new position_callable(/*&units,*/ static_cast(100 - analysis->chance_to_kill*100)))); } return variant(&vars); } }; //class evaluate_for_position_function : public function_expression { //public: // evaluate_for_position_function(const args_list& args, formula_ai& ai) // : function_expression("evaluate_for_position", args, 2, 2), ai_(ai) { // } // //private: // variant execute(const formula_callable& variables, formula_debugger *fdb) const { // variant position = args()[0]->evaluate(variables,add_debug_info(fdb,0,"evaluate_for_position:position")); // ai_.store_outcome_position(position); // position_callable* pos = convert_variant(position); // position_callable::swapper swapper(ai_, *pos); // return args()[1]->evaluate(variables,add_debug_info(fdb,1,"evaluate_for_position:formula")); // } // // formula_ai& ai_; //}; class get_unit_type_function : public function_expression { public: explicit get_unit_type_function(const args_list& args) : function_expression("get_unit_type", args, 1, 1) {} private: variant execute(const formula_callable& variables, formula_debugger *fdb) const { const std::string type = args()[0]->evaluate(variables,add_debug_info(fdb,0,"get_unit_type:name")).as_string(); const unit_type *ut = unit_types.find(type); if(ut) { return variant(new unit_type_callable(*ut)); } return variant(); } }; class rate_action_function : public function_expression { public: explicit rate_action_function(const args_list& args, const formula_ai &ai) : function_expression("rate_action", args, 1, 1), ai_(ai) {} private: variant execute(const formula_callable& variables, formula_debugger *fdb) const { variant act = args()[0]->evaluate(variables,add_debug_info(fdb,0,"rate_action:action")); ai::attack_analysis* analysis = convert_variant(act); return variant(analysis->rating(ai_.get_aggression(),ai_)*1000,variant::DECIMAL_VARIANT); } const formula_ai &ai_; }; class recall_function : public function_expression { public: explicit recall_function(const args_list& args) : function_expression("recall", args, 1, 2) {} private: variant execute(const formula_callable& variables, formula_debugger *fdb) const { const std::string id = args()[0]->evaluate(variables,add_debug_info(fdb,0,"recall:id")).as_string(); map_location loc; if(args().size() >= 2) { loc = convert_variant(args()[1]->evaluate(variables,add_debug_info(fdb,1,"recall:location")))->loc(); } return variant(new recall_callable(loc, id)); } }; class recruit_function : public function_expression { public: explicit recruit_function(const args_list& args) : function_expression("recruit", args, 1, 2) {} private: variant execute(const formula_callable& variables, formula_debugger *fdb) const { const std::string type = args()[0]->evaluate(variables,add_debug_info(fdb,0,"recruit:type")).as_string(); map_location loc; if(args().size() >= 2) { loc = convert_variant(args()[1]->evaluate(variables,add_debug_info(fdb,1,"recruit:location")))->loc(); } return variant(new recruit_callable(loc, type)); } }; class shortest_path_function : public function_expression { public: explicit shortest_path_function(const args_list& args, const formula_ai& ai) : function_expression("shortest_path", args, 2, 3), ai_(ai) {} private: variant execute(const formula_callable& variables, formula_debugger *fdb) const { std::vector locations; const map_location src = convert_variant(args()[0]->evaluate(variables,add_debug_info(fdb,0,"shortest_path:src")))->loc(); const map_location dst = convert_variant(args()[1]->evaluate(variables,add_debug_info(fdb,1,"shortest_path:dst")))->loc(); map_location unit_loc; if( src == dst ) return variant(&locations); if(args().size() > 2) unit_loc = convert_variant(args()[2]->evaluate(variables,add_debug_info(fdb,2,"shortest_path:unit_location")))->loc(); else unit_loc = src; unit_map::iterator unit_it = resources::units->find(unit_loc); if( unit_it == resources::units->end() ) { std::ostringstream str; str << "shortest_path function: expected unit at location (" << (unit_loc.x+1) << "," << (unit_loc.y+1) << ")"; throw formula_error( str.str(), "", "", 0); } pathfind::teleport_map allowed_teleports = ai_.get_allowed_teleports(unit_it); pathfind::plain_route route = ai_.shortest_path_calculator( src, dst, unit_it, allowed_teleports ); if( route.steps.size() < 2 ) { return variant(&locations); } for (std::vector::const_iterator loc_iter = route.steps.begin() + 1 ; loc_iter !=route.steps.end(); ++loc_iter) { locations.push_back( variant( new location_callable(*loc_iter) )); } return variant(&locations); } const formula_ai& ai_; }; class simplest_path_function : public function_expression { public: explicit simplest_path_function(const args_list& args, const formula_ai& ai) : function_expression("simplest_path", args, 2, 3), ai_(ai) {} private: variant execute(const formula_callable& variables, formula_debugger *fdb) const { std::vector locations; const map_location src = convert_variant(args()[0]->evaluate(variables,add_debug_info(fdb,0,"simplest_path:src")))->loc(); const map_location dst = convert_variant(args()[1]->evaluate(variables,add_debug_info(fdb,1,"simplest_path:dst")))->loc(); map_location unit_loc; if( src == dst ) return variant(&locations); if(args().size() > 2) unit_loc = convert_variant(args()[2]->evaluate(variables,add_debug_info(fdb,2,"simplest_path:unit_location")))->loc(); else unit_loc = src; unit_map::iterator unit_it = resources::units->find(unit_loc); if( unit_it == resources::units->end() ) { std::ostringstream str; str << "simplest_path function: expected unit at location (" << (unit_loc.x+1) << "," << (unit_loc.y+1) << ")"; throw formula_error( str.str(), "", "", 0); } pathfind::teleport_map allowed_teleports = ai_.get_allowed_teleports(unit_it); pathfind::emergency_path_calculator em_calc(*unit_it, resources::gameboard->map()); pathfind::plain_route route = pathfind::a_star_search(src, dst, 1000.0, &em_calc, resources::gameboard->map().w(), resources::gameboard->map().h(), &allowed_teleports); if( route.steps.size() < 2 ) { return variant(&locations); } for (std::vector::const_iterator loc_iter = route.steps.begin() + 1 ; loc_iter !=route.steps.end(); ++loc_iter) { if (unit_it->movement_cost((resources::gameboard->map())[*loc_iter]) < movetype::UNREACHABLE ) locations.push_back( variant( new location_callable(*loc_iter) )); else break; } return variant(&locations); } const formula_ai& ai_; }; class next_hop_function : public function_expression { public: explicit next_hop_function(const args_list& args, const formula_ai& ai) : function_expression("next_hop", args, 2, 3), ai_(ai) {} private: variant execute(const formula_callable& variables, formula_debugger *fdb) const { const map_location src = convert_variant(args()[0]->evaluate(variables,add_debug_info(fdb,0,"next_hop:src")))->loc(); const map_location dst = convert_variant(args()[1]->evaluate(variables,add_debug_info(fdb,1,"next_hop:dst")))->loc(); map_location unit_loc; if( src == dst ) return variant(); if(args().size() > 2) unit_loc = convert_variant(args()[2]->evaluate(variables,add_debug_info(fdb,2,"next_hop:unit_location")))->loc(); else unit_loc = src; unit_map::iterator unit_it = resources::units->find(unit_loc); if( unit_it == resources::units->end() ) { std::ostringstream str; str << "next_hop function: expected unit at location (" << (unit_loc.x+1) << "," << (unit_loc.y+1) << ")"; throw formula_error( str.str(), "", "", 0); } pathfind::teleport_map allowed_teleports = ai_.get_allowed_teleports(unit_it); pathfind::plain_route route = ai_.shortest_path_calculator( src, dst, unit_it, allowed_teleports ); if( route.steps.size() < 2 ) { return variant(); } map_location loc = map_location::null_location(); const ai::moves_map &possible_moves = ai_.get_possible_moves(); const ai::moves_map::const_iterator& p_it = possible_moves.find(unit_loc); if (p_it==possible_moves.end() ) { return variant(); } for (std::vector::const_iterator loc_iter = route.steps.begin() + 1 ; loc_iter !=route.steps.end(); ++loc_iter) { if (p_it->second.destinations.find(*loc_iter) != p_it->second.destinations.end() ) { loc = *loc_iter; } else { break; } } if (loc==map_location::null_location()) { return variant(); } return variant(new location_callable(loc)); } const formula_ai& ai_; }; class move_function : public function_expression { public: explicit move_function(const args_list& args) : function_expression("move", args, 2, 2) {} private: variant execute(const formula_callable& variables, formula_debugger *fdb) const { const map_location src = convert_variant(args()[0]->evaluate(variables,add_debug_info(fdb,0,"move:src")))->loc(); const map_location dst = convert_variant(args()[1]->evaluate(variables,add_debug_info(fdb,1,"move:dst")))->loc(); LOG_AI << "move(): " << src << ", " << dst << ")\n"; return variant(new move_callable(src, dst)); } }; class move_partial_function : public function_expression { public: explicit move_partial_function(const args_list& args) : function_expression("move_partial", args, 2, 2) {} private: variant execute(const formula_callable& variables, formula_debugger *fdb) const { const map_location src = convert_variant(args()[0]->evaluate(variables,add_debug_info(fdb,0,"move_partial:src")))->loc(); const map_location dst = convert_variant(args()[1]->evaluate(variables,add_debug_info(fdb,1,"move_partial:dst")))->loc(); LOG_AI << "move_partial(): " << src << ", " << dst << ")\n"; return variant(new move_partial_callable(src, dst)); } }; class set_var_function : public function_expression { public: explicit set_var_function(const args_list& args) : function_expression("set_var", args, 2, 2) {} private: variant execute(const formula_callable& variables, formula_debugger *fdb) const { return variant(new set_var_callable(args()[0]->evaluate(variables,add_debug_info(fdb,0,"set_var:key")).as_string(), args()[1]->evaluate(variables,add_debug_info(fdb,1,"set_var:value")))); } }; class set_unit_var_function : public function_expression { public: explicit set_unit_var_function(const args_list& args) : function_expression("set_unit_var", args, 3, 3) {} private: variant execute(const formula_callable& variables, formula_debugger *fdb) const { return variant(new set_unit_var_callable(args()[0]->evaluate(variables,add_debug_info(fdb,0,"set_unit_var:key")).as_string(), args()[1]->evaluate(variables,add_debug_info(fdb,1,"set_unit_var:value")), convert_variant(args()[2]->evaluate(variables,add_debug_info(fdb,2,"set_unit_var:unit_location")))->loc())); } }; class fallback_function : public function_expression { public: explicit fallback_function(const args_list& args) : function_expression("fallback", args, 0, 1) {} private: variant execute(const formula_callable& variables, formula_debugger*) const { // The parameter is not used, but is accepted for legacy compatibility if(args().size() == 1 && args()[0]->evaluate(variables).as_string() != "human") return variant(); return variant(new fallback_callable); } }; class attack_function : public function_expression { public: explicit attack_function(const args_list& args) : function_expression("attack", args, 3, 4) {} private: variant execute(const formula_callable& variables, formula_debugger *fdb) const { const map_location move_from = convert_variant(args()[0]->evaluate(variables,add_debug_info(fdb,0,"attack:move_from")))->loc(); const map_location src = convert_variant(args()[1]->evaluate(variables,add_debug_info(fdb,1,"attack:src")))->loc(); const map_location dst = convert_variant(args()[2]->evaluate(variables,add_debug_info(fdb,2,"attack:dst")))->loc(); const int weapon = args().size() == 4 ? args()[3]->evaluate(variables,add_debug_info(fdb,3,"attack:weapon")).as_int() : -1; if(resources::units->count(move_from) == 0 || resources::units->count(dst) == 0) { ERR_AI << "AI ERROR: Formula produced illegal attack: " << move_from << " -> " << src << " -> " << dst << std::endl; return variant(); } return variant(new attack_callable(move_from, src, dst, weapon)); } }; class safe_call_function : public function_expression { public: explicit safe_call_function(const args_list& args) : function_expression("safe_call", args, 2, 2) {} private: variant execute(const formula_callable& variables, formula_debugger *fdb) const { const variant main = args()[0]->evaluate(variables,fdb); const expression_ptr backup_formula = args()[1]; return variant(new safe_call_callable(main, backup_formula)); } }; class debug_label_function : public function_expression { public: explicit debug_label_function(const args_list& args, const formula_ai& ai) : function_expression("debug_label", args, 2, 2), ai_(ai) {} private: variant execute(const formula_callable& variables, formula_debugger *fdb) const { const args_list& arguments = args(); const variant var0 = arguments[0]->evaluate(variables,fdb); const variant var1 = arguments[1]->evaluate(variables,fdb); const map_location location = convert_variant(var0)->loc(); std::string text; if( var1.is_string() ) text = var1.as_string(); else text = var1.to_debug_string(); display_label(location,text); std::vector res; res.push_back(var0); res.push_back(var1); return variant(&res); } void display_label(const map_location& location, const std::string& text) const { display* gui = display::get_singleton(); std::string team_name; SDL_Color color = int_to_color(team::get_side_rgb(ai_.get_side())); const terrain_label *res; res = gui->labels().set_label(location, text, ai_.get_side() - 1, team_name, color); if (res && resources::recorder) resources::recorder->add_label(res); } const formula_ai& ai_; }; class is_village_function : public function_expression { public: explicit is_village_function(const args_list& args) : function_expression("is_village", args, 2, 3) {} private: variant execute(const formula_callable& variables, formula_debugger *fdb) const { const gamemap& m = convert_variant(args()[0]->evaluate(variables,add_debug_info(fdb,0,"is_village:map")))->get_gamemap(); map_location loc; if(args().size() == 2) { loc = convert_variant(args()[1]->evaluate(variables,add_debug_info(fdb,1,"is_village:location")))->loc(); } else { loc = map_location( args()[1]->evaluate(variables,add_debug_info(fdb,1,"is_village:x")).as_int() - 1, args()[2]->evaluate(variables,add_debug_info(fdb,2,"is_village:y")).as_int() - 1 ); } return variant(m.is_village(loc)); } }; class is_unowned_village_function : public function_expression { public: explicit is_unowned_village_function(const args_list& args, const formula_ai& ai) : function_expression("is_unowned_village", args, 2, 3), ai_(ai) {} private: variant execute(const formula_callable& variables, formula_debugger *fdb) const { const gamemap& m = convert_variant(args()[0]->evaluate(variables,add_debug_info(fdb,0,"is_unowned_village:map")))->get_gamemap(); const std::set& my_villages = ai_.current_team().villages(); map_location loc; if(args().size() == 2) { loc = convert_variant(args()[1]->evaluate(variables,add_debug_info(fdb,1,"is_unowned_village:location")))->loc(); } else { loc = map_location( args()[1]->evaluate(variables,add_debug_info(fdb,1,"is_unowned_village:x")).as_int() - 1, args()[2]->evaluate(variables,add_debug_info(fdb,2,"is_unowned_village:y")).as_int() - 1 ); } if(m.is_village(loc) && (my_villages.count(loc)==0) ) { return variant(true); } else { return variant(false); } } const formula_ai& ai_; }; class unit_at_function : public function_expression { public: unit_at_function(const args_list& args) : function_expression("unit_at", args, 1, 1) {} private: variant execute(const formula_callable& variables, formula_debugger *fdb) const { variant loc_var = args()[0]->evaluate(variables,add_debug_info(fdb,0,"unit_at:location")); if (loc_var.is_null()) { return variant(); } const location_callable* loc = convert_variant(loc_var); const unit_map::const_iterator i = resources::units->find(loc->loc()); if(i != resources::units->end()) { return variant(new unit_callable(*i)); } else { return variant(); } } }; class unit_moves_function : public function_expression { public: unit_moves_function(const args_list& args, const formula_ai& ai_object) : function_expression("unit_moves", args, 1, 1), ai_(ai_object) {} private: variant execute(const formula_callable& variables, formula_debugger *fdb) const { variant res = args()[0]->evaluate(variables,add_debug_info(fdb,0,"unit_moves:unit_location")); std::vector vars; if(res.is_null()) { return variant(&vars); } const map_location& loc = convert_variant(res)->loc(); const ai::move_map& srcdst = ai_.get_srcdst(); typedef ai::move_map::const_iterator Itor; std::pair range = srcdst.equal_range(loc); for(Itor i = range.first; i != range.second; ++i) { vars.push_back(variant(new location_callable(i->second))); } return variant(&vars); } const formula_ai& ai_; }; class units_can_reach_function : public function_expression { public: units_can_reach_function(const args_list& args) : function_expression("units_can_reach", args, 2, 2) {} private: variant execute(const formula_callable& variables, formula_debugger *fdb) const { std::vector vars; variant dstsrc_var = args()[0]->evaluate(variables,add_debug_info(fdb,0,"units_can_reach:possible_move_list")); const ai::move_map& dstsrc = convert_variant(dstsrc_var)->dstsrc(); std::pair range = dstsrc.equal_range(convert_variant(args()[1]->evaluate(variables,add_debug_info(fdb,1,"units_can_reach:possible_move_list")))->loc()); while(range.first != range.second) { unit_map::const_iterator un = resources::units->find(range.first->second); assert(un != resources::units->end()); vars.push_back(variant(new unit_callable(*un))); ++range.first; } return variant(&vars); } }; class defense_on_function : public function_expression { public: defense_on_function(const args_list& args) : function_expression("defense_on", args, 2, 2) {} private: variant execute(const formula_callable& variables, formula_debugger *fdb) const { variant u = args()[0]->evaluate(variables,add_debug_info(fdb,0,"defense_on:unit")); variant loc_var = args()[1]->evaluate(variables,add_debug_info(fdb,1,"defense_on:location")); if(u.is_null() || loc_var.is_null()) { return variant(); } const unit_callable* u_call = try_convert_variant(u); const unit_type_callable* u_type = try_convert_variant(u); const map_location& loc = convert_variant(loc_var)->loc(); if (u_call) { const unit& un = u_call->get_unit(); if( un.total_movement() < un.movement_cost( (resources::gameboard->map())[loc]) ) return variant(); if(!resources::gameboard->map().on_board(loc)) { return variant(); } return variant(100 - un.defense_modifier((resources::gameboard->map())[loc])); } if (u_type) { const unit_type& un = u_type->get_unit_type(); if( un.movement() < un.movement_type().movement_cost((resources::gameboard->map())[loc]) ) return variant(); if(!resources::gameboard->map().on_board(loc)) { return variant(); } return variant(100 - un.movement_type().defense_modifier((resources::gameboard->map())[loc])); } return variant(); } }; class chance_to_hit_function : public function_expression { public: chance_to_hit_function(const args_list& args) : function_expression("chance_to_hit", args, 2, 2) {} private: variant execute(const formula_callable& variables, formula_debugger *fdb) const { variant u = args()[0]->evaluate(variables,add_debug_info(fdb,0,"chance_to_hit:unit")); variant loc_var = args()[1]->evaluate(variables,add_debug_info(fdb,1,"chance_to_hit:location")); if(u.is_null() || loc_var.is_null()) { return variant(); } const unit_callable* u_call = try_convert_variant(u); const unit_type_callable* u_type = try_convert_variant(u); const map_location& loc = convert_variant(loc_var)->loc(); if (u_call) { const unit& un = u_call->get_unit(); if(!resources::gameboard->map().on_board(loc)) { return variant(); } return variant(un.defense_modifier((resources::gameboard->map())[loc])); } if (u_type) { const unit_type& un = u_type->get_unit_type(); if(!resources::gameboard->map().on_board(loc)) { return variant(); } return variant(un.movement_type().defense_modifier((resources::gameboard->map())[loc])); } return variant(); } }; class movement_cost_function : public function_expression { public: movement_cost_function(const args_list& args) : function_expression("movement_cost", args, 2, 2) {} private: variant execute(const formula_callable& variables, formula_debugger *fdb) const { variant u = args()[0]->evaluate(variables,add_debug_info(fdb,0,"movement_cost:unit")); variant loc_var = args()[1]->evaluate(variables,add_debug_info(fdb,0,"movement_cost:location")); if(u.is_null() || loc_var.is_null()) { return variant(); } //we can pass to this function either unit_callable or unit_type callable const unit_callable* u_call = try_convert_variant(u); const unit_type_callable* u_type = try_convert_variant(u); const map_location& loc = convert_variant(loc_var)->loc(); if (u_call) { const unit& un = u_call->get_unit(); if(!resources::gameboard->map().on_board(loc)) { return variant(); } return variant(un.movement_cost((resources::gameboard->map())[loc])); } if (u_type) { const unit_type& un = u_type->get_unit_type(); if(!resources::gameboard->map().on_board(loc)) { return variant(); } return variant(un.movement_type().movement_cost((resources::gameboard->map())[loc])); } return variant(); } }; class is_avoided_location_function : public function_expression { public: is_avoided_location_function(const args_list& args, const formula_ai& ai_object) : function_expression("is_avoided_location",args, 1, 1), ai_(ai_object) {} private: variant execute(const formula_callable& variables, formula_debugger *fdb) const { variant res = args()[0]->evaluate(variables,add_debug_info(fdb,0,"is_avoided_location:location")); if(res.is_null()) { return variant(); } const map_location& loc = convert_variant(res)->loc(); return variant(ai_.get_avoid().match(loc)); } const formula_ai &ai_; }; class max_possible_damage_function : public function_expression { public: max_possible_damage_function(const args_list& args) : function_expression("max_possible_damage", args, 2, 2) {} private: variant execute(const formula_callable& variables, formula_debugger *fdb) const { variant u1 = args()[0]->evaluate(variables,add_debug_info(fdb,0,"max_possible_damage:unit1")); variant u2 = args()[1]->evaluate(variables,add_debug_info(fdb,1,"max_possible_damage:unit2")); if(u1.is_null() || u2.is_null()) { return variant(); } std::vector attacks_tmp; std::vector& attacks = attacks_tmp; //we have to make sure that this function works with any combination of unit_callable/unit_type_callable passed to it const unit_callable* u_attacker = try_convert_variant(u1); if (u_attacker) { attacks = u_attacker->get_unit().attacks(); } else { const unit_type_callable* u_t_attacker = convert_variant(u1); attacks_tmp = u_t_attacker->get_unit_type().attacks(); } const unit_callable* u_defender = try_convert_variant(u2); if (u_defender) { const unit& defender = u_defender->get_unit(); int best = 0; for(std::vector::const_iterator i = attacks.begin(); i != attacks.end(); ++i) { const int dmg = round_damage(i->damage(), defender.damage_from(*i, false, map_location()), 100) * i->num_attacks(); if(dmg > best) best = dmg; } return variant(best); } else { const unit_type& defender = convert_variant(u2)->get_unit_type(); int best = 0; for(std::vector::const_iterator i = attacks.begin(); i != attacks.end(); ++i) { const int dmg = round_damage(i->damage(), defender.movement_type().resistance_against(*i), 100) * i->num_attacks(); if(dmg > best) best = dmg; } return variant(best); } } }; class max_possible_damage_with_retaliation_function : public function_expression { public: max_possible_damage_with_retaliation_function(const args_list& args) : function_expression("max_possible_damage_with_retaliation", args, 2, 2) {} private: std::pair best_melee_and_ranged_attacks(unit_adapter attacker, unit_adapter defender) const { int highest_melee_damage = 0; int highest_ranged_damage = 0; std::vector attacks = attacker.attacks(); for (const attack_type &attack : attacks) { const int dmg = round_damage(attack.damage(), defender.damage_from(attack), 100) * attack.num_attacks(); if (attack.range() == "melee") { highest_melee_damage = std::max(highest_melee_damage, dmg); } else { highest_ranged_damage = std::max(highest_ranged_damage, dmg); } } return std::make_pair(highest_melee_damage, highest_ranged_damage); } variant execute(const formula_callable& variables, formula_debugger *fdb) const { variant u1 = args()[0]->evaluate(variables,add_debug_info(fdb,0,"max_possible_damage_with_retaliation:unit1")); variant u2 = args()[1]->evaluate(variables,add_debug_info(fdb,1,"max_possible_damage_with_retaliation:unit2")); if(u1.is_null() || u2.is_null()) { return variant(); } unit_adapter attacker(u1); unit_adapter defender(u2); // find max damage inflicted by attacker and by defender to the attacker std::pair best_attacker_attacks = best_melee_and_ranged_attacks(attacker, defender); std::pair best_defender_attacks = best_melee_and_ranged_attacks(defender, attacker); std::vector vars; vars.push_back(variant(best_attacker_attacks.first)); vars.push_back(variant(best_attacker_attacks.second)); vars.push_back(variant(best_defender_attacks.first)); vars.push_back(variant(best_defender_attacks.second)); return variant(&vars); } }; template class ai_formula_function : public formula_function { protected: formula_ai& ai_; public: ai_formula_function(const std::string& name, ai::formula_ai& ai) : formula_function(name), ai_(ai) {} function_expression_ptr generate_function_expression(const std::vector& args) const { return function_expression_ptr(new T(args, ai_)); } }; ]=] function fcns:distance_to_nearest_unowned_village(from_loc) local best = 1000000 from_loc = from_loc() -- it's passed as a formula, but we don't need any fancy stuff, so pre-evaluate it -- Not quite sure what the API looks like for getting villages (or if it even exists) local my_villages = ai.get_villages() for v_loc in wesnoth.get_villages() do local distance = wesnoth.map_location.distance_between(from_loc, v_loc) if distance < best and v_loc not in my_villages then -- Note: I'm not sure if "not in" is a valid Lua operator best = distance end end return best, 'integer' end function fcns:run_file(file) file = tostring(file()) -- pre-evaluate the argument formula -- Idea: Pass debug info as an optional string to the formula? -- Alternate idea: automatically add debug info to function arguments local formula_string = wesnoth.read_file(file) -- or whatever the API is return wesnoth.eval_formula(formula_string, self) -- Note: "self" is automatically some sort of reference to the variable scope in a Lua formula function -- Note: This should also print an error message if the formula didn't compile end function fcns:timeofday_modifier(unit, location_opt) unit = unit() if getmetatable(unit) ~= 'unit' then return nil end -- This uses an API function that I think does not exist: if location_opt then return unit:combat_modifier(location_opt()), 'integer' else return unit:combat_modifier(), 'integer' end end function fcns:suitable_keep(unit) unit = unit() if getmetatable(unit) ~= 'unit' then return nil end return ai.suitable_keep(unit) -- not quite sure if this exists? end function fcns:find_shroud(map_opt) local w, h, map if map_opt then map = map_opt() else map = wesnoth.map -- Pretty sure this doesn't exist end w = map.w h = map.h local side, vars = wesnoth.sides[ai.side], {} for i = 1,w do for j = 1,h do if side.shrouded(i, j) then table.insert(vars, {x=i, y=j, __wfl_type='location'}) end end end return vars, 'list' end -- Note: This function could actually be implemented entirely in WFL -- Consider allowing something like fcns.xxx = wesnoth.compile_formula('yyy')? -- Or maybe it should go something like this: --[[ wesnoth.compile_formula([=[ def close_enemies(loc, range) # I think "me" is not quite right here # filter(me.units, distance_between(loc, self.loc) <= ran) where ran = if(range < 0, 0, range) ; ]=], fcns) ]] -- Or fcns.xxx = [[def xxx(a,b,c) formula_here;]] does the equivalent of the above? -- Obviously this can only work for functions with a fixed number of arguments. function fcns:close_enemies(loc, range) loc = loc() range = range() local vars = {} if range < 0 then -- TODO Put warning range = 0 end local units = wesnoth.get_units{side = ai.side} for u in units do -- Does u.loc exist? if wesnoth.map_location.distance_between(loc, u.loc) <= range then table.insert(vars, u) end end return vars, 'list' end -- and so on and so on -- TODO: An example with unlimited args? (None of the existing AI functions have that though) -- TODO: An example of something like reduce() that evaluates its arguments with inputs? -- (Again, none of the existing AI functions have that, and there's probably few things you can think of that would need it.)