Logo Search packages:      
Sourcecode: wesnoth version File versions

actions.cpp

/* $Id: actions.cpp,v 1.204 2005/04/09 22:22:19 silene Exp $ */
/*
   Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
   Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net

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

   See the COPYING file for more details.
*/

#include "global.hpp"

#include "actions.hpp"
#include "display.hpp"
#include "events.hpp"
#include "game_config.hpp"
#include "game_errors.hpp"
#include "game_events.hpp"
#include "gettext.hpp"
#include "halo.hpp"
#include "hotkeys.hpp"
#include "key.hpp"
#include "log.hpp"
#include "map.hpp"
#include "pathfind.hpp"
#include "playlevel.hpp"
#include "playturn.hpp"
#include "preferences.hpp"
#include "replay.hpp"
#include "sound.hpp"
#include "statistics.hpp"
#include "unit_display.hpp"
#include "util.hpp"
#include "video.hpp"
#include "wassert.hpp"
#include "wml_separators.hpp"
#include "serialization/string_utils.hpp"
#include "widgets/menu.hpp"

#include <cmath>
#include <set>
#include <string>
#include <sstream>

#define LOG_NG LOG_STREAM(info, engine)
#define ERR_NW LOG_STREAM(err, network)

struct castle_cost_calculator : cost_calculator
{
      castle_cost_calculator(const gamemap& map) : map_(map)
      {}

      virtual double cost(const gamemap::location& loc, const double, const bool) const
      {
            if(!map_.is_castle(loc))
                  return 10000;

            return 1;
      }

private:
      const gamemap& map_;
};

// Conditions placed on victory must be accessible from the global function
// check_victory, but shouldn't be passed to that function as parameters,
// since it is called from a variety of places.
namespace victory_conditions
{ 
      bool when_enemies_defeated = true;

      void set_victory_when_enemies_defeated(bool on) 
      {
            when_enemies_defeated = on;
      }

      bool victory_when_enemies_defeated() 
      {
            return when_enemies_defeated;
      }
}

std::string recruit_unit(const gamemap& map, int side,
       std::map<gamemap::location,unit>& units, unit& new_unit,
       gamemap::location& recruit_location, display* disp, bool need_castle, bool full_movement)
{
      const command_disabler disable_commands;

      LOG_NG << "recruiting unit for side " << side << "\n";
      typedef std::map<gamemap::location,unit> units_map;

      //find the unit that can recruit
      units_map::const_iterator u;

      for(u = units.begin(); u != units.end(); ++u) {
            if(u->second.can_recruit() && u->second.side() == side) {
                  break;
            }
      }

      if(u == units.end())
            return _("You don't have a leader to recruit with.");

      if(map.is_keep(u->first) == false) {
            LOG_NG << "Leader not on start: leader is on " << (u->first.x+1) << "," << (u->first.y+1) << "\n";
            return _("You must have your leader on a keep to recruit or recall units.");
      }

      if(need_castle && map.on_board(recruit_location)) {
            castle_cost_calculator calc(map);
            const paths::route& rt = a_star_search(u->first, recruit_location, 100.0, &calc, map.x(), map.y());         
            if(rt.steps.empty() || units.find(recruit_location) != units.end() ||
               !map.is_castle(recruit_location))
                  recruit_location = gamemap::location();
      }

      if(!map.on_board(recruit_location)) {
            recruit_location = find_vacant_tile(map,units,u->first,
                                                need_castle ? VACANT_CASTLE : VACANT_ANY);
      }

      if(!map.on_board(recruit_location)) {
            return _("There are no vacant castle tiles in which to recruit a unit.");
      }

      if(full_movement) {
            new_unit.set_movement(new_unit.total_movement());
      } else {
            new_unit.set_movement(0);
            new_unit.set_attacked();
      }

      const bool show = disp != NULL && !disp->turbo() &&
                        !disp->fogged(recruit_location.x,recruit_location.y);
      if(show) {
            disp->draw(true,true);
      }

      units.insert(std::pair<gamemap::location,unit>(
                                          recruit_location,new_unit));

      LOG_NG << "firing recruit event\n";
      game_events::fire("recruit",recruit_location);

      if(show) {

            for(fixed_t alpha = ftofxp(0.0); alpha <= ftofxp(1.0); alpha += ftofxp(0.1)) {
                  events::pump();
                  disp->draw_tile(recruit_location.x,recruit_location.y,NULL,alpha);
                  disp->update_display();
                  SDL_Delay(20);
            }
      }

      return std::string();
}

gamemap::location under_leadership(const std::map<gamemap::location,unit>& units,
                                   const gamemap::location& loc, int* bonus)
{
      gamemap::location adjacent[6];
      get_adjacent_tiles(loc,adjacent);
      const unit_map::const_iterator un = units.find(loc);
      if(un == units.end()) {
            return gamemap::location::null_location;
      }

      const int side = un->second.side();
      const int level = un->second.type().level();

      int best_leader = -1;
      gamemap::location best_loc;
      for(int i = 0; i != 6; ++i) {
            const unit_map::const_iterator it = units.find(adjacent[i]);
            if(it != units.end() && it->second.side() == side &&
                  it->second.type().is_leader() && it->second.type().level() > level &&
                  it->second.type().level() > best_leader &&
                  it->second.incapacitated() == false) {
                  best_leader = it->second.type().level();
                  best_loc = adjacent[i];
            }
      }

      if(bonus != NULL && best_leader != -1) {
            *bonus = (best_leader - level)*game_config::leadership_bonus;
      }

      return best_loc;
}

static int compute_damage(int base_damage, int bonus, int divisor)
{
      // we want to round (base_damage * bonus / divisor) to the closest integer
      // but up or down towards base_damage
      int rounding = divisor / 2 - (bonus < divisor ? 0 : 1);
      return maximum<int>(1, (base_damage * bonus + rounding) / divisor);
}

