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