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