battle_stats evaluate_battle_stats(const gamemap& map,
                                   const gamemap::location& attacker,
                                   const gamemap::location& defender,
                                   int attack_with,
                                   std::map<gamemap::location,unit>& units,
                                   const gamestatus& state,
                                   gamemap::TERRAIN attacker_terrain_override,
                                   battle_stats_strings *strings)
{
      battle_stats res;

      res.attack_with = attack_with;

      if (strings)
            strings->defend_name = _("none");

      const std::map<gamemap::location,unit>::iterator a = units.find(attacker);
      const std::map<gamemap::location,unit>::iterator d = units.find(defender);

      wassert(a != units.end());
      wassert(d != units.end());

      const gamemap::TERRAIN attacker_terrain = attacker_terrain_override ?
                       attacker_terrain_override : map[attacker.x][attacker.y];
      const gamemap::TERRAIN defender_terrain = map[defender.x][defender.y];

      res.chance_to_hit_attacker = a->second.defense_modifier(map,attacker_terrain);
      res.chance_to_hit_defender = d->second.defense_modifier(map,defender_terrain);

      const std::vector<attack_type>& attacker_attacks = a->second.attacks();
      const std::vector<attack_type>& defender_attacks = d->second.attacks();

      wassert((unsigned)attack_with < attacker_attacks.size());
      const attack_type& attack = attacker_attacks[attack_with];
      res.attacker_special = attack.special();

      static const std::string charge_string("charge");
      const bool charge = res.attacker_special == charge_string;
      const bool steadfast = d->second.type().steadfast();

      bool backstab = false;

      static const std::string to_the_death_string("berserk");
      res.to_the_death = res.attacker_special == to_the_death_string;
      res.defender_strikes_first = false;

      static const std::string backstab_string("backstab");
      if (res.attacker_special == backstab_string) {
            gamemap::location adj[6];
            get_adjacent_tiles(defender, adj);
            int i;
            for(i = 0; i != 6; ++i) {
                  if(adj[i] == attacker)
                        break;
            }

            if(i != 6) {
                  const std::map<gamemap::location,unit>::const_iterator u =
                                      units.find(adj[(i+3)%6]);
                  if(u != units.end() && u->second.side() == a->second.side()) {
                        backstab = true;
                  }
            }
      }

      static const std::string plague_string("plague");
      res.attacker_plague = !d->second.type().not_living() && 
        (res.attacker_special.substr(0,6) == plague_string) && 
        strcmp(d->second.type().undead_variation().c_str(),"null") && 
        !map.is_village(defender);
      if(res.attacker_special.size()>8){ //plague(type) used
        res.attacker_plague_type=res.attacker_special.substr(7,res.attacker_special.size()-8);
      }else{//plague type is that of attacker
        res.attacker_plague_type= a->second.type().id();
      }
      res.defender_plague = false;

      static const std::string slow_string("slow");
      res.attacker_slows = res.attacker_special == slow_string;

      if (strings) {
            strings->attack_name = egettext(attack.name().c_str());
            strings->attack_type = egettext(attack.type().c_str());
            strings->attack_special = egettext(res.attacker_special.c_str());
            strings->attack_icon = attack.icon();

            //don't show backstabbing unless it's actually happening
            if(attack.special() == backstab_string && !backstab)
                  strings->attack_special = "";

            strings->range = gettext(attack.range() == attack_type::SHORT_RANGE ? N_("melee") : N_("ranged"));
      }

      res.nattacks = attack.num_attacks();
      double best_defend_rating = 0.0;
      int defend_with = -1;
      res.ndefends = 0;
      for(int defend_option = 0; defend_option != int(defender_attacks.size()); ++defend_option) {
            if(defender_attacks[defend_option].range() == attack.range()) {
                  const double rating = a->second.damage_against(defender_attacks[defend_option])
                                        *defender_attacks[defend_option].damage()
                                        *defender_attacks[defend_option].num_attacks()
                                          *defender_attacks[defend_option].defense_weight();
                  if(defend_with == -1 || rating > best_defend_rating) {
                        best_defend_rating = rating;
                        defend_with = defend_option;
                  }
            }
      }

      res.defend_with = defend_with;

      const bool counterattack = defend_with != -1;

      static const std::string drain_string("drain");
      static const std::string magical_string("magical");
      static const std::string EMPTY_COLUMN = std::string(1, COLUMN_SEPARATOR) + ' ' + COLUMN_SEPARATOR;

      res.damage_attacker_takes = 0;
      if (counterattack) {
            const attack_type& defend = defender_attacks[defend_with];
            res.defender_special = defend.special();

            //magical attacks always have a 70% chance to hit
            if (res.defender_special == magical_string) {
                  res.chance_to_hit_attacker = 70;
            }

            int bonus = 100;
            int divisor = 100;

            const int base_damage = defend.damage();
            const int resistance_modifier = a->second.damage_against(defend);
            
            if (strings) {
                  std::stringstream str_base;
                  str_base << _("base damage") << COLUMN_SEPARATOR << base_damage;
                  strings->defend_calculations.push_back(str_base.str());
            }

            const int tod_modifier = combat_modifier(state,units,d->first,d->second.type().alignment());
            bonus += tod_modifier;

            if (strings && tod_modifier != 0) {
                  std::stringstream str_mod;
                  const time_of_day& tod = timeofday_at(state,units,d->first);
                  str_mod << tod.name << EMPTY_COLUMN << (tod_modifier > 0 ? "+" : "") << tod_modifier << '%';
                  strings->defend_calculations.push_back(str_mod.str());
            }

            int leader_bonus = 0;
            if (under_leadership(units, defender, &leader_bonus).valid()) {
                  bonus += leader_bonus;

                  if (strings) {
                        std::stringstream str;
                        str << _("leadership") << EMPTY_COLUMN << '+' << leader_bonus << '%';
                        strings->defend_calculations.push_back(str.str());
                  }
            }

            if (charge) {
                  bonus *= 2;

                  if (strings) {
                        std::stringstream str;
                        str << _("charge") << EMPTY_COLUMN << _("Doubled");
                        strings->defend_calculations.push_back(str.str());
                  }
            }

            if (strings && resistance_modifier != 100) {
                  const int resist = resistance_modifier - 100;
                  std::stringstream str_resist;
                  str_resist << gettext(resist < 0 ? N_("attacker resistance vs") : N_("attacker vulnerability vs"))
                             << ' ' << gettext(defend.type().c_str()) << EMPTY_COLUMN
                             << (resist > 0 ? "+" : "") << resist << '%';
                  strings->defend_calculations.push_back(str_resist.str());
            }

            bonus *= resistance_modifier;
            divisor *= 100;
            const int final_damage = compute_damage(base_damage, bonus, divisor);
            res.damage_attacker_takes = final_damage;

            if (strings) {
                  const int difference = final_damage - base_damage;
                  std::stringstream str;
                  str << _("total damage") << COLUMN_SEPARATOR << res.damage_attacker_takes
                      << COLUMN_SEPARATOR << (difference >= 0 ? "+" : "")
                      << difference;
                  strings->defend_calculations.push_back(str.str());
            }

            res.ndefends = defend.num_attacks();

            if (strings) {
                  strings->defend_name = egettext(defend.name().c_str());
                  strings->defend_type = egettext(defend.type().c_str());
                  strings->defend_special = egettext(res.defender_special.c_str());
                  strings->defend_icon = defend.icon();
            }

            //if the defender drains, and the attacker is a living creature, then
            //the defender will drain for half the damage it does
            if (res.defender_special == drain_string && !a->second.type().not_living()) {
                  res.amount_defender_drains = res.damage_attacker_takes/2;
            } else {
                  res.amount_defender_drains = 0;
            }

            res.defender_plague = !a->second.type().not_living() && 
              (res.defender_special.substr(0,6) == plague_string) &&
              strcmp(a->second.type().undead_variation().c_str(),"null") &&
              !map.is_village(attacker);
            if(res.defender_special.size()>8){ //plague(type) used
                    res.defender_plague_type=res.defender_special.substr(7,res.defender_special.size()-8);
            }else{//plague type is that of attacker
                    res.defender_plague_type= d->second.type().id();
            }

            res.defender_slows = (defend.special() == slow_string);

            static const std::string first_strike = "firststrike";
            res.defender_strikes_first = res.defender_special == first_strike && res.attacker_special != first_strike;
      }

      if (res.attacker_special == magical_string)
            res.chance_to_hit_defender = 70;

      static const std::string marksman_string("marksman");

      //offensive marksman attacks always have at least 60% chance to hit
      if(res.chance_to_hit_defender < 60 && res.attacker_special == marksman_string)
            res.chance_to_hit_defender = 60;

      int bonus = 100;
      int divisor = 100;

      const int base_damage = attack.damage();
      const int resistance_modifier = d->second.damage_against(attack);

      if (strings) {
            std::stringstream str_base;
            str_base << _("base damage") << COLUMN_SEPARATOR << base_damage << COLUMN_SEPARATOR;
            strings->attack_calculations.push_back(str_base.str());
      }

      const int tod_modifier = combat_modifier(state,units,a->first,a->second.type().alignment());

      bonus += tod_modifier;

      if (strings && tod_modifier != 0) {
            std::stringstream str_mod;
            const time_of_day& tod = timeofday_at(state,units,a->first);
            str_mod << tod.name << EMPTY_COLUMN << (tod_modifier > 0 ? "+" : "") << tod_modifier << '%';
            strings->attack_calculations.push_back(str_mod.str());
      }

      int leader_bonus = 0;
      if (under_leadership(units,attacker,&leader_bonus).valid()) {
            bonus += leader_bonus;
            
            if (strings) {
                  std::stringstream str;
                  str << _("leadership") << EMPTY_COLUMN << '+' << leader_bonus << '%';
                  strings->attack_calculations.push_back(str.str());
            }
      }

      if (charge) {
            bonus *= 2;
            if (strings) {
                  std::stringstream str;
                  str << _("charge") << EMPTY_COLUMN << _("Doubled");
                  strings->attack_calculations.push_back(str.str());
            }
      }

      if (backstab) {
            bonus *= 2;
            if (strings) {
                  std::stringstream str;
                  str << _("backstab") << EMPTY_COLUMN << _("Doubled");
                  strings->attack_calculations.push_back(str.str());
            }
      }

      if (steadfast) {
            divisor *= 2;
            if (strings) {
                  std::stringstream str;
                  str << _("steadfast") << EMPTY_COLUMN << _("Halved");
                  strings->attack_calculations.push_back(str.str());
            }
      }

      if (strings && resistance_modifier != 100) {
            const int resist = resistance_modifier - 100;
            std::stringstream str_resist;
            str_resist << gettext(resist < 0 ? N_("defender resistance vs") : N_("defender vulnerability vs"))
                       << ' ' << gettext(attack.type().c_str()) << EMPTY_COLUMN
                       << (resist > 0 ? "+" : "") << resist << '%';
            strings->attack_calculations.push_back(str_resist.str());
      }

      bonus *= resistance_modifier;
      divisor *= 100;
      const int final_damage = compute_damage(base_damage, bonus, divisor);
      res.damage_defender_takes = final_damage;

      if (strings) {
            const int difference = final_damage - base_damage;
            std::stringstream str;
            str << _("total damage") << COLUMN_SEPARATOR << res.damage_defender_takes
                << COLUMN_SEPARATOR << (difference >= 0 ? "+" : "")
                << difference;
            strings->attack_calculations.push_back(str.str());
      }

      //if the attacker drains, and the defender is a living creature, then
      //the attacker will drain for half the damage it does
      if(attack.special() == drain_string && !d->second.type().not_living()) {
            res.amount_attacker_drains = res.damage_defender_takes/2;
      } else {
            res.amount_attacker_drains = 0;
      }

      static const std::string slowed_string("slowed");
      if(a->second.has_flag(slowed_string) && res.nattacks > 1)
            --res.nattacks;

      if(d->second.has_flag(slowed_string) && res.ndefends > 1)
            --res.ndefends;

      return res;
}

