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