The Battle for Wesnoth  1.19.5+dev
frame.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2006 - 2024
3  by Jeremy Rosen <jeremy.rosen@enst-bretagne.fr>
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 #include "units/frame.hpp"
17 
18 #include "color.hpp"
19 #include "draw.hpp"
20 #include "game_display.hpp"
21 #include "log.hpp"
22 #include "sound.hpp"
23 
24 static lg::log_domain log_engine("engine");
25 #define ERR_NG LOG_STREAM(err, log_engine)
26 
28  : duration_(1)
29  , auto_vflip_(boost::logic::indeterminate)
30  , auto_hflip_(boost::logic::indeterminate)
31  , primary_frame_(boost::logic::indeterminate)
32  , drawing_layer_(std::to_string(get_abs_frame_layer(drawing_layer::unit_default)))
33 {}
34 
35 frame_builder::frame_builder(const config& cfg,const std::string& frame_string)
36  : duration_(1)
37  , image_(cfg[frame_string + "image"])
38  , image_diagonal_(cfg[frame_string + "image_diagonal"])
39  , image_mod_(cfg[frame_string + "image_mod"])
40  , halo_(cfg[frame_string + "halo"])
41  , halo_x_(cfg[frame_string + "halo_x"])
42  , halo_y_(cfg[frame_string + "halo_y"])
43  , halo_mod_(cfg[frame_string + "halo_mod"])
44  , sound_(cfg[frame_string + "sound"])
45  , text_(cfg[frame_string + "text"])
46  , blend_ratio_(cfg[frame_string + "blend_ratio"])
47  , highlight_ratio_(cfg[frame_string + "alpha"])
48  , offset_(cfg[frame_string + "offset"])
49  , submerge_(cfg[frame_string + "submerge"])
50  , x_(cfg[frame_string + "x"])
51  , y_(cfg[frame_string + "y"])
52  , directional_x_(cfg[frame_string + "directional_x"])
53  , directional_y_(cfg[frame_string + "directional_y"])
54  , auto_vflip_(boost::logic::indeterminate)
55  , auto_hflip_(boost::logic::indeterminate)
56  , primary_frame_(boost::logic::indeterminate)
57  , drawing_layer_(cfg[frame_string + "layer"])
58 {
59  if(cfg.has_attribute(frame_string + "auto_vflip")) {
60  auto_vflip_ = cfg[frame_string + "auto_vflip"].to_bool();
61  }
62 
63  if(cfg.has_attribute(frame_string + "auto_hflip")) {
64  auto_hflip_ = cfg[frame_string + "auto_hflip"].to_bool();
65  }
66 
67  if(cfg.has_attribute(frame_string + "primary")) {
68  primary_frame_ = cfg[frame_string + "primary"].to_bool();
69  }
70 
71  const auto& text_color_key = cfg[frame_string + "text_color"];
72  if(!text_color_key.empty()) {
73  try {
74  text_color_ = color_t::from_rgb_string(text_color_key.str());
75  } catch(const std::invalid_argument& e) {
76  // Might be thrown either due to an incorrect number of elements or std::stoul failure.
77  ERR_NG << "Invalid RBG text color in unit animation: " << text_color_key.str()
78  << "\n" << e.what();
79  }
80  }
81 
82  if(const config::attribute_value* v = cfg.get(frame_string + "duration")) {
83  duration(v->to_int());
84  } else if(!cfg.get(frame_string + "end")) {
85  int halo_duration = (progressive_string(halo_, 1)).duration();
86  int image_duration = (progressive_image(image_, 1)).duration();
87  int image_diagonal_duration = (progressive_image(image_diagonal_, 1)).duration();
88 
89  duration(std::max(std::max(image_duration, image_diagonal_duration), halo_duration));
90  } else {
91  duration(cfg[frame_string + "end"].to_int() - cfg[frame_string + "begin"].to_int());
92  }
93 
94  duration_ = std::max(duration_, 1);
95 
96  const auto& blend_color_key = cfg[frame_string + "blend_color"];
97  if(!blend_color_key.empty()) {
98  try {
99  blend_with_ = color_t::from_rgb_string(blend_color_key.str());
100  } catch(const std::invalid_argument& e) {
101  // Might be thrown either due to an incorrect number of elements or std::stoul failure.
102  ERR_NG << "Invalid RBG blend color in unit animation: " << blend_color_key.str()
103  << "\n" << e.what();
104  }
105  }
106 }
107 
108 frame_builder& frame_builder::image(const std::string& image ,const std::string& image_mod)
109 {
110  image_ = image;
111  image_mod_ = image_mod;
112  return *this;
113 }
114 
115 frame_builder& frame_builder::image_diagonal(const std::string& image_diagonal,const std::string& image_mod)
116 {
118  image_mod_ = image_mod;
119  return *this;
120 }
121 
123 {
124  sound_ = sound;
125  return *this;
126 }
127 
128 frame_builder& frame_builder::text(const std::string& text,const color_t text_color)
129 {
130  text_ = text;
131  text_color_ = text_color;
132  return *this;
133 }
134 
135 frame_builder& frame_builder::halo(const std::string& halo, const std::string& halo_x, const std::string& halo_y,const std::string& halo_mod)
136 {
137  halo_ = halo;
138  halo_x_ = halo_x;
139  halo_y_ = halo_y;
140  halo_mod_= halo_mod;
141  return *this;
142 }
143 
145 {
147  return *this;
148 }
149 
150 frame_builder& frame_builder::blend(const std::string& blend_ratio,const color_t blend_color)
151 {
152  blend_with_ = blend_color;
153  blend_ratio_ = blend_ratio;
154  return *this;
155 }
156 
157 frame_builder& frame_builder::highlight(const std::string& highlight)
158 {
160  return *this;
161 }
162 
163 frame_builder& frame_builder::offset(const std::string& offset)
164 {
165  offset_ = offset;
166  return *this;
167 }
168 
169 frame_builder& frame_builder::submerge(const std::string& submerge)
170 {
172  return *this;
173 }
174 
175 frame_builder& frame_builder::x(const std::string& x)
176 {
177  x_ = x;
178  return *this;
179 }
180 
181 frame_builder& frame_builder::y(const std::string& y)
182 {
183  y_ = y;
184  return *this;
185 }
186 
187 frame_builder& frame_builder::directional_x(const std::string& directional_x)
188 {
190  return *this;
191 }
192 
193 frame_builder& frame_builder::directional_y(const std::string& directional_y)
194 {
196  return *this;
197 }
198 
200 {
202  return *this;
203 }
204 
206 {
208  return *this;
209 }
210 
211 frame_builder& frame_builder::primary_frame(const bool primary_frame)
212 {
214  return *this;
215 }
216 
218 {
220  return *this;
221 }
222 
224  : duration_(duration ? duration : builder.duration_)
225  , image_(builder.image_,duration_)
226  , image_diagonal_(builder.image_diagonal_,duration_)
227  , image_mod_(builder.image_mod_)
228  , halo_(builder.halo_,duration_)
229  , halo_x_(builder.halo_x_,duration_)
230  , halo_y_(builder.halo_y_,duration_)
231  , halo_mod_(builder.halo_mod_)
232  , sound_(builder.sound_)
233  , text_(builder.text_)
234  , text_color_(builder.text_color_)
235  , blend_with_(builder.blend_with_)
236  , blend_ratio_(builder.blend_ratio_,duration_)
237  , highlight_ratio_(builder.highlight_ratio_,duration_)
238  , offset_(builder.offset_,duration_)
239  , submerge_(builder.submerge_,duration_)
240  , x_(builder.x_,duration_)
241  , y_(builder.y_,duration_)
242  , directional_x_(builder.directional_x_,duration_)
243  , directional_y_(builder.directional_y_,duration_)
244  , auto_vflip_(builder.auto_vflip_)
245  , auto_hflip_(builder.auto_hflip_)
246  , primary_frame_(builder.primary_frame_)
247  , drawing_layer_(builder.drawing_layer_,duration_)
248 {}
249 
251 {
252  return
262  x_.does_not_change() &&
263  y_.does_not_change() &&
267 }
268 
270 {
271  return !this->does_not_change();
272 }
273 
275 {
276 #ifdef __cpp_designated_initializers
277  return {
278  .duration = duration_,
279  .image = image_.get_current_element(current_time),
280  .image_diagonal = image_diagonal_.get_current_element(current_time),
281  .image_mod = image_mod_,
282  .halo = halo_.get_current_element(current_time),
283  .halo_x = halo_x_.get_current_element(current_time),
284  .halo_y = halo_y_.get_current_element(current_time),
285  .halo_mod = halo_mod_,
286  .sound = sound_,
287  .text = text_,
288  .text_color = text_color_,
289  .blend_with = blend_with_,
290  .blend_ratio = blend_ratio_.get_current_element(current_time),
291  .highlight_ratio = highlight_ratio_.get_current_element(current_time,1.0),
292  .offset = offset_.get_current_element(current_time,-1000),
293  .submerge = submerge_.get_current_element(current_time),
294  .x = x_.get_current_element(current_time),
295  .y = y_.get_current_element(current_time),
296  .directional_x = directional_x_.get_current_element(current_time),
297  .directional_y = directional_y_.get_current_element(current_time),
298  .auto_vflip = auto_vflip_,
299  .auto_hflip = auto_hflip_,
300  .primary_frame = primary_frame_,
302  };
303 #else
304  frame_parameters result;
305  result.duration = duration_;
306  result.image = image_.get_current_element(current_time);
307  result.image_diagonal = image_diagonal_.get_current_element(current_time);
308  result.image_mod = image_mod_;
309  result.halo = halo_.get_current_element(current_time);
310  result.halo_x = halo_x_.get_current_element(current_time);
311  result.halo_y = halo_y_.get_current_element(current_time);
312  result.halo_mod = halo_mod_;
313  result.sound = sound_;
314  result.text = text_;
315  result.text_color = text_color_;
316  result.blend_with = blend_with_;
317  result.blend_ratio = blend_ratio_.get_current_element(current_time);
318  result.highlight_ratio = highlight_ratio_.get_current_element(current_time,1.0);
319  result.offset = offset_.get_current_element(current_time,-1000);
320  result.submerge = submerge_.get_current_element(current_time);
321  result.x = x_.get_current_element(current_time);
322  result.y = y_.get_current_element(current_time);
323  result.directional_x = directional_x_.get_current_element(current_time);
324  result.directional_y = directional_y_.get_current_element(current_time);
325  result.auto_vflip = auto_vflip_;
326  result.auto_hflip = auto_hflip_;
327  result.primary_frame = primary_frame_;
329  return result;
330 #endif
331 }
332 
334  const std::string& highlight,
335  const std::string& blend_ratio,
336  color_t blend_color,
337  const std::string& offset,
338  const std::string& layer,
339  const std::string& modifiers)
340 {
341  if(!highlight.empty()) {
343  } else if(duration != duration_){
345  }
346 
347  if(!offset.empty()) {
349  } else if(duration != duration_){
351  }
352 
353  if(!blend_ratio.empty()) {
355  blend_with_ = blend_color;
356  } else if(duration != duration_){
358  }
359 
360  if(!layer.empty()) {
362  } else if(duration != duration_){
364  }
365 
366  if(!modifiers.empty()) {
367  image_mod_ += modifiers;
368  }
369 
370  if(duration != duration_) {
382  }
383 }
384 
385 std::vector<std::string> frame_parsed_parameters::debug_strings() const
386 {
387  std::vector<std::string> v;
388 
389  if(duration_ > 0) {
390  v.emplace_back("duration=" + utils::half_signed_value(duration_));
391  }
392 
393  if(!image_.get_original().empty()) {
394  v.emplace_back("image=" + image_.get_original());
395  }
396 
397  if(!image_diagonal_.get_original().empty()) {
398  v.emplace_back("image_diagonal=" + image_diagonal_.get_original());
399  }
400 
401  if(!image_mod_.empty()) {
402  v.emplace_back("image_mod=" + image_mod_);
403  }
404 
405  if(!halo_.get_original().empty()) {
406  v.emplace_back("halo=" + halo_.get_original());
407  }
408 
409  if(!halo_x_.get_original().empty()) {
410  v.emplace_back("halo_x=" + halo_x_.get_original());
411  }
412 
413  if(!halo_y_.get_original().empty()) {
414  v.emplace_back("halo_y=" + halo_y_.get_original());
415  }
416 
417  if(!halo_mod_.empty()) {
418  v.emplace_back("halo_mod=" + halo_mod_);
419  }
420 
421  if(!sound_.empty()) {
422  v.emplace_back("sound=" + sound_);
423  }
424 
425  if(!text_.empty()) {
426  v.emplace_back("text=" + text_);
427 
428  if(text_color_) {
429  v.emplace_back("text_color=" + text_color_->to_rgba_string());
430  }
431  }
432 
433  if(!blend_ratio_.get_original().empty()) {
434  v.emplace_back("blend_ratio=" + blend_ratio_.get_original());
435 
436  if(blend_with_) {
437  v.emplace_back("blend_with=" + blend_with_->to_rgba_string());
438  }
439  }
440 
441  if(!highlight_ratio_.get_original().empty()) {
442  v.emplace_back("highlight_ratio=" + highlight_ratio_.get_original());
443  }
444 
445  if(!offset_.get_original().empty()) {
446  v.emplace_back("offset=" + offset_.get_original());
447  }
448 
449  if(!submerge_.get_original().empty()) {
450  v.emplace_back("submerge=" + submerge_.get_original());
451  }
452 
453  if(!x_.get_original().empty()) {
454  v.emplace_back("x=" + x_.get_original());
455  }
456 
457  if(!y_.get_original().empty()) {
458  v.emplace_back("y=" + y_.get_original());
459  }
460 
461  if(!directional_x_.get_original().empty()) {
462  v.emplace_back("directional_x=" + directional_x_.get_original());
463  }
464 
465  if(!directional_y_.get_original().empty()) {
466  v.emplace_back("directional_y=" + directional_y_.get_original());
467  }
468 
469  if(!boost::indeterminate(auto_vflip_)) {
470  v.emplace_back("auto_vflip=" + utils::bool_string(static_cast<bool>(auto_vflip_)));
471  }
472 
473  if(!boost::indeterminate(auto_hflip_)) {
474  v.emplace_back("auto_hflip=" + utils::bool_string(static_cast<bool>(auto_hflip_)));
475  }
476 
477  if(!boost::indeterminate(primary_frame_)) {
478  v.emplace_back("primary_frame=" + utils::bool_string(static_cast<bool>(primary_frame_)));
479  }
480 
481  if(!drawing_layer_.get_original().empty()) {
482  v.emplace_back("drawing_layer=" + drawing_layer_.get_original());
483  }
484 
485  return v;
486 }
487 
488 namespace
489 {
490 void render_unit_image(
491  int x,
492  int y,
494  const map_location& loc,
495  const image::locator& i_locator,
496  bool hreverse,
497  uint8_t alpha,
498  double highlight,
499  color_t blendto,
500  double blend_ratio,
501  double submerge,
502  bool vreverse)
503 {
504  const point image_size = image::get_size(i_locator);
505  if(!image_size.x || !image_size.y) {
506  return;
507  }
508 
510 
511  rect dest = disp->scaled_to_zoom({x, y, image_size.x, image_size.y});
512  if(!dest.overlaps(disp->map_area())) {
513  return;
514  }
515 
516  texture tex = image::get_texture(i_locator);
517 
518  // Clamp blend ratio so nothing weird happens
519  blend_ratio = std::clamp(blend_ratio, 0.0, 1.0);
520 
521  submerge_data data = display::get_submerge_data(dest, submerge, image_size, alpha, hreverse, vreverse);
522 
523  disp->drawing_buffer_add(drawing_layer, loc, [=](const rect&) mutable {
524  tex.set_alpha_mod(alpha);
525 
526  if(submerge > 0.0) {
527  // set clip for dry part
528  // smooth_shaded doesn't use the clip information so it's fine to set it up front
529  tex.set_src(data.unsub_src);
530 
531  // draw underwater part
532  draw::smooth_shaded(tex, data.alpha_verts);
533 
534  // draw dry part
535  draw::flipped(tex, data.unsub_dest, hreverse, vreverse);
536  } else {
537  // draw whole texture
538  draw::flipped(tex, dest, hreverse, vreverse);
539  }
540 
541  if(uint8_t hl = float_to_color(highlight); hl > 0) {
542  tex.set_blend_mode(SDL_BLENDMODE_ADD);
543  tex.set_alpha_mod(hl);
544 
545  if(submerge > 0.0) {
546  // draw underwater part
547  draw::smooth_shaded(tex, data.alpha_verts);
548 
549  // draw dry part
550  draw::flipped(tex, data.unsub_dest, hreverse, vreverse);
551  } else {
552  // draw whole texture
553  draw::flipped(tex, dest, hreverse, vreverse);
554  }
555  }
556 
557  tex.set_blend_mode(SDL_BLENDMODE_BLEND);
558  tex.set_alpha_mod(SDL_ALPHA_OPAQUE);
559  });
560 
561  // SDL hax to apply an active washout tint at the correct ratio
562  if(blend_ratio == 0.0) {
563  return;
564  }
565 
566  // Get a pure-white version of the texture
567  const image::locator whiteout_locator(
568  i_locator.get_filename(),
569  i_locator.get_modifications()
570  + "~CHAN(255, 255, 255, alpha)"
571  );
572 
573  disp->drawing_buffer_add(drawing_layer, loc, [=, tex = image::get_texture(whiteout_locator)](const rect&) mutable {
574  tex.set_alpha_mod(alpha * blend_ratio);
575  tex.set_color_mod(blendto);
576 
577  if(submerge > 0.0) {
578  // also draw submerged portion
579  // alpha_mod and color_mod are ignored,
580  // so we have to put them in the smooth shaded vertex data.
581  // This also has to incorporate the existing submerge alpha.
582  blendto.a = uint8_t(data.alpha_verts[0].color.a * blend_ratio);
583  data.alpha_verts[0].color = blendto;
584  data.alpha_verts[1].color = blendto;
585 
586  blendto.a = uint8_t(data.alpha_verts[2].color.a * blend_ratio);
587  data.alpha_verts[2].color = blendto;
588  data.alpha_verts[3].color = blendto;
589 
590  // set clip for dry part
591  // smooth_shaded doesn't use the clip information so it's fine to set it up front
592  tex.set_src(data.unsub_src);
593 
594  // draw underwater part
595  draw::smooth_shaded(tex, data.alpha_verts);
596 
597  // draw dry part
598  draw::flipped(tex, data.unsub_dest, hreverse, vreverse);
599  } else {
600  // draw whole texture
601  draw::flipped(tex, dest, hreverse, vreverse);
602  }
603 
604  if(uint8_t hl = float_to_color(highlight); hl > 0) {
605  tex.set_blend_mode(SDL_BLENDMODE_ADD);
606  tex.set_alpha_mod(hl);
607 
608  if(submerge > 0.0) {
609  // draw underwater part
610  draw::smooth_shaded(tex, data.alpha_verts);
611 
612  // draw dry part
613  draw::flipped(tex, data.unsub_dest, hreverse, vreverse);
614  } else {
615  // draw whole texture
616  draw::flipped(tex, dest, hreverse, vreverse);
617  }
618  }
619 
620  tex.set_color_mod(255, 255, 255);
621  tex.set_blend_mode(SDL_BLENDMODE_BLEND);
622  tex.set_alpha_mod(SDL_ALPHA_OPAQUE);
623  });
624 }
625 } // namespace
626 
627 void unit_frame::redraw(const int frame_time, bool on_start_time, bool in_scope_of_frame,
628  const map_location& src, const map_location& dst,
629  halo::handle& halo_id, halo::manager& halo_man,
630  const frame_parameters& animation_val, const frame_parameters& engine_val) const
631 {
633 
634  const auto [xsrc, ysrc] = game_disp->get_location(src);
635  const auto [xdst, ydst] = game_disp->get_location(dst);
636  const map_location::direction direction = src.get_relative_dir(dst);
637 
638  const frame_parameters current_data = merge_parameters(frame_time,animation_val,engine_val);
639  double tmp_offset = current_data.offset;
640 
641  // Debug code to see the number of frames and their position
642  //if(tmp_offset) {
643  // std::cout << static_cast<int>(tmp_offset * 100) << "," << "\n";
644  //}
645 
646  if(on_start_time) {
647  // Stuff that should be done only once per frame
648  if(!current_data.sound.empty() ) {
649  sound::play_sound(current_data.sound);
650  }
651 
652  if(!current_data.text.empty() && current_data.text_color) {
653  game_disp->float_label(src, current_data.text, *current_data.text_color);
654  }
655  }
656 
657  image::locator image_loc;
658  if(direction != map_location::direction::north && direction != map_location::direction::south) {
659  image_loc = current_data.image_diagonal.clone(current_data.image_mod);
660  }
661 
662  if(image_loc.is_void() || image_loc.get_filename().empty()) { // invalid diag image, or not diagonal
663  image_loc = current_data.image.clone(current_data.image_mod);
664  }
665 
666  point image_size {0, 0};
667  if(!image_loc.is_void() && !image_loc.get_filename().empty()) { // invalid diag image, or not diagonal
668  image_size = image::get_size(image_loc);
669  }
670 
671  const int d2 = display::get_singleton()->hex_size() / 2;
672 
673  const int x = static_cast<int>(tmp_offset * xdst + (1.0 - tmp_offset) * xsrc) + d2;
674  const int y = static_cast<int>(tmp_offset * ydst + (1.0 - tmp_offset) * ysrc) + d2;
675  const double disp_zoom = display::get_singleton()->get_zoom_factor();
676 
677  if(image_size.x && image_size.y) {
678  bool facing_west = (
681 
682  bool facing_north = (
684  direction == map_location::direction::north ||
686 
687  if(!current_data.auto_hflip) { facing_west = false; }
688  if(!current_data.auto_vflip) { facing_north = true; }
689 
690  int my_x = x + disp_zoom * (current_data.x - image_size.x / 2);
691  int my_y = y + disp_zoom * (current_data.y - image_size.y / 2);
692 
693  if(facing_west) {
694  my_x -= current_data.directional_x * disp_zoom;
695  } else {
696  my_x += current_data.directional_x * disp_zoom;
697  }
698 
699  if(facing_north) {
700  my_y += current_data.directional_y * disp_zoom;
701  } else {
702  my_y -= current_data.directional_y * disp_zoom;
703  }
704 
705  // TODO: don't conflate highlights and alpha
706  double brighten;
707  uint8_t alpha;
708  if(current_data.highlight_ratio >= 1.0) {
709  brighten = current_data.highlight_ratio - 1.0;
710  alpha = 255;
711  } else {
712  brighten = 0.0;
713  alpha = float_to_color(current_data.highlight_ratio);
714  }
715 
716  if(alpha != 0) {
717  render_unit_image(my_x, my_y,
718  drawing_layer { int(drawing_layer::unit_first) + current_data.drawing_layer },
719  src,
720  image_loc,
721  facing_west,
722  alpha,
723  brighten,
724  current_data.blend_with ? *current_data.blend_with : color_t(),
725  current_data.blend_ratio,
726  current_data.submerge,
727  !facing_north
728  );
729  }
730  }
731 
732  halo_id.reset();
733 
734  if(!in_scope_of_frame) { //check after frame as first/last frame image used in defense/attack anims
735  return;
736  }
737 
738  // No halos, exit
739  if(current_data.halo.empty()) {
740  return;
741  }
742 
743  halo::ORIENTATION orientation;
744  switch(direction)
745  {
748  orientation = halo::NORMAL;
749  break;
752  if(!current_data.auto_vflip) {
753  orientation = halo::NORMAL;
754  } else {
755  orientation = halo::VREVERSE;
756  }
757  break;
759  if(!current_data.auto_vflip) {
760  orientation = halo::HREVERSE;
761  } else {
762  orientation = halo::HVREVERSE;
763  }
764  break;
766  orientation = halo::HREVERSE;
767  break;
769  default:
770  orientation = halo::NORMAL;
771  break;
772  }
773 
775  halo_id = halo_man.add(
776  static_cast<int>(x + current_data.halo_x * disp_zoom),
777  static_cast<int>(y + current_data.halo_y * disp_zoom),
778  current_data.halo + current_data.halo_mod,
779  map_location(-1, -1),
780  orientation
781  );
782  } else {
783  halo_id = halo_man.add(
784  static_cast<int>(x - current_data.halo_x * disp_zoom),
785  static_cast<int>(y + current_data.halo_y * disp_zoom),
786  current_data.halo + current_data.halo_mod,
787  map_location(-1, -1),
788  orientation
789  );
790  }
791 }
792 
793 std::set<map_location> unit_frame::get_overlaped_hex(const int frame_time, const map_location& src, const map_location& dst,
794  const frame_parameters& animation_val, const frame_parameters& engine_val) const
795 {
797 
798  const auto [xsrc, ysrc] = disp->get_location(src);
799  const auto [xdst, ydst] = disp->get_location(dst);
800  const map_location::direction direction = src.get_relative_dir(dst);
801 
802  const frame_parameters current_data = merge_parameters(frame_time, animation_val, engine_val);
803 
804  double tmp_offset = current_data.offset;
805  const int d2 = display::get_singleton()->hex_size() / 2;
806 
807  image::locator image_loc;
808  if(direction != map_location::direction::north && direction != map_location::direction::south) {
809  image_loc = current_data.image_diagonal.clone(current_data.image_mod);
810  }
811 
812  if(image_loc.is_void() || image_loc.get_filename().empty()) { // invalid diag image, or not diagonal
813  image_loc = current_data.image.clone(current_data.image_mod);
814  }
815 
816  // We always invalidate our own hex because we need to be called at redraw time even
817  // if we don't draw anything in the hex itself
818  std::set<map_location> result;
819  if(tmp_offset == 0 && current_data.x == 0 && current_data.directional_x == 0 && image::is_in_hex(image_loc)) {
820  result.insert(src);
821 
822  bool facing_north = (
824  direction == map_location::direction::north ||
826 
827  if(!current_data.auto_vflip) { facing_north = true; }
828 
829  int my_y = current_data.y;
830  if(facing_north) {
831  my_y += current_data.directional_y;
832  } else {
833  my_y -= current_data.directional_y;
834  }
835 
836  if(my_y < 0) {
837  result.insert(src.get_direction(map_location::direction::north));
838  result.insert(src.get_direction(map_location::direction::north_east));
839  result.insert(src.get_direction(map_location::direction::north_west));
840  } else if(my_y > 0) {
841  result.insert(src.get_direction(map_location::direction::south));
842  result.insert(src.get_direction(map_location::direction::south_east));
843  result.insert(src.get_direction(map_location::direction::south_west));
844  }
845  } else {
846  int w = 0, h = 0;
847 
848  if(!image_loc.is_void() && !image_loc.get_filename().empty()) {
849  const point s = image::get_size(image_loc);
850  w = s.x;
851  h = s.y;
852  }
853 
854  if(w != 0 || h != 0) {
855  // TODO: unduplicate this code
856  const int x = static_cast<int>(tmp_offset * xdst + (1.0 - tmp_offset) * xsrc) + d2;
857  const int y = static_cast<int>(tmp_offset * ydst + (1.0 - tmp_offset) * ysrc) + d2;
858  const double disp_zoom = display::get_singleton()->get_zoom_factor();
859 
860  bool facing_west = (
863 
864  bool facing_north = (
866  direction == map_location::direction::north ||
868 
869  if(!current_data.auto_hflip) { facing_west = false; }
870  if(!current_data.auto_vflip) { facing_north = true; }
871 
872  int my_x = x + disp_zoom * (current_data.x - w / 2);
873  int my_y = y + disp_zoom * (current_data.y - h / 2);
874 
875  if(facing_west) {
876  my_x -= current_data.directional_x * disp_zoom;
877  } else {
878  my_x += current_data.directional_x * disp_zoom;
879  }
880 
881  if(facing_north) {
882  my_y += current_data.directional_y * disp_zoom;
883  } else {
884  my_y -= current_data.directional_y * disp_zoom;
885  }
886 
887  // Check if our underlying hexes are invalidated. If we need to update ourselves because we changed,
888  // invalidate our hexes and return whether or not was successful.
889  const SDL_Rect r {my_x, my_y, int(w * disp_zoom), int(h * disp_zoom)};
890  display::rect_of_hexes underlying_hex = disp->hexes_under_rect(r);
891 
892  result.insert(src);
893  result.insert(underlying_hex.begin(), underlying_hex.end());
894  } else {
895  // We have no "redraw surface" but we still need to invalidate our own hex in case we have a halo
896  // and/or sound that needs a redraw.
897  result.insert(src);
898  result.insert(dst);
899  }
900  }
901 
902  return result;
903 }
904 
905 /**
906  * This function merges the value provided by:
907  * - the frame
908  * - the engine (poison, flying unit...)
909  * - the animation as a whole
910  *
911  * There is no absolute rule for merging, so creativity is the rule. If a value is never provided by the engine, assert.
912  * This way if it becomes used, people will easily find the right place to look.
913  */
914 frame_parameters unit_frame::merge_parameters(int current_time, const frame_parameters& animation_val,
915  const frame_parameters& engine_val) const
916 {
917  frame_parameters result;
918  const frame_parameters& current_val = builder_.parameters(current_time);
919 
920  result.primary_frame = engine_val.primary_frame;
921  if(!boost::logic::indeterminate(animation_val.primary_frame)) {
922  result.primary_frame = animation_val.primary_frame;
923  }
924 
925  if(!boost::logic::indeterminate(current_val.primary_frame)) {
926  result.primary_frame = current_val.primary_frame;
927  }
928 
929  // Convert the tribool to bool
930  const bool primary = static_cast<bool>(result.primary_frame) || boost::logic::indeterminate(result.primary_frame);
931 
932  /** The engine provides a default image to use for the unit when none is available */
933  result.image = current_val.image.is_void() || current_val.image.get_filename().empty()
934  ? animation_val.image
935  : current_val.image;
936 
937  if(primary && (result.image.is_void() || result.image.get_filename().empty())) {
938  result.image = engine_val.image;
939  }
940 
941  /** The engine provides a default image to use for the unit when none is available */
942  result.image_diagonal = current_val.image_diagonal.is_void() || current_val.image_diagonal.get_filename().empty()
943  ? animation_val.image_diagonal
944  : current_val.image_diagonal;
945 
946  if(primary && (result.image_diagonal.is_void() || result.image_diagonal.get_filename().empty())) {
947  result.image_diagonal = engine_val.image_diagonal;
948  }
949 
950  /**
951  * The engine provides a string for "petrified" and "team color" modifications.
952  * Note that image_mod is the complete modification and halo_mod is only the TC part.
953  */
954  result.image_mod = current_val.image_mod + animation_val.image_mod;
955  if(primary) {
956  result.image_mod += engine_val.image_mod;
957  } else {
958  result.image_mod += engine_val.halo_mod;
959  }
960 
961  assert(engine_val.halo.empty());
962  result.halo = current_val.halo.empty() ? animation_val.halo : current_val.halo;
963 
964  assert(engine_val.halo_x == 0);
965  result.halo_x = current_val.halo_x ? current_val.halo_x : animation_val.halo_x;
966 
967  /** The engine provides a y modification for terrain with height adjust and flying units */
968  result.halo_y = current_val.halo_y ? current_val.halo_y : animation_val.halo_y;
969  result.halo_y += engine_val.halo_y;
970 
971  result.halo_mod = current_val.halo_mod + animation_val.halo_mod;
972  result.halo_mod += engine_val.halo_mod;
973 
974  assert(engine_val.duration == 0);
975  result.duration = current_val.duration;
976 
977  assert(engine_val.sound.empty());
978  result.sound = current_val.sound.empty() ? animation_val.sound : current_val.sound;
979 
980  assert(engine_val.text.empty());
981  result.text = current_val.text.empty() ? animation_val.text : current_val.text;
982 
983  assert(!engine_val.text_color);
984  result.text_color = current_val.text_color ? current_val.text_color : animation_val.text_color;
985 
986  /** The engine provides a blend color for poisoned units */
987  result.blend_with = current_val.blend_with ? current_val.blend_with : animation_val.blend_with;
988  if(primary && engine_val.blend_with) {
989  result.blend_with = engine_val.blend_with->blend_lighten(result.blend_with ? *result.blend_with : color_t(0,0,0));
990  }
991 
992  /** The engine provides a blend color for poisoned units */
993  result.blend_ratio = current_val.blend_ratio != 0 ? current_val.blend_ratio:animation_val.blend_ratio;
994  if(primary && engine_val.blend_ratio != 0) {
995  result.blend_ratio = std::min(result.blend_ratio + engine_val.blend_ratio, 1.0);
996  }
997 
998  /** The engine provides a highlight ratio for selected units and visible "invisible" units */
999  result.highlight_ratio = (current_val.highlight_ratio < 0.999 || current_val.highlight_ratio > 1.001) ?
1000  current_val.highlight_ratio : animation_val.highlight_ratio;
1001  if(primary && (engine_val.highlight_ratio < 0.999 || engine_val.highlight_ratio > 1.001)) {
1002  result.highlight_ratio = result.highlight_ratio * engine_val.highlight_ratio; // selected unit
1003  }
1004 
1005  assert(engine_val.offset == 0);
1006  result.offset = (current_val.offset != -1000) ? current_val.offset : animation_val.offset;
1007  if(result.offset == -1000) {
1008  result.offset = 0.0;
1009  }
1010 
1011  /** The engine provides a submerge for units in water */
1012  result.submerge = current_val.submerge != 0 ? current_val.submerge : animation_val.submerge;
1013  if(primary && engine_val.submerge != 0 && result.submerge == 0) {
1014  result.submerge = engine_val.submerge;
1015  }
1016 
1017  assert(engine_val.x == 0);
1018  result.x = current_val.x ? current_val.x : animation_val.x;
1019 
1020  /** The engine provides a y modification for terrain with height adjust and flying units */
1021  result.y = current_val.y?current_val.y:animation_val.y;
1022  result.y += engine_val.y;
1023 
1024  assert(engine_val.directional_x == 0);
1025  result.directional_x = current_val.directional_x ? current_val.directional_x : animation_val.directional_x;
1026 
1027  assert(engine_val.directional_y == 0);
1028  result.directional_y = current_val.directional_y ? current_val.directional_y : animation_val.directional_y;
1029 
1032  ? current_val.drawing_layer
1033  : animation_val.drawing_layer;
1034 
1035  /** The engine provides us with a default value to compare to. Update if different */
1036  result.auto_hflip = engine_val.auto_hflip;
1037 
1038  if(!boost::logic::indeterminate(animation_val.auto_hflip)) {
1039  result.auto_hflip = animation_val.auto_hflip;
1040  }
1041 
1042  if(!boost::logic::indeterminate(current_val.auto_hflip)) {
1043  result.auto_hflip = current_val.auto_hflip;
1044  }
1045 
1046  if(boost::logic::indeterminate(result.auto_hflip)) {
1047  result.auto_hflip = true;
1048  }
1049 
1050  result.auto_vflip = engine_val.auto_vflip;
1051 
1052  if(!boost::logic::indeterminate(animation_val.auto_vflip)) {
1053  result.auto_vflip = animation_val.auto_vflip;
1054  }
1055 
1056  if(!boost::logic::indeterminate(current_val.auto_vflip)) {
1057  result.auto_vflip = current_val.auto_vflip;
1058  }
1059 
1060  if(boost::logic::indeterminate(result.auto_vflip)) {
1061  result.auto_vflip = !primary;
1062  }
1063 
1064  return result;
1065 }
Variant for storing WML attributes.
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:172
bool has_attribute(config_key_type key) const
Definition: config.cpp:157
const attribute_value * get(config_key_type key) const
Returns a pointer to the attribute with the given key or nullptr if it does not exist.
Definition: config.cpp:685
Sort-of-Singleton that many classes, both GUI and non-GUI, use to access the game data.
Definition: display.hpp:97
point get_location(const map_location &loc) const
Functions to get the on-screen positions of hexes.
Definition: display.cpp:677
static int hex_size()
Function which returns the size of a hex in pixels (from top tip to bottom tip or left edge to right ...
Definition: display.hpp:267
static double get_zoom_factor()
Returns the current zoom factor.
Definition: display.hpp:270
static submerge_data get_submerge_data(const rect &dest, double submerge, const point &size, uint8_t alpha, bool hreverse, bool vreverse)
Definition: display.cpp:2157
void drawing_buffer_add(const drawing_layer layer, const map_location &loc, decltype(draw_helper::do_draw) draw_func)
Add an item to the drawing buffer.
Definition: display.cpp:1256
const rect_of_hexes hexes_under_rect(const rect &r) const
Return the rectangular area of hexes overlapped by r (r is in screen coordinates)
Definition: display.cpp:624
rect map_area() const
Returns the area used for the map.
Definition: display.cpp:493
static display * get_singleton()
Returns the display object if a display object exists.
Definition: display.hpp:111
static rect scaled_to_zoom(const SDL_Rect &r)
Scale the width and height of a rect by the current zoom factor.
Definition: display.hpp:276
Easily build frame parameters with the serialized constructors.
Definition: frame.hpp:82
std::string offset_
Definition: frame.hpp:127
std::string halo_y_
Definition: frame.hpp:117
std::string image_mod_
Definition: frame.hpp:114
utils::optional< color_t > blend_with_
Definition: frame.hpp:123
std::string halo_mod_
Definition: frame.hpp:118
std::string image_diagonal_
Definition: frame.hpp:113
frame_builder()
Definition: frame.cpp:27
boost::tribool auto_vflip_
Definition: frame.hpp:134
boost::tribool primary_frame_
Definition: frame.hpp:136
frame_builder & duration(const int duration)
Allow easy chained modifications.
Definition: frame.cpp:144
frame_builder & auto_hflip(const bool auto_hflip)
Definition: frame.cpp:205
std::string halo_
Definition: frame.hpp:115
frame_builder & highlight(const std::string &highlight)
Definition: frame.cpp:157
std::string sound_
Definition: frame.hpp:119
frame_builder & image_diagonal(const std::string &image_diagonal, const std::string &image_mod="")
Definition: frame.cpp:115
frame_builder & directional_x(const std::string &directional_x)
Definition: frame.cpp:187
frame_builder & submerge(const std::string &submerge)
Definition: frame.cpp:169
std::string image_
Definition: frame.hpp:112
std::string submerge_
Definition: frame.hpp:128
int duration_
Definition: frame.hpp:110
frame_builder & y(const std::string &y)
Definition: frame.cpp:181
frame_builder & primary_frame(const bool primary_frame)
Definition: frame.cpp:211
frame_builder & auto_vflip(const bool auto_vflip)
Definition: frame.cpp:199
std::string y_
Definition: frame.hpp:130
std::string highlight_ratio_
Definition: frame.hpp:126
boost::tribool auto_hflip_
Definition: frame.hpp:135
std::string blend_ratio_
Definition: frame.hpp:125
std::string text_
Definition: frame.hpp:120
std::string drawing_layer_
Definition: frame.hpp:138
frame_builder & drawing_layer(const std::string &drawing_layer)
Definition: frame.cpp:217
std::string directional_x_
Definition: frame.hpp:131
std::string directional_y_
Definition: frame.hpp:132
std::string halo_x_
Definition: frame.hpp:116
frame_builder & text(const std::string &text, const color_t text_color)
Definition: frame.cpp:128
frame_builder & blend(const std::string &blend_ratio, const color_t blend_color)
Definition: frame.cpp:150
frame_builder & x(const std::string &x)
Definition: frame.cpp:175
utils::optional< color_t > text_color_
Definition: frame.hpp:122
frame_builder & offset(const std::string &offset)
Definition: frame.cpp:163
frame_builder & directional_y(const std::string &directional_y)
Definition: frame.cpp:193
frame_builder & image(const std::string &image, const std::string &image_mod="")
Definition: frame.cpp:108
frame_builder & sound(const std::string &sound)
Definition: frame.cpp:122
std::string x_
Definition: frame.hpp:129
frame_builder & halo(const std::string &halo, const std::string &halo_x, const std::string &halo_y, const std::string &halo_mod)
Definition: frame.cpp:135
std::string text_
Definition: frame.hpp:182
progressive_int directional_x_
Definition: frame.hpp:193
progressive_int halo_y_
Definition: frame.hpp:178
boost::tribool auto_hflip_
Definition: frame.hpp:197
progressive_image image_
Definition: frame.hpp:171
boost::tribool primary_frame_
Definition: frame.hpp:198
bool need_update() const
Definition: frame.cpp:269
progressive_double blend_ratio_
Definition: frame.hpp:187
progressive_double highlight_ratio_
Definition: frame.hpp:188
progressive_double offset_
Definition: frame.hpp:189
boost::tribool auto_vflip_
Definition: frame.hpp:196
progressive_string halo_
Definition: frame.hpp:176
utils::optional< color_t > blend_with_
Definition: frame.hpp:185
progressive_int x_
Definition: frame.hpp:191
int duration() const
Definition: frame.hpp:161
std::string sound_
Definition: frame.hpp:181
void override(int duration, const std::string &highlight="", const std::string &blend_ratio="", color_t blend_color={0, 0, 0}, const std::string &offset="", const std::string &layer="", const std::string &modifiers="")
Definition: frame.cpp:333
progressive_double submerge_
Definition: frame.hpp:190
std::string halo_mod_
Definition: frame.hpp:180
std::string image_mod_
Definition: frame.hpp:174
progressive_int halo_x_
Definition: frame.hpp:177
progressive_int y_
Definition: frame.hpp:192
progressive_int drawing_layer_
Definition: frame.hpp:200
utils::optional< color_t > text_color_
Definition: frame.hpp:184
frame_parameters parameters(int current_time) const
Getters for the different parameters.
Definition: frame.cpp:274
std::vector< std::string > debug_strings() const
Contents of frame in strings.
Definition: frame.cpp:385
progressive_int directional_y_
Definition: frame.hpp:194
progressive_image image_diagonal_
Definition: frame.hpp:172
bool does_not_change() const
Definition: frame.cpp:250
frame_parsed_parameters(const frame_builder &builder=frame_builder(), int override_duration=0)
Definition: frame.cpp:223
static game_display * get_singleton()
void float_label(const map_location &loc, const std::string &text, const color_t &color)
Function to float a label above a tile.
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
Generic locator abstracting the location of an image.
Definition: picture.hpp:59
bool is_void() const
Returns true if the locator does not correspond to an actual image.
Definition: picture.hpp:93
const std::string & get_filename() const
Definition: picture.hpp:82
const std::string & get_modifications() const
Definition: picture.hpp:87
locator clone(const std::string &mods) const
Returns a copy of this locator with the given IPF.
Definition: picture.cpp:218
std::string get_original() const
virtual bool does_not_change() const
virtual const T get_current_element(int current_time, T default_val=T()) const override
bool does_not_change() const override
virtual const T get_current_element(int current_time, T default_val=T()) const override
Wrapper class to encapsulate creation and management of an SDL_Texture.
Definition: texture.hpp:33
void set_blend_mode(SDL_BlendMode)
Blend mode.
Definition: texture.cpp:191
void set_src(const rect &r)
Set the source region of the texture used for drawing operations.
Definition: texture.cpp:129
void set_alpha_mod(uint8_t alpha)
Alpha modifier.
Definition: texture.cpp:151
void set_color_mod(uint8_t r, uint8_t g, uint8_t b)
Colour modifier.
Definition: texture.cpp:174
frame_parameters merge_parameters(int current_time, const frame_parameters &animation_val, const frame_parameters &engine_val=frame_parameters()) const
This function merges the value provided by:
Definition: frame.cpp:914
void redraw(const int frame_time, bool on_start_time, bool in_scope_of_frame, const map_location &src, const map_location &dst, halo::handle &halo_id, halo::manager &halo_man, const frame_parameters &animation_val, const frame_parameters &engine_val) const
Definition: frame.cpp:627
std::set< map_location > get_overlaped_hex(const int frame_time, const map_location &src, const map_location &dst, const frame_parameters &animation_val, const frame_parameters &engine_val) const
Definition: frame.cpp:793
frame_parsed_parameters builder_
Definition: frame.hpp:251
constexpr uint8_t float_to_color(double n)
Convert a double in the range [0.0,1.0] to an 8-bit colour value.
Definition: color.hpp:280
Drawing functions, for drawing things on the screen.
drawing_layer
@ unit_default
Default layer for drawing units.
@ unit_first
Reserve layers to be selected for wml.
static lg::log_domain log_engine("engine")
#define ERR_NG
Definition: frame.cpp:25
Frame for unit's animation sequence.
constexpr int get_abs_frame_layer(drawing_layer layer)
Definition: frame.hpp:35
progressive_pair< double > progressive_double
progressive_single< image::locator > progressive_image
progressive_single< std::string > progressive_string
progressive_pair< int > progressive_int
int w
Standard logging facilities (interface).
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 smooth_shaded(const texture &tex, const SDL_Rect &dst, const SDL_Color &cTL, const SDL_Color &cTR, const SDL_Color &cBL, const SDL_Color &cBR, const SDL_FPoint &uvTL, const SDL_FPoint &uvTR, const SDL_FPoint &uvBL, const SDL_FPoint &uvBR)
Draw a texture with smoothly varying colour and alpha modification, specified at the four corners of ...
Definition: draw.cpp:431
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
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
point get_size(const locator &i_locator, bool skip_cache)
Returns the width and height of an image.
Definition: picture.cpp:777
bool is_in_hex(const locator &i_locator)
Checks if an image fits into a single hex.
Definition: picture.cpp:786
Audio output for sound and music.
Definition: sound.cpp:40
void play_sound(const std::string &files, channel_group group, unsigned int repeats)
Definition: sound.cpp:1035
std::string half_signed_value(int val)
Sign with Unicode "−" if negative.
std::string bool_string(const bool value)
Converts a bool value to 'true' or 'false'.
std::string_view data
Definition: picture.cpp:178
rect dst
Location on the final composed sheet.
rect src
Non-transparent portion of the surface to compose.
The basic class for representing 8-bit RGB or RGBA colour values.
Definition: color.hpp:59
static color_t from_rgb_string(std::string_view c)
Creates a new opaque color_t object from a string variable in "R,G,B" format.
Definition: color.cpp:44
Rectangular area of hexes, allowing to decide how the top and bottom edges handles the vertical shift...
Definition: display.hpp:328
iterator end() const
Definition: display.cpp:619
iterator begin() const
Definition: display.cpp:615
All parameters from a frame at a given instant.
Definition: frame.hpp:42
std::string halo
Definition: frame.hpp:49
utils::optional< color_t > blend_with
Definition: frame.hpp:59
double highlight_ratio
Definition: frame.hpp:62
std::string text
Definition: frame.hpp:56
boost::tribool auto_hflip
Definition: frame.hpp:72
int directional_x
Definition: frame.hpp:68
double offset
Definition: frame.hpp:63
std::string sound
Definition: frame.hpp:55
std::string image_mod
Definition: frame.hpp:48
double submerge
Definition: frame.hpp:64
int drawing_layer
Definition: frame.hpp:75
image::locator image_diagonal
Definition: frame.hpp:46
boost::tribool primary_frame
Definition: frame.hpp:73
int directional_y
Definition: frame.hpp:69
image::locator image
Definition: frame.hpp:45
utils::optional< color_t > text_color
Definition: frame.hpp:58
double blend_ratio
Definition: frame.hpp:61
std::string halo_mod
Definition: frame.hpp:54
boost::tribool auto_vflip
Definition: frame.hpp:71
Encapsulates the map of the game.
Definition: location.hpp:45
direction
Valid directions which can be moved in our hexagonal world.
Definition: location.hpp:47
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
static map_location::direction s
#define e
#define h