The Battle for Wesnoth  1.13.10+dev
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages
recruitment.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2013 - 2018 by Felix Bauer
3  Part of the Battle for Wesnoth Project http://www.wesnoth.org/
4 
5  This program is free software; you can redistribute it and/or modify
6  it under the terms of the GNU General Public License as published by
7  the Free Software Foundation; either version 2 of the License, or
8  (at your option) any later version.
9  This program is distributed in the hope that it will be useful,
10  but WITHOUT ANY WARRANTY.
11 
12  See the COPYING file for more details.
13  */
14 
15 /**
16  * @file
17  * Recruitment Engine by flix
18  * See http://wiki.wesnoth.org/AI_Recruitment
19  */
20 
22 
23 #include "ai/actions.hpp"
24 #include "ai/composite/rca.hpp"
25 #include "ai/manager.hpp"
26 #include "actions/attack.hpp"
27 #include "attack_prediction.hpp"
28 #include "filter_context.hpp"
29 #include "game_board.hpp"
30 #include "game_display.hpp"
31 #include "log.hpp"
32 #include "map/map.hpp"
33 #include "map/label.hpp"
34 #include "pathfind/pathfind.hpp"
35 #include "pathutils.hpp"
36 #include "random.hpp"
37 #include "resources.hpp"
38 #include "team.hpp"
39 #include "tod_manager.hpp"
40 #include "units/filter.hpp"
41 #include "units/map.hpp"
42 #include "units/types.hpp"
43 #include "units/unit.hpp"
44 #include "variable.hpp"
45 #include "wml_exception.hpp"
46 
47 #include <cmath>
48 
49 static lg::log_domain log_ai_recruitment("ai/recruitment");
50 #define LOG_AI_RECRUITMENT LOG_STREAM(info, log_ai_recruitment)
51 #define ERR_AI_RECRUITMENT LOG_STREAM(err, log_ai_recruitment)
52 
53 #ifdef _MSC_VER
54 #pragma warning(push)
55 // silence "inherits via dominance" warnings
56 #pragma warning(disable:4250)
57 #endif
58 
59 namespace ai {
60 
61 namespace default_recruitment {
62 
63 namespace {
64 /**
65  * CONSTANTS
66  */
67 
68 // This is used for a income estimation. We'll calculate the estimated income of this much
69 // future turns and decide if we'd gain gold if we start to recruit no units anymore.
70 const static int SAVE_GOLD_FORECAST_TURNS = 5;
71 
72 // When a team has less then this much units, consider recruit-list too.
73 const static unsigned int UNIT_THRESHOLD = 5;
74 
75 // Defines the shape of the border-zone between enemies.
76 // Higher values mean more important hexes.
77 const static double MAP_BORDER_THICKNESS = 2.0;
78 const static double MAP_BORDER_WIDTH = 0.2;
79 
80 // This parameter can be used to shift all important hexes in one directon.
81 // For example if our AI should act rather defensivly we may want to set
82 // this value to a negative number. Then the AI will more care about hexes
83 // nearer to the own units.
84 const static int MAP_OFFENSIVE_SHIFT = 0;
85 
86 // When villages are this near to imprtant hexes they count as important.
87 const static int MAP_VILLAGE_NEARNESS_THRESHOLD = 3;
88 
89 // Radius of area around important villages.
90 const static int MAP_VILLAGE_SURROUNDING = 1;
91 
92 // Determines the power of a raw unit comparison
93 // A higher power means that *very good* units will be
94 // stronger favored compared to just *good* units.
95 const static double COMBAT_SCORE_POWER = 1.;
96 
97 // A cache is used to store the simulation results.
98 // This value determines how much the average defenses of the important hexes can differ
99 // until the simulation will run again.
100 const static double COMBAT_CACHE_TOLERANCY = 0.5;
101 
102 // The old recruitment CA usually recruited too many scouts.
103 // To prevent this we multiply the aspect village_per_scout with this constant.
104 const static double VILLAGE_PER_SCOUT_MULTIPLICATOR = 2.;
105 }
106 
108  std::stringstream s;
109  s << "---------------Content of leader data---------------\n";
110  s << "For leader: " << leader->name() << "\n";
111  s << "ratio_score: " << ratio_score << "\n";
112  s << "recruit_count: " << recruit_count << "\n\n";
113  for (const score_map::value_type& entry : scores) {
114  s << std::setw(20) << entry.first <<
115  " score: " << std::setw(7) << entry.second << "\n";
116  }
117  s << "----------------------------------------------------\n";
118  return s.str();
119 }
120 
122  : candidate_action(context, cfg),
123  important_hexes_(),
124  important_terrain_(),
125  own_units_in_combat_counter_(0),
126  average_local_cost_(),
127  cheapest_unit_costs_(),
128  combat_cache_(),
129  recruit_situation_change_observer_(),
130  average_lawful_bonus_(0.0),
131  recruitment_instructions_(),
132  recruitment_instructions_turn_(-1),
133  own_units_count_(),
134  total_own_units_(0),
135  scouts_wanted_(0)
136 {
137  if (cfg["state"] == "save_gold") {
138  state_ = SAVE_GOLD;
139  } else if (cfg["state"] == "spend_all_gold") {
141  } else {
142  state_ = NORMAL;
143  }
144 }
145 
148  if (state_ == SAVE_GOLD) {
149  cfg["state"] = "save_gold";
150  } else if (state_ == SPEND_ALL_GOLD) {
151  cfg["state"] = "spend_all_gold";
152  } else {
153  cfg["state"] = "normal";
154  }
155  return cfg;
156 }
157 
159  // Check if the recruitment list has changed.
160  // Then cheapest_unit_costs_ is not valid anymore.
162  cheapest_unit_costs_.clear();
164  }
165 
166  // When evaluate() is called the first time this turn,
167  // we'll retrieve the recruitment-instruction aspect.
172  LOG_AI_RECRUITMENT << "Recruitment-instructions updated:\n";
174  }
175 
176  // Check if we have something to do.
177  const config* job = get_most_important_job();
178  if (!job) {
179  return BAD_SCORE;
180  }
181 
183  const std::vector<unit_map::const_iterator> leaders = units.find_leaders(get_side());
184 
185  for (const unit_map::const_iterator& leader : leaders) {
186  if (leader == resources::gameboard->units().end()) {
187  return BAD_SCORE;
188  }
189  // Check Gold. But proceed if there is a unit with cost <= 0 (WML can do that)
190  int cheapest_unit_cost = get_cheapest_unit_cost_for_leader(leader);
191  if (current_team().gold() < cheapest_unit_cost && cheapest_unit_cost > 0) {
192  continue;
193  }
194 
195  const map_location& loc = leader->get_location();
196  if (resources::gameboard->map().is_keep(loc) &&
198  return get_score();
199  }
200  }
201 
202  return BAD_SCORE;
203 }
204 
206  LOG_AI_RECRUITMENT << "\n\n\n------------AI RECRUITMENT BEGIN---------------\n\n";
207  LOG_AI_RECRUITMENT << "TURN: " << resources::tod_manager->turn() <<
208  " SIDE: " << current_team().side() << "\n";
209 
210  /*
211  * Check which leaders can recruit and collect them in leader_data.
212  */
213 
215  const gamemap& map = resources::gameboard->map();
216  const std::vector<unit_map::const_iterator> leaders = units.find_leaders(get_side());
217 
218  // This is the central datastructure with all score_tables in it.
219  std::vector<data> leader_data;
220 
221  std::set<std::string> global_recruits;
222 
223  for (const unit_map::const_iterator& leader : leaders) {
224  const map_location& keep = leader->get_location();
225  if (!resources::gameboard->map().is_keep(keep)) {
226  LOG_AI_RECRUITMENT << "Leader " << leader->name() << " is not on keep. \n";
227  continue;
228  }
230  LOG_AI_RECRUITMENT << "Leader " << leader->name() << " has no free hexes \n";
231  continue;
232  }
233  int cheapest_unit_cost = get_cheapest_unit_cost_for_leader(leader);
234  if (current_team().gold() < cheapest_unit_cost && cheapest_unit_cost > 0) {
235  LOG_AI_RECRUITMENT << "Leader " << leader->name() << " recruits are too expensive. \n";
236  continue;
237  }
238 
239  // Leader can recruit.
240 
241  data data(leader);
242 
243  // Add team recruits.
244  for (const std::string& recruit : current_team().recruits()) {
245  if (!unit_types.find(recruit)) {
246  lg::wml_error() << "Unit-type \"" << recruit << "\" doesn't exist.\n";
247  }
248  data.recruits.insert(recruit);
249  data.scores[recruit] = 0.0;
250  global_recruits.insert(recruit);
251  }
252 
253  // Add extra recruits.
254  for (const std::string& recruit : leader->recruits()) {
255  if (!unit_types.find(recruit)) {
256  lg::wml_error() << "Unit-type \"" << recruit << "\" doesn't exist.\n";
257  }
258  data.recruits.insert(recruit);
259  data.scores[recruit] = 0.0;
260  global_recruits.insert(recruit);
261  }
262 
263  // Add recalls.
264  // Recalls are treated as recruits. While recruiting
265  // we'll check if we can do a recall instead of a recruitment.
266  for (const unit_const_ptr & recall : current_team().recall_list()) {
267  // Check if this leader is allowed to recall this unit.
268  const unit_filter ufilt( vconfig(leader->recall_filter()), resources::filter_con);
269  if (!ufilt(*recall, map_location::null_location())) {
270  continue;
271  }
272  data.recruits.insert(recall->type_id());
273  data.scores[recall->type_id()] = 0.0;
274  global_recruits.insert(recall->type_id());
275  }
276 
277  // Check if leader is in danger. (If a enemies unit can attack the leader)
278  data.in_danger = power_projection(leader->get_location(), get_enemy_dstsrc()) > 0;
279 
280  // If yes, set ratio_score very high, so this leader will get priority while recruiting.
281  if (data.in_danger) {
282  data.ratio_score = 50;
284  LOG_AI_RECRUITMENT << "Leader " << leader->name() << " is in danger.\n";
285  }
286 
287  leader_data.push_back(data);
288  }
289 
290  if (leader_data.empty()) {
291  LOG_AI_RECRUITMENT << "No leader available for recruiting. \n";
292  return; // This CA is going to be blacklisted for this turn.
293  }
294 
295  if (global_recruits.empty()) {
296  LOG_AI_RECRUITMENT << "All leaders have empty recruitment lists. \n";
297  return; // This CA is going to be blacklisted for this turn.
298  }
299 
300  /**
301  * Find important hexes and calculate other static things.
302  */
303 
305  // Show "x" on important hexes if debug mode is activated AND
306  // the log domain "ai/recruitment" is used.
307  if (game_config::debug && !lg::info().dont_log(log_ai_recruitment)) {
309  }
310 
311  for (const map_location& hex : important_hexes_) {
312  ++important_terrain_[map[hex]];
313  }
314 
318 
319  /**
320  * Fill scores.
321  */
322 
323  do_combat_analysis(&leader_data);
324 
325  LOG_AI_RECRUITMENT << "Scores before extra treatments:\n";
326  for (const data& data : leader_data) {
327  LOG_AI_RECRUITMENT << "\n" << data.to_string();
328  }
329 
330  do_similarity_penalty(&leader_data);
331  do_randomness(&leader_data);
332  handle_recruitment_more(&leader_data);
333 
334  LOG_AI_RECRUITMENT << "Scores after extra treatments:\n";
335  for (const data& data : leader_data) {
336  LOG_AI_RECRUITMENT << "\n" << data.to_string();
337  }
338 
339  /**
340  * Do recruitment according to [recruit]-tags and scores.
341  * Note that the scores don't indicate the preferred mix to recruit but rather
342  * the preferred mix of all units. So already existing units are considered.
343  */
344 
346  config* job = nullptr;
347  do { // Recruitment loop
349 
350  // Check if we may want to save gold by not recruiting.
351  update_state();
352  int save_gold_turn = get_recruitment_save_gold()["active"].to_int(2); // From aspect.
353  int current_turn = resources::tod_manager->turn();
354  bool save_gold_active = save_gold_turn > 0 && save_gold_turn <= current_turn;
355  if (state_ == SAVE_GOLD && save_gold_active) {
356  break;
357  }
358 
359  job = get_most_important_job();
360  if (!job) {
361  LOG_AI_RECRUITMENT << "All recruitment jobs (recruitment_instructions) done.\n";
362  break;
363  }
364  LOG_AI_RECRUITMENT << "Executing this job:\n" << *job << "\n";
365 
366  data* best_leader_data = get_best_leader_from_ratio_scores(leader_data, job);
367  if (!best_leader_data) {
368  LOG_AI_RECRUITMENT << "Leader with job (recruitment_instruction) is not on keep.\n";
369  if (remove_job_if_no_blocker(job)) {
370  continue;
371  } else {
372  break;
373  }
374  }
375  LOG_AI_RECRUITMENT << "We want to have " << scouts_wanted_ << " more scouts.\n";
376 
377  const std::string best_recruit = get_best_recruit_from_scores(*best_leader_data, job);
378  if (best_recruit.empty()) {
379  LOG_AI_RECRUITMENT << "Cannot fulfill recruitment-instruction.\n";
380  if (remove_job_if_no_blocker(job)) {
381  continue;
382  } else {
383  break;
384  }
385  }
386 
387  LOG_AI_RECRUITMENT << "Best recruit is: " << best_recruit << "\n";
388  const std::string* recall_id = get_appropriate_recall(best_recruit, *best_leader_data);
389  if (recall_id) {
390  LOG_AI_RECRUITMENT << "Found appropriate recall with id: " << *recall_id << "\n";
391  action_result = execute_recall(*recall_id, *best_leader_data);
392  } else {
393  action_result = execute_recruit(best_recruit, *best_leader_data);
394  }
395 
396  if (action_result->is_ok()) {
397  ++own_units_count_[best_recruit];
399  if (recruit_matches_type(best_recruit, "scout")) {
400  --scouts_wanted_;
401  }
402 
403  // Update the current job.
404  if (!job->operator[]("total").to_bool(false)) {
405  job->operator[]("number") = job->operator[]("number").to_int(99999) - 1;
406  }
407 
408  // Check if something changed in the recruitment list (WML can do that).
409  // If yes, just return/break. evaluate() and execute() will be called again.
411  break;
412  }
413  // Check if the gamestate changed more than once.
414  // (Recruitment will trigger one gamestate change, WML could trigger more changes.)
415  // If yes, just return/break. evaluate() and execute() will be called again.
417  break;
418  }
419 
420  } else {
421  LOG_AI_RECRUITMENT << "Recruit result not ok.\n";
422  // We'll end up here if
423  // 1. We haven't enough gold,
424  // 2. There aren't any free hexes around leaders,
425  // 3. This leader can not recruit this type (this can happen after a recall)
426  }
427  } while((action_result && action_result->is_ok()) || !action_result);
428  // A action_result may be uninitialized if a job was removed. Continue then anyway.
429 
430  // Recruiting is done now.
431  // Update state_ for next execution().
432 
433  if (state_ == LEADER_IN_DANGER) {
434  state_ = NORMAL;
435  }
436 
437  int status = (action_result) ? action_result->get_status() : -1;
438  bool no_gold = (status == recruit_result::E_NO_GOLD || status == recall_result::E_NO_GOLD);
439  if (state_ == SPEND_ALL_GOLD && no_gold) {
440  state_ = SAVE_GOLD;
441  }
442  if (job && no_gold) {
444  }
445 }
446 
447 /**
448  * A helper function for execute().
449  */
452  recall_result = check_recall_action(id, map_location::null_location(),
453  leader_data.leader->get_location());
454  if (recall_result->is_ok()) {
455  recall_result->execute();
456  ++leader_data.recruit_count;
457  }
458  return recall_result;
459 }
460 
461 /**
462  * A helper function for execute().
463  */
466  recruit_result = check_recruit_action(type, map_location::null_location(),
467  leader_data.leader->get_location());
468 
469  if (recruit_result->is_ok()) {
470  recruit_result->execute();
471  LOG_AI_RECRUITMENT << "Recruited " << type << "\n";
472  ++leader_data.recruit_count;
473  }
474  return recruit_result;
475 }
476 
477 /**
478  * A helper function for execute().
479  * Checks if this unit type can be recalled.
480  * If yes, we calculate a estimated value in gold of the recall unit.
481  * If this value is less then the recall cost, we dismiss the unit.
482  * The unit with the highest value will be returned.
483  */
485  const data& leader_data) const {
486  const std::string* best_recall_id = nullptr;
487  double best_recall_value = -1;
488  for (const unit_const_ptr & recall_unit : current_team().recall_list()) {
489  if (type != recall_unit->type_id()) {
490  continue;
491  }
492  // Check if this leader is allowed to recall this unit.
493  const unit_filter ufilt(vconfig(leader_data.leader->recall_filter()), resources::filter_con);
494  if (!ufilt(*recall_unit, map_location::null_location())) {
495  LOG_AI_RECRUITMENT << "Refused recall because of filter: " << recall_unit->id() << "\n";
496  continue;
497  }
498  double average_cost_of_advanced_unit = 0;
499  int counter = 0;
500  for (const std::string& advancement : recall_unit->advances_to()) {
501  const unit_type* advancement_type = unit_types.find(advancement);
502  if (!advancement_type) {
503  continue;
504  }
505  average_cost_of_advanced_unit += advancement_type->cost();
506  ++counter;
507  }
508  if (counter > 0) {
509  average_cost_of_advanced_unit /= counter;
510  } else {
511  // Unit don't have advancements. Use cost of unit itself.
512  average_cost_of_advanced_unit = recall_unit->cost();
513  }
514  double xp_quantity = static_cast<double>(recall_unit->experience()) /
515  recall_unit->max_experience();
516  double recall_value = recall_unit->cost() + xp_quantity * average_cost_of_advanced_unit;
517  if (recall_value < current_team().recall_cost()) {
518  continue; // Unit is not worth to get recalled.
519  }
520  if (recall_value > best_recall_value) {
521  best_recall_id = &recall_unit->id();
522  best_recall_value = recall_value;
523  }
524  }
525  return best_recall_id;
526 }
527 
528 /**
529  * A helper function for execute().
530  * Decides according to the leaders ratio scores which leader should recruit.
531  */
532 data* recruitment::get_best_leader_from_ratio_scores(std::vector<data>& leader_data,
533  const config* job) const {
534  assert(job);
535  // Find things for normalization.
536  int total_recruit_count = 0;
537  double ratio_score_sum = 0.0;
538  for (const data& data : leader_data) {
539  ratio_score_sum += data.ratio_score;
540  total_recruit_count += data.recruit_count;
541  }
542  assert(ratio_score_sum > 0.0);
543 
544  // Shuffle leader_data to break ties randomly.
545  std::shuffle(leader_data.begin(), leader_data.end(), randomness::rng::default_instance());
546 
547  // Find which leader should recruit according to ratio_scores.
548  data* best_leader_data = nullptr;
549  double biggest_difference = -99999.;
550  for (data& data : leader_data) {
551  if (!leader_matches_job(data, job)) {
552  continue;
553  }
554  double desired_ammount = data.ratio_score / ratio_score_sum * (total_recruit_count + 1);
555  double current_ammount = data.recruit_count;
556  double difference = desired_ammount - current_ammount;
557  if (difference > biggest_difference) {
558  biggest_difference = difference;
559  best_leader_data = &data;
560  }
561  }
562  return best_leader_data;
563 }
564 
565 /**
566  * A helper function for execute().
567  * Counts own units and then decides what unit should be recruited so that the
568  * unit distribution approaches the given scores.
569  */
571  const config* job) {
572  assert(job);
573  std::string pattern_type = get_random_pattern_type_if_exists(leader_data, job);
574  if (!pattern_type.empty()) {
575  LOG_AI_RECRUITMENT << "Randomly chosen pattern_type: " << pattern_type << "\n";
576  }
577  std::string best_recruit = "";
578  double biggest_difference = -99999.;
579  for (const score_map::value_type& i : leader_data.get_normalized_scores()) {
580  const std::string& unit = i.first;
581  const double score = i.second;
582 
583  if (!limit_ok(unit)) {
584  continue;
585  }
586  if (!pattern_type.empty()) {
587  if (!recruit_matches_type(unit, pattern_type)) {
588  continue;
589  }
590  } else {
591  if (!recruit_matches_job(unit, job)) {
592  continue;
593  }
594  }
595 
596  double desired_ammount = score * (total_own_units_ + 1);
597  double current_ammount = own_units_count_[unit];
598  double difference = desired_ammount - current_ammount;
599  if (scouts_wanted_ > 0 && recruit_matches_type(unit, "scout")) {
600  difference += 1000.;
601  }
602  if (difference > biggest_difference) {
603  biggest_difference = difference;
604  best_recruit = unit;
605  }
606  }
607  return best_recruit;
608 }
609 
610 /**
611  * For Map Analysis
612  * Computes from our cost map and the combined cost map of all enemies the important hexes.
613  */
615  const pathfind::full_cost_map& my_cost_map,
616  const pathfind::full_cost_map& enemy_cost_map) {
617 
618  const gamemap& map = resources::gameboard->map();
619 
620  // First collect all hexes where the average costs are similar in important_hexes_candidates
621  // Then chose only those hexes where the average costs are relatively low.
622  // This is done to remove hexes to where the teams need a similar amount of moves but
623  // which are relatively far away comparing to other important hexes.
624  typedef std::map<map_location, double> border_cost_map;
625  border_cost_map important_hexes_candidates;
626  double smallest_border_movecost = 999999;
627  double biggest_border_movecost = 0;
628  for(int x = 0; x < map.w(); ++x) {
629  for (int y = 0; y < map.h(); ++y) {
630  double my_cost_average = my_cost_map.get_average_cost_at(x, y);
631  double enemy_cost_average = enemy_cost_map.get_average_cost_at(x, y);
632  if (my_cost_average == -1 || enemy_cost_average == -1) {
633  continue;
634  }
635  // We multiply the threshold MAP_BORDER_THICKNESS by the average_local_cost
636  // to favor high cost hexes (a bit).
637  if (std::abs(my_cost_average - MAP_OFFENSIVE_SHIFT - enemy_cost_average) <
638  MAP_BORDER_THICKNESS * average_local_cost_[map_location(x, y)]) {
639  double border_movecost = (my_cost_average + enemy_cost_average) / 2;
640 
641  important_hexes_candidates[map_location(x, y)] = border_movecost;
642 
643  if (border_movecost < smallest_border_movecost) {
644  smallest_border_movecost = border_movecost;
645  }
646  if (border_movecost > biggest_border_movecost) {
647  biggest_border_movecost = border_movecost;
648  }
649  }
650  } // for
651  } // for
652  double threshold = (biggest_border_movecost - smallest_border_movecost) *
653  MAP_BORDER_WIDTH + smallest_border_movecost;
654  for (const border_cost_map::value_type& candidate : important_hexes_candidates) {
655  if (candidate.second < threshold) {
656  important_hexes_.insert(candidate.first);
657  }
658  }
659 }
660 
661 /**
662  * For Map Analysis.
663  * Calculates for a given unit the average defense on the map.
664  * (According to important_hexes_ / important_terrain_)
665  */
666 double recruitment::get_average_defense(const std::string& u_type) const {
667  const unit_type* const u_info = unit_types.find(u_type);
668  if (!u_info) {
669  return 0.0;
670  }
671  long summed_defense = 0;
672  int total_terrains = 0;
673  for (const terrain_count_map::value_type& entry : important_terrain_) {
674  const t_translation::terrain_code& terrain = entry.first;
675  int count = entry.second;
676  int defense = 100 - u_info->movement_type().defense_modifier(terrain);
677  summed_defense += defense * count;
678  total_terrains += count;
679  }
680  double average_defense = (total_terrains == 0) ? 0.0 :
681  static_cast<double>(summed_defense) / total_terrains;
682  return average_defense;
683 }
684 
685 /**
686  * For Map Analysis.
687  * Creates cost maps for a side. Each hex is map to
688  * a) the summed movecost and
689  * b) how many units can reach this hex
690  * for all units of side.
691  */
694  const team& team = resources::gameboard->get_team(side);
695 
696  pathfind::full_cost_map cost_map(true, true, team, true, true);
697 
698  // First add all existing units to cost_map.
699  unsigned int unit_count = 0;
700  for (const unit& unit : units) {
701  if (unit.side() != side || unit.can_recruit() ||
702  unit.incapacitated() || unit.total_movement() <= 0) {
703  continue;
704  }
705  ++unit_count;
706  cost_map.add_unit(unit);
707  }
708 
709  // If this side has not so many units yet, add unit_types with the leaders position as origin.
710  if (unit_count < UNIT_THRESHOLD) {
711  std::vector<unit_map::const_iterator> leaders = units.find_leaders(side);
712  for (const unit_map::const_iterator& leader : leaders) {
713  // First add team-recruits (it's fine when (team-)recruits are added multiple times).
714  for (const std::string& recruit : team.recruits()) {
715  cost_map.add_unit(leader->get_location(), unit_types.find(recruit), side);
716  }
717 
718  // Next add extra-recruits.
719  for (const std::string& recruit : leader->recruits()) {
720  cost_map.add_unit(leader->get_location(), unit_types.find(recruit), side);
721  }
722  }
723  }
724  return cost_map;
725 }
726 
727 /**
728  * For Map Analysis.
729  * Shows the important hexes for debugging purposes on the map. Only if debug is activated.
730  */
732  if (!game_config::debug) {
733  return;
734  }
736  for (const map_location& loc : important_hexes_) {
737  // Little hack: use map_location north from loc and make 2 linebreaks to center the "X".
738  resources::screen->labels().set_label(loc.get_direction(map_location::NORTH), "\n\nX");
739  }
740 }
741 
742 /**
743  * Calculates a average lawful bonus, so Combat Analysis will work
744  * better in caves and custom time of day cycles.
745  */
747  int sum = 0;
748  int counter = 0;
749  for (const time_of_day& time : resources::tod_manager->times()) {
750  sum += time.lawful_bonus;
751  ++counter;
752  }
753  if (counter > 0) {
754  average_lawful_bonus_ = round_double(static_cast<double>(sum) / counter);
755  }
756 }
757 
758 /**
759  * For Map Analysis.
760  * Creates a map where each hex is mapped to the average cost of the terrain for our units.
761  */
763  average_local_cost_.clear();
764  const gamemap& map = resources::gameboard->map();
766 
767  for(int x = 0; x < map.w(); ++x) {
768  for (int y = 0; y < map.h(); ++y) {
769  map_location loc(x, y);
770  int summed_cost = 0;
771  int count = 0;
772  for (const std::string& recruit : team.recruits()) {
773  const unit_type* const unit_type = unit_types.find(recruit);
774  if (!unit_type) {
775  continue;
776  }
777  int cost = unit_type->movement_type().get_movement().cost(map[loc]);
778  if (cost < 99) {
779  summed_cost += cost;
780  ++count;
781  }
782  }
783  average_local_cost_[loc] = (count == 0) ? 0 : static_cast<double>(summed_cost) / count;
784  }
785  }
786 }
787 
788 /**
789  * For Map Analysis.
790  * Creates a std::set of hexes where a fight will occur with high probability.
791  */
793  important_hexes_.clear();
794  important_terrain_.clear();
796 
798  const gamemap& map = resources::gameboard->map();
800 
801  // Mark battle areas as important
802  // This are locations where one of my units is adjacent
803  // to a enemies unit.
804  for (const unit& unit : units) {
805  if (unit.side() != get_side()) {
806  continue;
807  }
809  // We found a enemy next to us. Mark our unit and all adjacent
810  // hexes as important.
811  std::vector<map_location> surrounding;
812  get_tiles_in_radius(unit.get_location(), 1, surrounding);
814  std::copy(surrounding.begin(), surrounding.end(),
815  std::inserter(important_hexes_, important_hexes_.begin()));
817  }
818  }
819 
820  // Mark area between me and enemies as important
821  // This is done by creating a cost_map for each team.
822  // A cost_map maps to each hex the average costs to reach this hex
823  // for all units of the team.
824  // The important hexes are those where my value on the cost map is
825  // similar to a enemies one.
827  for (const team& team : resources::gameboard->teams()) {
828  if (current_team().is_enemy(team.side())) {
829  const pathfind::full_cost_map enemy_cost_map = get_cost_map_of_side(team.side());
830 
831  compare_cost_maps_and_update_important_hexes(my_cost_map, enemy_cost_map);
832  }
833  }
834 
835  // Mark 'near' villages and area around them as important
836  // To prevent a 'feedback' of important locations collect all
837  // important villages first and add them and their surroundings
838  // to important_hexes_ in a second step.
839  std::vector<map_location> important_villages;
840  for (const map_location& village : map.villages()) {
841  std::vector<map_location> surrounding;
842  get_tiles_in_radius(village, MAP_VILLAGE_NEARNESS_THRESHOLD, surrounding);
843  for (const map_location& hex : surrounding) {
844  if (important_hexes_.find(hex) != important_hexes_.end()) {
845  important_villages.push_back(village);
846  break;
847  }
848  }
849  }
850  for (const map_location& village : important_villages) {
851  important_hexes_.insert(village);
852  std::vector<map_location> surrounding;
853  get_tiles_in_radius(village, MAP_VILLAGE_SURROUNDING, surrounding);
854  for (const map_location& hex : surrounding) {
855  // only add hex if one of our units can reach the hex
856  if (map.on_board(hex) && my_cost_map.get_cost_at(hex.x, hex.y) != -1) {
857  important_hexes_.insert(hex);
858  }
859  }
860  }
861 }
862 
863 /**
864  * For Combat Analysis.
865  * Calculates how good unit-type a is against unit type b.
866  * If the value is bigger then 0, a is better then b.
867  * If the value is 2.0 then unit-type a is twice as good as unit-type b.
868  * Since this function is called very often it uses a cache.
869  */
871  const unit_type* const type_a = unit_types.find(a);
872  const unit_type* const type_b = unit_types.find(b);
873  if (!type_a || !type_b) {
874  ERR_AI_RECRUITMENT << "Couldn't find unit type: " << ((type_a) ? b : a) << "." << std::endl;
875  return 0.0;
876  }
877  double defense_a = get_average_defense(a);
878  double defense_b = get_average_defense(b);
879 
880  const double* cache_value = get_cached_combat_value(a, b, defense_a, defense_b);
881  if (cache_value) {
882  return *cache_value;
883  }
884 
885  double damage_to_a = 0.0;
886  double damage_to_b = 0.0;
887 
888  // a attacks b
889  simulate_attack(type_a, type_b, defense_a, defense_b, &damage_to_a, &damage_to_b);
890  // b attacks a
891  simulate_attack(type_b, type_a, defense_b, defense_a, &damage_to_b, &damage_to_a);
892 
893  int a_cost = (type_a->cost() > 0) ? type_a->cost() : 1;
894  int b_cost = (type_b->cost() > 0) ? type_b->cost() : 1;
895  int a_max_hp = (type_a->hitpoints() > 0) ? type_a->hitpoints() : 1;
896  int b_max_hp = (type_b->hitpoints() > 0) ? type_b->hitpoints() : 1;
897 
898  double retval = 1.;
899  // There are rare cases where a unit deals 0 damage (eg. Elvish Lady).
900  // Then we just set the value to something reasonable.
901  if (damage_to_a <= 0 && damage_to_b <= 0) {
902  retval = 0.;
903  } else if (damage_to_a <= 0) {
904  retval = 2.;
905  } else if (damage_to_b <= 0) {
906  retval = -2.;
907  } else {
908  // Normal case
909  double value_of_a = damage_to_b / (b_max_hp * a_cost);
910  double value_of_b = damage_to_a / (a_max_hp * b_cost);
911 
912  if (value_of_a > value_of_b) {
913  return value_of_a / value_of_b;
914  } else if (value_of_a < value_of_b) {
915  return -value_of_b / value_of_a;
916  } else {
917  return 0.;
918  }
919  }
920 
921  // Insert in cache.
922  const cached_combat_value entry(defense_a, defense_b, retval);
923  std::set<cached_combat_value>& cache = combat_cache_[a][b];
924  cache.insert(entry);
925 
926  return retval;
927 }
928 
929 /**
930  * Combat Analysis.
931  * Main function.
932  * Compares all enemy units with all of our possible recruits and fills
933  * the scores.
934  */
935 void recruitment::do_combat_analysis(std::vector<data>* leader_data) {
937 
938  // Collect all enemy units (and their hp) we want to take into account in enemy_units.
939  typedef std::vector<std::pair<std::string, int> > unit_hp_vector;
940  unit_hp_vector enemy_units;
941  for (const unit& unit : units) {
942  if (!current_team().is_enemy(unit.side()) || unit.incapacitated()) {
943  continue;
944  }
945  enemy_units.emplace_back(unit.type_id(), unit.hitpoints());
946  }
947  if (enemy_units.size() < UNIT_THRESHOLD) {
948  // Use also enemies recruitment lists and insert units into enemy_units.
949  for (const team& team : resources::gameboard->teams()) {
950  if (!current_team().is_enemy(team.side())) {
951  continue;
952  }
953  std::set<std::string> possible_recruits;
954  // Add team recruits.
955  possible_recruits.insert(team.recruits().begin(), team.recruits().end());
956  // Add extra recruits.
957  const std::vector<unit_map::const_iterator> leaders = units.find_leaders(team.side());
958  for (unit_map::const_iterator leader : leaders) {
959  possible_recruits.insert(leader->recruits().begin(), leader->recruits().end());
960  }
961  // Insert set in enemy_units.
962  for (const std::string& possible_recruit : possible_recruits) {
963  const unit_type* recruit_type = unit_types.find(possible_recruit);
964  if (recruit_type) {
965  int hp = recruit_type->hitpoints();
966  enemy_units.emplace_back(possible_recruit, hp);
967  }
968  }
969  }
970  }
971 
972  for (data& leader : *leader_data) {
973  if (leader.recruits.empty()) {
974  continue;
975  }
976  typedef std::map<std::string, double> simple_score_map;
977  simple_score_map temp_scores;
978 
979  for (const unit_hp_vector::value_type& entry : enemy_units) {
980  const std::string& enemy_unit = entry.first;
981  int enemy_unit_hp = entry.second;
982  for (const std::string& recruit : leader.recruits) {
983  double score = compare_unit_types(recruit, enemy_unit);
984  score *= enemy_unit_hp;
985  score = pow(score, COMBAT_SCORE_POWER);
986  temp_scores[recruit] += score;
987  }
988  }
989 
990  if (temp_scores.empty()) {
991  return;
992  }
993  // Find things for normalization.
994  double max = -99999.;
995  double sum = 0;
996  for (const simple_score_map::value_type& entry : temp_scores) {
997  double score = entry.second;
998  if (score > max) {
999  max = score;
1000  }
1001  sum += score;
1002  }
1003  double average = sum / temp_scores.size();
1004 
1005  // What we do now is a linear transformation.
1006  // We want to map the scores in temp_scores to something between 0 and 100.
1007  // The max score shall always be 100.
1008  // The min score depends on the aspect "recruitment_diversity".
1009  double new_100 = max;
1010  double score_threshold = get_recruitment_diversity();
1011  if (score_threshold <= 0) {
1012  score_threshold = 0.0001;
1013  }
1014  double new_0 = max - (score_threshold * (max - average));
1015  if (new_100 == new_0) {
1016  // This can happen if max == average. (E.g. only one possible recruit)
1017  new_0 -= 0.000001;
1018  }
1019 
1020  for (const simple_score_map::value_type& entry : temp_scores) {
1021  const std::string& recruit = entry.first;
1022  double score = entry.second;
1023 
1024  // Here we transform.
1025  // (If score <= new_0 then normalized_score will be 0)
1026  // (If score = new_100 then normalized_score will be 100)
1027  double normalized_score = 100 * ((score - new_0) / (new_100 - new_0));
1028  if (normalized_score < 0) {
1029  normalized_score = 0;
1030  }
1031  leader.scores[recruit] += normalized_score;
1032  }
1033  } // for all leaders
1034 }
1035 
1036 /**
1037  * For Combat Analysis.
1038  * Returns the cached combat value for two unit types
1039  * or nullptr if there is none or terrain defenses are not within range.
1040  */
1042  double a_defense, double b_defense) {
1043  double best_distance = 999;
1044  const double* best_value = nullptr;
1045  const std::set<cached_combat_value>& cache = combat_cache_[a][b];
1046  for (const cached_combat_value& entry : cache) {
1047  double distance_a = std::abs(entry.a_defense - a_defense);
1048  double distance_b = std::abs(entry.b_defense - b_defense);
1049  if (distance_a <= COMBAT_CACHE_TOLERANCY && distance_b <= COMBAT_CACHE_TOLERANCY) {
1050  if(distance_a + distance_b <= best_distance) {
1051  best_distance = distance_a + distance_b;
1052  best_value = &entry.value;
1053  }
1054  }
1055  }
1056  return best_value;
1057 }
1058 
1059 /**
1060  * For Combat Analysis.
1061  * This struct encapsulates all information for one attack simulation.
1062  * One attack simulation is defined by the unit-types, the weapons and the units defenses.
1063  */
1071 
1072  attack_simulation(const unit_type* attacker, const unit_type* defender,
1073  double attacker_defense, double defender_defense,
1074  const_attack_ptr att_weapon, const_attack_ptr def_weapon,
1075  int average_lawful_bonus) :
1076  attacker_type(attacker),
1077  defender_type(defender),
1078  attacker_stats(attacker, att_weapon, true, defender, def_weapon,
1079  round_double(defender_defense), average_lawful_bonus),
1080  defender_stats(defender, def_weapon, false, attacker, att_weapon,
1081  round_double(attacker_defense), average_lawful_bonus),
1082  attacker_combatant(attacker_stats),
1083  defender_combatant(defender_stats)
1084  {
1085  attacker_combatant.fight(defender_combatant);
1086  }
1087 
1088  bool better_result(const attack_simulation* other, bool for_defender) {
1089  assert(other);
1090  if (for_defender) {
1092  defender_combatant, attacker_combatant,
1093  other->defender_combatant, other->attacker_combatant, 0);
1094  } else {
1096  attacker_combatant, defender_combatant,
1097  other->attacker_combatant, other->defender_combatant, 0);
1098  }
1099  }
1100 
1101  double get_avg_hp_of_defender() const {
1102  return get_avg_hp_of_combatant(false);
1103  }
1104 
1105  double get_avg_hp_of_attacker() const {
1106  return get_avg_hp_of_combatant(true);
1107  }
1108  double get_avg_hp_of_combatant(bool attacker) const {
1109  const combatant& combatant = (attacker) ? attacker_combatant : defender_combatant;
1110  const unit_type* unit_type = (attacker) ? attacker_type : defender_type;
1111  double avg_hp = combatant.average_hp(0);
1112 
1113  // handle poisson
1114  avg_hp -= combatant.poisoned * game_config::poison_amount;
1115 
1116  avg_hp = std::max(0., avg_hp);
1117  avg_hp = std::min(static_cast<double>(unit_type->hitpoints()), avg_hp);
1118  return avg_hp;
1119  }
1120 };
1121 
1122 /**
1123  * For Combat Analysis.
1124  * Simulates a attack with a attacker and a defender.
1125  * The function will use battle_context::better_combat() to decide which weapon to use.
1126  */
1128  const unit_type* const attacker, const unit_type* const defender,
1129  double attacker_defense, double defender_defense,
1130  double* damage_to_attacker, double* damage_to_defender) const {
1131  if(!attacker || !defender || !damage_to_attacker || !damage_to_defender) {
1132  ERR_AI_RECRUITMENT << "nullptr pointer in simulate_attack()" << std::endl;
1133  return;
1134  }
1135  const_attack_itors attacker_weapons = attacker->attacks();
1136  const_attack_itors defender_weapons = defender->attacks();
1137 
1138  std::shared_ptr<attack_simulation> best_att_attack;
1139 
1140  // Let attacker choose weapon
1141  for (const attack_type& att_weapon : attacker_weapons) {
1142  std::shared_ptr<attack_simulation> best_def_response;
1143  // Let defender choose weapon
1144  for (const attack_type& def_weapon : defender_weapons) {
1145  if (att_weapon.range() != def_weapon.range()) {
1146  continue;
1147  }
1148  std::shared_ptr<attack_simulation> simulation(new attack_simulation(
1149  attacker, defender,
1150  attacker_defense, defender_defense,
1151  att_weapon.shared_from_this(), def_weapon.shared_from_this(), average_lawful_bonus_));
1152  if (!best_def_response || simulation->better_result(best_def_response.get(), true)) {
1153  best_def_response = simulation;
1154  }
1155  } // for defender weapons
1156 
1157  if (!best_def_response) {
1158  // Defender can not fight back. Simulate this as well.
1159  best_def_response.reset(new attack_simulation(
1160  attacker, defender,
1161  attacker_defense, defender_defense,
1162  att_weapon.shared_from_this(), nullptr, average_lawful_bonus_));
1163  }
1164  if (!best_att_attack || best_def_response->better_result(best_att_attack.get(), false)) {
1165  best_att_attack = best_def_response;
1166  }
1167  } // for attacker weapons
1168 
1169  if (!best_att_attack) {
1170  return;
1171  }
1172 
1173  *damage_to_defender += (defender->hitpoints() - best_att_attack->get_avg_hp_of_defender());
1174  *damage_to_attacker += (attacker->hitpoints() - best_att_attack->get_avg_hp_of_attacker());
1175 }
1176 
1177 /**
1178  * For Configuration / Aspect "recruitment-instructions"
1179  * We call a [recruit] tag a "job".
1180  */
1182  config* most_important_job = nullptr;
1183  int most_important_importance = -1;
1184  int biggest_number = -1;
1185  for (config& job : recruitment_instructions_.child_range("recruit")) {
1186  if (job.empty()) {
1187  continue;
1188  }
1189  int importance = job["importance"].to_int(1);
1190  int number = job["number"].to_int(99999);
1191  bool total = job["total"].to_bool(false);
1192  if (total) {
1193  // If the total flag is set we have to subtract
1194  // all existing units which matches the type.
1196  for (const count_map::value_type& entry : own_units_count_) {
1197  const std::string& unit_type = entry.first;
1198  const int count = entry.second;
1199  if (recruit_matches_job(unit_type, &job)) {
1200  number = number - count;
1201  }
1202  }
1203  }
1204  if (number <= 0) {
1205  continue;
1206  }
1207  if (importance > most_important_importance ||
1208  (importance == most_important_importance && biggest_number > number)) {
1209  most_important_job = &job;
1210  most_important_importance = importance;
1211  biggest_number = number;
1212  }
1213  }
1214  return most_important_job;
1215 }
1216 
1217 /**
1218  * For Configuration / Aspect "recruitment-instructions"
1219  * If the flag pattern is set, this method returns a random element of the
1220  * type-attribute.
1221  */
1223  const config* job) const {
1224  std::string choosen_type;
1225  if (job->operator[]("pattern").to_bool(false)) {
1226  std::vector<std::string> job_types = utils::split(job->operator[]("type"));
1227 
1228  if (job_types.empty()) {
1229  // Empty type attribute means random recruiting.
1230  // Fill job_types with recruitment list.
1231  std::copy(leader_data.recruits.begin(), leader_data.recruits.end(),
1232  std::back_inserter(job_types));
1233  }
1234 
1235  // Before we choose a random pattern type, we make sure that at least one recruit
1236  // matches the types and doesn't exceed the [limit].
1237  // We do this by erasing elements of job_types.
1238  std::vector<std::string>::iterator job_types_it = job_types.begin();
1239 
1240  // Iteration through all elements.
1241  while (job_types_it != job_types.end()) {
1242  bool type_ok = false;
1243  for (const std::string& recruit : leader_data.recruits) {
1244  if (recruit_matches_type(recruit, *job_types_it) && limit_ok(recruit)) {
1245  type_ok = true;
1246  break;
1247  }
1248  }
1249  if (type_ok) {
1250  ++job_types_it;
1251  } else {
1252  // Erase Element. erase() will return iterator of next element.
1253  LOG_AI_RECRUITMENT << "Erase type " << *job_types_it << " from pattern.\n";
1254  job_types_it = job_types.erase(job_types_it);
1255  }
1256  }
1257 
1258  if (!job_types.empty()) {
1259  // Choose a random job_type.
1260  choosen_type = job_types[randomness::generator->get_random_int(0, job_types.size()-1)];
1261  }
1262  }
1263  return choosen_type;
1264 }
1265 
1266 /**
1267  * For Configuration / Aspect "recruitment_pattern"
1268  * Converts the (old) recruitment_pattern into a recruitment_instruction (job).
1269  */
1271  const std::vector<std::string> recruitment_pattern = get_recruitment_pattern();
1272  if (recruitment_pattern.empty()) {
1273  return;
1274  }
1275  // Create a job (recruitment_instruction).
1276  config job;
1277  std::stringstream s;
1278  for (std::vector<std::string>::const_iterator type = recruitment_pattern.begin();
1279  type != recruitment_pattern.end(); ++type) {
1280  s << *type;
1281  if (type != recruitment_pattern.end() - 1) { // Avoid trailing comma.
1282  s << ", ";
1283  }
1284  }
1285  job["type"] = s.str();
1286  job["number"] = 99999;
1287  job["pattern"] = true;
1288  job["blocker"] = true;
1289  job["total"] = false;
1290  job["importance"] = 1;
1291  recruitment_instructions_.add_child("recruit", job);
1292 }
1293 
1294 /**
1295  * For Configuration / Aspect "recruitment-instructions"
1296  * Checks if a given leader is specified in the "leader_id" attribute.
1297  */
1298 bool recruitment::leader_matches_job(const data& leader_data, const config* job) const {
1299  assert(job);
1300  // First we make sure that this leader can recruit
1301  // at least one unit-type specified in the job.
1302  bool is_ok = false;
1303  for (const std::string& recruit : leader_data.recruits) {
1304  if (recruit_matches_job(recruit, job) && limit_ok(recruit)) {
1305  is_ok = true;
1306  break;
1307  }
1308  }
1309  if (!is_ok) {
1310  return false;
1311  }
1312 
1313  std::vector<std::string> ids = utils::split(job->operator[]("leader_id"));
1314  if (ids.empty()) {
1315  // If no leader is specified, all leaders are okay.
1316  return true;
1317  }
1318  return (std::find(ids.begin(), ids.end(), leader_data.leader->id()) != ids.end());
1319 }
1320 
1321 /**
1322  * For Configuration / Aspect "recruitment-instructions"
1323  * Checks if a recruit-type can be recruited according to the [limit]-tag.
1324  */
1325 bool recruitment::limit_ok(const std::string& recruit) const {
1326  // We don't use the member recruitment_instruction_ but instead
1327  // retrieve the aspect again. So the [limit]s can be altered during a turn.
1329 
1330  for (const config& limit : aspect.child_range("limit")) {
1331  std::vector<std::string> types = utils::split(limit["type"]);
1332  // First check if the recruit matches one of the types.
1333  if (recruit_matches_types(recruit, types)) {
1334  // Count all own existing units which matches the type.
1335  int count = 0;
1336  for (const count_map::value_type& entry : own_units_count_) {
1337  const std::string& unit = entry.first;
1338  int number = entry.second;
1339  if (recruit_matches_types(unit, types)) {
1340  count += number;
1341  }
1342  }
1343  // Check if we reached the limit.
1344  if (count >= limit["max"].to_int(0)) {
1345  return false;
1346  }
1347  }
1348  }
1349  return true;
1350 }
1351 
1352 /**
1353  * For Configuration / Aspect "recruitment-instructions"
1354  * Checks if a given recruit-type is specified in the "type" attribute.
1355  */
1356 bool recruitment::recruit_matches_job(const std::string& recruit, const config* job) const {
1357  assert(job);
1358  std::vector<std::string> job_types = utils::split(job->operator[]("type"));
1359  return recruit_matches_types(recruit, job_types);
1360 }
1361 
1362 /**
1363  * For Configuration / Aspect "recruitment-instructions"
1364  * Checks if a given recruit-type matches one atomic "type" attribute.
1365  */
1367  const unit_type* recruit_type = unit_types.find(recruit);
1368  if (!recruit_type) {
1369  return false;
1370  }
1371  // Consider type-name.
1372  if (recruit_type->id() == type) {
1373  return true;
1374  }
1375  // Consider usage.
1376  if (recruit_type->usage() == type) {
1377  return true;
1378  }
1379  // Consider level.
1380  std::stringstream s;
1381  s << recruit_type->level();
1382  if (s.str() == type) {
1383  return true;
1384  }
1385  return false;
1386 }
1387 
1388 /**
1389  * For Configuration / Aspect "recruitment-instructions"
1390  * Checks if a given recruit-type matches one of the given types.
1391  */
1393  const std::vector<std::string>& types) const {
1394  // If no type is specified, all recruits are okay.
1395  if (types.empty()) {
1396  return true;
1397  }
1398  for (const std::string& type : types) {
1399  if (recruit_matches_type(recruit, type)) {
1400  return true;
1401  }
1402  }
1403  return false;
1404 }
1405 
1406 /**
1407  * For Configuration / Aspect "recruitment-instructions"
1408  */
1410  assert(job);
1411  if ((*job)["blocker"].to_bool(true)) {
1412  LOG_AI_RECRUITMENT << "Canceling job.\n";
1413  job->clear();
1414  return true;
1415  } else {
1416  LOG_AI_RECRUITMENT << "Aborting recruitment.\n";
1417  return false;
1418  }
1419 }
1420 
1421 /**
1422  * For Aspect "recruitment_save_gold".
1423  * Guess the income over the next turns.
1424  * This doesn't need to be exact. In the end we are just interested if this value is
1425  * positive or negative.
1426  */
1429  const size_t own_villages = team.villages().size();
1430  const double village_gain = get_estimated_village_gain();
1431  const double unit_gain = get_estimated_unit_gain();
1432 
1433  double total_income = 0;
1434  for (int i = 1; i <= turns; ++i) {
1435  double income = (own_villages + village_gain * i) * game_config::village_income;
1436  double upkeep = resources::gameboard->side_upkeep(get_side()) + unit_gain * i -
1437  (own_villages + village_gain * i) * game_config::village_support;
1438  double resulting_income = team.base_income() + income - std::max(0., upkeep);
1439  total_income += resulting_income;
1440  }
1441  return total_income;
1442 }
1443 
1444 /**
1445  * For Aspect "recruitment_save_gold".
1446  * Guess how many units we will gain / loose over the next turns per turn.
1447  */
1449  return - own_units_in_combat_counter_ / 3.;
1450 }
1451 
1452 /**
1453  * For Aspect "recruitment_save_gold".
1454  * Guess how many villages we will gain over the next turns per turn.
1455  */
1457  const gamemap& map = resources::gameboard->map();
1458  int neutral_villages = 0;
1459  for (const map_location& village : map.villages()) {
1460  if (resources::gameboard->village_owner(village) == -1) {
1461  ++neutral_villages;
1462  }
1463  }
1464  return (neutral_villages / resources::gameboard->teams().size()) / 4.;
1465 }
1466 
1467 /**
1468  * For Aspect "recruitment_save_gold".
1469  * Returns our_total_unit_costs / enemy_total_unit_costs.
1470  */
1473  double own_total_value = 0.;
1474  double team_total_value = 0.;
1475  double enemy_total_value = 0.;
1476  for (const unit& unit : units) {
1477  if (unit.incapacitated() || unit.total_movement() <= 0 || unit.can_recruit()) {
1478  continue;
1479  }
1480  double value = unit.cost() *
1481  static_cast<double>(unit.hitpoints()) / static_cast<double>(unit.max_hitpoints());
1482  if (current_team().is_enemy(unit.side())) {
1483  enemy_total_value += value;
1484  } else {
1485  team_total_value += value;
1486  if (unit.side() == current_team().side()) {
1487  own_total_value += value;
1488  }
1489  }
1490  }
1491  int allies_count = 0;
1492  for (const team& team : resources::gameboard->teams()) {
1493  if (!current_team().is_enemy(team.side())) {
1494  ++allies_count;
1495  }
1496  }
1497  // If only the leader is left, the values could be 0.
1498  // Catch those cases and return something reasonable.
1499  if ((own_total_value == 0. || team_total_value == 0) && enemy_total_value == 0.) {
1500  return 0.; // do recruit
1501  } else if (enemy_total_value == 0.) {
1502  return 999.; // save money
1503  }
1504 
1505  // We calculate two ratios: One for the team and one for just our self.
1506  // Then we return the minimum.
1507  // This prevents cases where side1 will recruit until the save_gold begin threshold
1508  // is reached, and side2 won't recruit anything. (assuming side1 and side2 are allied)
1509  double own_ratio = (own_total_value / enemy_total_value) * allies_count;
1510  double team_ratio = team_total_value / enemy_total_value;
1511  return std::min<double>(own_ratio, team_ratio);
1512 }
1513 
1514 /**
1515  * For Aspect "recruitment_save_gold".
1516  * Main method.
1517  */
1520  return;
1521  }
1522  // Retrieve from aspect.
1523  int spend_all_gold = get_recruitment_save_gold()["spend_all_gold"].to_int(-1);
1524  if (spend_all_gold > 0 && current_team().gold() >= spend_all_gold) {
1526  LOG_AI_RECRUITMENT << "Changed state_ to SPEND_ALL_GOLD. \n";
1527  return;
1528  }
1529  double ratio = get_unit_ratio();
1530  double income_estimation = 1.;
1531  if (!get_recruitment_save_gold()["save_on_negative_income"].to_bool(false)) {
1532  income_estimation = get_estimated_income(SAVE_GOLD_FORECAST_TURNS);
1533  }
1534  LOG_AI_RECRUITMENT << "Ratio is " << ratio << "\n";
1535  LOG_AI_RECRUITMENT << "Estimated income is " << income_estimation << "\n";
1536 
1537  // Retrieve from aspect.
1538  double save_gold_begin = get_recruitment_save_gold()["begin"].to_double(1.5);
1539  double save_gold_end = get_recruitment_save_gold()["end"].to_double(1.1);
1540 
1541  if (state_ == NORMAL && ratio > save_gold_begin && income_estimation > 0) {
1542  state_ = SAVE_GOLD;
1543  LOG_AI_RECRUITMENT << "Changed state to SAVE_GOLD.\n";
1544  } else if (state_ == SAVE_GOLD && ratio < save_gold_end) {
1545  state_ = NORMAL;
1546  LOG_AI_RECRUITMENT << "Changed state to NORMAL.\n";
1547  }
1548 }
1549 
1550 /**
1551  * Will add a random value between 0 and "recruitment_randomness"
1552  * to all recruits
1553  */
1554 void recruitment::do_randomness(std::vector<data>* leader_data) const {
1555  if (!leader_data) {
1556  return;
1557  }
1558  for (data& data : *leader_data) {
1559  for (score_map::value_type& entry : data.scores) {
1560  double& score = entry.second;
1562  }
1563  }
1564 }
1565 
1566 /**
1567  * Will give a penalty to similar units. Similar units are units in one advancement tree.
1568  * Example (Archer can advance to Ranger):
1569  * before after
1570  * Elvish Fighter: 50 50
1571  * Elvish Archer: 50 25
1572  * Elvish Ranger: 50 25
1573  */
1574 void recruitment::do_similarity_penalty(std::vector<data>* leader_data) const {
1575  if (!leader_data) {
1576  return;
1577  }
1578  for (data& data : *leader_data) {
1579  // First we count how many similarities each recruit have to other ones (in a map).
1580  // Some examples:
1581  // If unit A and unit B have nothing to do with each other, they have similarity = 0.
1582  // If A advances to B both have similarity = 1.
1583  // If A advances to B and B to C, A, B and C have similarity = 2.
1584  // If A advances to B or C, A have similarity = 2. B and C have similarity = 1.
1585  typedef std::map<std::string, int> similarity_map;
1586  similarity_map similarities;
1587  for (const score_map::value_type& entry : data.scores) {
1588  const std::string& recruit = entry.first;
1589  const unit_type* recruit_type = unit_types.find(recruit);
1590  if (!recruit_type) {
1591  continue;
1592  }
1593  for (const std::string& advanced_type : recruit_type->advancement_tree()) {
1594  if (data.scores.count(advanced_type) != 0) {
1595  ++similarities[recruit];
1596  ++similarities[advanced_type];
1597  }
1598  }
1599  }
1600  // Now we divide each score by similarity + 1.
1601  for (score_map::value_type& entry : data.scores) {
1602  const std::string& recruit = entry.first;
1603  double& score = entry.second;
1604  score /= (similarities[recruit] + 1);
1605  }
1606  }
1607 }
1608 
1609 /**
1610  * Called at the beginning and whenever the recruitment list changes.
1611  */
1613  std::map<size_t, int>::const_iterator it = cheapest_unit_costs_.find(leader->underlying_id());
1614  if (it != cheapest_unit_costs_.end()) {
1615  return it->second;
1616  }
1617 
1618  int cheapest_cost = 999999;
1619 
1620  // team recruits
1621  for (const std::string& recruit : current_team().recruits()) {
1622  const unit_type* const info = unit_types.find(recruit);
1623  if (!info) {
1624  continue;
1625  }
1626  if (info->cost() < cheapest_cost) {
1627  cheapest_cost = info->cost();
1628  }
1629  }
1630  // extra recruits
1631  for (const std::string& recruit : leader->recruits()) {
1632  const unit_type* const info = unit_types.find(recruit);
1633  if (!info) {
1634  continue;
1635  }
1636  if (info->cost() < cheapest_cost) {
1637  cheapest_cost = info->cost();
1638  }
1639  }
1640  // Consider recall costs.
1641  if (!current_team().recall_list().empty() && current_team().recall_cost() < cheapest_cost) {
1642  cheapest_cost = current_team().recall_cost();
1643  }
1644  LOG_AI_RECRUITMENT << "Cheapest unit cost updated to " << cheapest_cost << ".\n";
1645  cheapest_unit_costs_[leader->underlying_id()] = cheapest_cost;
1646  return cheapest_cost;
1647 }
1648 
1649 /**
1650  * For Aspect "recruitment_more"
1651  */
1652 void recruitment::handle_recruitment_more(std::vector<data>* leader_data) const {
1653  if (!leader_data) {
1654  return;
1655  }
1656  const std::vector<std::string> aspect = get_recruitment_more();
1657  for (const std::string& type : aspect) {
1658  for (data& data : *leader_data) {
1659  for (score_map::value_type& entry : data.scores) {
1660  const std::string& recruit = entry.first;
1661  double& score = entry.second;
1662  if (recruit_matches_type(recruit, type)) {
1663  score += 25.;
1664  }
1665  }
1666  }
1667  }
1668 }
1669 
1670 /**
1671  * Helper function.
1672  * Returns true if there is a enemy within the radius.
1673  */
1674 bool recruitment::is_enemy_in_radius(const map_location& loc, int radius) const {
1676  std::vector<map_location> surrounding;
1677  get_tiles_in_radius(loc, radius, surrounding);
1678  if (surrounding.empty()) {
1679  return false;
1680  }
1681  for (const map_location& l : surrounding) {
1682  const unit_map::const_iterator& enemy_it = units.find(l);
1683  if(enemy_it == units.end()) {
1684  continue;
1685  }
1686  if (!current_team().is_enemy(enemy_it->side()) || enemy_it->incapacitated()) {
1687  continue;
1688  }
1689  return true;
1690  }
1691  return false;
1692 }
1693 
1694 /*
1695  * Helper Function.
1696  * Counts own units on the map and saves the result
1697  * in member own_units_count_
1698  */
1700  own_units_count_.clear();
1701  total_own_units_ = 0;
1703  for (const unit& unit : units) {
1704  if (unit.side() != get_side() || unit.can_recruit() ||
1705  unit.incapacitated() || unit.total_movement() <= 0) {
1706  continue;
1707  }
1709  ++total_own_units_;
1710  }
1711 }
1712 
1713 /**
1714  * This function will use the aspect villages_per_scout to decide how many
1715  * scouts we want to recruit.
1716  */
1718  scouts_wanted_ = 0;
1719  if (get_villages_per_scout() == 0) {
1720  return;
1721  }
1722  int neutral_villages = 0;
1723  // We recruit the initial allocation of scouts
1724  // based on how many neutral villages there are.
1725  for (const map_location& village : resources::gameboard->map().villages()) {
1726  if (resources::gameboard->village_owner(village) == -1) {
1727  ++neutral_villages;
1728  }
1729  }
1730  double our_share = static_cast<double>(neutral_villages) / resources::gameboard->teams().size();
1731 
1732  // The villages per scout is for a two-side battle,
1733  // accounting for all neutral villages on the map.
1734  // We only look at our share of villages, so we halve it,
1735  // making us get twice as many scouts.
1736  double villages_per_scout = (VILLAGE_PER_SCOUT_MULTIPLICATOR * get_villages_per_scout()) / 2;
1737 
1738  scouts_wanted_ = (villages_per_scout > 0) ? round_double(our_share / villages_per_scout) : 0;
1739 
1740  if (scouts_wanted_ == 0) {
1741  return;
1742  }
1743 
1744  // Subtract already recruited scouts.
1745  for (const count_map::value_type& entry : own_units_count_) {
1746  const std::string& unit_type = entry.first;
1747  const int count = entry.second;
1748  if (recruit_matches_type(unit_type, "scout")) {
1749  scouts_wanted_ -= count;
1750  }
1751  }
1752 }
1753 
1754 /**
1755  * Observer Code
1756  */
1758  : recruit_list_changed_(false), gamestate_changed_(0) {
1761 }
1762 
1764  const std::string& event) {
1765  if (event == "ai_recruit_list_changed") {
1766  LOG_AI_RECRUITMENT << "Recruitment List is not valid anymore.\n";
1767  set_recruit_list_changed(true);
1768  } else {
1769  ++gamestate_changed_;
1770  }
1771 }
1772 
1776 }
1777 
1779  return recruit_list_changed_;
1780 }
1781 
1783  recruit_list_changed_ = changed;
1784 }
1785 
1787  return gamestate_changed_;
1788 }
1789 
1791  gamestate_changed_ = 0;
1792 }
1793 
1795  : standard_aspect<config>(context, cfg, id)
1796 {
1797  config parsed_cfg(cfg.has_child("value") ? cfg.child("value") : cfg);
1798  // First, transform simplified tags into [recruit] tags.
1799  for (config pattern : parsed_cfg.child_range("pattern")) {
1800  parsed_cfg["pattern"] = true;
1801  parsed_cfg.add_child("recruit", std::move(pattern));
1802  }
1803  for (config total : parsed_cfg.child_range("total")) {
1804  parsed_cfg["total"] = true;
1805  parsed_cfg.add_child("recruit", std::move(total));
1806  }
1807  parsed_cfg.clear_children("pattern", "total");
1808  // Then, if there's no [recruit], add one.
1809  if (!parsed_cfg.has_child("recruit")) {
1810  parsed_cfg.add_child("recruit", config {"importance", 0});
1811  }
1812  // Finally, populate our lists
1813  for (config job : parsed_cfg.child_range("recruit")) {
1814  create_job(jobs_, job);
1815  }
1816  for (config lim : parsed_cfg.child_range("limit")) {
1817  create_limit(limits_, lim);
1818  }
1819  std::function<void(std::vector<std::shared_ptr<recruit_job> >&, const config&)> factory_jobs =
1820  std::bind(&recruitment_aspect::create_job,*this,_1,_2);
1821  std::function<void(std::vector<std::shared_ptr<recruit_limit> >&, const config&)> factory_limits =
1822  std::bind(&recruitment_aspect::create_limit,*this,_1,_2);
1823  register_vector_property(property_handlers(), "recruit", jobs_, factory_jobs);
1824  register_vector_property(property_handlers(), "limit", limits_, factory_limits);
1825 }
1826 
1828  config cfg;
1829  for (const std::shared_ptr<recruit_job>& job : jobs_) {
1830  cfg.add_child("recruit", job->to_config());
1831  }
1832  for (const std::shared_ptr<recruit_limit>& lim : limits_) {
1833  cfg.add_child("limit", lim->to_config());
1834  }
1835  *this->value_ = cfg;
1836  this->valid_ = true;
1837 }
1838 
1839 void recruitment_aspect::create_job(std::vector<std::shared_ptr<recruit_job> > &jobs, const config &job) {
1840  std::shared_ptr<recruit_job> job_ptr(new recruit_job(
1841  utils::split(job["type"]),
1842  job["leader_id"], job["id"],
1843  job["number"].to_int(-1), job["importance"].to_int(1),
1844  job["total"].to_bool(false),
1845  job["blocker"].to_bool(true),
1846  job["pattern"].to_bool(true)
1847  ));
1848  jobs.push_back(job_ptr);
1849 }
1850 
1851 void recruitment_aspect::create_limit(std::vector<std::shared_ptr<recruit_limit> > &limits, const config &lim) {
1852  std::shared_ptr<recruit_limit> lim_ptr(new recruit_limit(
1853  utils::split(lim["type"]),
1854  lim["id"],
1855  lim["max"].to_int(0)
1856  ));
1857  limits.push_back(lim_ptr);
1858 }
1859 
1860 } // namespace default_recruitment
1861 } // namespace ai
std::shared_ptr< action_result > action_result_ptr
Definition: game_info.hpp:88
std::string to_string() const
boost::intrusive_ptr< const unit > unit_const_ptr
Definition: ptr.hpp:30
bool recall_unit(const std::string &id, team &current_team, const map_location &loc, const map_location &from, map_location::DIRECTION facing, bool show, bool use_undo)
Recalls the unit with the indicated ID for the provided team.
Definition: create.cpp:736
const double * get_cached_combat_value(const std::string &a, const std::string &b, double a_defense, double b_defense)
For Combat Analysis.
config & child(config_key_type key, int n=0)
Returns the nth child with the given key, or a reference to an invalid config if there is none...
Definition: config.cpp:419
::tod_manager * tod_manager
Definition: resources.cpp:30
virtual int get_villages_per_scout() const override
Definition: contexts.hpp:881
attack_simulation(const unit_type *attacker, const unit_type *defender, double attacker_defense, double defender_defense, const_attack_ptr att_weapon, const_attack_ptr def_weapon, int average_lawful_bonus)
int total_movement() const
The maximum moves this unit has.
Definition: unit.hpp:1057
std::vector< unit_iterator > find_leaders(int side)
Definition: map.cpp:357
virtual const unit_map & units() const
Definition: game_board.hpp:97
unit_iterator end()
Definition: map.hpp:415
std::vector< char_t > string
std::map< size_t, int > cheapest_unit_costs_
bool incapacitated() const
Check if the unit has been petrified.
Definition: unit.hpp:763
bool recruit_matches_job(const std::string &recruit, const config *job) const
For Configuration / Aspect "recruitment-instructions" Checks if a given recruit-type is specified in ...
int village_support
Definition: game_config.cpp:72
void update_average_local_cost()
For Map Analysis.
This class represents a single unit of a specific type.
Definition: unit.hpp:100
bool remove_job_if_no_blocker(config *job)
For Configuration / Aspect "recruitment-instructions".
map_location find_vacant_castle(const unit &leader)
Wrapper for find_vacant_tile() when looking for a vacant castle tile near a leader.
Definition: pathfind.cpp:119
std::set< std::string > advancement_tree() const
Get the advancement tree.
Definition: types.cpp:667
bool better_result(const attack_simulation *other, bool for_defender)
virtual void execute()
Execute the candidate action.
#define ERR_AI_RECRUITMENT
Definition: recruitment.cpp:51
double get_avg_hp_of_combatant(bool attacker) const
Various functions that implement attacks and attack calculations.
const std::set< std::string > & recruits() const
Definition: team.hpp:222
virtual const config get_recruitment_save_gold() const override
Definition: contexts.hpp:845
#define LOG_AI_RECRUITMENT
Definition: recruitment.cpp:50
Add a special kind of assert to validate whether the input from WML doesn't contain any problems that...
static bool better_combat(const combatant &us_a, const combatant &them_a, const combatant &us_b, const combatant &them_b, double harm_weight)
Definition: attack.cpp:495
const std::string get_random_pattern_type_if_exists(const data &leader_data, const config *job) const
For Configuration / Aspect "recruitment-instructions" If the flag pattern is set, this method returns...
bool limit_ok(const std::string &recruit) const
For Configuration / Aspect "recruitment-instructions" Checks if a recruit-type can be recruited accor...
int cost(const t_translation::terrain_code &terrain, bool slowed=false) const
Returns the cost associated with the given terrain.
Definition: movetype.hpp:107
logger & info()
Definition: log.cpp:91
void handle_recruitment_more(std::vector< data > *leader_data) const
For Aspect "recruitment_more".
#define a
Managing the AI-Game interaction - AI actions and their results.
virtual int get_recruitment_randomness() const override
Definition: contexts.hpp:839
game_display * screen
Definition: resources.cpp:27
void get_tiles_in_radius(const map_location &center, const int radius, std::vector< map_location > &result)
Function that will add to result all locations within radius tiles of center (excluding center itself...
Definition: pathutils.cpp:54
const movetype & movement_type() const
Definition: types.hpp:175
property_handler_map & property_handlers()
Definition: component.cpp:132
const_attack_itors attacks() const
Definition: types.cpp:492
double get_score() const
Get the usual score of the candidate action without re-evaluation.
Definition: rca.cpp:72
bool is_enemy(int n) const
Definition: team.hpp:241
child_itors child_range(config_key_type key)
Definition: config.cpp:362
double get_random_double()
This helper method returns a floating-point number in the range [0,1[.
Definition: random.cpp:108
std::map< map_location, double > average_local_cost_
void update_important_hexes()
For Map Analysis.
std::vector< std::shared_ptr< recruit_job > > jobs_
A terrain string which is converted to a terrain is a string with 1 or 2 layers the layers are separa...
Definition: translation.hpp:55
data * get_best_leader_from_ratio_scores(std::vector< data > &leader_data, const config *job) const
A helper function for execute().
unit_type_data unit_types
Definition: types.cpp:1455
void do_randomness(std::vector< data > *leader_data) const
Will add a random value between 0 and "recruitment_randomness" to all recruits.
void update_scouts_wanted()
This function will use the aspect villages_per_scout to decide how many scouts we want to recruit...
std::shared_ptr< recruit_result > recruit_result_ptr
Definition: game_info.hpp:93
boost::iterator_range< boost::indirect_iterator< attack_list::const_iterator >> const_attack_itors
int get_cheapest_unit_cost_for_leader(const unit_map::const_iterator &leader)
Called at the beginning and whenever the recruitment list changes.
void clear()
Definition: config.cpp:809
void add_unit(const unit &u, bool use_max_moves=true)
Adds a units cost map to cost_map (increments the elements in cost_map)
Definition: pathfind.cpp:893
const std::string & type_id() const
The id of this unit's type.
Definition: unit.hpp:267
std::shared_ptr< config > value_
Definition: aspect.hpp:188
const std::string get_best_recruit_from_scores(const data &leader_data, const config *job)
A helper function for execute().
-file sdl_utils.hpp
int level() const
Definition: types.hpp:151
double get_estimated_unit_gain() const
For Aspect "recruitment_save_gold".
recruit_situation_change_observer recruit_situation_change_observer_
int cost() const
How much gold is required to recruit this unit.
Definition: unit.hpp:502
std::vector< std::string > split(const std::string &val, const char c, const int flags)
Splits a (comma-)separated string into a vector of pieces.
static void register_vector_property(property_handler_map &property_handlers, const std::string &property, std::vector< std::shared_ptr< X >> &values, std::function< void(std::vector< std::shared_ptr< X >> &, const config &)> construction_factory)
double get_average_cost_at(int x, int y) const
Accessor for the costs.
Definition: pathfind.cpp:967
A single unit type that the player may recruit.
Definition: types.hpp:43
std::set< map_location > important_hexes_
double compare_unit_types(const std::string &a, const std::string &b)
For Combat Analysis.
const battle_context_unit_stats attacker_stats
virtual const std::vector< std::string > get_recruitment_more() const override
Definition: contexts.hpp:827
#define b
double get_estimated_village_gain() const
For Aspect "recruitment_save_gold".
int side_upkeep(int side_num) const
bool recruit_matches_type(const std::string &recruit, const std::string &type) const
For Configuration / Aspect "recruitment-instructions" Checks if a given recruit-type matches one atom...
Object which defines a time of day with associated bonuses, image, sounds etc.
Definition: time_of_day.hpp:57
double get_unit_ratio() const
For Aspect "recruitment_save_gold".
This class stores all the data for a single 'side' (in game nomenclature).
Definition: team.hpp:44
A small explanation about what's going on here: Each action has access to two game_info objects First...
Definition: actions.cpp:58
static void remove_gamestate_observer(events::observer *event_observer)
Removes an observer of game events except ai_user_interact event and ai_sync_network event...
Definition: manager.cpp:357
team & get_team(int i)
Definition: game_board.hpp:94
int recall_cost() const
Definition: team.hpp:192
std::shared_ptr< recall_result > recall_result_ptr
Definition: game_info.hpp:92
double get_average_defense(const std::string &unit_type) const
For Map Analysis.
void create_limit(std::vector< std::shared_ptr< recruit_limit > > &limits, const config &lim)
void update_average_lawful_bonus()
Calculates a average lawful bonus, so Combat Analysis will work better in caves and custom time of da...
const map_location & get_location() const
The current map location this unit is at.
Definition: unit.hpp:1141
const terrain_label * set_label(const map_location &loc, const t_string &text, const int creator=-1, const std::string &team="", const color_t color=font::NORMAL_COLOR, const bool visible_in_fog=true, const bool visible_in_shroud=false, const bool immutable=false, const std::string &category="", const t_string &tooltip="")
Definition: label.cpp:146
void clear_children(T...keys)
Definition: config.hpp:510
virtual recall_result_ptr check_recall_action(const std::string &id, const map_location &where=map_location::null_location(), const map_location &from=map_location::null_location()) override
Definition: contexts.hpp:562
filter_context * filter_con
Definition: resources.cpp:23
int hitpoints() const
The current number of hitpoints this unit has.
Definition: unit.hpp:393
void integrate_recruitment_pattern_in_recruitment_instructions()
For Configuration / Aspect "recruitment_pattern" Converts the (old) recruitment_pattern into a recrui...
int round_double(double d)
Definition: math.hpp:65
void create_job(std::vector< std::shared_ptr< recruit_job > > &jobs, const config &job)
int base_income() const
Definition: team.hpp:190
int w() const
Effective map width.
Definition: map.hpp:90
game_board * gameboard
Definition: resources.cpp:20
virtual double get_recruitment_diversity() const override
Definition: contexts.hpp:815
config * get_most_important_job()
For Configuration / Aspect "recruitment-instructions" We call a [recruit] tag a "job".
virtual config to_config() const
serialize
Definition: rca.cpp:88
Encapsulates the map of the game.
Definition: map.hpp:34
int max_hitpoints() const
The max number of hitpoints this unit can have.
Definition: unit.hpp:399
void fight(combatant &opponent, bool levelup_considered=true)
Simulate a fight! Can be called multiple times for cumulative calculations.
bool is_enemy_in_radius(const map_location &loc, int radius) const
Helper function.
virtual const move_map & get_enemy_dstsrc() const override
Definition: contexts.hpp:706
Managing the AIs lifecycle - headers.
virtual const std::vector< std::string > get_recruitment_pattern() const override
Definition: contexts.hpp:833
Structure describing the statistics of a unit involved in the battle.
Definition: attack.hpp:47
Recruitment Engine by flix See http://wiki.wesnoth.org/AI_Recruitment.
static const map_location & null_location()
Definition: location.hpp:220
void do_combat_analysis(std::vector< data > *leader_data)
Combat Analysis.
const std::set< map_location > & villages() const
Definition: team.hpp:183
static const ::config * terrain
The terrain used to create the cache.
Definition: minimap.cpp:133
virtual recruit_result_ptr check_recruit_action(const std::string &unit_name, const map_location &where=map_location::null_location(), const map_location &from=map_location::null_location()) override
Definition: contexts.hpp:569
terrain_costs & get_movement()
Definition: movetype.hpp:179
bool recruit_matches_types(const std::string &recruit, const std::vector< std::string > &types) const
For Configuration / Aspect "recruitment-instructions" Checks if a given recruit-type matches one of t...
config to_config() const
serialize
action_result_ptr execute_recall(const std::string &id, data &leader_data)
A helper function for execute().
int cost() const
Definition: types.hpp:159
bool has_child(config_key_type key) const
Determine whether a config has a child or not.
Definition: config.cpp:412
Encapsulates the map of the game.
Definition: location.hpp:40
All combat-related info.
const std::string & usage() const
Definition: types.hpp:162
std::stringstream & wml_error()
Use this logger to send errors due to deprecated WML.
Definition: log.cpp:269
int h() const
Effective map height.
Definition: map.hpp:93
virtual const team & current_team() const override
Definition: contexts.hpp:536
const std::set< std::string > movetype::effects defense
Definition: movetype.cpp:782
int village_income
Definition: game_config.cpp:71
void compare_cost_maps_and_update_important_hexes(const pathfind::full_cost_map &my_cost_map, const pathfind::full_cost_map &enemy_cost_map)
For Map Analysis Computes from our cost map and the combined cost map of all enemies the important he...
static map_location::DIRECTION s
static tcache cache
Definition: minimap.cpp:137
int turn() const
Structure which uses find_routes() to build a cost map This maps each hex to a the movements a unit w...
Definition: pathfind.hpp:266
virtual double power_projection(const map_location &loc, const move_map &dstsrc) const override
Function which finds how much 'power' a side can attack a certain location with.
Definition: contexts.hpp:803
size_t i
Definition: function.cpp:933
action_result_ptr execute_recruit(const std::string &type, data &leader_data)
A helper function for execute().
static void add_recruit_list_changed_observer(events::observer *event_observer)
Adds an observer of 'ai_recruit_list_changed' event.
Definition: manager.cpp:381
virtual side_number get_side() const override
Get the side number.
Definition: contexts.hpp:477
bool can_recruit() const
Whether this unit can recruit other units - ie, are they a leader unit.
Definition: unit.hpp:481
std::vector< std::shared_ptr< recruit_limit > > limits_
int get_random_int(int min, int max)
This helper method provides a random int from the underlying generator, using results of next_random...
Definition: random.hpp:51
int side() const
Definition: team.hpp:187
rng * generator
This generator is automatically synced during synced context.
Definition: random.cpp:57
double get_estimated_income(int turns) const
For Aspect "recruitment_save_gold".
config & add_child(config_key_type key)
Definition: config.cpp:475
int hitpoints() const
Definition: types.hpp:148
bool valid_
Definition: aspect.hpp:99
virtual const gamemap & map() const
Definition: game_board.hpp:96
bool on_board(const map_location &loc) const
Tell if a location is on the map.
Definition: map.cpp:369
int turns()
Definition: game.cpp:560
bool find(E event, F functor)
Tests whether an event handler is available.
recruitment(rca_context &context, const config &cfg)
const std::string * get_appropriate_recall(const std::string &type, const data &leader_data) const
A helper function for execute().
static void add_gamestate_observer(events::observer *event_observer)
Adds observer of game events except ai_user_interact event and ai_sync_network event.
Definition: manager.cpp:350
void simulate_attack(const unit_type *const attacker, const unit_type *const defender, double attacker_defense, double defender_defense, double *damage_to_attacker, double *damage_to_defender) const
For Combat Analysis.
A variable-expanding proxy for the config class.
Definition: variable.hpp:42
static lg::log_domain log_ai_recruitment("ai/recruitment")
Standard logging facilities (interface).
recruitment_aspect(readonly_context &context, const config &cfg, const std::string &id)
virtual const config get_recruitment_instructions() const override
Definition: contexts.hpp:821
static const double BAD_SCORE
Definition: rca.hpp:37
Container associating units to locations.
Definition: map.hpp:99
const unit_type * find(const std::string &key, unit_type::BUILD_STATUS status=unit_type::FULL) const
Finds a unit_type by its id() and makes sure it is built to the specified level.
Definition: types.cpp:1273
double average_hp(unsigned int healing=0) const
What's the average hp (weighted average of hp_dist).
map_labels & labels()
Definition: display.cpp:2534
score_map get_normalized_scores() const
Definition: recruitment.hpp:75
std::vector< game_tip > shuffle(const std::vector< game_tip > &tips)
Shuffles the tips.
Definition: tips.cpp:46
unit_iterator find(size_t id)
Definition: map.cpp:311
int get_cost_at(int x, int y) const
Accessor for the costs.
Definition: pathfind.cpp:956
void clear_all()
Definition: label.cpp:239
unit_map::const_iterator leader
Definition: recruitment.hpp:54
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:93
virtual double evaluate()
Evaluate the candidate action, resetting the internal state of the action.
std::shared_ptr< const attack_type > const_attack_ptr
Definition: ptr.hpp:37
void update_state()
For Aspect "recruitment_save_gold".
const pathfind::full_cost_map get_cost_map_of_side(int side) const
For Map Analysis.
const std::vector< map_location > & villages() const
Return a list of the locations of villages on the map.
Definition: map.hpp:157
This module contains various pathfinding functions and utilities.
void show_important_hexes() const
For Map Analysis.
static rng & default_instance()
Definition: random.cpp:70
const battle_context_unit_stats defender_stats
void do_similarity_penalty(std::vector< data > *leader_data) const
Will give a penalty to similar units.
static void remove_recruit_list_changed_observer(events::observer *event_observer)
Deletes an observer of 'ai_recruit_list_changed' event.
Definition: manager.cpp:393
std::string::const_iterator iterator
Definition: tokenizer.hpp:24
unit_map * units
Definition: resources.cpp:34
int side() const
The side this unit belongs to.
Definition: unit.hpp:244
int defense_modifier(const t_translation::terrain_code &terrain) const
Returns the defensive value of the indicated terrain.
Definition: movetype.hpp:207
std::set< std::string > recruits
Definition: recruitment.hpp:55
const std::string & id() const
The id for this unit_type.
Definition: types.hpp:140
double poisoned
Resulting chance we are poisoned.
candidate action framework
bool leader_matches_job(const data &leader_data, const config *job) const
For Configuration / Aspect "recruitment-instructions" Checks if a given leader is specified in the "l...