actions.cpp

Go to the documentation of this file.
00001 /* $Id: actions.cpp 26628 2008-05-14 20:48:25Z suokko $ */
00002 /*
00003    Copyright (C) 2003 - 2008 by David White <dave@whitevine.net>
00004    Part of the Battle for Wesnoth Project http://www.wesnoth.org/
00005 
00006    This program is free software; you can redistribute it and/or modify
00007    it under the terms of the GNU General Public License version 2
00008    or at your option any later version.
00009    This program is distributed in the hope that it will be useful,
00010    but WITHOUT ANY WARRANTY.
00011 
00012    See the COPYING file for more details.
00013 */
00014 
00015 //! @file actions.cpp
00016 //! Recruiting, Fighting.
00017 
00018 #include "actions.hpp"
00019 #include "attack_prediction.hpp"
00020 #include "game_config.hpp"
00021 #include "game_errors.hpp"
00022 #include "game_events.hpp"
00023 #include "game_preferences.hpp"
00024 #include "gamestatus.hpp"
00025 #include "gettext.hpp"
00026 #include "halo.hpp"
00027 #include "hotkeys.hpp"
00028 #include "log.hpp"
00029 #include "menu_events.hpp"
00030 #include "mouse_events.hpp"
00031 #include "pathfind.hpp"
00032 #include "random.hpp"
00033 #include "replay.hpp"
00034 #include "sound.hpp"
00035 #include "statistics.hpp"
00036 #include "unit_abilities.hpp"
00037 #include "unit_display.hpp"
00038 #include "wml_exception.hpp"
00039 #include "wml_separators.hpp"
00040 #include "serialization/binary_wml.hpp"
00041 #include "serialization/parser.hpp"
00042 
00043 #include <cassert>
00044 
00045 #define DBG_NG LOG_STREAM(debug, engine)
00046 #define LOG_NG LOG_STREAM(info, engine)
00047 #define ERR_NG LOG_STREAM(err, engine)
00048 
00049 struct castle_cost_calculator : cost_calculator
00050 {
00051     castle_cost_calculator(const gamemap& map) : map_(map)
00052     {}
00053 
00054     virtual double cost(const gamemap::location&, const gamemap::location& loc, const double) const
00055     {
00056         if(!map_.is_castle(loc))
00057             return 10000;
00058 
00059         return 1;
00060     }
00061 
00062 private:
00063     const gamemap& map_;
00064 };
00065 
00066 // Conditions placed on victory must be accessible from the global function
00067 // check_victory, but shouldn't be passed to that function as parameters,
00068 // since it is called from a variety of places.
00069 namespace victory_conditions
00070 {
00071     static bool when_enemies_defeated = true;
00072     static int carryover_percentage = 80;
00073     static bool carryover_add = false;
00074 
00075     void set_victory_when_enemies_defeated(const bool on)
00076     {
00077         when_enemies_defeated = on;
00078     }
00079 
00080     static bool victory_when_enemies_defeated()
00081     {
00082         return when_enemies_defeated;
00083     }
00084 
00085     void set_carryover_percentage(const int percentage)
00086     {
00087         carryover_percentage = percentage;
00088     }
00089 
00090     static int get_carryover_percentage()
00091     {
00092         return carryover_percentage;
00093     }
00094 
00095     void set_carryover_add(const bool add)
00096     {
00097         carryover_add = add;
00098     }
00099 
00100     static bool get_carryover_add()
00101     {
00102         return carryover_add;
00103     }
00104 }
00105 
00106 bool can_recruit_on(const gamemap& map, const gamemap::location& leader, const gamemap::location loc)
00107 {
00108     if(!map.on_board(loc))
00109         return false;
00110 
00111     if(!map.is_castle(loc))
00112         return false;
00113 
00114     castle_cost_calculator calc(map);
00115     const paths::route& rt = a_star_search(leader, loc, 100.0, &calc, map.w(), map.h());
00116 
00117     if(rt.steps.empty())
00118         return false;
00119 
00120     return true;
00121 }
00122 
00123 std::string recruit_unit(const gamemap& map, const int side, unit_map& units,
00124         unit new_unit, gamemap::location& recruit_location,const bool is_recall,
00125         const bool show, const bool need_castle, const bool full_movement,
00126         const bool wml_triggered)
00127 {
00128     const events::command_disabler disable_commands;
00129 
00130     LOG_NG << "recruiting unit for side " << side << "\n";
00131 
00132     // Find the unit that can recruit
00133     unit_map::const_iterator u = units.begin();
00134 
00135     for(; u != units.end(); ++u) {
00136         if(u->second.can_recruit() &&
00137                 static_cast<int>(u->second.side()) == side) {
00138 
00139             break;
00140         }
00141     }
00142 
00143     if(u == units.end() && (need_castle || !map.on_board(recruit_location))) {
00144         return _("You don't have a leader to recruit with.");
00145     }
00146 
00147     assert(u != units.end() || !need_castle);
00148 
00149     if(need_castle && map.is_keep(u->first) == false) {
00150         LOG_NG << "Leader not on start: leader is on " << u->first << '\n';
00151         return _("You must have your leader on a keep to recruit or recall units.");
00152     }
00153 
00154     if(need_castle) {
00155         if (units.find(recruit_location) != units.end() ||
00156             !can_recruit_on(map, u->first, recruit_location)) {
00157 
00158             recruit_location = gamemap::location();
00159         }
00160     }
00161 
00162     if(!map.on_board(recruit_location)) {
00163         recruit_location = find_vacant_tile(map,units,u->first,
00164                                             need_castle ? VACANT_CASTLE : VACANT_ANY);
00165     } else if(units.count(recruit_location) == 1) {
00166         recruit_location = find_vacant_tile(map,units,recruit_location,VACANT_ANY);
00167     }
00168 
00169     if(!map.on_board(recruit_location)) {
00170         return _("There are no vacant castle tiles in which to recruit a unit.");
00171     }
00172 
00173     if(full_movement) {
00174         new_unit.set_movement(new_unit.total_movement());
00175     } else {
00176         new_unit.set_movement(0);
00177         new_unit.set_attacks(0);
00178     }
00179     new_unit.heal_all();
00180 
00181     units.add(new std::pair<gamemap::location,unit>(recruit_location,new_unit));
00182 
00183     if (is_recall)
00184     {
00185         LOG_NG << "firing prerecall event\n";
00186         game_events::fire("prerecall",recruit_location);
00187     }
00188     else
00189     {
00190         LOG_NG << "firing prerecruit event\n";
00191         game_events::fire("prerecruit",recruit_location);
00192     }
00193     if(show)unit_display::unit_recruited(recruit_location);
00194     if (is_recall)
00195     {
00196         LOG_NG << "firing recall event\n";
00197         game_events::fire("recall",recruit_location);
00198     }
00199     else
00200     {
00201         LOG_NG << "firing recruit event\n";
00202         game_events::fire("recruit",recruit_location);
00203     }
00204 
00205     const std::string checksum = get_checksum(new_unit);
00206 
00207     const config* ran_results = get_random_results();
00208     if(ran_results != NULL) {
00209         // When recalling from WML there should be no random results, if we use
00210         // random we might get the replay out of sync.
00211         assert(!wml_triggered);
00212         const std::string rc = (*ran_results)["checksum"];
00213         if(rc != checksum) {
00214             ERR_NG << "SYNC: In recruit " << new_unit.type_id() <<
00215                 ": has checksum " << checksum <<
00216                 " while datasource has checksum " <<
00217                 rc << "\n";
00218 
00219             config cfg_unit1;
00220             new_unit.write(cfg_unit1);
00221             DBG_NG << cfg_unit1;
00222             if (!game_config::ignore_replay_errors) {
00223                 throw replay::error("OOS while recruiting.");
00224             }
00225         }
00226 
00227     } else if(wml_triggered == false) {
00228         config cfg;
00229         cfg["checksum"] = checksum;
00230         set_random_results(cfg);
00231     }
00232 
00233     return std::string();
00234 }
00235 
00236 gamemap::location under_leadership(const unit_map& units,
00237         const gamemap::location& loc, int* bonus)
00238 {
00239 
00240     const unit_map::const_iterator un = units.find(loc);
00241     if(un == units.end()) {
00242         return gamemap::location::null_location;
00243     }
00244     unit_ability_list abil = un->second.get_abilities("leadership",loc);
00245     int best_bonus = abil.highest("value").first;
00246     if(bonus) {
00247         *bonus = best_bonus;
00248     }
00249     return abil.highest("value").second;
00250 }
00251 
00252 battle_context::battle_context(const gamemap& map, const std::vector<team>& teams, const unit_map& units,
00253         const gamestatus& status,
00254         const gamemap::location& attacker_loc, const gamemap::location& defender_loc,
00255         int attacker_weapon, int defender_weapon, double aggression, const combatant *prev_def, const unit* attacker_ptr)
00256 : attacker_stats_(NULL), defender_stats_(NULL), attacker_combatant_(NULL), defender_combatant_(NULL)
00257 {
00258     const unit& attacker = attacker_ptr ? *attacker_ptr : units.find(attacker_loc)->second;
00259     const unit& defender = units.find(defender_loc)->second;
00260     const double harm_weight = 1.0 - aggression;
00261 
00262     // A Python AI can send an invalid weapon and crash Wesnoth.
00263     // Haven't found a way for the Python API to prevent this problem.
00264     // So instead of segfaulting it sends an assertion failure.
00265     VALIDATE(attacker_weapon < static_cast<int>(attacker.attacks().size()),
00266             _("An invalid weapon is selected, possibly by the Python AI."));
00267 
00268     if (attacker_weapon == -1 && attacker.attacks().size() == 1 && attacker.attacks()[0].attack_weight() > 0 )
00269         attacker_weapon = 0;
00270 
00271     if (attacker_weapon == -1) {
00272         VALIDATE(defender_weapon == -1,
00273                 _("An invalid weapon is send, possibly due to the Python AI."));
00274 
00275         attacker_weapon = choose_attacker_weapon(attacker, defender, map, teams, units,
00276                 status, attacker_loc, defender_loc,
00277                 harm_weight, &defender_weapon, prev_def);
00278     } else if (defender_weapon == -1) {
00279         defender_weapon = choose_defender_weapon(attacker, defender, attacker_weapon, map, teams,
00280                 units, status, attacker_loc, defender_loc, prev_def);
00281     }
00282 
00283     // If those didn't have to generate statistics, do so now.
00284     if (!attacker_stats_) {
00285         const attack_type *adef = NULL;
00286         const attack_type *ddef = NULL;
00287         if (attacker_weapon >= 0) {
00288             VALIDATE(attacker_weapon < static_cast<int>(attacker.attacks().size()),
00289                     _("An invalid weapon is selected, possibly by the Python AI."));
00290             adef = &attacker.attacks()[attacker_weapon];
00291         }
00292         if (defender_weapon >= 0) {
00293             VALIDATE(defender_weapon < static_cast<int>(defender.attacks().size()),
00294                     _("An invalid weapon is selected, possibly by the Python AI."));
00295             ddef = &defender.attacks()[defender_weapon];
00296         }
00297         assert(!defender_stats_ && !attacker_combatant_ && !defender_combatant_);
00298         attacker_stats_ = new unit_stats(attacker, attacker_loc, attacker_weapon,
00299                 true, defender, defender_loc, ddef,
00300                 units, teams, status, map);
00301         defender_stats_ = new unit_stats(defender, defender_loc, defender_weapon, false,
00302                 attacker, attacker_loc, adef,
00303                 units, teams, status, map);
00304     }
00305 }
00306 
00307     battle_context::battle_context(const battle_context &other)
00308 : attacker_stats_(NULL), defender_stats_(NULL), attacker_combatant_(NULL), defender_combatant_(NULL)
00309 {
00310     *this = other;
00311 }
00312 
00313 battle_context::battle_context(const unit_stats &att, const unit_stats &def) :
00314     attacker_stats_(new unit_stats(att)),
00315     defender_stats_(new unit_stats(def)),
00316     attacker_combatant_(0),
00317     defender_combatant_(0)
00318 {
00319 }
00320 
00321 battle_context::~battle_context()
00322 {
00323     delete attacker_stats_;
00324     delete defender_stats_;
00325     delete attacker_combatant_;
00326     delete defender_combatant_;
00327 }
00328 battle_context& battle_context::operator=(const battle_context &other)
00329 {
00330     if (&other != this) {
00331         delete attacker_stats_;
00332         delete defender_stats_;
00333         delete attacker_combatant_;
00334         delete defender_combatant_;
00335         attacker_stats_ = new unit_stats(*other.attacker_stats_);
00336         defender_stats_ = new unit_stats(*other.defender_stats_);
00337         attacker_combatant_ = other.attacker_combatant_ ? new combatant(*other.attacker_combatant_, *attacker_stats_) : NULL;
00338         defender_combatant_ = other.defender_combatant_ ? new combatant(*other.defender_combatant_, *defender_stats_) : NULL;
00339     }
00340     return *this;
00341 }
00342 
00343 //! @todo FIXME: Hand previous defender unit in here.
00344 int battle_context::choose_defender_weapon(const unit &attacker, const unit &defender, unsigned attacker_weapon,
00345         const gamemap& map, const std::vector<team>& teams, const unit_map& units,
00346         const gamestatus& status,
00347         const gamemap::location& attacker_loc, const gamemap::location& defender_loc,
00348         const combatant *prev_def)
00349 {
00350     const attack_type &att = attacker.attacks()[attacker_weapon];
00351     std::vector<unsigned int> choices;
00352 
00353     // What options does defender have?
00354     unsigned int i;
00355     for (i = 0; i < defender.attacks().size(); i++) {
00356         const attack_type &def = defender.attacks()[i];
00357         if (def.range() == att.range() && def.defense_weight() > 0) {
00358             choices.push_back(i);
00359         }
00360     }
00361     if (choices.size() == 0)
00362         return -1;
00363     if (choices.size() == 1)
00364         return choices[0];
00365 
00366     // Multiple options:
00367     // First pass : get the best weight and the minimum simple rating for this weight.
00368     // simple rating = number of blows * damage per blows (resistance taken in account) * cth * weight
00369     // Elligible attacks for defense should have a simple rating greater or equal to this weight.
00370 
00371     double max_weight = 0.0;
00372     int min_rating = 0;
00373 
00374     for (i = 0; i < choices.size(); i++) {
00375         const attack_type &def = defender.attacks()[choices[i]];
00376         if (def.defense_weight() > max_weight) {
00377             max_weight = def.defense_weight();
00378             unit_stats *def_stats = new unit_stats(defender, defender_loc, choices[i], false,
00379                     attacker, attacker_loc, &att,
00380                     units, teams, status, map);
00381             min_rating = static_cast<int>(def_stats->num_blows * def_stats->damage *
00382                     def_stats->chance_to_hit * def.defense_weight());
00383 
00384             delete def_stats;
00385         }
00386         else if (def.defense_weight() == max_weight) {
00387             unit_stats *def_stats = new unit_stats(defender, defender_loc, choices[i], false,
00388                     attacker, attacker_loc, &att,
00389                     units, teams, status, map);
00390             int simple_rating = static_cast<int>(def_stats->num_blows * def_stats->damage *
00391                     def_stats->chance_to_hit * def.defense_weight());
00392 
00393             if (simple_rating < min_rating )
00394                 min_rating = simple_rating;
00395             delete def_stats;
00396         }
00397     }
00398 
00399     // Multiple options: simulate them, save best.
00400     for (i = 0; i < choices.size(); i++) {
00401         const attack_type &def = defender.attacks()[choices[i]];
00402         unit_stats *att_stats = new unit_stats(attacker, attacker_loc, attacker_weapon,
00403                 true, defender, defender_loc, &def,
00404                 units, teams, status, map);
00405         unit_stats *def_stats = new unit_stats(defender, defender_loc, choices[i], false,
00406                 attacker, attacker_loc, &att,
00407                 units, teams, status, map);
00408 
00409         combatant *att_comb = new combatant(*att_stats);
00410         combatant *def_comb = new combatant(*def_stats, prev_def);
00411         att_comb->fight(*def_comb);
00412 
00413         int simple_rating = static_cast<int>(def_stats->num_blows *
00414                 def_stats->damage * def_stats->chance_to_hit * def.defense_weight());
00415 
00416         if (simple_rating >= min_rating &&
00417                 ( !attacker_combatant_ || better_combat(*def_comb, *att_comb, *defender_combatant_, *attacker_combatant_, 1.0) )
00418            ) {
00419             delete attacker_combatant_;
00420             delete defender_combatant_;
00421             delete attacker_stats_;
00422             delete defender_stats_;
00423             attacker_combatant_ = att_comb;
00424             defender_combatant_ = def_comb;
00425             attacker_stats_ = att_stats;
00426             defender_stats_ = def_stats;
00427         } else {
00428             delete att_comb;
00429             delete def_comb;
00430             delete att_stats;
00431             delete def_stats;
00432         }
00433     }
00434 
00435     return defender_stats_->attack_num;
00436 }
00437 
00438 int battle_context::choose_attacker_weapon(const unit &attacker, const unit &defender,
00439         const gamemap& map, const std::vector<team>& teams, const unit_map& units,
00440         const gamestatus& status,
00441         const gamemap::location& attacker_loc, const gamemap::location& defender_loc,
00442         double harm_weight, int *defender_weapon, const combatant *prev_def)
00443 {
00444     std::vector<unsigned int> choices;
00445 
00446     // What options does attacker have?
00447     unsigned int i;
00448     for (i = 0; i < attacker.attacks().size(); i++) {
00449         const attack_type &att = attacker.attacks()[i];
00450         if (att.attack_weight() > 0) {
00451             choices.push_back(i);
00452         }
00453     }
00454     if (choices.size() == 0)
00455         return -1;
00456     if (choices.size() == 1) {
00457         *defender_weapon = choose_defender_weapon(attacker, defender, choices[0], map, teams, units,
00458                 status, attacker_loc, defender_loc, prev_def);
00459         return choices[0];
00460     }
00461 
00462     // Multiple options: simulate them, save best.
00463     unit_stats *best_att_stats = NULL, *best_def_stats = NULL;
00464     combatant *best_att_comb = NULL, *best_def_comb = NULL;
00465 
00466     for (i = 0; i < choices.size(); i++) {
00467         const attack_type &att = attacker.attacks()[choices[i]];
00468         int def_weapon = choose_defender_weapon(attacker, defender, choices[i], map, teams, units,
00469                 status, attacker_loc, defender_loc, prev_def);
00470         // If that didn't simulate, do so now.
00471         if (!attacker_combatant_) {
00472             const attack_type *def = NULL;
00473             if (def_weapon >= 0) {
00474                 def = &defender.attacks()[def_weapon];
00475             }
00476             attacker_stats_ = new unit_stats(attacker, attacker_loc, choices[i],
00477                     true, defender, defender_loc, def,
00478                     units, teams, status, map);
00479             defender_stats_ = new unit_stats(defender, defender_loc, def_weapon, false,
00480                     attacker, attacker_loc, &att,
00481                     units, teams, status, map);
00482             attacker_combatant_ = new combatant(*attacker_stats_);
00483             defender_combatant_ = new combatant(*defender_stats_, prev_def);
00484             attacker_combatant_->fight(*defender_combatant_);
00485         }
00486         if (!best_att_comb || better_combat(*attacker_combatant_, *defender_combatant_,
00487                     *best_att_comb, *best_def_comb, harm_weight)) {
00488             delete best_att_comb;
00489             delete best_def_comb;
00490             delete best_att_stats;
00491             delete best_def_stats;
00492             best_att_comb = attacker_combatant_;
00493             best_def_comb = defender_combatant_;
00494             best_att_stats = attacker_stats_;
00495             best_def_stats = defender_stats_;
00496         } else {
00497             delete attacker_combatant_;
00498             delete defender_combatant_;
00499             delete attacker_stats_;
00500             delete defender_stats_;
00501         }
00502         attacker_combatant_ = NULL;
00503         defender_combatant_ = NULL;
00504         attacker_stats_ = NULL;
00505         defender_stats_ = NULL;
00506     }
00507 
00508     attacker_combatant_ = best_att_comb;
00509     defender_combatant_ = best_def_comb;
00510     attacker_stats_ = best_att_stats;
00511     defender_stats_ = best_def_stats;
00512 
00513     *defender_weapon = defender_stats_->attack_num;
00514     return attacker_stats_->attack_num;
00515 }
00516 
00517 // Does combat A give us a better result than combat B?
00518 bool battle_context::better_combat(const combatant &us_a, const combatant &them_a,
00519         const combatant &us_b, const combatant &them_b,
00520         double harm_weight)
00521 {
00522     double a, b;
00523 
00524     // Compare: P(we kill them) - P(they kill us).
00525     a = them_a.hp_dist[0] - us_a.hp_dist[0] * harm_weight;
00526     b = them_b.hp_dist[0] - us_b.hp_dist[0] * harm_weight;
00527     if (a - b < -0.01)
00528         return false;
00529     if (a - b > 0.01)
00530         return true;
00531 
00532     // Compare: damage to them - damage to us (average_hp replaces -damage)
00533     a = us_a.average_hp()*harm_weight - them_a.average_hp();
00534     b = us_b.average_hp()*harm_weight - them_b.average_hp();
00535     if (a - b < -0.01)
00536         return false;
00537     if (a - b > 0.01)
00538         return true;
00539 
00540     // All else equal: go for most damage.
00541     return them_a.average_hp() < them_b.average_hp();
00542 }
00543 
00544 // Get the simulation results.
00545 //! @todo FIXME: better to initialize combatant initially (move into unit_stats?), just do fight() when required.
00546 const combatant &battle_context::get_attacker_combatant(const combatant *prev_def)
00547 {
00548     // We calculate this lazily, since AI doesn't always need it.
00549     if (!attacker_combatant_) {
00550         assert(!defender_combatant_);
00551         attacker_combatant_ = new combatant(*attacker_stats_);
00552         defender_combatant_ = new combatant(*defender_stats_, prev_def);
00553         attacker_combatant_->fight(*defender_combatant_);
00554     }
00555     return *attacker_combatant_;
00556 }
00557 
00558 const combatant &battle_context::get_defender_combatant(const combatant *prev_def)
00559 {
00560     // We calculate this lazily, since AI doesn't always need it.
00561     if (!defender_combatant_) {
00562         assert(!attacker_combatant_);
00563         attacker_combatant_ = new combatant(*attacker_stats_);
00564         defender_combatant_ = new combatant(*defender_stats_, prev_def);
00565         attacker_combatant_->fight(*defender_combatant_);
00566     }
00567     return *defender_combatant_;
00568 }
00569 
00570 battle_context::unit_stats::unit_stats(const unit &u, const gamemap::location& u_loc,
00571         int u_attack_num, bool attacking,
00572         const unit &opp, const gamemap::location& opp_loc,
00573         const attack_type *opp_weapon,
00574         const unit_map& units,
00575         const std::vector<team>& teams,
00576         const gamestatus& status,
00577         const gamemap& map) :
00578     weapon(0),
00579     attack_num(u_attack_num),
00580     is_attacker(attacking),
00581     is_poisoned(utils::string_bool(u.get_state("poisoned"))),
00582     is_slowed(utils::string_bool(u.get_state("slowed"))),
00583     slows(false),
00584     drains(false),
00585     stones(false),
00586     plagues(false),
00587     poisons(false),
00588     backstab_pos(false),
00589     swarm(false),
00590     firststrike(false),
00591     rounds(1),
00592     hp(0),
00593     max_hp(max_hp = u.max_hitpoints()),
00594     chance_to_hit(0),
00595     damage(0),
00596     slow_damage(0),
00597     num_blows(0),
00598     swarm_min(0),
00599     swarm_max(0),
00600     plague_type()
00601 {
00602     // Get the current state of the unit.
00603     if (attack_num >= 0) {
00604         weapon = &u.attacks()[attack_num];
00605     }
00606     if(u.hitpoints() < 0) {
00607         //! @todo FIXME enable after 1.3.2 and find out why this happens -- Mordante
00608         //      LOG_STREAM(err, config) << "Unit with " << u.hitpoints() << " hitpoints found, set to 0 for damage calculations\n";
00609         hp = 0;
00610     } else if(u.hitpoints() > u.max_hitpoints()) {
00611         // If a unit has more hp as it's maximum the engine will fail
00612         // with an assertion failure due to accessing the prob_matrix
00613         // out of bounds.
00614         hp = u.max_hitpoints();
00615     } else {
00616         hp = u.hitpoints();
00617     }
00618     const gamemap::location* aloc = &u_loc;
00619     const gamemap::location* dloc = &opp_loc;
00620 
00621     if (!attacking)
00622     {
00623         aloc = &opp_loc;
00624         dloc = &u_loc;
00625     }
00626 
00627     // Get the weapon characteristics, if any.
00628     if (weapon) {
00629         weapon->set_specials_context(*aloc, *dloc, &units, &map, &status, &teams, attacking, opp_weapon);
00630         if (opp_weapon)
00631             opp_weapon->set_specials_context(*aloc, *dloc, &units, &map, &status, &teams, !attacking, weapon);
00632         slows = weapon->get_special_bool("slow");
00633         drains = weapon->get_special_bool("drains") && !utils::string_bool(opp.get_state("not_living"));
00634         stones = weapon->get_special_bool("stones");
00635         poisons = weapon->get_special_bool("poison") && opp.get_state("not_living") != "yes" && opp.get_state("poisoned") != "yes";
00636         backstab_pos = is_attacker && backstab_check(u_loc, opp_loc, units, teams);
00637         rounds = weapon->get_specials("berserk").highest("value", 1).first;
00638         firststrike = weapon->get_special_bool("firststrike");
00639 
00640         // Handle plague.
00641         unit_ability_list plague_specials = weapon->get_specials("plague");
00642         plagues = !plague_specials.empty() && opp.get_state("not_living") != "yes" &&
00643             strcmp(opp.undead_variation().c_str(), "null") && !map.is_village(opp_loc);
00644 
00645         if (!plague_specials.empty()) {
00646             if((*plague_specials.cfgs.front().first)["type"] == "")
00647                 plague_type = u.type_id();
00648             else
00649                 plague_type = (*plague_specials.cfgs.front().first)["type"];
00650         }
00651 
00652         // Compute chance to hit.
00653         chance_to_hit = opp.defense_modifier(map.get_terrain(opp_loc)) + weapon->accuracy() - (opp_weapon ? opp_weapon->parry() : 0);
00654         if(chance_to_hit > 100) {
00655             chance_to_hit = 100;
00656         }
00657 
00658         unit_ability_list cth_specials = weapon->get_specials("chance_to_hit");
00659         unit_abilities::effect cth_effects(cth_specials, chance_to_hit, backstab_pos);
00660         chance_to_hit = cth_effects.get_composite_value();
00661 
00662         // Compute base damage done with the weapon.
00663         int base_damage = weapon->damage();
00664         unit_ability_list dmg_specials = weapon->get_specials("damage");
00665         unit_abilities::effect dmg_effect(dmg_specials, base_damage, backstab_pos);
00666         base_damage = dmg_effect.get_composite_value();
00667 
00668         // Get the damage multiplier applied to the base damage of the weapon.
00669         int damage_multiplier = 100;
00670 
00671         // Time of day bonus.
00672         damage_multiplier += combat_modifier(status, units, u_loc, u.alignment(), u.is_fearless(), map);
00673 
00674         // Leadership bonus.
00675         int leader_bonus = 0;
00676         if (under_leadership(units, u_loc, &leader_bonus).valid())
00677             damage_multiplier += leader_bonus;
00678 
00679         // Resistance modifier.
00680         damage_multiplier *= opp.damage_from(*weapon, !attacking, opp_loc);
00681 
00682         // Compute both the normal and slowed damage. For the record,
00683         // drain = normal damage / 2 and slow_drain = slow_damage / 2.
00684         damage = round_damage(base_damage, damage_multiplier, 10000);
00685         slow_damage = round_damage(base_damage, damage_multiplier, 20000);
00686         if (is_slowed)
00687             damage = slow_damage;
00688 
00689         // Compute the number of blows and handle swarm.
00690         unit_ability_list swarm_specials = weapon->get_specials("attacks");
00691 
00692         if (!swarm_specials.empty()) {
00693             swarm = true;
00694             swarm_min = swarm_specials.highest("attacks_min").first;
00695             swarm_max = swarm_specials.highest("attacks_max", weapon->num_attacks()).first;
00696             num_blows = swarm_min + (swarm_max - swarm_min) * hp / max_hp;
00697         } else {
00698             swarm = false;
00699             num_blows = weapon->num_attacks();
00700             swarm_min = num_blows;
00701             swarm_max = num_blows;
00702         }
00703     }
00704 }
00705 
00706 battle_context::unit_stats::~unit_stats()
00707 {
00708 }
00709 
00710 void battle_context::unit_stats::dump() const
00711 {
00712     printf("==================================\n");
00713     printf("is_attacker:    %d\n", static_cast<int>(is_attacker));
00714     printf("is_poisoned:    %d\n", static_cast<int>(is_poisoned));
00715     printf("is_slowed:  %d\n", static_cast<int>(is_slowed));
00716     printf("slows:      %d\n", static_cast<int>(slows));
00717     printf("drains:     %d\n", static_cast<int>(drains));
00718     printf("stones:     %d\n", static_cast<int>(stones));
00719     printf("poisons:    %d\n", static_cast<int>(poisons));
00720     printf("backstab_pos:   %d\n", static_cast<int>(backstab_pos));
00721     printf("swarm:      %d\n", static_cast<int>(swarm));
00722     printf("rounds: %d\n", static_cast<int>(rounds));
00723     printf("firststrike:    %d\n", static_cast<int>(firststrike));
00724     printf("\n");
00725     printf("hp:     %d\n", hp);
00726     printf("max_hp:     %d\n", max_hp);
00727     printf("chance_to_hit:  %d\n", chance_to_hit);
00728     printf("damage:     %d\n", damage);
00729     printf("slow_damage:    %d\n", slow_damage);
00730     printf("num_blows:  %d\n", num_blows);
00731     printf("swarm_min:  %d\n", swarm_min);
00732     printf("swarm_max:  %d\n", swarm_max);
00733     printf("\n");
00734 }
00735 
00736 // Given this harm_weight, are we better than this other context?
00737 bool battle_context::better_attack(class battle_context &that, double harm_weight)
00738 {
00739     return better_combat(get_attacker_combatant(), get_defender_combatant(),
00740             that.get_attacker_combatant(), that.get_defender_combatant(), harm_weight);
00741 }
00742 
00743 static std::string unit_dump(std::pair< gamemap::location, unit > const &u)
00744 {
00745     std::stringstream s;
00746     s << u.second.type_id() << " (" << u.first.x + 1 << ',' << u.first.y + 1 << ')';
00747     return s.str();
00748 }
00749 
00750 void attack::fire_event(const std::string& n)
00751 {
00752     LOG_NG << "firing " << n << " event\n";
00753     if(n == "attack_end") {
00754         config dat;
00755         dat.add_child("first");
00756         dat.add_child("second");
00757         if(a_ != units_.end()) {
00758             (*(dat.child("first")))["weapon"]= a_stats_->weapon ? a_stats_->weapon->id() : "none";
00759 
00760         }
00761         if(d_ != units_.end()) {
00762             config *tempcfg = dat.child("second");
00763             t_string d_weap = "none";
00764             if(d_stats_->weapon != NULL) {
00765                 if(a_ != units_.end()) {
00766                     d_weap = d_stats_->weapon->id();
00767                 } else {
00768                     // The weapon choice will be invalid since the attacker was removed
00769                     d_weap = "invalid";
00770                 }
00771             }
00772             std::pair<std::string,t_string> to_insert("weapon", d_weap);
00773             tempcfg->values.insert(to_insert);
00774         }
00775 
00776         // We want to fire attack_end event in any case! Even if one of units was removed by WML
00777         DELAY_END_LEVEL(delayed_exception, game_events::fire(n,
00778                     attacker_,
00779                     defender_, dat));
00780         a_ = units_.find(attacker_);
00781         d_ = units_.find(defender_);
00782         return;
00783     }
00784     const int defender_side = d_->second.side();
00785     const int attacker_side = a_->second.side();
00786     config dat;
00787     dat.add_child("first");
00788     dat.add_child("second");
00789     (*(dat.child("first")))["weapon"]=a_stats_->weapon->id();
00790     (*(dat.child("second")))["weapon"]=d_stats_->weapon != NULL ? d_stats_->weapon->id() : "none";
00791     DELAY_END_LEVEL(delayed_exception, game_events::fire(n,
00792                                 game_events::entity_location(attacker_,a_id_),
00793                                 game_events::entity_location(defender_,d_id_),dat));
00794 
00795     // The event could have killed either the attacker or
00796     // defender, so we have to make sure they still exist
00797     a_ = units_.find(attacker_);
00798     d_ = units_.find(defender_);
00799     //! @todo FIXME: If the event removes this attack, we should stop attacking.
00800     // The previous code checked if 'attack_with' and 'defend_with'
00801     // were still within the bounds of the attack arrays,
00802     // or -1, but it was incorrect.
00803     // The attack used could be removed and '*_with' variables
00804     // could still be in bounds but point to a different attack.
00805     if(a_ == units_.end() || d_ == units_.end()) {
00806         if (update_display_){
00807             recalculate_fog(map_,units_,teams_,attacker_side-1);
00808             recalculate_fog(map_,units_,teams_,defender_side-1);
00809             gui_.recalculate_minimap();
00810             gui_.draw(true,true);
00811         }
00812         fire_event("attack_end");
00813         throw attack_end_exception();
00814     }
00815 }
00816 
00817 void attack::refresh_bc()
00818 {
00819     a_ = units_.find(attacker_);
00820     d_ = units_.find(defender_);
00821     if(a_ == units_.end() || d_ == units_.end()) {
00822         return;
00823     }
00824     *bc_ =  battle_context(map_, teams_, units_, state_, attacker_, defender_, attack_with_, defend_with_);
00825     a_stats_ = &bc_->get_attacker_stats();
00826     d_stats_ = &bc_->get_defender_stats();
00827     attacker_cth_ = a_stats_->chance_to_hit;
00828     defender_cth_ = d_stats_->chance_to_hit;
00829     attacker_damage_ = a_stats_->damage;
00830     defender_damage_ = d_stats_->damage;
00831 }
00832 
00833 attack::~attack()
00834 {
00835     delete delayed_exception;
00836     delete bc_;
00837 }
00838 
00839 //! Battle logic
00840 
00841 attack::attack(game_display& gui, const gamemap& map,
00842         std::vector<team>& teams,
00843         gamemap::location attacker,
00844         gamemap::location defender,
00845         int attack_with,
00846         int defend_with,
00847         unit_map& units,
00848         const gamestatus& state,
00849         bool update_display) :
00850     gui_(gui),
00851     map_(map),
00852     teams_(teams),
00853     attacker_(attacker),
00854     defender_(defender),
00855     attack_with_(attack_with),
00856     defend_with_(defend_with),
00857     units_(units),
00858     state_(state),
00859     a_(units_.find(attacker)),
00860     d_(units_.find(defender)),
00861     a_id_(),
00862     d_id_(),
00863     errbuf_(),
00864     bc_(0),
00865     a_stats_(0),
00866     d_stats_(0),
00867     orig_attacks_(0),
00868     orig_defends_(0),
00869     n_attacks_(0),
00870     n_defends_(0),
00871     attacker_cth_(0),
00872     defender_cth_(0),
00873     attacker_damage_(0),
00874     defender_damage_(0),
00875     attackerxp_(0),
00876     defenderxp_(0),
00877     update_display_(update_display),
00878     OOS_error_(false),
00879     delayed_exception(0)
00880 {
00881     // Stop the user from issuing any commands while the units are fighting
00882     const events::command_disabler disable_commands;
00883 
00884     if(a_ == units_.end() || d_ == units_.end()) {
00885         return;
00886     }
00887 
00888     a_id_ = a_->second.underlying_id();
00889     d_id_ = d_->second.underlying_id();
00890 
00891     // no attack weapon => stop here and don't attack
00892     if (attack_with < 0) {
00893         a_->second.set_attacks(a_->second.attacks_left()-1);
00894         a_->second.set_movement(-1);
00895         return;
00896     }
00897 
00898     a_->second.set_attacks(a_->second.attacks_left()-1);
00899     a_->second.set_movement(a_->second.movement_left()-a_->second.attacks()[attack_with].movement_used());
00900     a_->second.set_state("not_moved","");
00901     d_->second.set_resting(false);
00902 
00903     // If the attacker was invisible, she isn't anymore!
00904     a_->second.set_state("hides","");
00905 
00906     bc_ = new battle_context(map_, teams_, units_, state_, attacker_, defender_, attack_with_, defend_with_);
00907     a_stats_ = &bc_->get_attacker_stats();
00908     d_stats_ = &bc_->get_defender_stats();
00909 
00910     try {
00911         fire_event("attack");
00912     } catch (attack_end_exception) {
00913         return;
00914     }
00915     refresh_bc();
00916 
00917     DBG_NG << "getting attack statistics\n";
00918     statistics::attack_context attack_stats(a_->second, d_->second, a_stats_->chance_to_hit, d_stats_->chance_to_hit);
00919 
00920     {
00921         // Calculate stats for battle
00922         combatant attacker(bc_->get_attacker_stats());
00923         combatant defender(bc_->get_defender_stats());
00924         attacker.fight(defender);
00925         const double attacker_inflict = static_cast<double>(d_->second.hitpoints()) - defender.average_hp();
00926         const double defender_inflict = static_cast<double>(a_->second.hitpoints()) - attacker.average_hp();
00927     
00928         attack_stats.attack_excepted_damage(attacker_inflict,defender_inflict);
00929     }
00930 
00931     orig_attacks_ = a_stats_->num_blows;
00932     orig_defends_ = d_stats_->num_blows;
00933     n_attacks_ = orig_attacks_;
00934     n_defends_ = orig_defends_;
00935     attackerxp_ = d_->second.level();
00936     defenderxp_ = a_->second.level();
00937 
00938     bool defender_strikes_first = (d_stats_->firststrike && ! a_stats_->firststrike);
00939     unsigned int rounds = maximum<unsigned int>(a_stats_->rounds, d_stats_->rounds) - 1;
00940     int abs_n_attack_ = 0;
00941     int abs_n_defend_ = 0;
00942 
00943     static const std::string poison_string("poison");
00944 
00945     LOG_NG << "Fight: (" << attacker << ") vs (" << defender << ") ATT: " << a_stats_->weapon->name() << " " << a_stats_->damage << "-" << a_stats_->num_blows << "(" << a_stats_->chance_to_hit << "%) vs DEF: " << (d_stats_->weapon ? d_stats_->weapon->name() : "none") << " " << d_stats_->damage << "-" << d_stats_->num_blows << "(" << d_stats_->chance_to_hit << "%)" << (defender_strikes_first ? " defender first-strike" : "") << "\n";
00946 
00947     while(n_attacks_ > 0 || n_defends_ > 0) {
00948         DBG_NG << "start of attack loop...\n";
00949         abs_n_attack_++;
00950 
00951         if(n_attacks_ > 0 && defender_strikes_first == false) {
00952             const int ran_num = get_random();
00953             bool hits = (ran_num%100) < attacker_cth_;
00954 
00955             int damage_defender_takes;
00956             if(hits) {
00957                 damage_defender_takes = attacker_damage_;
00958             } else {
00959                 damage_defender_takes = 0;
00960             }
00961             // Make sure that if we're serializing a game here,
00962             // we got the same results as the game did originally.
00963             const config* ran_results = get_random_results();
00964             if(ran_results != NULL) {
00965                 const int results_chance = atoi((*ran_results)["chance"].c_str());
00966                 const bool results_hits = (*ran_results)["hits"] == "yes";
00967                 const int results_damage = atoi((*ran_results)["damage"].c_str());
00968 
00969                 if(results_chance != attacker_cth_) {
00970                     errbuf_ << "SYNC: In attack " << unit_dump(*a_) << " vs " << unit_dump(*d_)
00971                         << ": chance to hit defender is inconsistent. Data source: "
00972                         << results_chance << "; Calculation: " << attacker_cth_
00973                         << " (over-riding game calculations with data source results)\n";
00974                     attacker_cth_ = results_chance;
00975                     OOS_error_ = true;
00976                 }
00977                 if(hits != results_hits) {
00978                     errbuf_ << "SYNC: In attack " << unit_dump(*a_) << " vs " << unit_dump(*d_)
00979                         << ": the data source says the hit was "
00980                         << (results_hits ? "successful" : "unsuccessful")
00981                         << ", while in-game calculations say the hit was "
00982                         << (hits ? "successful" : "unsuccessful")
00983                         << " random number: " << ran_num << " = "
00984                         << (ran_num%100) << "/" << results_chance
00985                         << " (over-riding game calculations with data source results)\n";
00986                     hits = results_hits;
00987                     OOS_error_ = true;
00988                 }
00989                 if(results_damage != damage_defender_takes) {
00990                     errbuf_ << "SYNC: In attack " << unit_dump(*a_) << " vs " << unit_dump(*d_)
00991                         << ": the data source says the hit did " << results_damage
00992                         << " damage, while in-game calculations show the hit doing "
00993                         << damage_defender_takes
00994                         << " damage (over-riding game calculations with data source results)\n";
00995                     damage_defender_takes = results_damage;
00996                     OOS_error_ = true;
00997                 }
00998             }
00999 
01000             if(update_display_) {
01001                 std::string float_text = "";
01002                 if(hits) {
01003                     if (a_stats_->poisons &&
01004                             !utils::string_bool(d_->second.get_state("poisoned"))) {
01005                         float_text = float_text + _("poisoned") + "\n";
01006                     }
01007 
01008                     if(a_stats_->slows && !utils::string_bool(d_->second.get_state("slowed"))) {
01009                         float_text = float_text + _("slowed") + "\n";
01010                     }
01011 
01012                     // If the defender is turned to stone, the fight stops immediately
01013                     static const std::string stone_string("stone");
01014                     if (a_stats_->stones) {
01015                         float_text = float_text + _("stone") + "\n";
01016                     }
01017                 }
01018 
01019                 unit_display::unit_attack(attacker_,defender_,
01020                         damage_defender_takes,
01021                         *a_stats_->weapon,d_stats_->weapon,
01022                         abs_n_attack_,float_text,a_stats_->drains,"");
01023             }
01024             
01025             // Used for stat calcualtion
01026             const int drains_damage = a_stats_->drains ? 
01027                 minimum<int>(damage_defender_takes / 2,a_->second.max_hitpoints() - a_->second.hitpoints()) : 0;
01028             const int damage_done = minimum<int>(d_->second.hitpoints(), attacker_damage_);
01029             bool dies = d_->second.take_hit(damage_defender_takes);
01030             LOG_NG << "defender took " << damage_defender_takes << (dies ? " and died" : "") << "\n";
01031             attack_stats.attack_result(hits ? (dies ? statistics::attack_context::KILLS : statistics::attack_context::HITS)
01032                     : statistics::attack_context::MISSES, damage_done,drains_damage);
01033 
01034             if(ran_results == NULL) {
01035                 log_scope2(engine, "setting random results");
01036                 config cfg;
01037                 cfg["hits"] = (hits ? "yes" : "no");
01038                 cfg["dies"] = (dies ? "yes" : "no");
01039 
01040                 cfg["damage"] = lexical_cast<std::string>(damage_defender_takes);
01041                 cfg["chance"] = lexical_cast<std::string>(attacker_cth_);
01042 
01043                 set_random_results(cfg);
01044             } else {
01045                 const bool results_dies = (*ran_results)["dies"] == "yes";
01046                 if(results_dies != dies) {
01047                     errbuf_ << "SYNC: In attack " << unit_dump(*a_) << " vs " << unit_dump(*d_)
01048                         << ": the data source the unit "
01049                         << (results_dies ? "perished" : "survived")
01050                         << " while in-game calculations show the unit "
01051                         << (dies ? "perished" : "survived")
01052                         << " (over-riding game calculations with data source results)\n";
01053                     dies = results_dies;
01054                     OOS_error_ = true;
01055                 }
01056             }
01057             if(hits) {
01058                 try {
01059                     fire_event("attacker_hits");
01060                 } catch (attack_end_exception) {
01061                     refresh_bc();
01062                     break;
01063                 }
01064                 refresh_bc();
01065             } else {
01066                 try {
01067                     fire_event("attacker_misses");
01068                 } catch (attack_end_exception) {
01069                     refresh_bc();
01070                     break;
01071                 }
01072                 refresh_bc();
01073             }
01074 
01075             DBG_NG << "done attacking\n";
01076             if(dies || hits) {
01077                 int amount_drained = a_stats_->drains ? attacker_damage_ / 2 : 0;
01078 
01079                 if(amount_drained > 0) {
01080                     a_->second.heal(amount_drained);
01081                 }
01082             }
01083 
01084             if(dies) { // attacker kills defender
01085                 attackerxp_ = game_config::kill_experience*d_->second.level();
01086                 if(d_->second.level() == 0)
01087                     attackerxp_ = game_config::kill_experience/2;
01088                 defenderxp_ = 0;
01089                 gui_.invalidate(a_->first);
01090 
01091                 game_events::entity_location death_loc(d_);
01092                 game_events::entity_location attacker_loc(a_);
01093                 std::string undead_variation = d_->second.undead_variation();
01094                 const int defender_side = d_->second.side();
01095                 fire_event("attack_end");
01096                 DELAY_END_LEVEL(delayed_exception, game_events::fire("last breath", death_loc, attacker_loc));
01097 
01098                 d_ = units_.find(death_loc);
01099                 a_ = units_.find(attacker_loc);
01100                 if(d_ == units_.end() || !death_loc.matches_unit(d_->second)
01101                         || d_->second.hitpoints() > 0) {
01102                     // WML has invalidated the dying unit, abort
01103                     break;
01104                 }
01105                 bool attacker_invalid = false;
01106                 if(a_ == units_.end() || !attacker_loc.matches_unit(a_->second)) {
01107                     if(d_->second.hitpoints() <= 0) {
01108                         units_.erase(d_);
01109                         d_ = units_.end();
01110                     }
01111                     // WML has invalidated the killing unit
01112                     attacker_invalid = true;
01113                 }
01114                 refresh_bc();
01115                 if(attacker_invalid) {
01116                     unit_display::unit_die(d_->first, d_->second, NULL, d_stats_->weapon, NULL);
01117                 } else {
01118                     unit_display::unit_die(d_->first, d_->second,a_stats_->weapon,d_stats_->weapon, &(a_->second));
01119                 }
01120 
01121                 DELAY_END_LEVEL(delayed_exception, game_events::fire("die",death_loc,attacker_loc));
01122 
01123                 d_ = units_.find(death_loc);
01124                 a_ = units_.find(attacker_loc);
01125                 if(d_ == units_.end() || !death_loc.matches_unit(d_->second)) {
01126                     // WML has invalidated the dying unit, abort
01127                     break;
01128                 } else if(d_->second.hitpoints() <= 0) {
01129                     units_.erase(d_);
01130                     d_ = units_.end();
01131                 }
01132 
01133                 if(attacker_invalid || a_ == units_.end()
01134                         || !attacker_loc.matches_unit(a_->second)) {
01135                     // WML has invalidated the killing unit, abort
01136                     break;
01137                 }
01138                 refresh_bc();
01139 
01140                 if(a_stats_->plagues) {
01141                     // plague units make new units on the target hex
01142                     unit_type_data::unit_type_map::const_iterator reanimitor;
01143                     LOG_NG<<"trying to reanimate "<<a_stats_->plague_type<<std::endl;
01144                     reanimitor = unit_type_data::types().find(a_stats_->plague_type);
01145                     LOG_NG<<"found unit type:"<<reanimitor->second.id()<<std::endl;
01146                     if(reanimitor != unit_type_data::types().end()) {
01147                         unit newunit=unit(&units_,&map_,&state_,&teams_,&reanimitor->second,a_->second.side(),true,true);
01148                         newunit.set_attacks(0);
01149                         // Apply variation
01150                         if(strcmp(undead_variation.c_str(),"null")) {
01151                             config mod;
01152                             config& variation=mod.add_child("effect");
01153                             variation["apply_to"]="variation";
01154                             variation["name"]=undead_variation;
01155                             newunit.add_modification("variation",mod);
01156                             newunit.heal_all();
01157                         }
01158                         units_.add(new std::pair<gamemap::location,unit>(death_loc,newunit));
01159                         preferences::encountered_units().insert(newunit.type_id());
01160                         if (update_display_){
01161                             gui_.invalidate(death_loc);
01162                         }
01163                     }
01164                 } else {
01165                     LOG_NG<<"unit not reanimated"<<std::endl;
01166                 }
01167                 if (update_display_){
01168                     recalculate_fog(map_,units_,teams_,defender_side-1);
01169                     gui_.invalidate_all();
01170                     gui_.recalculate_minimap();
01171                     gui_.draw();
01172                 }
01173                 break;
01174             } else if(hits) {
01175                 if (a_stats_->poisons &&
01176                         !utils::string_bool(d_->second.get_state("poisoned"))) {
01177                     d_->second.set_state("poisoned","yes");
01178                     LOG_NG << "defender poisoned\n";
01179                 }
01180 
01181                 if(a_stats_->slows && !utils::string_bool(d_->second.get_state("slowed"))) {
01182                     d_->second.set_state("slowed","yes");
01183                     defender_damage_ = d_stats_->slow_damage;
01184                     LOG_NG << "defender slowed\n";
01185                 }
01186 
01187                 // If the defender is turned to stone, the fight stops immediately
01188                 static const std::string stone_string("stone");
01189                 if (a_stats_->stones) {
01190                     d_->second.set_state("stoned","yes");
01191                     n_defends_ = 0;
01192                     n_attacks_ = 0;
01193                     DELAY_END_LEVEL(delayed_exception, game_events::fire(stone_string,d_->first,a_->first));
01194                     refresh_bc();
01195 
01196                 }
01197             }
01198 
01199             --n_attacks_;
01200         }
01201 
01202         // If the defender got to strike first, they use it up here.
01203         defender_strikes_first = false;
01204         abs_n_defend_++;
01205 
01206         if(n_defends_ > 0) {
01207             DBG_NG << "doing defender attack...\n";
01208 
01209             const int ran_num = get_random();
01210             bool hits = (ran_num%100) < defender_cth_;
01211 
01212             int damage_attacker_takes;
01213             if(hits) {
01214                 damage_attacker_takes = defender_damage_;
01215             } else {
01216                 damage_attacker_takes = 0;
01217             }
01218             // Make sure that if we're serializing a game here,
01219             // we got the same results as the game did originally.
01220             const config* ran_results = get_random_results();
01221             if(ran_results != NULL) {
01222                 const int results_chance = atoi((*ran_results)["chance"].c_str());
01223                 const bool results_hits = (*ran_results)["hits"] == "yes";
01224                 const int results_damage = atoi((*ran_results)["damage"].c_str());
01225 
01226                 if(results_chance != defender_cth_) {
01227                     errbuf_ << "SYNC: In defend " << unit_dump(*a_) << " vs " << unit_dump(*d_)
01228                         << ": chance to hit attacker is inconsistent. Data source: "
01229                         << results_chance << "; Calculation: " << defender_cth_
01230                         << " (over-riding game calculations with data source results)\n";
01231                     defender_cth_ = results_chance;
01232                     OOS_error_ = true;
01233                 }
01234                 if(hits != results_hits) {
01235                     errbuf_ << "SYNC: In defend " << unit_dump(*a_) << " vs " << unit_dump(*d_)
01236                         << ": the data source says the hit was "
01237                         << (results_hits ? "successful" : "unsuccessful")
01238                         << ", while in-game calculations say the hit was "
01239                         << (hits ? "successful" : "unsuccessful")
01240                         << " random number: " << ran_num << " = " << (ran_num%100) << "/"
01241                         << results_chance
01242                         << " (over-riding game calculations with data source results)\n";
01243                     hits = results_hits;
01244                     OOS_error_ = true;
01245                 }
01246                 if(results_damage != damage_attacker_takes) {
01247                     errbuf_ << "SYNC: In defend " << unit_dump(*a_) << " vs " << unit_dump(*d_)
01248                         << ": the data source says the hit did " << results_damage
01249                         << " damage, while in-game calculations show the hit doing "
01250                         << damage_attacker_takes
01251                         << " damage (over-riding game calculations with data source results)\n";
01252                     damage_attacker_takes = results_damage;
01253                     OOS_error_ = true;
01254                 }
01255             }
01256 
01257             if(update_display_) {
01258                 std::string float_text = "";
01259                 if(hits) {
01260                     if (d_stats_->poisons &&
01261                             !utils::string_bool(a_->second.get_state("poisoned"))) {
01262                         float_text = float_text + _("poisoned") + "\n";
01263                     }
01264 
01265                     if(d_stats_->slows && !utils::string_bool(a_->second.get_state("slowed"))) {
01266                         float_text = float_text + _("slowed") + "\n";
01267                     }
01268 
01269                     // If the defender is turned to stone, the fight stops immediately
01270                     static const std::string stone_string("stone");
01271                     if (d_stats_->stones) {
01272                         float_text = float_text + _("stone") + "\n";
01273                     }
01274                 }
01275                 unit_display::unit_attack(defender_,attacker_,
01276                         damage_attacker_takes,
01277                         *d_stats_->weapon,a_stats_->weapon,
01278                         abs_n_defend_,float_text,d_stats_->drains,"");
01279             }
01280 
01281             // used for stats calculation
01282             const int drains_damage = d_stats_->drains ? minimum<int>(damage_attacker_takes / 2,d_->second.max_hitpoints() - d_->second.hitpoints()): 0;
01283             const int damage_done   = minimum<int>(a_->second.hitpoints(), defender_damage_);
01284             bool dies = a_->second.take_hit(damage_attacker_takes);
01285             LOG_NG << "attacker took " << damage_attacker_takes << (dies ? " and died" : "") << "\n";
01286             if(dies) {
01287                 unit_display::unit_die(attacker_,a_->second,a_stats_->weapon,d_stats_->weapon, &(d_->second));
01288             }
01289             if(ran_results == NULL) {
01290                 config cfg;
01291                 cfg["hits"] = (hits ? "yes" : "no");
01292                 cfg["dies"] = (dies ? "yes" : "no");
01293                 cfg["damage"] = lexical_cast<std::string>(damage_attacker_takes);
01294                 cfg["chance"] = lexical_cast<std::string>(defender_cth_);
01295 
01296                 set_random_results(cfg);
01297             } else {
01298                 const bool results_dies = (*ran_results)["dies"] == "yes";
01299                 if(results_dies != dies) {
01300                     errbuf_ << "SYNC: In defend " << unit_dump(*a_) << " vs " << unit_dump(*d_)
01301                         << ": the data source the unit "
01302                         << (results_dies ? "perished" : "survived")
01303                         << " while in-game calculations show the unit "
01304                         << (dies ? "perished" : "survived")
01305                         << " (over-riding game calculations with data source results)\n";
01306                     dies = results_dies;
01307                     OOS_error_ = true;
01308                 }
01309             }
01310             if(hits) {
01311                 try {
01312                     fire_event("defender_hits");
01313                 } catch (attack_end_exception) {
01314                     refresh_bc();
01315                     break;
01316                 }
01317                 refresh_bc();
01318             } else {
01319                 try {
01320                     fire_event("defender_misses");
01321                 } catch (attack_end_exception) {
01322                     refresh_bc();
01323                     break;
01324                 }
01325                 refresh_bc();
01326             }
01327             attack_stats.defend_result(hits ? (dies ? statistics::attack_context::KILLS : statistics::attack_context::HITS)
01328                     : statistics::attack_context::MISSES, damage_done, drains_damage);
01329             if(hits || dies){
01330                 int amount_drained = d_stats_->drains ? defender_damage_ / 2 : 0;
01331 
01332                 if(amount_drained > 0) {
01333                     d_->second.heal(amount_drained);
01334                 }
01335             }
01336 
01337             if(dies) { // defender kills attacker
01338                 defenderxp_ = game_config::kill_experience*a_->second.level();
01339                 if(a_->second.level() == 0)
01340                     defenderxp_ = game_config::kill_experience/2;
01341                 attackerxp_ = 0;
01342                 gui_.invalidate(d_->first);
01343 
01344                 std::string undead_variation = a_->second.undead_variation();
01345 
01346                 game_events::entity_location death_loc(a_);
01347                 game_events::entity_location defender_loc(d_);
01348                 const int attacker_side = a_->second.side();
01349                 fire_event("attack_end");
01350                 DELAY_END_LEVEL(delayed_exception, game_events::fire("die",death_loc,defender_loc));
01351 
01352                 // Don't try to call refresh_bc() here the attacker or defender might have
01353                 // been replaced by another unit, which might have a lower number of weapons.
01354                 // In that case refresh_bc() will terminate with an invalid selected weapon.
01355                 a_ = units_.find(attacker_);
01356                 d_ = units_.find(defender_);
01357 
01358                 if(a_ == units_.end() || !death_loc.matches_unit(a_->second)) {
01359                     // WML has invalidated the dying unit, abort
01360                     break;
01361                 } else if(a_->second.hitpoints() <= 0) {
01362                     units_.erase(a_);
01363                     a_ = units_.end();
01364                 }
01365 
01366                 if(d_ == units_.end() || !defender_loc.matches_unit(d_->second)) {
01367                     // WML has invalidated the killing unit, abort
01368                     break;
01369                 } else if(d_stats_->plagues) {
01370                     // plague units make new units on the target hex.
01371                     unit_type_data::unit_type_map::const_iterator reanimitor;
01372                     LOG_NG<<"trying to reanimate "<<d_stats_->plague_type<<std::endl;
01373                     reanimitor = unit_type_data::types().find(d_stats_->plague_type);
01374                     LOG_NG<<"found unit type:"<<reanimitor->second.id()<<std::endl;
01375                     if(reanimitor != unit_type_data::types().end()) {
01376                         unit newunit=unit(&units_,&map_,&state_,&teams_,&reanimitor->second,d_->second.side(),true,true);
01377                         // Apply variation
01378                         if(strcmp(undead_variation.c_str(),"null")){
01379                             config mod;
01380                             config& variation=mod.add_child("effect");
01381                             variation["apply_to"]="variation";
01382                             variation["name"]=undead_variation;
01383                             newunit.add_modification("variation",mod);
01384                         }
01385                         units_.add(new std::pair<gamemap::location,unit>(death_loc,newunit));
01386                         preferences::encountered_units().insert(newunit.type_id());
01387                         if (update_display_){
01388                             gui_.invalidate(death_loc);
01389                         }
01390                     }
01391                 } else {
01392                     LOG_NG<<"unit not reanimated"<<std::endl;
01393                 }
01394                 if (update_display_){
01395                     recalculate_fog(map_,units_,teams_,attacker_side-1);
01396                     gui_.invalidate_all();
01397                     gui_.recalculate_minimap();
01398                     gui_.draw();
01399                 }
01400                 break;
01401             } else if(hits) {
01402                 if (d_stats_->poisons &&
01403                         !utils::string_bool(a_->second.get_state("poisoned"))) {
01404                     a_->second.set_state("poisoned","yes");
01405                     LOG_NG << "attacker poisoned\n";
01406                 }
01407 
01408                 if(d_stats_->slows && !utils::string_bool(a_->second.get_state("slowed"))) {
01409                     a_->second.set_state("slowed","yes");
01410                     attacker_damage_ = a_stats_->slow_damage;
01411                     LOG_NG << "attacker slowed\n";
01412                 }
01413 
01414 
01415                 // If the attacker is turned to stone, the fight stops immediately
01416                 static const std::string stone_string("stone");
01417                 if (d_stats_->stones) {
01418                     a_->second.set_state("stoned","yes");
01419                     n_defends_ = 0;
01420                     n_attacks_ = 0;
01421 
01422                     DELAY_END_LEVEL(delayed_exception, game_events::fire(stone_string,a_->first,d_->first));
01423                     refresh_bc();
01424                 }
01425             }
01426 
01427             --n_defends_;
01428         }
01429 
01430         // Continue the fight to death; if one of the units got stoned,
01431         // either n_attacks or n_defends is -1
01432         if(rounds > 0 && n_defends_ == 0 && n_attacks_ == 0) {
01433             n_attacks_ = orig_attacks_;
01434             n_defends_ = orig_defends_;
01435             --rounds;
01436             defender_strikes_first = (d_stats_->firststrike && ! a_stats_->firststrike);
01437         }
01438         if(n_attacks_ <= 0 && n_defends_ <= 0) {
01439             fire_event("attack_end");
01440             refresh_bc();
01441         }
01442     }
01443 
01444     if(a_ != units.end()) {
01445         a_->second.set_standing(a_->first);
01446         if(attackerxp_)
01447             a_->second.get_experience(attackerxp_);
01448     }
01449 
01450     if(d_ != units.end()) {
01451         d_->second.set_standing(d_->first);
01452         if(defenderxp_)
01453             d_->second.get_experience(defenderxp_);
01454     }
01455 
01456     if (update_display_){
01457         gui_.invalidate_unit();
01458         gui_.invalidate(attacker);
01459         gui_.invalidate(defender);
01460         gui_.draw(true,true);
01461     }
01462 
01463     if(OOS_error_) {
01464         replay::throw_error(errbuf_.str());
01465     }
01466 
01467     THROW_END_LEVEL(delayed_exception);
01468 
01469 }
01470 
01471 
01472 int village_owner(const gamemap::location& loc, const std::vector<team>& teams)
01473 {
01474     for(size_t i = 0; i != teams.size(); ++i) {
01475         if(teams[i].owns_village(loc))
01476             return i;
01477     }
01478 
01479     return -1;
01480 }
01481 
01482 bool get_village(const gamemap::location& loc, game_display& disp,
01483         std::vector<team>& teams, size_t team_num,
01484         const unit_map& units, int *action_timebonus)
01485 {
01486     if(team_num < teams.size() && teams[team_num].owns_village(loc)) {
01487         return false;
01488     }
01489 
01490     const bool has_leader = find_leader(units,int(team_num+1)) != units.end();
01491     bool grants_timebonus = false;
01492 
01493     // We strip the village off all other sides, unless it is held by an ally
01494     // and we don't have a leader (and thus can't occupy it)
01495     for(std::vector<team>::iterator i = teams.begin(); i != teams.end(); ++i) {
01496         const unsigned int side = i - teams.begin() + 1;
01497         if(team_num >= teams.size() || has_leader || teams[team_num].is_enemy(side)) {
01498             i->lose_village(loc);
01499             if(team_num + 1 != side && action_timebonus) {
01500                 grants_timebonus = true;
01501             }
01502         }
01503     }
01504 
01505     if(grants_timebonus) {
01506         teams[team_num].set_action_bonus_count(1 + teams[team_num].action_bonus_count());
01507         *action_timebonus = 1;
01508     }
01509 
01510     if(team_num >= teams.size()) {
01511         return false;
01512     }
01513 
01514     if(has_leader) {
01515         disp.invalidate(loc);
01516         return teams[team_num].get_village(loc);
01517     }
01518 
01519     return false;
01520 }
01521 
01522 unit_map::iterator find_leader(unit_map& units, int side)
01523 {
01524     for(unit_map::iterator i = units.begin(); i != units.end(); ++i) {
01525         if(static_cast<int>(i->second.side()) == side && i->second.can_recruit())
01526             return i;
01527     }
01528 
01529     return units.end();
01530 }
01531 
01532 unit_map::const_iterator find_leader(const unit_map& units, int side)
01533 {
01534     for(unit_map::const_iterator i = units.begin(); i != units.end(); ++i) {
01535         if(static_cast<int>(i->second.side()) == side && i->second.can_recruit())
01536             return i;
01537     }
01538 
01539     return units.end();
01540 }
01541 
01542 // Simple algorithm: no maximum number of patients per healer.
01543 void reset_resting(unit_map& units, unsigned int side)
01544 {
01545     for (unit_map::iterator i = units.begin(); i != units.end(); ++i) {
01546         if (i->second.side() == side)
01547             i->second.set_resting(true);
01548     }
01549 }
01550 
01551 void calculate_healing(game_display& disp, const gamemap& map,
01552         unit_map& units, unsigned int side,
01553         const std::vector<team>& teams, bool update_display)
01554 {
01555     // We look for all allied units, then we see if our healer is near them.
01556     for (unit_map::iterator i = units.begin(); i != units.end(); ++i) {
01557 
01558         if (!utils::string_bool(i->second.get_state("healable"),true))
01559             continue;
01560 
01561         unit_map::iterator curer = units.end();
01562         std::vector<unit_map::iterator> healers;
01563 
01564         int healing = 0;
01565         int rest_healing = 0;
01566 
01567         std::string curing;
01568 
01569         unit_ability_list heal = i->second.get_abilities("heals",i->first);
01570 
01571         const bool is_poisoned = utils::string_bool(i->second.get_state("poisoned"));
01572         if(is_poisoned) {
01573             // Remove the enemies' healers to determine if poison is slowed or cured
01574             for(std::vector<std::pair<config*,gamemap::location> >::iterator
01575                     h_it = heal.cfgs.begin(); h_it != heal.cfgs.end();) {
01576 
01577                 unit_map::iterator potential_healer = units.find(h_it->second);
01578 
01579                 assert(potential_healer != units.end());
01580                 if(teams[potential_healer->second.side()-1].is_enemy(side)) {
01581                     h_it = heal.cfgs.erase(h_it);
01582                 } else {
01583                     ++h_it;
01584                 }
01585             }
01586             for(std::vector<std::pair<config*,gamemap::location> >::const_iterator
01587                     heal_it = heal.cfgs.begin(); heal_it != heal.cfgs.end(); ++heal_it) {
01588 
01589                 if((*heal_it->first)["poison"] == "cured") {
01590                     curer = units.find(heal_it->second);
01591                     // Full curing only occurs on the healer turn (may be changed)
01592                     if(curer->second.side() == side) {
01593                         curing = "cured";
01594                     } else if(curing != "cured") {
01595                         curing = "slowed";
01596                     }
01597                 } else if(curing != "cured" && (*heal_it->first)["poison"] == "slowed") {
01598                     curer = units.find(heal_it->second);
01599                     curing = "slowed";
01600                 }
01601             }
01602         }
01603 
01604         // For heal amounts, only consider healers on side which is starting now.
01605         // Remove all healers not on this side.
01606         for(std::vector<std::pair<config*,gamemap::location> >::iterator h_it =
01607                 heal.cfgs.begin(); h_it != heal.cfgs.end();) {
01608 
01609             unit_map::iterator potential_healer = units.find(h_it->second);
01610             assert(potential_healer != units.end());
01611             if(potential_healer->second.side() != side) {
01612                 h_it = heal.cfgs.erase(h_it);
01613             } else {
01614                 ++h_it;
01615             }
01616         }
01617 
01618         unit_abilities::effect heal_effect(heal,0,false);
01619         healing = heal_effect.get_composite_value();
01620 
01621         for(std::vector<unit_abilities::individual_effect>::const_iterator heal_loc = heal_effect.begin(); heal_loc != heal_effect.end(); ++heal_loc) {
01622             healers.push_back(units.find(heal_loc->loc));
01623         }
01624 
01625         if(i->second.side() == side) {
01626             unit_ability_list regen = i->second.get_abilities("regenerate",i->first);
01627             unit_abilities::effect regen_effect(regen,0,false);
01628             if(regen_effect.get_composite_value() > healing) {
01629                 healing = regen_effect.get_composite_value();
01630                 healers.clear();
01631             }
01632             if(regen.cfgs.size()) {
01633                 for(std::vector<std::pair<config*,gamemap::location> >::const_iterator regen_it = regen.cfgs.begin(); regen_it != regen.cfgs.end(); ++regen_it) {
01634                     if((*regen_it->first)["poison"] == "cured") {
01635                         curer = units.end();
01636                         curing = "cured";
01637                     } else if(curing != "cured" && (*regen_it->first)["poison"] == "slowed") {
01638                         curer = units.end();
01639                         curing = "slowed";
01640                     }
01641                 }
01642             }
01643             if (map.gives_healing(i->first)) {
01644                 if(map.gives_healing(i->first) > healing) {
01645                     healing = map.gives_healing(i->first);
01646                     healers.clear();
01647                 }
01648                 //! @todo FIXME
01649                 curing = "cured";
01650                 curer = units.end();
01651             }
01652             if(i->second.resting()) {
01653                 rest_healing = game_config::rest_heal_amount;
01654                 healing += rest_healing;
01655             }
01656         }
01657         if(is_poisoned) {
01658             if(curing == "cured") {
01659                 i->second.set_state("poisoned","");
01660                 healing = rest_healing;
01661                 healers.clear();
01662                 healers.push_back(curer);
01663             } else if(curing == "slowed") {
01664                 healing = rest_healing;
01665                 healers.clear();
01666                 healers.push_back(curer);
01667             } else {
01668                 healers.clear();
01669                 healing = rest_healing;
01670                 if(i->second.side() == side) {
01671                     healing -= game_config::poison_amount;
01672                 }
01673             }
01674         }
01675 
01676         if (curing == "" && healing==0) {
01677             continue;
01678         }
01679 
01680         int pos_max = i->second.max_hitpoints() - i->second.hitpoints();
01681         int neg_max = -(i->second.hitpoints() - 1);
01682         if(healing > 0 && pos_max <= 0) {
01683             // Do not try to "heal" if HP >= max HP
01684             continue;
01685         }
01686         if(healing > pos_max) {
01687             healing = pos_max;
01688         } else if(healing < neg_max) {
01689             healing = neg_max;
01690         }
01691 
01692         if ( !recorder.is_skipping()
01693                 && update_display
01694                 && !(i->second.invisible(i->first,units,teams) &&
01695                     teams[disp.viewing_team()].is_enemy(side))) {
01696             unit_display::unit_healing(i->second,i->first,healers,healing);
01697         }
01698         if (healing > 0)
01699             i->second.heal(healing);
01700         else if (healing < 0)
01701             i->second.take_hit(-healing);
01702         disp.invalidate_unit();
01703     }
01704 }
01705 
01706 
01707 unit get_advanced_unit(unit_map& units,
01708         const gamemap::location& loc, const std::string& advance_to)
01709 {
01710     const std::map<std::string,unit_type>::const_iterator new_type = unit_type_data::types().find(advance_to);
01711     const unit_map::iterator un = units.find(loc);
01712     if(new_type != unit_type_data::types().end() && un != units.end()) {
01713         unit new_unit(un->second);
01714         new_unit.get_experience(-new_unit.max_experience());
01715         new_unit.advance_to(&(new_type->second));
01716         return new_unit;
01717     } else {
01718         throw game::game_error("Could not find the unit being advanced"
01719                 " to: " + advance_to);
01720     }
01721 }
01722 
01723 void advance_unit(unit_map& units,
01724         gamemap::location loc, const std::string& advance_to)
01725 {
01726     if(units.count(loc) == 0) {
01727         return;
01728     }
01729     const unit& new_unit = get_advanced_unit(units,loc,advance_to);
01730     LOG_NG << "firing advance event\n";
01731     game_events::fire("advance",loc);
01732     statistics::advance_unit(new_unit);
01733 
01734     preferences::encountered_units().insert(new_unit.type_id());
01735     LOG_STREAM(info, config) << "Added '" << new_unit.type_id() << "' to encountered units\n";
01736 
01737     units.replace(new std::pair<gamemap::location,unit>(loc,new_unit));
01738     LOG_NG << "firing post_advance event\n";
01739     game_events::fire("post_advance",loc);
01740 }
01741 
01742 void check_victory(unit_map& units, std::vector<team>& teams, display& disp)
01743 {
01744     std::vector<int> seen_leaders;
01745     for(unit_map::const_iterator i = units.begin();
01746             i != units.end(); ++i) {
01747         if(i->second.can_recruit()) {
01748             DBG_NG << "seen leader for side " << i->second.side() << "\n";
01749             seen_leaders.push_back(i->second.side());
01750         }
01751     }
01752 
01753     // Clear villages for teams that have no leader
01754     for(std::vector<team>::iterator tm = teams.begin(); tm != teams.end(); ++tm) {
01755         if(std::find(seen_leaders.begin(),seen_leaders.end(),tm-teams.begin() + 1) == seen_leaders.end()) {
01756             tm->clear_villages();
01757             // invalidate_all() is overkill and expensive but this code is
01758             // run rarely so do it the expensive way.
01759             disp.invalidate_all();
01760         }
01761     }
01762 
01763     bool found_enemies = false;
01764     bool found_player = false;
01765 
01766     for(size_t n = 0; n != seen_leaders.size(); ++n) {
01767         const size_t side = seen_leaders[n]-1;
01768 
01769         assert(side < teams.size());
01770 
01771         for(size_t m = n+1; m != seen_leaders.size(); ++m) {
01772             if(side < teams.size() && teams[side].is_enemy(seen_leaders[m])) {
01773                 found_enemies = true;
01774             }
01775         }
01776 
01777         if (teams[side].is_human() || teams[side].is_persistent()) {
01778             found_player = true;
01779         }
01780     }
01781 
01782     if(found_enemies == false) {
01783         if (found_player) {
01784             game_events::fire("enemies defeated");
01785         }
01786 
01787         if (victory_conditions::victory_when_enemies_defeated() == false
01788                 && (found_player || is_observer())){
01789             // This level has asked not to be ended by this condition.
01790             return;
01791         }
01792 
01793         if(non_interactive()) {
01794             std::cout << "winner: ";
01795             for(std::vector<int>::const_iterator i = seen_leaders.begin(); i != seen_leaders.end(); ++i) {
01796                 std::string ai = teams[*i - 1].ai_algorithm();
01797                 if (ai == "") ai = "default ai";
01798                 std::cout << *i << " (using " << ai << ") ";
01799             }
01800             std::cout << "\n";
01801         }
01802 
01803         LOG_NG << "throwing end level exception...\n";
01804 
01805         throw end_level_exception(found_player ? VICTORY : DEFEAT,
01806                 victory_conditions::get_carryover_percentage(),
01807                 victory_conditions::get_carryover_add());
01808     }
01809 }
01810 
01811 time_of_day timeofday_at(const gamestatus& status,const unit_map& units,const gamemap::location& loc, const gamemap& map)
01812 {
01813     int lighten = maximum<int>(map.get_terrain_info(map.get_terrain(loc)).light_modification() , 0);
01814     int darken = minimum<int>(map.get_terrain_info(map.get_terrain(loc)).light_modification() , 0);
01815 
01816     time_of_day tod = status.get_time_of_day(lighten + darken,loc);
01817 
01818     if(loc.valid()) {
01819         gamemap::location locs[7];
01820         locs[0] = loc;
01821         get_adjacent_tiles(loc,locs+1);
01822 
01823         for(int i = 0; i != 7; ++i) {
01824             const unit_map::const_iterator itor = units.find(locs[i]);
01825             if(itor != units.end() &&
01826                     itor->second.get_ability_bool("illuminates",itor->first) && !itor->second.incapacitated()) {
01827                 unit_ability_list illum = itor->second.get_abilities("illuminates",itor->first);
01828                 unit_abilities::effect illum_effect(illum,lighten,false);
01829                 int mod = illum_effect.get_composite_value();
01830                 if(mod + tod.lawful_bonus > illum.highest("max_value").first) {
01831                     mod = illum.highest("max_value").first - tod.lawful_bonus;
01832                 }
01833                 lighten = maximum<int>(mod, lighten);
01834                 darken = minimum<int>(mod, darken);
01835             }
01836         }
01837     }
01838     tod = status.get_time_of_day(lighten + darken,loc);
01839 
01840     return tod;
01841 }
01842 
01843 int combat_modifier(const gamestatus& status,
01844         const unit_map& units,
01845         const gamemap::location& loc,
01846         unit_type::ALIGNMENT alignment,
01847         bool is_fearless,
01848         const gamemap& map)
01849 {
01850     const time_of_day& tod = timeofday_at(status,units,loc,map);
01851 
01852     int bonus = tod.lawful_bonus;
01853 
01854     if(alignment == unit_type::NEUTRAL)
01855         bonus = 0;
01856     else if(alignment == unit_type::CHAOTIC)
01857         bonus = -bonus;
01858     if(is_fearless)
01859         bonus = maximum<int>(bonus, 0);
01860 
01861     return bonus;
01862 }
01863 
01864 namespace {
01865 
01866     bool clear_shroud_loc(const gamemap& map, team& tm,
01867             const gamemap::location& loc,
01868             std::vector<gamemap::location>* cleared)
01869     {
01870         bool result = false;
01871         gamemap::location adj[7];
01872         get_adjacent_tiles(loc,adj);
01873         adj[6] = loc;
01874         bool on_board_loc = map.on_board(loc);
01875         for(int i = 0; i != 7; ++i) {
01876 
01877             // We clear one past the edge of the board, so that the half-hexes
01878             // at the edge can also be cleared of fog/shroud.
01879             if (on_board_loc || map.on_board(adj[i],true)) {
01880                 // Both functions should be executed so don't use || which
01881                 // uses short-cut evaluation.
01882                 const bool res = tm.clear_shroud(adj[i]) | tm.clear_fog(adj[i]);
01883 
01884                 if(res) {
01885                     if(res) {
01886                         result = true;
01887                         // If we're near the corner it might be the corner also needs to be cleared
01888                         // this always happens at the lower left corner and depending on the with
01889                         // at the upper or lower right corner.
01890                         if(adj[i].x == 0 && adj[i].y == map.h() - 1) { // Lower left corner
01891                             const gamemap::location corner(-1 , map.h());
01892                             tm.clear_shroud(corner);
01893                             tm.clear_fog(corner);
01894                         } else if(map.w() % 2 && adj[i].x == map.w() - 1 && adj[i].y == map.h() - 1) { // Lower right corner
01895                             const gamemap::location corner(map.w() , map.h());
01896                             tm.clear_shroud(corner);
01897                             tm.clear_fog(corner);
01898                         } else if(!(map.w() % 2) && adj[i].x == map.w() - 1 && adj[i].y == 0) { // Upper right corner
01899                             const gamemap::location corner(map.w() , -1);
01900                             tm.clear_shroud(corner);
01901                             tm.clear_fog(corner);
01902                         }
01903                         if(cleared) {
01904                             cleared->push_back(adj[i]);
01905                         }
01906                     }
01907                 }
01908             }
01909         }
01910 
01911         return result;
01912     }
01913 
01914     //! Returns true if some shroud is cleared.
01915     //! seen_units will return new units that have been seen by this unit.
01916     //! If known_units is NULL, seen_units can be NULL and will not be changed.
01917     bool clear_shroud_unit(const gamemap& map,
01918             const unit_map& units, const gamemap::location& loc,
01919             std::vector<team>& teams, int team,
01920             const std::set<gamemap::location>* known_units = NULL,
01921             std::set<gamemap::location>* seen_units = NULL,
01922             std::set<gamemap::location>* stoned_units = NULL)
01923     {
01924         std::vector<gamemap::location> cleared_locations;
01925 
01926         const unit_map::const_iterator u = units.find(loc);
01927         if(u == units.end()) {
01928             return false;
01929         }
01930 
01931         paths p(map,units,loc,teams,true,false,teams[team],0,false,true);
01932         for(paths::routes_map::const_iterator i = p.routes.begin();
01933                 i != p.routes.end(); ++i) {
01934             clear_shroud_loc(map,teams[team],i->first,&cleared_locations);
01935         }
01936 
01937         // clear_shroud_loc is supposed not introduce repetition in cleared_locations
01938         for(std::vector<gamemap::location>::const_iterator it =
01939                 cleared_locations.begin(); it != cleared_locations.end(); ++it) {
01940 
01941             const unit_map::const_iterator sighted = units.find(*it);
01942             if(sighted != units.end() &&
01943                     (sighted->second.invisible(*it,units,teams) == false
01944                      || teams[team].is_enemy(sighted->second.side()) == false)) {
01945                 //check if we know this unit, but we always know ourself
01946                 //just in case we managed to move on a fogged hex (teleport)
01947                 if(seen_units != NULL && known_units != NULL
01948                         && known_units->count(*it) == 0 && *it != loc) {
01949                     if (!utils::string_bool(sighted->second.get_state("stoned")))
01950                     {
01951                         seen_units->insert(*it);
01952                     }
01953                     else if (stoned_units != NULL)
01954                     {
01955                         stoned_units->insert(*it);
01956                     }
01957                 }
01958             }
01959         }
01960 
01961         return cleared_locations.empty() == false;
01962     }
01963 
01964 }
01965 
01966 void recalculate_fog(const gamemap& map,
01967         unit_map& units,
01968         std::vector<team>& teams, int team)
01969 {
01970     if (teams[team].uses_fog() == false)
01971         return;
01972 
01973     assert(team >= 0 && static_cast<size_t>(team) < teams.size());
01974     teams[team].refog();
01975 
01976     for(unit_map::iterator i = units.begin(); i != units.end(); ++i) {
01977         if(static_cast<int>(i->second.side()) == team + 1) {
01978             const unit_movement_resetter move_resetter(i->second);
01979 
01980             clear_shroud_unit(map,units,i->first,teams,team,NULL,NULL);
01981         }
01982     }
01983 
01984     //FIXME: This pump don't catch any sighted events (they are not fired by
01985     // clear_shroud_unit) and if it caches another old event, maybe the caller
01986     // don't want to pump it here
01987     game_events::pump();
01988 }
01989 
01990 bool clear_shroud(game_display& disp, const gamemap& map,
01991         unit_map& units, std::vector<team>& teams, int team)
01992 {
01993     if(teams[team].uses_shroud() == false && teams[team].uses_fog() == false)
01994         return false;
01995 
01996     bool result = false;
01997 
01998     unit_map::iterator i;
01999     for(i = units.begin(); i != units.end(); ++i) {
02000         if(static_cast<int>(i->second.side()) == team + 1) {
02001             const unit_movement_resetter move_resetter(i->second);
02002 
02003             result |= clear_shroud_unit(map,units,i->first,teams,team,NULL,NULL);
02004         }
02005     }
02006 
02007     //FIXME: This pump don't catch any sighted events (they are not fired by
02008     // clear_shroud_unit) and if it caches another old event, maybe the caller
02009     // don't want to pump it here
02010     game_events::pump();
02011 
02012     if (teams[team].uses_fog()) {
02013         recalculate_fog(map,units,teams,team);
02014     }
02015 
02016     disp.labels().recalculate_shroud();
02017 
02018     return result;
02019 }
02020 
02021 size_t move_unit(game_display* disp,
02022         const gamemap& map,
02023         unit_map& units, std::vector<team>& teams,
02024         std::vector<gamemap::location> route,
02025         replay* move_recorder, undo_list* undo_stack,
02026         gamemap::location *next_unit, bool continue_move,
02027         bool should_clear_shroud)
02028 {
02029     assert(route.empty() == false);
02030 
02031     // Stop the user from issuing any commands while the unit is moving
02032     const events::command_disabler disable_commands;
02033 
02034     unit_map::iterator ui = units.find(route.front());
02035 
02036     assert(ui != units.end());
02037 
02038     ui->second.set_goto(gamemap::location());
02039 
02040     size_t team_num = ui->second.side()-1;
02041     team& team = teams[team_num];
02042 
02043     const bool check_shroud = should_clear_shroud && team.auto_shroud_updates() &&
02044         (team.uses_shroud() || team.uses_fog());
02045 
02046     std::set<gamemap::location> known_units;
02047     if(check_shroud) {
02048         for(unit_map::const_iterator u = units.begin(); u != units.end(); ++u) {
02049             if(team.fogged(u->first) == false) {
02050                 known_units.insert(u->first);
02051                 team.see(u->second.side()-1);
02052             }
02053         }
02054     }
02055 
02056     // See how far along the given path we can move.
02057     const int starting_moves = ui->second.movement_left();
02058     int moves_left = starting_moves;
02059     std::set<gamemap::location> seen_units;
02060     std::set<gamemap::location> stoned_units;
02061     bool discovered_unit = false;
02062     bool teleport_failed = false;
02063     bool should_clear_stack = false;
02064     std::vector<gamemap::location>::const_iterator step;
02065     std::string ambushed_string;
02066 
02067     for(step = route.begin()+1; step != route.end(); ++step) {
02068         const t_translation::t_terrain terrain = map[*step];
02069 
02070         const int cost = ui->second.movement_cost(terrain);
02071         if(cost >moves_left || discovered_unit || (continue_move == false && seen_units.empty() == false)) {
02072             break; // not enough MP or spotted new enemies
02073         }
02074 
02075         const unit_map::const_iterator enemy_unit = units.find(*step);
02076         if (enemy_unit != units.end()) {
02077             if (team.is_enemy(enemy_unit->second.side())) {
02078                 break; // can't traverse enemy
02079             } else if (!tiles_adjacent(*(step-1),*step)) {
02080                 // can't teleport on ally (on fogged village, with no-leader and view not-shared)
02081                 teleport_failed = true;
02082                 break;
02083             }
02084         }
02085 
02086         moves_left -= cost;
02087 
02088         // If we use fog or shroud, see if we have sighted an enemy unit,
02089         // in which case we should stop immediately.
02090         // Cannot use check shroud, because also need to check if delay shroud is on.
02091         if(should_clear_shroud && (team.uses_shroud() || team.uses_fog())) {
02092             //we don't want to interrupt our move when we are on an other unit
02093             //or a uncaptured village (except if it was our plan to end there)
02094             if( units.count(*step) == 0 &&
02095                     (!map.is_village(*step) || team.owns_village(*step) || step+1==route.end()) ) {
02096                 DBG_NG << "checking for units from " << (step->x+1) << "," << (step->y+1) << "\n";
02097 
02098                 // Temporarily reset the unit's moves to full
02099                 const unit_movement_resetter move_resetter(ui->second);
02100 
02101                 // We have to swap out any unit that is already in the hex,
02102                 // so we can put our unit there, then we'll swap back at the end.
02103                 const temporary_unit_placer unit_placer(units,*step,ui->second);
02104                 if( team.auto_shroud_updates()) {
02105                     should_clear_stack |= clear_shroud_unit(map,units,*step,teams,
02106                             ui->second.side()-1,&known_units,&seen_units,&stoned_units);
02107                 } else {
02108                     clear_shroud_unit(map,units,*step,teams,
02109                             ui->second.side()-1,&known_units,&seen_units,&stoned_units);
02110                 }
02111                 if(should_clear_stack) {
02112                     disp->invalidate_all();
02113                 }
02114             }
02115         }
02116 #if 0
02117         // Check to see if the unit was deleted
02118         // during a sighted event in clear_shroud_unit()
02119         ui = units.find(route.front());
02120         if(ui == units.end()) {
02121             //! @todo FIXME: the correct behavior for sighted event would be
02122             // to halt movement, then fire "sighted" after firing "moveto" (see below).
02123             // However, since we have fired "sighted" during movement calculations
02124             // this is a workaround to prevent a crash.
02125             if(move_recorder != NULL) {
02126                 move_recorder->add_movement(route.front(),*step);
02127             }
02128             return (step - route.begin());
02129         }
02130 #endif
02131         // we also refreh its side, just in case if an event change it
02132         team_num = ui->second.side()-1;
02133         team = teams[team_num];
02134 
02135         const bool skirmisher = ui->second.get_ability_bool("skirmisher",*step);
02136         if(!skirmisher && enemy_zoc(map,units,teams,*step,team,ui->second.side())) {
02137             moves_left = 0;
02138         }
02139 
02140         // Check if we have discovered an invisible enemy unit
02141         gamemap::location adjacent[6];
02142         get_adjacent_tiles(*step,adjacent);
02143 
02144         for(int i = 0; i != 6; ++i) {
02145             // Check if we are checking ourselves
02146             if(adjacent[i] == ui->first)
02147                 continue;
02148 
02149             const unit_map::const_iterator it = units.find(adjacent[i]);
02150             if(it != units.end() && team.is_enemy(it->second.side()) &&
02151                     it->second.invisible(it->first,units,teams)) {
02152                 discovered_unit = true;
02153                 unit_ability_list hides = it->second.get_abilities("hides",it->first);
02154                 for(std::vector<std::pair<config*,gamemap::location> >::const_iterator hide_it = hides.cfgs.begin();
02155                         hide_it != hides.cfgs.end(); ++hide_it) {
02156                     ambushed_string =(*hide_it->first)["alert"];
02157                 }
02158                 should_clear_stack = true;
02159                 moves_left = 0;
02160                 break;
02161             }
02162         }
02163     }
02164 
02165     // Make sure we don't tread on another unit.
02166     std::vector<gamemap::location>::const_iterator begin = route.begin();
02167 
02168     std::vector<gamemap::location> steps(begin,step);
02169     while (!steps.empty()) {
02170         gamemap::location const &loc = steps.back();
02171         if (units.count(loc) == 0)
02172             break;
02173         moves_left += ui->second.movement_cost(map[loc]);
02174         steps.pop_back();
02175     }
02176 
02177     assert(steps.size() <= route.size());
02178 
02179     // If we can't get all the way there and have to set a go-to.
02180     if(steps.size() != route.size() && discovered_unit == false) {
02181         if(seen_units.empty() == false) {
02182             ui->second.set_interrupted_move(route.back());
02183         } else {
02184             ui->second.set_goto(route.back());
02185         }
02186     } else {
02187         ui->second.set_interrupted_move(gamemap::location());
02188     }
02189 
02190     if(steps.size() < 2) {
02191         return 0;
02192     }
02193 
02194     if (next_unit != NULL)
02195         *next_unit = steps.back();
02196 
02197     // Move the unit on the screen. Hide the unit in its current location,
02198     // but don't actually remove it until the move is done,
02199     // so that while the unit is moving status etc.
02200     // will still display the correct number of units.
02201     unit_display::move_unit(steps,ui->second,teams);
02202 
02203     ui->second.set_movement(moves_left);
02204 
02205     std::pair<gamemap::location,unit> *p = units.extract(ui->first);
02206     p->first = steps.back();
02207     units.add(p);
02208     ui = units.find(p->first);
02209     unit::clear_status_caches();
02210 
02211     if(move_recorder != NULL) {
02212         move_recorder->add_movement(steps.front(),steps.back());
02213     }
02214 
02215     bool event_mutated = false;
02216 
02217     int orig_village_owner = -1;
02218     int action_time_bonus = 0;
02219 
02220     if(map.is_village(steps.back())) {
02221         orig_village_owner = village_owner(steps.back(),teams);
02222 
02223         if(size_t(orig_village_owner) != team_num) {
02224             ui->second.set_movement(0);
02225             event_mutated = get_village(steps.back(),*disp,teams,team_num,units,&action_time_bonus);
02226         }
02227     }
02228 
02229     if(disp != NULL) {
02230         // Show the final move animation step
02231         disp->draw();
02232         // Clear display helpers before firing events
02233     }
02234 
02235 
02236     if ( teams[ui->second.side()-1].uses_shroud() || teams[ui->second.side()-1].uses_fog())
02237     {
02238         std::set<gamemap::location>::iterator sight_it;
02239         const std::string sighted_str("sighted");
02240         // Fire sighted event here
02241         for (sight_it = seen_units.begin();
02242                 sight_it != seen_units.end(); ++sight_it)
02243         {
02244 
02245             game_events::raise(sighted_str,*sight_it,steps.back());
02246         }
02247 
02248         for (sight_it = stoned_units.begin();
02249                 sight_it != stoned_units.end(); ++sight_it)
02250         {
02251 
02252             game_events::raise(sighted_str,*sight_it,steps.back());
02253         }
02254     }
02255 
02256     game_events::raise("moveto",steps.back());
02257 
02258     event_mutated |= game_events::pump();
02259 
02260     ui = units.find(steps.back());
02261 
02262     if(undo_stack != NULL) {
02263         if(event_mutated || should_clear_stack || ui == units.end()) {
02264             apply_shroud_changes(*undo_stack,disp,map,units,teams,team_num);
02265             undo_stack->clear();
02266         } else {
02267             // MP_COUNTDOWN: added param
02268             undo_stack->push_back(undo_action(ui->second,steps,starting_moves,action_time_bonus,orig_village_owner));
02269         }
02270     }
02271 
02272     if(disp != NULL) {
02273         bool redraw = false;
02274 
02275         // Show messages on the screen here
02276         if(discovered_unit) {
02277             if (ambushed_string.empty())
02278                 ambushed_string = _("Ambushed!");
02279             // We've been ambushed, display an appropriate message
02280             disp->announce(ambushed_string, font::BAD_COLOUR);
02281             redraw = true;
02282         }
02283 
02284         if(teleport_failed) {
02285             std::string teleport_string = _ ("Failed teleport! Exit not empty");
02286             disp->announce(teleport_string, font::BAD_COLOUR);
02287             redraw = true;
02288         }
02289 
02290         if(continue_move == false && seen_units.empty() == false) {
02291             // The message depends on how many units have been sighted,
02292             // and whether they are allies or enemies, so calculate that out here
02293             int nfriends = 0, nenemies = 0;
02294             for(std::set<gamemap::location>::const_iterator i = seen_units.begin(); i != seen_units.end(); ++i) {
02295                 DBG_NG << "processing unit at " << (i->x+1) << "," << (i->y+1) << "\n";
02296                 const unit_map::const_iterator u = units.find(*i);
02297 
02298                 // Unit may have been removed by an event.
02299                 if(u == units.end()) {
02300                     DBG_NG << "was removed\n";
02301                     continue;
02302                 }
02303 
02304                 if(team.is_enemy(u->second.side())) {
02305                     ++nenemies;
02306                 } else {
02307                     ++nfriends;
02308                 }
02309 
02310                 DBG_NG << "processed...\n";
02311                 team.see(u->second.side()-1);
02312             }
02313 
02314             // The message we display is different depending on
02315             // whether the units sighted were enemies or friends,
02316             // and their respective number.
02317             utils::string_map symbols;
02318             symbols["friends"] = lexical_cast<std::string>(nfriends);
02319             symbols["enemies"] = lexical_cast<std::string>(nenemies);
02320             std::string message;
02321             SDL_Color msg_colour;
02322             if(nfriends == 0 || nenemies == 0) {
02323                 if(nfriends > 0) {
02324                     message = vngettext("Friendly unit sighted", "$friends friendly units sighted", nfriends, symbols);
02325                     msg_colour = font::GOOD_COLOUR;
02326                 } else if(nenemies > 0) {
02327                     message = vngettext("Enemy unit sighted!", "$enemies enemy units sighted!", nenemies, symbols);
02328                     msg_colour = font::BAD_COLOUR;
02329                 }
02330             }
02331             else {
02332                 symbols["friendphrase"] = vngettext("Part of 'Units sighted! (...)' sentence^1 friendly", "$friends friendly", nfriends, symbols);
02333                 symbols["enemyphrase"] = vngettext("Part of 'Units sighted! (...)' sentence^1 enemy", "$enemies enemy", nenemies, symbols);
02334                 message = vgettext("Units sighted! ($friendphrase, $enemyphrase)", symbols);
02335                 msg_colour = font::NORMAL_COLOUR;
02336             }
02337 
02338             if(steps.size() < route.size()) {
02339                 // See if the "Continue Move" action has an associated hotkey
02340                 const hotkey::hotkey_item& hk = hotkey::get_hotkey(hotkey::HOTKEY_CONTINUE_MOVE);
02341                 if(!hk.null()) {
02342                     symbols["hotkey"] = hk.get_name();
02343                     message += "\n" + vgettext("(press $hotkey to keep moving)", symbols);
02344                 }
02345             }
02346 
02347             disp->announce(message, msg_colour);
02348             redraw = true;
02349         }
02350 
02351         if (redraw) {
02352             // Not sure why this would be needed. Maybe during replays?
02353             disp->draw();
02354         }
02355         disp->recalculate_minimap();
02356     }
02357 
02358     assert(steps.size() <= route.size());
02359 
02360     return steps.size();
02361 }
02362 
02363 bool unit_can_move(const gamemap::location& loc, const unit_map& units,
02364         const gamemap& map, const std::vector<team>& teams)
02365 {
02366     const unit_map::const_iterator u_it = units.find(loc);
02367     assert(u_it != units.end());
02368 
02369     const unit& u = u_it->second;
02370     const team& current_team = teams[u.side()-1];
02371 
02372     if(!u.attacks_left() && u.movement_left()==0)
02373         return false;
02374 
02375     // Units with goto commands that have already done their gotos this turn
02376     // (i.e. don't have full movement left) should have red globes.
02377     if(u.has_moved() && u.has_goto()) {
02378         return false;
02379     }
02380 
02381     gamemap::location locs[6];
02382     get_adjacent_tiles(loc,locs);
02383     for(int n = 0; n != 6; ++n) {
02384         if(map.on_board(locs[n])) {
02385             const unit_map::const_iterator i = units.find(locs[n]);
02386             if(i != units.end()) {
02387                 if(!i->second.incapacitated() && current_team.is_enemy(i->second.side())) {
02388                     return true;
02389                 }
02390             }
02391 
02392             if(u.movement_cost(map[locs[n]]) <= u.movement_left()) {
02393                 return true;
02394             }
02395         }
02396     }
02397 
02398     return false;
02399 }
02400 
02401 void apply_shroud_changes(undo_list& undos, game_display* disp, const gamemap& map,
02402         unit_map& units, std::vector<team>& teams, int team)
02403 {
02404     // No need to do this if the team isn't using fog or shroud.
02405     if(!teams[team].uses_shroud() && !teams[team].uses_fog())
02406         return;
02407 
02408     /*
02409        This function works thusly:
02410        1. run through the list of undo_actions
02411        2. for each one, play back the unit's move
02412        3. for each location along the route, clear any "shrouded" hexes that the unit can see
02413           and record sighted events
02414        4. render shroud/fog cleared.
02415        5. pump all events
02416        6. call clear_shroud to update the fog of war for each unit
02417        7. fix up associated display stuff (done in a similar way to turn_info::undo())
02418     */
02419 
02420     std::set<gamemap::location> known_units;
02421     for(unit_map::const_iterator u = units.begin(); u != units.end(); ++u) {
02422         if(teams[team].fogged(u->first) == false) {
02423             known_units.insert(u->first);
02424         }
02425     }
02426 
02427     bool cleared_shroud = false;  // for further optimization
02428     bool sighted_event = false;
02429 
02430     for(undo_list::iterator un = undos.begin(); un != undos.end(); ++un) {
02431         LOG_NG << "Turning an undo...\n";
02432         //NOTE: for the moment shroud cleared during recall seems never delayed
02433         if(un->is_recall() || un->is_recruit()) continue;
02434 
02435         // We're not really going to mutate the unit, just temporarily
02436         // set its moves to maximum, but then switch them back.
02437         // TODO: do this for the temporary unit instead
02438         // (and maybe move it instead or recreate it for each step ?)
02439         const unit_movement_resetter move_resetter(un->affected_unit);
02440 
02441         std::vector<gamemap::location>::const_iterator step;
02442         for(step = un->route.begin(); step != un->route.end(); ++step) {
02443             // we search where is the unit now, before placing its temporary clone
02444             unit_map::const_iterator real_unit = units.find(un->affected_unit.underlying_id());
02445 
02446             // We have to swap out any unit that is already in the hex,
02447             // so we can put our unit there, then we'll swap back at the end.
02448             // FIXME: in other move functions, we are blind when traversing occupied hex
02449             const temporary_unit_placer unit_placer(units,*step,un->affected_unit);
02450 
02451             // In theory we don't know this clone, but
02452             // - he can't be in newly cleared locations
02453             // - clear_shroud_unit skip "self-detection"
02454             // so we normaly don't need to flood known_units with temporary stuff
02455             // known_units.insert(*step);
02456 
02457             // Clear the shroud, and collect new seen_units
02458             //FIXME: we don't use a separate stoned_units because I don't see the point
02459             // This avoid to change the sighted order (more risky here)
02460             // but must be cleaned (the function or the call)
02461             std::set<gamemap::location> seen_units;
02462             cleared_shroud |= clear_shroud_unit(map,units,*step,teams,team,
02463                 &known_units,&seen_units,&seen_units);
02464 
02465             for (std::set<gamemap::location>::iterator sight_it = seen_units.begin();
02466                 sight_it != seen_units.end(); ++sight_it)
02467             {
02468                 known_units.insert(*sight_it);
02469 
02470                 unit_map::const_iterator new_unit = units.find(*sight_it);
02471                 assert(new_unit != units.end());
02472                 teams[team].see(new_unit->second.side()-1);
02473 
02474                 game_events::raise("sighted",*sight_it,real_unit->first);
02475                 sighted_event = true;
02476             }
02477 
02478             // NOTE: (invalid now, we track the source, but let the warning for the moment)
02479             // There is potential for a bug, here. If the "sighted"
02480             // events, raised by the clear_shroud_unit function,
02481             // loops on all units, changing them all, the unit which
02482             // was swapped by the temporary unit placer will not be
02483             // affected. However, if we place the pump() out of the
02484             // temporary_unit_placer scope, the "sighted" event will
02485             // be raised with an invalid source unit, which is even
02486             // worse.
02487             // game_events::pump();
02488 
02489         }
02490     }
02491 
02492     // TODO: optimization: nothing cleared, so no sighted, and we can skip redraw
02493     // if (!cleared_shroud) return;
02494 
02495     // render shroud/fog cleared before pumping events
02496     // we don't refog yet to avoid hiding sighted stuff
02497     if(sighted_event && disp != NULL) {
02498         disp->invalidate_unit();
02499         disp->invalidate_all();
02500         disp->recalculate_minimap();
02501         disp->draw();
02502     }
02503 
02504     game_events::pump();
02505 
02506     // refog and invalidate stuff
02507     if(disp != NULL) {
02508         disp->invalidate_unit();
02509         disp->invalidate_game_status();
02510         clear_shroud(*disp,map,units,teams,team);
02511         disp->recalculate_minimap();
02512         disp->invalidate_all();
02513     } else {
02514         recalculate_fog(map,units,teams,team);
02515     }
02516 }
02517 
02518 bool backstab_check(const gamemap::location& attacker_loc,
02519         const gamemap::location& defender_loc,
02520         const unit_map& units, const std::vector<team>& teams)
02521 {
02522     const unit_map::const_iterator defender =
02523         units.find(defender_loc);
02524     if(defender == units.end()) return false; // No defender
02525 
02526     gamemap::location adj[6];
02527     get_adjacent_tiles(defender_loc, adj);
02528     int i;
02529     for(i = 0; i != 6; ++i) {
02530         if(adj[i] == attacker_loc)
02531             break;
02532     }
02533     if(i >= 6) return false;  // Attack not from adjacent location
02534 
02535     const unit_map::const_iterator opp =
02536         units.find(adj[(i+3)%6]);
02537     if(opp == units.end()) return false; // No opposite unit
02538     if(opp->second.incapacitated()) return false;
02539     if(size_t(defender->second.side()-1) >= teams.size() ||
02540             size_t(opp->second.side()-1) >= teams.size())
02541         return true; // If sides aren't valid teams, then they are enemies
02542     if(teams[defender->second.side()-1].is_enemy(opp->second.side()))
02543         return true; // Defender and opposite are enemies
02544     return false; // Defender and opposite are friends
02545 }

Generated by doxygen 1.5.5 on 23 May 2008 for The Battle for Wesnoth
Gna! | Forum | Wiki | CIA | devdocs