Logo Search packages:      
Sourcecode: wesnoth-1.7 version File versions  Download package

function_table.cpp

/*
   Copyright (C) 2009 - 2010 by Bartosz Waresiak <dragonking@o2.pl>
   Part of the Battle for Wesnoth Project http://www.wesnoth.org/

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License version 2
   or at your option any later version.
   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY.

   See the COPYING file for more details.
*/


#include <queue>
#include <set>
#include <utility>
#include <vector>

#include "ai.hpp"
#include "callable_objects.hpp"
#include "function_table.hpp"

#include "../../attack_prediction.hpp"
#include "../../filesystem.hpp"
#include "../../foreach.hpp"
#include "../../log.hpp"
#include "../../map_label.hpp"
#include "../../menu_events.hpp"
#include "../../replay.hpp"
#include "../../terrain_filter.hpp"
#include "../../unit.hpp"

static lg::log_domain log_formula_ai("ai/engine/fai");
#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 game_logic {
using ai::formula_ai;

namespace {

/*
 * unit adapters let us treat unit and unit_type the same if we want to get access to attacks or movement cost
 */
class unit_adapter {
      public:
            unit_adapter(const variant& arg) : unit_type_(), unit_() {
                  const unit_callable* unit = try_convert_variant<unit_callable>(arg);

                  if (unit) {
                        unit_ = &unit->get_unit();
                  } else {
                        unit_type_ = &(convert_variant<unit_type_callable>(arg)->get_unit_type());
                  }
            }

            int damage_from(const attack_type& attack) const {
                  if(unit_type_ != NULL) {
                        return unit_type_->movement_type().resistance_against(attack);
                  } else {
                        return unit_->damage_from(attack, false, map_location());
                  }
            }

            // FIXME: we return a vector by value because unit_type and unit APIs
            // disagree as to what should be returned by their attacks() method
            std::vector<attack_type> attacks() const {
                  if(unit_type_ != NULL) {
                        return unit_type_->attacks();
                  } else {
                        return unit_->attacks();
                  }
            }

            int movement_cost(const gamemap& map, const t_translation::t_terrain terrain) const {
                  if(unit_type_ != NULL) {
                        return unit_type_->movement_type().movement_cost(map, terrain);
                  } else {
                        return unit_->movement_cost(terrain);
                  }
            }


      private:
            const unit_type *unit_type_;
            const unit* unit_;
};

class distance_between_function : public function_expression {
public:
      explicit distance_between_function(const args_list& args)
        : function_expression("distance_between", args, 2, 2)
      {}

private:
      variant execute(const formula_callable& variables, formula_debugger *fdb) const {
            const map_location loc1 = convert_variant<location_callable>(args()[0]->evaluate(variables,add_debug_info(fdb,0,"distance_between:location_A")))->loc();
            const map_location loc2 = convert_variant<location_callable>(args()[1]->evaluate(variables,add_debug_info(fdb,1,"distance_between:location_B")))->loc();
            return variant(distance_between(loc1, loc2));
      }
};


class distance_to_nearest_unowned_village_function : public function_expression {
public:
      distance_to_nearest_unowned_village_function(const args_list& args, const formula_ai& ai)
        : function_expression("distance_to_nearest_unowned_village", args, 1, 1), ai_(ai) {
      }

private:
      variant execute(const formula_callable& variables, formula_debugger *fdb) const {
            const map_location loc = convert_variant<location_callable>(args()[0]->evaluate(variables,add_debug_info(fdb,0,"distance_to_nearest_unowned_village:location")))->loc();
            int best = 1000000;
            const std::vector<map_location>& villages = ai_.get_info().map.villages();
            const std::set<map_location>& my_villages = ai_.current_team().villages();
            for(std::vector<map_location>::const_iterator i = villages.begin(); i != villages.end(); ++i) {
                  int distance = distance_between(loc, *i);
                  if(distance < best) {
                        if(my_villages.count(*i) == 0) {
                              best = distance;
                        }
                  }
            }

            return variant(best);
      }

