The Battle for Wesnoth  1.17.0-dev
recruitment.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2013 - 2018 by Felix Bauer
3  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
4 
5  This program is free software; you can redistribute it and/or modify
6  it under the terms of the GNU General Public License as published by
7  the Free Software Foundation; either version 2 of the License, or
8  (at your option) any later version.
9  This program is distributed in the hope that it will be useful,
10  but WITHOUT ANY WARRANTY.
11 
12  See the COPYING file for more details.
13  */
14 
15 /**
16  * @file
17  * Recruitment Engine by flix
18  * See https://wiki.wesnoth.org/AI_Recruitment
19  */
20 
22 
23 #include "ai/actions.hpp"
24 #include "ai/composite/rca.hpp"
25 #include "ai/manager.hpp"
26 #include "actions/attack.hpp"
27 #include "attack_prediction.hpp"
28 #include "display.hpp"
29 #include "filter_context.hpp"
30 #include "game_board.hpp"
31 #include "log.hpp"
32 #include "map/map.hpp"
33 #include "map/label.hpp"
34 #include "pathfind/pathfind.hpp"
35 #include "pathutils.hpp"
36 #include "random.hpp"
37 #include "resources.hpp"
38 #include "team.hpp"
39 #include "tod_manager.hpp"
40 #include "units/filter.hpp"
41 #include "units/map.hpp"
42 #include "units/types.hpp"
43 #include "units/unit.hpp"
44 #include "variable.hpp"
45 
46 #include <cmath>
47 
48 static lg::log_domain log_ai_recruitment("ai/recruitment");
49 #define LOG_AI_RECRUITMENT LOG_STREAM(info, log_ai_recruitment)
50 #define ERR_AI_RECRUITMENT LOG_STREAM(err, log_ai_recruitment)
51 
52 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:\n";
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\n";
209  LOG_AI_RECRUITMENT << "TURN: " << resources::tod_manager->turn() <<
210  " SIDE: " << current_team().side() << "\n";
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. \n";
229  continue;
230  }
231  if (!resources::gameboard->map().is_keep(keep)) {
232  LOG_AI_RECRUITMENT << "Leader " << leader->name() << " is not on keep. \n";
233  continue;
234  }
236  LOG_AI_RECRUITMENT << "Leader " << leader->name() << " has no free hexes \n";
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. \n";
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.\n";
297  }
298 
299  leader_data.push_back(data);
300  }
301 
302  if (leader_data.empty()) {
303  LOG_AI_RECRUITMENT << "No leader available for recruiting. \n";
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. \n";
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:\n";
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:\n";
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.\n";
374  break;
375  }
376  LOG_AI_RECRUITMENT << "Executing this job:\n" << *job << "\n";
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.\n";
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.\n";
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.\n";
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 << "\n";
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 << "\n";
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.\n";
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) {
464  recall_result = check_recall_action(id, map_location::null_location(),
465  leader_data.leader->get_location());
466  if (recall_result->is_ok()) {
467  recall_result->execute();
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) {
478  recruit_result = check_recruit_action(type, map_location::null_location(),
479  leader_data.leader->get_location());
480 
481  if (recruit_result->is_ok()) {
482  recruit_result->execute();
483  LOG_AI_RECRUITMENT << "Recruited " << type << "\n";
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() << "\n";
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 << "\n";
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 += 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::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) << "." << std::endl;
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  return value_of_a / value_of_b;
936  } else if (value_of_a < value_of_b) {
937  return -value_of_b / value_of_a;
938  } else {
939  return 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),
1104  attacker_combatant(attacker_stats),
1105  defender_combatant(defender_stats)
1106  {
1107  attacker_combatant.fight(defender_combatant);
1108  }
1109 
1110  bool better_result(const attack_simulation* other, bool for_defender) {
1111  assert(other);
1112  if (for_defender) {
1114  defender_combatant, attacker_combatant,
1115  other->defender_combatant, other->attacker_combatant, 0);
1116  } else {
1118  attacker_combatant, defender_combatant,
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
1136  avg_hp -= combatant.poisoned * game_config::poison_amount;
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()" << std::endl;
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.\n";
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.\n";
1435  job->clear();
1436  return true;
1437  } else {
1438  LOG_AI_RECRUITMENT << "Aborting recruitment.\n";
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  */
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. \n";
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 << "\n";
1557  LOG_AI_RECRUITMENT << "Estimated income is " << income_estimation << "\n";
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.\n";
1566  } else if (state_ == SAVE_GOLD && ratio < save_gold_end) {
1567  state_ = NORMAL;
1568  LOG_AI_RECRUITMENT << "Changed state to NORMAL.\n";
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 << ".\n";
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.\n";
1790  } else {
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.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
std::shared_ptr< action_result > action_result_ptr
Definition: game_info.hpp:78
int defense_modifier(const t_translation::terrain_code &terrain) const
Returns the defensive value of the indicated terrain.
Definition: movetype.hpp:291
const double * get_cached_combat_value(const std::string &a, const std::string &b, double a_defense, double b_defense)
For Combat Analysis.
double get_avg_hp_of_combatant(bool attacker) const
config & child(config_key_type key, int n=0)
Returns the nth child with the given key, or a reference to an invalid config if there is none...
Definition: config.cpp:418
::tod_manager * tod_manager
Definition: resources.cpp:29
virtual int get_villages_per_scout() const override
Definition: contexts.hpp:750
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)
std::vector< unit_iterator > find_leaders(int side)
Definition: map.cpp:355
unit_iterator end()
Definition: map.hpp:428
double get_average_defense(const std::string &unit_type) const
For Map Analysis.
static display * get_singleton()
Returns the display object if a display object exists.
Definition: display.hpp:91
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:1261
virtual const unit_map & units() const override
Definition: game_board.hpp:111
void clear_children(T... keys)
Definition: config.hpp:526
int village_support
Definition: game_config.cpp:55
void update_average_local_cost()
For Map Analysis.
This class represents a single unit of a specific type.
Definition: unit.hpp:120
const std::string & type_id() const
The id of this unit&#39;s type.
Definition: unit.cpp:1770
double get_estimated_unit_gain() const
For Aspect "recruitment_save_gold".
bool remove_job_if_no_blocker(config *job)
For Configuration / Aspect "recruitment-instructions".
map_location find_vacant_castle(const unit &leader)
Wrapper for find_vacant_tile() when looking for a vacant castle tile near a leader.
Definition: pathfind.cpp:117
bool better_result(const attack_simulation *other, bool for_defender)
static manager & get_singleton()
Definition: manager.hpp:143
virtual void execute()
Execute the candidate action.
#define ERR_AI_RECRUITMENT
Definition: recruitment.cpp:50
Various functions that implement attacks and attack calculations.
virtual const config get_recruitment_save_gold() const override
Definition: contexts.hpp:710
#define LOG_AI_RECRUITMENT
Definition: recruitment.cpp:49
static bool better_combat(const combatant &us_a, const combatant &them_a, const combatant &us_b, const combatant &them_b, double harm_weight)
Definition: attack.cpp:497
void create_job(std::vector< std::shared_ptr< recruit_job >> &jobs, const config &job)
int cost(const t_translation::terrain_code &terrain, bool slowed=false) const
Returns the cost associated with the given terrain.
Definition: movetype.hpp:68
logger & info()
Definition: log.cpp:88
#define a
Managing the AI-Game interaction - AI actions and their results.
int hitpoints() const
The current number of hitpoints this unit has.
Definition: unit.hpp:489
bool has_child(config_key_type key) const
Determine whether a config has a child or not.
Definition: config.cpp:410
virtual int get_recruitment_randomness() const override
Definition: contexts.hpp:705
void get_tiles_in_radius(const map_location &center, const int radius, std::vector< map_location > &result)
Function that will add to result all locations within radius tiles of center (excluding center itself...
Definition: pathutils.cpp:55
property_handler_map & property_handlers()
Definition: component.cpp:124
void create_limit(std::vector< std::shared_ptr< recruit_limit >> &limits, const config &lim)
child_itors child_range(config_key_type key)
Definition: config.cpp:360
double get_random_double()
This helper method returns a floating-point number in the range [0,1[.
Definition: random.cpp:111
std::map< map_location, double > average_local_cost_
double average_hp(unsigned int healing=0) const
What&#39;s the average hp (weighted average of hp_dist).
void update_important_hexes()
For Map Analysis.
bool recall_unit(const std::string &id, team &current_team, const map_location &loc, const map_location &from, map_location::DIRECTION facing, bool show, bool use_undo)
Recalls the unit with the indicated ID for the provided team.
Definition: create.cpp:736
virtual const gamemap & map() const override
Definition: game_board.hpp:101
std::vector< std::shared_ptr< recruit_job > > jobs_
A terrain string which is converted to a terrain is a string with 1 or 2 layers the layers are separa...
Definition: translation.hpp:49
unit_type_data unit_types
Definition: types.cpp:1480
STL namespace.
void update_scouts_wanted()
This function will use the aspect villages_per_scout to decide how many scouts we want to recruit...
std::set< std::string > advancement_tree() const
Get the advancement tree.
Definition: types.cpp:647
const std::string * get_appropriate_recall(const std::string &type, const data &leader_data) const
std::shared_ptr< recruit_result > recruit_result_ptr
Definition: game_info.hpp:83
int get_cheapest_unit_cost_for_leader(const unit_map::const_iterator &leader)
Called at the beginning and whenever the recruitment list changes.
void clear()
Definition: config.cpp:900
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:908
std::shared_ptr< config > value_
Definition: aspect.hpp:177
const std::string get_best_recruit_from_scores(const data &leader_data, const config *job)
A helper function for execute().
const std::string get_random_pattern_type_if_exists(const data &leader_data, const config *job) const
For Configuration / Aspect "recruitment-instructions" If the flag pattern is set, this method returns...
recruit_situation_change_observer recruit_situation_change_observer_
static void register_vector_property(property_handler_map &property_handlers, const std::string &property, std::vector< std::shared_ptr< X >> &values, std::function< void(std::vector< std::shared_ptr< X >> &, const config &)> construction_factory)
int cost() const
How much gold is required to recruit this unit.
Definition: unit.hpp:623
A single unit type that the player may recruit.
Definition: types.hpp:44
void do_similarity_penalty(std::vector< data > *leader_data) const
Will give a penalty to similar units.
std::set< map_location > important_hexes_
double compare_unit_types(const std::string &a, const std::string &b)
For Combat Analysis.
void show_important_hexes() const
For Map Analysis.
double get_average_cost_at(map_location loc) const
Accessor for the costs.
Definition: pathfind.cpp:988
const battle_context_unit_stats attacker_stats
virtual const std::vector< std::string > get_recruitment_more() const override
Definition: contexts.hpp:695
#define b
double get_estimated_income(int turns) const
For Aspect "recruitment_save_gold".
std::shared_ptr< const unit > unit_const_ptr
Definition: ptr.hpp:26
Object which defines a time of day with associated bonuses, image, sounds etc.
Definition: time_of_day.hpp:55
double get_unit_ratio() const
For Aspect "recruitment_save_gold".
This class stores all the data for a single &#39;side&#39; (in game nomenclature).
Definition: team.hpp:71
A small explanation about what&#39;s going on here: Each action has access to two game_info objects First...
Definition: actions.cpp:60
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:344
team & get_team(int i)
Definition: game_board.hpp:96
std::shared_ptr< recall_result > recall_result_ptr
Definition: game_info.hpp:82
int cost() const
Definition: types.hpp:174
void update_average_lawful_bonus()
Calculates a average lawful bonus, so Combat Analysis will work better in caves and custom time of da...
int w() const
Effective map width.
Definition: map.hpp:49
const movetype & movement_type() const
Definition: types.hpp:190
const terrain_label * set_label(const map_location &loc, const t_string &text, const int creator=-1, const std::string &team="", const color_t color=font::NORMAL_COLOR, const bool visible_in_fog=true, const bool visible_in_shroud=false, const bool immutable=false, const std::string &category="", const t_string &tooltip="")
Definition: label.cpp:146
void handle_recruitment_more(std::vector< data > *leader_data) const
For Aspect "recruitment_more".
void for_each_walkable_loc(const F &f) const
Definition: map.hpp:145
const terrain_costs & get_movement() const
Definition: movetype.hpp:269
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:479
bool is_allowed_unit(const unit &u) const
Flag indicating whether unit may be used by this candidate action.
Definition: rca.cpp:89
void integrate_recruitment_pattern_in_recruitment_instructions()
For Configuration / Aspect "recruitment_pattern" Converts the (old) recruitment_pattern into a recrui...
const std::string & usage() const
Definition: types.hpp:177
int side_upkeep(int side_num) const
const pathfind::full_cost_map get_cost_map_of_side(int side) const
For Map Analysis.
game_board * gameboard
Definition: resources.cpp:20
virtual double get_recruitment_diversity() const override
Definition: contexts.hpp:685
config * get_most_important_job()
For Configuration / Aspect "recruitment-instructions" We call a [recruit] tag a "job".
Encapsulates the map of the game.
Definition: map.hpp:170
score_map get_normalized_scores() const
Definition: recruitment.hpp:69
bool limit_ok(const std::string &recruit) const
For Configuration / Aspect "recruitment-instructions" Checks if a recruit-type can be recruited accor...
bool is_enemy(int n) const
Definition: team.hpp:254
void fight(combatant &opponent, bool levelup_considered=true)
Simulate a fight! Can be called multiple times for cumulative calculations.
virtual const move_map & get_enemy_dstsrc() const override
Definition: contexts.hpp:600
const std::string & id() const
The id for this unit_type.
Definition: types.hpp:143
Managing the AIs lifecycle - headers TODO: Refactor history handling and internal commands...
map_display and display: classes which take care of displaying the map and game-data on the screen...
virtual const std::vector< std::string > get_recruitment_pattern() const override
Definition: contexts.hpp:700
Structure describing the statistics of a unit involved in the battle.
Definition: attack.hpp:50
Recruitment Engine by flix See https://wiki.wesnoth.org/AI_Recruitment.
bool recruit_matches_types(const std::string &recruit, const std::vector< std::string > &types) const
For Configuration / Aspect "recruitment-instructions" Checks if a given recruit-type matches one of t...
double get_estimated_village_gain() const
For Aspect "recruitment_save_gold".
void do_combat_analysis(std::vector< data > *leader_data)
Combat Analysis.
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:485
void simulate_attack(const unit_type *const attacker, const unit_type *const defender, double attacker_defense, double defender_defense, double *damage_to_attacker, double *damage_to_defender) const
For Combat Analysis.
double recall_unit_value(const unit_const_ptr &recall_unit) const
A helper function for execute().
action_result_ptr execute_recall(const std::string &id, data &leader_data)
A helper function for execute().
int recall_cost() const
Definition: team.hpp:204
Encapsulates the map of the game.
Definition: location.hpp:37
std::string to_string() const
#define ERR_WML
Definition: recruitment.cpp:53
unit_iterator find(std::size_t id)
Definition: map.cpp:309
All combat-related info.
data * get_best_leader_from_ratio_scores(std::vector< data > &leader_data, const config *job) const
A helper function for execute().
std::size_t i
Definition: function.cpp:940
virtual const team & current_team() const override
Definition: contexts.hpp:454
int max_hitpoints() const
The max number of hitpoints this unit can have.
Definition: unit.hpp:495
bool recruit_matches_type(const std::string &recruit, const std::string &type) const
For Configuration / Aspect "recruitment-instructions" Checks if a given recruit-type matches one atom...
virtual config to_config() const
serialize
Definition: rca.cpp:102
int village_income
Definition: game_config.cpp:54
void compare_cost_maps_and_update_important_hexes(const pathfind::full_cost_map &my_cost_map, const pathfind::full_cost_map &enemy_cost_map)
For Map Analysis Computes from our cost map and the combined cost map of all enemies the important he...
static map_location::DIRECTION s
static tcache cache
Definition: minimap.cpp:123
Structure which uses find_routes() to build a cost map This maps each hex to a the movements a unit w...
Definition: pathfind.hpp:266
bool can_recruit() const
Whether this unit can recruit other units - ie, are they a leader unit.
Definition: unit.hpp:602
bool is_enemy_in_radius(const map_location &loc, int radius) const
Helper function.
virtual double power_projection(const map_location &loc, const move_map &dstsrc) const override
Function which finds how much &#39;power&#39; a side can attack a certain location with.
Definition: contexts.hpp:680
action_result_ptr execute_recruit(const std::string &type, data &leader_data)
A helper function for execute().
void add_recruit_list_changed_observer(events::observer *event_observer)
Adds an observer of &#39;ai_recruit_list_changed&#39; event.
Definition: manager.cpp:363
virtual side_number get_side() const override
Get the side number.
Definition: contexts.hpp:400
bool on_board(const map_location &loc) const
Tell if a location is on the map.
Definition: map.cpp:384
std::vector< std::shared_ptr< recruit_limit > > limits_
int level() const
Definition: types.hpp:166
int get_random_int(int min, int max)
This helper method provides a random int from the underlying generator, using results of next_random...
Definition: random.hpp:51
const bool & debug
int hitpoints() const
Definition: types.hpp:163
bool recruit_matches_job(const std::string &recruit, const config *job) const
For Configuration / Aspect "recruitment-instructions" Checks if a given recruit-type is specified in ...
rng * generator
This generator is automatically synced during synced context.
Definition: random.cpp:60
config & add_child(config_key_type key)
Definition: config.cpp:504
bool valid_
Definition: aspect.hpp:85
int turns()
Definition: game.cpp:558
bool leader_matches_job(const data &leader_data, const config *job) const
For Configuration / Aspect "recruitment-instructions" Checks if a given leader is specified in the "l...
std::vector< std::string > split(const config_attribute_value &val)
recruitment(rca_context &context, const config &cfg)
const std::vector< map_location > & villages() const
Return a list of the locations of villages on the map.
Definition: map.hpp:236
const map_location & get_location() const
The current map location this unit is at.
Definition: unit.hpp:1345
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:338
A variable-expanding proxy for the config class.
Definition: variable.hpp:44
static lg::log_domain log_ai_recruitment("ai/recruitment")
Standard logging facilities (interface).
static const map_location & null_location()
Definition: location.hpp:80
recruitment_aspect(readonly_context &context, const config &cfg, const std::string &id)
virtual const config get_recruitment_instructions() const override
Definition: contexts.hpp:690
static const double BAD_SCORE
Definition: rca.hpp:32
Container associating units to locations.
Definition: map.hpp:97
bool incapacitated() const
Check if the unit has been petrified.
Definition: unit.hpp:893
int total_movement() const
The maximum moves this unit has.
Definition: unit.hpp:1254
config to_config() const
serialize
int base_income() const
Definition: team.hpp:202
map_labels & labels()
Definition: display.cpp:2535
int turn() const
static lg::log_domain log_wml("wml")
retval
Default window/dialog return values.
Definition: retval.hpp:28
int side() const
The side this unit belongs to.
Definition: unit.hpp:333
std::vector< game_tip > shuffle(const std::vector< game_tip > &tips)
Shuffles the tips.
Definition: tips.cpp:46
void clear_all()
Definition: label.cpp:239
unit_map::const_iterator leader
Definition: recruitment.hpp:48
const_attack_itors attacks() const
Definition: types.cpp:545
boost::iterator_range< boost::indirect_iterator< attack_list::const_iterator > > const_attack_itors
int side() const
Definition: team.hpp:199
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:59
virtual double evaluate()
Evaluate the candidate action, resetting the internal state of the action.
void do_randomness(std::vector< data > *leader_data) const
Will add a random value between 0 and "recruitment_randomness" to all recruits.
std::shared_ptr< const attack_type > const_attack_ptr
Definition: ptr.hpp:33
void update_state()
For Aspect "recruitment_save_gold".
double get_score() const
Get the usual score of the candidate action without re-evaluation.
Definition: rca.cpp:74
int h() const
Effective map height.
Definition: map.hpp:52
const std::set< map_location > & villages() const
Definition: team.hpp:195
This module contains various pathfinding functions and utilities.
static rng & default_instance()
Definition: random.cpp:73
const battle_context_unit_stats defender_stats
void remove_recruit_list_changed_observer(events::observer *event_observer)
Deletes an observer of &#39;ai_recruit_list_changed&#39; event.
Definition: manager.cpp:373
std::string::const_iterator iterator
Definition: tokenizer.hpp:24
std::set< std::string > recruits
Definition: recruitment.hpp:49
double poisoned
Resulting chance we are poisoned.
const std::set< std::string > & recruits() const
Definition: team.hpp:234
std::stringstream & log_to_chat()
Use this to show WML errors in the ingame chat.
Definition: log.cpp:288
candidate action framework
int get_cost_at(map_location loc) const
Accessor for the costs.
Definition: pathfind.cpp:977
std::map< std::size_t, int > cheapest_unit_costs_