static std::string unit_dump(std::pair< gamemap::location, unit > const &u)
{
      std::stringstream s;
      s << u.second.type().id() << " (" << u.first.x + 1 << ',' << u.first.y + 1 << ')';
      return s.str();
}

void attack(display& gui, const gamemap& map, 
            std::vector<team>& teams,
            gamemap::location attacker,
            gamemap::location defender,
            int attack_with,
            std::map<gamemap::location,unit>& units,
            const gamestatus& state,
            const game_data& info)
{
      //stop the user from issuing any commands while the units are fighting
      const command_disabler disable_commands;

      std::map<gamemap::location,unit>::iterator a = units.find(attacker);
      std::map<gamemap::location,unit>::iterator d = units.find(defender);

      if(a == units.end() || d == units.end()) {
            return;
      }

      int attackerxp = d->second.type().level();
      int defenderxp = a->second.type().level();

      a->second.set_attacked();
      d->second.set_resting(false);

      //if the attacker was invisible, she isn't anymore!
      static const std::string forest_invisible("ambush");
      a->second.remove_flag(forest_invisible);
      static const std::string night_invisible("nightstalk");
      a->second.remove_flag(night_invisible);

      battle_stats stats = evaluate_battle_stats(map, attacker, defender,
                                                 attack_with, units, state);

      statistics::attack_context attack_stats(a->second,d->second,stats);

      const int orig_attacks = stats.nattacks;
      const int orig_defends = stats.ndefends;
      int to_the_death = stats.to_the_death ? 30 : 0;

      static const std::string poison_string("poison");

      while(stats.nattacks > 0 || stats.ndefends > 0) {
            LOG_NG << "start of attack loop...\n";

            if(stats.nattacks > 0 && stats.defender_strikes_first == false) {
                  const int ran_num = get_random();
                  bool hits = (ran_num%100) < stats.chance_to_hit_defender;

                  //make sure that if we're serializing a game here,
                  //we got the same results as the game did originally
                  const config* ran_results = get_random_results();
                  if(ran_results != NULL) {
                        const int results_chance = atoi((*ran_results)["chance"].c_str());
                        const bool results_hits = (*ran_results)["hits"] == "yes";
                        const int results_damage = atoi((*ran_results)["damage"].c_str());

                        if(results_chance != stats.chance_to_hit_defender) {
                              ERR_NW << "SYNC: In attack " << unit_dump(*a) << " vs " << unit_dump(*d)
                                    << ": chance to hit defender is inconsistent. Data source: "
                                    << results_chance << "; Calculation: " << stats.chance_to_hit_defender
                                    << " (over-riding game calculations with data source results)\n";
                              stats.chance_to_hit_defender = results_chance;
                        }
                        if(hits != results_hits) {
                              ERR_NW << "SYNC: In attack " << unit_dump(*a) << " vs " << unit_dump(*d)
                                    << ": the data source says the hit was "
                                    << (results_hits ? "successful" : "unsuccessful")
                                    << ", while in-game calculations say the hit was "
                                    << (hits ? "successful" : "unsuccessful")
                                    << " random number: " << ran_num << " = "
                                    << (ran_num%100) << "/" << results_chance
                                    << " (over-riding game calculations with data source results)\n";
                              hits = results_hits;
                        }
                        if(results_damage != stats.damage_defender_takes) {
                              ERR_NW << "SYNC: In attack " << unit_dump(*a) << " vs " << unit_dump(*d)
                                    << ": the data source says the hit did " << results_damage
                                    << " damage, while in-game calculations show the hit doing "
                                    << stats.damage_defender_takes
                                    << " damage (over-riding game calculations with data source results)\n";
                              stats.damage_defender_takes = results_damage;
                        }
                  }

                  bool dies = unit_display::unit_attack(gui,units,map,attacker,defender,
                                    hits ? stats.damage_defender_takes : 0,
                                          a->second.attacks()[attack_with]);

                  LOG_NG << "done attacking\n";

                  attack_stats.attack_result(hits ? (dies ? statistics::attack_context::KILLS : statistics::attack_context::HITS)
                                             : statistics::attack_context::MISSES);

                  if(ran_results == NULL) {
                        log_scope2(engine, "setting random results");
                        config cfg;
                        cfg["hits"] = (hits ? "yes" : "no");
                        cfg["dies"] = (dies ? "yes" : "no");

                        cfg["damage"] = lexical_cast<std::string>(stats.damage_defender_takes);
                        cfg["chance"] = lexical_cast<std::string>(stats.chance_to_hit_defender);

                        set_random_results(cfg);
                  } else {
                        const bool results_dies = (*ran_results)["dies"] == "yes";
                        if(results_dies != dies) {
                              ERR_NW << "SYNC: In attack " << unit_dump(*a) << " vs " << unit_dump(*d)
                                    << ": the data source the unit "
                                    << (results_dies ? "perished" : "survived")
                                    << " while in-game calculations show the unit "
                                    << (dies ? "perished" : "survived")
                                    << " (over-riding game calculations with data source results)\n";
                              dies = results_dies;
                        }
                  }

                  if(dies || hits) {
                        if(stats.amount_attacker_drains > 0) {
                              char buf[50];
                              sprintf(buf,"%d",stats.amount_attacker_drains);
                              gui.float_label(a->first,buf,0,255,0);
                              a->second.heal(stats.amount_attacker_drains);
                        }
                  }

                  if(dies) {//attacker kills defender
                        attackerxp = game_config::kill_experience*d->second.type().level();
                        if(d->second.type().level() == 0)
                              attackerxp = game_config::kill_experience/2;

                        a->second.get_experience(attackerxp);
                        gui.invalidate(a->first);
                        attackerxp = 0;
                        defenderxp = 0;

                        gamemap::location loc = d->first;
                        gamemap::location attacker_loc = a->first;
                        std::string undead_variation = d->second.type().undead_variation();
                        const int defender_side = d->second.side();
                        LOG_NG << "firing die event\n";
                        game_events::fire("die",loc,a->first);
                        d = units.find(loc);
                        a = units.end();

                        if(d != units.end() && d->second.hitpoints() <= 0) {
                              units.erase(d);
                              d = units.end();
                        }

                        //plague units make new units on the target hex
                        if(stats.attacker_plague) {
                                a = units.find(attacker_loc); 
                                game_data::unit_type_map::const_iterator reanimitor;
                                LOG_NG<<"trying to reanimate "<<stats.attacker_plague_type<<std::endl;
                                reanimitor = info.unit_types.find(stats.attacker_plague_type);
                                LOG_NG<<"found unit type:"<<reanimitor->second.id()<<std::endl;
                              
                              if(reanimitor != info.unit_types.end()) {
                                     unit newunit=unit(&reanimitor->second,a->second.side(),false,true);
                                     //apply variation                                     
                                     if(strcmp(undead_variation.c_str(),"null")){
                                     config mod;
                                     config& variation=mod.add_child("effect");
                                     variation["apply_to"]="variation";
                                     variation["name"]=undead_variation;
                                     newunit.add_modification("variation",mod);
                                     }

                                     units.insert(std::pair<gamemap::location,unit>(loc,newunit));
                                     gui.draw_tile(loc.x,loc.y);
                              }else{
                                     LOG_NG<<"unit not reanimated"<<std::endl;
                              }
                        }
                        recalculate_fog(map,state,info,units,teams,defender_side-1);
                        gui.recalculate_minimap();
                        gui.update_display();
                        break;
                  } else if(hits) {
                        if (stats.attacker_special == poison_string &&
                           d->second.has_flag("poisoned") == false &&
                           !d->second.type().not_living()) {
                              gui.float_label(d->first,_("poisoned"),255,0,0);
                              d->second.set_flag("poisoned");
                        }

                        if(stats.attacker_slows && d->second.has_flag("slowed") == false) {
                              gui.float_label(d->first,_("slowed"),255,0,0);
                              d->second.set_flag("slowed");
                              if(stats.ndefends > 1)
                                    --stats.ndefends;
                        }

                        //if the defender is turned to stone, the fight stops immediately
                        static const std::string stone_string("stone");
                        if (stats.attacker_special == stone_string) {
                              gui.float_label(d->first,_("stone"),255,0,0);
                              d->second.set_flag(stone_string);
                              stats.ndefends = 0;
                              stats.nattacks = 0;
                              game_events::fire(stone_string,d->first,a->first);
                        }
                  }

                  --stats.nattacks;
            }

            //if the defender got to strike first, they use it up here.
            stats.defender_strikes_first = false;

            if(stats.ndefends > 0) {
                  LOG_NG << "doing defender attack...\n";

                  const int ran_num = get_random();
                  bool hits = (ran_num%100) < stats.chance_to_hit_attacker;

                  //make sure that if we're serializing a game here,
                  //we got the same results as the game did originally
                  const config* ran_results = get_random_results();
                  if(ran_results != NULL) {
                        const int results_chance = atoi((*ran_results)["chance"].c_str());
                        const bool results_hits = (*ran_results)["hits"] == "yes";
                        const int results_damage = atoi((*ran_results)["damage"].c_str());

                        if(results_chance != stats.chance_to_hit_attacker) {
                              ERR_NW << "SYNC: In defend " << unit_dump(*a) << " vs " << unit_dump(*d)
                                    << ": chance to hit attacker is inconsistent. Data source: "
                                    << results_chance << "; Calculation: " << stats.chance_to_hit_attacker
                                    << " (over-riding game calculations with data source results)\n";
                              stats.chance_to_hit_attacker = results_chance;
                        }
                        if(hits != results_hits) {
                              ERR_NW << "SYNC: In defend " << unit_dump(*a) << " vs " << unit_dump(*d)
                                    << ": the data source says the hit was "
                                    << (results_hits ? "successful" : "unsuccessful")
                                    << ", while in-game calculations say the hit was "
                                    << (hits ? "successful" : "unsuccessful")
                                    << " random number: " << ran_num << " = " << (ran_num%100) << "/"
                                    << results_chance
                                    << " (over-riding game calculations with data source results)\n";
                              hits = results_hits;
                        }
                        if(results_damage != stats.damage_attacker_takes) {
                              ERR_NW << "SYNC: In defend " << unit_dump(*a) << " vs " << unit_dump(*d)
                                    << ": the data source says the hit did " << results_damage
                                    << " damage, while in-game calculations show the hit doing "
                                    << stats.damage_attacker_takes
                                    << " damage (over-riding game calculations with data source results)\n";
                              stats.damage_attacker_takes = results_damage;
                        }
                  }

                  bool dies = unit_display::unit_attack(gui,units,map,defender,attacker,
                                 hits ? stats.damage_attacker_takes : 0,
                                       d->second.attacks()[stats.defend_with]);

                  attack_stats.defend_result(hits ? (dies ? statistics::attack_context::KILLS : statistics::attack_context::HITS)
                                             : statistics::attack_context::MISSES);

                  if(ran_results == NULL) {
                        config cfg;
                        cfg["hits"] = (hits ? "yes" : "no");
                        cfg["dies"] = (dies ? "yes" : "no");
                        cfg["damage"] = lexical_cast<std::string>(stats.damage_attacker_takes);
                        cfg["chance"] = lexical_cast<std::string>(stats.chance_to_hit_attacker);

                        set_random_results(cfg);
                  } else {
                        const bool results_dies = (*ran_results)["dies"] == "yes";
                        if(results_dies != dies) {
                              ERR_NW << "SYNC: In defend " << unit_dump(*a) << " vs " << unit_dump(*d)
                                    << ": the data source the unit "
                                    << (results_dies ? "perished" : "survived")
                                    << " while in-game calculations show the unit "
                                    << (dies ? "perished" : "survived")
                                    << " (over-riding game calculations with data source results)\n";
                              dies = results_dies;
                        }
                  }

                  if(hits || dies){
                        if(stats.amount_defender_drains > 0) {
                              char buf[50];
                              sprintf(buf,"%d",stats.amount_defender_drains);
                              gui.float_label(d->first,buf,0,255,0);
                              d->second.heal(stats.amount_defender_drains);
                        }
                  }
                        
                  if(dies) {//defender kills attacker
                        defenderxp = game_config::kill_experience*a->second.type().level();
                        if(a->second.type().level() == 0)
                              defenderxp = game_config::kill_experience/2;

                        d->second.get_experience(defenderxp);
                        gui.invalidate(d->first);
                        defenderxp = 0;
                        attackerxp = 0;

                        std::string undead_variation = a->second.type().undead_variation();
                        gamemap::location loc = a->first;
                        gamemap::location defender_loc = d->first;
                        const int attacker_side = a->second.side();
                        game_events::fire("die",loc,d->first);
                        a = units.find(loc);
                        d = units.end();

                        if(a != units.end() && a->second.hitpoints() <= 0) {
                              units.erase(a);
                              a = units.end();
                        }

                        //plague units make new units on the target hex.
                        if(stats.defender_plague) {
                                d = units.find(defender_loc); 
                                game_data::unit_type_map::const_iterator reanimitor;
                                LOG_NG<<"trying to reanimate "<<stats.defender_plague_type<<std::endl;
                                reanimitor = info.unit_types.find(stats.defender_plague_type);
                                LOG_NG<<"found unit type:"<<reanimitor->second.id()<<std::endl;
                              
                              if(reanimitor != info.unit_types.end()) {
                                     unit newunit=unit(&reanimitor->second,d->second.side(),false,true);
                                     //apply variation
                                     if(strcmp(undead_variation.c_str(),"null")){
                                     config mod;
                                     config& variation=mod.add_child("effect");
                                     variation["apply_to"]="variation";
                                     variation["name"]=undead_variation;
                                     newunit.add_modification("variation",mod);
                                     }

                                     units.insert(std::pair<gamemap::location,unit>(loc,newunit));
                                     gui.draw_tile(loc.x,loc.y);
                              }else{
                                     LOG_NG<<"unit not reanimated"<<std::endl;
                              }
                        }
                        gui.recalculate_minimap();
                        gui.update_display();
                        recalculate_fog(map,state,info,units,teams,attacker_side-1);
                        break;
                  } else if(hits) {
                        if (stats.defender_special == poison_string &&
                           a->second.has_flag("poisoned") == false &&
                           !a->second.type().not_living()) {
                              gui.float_label(a->first,_("poisoned"),255,0,0);
                              a->second.set_flag("poisoned");
                        }

                        if(stats.defender_slows && a->second.has_flag("slowed") == false) {
                              gui.float_label(a->first,_("slowed"),255,0,0);
                              a->second.set_flag("slowed");
                              if(stats.nattacks > 1)
                                    --stats.nattacks;
                        }


                        //if the attacker is turned to stone, the fight stops immediately
                        static const std::string stone_string("stone");
                        if (stats.defender_special == stone_string) {
                              gui.float_label(a->first,_("stone"),255,0,0);
                              a->second.set_flag(stone_string);
                              stats.ndefends = 0;
                              stats.nattacks = 0;
                              game_events::fire(stone_string,a->first,d->first);
                        }
                  }

                  --stats.ndefends;
            }

            if(to_the_death > 0 && stats.ndefends == 0 && stats.nattacks == 0) {
                  stats.nattacks = orig_attacks;
                  stats.ndefends = orig_defends;
                  --to_the_death;
            }
      }

      if(attackerxp) {
            a->second.get_experience(attackerxp);
      }

      if(defenderxp) {
            d->second.get_experience(defenderxp);
      }

      gui.invalidate_unit();
      gui.invalidate(attacker);
      gui.invalidate(defender);
      gui.draw(true,true);
}