      const formula_ai& ai_;
};

static unsigned search_counter;

//@todo 1.8: document
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<node>& nodes;
            comp(const std::vector<node>& 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<int>& scores, bool allow_teleport) const
      {
            const std::set<map_location>& teleports = allow_teleport ? ai_.current_team().villages() : std::set<map_location>();

            const gamemap& map = ai_.get_info().map;

            std::vector<map_location> 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<node> 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<int> 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, 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 = ai_.get_info().map.w();
            int h = ai_.get_info().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<int> > 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 = ai_.get_info().units.begin(); i != ai_.get_info().units.end(); ++i) {
//                unit_counter[i->second.side()-1]++;
//                unit_adapter unit(i->second);
//                find_movemap( ai_.get_info().map, ai_.get_info().units, unit, i->first, scores[i->second.side()-1], ai_.get_info().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<location_callable>(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<int> >::iterator i = scores.begin() ; i != scores.end() ; ++i) {
                  for( std::vector<int>::iterator j = i->begin() ; j != i->end() ; ++j )
                        *j = *j / units_input[index].num_elements();

                  ++index;
            }
            //std::vector<variant> res;
            std::map<variant, variant> res;

            size_t current_side = ai_.get_side() - 1 ;

            std::vector<int> enemies;
            std::vector<int> 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;

