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