Fix OOS bug...

...where one player (or observer) chooses a different defensive
weapon, as seen by Soliton (IIRC)

Xan pointed out this was a possibility: we calculate defense weapon
now based on attack_prediction code, but this uses floating point
calculations, which means that in the case where results are close,
rounding differences can mean that both ends choose different weapons.

The answer is to store the defense weapon as well as the attack
weapon, and pass it to all the relevent routines.
This commit is contained in:
Rusty Russell 2006-06-08 07:16:37 +00:00
parent eb93886325
commit 3d280bbc3d
9 changed files with 62 additions and 41 deletions

View File

@ -223,27 +223,24 @@ gamemap::location under_leadership(const unit_map& units,
return abil.highest("value").second;
}
unsigned int num_battle_contexts;
battle_context::battle_context(const gamemap& map, const std::vector<team>& teams, const unit_map& units,
const gamestatus& status, const game_data& gamedata,
const gamemap::location& attacker_loc, const gamemap::location& defender_loc,
int attacker_weapon, double harm_weight, const combatant *prev_def)
int attacker_weapon, int defender_weapon, double harm_weight, const combatant *prev_def)
: attacker_stats_(NULL), defender_stats_(NULL), attacker_combatant_(NULL), defender_combatant_(NULL)
{
num_battle_contexts++;
const unit& attacker = units.find(attacker_loc)->second;
const unit& defender = units.find(defender_loc)->second;
if (attacker_weapon == -1 && attacker.attacks().size() == 1)
attacker_weapon = 0;
int defender_weapon;
if (attacker_weapon == -1) {
wassert(defender_weapon == -1);
attacker_weapon = choose_attacker_weapon(attacker, defender, map, teams, units,
status, gamedata, attacker_loc, defender_loc,
harm_weight, &defender_weapon, prev_def);
} else {
} else if (defender_weapon == -1) {
defender_weapon = choose_defender_weapon(attacker, defender, attacker_weapon, map, teams,
units, status, gamedata, attacker_loc, defender_loc, prev_def);
}
@ -252,6 +249,7 @@ battle_context::battle_context(const gamemap& map, const std::vector<team>& team
if (!attacker_stats_) {
const attack_type *def = NULL;
if (defender_weapon >= 0) {
wassert(defender_weapon < defender.attacks().size());
def = &defender.attacks()[defender_weapon];
}
wassert(!defender_stats_ && !attacker_combatant_ && !defender_combatant_);
@ -633,6 +631,7 @@ void attack(display& gui, const gamemap& map,
gamemap::location attacker,
gamemap::location defender,
int attack_with,
int defend_with,
unit_map& units,
const gamestatus& state,
const game_data& info,
@ -663,7 +662,7 @@ void attack(display& gui, const gamemap& map,
config dat;
{
battle_context bc(map, teams, units, state, info, attacker, defender, attack_with);
battle_context bc(map, teams, units, state, info, attacker, defender, attack_with, defend_with);
const battle_context::unit_stats& a_stats = bc.get_attacker_stats();
const battle_context::unit_stats& d_stats = bc.get_defender_stats();
LOG_NG << "firing attack event\n";
@ -680,7 +679,7 @@ void attack(display& gui, const gamemap& map,
return;
}
}
battle_context bc(map, teams, units, state, info, attacker, defender, attack_with);
battle_context bc(map, teams, units, state, info, attacker, defender, attack_with, defend_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

@ -101,7 +101,7 @@ public:
battle_context(const gamemap& map, const std::vector<team>& teams, const unit_map& units,
const gamestatus& status, const game_data& gamedata,
const gamemap::location& attacker_loc, const gamemap::location& defender_loc,
int attacker_weapon = -1, double harm_weight = 1.0, const combatant *prev_def = NULL);
int attacker_weapon = -1, int defender_weapon = -1, double harm_weight = 1.0, const combatant *prev_def = NULL);
battle_context(const battle_context &other);
~battle_context() { delete attacker_stats_; delete defender_stats_; }
@ -150,6 +150,7 @@ void attack(display& gui, const gamemap& map,
gamemap::location attacker,
gamemap::location defender,
int attack_with,
int defend_with,
unit_map& units,
const gamestatus& state,
const game_data& info,

View File

@ -86,8 +86,13 @@ protected:
if(best_defense != -1) {
move_unit(best_movement.second,best_movement.first,possible_moves);
const int weapon = choose_weapon(best_movement.first,i->first);
attack_enemy(best_movement.first,i->first,weapon);
battle_context bc(get_info().map, get_info().teams,
get_info().units, get_info().state,
get_info().gameinfo, best_movement.first,
i->first, -1, -1, current_team().aggression());
attack_enemy(best_movement.first,i->first,
bc.get_attacker_stats().attack_num,
bc.get_defender_stats().attack_num);
do_attacks();
return;
}
@ -95,13 +100,6 @@ protected:
}
}
int choose_weapon(const location& attacker, const location& defender) {
battle_context bc(get_info().map, get_info().teams,
get_info().units, get_info().state,
get_info().gameinfo, attacker, defender, -1, current_team().aggression());
return bc.get_attacker_stats().attack_num;
}
void get_villages() {
std::map<location,paths> possible_moves;
move_map srcdst, dstsrc;
@ -593,8 +591,8 @@ gamemap::location ai::move_unit(location from, location to, std::map<location,pa
if(itor != units_.end() && current_team().is_enemy(itor->second.side()) &&
!itor->second.incapacitated()) {
battle_context bc(map_, teams_, units_, state_,
gameinfo_, res, *adj_i, -1, current_team().aggression());
attack_enemy(res,itor->first,bc.get_attacker_stats().attack_num);
gameinfo_, res, *adj_i, -1, -1, current_team().aggression());
attack_enemy(res,itor->first,bc.get_attacker_stats().attack_num,bc.get_defender_stats().attack_num);
break;
}
}
@ -617,10 +615,10 @@ bool ai::attack_close(const gamemap::location& loc) const
return false;
}
void ai::attack_enemy(const location& attacking_unit, const location& target, int weapon)
void ai::attack_enemy(const location& attacking_unit, const location& target, int att_weapon, int def_weapon)
{
attacks_.insert(attacking_unit);
ai_interface::attack_enemy(attacking_unit,target,weapon);
ai_interface::attack_enemy(attacking_unit,target,att_weapon,def_weapon);
}
void ai_interface::calculate_possible_moves(std::map<location,paths>& res, move_map& srcdst, move_map& dstsrc, bool enemy, bool assume_full_movement, const std::set<gamemap::location>* remove_destinations)
@ -978,7 +976,7 @@ bool ai::do_combat(std::map<gamemap::location,paths>& possible_moves, const move
return true;
}
attack_enemy(to,target_loc,weapon);
attack_enemy(to,target_loc,weapon,choice_it->def_weapons[0]);
//if this is the only unit in the attack, and the target
//is still alive, then also summon reinforcements
@ -993,7 +991,7 @@ bool ai::do_combat(std::map<gamemap::location,paths>& possible_moves, const move
}
}
void ai_interface::attack_enemy(const location& u, const location& target, int weapon)
void ai_interface::attack_enemy(const location& u, const location& target, int weapon, int def_weapon)
{
//stop the user from issuing any commands while the unit is attacking
const events::command_disabler disable_commands;
@ -1008,9 +1006,9 @@ void ai_interface::attack_enemy(const location& u, const location& target, int w
return;
}
recorder.add_attack(u,target,weapon);
recorder.add_attack(u,target,weapon,def_weapon);
attack(info_.disp, info_.map, info_.teams, u, target, weapon, info_.units, info_.state, info_.gameinfo);
attack(info_.disp, info_.map, info_.teams, u, target, weapon, def_weapon, info_.units, info_.state, info_.gameinfo);
check_victory(info_.units,info_.teams);
dialogs::advance_unit(info_.gameinfo,info_.map,info_.units,u,info_.disp,true);
@ -1408,9 +1406,9 @@ bool ai::move_to_targets(std::map<gamemap::location,paths>& possible_moves, move
if(enemy != units_.end() &&
current_team().is_enemy(enemy->second.side()) && !enemy->second.incapacitated()) {
//current behavior is to only make risk-free attacks
battle_context bc(map_, teams_, units_, state_, gameinfo_, arrived_at, adj[n], -1, 100.0);
battle_context bc(map_, teams_, units_, state_, gameinfo_, arrived_at, adj[n], -1, -1, 100.0);
if (bc.get_defender_stats().damage == 0) {
attack_enemy(arrived_at,adj[n],bc.get_attacker_stats().attack_num);
attack_enemy(arrived_at,adj[n],bc.get_attacker_stats().attack_num,bc.get_defender_stats().attack_num);
break;
}
}

View File

@ -97,7 +97,7 @@ protected:
location move_unit(location from, location to, std::map<location,paths>& possible_moves);
//our own version of 'attack_enemy'. We record all attacks to support group attacking
void attack_enemy(const location& attacking_unit, const location& target, int weapon);
void attack_enemy(const location& attacking_unit, const location& target, int att_weapon, int def_weapon);
std::set<location> attacks_;
@ -118,6 +118,7 @@ protected:
gamemap::location target;
std::vector<std::pair<gamemap::location,gamemap::location> > movements;
std::vector<int> weapons;
std::vector<int> def_weapons;
//the value of the unit being targeted
double target_value;

View File

@ -330,6 +330,7 @@ void ai::attack_analysis::analyze(const gamemap& map, unit_map& units,
avg_losses = 0.0;
chance_to_kill = 0.0;
weapons.clear();
def_weapons.clear();
double def_avg_experience = 0.0;
double first_chance_kill = 0.0;
@ -352,7 +353,7 @@ void ai::attack_analysis::analyze(const gamemap& map, unit_map& units,
leader_threat = false;
}
battle_context *bc = new battle_context(map, teams, units, status, gamedata, m->second, target, -1, 1.0 - aggression, prev_def);
battle_context *bc = new battle_context(map, teams, units, status, gamedata, m->second, target, -1, -1, 1.0 - aggression, prev_def);
const combatant &att = bc->get_attacker_combatant(prev_def);
const combatant &def = bc->get_defender_combatant(prev_def);
@ -361,6 +362,7 @@ void ai::attack_analysis::analyze(const gamemap& map, unit_map& units,
prev_def = &bc->get_defender_combatant(prev_def);
weapons.push_back(bc->get_attacker_stats().attack_num);
def_weapons.push_back(bc->get_defender_stats().attack_num);
// Note we didn't fight at all if defender already dead.
double prob_fought = (1.0 - prob_dead_already);
@ -487,6 +489,11 @@ double ai::attack_analysis::rating(double aggression, ai& ai_obj) const
value -= double(ai_obj.current_team().gold())*0.5;
}
//must be more cautious with leader
if (uses_leader) {
value -= vulnerability / support;
}
//prefer to attack already damaged targets
value += ((target_starting_damage/3 + avg_damage_inflicted) - (1.0-aggression)*avg_damage_taken)/10.0;
@ -694,6 +701,7 @@ bool ai::desperate_attack(const gamemap::location &loc)
double best_kill_prob = 0.0;
unsigned int best_weapon = 0;
int best_def_weapon = -1;
unsigned best_dir = 0;
{
@ -712,6 +720,7 @@ bool ai::desperate_attack(const gamemap::location &loc)
if (def.hp_dist[0] > best_kill_prob) {
best_kill_prob = def.hp_dist[0];
best_weapon = i;
best_def_weapon = bc.get_defender_stats().attack_num;
best_dir = n;
}
}
@ -721,7 +730,7 @@ bool ai::desperate_attack(const gamemap::location &loc)
}
if (best_kill_prob > 0.0) {
attack_enemy(loc, adj[best_dir], best_weapon);
attack_enemy(loc, adj[best_dir], best_weapon, best_def_weapon);
return true;
}
@ -754,8 +763,9 @@ bool ai::desperate_attack(const gamemap::location &loc)
// It's possible that there were no adjacent units to attack...
if (least_hp != u.hitpoints() + 1) {
battle_context bc(map_, teams_, units_, state_, gameinfo_, loc, adj[best_dir], -1, 0.5);
attack_enemy(loc, adj[best_dir], bc.get_attacker_stats().attack_num);
battle_context bc(map_, teams_, units_, state_, gameinfo_, loc, adj[best_dir], -1, -1, 0.5);
attack_enemy(loc, adj[best_dir], bc.get_attacker_stats().attack_num,
bc.get_defender_stats().attack_num);
return true;
}
return false;

View File

@ -96,9 +96,9 @@ protected:
///'attacking_unit': the location of the attacking unit
///'target': the location of the target unit. This unit must be in range of the
///attacking unit's weapon
///'weapon': the number of the weapon (0-based) which should be used in the attack.
///'att_weapon': the number of the weapon (0-based) which should be used in the attack.
///must be a valid weapon of the attacking unit
void attack_enemy(const location& attacking_unit, const location& target, int weapon);
void attack_enemy(const location& attacking_unit, const location& target, int att_weapon, int def_weapon);
///this function should be called to move a unit. Once the unit has been moved, its
///movement allowance is set to 0.

View File

@ -1249,6 +1249,7 @@ bool mouse_handler::attack_enemy(unit_map::iterator attacker, unit_map::iterator
cursor::set(cursor::NORMAL);
if(size_t(res) < bc_vector.size()) {
const battle_context::unit_stats &att = bc_vector[res].get_attacker_stats();
const battle_context::unit_stats &def = bc_vector[res].get_defender_stats();
attacker->second.set_goto(gamemap::location());
clear_undo_stack();
@ -1262,13 +1263,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,att.attack_num);
recorder.add_attack(attacker_loc,defender_loc,att.attack_num,def.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,att.attack_num,units_,status_,gameinfo_);
attack(*gui_,map_,teams_,attacker_loc,defender_loc,att.attack_num,def.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

View File

@ -295,12 +295,14 @@ void replay::add_movement(const gamemap::location& a,const gamemap::location& b)
add_pos("move",a,b);
}
void replay::add_attack(const gamemap::location& a, const gamemap::location& b, int weapon)
void replay::add_attack(const gamemap::location& a, const gamemap::location& b, int att_weapon, int def_weapon)
{
add_pos("attack",a,b);
char buf[100];
snprintf(buf,sizeof(buf),"%d",weapon);
snprintf(buf,sizeof(buf),"%d",att_weapon);
current_->child("attack")->values["weapon"] = buf;
snprintf(buf,sizeof(buf),"%d",def_weapon);
current_->child("attack")->values["defender_weapon"] = buf;
add_unit_checksum(a,current_);
add_unit_checksum(b,current_);
}
@ -953,6 +955,15 @@ bool do_replay(display& disp, const gamemap& map, const game_data& gameinfo,
const std::string& weapon = (*child)["weapon"];
const int weapon_num = atoi(weapon.c_str());
const std::string& def_weapon = (*child)["defender_weapon"];
int def_weapon_num = -1;
if (def_weapon.empty()) {
// Let's not gratuitously destroy backwards compat.
ERR_NW << "Old data, having to guess weapon\n";
} else {
def_weapon_num = atoi(def_weapon.c_str());
}
unit_map::iterator u = units.find(src);
if(u == units.end()) {
ERR_NW << "unfound location for source of attack\n";
@ -971,7 +982,7 @@ bool do_replay(display& disp, const gamemap& map, const game_data& gameinfo,
if (!game_config::ignore_replay_errors) throw replay::error();
}
attack(disp, map, teams, src, dst, weapon_num, units, state, gameinfo, !replayer.is_skipping());
attack(disp, map, teams, src, dst, weapon_num, def_weapon_num, units, state, gameinfo, !replayer.is_skipping());
u = units.find(src);
tgt = units.find(dst);

View File

@ -51,7 +51,7 @@ public:
void add_countdown_update(int value,int team);
void add_movement(const gamemap::location& a, const gamemap::location& b);
void add_attack(const gamemap::location& a, const gamemap::location& b,
int weapon);
int att_weapon, int def_weapon);
void choose_option(int index);
void add_label(const std::string& text, const gamemap::location& loc);
void clear_labels();