                        foreach( 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 ) {
                              foreach( 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, variant>(variant(new location_callable(map_location(x, y))), variant(scores[0][i] + 10000) ));
                              else
                                    res.insert( std::pair<variant, variant>(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, const formula_ai& ai)
        : function_expression("nearest_loc", args, 2, 2), ai_(ai) {
      }

private:
      variant execute(const formula_callable& variables, formula_debugger *fdb) const {
            const map_location loc = convert_variant<location_callable>(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<location_callable>(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<location_callable>(items[best_i])->loc()));
            else
                  return variant();
      }

      const formula_ai& ai_;
};


class adjacent_locs_function : public function_expression {
public:
      adjacent_locs_function(const args_list& args, const formula_ai& ai)
        : function_expression("adjacent_locs", args, 1, 1), ai_(ai) {
      }

private:
      variant execute(const formula_callable& variables, formula_debugger *fdb) const {
            const map_location loc = convert_variant<location_callable>(args()[0]->evaluate(variables,add_debug_info(fdb,0,"adjacent_locs:location")))->loc();
            map_location adj[6];
            get_adjacent_tiles(loc, adj);

            std::vector<variant> v;
            for(int n = 0; n != 6; ++n) {
                        if (ai_.get_info().map.on_board(adj[n]) )
                            v.push_back(variant(new location_callable(adj[n])));
            }

            return variant(&v);
      }

      const formula_ai& ai_;
};


class locations_in_radius_function : public function_expression {
public:
      locations_in_radius_function(const args_list& args, const formula_ai& ai)
        : function_expression("locations_in_radius", args, 2, 2), ai_(ai) {
      }

private:
      variant execute(const formula_callable& variables, formula_debugger *fdb) const {
            const map_location loc = convert_variant<location_callable>(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<map_location> res;

            get_tiles_in_radius( loc, range, res);

            std::vector<variant> v;
            v.reserve(res.size()+1);
            v.push_back(variant(new location_callable(loc)));

            for(size_t n = 0; n != res.size(); ++n) {
                        if (ai_.get_info().map.on_board(res[n]) )
                            v.push_back(variant(new location_callable(res[n])));
            }

            return variant(&v);
      }

      const formula_ai& ai_;
};


/** 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 = 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 = 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, const formula_ai& ai)
        : function_expression("castle_locs", args, 1, 1), ai_(ai) {
      }

private:
      variant execute(const formula_callable& variables, formula_debugger *fdb) const {
            const map_location starting_loc = convert_variant<location_callable>(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 ( ai_.get_info().map.on_board(adj[n]) && visited_locs.find( adj[n] ) == visited_locs.end() ) {
                            if ( ai_.get_info().map.get_terrain_info(adj[n]).is_keep() ||
                                    ai_.get_info().map.get_terrain_info(adj[n]).is_castle() ) {
                                queued_locs.push(adj[n]);
                            }
                        }
                   }
                }

                if ( !ai_.get_info().map.get_terrain_info(starting_loc).is_keep() &&
                     !ai_.get_info().map.get_terrain_info(starting_loc).is_castle() )
                    visited_locs.erase(starting_loc);

                std::vector<variant> res;
                foreach( const map_location& ml, visited_locs) {
                    res.push_back( variant(new location_callable( ml ) ) );
                }

            return variant(&res);
      }

      const formula_ai& ai_;
};


/**
 * 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, const formula_ai& ai)
        : function_expression("timeofday_modifier", args, 1, 2), ai_(ai) {
      }
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<unit_callable>(u);

            if (u_call == NULL) {
                  return variant();
            }

            const unit& un = u_call->get_unit();

            map_location const* loc = NULL;

            if(args().size()==2) {
                  loc = &convert_variant<location_callable>(args()[1]->evaluate(variables,add_debug_info(fdb,1,"timeofday_modifier:location")))->loc();
            }

            if (loc == NULL) {
                  loc = &u_call->get_location();
            }

            return variant(combat_modifier(ai_.get_info().units, *loc, un.alignment(), un.is_fearless()));
      }

      const formula_ai& ai_;
};


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<location_callable>(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<location_callable>(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<location_callable>(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<location_callable>(args()[0]->evaluate(variables,add_debug_info(fdb,0,"suitable_keep:location")))->loc();
            if (ai_.get_info().units.find(loc)==ai_.get_info().units.end()){
                  return variant();
            }
            const pathfind::paths unit_paths(ai_.get_info().map, ai_.get_info().units, loc ,ai_.get_info().teams, false, false, 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<variant> vars;
            int w,h;

            if(args().size()==1) {
                  const gamemap& m = convert_variant<gamemap_callable>(args()[0]->evaluate(variables,add_debug_info(fdb,0,"find_shroud:gamemap")))->get_gamemap();
                  w = m.w();
                  h = m.h();
            } else {
                  w = ai_.get_info().map.w();
                  h = ai_.get_info().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<variant> vars;
            const map_location loc = convert_variant<location_callable>(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 << ")\n";
                  range_s = 0;
            }
            size_t range = static_cast<size_t>(range_s);
            unit_map::const_iterator un = ai_.get_info().units.begin();
            unit_map::const_iterator end = ai_.get_info().units.end();
            while (un != end) {
                  if (distance_between(loc, un->first) <= range) {
                        if (un->second.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, const formula_ai& ai)
        : function_expression("calculate_outcome", args, 3, 4), ai_(ai) {
      }

private:
      variant execute(const formula_callable& variables, formula_debugger *fdb) const {
            std::vector<variant> 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;

            map_location attacker_location =
                  convert_variant<location_callable>(args()[0]->evaluate(variables,add_debug_info(fdb,0,"calculate_outcome:attacker_current_location")))->loc();
            if(ai_.get_info().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<location_callable>(args()[2]->evaluate(variables,add_debug_info(fdb,2,"calculate_outcome:defender_location")))->loc();
            if(ai_.get_info().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(ai_.get_info().units, convert_variant<location_callable>(args()[1]->evaluate(variables,add_debug_info(fdb,1,"calculate_outcome:attacker_attack_location")))->loc(),
                  defender_location, weapon, -1, 1.0, NULL, &ai_.get_info().units.find(attacker_location)->second);
            std::vector<double> hp_dist = bc.get_attacker_combatant().hp_dist;
            std::vector<double>::iterator it = hp_dist.begin();
            int i = 0;
            std::vector<variant> hitLeft;
            std::vector<variant> 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<variant> 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<unsigned int>(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<unsigned int>(hitLeft[0].as_int()) != bc.get_attacker_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);
      }

      const formula_ai& ai_;
};


class outcomes_function : public function_expression {
public:
      outcomes_function(const args_list& args, const formula_ai& ai)
        : function_expression("outcomes", args, 1, 1), ai_(ai) {
      }

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<ai::attack_analysis>(attack);
            unit_map units_with_moves(ai_.get_info().units);
            typedef std::pair<map_location, map_location> mv;
            foreach (const mv &m, analysis->movements) {
                  units_with_moves.move(m.first, m.second);
            }

            std::vector<variant> 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<int>(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<int>(100 - analysis->chance_to_kill*100))));
            }

            return variant(&vars);
      }

      const formula_ai& ai_;
};

//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_callable>(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<ai::attack_analysis>(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<location_callable>(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<location_callable>(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<variant> locations;

            const map_location src = convert_variant<location_callable>(args()[0]->evaluate(variables,add_debug_info(fdb,0,"shortest_path:src")))->loc();
            const map_location dst = convert_variant<location_callable>(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<location_callable>(args()[2]->evaluate(variables,add_debug_info(fdb,2,"shortest_path:unit_location")))->loc();
                else
                    unit_loc = src;

                unit_map::iterator unit_it = ai_.get_info().units.find(unit_loc);

            if( unit_it == ai_.get_info().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);
            }

                std::set<map_location> 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<map_location>::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<variant> locations;

            const map_location src = convert_variant<location_callable>(args()[0]->evaluate(variables,add_debug_info(fdb,0,"simplest_path:src")))->loc();
            const map_location dst = convert_variant<location_callable>(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<location_callable>(args()[2]->evaluate(variables,add_debug_info(fdb,2,"simplest_path:unit_location")))->loc();
                else
                    unit_loc = src;

                unit_map::iterator unit_it = ai_.get_info().units.find(unit_loc);

            if( unit_it == ai_.get_info().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);
            }

                std::set<map_location> allowed_teleports = ai_.get_allowed_teleports(unit_it);

            pathfind::emergency_path_calculator em_calc(unit_it->second, ai_.get_info().map);

                pathfind::plain_route route = pathfind::a_star_search(src, dst, 1000.0, &em_calc, ai_.get_info().map.w(), ai_.get_info().map.h(), &allowed_teleports);

                if( route.steps.size() < 2 ) {
                    return variant(&locations);
                }

                for (std::vector<map_location>::const_iterator loc_iter = route.steps.begin() + 1 ; loc_iter !=route.steps.end(); ++loc_iter) {
                    if( unit_it->second.movement_cost(ai_.get_info().map[*loc_iter]) < 99 )
                        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<location_callable>(args()[0]->evaluate(variables,add_debug_info(fdb,0,"next_hop:src")))->loc();
            const map_location dst = convert_variant<location_callable>(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<location_callable>(args()[2]->evaluate(variables,add_debug_info(fdb,2,"next_hop:unit_location")))->loc();
                else
                    unit_loc = src;

                unit_map::iterator unit_it = ai_.get_info().units.find(unit_loc);

            if( unit_it == ai_.get_info().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);
            }

                std::set<map_location> 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<map_location>::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<location_callable>(args()[0]->evaluate(variables,add_debug_info(fdb,0,"move:src")))->loc();
            const map_location dst = convert_variant<location_callable>(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<location_callable>(args()[0]->evaluate(variables,add_debug_info(fdb,0,"move_partial:src")))->loc();
            const map_location dst = convert_variant<location_callable>(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<location_callable>(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 *fdb) const {
            if( args().size() == 0 )
                  return variant(new fallback_callable(""));
            return variant(new fallback_callable(args()[0]->evaluate(variables,add_debug_info(fdb,0,"fallback:name")).as_string()));
      }
};


class attack_function : public function_expression {
public:
      explicit attack_function(const args_list& args, const formula_ai& ai)
        : function_expression("attack", args, 3, 4),
            ai_(ai)
      {}
private:
      variant execute(const formula_callable& variables, formula_debugger *fdb) const {
            const map_location move_from = convert_variant<location_callable>(args()[0]->evaluate(variables,add_debug_info(fdb,0,"attack:move_from")))->loc();
            const map_location src = convert_variant<location_callable>(args()[1]->evaluate(variables,add_debug_info(fdb,1,"attack:src")))->loc();
            const map_location dst = convert_variant<location_callable>(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(ai_.get_info().units.count(move_from) == 0 || ai_.get_info().units.count(dst) == 0) {
                  ERR_AI << "AI ERROR: Formula produced illegal attack: " << move_from << " -> " << src << " -> " << dst << "\n";
                  return variant();
            }
            return variant(new attack_callable(ai_, move_from, src, dst, weapon));
      }

      const formula_ai& ai_;
};


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<location_callable>(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<variant> res;
            res.push_back(var0);
            res.push_back(var1);
                return variant(&res);
        }

        void display_label(const map_location& location, const std::string& text) const {
                game_display* gui = game_display::get_singleton();
            std::string team_name;

            SDL_Color colour = int_to_color(team::get_side_rgb(ai_.get_side()));

            const terrain_label *res;
            res = gui->labels().set_label(location, text, team_name, colour);
            if (res)
                  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<gamemap_callable>(args()[0]->evaluate(variables,add_debug_info(fdb,0,"is_village:map")))->get_gamemap();

            map_location loc;
            if(args().size() == 2) {
                  loc = convert_variant<location_callable>(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<gamemap_callable>(args()[0]->evaluate(variables,add_debug_info(fdb,0,"is_unowned_village:map")))->get_gamemap();
            const std::set<map_location>& my_villages = ai_.current_team().villages();

            map_location loc;
            if(args().size() == 2) {
                  loc = convert_variant<location_callable>(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, const formula_ai& ai_object)
        : function_expression("unit_at", args, 1, 1), ai_(ai_object)
      {}
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<location_callable>(loc_var);
            const unit_map::const_iterator i = ai_.get_info().units.find(loc->loc());
            if(i != ai_.get_info().units.end()) {
                  return variant(new unit_callable(*i));
            } else {
                  return variant();
            }
      }

      const formula_ai& ai_;
};


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<variant> vars;
            if(res.is_null()) {
                  return variant(&vars);
            }

            const map_location& loc = convert_variant<location_callable>(res)->loc();
            const ai::move_map& srcdst = ai_.get_srcdst();
            typedef ai::move_map::const_iterator Itor;
            std::pair<Itor,Itor> 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, const formula_ai& ai_object)
        : function_expression("units_can_reach", args, 2, 2), ai_(ai_object)
      {}
private:
      variant execute(const formula_callable& variables, formula_debugger *fdb) const {
            std::vector<variant> 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<move_map_callable>(dstsrc_var)->dstsrc();
            std::pair<ai::move_map::const_iterator,ai::move_map::const_iterator> range =
                  dstsrc.equal_range(convert_variant<location_callable>(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 = ai_.get_info().units.find(range.first->second);
                  assert(un != ai_.get_info().units.end());
                  vars.push_back(variant(new unit_callable(*un)));
                  ++range.first;
            }

            return variant(&vars);
      }

      const formula_ai& ai_;
};


class defense_on_function : public function_expression {
public:
      defense_on_function(const args_list& args, const formula_ai& ai_object)
        : function_expression("defense_on", args, 2, 2), ai_(ai_object)
      {}
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<unit_callable>(u);
            const unit_type_callable* u_type = try_convert_variant<unit_type_callable>(u);
            const map_location& loc = convert_variant<location_callable>(loc_var)->loc();

            if (u_call)
            {
                  const unit& un = u_call->get_unit();

                        if( un.total_movement() < un.movement_cost( ai_.get_info().map[loc]) )
                            return variant();

                  if(!ai_.get_info().map.on_board(loc)) {
                        return variant();
                  }

                  return variant(100 - un.defense_modifier(ai_.get_info().map[loc]));
            }

            if (u_type)
            {
                  const unit_type& un = u_type->get_unit_type();

                        if( un.movement() < un.movement_type().movement_cost(ai_.get_info().map, ai_.get_info().map[loc]) )
                            return variant();

                  if(!ai_.get_info().map.on_board(loc)) {
                        return variant();
                  }

                  return variant(100 - un.movement_type().defense_modifier(ai_.get_info().map, ai_.get_info().map[loc]));
            }

            return variant();
      }

      const formula_ai& ai_;
};


class chance_to_hit_function : public function_expression {
public:
      chance_to_hit_function(const args_list& args, const formula_ai& ai_object)
        : function_expression("chance_to_hit", args, 2, 2), ai_(ai_object)
      {}
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<unit_callable>(u);
            const unit_type_callable* u_type = try_convert_variant<unit_type_callable>(u);
            const map_location& loc = convert_variant<location_callable>(loc_var)->loc();

            if (u_call)
            {
                  const unit& un = u_call->get_unit();

                  if(!ai_.get_info().map.on_board(loc)) {
                        return variant();
                  }

                  return variant(un.defense_modifier(ai_.get_info().map[loc]));
            }

            if (u_type)
            {
                  const unit_type& un = u_type->get_unit_type();

                  if(!ai_.get_info().map.on_board(loc)) {
                        return variant();
                  }

                  return variant(un.movement_type().defense_modifier(ai_.get_info().map, ai_.get_info().map[loc]));
            }

            return variant();
      }

      const formula_ai& ai_;
};


class movement_cost_function : public function_expression {
public:
      movement_cost_function(const args_list& args, const formula_ai& ai_object)
        : function_expression("movement_cost", args, 2, 2), ai_(ai_object)
      {}
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<unit_callable>(u);
            const unit_type_callable* u_type = try_convert_variant<unit_type_callable>(u);
            const map_location& loc = convert_variant<location_callable>(loc_var)->loc();

            if (u_call)
            {
                  const unit& un = u_call->get_unit();

                  if(!ai_.get_info().map.on_board(loc)) {
                        return variant();
                  }

                  return variant(un.movement_cost(ai_.get_info().map[loc]));
            }

            if (u_type)
            {
                  const unit_type& un = u_type->get_unit_type();

                  if(!ai_.get_info().map.on_board(loc)) {
                        return variant();
                  }

                  return variant(un.movement_type().movement_cost(ai_.get_info().map, ai_.get_info().map[loc]));
            }

            return variant();
      }

      const formula_ai& ai_;
};

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<location_callable>(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, const formula_ai& ai_object)
        : function_expression("max_possible_damage", args, 2, 2), ai_(ai_object)
      {}
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<attack_type> attacks_tmp;
            std::vector<attack_type>& 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<unit_callable>(u1);
            if (u_attacker)
            {
                  attacks = u_attacker->get_unit().attacks();
            } else
            {
                  const unit_type_callable* u_t_attacker = convert_variant<unit_type_callable>(u1);
                  attacks_tmp = u_t_attacker->get_unit_type().attacks();
            }

            const unit_callable* u_defender = try_convert_variant<unit_callable>(u2);
            if (u_defender)
            {
                  const unit& defender = u_defender->get_unit();
                  int best = 0;
                  for(std::vector<attack_type>::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<unit_type_callable>(u2)->get_unit_type();
                  int best = 0;
                  for(std::vector<attack_type>::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);
            }
      }

      const formula_ai& ai_;
};


class max_possible_damage_with_retaliation_function : public function_expression {
public:
      max_possible_damage_with_retaliation_function(const args_list& args, const formula_ai& ai_object)
        : function_expression("max_possible_damage_with_retaliation", args, 2, 2), ai_(ai_object)
      {}
private:

      std::pair<int, int> best_melee_and_ranged_attacks(unit_adapter attacker, unit_adapter defender) const {
            int highest_melee_damage = 0;
            int highest_ranged_damage = 0;

            std::vector<attack_type> attacks = attacker.attacks();

            foreach(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<int, int> best_attacker_attacks = best_melee_and_ranged_attacks(attacker, defender);
            std::pair<int, int> best_defender_attacks = best_melee_and_ranged_attacks(defender, attacker);

            std::vector<variant> 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);
      }

      const formula_ai& ai_;
};

}


expression_ptr ai_function_symbol_table::create_function(const std::string &fn,
                        const std::vector<expression_ptr>& args) const {
      if(fn == "outcomes") {
            return expression_ptr(new outcomes_function(args, ai_));
      //} else if(fn == "evaluate_for_position") {
      //    return expression_ptr(new evaluate_for_position_function(args, ai_));
      } else if(fn == "move") {
            return expression_ptr(new move_function(args));
      } else if(fn == "move_partial") {
            return expression_ptr(new move_partial_function(args));
      } else if(fn == "attack") {
            return expression_ptr(new attack_function(args, ai_));
      } else if(fn == "rate_action") {
            return expression_ptr(new rate_action_function(args, ai_));
      } else if(fn == "recall") {
            return expression_ptr(new recall_function(args));
      } else if(fn == "recruit") {
            return expression_ptr(new recruit_function(args));
      } else if(fn == "safe_call") {
            return expression_ptr(new safe_call_function(args));
      } else if(fn == "get_unit_type") {
            return expression_ptr(new get_unit_type_function(args));
      } else if(fn == "is_avoided_location") {
            return expression_ptr(new is_avoided_location_function(args,ai_));
      } else if(fn == "is_village") {
            return expression_ptr(new is_village_function(args));
      } else if(fn == "is_unowned_village") {
            return expression_ptr(new is_unowned_village_function(args, ai_));
      } else if(fn == "unit_at") {
            return expression_ptr(new unit_at_function(args, ai_));
      } else if(fn == "unit_moves") {
            return expression_ptr(new unit_moves_function(args, ai_));
      } else if(fn == "set_var") {
            return expression_ptr(new set_var_function(args));
      } else if(fn == "set_unit_var") {
            return expression_ptr(new set_unit_var_function(args));
      } else if(fn == "fallback") {
            return expression_ptr(new fallback_function(args));
      } else if(fn == "units_can_reach") {
            return expression_ptr(new units_can_reach_function(args, ai_));
      } else if(fn == "debug_label") {
            return expression_ptr(new debug_label_function(args, ai_));
      } else if(fn == "defense_on") {
            return expression_ptr(new defense_on_function(args, ai_));
      } else if(fn == "chance_to_hit") {
            return expression_ptr(new chance_to_hit_function(args, ai_));
      } else if(fn == "movement_cost") {
            return expression_ptr(new movement_cost_function(args, ai_));
      } else if(fn == "max_possible_damage") {
            return expression_ptr(new max_possible_damage_function(args, ai_));
      } else if(fn == "max_possible_damage_with_retaliation") {
            return expression_ptr(new max_possible_damage_with_retaliation_function(args, ai_));
      } else if(fn == "next_hop") {
            return expression_ptr(new next_hop_function(args, ai_));
      } else if(fn == "adjacent_locs") {
            return expression_ptr(new adjacent_locs_function(args, ai_));
      } else if(fn == "locations_in_radius") {
            return expression_ptr(new locations_in_radius_function(args, ai_));
      } else if(fn == "castle_locs") {
            return expression_ptr(new castle_locs_function(args, ai_));
      } else if(fn == "timeofday_modifier") {
            return expression_ptr(new timeofday_modifier_function(args, ai_));
      } else if(fn == "distance_to_nearest_unowned_village") {
            return expression_ptr(new distance_to_nearest_unowned_village_function(args, ai_));
      } else if(fn == "shortest_path") {
            return expression_ptr(new shortest_path_function(args, ai_));
      } else if(fn == "simplest_path") {
            return expression_ptr(new simplest_path_function(args, ai_));
      } else if(fn == "nearest_keep") {
            return expression_ptr(new nearest_keep_function(args, ai_));
      } else if(fn == "suitable_keep") {
            return expression_ptr(new suitable_keep_function(args, ai_));
      } else if(fn == "nearest_loc") {
            return expression_ptr(new nearest_loc_function(args, ai_));
      } else if(fn == "find_shroud") {
            return expression_ptr(new find_shroud_function(args, ai_));
      } else if(fn == "close_enemies") {
            return expression_ptr(new close_enemies_function(args, ai_));
      } else if(fn == "calculate_outcome") {
            return expression_ptr(new calculate_outcome_function(args, ai_));
      } else if(fn == "distance_between") {
            return expression_ptr(new distance_between_function(args));
      } else if(fn == "run_file") {
            return expression_ptr(new run_file_function(args, ai_));
      } else if(fn == "calculate_map_ownership") {
            return expression_ptr(new calculate_map_ownership_function(args, ai_));
      } else {
            return function_symbol_table::create_function(fn, args);
      }
}

}

Generated by  Doxygen 1.6.0   Back to index