Beispiel #1
0
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);
	}
}
Beispiel #2
0
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 &current_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);
        }
    }
}