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