AI improvements to make it group better
This commit is contained in:
parent
91c2736d50
commit
4e5608d631
15 changed files with 587 additions and 113 deletions
|
@ -1,3 +1,29 @@
|
|||
[multiplayer]
|
||||
name=ai-test
|
||||
map=ai-test
|
||||
turns=90
|
||||
id=multiplayer-ai-test
|
||||
|
||||
{DAWN}
|
||||
{MORNING}
|
||||
{AFTERNOON}
|
||||
{DUSK}
|
||||
{FIRST_WATCH}
|
||||
{SECOND_WATCH}
|
||||
|
||||
[side]
|
||||
side=1
|
||||
enemy=2
|
||||
canrecruit=1
|
||||
controller=human
|
||||
[/side]
|
||||
[side]
|
||||
side=2
|
||||
enemy=1
|
||||
canrecruit=1
|
||||
controller=human
|
||||
[/side]
|
||||
[/multiplayer]
|
||||
[multiplayer]
|
||||
name="Across The River"
|
||||
map_data="ffffffffffffffgggggggggggggggggggggggg
|
||||
|
|
|
@ -313,7 +313,7 @@ language_button="Language"
|
|||
configure_sides="Configure Sides:"
|
||||
choose_side="Choose Team Settings:"
|
||||
client_choose_side="Choose your side:"
|
||||
no_sides_available="There are no available sides, and observers are not allowed in this game."
|
||||
no_sides_available="There are no available sides in this game."
|
||||
start_game="Start Game!"
|
||||
game_cancelled="The game has been cancelled"
|
||||
side_unavailable="The side you have chosen is no longer available"
|
||||
|
|
146
src/ai.cpp
146
src/ai.cpp
|
@ -25,6 +25,7 @@
|
|||
#include "show_dialog.hpp"
|
||||
#include "statistics.hpp"
|
||||
#include "unit_display.hpp"
|
||||
#include "util.hpp"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
|
@ -143,6 +144,20 @@ void ai_interface::user_interact()
|
|||
last_interact_ = SDL_GetTicks();
|
||||
}
|
||||
|
||||
void ai_interface::diagnostic(const std::string& msg)
|
||||
{
|
||||
if(game_config::debug) {
|
||||
info_.disp.set_diagnostic(msg);
|
||||
}
|
||||
}
|
||||
|
||||
void ai_interface::log_message(const std::string& msg)
|
||||
{
|
||||
if(game_config::debug) {
|
||||
info_.disp.add_chat_message("ai",info_.team_num,msg,display::MESSAGE_PUBLIC);
|
||||
}
|
||||
}
|
||||
|
||||
team& ai_interface::current_team()
|
||||
{
|
||||
return info_.teams[info_.team_num-1];
|
||||
|
@ -455,6 +470,8 @@ void ai::do_move()
|
|||
{
|
||||
log_scope("doing ai move");
|
||||
|
||||
game_config::debug = true;
|
||||
|
||||
invalidate_defensive_position_cache();
|
||||
|
||||
user_interact();
|
||||
|
@ -480,6 +497,8 @@ void ai::do_move()
|
|||
|
||||
std::vector<attack_analysis> analysis;
|
||||
|
||||
AI_DIAGNOSTIC("combat phase");
|
||||
|
||||
if(consider_combat_) {
|
||||
std::cerr << "combat...\n";
|
||||
consider_combat_ = do_combat(possible_moves,srcdst,dstsrc,enemy_srcdst,enemy_dstsrc);
|
||||
|
@ -489,6 +508,8 @@ void ai::do_move()
|
|||
}
|
||||
}
|
||||
|
||||
AI_DIAGNOSTIC("get villages phase");
|
||||
|
||||
std::cerr << "villages...\n";
|
||||
const bool got_village = get_villages(possible_moves,srcdst,dstsrc,enemy_srcdst,enemy_dstsrc,leader);
|
||||
if(got_village) {
|
||||
|
@ -496,6 +517,8 @@ void ai::do_move()
|
|||
return;
|
||||
}
|
||||
|
||||
AI_DIAGNOSTIC("healing phase");
|
||||
|
||||
std::cerr << "healing...\n";
|
||||
const bool healed_unit = get_healing(possible_moves,srcdst,dstsrc,enemy_srcdst,enemy_dstsrc);
|
||||
if(healed_unit) {
|
||||
|
@ -503,6 +526,8 @@ void ai::do_move()
|
|||
return;
|
||||
}
|
||||
|
||||
AI_DIAGNOSTIC("retreat phase");
|
||||
|
||||
std::cerr << "retreating...\n";
|
||||
const bool retreated_unit = retreat_units(possible_moves,srcdst,dstsrc,enemy_srcdst,enemy_dstsrc,leader);
|
||||
if(retreated_unit) {
|
||||
|
@ -514,6 +539,8 @@ void ai::do_move()
|
|||
remove_unit_from_moves(leader->first,srcdst,dstsrc);
|
||||
}
|
||||
|
||||
AI_DIAGNOSTIC("move/targetting phase");
|
||||
|
||||
const bool met_invisible_unit = move_to_targets(possible_moves,srcdst,dstsrc,enemy_srcdst,enemy_dstsrc,leader);
|
||||
if(met_invisible_unit) {
|
||||
std::cerr << "met_invisible_unit\n";
|
||||
|
@ -523,6 +550,8 @@ void ai::do_move()
|
|||
|
||||
std::cerr << "done move to targets\n";
|
||||
|
||||
AI_DIAGNOSTIC("leader/recruitment phase");
|
||||
|
||||
//recruitment phase and leader movement phase
|
||||
if(leader != units_.end()) {
|
||||
move_leader_to_keep(enemy_dstsrc);
|
||||
|
@ -533,6 +562,8 @@ void ai::do_move()
|
|||
}
|
||||
}
|
||||
|
||||
AI_DIAGNOSTIC("");
|
||||
|
||||
recorder.end_turn();
|
||||
}
|
||||
|
||||
|
@ -565,7 +596,7 @@ bool ai::do_combat(std::map<gamemap::location,paths>& possible_moves, const move
|
|||
if(skip_num > 0 && ((it - analysis.begin())%skip_num) && it->movements.size() > 1)
|
||||
continue;
|
||||
|
||||
const double rating = it->rating(current_team().aggression());
|
||||
const double rating = it->rating(current_team().aggression(),*this);
|
||||
std::cerr << "attack option rated at " << rating << " (" << current_team().aggression() << ")\n";
|
||||
if(rating > choice_rating) {
|
||||
choice_it = it;
|
||||
|
@ -599,7 +630,7 @@ bool ai::do_combat(std::map<gamemap::location,paths>& possible_moves, const move
|
|||
//if this is the only unit in the planned attack, and the target
|
||||
//is still alive, then also summon reinforcements
|
||||
if(choice_it->movements.size() == 1 && units_.count(target_loc)) {
|
||||
additional_targets_.push_back(target(target_loc,3.0));
|
||||
additional_targets_.push_back(target(target_loc,3.0,target::BATTLE_AID));
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -615,7 +646,7 @@ bool ai::do_combat(std::map<gamemap::location,paths>& possible_moves, const move
|
|||
if(already_done.count(loc) > 0)
|
||||
continue;
|
||||
|
||||
additional_targets_.push_back(target(loc,3.0));
|
||||
additional_targets_.push_back(target(loc,3.0,target::BATTLE_AID));
|
||||
already_done.insert(loc);
|
||||
}
|
||||
|
||||
|
@ -712,6 +743,8 @@ bool ai::get_villages(std::map<gamemap::location,paths>& possible_moves, const m
|
|||
start_pos = nearest_keep(leader->first);
|
||||
}
|
||||
|
||||
std::map<location,double> vulnerability;
|
||||
|
||||
//we want to build up a list of possible moves we can make that will capture villages.
|
||||
//limit the moves to 'max_village_moves' to make sure things don't get out of hand.
|
||||
const size_t max_village_moves = 50;
|
||||
|
@ -750,6 +783,25 @@ bool ai::get_villages(std::map<gamemap::location,paths>& possible_moves, const m
|
|||
continue;
|
||||
}
|
||||
|
||||
double threat = 0.0;
|
||||
const std::map<location,double>::const_iterator vuln = vulnerability.find(j->first);
|
||||
if(vuln != vulnerability.end()) {
|
||||
threat = vuln->second;
|
||||
} else {
|
||||
threat = power_projection(j->first,enemy_srcdst,enemy_dstsrc);
|
||||
vulnerability.insert(std::pair<location,double>(j->first,threat));
|
||||
}
|
||||
|
||||
const unit_map::const_iterator u = units_.find(j->second);
|
||||
if(u == units_.end()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const unit& un = u->second;
|
||||
if(un.hitpoints() < (threat*2*un.defense_modifier(map_,map_.get_terrain(j->first)))/100) {
|
||||
continue;
|
||||
}
|
||||
|
||||
village_moves.push_back(*j);
|
||||
}
|
||||
|
||||
|
@ -950,7 +1002,7 @@ bool ai::move_to_targets(std::map<gamemap::location,paths>& possible_moves, move
|
|||
}
|
||||
|
||||
std::cerr << "choosing move...\n";
|
||||
std::pair<location,location> move = choose_move(targets,dstsrc,enemy_srcdst,enemy_dstsrc);
|
||||
std::pair<location,location> move = choose_move(targets,srcdst,dstsrc,enemy_srcdst,enemy_dstsrc);
|
||||
for(std::vector<target>::const_iterator ittg = targets.begin(); ittg != targets.end(); ++ittg) {
|
||||
assert(map_.on_board(ittg->loc));
|
||||
}
|
||||
|
@ -959,6 +1011,10 @@ bool ai::move_to_targets(std::map<gamemap::location,paths>& possible_moves, move
|
|||
break;
|
||||
}
|
||||
|
||||
if(move.second.valid() == false) {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::cerr << "move: " << move.first.x << ", " << move.first.y << " - " << move.second.x << ", " << move.second.y << "\n";
|
||||
|
||||
//search to see if there are any enemy units next
|
||||
|
@ -1223,9 +1279,6 @@ void ai::do_recruitment()
|
|||
analyze_potential_recruit_movements();
|
||||
analyze_potential_recruit_combat();
|
||||
|
||||
//currently just spend all the gold we can!
|
||||
const int min_gold = 0;
|
||||
|
||||
size_t neutral_villages = 0;
|
||||
|
||||
//we recruit the initial allocation of scouts based on how many neutral villages
|
||||
|
@ -1239,12 +1292,10 @@ void ai::do_recruitment()
|
|||
bool closest = true;
|
||||
for(std::vector<team>::const_iterator i = teams_.begin(); i != teams_.end(); ++i) {
|
||||
const int index = i - teams_.begin() + 1;
|
||||
if(team_num_ != index) {
|
||||
const gamemap::location& loc = nearest_keep(leader->first);
|
||||
if(distance_between(loc,*v) < distance) {
|
||||
closest = false;
|
||||
break;
|
||||
}
|
||||
const gamemap::location& loc = map_.starting_position(index);
|
||||
if(loc != start_pos && distance_between(loc,*v) < distance) {
|
||||
closest = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1262,7 +1313,18 @@ void ai::do_recruitment()
|
|||
//get scouts depending on how many neutral villages there are
|
||||
int scouts_wanted = villages_per_scout > 0 ? neutral_villages/villages_per_scout : 0;
|
||||
|
||||
std::cerr << "scouts_wanted: " << neutral_villages << "/" << villages_per_scout << " = " << scouts_wanted << "\n";
|
||||
|
||||
std::map<std::string,int> unit_types;
|
||||
|
||||
for(unit_map::const_iterator u = units_.begin(); u != units_.end(); ++u) {
|
||||
if(u->second.side() == team_num_) {
|
||||
++unit_types[u->second.type().usage()];
|
||||
}
|
||||
}
|
||||
|
||||
std::cerr << "we have " << unit_types["scout"] << " scouts already and we want " << scouts_wanted << " in total\n";
|
||||
|
||||
while(unit_types["scout"] < scouts_wanted) {
|
||||
if(recruit_usage("scout") == false)
|
||||
break;
|
||||
|
@ -1342,39 +1404,39 @@ void ai::move_leader_after_recruit(const move_map& enemy_dstsrc)
|
|||
std::map<gamemap::location,paths> possible_moves;
|
||||
possible_moves.insert(std::pair<gamemap::location,paths>(leader->first,leader_paths));
|
||||
|
||||
//see if the leader can capture a village safely
|
||||
//if the keep is accessible by an enemy unit, we don't want to leave it
|
||||
if(is_accessible(leader->first,enemy_dstsrc)) {
|
||||
return;
|
||||
}
|
||||
if(current_team().gold() < 20 && is_accessible(leader->first,enemy_dstsrc) == false) {
|
||||
//see if we want to ward any enemy units off from getting our villages
|
||||
for(move_map::const_iterator i = enemy_dstsrc.begin(); i != enemy_dstsrc.end(); ++i) {
|
||||
|
||||
//search through villages finding one to capture
|
||||
const std::vector<gamemap::location>& villages = map_.villages();
|
||||
for(std::vector<gamemap::location>::const_iterator v = villages.begin();
|
||||
v != villages.end(); ++v) {
|
||||
const paths::routes_map::const_iterator itor = leader_paths.routes.find(*v);
|
||||
if(itor == leader_paths.routes.end() || units_.count(*v) != 0) {
|
||||
continue;
|
||||
}
|
||||
//if this is a village of ours, that an enemy can capture on their turn, and
|
||||
//which we might be able to reach in two turns.
|
||||
if(map_.is_village(i->first) && current_team().owns_village(i->first) &&
|
||||
distance_between(i->first,leader->first) <= leader->second.total_movement()*2) {
|
||||
|
||||
int current_distance = distance_between(i->first,leader->first);
|
||||
location current_loc;
|
||||
|
||||
const int owner = village_owner(*v,teams_);
|
||||
if(owner == -1 || current_team().is_enemy(owner+1) || leader->second.hitpoints() < leader->second.max_hitpoints()) {
|
||||
for(paths::routes_map::const_iterator j = leader_paths.routes.begin(); j != leader_paths.routes.end(); ++j) {
|
||||
const int distance = distance_between(i->first,j->first);
|
||||
if(distance < current_distance && is_accessible(j->first,enemy_dstsrc) == false) {
|
||||
current_distance = distance;
|
||||
current_loc = j->first;
|
||||
}
|
||||
}
|
||||
|
||||
//check that no enemies can reach the village
|
||||
gamemap::location adj[6];
|
||||
get_adjacent_tiles(*v,adj);
|
||||
size_t n;
|
||||
for(n = 0; n != 6; ++n) {
|
||||
if(enemy_dstsrc.count(adj[n]))
|
||||
break;
|
||||
//if this location is in range of the village, then we consider it
|
||||
if(current_loc.valid()) {
|
||||
AI_LOG("considering movement to " + str_cast(current_loc.x+1) + "," + str_cast(current_loc.y+1));
|
||||
unit_map temp_units;
|
||||
temp_units.insert(std::pair<location,unit>(current_loc,leader->second));
|
||||
const paths p(map_,state_,gameinfo_,temp_units,current_loc,teams_,false,false);
|
||||
|
||||
if(p.routes.count(i->first)) {
|
||||
move_unit(leader->first,current_loc,possible_moves);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(n != 6)
|
||||
continue;
|
||||
|
||||
move_unit(leader->first,*v,possible_moves);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
41
src/ai.hpp
41
src/ai.hpp
|
@ -22,6 +22,9 @@
|
|||
|
||||
#include <map>
|
||||
|
||||
#define AI_DIAGNOSTIC(MSG) if(game_config::debug) { diagnostic(MSG); std::cerr << "AI_DIAGNOSTIC: " << MSG << "\n"; }
|
||||
#define AI_LOG(MSG) if(game_config::debug) { log_message(MSG); std::cerr << "AI_LOG: " << MSG << "\n";}
|
||||
|
||||
class ai_interface {
|
||||
public:
|
||||
|
||||
|
@ -89,6 +92,12 @@ public:
|
|||
///function to update network players as to what the AI has done so far this turn
|
||||
void sync_network();
|
||||
|
||||
///function to show a diagnostic message on the screen
|
||||
void diagnostic(const std::string& msg);
|
||||
|
||||
///function to display a debug message as a chat message is displayed
|
||||
void log_message(const std::string& msg);
|
||||
|
||||
protected:
|
||||
///this function should be called to attack an enemy.
|
||||
///'attacking_unit': the location of the attacking unit
|
||||
|
@ -299,10 +308,14 @@ public:
|
|||
battle_stats& cur_stats, gamemap::TERRAIN terrain);
|
||||
|
||||
struct target {
|
||||
target(const location& pos, double val) : loc(pos), value(val)
|
||||
enum TYPE { VILLAGE, LEADER, EXPLICIT, THREAT, BATTLE_AID, MASS };
|
||||
|
||||
target(const location& pos, double val, TYPE target_type=VILLAGE) : loc(pos), value(val), type(target_type)
|
||||
{}
|
||||
location loc;
|
||||
double value;
|
||||
|
||||
TYPE type;
|
||||
};
|
||||
|
||||
struct defensive_position {
|
||||
|
@ -360,7 +373,7 @@ protected:
|
|||
class ai& ai_obj, const move_map& dstsrc, const move_map& srcdst,
|
||||
const move_map& enemy_dstsrc, const move_map& enemy_srcdst);
|
||||
|
||||
double rating(double aggression) const;
|
||||
double rating(double aggression, class ai& ai_obj) const;
|
||||
|
||||
gamemap::location target;
|
||||
std::vector<std::pair<gamemap::location,gamemap::location> > movements;
|
||||
|
@ -412,6 +425,7 @@ protected:
|
|||
virtual void do_attack_analysis(
|
||||
const 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 location* tiles, bool* used_locations,
|
||||
std::vector<location>& units,
|
||||
|
@ -420,6 +434,13 @@ protected:
|
|||
);
|
||||
|
||||
|
||||
//function which finds how much 'power' a side can attack a certain location with. This is basically
|
||||
//the maximum hp of damage that can be inflicted upon a unit on loc by full-health units, multiplied by
|
||||
//the defense these units will have. (if 'use_terrain' is false, then it will be multiplied by 0.5)
|
||||
//
|
||||
//Example: 'loc' can be reached by two units, one of whom has a 10-3 attack and has 48/48 hp, and
|
||||
//can defend at 40% on the adjacent grassland. The other has a 8-2 attack, and has 30/40 hp, and
|
||||
//can defend at 60% on the adjacent mountain. The rating will be 10*3*1.0*0.4 + 8*2*0.75*0.6 = 19.2
|
||||
virtual double power_projection(const gamemap::location& loc, const move_map& srcdst, const move_map& dstsrc, bool use_terrain=true) const;
|
||||
|
||||
virtual std::vector<attack_analysis> analyze_targets(
|
||||
|
@ -431,7 +452,21 @@ protected:
|
|||
|
||||
virtual std::vector<target> find_targets(unit_map::const_iterator leader, const move_map& enemy_srcdst, const move_map& enemy_dstsrc);
|
||||
|
||||
virtual std::pair<location,location> choose_move(std::vector<target>& targets,const move_map& dstsrc, const move_map& enemy_srcdst, const move_map& enemy_dstsrc);
|
||||
//function to form a group of units suitable for moving along the route, 'route'.
|
||||
//returns the location which the group may reach this turn.
|
||||
//stores the locations of the units in the group in 'units'
|
||||
virtual location form_group(const std::vector<location>& route, const move_map& dstsrc, const move_map& srcdst, std::set<location>& units);
|
||||
|
||||
//function to return the group of enemies that threaten a certain path
|
||||
virtual void enemies_along_path(const std::vector<location>& route, const move_map& dstsrc, const move_map& srcdst, std::set<location>& units);
|
||||
|
||||
virtual bool move_group(const location& dst, const std::vector<location>& route, const std::set<location>& units);
|
||||
|
||||
virtual double rate_group(const std::set<location>& group, const std::vector<location>& battlefield) const;
|
||||
|
||||
virtual double compare_groups(const std::set<location>& our_group, const std::set<location>& enemy_groups, const std::vector<location>& battlefield) const;
|
||||
|
||||
virtual std::pair<location,location> choose_move(std::vector<target>& targets,const move_map& srcdst, const move_map& dstsrc, const move_map& enemy_srcdst, const move_map& enemy_dstsrc);
|
||||
|
||||
//function which rates the value of moving onto certain terrain for a unit
|
||||
virtual int rate_terrain(const unit& u, const location& loc);
|
||||
|
|
|
@ -30,6 +30,7 @@ const int max_positions = 10000;
|
|||
void ai::do_attack_analysis(
|
||||
const 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 location* tiles, bool* used_locations,
|
||||
std::vector<location>& units,
|
||||
|
@ -56,8 +57,8 @@ void ai::do_attack_analysis(
|
|||
return;
|
||||
}
|
||||
|
||||
const double cur_rating = cur_analysis.movements.empty() ? 0 :
|
||||
cur_analysis.rating(0.0);
|
||||
const double cur_rating = cur_analysis.movements.empty() ? -1.0 :
|
||||
cur_analysis.rating(0.0,*this);
|
||||
|
||||
double rating_to_beat = cur_rating;
|
||||
|
||||
|
@ -145,7 +146,7 @@ void ai::do_attack_analysis(
|
|||
//calculate how much support we have on this hex from allies. Support does not
|
||||
//take into account terrain, because we don't want to move into a hex that is
|
||||
//surrounded by good defensive terrain
|
||||
const double support = power_projection(tiles[j],srcdst,dstsrc,false);
|
||||
const double support = power_projection(tiles[j],fullmove_srcdst,fullmove_dstsrc,false);
|
||||
|
||||
//if this is a position with equal defense to another position, but more vulnerability
|
||||
//then we don't want to use it
|
||||
|
@ -170,11 +171,11 @@ void ai::do_attack_analysis(
|
|||
|
||||
cur_analysis.analyze(map_,units_,state_,gameinfo_,50,*this,dstsrc,srcdst,enemy_dstsrc,enemy_srcdst);
|
||||
|
||||
if(cur_analysis.rating(0.0) > rating_to_beat) {
|
||||
if(cur_analysis.rating(0.0,*this) > rating_to_beat) {
|
||||
|
||||
result.push_back(cur_analysis);
|
||||
used_locations[cur_position] = true;
|
||||
do_attack_analysis(loc,srcdst,dstsrc,enemy_srcdst,enemy_dstsrc,
|
||||
do_attack_analysis(loc,srcdst,dstsrc,fullmove_srcdst,fullmove_dstsrc,enemy_srcdst,enemy_dstsrc,
|
||||
tiles,used_locations,
|
||||
units,result,cur_analysis);
|
||||
used_locations[cur_position] = false;
|
||||
|
@ -502,7 +503,7 @@ void ai::attack_analysis::analyze(const gamemap& map,
|
|||
}
|
||||
}
|
||||
|
||||
double ai::attack_analysis::rating(double aggression) const
|
||||
double ai::attack_analysis::rating(double aggression, ai& ai_obj) const
|
||||
{
|
||||
if(leader_threat) {
|
||||
aggression = 1.0;
|
||||
|
@ -522,16 +523,24 @@ double ai::attack_analysis::rating(double aggression) const
|
|||
//of their optimal terrain into sub-optimal terrain.
|
||||
//calculate the 'exposure' of our units to risk
|
||||
|
||||
const double exposure = resources_used*(terrain_quality - alternative_terrain_quality);
|
||||
std::cerr << "attack option has base value " << value << " with exposure " << exposure << "\n";
|
||||
const double exposure_mod = uses_leader ? 2.0 : ai_obj.current_team().caution();
|
||||
const double exposure = exposure_mod*resources_used*(terrain_quality - alternative_terrain_quality)*vulnerability/maximum<double>(0.01,support);
|
||||
std::cerr << "attack option has base value " << value << " with exposure " << exposure << ": " << vulnerability << "/" << support << " = " << (vulnerability/support) << "\n";
|
||||
if(uses_leader) {
|
||||
ai_obj.log_message("attack option has value " + str_cast(value) + " with exposure " + str_cast(exposure) + ": " + str_cast(vulnerability) + "/" + str_cast(support));
|
||||
}
|
||||
|
||||
value -= exposure;
|
||||
}
|
||||
|
||||
//prefer to attack already damaged targets
|
||||
value += ((target_starting_damage/3 + avg_damage_inflicted)*
|
||||
(target_value/resources_used) -
|
||||
(1.0-aggression)*avg_damage_taken*(resources_used/target_value))/10.0;
|
||||
value += ((target_starting_damage/3 + avg_damage_inflicted) - (1.0-aggression)*avg_damage_taken)/10.0;
|
||||
|
||||
//sanity check: if we're putting ourselves at major risk, and have no chance to kill
|
||||
//then don't do it
|
||||
if(vulnerability > 50.0 && vulnerability > support*5.0 && chance_to_kill < 0.02) {
|
||||
return -1.0;
|
||||
}
|
||||
|
||||
if(!leader_threat && vulnerability*terrain_quality > 0.0) {
|
||||
value *= support/(vulnerability*terrain_quality);
|
||||
|
@ -543,7 +552,9 @@ double ai::attack_analysis::rating(double aggression) const
|
|||
value *= 5.0;
|
||||
}
|
||||
|
||||
std::cerr << "value: " << value << " vulnerability: " << vulnerability << " quality: " << terrain_quality << " alternative quality: " << alternative_terrain_quality << "\n";
|
||||
std::cerr << "attack on " << (target.x+1) << "," << (target.y+1) << ": attackers: " << movements.size() << " value: " << value
|
||||
<< " chance to kill: " << chance_to_kill << " damage inflicted: " << avg_damage_inflicted << " damage taken: " << avg_damage_taken
|
||||
<< " vulnerability: " << vulnerability << " support: " << support << " quality: " << terrain_quality << " alternative quality: " << alternative_terrain_quality << "\n";
|
||||
|
||||
return value;
|
||||
}
|
||||
|
@ -569,6 +580,10 @@ std::vector<ai::attack_analysis> ai::analyze_targets(
|
|||
bool used_locations[6];
|
||||
std::fill(used_locations,used_locations+6,false);
|
||||
|
||||
std::map<location,paths> dummy_moves;
|
||||
move_map fullmove_srcdst, fullmove_dstsrc;
|
||||
calculate_possible_moves(dummy_moves,fullmove_srcdst,fullmove_dstsrc,false,true);
|
||||
|
||||
for(unit_map::const_iterator j = units_.begin(); j != units_.end(); ++j) {
|
||||
|
||||
//attack anyone who is on the enemy side, and who is not invisible or turned to stone
|
||||
|
@ -585,7 +600,7 @@ std::vector<ai::attack_analysis> ai::analyze_targets(
|
|||
|
||||
const int ticks = SDL_GetTicks();
|
||||
|
||||
do_attack_analysis(j->first,srcdst,dstsrc,enemy_srcdst,enemy_dstsrc,
|
||||
do_attack_analysis(j->first,srcdst,dstsrc,fullmove_srcdst,fullmove_dstsrc,enemy_srcdst,enemy_dstsrc,
|
||||
adjacent,used_locations,unit_locs,res,analysis);
|
||||
|
||||
const int time_taken = SDL_GetTicks() - ticks;
|
||||
|
@ -607,6 +622,8 @@ double ai::power_projection(const gamemap::location& loc, const move_map& srcdst
|
|||
static gamemap::location locs[6];
|
||||
get_adjacent_tiles(loc,locs);
|
||||
|
||||
const int lawful_bonus = state_.get_time_of_day().lawful_bonus;
|
||||
|
||||
double res = 0.0;
|
||||
|
||||
for(int i = 0; i != 6; ++i) {
|
||||
|
@ -640,19 +657,27 @@ double ai::power_projection(const gamemap::location& loc, const move_map& srcdst
|
|||
}
|
||||
|
||||
const unit& un = u->second;
|
||||
|
||||
int tod_modifier = 0;
|
||||
if(un.type().alignment() == unit_type::LAWFUL) {
|
||||
tod_modifier = lawful_bonus;
|
||||
} else if(un.type().alignment() == unit_type::CHAOTIC) {
|
||||
tod_modifier = -lawful_bonus;
|
||||
}
|
||||
|
||||
const double hp = double(un.hitpoints())/
|
||||
double(un.max_hitpoints());
|
||||
int most_damage = 0;
|
||||
for(std::vector<attack_type>::const_iterator att =
|
||||
un.attacks().begin(); att != un.attacks().end(); ++att) {
|
||||
const int damage = att->damage()*att->num_attacks();
|
||||
if(damage > most_damage)
|
||||
const int damage = (att->damage()*att->num_attacks()*(100+tod_modifier))/100;
|
||||
if(damage > most_damage) {
|
||||
most_damage = damage;
|
||||
}
|
||||
}
|
||||
|
||||
const bool village = map_.is_village(terrain);
|
||||
const double village_bonus = (use_terrain && village) ? 2.0 : 1.0;
|
||||
const double village_bonus = (use_terrain && village) ? 1.5 : 1.0;
|
||||
|
||||
const double defense = use_terrain ? double(100 - un.defense_modifier(map_,terrain))/100.0 : 0.5;
|
||||
const double rating = village_bonus*hp*defense*double(most_damage);
|
||||
|
|
346
src/ai_move.cpp
346
src/ai_move.cpp
|
@ -21,13 +21,17 @@
|
|||
|
||||
struct move_cost_calculator
|
||||
{
|
||||
enum MODE { NO_AVOID_ENEMIES, AVOID_ENEMIES };
|
||||
|
||||
move_cost_calculator(const unit& u, const gamemap& map,
|
||||
const game_data& data,
|
||||
const unit_map& units,
|
||||
const gamemap::location& loc,
|
||||
const std::multimap<gamemap::location,gamemap::location>& dstsrc)
|
||||
const ai::move_map& dstsrc,
|
||||
const ai::move_map& enemy_dstsrc)
|
||||
: unit_(u), map_(map), data_(data), units_(units),
|
||||
move_type_(u.type().movement_type()), loc_(loc), dstsrc_(dstsrc)
|
||||
move_type_(u.type().movement_type()), loc_(loc), dstsrc_(dstsrc), enemy_dstsrc_(enemy_dstsrc),
|
||||
avoid_enemies_(u.type().usage() == "scout")
|
||||
{}
|
||||
|
||||
double cost(const gamemap::location& loc, double so_far) const
|
||||
|
@ -52,24 +56,8 @@ struct move_cost_calculator
|
|||
const double modifier = 1.0;//move_type_.defense_modifier(map_,terrain);
|
||||
const double move_cost = move_type_.movement_cost(map_,terrain);
|
||||
|
||||
double enemies = 0;
|
||||
/* //is this stuff responsible for making it take a long time?
|
||||
location adj[7];
|
||||
adj[0] = loc;
|
||||
get_adjacent_tiles(loc,adj+1);
|
||||
for(int i = 0; i != 7; ++i) {
|
||||
const std::map<location,unit>::const_iterator en=units_.find(adj[i]);
|
||||
//at the moment, don't allow any units to be in the path
|
||||
if(i == 0 && en != units_.end()) {
|
||||
return 1000.0;
|
||||
}
|
||||
|
||||
if(en != units_.end() && en->second.side() == enemy_) {
|
||||
enemies += 1.0;
|
||||
}
|
||||
}
|
||||
*/
|
||||
double res = modifier*move_cost + enemies*2.0;
|
||||
const int enemies = 1 + (avoid_enemies_ ? enemy_dstsrc_.count(loc) : 0);
|
||||
double res = modifier*move_cost*double(enemies);
|
||||
|
||||
//if there is a unit (even a friendly one) on this tile, we increase the cost to
|
||||
//try discourage going through units, to thwart the 'single file effect'
|
||||
|
@ -87,8 +75,8 @@ private:
|
|||
const unit_map& units_;
|
||||
const unit_movement_type& move_type_;
|
||||
const gamemap::location loc_;
|
||||
const ai::move_map dstsrc_;
|
||||
|
||||
const ai::move_map dstsrc_, enemy_dstsrc_;
|
||||
const bool avoid_enemies_;
|
||||
};
|
||||
|
||||
std::vector<ai::target> ai::find_targets(unit_map::const_iterator leader, const move_map& enemy_srcdst, const move_map& enemy_dstsrc)
|
||||
|
@ -123,7 +111,7 @@ std::vector<ai::target> ai::find_targets(unit_map::const_iterator leader, const
|
|||
|
||||
const double value = threat/double(threats.size());
|
||||
for(std::set<gamemap::location>::const_iterator i = threats.begin(); i != threats.end(); ++i) {
|
||||
targets.push_back(target(*i,value));
|
||||
targets.push_back(target(*i,value,target::THREAT));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -141,7 +129,7 @@ std::vector<ai::target> ai::find_targets(unit_map::const_iterator leader, const
|
|||
}
|
||||
|
||||
if(get_village) {
|
||||
targets.push_back(target(*t,current_team().village_value()));
|
||||
targets.push_back(target(*t,current_team().village_value(),target::VILLAGE));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -155,7 +143,7 @@ std::vector<ai::target> ai::find_targets(unit_map::const_iterator leader, const
|
|||
//is an enemy leader
|
||||
if(u->second.can_recruit() && current_team().is_enemy(u->second.side())) {
|
||||
assert(map_.on_board(u->first));
|
||||
targets.push_back(target(u->first,current_team().leader_value()));
|
||||
targets.push_back(target(u->first,current_team().leader_value(),target::LEADER));
|
||||
}
|
||||
|
||||
//explicit targets for this team
|
||||
|
@ -163,7 +151,7 @@ std::vector<ai::target> ai::find_targets(unit_map::const_iterator leader, const
|
|||
j != team_targets.end(); ++j) {
|
||||
if(u->second.matches_filter(j->criteria)) {
|
||||
std::cerr << "found explicit target..." << j->value << "\n";
|
||||
targets.push_back(target(u->first,j->value));
|
||||
targets.push_back(target(u->first,j->value,target::EXPLICIT));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -195,7 +183,192 @@ std::vector<ai::target> ai::find_targets(unit_map::const_iterator leader, const
|
|||
return targets;
|
||||
}
|
||||
|
||||
std::pair<gamemap::location,gamemap::location> ai::choose_move(std::vector<target>& targets,const move_map& dstsrc, const move_map& enemy_srcdst, const move_map& enemy_dstsrc)
|
||||
gamemap::location ai::form_group(const std::vector<location>& route, const move_map& dstsrc, const move_map& srcdst, std::set<location>& res)
|
||||
{
|
||||
if(route.empty()) {
|
||||
return location();
|
||||
}
|
||||
|
||||
std::vector<location>::const_iterator i;
|
||||
for(i = route.begin(); i != route.end(); ++i) {
|
||||
if(units_.count(*i) > 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
size_t n = 0, nunits = res.size();
|
||||
|
||||
const std::pair<move_map::const_iterator,move_map::const_iterator> itors = dstsrc.equal_range(*i);
|
||||
for(move_map::const_iterator j = itors.first; j != itors.second; ++j) {
|
||||
if(res.count(j->second) != 0) {
|
||||
++n;
|
||||
} else {
|
||||
const unit_map::const_iterator un = units_.find(j->second);
|
||||
if(un == units_.end() || un->second.can_recruit() || un->second.movement_left() < un->second.total_movement()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
res.insert(j->second);
|
||||
}
|
||||
}
|
||||
|
||||
//if not all our units can reach this position.
|
||||
if(n < nunits) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(i != route.begin()) {
|
||||
--i;
|
||||
}
|
||||
|
||||
return *i;
|
||||
}
|
||||
|
||||
void ai::enemies_along_path(const std::vector<location>& route, const move_map& dstsrc, const move_map& srcdst, std::set<location>& res)
|
||||
{
|
||||
for(std::vector<location>::const_iterator i = route.begin(); i != route.end(); ++i) {
|
||||
gamemap::location adj[6];
|
||||
get_adjacent_tiles(*i,adj);
|
||||
for(size_t n = 0; n != 6; ++n) {
|
||||
const std::pair<move_map::const_iterator,move_map::const_iterator> itors = dstsrc.equal_range(adj[n]);
|
||||
for(move_map::const_iterator j = itors.first; j != itors.second; ++j) {
|
||||
res.insert(j->second);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool ai::move_group(const location& dst, const std::vector<location>& route, const std::set<location>& units)
|
||||
{
|
||||
const std::vector<location>::const_iterator itor = std::find(route.begin(),route.end(),dst);
|
||||
if(itor == route.end()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
AI_DIAGNOSTIC("group has " + str_cast(units.size()) + " members");
|
||||
|
||||
location next;
|
||||
|
||||
size_t direction = 0;
|
||||
|
||||
//find the direction the group is moving in
|
||||
if(itor+1 != route.end()) {
|
||||
next = *(itor+1);
|
||||
} else if(itor != route.begin()) {
|
||||
next = *(itor-1);
|
||||
}
|
||||
|
||||
if(next.valid()) {
|
||||
location adj[6];
|
||||
get_adjacent_tiles(dst,adj);
|
||||
|
||||
direction = std::find(adj,adj+6,next) - adj;
|
||||
}
|
||||
|
||||
std::deque<location> preferred_moves;
|
||||
preferred_moves.push_back(dst);
|
||||
|
||||
std::map<location,paths> possible_moves;
|
||||
move_map srcdst, dstsrc;
|
||||
calculate_possible_moves(possible_moves,srcdst,dstsrc,false);
|
||||
|
||||
bool res = false;
|
||||
|
||||
for(std::set<location>::const_iterator i = units.begin(); i != units.end(); ++i) {
|
||||
const unit_map::const_iterator un = units_.find(*i);
|
||||
if(un == units_.end()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
location best_loc;
|
||||
int best_defense = -1;
|
||||
for(std::deque<location>::const_iterator j = preferred_moves.begin(); j != preferred_moves.end(); ++j) {
|
||||
if(units_.count(*j)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const std::pair<move_map::const_iterator,move_map::const_iterator> itors = dstsrc.equal_range(*j);
|
||||
move_map::const_iterator m;
|
||||
for(m = itors.first; m != itors.second; ++m) {
|
||||
if(m->second == *i) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(m == itors.second) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const int defense = un->second.defense_modifier(map_,map_.get_terrain(*j));
|
||||
if(best_loc.valid() == false || defense < best_defense) {
|
||||
best_loc = *j;
|
||||
best_defense = defense;
|
||||
}
|
||||
}
|
||||
|
||||
if(best_loc.valid()) {
|
||||
res = true;
|
||||
move_unit(*i,best_loc,possible_moves);
|
||||
preferred_moves.erase(std::find(preferred_moves.begin(),preferred_moves.end(),best_loc));
|
||||
|
||||
//find locations that are 'perpendicular' to the direction of movement for further units to move to.
|
||||
location adj[6];
|
||||
get_adjacent_tiles(best_loc,adj);
|
||||
for(size_t n = 0; n != 6; ++n) {
|
||||
if(n != direction && ((n+3)%6) != direction && map_.on_board(adj[n]) &&
|
||||
units_.count(adj[n]) == 0 && std::count(preferred_moves.begin(),preferred_moves.end(),adj[n]) == 0) {
|
||||
preferred_moves.push_front(adj[n]);
|
||||
AI_LOG("added moves: " + str_cast(adj[n].x+1) + "," + str_cast(adj[n].y+1));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
AI_DIAGNOSTIC("Could not move group member to any of " + str_cast(preferred_moves.size()) + " locations");
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
double ai::rate_group(const std::set<location>& group, const std::vector<location>& battlefield) const
|
||||
{
|
||||
double strength = 0.0;
|
||||
for(std::set<location>::const_iterator i = group.begin(); i != group.end(); ++i) {
|
||||
const unit_map::const_iterator u = units_.find(*i);
|
||||
if(u == units_.end()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const unit& un = u->second;
|
||||
|
||||
int defense = 0;
|
||||
for(std::vector<location>::const_iterator j = battlefield.begin(); j != battlefield.end(); ++j) {
|
||||
defense += un.defense_modifier(map_,map_.get_terrain(*j));
|
||||
}
|
||||
|
||||
defense /= battlefield.size();
|
||||
|
||||
int best_attack = 0;
|
||||
const std::vector<attack_type>& attacks = un.attacks();
|
||||
for(std::vector<attack_type>::const_iterator a = attacks.begin(); a != attacks.end(); ++a) {
|
||||
const int strength = a->num_attacks()*a->damage();
|
||||
best_attack = maximum<int>(strength,best_attack);
|
||||
}
|
||||
|
||||
const int rating = (defense*best_attack*un.hitpoints())/(100*un.max_hitpoints());
|
||||
strength += double(rating);
|
||||
}
|
||||
|
||||
return strength;
|
||||
}
|
||||
|
||||
double ai::compare_groups(const std::set<location>& our_group, const std::set<location>& their_group, const std::vector<location>& battlefield) const
|
||||
{
|
||||
const double a = rate_group(our_group,battlefield);
|
||||
const double b = maximum<double>(rate_group(their_group,battlefield),0.01);
|
||||
return a/b;
|
||||
}
|
||||
|
||||
std::pair<gamemap::location,gamemap::location> ai::choose_move(std::vector<target>& targets, const move_map& srcdst, const move_map& dstsrc, const move_map& enemy_srcdst, const move_map& enemy_dstsrc)
|
||||
{
|
||||
log_scope("choosing move");
|
||||
|
||||
|
@ -231,7 +404,7 @@ std::pair<gamemap::location,gamemap::location> ai::choose_move(std::vector<targe
|
|||
return std::pair<location,location>(u->first,u->first);
|
||||
}
|
||||
|
||||
const move_cost_calculator cost_calc(u->second,map_,gameinfo_,units_,u->first,dstsrc);
|
||||
const move_cost_calculator cost_calc(u->second,map_,gameinfo_,units_,u->first,dstsrc,enemy_dstsrc);
|
||||
|
||||
//choose the best target for that unit
|
||||
for(std::vector<target>::iterator tg = targets.begin(); tg != targets.end(); ++tg) {
|
||||
|
@ -240,7 +413,19 @@ std::pair<gamemap::location,gamemap::location> ai::choose_move(std::vector<targe
|
|||
assert(map_.on_board(tg->loc));
|
||||
const paths::route cur_route = a_star_search(u->first,tg->loc,
|
||||
minimum(tg->value/best_rating,500.0),cost_calc);
|
||||
const double rating = tg->value/cur_route.move_left;
|
||||
|
||||
double rating = tg->value/cur_route.move_left;
|
||||
|
||||
//scouts do not like encountering enemies on their paths
|
||||
if(u->second.type().usage() == "scout") {
|
||||
std::set<location> enemies_guarding;
|
||||
enemies_along_path(cur_route.steps,enemy_dstsrc,enemy_srcdst,enemies_guarding);
|
||||
|
||||
if(enemies_guarding.size() > 1) {
|
||||
rating /= enemies_guarding.size();
|
||||
}
|
||||
}
|
||||
|
||||
std::cerr << tg->value << "/" << cur_route.move_left << " = " << rating << "\n";
|
||||
if(best_target == targets.end() || rating > best_rating) {
|
||||
best_rating = rating;
|
||||
|
@ -270,10 +455,21 @@ std::pair<gamemap::location,gamemap::location> ai::choose_move(std::vector<targe
|
|||
|
||||
user_interact();
|
||||
|
||||
const move_cost_calculator calc(u->second,map_,gameinfo_,units_,u->first,dstsrc);
|
||||
const move_cost_calculator calc(u->second,map_,gameinfo_,units_,u->first,dstsrc,enemy_dstsrc);
|
||||
const paths::route cur_route = a_star_search(u->first,best_target->loc,
|
||||
minimum(best_target->value/best_rating,100.0),calc);
|
||||
const double rating = best_target->value/cur_route.move_left;
|
||||
double rating = best_target->value/cur_route.move_left;
|
||||
|
||||
//scouts do not like encountering enemies on their paths
|
||||
if(u->second.type().usage() == "scout") {
|
||||
std::set<location> enemies_guarding;
|
||||
enemies_along_path(cur_route.steps,enemy_dstsrc,enemy_srcdst,enemies_guarding);
|
||||
|
||||
if(enemies_guarding.size() > 1) {
|
||||
rating /= enemies_guarding.size();
|
||||
}
|
||||
}
|
||||
|
||||
if(best == units_.end() || rating > best_rating) {
|
||||
best_rating = rating;
|
||||
best = u;
|
||||
|
@ -295,11 +491,93 @@ std::pair<gamemap::location,gamemap::location> ai::choose_move(std::vector<targe
|
|||
move_map fullmove_dstsrc;
|
||||
calculate_possible_moves(dummy_possible_moves,fullmove_srcdst,fullmove_dstsrc,false,true);
|
||||
|
||||
bool dangerous = false;
|
||||
|
||||
//we stop and consider whether the route to this
|
||||
//target is dangerous, and whether we need to group some units to move in unison toward the target
|
||||
//if any point along the path is too dangerous for our single unit, then we hold back
|
||||
for(std::vector<location>::const_iterator i = best_route.steps.begin(); i != best_route.steps.end(); ++i) {
|
||||
const unit_map::const_iterator unit_at_target = units_.find(best_target->loc);
|
||||
const double threat = power_projection(*i,enemy_srcdst,enemy_dstsrc);
|
||||
if(threat >= double(best->second.hitpoints()) && threat > power_projection(*i,fullmove_srcdst,fullmove_dstsrc) ||
|
||||
unit_at_target != units_.end() && current_team().is_enemy(unit_at_target->second.side())) {
|
||||
dangerous = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(dangerous) {
|
||||
AI_DIAGNOSTIC("dangerous path");
|
||||
std::set<location> group, enemies;
|
||||
const location dst = form_group(best_route.steps,dstsrc,srcdst,group);
|
||||
enemies_along_path(best_route.steps,enemy_dstsrc,enemy_srcdst,enemies);
|
||||
|
||||
const double our_strength = compare_groups(group,enemies,best_route.steps);
|
||||
|
||||
if(our_strength > 1.0 + current_team().caution()) {
|
||||
AI_DIAGNOSTIC("moving group");
|
||||
const bool res = move_group(dst,best_route.steps,group);
|
||||
AI_DIAGNOSTIC("");
|
||||
if(res) {
|
||||
return std::pair<location,location>(location(1,1),location());
|
||||
} else {
|
||||
AI_LOG("group didn't move " + str_cast(group.size()));
|
||||
|
||||
//the group didn't move, so end the first unit in the group's turn, to prevent an infinite loop
|
||||
return std::pair<location,location>(*group.begin(),*group.begin());
|
||||
|
||||
}
|
||||
} else {
|
||||
AI_DIAGNOSTIC("massing to attack " + str_cast(best_target->loc.x+1) + "," + str_cast(best_target->loc.y+1) + " " + str_cast(our_strength));
|
||||
|
||||
const double value = best_target->value;
|
||||
const location target_loc = best_target->loc;
|
||||
const location loc = best->first;
|
||||
const unit& un = best->second;
|
||||
|
||||
targets.erase(best_target);
|
||||
|
||||
//find the best location to mass units at for an attack on the enemies
|
||||
location best_loc;
|
||||
double best_threat = 0.0;
|
||||
int best_distance = 0;
|
||||
|
||||
const double max_acceptable_threat = un.hitpoints()/4;
|
||||
|
||||
std::set<location> mass_locations;
|
||||
|
||||
const std::pair<move_map::const_iterator,move_map::const_iterator> itors = srcdst.equal_range(loc);
|
||||
for(move_map::const_iterator i = itors.first; i != itors.second; ++i) {
|
||||
const int distance = distance_between(target_loc,i->second);
|
||||
const int defense = un.defense_modifier(map_,map_.get_terrain(i->second));
|
||||
const double threat = (power_projection(i->second,enemy_srcdst,enemy_dstsrc)*defense)/100;
|
||||
|
||||
if(best_loc.valid() == false || threat < maximum<double>(best_threat,max_acceptable_threat) && distance < best_distance) {
|
||||
best_loc = i->second;
|
||||
best_threat = threat;
|
||||
best_distance = distance;
|
||||
}
|
||||
|
||||
if(threat < max_acceptable_threat) {
|
||||
mass_locations.insert(i->second);
|
||||
}
|
||||
}
|
||||
|
||||
for(std::set<location>::const_iterator j = mass_locations.begin(); j != mass_locations.end(); ++j) {
|
||||
if(*j != best_loc && distance_between(*j,best_loc) < 3) {
|
||||
targets.push_back(target(*j,value*4.0,target::MASS));
|
||||
}
|
||||
}
|
||||
|
||||
return std::pair<location,location>(loc,best_loc);
|
||||
}
|
||||
}
|
||||
|
||||
for(std::vector<location>::reverse_iterator ri =
|
||||
best_route.steps.rbegin(); ri != best_route.steps.rend(); ++ri) {
|
||||
|
||||
if(game_config::debug) {
|
||||
display::debug_highlight(*ri,0.2);
|
||||
//display::debug_highlight(*ri,0.2);
|
||||
}
|
||||
|
||||
//this is set to 'true' if we are hesitant to proceed because of enemy units,
|
||||
|
@ -313,11 +591,11 @@ std::pair<gamemap::location,gamemap::location> ai::choose_move(std::vector<targe
|
|||
if(!should_retreat(its.first->first,best,fullmove_srcdst,fullmove_dstsrc,enemy_srcdst,enemy_dstsrc)) {
|
||||
const double value = best_target->value - best->second.type().cost()/20.0;
|
||||
|
||||
if(value > 0.0) {
|
||||
if(value > 0.0 && best_target->type != target::MASS) {
|
||||
//there are enemies ahead. Rally troops around us to
|
||||
//try to take the target
|
||||
if(is_dangerous) {
|
||||
targets.push_back(target(its.first->first,value*2.0));
|
||||
targets.push_back(target(its.first->first,value*2.0,target::BATTLE_AID));
|
||||
}
|
||||
|
||||
best_target->value = value;
|
||||
|
|
|
@ -66,7 +66,7 @@ display::display(unit_map& units, CVideo& video, const gamemap& map,
|
|||
turbo_(false), grid_(false), sidebarScaling_(1.0),
|
||||
theme_(theme_cfg,screen_area()), builder_(built_terrains, map),
|
||||
first_turn_(true), in_game_(false), map_labels_(*this,map),
|
||||
tod_hex_mask1(NULL), tod_hex_mask2(NULL)
|
||||
tod_hex_mask1(NULL), tod_hex_mask2(NULL), diagnostic_label_(0)
|
||||
{
|
||||
if(non_interactive())
|
||||
updatesLocked_++;
|
||||
|
@ -2194,6 +2194,18 @@ void display::prune_chat_messages(bool remove_all)
|
|||
}
|
||||
}
|
||||
|
||||
void display::set_diagnostic(const std::string& msg)
|
||||
{
|
||||
if(diagnostic_label_ != 0) {
|
||||
font::remove_floating_label(diagnostic_label_);
|
||||
diagnostic_label_ = 0;
|
||||
}
|
||||
|
||||
if(msg != "") {
|
||||
diagnostic_label_ = font::add_floating_label(msg,16,font::YELLOW_COLOUR,300.0,50.0,0.0,0.0,-1,map_area());
|
||||
}
|
||||
}
|
||||
|
||||
void display::rebuild_terrain(const gamemap::location &loc) {
|
||||
builder_.rebuild_terrain(loc);
|
||||
}
|
||||
|
|
|
@ -291,6 +291,8 @@ public:
|
|||
map_labels& labels() { return map_labels_; }
|
||||
const map_labels& labels() const { return map_labels_; }
|
||||
|
||||
void set_diagnostic(const std::string& msg);
|
||||
|
||||
enum MESSAGE_TYPE { MESSAGE_PUBLIC, MESSAGE_PRIVATE };
|
||||
void add_chat_message(const std::string& speaker, int side, const std::string& msg, MESSAGE_TYPE type);
|
||||
|
||||
|
@ -311,7 +313,7 @@ public:
|
|||
//rebuild the dynamic terrain at the given location.
|
||||
void rebuild_terrain(const gamemap::location &location);
|
||||
|
||||
//Add a location to highlight. Note that this has nothign to do with
|
||||
//Add a location to highlight. Note that this has nothing to do with
|
||||
//selecting hexes, it is pure highlighting.
|
||||
void add_highlighted_loc(const gamemap::location &hex);
|
||||
|
||||
|
@ -455,6 +457,8 @@ private:
|
|||
static std::map<gamemap::location,double> debugHighlights_;
|
||||
|
||||
std::set<gamemap::location> highlighted_locations_;
|
||||
|
||||
int diagnostic_label_;
|
||||
};
|
||||
|
||||
//an object which will lock the display for the duration of its lifetime.
|
||||
|
|
|
@ -138,25 +138,36 @@ std::vector<hotkey::hotkey_item> hotkeys;
|
|||
}
|
||||
|
||||
struct hotkey_pressed {
|
||||
hotkey_pressed(const SDL_KeyboardEvent& event);
|
||||
//this distinguishes between two modes of operation. If mods are disallowed,
|
||||
//then any match must be exact. I.e. "shift+a" does not match "a". If they are allowed,
|
||||
//then shift+a will match "a"
|
||||
enum ALLOW_MOD_KEYS { DISALLOW_MODS, ALLOW_MODS };
|
||||
hotkey_pressed(const SDL_KeyboardEvent& event, ALLOW_MOD_KEYS allow_mods=DISALLOW_MODS);
|
||||
|
||||
bool operator()(const hotkey::hotkey_item& hk) const;
|
||||
|
||||
private:
|
||||
int keycode_;
|
||||
bool shift_, ctrl_, alt_, command_;
|
||||
bool mods_;
|
||||
};
|
||||
|
||||
hotkey_pressed::hotkey_pressed(const SDL_KeyboardEvent& event)
|
||||
hotkey_pressed::hotkey_pressed(const SDL_KeyboardEvent& event, ALLOW_MOD_KEYS mods)
|
||||
: keycode_(event.keysym.sym), shift_(event.keysym.mod&KMOD_SHIFT),
|
||||
ctrl_(event.keysym.mod&KMOD_CTRL), alt_(event.keysym.mod&KMOD_ALT),
|
||||
command_(event.keysym.mod&KMOD_LMETA)
|
||||
command_(event.keysym.mod&KMOD_LMETA), mods_(mods == ALLOW_MODS)
|
||||
{}
|
||||
|
||||
bool hotkey_pressed::operator()(const hotkey::hotkey_item& hk) const
|
||||
{
|
||||
return hk.keycode == keycode_ && shift_ == hk.shift &&
|
||||
ctrl_ == hk.ctrl && alt_ == hk.alt && command_ == hk.command;
|
||||
if(mods_) {
|
||||
return hk.keycode == keycode_ && (shift_ == hk.shift || shift_ == true)
|
||||
&& (ctrl_ == hk.ctrl || ctrl_ == true)
|
||||
&& (alt_ == hk.alt || alt_ == true);
|
||||
} else {
|
||||
return hk.keycode == keycode_ && shift_ == hk.shift &&
|
||||
ctrl_ == hk.ctrl && alt_ == hk.alt && command_ == hk.command;
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
@ -255,10 +266,16 @@ void key_event(display& disp, const SDL_KeyboardEvent& event, command_executor*
|
|||
}
|
||||
}
|
||||
|
||||
const std::vector<hotkey_item>::iterator i = std::find_if(hotkeys.begin(),hotkeys.end(),hotkey_pressed(event));
|
||||
std::vector<hotkey_item>::iterator i = std::find_if(hotkeys.begin(),hotkeys.end(),hotkey_pressed(event));
|
||||
|
||||
if(i == hotkeys.end())
|
||||
if(i == hotkeys.end()) {
|
||||
//no matching hotkey was found, but try an in-exact match.
|
||||
i = std::find_if(hotkeys.begin(),hotkeys.end(),hotkey_pressed(event,hotkey_pressed::ALLOW_MODS));
|
||||
}
|
||||
|
||||
if(i == hotkeys.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
execute_command(disp,i->action,executor);
|
||||
}
|
||||
|
|
|
@ -171,7 +171,6 @@ void set_wm_icon()
|
|||
#if !(defined(__APPLE__))
|
||||
scoped_sdl_surface icon(get_image(game_config::game_icon,UNSCALED));
|
||||
if(icon != NULL) {
|
||||
std::cerr << "setting icon...\n";
|
||||
::SDL_WM_SetIcon(icon,NULL);
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -78,8 +78,7 @@ int mp_connect::load_map(const std::string& era, int map, int num_turns, int vil
|
|||
|
||||
const config::child_list& levels = cfg_->get_children("multiplayer");
|
||||
|
||||
if(map == levels.size() )
|
||||
{
|
||||
if(map == levels.size()) {
|
||||
//Load a saved game
|
||||
save_ = true;
|
||||
bool show_replay = false;
|
||||
|
|
|
@ -264,8 +264,9 @@ RESULT enter(display& disp, config& game_data, const config& terrain_data, dialo
|
|||
key[SDLK_UP],key[SDLK_DOWN],
|
||||
key[SDLK_PAGEUP],key[SDLK_PAGEDOWN]);
|
||||
|
||||
const bool observe = observe_game.pressed();
|
||||
if(games_available && (observe || join_game.pressed() || games_menu.double_clicked())) {
|
||||
const bool double_click = games_menu.double_clicked();
|
||||
const bool observe = observe_game.pressed() || !games_available && double_click;
|
||||
if(games_available && (observe || join_game.pressed() || double_click)) {
|
||||
const size_t index = size_t(games_menu.selection());
|
||||
const config::const_child_itors i = gamelist->child_range("game");
|
||||
assert(index < size_t(i.second - i.first));
|
||||
|
|
|
@ -1549,6 +1549,11 @@ void turn_info::write_game_snapshot(config& start) const
|
|||
game_events::write_events(start);
|
||||
|
||||
write_game(state_of_game_,start);
|
||||
|
||||
//clear any unnecessary children that we don't want in a snapshot
|
||||
start.clear_children("snapshot");
|
||||
start.clear_children("replay_start");
|
||||
|
||||
start["gold"] = "-1000000"; //just make sure gold is read in from the teams
|
||||
|
||||
//write out the current state of the map
|
||||
|
@ -1850,9 +1855,12 @@ void turn_info::recall()
|
|||
|
||||
bool turn_info::has_friends() const
|
||||
{
|
||||
if(is_observer()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for(size_t n = 0; n != teams_.size(); ++n) {
|
||||
if(n != gui_.viewing_team() && teams_[gui_.viewing_team()].team_name() == teams_[n].team_name() &&
|
||||
teams_[n].is_network()) {
|
||||
if(n != gui_.viewing_team() && teams_[gui_.viewing_team()].team_name() == teams_[n].team_name() && teams_[n].is_network()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -101,7 +101,8 @@ unit::unit(const unit_type* t, const unit& u) :
|
|||
attacks_(t->attacks()), backupAttacks_(t->attacks()),
|
||||
modifications_(u.modifications_),
|
||||
traitsDescription_(u.traitsDescription_),
|
||||
guardian_(false), upkeep_(u.upkeep_)
|
||||
guardian_(false), upkeep_(u.upkeep_),
|
||||
overlays_(u.overlays_), variables_(u.variables_)
|
||||
{
|
||||
validate_side(side_);
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
|
||||
#include <cmath>
|
||||
#include <map>
|
||||
#include <sstream>
|
||||
|
||||
//instead of playing with VC++'s crazy definitions of min and max,
|
||||
//just define our own
|
||||
|
@ -73,6 +74,12 @@ To lexical_cast_default(From a, To def=To())
|
|||
}
|
||||
}
|
||||
|
||||
template<typename From>
|
||||
std::string str_cast(From a)
|
||||
{
|
||||
return lexical_cast<std::string,From>(a);
|
||||
}
|
||||
|
||||
inline bool chars_equal_insensitive(char a, char b) { return tolower(a) == tolower(b); }
|
||||
|
||||
//a definition of 'push_back' for strings, since some implementations
|
||||
|
|
Loading…
Add table
Reference in a new issue