int village_owner(const gamemap::location& loc, const std::vector<team>& teams)
{
      for(size_t i = 0; i != teams.size(); ++i) {
            if(teams[i].owns_village(loc))
                  return i;
      }

      return -1;
}


void get_village(const gamemap::location& loc, std::vector<team>& teams,
                 size_t team_num, const unit_map& units)
{
      if(team_num < teams.size() && teams[team_num].owns_village(loc)) {
            return;
      }

      const bool has_leader = find_leader(units,int(team_num+1)) != units.end();

      //we strip the village off all other sides, unless it is held by an ally
      //and we don't have a leader (and thus can't occupy it)
      for(std::vector<team>::iterator i = teams.begin(); i != teams.end(); ++i) {
            const int side = i - teams.begin() + 1;
            if(team_num >= teams.size() || has_leader || teams[team_num].is_enemy(side)) {
                  i->lose_village(loc);
            }
      }

      if(team_num >= teams.size()) {
            return;
      }

      if(has_leader) {
            teams[team_num].get_village(loc);
      }
}

unit_map::iterator find_leader(unit_map& units, int side)
{
      for(unit_map::iterator i = units.begin(); i != units.end(); ++i) {
            if(i->second.side() == side && i->second.can_recruit())
                  return i;
      }

      return units.end();
}

