The Battle for Wesnoth  1.17.14+dev
create.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2022
3  by David White <dave@whitevine.net>
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  * Recruiting, recalling.
19  */
20 
21 #include "actions/create.hpp"
22 
23 #include "actions/move.hpp"
24 #include "actions/undo.hpp"
25 #include "actions/vision.hpp"
26 
27 #include "config.hpp"
28 #include "display.hpp"
29 #include "filter_context.hpp"
30 #include "game_events/pump.hpp"
31 #include "game_state.hpp"
32 #include "preferences/game.hpp"
33 #include "game_data.hpp"
34 #include "gettext.hpp"
35 #include "log.hpp"
36 #include "map/map.hpp"
37 #include "pathfind/pathfind.hpp"
38 #include "recall_list_manager.hpp"
39 #include "replay.hpp"
40 #include "replay_helper.hpp"
41 #include "resources.hpp"
42 #include "statistics.hpp"
43 #include "synced_checkup.hpp"
44 #include "synced_context.hpp"
45 #include "team.hpp"
46 #include "units/unit.hpp"
47 #include "units/udisplay.hpp"
48 #include "units/filter.hpp"
49 #include "units/types.hpp"
50 #include "utils/general.hpp"
51 #include "variable.hpp"
52 #include "whiteboard/manager.hpp"
53 
54 static lg::log_domain log_engine("engine");
55 #define DBG_NG LOG_STREAM(debug, log_engine)
56 #define LOG_NG LOG_STREAM(info, log_engine)
57 #define ERR_NG LOG_STREAM(err, log_engine)
58 
59 namespace actions {
60 
61 const std::set<std::string> get_recruits(int side, const map_location &recruit_loc)
62 {
63  const team & current_team = resources::gameboard->get_team(side);
64 
65  LOG_NG << "getting recruit list for side " << side << " at location " << recruit_loc;
66 
67  std::set<std::string> local_result;
68  std::set<std::string> global_result;
70  u_end = resources::gameboard->units().end();
71 
72  bool leader_in_place = false;
73  bool allow_local = resources::gameboard->map().is_castle(recruit_loc);
74 
75 
76  // Check for a leader at recruit_loc (means we are recruiting from there,
77  // rather than to there).
78  unit_map::const_iterator find_it = resources::gameboard->units().find(recruit_loc);
79  if ( find_it != u_end ) {
80  if ( find_it->can_recruit() && find_it->side() == side &&
81  resources::gameboard->map().is_keep(recruit_loc) )
82  {
83  // We have been requested to get the recruit list for this
84  // particular leader.
85  leader_in_place = true;
86  local_result.insert(find_it->recruits().begin(),
87  find_it->recruits().end());
88  }
89  else if ( find_it->is_visible_to_team(current_team, false) )
90  {
91  // This hex is visibly occupied, so we cannot recruit here.
92  allow_local = false;
93  }
94  }
95 
96  if ( !leader_in_place ) {
97  // Check all leaders for their ability to recruit here.
98  for( ; u != u_end; ++u ) {
99  // Only consider leaders on this side.
100  if ( !(u->can_recruit() && u->side() == side) )
101  continue;
102 
103  // Check if the leader is on a connected keep.
104  if (allow_local && dynamic_cast<game_state&>(*resources::filter_con).can_recruit_on(*u, recruit_loc)) {
105  leader_in_place= true;
106  local_result.insert(u->recruits().begin(), u->recruits().end());
107  }
108  else if ( !leader_in_place )
109  global_result.insert(u->recruits().begin(), u->recruits().end());
110  }
111  }
112 
113  // Determine which result set to use.
114  std::set<std::string> & result = leader_in_place ? local_result : global_result;
115 
116  // Add the team-wide recruit list.
117  const std::set<std::string>& recruit_list = current_team.recruits();
118  result.insert(recruit_list.begin(), recruit_list.end());
119 
120  return result;
121 }
122 
123 
124 namespace { // Helpers for get_recalls()
125  /**
126  * Adds to @a result those units that @a leader (assumed a leader) can recall.
127  * If @a already_added is supplied, it contains the underlying IDs of units
128  * that can be skipped (because they are already in @a result), and the
129  * underlying ID of units added to @a result will be added to @a already_added.
130  */
131  void add_leader_filtered_recalls(const unit_const_ptr leader,
132  std::vector< unit_const_ptr > & result,
133  std::set<std::size_t> * already_added = nullptr)
134  {
135  const team& leader_team = resources::gameboard->get_team(leader->side());
136  const std::string& save_id = leader_team.save_id_or_number();
137 
138  const unit_filter ufilt(vconfig(leader->recall_filter()));
139 
140  for (const unit_const_ptr recall_unit_ptr : leader_team.recall_list())
141  {
142  const unit & recall_unit = *recall_unit_ptr;
143  // Do not add a unit twice.
144  std::size_t underlying_id = recall_unit.underlying_id();
145  if ( !already_added || already_added->count(underlying_id) == 0 )
146  {
147  // Only units that match the leader's recall filter are valid.
148  scoped_recall_unit this_unit("this_unit", save_id, leader_team.recall_list().find_index(recall_unit.id()));
149 
150  if ( ufilt(recall_unit, map_location::null_location()) )
151  {
152  result.push_back(recall_unit_ptr);
153  if ( already_added != nullptr )
154  already_added->insert(underlying_id);
155  }
156  }
157  }
158  }
159 }// anonymous namespace
160 
161 std::vector<unit_const_ptr > get_recalls(int side, const map_location &recall_loc)
162 {
163  LOG_NG << "getting recall list for side " << side << " at location " << recall_loc;
164 
165  std::vector<unit_const_ptr > result;
166 
167  /*
168  * We have three use cases:
169  * 1. An empty castle tile is highlighted; we return only the units recallable there.
170  * 2. A leader on a keep is highlighted; we return only the units recallable by that leader.
171  * 3. Otherwise, we return all units in the recall list that can be recalled by any leader on the map.
172  */
173 
174  bool leader_in_place = false;
175  bool allow_local = resources::gameboard->map().is_castle(recall_loc);
176 
177 
178  // Check for a leader at recall_loc (means we are recalling from there,
179  // rather than to there).
180  const unit_map::const_iterator find_it = resources::gameboard->units().find(recall_loc);
181  if ( find_it != resources::gameboard->units().end() ) {
182  if ( find_it->can_recruit() && find_it->side() == side &&
183  resources::gameboard->map().is_keep(recall_loc) )
184  {
185  // We have been requested to get the recalls for this
186  // particular leader.
187  add_leader_filtered_recalls(find_it.get_shared_ptr(), result);
188  return result;
189  }
190  else if ( find_it->is_visible_to_team(resources::gameboard->get_team(side), false) )
191  {
192  // This hex is visibly occupied, so we cannot recall here.
193  allow_local = false;
194  }
195  }
196 
197  if ( allow_local )
198  {
200  u_end = resources::gameboard->units().end();
201  std::set<std::size_t> valid_local_recalls;
202 
203  for(; u != u_end; ++u) {
204  //We only consider leaders on our side.
205  if (!(u->can_recruit() && u->side() == side))
206  continue;
207 
208  // Check if the leader is on a connected keep.
209  if (!dynamic_cast<game_state&>(*resources::filter_con).can_recruit_on(*u, recall_loc))
210  continue;
211  leader_in_place= true;
212 
213  add_leader_filtered_recalls(u.get_shared_ptr(), result, &valid_local_recalls);
214  }
215  }
216 
217  if ( !leader_in_place )
218  {
219  std::set<std::size_t> valid_local_recalls;
220 
221  for(auto u = resources::gameboard->units().begin(); u != resources::gameboard->units().end(); ++u) {
222  //We only consider leaders on our side.
223  if(!u->can_recruit() || u->side() != side) {
224  continue;
225  }
226 
227  add_leader_filtered_recalls(u.get_shared_ptr(), result, &valid_local_recalls);
228  }
229  }
230 
231  return result;
232 }
233 
234 namespace { // Helpers for check_recall_location()
235  /**
236  * Checks if @a recaller can recall @a recall_unit at @a preferred.
237  * If recalling can occur but not at the preferred location, then a
238  * permissible location is stored in @a alternative.
239  * @returns the reason why recalling is not allowed (or RECRUIT_OK).
240  */
241  RECRUIT_CHECK check_unit_recall_location(
242  const unit & recaller, const unit & recall_unit,
243  const map_location & preferred, map_location & alternative)
244  {
245  // Make sure the unit can actually recall.
246  if ( !recaller.can_recruit() )
247  return RECRUIT_NO_LEADER;
248 
249  // Make sure the recalling unit can recall this specific unit.
250  team& recall_team = (*resources::gameboard).get_team(recaller.side());
251  scoped_recall_unit this_unit("this_unit", recall_team.save_id_or_number(),
252  recall_team.recall_list().find_index(recall_unit.id()));
253 
254  const unit_filter ufilt(vconfig(recaller.recall_filter()));
255  if ( !ufilt(recall_unit, map_location::null_location()) )
256  return RECRUIT_NO_ABLE_LEADER;
257 
258  // Make sure the unit is on a keep.
259  if ( !resources::gameboard->map().is_keep(recaller.get_location()) )
260  return RECRUIT_NO_KEEP_LEADER;
261 
262  // Make sure there is a permissible location to which to recruit.
263  map_location permissible = pathfind::find_vacant_castle(recaller);
264  if ( !permissible.valid() )
265  return RECRUIT_NO_VACANCY;
266 
267  // See if the preferred location cannot be used.
268  if (!dynamic_cast<game_state&>(*resources::filter_con).can_recruit_on(recaller, preferred)) {
269  alternative = permissible;
271  }
272 
273  // All tests passed.
274  return RECRUIT_OK;
275  }
276 }//anonymous namespace
277 
278 /** Checks if there is a location on which to recall @a unit_recall. */
279 RECRUIT_CHECK check_recall_location(const int side, map_location& recall_location,
280  map_location& recall_from,
281  const unit &unit_recall)
282 {
283  const unit_map & units = resources::gameboard->units();
284  const unit_map::const_iterator u_end = units.end();
285 
286  map_location check_location = recall_location;
287  map_location alternative; // Set by check_unit_recall_location().
288 
289  // If the specified location is occupied, proceed as if no location was specified.
290  if ( resources::gameboard->units().count(recall_location) != 0 )
291  check_location = map_location::null_location();
292 
293  // If the check location is not valid, we will never get an "OK" result.
294  RECRUIT_CHECK const goal_result = check_location.valid() ? RECRUIT_OK :
296  RECRUIT_CHECK best_result = RECRUIT_NO_LEADER;
297 
298  // Test the specified recaller (if there is one).
299  unit_map::const_iterator u = units.find(recall_from);
300  if ( u != u_end && u->side() == side ) {
301  best_result =
302  check_unit_recall_location(*u, unit_recall, check_location, alternative);
303  }
304 
305  // Loop through all units on the specified side.
306  for ( u = units.begin(); best_result < goal_result && u != u_end; ++u ) {
307  if ( u->side() != side )
308  continue;
309 
310  // Check this unit's viability as a recaller.
311  RECRUIT_CHECK current_result =
312  check_unit_recall_location(*u, unit_recall, check_location, alternative);
313 
314  // If this is not an improvement, proceed to the next unit.
315  if ( current_result <= best_result )
316  continue;
317  best_result = current_result;
318 
319  // If we have a viable recaller, record its location.
320  if ( current_result >= RECRUIT_ALTERNATE_LOCATION )
321  recall_from = u->get_location();
322  }
323 
324  if ( best_result == RECRUIT_ALTERNATE_LOCATION )
325  // Report the alternate location to the caller.
326  recall_location = alternative;
327 
328  return best_result;
329 }
330 
331 std::string find_recall_location(const int side, map_location& recall_location, map_location& recall_from, const unit &unit_recall)
332 {
333  LOG_NG << "finding recall location for side " << side << " and unit " << unit_recall.id();
334 
335  // This function basically translates check_recall_location() to a
336  // human-readable string.
337  switch ( check_recall_location(side, recall_location, recall_from, unit_recall) )
338  {
339  case RECRUIT_NO_LEADER:
340  LOG_NG << "No leaders on side " << side << " when recalling " << unit_recall.id() << ".";
341  return _("You do not have a leader to recall with.");
342 
344  LOG_NG << "No leader is able to recall " << unit_recall.id() << " on side " << side << ".";
345  return _("None of your leaders are able to recall that unit.");
346 
348  LOG_NG << "No leader able to recall " << unit_recall.id() << " is on a keep.";
349  return _("You must have a leader on a keep who is able to recall that unit.");
350 
351  case RECRUIT_NO_VACANCY:
352  LOG_NG << "No vacant castle tiles around a keep are available for recalling " << unit_recall.id() << "; requested location is " << recall_location << ".";
353  return _("There are no vacant castle tiles in which to recall the unit.");
354 
356  case RECRUIT_OK:
357  return std::string();
358  }
359 
360  // We should never get down to here. But just in case someone decides to
361  // mess with the enum without updating this function:
362  ERR_NG << "Unrecognized enum in find_recall_location()";
363  return _("An unrecognized error has occurred.");
364 }
365 
366 namespace { // Helpers for check_recruit_location()
367  /**
368  * Checks if @a recruiter can recruit at @a preferred.
369  * If @a unit_type is not empty, it must be in the unit-specific recruit list.
370  * If recruitment can occur but not at the preferred location, then a
371  * permissible location is stored in @a alternative.
372  * @returns the reason why recruitment is not allowed (or RECRUIT_OK).
373  */
374  RECRUIT_CHECK check_unit_recruit_location(
375  const unit & recruiter, const std::string & unit_type,
376  const map_location & preferred, map_location & alternative)
377  {
378  // Make sure the unit can actually recruit.
379  if ( !recruiter.can_recruit() )
380  return RECRUIT_NO_LEADER;
381 
382  if ( !unit_type.empty() ) {
383  // Make sure the specified type is in the unit's recruit list.
384  if ( !utils::contains(recruiter.recruits(), unit_type) )
385  return RECRUIT_NO_ABLE_LEADER;
386  }
387 
388  // Make sure the unit is on a keep.
389  if ( !resources::gameboard->map().is_keep(recruiter.get_location()) )
390  return RECRUIT_NO_KEEP_LEADER;
391 
392  // Make sure there is a permissible location to which to recruit.
393  map_location permissible = pathfind::find_vacant_castle(recruiter);
394  if ( !permissible.valid() )
395  return RECRUIT_NO_VACANCY;
396 
397  // See if the preferred location cannot be used.
398  if (!dynamic_cast<game_state&>(*resources::filter_con).can_recruit_on(recruiter, preferred)) {
399  alternative = permissible;
401  }
402 
403  // All tests passed.
404  return RECRUIT_OK;
405  }
406 }//anonymous namespace
407 
408 /** Checks if there is a location on which to place a recruited unit. */
409 RECRUIT_CHECK check_recruit_location(const int side, map_location &recruit_location,
410  map_location& recruited_from,
411  const std::string& unit_type)
412 {
413  const unit_map & units = resources::gameboard->units();
414  const unit_map::const_iterator u_end = units.end();
415 
416  map_location check_location = recruit_location;
417  std::string check_type = unit_type;
418  map_location alternative; // Set by check_unit_recruit_location().
419 
420  // If the specified location is occupied, proceed as if no location was specified.
421  if ( resources::gameboard->units().count(recruit_location) != 0 )
422  check_location = map_location::null_location();
423 
424  // If the specified unit type is in the team's recruit list, there is no
425  // need to check each leader's list.
426  if ( utils::contains(resources::gameboard->get_team(side).recruits(), unit_type) )
427  check_type.clear();
428 
429  // If the check location is not valid, we will never get an "OK" result.
430  RECRUIT_CHECK const goal_result = check_location.valid() ? RECRUIT_OK :
432  RECRUIT_CHECK best_result = RECRUIT_NO_LEADER;
433 
434  // Test the specified recruiter (if there is one).
435  unit_map::const_iterator u = units.find(recruited_from);
436  if ( u != u_end && u->side() == side ) {
437  best_result =
438  check_unit_recruit_location(*u, check_type, check_location, alternative);
439  }
440 
441  // Loop through all units on the specified side.
442  for ( u = units.begin(); best_result < goal_result && u != u_end; ++u ) {
443  if ( u->side() != side )
444  continue;
445 
446  // Check this unit's viability as a recruiter.
447  RECRUIT_CHECK current_result =
448  check_unit_recruit_location(*u, check_type, check_location, alternative);
449 
450  // If this is not an improvement, proceed to the next unit.
451  if ( current_result <= best_result )
452  continue;
453  best_result = current_result;
454 
455  // If we have a viable recruiter, record its location.
456  if ( current_result >= RECRUIT_ALTERNATE_LOCATION )
457  recruited_from = u->get_location();
458  }
459 
460  if ( best_result == RECRUIT_ALTERNATE_LOCATION )
461  // Report the alternate location to the caller.
462  recruit_location = alternative;
463 
464  return best_result;
465 }
466 
467 std::string find_recruit_location(const int side, map_location& recruit_location, map_location& recruited_from, const std::string& unit_type)
468 {
469  LOG_NG << "finding recruit location for side " << side;
470 
471  // This function basically translates check_recruit_location() to a
472  // human-readable string.
473  switch ( check_recruit_location(side, recruit_location, recruited_from, unit_type) )
474  {
475  case RECRUIT_NO_LEADER:
476  LOG_NG << "No leaders on side " << side << " when recruiting '" << unit_type << "'.";
477  return _("You do not have a leader to recruit with.");
478 
480  LOG_NG << "No leader is able to recruit '" << unit_type << "' on side " << side << ".";
481  return _("None of your leaders are able to recruit this unit.");
482 
484  LOG_NG << "No leader able to recruit '" << unit_type << "' is on a keep.";
485  return _("You must have a leader on a keep who is able to recruit the unit.");
486 
487  case RECRUIT_NO_VACANCY:
488  LOG_NG << "No vacant castle tiles around a keep are available for recruiting '" << unit_type << "'; requested location is " << recruit_location << ".";
489  return _("There are no vacant castle tiles in which to recruit the unit.");
490 
492  case RECRUIT_OK:
493  return std::string();
494  }
495 
496  // We should never get down to here. But just in case someone decides to
497  // mess with the enum without updating this function:
498  ERR_NG << "Unrecognized enum in find_recruit_location()";
499  return _("An unrecognized error has occurred.");
500 }
501 
502 
503 namespace { // Helpers for place_recruit()
504  /**
505  * Performs a checksum check on a newly recruited/recalled unit.
506  */
507  void recruit_checksums(const unit &new_unit, bool wml_triggered)
508  {
509  if(wml_triggered)
510  {
511  return;
512  }
513  const std::string checksum = get_checksum(new_unit);
514  config original_checksum_config;
515 
516  bool checksum_equals = checkup_instance->local_checkup(config {"checksum", checksum},original_checksum_config);
517  if(!checksum_equals)
518  {
519  // This can't call local_checkup() again, but local_checkup() should have already stored the
520  // expected value in original_checksum_config. If it hasn't then the result will be the same as
521  // if the checksum didn't match, which is a reasonably graceful failure.
522  const std::string alternate_checksum = get_checksum(new_unit, backwards_compatibility::unit_checksum_version::version_1_16_or_older);
523  checksum_equals = original_checksum_config["checksum"] == alternate_checksum;
524  }
525  if(!checksum_equals)
526  {
527  const std::string old_checksum = original_checksum_config["checksum"];
528  std::stringstream error_msg;
529  error_msg << "SYNC: In recruit " << new_unit.type_id() <<
530  ": has checksum " << checksum <<
531  " while datasource has checksum " << old_checksum << "\n";
532  if(old_checksum.empty())
533  {
534  error_msg << "Original result is \n" << original_checksum_config << "\n";
535  }
536  config cfg_unit1;
537  new_unit.write(cfg_unit1);
538  DBG_NG << cfg_unit1;
539  replay::process_error(error_msg.str());
540  }
541  }
542 
543  /**
544  * Locates a leader on side @a side who can recruit at @a recruit_location.
545  * A leader at @a recruited_from is chosen in preference to others.
546  */
547  const map_location & find_recruit_leader(int side,
548  const map_location &recruit_location, const map_location &recruited_from)
549  {
550  const unit_map & units = resources::gameboard->units();
551 
552  // See if the preferred location is an option.
553  unit_map::const_iterator leader = units.find(recruited_from);
554  if (leader != units.end() && leader->can_recruit() &&
555  leader->side() == side && dynamic_cast<game_state&>(*resources::filter_con).can_recruit_on(*leader, recruit_location))
556  return leader->get_location();
557 
558  // Check all units.
559  for (leader = units.begin(); leader != units.end(); ++leader)
560  if (leader->can_recruit() && leader->side() == side &&
561  dynamic_cast<game_state&>(*resources::filter_con).can_recruit_on(*leader, recruit_location))
562  return leader->get_location();
563 
564  // No usable leader found.
566  }
567 
568  /**
569  * Tries to make @a un_it valid, and updates @a current_loc.
570  * Used by place_recruit() after WML might have changed something.
571  * @returns true if the iterator was made valid.
572  */
573  bool validate_recruit_iterator(unit_map::iterator & un_it,
574  map_location & current_loc)
575  {
576  if ( !un_it.valid() ) {
577  // Maybe WML provided a replacement?
578  un_it = resources::gameboard->units().find(current_loc);
579  if ( un_it == resources::gameboard->units().end() )
580  // The unit is gone.
581  return false;
582  }
583  current_loc = un_it->get_location();
584  return true;
585  }
586 
587  void set_recruit_facing(unit_map::iterator &new_unit_itor, const unit &new_unit,
588  const map_location &recruit_loc, const map_location &leader_loc)
589  {
590  // Find closest enemy and turn towards it (level 2s count more than level 1s, etc.)
591  const gamemap *map = & resources::gameboard->map();
592  const unit_map & units = resources::gameboard->units();
593  unit_map::const_iterator unit_itor;
594  map_location min_loc;
595  int min_dist = INT_MAX;
596 
597  for ( unit_itor = units.begin(); unit_itor != units.end(); ++unit_itor ) {
598  if (resources::gameboard->get_team(unit_itor->side()).is_enemy(new_unit.side()) &&
599  unit_itor->is_visible_to_team(resources::gameboard->get_team(new_unit.side()), false)) {
600  int dist = distance_between(unit_itor->get_location(),recruit_loc) - unit_itor->level();
601  if (dist < min_dist) {
602  min_dist = dist;
603  min_loc = unit_itor->get_location();
604  }
605  }
606  }
607  if (min_dist < INT_MAX) {
608  // Face towards closest enemy
609  new_unit_itor->set_facing(recruit_loc.get_relative_dir(min_loc));
610  } else if (leader_loc != map_location::null_location()) {
611  // Face away from leader
612  new_unit_itor->set_facing(map_location::get_opposite_dir(recruit_loc.get_relative_dir(leader_loc)));
613  } else {
614  // Face towards center of map
615  const map_location center(map->w()/2, map->h()/2);
616  new_unit_itor->set_facing(recruit_loc.get_relative_dir(center));
617  }
618  }
619 }// anonymous namespace
620 //Used by recalls and recruits
621 place_recruit_result place_recruit(unit_ptr u, const map_location &recruit_location, const map_location& recruited_from,
622  int cost, bool is_recall, map_location::DIRECTION facing, bool show, bool fire_event, bool full_movement,
623  bool wml_triggered)
624 {
625  place_recruit_result res(false, 0, false);
626  LOG_NG << "placing new unit on location " << recruit_location;
627  if (full_movement) {
628  u->set_movement(u->total_movement(), true);
629  } else {
630  u->set_movement(0, true);
631  u->set_attacks(0);
632  }
633  if(!is_recall) {
634  u->heal_fully();
635  }
636  u->set_hidden(true);
637 
638  // Get the leader location before adding the unit to the board.
639  const map_location leader_loc = !show ? map_location::null_location() :
640  find_recruit_leader(u->side(), recruit_location, recruited_from);
641  u->set_location(recruit_location);
642 
643  // Add the unit to the board.
644  auto [new_unit_itor, success] = resources::gameboard->units().insert(u);
645  assert(success);
646 
647  map_location current_loc = recruit_location;
648 
649  if (facing == map_location::NDIRECTIONS) {
650  set_recruit_facing(new_unit_itor, *u, recruit_location, leader_loc);
651  } else {
652  new_unit_itor->set_facing(facing);
653  }
654 
655  // Do some bookkeeping.
656  team& current_team = resources::gameboard->get_team(u->side());
657  recruit_checksums(*u, wml_triggered);
658  resources::whiteboard->on_gamestate_change();
659 
660  resources::game_events->pump().fire("unit_placed", current_loc);
661  if(!new_unit_itor.valid()) {
662  return place_recruit_result { true, 0, false };
663  }
664 
665  if ( fire_event ) {
666  const std::string event_name = is_recall ? "prerecall" : "prerecruit";
667  LOG_NG << "firing " << event_name << " event";
668  {
669  std::get<0>(res) |= std::get<0>(resources::game_events->pump().fire(event_name, current_loc, recruited_from));
670  }
671  if ( !validate_recruit_iterator(new_unit_itor, current_loc) )
672  return std::tuple(true, 0, false);
673  new_unit_itor->set_hidden(true);
674  }
675  preferences::encountered_units().insert(new_unit_itor->type_id());
676  current_team.spend_gold(cost);
677 
678  if ( show ) {
679  unit_display::unit_recruited(current_loc, leader_loc);
680  }
681  // Make sure the unit appears (if either !show or the animation is suppressed).
682  new_unit_itor->set_hidden(false);
683  if (display::get_singleton() != nullptr ) {
684  display::get_singleton()->invalidate(current_loc);
686  }
687 
688  // Village capturing.
689  if ( resources::gameboard->map().is_village(current_loc) ) {
690  std::get<1>(res) = resources::gameboard->village_owner(current_loc);
691  std::get<0>(res) |= std::get<0>(actions::get_village(current_loc, new_unit_itor->side(), &std::get<2>(res)));
692  if ( !validate_recruit_iterator(new_unit_itor, current_loc) )
693  return std::tuple(true, 0, false);
694  }
695 
696  // Fog clearing.
697  actions::shroud_clearer clearer;
698  if ( !wml_triggered && current_team.auto_shroud_updates() ) // To preserve current WML behavior.
699  std::get<0>(res) |= clearer.clear_unit(current_loc, *new_unit_itor);
700 
701  if ( fire_event ) {
702  const std::string event_name = is_recall ? "recall" : "recruit";
703  LOG_NG << "firing " << event_name << " event";
704  {
705  std::get<0>(res) |= std::get<0>(resources::game_events->pump().fire(event_name, current_loc, recruited_from));
706  }
707  }
708 
709  // "sighted" event(s).
710  std::get<0>(res) |= std::get<0>(clearer.fire_events());
711  if ( new_unit_itor.valid() )
712  std::get<0>(res) |= std::get<0>(actions::actor_sighted(*new_unit_itor));
713 
714  return res;
715 }
716 
717 void recruit_unit(const unit_type & u_type, int side_num, const map_location & loc,
718  const map_location & from, bool show, bool use_undo)
719 {
720  const unit_ptr new_unit = unit::create(u_type, side_num, true);
721 
722 
723  // Place the recruit.
724  place_recruit_result res = place_recruit(new_unit, loc, from, u_type.cost(), false, map_location::NDIRECTIONS, show);
725  statistics::recruit_unit(*new_unit);
726 
727  // To speed things a bit, don't bother with the undo stack during
728  // an AI turn. The AI will not undo nor delay shroud updates.
729  // (Undo stack processing is also suppressed when redoing a recruit.)
730  if ( use_undo ) {
731  resources::undo_stack->add_recruit(new_unit, loc, from, std::get<1>(res), std::get<2>(res));
732  // Check for information uncovered or randomness used.
733 
734  if ( std::get<0>(res) || !synced_context::can_undo()) {
736  }
737  }
738 
739  // Update the screen.
740  if (display::get_singleton() != nullptr )
742  // Other updates were done by place_recruit().
743 }
744 
745 bool recall_unit(const std::string & id, team & current_team,
746  const map_location & loc, const map_location & from,
747  map_location::DIRECTION facing, bool show, bool use_undo)
748 {
749  unit_ptr recall = current_team.recall_list().extract_if_matches_id(id);
750 
751  if ( !recall )
752  return false;
753 
754 
755  // ** IMPORTANT: id might become invalid at this point!
756  // (Use recall.id() instead, if needed.)
757 
758  // Place the recall.
759  // We also check to see if a custom unit level recall has been set if not,
760  // we use the team's recall cost otherwise the unit's.
762  if (recall->recall_cost() < 0) {
763  res = place_recruit(recall, loc, from, current_team.recall_cost(),
764  true, facing, show);
765  }
766  else {
767  res = place_recruit(recall, loc, from, recall->recall_cost(),
768  true, facing, show);
769  }
770  statistics::recall_unit(*recall);
771 
772  // To speed things a bit, don't bother with the undo stack during
773  // an AI turn. The AI will not undo nor delay shroud updates.
774  // (Undo stack processing is also suppressed when redoing a recall.)
775  if ( use_undo ) {
776  resources::undo_stack->add_recall(recall, loc, from, std::get<1>(res), std::get<2>(res));
777  if ( std::get<0>(res) || !synced_context::can_undo()) {
779  }
780  }
781 
782  // Update the screen.
783  if (display::get_singleton() != nullptr )
785  // Other updates were done by place_recruit().
786 
787  return true;
788 }
789 
790 
791 }//namespace actions
void clear()
Clears the stack of undoable (and redoable) actions.
Definition: undo.cpp:220
bool is_keep(const map_location &loc) const
Definition: map.cpp:72
unit_iterator end()
Definition: map.hpp:429
static display * get_singleton()
Returns the display object if a display object exists.
Definition: display.hpp:98
void invalidate_game_status()
Function to invalidate the game status displayed on the sidebar.
Definition: display.hpp:296
virtual const unit_map & units() const override
Definition: game_board.hpp:113
DIRECTION get_relative_dir(const map_location &loc, map_location::RELATIVE_DIR_MODE mode) const
Definition: location.cpp:227
bool invalidate(const map_location &loc)
Function to invalidate a specific tile for redrawing.
Definition: display.cpp:3122
This class represents a single unit of a specific type.
Definition: unit.hpp:133
const std::string & type_id() const
The id of this unit&#39;s type.
Definition: unit.cpp:1879
bool is_castle(const map_location &loc) const
Definition: map.cpp:70
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
No leaders exist.
Definition: create.hpp:39
Various functions implementing vision (through fog of war and shroud).
umap_retval_pair_t insert(unit_ptr p)
Inserts the unit pointed to by p into the map.
Definition: map.cpp:134
bool can_recruit_on(const map_location &leader_loc, const map_location &recruit_loc, int side) const
Checks to see if a leader at leader_loc could recruit on recruit_loc.
Definition: game_state.cpp:327
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:745
virtual const gamemap & map() const override
Definition: game_board.hpp:103
unit_iterator begin()
Definition: map.hpp:419
std::tuple< bool, int, bool > place_recruit_result
Definition: create.hpp:143
Recruitment OK, but not at the specified location.
Definition: create.hpp:43
#define DBG_NG
Definition: create.cpp:55
Replay control code.
std::string save_id_or_number() const
Definition: team.hpp:220
RECRUIT_CHECK
The possible results of finding a location for recruiting (or recalling).
Definition: create.hpp:37
static config unit_type(const unit *u)
Definition: reports.cpp:189
static std::string _(const char *str)
Definition: gettext.hpp:93
Definitions for the interface to Wesnoth Markup Language (WML).
void redraw_minimap()
Schedule the minimap to be redrawn.
Definition: display.cpp:1641
std::shared_ptr< unit > unit_ptr
Definition: ptr.hpp:26
std::vector< unit_const_ptr > get_recalls(int side, const map_location &recall_loc)
Gets the recallable units for a side, restricted by that side&#39;s leaders&#39; personal abilities to recall...
Definition: create.cpp:161
void recruit_unit(const unit &u)
Definition: statistics.cpp:647
static unit_ptr create(const config &cfg, bool use_traits=false, const vconfig *vcfg=nullptr)
Initializes a unit from a config.
Definition: unit.hpp:202
A single unit type that the player may recruit.
Definition: types.hpp:45
void write(config &cfg, bool write_all=true) const
Serializes the current unit metadata values.
Definition: unit.cpp:1498
bool contains(const Container &container, const Value &value)
Returns true iff value is found in container.
Definition: general.hpp:84
bool clear_unit(const map_location &view_loc, team &view_team, std::size_t viewer_id, int sight_range, bool slowed, const movetype::terrain_costs &costs, const map_location &real_loc, const std::set< map_location > *known_units=nullptr, std::size_t *enemy_count=nullptr, std::size_t *friend_count=nullptr, move_unit_spectator *spectator=nullptr, bool instant=true)
Clears shroud (and fog) around the provided location for view_team based on sight_range, costs, and slowed.
Definition: vision.cpp:332
bool fire_event(const ui_event event, const std::vector< std::pair< widget *, ui_event >> &event_chain, widget *dispatcher, widget *w, F &&... params)
Helper function for fire_event.
std::shared_ptr< const unit > unit_const_ptr
Definition: ptr.hpp:27
This class stores all the data for a single &#39;side&#39; (in game nomenclature).
Definition: team.hpp:75
team & get_team(int i)
Definition: game_board.hpp:98
game_events::pump_result_t fire_events()
Fires the sighted events that were earlier recorded by fog/shroud clearing.
Definition: vision.cpp:543
Included some of the flavortext from weapon specials.
const std::string & id() const
Gets this unit&#39;s id.
Definition: unit.hpp:383
int cost() const
Definition: types.hpp:175
int w() const
Effective map width.
Definition: map.hpp:50
const std::set< std::string > get_recruits(int side, const map_location &recruit_loc)
Gets the recruitable units from a side&#39;s leaders&#39; personal recruit lists who can recruit on or from a...
Definition: create.cpp:61
filter_context * filter_con
Definition: resources.cpp:24
const config & recall_filter() const
Gets the filter constraints upon which units this unit may recall, if able.
Definition: unit.hpp:655
bool valid() const
Definition: location.hpp:89
game_board * gameboard
Definition: resources.cpp:21
#define LOG_NG
Definition: create.cpp:56
Encapsulates the map of the game.
Definition: map.hpp:171
checkup * checkup_instance
#define ERR_NG
Definition: create.cpp:57
static bool can_undo()
map_display and display: classes which take care of displaying the map and game-data on the screen...
std::string get_checksum(const unit &u, backwards_compatibility::unit_checksum_version version)
Gets a checksum for a unit.
Definition: unit.cpp:2764
void spend_gold(const int amount)
Definition: team.hpp:196
No vacant castle tiles around a leader on a keep.
Definition: create.hpp:42
game_events::manager * game_events
Definition: resources.cpp:25
void pump()
Process all events currently in the queue.
Definition: events.cpp:478
int recall_cost() const
Definition: team.hpp:181
Encapsulates the map of the game.
Definition: location.hpp:38
bool auto_shroud_updates() const
Definition: team.hpp:326
Various functions related to moving units.
void add_recruit(const unit_const_ptr u, const map_location &loc, const map_location &from, int orig_village_owner, bool time_bonus)
Adds a recruit to the undo stack.
Definition: undo.cpp:194
unit_iterator find(std::size_t id)
Definition: map.cpp:301
std::shared_ptr< wb::manager > whiteboard
Definition: resources.cpp:34
static void process_error(const std::string &msg)
Definition: replay.cpp:198
Various functions related to the creation of units (recruits, recalls, and placed units)...
pointer get_shared_ptr() const
This is exactly the same as operator-> but it&#39;s slightly more readable, and can replace &*iter syntax...
Definition: map.hpp:218
RECRUIT_CHECK check_recall_location(const int side, map_location &recall_location, map_location &recall_from, const unit &unit_recall)
Checks if there is a location on which to recall unit_recall.
Definition: create.cpp:279
No able leaders are on a keep.
Definition: create.hpp:41
void add_recall(const unit_const_ptr u, const map_location &loc, const map_location &from, int orig_village_owner, bool time_bonus)
Adds a recall to the undo stack.
Definition: undo.cpp:185
bool can_recruit() const
Whether this unit can recruit other units - ie, are they a leader unit.
Definition: unit.hpp:615
Define the game&#39;s event mechanism.
std::string find_recall_location(const int side, map_location &recall_location, map_location &recall_from, const unit &unit_recall)
Finds a location on which to recall unit_recall.
Definition: create.cpp:331
RECRUIT_CHECK check_recruit_location(const int side, map_location &recruit_location, map_location &recruited_from, const std::string &unit_type)
Checks if there is a location on which to place a recruited unit.
Definition: create.cpp:409
DIRECTION
Valid directions which can be moved in our hexagonal world.
Definition: location.hpp:40
std::set< std::string > & encountered_units()
Definition: game.cpp:916
unit_ptr extract_if_matches_id(const std::string &unit_id, int *pos=nullptr)
Find a unit by id, and extract from this object if found.
void recall_unit(const unit &u)
Definition: statistics.cpp:654
pump_result_t fire(const std::string &event, const entity_location &loc1=entity_location::null_entity, const entity_location &loc2=entity_location::null_entity, const config &data=config())
Function to fire an event.
Definition: pump.cpp:402
const std::vector< std::string > & recruits() const
The type IDs of the other units this unit may recruit, if possible.
Definition: unit.hpp:627
static DIRECTION get_opposite_dir(DIRECTION d)
Definition: location.hpp:55
void recruit_unit(const unit_type &u_type, int side_num, const map_location &loc, const map_location &from, bool show, bool use_undo)
Recruits a unit of the given type for the given side.
Definition: create.cpp:717
std::size_t distance_between(const map_location &a, const map_location &b)
Function which gives the number of hexes between two tiles (i.e.
Definition: location.cpp:546
game_events::pump_result_t get_village(const map_location &loc, int side, bool *action_timebonus, bool fire_event)
Makes it so the village at the given location is owned by the given side.
Definition: move.cpp:140
const map_location & get_location() const
The current map location this unit is at.
Definition: unit.hpp:1360
void unit_recruited(const map_location &loc, const map_location &leader_loc)
Definition: udisplay.cpp:803
A variable-expanding proxy for the config class.
Definition: variable.hpp:44
Various functions that implement the undoing (and redoing) of in-game commands.
std::string find_recruit_location(const int side, map_location &recruit_location, map_location &recruited_from, const std::string &unit_type)
Finds a location on which to place a unit.
Definition: create.cpp:467
Standard logging facilities (interface).
recall_list_manager & recall_list()
Definition: team.hpp:203
static const map_location & null_location()
Definition: location.hpp:81
Container associating units to locations.
Definition: map.hpp:98
Class to encapsulate fog/shroud clearing and the resultant sighted events.
Definition: vision.hpp:72
static lg::log_domain log_engine("engine")
int side() const
The side this unit belongs to.
Definition: unit.hpp:346
actions::undo_list * undo_stack
Definition: resources.cpp:33
game_events::wml_event_pump & pump()
Definition: manager.cpp:252
virtual bool local_checkup(const config &expected_data, config &real_data)=0
Compares data to the results calculated during the original game.
place_recruit_result place_recruit(unit_ptr u, const map_location &recruit_location, const map_location &recruited_from, int cost, bool is_recall, map_location::DIRECTION facing, bool show, bool fire_event, bool full_movement, bool wml_triggered)
Place a unit into the game.
Definition: create.cpp:621
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:60
bool valid() const
Definition: map.hpp:274
int h() const
Effective map height.
Definition: map.hpp:53
This module contains various pathfinding functions and utilities.
std::size_t underlying_id() const
This unit&#39;s unique internal ID.
Definition: unit.hpp:395
Display units performing various actions: moving, attacking, and dying.
std::size_t find_index(const std::string &unit_id) const
Find the index of a unit by its id.
No leaders able to recall/recruit the given unit/type.
Definition: create.hpp:40
const std::set< std::string > & recruits() const
Definition: team.hpp:211
game_events::pump_result_t actor_sighted(const unit &target, const std::vector< int > *cache)
Fires sighted events for the sides that can see target.
Definition: vision.cpp:619
void show(const std::string &window_id, const t_string &message, const point &mouse, const SDL_Rect &source_rect)
Shows a tip.
Definition: tooltip.cpp:81