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