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  * Sighting.
18  */
19 
20 #include "actions/vision.hpp"
21 
22 #include "actions/move.hpp"
23 
24 #include "config.hpp"
25 #include "game_events/pump.hpp"
26 #include "log.hpp"
27 #include "map/map.hpp"
28 #include "map/label.hpp"
29 #include "map/location.hpp"
30 #include "pathfind/pathfind.hpp"
31 #include "play_controller.hpp"
32 #include "resources.hpp"
33 #include "team.hpp"
34 #include "units/unit.hpp"
35 
36 #include <boost/dynamic_bitset.hpp>
37 
38 class unit_animation;
39 
40 static lg::log_domain log_engine("engine");
41 #define DBG_NG LOG_STREAM(debug, log_engine)
42 #define ERR_NG LOG_STREAM(err, log_engine)
43 
44 
45 static const std::string sighted_str("sighted");
46 
47 
48 /**
49  * Sets @a jamming to the (newly calculated) "jamming" map for @a view_team.
50  */
create_jamming_map(std::map<map_location,int> & jamming,const team & view_team)51 static void create_jamming_map(std::map<map_location, int> & jamming,
52                                const team & view_team)
53 {
54 	// Reset the map.
55 	jamming.clear();
56 
57 	// Build the map.
58 	for (const unit &u : resources::gameboard->units())
59 	{
60 		if ( u.jamming() < 1  ||  !view_team.is_enemy(u.side()) )
61 			continue;
62 
63 		pathfind::jamming_path jam_path(u, u.get_location());
64 		for (const pathfind::paths::step& st : jam_path.destinations) {
65 			if ( jamming[st.curr] < st.move_left )
66 				jamming[st.curr] = st.move_left;
67 		}
68 	}
69 }
70 
71 
72 /**
73  * Determines if @a loc is within @a viewer's visual range.
74  * This is a moderately expensive function (vision is recalculated
75  * with each call), so avoid using it heavily.
76  * If @a jamming is left as nullptr, the jamming map is also calculated
77  * with each invocation.
78  */
can_see(const unit & viewer,const map_location & loc,const std::map<map_location,int> * jamming=nullptr)79 static bool can_see(const unit & viewer, const map_location & loc,
80                     const std::map<map_location, int> * jamming = nullptr)
81 {
82 	// Make sure we have a "jamming" map.
83 	std::map<map_location, int> local_jamming;
84 	if ( jamming == nullptr ) {
85 		create_jamming_map(local_jamming, resources::gameboard->get_team(viewer.side()));
86 		jamming = &local_jamming;
87 	}
88 
89 	// Determine which hexes this unit can see.
90 	pathfind::vision_path sight(viewer, viewer.get_location(), *jamming);
91 
92 	return sight.destinations.contains(loc)  ||  sight.edges.count(loc) != 0;
93 }
94 
95 
96 namespace actions {
97 
98 
99 /**
100  * Constructor from a unit.
101  */
clearer_info(const unit & viewer)102 clearer_info::clearer_info(const unit & viewer) :
103 	underlying_id(viewer.underlying_id()),
104 	sight_range(viewer.vision()),
105 	slowed(viewer.get_state(unit::STATE_SLOWED)),
106 	costs(viewer.movement_type().get_vision().make_standalone())
107 {
108 }
109 
110 /**
111  * Constructor from a config.
112  */
clearer_info(const config & cfg)113 clearer_info::clearer_info(const config & cfg) :
114 	underlying_id(cfg["underlying_id"].to_size_t()),
115 	sight_range(cfg["vision"].to_int()),
116 	slowed(cfg.child_or_empty("status")["slowed"].to_bool()),
117 	costs(movetype::read_terrain_costs(cfg.child_or_empty("vision_costs")))
118 {
119 }
120 
121 /**
122  * Writes to a config.
123  */
write(config & cfg) const124 void clearer_info::write(config & cfg) const
125 {
126 	// The key and tag names are intended to mirror those used by [unit]
127 	// (so a clearer_info can be constructed from a unit's config).
128 	cfg["underlying_id"] = underlying_id;
129 	cfg["vision"] = sight_range;
130 	if ( slowed )
131 		cfg.add_child("status")["slowed"] = true;
132 	costs->write(cfg, "vision_costs");
133 }
134 
135 
136 /**
137  * A record of a sighting event.
138  * Records the unit doing a sighting, the location of that unit at the
139  * time of the sighting, and the location of the sighted unit.
140  */
141 struct shroud_clearer::sight_data {
sight_dataactions::shroud_clearer::sight_data142 	sight_data(size_t viewed_id, const map_location & viewed_loc,
143 	           size_t viewer_id, const map_location & viewer_loc) :
144 		seen_id(viewed_id),    seen_loc(viewed_loc),
145 		sighter_id(viewer_id), sighter_loc(viewer_loc)
146 	{}
147 
148 	size_t       seen_id;
149 	map_location seen_loc;
150 	size_t       sighter_id;
151 	map_location sighter_loc;
152 };
153 
154 
155 /**
156  * Convenience wrapper for adding sighting data to the sightings_ vector.
157  */
record_sighting(const unit & seen,const map_location & seen_loc,size_t sighter_id,const map_location & sighter_loc)158 inline void shroud_clearer::record_sighting(
159 	const unit & seen, const map_location & seen_loc,
160 	size_t sighter_id, const map_location & sighter_loc)
161 {
162 	sightings_.emplace_back(seen.underlying_id(), seen_loc, sighter_id, sighter_loc);
163 }
164 
165 
166 /**
167  * Default constructor.
168  */
shroud_clearer()169 shroud_clearer::shroud_clearer() : jamming_(), sightings_(), view_team_(nullptr)
170 {}
171 
172 
173 /**
174  * Destructor.
175  * The purpose of explicitly defining this is so we can log an error if the
176  * sighted events were neither fired nor explicitly ignored.
177  */
~shroud_clearer()178 shroud_clearer::~shroud_clearer()
179 {
180 	if ( !sightings_.empty() ) {
181 		ERR_NG << sightings_.size() << " sighted events were ignored." << std::endl;
182 	}
183 }
184 
185 /**
186  * Causes this object's "jamming" map to be recalculated.
187  * This gets called as needed, and can also be manually invoked
188  * via cache_units().
189  * @param[in] new_team  The team whose vision will be used. If nullptr, the
190  *                      jamming map will be cleared.
191  */
calculate_jamming(const team * new_team)192 void shroud_clearer::calculate_jamming(const team * new_team)
193 {
194 	// Reset data.
195 	jamming_.clear();
196 	view_team_ = new_team;
197 
198 	if ( view_team_ == nullptr )
199 		return;
200 
201 	// Build the map.
202 	create_jamming_map(jamming_, *view_team_);
203 }
204 
205 
206 /**
207  * Clears shroud from a single location.
208  * This also records sighted events for later firing.
209  *
210  * In a few cases, this will also clear corner hexes that otherwise would
211  * not normally get cleared.
212  * @param tm               The team whose fog/shroud is affected.
213  * @param loc              The location to clear.
214  * @param view_loc         The location viewer is assumed at (for sighted events).
215  * @param event_non_loc    The unit at this location cannot be sighted
216  *                         (used to prevent a unit from sighting itself).
217  * @param viewer_id        The underlying ID of the unit doing the sighting (for events).
218  * @param check_units      If false, there is no checking for an uncovered unit.
219  * @param enemy_count      Incremented if an enemy is uncovered.
220  * @param friend_count     Incremented if a friend is uncovered.
221  * @param spectator        Will be told if a unit is uncovered.
222  *
223  * @return whether or not information was uncovered (i.e. returns true if
224  *         the specified location was fogged/ shrouded under shared vision/maps).
225  */
clear_loc(team & tm,const map_location & loc,const map_location & view_loc,const map_location & event_non_loc,size_t viewer_id,bool check_units,size_t & enemy_count,size_t & friend_count,move_unit_spectator * spectator)226 bool shroud_clearer::clear_loc(team &tm, const map_location &loc,
227                                const map_location &view_loc,
228                                const map_location &event_non_loc,
229                                size_t viewer_id, bool check_units,
230                                size_t &enemy_count, size_t &friend_count,
231                                move_unit_spectator * spectator)
232 {
233 	const gamemap &map = resources::gameboard->map();
234 	// This counts as clearing a tile for the return value if it is on the
235 	// board and currently fogged under shared vision. (No need to explicitly
236 	// check for shrouded since shrouded implies fogged.)
237 	bool was_fogged = tm.fogged(loc);
238 	bool result = was_fogged && map.on_board(loc);
239 
240 	// Clear the border as well as the board, so that the half-hexes
241 	// at the edge can also be cleared of fog/shroud.
242 	if ( map.on_board_with_border(loc) ) {
243 		// Both functions should be executed so don't use || which
244 		// uses short-cut evaluation.
245 		// (This is different than the return value because shared vision does
246 		// not apply here.)
247 		if ( tm.clear_shroud(loc) | tm.clear_fog(loc) ) {
248 			// If we are near a corner, the corner might also need to be cleared.
249 			// This happens at the lower-left corner and at either the upper- or
250 			// lower- right corner (depending on the width).
251 
252 			// Lower-left corner:
253 			if ( loc.x == 0  &&  loc.y == map.h()-1 ) {
254 				const map_location corner(-1, map.h());
255 				tm.clear_shroud(corner);
256 				tm.clear_fog(corner);
257 			}
258 			// Lower-right corner, odd width:
259 			else if ( is_odd(map.w())  &&  loc.x == map.w()-1  &&  loc.y == map.h()-1 ) {
260 				const map_location corner(map.w(), map.h());
261 				tm.clear_shroud(corner);
262 				tm.clear_fog(corner);
263 			}
264 			// Upper-right corner, even width:
265 			else if ( is_even(map.w())  &&  loc.x == map.w()-1  &&  loc.y == 0) {
266 				const map_location corner(map.w(), -1);
267 				tm.clear_shroud(corner);
268 				tm.clear_fog(corner);
269 			}
270 		}
271 	}
272 
273 	// Possible screen invalidation.
274 	if ( was_fogged ) {
275 		display::get_singleton()->invalidate(loc);
276 		// Need to also invalidate adjacent hexes to get rid of the
277 		// "fog edge" graphics.
278 		adjacent_loc_array_t adjacent;
279 		get_adjacent_tiles(loc, adjacent.data());
280 		for (unsigned i = 0; i < adjacent.size(); ++i )
281 			display::get_singleton()->invalidate(adjacent[i]);
282 	}
283 
284 	// Check for units?
285 	if ( result  &&  check_units  &&  loc != event_non_loc ) {
286 		// Uncovered a unit?
287 		unit_map::const_iterator sight_it = resources::gameboard->find_visible_unit(loc, tm);
288 		if ( sight_it.valid() ) {
289 			record_sighting(*sight_it, loc, viewer_id, view_loc);
290 
291 			// Track this?
292 			if ( !sight_it->get_state(unit::STATE_PETRIFIED) ) {
293 				if ( tm.is_enemy(sight_it->side()) ) {
294 					++enemy_count;
295 					if ( spectator )
296 						spectator->add_seen_enemy(sight_it);
297 				} else {
298 					++friend_count;
299 					if ( spectator )
300 						spectator->add_seen_friend(sight_it);
301 				}
302 			}
303 		}
304 	}
305 
306 	return result;
307 }
308 
309 
310 /**
311  * Clears shroud (and fog) around the provided location for @a view_team
312  * based on @a sight_range, @a costs, and @a slowed.
313  * This will also record sighted events, which should be either fired or
314  * explicitly dropped. (The sighter is the unit with underlying id @a viewer_id.)
315  *
316  * This should only be called if delayed shroud updates is off.
317  * It is wasteful to call this if view_team uses neither fog nor shroud.
318  *
319  * @param real_loc         The actual location of the viewing unit.
320  *                         (This is used to avoid having a unit sight itself.)
321  * @param known_units      These locations are not checked for uncovered units.
322  * @param enemy_count      Incremented for each enemy uncovered (excluding known_units).
323  * @param friend_count     Incremented for each friend uncovered (excluding known_units).
324  * @param spectator        Will be told of uncovered units (excluding known_units).
325  * @param instant          If false, then drawing delays (used to make movement look better) are allowed.
326  *
327  * @return whether or not information was uncovered (i.e. returns true if any
328  *         locations in visual range were fogged/shrouded under shared vision/maps).
329  */
clear_unit(const map_location & view_loc,team & view_team,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,size_t * enemy_count,size_t * friend_count,move_unit_spectator * spectator,bool instant)330 bool shroud_clearer::clear_unit(const map_location &view_loc, team &view_team,
331                                 size_t viewer_id, int sight_range, bool slowed,
332                                 const movetype::terrain_costs & costs,
333                                 const map_location & real_loc,
334                                 const std::set<map_location>* known_units,
335                                 size_t * enemy_count, size_t * friend_count,
336                                 move_unit_spectator * spectator, bool instant)
337 {
338 	// Give animations a chance to progress; see bug #20324.
339 	if ( !instant  && display::get_singleton() )
340 		display::get_singleton()->draw(true);
341 
342 	bool cleared_something = false;
343 	// Dummy variables to make some logic simpler.
344 	size_t enemies=0, friends=0;
345 	if ( enemy_count == nullptr )
346 		enemy_count = &enemies;
347 	if ( friend_count == nullptr )
348 		friend_count = &friends;
349 
350 	// Make sure the jamming map is up-to-date.
351 	if ( view_team_ != &view_team ) {
352 		calculate_jamming(&view_team);
353 		// Give animations a chance to progress; see bug #20324.
354 		if ( !instant  && display::get_singleton() )
355 			display::get_singleton()->draw(true);
356 	}
357 
358 	// Determine the hexes to clear.
359 	pathfind::vision_path sight(costs, slowed, sight_range, view_loc, jamming_);
360 	// Give animations a chance to progress; see bug #20324.
361 	if ( !instant  && display::get_singleton() )
362 		display::get_singleton()->draw(true);
363 
364 	// Clear the fog.
365 	for (const pathfind::paths::step &dest : sight.destinations) {
366 		bool known = known_units  &&  known_units->count(dest.curr) != 0;
367 		if ( clear_loc(view_team, dest.curr, view_loc, real_loc, viewer_id, !known,
368 		               *enemy_count, *friend_count, spectator) )
369 			cleared_something = true;
370 	}
371 	//TODO guard with game_config option
372 	for (const map_location &dest : sight.edges) {
373 		bool known = known_units  &&  known_units->count(dest) != 0;
374 		if ( clear_loc(view_team, dest, view_loc, real_loc, viewer_id, !known,
375 		               *enemy_count, *friend_count, spectator) )
376 			cleared_something = true;
377 	}
378 
379 	return cleared_something;
380 }
381 
382 
383 /**
384  * Clears shroud (and fog) around the provided location for @a view_team
385  * as if @a viewer was standing there.
386  * This will also record sighted events, which should be either fired or
387  * explicitly dropped.
388  *
389  * This should only be called if delayed shroud updates is off.
390  * It is wasteful to call this if view_team uses neither fog nor shroud.
391  *
392  * @param known_units      These locations are not checked for uncovered units.
393  * @param enemy_count      Incremented for each enemy uncovered (excluding known_units).
394  * @param friend_count     Incremented for each friend uncovered (excluding known_units).
395  * @param spectator        Will be told of uncovered units (excluding known_units).
396  * @param instant          If false, then drawing delays (used to make movement look better) are allowed.
397  *
398  * @return whether or not information was uncovered (i.e. returns true if any
399  *         locations in visual range were fogged/shrouded under shared vision/maps).
400  */
clear_unit(const map_location & view_loc,const unit & viewer,team & view_team,const std::set<map_location> * known_units,size_t * enemy_count,size_t * friend_count,move_unit_spectator * spectator,bool instant)401 bool shroud_clearer::clear_unit(const map_location &view_loc,
402                                 const unit &viewer, team &view_team,
403                                 const std::set<map_location>* known_units,
404                                 size_t * enemy_count, size_t * friend_count,
405                                 move_unit_spectator * spectator, bool instant)
406 {
407 	// This is just a translation to the more general interface. It is
408 	// not inlined so that vision.hpp does not have to include unit.hpp.
409 	return clear_unit(view_loc, view_team, viewer.underlying_id(),
410 	                  viewer.vision(), viewer.get_state(unit::STATE_SLOWED),
411 	                  viewer.movement_type().get_vision(), viewer.get_location(),
412 	                  known_units, enemy_count, friend_count, spectator, instant);
413 }
414 
415 
416 /**
417  * Clears shroud (and fog) around the provided location for @a view_team
418  * as if @a viewer was standing there.
419  * This will also record sighted events, which should be either fired or
420  * explicitly dropped.
421  *
422  * This should only be called if delayed shroud updates is off.
423  * It is wasteful to call this if view_team uses neither fog nor shroud.
424  *
425  * @param instant          If false, then drawing delays (used to make movement look better) are allowed.
426  *
427  * @return whether or not information was uncovered (i.e. returns true if any
428  *         locations in visual range were fogged/shrouded under shared vision/maps).
429  */
clear_unit(const map_location & view_loc,team & view_team,const clearer_info & viewer,bool instant)430 bool shroud_clearer::clear_unit(const map_location &view_loc, team &view_team,
431                                 const clearer_info &viewer, bool instant)
432 {
433 	// Locate the unit in question.
434 	unit_map::const_iterator find_it = resources::gameboard->units().find(viewer.underlying_id);
435 	const map_location & real_loc = find_it == resources::gameboard->units().end() ?
436 		                                map_location::null_location() :
437 		                                find_it->get_location();
438 
439 	return clear_unit(view_loc, view_team, viewer.underlying_id,
440 	                  viewer.sight_range, viewer.slowed, *viewer.costs,
441 	                  real_loc, nullptr, nullptr, nullptr, nullptr, instant);
442 }
443 
444 
445 /**
446  * Clears shroud (and fog) around the provided location as if @a viewer
447  * was standing there.
448  * This version of shroud_clearer::clear_unit() will abort if the viewer's
449  * team uses neither fog nor shroud. If @a can_delay is left as true, then
450  * this function also aborts on the viewing team's turn if delayed shroud
451  * updates is on. (Not supplying a team suggests that it would be inconvenient
452  * for the caller to check these.)
453  * In addition, if @a invalidate is left as true, invalidate_after_clear()
454  * will be called.
455  * Setting @a instant to false allows some drawing delays that are used to
456  * make movement look better.
457  *
458  * @return whether or not information was uncovered (i.e. returns true if any
459  *         locations in visual range were fogged/shrouded under shared vision/maps).
460  */
clear_unit(const map_location & view_loc,const unit & viewer,bool can_delay,bool invalidate,bool instant)461 bool shroud_clearer::clear_unit(const map_location &view_loc, const unit &viewer,
462                                 bool can_delay, bool invalidate, bool instant)
463 {
464 	team & viewing_team = resources::gameboard->get_team(viewer.side());
465 
466 	// Abort if there is nothing to clear.
467 	if ( !viewing_team.fog_or_shroud() )
468 		return false;
469 	if ( can_delay  &&  !viewing_team.auto_shroud_updates()  &&
470 	     viewer.side() == resources::controller->current_side()  )
471 		return false;
472 
473 	if ( !clear_unit(view_loc, viewer, viewing_team, instant) )
474 		// Nothing uncovered.
475 		return false;
476 
477 	if ( invalidate )
478 		invalidate_after_clear();
479 
480 	return true;
481 }
482 
483 
484 /**
485  * Clears shroud (and fog) at the provided location and its immediate neighbors.
486  * This is an aid for the [teleport] action, allowing the destination to be
487  * cleared before teleporting, while the unit's full visual range gets cleared
488  * after.
489  * The @a viewer is needed for correct firing of sighted events.
490  *
491  * @return whether or not information was uncovered (i.e. returns true if the
492  *         locations in question were fogged/shrouded under shared vision/maps).
493  */
clear_dest(const map_location & dest,const unit & viewer)494 bool shroud_clearer::clear_dest(const map_location &dest, const unit &viewer)
495 {
496 	team & viewing_team = resources::gameboard->get_team(viewer.side());
497 	// A pair of dummy variables needed to simplify some logic.
498 	size_t enemies, friends;
499 
500 	// Abort if there is nothing to clear.
501 	if ( !viewing_team.fog_or_shroud() )
502 		return false;
503 
504 	// Cache some values.
505 	const map_location & real_loc = viewer.get_location();
506 	const size_t viewer_id = viewer.underlying_id();
507 
508 	// Clear the destination.
509 	bool cleared_something = clear_loc(viewing_team, dest, dest, real_loc,
510 	                                   viewer_id, true, enemies, friends);
511 
512 	// Clear the adjacent hexes (will be seen even if vision is 0, and the
513 	// graphics do not work so well for an isolated cleared hex).
514 	adjacent_loc_array_t adjacent;
515 	get_adjacent_tiles(dest, adjacent.data());
516 	for (unsigned i = 0; i < adjacent.size(); ++i )
517 		if ( clear_loc(viewing_team, adjacent[i], dest, real_loc, viewer_id,
518 		               true, enemies, friends) )
519 			cleared_something = true;
520 
521 	if ( cleared_something )
522 		invalidate_after_clear();
523 
524 	return cleared_something;
525 }
526 
527 
528 /**
529  * Clears the record of sighted events from earlier fog/shroud clearing.
530  * This should be called if the events are to be ignored and not fired.
531  * (Non-cleared, non-fired events will be logged as an error.)
532  */
drop_events()533 void shroud_clearer::drop_events()
534 {
535 	if ( !sightings_.empty() ) {
536 		DBG_NG << sightings_.size() << " sighted events were dropped.\n";
537 	}
538 	sightings_.clear();
539 }
540 
541 
542 /**
543  * Fires the sighted events that were recorded by earlier fog/shroud clearing.
544  * @return true if the events have mutated the game state.
545  */
fire_events()546 game_events::pump_result_t shroud_clearer::fire_events()
547 {
548 	const unit_map & units = resources::gameboard->units();
549 
550 	// Possible/probable quick abort.
551 	if ( sightings_.empty() )
552 		return game_events::pump_result_t();
553 
554 	// In case of exceptions, clear sightings_ before processing events.
555 	std::vector<sight_data> sight_list;
556 	sight_list.swap(sightings_);
557 
558 	for (const sight_data & event : sight_list) {
559 		// Try to locate the sighting unit.
560 		unit_map::const_iterator find_it = units.find(event.sighter_id);
561 		const map_location & sight_loc =
562 			find_it == units.end() ? map_location::null_location() :
563 			                         find_it->get_location();
564 
565 		{	// Raise the event based on the latest data.
566 			resources::game_events->pump().raise(sighted_str,
567 			      game_events::entity_location(event.seen_loc, event.seen_id),
568 			      game_events::entity_location(sight_loc, event.sighter_id, event.sighter_loc));
569 		}
570 	}
571 
572 	return resources::game_events->pump()();
573 }
574 
575 
576 /**
577  * The invalidations that should occur after invoking clear_unit().
578  * This is separate since clear_unit() might be invoked several
579  * times in a row, and the invalidations might only need to be done once.
580  */
invalidate_after_clear()581 void shroud_clearer::invalidate_after_clear()
582 {
583 	display::get_singleton()->invalidate_game_status();
584 	display::get_singleton()->recalculate_minimap();
585 	display::get_singleton()->labels().recalculate_shroud();
586 	// The tiles are invalidated as they are cleared, so no need
587 	// to invalidate them here.
588 }
589 
590 
591 /**
592  * Returns the sides that cannot currently see @a target.
593  * (Used to cache visibility before a move.)
594  */
get_sides_not_seeing(const unit & target)595 std::vector<int> get_sides_not_seeing(const unit & target)
596 {
597 	const std::vector<team> & teams = resources::gameboard->teams();
598 	std::vector<int> not_seeing;
599 
600 	size_t team_size = teams.size();
601 	for ( size_t i = 0; i != team_size; ++i)
602 		if ( !target.is_visible_to_team(teams[i], *resources::gameboard, false) )
603 			// not_see contains side numbers; i is a team index, so add 1.
604 			not_seeing.push_back(i+1);
605 
606 	return not_seeing;
607 }
608 
609 
610 /**
611  * Fires sighted events for the sides that can see @a target.
612  * If @a cache is supplied, only those sides might get events.
613  * If @a cache is nullptr, all sides might get events.
614  * This function is for the sighting *of* units that clear the shroud; it is
615  * the complement of shroud_clearer::fire_events(), which handles sighting *by*
616  * units that clear the shroud.
617  *
618  * See get_sides_not_seeing() for a way to obtain a cache.
619  *
620  * @returns true if an event has mutated the game state.
621  */
actor_sighted(const unit & target,const std::vector<int> * cache)622 game_events::pump_result_t actor_sighted(const unit & target, const std::vector<int> * cache)
623 /* Current logic:
624  * 1) One event is fired per side that can see the target.
625  * 2) The second unit for the event is one that can see the target, if possible.
626  * 3) If no units on a side can see the target, a second unit is chosen as
627  *    close as possible (but this behavior should not be relied on; it is
628  *    subject to change at any time, should it become inconvenient).
629  * 4) A side with no units at all will not get a sighted event.
630  * 5) Sides that do not use fog or shroud CAN get sighted events.
631  */
632 {
633 	const std::vector<team> & teams = resources::gameboard->teams();
634 	const size_t teams_size = teams.size();
635 	const map_location & target_loc = target.get_location();
636 
637 	// Determine the teams that (probably) should get events.
638 	boost::dynamic_bitset<> needs_event;
639 	needs_event.resize(teams_size, cache == nullptr);
640 	if ( cache != nullptr ) {
641 		// Flag just the sides in the cache as needing events.
642 		for (int side : *cache)
643 			needs_event[side-1] = true;
644 	}
645 	// Exclude the target's own team.
646 	needs_event[target.side()-1] = false;
647 	// Exclude those teams that cannot see the target.
648 	for ( size_t i = 0; i != teams_size; ++i )
649 		needs_event[i] = needs_event[i] && target.is_visible_to_team(teams[i], *resources::gameboard, false);
650 
651 	// Cache "jamming".
652 	std::vector< std::map<map_location, int>> jamming_cache(teams_size);
653 	for ( size_t i = 0; i != teams_size; ++i )
654 		if ( needs_event[i] )
655 			create_jamming_map(jamming_cache[i], teams[i]);
656 
657 	// Look for units that can be used as the second unit in sighted events.
658 	std::vector<const unit *> second_units(teams_size, nullptr);
659 	std::vector<size_t> distances(teams_size, UINT_MAX);
660 	for (const unit & viewer : resources::gameboard->units()) {
661 		const size_t index = viewer.side() - 1;
662 		// Does viewer belong to a team for which we still need a unit?
663 		if ( needs_event[index]  &&  distances[index] != 0 ) {
664 			if ( can_see(viewer, target_loc, &jamming_cache[index]) ) {
665 				// Definitely use viewer as the second unit.
666 				second_units[index] = &viewer;
667 				distances[index] = 0;
668 			}
669 			else {
670 				// Consider viewer as a backup if it is close.
671 				size_t viewer_distance =
672 					distance_between(target_loc, viewer.get_location());
673 				if ( viewer_distance < distances[index] ) {
674 					second_units[index] = &viewer;
675 					distances[index] = viewer_distance;
676 				}
677 			}
678 		}
679 	}
680 
681 	// Raise events for the appropriate teams.
682 	const game_events::entity_location target_entity(target);
683 	for ( size_t i = 0; i != teams_size; ++i )
684 		if ( second_units[i] != nullptr ) {
685 			resources::game_events->pump().raise(sighted_str, target_entity, game_events::entity_location(*second_units[i]));
686 		}
687 
688 	// Fire the events and return.
689 	return resources::game_events->pump()();
690 }
691 
692 
693 /**
694  * Function that recalculates the fog of war.
695  *
696  * This is used at the end of a turn and for the defender at the end of
697  * combat. As a back-up, it is also called when clearing shroud at the
698  * beginning of a turn.
699  * This function does nothing if the indicated side does not use fog.
700  * This function ignores the "delayed shroud updates" setting.
701  * The display is invalidated as needed.
702  *
703  * @param[in] side The side whose fog will be recalculated.
704  */
recalculate_fog(int side)705 void recalculate_fog(int side)
706 {
707 	team &tm = resources::gameboard->get_team(side);
708 
709 	if (!tm.uses_fog())
710 		return;
711 
712 	// Exclude currently seen units from sighted events.
713 	std::set<map_location> visible_locs;
714 	for (const unit &u : resources::gameboard->units()) {
715 		const map_location & u_location = u.get_location();
716 
717 		if ( !tm.fogged(u_location) )
718 			visible_locs.insert(u_location);
719 	}
720 
721 	tm.refog();
722 	// Invalidate the screen before clearing the shroud.
723 	// This speeds up the invalidations within clear_shroud_unit().
724 	display::get_singleton()->invalidate_all();
725 
726 	shroud_clearer clearer;
727 	for (const unit &u : resources::gameboard->units())
728 	{
729 		if ( u.side() == side )
730 			clearer.clear_unit(u.get_location(), u, tm, &visible_locs);
731 	}
732 	// Update the screen.
733 	clearer.invalidate_after_clear();
734 
735 	// Fire any sighted events we picked up.
736 	clearer.fire_events();
737 }
738 
739 
740 /**
741  * Function that will clear shroud (and fog) based on current unit positions.
742  *
743  * This will not re-fog hexes unless reset_fog is set to true.
744  * This function will do nothing if the side uses neither shroud nor fog.
745  * This function ignores the "delayed shroud updates" setting.
746  * The display is invalidated as needed.
747  *
748  * @param[in] side         The side whose shroud (and fog) will be cleared.
749  * @param[in] reset_fog    If set to true, the fog will also be recalculated
750  *                         (refogging hexes that can no longer be seen).
751  * @param[in] fire_events  If set to false, sighted events will not be fired.
752  * @returns true if some shroud/fog is actually cleared away.
753  */
clear_shroud(int side,bool reset_fog,bool fire_events)754 bool clear_shroud(int side, bool reset_fog, bool fire_events)
755 {
756 	team &tm = resources::gameboard->get_team(side);
757 	if (!tm.uses_shroud() && !tm.uses_fog())
758 		return false;
759 
760 	bool result = false;
761 
762 	shroud_clearer clearer;
763 	for (const unit &u : resources::gameboard->units())
764 	{
765 		if ( u.side() == side )
766 			result |= clearer.clear_unit(u.get_location(), u, tm);
767 	}
768 	// Update the screen.
769 	if ( result )
770 		clearer.invalidate_after_clear();
771 
772 	// Sighted events.
773 	if ( fire_events )
774 		clearer.fire_events();
775 	else
776 		clearer.drop_events();
777 
778 	if ( reset_fog ) {
779 		// Note: This will not reveal any new tiles, so result is not affected.
780 		//       Also, we do not have to check fire_events at this point.
781 		recalculate_fog(side);
782 	}
783 
784 	return result;
785 }
786 
787 }//namespace actions
788