uint32_t install_(const std::string& fpath) { unit_map_cit cit = unit_map_.find(fpath); if(cit != unit_map_.end()) { // find it !, To be not install. return 0; } auto tpath = strip_last_of_delimita_path(fpath); auto bpath = get_file_path(tpath); bpath += '/'; unit_map_it it = unit_map_.find(bpath); if(it != unit_map_.end()) { unit_t& t = it->second; std::string name = get_file_name(tpath); if(fpath.back() == '/') name += '/'; t.install_child(name); } uint32_t hnd = handle_set_.create(); unit_t u; u.set_id(hnd); unit_map_.emplace(fpath, u); return hnd; }
bool backstab_check(const map_location& attacker_loc, const map_location& defender_loc, const unit_map& units, const std::vector<team>& teams) { const unit_map::const_iterator defender = units.find(defender_loc); if(defender == units.end()) return false; // No defender map_location adj[6]; get_adjacent_tiles(defender_loc, adj); int i; for(i = 0; i != 6; ++i) { if(adj[i] == attacker_loc) break; } if(i >= 6) return false; // Attack not from adjacent location const unit_map::const_iterator opp = units.find(adj[(i+3)%6]); if(opp == units.end()) return false; // No opposite unit if (opp->incapacitated()) return false; if (size_t(defender->side() - 1) >= teams.size() || size_t(opp->side() - 1) >= teams.size()) return true; // If sides aren't valid teams, then they are enemies if (teams[defender->side() - 1].is_enemy(opp->side())) return true; // Defender and opposite are enemies return false; // Defender and opposite are friends }
battle_context::battle_context(const unit_map& units, const map_location& attacker_loc, const map_location& defender_loc, int attacker_weapon, int defender_weapon, double aggression, const combatant *prev_def, const unit* attacker_ptr) : attacker_stats_(nullptr), defender_stats_(nullptr), attacker_combatant_(nullptr), defender_combatant_(nullptr) { const unit &attacker = attacker_ptr ? *attacker_ptr : *units.find(attacker_loc); const unit &defender = *units.find(defender_loc); const double harm_weight = 1.0 - aggression; if (attacker_weapon == -1 && attacker.attacks().size() == 1 && attacker.attacks()[0].attack_weight() > 0 ) attacker_weapon = 0; if (attacker_weapon == -1) { attacker_weapon = choose_attacker_weapon(attacker, defender, units, attacker_loc, defender_loc, harm_weight, &defender_weapon, prev_def); } else if (defender_weapon == -1) { defender_weapon = choose_defender_weapon(attacker, defender, attacker_weapon, units, attacker_loc, defender_loc, prev_def); } // If those didn't have to generate statistics, do so now. if (!attacker_stats_) { const attack_type *adef = nullptr; const attack_type *ddef = nullptr; if (attacker_weapon >= 0) { VALIDATE(attacker_weapon < static_cast<int>(attacker.attacks().size()), _("An invalid attacker weapon got selected.")); adef = &attacker.attacks()[attacker_weapon]; } if (defender_weapon >= 0) { VALIDATE(defender_weapon < static_cast<int>(defender.attacks().size()), _("An invalid defender weapon got selected.")); ddef = &defender.attacks()[defender_weapon]; } assert(!defender_stats_ && !attacker_combatant_ && !defender_combatant_); attacker_stats_ = new battle_context_unit_stats(attacker, attacker_loc, attacker_weapon, true, defender, defender_loc, ddef, units); defender_stats_ = new battle_context_unit_stats(defender, defender_loc, defender_weapon, false, attacker, attacker_loc, adef, units); } // There have been various bugs where only one of these was set assert(attacker_stats_); assert(defender_stats_); }
bool install_(const std::string& key, const T& value) { std::string fpath; if(!create_full_path(key, fpath)) { return false; } bool f = false; utils::strings ss = split_text(fpath, "/"); std::string p; for(uint32_t i = 0; i < ss.size(); ++i) { p += '/'; p += ss[i]; unit_t u; u.value = value; u.set_id(serial_id_); std::pair<unit_map_it, bool> ret = unit_map_.insert(unit_pair(p, u)); if(ret.second) { ++serial_id_; auto prev = utils::get_file_path(p); if(p != prev) { unit_map_it it = unit_map_.find(prev); if(it != unit_map_.end()) { unit_t& t = it->second; t.install_child(utils::get_file_name(p)); } } f = true; } else { f = false; } } return f; }
void move::apply_temp_modifier(unit_map& unit_map) { if (get_source_hex() == get_dest_hex()) return; //zero-hex move, used by attack subclass // Safety: Make sure the old temporary_unit_mover (if any) is destroyed // before creating a new one. mover_.reset(); //@todo: deal with multi-turn moves, which may for instance end their first turn // by capturing a village //@todo: we may need to change unit status here and change it back in remove_temp_modifier unit* unit; { unit_map::iterator unit_it = unit_map.find(get_source_hex()); assert(unit_it != unit_map.end()); unit = &*unit_it; } //Modify movement points DBG_WB <<"Move: Changing movement points for unit " << unit->name() << " [" << unit->id() << "] from " << unit->movement_left() << " to " << unit->movement_left() - movement_cost_ << ".\n"; // Move the unit DBG_WB << "Move: Temporarily moving unit " << unit->name() << " [" << unit->id() << "] from (" << get_source_hex() << ") to (" << get_dest_hex() <<")\n"; mover_.reset(new temporary_unit_mover(unit_map, get_source_hex(), get_dest_hex(), unit->movement_left() - movement_cost_)); //Update status of fake unit (not undone by remove_temp_modifiers) //@todo this contradicts the name "temp_modifiers" fake_unit_->set_movement(unit->movement_left(), true); }
bool can_generate(const gamemap& map, const std::vector<team>& teams, const unit_map& units, const unit& u, const map_location& loc) { if (!map.on_board(loc)) { return false; } if (u.movement_cost(map[loc]) == unit_movement_type::UNREACHABLE) { return false; } unit_map::const_iterator it = units.find(loc, false); if (it.valid() && !it->can_stand(u)) { return false; } map_location locs[6]; get_adjacent_tiles(loc, locs); for (int i = 0; i != 6; ++i) { if (!map.on_board(locs[i])) { continue; } if (u.movement_cost(map[locs[i]]) != unit_movement_type::UNREACHABLE) { return true; } } return false; }
time_of_day tod_manager::time_of_day_at(const unit_map& units,const map_location& loc, const gamemap& map) const { int lighten = std::max<int>(map.get_terrain_info(map.get_terrain(loc)).light_modification() , 0); int darken = std::min<int>(map.get_terrain_info(map.get_terrain(loc)).light_modification() , 0); time_of_day tod = get_time_of_day(lighten + darken,loc); if(loc.valid()) { map_location locs[7]; locs[0] = loc; get_adjacent_tiles(loc,locs+1); for(int i = 0; i != 7; ++i) { const unit_map::const_iterator itor = units.find(locs[i]); if(itor != units.end() && itor->second.get_ability_bool("illuminates") && !itor->second.incapacitated()) { unit_ability_list illum = itor->second.get_abilities("illuminates"); unit_abilities::effect illum_effect(illum,lighten,false); int mod = illum_effect.get_composite_value(); if(mod + tod.lawful_bonus > illum.highest("max_value").first) { mod = illum.highest("max_value").first - tod.lawful_bonus; } lighten = std::max<int>(mod, lighten); darken = std::min<int>(mod, darken); } } } tod = get_time_of_day(lighten + darken,loc); return tod; }
//-----------------------------------------------------------------// utils::strings get_sub_directory(const std::string& root, bool full) { utils::strings list; auto fpath = create_full_path(root); if(fpath.empty()) { return list; } if(fpath.back() != '/') fpath += '/'; unit_map_it it = unit_map_.find(fpath); if(it != unit_map_.end()) { const typename unit_t::childs& chs = it->second.get_childs(); list.resize(chs.size()); list.clear(); for(const auto& s : chs) { if(full) { list.push_back(fpath + strip_last_of_delimita_path(s)); } else { list.push_back(s); } } } return list; }
//-----------------------------------------------------------------// unit_map_its create_list(const std::string& root) { unit_map_its list; if(unit_map_.empty()) { list.clear(); return list; } if(root.empty()) { list.resize(unit_map_.size()); list.clear(); for(unit_map_it it = unit_map_.begin(); it != unit_map_.end(); ++it) { list.push_back(it); } } else { auto fpath = create_full_path(root); if(fpath.empty()) { list.clear(); return list; } unit_map_it it = unit_map_.find(fpath); if(it != unit_map_.end()) { const typename unit_t::childs& ch = it->second.get_childs(); list.resize(ch.size()); list.clear(); for(const auto& s : ch) { auto path = create_full_path(s); if(!path.empty()) { it = unit_map_.find(path); if(it != unit_map_.end()) { list.push_back(it); } } } } else { list.clear(); } } std::sort(list.begin(), list.end(), [] (unit_map_it l, unit_map_it r) { return l->first < r->first; } ); return list; }
std::pair<int, map_location> under_leadership(const unit_map& units, const map_location& loc) { const unit_map::const_iterator un = units.find(loc); if(un == units.end()) { return {0, map_location::null_location()}; } unit_ability_list abil = un->get_abilities("leadership"); return abil.highest("value"); }
//-----------------------------------------------------------------// bool is_directory(const std::string& path) const { auto fpath = create_full_path(path); if(fpath.empty()) { return false; } if(fpath.back() != '/') fpath += '/'; return is_directory(unit_map_.find(fpath)); }
//-----------------------------------------------------------------// uint32_t find(const std::string& path) const { auto fpath = create_full_path(path); if(fpath.empty()) { return 0; } unit_map_cit cit = unit_map_.find(fpath); if(cit != unit_map_.end()) { return cit->second.get_id(); } else { return 0; } }
map_location under_leadership(const unit_map& units, const map_location& loc, int* bonus) { const unit_map::const_iterator un = units.find(loc); if(un == units.end()) { return map_location::null_location; } unit_ability_list abil = un->get_abilities("leadership"); if(bonus) { *bonus = abil.highest("value").first; } return abil.highest("value").second; }
map_location pathfind::find_vacant_tile(const gamemap& map, const unit_map& units, const map_location& loc, pathfind::VACANT_TILE_TYPE vacancy, const unit* pass_check) { if (!map.on_board(loc)) return map_location(); std::set<map_location> pending_tiles_to_check, tiles_checked; pending_tiles_to_check.insert(loc); // Iterate out 50 hexes from loc for (int distance = 0; distance < 50; ++distance) { if (pending_tiles_to_check.empty()) return map_location(); //Copy over the hexes to check and clear the old set std::set<map_location> tiles_checking; tiles_checking.swap(pending_tiles_to_check); //Iterate over all the hexes we need to check foreach (const map_location &loc, tiles_checking) { //If this area is not a castle but should, skip it. if (vacancy == pathfind::VACANT_CASTLE && !map.is_castle(loc)) continue; const bool pass_check_and_unreachable = pass_check && pass_check->movement_cost(map[loc]) == unit_movement_type::UNREACHABLE; //If the unit can't reach the tile and we have searched //an area of at least radius 10 (arbitrary), skip the tile. //Neccessary for cases such as an unreachable //starting hex surrounded by 6 other unreachable hexes, in which case //the algorithm would not even search distance==1 //even if there's a reachable hex for distance==2. if (pass_check_and_unreachable && distance > 10) continue; //If the hex is empty and we do either no pass check or the hex is reachable, return it. if (units.find(loc) == units.end() && !pass_check_and_unreachable) return loc; map_location adjs[6]; get_adjacent_tiles(loc,adjs); foreach (const map_location &loc, adjs) { if (!map.on_board(loc)) continue; // Add the tile to be checked if it hasn't already been and // isn't being checked. if (tiles_checked.find(loc) == tiles_checked.end() && tiles_checking.find(loc) == tiles_checking.end()) { pending_tiles_to_check.insert(loc); } } } tiles_checked.swap(tiles_checking); } return map_location(); }
uint32_t erase_(const std::string& fpath, handles& hnds) { unit_map_it it = unit_map_.find(fpath); if(it == unit_map_.end()) return 0; auto tpath = strip_last_of_delimita_path(fpath); auto bpath = get_file_path(tpath); bpath += '/'; if(fpath.back() == '/') { // is directory // erase sub directories unit_t::childs chs = it->second.get_childs(); for(const auto& s : chs) { // std::cout << "Loop: " << (fpath + s) << std::endl; auto hnd = erase_(fpath + s, hnds); hnds.push_back(hnd); } } uint32_t hnd = 0; { unit_map_it it = unit_map_.find(fpath); if(it == unit_map_.end()) return 0; hnd = it->second.get_id(); handle_set_.erase(hnd); unit_map_.erase(it); } { // previous directory unit_map_it it = unit_map_.find(bpath); if(it != unit_map_.end()) { std::string s = get_file_name(tpath); if(fpath.back() == '/') s += '/'; it->second.erase_child(s); } } return hnd; }
map_location find_vacant_tile(const gamemap& map, const unit_map& units, const map_location& loc, VACANT_TILE_TYPE vacancy, const unit* pass_check) { std::set<map_location> pending_tiles_to_check; std::set<map_location> tiles_checked; pending_tiles_to_check.insert( loc ); // Iterate out 50 hexes from loc for (int distance = 0; distance < 50; ++distance) { if (pending_tiles_to_check.empty()) return map_location(); //Copy over the hexes to check and clear the old set std::set<map_location> tiles_checking = pending_tiles_to_check; std::set<map_location>::const_iterator tc_itor = tiles_checking.begin(); pending_tiles_to_check.clear(); //Iterate over all the hexes we need to check for ( ; tc_itor != tiles_checking.end(); ++tc_itor ) { //If the unit cannot reach this area or it's not a castle but should, skip it. if ((vacancy == VACANT_CASTLE && !map.is_castle(*tc_itor)) || (pass_check && pass_check->movement_cost(map[*tc_itor]) == unit_movement_type::UNREACHABLE)) continue; //If the hex is empty, return it. if (map.on_board(*tc_itor) && units.find(*tc_itor) == units.end()) return (*tc_itor); map_location adjs[6]; get_adjacent_tiles(*tc_itor,adjs); for (int i = 0; i != 6; ++i) { //Add the tile to be checked if it hasn't already been and isn't already //pending to be checked if (pending_tiles_to_check.find(adjs[i]) == pending_tiles_to_check.end() && tiles_checked.find(adjs[i]) == tiles_checked.end() && tiles_checking.find(adjs[i]) == tiles_checking.end()) { pending_tiles_to_check.insert(adjs[i]); } } } tiles_checked = tiles_checking; } return map_location(); }
map_location pathfind::find_vacant_tile(const gamemap& map, const unit_map& units, const map_location& loc, pathfind::VACANT_TILE_TYPE vacancy, const unit* pass_check) { if (!map.on_board(loc)) return map_location(); std::set<map_location> pending_tiles_to_check, tiles_checked; pending_tiles_to_check.insert(loc); // Iterate out 50 hexes from loc for (int distance = 0; distance < 50; ++distance) { if (pending_tiles_to_check.empty()) return map_location(); //Copy over the hexes to check and clear the old set std::set<map_location> tiles_checking; tiles_checking.swap(pending_tiles_to_check); //Iterate over all the hexes we need to check foreach (const map_location &loc, tiles_checking) { //If the unit cannot reach this area or it's not a castle but should, skip it. if ((vacancy == pathfind::VACANT_CASTLE && !map.is_castle(loc)) || (pass_check && pass_check->movement_cost(map[loc]) == unit_movement_type::UNREACHABLE)) continue; //If the hex is empty, return it. if (units.find(loc) == units.end()) return loc; map_location adjs[6]; get_adjacent_tiles(loc,adjs); foreach (const map_location &loc, adjs) { if (!map.on_board(loc)) continue; // Add the tile to be checked if it hasn't already been and // isn't being checked. if (tiles_checked.find(loc) == tiles_checked.end() && tiles_checking.find(loc) == tiles_checking.end()) { pending_tiles_to_check.insert(loc); } } } tiles_checked.swap(tiles_checking); } return map_location(); }
paths::paths(gamemap const &map, unit_map const &units, map_location const &loc, std::vector<team> const &teams, bool force_ignore_zoc, bool allow_teleport, const team &viewing_team, int additional_turns, bool see_all, bool ignore_units) : destinations() { const unit_map::const_iterator i = units.find(loc); if(i == units.end()) { ERR_PF << "paths::paths() -- unit not found\n"; return; } if(i->second.side() < 1 || i->second.side() > int(teams.size())) { return; } find_routes(map,units,i->second,loc, i->second.movement_left(), destinations, teams, force_ignore_zoc, allow_teleport,additional_turns,viewing_team, see_all, ignore_units); }
map_location pathfind::find_vacant_tile(const gamemap& map, const std::vector<team>& teams, const unit_map& units, const map_location& loc, const unit* pass_check) { if (!map.on_board(loc)) return map_location(); std::set<map_location> pending_tiles_to_check, tiles_checked; pending_tiles_to_check.insert(loc); // Iterate out 50 hexes from loc for (int distance = 0; distance < 50; ++distance) { //Copy over the hexes to check and clear the old set std::set<map_location> tiles_checking; tiles_checking.swap(pending_tiles_to_check); //Iterate over all the hexes we need to check BOOST_FOREACH (const map_location &loc, tiles_checking) { tiles_checked.insert(loc); // If the unit cannot reach this area or it's not a castle but should, skip it. if (!pass_check || can_generate(map, teams, units, *pass_check, loc)) { // If the hex is empty, return it. if (units.find(loc) == units.end()) { return loc; } } map_location adjs[6]; get_adjacent_tiles(loc, adjs); BOOST_FOREACH (const map_location &loc, adjs) { if (!map.on_board(loc)) continue; // Add the tile to be checked if it hasn't already been and // isn't being checked. if (tiles_checked.find(loc) == tiles_checked.end()) { pending_tiles_to_check.insert(loc); } } } }
const time_of_day tod_manager::get_illuminated_time_of_day(const unit_map & units, const gamemap & map, const map_location& loc, int for_turn) const { // get ToD ignoring illumination time_of_day tod = get_time_of_day(loc, for_turn); if ( map.on_board_with_border(loc) ) { // Now add terrain illumination. const int terrain_light = map.get_terrain_info(loc).light_bonus(tod.lawful_bonus); std::vector<int> mod_list; std::vector<int> max_list; std::vector<int> min_list; int most_add = 0; int most_sub = 0; // Find the "illuminates" effects from units that can affect loc. map_location locs[7]; locs[0] = loc; get_adjacent_tiles(loc,locs+1); for ( size_t i = 0; i != 7; ++i ) { const unit_map::const_iterator itor = units.find(locs[i]); if (itor != units.end() && itor->get_ability_bool("illuminates") && !itor->incapacitated()) { unit_ability_list illum = itor->get_abilities("illuminates"); unit_abilities::effect illum_effect(illum, terrain_light, false); const int unit_mod = illum_effect.get_composite_value(); // Record this value. mod_list.push_back(unit_mod); max_list.push_back(illum.highest("max_value").first); min_list.push_back(illum.lowest("min_value").first); if ( unit_mod > most_add ) most_add = unit_mod; else if ( unit_mod < most_sub ) most_sub = unit_mod; } } const bool net_darker = most_add < -most_sub; // Apply each unit's effect, tracking the best result. int best_result = terrain_light; const int base_light = terrain_light + (net_darker ? most_add : most_sub); for ( size_t i = 0; i != mod_list.size(); ++i ) { int result = bounded_add(base_light, mod_list[i], max_list[i], min_list[i]); if ( net_darker && result < best_result ) best_result = result; else if ( !net_darker && result > best_result ) best_result = result; } // Update the object we will return. tod.bonus_modified = best_result - tod.lawful_bonus; tod.lawful_bonus = best_result; } return tod; }
unit_map::iterator find_unit(const map_location & loc) { return units_.find(loc); }
static void verify(const unit_map& units, const config& cfg) { std::stringstream errbuf; LOG_REPLAY << "verifying unit structure...\n"; const size_t nunits = cfg["num_units"].to_size_t(); if(nunits != units.size()) { errbuf << "SYNC VERIFICATION FAILED: number of units from data source differ: " << nunits << " according to data source. " << units.size() << " locally\n"; std::set<map_location> locs; for (const config &u : cfg.child_range("unit")) { const map_location loc(u); locs.insert(loc); if(units.count(loc) == 0) { errbuf << "data source says there is a unit at " << loc << " but none found locally\n"; } } for(unit_map::const_iterator j = units.begin(); j != units.end(); ++j) { if (locs.count(j->get_location()) == 0) { errbuf << "local unit at " << j->get_location() << " but none in data source\n"; } } replay::process_error(errbuf.str()); errbuf.clear(); } for (const config &un : cfg.child_range("unit")) { const map_location loc(un); const unit_map::const_iterator u = units.find(loc); if(u == units.end()) { errbuf << "SYNC VERIFICATION FAILED: data source says there is a '" << un["type"] << "' (side " << un["side"] << ") at " << loc << " but there is no local record of it\n"; replay::process_error(errbuf.str()); errbuf.clear(); } config u_cfg; u->write(u_cfg); bool is_ok = true; static const std::string fields[] = {"type","hitpoints","experience","side",""}; for(const std::string* str = fields; str->empty() == false; ++str) { if (u_cfg[*str] != un[*str]) { errbuf << "ERROR IN FIELD '" << *str << "' for unit at " << loc << " data source: '" << un[*str] << "' local: '" << u_cfg[*str] << "'\n"; is_ok = false; } } if(!is_ok) { errbuf << "(SYNC VERIFICATION FAILED)\n"; replay::process_error(errbuf.str()); errbuf.clear(); } } LOG_REPLAY << "verification passed\n"; }
void attack_analysis::analyze(const gamemap& map, unit_map& units, const readonly_context& ai_obj, const move_map& dstsrc, const move_map& srcdst, const move_map& enemy_dstsrc, double aggression) { const unit_map::const_iterator defend_it = units.find(target); assert(defend_it != units.end()); // See if the target is a threat to our leader or an ally's leader. map_location adj[6]; get_adjacent_tiles(target,adj); size_t tile; for(tile = 0; tile != 6; ++tile) { const unit_map::const_iterator leader = units.find(adj[tile]); if(leader != units.end() && leader->can_recruit() && !ai_obj.current_team().is_enemy(leader->side())) { break; } } leader_threat = (tile != 6); uses_leader = false; target_value = defend_it->cost(); target_value += (double(defend_it->experience())/ double(defend_it->max_experience()))*target_value; target_starting_damage = defend_it->max_hitpoints() - defend_it->hitpoints(); // Calculate the 'alternative_terrain_quality' -- the best possible defensive values // the attacking units could hope to achieve if they didn't attack and moved somewhere. // This is used for comparative purposes, to see just how vulnerable the AI is // making itself. alternative_terrain_quality = 0.0; double cost_sum = 0.0; for(size_t i = 0; i != movements.size(); ++i) { const unit_map::const_iterator att = units.find(movements[i].first); const double cost = att->cost(); cost_sum += cost; alternative_terrain_quality += cost*ai_obj.best_defensive_position(movements[i].first,dstsrc,srcdst,enemy_dstsrc).chance_to_hit; } alternative_terrain_quality /= cost_sum*100; avg_damage_inflicted = 0.0; avg_damage_taken = 0.0; resources_used = 0.0; terrain_quality = 0.0; avg_losses = 0.0; chance_to_kill = 0.0; double def_avg_experience = 0.0; double first_chance_kill = 0.0; double prob_dead_already = 0.0; assert(!movements.empty()); std::vector<std::pair<map_location,map_location> >::const_iterator m; battle_context *prev_bc = NULL; const combatant *prev_def = NULL; for (m = movements.begin(); m != movements.end(); ++m) { // We fix up units map to reflect what this would look like. unit_ptr up = units.extract(m->first); up->set_location(m->second); units.insert(up); double m_aggression = aggression; if (up->can_recruit()) { uses_leader = true; // FIXME: suokko's r29531 omitted this line leader_threat = false; m_aggression = ai_obj.get_leader_aggression(); } bool from_cache = false; battle_context *bc; // This cache is only about 99% correct, but speeds up evaluation by about 1000 times. // We recalculate when we actually attack. const readonly_context::unit_stats_cache_t::key_type cache_key = std::make_pair(target, &up->type()); const readonly_context::unit_stats_cache_t::iterator usc = ai_obj.unit_stats_cache().find(cache_key); // Just check this attack is valid for this attacking unit (may be modified) if (usc != ai_obj.unit_stats_cache().end() && usc->second.first.attack_num < static_cast<int>(up->attacks().size())) { from_cache = true; bc = new battle_context(usc->second.first, usc->second.second); } else { bc = new battle_context(units, m->second, target, -1, -1, m_aggression, prev_def); } const combatant &att = bc->get_attacker_combatant(prev_def); const combatant &def = bc->get_defender_combatant(prev_def); delete prev_bc; prev_bc = bc; prev_def = &bc->get_defender_combatant(prev_def); if ( !from_cache ) { ai_obj.unit_stats_cache().insert( std::make_pair(cache_key, std::make_pair(bc->get_attacker_stats(), bc->get_defender_stats()))); } // Note we didn't fight at all if defender is already dead. double prob_fought = (1.0 - prob_dead_already); /** @todo 1.9 add combatant.prob_killed */ double prob_killed = def.hp_dist[0] - prob_dead_already; prob_dead_already = def.hp_dist[0]; double prob_died = att.hp_dist[0]; double prob_survived = (1.0 - prob_died) * prob_fought; double cost = up->cost(); const bool on_village = map.is_village(m->second); // Up to double the value of a unit based on experience cost += (double(up->experience()) / up->max_experience())*cost; resources_used += cost; avg_losses += cost * prob_died; // add half of cost for poisoned unit so it might get chance to heal avg_losses += cost * up->get_state(unit::STATE_POISONED) /2; if (!bc->get_defender_stats().is_poisoned) { avg_damage_inflicted += game_config::poison_amount * 2 * bc->get_defender_combatant().poisoned * (1 - prob_killed); } // Double reward to emphasize getting onto villages if they survive. if (on_village) { avg_damage_taken -= game_config::poison_amount*2 * prob_survived; } terrain_quality += (double(bc->get_defender_stats().chance_to_hit)/100.0)*cost * (on_village ? 0.5 : 1.0); double advance_prob = 0.0; // The reward for advancing a unit is to get a 'negative' loss of that unit if (!up->advances_to().empty()) { int xp_for_advance = up->max_experience() - up->experience(); // See bug #6272... in some cases, unit already has got enough xp to advance, // but hasn't (bug elsewhere?). Can cause divide by zero. if (xp_for_advance <= 0) xp_for_advance = 1; int fight_xp = defend_it->level(); int kill_xp = game_config::kill_xp(fight_xp); if (fight_xp >= xp_for_advance) { advance_prob = prob_fought; avg_losses -= up->cost() * prob_fought; } else if (kill_xp >= xp_for_advance) { advance_prob = prob_killed; avg_losses -= up->cost() * prob_killed; // The reward for getting a unit closer to advancement // (if it didn't advance) is to get the proportion of // remaining experience needed, and multiply it by // a quarter of the unit cost. // This will cause the AI to heavily favor // getting xp for close-to-advance units. avg_losses -= up->cost() * 0.25 * fight_xp * (prob_fought - prob_killed) / xp_for_advance; } else { avg_losses -= up->cost() * 0.25 * (kill_xp * prob_killed + fight_xp * (prob_fought - prob_killed)) / xp_for_advance; } // The reward for killing with a unit that plagues // is to get a 'negative' loss of that unit. if (bc->get_attacker_stats().plagues) { avg_losses -= prob_killed * up->cost(); } } // If we didn't advance, we took this damage. avg_damage_taken += (up->hitpoints() - att.average_hp()) * (1.0 - advance_prob); /** * @todo 1.9: attack_prediction.cpp should understand advancement * directly. For each level of attacker def gets 1 xp or * kill_experience. */ int fight_xp = up->level(); int kill_xp = game_config::kill_xp(fight_xp); def_avg_experience += fight_xp * (1.0 - att.hp_dist[0]) + kill_xp * att.hp_dist[0]; if (m == movements.begin()) { first_chance_kill = def.hp_dist[0]; } } if (!defend_it->advances_to().empty() && def_avg_experience >= defend_it->max_experience() - defend_it->experience()) { // It's likely to advance: only if we can kill with first blow. chance_to_kill = first_chance_kill; // Negative average damage (it will advance). avg_damage_inflicted += defend_it->hitpoints() - defend_it->max_hitpoints(); } else { chance_to_kill = prev_def->hp_dist[0]; avg_damage_inflicted += defend_it->hitpoints() - prev_def->average_hp(map.gives_healing(defend_it->get_location())); } delete prev_bc; terrain_quality /= resources_used; // Restore the units to their original positions. for (m = movements.begin(); m != movements.end(); ++m) { units.move(m->second, m->first); } }