The Battle for Wesnoth  1.19.5+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(std::chrono::milliseconds{0}, infinite);
165 
166  update();
167 }
168 
170 {
171  point new_center = point{x, y} - disp->get_location(map_location::ZERO());
172  if(new_center != abs_mid_) {
173  DBG_HL << "setting halo location " << new_center;
174  abs_mid_ = new_center;
175  }
176 }
177 
179 {
180  return screen_loc_;
181 }
182 
183 
184 /** Update the current location, animation frame, etc. */
186 {
187  double zf = disp->get_zoom_factor();
188 
189  if(map_loc_.x != -1 && map_loc_.y != -1) {
190  // If the halo is attached to a particular map location,
191  // make sure it stays attached.
192  auto [x, y] = disp->get_location_rect(map_loc_).center();
193  set_location(x, y);
194  } else {
195  // It would be good to attach to a position within a hex,
196  // or persistently to an item or unit. That's not the case,
197  // so we use some horrible hacks to compensate for zoom changes.
198  if(cached_zoom_ != zf) {
199  abs_mid_.x *= zf / cached_zoom_;
200  abs_mid_.y *= zf / cached_zoom_;
201  cached_zoom_ = zf;
202  }
203  }
204 
205  // Load texture for current animation frame
206  tex_ = image::get_texture(current_image());
207  if(!tex_) {
208  ERR_HL << "no texture found for current halo animation frame";
209  screen_loc_ = {};
210  return;
211  }
212 
213  // Update draw location
214  int w(tex_.w() * disp->get_zoom_factor());
215  int h(tex_.h() * disp->get_zoom_factor());
216 
217  const auto [zero_x, zero_y] = disp->get_location(map_location::ZERO());
218 
219  const int xpos = zero_x + abs_mid_.x - w/2;
220  const int ypos = zero_y + abs_mid_.y - h/2;
221 
222  screen_loc_ = {xpos, ypos, w, h};
223 
224  // Queue display updates if position has changed
225  if(screen_loc_ != last_draw_loc_) {
226  queue_undraw();
227  queue_redraw();
228  last_draw_loc_ = screen_loc_;
229  }
230 }
231 
233 {
234  // Source is shrouded
235  // The halo will be completely obscured here, even if it would
236  // technically be large enough to peek out of the shroud.
237  if(map_loc_.x != -1 && map_loc_.y != -1 && disp->shrouded(map_loc_)) {
238  return false;
239  }
240 
241  // Halo is completely off screen
242  if(!screen_loc_.overlaps(disp->map_outside_area())) {
243  return false;
244  }
245 
246  return true;
247 }
248 
250 {
251  // This should only be set if we actually draw something
252  last_draw_loc_ = {};
253 
254  // Update animation frame, even if we didn't actually draw it
255  images_.update_last_draw_time();
256 
257  if(!visible()) {
258  return false;
259  }
260 
261  // Make sure we clip to the map area
262  auto clipper = draw::reduce_clip(disp->map_outside_area());
263 
264  DBG_HL << "drawing halo at " << screen_loc_;
265 
266  if (orientation_ == NORMAL) {
267  draw::blit(tex_, screen_loc_);
268  } else {
269  draw::flipped(tex_, screen_loc_,
270  orientation_ == HREVERSE || orientation_ == HVREVERSE,
271  orientation_ == VREVERSE || orientation_ == HVREVERSE);
272  }
273 
274  last_draw_loc_ = screen_loc_;
275 
276  return true;
277 }
278 
280 {
281  if(!last_draw_loc_.overlaps(disp->map_outside_area())) {
282  return;
283  }
284  DBG_HL << "queueing halo undraw at " << last_draw_loc_;
285  draw_manager::invalidate_region(last_draw_loc_);
286 }
287 
289 {
290  if(!visible()) {
291  return;
292  }
293  DBG_HL << "queueing halo redraw at " << screen_loc_;
294  draw_manager::invalidate_region(screen_loc_);
295 }
296 
297 
298 
299 /*************/
300 /* halo_impl */
301 /*************/
302 
303 
304 int halo_impl::add(int x, int y, const std::string& image, const map_location& loc,
305  ORIENTATION orientation, bool infinite)
306 {
307  const int id = halo_id++;
308  DBG_HL << "adding 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(const std::invalid_argument&) {
322  ERR_HL << "Invalid time value found when constructing halo: " << sub_items.back();
323  }
324  }
325  image_vector.push_back(animated<image::locator>::frame_description(time,image::locator(str)));
326 
327  }
328  haloes.emplace(id, effect(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 
356 {
357  if(haloes.empty()) {
358  return;
359  }
360 
361  // Mark expired haloes for removal
362  for(auto& [id, effect] : haloes) {
363  if(effect.expired()) {
364  DBG_HL << "expiring halo " << id;
365  deleted_haloes.insert(id);
366  }
367  }
368  // Make sure deleted halos get undrawn
369  for(int id : deleted_haloes) {
370  DBG_HL << "invalidating deleted halo " << id;
371  haloes.at(id).queue_undraw();
372  }
373  // Remove deleted halos
374  for(int id : deleted_haloes) {
375  DBG_HL << "deleting halo " << id;
376  changing_haloes.erase(id);
377  haloes.erase(id);
378  }
379  deleted_haloes.clear();
380 
381  // Update the location and animation frame of the remaining halos
382  for(auto& [id, halo] : haloes) { (void)id;
383  halo.update();
384  }
385 
386  // Invalidate any animated halos which need updating
387  for(int id : changing_haloes) {
388  auto& halo = haloes.at(id);
389  if(halo.need_update() && halo.visible()) {
390  DBG_HL << "invalidating changed halo " << id;
391  halo.queue_redraw();
392  }
393  }
394 }
395 
396 void halo_impl::render(const rect& region)
397 {
398  if(haloes.empty()) {
399  return;
400  }
401 
402  for(auto& [id, effect] : haloes) {
403  if(region.overlaps(effect.get_draw_location())) {
404  DBG_HL << "drawing intersected halo " << id;
405  effect.render();
406  }
407  }
408 }
409 
410 
411 
412 /*****************/
413 /* halo::manager */
414 /*****************/
415 
416 
418 {}
419 
420 handle manager::add(int x, int y, const std::string& image, const map_location& loc,
421  ORIENTATION orientation, bool infinite)
422 {
423  int new_halo = impl_->add(x,y,image, loc, orientation, infinite);
424  return handle(new halo_record(new_halo, impl_));
425 }
426 
427 /** Set the position of an existing haloing effect, according to its handle. */
428 void manager::set_location(const handle & h, int x, int y)
429 {
430  impl_->set_location(h->id_,x,y);
431 }
432 
433 /** Remove the halo with the given handle. */
434 void manager::remove(const handle & h)
435 {
436  impl_->remove(h->id_);
437  h->id_ = NO_HALO;
438 }
439 
441 {
442  impl_->update();
443 }
444 
445 void manager::render(const rect& r)
446 {
447  impl_->render(r);
448 }
449 
450 // end halo::manager implementation
451 
452 
453 /**
454  * halo::halo_record implementation
455  */
456 
458  id_(NO_HALO), //halo::NO_HALO
459  my_manager_()
460 {}
461 
462 halo_record::halo_record(int id, const std::shared_ptr<halo_impl> & my_manager) :
463  id_(id),
464  my_manager_(my_manager)
465 {}
466 
468 {
469  if (!valid()) return;
470 
471  std::shared_ptr<halo_impl> man = my_manager_.lock();
472 
473  if(man) {
474  man->remove(id_);
475  }
476 }
477 
478 } //end namespace halo
Animate units.
void start_animation(const std::chrono::milliseconds &start_time, bool cycles=false)
Starts an animation cycle.
bool does_not_change() const
Definition: animated.hpp:104
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:75
Sort-of-Singleton that many classes, both GUI and non-GUI, use to access the game data.
Definition: display.hpp:97
static double get_zoom_factor()
Returns the current zoom factor.
Definition: display.hpp:270
bool visible()
Whether the halo is currently visible.
Definition: halo.cpp:232
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:178
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:185
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:336
void render(const rect &)
Render all halos overlapping the given region.
Definition: halo.cpp:396
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:304
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:355
void remove(int handle)
Remove the halo with the given handle.
Definition: halo.cpp:344
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:457
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:440
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:428
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:420
void remove(const handle &h)
Remove the halo with the given handle.
Definition: halo.cpp:434
void render(const rect &r)
Render halos in region.
Definition: halo.cpp:445
Generic locator abstracting the location of an image.
Definition: picture.hpp:59
Wrapper class to encapsulate creation and management of an SDL_Texture.
Definition: texture.hpp:33
map_display and display: classes which take care of displaying the map and game-data on the screen.
Drawing functions, for drawing things on the screen.
int w
std::string id
Text to match against addon_info.tags()
Definition: manager.cpp:198
#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:502
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
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:920
std::string img(const std::string &src, const std::string &align, const bool floating)
Definition: markup.cpp:29
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:45
static const map_location & ZERO()
Definition: location.hpp:96
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:73
#define h