wesnoth/src/actions.cpp
2006-03-29 01:57:48 +00:00

2433 lines
79 KiB
C++

/* $Id$ */
/*
Copyright (C) 2003 by David White <davidnwhite@verizon.net>
Part of the Battle for Wesnoth Project http://www.wesnoth.org/
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.
See the COPYING file for more details.
*/
#include "actions.hpp"
#include "checksum.hpp"
#include "game_config.hpp"
#include "game_errors.hpp"
#include "game_events.hpp"
#include "gettext.hpp"
#include "pathfind.hpp"
#include "playlevel.hpp"
#include "playturn.hpp"
#include "preferences.hpp"
#include "random.hpp"
#include "replay.hpp"
#include "sound.hpp"
#include "unit_display.hpp"
#include "wassert.hpp"
#include "wml_separators.hpp"
#include "serialization/binary_wml.hpp"
#include "serialization/parser.hpp"
#define LOG_NG LOG_STREAM(info, engine)
#define ERR_NW LOG_STREAM(err, network)
struct castle_cost_calculator : cost_calculator
{
castle_cost_calculator(const gamemap& map) : map_(map)
{}
virtual double cost(const gamemap::location& src, const gamemap::location& loc, const double, const bool) const
{
if(!map_.is_castle(loc))
return 10000;
return 1;
}
private:
const gamemap& map_;
};
// Conditions placed on victory must be accessible from the global function
// check_victory, but shouldn't be passed to that function as parameters,
// since it is called from a variety of places.
namespace victory_conditions
{
bool when_enemies_defeated = true;
void set_victory_when_enemies_defeated(bool on)
{
when_enemies_defeated = on;
}
bool victory_when_enemies_defeated()
{
return when_enemies_defeated;
}
}
bool can_recruit_on(const gamemap& map, const gamemap::location& leader, const gamemap::location loc)
{
if(!map.on_board(loc))
return false;
if(!map.is_castle(loc))
return false;
castle_cost_calculator calc(map);
const paths::route& rt = a_star_search(leader, loc, 100.0, &calc, map.x(), map.y());
if(rt.steps.empty())
return false;
return true;
}
std::string recruit_unit(const gamemap& map, int side,
unit_map& units, unit new_unit,
gamemap::location& recruit_location, display* disp, bool need_castle, bool full_movement)
{
const command_disabler disable_commands;
LOG_NG << "recruiting unit for side " << side << "\n";
typedef std::map<gamemap::location,unit> units_map;
//find the unit that can recruit
units_map::const_iterator u = units.begin();
for(; u != units.end(); ++u) {
if(u->second.can_recruit() && (int)u->second.side() == side) {
break;
}
}
if(u == units.end() && (need_castle || !map.on_board(recruit_location))) {
return _("You don't have a leader to recruit with.");
}
wassert(u != units.end() || !need_castle);
if(need_castle && map.is_keep(u->first) == false) {
LOG_NG << "Leader not on start: leader is on " << u->first << '\n';
return _("You must have your leader on a keep to recruit or recall units.");
}
if(need_castle) {
if (units.find(recruit_location) != units.end() ||
!can_recruit_on(map, u->first, recruit_location)) {
recruit_location = gamemap::location();
}
}
if(!map.on_board(recruit_location)) {
recruit_location = find_vacant_tile(map,units,u->first,
need_castle ? VACANT_CASTLE : VACANT_ANY);
} else if(units.count(recruit_location) == 1) {
recruit_location = find_vacant_tile(map,units,recruit_location,VACANT_ANY);
}
if(!map.on_board(recruit_location)) {
return _("There are no vacant castle tiles in which to recruit a unit.");
}
if(full_movement) {
new_unit.set_movement(new_unit.total_movement());
} else {
new_unit.set_movement(0);
new_unit.set_attacks(0);
}
new_unit.heal_all();
const bool show = disp != NULL && !disp->turbo() &&
!disp->fogged(recruit_location.x,recruit_location.y);
if(show) {
disp->draw(true,true);
}
units.insert(std::pair<gamemap::location,unit>( recruit_location,new_unit));
unit_map::iterator un = units.find(recruit_location);
if(show) {
un->second.set_recruited(*disp);
disp->scroll_to_tile(recruit_location.x,recruit_location.y,display::ONSCREEN);
while(!un->second.get_animation()->animation_finished()) {
disp->draw_tile(recruit_location.x,recruit_location.y);
disp->update_display();
events::pump();
if(!disp->turbo()) SDL_Delay(10);
}
un->second.set_standing(*disp);
}
LOG_NG << "firing recruit event\n";
game_events::fire("recruit",recruit_location);
checksumstream cs;
config cfg_unit;
new_unit.write(cfg_unit);
::write_compressed(cs,cfg_unit);
const config* ran_results = get_random_results();
if(ran_results != NULL) {
unsigned long rc = lexical_cast_default<unsigned long>
((*ran_results)["checksum"], 0);
if((*ran_results)["checksum"].empty() || rc != cs.checksum()) {
ERR_NW << "SYNC: In recruit " << new_unit.id() <<
": has checksum " << cs.checksum() <<
" while datasource has checksum " <<
rc << "\n";
::write(std::cerr, cfg_unit);
// FIXME: this was not playtested, so I will disable it
// for release.
//if (!game_config::ignore_replay_errors) throw replay::error();
}
} else {
config cfg;
cfg["checksum"] = lexical_cast<std::string>(cs.checksum());
set_random_results(cfg);
}
return std::string();
}
void validate_recruit_unit()
{
}
gamemap::location under_leadership(const units_map& units,
const gamemap::location& loc, int* bonus)
{
const unit_map::const_iterator un = units.find(loc);
if(un == units.end()) {
return gamemap::location::null_location;
}
int best_bonus = un->second.get_abilities("leadership",loc).highest("value").first;
if(bonus) {
*bonus = best_bonus;
}
return un->second.get_abilities("leadership",loc).highest("value").second;
/*
gamemap::location adjacent[6];
get_adjacent_tiles(loc,adjacent);
const int side = un->second.side();
const int level = un->second.level();
int bonus_tracker = 0;
int current_bonus = 0;
gamemap::location best_loc;
for(int i = 0; i != 6; ++i) {
const unit_map::const_iterator it = units.find(adjacent[i]);
if(it != units.end() && (int)it->second.side() == side &&
it->second.get_ability_bool("leader",adjacent[i]) &&
it->second.get_state("stoned") != "yes") {
current_bonus = maximum<int>(current_bonus,it->second.get_abilities("leadership",adjacent[i]).highest("value"));
if(current_bonus != bonus_tracker) {
best_loc = adjacent[i];
bonus_tracker = current_bonus;
}
}
}
if(bonus) {
*bonus = current_bonus;
}
return best_loc;
*/
}
double pr_atleast(int m, double p, int n, int d)
{
// calculate Pr[A does damage in [m,...)], where
// p probability to hit, n swings, d damage/swing
double P = 0.0;
// 0 damage can happen when unit has no attack of this type
if(d == 0)
return (m <= 0) ? 1.0 : P;
for(int k = (m + d - 1)/d; k <= n; ++k) {
double r = 1.0;
const int k2 = (k > n - k) ? (n - k) : k;
for(int i = 0; i < k2; ++i) { r *= (n-i); r /= (k2-i); }
P += r * pow(p, k) * pow(1-p, n-k);
}
return P;
}
double pr_between(int mn, int mx, double p, int n, int d)
{
// calculate Pr[A does damage in [mn,mx)], where
// p probability to hit, n swings, d damage/swing
return pr_atleast(mn, p, n, d) - pr_atleast(mx, p, n, d);
}
int reduce(int i, int u, int v)
{
// i-th swingpair, but reduce since u < v
if(i == 0)
return i;
else
return ((i-1) / v)*u + minimum<int>(u, ((i-1) % v) + 1);
}
double pr_kills_during(const int hpa, const int dmga, const double pa,
const int swa, const int hpb, const int dmgb, const double pb,
const int swb, const int n, const bool second)
{
if ((swa < swb) && (swa < (n-1) % swb + 1)) // not our move
return 0.0;
// A kills B during swing n, and is it second?
// take into account where one unit doesn't get all n swings
const double t1 = pr_between(hpb - dmga, hpb, pa,
(swa<swb) ? reduce(n-1, swa, swb) : (n-1), dmga);
const int n2 = second ? n : (n - 1);
const double t2 = 1.0 - pr_atleast(hpa, pb,
(swa>swb) ? reduce(n2, swb, swa) : n2, dmgb);
return t1 * pa * t2;
}
battle_stats evaluate_battle_stats(const gamemap& map,
std::vector<team>& teams,
const gamemap::location& attacker,
const gamemap::location& defender,
int attack_with,
units_map& units,
const gamestatus& state,
const game_data& gamedata,
gamemap::TERRAIN attacker_terrain_override,
battle_stats_strings *strings)
{
battle_stats res;
LOG_NG << "Evaluating battle stats...\n";
res.attack_with = attack_with;
if (strings)
strings->defend_name = _("none");
const units_map::iterator a = units.find(attacker);
const units_map::iterator d = units.find(defender);
wassert(a != units.end());
wassert(d != units.end());
const gamemap::TERRAIN attacker_terrain = attacker_terrain_override ?
attacker_terrain_override : map[attacker.x][attacker.y];
const gamemap::TERRAIN defender_terrain = map[defender.x][defender.y];
res.attacker_hp = a->second.hitpoints();
res.defender_hp = d->second.hitpoints();
res.chance_to_hit_attacker = a->second.defense_modifier(attacker_terrain);
res.chance_to_hit_defender = d->second.defense_modifier(defender_terrain);
std::vector<attack_type>& attacker_attacks = a->second.attacks();
std::vector<attack_type>& defender_attacks = d->second.attacks();
wassert((unsigned)attack_with < attacker_attacks.size());
attack_type& attack = attacker_attacks[attack_with];
double best_defend_rating = 0.0;
int defend_with = -1;
res.ndefends = 0;
LOG_NG << "Finding defender weapon...\n";
for(int defend_option = 0; defend_option != int(defender_attacks.size()); ++defend_option) {
if(defender_attacks[defend_option].range() == attack.range()) {
if (defender_attacks[defend_option].defense_weight() > 0) {
attack_type& defend = defender_attacks[defend_option];
attack.set_specials_context(attacker,defender,&gamedata,&units,&map,&state,&teams,true,&defend);
defend.set_specials_context(attacker,defender,&gamedata,&units,&map,&state,&teams,false,&attack);
int d_nattacks = defend.num_attacks();
weapon_special_list swarm = defend.get_specials("attacks");
if(!swarm.empty()) {
int swarm_min_attacks = swarm.highest("attacks_max");
int swarm_max_attacks = swarm.highest("attacks_min");
int hitp = d->second.hitpoints();
int mhitp = d->second.max_hitpoints();
d_nattacks = swarm_min_attacks + swarm_max_attacks * hitp / mhitp;
}
// calculate damage
int bonus = 100;
int divisor = 100;
int base_damage = defend.damage();
int resistance_modifier = a->second.damage_from(defend,true,a->first);
{ // modify damage
weapon_special_list dmg_specials = defend.get_specials("damage");
int dmg_def = base_damage;
int dmg_def_mod = 0;
int dmg_def_mul_cum = 1;
int dmg_def_mul_ncum = 1;
bool dmg_def_set = false;
bool dmg_def_mod_set = false;
for(config::child_list::const_iterator dmg_it = dmg_specials.cfgs.begin(); dmg_it != dmg_specials.cfgs.end(); ++dmg_it) {
if((**dmg_it)["cumulative"]=="yes") {
dmg_def = maximum<int>(dmg_def,lexical_cast_default<int>((**dmg_it)["value"]));
dmg_def_mul_cum *= lexical_cast_default<int>((**dmg_it)["multiply"]);
if(dmg_def_mod_set && (**dmg_it)["add"] != "") {
dmg_def_mod += lexical_cast_default<int>((**dmg_it)["add"]);
} else if((**dmg_it)["add"] != "") {
dmg_def_mod = lexical_cast_default<int>((**dmg_it)["add"]);
dmg_def_mod_set = true;
}
} else {
dmg_def_mul_ncum = maximum<int>(dmg_def_mul_ncum,lexical_cast_default<int>((**dmg_it)["multiply"]));
if(dmg_def_set) {
dmg_def = maximum<int>(dmg_def,lexical_cast_default<int>((**dmg_it)["value"]));
} else {
dmg_def = lexical_cast_default<int>((**dmg_it)["value"]);
dmg_def_set = true;
}
if(dmg_def_mod_set && (**dmg_it)["add"] != "") {
dmg_def_mod = maximum<int>(dmg_def,lexical_cast_default<int>((**dmg_it)["add"]));
} else if((**dmg_it)["add"] != "") {
dmg_def_mod = lexical_cast_default<int>((**dmg_it)["add"]);
dmg_def_mod_set = true;
}
}
}
base_damage = (dmg_def * maximum<int>(dmg_def_mul_cum,dmg_def_mul_ncum)) + dmg_def_mod;
}
const int tod_modifier = combat_modifier(state,units,d->first,d->second.alignment(),map);
bonus += tod_modifier;
int leader_bonus = 0;
if (under_leadership(units, defender, &leader_bonus).valid()) {
bonus += leader_bonus;
}
if (d->second.get_state("slowed") == "yes") {
divisor *= 2;
}
bonus *= resistance_modifier;
divisor *= 100;
const int final_damage = round_damage(base_damage, bonus, divisor);
const double rating = a->second.damage_from(defender_attacks[defend_option],true,a->first)
*final_damage
*d_nattacks
*defender_attacks[defend_option].defense_weight();
if(defend_with == -1 || rating > best_defend_rating) {
best_defend_rating = rating;
defend_with = defend_option;
}
}
}
}
res.defend_with = defend_with;
int defend_weapon = defend_with == -1 ? 0 : defend_with;
attack_type& defend = defender_attacks[defend_weapon];
attack.set_specials_context(attacker,defender,&gamedata,&units,&map,&state,&teams,true,&defend);
defend.set_specials_context(attacker,defender,&gamedata,&units,&map,&state,&teams,false,&attack);
LOG_NG << "getting weapon specials...\n";
static const std::string to_the_death_string("berserk");
res.rounds = attack.get_specials(to_the_death_string).highest("rounds");
res.defender_strikes_first = false;
static const std::string backstab_string("backstab");
weapon_special_list plague = attack.get_specials("plague");
static const std::string plague_string("plague");
res.attacker_plague = d->second.get_state("not_living") != "yes" &&
(!plague.empty()) &&
strcmp(d->second.undead_variation().c_str(),"null") &&
!map.is_village(defender);
if(!plague.empty()) {
if((*plague.cfgs.front())["type"] == "") {
res.attacker_plague_type = a->second.id();
} else {
res.attacker_plague_type = (*plague.cfgs.front())["type"];
}
}
res.defender_plague = false;
static const std::string slow_string("slow");
res.attacker_slows = attack.get_special_bool(slow_string);
static const std::string poison_string("poison");
res.attacker_poisons = attack.get_special_bool(poison_string);
static const std::string stones_string("stones");
res.attacker_stones = attack.get_special_bool(stones_string);
{ // modify chance to hit
weapon_special_list cth_specials = attack.get_specials("chance_to_hit");
int cth_def = res.chance_to_hit_defender;
int cth_def_mod = 0;
bool cth_def_set = false;
bool cth_def_mod_set = false;
for(config::child_list::const_iterator cth_it = cth_specials.cfgs.begin(); cth_it != cth_specials.cfgs.end(); ++cth_it) {
if((**cth_it)["cumulative"]=="yes") {
cth_def = maximum<int>(cth_def,lexical_cast_default<int>((**cth_it)["value"]));
if(cth_def_mod_set && (**cth_it)["add"] != "") {
cth_def_mod += lexical_cast_default<int>((**cth_it)["add"]);
} else if((**cth_it)["add"] != "") {
cth_def_mod = lexical_cast_default<int>((**cth_it)["add"]);
cth_def_mod_set = true;
}
} else {
if(cth_def_set) {
cth_def = maximum<int>(cth_def,lexical_cast_default<int>((**cth_it)["value"]));
} else {
cth_def = lexical_cast_default<int>((**cth_it)["value"]);
cth_def_set = true;
}
if(cth_def_mod_set && (**cth_it)["add"] != "") {
cth_def_mod = maximum<int>(cth_def,lexical_cast_default<int>((**cth_it)["add"]));
} else if((**cth_it)["add"] != "") {
cth_def_mod = lexical_cast_default<int>((**cth_it)["add"]);
cth_def_mod_set = true;
}
}
}
res.chance_to_hit_defender = cth_def + cth_def_mod;
}
// compute swarm attacks;
weapon_special_list swarm = attack.get_specials("attacks");
if(!swarm.empty()) {
int swarm_min_attacks = swarm.highest("attacks_max");
int swarm_max_attacks = swarm.highest("attacks_min");
int hitp = a->second.hitpoints();
int mhitp = a->second.max_hitpoints();
res.nattacks = swarm_min_attacks + swarm_max_attacks * hitp / mhitp;
} else {
res.nattacks = attack.num_attacks();
}
if (strings) {
strings->attack_name = attack.name();
strings->attack_type = egettext(attack.type().c_str());
strings->attack_special = attack.weapon_specials();
strings->attack_icon = attack.icon();
strings->range = gettext(N_(attack.range().c_str()));
}
const bool counterattack = defend_with != -1;
static const std::string EMPTY_COLUMN = std::string(1, COLUMN_SEPARATOR) + ' ' + COLUMN_SEPARATOR;
res.damage_attacker_takes = 0;
res.amount_attacker_drains = 0;
res.amount_defender_drains = 0;
if (counterattack) {
res.rounds = maximum<int>(res.rounds,defend.get_specials(to_the_death_string).highest("rounds"));
{ // modify chance to hit
weapon_special_list cth_specials = defend.get_specials("chance_to_hit");
int cth_def = res.chance_to_hit_attacker;
int cth_def_mod = 0;
bool cth_def_set = false;
bool cth_def_mod_set = false;
for(config::child_list::const_iterator cth_it = cth_specials.cfgs.begin(); cth_it != cth_specials.cfgs.end(); ++cth_it) {
if((**cth_it)["cumulative"]=="yes") {
cth_def = maximum<int>(cth_def,lexical_cast_default<int>((**cth_it)["value"]));
if(cth_def_mod_set && (**cth_it)["add"] != "") {
cth_def_mod += lexical_cast_default<int>((**cth_it)["add"]);
} else if((**cth_it)["add"] != "") {
cth_def_mod = lexical_cast_default<int>((**cth_it)["add"]);
cth_def_mod_set = true;
}
} else {
if(cth_def_set) {
cth_def = maximum<int>(cth_def,lexical_cast_default<int>((**cth_it)["value"]));
} else {
cth_def = lexical_cast_default<int>((**cth_it)["value"]);
cth_def_set = true;
}
if(cth_def_mod_set && (**cth_it)["add"] != "") {
cth_def_mod = maximum<int>(cth_def,lexical_cast_default<int>((**cth_it)["add"]));
} else if((**cth_it)["add"] != "") {
cth_def_mod = lexical_cast_default<int>((**cth_it)["add"]);
cth_def_mod_set = true;
}
}
}
res.chance_to_hit_attacker = cth_def + cth_def_mod;
}
int bonus = 100;
int divisor = 100;
int base_damage = defend.damage();
int resistance_modifier = a->second.damage_from(defend,true,a->first);
{ // modify damage
weapon_special_list dmg_specials = defend.get_specials("damage");
int dmg_def = base_damage;
int dmg_def_mod = 0;
int dmg_def_mul_cum = 1;
int dmg_def_mul_ncum = 1;
bool dmg_def_set = false;
bool dmg_def_mod_set = false;
for(config::child_list::const_iterator dmg_it = dmg_specials.cfgs.begin(); dmg_it != dmg_specials.cfgs.end(); ++dmg_it) {
if((**dmg_it)["cumulative"]=="yes") {
dmg_def = maximum<int>(dmg_def,lexical_cast_default<int>((**dmg_it)["value"]));
dmg_def_mul_cum *= lexical_cast_default<int>((**dmg_it)["multiply"]);
if(dmg_def_mod_set && (**dmg_it)["add"] != "") {
dmg_def_mod += lexical_cast_default<int>((**dmg_it)["add"]);
} else if((**dmg_it)["add"] != "") {
dmg_def_mod = lexical_cast_default<int>((**dmg_it)["add"]);
dmg_def_mod_set = true;
}
} else {
dmg_def_mul_ncum = maximum<int>(dmg_def_mul_ncum,lexical_cast_default<int>((**dmg_it)["multiply"]));
if(dmg_def_set) {
dmg_def = maximum<int>(dmg_def,lexical_cast_default<int>((**dmg_it)["value"]));
} else {
dmg_def = lexical_cast_default<int>((**dmg_it)["value"]);
dmg_def_set = true;
}
if(dmg_def_mod_set && (**dmg_it)["add"] != "") {
dmg_def_mod = maximum<int>(dmg_def,lexical_cast_default<int>((**dmg_it)["add"]));
} else if((**dmg_it)["add"] != "") {
dmg_def_mod = lexical_cast_default<int>((**dmg_it)["add"]);
dmg_def_mod_set = true;
}
}
}
base_damage = (dmg_def * maximum<int>(dmg_def_mul_cum,dmg_def_mul_ncum)) + dmg_def_mod;
}
if (strings) {
std::stringstream str_base;
str_base << _("base damage") << COLUMN_SEPARATOR << base_damage;
strings->defend_calculations.push_back(str_base.str());
}
const int tod_modifier = combat_modifier(state,units,d->first,d->second.alignment(),map);
bonus += tod_modifier;
if (strings && tod_modifier != 0) {
std::stringstream str_mod;
const time_of_day& tod = timeofday_at(state,units,d->first,map);
str_mod << tod.name << EMPTY_COLUMN << (tod_modifier > 0 ? "+" : "") << tod_modifier << '%';
strings->defend_calculations.push_back(str_mod.str());
}
int leader_bonus = 0;
if (under_leadership(units, defender, &leader_bonus).valid()) {
bonus += leader_bonus;
if (strings) {
std::stringstream str;
str << _("leadership") << EMPTY_COLUMN << '+' << leader_bonus << '%';
strings->defend_calculations.push_back(str.str());
}
}
/*
if (charge) {
bonus *= 2;
if (strings) {
std::stringstream str;
str << _("charge") << EMPTY_COLUMN << _("Doubled");
strings->defend_calculations.push_back(str.str());
}
}
*/
if (d->second.get_state("slowed") == "yes") {
divisor *= 2;
if (strings) {
std::stringstream str;
str << _("slowed") << EMPTY_COLUMN << _("Halved");
strings->defend_calculations.push_back(str.str());
}
}
if (strings && resistance_modifier != 100) {
const int resist = resistance_modifier - 100;
std::stringstream str_resist;
str_resist << gettext(resist < 0 ? N_("attacker resistance vs") : N_("attacker vulnerability vs"))
<< ' ' << gettext(defend.type().c_str()) << EMPTY_COLUMN
<< (resist > 0 ? "+" : "") << resist << '%';
strings->defend_calculations.push_back(str_resist.str());
}
bonus *= resistance_modifier;
divisor *= 100;
const int final_damage = round_damage(base_damage, bonus, divisor);
res.damage_attacker_takes = final_damage;
if (strings) {
const int difference = final_damage - base_damage;
std::stringstream str;
str << _("total damage") << COLUMN_SEPARATOR << res.damage_attacker_takes
<< COLUMN_SEPARATOR << (difference >= 0 ? "+" : "")
<< difference;
strings->defend_calculations.push_back(str.str());
}
// compute swarm attacks;
weapon_special_list swarm = defend.get_specials("attacks");
if(!swarm.empty()) {
int swarm_min_attacks = swarm.highest("attacks_max");
int swarm_max_attacks = swarm.highest("attacks_min");
int hitp = d->second.hitpoints();
int mhitp = d->second.max_hitpoints();
res.ndefends = swarm_min_attacks + swarm_max_attacks * hitp / mhitp;
} else {
res.ndefends = defend.num_attacks();
}
if (strings) {
strings->defend_name = defend.name();
strings->defend_type = egettext(defend.type().c_str());
strings->defend_special = defend.weapon_specials();
strings->defend_icon = defend.icon();
}
//if the defender drains, and the attacker is a living creature, then
//the defender will drain for half the damage it does
if (defend.get_special_bool("drains") && a->second.get_state("not_living") != "yes") {
res.amount_defender_drains = res.damage_attacker_takes/2;
}
weapon_special_list defend_plague = attack.get_specials("plague");
res.defender_plague = a->second.get_state("not_living") != "yes" &&
(!defend_plague.empty()) &&
strcmp(a->second.undead_variation().c_str(),"null") &&
!map.is_village(attacker);
if(!plague.empty()) {
if((*plague.cfgs.front())["type"] == "") {
res.defender_plague_type = d->second.id();
} else {
res.defender_plague_type = (*plague.cfgs.front())["type"];
}
}
res.defender_slows = (defend.get_special_bool(slow_string));
res.defender_poisons = (defend.get_special_bool(poison_string));
res.defender_stones = (defend.get_special_bool(stones_string));
static const std::string first_strike = "firststrike";
res.defender_strikes_first = defend.get_special_bool(first_strike) && !attack.get_special_bool(first_strike);
}
int bonus = 100;
int divisor = 100;
int base_damage = attack.damage();
int resistance_modifier = d->second.damage_from(attack,false,d->first);
{ // modify damage
weapon_special_list dmg_specials = attack.get_specials("damage");
int dmg_def = base_damage;
int dmg_def_mod = 0;
int dmg_def_mul_cum = 1;
int dmg_def_mul_ncum = 1;
bool dmg_def_set = false;
bool dmg_def_mod_set = false;
for(config::child_list::const_iterator dmg_it = dmg_specials.cfgs.begin(); dmg_it != dmg_specials.cfgs.end(); ++dmg_it) {
if((**dmg_it)["cumulative"]=="yes") {
dmg_def = maximum<int>(dmg_def,lexical_cast_default<int>((**dmg_it)["value"]));
dmg_def_mul_cum *= lexical_cast_default<int>((**dmg_it)["multiply"]);
if(dmg_def_mod_set && (**dmg_it)["add"] != "") {
dmg_def_mod += lexical_cast_default<int>((**dmg_it)["add"]);
} else if((**dmg_it)["add"] != "") {
dmg_def_mod = lexical_cast_default<int>((**dmg_it)["add"]);
dmg_def_mod_set = true;
}
} else {
dmg_def_mul_ncum = maximum<int>(dmg_def_mul_ncum,lexical_cast_default<int>((**dmg_it)["multiply"]));
if(dmg_def_set) {
dmg_def = maximum<int>(dmg_def,lexical_cast_default<int>((**dmg_it)["value"]));
} else {
dmg_def = dmg_def,lexical_cast_default<int>((**dmg_it)["value"]);
dmg_def_set = true;
}
if(dmg_def_mod_set && (**dmg_it)["add"] != "") {
dmg_def_mod = maximum<int>(dmg_def,lexical_cast_default<int>((**dmg_it)["add"]));
} else if((**dmg_it)["add"] != "") {
dmg_def_mod = lexical_cast_default<int>((**dmg_it)["add"]);
dmg_def_mod_set = true;
}
}
}
base_damage = (dmg_def*maximum<int>(dmg_def_mul_cum,dmg_def_mul_ncum)) + dmg_def_mod;
}
if (strings) {
std::stringstream str_base;
str_base << _("base damage") << COLUMN_SEPARATOR << base_damage << COLUMN_SEPARATOR;
strings->attack_calculations.push_back(str_base.str());
}
const int tod_modifier = combat_modifier(state,units,a->first,a->second.alignment(),map);
bonus += tod_modifier;
if (strings && tod_modifier != 0) {
std::stringstream str_mod;
const time_of_day& tod = timeofday_at(state,units,a->first,map);
str_mod << tod.name << EMPTY_COLUMN << (tod_modifier > 0 ? "+" : "") << tod_modifier << '%';
strings->attack_calculations.push_back(str_mod.str());
}
int leader_bonus = 0;
if (under_leadership(units,attacker,&leader_bonus).valid()) {
bonus += leader_bonus;
if (strings) {
std::stringstream str;
str << _("leadership") << EMPTY_COLUMN << '+' << leader_bonus << '%';
strings->attack_calculations.push_back(str.str());
}
}
/*
if (charge) {
bonus *= 2;
if (strings) {
std::stringstream str;
str << _("charge") << EMPTY_COLUMN << _("Doubled");
strings->attack_calculations.push_back(str.str());
}
}
*/
if (attack.get_special_bool(backstab_string)) {
bonus *= 2;
if (strings) {
std::stringstream str;
str << _("backstab") << EMPTY_COLUMN << _("Doubled");
strings->attack_calculations.push_back(str.str());
}
}
if (strings && resistance_modifier != 100) {
const int resist = resistance_modifier - 100;
std::stringstream str_resist;
str_resist << gettext(resist < 0 ? N_("defender resistance vs") : N_("defender vulnerability vs"))
<< ' ' << gettext(attack.type().c_str());
// if(steadfast && resistance_modifier < 100) {
// str_resist << ' ' << _(" (+steadfast)");
// }
str_resist << EMPTY_COLUMN
<< (resist > 0 ? "+" : "") << resist << '%';
strings->attack_calculations.push_back(str_resist.str());
}
if (a->second.get_state("slowed") == "yes") {
divisor *= 2;
if (strings) {
std::stringstream str;
str << _("slowed") << EMPTY_COLUMN << _("Halved");
strings->attack_calculations.push_back(str.str());
}
}
bonus *= resistance_modifier;
divisor *= 100;
const int final_damage = round_damage(base_damage, bonus, divisor);
res.damage_defender_takes = final_damage;
if (strings) {
const int difference = final_damage - base_damage;
std::stringstream str;
str << _("total damage") << COLUMN_SEPARATOR << res.damage_defender_takes
<< COLUMN_SEPARATOR << (difference >= 0 ? "+" : "")
<< difference;
strings->attack_calculations.push_back(str.str());
}
//if the attacker drains, and the defender is a living creature, then
//the attacker will drain for half the damage it does
if(attack.get_special_bool("drains") && d->second.get_state("not_living") != "yes") {
res.amount_attacker_drains = res.damage_defender_takes/2;
}
// FIXME: doesn't take into account berserk+slow or drain
if (strings && res.amount_attacker_drains == 0 &&
res.amount_defender_drains == 0 &&
!(res.rounds &&
(res.attacker_slows || res.defender_slows)))
{
const int maxrounds = (res.rounds ? res.rounds : 1);
const int hpa = res.attacker_hp;
const int hpb = res.defender_hp;
const int dmga = res.damage_defender_takes;
const int dmgb = res.damage_attacker_takes;
const double pa = res.chance_to_hit_defender/100.0;
const double pb = res.chance_to_hit_attacker/100.0;
const int swa = res.nattacks;
const int swb = res.ndefends;
double P1 = 0;
for(int n = 1; n <= maxrounds*maximum<int>(swa,swb); ++n)
P1 += pr_kills_during(hpa, dmga, pa, swa,
hpb, dmgb, pb, swb, n, res.defender_strikes_first);
const double P3 = (1.0 - pr_atleast(hpb,pa,maxrounds*swa,dmga))
* (1.0 - pr_atleast(hpa,pb,maxrounds*swb,dmgb));
std::stringstream str;
if (P3 > 0.99) {
str << _("(both should survive)") << EMPTY_COLUMN;
} else {
str << _("% Pr[kills/killed by/both survive]")
<< EMPTY_COLUMN << (int)(P1*100+0.5)
<< '/' << (int)((1-P1-P3)*100+0.5)
<< '/' << (int)(P3*100+0.5);
}
strings->attack_calculations.push_back(str.str());
}
LOG_NG << "done...\n";
return res;
}
static std::string unit_dump(std::pair< gamemap::location, unit > const &u)
{
std::stringstream s;
s << u.second.id() << " (" << u.first.x + 1 << ',' << u.first.y + 1 << ')';
return s.str();
}
void attack(display& gui, const gamemap& map,
std::vector<team>& teams,
gamemap::location attacker,
gamemap::location defender,
int attack_with,
units_map& units,
const gamestatus& state,
const game_data& info,
bool update_display)
{
//stop the user from issuing any commands while the units are fighting
const command_disabler disable_commands;
units_map::iterator a = units.find(attacker);
units_map::iterator d = units.find(defender);
if(a == units.end() || d == units.end()) {
return;
}
bool OOS_error = false;
int attackerxp = d->second.level();
int defenderxp = a->second.level();
a->second.set_attacks(a->second.attacks_left()-1);
a->second.set_movement(a->second.movement_left()-a->second.attacks()[attack_with].movement_used());
d->second.set_resting(false);
//if the attacker was invisible, she isn't anymore!
static const std::string hides("hides");
a->second.set_state(hides,"");
battle_stats stats = evaluate_battle_stats(map, teams, attacker,
defender, attack_with,
units, state, info);
LOG_NG << "getting attack statistics\n";
statistics::attack_context attack_stats(a->second,d->second,stats);
LOG_NG << "firing attack event\n";
config dat;
dat.add_child("first");
dat.add_child("second");
(*(dat.child("first")))["weapon"]=a->second.attacks()[attack_with].name();
(*(dat.child("second")))["weapon"]=stats.defend_with != -1 ? d->second.attacks()[stats.defend_with].name() : "none";
game_events::fire("attack",attacker,defender,dat);
//the event could have killed either the attacker or
//defender, so we have to make sure they still exist
a = units.find(attacker);
d = units.find(defender);
if(a == units.end() || d == units.end() || (attack_with != -1 && size_t(attack_with) >= a->second.attacks().size()) || (stats.defend_with != -1 && size_t(stats.defend_with) >= d->second.attacks().size())) {
return;
}
int orig_attacks = stats.nattacks;
int orig_defends = stats.ndefends;
int to_the_death = stats.rounds ? stats.rounds : 0;
static const std::string poison_string("poison");
while(stats.nattacks > 0 || stats.ndefends > 0) {
LOG_NG << "start of attack loop...\n";
if(stats.nattacks > 0 && stats.defender_strikes_first == false) {
const int ran_num = get_random();
bool hits = (ran_num%100) < stats.chance_to_hit_defender;
int damage_defender_takes;
if(hits) {
damage_defender_takes = stats.damage_defender_takes;
} else {
damage_defender_takes = 0;
}
//make sure that if we're serializing a game here,
//we got the same results as the game did originally
const config* ran_results = get_random_results();
if(ran_results != NULL) {
const int results_chance = atoi((*ran_results)["chance"].c_str());
const bool results_hits = (*ran_results)["hits"] == "yes";
const int results_damage = atoi((*ran_results)["damage"].c_str());
if(results_chance != stats.chance_to_hit_defender) {
ERR_NW << "SYNC: In attack " << unit_dump(*a) << " vs " << unit_dump(*d)
<< ": chance to hit defender is inconsistent. Data source: "
<< results_chance << "; Calculation: " << stats.chance_to_hit_defender
<< " (over-riding game calculations with data source results)\n";
stats.chance_to_hit_defender = results_chance;
OOS_error = true;
}
if(hits != results_hits) {
ERR_NW << "SYNC: In attack " << unit_dump(*a) << " vs " << unit_dump(*d)
<< ": the data source says the hit was "
<< (results_hits ? "successful" : "unsuccessful")
<< ", while in-game calculations say the hit was "
<< (hits ? "successful" : "unsuccessful")
<< " random number: " << ran_num << " = "
<< (ran_num%100) << "/" << results_chance
<< " (over-riding game calculations with data source results)\n";
hits = results_hits;
OOS_error = true;
}
if(results_damage != damage_defender_takes) {
ERR_NW << "SYNC: In attack " << unit_dump(*a) << " vs " << unit_dump(*d)
<< ": the data source says the hit did " << results_damage
<< " damage, while in-game calculations show the hit doing "
<< damage_defender_takes
<< " damage (over-riding game calculations with data source results)\n";
damage_defender_takes = results_damage;
OOS_error = true;
}
}
bool dies = unit_display::unit_attack(gui,units,map,attacker,defender,
damage_defender_takes,
a->second.attacks()[attack_with],
update_display);
if(hits) {
const int defender_side = d->second.side();
const int attacker_side = a->second.side();
LOG_NG << "firing attacker_hits event\n";
config dat;
dat.add_child("first");
dat.add_child("second");
(*(dat.child("first")))["weapon"]=a->second.attacks()[attack_with].name();
(*(dat.child("second")))["weapon"]=stats.defend_with != -1 ? d->second.attacks()[stats.defend_with].name() : "none";
game_events::fire("attacker_hits",attacker,defender,dat);
a = units.find(attacker);
d = units.find(defender);
if(a == units.end() || d == units.end() || (attack_with != -1 && size_t(attack_with) >= a->second.attacks().size()) || (stats.defend_with != -1 && size_t(stats.defend_with)) >= d->second.attacks().size()) {
if (update_display){
recalculate_fog(map,state,info,units,teams,attacker_side-1);
recalculate_fog(map,state,info,units,teams,defender_side-1);
gui.recalculate_minimap();
gui.update_display();
}
LOG_NG << "firing attack_end event\n";
game_events::fire("attack_end",attacker,defender,dat);
a = units.find(attacker);
d = units.find(defender);
break;
}
} else {
const int defender_side = d->second.side();
const int attacker_side = a->second.side();
LOG_NG << "firing attacker_misses event\n";
config dat;
dat.add_child("first");
dat.add_child("second");
(*(dat.child("first")))["weapon"]=a->second.attacks()[attack_with].name();
(*(dat.child("second")))["weapon"]=stats.defend_with != -1 ? d->second.attacks()[stats.defend_with].name() : "none";
game_events::fire("attacker_misses",attacker,defender,dat);
a = units.find(attacker);
d = units.find(defender);
if(a == units.end() || d == units.end() || (attack_with != -1 && size_t(attack_with) >= a->second.attacks().size()) || (stats.defend_with != -1 && size_t(stats.defend_with) >= d->second.attacks().size())) {
if (update_display){
recalculate_fog(map,state,info,units,teams,attacker_side-1);
recalculate_fog(map,state,info,units,teams,defender_side-1);
gui.recalculate_minimap();
gui.update_display();
}
LOG_NG << "firing attack_end event\n";
game_events::fire("attack_end",attacker,defender,dat);
a = units.find(attacker);
d = units.find(defender);
break;
}
}
LOG_NG << "done attacking\n";
attack_stats.attack_result(hits ? (dies ? statistics::attack_context::KILLS : statistics::attack_context::HITS)
: statistics::attack_context::MISSES);
if(ran_results == NULL) {
log_scope2(engine, "setting random results");
config cfg;
cfg["hits"] = (hits ? "yes" : "no");
cfg["dies"] = (dies ? "yes" : "no");
cfg["damage"] = lexical_cast<std::string>(damage_defender_takes);
cfg["chance"] = lexical_cast<std::string>(stats.chance_to_hit_defender);
set_random_results(cfg);
} else {
const bool results_dies = (*ran_results)["dies"] == "yes";
if(results_dies != dies) {
ERR_NW << "SYNC: In attack " << unit_dump(*a) << " vs " << unit_dump(*d)
<< ": the data source the unit "
<< (results_dies ? "perished" : "survived")
<< " while in-game calculations show the unit "
<< (dies ? "perished" : "survived")
<< " (over-riding game calculations with data source results)\n";
dies = results_dies;
OOS_error = true;
}
}
if(dies || hits) {
if(stats.amount_attacker_drains > 0) {
int amount_drained;
amount_drained = stats.amount_attacker_drains;
char buf[50];
snprintf(buf,sizeof(buf),"%d",amount_drained);
if (update_display){
gui.float_label(a->first,buf,0,255,0);
}
a->second.heal(amount_drained);
}
}
if(dies) {//attacker kills defender
attackerxp = game_config::kill_experience*d->second.level();
if(d->second.level() == 0)
attackerxp = game_config::kill_experience/2;
a->second.get_experience(attackerxp);
gui.invalidate(a->first);
attackerxp = 0;
defenderxp = 0;
gamemap::location loc = d->first;
gamemap::location attacker_loc = a->first;
std::string undead_variation = d->second.undead_variation();
const int defender_side = d->second.side();
LOG_NG << "firing attack_end event\n";
game_events::fire("attack_end",attacker,defender,dat);
LOG_NG << "firing die event\n";
game_events::fire("die",loc,a->first);
d = units.find(loc);
a = units.end();
if(d != units.end() && d->second.hitpoints() <= 0) {
units.erase(d);
d = units.end();
}
//plague units make new units on the target hex
if(stats.attacker_plague) {
a = units.find(attacker_loc);
game_data::unit_type_map::const_iterator reanimitor;
LOG_NG<<"trying to reanimate "<<stats.attacker_plague_type<<std::endl;
reanimitor = info.unit_types.find(stats.attacker_plague_type);
LOG_NG<<"found unit type:"<<reanimitor->second.id()<<std::endl;
if(reanimitor != info.unit_types.end()) {
unit newunit=unit(&info,&units,&map,&state,&teams,&reanimitor->second,a->second.side(),true,true);
newunit.set_attacks(0);
//apply variation
if(strcmp(undead_variation.c_str(),"null")){
config mod;
config& variation=mod.add_child("effect");
variation["apply_to"]="variation";
variation["name"]=undead_variation;
newunit.add_modification("variation",mod);
newunit.heal_all();
}
units.insert(std::pair<gamemap::location,unit>(loc,newunit));
if (update_display){
gui.draw_tile(loc.x,loc.y);
}
}else{
LOG_NG<<"unit not reanimated"<<std::endl;
}
}
if (update_display){
recalculate_fog(map,state,info,units,teams,defender_side-1);
gui.recalculate_minimap();
gui.update_display();
}
break;
} else if(hits) {
if (stats.attacker_poisons &&
d->second.get_state("poisoned") != "yes" &&
d->second.get_state("not_living") != "yes") {
if (update_display){
gui.float_label(d->first,_("poisoned"),255,0,0);
}
d->second.set_state("poisoned","yes");
}
if(stats.attacker_slows && d->second.get_state("slowed") != "yes") {
if (update_display){
gui.float_label(d->first,_("slowed"),255,0,0);
}
d->second.set_state("slowed","yes");
stats.damage_attacker_takes = round_damage(stats.damage_attacker_takes,1,2);
}
//if the defender is turned to stone, the fight stops immediately
static const std::string stone_string("stone");
if (stats.attacker_stones) {
if (update_display){
gui.float_label(d->first,_("stone"),255,0,0);
}
d->second.set_state("stoned","yes");
stats.ndefends = 0;
stats.nattacks = 0;
game_events::fire(stone_string,d->first,a->first);
}
}
--stats.nattacks;
}
//if the defender got to strike first, they use it up here.
stats.defender_strikes_first = false;
if(stats.ndefends > 0 ) {
LOG_NG << "doing defender attack...\n";
const int ran_num = get_random();
bool hits = (ran_num%100) < stats.chance_to_hit_attacker;
int damage_attacker_takes;
if(hits) {
damage_attacker_takes = stats.damage_attacker_takes;
} else {
damage_attacker_takes = 0;
}
//make sure that if we're serializing a game here,
//we got the same results as the game did originally
const config* ran_results = get_random_results();
if(ran_results != NULL) {
const int results_chance = atoi((*ran_results)["chance"].c_str());
const bool results_hits = (*ran_results)["hits"] == "yes";
const int results_damage = atoi((*ran_results)["damage"].c_str());
if(results_chance != stats.chance_to_hit_attacker) {
ERR_NW << "SYNC: In defend " << unit_dump(*a) << " vs " << unit_dump(*d)
<< ": chance to hit attacker is inconsistent. Data source: "
<< results_chance << "; Calculation: " << stats.chance_to_hit_attacker
<< " (over-riding game calculations with data source results)\n";
stats.chance_to_hit_attacker = results_chance;
OOS_error = true;
}
if(hits != results_hits) {
ERR_NW << "SYNC: In defend " << unit_dump(*a) << " vs " << unit_dump(*d)
<< ": the data source says the hit was "
<< (results_hits ? "successful" : "unsuccessful")
<< ", while in-game calculations say the hit was "
<< (hits ? "successful" : "unsuccessful")
<< " random number: " << ran_num << " = " << (ran_num%100) << "/"
<< results_chance
<< " (over-riding game calculations with data source results)\n";
hits = results_hits;
OOS_error = true;
}
if(results_damage != damage_attacker_takes) {
ERR_NW << "SYNC: In defend " << unit_dump(*a) << " vs " << unit_dump(*d)
<< ": the data source says the hit did " << results_damage
<< " damage, while in-game calculations show the hit doing "
<< damage_attacker_takes
<< " damage (over-riding game calculations with data source results)\n";
damage_attacker_takes = results_damage;
OOS_error = true;
}
}
bool dies = unit_display::unit_attack(gui,units,map,defender,attacker,
damage_attacker_takes,
d->second.attacks()[stats.defend_with],
update_display);
if(hits) {
const int defender_side = d->second.side();
const int attacker_side = a->second.side();
LOG_NG << "firing defender_hits event\n";
config dat;
dat.add_child("first");
dat.add_child("second");
(*(dat.child("first")))["weapon"]=a->second.attacks()[attack_with].name();
(*(dat.child("second")))["weapon"]=stats.defend_with != -1 ? d->second.attacks()[stats.defend_with].name() : "none";
game_events::fire("defender_hits",attacker,defender,dat);
a = units.find(attacker);
d = units.find(defender);
if(a == units.end() || d == units.end() || (attack_with != -1 && size_t(attack_with) >= a->second.attacks().size()) || (stats.defend_with != -1 && size_t(stats.defend_with) >= d->second.attacks().size())) {
if (update_display){
recalculate_fog(map,state,info,units,teams,attacker_side-1);
recalculate_fog(map,state,info,units,teams,defender_side-1);
gui.recalculate_minimap();
gui.update_display();
}
LOG_NG << "firing attack_end event\n";
game_events::fire("attack_end",attacker,defender,dat);
break;
}
} else {
const int defender_side = d->second.side();
const int attacker_side = a->second.side();
LOG_NG << "firing defender_misses event\n";
config dat;
dat.add_child("first");
dat.add_child("second");
(*(dat.child("first")))["weapon"]=a->second.attacks()[attack_with].name();
(*(dat.child("second")))["weapon"]=stats.defend_with != -1 ? d->second.attacks()[stats.defend_with].name() : "none";
game_events::fire("defender_misses",attacker,defender,dat);
a = units.find(attacker);
d = units.find(defender);
if(a == units.end() || d == units.end() || (attack_with != -1 && size_t(attack_with) >= a->second.attacks().size()) || (stats.defend_with != -1 && size_t(stats.defend_with) >= d->second.attacks().size())) {
if (update_display){
recalculate_fog(map,state,info,units,teams,attacker_side-1);
recalculate_fog(map,state,info,units,teams,defender_side-1);
gui.recalculate_minimap();
gui.update_display();
}
LOG_NG << "firing attack_end event\n";
game_events::fire("attack_end",attacker,defender,dat);
break;
}
}
attack_stats.defend_result(hits ? (dies ? statistics::attack_context::KILLS : statistics::attack_context::HITS)
: statistics::attack_context::MISSES);
if(ran_results == NULL) {
config cfg;
cfg["hits"] = (hits ? "yes" : "no");
cfg["dies"] = (dies ? "yes" : "no");
cfg["damage"] = lexical_cast<std::string>(damage_attacker_takes);
cfg["chance"] = lexical_cast<std::string>(stats.chance_to_hit_attacker);
set_random_results(cfg);
} else {
const bool results_dies = (*ran_results)["dies"] == "yes";
if(results_dies != dies) {
ERR_NW << "SYNC: In defend " << unit_dump(*a) << " vs " << unit_dump(*d)
<< ": the data source the unit "
<< (results_dies ? "perished" : "survived")
<< " while in-game calculations show the unit "
<< (dies ? "perished" : "survived")
<< " (over-riding game calculations with data source results)\n";
dies = results_dies;
OOS_error = true;
}
}
if(hits || dies){
if(stats.amount_defender_drains > 0) {
int amount_drained;
amount_drained = stats.amount_defender_drains;
char buf[50];
snprintf(buf,sizeof(buf),"%d",amount_drained);
if (update_display){
gui.float_label(d->first,buf,0,255,0);
}
d->second.heal(amount_drained);
}
}
if(dies) {//defender kills attacker
defenderxp = game_config::kill_experience*a->second.level();
if(a->second.level() == 0)
defenderxp = game_config::kill_experience/2;
d->second.get_experience(defenderxp);
gui.invalidate(d->first);
defenderxp = 0;
attackerxp = 0;
std::string undead_variation = a->second.undead_variation();
gamemap::location loc = a->first;
gamemap::location defender_loc = d->first;
const int attacker_side = a->second.side();
LOG_NG << "firing attack_end event\n";
game_events::fire("attack_end",attacker,defender,dat);
LOG_NG << "firing die event\n";
game_events::fire("die",loc,d->first);
a = units.find(loc);
d = units.end();
if(a != units.end() && a->second.hitpoints() <= 0) {
units.erase(a);
a = units.end();
}
//plague units make new units on the target hex.
if(stats.defender_plague) {
d = units.find(defender_loc);
game_data::unit_type_map::const_iterator reanimitor;
LOG_NG<<"trying to reanimate "<<stats.defender_plague_type<<std::endl;
reanimitor = info.unit_types.find(stats.defender_plague_type);
LOG_NG<<"found unit type:"<<reanimitor->second.id()<<std::endl;
if(reanimitor != info.unit_types.end()) {
unit newunit=unit(&info,&units,&map,&state,&teams,&reanimitor->second,d->second.side(),true,true);
//apply variation
if(strcmp(undead_variation.c_str(),"null")){
config mod;
config& variation=mod.add_child("effect");
variation["apply_to"]="variation";
variation["name"]=undead_variation;
newunit.add_modification("variation",mod);
}
units.insert(std::pair<gamemap::location,unit>(loc,newunit));
if (update_display){
gui.draw_tile(loc.x,loc.y);
}
}else{
LOG_NG<<"unit not reanimated"<<std::endl;
}
}
if (update_display){
gui.recalculate_minimap();
gui.update_display();
recalculate_fog(map,state,info,units,teams,attacker_side-1);
}
break;
} else if(hits) {
if (stats.defender_poisons &&
a->second.get_state("poisoned") != "yes" &&
a->second.get_state("not_living") != "yes") {
if (update_display){
gui.float_label(a->first,_("poisoned"),255,0,0);
}
a->second.set_state("poisoned","yes");
}
if(stats.defender_slows && a->second.get_state("slowed") != "yes") {
if (update_display){
gui.float_label(a->first,_("slowed"),255,0,0);
}
a->second.set_state("slowed","yes");
stats.damage_defender_takes = round_damage(stats.damage_defender_takes,1,2);
}
//if the attacker is turned to stone, the fight stops immediately
static const std::string stone_string("stone");
if (stats.defender_stones) {
if (update_display){
gui.float_label(a->first,_("stone"),255,0,0);
}
a->second.set_state("stoned","yes");
stats.ndefends = 0;
stats.nattacks = 0;
game_events::fire(stone_string,a->first,d->first);
}
}
--stats.ndefends;
}
// continue the fight to death; if one of the units got stoned,
// either nattacks or ndefends is -1
if(to_the_death > 0 && stats.ndefends == 0 && stats.nattacks == 0) {
stats.nattacks = orig_attacks;
stats.ndefends = orig_defends;
--to_the_death;
}
if(stats.nattacks <= 0 && stats.ndefends <= 0) {
LOG_NG << "firing attack_end event\n";
game_events::fire("attack_end",attacker,defender,dat);
a = units.find(attacker);
d = units.find(defender);
}
}
if(attackerxp && a != units.end()) {
a->second.get_experience(attackerxp);
}
if(defenderxp && d != units.end()) {
d->second.get_experience(defenderxp);
}
if (update_display){
gui.invalidate_unit();
gui.invalidate(attacker);
gui.invalidate(defender);
gui.draw(true,true);
}
if(OOS_error) {
if (!game_config::ignore_replay_errors) {
throw replay::error();
}
}
}
int village_owner(const gamemap::location& loc, const std::vector<team>& teams)
{
for(size_t i = 0; i != teams.size(); ++i) {
if(teams[i].owns_village(loc))
return i;
}
return -1;
}
bool get_village(const gamemap::location& loc, std::vector<team>& teams,
size_t team_num, const unit_map& units)
{
return get_village(loc,teams,team_num,units,NULL);
}
bool get_village(const gamemap::location& loc, std::vector<team>& teams,
size_t team_num, const unit_map& units, int *action_timebonus)
{
if(team_num < teams.size() && teams[team_num].owns_village(loc)) {
return false;
}
const bool has_leader = find_leader(units,int(team_num+1)) != units.end();
bool grants_timebonus = false;
//we strip the village off all other sides, unless it is held by an ally
//and we don't have a leader (and thus can't occupy it)
for(std::vector<team>::iterator i = teams.begin(); i != teams.end(); ++i) {
const int side = i - teams.begin() + 1;
if(team_num >= teams.size() || has_leader || teams[team_num].is_enemy(side)) {
i->lose_village(loc);
if(team_num + 1 != side && action_timebonus) {
grants_timebonus = true;
}
}
}
if(grants_timebonus) {
teams[team_num].set_action_bonus_count(1 + teams[team_num].action_bonus_count());
*action_timebonus = 1;
}
if(team_num >= teams.size()) {
return false;
}
if(has_leader) {
return teams[team_num].get_village(loc);
}
return false;
}
unit_map::iterator find_leader(unit_map& units, int side)
{
for(unit_map::iterator i = units.begin(); i != units.end(); ++i) {
if((int)i->second.side() == side && i->second.can_recruit())
return i;
}
return units.end();
}
unit_map::const_iterator find_leader(const unit_map& units, int side)
{
for(unit_map::const_iterator i = units.begin(); i != units.end(); ++i) {
if((int)i->second.side() == side && i->second.can_recruit())
return i;
}
return units.end();
}
namespace {
struct patient
{
patient(unit &un, const gamemap::location& loc, bool allied)
: u(un), l(loc), healing(0), poison_stopped(false), ally(allied) { }
unit &u;
const gamemap::location& l;
// How much have we been healed?
int healing;
// Was our poison stopped / cured?
bool poison_stopped;
// Are we an ally? In which case we only get healed.
bool ally;
void heal(int amount, bool allies_too);
void harm(int amount);
void rest();
};
void patient::heal(int amount, bool allies_too)
{
if (ally && !allies_too)
return;
if (u.get_state("poisoned")=="yes") {
if (amount >= game_config::cure_amount)
u.set_state("poisoned","");
poison_stopped = true;
} else {
healing = minimum(u.max_hitpoints() - u.hitpoints(), amount);
std::cerr << "healing by " << lexical_cast<std::string>(healing) << " of potential "
<< lexical_cast<std::string>(u.max_hitpoints() - u.hitpoints()) << "\n";
}
}
void patient::harm(int amount)
{
if (ally)
return;
wassert(!healing && !poison_stopped);
healing = -minimum(u.hitpoints() - 1, amount);
}
void patient::rest()
{
if (ally)
return;
healing += minimum(u.max_hitpoints() - (u.hitpoints() + healing), game_config::rest_heal_amount);
std::cerr << "resting by " << lexical_cast<std::string>(minimum(u.max_hitpoints() - (u.hitpoints() + healing), game_config::rest_heal_amount)) << "\n";
}
// Find the best adjacent healer.
unit_map::iterator find_healer(const gamemap::location &loc, std::map<gamemap::location,unit>& units,
unsigned int side)
{
gamemap::location adjacent[6];
unit_map::iterator healer = units.end();
get_adjacent_tiles(loc, adjacent);
for (unsigned int n = 0; n != 6U; ++n) {
unit_map::iterator i = units.find(adjacent[n]);
if (i != units.end()) {
if (i->second.get_state("stoned")=="yes")
continue;
if (i->second.side() != side)
continue;
int healing = i->second.get_abilities("heals",i->first).highest("value").first;
if (healer == units.end() || healing > healer->second.get_abilities("heals",healer->first).highest("value").first)
healer = i;
}
}
return healer;
}
}
void reset_resting(std::map<gamemap::location,unit>& units, unsigned int side)
{
for (unit_map::iterator i = units.begin(); i != units.end(); ++i) {
if (i->second.side() == side)
i->second.set_resting(true);
}
}
// Simple algorithm: no maximum number of patients per healer.
void calculate_healing(display& disp, const gamestatus& status, const gamemap& map,
units_map& units, unsigned int side,
const std::vector<team>& teams, bool update_display)
{
// We look for all allied units, then we see if our healer is near them.
for (unit_map::iterator i = units.begin(); i != units.end(); ++i) {
if (teams[i->second.side()-1].is_enemy(side))
continue;
if (i->second.get_state("healable") == "no")
continue;
patient p(i->second, i->first,i->second.side() != side);
unit_map::iterator healer = units.end();
// FIXME: regenerates shouldn't prevent healing?
int regen = p.u.get_abilities("regenerate",p.l).highest("value").first;
if (regen)
p.heal(regen, false);
else if (map.gives_healing(i->first))
p.heal(game_config::cure_amount, false);
else {
healer = find_healer(i->first, units, side);
if (healer != units.end())
p.heal(healer->second.get_abilities("heals",healer->first).highest("value").first, true);
}
if (p.u.get_state("poisoned")=="yes" && !p.poison_stopped)
p.harm(game_config::cure_amount);
if (p.u.resting())
p.rest();
if (!p.healing && !p.poison_stopped)
continue;
if (disp.turbo() || recorder.is_skipping()
|| disp.fogged(i->first.x, i->first.y)
|| !update_display
|| (p.u.invisible(map.underlying_union_terrain(map[i->first.x][i->first.y]),
status.get_time_of_day().lawful_bonus,i->first,units,teams) &&
teams[disp.viewing_team()].is_enemy(side))) {
// Simple path.
if (p.healing > 0)
p.u.heal(p.healing);
else if (p.healing < 0)
p.u.take_hit(-p.healing);
continue;
}
// This is all the pretty stuff.
int start_time = 0;
disp.scroll_to_tile(i->first.x, i->first.y, display::ONSCREEN);
disp.select_hex(i->first);
if (healer != units.end()) {
healer->second.set_healing(disp);
start_time = healer->second.get_animation()->get_first_frame_time();
}
if (p.healing < 0) {
p.u.set_poisoned(disp, -p.healing);
start_time = minimum<int>(start_time, p.u.get_animation()->get_first_frame_time());
sound::play_sound("groan.wav");
disp.float_label(i->first, lexical_cast<std::string>(-p.healing), 255,0,0);
} else {
p.u.set_healed(disp, p.healing);
start_time = minimum<int>(start_time, p.u.get_animation()->get_first_frame_time());
sound::play_sound("heal.wav");
disp.float_label(i->first, lexical_cast<std::string>(p.healing), 0,255,0);
}
// restart both anims in a synchronized way
p.u.restart_animation(disp, start_time);
if (healer != units.end())
healer->second.restart_animation(disp, start_time);
bool finished;
do {
finished = (p.u.get_animation()->animation_finished());
disp.draw_tile(i->first.x, i->first.y);
if (healer != units.end()) {
finished &= healer->second.get_animation()->animation_finished();
disp.draw_tile(healer->first.x, healer->first.y);
}
if (p.healing > 0) {
p.u.heal(1);
--p.healing;
} else if (p.healing < 0) {
p.u.take_hit(1);
++p.healing;
}
finished &= (!p.healing);
disp.update_display();
events::pump();
SDL_Delay(10);
} while (!finished);
p.u.set_standing(disp);
if (healer != units.end())
healer->second.set_standing(disp);
disp.update_display();
events::pump();
}
}
unit get_advanced_unit(const game_data& info,
units_map& units,
const gamemap::location& loc, const std::string& advance_to)
{
const std::map<std::string,unit_type>::const_iterator new_type = info.unit_types.find(advance_to);
const units_map::iterator un = units.find(loc);
if(new_type != info.unit_types.end() && un != units.end()) {
unit new_unit(un->second);
new_unit.advance_to(&(new_type->second));
return new_unit;
} else {
throw game::game_error("Could not find the unit being advanced"
" to: " + advance_to);
}
}
void advance_unit(const game_data& info,
units_map& units,
gamemap::location loc, const std::string& advance_to)
{
if(units.count(loc) == 0) {
return;
}
const unit& new_unit = get_advanced_unit(info,units,loc,advance_to);
LOG_NG << "firing advance event\n";
game_events::fire("advance",loc);
statistics::advance_unit(new_unit);
preferences::encountered_units().insert(new_unit.id());
LOG_STREAM(info, config) << "Added '" << new_unit.id() << "' to encountered units\n";
units.erase(loc);
units.insert(std::pair<gamemap::location,unit>(loc,new_unit));
LOG_NG << "firing post_advance event\n";
game_events::fire("post_advance",loc);
}
void check_victory(units_map& units,
std::vector<team>& teams)
{
std::vector<int> seen_leaders;
for(units_map::const_iterator i = units.begin();
i != units.end(); ++i) {
if(i->second.can_recruit()) {
LOG_NG << "seen leader for side " << i->second.side() << "\n";
seen_leaders.push_back(i->second.side());
}
}
//clear villages for teams that have no leader
for(std::vector<team>::iterator tm = teams.begin(); tm != teams.end(); ++tm) {
if(std::find(seen_leaders.begin(),seen_leaders.end(),tm-teams.begin() + 1) == seen_leaders.end()) {
tm->clear_villages();
}
}
bool found_enemies = false;
bool found_player = false;
for(size_t n = 0; n != seen_leaders.size(); ++n) {
const size_t side = seen_leaders[n]-1;
wassert(side < teams.size());
for(size_t m = n+1; m != seen_leaders.size(); ++m) {
if(side < teams.size() && teams[side].is_enemy(seen_leaders[m])) {
found_enemies = true;
}
}
if (teams[side].is_human() || teams[side].is_persistent()) {
found_player = true;
}
}
if(found_enemies == false) {
if (found_player) {
game_events::fire("enemies defeated");
if (victory_conditions::victory_when_enemies_defeated() == false) {
// this level has asked not to be ended by this condition
return;
}
}
if(non_interactive()) {
std::cout << "winner: ";
for(std::vector<int>::const_iterator i = seen_leaders.begin(); i != seen_leaders.end(); ++i) {
std::cout << *i << " ";
}
std::cout << "\n";
}
LOG_NG << "throwing end level exception...\n";
throw end_level_exception(found_player ? VICTORY : DEFEAT);
}
}
const time_of_day& timeofday_at(const gamestatus& status,const unit_map& units,const gamemap::location& loc, const gamemap& map)
{
int lighten = maximum<int>(map.get_terrain_info(map.get_terrain(loc)).light_modification() , 0);
int darken = minimum<int>(map.get_terrain_info(map.get_terrain(loc)).light_modification() , 0);
if(loc.valid()) {
gamemap::location locs[7];
locs[0] = loc;
get_adjacent_tiles(loc,locs+1);
for(int i = 0; i != 7; ++i) {
const unit_map::const_iterator itor = units.find(locs[i]);
if(itor != units.end() &&
itor->second.get_ability_bool("illuminates",itor->first) && itor->second.get_state("stoned")!="yes") {
lighten = maximum<int>(itor->second.get_abilities("illuminates",itor->first).highest("value").first, lighten);
darken = minimum<int>(itor->second.get_abilities("illuminates",itor->first).lowest("value").first, darken);
}
}
}
return status.get_time_of_day(lighten + darken,loc);
}
int combat_modifier(const gamestatus& status,
const units_map& units,
const gamemap::location& loc,
unit_type::ALIGNMENT alignment,
const gamemap& map)
{
const time_of_day& tod = timeofday_at(status,units,loc,map);
int bonus = tod.lawful_bonus;
if(alignment == unit_type::NEUTRAL)
bonus = 0;
else if(alignment == unit_type::CHAOTIC)
bonus = -bonus;
return bonus;
}
namespace {
bool clear_shroud_loc(const gamemap& map, team& tm,
const gamemap::location& loc,
std::vector<gamemap::location>* cleared)
{
bool result = false;
gamemap::location adj[7];
get_adjacent_tiles(loc,adj);
adj[6] = loc;
for(int i = 0; i != 7; ++i) {
//we clear one past the edge of the board, so that the half-hexes
//at the edge can also be cleared of fog/shroud
if(map.on_board(adj[i]) || map.on_board(loc)) {
const bool res = tm.clear_shroud(adj[i].x,adj[i].y) ||
tm.clear_fog(adj[i].x,adj[i].y);
if(res && cleared != NULL) {
cleared->push_back(adj[i]);
}
result |= res;
}
}
return result;
}
//returns true iff some shroud is cleared
//seen_units will return new units that have been seen by this unit
//if known_units is NULL, seen_units can be NULL and will not be changed
bool clear_shroud_unit(const gamemap& map,
const gamestatus& status,
const game_data& gamedata,
const unit_map& units, const gamemap::location& loc,
std::vector<team>& teams, int team,
const std::set<gamemap::location>* known_units,
std::set<gamemap::location>* seen_units)
{
std::vector<gamemap::location> cleared_locations;
const unit_map::const_iterator u = units.find(loc);
if(u == units.end()) {
return false;
}
unit_map temp_units;
temp_units.insert(*u);
paths p(map,status,gamedata,temp_units,loc,teams,true,false,teams[team]);
for(paths::routes_map::const_iterator i = p.routes.begin();
i != p.routes.end(); ++i) {
clear_shroud_loc(map,teams[team],i->first,&cleared_locations);
}
//clear the location the unit is at
clear_shroud_loc(map,teams[team],loc,&cleared_locations);
for(std::vector<gamemap::location>::const_iterator it =
cleared_locations.begin(); it != cleared_locations.end(); ++it) {
const unit_map::const_iterator sighted = units.find(*it);
if(sighted != units.end() &&
(sighted->second.invisible(map.underlying_union_terrain(map[it->x][it->y]),status.get_time_of_day().lawful_bonus,*it,units,teams) == false
|| teams[team].is_enemy(sighted->second.side()) == false)) {
if(seen_units == NULL || known_units == NULL) {
static const std::string sighted("sighted");
game_events::raise(sighted,*it,loc);
} else if(known_units->count(*it) == 0) {
seen_units->insert(*it);
}
}
}
return cleared_locations.empty() == false;
}
}
void recalculate_fog(const gamemap& map, const gamestatus& status,
const game_data& gamedata, unit_map& units,
std::vector<team>& teams, int team) {
teams[team].refog();
for(unit_map::iterator i = units.begin(); i != units.end(); ++i) {
if((int)i->second.side() == team+1) {
const unit_movement_resetter move_resetter(i->second);
clear_shroud_unit(map,status,gamedata,units,i->first,teams,team,NULL,NULL);
}
}
game_events::pump();
}
bool clear_shroud(display& disp, const gamestatus& status,
const gamemap& map, const game_data& gamedata,
unit_map& units, std::vector<team>& teams, int team)
{
if(teams[team].uses_shroud() == false && teams[team].uses_fog() == false)
return false;
bool result = false;
unit_map::iterator i;
for(i = units.begin(); i != units.end(); ++i) {
if((int)i->second.side() == team+1) {
const unit_movement_resetter move_resetter(i->second);
result |= clear_shroud_unit(map,status,gamedata,units,i->first,teams,team,NULL,NULL);
}
}
game_events::pump();
recalculate_fog(map,status,gamedata,units,teams,team);
disp.labels().recalculate_shroud();
return result;
}
size_t move_unit(display* disp, const game_data& gamedata,
const gamestatus& status, const gamemap& map,
unit_map& units, std::vector<team>& teams,
std::vector<gamemap::location> route,
replay* move_recorder, undo_list* undo_stack,
gamemap::location *next_unit, bool continue_move, bool should_clear_shroud)
{
wassert(route.empty() == false);
//stop the user from issuing any commands while the unit is moving
const command_disabler disable_commands;
unit_map::iterator ui = units.find(route.front());
wassert(ui != units.end());
ui->second.set_goto(gamemap::location());
unit u = ui->second;
const size_t team_num = u.side()-1;
const bool skirmisher = u.get_ability_bool("skirmisher",ui->first);
team& team = teams[team_num];
const bool check_shroud = should_clear_shroud && team.auto_shroud_updates() &&
(team.uses_shroud() || team.uses_fog());
//if we use shroud/fog of war, count out the units we can currently see
std::set<gamemap::location> known_units;
if(check_shroud) {
for(unit_map::const_iterator u = units.begin(); u != units.end(); ++u) {
if(team.fogged(u->first.x,u->first.y) == false) {
known_units.insert(u->first);
team.see(u->second.side()-1);
}
}
}
//see how far along the given path we can move
const int starting_moves = u.movement_left();
int moves_left = starting_moves;
std::set<gamemap::location> seen_units;
bool discovered_unit = false;
bool should_clear_stack = false;
std::vector<gamemap::location>::const_iterator step;
for(step = route.begin()+1; step != route.end(); ++step) {
const gamemap::TERRAIN terrain = map[step->x][step->y];
const unit_map::const_iterator enemy_unit = units.find(*step);
const int mv = u.movement_cost(terrain);
if(discovered_unit || continue_move == false && seen_units.empty() == false ||
mv > moves_left || enemy_unit != units.end() && team.is_enemy(enemy_unit->second.side())) {
break;
} else {
moves_left -= mv;
}
if(!skirmisher && enemy_zoc(map,status,units,teams,*step,team,u.side())) {
moves_left = 0;
}
//if we use fog or shroud, see if we have sighted an enemy unit, in
//which case we should stop immediately.
if(check_shroud) {
if(units.count(*step) == 0 && !map.is_village(*step)) {
LOG_NG << "checking for units from " << (step->x+1) << "," << (step->y+1) << "\n";
//temporarily reset the unit's moves to full
const unit_movement_resetter move_resetter(ui->second);
//we have to swap out any unit that is already in the hex, so we can put our
//unit there, then we'll swap back at the end.
const temporary_unit_placer unit_placer(units,*step,ui->second);
should_clear_stack |= clear_shroud_unit(map,status,gamedata,units,*step,teams,
ui->second.side()-1,&known_units,&seen_units);
if(should_clear_stack) {
disp->invalidate_all();
}
}
}
//check if we have discovered an invisible enemy unit
gamemap::location adjacent[6];
get_adjacent_tiles(*step,adjacent);
for(int i = 0; i != 6; ++i) {
//check if we are checking ourselves
if(adjacent[i] == ui->first)
continue;
const units_map::const_iterator it = units.find(adjacent[i]);
if(it != units.end() && teams[u.side()-1].is_enemy(it->second.side()) &&
it->second.invisible(map.underlying_union_terrain(map[it->first.x][it->first.y]),
status.get_time_of_day().lawful_bonus,it->first,units,teams)) {
discovered_unit = true;
should_clear_stack = true;
moves_left = 0;
break;
}
}
}
//make sure we don't tread on another unit
std::vector<gamemap::location>::const_iterator begin = route.begin();
std::vector<gamemap::location> steps(begin,step);
while (!steps.empty()) {
gamemap::location const &loc = steps.back();
if (units.count(loc) == 0)
break;
moves_left += u.movement_cost(map[loc.x][loc.y]);
steps.pop_back();
}
wassert(steps.size() <= route.size());
if (next_unit != NULL)
*next_unit = steps.back();
//if we can't get all the way there and have to set a go-to.
if(steps.size() != route.size() && discovered_unit == false) {
if(seen_units.empty() == false) {
u.set_interrupted_move(route.back());
} else {
u.set_goto(route.back());
}
} else {
u.set_interrupted_move(gamemap::location());
}
if(steps.size() < 2) {
return 0;
}
//move the unit on the screen. Hide the unit in its current location, but don't actually
//remove it until the move is done, so that while the unit is moving status etc will
//still display the correct number of units.
if(disp != NULL) {
ui->second.set_hidden(true);
disp->draw_tile(ui->first.x,ui->first.y);
unit_display::move_unit(*disp,map,steps,u,status.get_time_of_day(),units,teams);
ui->second.set_hidden(false);
}
u.set_movement(moves_left);
units.erase(ui);
ui = units.insert(std::pair<gamemap::location,unit>(steps.back(),u)).first;
if(disp != NULL) {
disp->invalidate_unit();
disp->invalidate(steps.back());
}
if(move_recorder != NULL) {
move_recorder->add_movement(steps.front(),steps.back());
}
bool event_mutated = false;
int orig_village_owner = -1;
int action_time_bonus = 0;
if(map.is_village(steps.back())) {
orig_village_owner = village_owner(steps.back(),teams);
if(size_t(orig_village_owner) != team_num) {
ui->second.set_movement(0);
event_mutated = get_village(steps.back(),teams,team_num,units,&action_time_bonus);
}
}
if(game_events::fire("moveto",steps.back())) {
event_mutated = true;
}
if(undo_stack != NULL) {
if(event_mutated || should_clear_stack) {
apply_shroud_changes(*undo_stack,disp,status,map,gamedata,units,teams,team_num);
undo_stack->clear();
} else {
//MP_COUNTDOWN: added param
undo_stack->push_back(undo_action(u,steps,starting_moves,action_time_bonus,orig_village_owner));
}
}
if(disp != NULL) {
disp->set_route(NULL);
//show messages on the screen here
if(discovered_unit) {
//we've been ambushed, so display an appropriate message
font::add_floating_label(_("Ambushed!"),font::SIZE_XLARGE,font::BAD_COLOUR,
disp->map_area().w/2,disp->map_area().h/3,
0.0,0.0,100,disp->map_area(),font::CENTER_ALIGN);
}
if(continue_move == false && seen_units.empty() == false) {
//the message depends on how many units have been sighted, and whether
//they are allies or enemies, so calculate that out here
int nfriends = 0, nenemies = 0;
for(std::set<gamemap::location>::const_iterator i = seen_units.begin(); i != seen_units.end(); ++i) {
LOG_NG << "processing unit at " << (i->x+1) << "," << (i->y+1) << "\n";
const unit_map::const_iterator u = units.find(*i);
//unit may have been removed by an event.
if(u == units.end()) {
LOG_NG << "was removed\n";
continue;
}
if(team.is_enemy(u->second.side())) {
++nenemies;
} else {
++nfriends;
}
LOG_NG << "processed...\n";
teams[team_num].see(u->second.side()-1);
}
char* msg_id;
//the message we display is different depending on whether units sighted
//were enemies or friends, and whether there is one or more
if(seen_units.size() == 1) {
if(nfriends == 1) {
msg_id = N_("Friendly unit sighted");
} else {
msg_id = N_("Enemy unit sighted!");
}
}
else if(nfriends == 0 || nenemies == 0) {
if(nfriends > 0) {
msg_id = N_("$friends Friendly units sighted");
} else {
msg_id = N_("$enemies Enemy units sighted!");
}
}
else {
msg_id = N_("Units sighted! ($friends friendly, $enemies enemy)");
}
utils::string_map symbols;
symbols["friends"] = lexical_cast<std::string>(nfriends);
symbols["enemies"] = lexical_cast<std::string>(nenemies);
std::stringstream msg;
msg << gettext(msg_id);
if(steps.size() < route.size()) {
//see if the "Continue Move" action has an associated hotkey
const hotkey::hotkey_item& hk = hotkey::get_hotkey(hotkey::HOTKEY_CONTINUE_MOVE);
if(!hk.null()) {
symbols["hotkey"] = hk.get_name();
msg << '\n' << _("(press $hotkey to continue)");
}
}
const std::string message = utils::interpolate_variables_into_string(msg.str(), &symbols);
font::add_floating_label(message,font::SIZE_XLARGE,font::BAD_COLOUR,
disp->map_area().w/2,disp->map_area().h/3,
0.0,0.0,100,disp->map_area(),font::CENTER_ALIGN);
}
disp->draw();
disp->recalculate_minimap();
}
wassert(steps.size() <= route.size());
return steps.size();
}
bool unit_can_move(const gamemap::location& loc, const unit_map& units,
const gamemap& map, const std::vector<team>& teams)
{
const unit_map::const_iterator u_it = units.find(loc);
wassert(u_it != units.end());
const unit& u = u_it->second;
const team& current_team = teams[u.side()-1];
if(!u.attacks_left() && u.movement_left()==0)
return false;
//units with goto commands that have already done their gotos this turn
//(i.e. don't have full movement left) should be red
if(u.has_moved() && u.has_goto()) {
return false;
}
gamemap::location locs[6];
get_adjacent_tiles(loc,locs);
for(int n = 0; n != 6; ++n) {
if(map.on_board(locs[n])) {
const unit_map::const_iterator i = units.find(locs[n]);
if(i != units.end()) {
if(i->second.get_state("stoned")!="yes" && current_team.is_enemy(i->second.side())) {
return true;
}
}
if(u.movement_cost(map[locs[n].x][locs[n].y]) <= u.movement_left()) {
return true;
}
}
}
return false;
}
void apply_shroud_changes(undo_list& undos, display* disp, const gamestatus& status, const gamemap& map,
const game_data& gamedata, unit_map& units, std::vector<team>& teams, int team)
{
// No need to do this if the team isn't using fog or shroud.
if(!teams[team].uses_shroud() && !teams[team].uses_fog())
return;
/*
This function works thusly:
1. run through the list of undo_actions
2. for each one, play back the unit's move
3. for each location along the route, clear any "shrouded" squares that the unit can see
4. clear the undo_list
5. call clear_shroud to update the fog of war for each unit.
6. fix up associated display stuff (done in a similar way to turn_info::undo())
*/
for(undo_list::iterator un = undos.begin(); un != undos.end(); ++un) {
std::cout << "Turning an undo...\n";
if(un->is_recall()) continue;
//we're not really going to mutate the unit, just temporarily
//set its moves to maximum, but then switch them back
const unit_movement_resetter move_resetter(un->affected_unit);
std::vector<gamemap::location>::const_iterator step;
for(step = un->route.begin(); step != un->route.end(); ++step) {
//we have to swap out any unit that is already in the hex, so we can put our
//unit there, then we'll swap back at the end.
const temporary_unit_placer unit_placer(units,*step,un->affected_unit);
clear_shroud_unit(map,status,gamedata,units,*step,teams,team,NULL,NULL);
//FIXME
//there is potential for a bug, here. If the "sighted"
//events, raised by the clear_shroud_unit function,
//loops on all units, changing them all, the unit which
//was swapped by the temporary unit placer will not be
//affected. However, if we place the pump() out of the
//temporary_unit_placer scope, the "sighted" event will
//be raised with an invalid source unit, which is even
//worse.
game_events::pump();
}
}
if(disp != NULL) {
disp->invalidate_unit();
disp->invalidate_game_status();
clear_shroud(*disp,status,map,gamedata,units,teams,team);
disp->recalculate_minimap();
disp->unhighlight_reach();
disp->set_route(NULL);
} else {
recalculate_fog(map,status,gamedata,units,teams,team);
}
}
bool backstab_check(const gamemap::location& attacker_loc,
const gamemap::location& defender_loc,
units_map& units, std::vector<team>& teams)
{
const units_map::const_iterator defender =
units.find(defender_loc);
if(defender == units.end()) return false; // No defender
gamemap::location adj[6];
get_adjacent_tiles(defender_loc, adj);
int i;
for(i = 0; i != 6; ++i) {
if(adj[i] == attacker_loc)
break;
}
if(i >= 6) return false; // Attack not from adjacent location
const units_map::const_iterator opp =
units.find(adj[(i+3)%6]);
if(opp == units.end()) return false; // No opposite unit
if(opp->second.get_state("stoned") == "yes") return false;
if(size_t(defender->second.side()-1) >= teams.size() ||
size_t(opp->second.side()-1) >= teams.size())
return true; // If sides aren't valid teams, then they are enemies
if(teams[defender->second.side()-1].is_enemy(opp->second.side()))
return true; // Defender and opposite are enemies
return false; // Defender and opposite are friends
}