unit_map::const_iterator find_leader(const unit_map& units, int side)
{
      for(unit_map::const_iterator i = units.begin(); i != units.end(); ++i) {
            if(i->second.side() == side && i->second.can_recruit())
                  return i;
      }

      return units.end();
}

namespace {

//function which returns true iff the unit at 'loc' will heal a unit from side 'side'
//on this turn.
//
//units heal other units if they are (1) on the same side as them; or (2) are on a
//different but allied side, and there are no 'higher priority' sides also adjacent
//to the healer
bool will_heal(const gamemap::location& loc, int side, const std::vector<team>& teams,
                     const unit_map& units)
{
      const unit_map::const_iterator healer_it = units.find(loc);
      if(healer_it == units.end() || healer_it->second.type().heals() == false) {
            return false;
      }

      const unit& healer = healer_it->second;

      if(healer.incapacitated()) {
            return false;
      }

      if(healer.side() == side) {
            return true;
      }

      if(size_t(side-1) >= teams.size() || size_t(healer.side()-1) >= teams.size()) {
            return false;
      }

      //if the healer is an enemy, it won't heal
      if(teams[healer.side()-1].is_enemy(side)) {
            return false;
      }

      gamemap::location adjacent[6];
      get_adjacent_tiles(loc,adjacent);
      for(int n = 0; n != 6; ++n) {
            const unit_map::const_iterator u = units.find(adjacent[n]);
            if(u != units.end() && (u->second.hitpoints() < u->second.max_hitpoints() || u->second.poisoned())) {
                  const int unit_side = u->second.side();

                  //the healer won't heal an ally if there is a wounded unit on the same
                  //side next to her
                  if(unit_side == healer.side())
                        return false;

                  //choose an arbitrary order for healing
                  if(unit_side > side)
                        return false;
            }
      }

      //there's no-one of higher priority nearby, so the ally will heal
      return true;
}

}

void calculate_healing(display& disp, const gamestatus& status, const gamemap& map,
                       std::map<gamemap::location,unit>& units, int side,
                                 const std::vector<team>& teams)
{
      std::map<gamemap::location,int> healed_units, max_healing;

      //a map of healed units to their healers
      std::multimap<gamemap::location,gamemap::location> healers;

      std::map<gamemap::location,unit>::iterator i;
      int amount_healed;
      for(i = units.begin(); i != units.end(); ++i) {
            amount_healed = 0;

            //the unit heals if it's on this side, and it's on a village or
            //it has regeneration, and it is wounded
            if(i->second.side() == side) {
                  if(i->second.hitpoints() < i->second.max_hitpoints() || i->second.poisoned()){
                        if(map.gives_healing(i->first) || i->second.type().regenerates()) {
                              amount_healed = game_config::cure_amount;
                        }
                  }
                  if(amount_healed != 0)
                        healed_units.insert(std::pair<gamemap::location,int>(
                                              i->first, amount_healed));
            }

            //otherwise find the maximum healing for the unit
            if(amount_healed == 0) {
                  int max_heal = 0;
                  gamemap::location adjacent[6];
                  get_adjacent_tiles(i->first,adjacent);
                  for(int j = 0; j != 6; ++j) {
                        if(will_heal(adjacent[j],i->second.side(),teams,units)) {
                              const unit_map::const_iterator healer = units.find(adjacent[j]);
                              max_heal = maximum(max_heal,healer->second.type().max_unit_healing());

                              healers.insert(std::pair<gamemap::location,gamemap::location>(i->first,adjacent[j]));
                        }
                  }

                  if(max_heal > 0) {
                        max_healing.insert(std::pair<gamemap::location,int>(i->first,max_heal));
                  }
            }
      }

      //now see about units that can heal other units
      for(i = units.begin(); i != units.end(); ++i) {

            if(will_heal(i->first,side,teams,units)) {
                  gamemap::location adjacent[6];
                  bool gets_healed[6];
                  get_adjacent_tiles(i->first,adjacent);

                  int nhealed = 0;
                  int j;
                  for(j = 0; j != 6; ++j) {
                        const std::map<gamemap::location,unit>::const_iterator adj =
                                                           units.find(adjacent[j]);
                        if(adj != units.end() &&
                           adj->second.hitpoints() < adj->second.max_hitpoints() &&
                           adj->second.side() == side &&
                           healed_units[adj->first] < max_healing[adj->first]) {
                              ++nhealed;
                              gets_healed[j] = true;
                        } else {
                              gets_healed[j] = false;
                        }
                  }

                  if(nhealed == 0)
                        continue;

                  const int healing_per_unit = i->second.type().heals()/nhealed;

                  for(j = 0; j != 6; ++j) {
                        if(!gets_healed[j])
                              continue;

                        wassert(units.find(adjacent[j]) != units.end());

                        healed_units[adjacent[j]]
                                = minimum(max_healing[adjacent[j]],
                                          healed_units[adjacent[j]]+healing_per_unit);
                  }
            }
      }

      //poisoned units will take the same amount of damage per turn, as
      //curing heals until they are reduced to 1 hitpoint. If they are
      //cured on a turn, they recover 0 hitpoints that turn, but they
      //are no longer poisoned
      for(i = units.begin(); i != units.end(); ++i) {

            if(i->second.side() == side && i->second.poisoned()) {
                  const int damage = minimum<int>(game_config::cure_amount,
                                                  i->second.hitpoints()-1);

                  if(damage > 0) {
                        healed_units.insert(std::pair<gamemap::location,int>(i->first,-damage));
                  }
            }
      }

      for(i = units.begin(); i != units.end(); ++i) {
            if(i->second.side() == side) {
                  if(i->second.hitpoints() < i->second.max_hitpoints() || 
                              i->second.poisoned()){
                        if(i->second.is_resting()) {
                              const std::map<gamemap::location,int>::iterator u =
                                    healed_units.find(i->first);
                              if(u != healed_units.end()) {
                                    healed_units[i->first] += game_config::rest_heal_amount;
                              } else {
                                    healed_units.insert(std::pair<gamemap::location,int>(i->first,game_config::rest_heal_amount));
                              }
                        }
                  }
                  i->second.set_resting(true);
            }
      }

      for(std::map<gamemap::location,int>::iterator h = healed_units.begin();
          h != healed_units.end(); ++h) {

            const gamemap::location& loc = h->first;

            wassert(units.count(loc) == 1);

            unit& u = units.find(loc)->second;

            const bool show_healing = !disp.turbo() && !recorder.skipping() &&
                                      !disp.fogged(loc.x,loc.y) &&
                                                  (!u.invisible(map.underlying_terrain(map[h->first.x][h->first.y]),
                                                                status.get_time_of_day().lawful_bonus,h->first,units,teams) ||
                                                                teams[disp.viewing_team()].is_enemy(side) == false);

            typedef std::multimap<gamemap::location,gamemap::location>::const_iterator healer_itor;
            const std::pair<healer_itor,healer_itor> healer_itors = healers.equal_range(loc);

            if(show_healing) {
                  disp.scroll_to_tile(loc.x,loc.y,display::WARP);
                  disp.select_hex(loc);


                  //iterate over any units that are healing this unit, and make them
                  //enter their healing frame
                  for(healer_itor i = healer_itors.first; i != healer_itors.second; ++i) {
                        wassert(units.count(i->second));
                        unit& healer = units.find(i->second)->second;

                        const std::string& halo_image = healer.type().image_halo_healing();

                        //only show healing frames if haloing is enabled, or if there is no halo
                        if(halo_image.empty() || preferences::show_haloes()) {
                              healer.set_healing(true);

                              if(halo_image.empty() == false) {
                                    halo::add(disp.get_location_x(i->second)+disp.hex_size()/2,disp.get_location_y(i->second)+disp.hex_size()/2,
                                                halo_image,healer.facing_left() ? halo::NORMAL : halo::REVERSE,1);
                              }

                              disp.draw_tile(i->second.x,i->second.y);
                        }
                  }

                  disp.update_display();
            }

            const int DelayAmount = 50;

            LOG_NG << "unit is poisoned? " << (u.has_flag("poisoned") ? "yes" : "no") << ","
                  << h->second << "," << max_healing[h->first] << "\n";

            if(u.has_flag("poisoned") && h->second > 0) {

                  //poison is purged only if we are on a village or next to a curer
                  if(h->second >= game_config::cure_amount ||
                     max_healing[h->first] >= game_config::cure_amount) {
                        u.remove_flag("poisoned");

                        if(show_healing) {
                              events::pump();

                              sound::play_sound("heal.wav");
                              SDL_Delay(DelayAmount);
                              disp.invalidate_unit();
                              disp.update_display();
                        }
                  }

                  h->second = 0;
            } else {
                  if(h->second > 0 && h->second > u.max_hitpoints()-u.hitpoints()) {
                        h->second = u.max_hitpoints()-u.hitpoints();
                        if(h->second <= 0)
                              continue;
                  }
                  
                  if(h->second < 0) {
                        if(show_healing) {
                              sound::play_sound("groan.wav");
                              disp.float_label(h->first,lexical_cast<std::string>(h->second*-1),255,0,0);
                        }
                  } else if(h->second > 0) {
                        if(show_healing) {
                              sound::play_sound("heal.wav");
                              disp.float_label(h->first,lexical_cast<std::string>(h->second),0,255,0);
                        }
                  }
            }

            while(h->second > 0) {
                  const Uint32 heal_colour = disp.rgb(0,0,200);
                  u.heal(1);

                  if(show_healing) {
                        if(is_odd(h->second))
                              disp.draw_tile(loc.x,loc.y,NULL,ftofxp(0.5),heal_colour);
                        else
                              disp.draw_tile(loc.x,loc.y);

                        events::pump();
                        SDL_Delay(DelayAmount);
                        disp.update_display();
                  }

                  --h->second;
            }

            while(h->second < 0) {
                  const Uint32 damage_colour = disp.rgb(200,0,0);
                  u.gets_hit(1);

                  if(show_healing) {
                        if(is_odd(h->second))
                              disp.draw_tile(loc.x,loc.y,NULL,ftofxp(0.5),damage_colour);
                        else
                              disp.draw_tile(loc.x,loc.y);

                        events::pump();

                        SDL_Delay(DelayAmount);
                        disp.update_display();
                  }

                  ++h->second;
            }

            if(show_healing) {
                  for(healer_itor i = healer_itors.first; i != healer_itors.second; ++i) {
                        wassert(units.count(i->second));
                        unit& healer = units.find(i->second)->second;
                        healer.set_healing(false);

                        events::pump();

                        disp.draw_tile(i->second.x,i->second.y);
                  }
                  
                  disp.draw_tile(loc.x,loc.y);
                  disp.update_display();
            }
      }
}

