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