mirror of
https://github.com/wesnoth/wesnoth
synced 2025-05-02 22:44:33 +00:00
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:
parent
eb93886325
commit
3d280bbc3d
@ -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();
|
||||
|
||||
|
@ -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,
|
||||
|
36
src/ai.cpp
36
src/ai.cpp
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
|
Loading…
x
Reference in New Issue
Block a user