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