unit get_advanced_unit(const game_data& info,
                  std::map<gamemap::location,unit>& units,
                  const gamemap::location& loc, const std::string& advance_to)
{
      const std::map<std::string,unit_type>::const_iterator new_type = info.unit_types.find(advance_to);
      const std::map<gamemap::location,unit>::iterator un = units.find(loc);
      if(new_type != info.unit_types.end() && un != units.end()) {
            return unit(&(new_type->second),un->second);
      } else {
            throw game::game_error("Could not find the unit being advanced"
                                         " to: " + advance_to);
      }
}

void advance_unit(const game_data& info,
                  std::map<gamemap::location,unit>& units,
                  gamemap::location loc, const std::string& advance_to)
{
      if(units.count(loc) == 0) {
            return;
      }

      const unit& new_unit = get_advanced_unit(info,units,loc,advance_to);

      statistics::advance_unit(new_unit);

      preferences::encountered_units().insert(new_unit.type().id());
      LOG_STREAM(info, config) << "Added '" << new_unit.type().id() << "' to encountered units\n";

      units.erase(loc);
      units.insert(std::pair<gamemap::location,unit>(loc,new_unit));
}

void check_victory(std::map<gamemap::location,unit>& units,
                   std::vector<team>& teams)
{
      std::vector<int> seen_leaders;
      for(std::map<gamemap::location,unit>::const_iterator i = units.begin();
          i != units.end(); ++i) {
            if(i->second.can_recruit()) {
                  LOG_NG << "seen leader for side " << i->second.side() << "\n";
                  seen_leaders.push_back(i->second.side());
            }
      }

      //clear villages for teams that have no leader
      for(std::vector<team>::iterator tm = teams.begin(); tm != teams.end(); ++tm) {
            if(std::find(seen_leaders.begin(),seen_leaders.end(),tm-teams.begin() + 1) == seen_leaders.end()) {
                  tm->clear_villages();
            }
      }

      bool found_enemies = false;
      bool found_player = false;

      for(size_t n = 0; n != seen_leaders.size(); ++n) {
            const size_t side = seen_leaders[n]-1;

            wassert(side < teams.size());

            for(size_t m = n+1; m != seen_leaders.size(); ++m) {
                  if(side < teams.size() && teams[side].is_enemy(seen_leaders[m])) {
                        found_enemies = true;
                  }
            }

            if (teams[side].is_human() || teams[side].is_persistent()) {
                  found_player = true;
            }
      }

      if(found_enemies == false) {
            if (found_player) {
                  game_events::fire("enemies defeated");
                  if (victory_conditions::victory_when_enemies_defeated() == false) {
                        // this level has asked not to be ended by this condition
                        return;
                  }
            }


            if(non_interactive()) {
                  std::cout << "winner: ";
                  for(std::vector<int>::const_iterator i = seen_leaders.begin(); i != seen_leaders.end(); ++i) {
                        std::cout << *i << " ";
                  }

                  std::cout << "\n";
            }

            LOG_NG << "throwing end level exception...\n";
            throw end_level_exception(found_player ? VICTORY : DEFEAT);
      }
}

const time_of_day& timeofday_at(const gamestatus& status,const unit_map& units,const gamemap::location& loc)
{
      bool lighten = false;

      if(loc.valid()) {
            gamemap::location locs[7];
            locs[0] = loc;
            get_adjacent_tiles(loc,locs+1);

            for(int i = 0; i != 7; ++i) {
                  const unit_map::const_iterator itor = units.find(locs[i]);
                  if(itor != units.end() &&
                     itor->second.type().illuminates() && itor->second.incapacitated() == false) {
                        lighten = true;
                  }
            }
      }

      return status.get_time_of_day(lighten,loc);
}

