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