The Battle for Wesnoth  1.17.0-dev
halo.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2021
3  by David White <dave@whitevine.net>
4  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
5 
6  This program is free software; you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation; either version 2 of the License, or
9  (at your option) any later version.
10  This program is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY.
12 
13  See the COPYING file for more details.
14 */
15 
16 /**
17  * @file
18  * Maintain halo-effects for units and items.
19  * Examples: white mage, lighthouse.
20  */
21 
22 #include "animated.hpp"
23 #include "display.hpp"
24 #include "preferences/game.hpp"
25 #include "halo.hpp"
26 #include "log.hpp"
28 
29 #include <iostream>
30 
31 static lg::log_domain log_display("display");
32 #define ERR_DP LOG_STREAM(err, log_display)
33 
34 namespace halo
35 {
36 
37 class halo_impl
38 {
39 
40 class effect
41 {
42 public:
43  effect(display * screen, int xpos, int ypos, const animated<image::locator>::anim_description& img,
44  const map_location& loc, ORIENTATION, bool infinite);
45 
46  void set_location(int x, int y);
47 
48  bool render();
49  void unrender();
50 
51  bool expired() const { return !images_.cycles() && images_.animation_finished(); }
52  bool need_update() const { return images_.need_update(); }
53  bool does_change() const { return !images_.does_not_change(); }
54  bool on_location(const std::set<map_location>& locations) const;
55  bool location_not_known() const;
56 
57  void add_overlay_location(std::set<map_location>& locations);
58 private:
59 
61 
63 
65 
66  int x_, y_;
68  SDL_Rect rect_;
69 
70  /** The location of the center of the halo. */
72 
73  /** All locations over which the halo lies. */
74  std::vector<map_location> overlayed_hexes_;
75 
77 };
78 
80 
81 std::map<int, effect> haloes;
82 int halo_id;
83 
84 /**
85  * Upon unrendering, an invalidation list is send. All haloes in that area and
86  * the other invalidated haloes are stored in this set. Then there'll be
87  * tested which haloes overlap and they're also stored in this set.
88  */
89 std::set<int> invalidated_haloes;
90 
91 /**
92  * Upon deleting, a halo isn't deleted but added to this set, upon unrendering
93  * the image is unrendered and deleted.
94  */
95 std::set<int> deleted_haloes;
96 
97 /**
98  * Haloes that have an animation or expiration time need to be checked every
99  * frame and are stored in this set.
100  */
101 std::set<int> changing_haloes;
102 
103 public:
104 /**
105  * impl's of exposed functions
106  */
107 
108 explicit halo_impl(display & screen) :
109  disp(&screen),
110  haloes(),
111  halo_id(1),
112  invalidated_haloes(),
113  deleted_haloes(),
114  changing_haloes()
115 {}
116 
117 
118 int add(int x, int y, const std::string& image, const map_location& loc,
119  ORIENTATION orientation=NORMAL, bool infinite=true);
120 
121 /** Set the position of an existing haloing effect, according to its handle. */
122 void set_location(int handle, int x, int y);
123 
124 /** Remove the halo with the given handle. */
125 void remove(int handle);
126 
127 /**
128  * Render and unrender haloes.
129  *
130  * Which haloes are rendered is determined by invalidated_locations and the
131  * internal state in the control sets (in halo.cpp).
132  */
133 void unrender(std::set<map_location> invalidated_locations);
134 void render();
135 
136 }; //end halo_impl
137 
139  const map_location& loc, ORIENTATION orientation, bool infinite) :
140  images_(img),
141  orientation_(orientation),
142  x_(0),
143  y_(0),
144  surf_(nullptr),
145  buffer_(nullptr),
146  rect_(sdl::empty_rect),
147  loc_(loc),
149  disp(screen)
150 {
151  assert(disp != nullptr);
152 
153  set_location(xpos,ypos);
154 
155  images_.start_animation(0,infinite);
156 
157 }
158 
160 {
161  int new_x = x - disp->get_location_x(map_location::ZERO());
162  int new_y = y - disp->get_location_y(map_location::ZERO());
163  if (new_x != x_ || new_y != y_) {
164  x_ = new_x;
165  y_ = new_y;
166  buffer_ = nullptr;
167  overlayed_hexes_.clear();
168  }
169 }
170 
172 {
173  if(disp == nullptr) {
174  return false;
175  }
176 
177  if(loc_.x != -1 && loc_.y != -1) {
178  if(disp->shrouded(loc_)) {
179  return false;
180  } else {
181  // The location of a halo is an x,y value and not a map location.
182  // This means when a map is zoomed, the halo's won't move,
183  // This glitch is most visible on [item] haloes.
184  // This workaround always recalculates the location of the halo
185  // (item haloes have a location parameter to hide them under the shroud)
186  // and reapplies that location.
187  // It might be optimized by storing and comparing the zoom value.
188  set_location(
189  disp->get_location_x(loc_) + disp->hex_size() / 2,
190  disp->get_location_y(loc_) + disp->hex_size() / 2);
191  }
192  }
193 
196  if(surf_ == nullptr) {
197  return false;
198  }
201  }
204  }
205 
206  const int screenx = disp->get_location_x(map_location::ZERO());
207  const int screeny = disp->get_location_y(map_location::ZERO());
208 
209  const int xpos = x_ + screenx - surf_->w/2;
210  const int ypos = y_ + screeny - surf_->h/2;
211 
212  SDL_Rect rect {xpos, ypos, surf_->w, surf_->h};
213  rect_ = rect;
214  SDL_Rect clip_rect = disp->map_outside_area();
215 
216  // If rendered the first time, need to determine the area affected.
217  // If a halo changes size, it is not updated.
218  if(location_not_known()) {
220  display::rect_of_hexes::iterator i = hexes.begin(), end = hexes.end();
221  for (;i != end; ++i) {
222  overlayed_hexes_.push_back(*i);
223  }
224  }
225 
226  if(sdl::rects_overlap(rect,clip_rect) == false) {
227  buffer_ = nullptr;
228  return false;
229  }
230 
231  surface& screen = disp->get_screen_surface();
232 
233  const clip_rect_setter clip_setter(screen, &clip_rect);
234  if(buffer_ == nullptr || buffer_->w != rect.w || buffer_->h != rect.h) {
235  SDL_Rect rect2 = rect_;
236  buffer_ = get_surface_portion(screen,rect2);
237  } else {
238  SDL_Rect rect2 = rect_;
239  sdl_copy_portion(screen,&rect2,buffer_,nullptr);
240  }
241 
242  sdl_blit(surf_,nullptr,screen,&rect);
243 
244  return true;
245 }
246 
248 {
249  if (!surf_ || !buffer_) {
250  return;
251  }
252 
253  // Shrouded haloes are never rendered unless shroud has been re-placed; in
254  // that case, unrendering causes the hidden terrain (and previous halo
255  // frame, when dealing with animated halos) to glitch through shroud. We
256  // don't need to unrender them because shroud paints over the underlying
257  // area anyway.
258  if (loc_.x != -1 && loc_.y != -1 && disp->shrouded(loc_)) {
259  return;
260  }
261 
262  surface& screen = disp->get_screen_surface();
263 
264  SDL_Rect clip_rect = disp->map_outside_area();
265  const clip_rect_setter clip_setter(screen, &clip_rect);
266 
267  // Due to scrolling, the location of the rendered halo
268  // might have changed; recalculate
269  const int screenx = disp->get_location_x(map_location::ZERO());
270  const int screeny = disp->get_location_y(map_location::ZERO());
271 
272  const int xpos = x_ + screenx - surf_->w/2;
273  const int ypos = y_ + screeny - surf_->h/2;
274 
275  SDL_Rect rect {xpos, ypos, surf_->w, surf_->h};
276  sdl_blit(buffer_,nullptr,screen,&rect);
277 }
278 
279 bool halo_impl::effect::on_location(const std::set<map_location>& locations) const
280 {
281  for(std::vector<map_location>::const_iterator itor = overlayed_hexes_.begin();
282  itor != overlayed_hexes_.end(); ++itor) {
283  if(locations.find(*itor) != locations.end()) {
284  return true;
285  }
286  }
287  return false;
288 }
289 
291 {
292  return overlayed_hexes_.empty();
293 }
294 
295 void halo_impl::effect::add_overlay_location(std::set<map_location>& locations)
296 {
297  for(std::vector<map_location>::const_iterator itor = overlayed_hexes_.begin();
298  itor != overlayed_hexes_.end(); ++itor) {
299 
300  locations.insert(*itor);
301  }
302 }
303 
304 // End halo_impl::effect impl's
305 
306 int halo_impl::add(int x, int y, const std::string& image, const map_location& loc,
307  ORIENTATION orientation, bool infinite)
308 {
309  const int id = halo_id++;
311  std::vector<std::string> items = utils::square_parenthetical_split(image, ',');
312 
313  for(const std::string& item : items) {
314  const std::vector<std::string>& sub_items = utils::split(item, ':');
315  std::string str = item;
316  int time = 100;
317 
318  if(sub_items.size() > 1) {
319  str = sub_items.front();
320  try {
321  time = std::stoi(sub_items.back());
322  } catch(const std::invalid_argument&) {
323  ERR_DP << "Invalid time value found when constructing halo: " << sub_items.back() << "\n";
324  }
325  }
326  image_vector.push_back(animated<image::locator>::frame_description(time,image::locator(str)));
327 
328  }
329  haloes.emplace(id, effect(disp, x, y, image_vector, loc, orientation, infinite));
330  invalidated_haloes.insert(id);
331  if(haloes.find(id)->second.does_change() || !infinite) {
332  changing_haloes.insert(id);
333  }
334  return id;
335 }
336 
337 void halo_impl::set_location(int handle, int x, int y)
338 {
339  const std::map<int,effect>::iterator itor = haloes.find(handle);
340  if(itor != haloes.end()) {
341  itor->second.set_location(x,y);
342  }
343 }
344 
346 {
347  // Silently ignore invalid haloes.
348  // This happens when Wesnoth is being terminated as well.
349  if(handle == NO_HALO || haloes.find(handle) == haloes.end()) {
350  return;
351  }
352 
353  deleted_haloes.insert(handle);
354 }
355 
356 void halo_impl::unrender(std::set<map_location> invalidated_locations)
357 {
358  if(haloes.empty()) {
359  return;
360  }
361  //assert(invalidated_haloes.empty());
362 
363  // Remove expired haloes
365  for(; itor != haloes.end(); ++itor ) {
366  if(itor->second.expired()) {
367  deleted_haloes.insert(itor->first);
368  }
369  }
370 
371  // Add the haloes marked for deletion to the invalidation set
372  std::set<int>::const_iterator set_itor = deleted_haloes.begin();
373  for(;set_itor != deleted_haloes.end(); ++set_itor) {
374  invalidated_haloes.insert(*set_itor);
375  haloes.find(*set_itor)->second.add_overlay_location(invalidated_locations);
376  }
377 
378  // Test the multi-frame haloes whether they need an update
379  for(set_itor = changing_haloes.begin();
380  set_itor != changing_haloes.end(); ++set_itor) {
381  if(haloes.find(*set_itor)->second.need_update()) {
382  invalidated_haloes.insert(*set_itor);
383  haloes.find(*set_itor)->second.add_overlay_location(invalidated_locations);
384  }
385  }
386 
387  // Find all halo's in a the invalidated area
388  size_t halo_count;
389 
390  // Repeat until set of haloes in the invalidated area didn't change
391  // (including none found) or all existing haloes are found.
392  do {
393  halo_count = invalidated_haloes.size();
394  for(itor = haloes.begin(); itor != haloes.end(); ++itor) {
395  // Test all haloes not yet in the set
396  // which match one of the locations
397  if(invalidated_haloes.find(itor->first) == invalidated_haloes.end() &&
398  (itor->second.location_not_known() ||
399  itor->second.on_location(invalidated_locations))) {
400 
401  // If found, add all locations which the halo invalidates,
402  // and add it to the set
403  itor->second.add_overlay_location(invalidated_locations);
404  invalidated_haloes.insert(itor->first);
405  }
406  }
407  } while (halo_count != invalidated_haloes.size() && halo_count != haloes.size());
408 
409  if(halo_count == 0) {
410  return;
411  }
412 
413  // Render the haloes:
414  // iterate through all the haloes and invalidate if in set
415  for(std::map<int, effect>::reverse_iterator ritor = haloes.rbegin(); ritor != haloes.rend(); ++ritor) {
416  if(invalidated_haloes.find(ritor->first) != invalidated_haloes.end()) {
417  ritor->second.unrender();
418  }
419  }
420 
421  // Really delete the haloes marked for deletion
422  for(set_itor = deleted_haloes.begin(); set_itor != deleted_haloes.end(); ++set_itor) {
423  changing_haloes.erase(*set_itor);
424  invalidated_haloes.erase(*set_itor);
425  haloes.erase(*set_itor);
426  }
427 
428  deleted_haloes.clear();
429 }
430 
432 {
433  if(haloes.empty() ||
434  invalidated_haloes.empty()) {
435  return;
436  }
437 
438  // Render the haloes: draw all invalidated haloes
439  for(int id : invalidated_haloes) {
440  haloes.at(id).render();
441  }
442 
443  invalidated_haloes.clear();
444 }
445 
446 // end halo_impl implementations
447 
448 // begin halo::manager
449 
450 manager::manager(display& screen) : impl_(new halo_impl(screen))
451 {}
452 
453 handle manager::add(int x, int y, const std::string& image, const map_location& loc,
454  ORIENTATION orientation, bool infinite)
455 {
456  int new_halo = impl_->add(x,y,image, loc, orientation, infinite);
457  return handle(new halo_record(new_halo, impl_));
458 }
459 
460 /** Set the position of an existing haloing effect, according to its handle. */
461 void manager::set_location(const handle & h, int x, int y)
462 {
463  impl_->set_location(h->id_,x,y);
464 }
465 
466 /** Remove the halo with the given handle. */
467 void manager::remove(const handle & h)
468 {
469  impl_->remove(h->id_);
470  h->id_ = NO_HALO;
471 }
472 
473 /**
474  * Render and unrender haloes.
475  *
476  * Which haloes are rendered is determined by invalidated_locations and the
477  * internal state in the control sets (in halo.cpp).
478  */
479 void manager::unrender(std::set<map_location> invalidated_locations)
480 {
481  impl_->unrender(invalidated_locations);
482 }
483 
485 {
486  impl_->render();
487 }
488 
489 // end halo::manager implementation
490 
491 
492 /**
493  * halo::halo_record implementation
494  */
495 
497  id_(NO_HALO), //halo::NO_HALO
498  my_manager_()
499 {}
500 
501 halo_record::halo_record(int id, const std::shared_ptr<halo_impl> & my_manager) :
502  id_(id),
503  my_manager_(my_manager)
504 {}
505 
507 {
508  if (!valid()) return;
509 
510  std::shared_ptr<halo_impl> man = my_manager_.lock();
511 
512  if(man) {
513  man->remove(id_);
514  }
515 }
516 
517 } //end namespace halo
surface get_image(const image::locator &i_locator, TYPE type)
Caches and returns an image.
Definition: picture.cpp:816
std::vector< map_location > overlayed_hexes_
All locations over which the halo lies.
Definition: halo.cpp:74
static int hex_size()
Function which returns the size of a hex in pixels (from top tip to bottom tip or left edge to right ...
Definition: display.hpp:260
bool cycles() const
Definition: animated.hpp:72
static const map_location & ZERO()
Definition: location.hpp:75
animated< image::locator > images_
Definition: halo.cpp:62
bool rects_overlap(const SDL_Rect &rect1, const SDL_Rect &rect2)
Tests whether two rectangles overlap.
Definition: rect.cpp:34
surface reverse_image(const surface &surf)
Horizontally flips an image.
Definition: picture.cpp:989
bool need_update() const
halo_impl(display &screen)
impl&#39;s of exposed functions
Definition: halo.cpp:108
map_location loc_
The location of the center of the halo.
Definition: halo.cpp:71
const int NO_HALO
Definition: halo.hpp:36
std::set< int > deleted_haloes
Upon deleting, a halo isn&#39;t deleted but added to this set, upon unrendering the image is unrendered a...
Definition: halo.cpp:95
bool valid() const
Definition: halo.hpp:87
#define h
bool need_update() const
Definition: halo.cpp:52
const std::vector< std::string > items
std::map< int, effect > haloes
Definition: halo.cpp:81
surface get_surface_portion(const surface &src, SDL_Rect &area)
Get a portion of the screen.
Definition: utils.cpp:2158
Rectangular area of hexes, allowing to decide how the top and bottom edges handles the vertical shift...
Definition: display.hpp:306
std::weak_ptr< halo_impl > my_manager_
Definition: halo.hpp:94
static lg::log_domain log_display("display")
void unrender(std::set< map_location > invalidated_locations)
Render and unrender haloes.
Definition: halo.cpp:356
very simple iterator to walk into the rect_of_hexes
Definition: display.hpp:313
iterator begin() const
Definition: display.cpp:679
halo_record()
halo::halo_record implementation
Definition: halo.cpp:496
void add_overlay_location(std::set< map_location > &locations)
Definition: halo.cpp:295
#define ERR_DP
Definition: halo.cpp:32
bool location_not_known() const
Definition: halo.cpp:290
surface flop_surface(const surface &surf)
Definition: utils.cpp:1981
effect(display *screen, int xpos, int ypos, const animated< image::locator >::anim_description &img, const map_location &loc, ORIENTATION, bool infinite)
Definition: halo.cpp:138
Animate units.
ORIENTATION orientation_
Definition: halo.cpp:64
bool does_not_change() const
Definition: animated.hpp:101
manager(display &screen)
Definition: halo.cpp:450
map_display and display: classes which take care of displaying the map and game-data on the screen...
void render()
Definition: halo.cpp:431
const rect_of_hexes hexes_under_rect(const SDL_Rect &r) const
Return the rectangular area of hexes overlapped by r (r is in screen coordinates) ...
Definition: display.cpp:688
void remove(int handle)
Remove the halo with the given handle.
Definition: halo.cpp:345
Generic locator abstracting the location of an image.
Definition: picture.hpp:60
handle add(int x, int y, const std::string &image, const map_location &loc, halo::ORIENTATION orientation=NORMAL, bool infinite=true)
Add a haloing effect using &#39;image centered on (x,y).
Definition: halo.cpp:453
Image rescaled according to the zoom settings.
Definition: picture.hpp:234
bool on_location(const std::set< map_location > &locations) const
Definition: halo.cpp:279
Encapsulates the map of the game.
Definition: location.hpp:38
bool shrouded(const map_location &loc) const
Returns true if location (x,y) is covered in shroud.
Definition: display.cpp:740
int add(int x, int y, const std::string &image, const map_location &loc, ORIENTATION orientation=NORMAL, bool infinite=true)
Definition: halo.cpp:306
std::string id
Text to match against addon_info.tags()
Definition: manager.cpp:215
surface & get_screen_surface()
return the screen surface or the surface used for map_screenshot.
Definition: display.hpp:204
std::size_t i
Definition: function.cpp:967
const T & get_current_frame() const
std::shared_ptr< halo_impl > impl_
Definition: halo.hpp:71
void update_last_draw_time(double acceleration=0)
std::set< int > changing_haloes
Haloes that have an animation or expiration time need to be checked every frame and are stored in thi...
Definition: halo.cpp:101
bool expired() const
Definition: halo.cpp:51
pump_impl & impl_
Definition: pump.cpp:135
int get_location_y(const map_location &loc) const
Definition: display.cpp:755
const SDL_Rect & map_outside_area() const
Returns the available area for a map, this may differ from the above.
Definition: display.hpp:242
void start_animation(int start_time, bool cycles=false)
Starts an animation cycle.
void unrender(std::set< map_location > invalidated_locations)
Render and unrender haloes.
Definition: halo.cpp:479
void set_location(int x, int y)
Definition: halo.cpp:159
void set_location(const handle &h, int x, int y)
Set the position of an existing haloing effect, according to its handle.
Definition: halo.cpp:461
void render()
Definition: halo.cpp:484
std::set< int > invalidated_haloes
Upon unrendering, an invalidation list is send.
Definition: halo.cpp:89
Definition: display.hpp:45
int get_location_x(const map_location &loc) const
Functions to get the on-screen positions of hexes.
Definition: display.cpp:750
ORIENTATION
Definition: halo.hpp:34
iterator end() const
Definition: display.cpp:683
constexpr const SDL_Rect empty_rect
Definition: rect.hpp:32
std::vector< std::string > split(const config_attribute_value &val)
Functions to load and save images from/to disk.
Standard logging facilities (interface).
void sdl_copy_portion(const surface &screen, SDL_Rect *screen_rect, surface &dst, SDL_Rect *dst_rect)
Definition: utils.hpp:36
void sdl_blit(const surface &src, SDL_Rect *src_rect, surface &dst, SDL_Rect *dst_rect)
Definition: utils.hpp:32
RAII object which manages a halo.
Definition: halo.hpp:77
const image::locator & current_image() const
Definition: halo.cpp:60
void set_location(int handle, int x, int y)
Set the position of an existing haloing effect, according to its handle.
Definition: halo.cpp:337
std::shared_ptr< halo_record > handle
Definition: halo.hpp:30
bool does_change() const
Definition: halo.cpp:53
std::string::const_iterator iterator
Definition: tokenizer.hpp:25
display * disp
Definition: halo.cpp:79
bool animation_finished() const
Returns true if the current animation was finished.
std::vector< std::string > square_parenthetical_split(const std::string &val, const char separator, const std::string &left, const std::string &right, const int flags)
Similar to parenthetical_split, but also expands embedded square brackets.
std::pair< std::string, unsigned > item
Definition: help_impl.hpp:410
void remove(const handle &h)
Remove the halo with the given handle.
Definition: halo.cpp:467