int combat_modifier(const gamestatus& status,
                    const std::map<gamemap::location,unit>& units,
                              const gamemap::location& loc,
                              unit_type::ALIGNMENT alignment)
{
      const time_of_day& tod = timeofday_at(status,units,loc);

      int bonus = tod.lawful_bonus;

      if(alignment == unit_type::NEUTRAL)
            bonus = 0;
      else if(alignment == unit_type::CHAOTIC)
            bonus = -bonus;

      return bonus;
}

namespace {

bool clear_shroud_loc(const gamemap& map, team& tm,
                      const gamemap::location& loc,
                      std::vector<gamemap::location>* cleared)
{
      bool result = false;
      gamemap::location adj[7];
      get_adjacent_tiles(loc,adj);
      adj[6] = loc;
      for(int i = 0; i != 7; ++i) {

            //we clear one past the edge of the board, so that the half-hexes
            //at the edge can also be cleared of fog/shroud
            if(map.on_board(adj[i]) || map.on_board(loc)) {
                  const bool res = tm.clear_shroud(adj[i].x,adj[i].y) ||
                                                tm.clear_fog(adj[i].x,adj[i].y);

                  if(res && cleared != NULL) {
                        cleared->push_back(adj[i]);
                  }

                  result |= res;
            }
      }

      return result;
}

//returns true iff some shroud is cleared
//seen_units will return new units that have been seen by this unit
//if known_units is NULL, seen_units can be NULL and will not be changed
bool clear_shroud_unit(const gamemap& map, 
                             const gamestatus& status,
                                            const game_data& gamedata,
                       const unit_map& units, const gamemap::location& loc,
                       std::vector<team>& teams, int team,
                                 const std::set<gamemap::location>* known_units,
                                 std::set<gamemap::location>* seen_units)
{
      std::vector<gamemap::location> cleared_locations;

      const unit_map::const_iterator u = units.find(loc);
      if(u == units.end()) {
            return false;
      }

      unit_map temp_units;
      temp_units.insert(*u);

      paths p(map,status,gamedata,temp_units,loc,teams,true,false);
      for(paths::routes_map::const_iterator i = p.routes.begin();
          i != p.routes.end(); ++i) {
            clear_shroud_loc(map,teams[team],i->first,&cleared_locations);
      }

      //clear the location the unit is at
      clear_shroud_loc(map,teams[team],loc,&cleared_locations);

      for(std::vector<gamemap::location>::const_iterator it =
          cleared_locations.begin(); it != cleared_locations.end(); ++it) {

            const unit_map::const_iterator sighted = units.find(*it);
            if(sighted != units.end() &&
              (sighted->second.invisible(map.underlying_terrain(map[it->x][it->y]),status.get_time_of_day().lawful_bonus,*it,units,teams) == false
              || teams[team].is_enemy(sighted->second.side()) == false)) {
                  if(seen_units == NULL || known_units == NULL) {
                        static const std::string sighted("sighted");
                        game_events::fire(sighted,*it,loc);
                  } else if(known_units->count(*it) == 0) {
                        seen_units->insert(*it);
                  }
            }
      }

      return cleared_locations.empty() == false;
}

}

void recalculate_fog(const gamemap& map, const gamestatus& status,
            const game_data& gamedata, const unit_map& units, 
            std::vector<team>& teams, int team) {

      teams[team].refog();

      for(unit_map::const_iterator i = units.begin(); i != units.end(); ++i) {
            if(i->second.side() == team+1) {

                  //we're not really going to mutate the unit, just temporarily
                  //set its moves to maximum, but then switch them back
                  unit& mutable_unit = const_cast<unit&>(i->second);
                  const unit_movement_resetter move_resetter(mutable_unit);

                  clear_shroud_unit(map,status,gamedata,units,i->first,teams,team,NULL,NULL);
            }
      }
}

bool clear_shroud(display& disp, const gamestatus& status, 
                        const gamemap& map, const game_data& gamedata,
                  const unit_map& units, std::vector<team>& teams, int team)
{
      if(teams[team].uses_shroud() == false && teams[team].uses_fog() == false)
            return false;

      bool result = false;

      unit_map::const_iterator i;
      for(i = units.begin(); i != units.end(); ++i) {
            if(i->second.side() == team+1) {

                  //we're not really going to mutate the unit, just temporarily
                  //set its moves to maximum, but then switch them back
                  unit& mutable_unit = const_cast<unit&>(i->second);
                  const unit_movement_resetter move_resetter(mutable_unit);

                  result |= clear_shroud_unit(map,status,gamedata,units,i->first,teams,team,NULL,NULL);
            }
      }

      recalculate_fog(map,status,gamedata,units,teams,team);

      disp.labels().recalculate_shroud();

      return result;
}

