The Battle for Wesnoth 1.21.0-dev
 
Loading...
Searching...
No Matches
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
32static 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
38using namespace std::chrono_literals;
39
40namespace halo
41{
42
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_.is_static(); }
68
69 private:
70
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
84 // Track the last used locator to avoid redundant cache lookups
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
95
96 display* disp = nullptr;
97 };
98
99 std::map<int, effect> haloes;
100 int halo_id{1};
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
121public:
122 int add(int x, int y, const std::string& image, const map_location& loc,
123 ORIENTATION orientation=NORMAL, bool infinite=true);
124
125 /** Set the position of an existing haloing effect, according to its handle. */
126 void set_location(int handle, int x, int y);
127
128 /** Remove the halo with the given handle. */
129 void remove(int handle);
130
131 void update();
132
133 /** Render all halos overlapping the given region */
134 void render(const rect&);
135
136}; //end halo_impl
137
138halo_impl::effect::effect(int xpos, int ypos,
140 const map_location& loc, ORIENTATION orientation, bool infinite) :
141 images_(img),
142 orientation_(orientation),
143 map_loc_(loc),
144 disp(display::get_singleton())
145{
146 assert(disp != nullptr);
147
149
150 set_location(xpos, ypos);
151
152 images_.start_animation(0ms, infinite);
153
154 update();
155}
156
158{
159 point new_center = point{x, y} - disp->get_location(map_location::ZERO());
160 if(new_center != abs_mid_) {
161 DBG_HL << "setting halo location " << new_center;
162 abs_mid_ = new_center;
163 }
164}
165
167{
168 return screen_loc_;
169}
170
171/** Update the current location, animation frame, etc. */
173{
174 if(images_.need_update()) {
175 images_.advance_to_current_frame();
176 }
177 double zf = disp->get_zoom_factor();
178
179 if(map_loc_.x != -1 && map_loc_.y != -1) {
180 // If the halo is attached to a particular map location,
181 // make sure it stays attached.
182 auto [x, y] = disp->get_location_rect(map_loc_).center();
183 set_location(x, y);
184 } else {
185 // It would be good to attach to a position within a hex,
186 // or persistently to an item or unit. That's not the case,
187 // so we use some horrible hacks to compensate for zoom changes.
188 if(cached_zoom_ != zf) {
189 abs_mid_.x *= zf / cached_zoom_;
190 abs_mid_.y *= zf / cached_zoom_;
191 cached_zoom_ = zf;
192 }
193 }
194
195 // Load texture for current animation frame
196 const image::locator& loc = current_image();
197 if (loc != last_locator_ || !tex_) {
198 tex_ = image::get_texture(loc);
199 last_locator_ = loc;
200 }
201 if(!tex_) {
202 ERR_HL << "no texture found for current halo animation frame";
203 screen_loc_ = {};
204 return;
205 }
206
207 // Update draw location
208 int w(tex_.w() * disp->get_zoom_factor());
209 int h(tex_.h() * disp->get_zoom_factor());
210
211 const auto [zero_x, zero_y] = disp->get_location(map_location::ZERO());
212
213 const int xpos = zero_x + abs_mid_.x - w/2;
214 const int ypos = zero_y + abs_mid_.y - h/2;
215
216 screen_loc_ = {xpos, ypos, w, h};
217
218 // Queue display updates if position has changed
219 if(screen_loc_ != last_draw_loc_) {
220 queue_undraw();
221 queue_redraw();
222 last_draw_loc_ = screen_loc_;
223 }
224}
225
227{
228 // Source is shrouded
229 // The halo will be completely obscured here, even if it would
230 // technically be large enough to peek out of the shroud.
231 if(map_loc_.x != -1 && map_loc_.y != -1 && disp->shrouded(map_loc_)) {
232 return false;
233 }
234
235 // Halo is completely off screen
236 if(!screen_loc_.overlaps(disp->map_outside_area())) {
237 return false;
238 }
239
240 return true;
241}
242
244{
245 // This should only be set if we actually draw something
246 last_draw_loc_ = {};
247
248 if(!visible()) {
249 return false;
250 }
251
252 // Make sure we clip to the map area
253 auto clipper = draw::reduce_clip(disp->map_outside_area());
254
255 DBG_HL << "drawing halo at " << screen_loc_;
256
257 if (orientation_ == NORMAL) {
258 draw::blit(tex_, screen_loc_);
259 } else {
260 draw::flipped(tex_, screen_loc_,
261 orientation_ == HREVERSE || orientation_ == HVREVERSE,
262 orientation_ == VREVERSE || orientation_ == HVREVERSE);
263 }
264
265 last_draw_loc_ = screen_loc_;
266
267 return true;
268}
269
271{
272 if(!last_draw_loc_.overlaps(disp->map_outside_area())) {
273 return;
274 }
275 DBG_HL << "queueing halo undraw at " << last_draw_loc_;
276 draw_manager::invalidate_region(last_draw_loc_);
277}
278
280{
281 if(!visible()) {
282 return;
283 }
284 DBG_HL << "queueing halo redraw at " << screen_loc_;
286}
287
288
289/*************/
290/* halo_impl */
291/*************/
292
293
294int halo_impl::add(int x, int y, const std::string& image, const map_location& loc,
295 ORIENTATION orientation, bool infinite)
296{
297 const int id = halo_id++;
298 DBG_HL << "adding halo " << id;
300 std::vector<std::string> items = utils::square_parenthetical_split(image, ',');
301
302 for(const std::string& item : items) {
303 const std::vector<std::string>& sub_items = utils::split(item, ':');
304 std::string str = item;
305 auto time = 100ms;
306
307 if(sub_items.size() > 1) {
308 str = sub_items.front();
309 try {
310 time = std::chrono::milliseconds{std::stoi(sub_items.back())};
311 } catch(const std::invalid_argument&) {
312 ERR_HL << "Invalid time value found when constructing halo: " << sub_items.back();
313 }
314 }
315 image_vector.emplace_back(time, image::locator(str));
316 }
317 haloes.try_emplace(id, x, y, image_vector, loc, orientation, infinite);
318 invalidated_haloes.insert(id);
319 if(haloes.find(id)->second.does_change() || !infinite) {
320 changing_haloes.insert(id);
321 }
322 return id;
323}
324
325void halo_impl::set_location(int handle, int x, int y)
326{
327 const std::map<int,effect>::iterator itor = haloes.find(handle);
328 if(itor != haloes.end()) {
329 itor->second.set_location(x,y);
330 }
331}
332
334{
335 // Silently ignore invalid haloes.
336 // This happens when Wesnoth is being terminated as well.
337 if(handle == NO_HALO || haloes.find(handle) == haloes.end()) {
338 return;
339 }
340
341 deleted_haloes.insert(handle);
342}
343
345{
346 if(haloes.empty()) {
347 return;
348 }
349
350 // Mark expired haloes for removal
351 for(auto& [id, effect] : haloes) {
352 if(effect.expired()) {
353 DBG_HL << "expiring halo " << id;
354 deleted_haloes.insert(id);
355 }
356 }
357 // Make sure deleted halos get undrawn
358 for(int id : deleted_haloes) {
359 DBG_HL << "invalidating deleted halo " << id;
360 haloes.at(id).queue_undraw();
361 }
362 // Remove deleted halos
363 for(int id : deleted_haloes) {
364 DBG_HL << "deleting halo " << id;
365 changing_haloes.erase(id);
366 haloes.erase(id);
367 }
368 deleted_haloes.clear();
369
370 // Invalidate any animated halos which need updating
371 for(int id : changing_haloes) {
372 auto& halo = haloes.at(id);
373 if(halo.need_update() && halo.visible()) {
374 DBG_HL << "invalidating changed halo " << id;
375 halo.queue_redraw();
376 }
377 }
378
379 // Update the location and animation frame of the remaining halos
380 for(auto& [id, halo] : haloes) {
381 halo.update();
382 }
383}
384
385void halo_impl::render(const rect& region)
386{
387 if(haloes.empty()) {
388 return;
389 }
390
391 for(auto& [id, effect] : haloes) {
392 if(region.overlaps(effect.get_draw_location())) {
393 DBG_HL << "drawing intersected halo " << id;
394 effect.render();
395 }
396 }
397}
398
399
400/*****************/
401/* halo::manager */
402/*****************/
403
404
407
408handle manager::add(int x, int y, const std::string& image, const map_location& loc,
409 ORIENTATION orientation, bool infinite)
410{
411 int new_halo = impl_->add(x,y,image, loc, orientation, infinite);
412 return handle(new halo_record(new_halo, impl_));
413}
414
415/** Set the position of an existing haloing effect, according to its handle. */
416void manager::set_location(const handle & h, int x, int y)
417{
418 impl_->set_location(h->id_,x,y);
419}
420
421/** Remove the halo with the given handle. */
423{
424 impl_->remove(h->id_);
425 h->id_ = NO_HALO;
426}
427
429{
430 impl_->update();
431}
432
433void manager::render(const rect& r)
434{
435 impl_->render(r);
436}
437
438// end halo::manager implementation
439
440
441/**
442 * halo::halo_record implementation
443 */
444
446 id_(NO_HALO), //halo::NO_HALO
447 my_manager_()
448{}
449
450halo_record::halo_record(int id, const std::shared_ptr<halo_impl> & my_manager) :
451 id_(id),
452 my_manager_(my_manager)
453{}
454
456{
457 if (!valid()) return;
458
459 std::shared_ptr<halo_impl> man = my_manager_.lock();
460
461 if(man) {
462 man->remove(id_);
463 }
464}
465
466} //end namespace halo
map_location loc
Definition move.cpp:172
Frame-based animation system with support for looping, acceleration, and time manipulation.
bool is_static() const
True when the single-unchanging-frame optimization is enabled: need_update() will always report false...
Definition animated.hpp:181
std::vector< frame_description > anim_description
Definition animated.hpp:52
void start_animation(const std::chrono::milliseconds &start_time_offset, bool cycles=false)
Anchors the timeline to the current tick, then begins playback.
bool need_update() const
True once the current tick has reached the current frame's end, meaning advance_to_current_frame() ha...
bool animation_finished() const
True if playback has run past the last frame, is not yet started, or has no frames.
const T & get_current_frame() const
bool cycles() const
True if the animation loops back to the first frame instead of stopping at the last.
Definition animated.hpp:112
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:262
bool visible()
Whether the halo is currently visible.
Definition halo.cpp:226
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:75
const image::locator & current_image() const
Definition halo.cpp:71
void set_location(int x, int y)
Definition halo.cpp:157
effect(int xpos, int ypos, const animated< image::locator >::anim_description &img, const map_location &loc, ORIENTATION, bool infinite)
Definition halo.cpp:138
image::locator last_locator_
Definition halo.cpp:85
animated< image::locator > images_
Definition halo.cpp:73
void update()
Update the current location, animation frame, etc.
Definition halo.cpp:172
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:325
void render(const rect &)
Render all halos overlapping the given region.
Definition halo.cpp:385
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:294
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:344
void remove(int handle)
Remove the halo with the given handle.
Definition halo.cpp:333
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:445
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:428
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:416
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:408
void remove(const handle &h)
Remove the halo with the given handle.
Definition halo.cpp:422
void render(const rect &r)
Render halos in region.
Definition halo.cpp:433
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).
void invalidate_region(const rect &region)
Mark a region of the screen as requiring redraw.
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:595
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:452
void blit(const texture &tex, const ::rect &dst)
Draws a texture, or part of a texture, at the given location.
Definition draw.cpp:409
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:33
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:955
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)
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
constexpr point center() const
The center point of the rectangle, accounting for origin.
Definition rect.hpp:106
bool overlaps(const rect &r) const
Whether the given rectangle and this rectangle overlap.
Definition rect.cpp:74
#define h