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