00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
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
00067
00068
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
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
00210
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
00263
00264
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
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
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
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
00367
00368
00369
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
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
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
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
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
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
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
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
00541 return them_a.average_hp() < them_b.average_hp();
00542 }
00543
00544
00545
00546 const combatant &battle_context::get_attacker_combatant(const combatant *prev_def)
00547 {
00548
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
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
00603 if (attack_num >= 0) {
00604 weapon = &u.attacks()[attack_num];
00605 }
00606 if(u.hitpoints() < 0) {
00607
00608
00609 hp = 0;
00610 } else if(u.hitpoints() > u.max_hitpoints()) {
00611
00612
00613
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
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
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
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
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
00669 int damage_multiplier = 100;
00670
00671
00672 damage_multiplier += combat_modifier(status, units, u_loc, u.alignment(), u.is_fearless(), map);
00673
00674
00675 int leader_bonus = 0;
00676 if (under_leadership(units, u_loc, &leader_bonus).valid())
00677 damage_multiplier += leader_bonus;
00678
00679
00680 damage_multiplier *= opp.damage_from(*weapon, !attacking, opp_loc);
00681
00682
00683
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
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
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
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
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
00796
00797 a_ = units_.find(attacker_);
00798 d_ = units_.find(defender_);
00799
00800
00801
00802
00803
00804
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
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
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
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
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
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
00962
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
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
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) {
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
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
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
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
01136 break;
01137 }
01138 refresh_bc();
01139
01140 if(a_stats_->plagues) {
01141
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
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
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
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
01219
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
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
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) {
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
01353
01354
01355 a_ = units_.find(attacker_);
01356 d_ = units_.find(defender_);
01357
01358 if(a_ == units_.end() || !death_loc.matches_unit(a_->second)) {
01359
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
01368 break;
01369 } else if(d_stats_->plagues) {
01370
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
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
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
01431
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
01494
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
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
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
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
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
01605
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
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
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
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
01758
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
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
01878
01879 if (on_board_loc || map.on_board(adj[i],true)) {
01880
01881
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
01888
01889
01890 if(adj[i].x == 0 && adj[i].y == map.h() - 1) {
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) {
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) {
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
01915
01916
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
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
01946
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
01985
01986
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
02008
02009
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
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
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;
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;
02079 } else if (!tiles_adjacent(*(step-1),*step)) {
02080
02081 teleport_failed = true;
02082 break;
02083 }
02084 }
02085
02086 moves_left -= cost;
02087
02088
02089
02090
02091 if(should_clear_shroud && (team.uses_shroud() || team.uses_fog())) {
02092
02093
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
02099 const unit_movement_resetter move_resetter(ui->second);
02100
02101
02102
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
02118
02119 ui = units.find(route.front());
02120 if(ui == units.end()) {
02121
02122
02123
02124
02125 if(move_recorder != NULL) {
02126 move_recorder->add_movement(route.front(),*step);
02127 }
02128 return (step - route.begin());
02129 }
02130 #endif
02131
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
02141 gamemap::location adjacent[6];
02142 get_adjacent_tiles(*step,adjacent);
02143
02144 for(int i = 0; i != 6; ++i) {
02145
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
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
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
02198
02199
02200
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
02231 disp->draw();
02232
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
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
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
02276 if(discovered_unit) {
02277 if (ambushed_string.empty())
02278 ambushed_string = _("Ambushed!");
02279
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
02292
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
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
02315
02316
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
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
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
02376
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
02405 if(!teams[team].uses_shroud() && !teams[team].uses_fog())
02406 return;
02407
02408
02409
02410
02411
02412
02413
02414
02415
02416
02417
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;
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
02433 if(un->is_recall() || un->is_recruit()) continue;
02434
02435
02436
02437
02438
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
02444 unit_map::const_iterator real_unit = units.find(un->affected_unit.underlying_id());
02445
02446
02447
02448
02449 const temporary_unit_placer unit_placer(units,*step,un->affected_unit);
02450
02451
02452
02453
02454
02455
02456
02457
02458
02459
02460
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
02479
02480
02481
02482
02483
02484
02485
02486
02487
02488
02489 }
02490 }
02491
02492
02493
02494
02495
02496
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
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;
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;
02534
02535 const unit_map::const_iterator opp =
02536 units.find(adj[(i+3)%6]);
02537 if(opp == units.end()) return false;
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;
02542 if(teams[defender->second.side()-1].is_enemy(opp->second.side()))
02543 return true;
02544 return false;
02545 }