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