size_t move_unit(display* disp, const game_data& gamedata, 
                 const gamestatus& status, const gamemap& map,
                 unit_map& units, std::vector<team>& teams,
                 std::vector<gamemap::location> route,
                 replay* move_recorder, undo_list* undo_stack,
                 gamemap::location *next_unit, bool continue_move, bool should_clear_shroud)
{
      wassert(route.empty() == false);

      //stop the user from issuing any commands while the unit is moving
      const command_disabler disable_commands;

      unit_map::iterator ui = units.find(route.front());

      wassert(ui != units.end());

      ui->second.set_goto(gamemap::location());

      unit u = ui->second;

      const size_t team_num = u.side()-1;

      const bool skirmisher = u.type().is_skirmisher();

      team& team = teams[team_num];
      const bool check_shroud = should_clear_shroud && team.auto_shroud_updates() &&
            (team.uses_shroud() || team.uses_fog());
      
      //if we use shroud/fog of war, count out the units we can currently see
      std::set<gamemap::location> known_units;
      if(check_shroud) {
            for(unit_map::const_iterator u = units.begin(); u != units.end(); ++u) {
                  if(team.fogged(u->first.x,u->first.y) == false) {
                        known_units.insert(u->first);
                  }
            }
      }

      //see how far along the given path we can move
      const int starting_moves = u.movement_left();
      int moves_left = starting_moves;
      std::set<gamemap::location> seen_units;
      bool discovered_unit = false;
      bool should_clear_stack = false;
      std::vector<gamemap::location>::const_iterator step;
      for(step = route.begin()+1; step != route.end(); ++step) {
            const gamemap::TERRAIN terrain = map[step->x][step->y];

            const unit_map::const_iterator enemy_unit = units.find(*step);
                  
            const int mv = u.movement_cost(map,terrain);
            if(discovered_unit || continue_move == false && seen_units.empty() == false ||
               mv > moves_left || enemy_unit != units.end() && team.is_enemy(enemy_unit->second.side())) {
                  break;
            } else {
                  moves_left -= mv;
            }

            if(!skirmisher && enemy_zoc(map,status,units,teams,*step,team,u.side())) {
                  moves_left = 0;
            }

            //if we use fog or shroud, see if we have sighted an enemy unit, in
            //which case we should stop immediately.
            if(check_shroud) {
                  if(units.count(*step) == 0 && !map.is_village(*step)) {
                        LOG_NG << "checking for units from " << (step->x+1) << "," << (step->y+1) << "\n";

                        //temporarily reset the unit's moves to full
                        const unit_movement_resetter move_resetter(ui->second);

                        //we have to swap out any unit that is already in the hex, so we can put our
                        //unit there, then we'll swap back at the end.
                        const temporary_unit_placer unit_placer(units,*step,ui->second);

                        should_clear_stack |= clear_shroud_unit(map,status,gamedata,units,*step,teams,
                                                                ui->second.side()-1,&known_units,&seen_units);

                        if(should_clear_stack) {
                              disp->invalidate_all();
                        }
                  }
            }

            //check if we have discovered an invisible enemy unit
            gamemap::location adjacent[6];
            get_adjacent_tiles(*step,adjacent);

            for(int i = 0; i != 6; ++i) {
                  //check if we are checking ourselves
                  if(adjacent[i] == ui->first)
                        continue;

                  const std::map<gamemap::location,unit>::const_iterator it = units.find(adjacent[i]);
                  if(it != units.end() && teams[u.side()-1].is_enemy(it->second.side()) &&
                     it->second.invisible(map.underlying_terrain(map[it->first.x][it->first.y]),
                     status.get_time_of_day().lawful_bonus,it->first,units,teams)) {
                        discovered_unit = true;
                        should_clear_stack = true;
                        moves_left = 0;
                        break;
                  }
            }
      }

      //make sure we don't tread on another unit
      std::vector<gamemap::location>::const_iterator begin = route.begin();

      std::vector<gamemap::location> steps(begin,step);
      while (!steps.empty()) {
            gamemap::location const &loc = steps.back();
            if (units.count(loc) == 0)
                  break;
            moves_left += u.movement_cost(map, map[loc.x][loc.y]);
            steps.pop_back();
      }

      wassert(steps.size() <= route.size());

      if (next_unit != NULL)
            *next_unit = steps.back();

      //if we can't get all the way there and have to set a go-to.
      if(steps.size() != route.size() && discovered_unit == false) {
            if(seen_units.empty() == false) {
                  u.set_interrupted_move(route.back());
            } else {
                  u.set_goto(route.back());
            }
      } else {
            u.set_interrupted_move(gamemap::location());
      }
      
      if(steps.size() < 2) {
            return 0;
      }

      units.erase(ui);
      if(disp != NULL) {
            unit_display::move_unit(*disp,map,steps,u,status.get_time_of_day(),units,teams);
      }

      u.set_movement(moves_left);

      ui = units.insert(std::pair<gamemap::location,unit>(steps.back(),u)).first;
      if(disp != NULL) {
            disp->invalidate_unit();
            disp->invalidate(steps.back());
      }

      int orig_village_owner = -1;
      if(map.is_village(steps.back())) {
            orig_village_owner = village_owner(steps.back(),teams);

            if (size_t(orig_village_owner) != team_num) {
                  ui->second.set_movement(0);
                  get_village(steps.back(),teams,team_num,units);
            }
      }

      if(move_recorder != NULL) {
            move_recorder->add_movement(steps.front(),steps.back());
      }

      const bool event_mutated = game_events::fire("moveto",steps.back());

      if(undo_stack != NULL) {
            if(event_mutated || should_clear_stack) {
                  apply_shroud_changes(*undo_stack,disp,status,map,gamedata,units,teams,team_num);
                  undo_stack->clear();
            } else {
                  undo_stack->push_back(undo_action(u,steps,starting_moves,orig_village_owner));
            }
      }

      if(disp != NULL) {
            disp->set_route(NULL);

            //show messages on the screen here
            if(discovered_unit) {
                  //we've been ambushed, so display an appropriate message
                  font::add_floating_label(_("Ambushed!"),font::SIZE_XLARGE,font::BAD_COLOUR,
                                           disp->map_area().w/2,disp->map_area().h/3,
                                                       0.0,0.0,100,disp->map_area(),font::CENTER_ALIGN);
            }

            if(continue_move == false && seen_units.empty() == false) {
                  //the message depends on how many units have been sighted, and whether
                  //they are allies or enemies, so calculate that out here
                  int nfriends = 0, nenemies = 0;
                  for(std::set<gamemap::location>::const_iterator i = seen_units.begin(); i != seen_units.end(); ++i) {
                        LOG_NG << "processing unit at " << (i->x+1) << "," << (i->y+1) << "\n";
                        const unit_map::const_iterator u = units.find(*i);
                        wassert(u != units.end());
                        if(team.is_enemy(u->second.side())) {
                              ++nenemies;
                        } else {
                              ++nfriends;
                        }

                        LOG_NG << "processed...\n";
                  }

                  char* msg_id;

                  //the message we display is different depending on whether units sighted
                  //were enemies or friends, and whether there is one or more
                  if(seen_units.size() == 1) {
                        if(nfriends == 1) {
                              msg_id = N_("Friendly unit sighted");
                        } else {
                              msg_id = N_("Enemy unit sighted!");
                        }
                  }
                  else if(nfriends == 0 || nenemies == 0) {
                        if(nfriends > 0) {
                              msg_id = N_("$friends Friendly units sighted");
                        } else {
                              msg_id = N_("$enemies Enemy units sighted!");
                        }
                  }
                  else {
                        msg_id = N_("Units sighted! ($friends friendly, $enemies enemy)");
                  }

                  utils::string_map symbols;
                  symbols["friends"] = lexical_cast<std::string>(nfriends);
                  symbols["enemies"] = lexical_cast<std::string>(nenemies);

                  std::stringstream msg;
                  msg << gettext(msg_id);
                  
                  if(steps.size() < route.size()) {
                        //see if the "Continue Move" action has an associated hotkey
                        const hotkey::hotkey_item& hk = hotkey::get_hotkey(hotkey::HOTKEY_CONTINUE_MOVE);
                        if(!hk.null()) {
                              symbols["hotkey"] = hk.get_name();
                              msg << '\n' << _("(press $hotkey to continue)");
                        }
                  }
                  const std::string message = utils::interpolate_variables_into_string(msg.str(), &symbols);

                  font::add_floating_label(message,font::SIZE_XLARGE,font::BAD_COLOUR,
                                           disp->map_area().w/2,disp->map_area().h/3,
                                                       0.0,0.0,100,disp->map_area(),font::CENTER_ALIGN);
            }

            disp->draw();
            disp->recalculate_minimap();
      }

      for(std::set<gamemap::location>::const_iterator seen = seen_units.begin(); seen != seen_units.end(); ++seen) {
            game_events::fire("sighted",*seen,steps.back());
      }

      wassert(steps.size() <= route.size());

      return steps.size();
}

bool unit_can_move(const gamemap::location& loc, const unit_map& units,
                   const gamemap& map, const std::vector<team>& teams)
{
      const unit_map::const_iterator u_it = units.find(loc);
      wassert(u_it != units.end());
      
      const unit& u = u_it->second;
      const team& current_team = teams[u.side()-1];

      if(!u.can_attack())
            return false;

      //units with goto commands that have already done their gotos this turn
      //(i.e. don't have full movement left) should be red
      if(u.movement_left() < u.total_movement() && u.get_goto().valid()) {
            return false;
      }

      gamemap::location locs[6];
      get_adjacent_tiles(loc,locs);
      for(int n = 0; n != 6; ++n) {
            if(map.on_board(locs[n])) {
                  const unit_map::const_iterator i = units.find(locs[n]);
                  if(i != units.end()) {
                        if(i->second.incapacitated() == false && current_team.is_enemy(i->second.side())) {
                              return true;
                        }
                  }
                  
                  if(u.movement_cost(map,map[locs[n].x][locs[n].y]) <= u.movement_left()) {
                        return true;
                  }
            }
      }

      return false;
}

void apply_shroud_changes(undo_list& undos, display* disp, const gamestatus& status, const gamemap& map,
      const game_data& gamedata, const unit_map& units, std::vector<team>& teams, int team)
{
      // No need to do this if the team isn't using fog or shroud.
      if(!teams[team].uses_shroud() && !teams[team].uses_fog())
            return;
      /*
            This function works thusly:
            1. run through the list of undo_actions
            2. for each one, play back the unit's move
            3. for each location along the route, clear any "shrouded" squares that the unit can see
            4. clear the undo_list
            5. call clear_shroud to update the fog of war for each unit.
            6. fix up associated display stuff (done in a similar way to turn_info::undo())
      */
      for(undo_list::const_iterator un = undos.begin(); un != undos.end(); ++un) {
            if(un->is_recall()) continue;
            //we're not really going to mutate the unit, just temporarily
            //set its moves to maximum, but then switch them back
            const unit_movement_resetter move_resetter(const_cast<unit&>(un->affected_unit));
            
            std::vector<gamemap::location>::const_iterator step;
            for(step = un->route.begin(); step != un->route.end(); ++step) {
                  //we have to swap out any unit that is already in the hex, so we can put our
                  //unit there, then we'll swap back at the end.
                  const temporary_unit_placer unit_placer(const_cast<unit_map&>(units),*step,un->affected_unit);
                  clear_shroud_unit(map,status,gamedata,units,*step,teams,team,NULL,NULL);
            }
      }
      if(disp != NULL) {
            disp->invalidate_unit();
            disp->invalidate_game_status();
            clear_shroud(*disp,status,map,gamedata,units,teams,team);
            disp->recalculate_minimap();
            disp->set_paths(NULL);
            disp->set_route(NULL);
      } else {
            recalculate_fog(map,status,gamedata,units,teams,team);
      }
}

Generated by  Doxygen 1.6.0   Back to index