The Battle for Wesnoth  1.17.21+dev
halo.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2023
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 "draw.hpp"
25 #include "draw_manager.hpp"
26 #include "preferences/game.hpp"
27 #include "halo.hpp"
28 #include "log.hpp"
30 #include "sdl/rect.hpp"
31 #include "sdl/texture.hpp"
32 
33 static lg::log_domain log_halo("halo");
34 #define ERR_HL LOG_STREAM(err, log_halo)
35 #define WRN_HL LOG_STREAM(warn, log_halo)
36 #define LOG_HL LOG_STREAM(info, log_halo)
37 #define DBG_HL LOG_STREAM(debug, log_halo)
38 
39 namespace halo
40 {
41 
42 class halo_impl
43 {
44 
45  class effect
46  {
47  public:
48  effect(
49  int xpos, int ypos,
51  const map_location& loc, ORIENTATION, bool infinite
52  );
53 
54  void set_location(int x, int y);
56 
57  /** Whether the halo is currently visible */
58  bool visible();
59 
60  void queue_undraw();
61  void queue_redraw();
62  void update();
63  bool render();
64 
65  bool expired() const { return !images_.cycles() && images_.animation_finished(); }
66  bool need_update() const { return images_.need_update(); }
67  bool does_change() const { return !images_.does_not_change(); }
68  bool on_location(const std::set<map_location>& locations) const;
69  bool location_not_known() const;
70 
71  private:
72 
74 
76 
78 
79  // The mid-point of the halo in pixels relative to the absolute top-left of the map, in screen coordinates.
80  // Yes it's just as ridiculous as it sounds...
81  // TODO: make this something sane. Like a floating-point map location.
82  point abs_mid_ = {0, 0};
83 
84  // The current halo image frame
85  texture tex_ = {};
86  // The current location where the halo will be drawn on the screen
88  // The last drawn location
90  // The display zoom level, cached so we can compensate when it changes.
91  double cached_zoom_ = 1.0;
92 
93  // The map location the halo is attached to, if any
94  map_location map_loc_ = {-1, -1};
95 
96  display* disp = nullptr;
97  };
98 
99  std::map<int, effect> haloes;
100  int halo_id;
101 
102  /**
103  * Upon unrendering, an invalidation list is send. All haloes in that area and
104  * the other invalidated haloes are stored in this set. Then there'll be
105  * tested which haloes overlap and they're also stored in this set.
106  */
107  std::set<int> invalidated_haloes;
108 
109  /**
110  * Upon deleting, a halo isn't deleted but added to this set, upon unrendering
111  * the image is unrendered and deleted.
112  */
113  std::set<int> deleted_haloes;
114 
115  /**
116  * Haloes that have an animation or expiration time need to be checked every
117  * frame and are stored in this set.
118  */
119  std::set<int> changing_haloes;
120 
121  public:
122  /**
123  * impl's of exposed functions
124  */
125 
126  explicit halo_impl() :
127  haloes(),
128  halo_id(1),
130  deleted_haloes(),
132  {}
133 
134 
135  int add(int x, int y, const std::string& image, const map_location& loc,
136  ORIENTATION orientation=NORMAL, bool infinite=true);
137 
138  /** Set the position of an existing haloing effect, according to its handle. */
139  void set_location(int handle, int x, int y);
140 
141  /** Remove the halo with the given handle. */
142  void remove(int handle);
143 
144  void update();
145 
146  /** Render all halos overlapping the given region */
147  void render(const rect&);
148 
149 }; //end halo_impl
150 
151 halo_impl::effect::effect(int xpos, int ypos,
153  const map_location& loc, ORIENTATION orientation, bool infinite) :
154  images_(img),
155  orientation_(orientation),
156  map_loc_(loc),
157  disp(display::get_singleton())
158 {
159  assert(disp != nullptr);
160 
162 
163  set_location(xpos, ypos);
164 
165  images_.start_animation(0, infinite);
166 
167  update();
168 }
169 
171 {
172  int new_x = x - disp->get_location_x(map_location::ZERO());
173  int new_y = y - disp->get_location_y(map_location::ZERO());
174  if (new_x != abs_mid_.x || new_y != abs_mid_.y) {
175  DBG_HL << "setting halo location " << point{new_x,new_y};
176  abs_mid_.x = new_x;
177  abs_mid_.y = new_y;
178  }
179 }
180 
182 {
183  return screen_loc_;
184 }
185 
186 
187 /** Update the current location, animation frame, etc. */
189 {
190  double zf = disp->get_zoom_factor();
191 
192  if(map_loc_.x != -1 && map_loc_.y != -1) {
193  // If the halo is attached to a particular map location,
194  // make sure it stays attached.
195  set_location(
196  disp->get_location_x(map_loc_) + disp->hex_size() / 2,
197  disp->get_location_y(map_loc_) + disp->hex_size() / 2
198  );
199  } else {
200  // It would be good to attach to a position within a hex,
201  // or persistently to an item or unit. That's not the case,
202  // so we use some horrible hacks to compensate for zoom changes.
203  if(cached_zoom_ != zf) {
204  abs_mid_.x *= zf / cached_zoom_;
205  abs_mid_.y *= zf / cached_zoom_;
206  cached_zoom_ = zf;
207  }
208  }
209 
210  // Load texture for current animation frame
211  tex_ = image::get_texture(current_image());
212  if(!tex_) {
213  ERR_HL << "no texture found for current halo animation frame";
214  screen_loc_ = {};
215  return;
216  }
217 
218  // Update draw location
219  int w(tex_.w() * disp->get_zoom_factor());
220  int h(tex_.h() * disp->get_zoom_factor());
221 
222  const int zero_x = disp->get_location_x(map_location::ZERO());
223  const int zero_y = disp->get_location_y(map_location::ZERO());
224 
225  const int xpos = zero_x + abs_mid_.x - w/2;
226  const int ypos = zero_y + abs_mid_.y - h/2;
227 
228  screen_loc_ = {xpos, ypos, w, h};
229 
230  // Queue display updates if position has changed
231  if(screen_loc_ != last_draw_loc_) {
232  queue_undraw();
233  queue_redraw();
234  last_draw_loc_ = screen_loc_;
235  }
236 }
237 
239 {
240  // Source is shrouded
241  // The halo will be completely obscured here, even if it would
242  // technically be large enough to peek out of the shroud.
243  if(map_loc_.x != -1 && map_loc_.y != -1 && disp->shrouded(map_loc_)) {
244  return false;
245  }
246 
247  // Halo is completely off screen
248  if(!screen_loc_.overlaps(disp->map_outside_area())) {
249  return false;
250  }
251 
252  return true;
253 }
254 
256 {
257  // This should only be set if we actually draw something
258  last_draw_loc_ = {};
259 
260  // Update animation frame, even if we didn't actually draw it
261  images_.update_last_draw_time();
262 
263  if(!visible()) {
264  return false;
265  }
266 
267  // Make sure we clip to the map area
268  auto clipper = draw::reduce_clip(disp->map_outside_area());
269 
270  DBG_HL << "drawing halo at " << screen_loc_;
271 
272  if (orientation_ == NORMAL) {
273  draw::blit(tex_, screen_loc_);
274  } else {
275  draw::flipped(tex_, screen_loc_,
276  orientation_ == HREVERSE || orientation_ == HVREVERSE,
277  orientation_ == VREVERSE || orientation_ == HVREVERSE);
278  }
279 
280  last_draw_loc_ = screen_loc_;
281 
282  return true;
283 }
284 
286 {
287  if(!last_draw_loc_.overlaps(disp->map_outside_area())) {
288  return;
289  }
290  DBG_HL << "queueing halo undraw at " << last_draw_loc_;
291  draw_manager::invalidate_region(last_draw_loc_);
292 }
293 
295 {
296  if(!visible()) {
297  return;
298  }
299  DBG_HL << "queueing halo redraw at " << screen_loc_;
300  draw_manager::invalidate_region(screen_loc_);
301 }
302 
303 
304 
305 /*************/
306 /* halo_impl */
307 /*************/
308 
309 
310 int halo_impl::add(int x, int y, const std::string& image, const map_location& loc,
311  ORIENTATION orientation, bool infinite)
312 {
313  const int id = halo_id++;
314  DBG_HL << "adding halo " << id;
316  std::vector<std::string> items = utils::square_parenthetical_split(image, ',');
317 
318  for(const std::string& item : items) {
319  const std::vector<std::string>& sub_items = utils::split(item, ':');
320  std::string str = item;
321  int time = 100;
322 
323  if(sub_items.size() > 1) {
324  str = sub_items.front();
325  try {
326  time = std::stoi(sub_items.back());
327  } catch(const std::invalid_argument&) {
328  ERR_HL << "Invalid time value found when constructing halo: " << sub_items.back();
329  }
330  }
331  image_vector.push_back(animated<image::locator>::frame_description(time,image::locator(str)));
332 
333  }
334  haloes.emplace(id, effect(x, y, image_vector, loc, orientation, infinite));
335  invalidated_haloes.insert(id);
336  if(haloes.find(id)->second.does_change() || !infinite) {
337  changing_haloes.insert(id);
338  }
339  return id;
340 }
341 
342 void halo_impl::set_location(int handle, int x, int y)
343 {
344  const std::map<int,effect>::iterator itor = haloes.find(handle);
345  if(itor != haloes.end()) {
346  itor->second.set_location(x,y);
347  }
348 }
349 
351 {
352  // Silently ignore invalid haloes.
353  // This happens when Wesnoth is being terminated as well.
354  if(handle == NO_HALO || haloes.find(handle) == haloes.end()) {
355  return;
356  }
357 
358  deleted_haloes.insert(handle);
359 }
360 
362 {
363  if(haloes.empty()) {
364  return;
365  }
366 
367  // Mark expired haloes for removal
368  for(auto& [id, effect] : haloes) {
369  if(effect.expired()) {
370  DBG_HL << "expiring halo " << id;
371  deleted_haloes.insert(id);
372  }
373  }
374  // Make sure deleted halos get undrawn
375  for(int id : deleted_haloes) {
376  DBG_HL << "invalidating deleted halo " << id;
377  haloes.at(id).queue_undraw();
378  }
379  // Remove deleted halos
380  for(int id : deleted_haloes) {
381  DBG_HL << "deleting halo " << id;
382  changing_haloes.erase(id);
383  haloes.erase(id);
384  }
385  deleted_haloes.clear();
386 
387  // Update the location and animation frame of the remaining halos
388  for(auto& [id, halo] : haloes) { (void)id;
389  halo.update();
390  }
391 
392  // Invalidate any animated halos which need updating
393  for(int id : changing_haloes) {
394  auto& halo = haloes.at(id);
395  if(halo.need_update() && halo.visible()) {
396  DBG_HL << "invalidating changed halo " << id;
397  halo.queue_redraw();
398  }
399  }
400 }
401 
402 void halo_impl::render(const rect& region)
403 {
404  if(haloes.empty()) {
405  return;
406  }
407 
408  for(auto& [id, effect] : haloes) {
409  if(region.overlaps(effect.get_draw_location())) {
410  DBG_HL << "drawing intersected halo " << id;
411  effect.render();
412  }
413  }
414 }
415 
416 
417 
418 /*****************/
419 /* halo::manager */
420 /*****************/
421 
422 
424 {}
425 
426 handle manager::add(int x, int y, const std::string& image, const map_location& loc,
427  ORIENTATION orientation, bool infinite)
428 {
429  int new_halo = impl_->add(x,y,image, loc, orientation, infinite);
430  return handle(new halo_record(new_halo, impl_));
431 }
432 
433 /** Set the position of an existing haloing effect, according to its handle. */
434 void manager::set_location(const handle & h, int x, int y)
435 {
436  impl_->set_location(h->id_,x,y);
437 }
438 
439 /** Remove the halo with the given handle. */
440 void manager::remove(const handle & h)
441 {
442  impl_->remove(h->id_);
443  h->id_ = NO_HALO;
444 }
445 
447 {
448  impl_->update();
449 }
450 
451 void manager::render(const rect& r)
452 {
453  impl_->render(r);
454 }
455 
456 // end halo::manager implementation
457 
458 
459 /**
460  * halo::halo_record implementation
461  */
462 
464  id_(NO_HALO), //halo::NO_HALO
465  my_manager_()
466 {}
467 
468 halo_record::halo_record(int id, const std::shared_ptr<halo_impl> & my_manager) :
469  id_(id),
470  my_manager_(my_manager)
471 {}
472 
474 {
475  if (!valid()) return;
476 
477  std::shared_ptr<halo_impl> man = my_manager_.lock();
478 
479  if(man) {
480  man->remove(id_);
481  }
482 }
483 
484 } //end namespace halo
Animate units.
void start_animation(int start_time, bool cycles=false)
Starts an animation cycle.
bool does_not_change() const
Definition: animated.hpp:102
bool need_update() const
bool animation_finished() const
Returns true if the current animation was finished.
const T & get_current_frame() const
bool cycles() const
Definition: animated.hpp:73
Sort-of-Singleton that many classes, both GUI and non-GUI, use to access the game data.
Definition: display.hpp:87
static double get_zoom_factor()
Returns the current zoom factor.
Definition: display.hpp:267
bool visible()
Whether the halo is currently visible.
Definition: halo.cpp:238
bool expired() const
Definition: halo.cpp:65
bool does_change() const
Definition: halo.cpp:67
bool need_update() const
Definition: halo.cpp:66
map_location map_loc_
Definition: halo.cpp:94
ORIENTATION orientation_
Definition: halo.cpp:77
rect get_draw_location()
Definition: halo.cpp:181
void set_location(int x, int y)
Definition: halo.cpp:170
effect(int xpos, int ypos, const animated< image::locator >::anim_description &img, const map_location &loc, ORIENTATION, bool infinite)
Definition: halo.cpp:151
animated< image::locator > images_
Definition: halo.cpp:75
const image::locator & current_image() const
Definition: halo.cpp:73
void update()
Update the current location, animation frame, etc.
Definition: halo.cpp:188
bool location_not_known() const
bool on_location(const std::set< map_location > &locations) const
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:119
void set_location(int handle, int x, int y)
Set the position of an existing haloing effect, according to its handle.
Definition: halo.cpp:342
void render(const rect &)
Render all halos overlapping the given region.
Definition: halo.cpp:402
halo_impl()
impl's of exposed functions
Definition: halo.cpp:126
std::set< int > invalidated_haloes
Upon unrendering, an invalidation list is send.
Definition: halo.cpp:107
int add(int x, int y, const std::string &image, const map_location &loc, ORIENTATION orientation=NORMAL, bool infinite=true)
Definition: halo.cpp:310
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:113
std::map< int, effect > haloes
Definition: halo.cpp:99
void update()
Definition: halo.cpp:361
void remove(int handle)
Remove the halo with the given handle.
Definition: halo.cpp:350
RAII object which manages a halo.
Definition: halo.hpp:77
std::weak_ptr< halo_impl > my_manager_
Definition: halo.hpp:93
halo_record()
halo::halo_record implementation
Definition: halo.cpp:463
bool valid() const
Definition: halo.hpp:86
std::shared_ptr< halo_impl > impl_
Definition: halo.hpp:70
void update()
Process animations, remove deleted halos, and invalidate screen regions now requiring redraw.
Definition: halo.cpp:446
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:434
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:426
void remove(const handle &h)
Remove the halo with the given handle.
Definition: halo.cpp:440
void render(const rect &r)
Render halos in region.
Definition: halo.cpp:451
Generic locator abstracting the location of an image.
Definition: picture.hpp:64
Wrapper class to encapsulate creation and management of an SDL_Texture.
Definition: texture.hpp:33
Drawing functions, for drawing things on the screen.
int w
std::string id
Text to match against addon_info.tags()
Definition: manager.cpp:215
#define ERR_HL
Definition: halo.cpp:34
#define DBG_HL
Definition: halo.cpp:37
static lg::log_domain log_halo("halo")
Standard logging facilities (interface).
void invalidate_region(const rect &region)
Mark a region of the screen as requiring redraw.
clip_setter reduce_clip(const SDL_Rect &clip)
Set the clipping area to the intersection of the current clipping area and the given rectangle.
Definition: draw.cpp:448
void flipped(const texture &tex, const SDL_Rect &dst, bool flip_h=true, bool flip_v=false)
Draws a texture, or part of a texture, at the given location, also mirroring/flipping the texture hor...
Definition: draw.cpp:331
void blit(const texture &tex, const SDL_Rect &dst)
Draws a texture, or part of a texture, at the given location.
Definition: draw.cpp:301
Definition: display.hpp:45
ORIENTATION
Definition: halo.hpp:35
@ HVREVERSE
Definition: halo.hpp:35
@ VREVERSE
Definition: halo.hpp:35
@ HREVERSE
Definition: halo.hpp:35
@ NORMAL
Definition: halo.hpp:35
const int NO_HALO
Definition: halo.hpp:37
std::shared_ptr< halo_record > handle
Definition: halo.hpp:31
std::pair< std::string, unsigned > item
Definition: help_impl.hpp:414
Functions to load and save images from/to disk.
texture get_texture(const image::locator &i_locator, TYPE type, bool skip_cache)
Returns an image texture suitable for hardware-accelerated rendering.
Definition: picture.cpp:985
const std::vector< std::string > items
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::vector< std::string > split(const config_attribute_value &val)
std::string::const_iterator iterator
Definition: tokenizer.hpp:25
pump_impl & impl_
Definition: pump.cpp:134
Contains the SDL_Rect helper code.
Encapsulates the map of the game.
Definition: location.hpp:38
static const map_location & ZERO()
Definition: location.hpp:75
Holds a 2D point.
Definition: point.hpp:25
An abstract description of a rectangle with integer coordinates.
Definition: rect.hpp:47
bool overlaps(const SDL_Rect &r) const
Whether the given rectangle and this rectangle overlap.
Definition: rect.cpp:74
#define h