Example #1
0
void recall::remove_temp_modifier(unit_map& unit_map)
{
	temp_unit_ = unit_map.extract(recall_hex_);
	assert(temp_unit_.get());

	//Put unit back into recall list
	resources::teams->at(team_index()).recall_list().add(temp_unit_);
}
Example #2
0
void recruit::remove_temp_modifier(unit_map& unit_map)
{
	temp_unit_ = unit_map.extract(recruit_hex_);
	assert(temp_unit_);

	/*
	 * Remove cost from money spent on recruits.
	 */
	resources::teams->at(team_index()).get_side_actions()->change_gold_spent_by(-temp_cost_);
	temp_cost_ = 0;
	resources::screen->invalidate_game_status();
}
Example #3
0
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);
	}
}
Example #4
0
void recruit::remove_temp_modifier(unit_map& unit_map)
{
	//Unit map gives back ownership of temp_unit_
	temp_unit_ = unit_map.extract(recruit_hex_);
	assert(temp_unit_.get());
}
Example #5
0
void attack_analysis::analyze(const gamemap& map, unit_map& units,
				std::map<std::pair<const unit*, const unit_type*>, battle_context*>& unit_stats_cache,
				const std::vector<std::pair<unit*, int> >& units2,
				const std::multimap<map_location, int>& srcdst2, const std::multimap<int, map_location>& dstsrc2,
				std::map<unit*, std::pair<map_location, unit*>* >& reside_cache,
				double aggression)
{
	target_value = target->cost();
	target_value += 0.5 * (double(target->experience()) / double(target->max_experience())) * target_value;
	target_starting_damage = target->max_hitpoints() - target->hitpoints();

	VALIDATE(!movements.empty(), _("ai::attack_analisis::analyze, movements is empty."));

	std::pair<std::pair<unit*, int>, map_location>& m = movements.back();
	map_location orig_loc = m.first.first->get_location();

	std::pair<map_location, unit*>* up;
	if (m.first.second < 0) {
		// We fix up units map to reflect what this would look like.
		if (m.second == orig_loc) {
			up = units.get_cookie(m.second, !m.first.first->base());
		} else {
			up = units.extract(orig_loc);
			up->first = m.second;
			units.place(up);
		}
	} else {
		// units.add(m.second, m.first.first);
		std::map<unit*, std::pair<map_location, unit*>* >::iterator cache_itor = reside_cache.find(m.first.first);
		if (cache_itor == reside_cache.end()) {
			up = new std::pair<map_location, unit*>(m.second, m.first.first);
			reside_cache[m.first.first] = up;
		} else {
			up = cache_itor->second;
		}
		up->first = m.second;
		units.place(up);
	}

	unit_map::node* base = units.get_cookie(m.second, false);
	bool on_wall = base && base->second->wall();

		int att_weapon = -1, def_weapon = -1;
		bool from_cache = false;
		battle_context *bc;
		const unit* src_ptr = up->second;

		// This cache is only about 99% correct, but speeds up evaluation by about 1000 times.
		// We recalculate when we actually attack.
		const unit_type* src_type;
		if (!src_ptr->packed()) {
			src_type = src_ptr->type();
		} else {
			src_type = unit_types.find(src_ptr->packee_type_id());
		}

		std::map<std::pair<const unit*, const unit_type*>, battle_context*>::iterator usc;
		usc = unit_stats_cache.find(std::pair<const unit*, const unit_type*>(target, src_type));
		// Just check this attack is valid for this attacking unit (may be modified)
		if (usc != unit_stats_cache.end() && usc->second->get_attacker_stats().attack_num < static_cast<int>(src_ptr->attacks().size())) {
			from_cache = true;
			bc = usc->second;
		} else {
			VALIDATE(false, _("ai::attack_analisis::analyze, cannot find unit_type pair in usc."));
		}

		// because save battle_context into usc direct, don't support prev_def.
		const combatant &att = bc->get_attacker_combatant(NULL);
		const combatant &def = bc->get_defender_combatant(NULL);

		// Note we didn't fight at all if defender is already dead.
		double prob_fought = (1.0 - prob_dead_already);

		// @todo 1.8 add combatant.prob_killed
		double prob_killed = def.dead_ - prob_dead_already;
		prob_dead_already = def.dead_;

		double prob_died = att.dead_;
		double prob_survived = (1.0 - prob_died) * prob_fought;

		double cost = up->second->cost();
		const bool on_village = map.is_village(m.second);
		// Up to double the value of a unit based on experience
		cost += 0.5 * (double(up->second->experience())/double(up->second->max_experience())) * cost;
		// We should encourage multi-phase attack.
		cost /= movements.size();

		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->second->get_state(unit::STATE_POISONED) /2;

		// Double reward to emphasize getting onto villages if they survive.
		if (on_village) {
			avg_damage_taken -= game_config::poison_amount*2 * prob_survived;
		}

		double quality = (double(bc->get_defender_stats().chance_to_hit)/100.0) * cost * (on_village ? 0.5 : 1.0);
		if (on_wall) {
			quality *= 0.2;
		}
		terrain_quality += quality;

		double advance_prob = 0.0;
		// The reward for advancing a unit is to get a 'negative' loss of that unit
		if (!up->second->advances_to().empty()) {
			int xp_for_advance = up->second->max_experience() - up->second->experience();
			int kill_xp, fight_xp;

			// 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;

			fight_xp = target->level();
			kill_xp = fight_xp ? fight_xp * game_config::kill_experience :
				game_config::kill_experience / 2;

			if (fight_xp >= xp_for_advance) {
				advance_prob = prob_fought;
				avg_losses -= up->second->cost() * prob_fought;
			} else if (kill_xp >= xp_for_advance) {
				advance_prob = prob_killed;
				avg_losses -= up->second->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->second->cost() * 0.25 *
					fight_xp * (prob_fought - prob_killed)
					/ xp_for_advance;
			} else {
				avg_losses -= up->second->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->second->cost();
			}
		}

		// If we didn't advance, we took this damage.
		avg_damage_taken += (up->second->hitpoints() - att.average_hp()) * (1.0 - advance_prob);

		//
		// @todo 1.8: attack_prediction.cpp should understand advancement
		// directly.  For each level of attacker def gets 1 xp or
		// kill_experience.
		//
		int fight_xp = up->second->level();
		int kill_xp = fight_xp ? fight_xp * game_config::kill_experience :
				game_config::kill_experience / 2;
		def_avg_experience += fight_xp * (1.0 - att.dead_) + kill_xp * att.dead_;
	

	if (!target->advances_to().empty() && def_avg_experience >= target->max_experience() - target->experience()) {
		// It's likely to advance: only if we can kill with first blow.
		if (movements.size() == 1) {
			chance_to_kill = def.dead_;
		}
		// Negative average damage (it will advance).
		avg_damage_inflicted += target->hitpoints() - target->max_hitpoints();
	} else {
		chance_to_kill += def.dead_;
		avg_damage_inflicted += target->hitpoints() - def.average_hp(map.gives_healing(target->get_location()));
	}

	// Restore the units to their original positions.
	if (m.first.second < 0) {
		if (m.second != orig_loc) {
			units.move(m.second, orig_loc);
		}
	} else {
		units.extract(m.second);
		// place() will update loc_ of up->second, it is time to be back to orig_loc.
		up->second->set_location(orig_loc);
		// delete up;
	}
}