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