New function best_attack_weapon:

uses (slightly modified) heuristic to figure out what the best weapon
to use is.  Make mouse_events use it to choose default attack to
highlight.

Plan is for ai to use this as well.
This commit is contained in:
Rusty Russell 2006-05-09 12:51:05 +00:00
parent a8001b7bda
commit 95727ed284
3 changed files with 91 additions and 68 deletions

View file

@ -730,10 +730,11 @@ battle_stats evaluate_battle_stats(const gamemap& map,
battle_context::battle_context(const gamemap& map, const std::vector<team>& teams, const std::map<gamemap::location,unit>& units,
const gamestatus& status, const game_data& gamedata,
const gamemap::location& attacker_loc, const gamemap::location& defender_loc,
const attack_type& attacker_weapon): attacker_stats_(NULL), defender_stats_(NULL)
unsigned int attacker_weapon): attacker_stats_(NULL), defender_stats_(NULL)
{
const unit& attacker = units.find(attacker_loc)->second;
const unit& defender = units.find(defender_loc)->second;
const attack_type &att = attacker.attacks()[attacker_weapon];
// To choose the best defender weapon, we have to compare different
// weapons with an heuristic function. The heuristic function requires
@ -743,21 +744,21 @@ battle_context::battle_context(const gamemap& map, const std::vector<team>& team
// has the best rating.
int best_rating = -1;
std::vector<attack_type>::const_iterator i;
for (i = defender.attacks().begin(); i != defender.attacks().end(); i++) {
for (unsigned int i = 0; i < defender.attacks().size(); i++) {
const attack_type &def = defender.attacks()[i];
// Skip weapons that do not match the attacker weapon range.
if (i->range() != attacker_weapon.range())
if (def.range() != att.range())
continue;
// Skip weapons that have a null defense weight.
if (i->defense_weight() <= 0)
if (def.defense_weight() <= 0)
continue;
unit_stats *current_attacker = new unit_stats(attacker, attacker_loc, &attacker_weapon, true,
defender, defender_loc, &(*i),
unit_stats *current_attacker = new unit_stats(attacker, attacker_loc, attacker_weapon,
true, defender, defender_loc, &def,
units, teams, status, map, gamedata);
unit_stats *current_defender = new unit_stats(defender, defender_loc, &(*i), false,
attacker, attacker_loc, &attacker_weapon,
unit_stats *current_defender = new unit_stats(defender, defender_loc, i, false,
attacker, attacker_loc, &att,
units, teams, status, map, gamedata);
int current_rating = rate_defender_weapon(*current_attacker, *current_defender);
assert(current_rating >= 0);
@ -777,11 +778,11 @@ battle_context::battle_context(const gamemap& map, const std::vector<team>& team
// If there is no defender weapon, compute the stats without a defender weapon.
if (attacker_stats_ == NULL) {
wassert(defender_stats_ == NULL);
attacker_stats_ = new unit_stats(attacker, attacker_loc, &attacker_weapon, true,
attacker_stats_ = new unit_stats(attacker, attacker_loc, attacker_weapon, true,
defender, defender_loc, NULL,
units, teams, status, map, gamedata);
defender_stats_ = new unit_stats(defender, defender_loc, NULL, false,
attacker, attacker_loc, &attacker_weapon,
defender_stats_ = new unit_stats(defender, defender_loc, -1, false,
attacker, attacker_loc, &att,
units, teams, status, map, gamedata);
}
}
@ -804,7 +805,7 @@ battle_context& battle_context::operator=(const battle_context &other)
}
battle_context::unit_stats::unit_stats(const unit &u, const gamemap::location& u_loc,
const attack_type *u_weapon, bool attacking,
int u_attack_num, bool attacking,
const unit &opp, const gamemap::location& opp_loc,
const attack_type *opp_weapon,
const std::map<gamemap::location,unit>& units,
@ -814,7 +815,12 @@ battle_context::unit_stats::unit_stats(const unit &u, const gamemap::location& u
const game_data& gamedata)
{
// Get the current state of the unit.
weapon = u_weapon;
attack_num = u_attack_num;
if (attack_num >= 0) {
weapon = &u.attacks()[attack_num];
} else {
weapon = NULL;
}
is_attacker = attacking;
is_poisoned = utils::string_bool(u.get_state("poisoned"));
is_slowed = utils::string_bool(u.get_state("slowed"));
@ -950,6 +956,9 @@ unsigned int battle_context::rate_attacker_weapon(double attack_weight) const
if (defender_stats_->drains)
attack_weight /= 2;
// Bias towards more damaging attacks.
attack_weight += attacker_stats_->num_blows * attacker_stats_->damage;
attack_weight *= attacker_stats_->num_blows * attacker_stats_->damage;
if (defender_stats_->num_blows * defender_stats_->damage)
attack_weight /= defender_stats_->num_blows * defender_stats_->damage;
@ -965,6 +974,32 @@ int battle_context::rate_defender_weapon(const unit_stats&, const unit_stats& d_
return (int) (d_stats.damage * d_stats.num_blows * d_stats.weapon->defense_weight());
}
int best_attack_weapon(const gamemap& map, const std::vector<team>& teams,
const std::map<gamemap::location,unit>& units,
const gamestatus& status, const game_data& gamedata,
const gamemap::location& attacker_loc,
const gamemap::location& defender_loc,
std::vector<battle_context> &bc_vector)
{
int best_weapon_index = -1;
unsigned int best_weapon_rating;
const std::vector<attack_type>& attacks = units.find(attacker_loc)->second.attacks();
for (unsigned int i = 0; i != attacks.size(); ++i) {
// skip weapons with attack_weight=0
if (attacks[i].attack_weight() > 0) {
battle_context bc(map, teams, units, status, gamedata, attacker_loc, defender_loc, i);
bc_vector.push_back(bc);
unsigned int weapon_rating = bc.rate_attacker_weapon(attacks[i].attack_weight());
if (best_weapon_index < 0 || best_weapon_rating < weapon_rating) {
best_weapon_index = i;
best_weapon_rating = weapon_rating;
}
}
}
return best_weapon_index;
}
static std::string unit_dump(std::pair< gamemap::location, unit > const &u)
{
@ -1005,7 +1040,7 @@ void attack(display& gui, const gamemap& map,
static const std::string hides("hides");
a->second.set_state(hides,"");
battle_context bc(map, teams, units, state, info, attacker, defender, a->second.attacks()[attack_with]);
battle_context bc(map, teams, units, state, info, attacker, defender, attack_with);
const battle_context::unit_stats& a_stats = bc.get_attacker_stats();
const battle_context::unit_stats& d_stats = bc.get_defender_stats();

View file

@ -111,6 +111,7 @@ public:
struct unit_stats
{
const attack_type *weapon; // The weapon used by the unit to attack the opponent, or NULL if there is none.
int attack_num; // Index into unit->attacks() or -1 for none.
bool is_attacker; // True if the unit is the attacker.
bool is_poisoned; // True if the unit is poisoned at the beginning of the battle.
bool is_slowed; // True if the unit is slowed at the beginning of the battle.
@ -137,7 +138,7 @@ public:
std::string plague_type; // The plague type used by the attack, if any.
unit_stats(const unit &u, const gamemap::location& u_loc,
const attack_type *u_weapon, bool attacking,
int u_attack_num, bool attacking,
const unit &opp, const gamemap::location& opp_loc,
const attack_type *opp_weapon,
const std::map<gamemap::location,unit>& units,
@ -151,7 +152,7 @@ public:
battle_context(const gamemap& map, const std::vector<team>& teams, const std::map<gamemap::location,unit>& units,
const gamestatus& status, const game_data& gamedata,
const gamemap::location& attacker_loc, const gamemap::location& defender_loc,
const attack_type& attacker_weapon);
unsigned int attacker_weapon);
battle_context(const battle_context &other);
~battle_context() { delete attacker_stats_; delete defender_stats_; }
@ -178,6 +179,12 @@ private:
unit_stats *attacker_stats_, *defender_stats_;
};
int best_attack_weapon(const gamemap& map, const std::vector<team>& teams,
const std::map<gamemap::location,unit>& units,
const gamestatus& status, const game_data& gamedata,
const gamemap::location& attacker_loc,
const gamemap::location& defender_loc,
std::vector<battle_context> &bc_vector);
//attack: executes an attack.
void attack(display& gui, const gamemap& map,

View file

@ -1163,60 +1163,41 @@ bool mouse_handler::attack_enemy(unit_map::iterator attacker, unit_map::iterator
const gamemap::location attacker_loc = attacker->first;
const gamemap::location defender_loc = defender->first;
const std::vector<attack_type>& attacks = attacker->second.attacks();
std::vector<std::string> items;
std::vector<int> weapons;
int best_weapon_index = -1;
unsigned int best_weapon_rating;
// Not every weapon gets included in bc_vector (attack_weight must be non-zero)
std::vector<battle_context> bc_vector;
int best = best_attack_weapon(map_, teams_, units_, status_, gameinfo_, attacker->first, defender->first, bc_vector);
for(size_t a = 0; a != attacks.size(); ++a) {
// skip weapons with attack_weight=0
if (attacks[a].attack_weight() > 0){
weapons.push_back(a);
battle_context bc(map_, teams_, units_, status_, gameinfo_, attacker_loc, defender_loc, attacks[a]);
bc_vector.push_back(bc);
unsigned int weapon_rating = bc.rate_attacker_weapon(attacks[a].attack_weight());
if (best_weapon_index < 0 || best_weapon_rating < weapon_rating) {
best_weapon_index = items.size();
best_weapon_rating = weapon_rating;
}
for (unsigned int i = 0; i < bc_vector.size(); i++) {
const battle_context::unit_stats& att = battle_context::unit_stats(bc_vector[i].get_attacker_stats());
const battle_context::unit_stats& def = battle_context::unit_stats(bc_vector[i].get_defender_stats());
config tmp_config;
attack_type no_weapon(tmp_config, "fake_attack", "");
const attack_type& attw = attack_type(*att.weapon);
const attack_type& defw = attack_type(def.weapon ? *def.weapon : no_weapon);
const battle_context::unit_stats& att = battle_context::unit_stats(bc.get_attacker_stats());
const battle_context::unit_stats& def = battle_context::unit_stats(bc.get_defender_stats());
//if there is an attack special or defend special, we output a single space for the other unit, to make sure
//that the attacks line up nicely.
std::string special_pad = "";
if (!attw.weapon_specials().empty() || !defw.weapon_specials().empty())
special_pad = " ";
config tmp_config;
attack_type no_weapon(tmp_config, "fake_attack", "");
const attack_type& attw = attack_type(*att.weapon);
const attack_type& defw = attack_type(def.weapon ? *def.weapon : no_weapon);
//if there is an attack special or defend special, we output a single space for the other unit, to make sure
//that the attacks line up nicely.
std::string special_pad = "";
if (!attw.weapon_specials().empty() || !defw.weapon_specials().empty())
special_pad = " ";
std::stringstream atts;
atts << IMAGE_PREFIX << attw.icon() << COLUMN_SEPARATOR
<< font::BOLD_TEXT << attw.name() << "\n" << att.damage << "-"
<< att.num_blows << " " << attw.range() << " (" << att.chance_to_hit << "%)\n"
<< attw.weapon_specials() << special_pad
<< COLUMN_SEPARATOR << _("vs") << COLUMN_SEPARATOR
<< font::BOLD_TEXT << defw.name() << "\n" << def.damage << "-"
<< def.num_blows << " " << defw.range() << " (" << def.chance_to_hit << "%)\n"
<< defw.weapon_specials() << special_pad << COLUMN_SEPARATOR
<< IMAGE_PREFIX << defw.icon();
items.push_back(atts.str());
std::stringstream atts;
if ((int)i == best) {
atts << DEFAULT_ITEM;
}
}
atts << IMAGE_PREFIX << attw.icon() << COLUMN_SEPARATOR
<< font::BOLD_TEXT << attw.name() << "\n" << att.damage << "-"
<< att.num_blows << " " << attw.range() << " (" << att.chance_to_hit << "%)\n"
<< attw.weapon_specials() << special_pad
<< COLUMN_SEPARATOR << _("vs") << COLUMN_SEPARATOR
<< font::BOLD_TEXT << defw.name() << "\n" << def.damage << "-"
<< def.num_blows << " " << defw.range() << " (" << def.chance_to_hit << "%)\n"
<< defw.weapon_specials() << special_pad << COLUMN_SEPARATOR
<< IMAGE_PREFIX << defw.icon();
if (best_weapon_index >= 0) {
items[best_weapon_index] = DEFAULT_ITEM + items[best_weapon_index];
items.push_back(atts.str());
}
//make it so that when we attack an enemy, the attacking unit
@ -1245,9 +1226,9 @@ bool mouse_handler::attack_enemy(unit_map::iterator attacker, unit_map::iterator
NULL,&buttons);
}
cursor::set(cursor::NORMAL)
;
if(size_t(res) < weapons.size()) {
cursor::set(cursor::NORMAL);
if(size_t(res) < bc_vector.size()) {
const battle_context::unit_stats &att = bc_vector[res].get_attacker_stats();
attacker->second.set_goto(gamemap::location());
clear_undo_stack();
@ -1261,13 +1242,13 @@ bool mouse_handler::attack_enemy(unit_map::iterator attacker, unit_map::iterator
const bool defender_human = teams_[defender->second.side()-1].is_human();
recorder.add_attack(attacker_loc,defender_loc,weapons[res]);
recorder.add_attack(attacker_loc,defender_loc,att.attack_num);
//MP_COUNTDOWN grant time bonus for attacking
current_team().set_action_bonus_count(1 + current_team().action_bonus_count());
try {
attack(*gui_,map_,teams_,attacker_loc,defender_loc,weapons[res],units_,status_,gameinfo_);
attack(*gui_,map_,teams_,attacker_loc,defender_loc,att.attack_num,units_,status_,gameinfo_);
} catch(end_level_exception&) {
//if the level ends due to a unit being killed, still see if
//either the attacker or defender should advance