mirror of
https://github.com/wesnoth/wesnoth
synced 2025-05-21 05:25:34 +00:00
1369 lines
42 KiB
C++
1369 lines
42 KiB
C++
/* $Id$ */
|
|
/*
|
|
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
|
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License.
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY.
|
|
|
|
See the COPYING file for more details.
|
|
*/
|
|
|
|
#include "actions.hpp"
|
|
#include "display.hpp"
|
|
#include "game.hpp"
|
|
#include "game_config.hpp"
|
|
#include "game_events.hpp"
|
|
#include "key.hpp"
|
|
#include "language.hpp"
|
|
#include "map.hpp"
|
|
#include "pathfind.hpp"
|
|
#include "playlevel.hpp"
|
|
#include "playturn.hpp"
|
|
#include "replay.hpp"
|
|
#include "sound.hpp"
|
|
#include "util.hpp"
|
|
|
|
#include <cmath>
|
|
#include <set>
|
|
#include <string>
|
|
#include <sstream>
|
|
|
|
struct castle_cost_calculator
|
|
{
|
|
castle_cost_calculator(const gamemap& map) : map_(map)
|
|
{}
|
|
|
|
double cost(const gamemap::location& loc, double cost_so_far) const
|
|
{
|
|
if(!map_.on_board(loc) || map_[loc.x][loc.y] != gamemap::CASTLE)
|
|
return 10000;
|
|
|
|
return 1;
|
|
}
|
|
|
|
private:
|
|
const gamemap& map_;
|
|
};
|
|
|
|
std::string recruit_unit(const gamemap& map, int side,
|
|
std::map<gamemap::location,unit>& units, unit& new_unit,
|
|
gamemap::location recruit_location, display* disp, bool need_castle, bool full_movement)
|
|
{
|
|
std::cerr << "recruiting unit for side " << side << "\n";
|
|
typedef std::map<gamemap::location,unit> units_map;
|
|
|
|
//find the unit that can recruit
|
|
units_map::const_iterator u;
|
|
|
|
for(u = units.begin(); u != units.end(); ++u) {
|
|
if(u->second.can_recruit() && u->second.side() == side) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(u == units.end())
|
|
return string_table["no_leader_to_recruit"];
|
|
|
|
if(map.get_terrain(u->first) != gamemap::KEEP) {
|
|
std::cerr << "Leader not on start: leader is on " << (u->first.x+1) << "," << (u->first.y+1) << "\n";
|
|
return string_table["leader_not_on_start"];
|
|
}
|
|
|
|
if(map.on_board(recruit_location)) {
|
|
const paths::route& rt = a_star_search(u->first,recruit_location,
|
|
100.0,castle_cost_calculator(map));
|
|
if(rt.steps.empty() || units.find(recruit_location) != units.end() ||
|
|
map[recruit_location.x][recruit_location.y] != gamemap::CASTLE)
|
|
recruit_location = gamemap::location();
|
|
}
|
|
|
|
if(!map.on_board(recruit_location)) {
|
|
recruit_location = find_vacant_tile(map,units,u->first,
|
|
need_castle ? gamemap::CASTLE : 0);
|
|
}
|
|
|
|
if(!map.on_board(recruit_location)) {
|
|
return string_table["no_recruit_location"];
|
|
}
|
|
|
|
if(full_movement) {
|
|
new_unit.set_movement(new_unit.total_movement());
|
|
} else {
|
|
new_unit.set_movement(0);
|
|
new_unit.set_attacked();
|
|
}
|
|
|
|
units.insert(std::pair<gamemap::location,unit>(
|
|
recruit_location,new_unit));
|
|
|
|
if(disp != NULL && !disp->turbo() &&
|
|
!disp->fogged(recruit_location.x,recruit_location.y)) {
|
|
disp->draw(true,true);
|
|
|
|
for(double alpha = 0.0; alpha <= 1.0; alpha += 0.1) {
|
|
disp->draw_tile(recruit_location.x,recruit_location.y,NULL,alpha);
|
|
disp->update_display();
|
|
SDL_Delay(20);
|
|
}
|
|
}
|
|
|
|
return std::string();
|
|
}
|
|
|
|
bool under_leadership(const std::map<gamemap::location,unit>& units,
|
|
const gamemap::location& loc)
|
|
{
|
|
gamemap::location adjacent[6];
|
|
get_adjacent_tiles(loc,adjacent);
|
|
const std::map<gamemap::location,unit>::const_iterator un =
|
|
units.find(loc);
|
|
if(un == units.end())
|
|
return false;
|
|
|
|
const int side = un->second.side();
|
|
const int level = un->second.type().level();
|
|
|
|
for(int i = 0; i != 6; ++i) {
|
|
const std::map<gamemap::location,unit>::const_iterator it =
|
|
units.find(adjacent[i]);
|
|
if(it != units.end() && it->second.side() == side &&
|
|
it->second.type().is_leader() && it->second.type().level() > level)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
battle_stats evaluate_battle_stats(
|
|
const gamemap& map,
|
|
const gamemap::location& attacker,
|
|
const gamemap::location& defender,
|
|
int attack_with,
|
|
std::map<gamemap::location,unit>& units,
|
|
const gamestatus& state,
|
|
const game_data& info,
|
|
gamemap::TERRAIN attacker_terrain_override,
|
|
bool include_strings)
|
|
{
|
|
//if these are both genuine positions, work out the range
|
|
//combat is taking place at
|
|
const int combat_range = attacker_terrain_override == 0 ? distance_between(attacker,defender) : 1;
|
|
|
|
battle_stats res;
|
|
|
|
res.attack_with = attack_with;
|
|
|
|
if(include_strings)
|
|
res.defend_name = string_table["weapon_none"];
|
|
|
|
const std::map<gamemap::location,unit>::iterator a = units.find(attacker);
|
|
const std::map<gamemap::location,unit>::iterator d = units.find(defender);
|
|
|
|
assert(a != units.end());
|
|
assert(d != units.end());
|
|
|
|
const gamemap::TERRAIN attacker_terrain = attacker_terrain_override ?
|
|
attacker_terrain_override : map[attacker.x][attacker.y];
|
|
const gamemap::TERRAIN defender_terrain = map[defender.x][defender.y];
|
|
|
|
res.chance_to_hit_attacker = a->second.defense_modifier(map,attacker_terrain);
|
|
res.chance_to_hit_defender = d->second.defense_modifier(map,defender_terrain);
|
|
|
|
const std::vector<attack_type>& attacker_attacks = a->second.attacks();
|
|
const std::vector<attack_type>& defender_attacks = d->second.attacks();
|
|
|
|
assert(attack_with >= 0 && attack_with < int(attacker_attacks.size()));
|
|
const attack_type& attack = attacker_attacks[attack_with];
|
|
|
|
static const std::string charge_string("charge");
|
|
const bool charge = (attack.special() == charge_string);
|
|
|
|
bool backstab = false;
|
|
|
|
static const std::string backstab_string("backstab");
|
|
if(attack.special() == backstab_string) {
|
|
gamemap::location adj[6];
|
|
get_adjacent_tiles(defender,adj);
|
|
int i;
|
|
for(i = 0; i != 6; ++i) {
|
|
if(adj[i] == attacker)
|
|
break;
|
|
}
|
|
|
|
if(i != 6) {
|
|
const std::map<gamemap::location,unit>::const_iterator u =
|
|
units.find(adj[(i+3)%6]);
|
|
if(u != units.end() && u->second.side() == a->second.side()) {
|
|
backstab = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
static const std::string plague_string("plague");
|
|
res.attacker_plague = !d->second.type().not_living() && (attack.special() == plague_string);
|
|
res.defender_plague = false;
|
|
|
|
res.attack_name = attack.name();
|
|
res.attack_type = attack.type();
|
|
|
|
if(include_strings) {
|
|
res.attack_special = attack.special();
|
|
res.attack_icon = attack.icon();
|
|
|
|
//don't show backstabbing unless it's actually happening
|
|
if(res.attack_special == "backstab" && !backstab)
|
|
res.attack_special = "";
|
|
|
|
//the leader is immune to being turned to stone
|
|
if(d->second.can_recruit() && res.attack_special == "stone") {
|
|
res.attack_special = "";
|
|
}
|
|
|
|
res.range = (attack.range() == attack_type::SHORT_RANGE ?
|
|
"Melee" : "Ranged");
|
|
}
|
|
|
|
res.nattacks = attack.num_attacks();
|
|
int defend;
|
|
res.ndefends = 0;
|
|
for(defend = 0; defend != int(defender_attacks.size()); ++defend) {
|
|
if(defender_attacks[defend].range() == attack.range() &&
|
|
defender_attacks[defend].hexes() >= combat_range)
|
|
break;
|
|
}
|
|
|
|
res.defend_with = defend != int(defender_attacks.size()) ? defend : -1;
|
|
|
|
const bool counterattack = defend != int(defender_attacks.size());
|
|
|
|
static const std::string drain_string("drain");
|
|
static const std::string magical_string("magical");
|
|
|
|
res.damage_attacker_takes = 0;
|
|
if(counterattack) {
|
|
//magical attacks always have a 70% chance to hit
|
|
if(defender_attacks[defend].special() == magical_string)
|
|
res.chance_to_hit_attacker = 70;
|
|
|
|
const int base_damage = a->second.damage_against(defender_attacks[defend]);
|
|
const int modifier = combat_modifier(state,units,d->first,d->second.type().alignment());
|
|
res.damage_attacker_takes = (base_damage * (100+modifier))/100;
|
|
|
|
if(charge)
|
|
res.damage_attacker_takes *= 2;
|
|
|
|
if(under_leadership(units,defender))
|
|
res.damage_attacker_takes += res.damage_attacker_takes/8 + 1;
|
|
|
|
if(res.damage_attacker_takes < 1)
|
|
res.damage_attacker_takes = 1;
|
|
|
|
res.ndefends = defender_attacks[defend].num_attacks();
|
|
|
|
res.defend_name = defender_attacks[defend].name();
|
|
res.defend_type = defender_attacks[defend].type();
|
|
|
|
if(include_strings) {
|
|
res.defend_special = defender_attacks[defend].special();
|
|
res.defend_icon = defender_attacks[defend].icon();
|
|
|
|
//leaders are immune to being turned to stone
|
|
if(a->second.can_recruit() && res.defend_special == "stone")
|
|
res.defend_special = "";
|
|
}
|
|
|
|
//if the defender drains, and the attacker is a living creature, then
|
|
//the defender will drain for half the damage it does
|
|
if(defender_attacks[defend].special() == drain_string && !a->second.type().not_living()) {
|
|
res.amount_defender_drains = res.damage_attacker_takes/2;
|
|
} else {
|
|
res.amount_defender_drains = 0;
|
|
}
|
|
|
|
res.defender_plague = (defender_attacks[defend].special() == plague_string);
|
|
}
|
|
|
|
if(attack.special() == magical_string)
|
|
res.chance_to_hit_defender = 70;
|
|
|
|
static const std::string marksman_string("marksman");
|
|
|
|
//offensive marksman attacks always have at least 60% chance to hit
|
|
if(res.chance_to_hit_defender < 60 && attack.special() == marksman_string)
|
|
res.chance_to_hit_defender = 60;
|
|
|
|
const int base_damage = d->second.damage_against(attack);
|
|
const int modifier = combat_modifier(state,units,a->first,a->second.type().alignment());
|
|
res.damage_defender_takes = ((base_damage * (100 + modifier))/100)
|
|
* (charge ? 2 : 1) * (backstab ? 2 : 1);
|
|
|
|
if(under_leadership(units,attacker))
|
|
res.damage_defender_takes += res.damage_defender_takes/8 + 1;
|
|
|
|
//if the attacker drains, and the defender is a living creature, then
|
|
//the attacker will drain for half the damage it does
|
|
if(attack.special() == drain_string && !d->second.type().not_living()) {
|
|
res.amount_attacker_drains = res.damage_defender_takes/2;
|
|
} else {
|
|
res.amount_attacker_drains = 0;
|
|
}
|
|
|
|
static const std::string slowed_string("slowed");
|
|
if(a->second.has_flag(slowed_string) && res.nattacks > 1)
|
|
--res.nattacks;
|
|
|
|
if(d->second.has_flag(slowed_string) && res.ndefends > 1)
|
|
--res.ndefends;
|
|
|
|
return res;
|
|
}
|
|
|
|
void attack(display& gui, const gamemap& map,
|
|
std::vector<team>& teams,
|
|
const gamemap::location& attacker,
|
|
const gamemap::location& defender,
|
|
int attack_with,
|
|
std::map<gamemap::location,unit>& units,
|
|
const gamestatus& state,
|
|
const game_data& info, bool player_is_attacker)
|
|
{
|
|
|
|
//stop the user from issuing any commands while the units are fighting
|
|
const command_disabler disable_commands;
|
|
|
|
std::map<gamemap::location,unit>::iterator a = units.find(attacker);
|
|
std::map<gamemap::location,unit>::iterator d = units.find(defender);
|
|
|
|
assert(a != units.end());
|
|
assert(d != units.end());
|
|
|
|
int attackerxp = d->second.type().level();
|
|
int defenderxp = a->second.type().level();
|
|
|
|
a->second.set_attacked();
|
|
d->second.set_resting(false);
|
|
|
|
//if the attacker was invisible, she isn't anymore!
|
|
static const std::string forest_invisible("ambush");
|
|
a->second.remove_flag(forest_invisible);
|
|
static const std::string night_invisible("nightstalk");
|
|
a->second.remove_flag(night_invisible);
|
|
|
|
battle_stats stats = evaluate_battle_stats(map,attacker,defender,
|
|
attack_with,units,state,info);
|
|
|
|
static const std::string poison_string("poison");
|
|
|
|
while(stats.nattacks > 0 || stats.ndefends > 0) {
|
|
if(stats.nattacks > 0) {
|
|
const int ran_num = get_random();
|
|
bool hits = (ran_num%100) < stats.chance_to_hit_defender;
|
|
|
|
//make sure that if we're serializing a game here,
|
|
//we got the same results as the game did originally
|
|
const config* ran_results = get_random_results();
|
|
if(ran_results != NULL) {
|
|
const int results_chance = atoi((*ran_results)["chance"].c_str());
|
|
const bool results_hits = (*ran_results)["hits"] == "yes";
|
|
const int results_damage = atoi((*ran_results)["damage"].c_str());
|
|
|
|
if(results_chance != stats.chance_to_hit_defender) {
|
|
std::cerr << "SYNC ERROR: In attack " << a->second.type().name() << " vs "
|
|
<< d->second.type().name() << ": 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";
|
|
hits = results_hits;
|
|
} else if(hits != results_hits) {
|
|
std::cerr << "SYNC ERROR: In attack " << a->second.type().name() << " vs "
|
|
<< d->second.type().name() << ": 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;
|
|
} else if(results_damage != stats.damage_defender_takes) {
|
|
std::cerr << "SYNC ERROR: In attack " << a->second.type().name() << " vs "
|
|
<< d->second.type().name() << ": the data source says the hit did "
|
|
<< results_damage << " damage, while in-game calculations show the hit doing "
|
|
<< stats.damage_defender_takes << " damage (over-riding game calculations with data source results)\n";
|
|
stats.damage_defender_takes = results_damage;
|
|
}
|
|
}
|
|
|
|
bool dies = gui.unit_attack(attacker,defender,
|
|
hits ? stats.damage_defender_takes : 0,
|
|
a->second.attacks()[attack_with]);
|
|
|
|
if(ran_results == NULL) {
|
|
config cfg;
|
|
cfg["hits"] = (hits ? "yes" : "no");
|
|
cfg["dies"] = (dies ? "yes" : "no");
|
|
char buf[50];
|
|
sprintf(buf,"%d",stats.damage_defender_takes);
|
|
cfg["damage"] = buf;
|
|
sprintf(buf,"%d",stats.chance_to_hit_defender);
|
|
cfg["chance"] = buf;
|
|
set_random_results(cfg);
|
|
} else {
|
|
const bool results_dies = (*ran_results)["dies"] == "yes";
|
|
if(results_dies != dies) {
|
|
std::cerr << "SYNC ERROR: In attack" << a->second.type().name() << " vs "
|
|
<< d->second.type().name() << ": the data source the unit "
|
|
<< (results_dies ? "perished" : "survived") << " while in-game calculations show the unit "
|
|
<< (dies ? "perished" : "survived") << " (over-riding game calculations with data source results)\n";
|
|
dies = results_dies;
|
|
}
|
|
}
|
|
|
|
if(dies) {
|
|
attackerxp = game_config::kill_experience*d->second.type().level();
|
|
if(d->second.type().level() == 0)
|
|
attackerxp = game_config::kill_experience/2;
|
|
|
|
a->second.get_experience(attackerxp);
|
|
attackerxp = 0;
|
|
defenderxp = 0;
|
|
|
|
gamemap::location loc = d->first;
|
|
gamemap::location attacker_loc = a->first;
|
|
const int defender_side = d->second.side();
|
|
game_events::fire("die",loc,a->first);
|
|
d = units.end();
|
|
a = units.end();
|
|
|
|
//the handling of the event may have removed the object
|
|
//so we have to find it again
|
|
units.erase(loc);
|
|
|
|
//plague units make clones of themselves on the target hex
|
|
//units on villages that die cannot be plagued
|
|
if(stats.attacker_plague && map.underlying_terrain(map[loc.x][loc.y]) != gamemap::TOWER) {
|
|
a = units.find(attacker_loc);
|
|
if(a != units.end()) {
|
|
units.insert(std::pair<gamemap::location,unit>(loc,a->second));
|
|
gui.draw_tile(loc.x,loc.y);
|
|
}
|
|
}
|
|
recalculate_fog(map,state,info,units,teams,defender_side-1);
|
|
gui.recalculate_minimap();
|
|
gui.update_display();
|
|
break;
|
|
} else if(hits) {
|
|
if(stats.attack_special == poison_string &&
|
|
d->second.has_flag("poisoned") == false &&
|
|
!d->second.type().not_living()) {
|
|
d->second.set_flag("poisoned");
|
|
}
|
|
|
|
static const std::string slow_string("slow");
|
|
if(stats.attack_special == slow_string &&
|
|
d->second.has_flag("slowed") == false) {
|
|
d->second.set_flag("slowed");
|
|
if(stats.ndefends > 1)
|
|
--stats.ndefends;
|
|
}
|
|
|
|
if(stats.amount_attacker_drains > 0) {
|
|
a->second.gets_hit(-stats.amount_attacker_drains);
|
|
}
|
|
|
|
//if the defender is turned to stone, the fight stops immediately
|
|
static const std::string stone_string("stone");
|
|
if(stats.attack_special == stone_string) {
|
|
d->second.set_flag("stone");
|
|
stats.ndefends = 0;
|
|
stats.nattacks = 0;
|
|
game_events::fire("stone",d->first,a->first);
|
|
}
|
|
}
|
|
|
|
--stats.nattacks;
|
|
}
|
|
|
|
if(stats.ndefends > 0) {
|
|
const int ran_num = get_random();
|
|
bool hits = (ran_num%100) < stats.chance_to_hit_attacker;
|
|
|
|
//make sure that if we're serializing a game here,
|
|
//we got the same results as the game did originally
|
|
const config* ran_results = get_random_results();
|
|
if(ran_results != NULL) {
|
|
const int results_chance = atoi((*ran_results)["chance"].c_str());
|
|
const bool results_hits = (*ran_results)["hits"] == "yes";
|
|
const int results_damage = atoi((*ran_results)["damage"].c_str());
|
|
|
|
if(results_chance != stats.chance_to_hit_attacker) {
|
|
std::cerr << "SYNC ERROR: In defend " << a->second.type().name() << " vs "
|
|
<< d->second.type().name() << ": 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";
|
|
hits = results_hits;
|
|
} else if(hits != results_hits) {
|
|
std::cerr << "SYNC ERROR: In defend " << a->second.type().name() << " vs "
|
|
<< d->second.type().name() << ": 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;
|
|
} else if(results_damage != stats.damage_attacker_takes) {
|
|
std::cerr << "SYNC ERROR: In defend " << a->second.type().name() << " vs "
|
|
<< d->second.type().name() << ": the data source says the hit did "
|
|
<< results_damage << " damage, while in-game calculations show the hit doing "
|
|
<< stats.damage_attacker_takes << " damage (over-riding game calculations with data source results)\n";
|
|
stats.damage_attacker_takes = results_damage;
|
|
}
|
|
}
|
|
|
|
bool dies = gui.unit_attack(defender,attacker,
|
|
hits ? stats.damage_attacker_takes : 0,
|
|
d->second.attacks()[stats.defend_with]);
|
|
|
|
if(ran_results == NULL) {
|
|
config cfg;
|
|
cfg["hits"] = (hits ? "yes" : "no");
|
|
cfg["dies"] = (dies ? "yes" : "no");
|
|
char buf[50];
|
|
sprintf(buf,"%d",stats.damage_attacker_takes);
|
|
cfg["damage"] = buf;
|
|
sprintf(buf,"%d",stats.chance_to_hit_attacker);
|
|
cfg["chance"] = buf;
|
|
set_random_results(cfg);
|
|
} else {
|
|
const bool results_dies = (*ran_results)["dies"] == "yes";
|
|
if(results_dies != dies) {
|
|
std::cerr << "SYNC ERROR: In defend" << a->second.type().name() << " vs "
|
|
<< d->second.type().name() << ": the data source the unit "
|
|
<< (results_dies ? "perished" : "survived") << " while in-game calculations show the unit "
|
|
<< (dies ? "perished" : "survived") << " (over-riding game calculations with data source results)\n";
|
|
dies = results_dies;
|
|
}
|
|
}
|
|
|
|
if(dies) {
|
|
defenderxp = game_config::kill_experience*a->second.type().level();
|
|
if(a->second.type().level() == 0)
|
|
defenderxp = game_config::kill_experience/2;
|
|
|
|
d->second.get_experience(defenderxp);
|
|
defenderxp = 0;
|
|
attackerxp = 0;
|
|
|
|
gamemap::location loc = a->first;
|
|
gamemap::location defender_loc = d->first;
|
|
const int attacker_side = a->second.side();
|
|
game_events::fire("die",loc,d->first);
|
|
a = units.end();
|
|
d = units.end();
|
|
|
|
//the handling of the event may have removed the object
|
|
//so we have to find it again
|
|
units.erase(loc);
|
|
|
|
//plague units make clones of themselves on the target hex.
|
|
//units on villages that die cannot be plagued
|
|
if(stats.defender_plague && map.underlying_terrain(map[loc.x][loc.y]) != gamemap::TOWER) {
|
|
d = units.find(defender_loc);
|
|
if(d != units.end()) {
|
|
units.insert(std::pair<gamemap::location,unit>(
|
|
loc,d->second));
|
|
gui.draw_tile(loc.x,loc.y);
|
|
}
|
|
}
|
|
gui.recalculate_minimap();
|
|
gui.update_display();
|
|
recalculate_fog(map,state,info,units,teams,attacker_side-1);
|
|
break;
|
|
} else if(hits) {
|
|
if(stats.defend_special == poison_string &&
|
|
a->second.has_flag("poisoned") == false &&
|
|
!a->second.type().not_living()) {
|
|
a->second.set_flag("poisoned");
|
|
}
|
|
|
|
static const std::string slow_string("slow");
|
|
if(stats.defend_special == slow_string &&
|
|
a->second.has_flag("slowed") == false) {
|
|
a->second.set_flag("slowed");
|
|
if(stats.nattacks > 1)
|
|
--stats.nattacks;
|
|
}
|
|
|
|
if(stats.amount_defender_drains > 0) {
|
|
d->second.gets_hit(-stats.amount_defender_drains);
|
|
}
|
|
|
|
//if the attacker is turned to stone, the fight stops immediately
|
|
static const std::string stone_string("stone");
|
|
if(stats.defend_special == stone_string) {
|
|
a->second.set_flag("stone");
|
|
stats.ndefends = 0;
|
|
stats.nattacks = 0;
|
|
game_events::fire("stone",a->first,d->first);
|
|
}
|
|
}
|
|
|
|
--stats.ndefends;
|
|
}
|
|
}
|
|
|
|
if(attackerxp) {
|
|
a->second.get_experience(attackerxp);
|
|
}
|
|
|
|
if(defenderxp) {
|
|
d->second.get_experience(defenderxp);
|
|
}
|
|
|
|
gui.invalidate_unit();
|
|
}
|
|
|
|
int tower_owner(const gamemap::location& loc, std::vector<team>& teams)
|
|
{
|
|
for(size_t i = 0; i != teams.size(); ++i) {
|
|
if(teams[i].owns_tower(loc))
|
|
return i;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
|
|
void get_tower(const gamemap::location& loc, std::vector<team>& teams,
|
|
size_t team_num, const unit_map& units)
|
|
{
|
|
for(size_t i = 0; i != teams.size(); ++i) {
|
|
if(i != team_num && teams[i].owns_tower(loc)) {
|
|
teams[i].lose_tower(loc);
|
|
}
|
|
}
|
|
|
|
//if the side doesn't have a leader, captured villages become neutral
|
|
const bool has_leader = find_leader(units,int(team_num+1)) != units.end();
|
|
if(has_leader && team_num < teams.size())
|
|
teams[team_num].get_tower(loc);
|
|
}
|
|
|
|
std::map<gamemap::location,unit>::iterator
|
|
find_leader(std::map<gamemap::location,unit>& units, int side)
|
|
{
|
|
for(std::map<gamemap::location,unit>::iterator i = units.begin();
|
|
i != units.end(); ++i) {
|
|
if(i->second.side() == side && i->second.can_recruit())
|
|
return i;
|
|
}
|
|
|
|
return units.end();
|
|
}
|
|
|
|
std::map<gamemap::location,unit>::const_iterator
|
|
find_leader(const std::map<gamemap::location,unit>& units, int side)
|
|
{
|
|
for(std::map<gamemap::location,unit>::const_iterator i = units.begin();
|
|
i != units.end(); ++i) {
|
|
if(i->second.side() == side && i->second.can_recruit())
|
|
return i;
|
|
}
|
|
|
|
return units.end();
|
|
}
|
|
|
|
namespace {
|
|
|
|
//function which returns true iff the unit at 'loc' will heal a unit from side 'side'
|
|
//on this turn.
|
|
//
|
|
//units heal other units if they are (1) on the same side as them; or (2) are on a
|
|
//different but allied side, and there are no 'higher priority' sides also adjacent
|
|
//to the healer
|
|
bool will_heal(const gamemap::location& loc, int side, const std::vector<team>& teams,
|
|
const unit_map& units)
|
|
{
|
|
const unit_map::const_iterator healer_it = units.find(loc);
|
|
if(healer_it == units.end() || healer_it->second.type().heals() == false)
|
|
return false;
|
|
|
|
const unit& healer = healer_it->second;
|
|
if(healer.side() == side)
|
|
return true;
|
|
|
|
if(size_t(side-1) >= teams.size() || size_t(healer.side()-1) >= teams.size())
|
|
return false;
|
|
|
|
//if the healer is an enemy, it won't heal
|
|
if(teams[healer.side()-1].is_enemy(side))
|
|
return false;
|
|
|
|
gamemap::location adjacent[6];
|
|
get_adjacent_tiles(loc,adjacent);
|
|
for(int n = 0; n != 6; ++n) {
|
|
const unit_map::const_iterator u = units.find(adjacent[n]);
|
|
if(u != units.end() && u->second.hitpoints() < u->second.max_hitpoints()) {
|
|
const int unit_side = u->second.side();
|
|
|
|
//the healer won't heal an ally if there is a wounded unit on the same
|
|
//side next to her
|
|
if(unit_side == healer.side())
|
|
return false;
|
|
|
|
//choose an arbitrary order for healing
|
|
if(unit_side > side)
|
|
return false;
|
|
}
|
|
}
|
|
|
|
//there's no-one of higher priority nearby, so the ally will heal
|
|
return true;
|
|
}
|
|
|
|
}
|
|
|
|
void calculate_healing(display& disp, const gamemap& map,
|
|
std::map<gamemap::location,unit>& units, int side,
|
|
const std::vector<team>& teams)
|
|
{
|
|
std::map<gamemap::location,int> healed_units, max_healing;
|
|
|
|
std::map<gamemap::location,unit>::iterator i;
|
|
int amount_healed;
|
|
for(i = units.begin(); i != units.end(); ++i) {
|
|
amount_healed = 0;
|
|
|
|
//the unit heals if it's on this side, and it's on a tower or
|
|
//it has regeneration, and it is wounded
|
|
if(i->second.side() == side) {
|
|
if(i->second.hitpoints() < i->second.max_hitpoints()){
|
|
if((map.underlying_terrain(map[i->first.x][i->first.y]) == gamemap::TOWER ||
|
|
i->second.type().regenerates())) {
|
|
amount_healed = game_config::cure_amount;
|
|
} else if(i->second.is_resting()){
|
|
amount_healed = game_config::rest_heal_amount;
|
|
}
|
|
}
|
|
i->second.set_resting(true);
|
|
if(amount_healed != 0)
|
|
healed_units.insert(std::pair<gamemap::location,int>(
|
|
i->first, amount_healed));
|
|
}
|
|
|
|
//otherwise find the maximum healing for the unit
|
|
if(amount_healed == 0) {
|
|
int max_heal = 0;
|
|
gamemap::location adjacent[6];
|
|
get_adjacent_tiles(i->first,adjacent);
|
|
for(int j = 0; j != 6; ++j) {
|
|
if(will_heal(adjacent[j],i->second.side(),teams,units)) {
|
|
const unit_map::const_iterator healer = units.find(adjacent[j]);
|
|
max_heal = maximum(max_heal,healer->second.type().max_unit_healing());
|
|
}
|
|
}
|
|
|
|
if(max_heal > 0) {
|
|
max_healing.insert(std::pair<gamemap::location,int>(i->first,max_heal));
|
|
}
|
|
}
|
|
}
|
|
|
|
//now see about units that can heal other units
|
|
for(i = units.begin(); i != units.end(); ++i) {
|
|
|
|
if(will_heal(i->first,side,teams,units)) {
|
|
gamemap::location adjacent[6];
|
|
bool gets_healed[6];
|
|
get_adjacent_tiles(i->first,adjacent);
|
|
|
|
int nhealed = 0;
|
|
int j;
|
|
for(j = 0; j != 6; ++j) {
|
|
const std::map<gamemap::location,unit>::const_iterator adj =
|
|
units.find(adjacent[j]);
|
|
if(adj != units.end() &&
|
|
adj->second.hitpoints() < adj->second.max_hitpoints() &&
|
|
adj->second.side() == side &&
|
|
healed_units[adj->first] < max_healing[adj->first]) {
|
|
++nhealed;
|
|
gets_healed[j] = true;
|
|
} else {
|
|
gets_healed[j] = false;
|
|
}
|
|
}
|
|
|
|
if(nhealed == 0)
|
|
continue;
|
|
|
|
const int healing_per_unit = i->second.type().heals()/nhealed;
|
|
|
|
for(j = 0; j != 6; ++j) {
|
|
if(!gets_healed[j])
|
|
continue;
|
|
|
|
assert(units.find(adjacent[j]) != units.end());
|
|
|
|
healed_units[adjacent[j]]
|
|
= minimum(max_healing[adjacent[j]],
|
|
healed_units[adjacent[j]]+healing_per_unit);
|
|
}
|
|
}
|
|
}
|
|
|
|
//poisoned units will take the same amount of damage per turn, as
|
|
//curing heals until they are reduced to 1 hitpoint. If they are
|
|
//cured on a turn, they recover 0 hitpoints that turn, but they
|
|
//are no longer poisoned
|
|
for(i = units.begin(); i != units.end(); ++i) {
|
|
|
|
if(i->second.side() == side && i->second.has_flag("poisoned")) {
|
|
const int damage = minimum<int>(game_config::cure_amount,
|
|
i->second.hitpoints()-1);
|
|
|
|
if(damage > 0) {
|
|
healed_units.insert(std::pair<gamemap::location,int>(i->first,-damage));
|
|
}
|
|
}
|
|
}
|
|
|
|
for(std::map<gamemap::location,int>::iterator h = healed_units.begin();
|
|
h != healed_units.end(); ++h) {
|
|
|
|
const gamemap::location& loc = h->first;
|
|
|
|
const bool show_healing = !disp.turbo() && !recorder.skipping() &&
|
|
!disp.fogged(loc.x,loc.y);
|
|
|
|
assert(units.count(loc) == 1);
|
|
|
|
unit& u = units.find(loc)->second;
|
|
|
|
if(show_healing) {
|
|
disp.scroll_to_tile(loc.x,loc.y,display::WARP);
|
|
disp.select_hex(loc);
|
|
disp.update_display();
|
|
}
|
|
|
|
const int DelayAmount = 50;
|
|
|
|
std::cerr << "unit is poisoned? " << (u.has_flag("poisoned") ? "yes" : "no") << "," << h->second << "," << max_healing[h->first] << "\n";
|
|
|
|
if(u.has_flag("poisoned") && h->second > 0) {
|
|
|
|
//poison is purged only if we are on a village or next to a curer
|
|
if(h->second >= game_config::cure_amount ||
|
|
max_healing[h->first] >= game_config::cure_amount) {
|
|
u.remove_flag("poisoned");
|
|
|
|
if(show_healing) {
|
|
sound::play_sound("heal.wav");
|
|
SDL_Delay(DelayAmount);
|
|
disp.invalidate_unit();
|
|
disp.update_display();
|
|
}
|
|
}
|
|
|
|
h->second = 0;
|
|
} else if(h->second < 0) {
|
|
if(show_healing)
|
|
sound::play_sound("groan.wav");
|
|
} else if(h->second > 0) {
|
|
if(show_healing)
|
|
sound::play_sound("heal.wav");
|
|
}
|
|
|
|
if(h->second > 0 && h->second > u.max_hitpoints()-u.hitpoints()) {
|
|
h->second = u.max_hitpoints()-u.hitpoints();
|
|
if(h->second <= 0)
|
|
continue;
|
|
}
|
|
|
|
while(h->second > 0) {
|
|
const Uint16 heal_colour = disp.rgb(0,0,200);
|
|
u.heal(1);
|
|
|
|
if(show_healing) {
|
|
if(is_odd(h->second))
|
|
disp.draw_tile(loc.x,loc.y,NULL,0.5,heal_colour);
|
|
else
|
|
disp.draw_tile(loc.x,loc.y);
|
|
SDL_Delay(DelayAmount);
|
|
disp.update_display();
|
|
}
|
|
|
|
--h->second;
|
|
}
|
|
|
|
while(h->second < 0) {
|
|
const Uint16 damage_colour = disp.rgb(200,0,0);
|
|
u.gets_hit(1);
|
|
|
|
if(show_healing) {
|
|
if(is_odd(h->second))
|
|
disp.draw_tile(loc.x,loc.y,NULL,0.5,damage_colour);
|
|
else
|
|
disp.draw_tile(loc.x,loc.y);
|
|
|
|
SDL_Delay(DelayAmount);
|
|
disp.update_display();
|
|
}
|
|
|
|
++h->second;
|
|
}
|
|
|
|
if(show_healing) {
|
|
disp.draw_tile(loc.x,loc.y);
|
|
disp.update_display();
|
|
}
|
|
}
|
|
}
|
|
|
|
unit get_advanced_unit(const game_data& info,
|
|
std::map<gamemap::location,unit>& units,
|
|
const gamemap::location& loc, const std::string& advance_to)
|
|
{
|
|
const std::map<std::string,unit_type>::const_iterator new_type = info.unit_types.find(advance_to);
|
|
const std::map<gamemap::location,unit>::iterator un = units.find(loc);
|
|
if(new_type != info.unit_types.end() && un != units.end()) {
|
|
return unit(&(new_type->second),un->second);
|
|
} else {
|
|
throw gamestatus::game_error("Could not find the unit being advanced"
|
|
" to: " + advance_to);
|
|
}
|
|
}
|
|
|
|
void advance_unit(const game_data& info,
|
|
std::map<gamemap::location,unit>& units,
|
|
gamemap::location loc, const std::string& advance_to)
|
|
{
|
|
const unit& new_unit = get_advanced_unit(info,units,loc,advance_to);
|
|
|
|
units.erase(loc);
|
|
units.insert(std::pair<gamemap::location,unit>(loc,new_unit));
|
|
}
|
|
|
|
void check_victory(std::map<gamemap::location,unit>& units,
|
|
std::vector<team>& teams)
|
|
{
|
|
std::vector<int> seen_leaders;
|
|
for(std::map<gamemap::location,unit>::const_iterator i = units.begin();
|
|
i != units.end(); ++i) {
|
|
if(i->second.can_recruit()) {
|
|
std::cerr << "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_towers();
|
|
}
|
|
}
|
|
|
|
bool found_enemies = false;
|
|
bool found_human = false;
|
|
|
|
for(size_t n = 0; n != seen_leaders.size(); ++n) {
|
|
const size_t side = seen_leaders[n]-1;
|
|
|
|
assert(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(side < teams.size() && teams[side].is_human()) {
|
|
found_human = true;
|
|
}
|
|
}
|
|
|
|
if(found_enemies == false) {
|
|
if(found_human) {
|
|
game_events::fire("enemies defeated");
|
|
}
|
|
|
|
throw end_level_exception(found_human ? VICTORY : DEFEAT);
|
|
}
|
|
|
|
//remove any units which are leaderless
|
|
//this code currently removed, to try not removing leaderless enemies
|
|
/*
|
|
std::map<gamemap::location,unit>::iterator j = units.begin();
|
|
while(j != units.end()) {
|
|
if(std::find(seen_leaders.begin(),seen_leaders.end(),j->second.side()) == seen_leaders.end()) {
|
|
units.erase(j);
|
|
j = units.begin();
|
|
} else {
|
|
++j;
|
|
}
|
|
}*/
|
|
}
|
|
|
|
const time_of_day& timeofday_at(const gamestatus& status,
|
|
const std::map<gamemap::location,unit>& units,
|
|
const gamemap::location& loc)
|
|
{
|
|
bool lighten = false;
|
|
|
|
if(loc.valid()) {
|
|
gamemap::location locs[7];
|
|
locs[0] = loc;
|
|
get_adjacent_tiles(loc,locs+1);
|
|
|
|
for(int i = 0; i != 7; ++i) {
|
|
const std::map<gamemap::location,unit>::const_iterator itor =
|
|
units.find(locs[i]);
|
|
if(itor != units.end() &&
|
|
itor->second.type().illuminates()) {
|
|
lighten = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return status.get_time_of_day(lighten);
|
|
}
|
|
|
|
int combat_modifier(const gamestatus& status,
|
|
const std::map<gamemap::location,unit>& units,
|
|
const gamemap::location& loc,
|
|
unit_type::ALIGNMENT alignment)
|
|
{
|
|
const time_of_day& tod = timeofday_at(status,units,loc);
|
|
|
|
int bonus = tod.lawful_bonus;
|
|
|
|
if(alignment == unit_type::NEUTRAL)
|
|
bonus = 0;
|
|
else if(alignment == unit_type::CHAOTIC)
|
|
bonus = -bonus;
|
|
|
|
return bonus;
|
|
}
|
|
|
|
namespace {
|
|
|
|
bool clear_shroud_loc(const gamemap& map, team& tm,
|
|
const gamemap::location& loc,
|
|
std::vector<gamemap::location>* cleared)
|
|
{
|
|
bool result = false;
|
|
gamemap::location adj[7];
|
|
get_adjacent_tiles(loc,adj);
|
|
adj[6] = loc;
|
|
for(int i = 0; i != 7; ++i) {
|
|
if(map.on_board(adj[i])) {
|
|
if(tm.fogged(adj[i].x,adj[i].y)) {
|
|
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
|
|
//returns true/false in seen_unit if new units has/has not been seen
|
|
//if known_units is NULL, seen_unit can be NULL and seen_unit is undefined
|
|
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,
|
|
bool* seen_unit)
|
|
{
|
|
bool res;
|
|
|
|
std::vector<gamemap::location> cleared_locations;
|
|
|
|
paths p(map,status,gamedata,units,loc,teams,true,false);
|
|
for(paths::routes_map::const_iterator i = p.routes.begin();
|
|
i != p.routes.end(); ++i) {
|
|
clear_shroud_loc(map,teams[team],i->first,&cleared_locations);
|
|
}
|
|
|
|
//clear the location the unit is at
|
|
clear_shroud_loc(map,teams[team],loc,&cleared_locations);
|
|
|
|
res = (cleared_locations.empty() == false);
|
|
|
|
for(std::vector<gamemap::location>::const_iterator it =
|
|
cleared_locations.begin(); it != cleared_locations.end(); ++it) {
|
|
if(units.count(*it)) {
|
|
if(seen_unit == NULL) {
|
|
static const std::string sighted("sighted");
|
|
game_events::fire(sighted,*it,loc);
|
|
} else if(known_units->count(*it) == 0) {
|
|
*seen_unit = true;
|
|
return res;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(seen_unit != NULL) {
|
|
*seen_unit = false;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
}
|
|
|
|
void recalculate_fog(const gamemap& map, const gamestatus& status,
|
|
const game_data& gamedata, const unit_map& units,
|
|
std::vector<team>& teams, int team) {
|
|
|
|
teams[team].refog();
|
|
|
|
for(unit_map::const_iterator i = units.begin(); i != units.end(); ++i) {
|
|
if(i->second.side() == team+1) {
|
|
|
|
//we're not really going to mutate the unit, just temporarily
|
|
//set its moves to maximum, but then switch them back
|
|
unit& mutable_unit = const_cast<unit&>(i->second);
|
|
const unit_movement_resetter move_resetter(mutable_unit);
|
|
|
|
clear_shroud_unit(map,status,gamedata,units,i->first,teams,team,NULL,NULL);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool clear_shroud(display& disp, const gamestatus& status,
|
|
const gamemap& map, const game_data& gamedata,
|
|
const unit_map& units, std::vector<team>& teams, int team)
|
|
{
|
|
if(teams[team].uses_shroud() == false && teams[team].uses_fog() == false)
|
|
return false;
|
|
|
|
bool result = false;
|
|
|
|
unit_map::const_iterator i;
|
|
for(i = units.begin(); i != units.end(); ++i) {
|
|
if(i->second.side() == team+1) {
|
|
|
|
//we're not really going to mutate the unit, just temporarily
|
|
//set its moves to maximum, but then switch them back
|
|
unit& mutable_unit = const_cast<unit&>(i->second);
|
|
const unit_movement_resetter move_resetter(mutable_unit);
|
|
|
|
result |= clear_shroud_unit(map,status,gamedata,units,i->first,teams,team,NULL,NULL);
|
|
}
|
|
}
|
|
|
|
recalculate_fog(map,status,gamedata,units,teams,team);
|
|
|
|
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,
|
|
const std::vector<gamemap::location>& route,
|
|
replay* move_recorder, undo_list* undo_stack, gamemap::location *next_unit)
|
|
{
|
|
//stop the user from issuing any commands while the unit is moving
|
|
const command_disabler disable_commands;
|
|
|
|
assert(!route.empty());
|
|
|
|
unit_map::iterator ui = units.find(route.front());
|
|
|
|
assert(ui != units.end());
|
|
|
|
ui->second.set_goto(gamemap::location());
|
|
|
|
unit u = ui->second;
|
|
|
|
const size_t team_num = u.side()-1;
|
|
|
|
const bool skirmisher = u.type().is_skirmisher();
|
|
|
|
//if we use shroud/fog of war, count out the units we can currently see
|
|
std::set<gamemap::location> seen_units;
|
|
if(teams[team_num].uses_shroud() || teams[team_num].uses_fog()) {
|
|
for(unit_map::const_iterator u = units.begin(); u != units.end(); ++u) {
|
|
if(teams[team_num].fogged(u->first.x,u->first.y) == false) {
|
|
seen_units.insert(u->first);
|
|
}
|
|
}
|
|
}
|
|
|
|
//see how far along the given path we can move
|
|
const int starting_moves = u.movement_left();
|
|
int moves_left = starting_moves;
|
|
bool seen_unit = false;
|
|
bool discovered_unit = false;
|
|
bool should_clear_stack = false;
|
|
std::vector<gamemap::location>::const_iterator step;
|
|
for(step = route.begin()+1; step != route.end(); ++step) {
|
|
const gamemap::TERRAIN terrain = map[step->x][step->y];
|
|
|
|
const unit_map::const_iterator enemy_unit = units.find(*step);
|
|
|
|
const int mv = u.movement_cost(map,terrain);
|
|
if(discovered_unit || seen_unit || mv > moves_left || enemy_unit != units.end() &&
|
|
teams[team_num].is_enemy(enemy_unit->second.side())) {
|
|
break;
|
|
} else {
|
|
moves_left -= mv;
|
|
}
|
|
|
|
if(!skirmisher && enemy_zoc(map,status,units,teams,*step,teams[team_num],
|
|
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(teams[team_num].uses_shroud() || teams[team_num].uses_fog()) {
|
|
if(units.count(*step) == 0 && map.underlying_terrain(map.get_terrain(*step)) != gamemap::TOWER) {
|
|
units.insert(std::pair<gamemap::location,unit>(*step,ui->second));
|
|
|
|
bool res;
|
|
|
|
should_clear_stack |=
|
|
clear_shroud_unit(map,status,gamedata,units,*step,teams,
|
|
ui->second.side()-1,&seen_units,&res);
|
|
units.erase(*step);
|
|
|
|
//we've seen a new unit. Stop on the next iteration
|
|
if(res) {
|
|
seen_unit = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
//check if we have discovered an invisible enemy unit
|
|
gamemap::location adjacent[6];
|
|
get_adjacent_tiles(*step,adjacent);
|
|
|
|
for(int i = 0; i != 6; ++i) {
|
|
//check if we are checking ourselves
|
|
if(adjacent[i]==ui->first) continue;
|
|
const std::map<gamemap::location,unit>::const_iterator it =
|
|
units.find(adjacent[i]);
|
|
if(it != units.end() && teams[u.side()].is_enemy(it->second.side()) &&
|
|
it->second.invisible(map.underlying_terrain(map[it->first.x][it->first.y]),status.get_time_of_day().lawful_bonus,it->first,units,teams)) {
|
|
discovered_unit = true;
|
|
should_clear_stack = true;
|
|
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() && units.count(steps.back()) != 0) {
|
|
steps.pop_back();
|
|
}
|
|
|
|
assert(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,
|
|
//unless we stop early because of sighting a unit
|
|
if(steps.size() != route.size() && !seen_unit) {
|
|
ui->second.set_goto(route.back());
|
|
u.set_goto(route.back());
|
|
}
|
|
|
|
if(steps.size() < 2) {
|
|
return 0;
|
|
}
|
|
|
|
units.erase(ui);
|
|
if(disp != NULL)
|
|
disp->move_unit(steps,u);
|
|
|
|
if(move_recorder != NULL)
|
|
move_recorder->add_movement(steps.front(),steps.back());
|
|
|
|
u.set_movement(moves_left);
|
|
|
|
ui = units.insert(std::pair<gamemap::location,unit>(steps.back(),u)).first;
|
|
if(disp != NULL) {
|
|
disp->invalidate_unit();
|
|
disp->invalidate(steps.back());
|
|
}
|
|
|
|
int orig_tower_owner = -1;
|
|
if(map.underlying_terrain(map[steps.back().x][steps.back().y]) == gamemap::TOWER) {
|
|
orig_tower_owner = tower_owner(steps.back(),teams);
|
|
|
|
if(orig_tower_owner != team_num) {
|
|
get_tower(steps.back(),teams,team_num,units);
|
|
ui->second.set_movement(0);
|
|
}
|
|
}
|
|
|
|
const bool event_mutated = game_events::fire("moveto",steps.back());
|
|
|
|
if(undo_stack != NULL) {
|
|
if(event_mutated || should_clear_stack) {
|
|
undo_stack->clear();
|
|
} else {
|
|
undo_stack->push_back(undo_action(steps,starting_moves,orig_tower_owner));
|
|
}
|
|
}
|
|
|
|
if(disp != NULL) {
|
|
disp->set_route(NULL);
|
|
disp->draw();
|
|
disp->recalculate_minimap();
|
|
}
|
|
|
|
assert(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);
|
|
assert(u_it != units.end());
|
|
|
|
const unit& u = u_it->second;
|
|
const team& current_team = teams[u.side()-1];
|
|
|
|
if(!u.can_attack())
|
|
return false;
|
|
|
|
//units with goto commands that have already done their gotos this turn
|
|
//(i.e. don't have full movement left) should be red
|
|
if(u.movement_left() < u.total_movement() && u.get_goto().valid()) {
|
|
return false;
|
|
}
|
|
|
|
gamemap::location locs[6];
|
|
get_adjacent_tiles(loc,locs);
|
|
for(int n = 0; n != 6; ++n) {
|
|
if(map.on_board(locs[n])) {
|
|
const unit_map::const_iterator i = units.find(locs[n]);
|
|
if(i != units.end()) {
|
|
if(current_team.is_enemy(i->second.side())) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if(u.movement_cost(map,map[locs[n].x][locs[n].y]) <= u.movement_left()) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|