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