battle_context_unit_stats::battle_context_unit_stats(const unit &u, const map_location& u_loc, int u_attack_num, bool attacking, const unit &opp, const map_location& opp_loc, const attack_type *opp_weapon, const unit_map& units) : weapon(NULL), attack_num(u_attack_num), is_attacker(attacking), is_poisoned(u.get_state(unit::STATE_POISONED)), is_slowed(u.get_state(unit::STATE_SLOWED)), slows(false), drains(false), petrifies(false), plagues(false), poisons(false), backstab_pos(false), swarm(false), firststrike(false), experience(u.experience()), max_experience(u.max_experience()), level(u.level()), rounds(1), hp(0), max_hp(u.max_hitpoints()), chance_to_hit(0), damage(0), slow_damage(0), drain_percent(0), drain_constant(0), num_blows(0), swarm_min(0), swarm_max(0), plague_type() { // Get the current state of the unit. if (attack_num >= 0) { weapon = &u.attacks()[attack_num]; } if(u.hitpoints() < 0) { LOG_CF << "Unit with " << u.hitpoints() << " hitpoints found, set to 0 for damage calculations\n"; hp = 0; } else if(u.hitpoints() > u.max_hitpoints()) { // If a unit has more hp than its maximum, the engine will fail // with an assertion failure due to accessing the prob_matrix // out of bounds. hp = u.max_hitpoints(); } else { hp = u.hitpoints(); } // Get the weapon characteristics, if any. if (weapon) { weapon->set_specials_context(u_loc, opp_loc, attacking, opp_weapon); if (opp_weapon) opp_weapon->set_specials_context(opp_loc, u_loc, !attacking, weapon); slows = weapon->get_special_bool("slow"); drains = !opp.get_state("undrainable") && weapon->get_special_bool("drains"); petrifies = weapon->get_special_bool("petrifies"); poisons = !opp.get_state("unpoisonable") && weapon->get_special_bool("poison") && !opp.get_state(unit::STATE_POISONED); backstab_pos = is_attacker && backstab_check(u_loc, opp_loc, units, *resources::teams); rounds = weapon->get_specials("berserk").highest("value", 1).first; firststrike = weapon->get_special_bool("firststrike"); // Handle plague. unit_ability_list plague_specials = weapon->get_specials("plague"); plagues = !opp.get_state("unplagueable") && !plague_specials.empty() && strcmp(opp.undead_variation().c_str(), "null") && !resources::game_map->is_village(opp_loc); if (plagues) { plague_type = (*plague_specials.front().first)["type"].str(); if (plague_type.empty()) plague_type = u.type().base_id(); } // Compute chance to hit. chance_to_hit = opp.defense_modifier( resources::game_map->get_terrain(opp_loc)) + weapon->accuracy() - (opp_weapon ? opp_weapon->parry() : 0); if(chance_to_hit > 100) { chance_to_hit = 100; } unit_ability_list cth_specials = weapon->get_specials("chance_to_hit"); unit_abilities::effect cth_effects(cth_specials, chance_to_hit, backstab_pos); chance_to_hit = cth_effects.get_composite_value(); // Compute base damage done with the weapon. int base_damage = weapon->modified_damage(backstab_pos); // Get the damage multiplier applied to the base damage of the weapon. int damage_multiplier = 100; // Time of day bonus. damage_multiplier += combat_modifier(u_loc, u.alignment(), u.is_fearless()); // Leadership bonus. int leader_bonus = 0; if (under_leadership(units, u_loc, &leader_bonus).valid()) damage_multiplier += leader_bonus; // Resistance modifier. damage_multiplier *= opp.damage_from(*weapon, !attacking, opp_loc); // Compute both the normal and slowed damage. damage = round_damage(base_damage, damage_multiplier, 10000); slow_damage = round_damage(base_damage, damage_multiplier, 20000); if (is_slowed) damage = slow_damage; // Compute drain amounts only if draining is possible. if(drains) { unit_ability_list drain_specials = weapon->get_specials("drains"); // Compute the drain percent (with 50% as the base for backward compatibility) unit_abilities::effect drain_percent_effects(drain_specials, 50, backstab_pos); drain_percent = drain_percent_effects.get_composite_value(); } // Add heal_on_hit (the drain constant) unit_ability_list heal_on_hit_specials = weapon->get_specials("heal_on_hit"); unit_abilities::effect heal_on_hit_effects(heal_on_hit_specials, 0, backstab_pos); drain_constant += heal_on_hit_effects.get_composite_value(); drains = drain_constant || drain_percent; // Compute the number of blows and handle swarm. weapon->modified_attacks(backstab_pos, swarm_min, swarm_max); swarm = swarm_min != swarm_max; num_blows = calc_blows(hp); } }
void aspect_attacks::do_attack_analysis( const map_location& loc, const move_map& srcdst, const move_map& dstsrc, const move_map& fullmove_srcdst, const move_map& fullmove_dstsrc, const move_map& enemy_srcdst, const move_map& enemy_dstsrc, const map_location* tiles, bool* used_locations, std::vector<map_location>& units, std::vector<attack_analysis>& result, attack_analysis& cur_analysis, const team ¤t_team ) const { // This function is called fairly frequently, so interact with the user here. ai::manager::raise_user_interact(); const int default_attack_depth = 5; if(cur_analysis.movements.size() >= size_t(default_attack_depth)) { //std::cerr << "ANALYSIS " << cur_analysis.movements.size() << " >= " << get_attack_depth() << "\n"; return; } gamemap &map_ = *resources::game_map; unit_map &units_ = *resources::units; std::vector<team> &teams_ = *resources::teams; const size_t max_positions = 1000; if(result.size() > max_positions && !cur_analysis.movements.empty()) { LOG_AI << "cut analysis short with number of positions\n"; return; } for(size_t i = 0; i != units.size(); ++i) { const map_location current_unit = units[i]; unit_map::iterator unit_itor = units_.find(current_unit); assert(unit_itor != units_.end()); // See if the unit has the backstab ability. // Units with backstab will want to try to have a // friendly unit opposite the position they move to. // // See if the unit has the slow ability -- units with slow only attack first. bool backstab = false, slow = false; std::vector<attack_type>& attacks = unit_itor->attacks(); for(std::vector<attack_type>::iterator a = attacks.begin(); a != attacks.end(); ++a) { a->set_specials_context(map_location(), map_location(), units_, true, NULL); if(a->get_special_bool("backstab")) { backstab = true; } if(a->get_special_bool("slow")) { slow = true; } } if(slow && cur_analysis.movements.empty() == false) { continue; } // Check if the friendly unit is surrounded, // A unit is surrounded if it is flanked by enemy units // and at least one other enemy unit is nearby // or if the unit is totaly surrounded by enemies // with max. one tile to escape. bool is_surrounded = false; bool is_flanked = false; int enemy_units_around = 0; int accessible_tiles = 0; map_location adj[6]; get_adjacent_tiles(current_unit, adj); size_t tile; for(tile = 0; tile != 3; ++tile) { const unit_map::const_iterator tmp_unit = units_.find(adj[tile]); bool possible_flanked = false; if(map_.on_board(adj[tile])) { accessible_tiles++; if (tmp_unit != units_.end() && current_team.is_enemy(tmp_unit->side())) { enemy_units_around++; possible_flanked = true; } } const unit_map::const_iterator tmp_opposite_unit = units_.find(adj[tile + 3]); if(map_.on_board(adj[tile + 3])) { accessible_tiles++; if (tmp_opposite_unit != units_.end() && current_team.is_enemy(tmp_opposite_unit->side())) { enemy_units_around++; if(possible_flanked) { is_flanked = true; } } } } if((is_flanked && enemy_units_around > 2) || enemy_units_around >= accessible_tiles - 1) is_surrounded = true; double best_vulnerability = 0.0, best_support = 0.0; int best_rating = 0; int cur_position = -1; // Iterate over positions adjacent to the unit, finding the best rated one. for(int j = 0; j != 6; ++j) { // If in this planned attack, a unit is already in this location. if(used_locations[j]) { continue; } // See if the current unit can reach that position. if (tiles[j] != current_unit) { typedef std::multimap<map_location,map_location>::const_iterator Itor; std::pair<Itor,Itor> its = dstsrc.equal_range(tiles[j]); while(its.first != its.second) { if(its.first->second == current_unit) break; ++its.first; } // If the unit can't move to this location. if(its.first == its.second || units_.find(tiles[j]) != units_.end()) { continue; } } unit_ability_list abil = unit_itor->get_abilities("leadership",tiles[j]); int best_leadership_bonus = abil.highest("value").first; double leadership_bonus = static_cast<double>(best_leadership_bonus+100)/100.0; if (leadership_bonus > 1.1) { LOG_AI << unit_itor->name() << " is getting leadership " << leadership_bonus << "\n"; } // Check to see whether this move would be a backstab. int backstab_bonus = 1; double surround_bonus = 1.0; if(tiles[(j+3)%6] != current_unit) { const unit_map::const_iterator itor = units_.find(tiles[(j+3)%6]); // Note that we *could* also check if a unit plans to move there // before we're at this stage, but we don't because, since the // attack calculations don't actually take backstab into account (too complicated), // this could actually make our analysis look *worse* instead of better. // So we only check for 'concrete' backstab opportunities. // That would also break backstab_check, since it assumes // the defender is in place. if(itor != units_.end() && backstab_check(tiles[j], loc, units_, teams_)) { if(backstab) { backstab_bonus = 2; } // No surround bonus if target is skirmisher if (!itor->get_ability_bool("skirmisher")) surround_bonus = 1.2; } } // See if this position is the best rated we've seen so far. int rating = static_cast<int>(rate_terrain(*unit_itor, tiles[j]) * backstab_bonus * leadership_bonus); if(cur_position >= 0 && rating < best_rating) { continue; } // Find out how vulnerable we are to attack from enemy units in this hex. //FIXME: suokko's r29531 multiplied this by a constant 1.5. ? const double vulnerability = power_projection(tiles[j],enemy_dstsrc);//? // Calculate how much support we have on this hex from allies. const double support = power_projection(tiles[j], fullmove_dstsrc);//? // If this is a position with equal defense to another position, // but more vulnerability then we don't want to use it. #ifdef SUOKKO //FIXME: this code was in sukko's r29531 Correct? // scale vulnerability to 60 hp unit if(cur_position >= 0 && rating < best_rating && (vulnerability/surround_bonus*30.0)/unit_itor->second.hitpoints() - (support*surround_bonus*30.0)/unit_itor->second.max_hitpoints() > best_vulnerability - best_support) { continue; } #else if(cur_position >= 0 && rating == best_rating && vulnerability/surround_bonus - support*surround_bonus >= best_vulnerability - best_support) { continue; } #endif cur_position = j; best_rating = rating; #ifdef SUOKKO //FIXME: this code was in sukko's r29531 Correct? best_vulnerability = (vulnerability/surround_bonus*30.0)/unit_itor->second.hitpoints(); best_support = (support*surround_bonus*30.0)/unit_itor->second.max_hitpoints(); #else best_vulnerability = vulnerability/surround_bonus; best_support = support*surround_bonus; #endif } if(cur_position != -1) { units.erase(units.begin() + i); cur_analysis.movements.push_back(std::pair<map_location,map_location>(current_unit,tiles[cur_position])); cur_analysis.vulnerability += best_vulnerability; cur_analysis.support += best_support; cur_analysis.is_surrounded = is_surrounded; cur_analysis.analyze(map_, units_, *this, dstsrc, srcdst, enemy_dstsrc, get_aggression()); result.push_back(cur_analysis); used_locations[cur_position] = true; do_attack_analysis(loc,srcdst,dstsrc,fullmove_srcdst,fullmove_dstsrc,enemy_srcdst,enemy_dstsrc, tiles,used_locations, units,result,cur_analysis, current_team); used_locations[cur_position] = false; cur_analysis.vulnerability -= best_vulnerability; cur_analysis.support -= best_support; cur_analysis.movements.pop_back(); units.insert(units.begin() + i, current_unit); } } }