The Battle for Wesnoth  1.19.16+dev
animation.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2006 - 2025
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/animation.hpp"
17 
18 #include "color.hpp"
19 #include "display.hpp"
20 #include "global.hpp"
21 #include "map/map.hpp"
22 #include "play_controller.hpp"
23 #include "random.hpp"
24 #include "resources.hpp"
25 #include "serialization/chrono.hpp"
27 #include "units/filter.hpp"
28 #include "units/unit.hpp"
29 #include "utils/from_chars.hpp"
30 #include "utils/general.hpp"
31 #include "variable.hpp"
32 
33 #include <algorithm>
34 #include <thread>
35 
36 using namespace std::chrono_literals;
37 
38 static std::string get_heal_sound(const config& cfg)
39 {
40  return cfg["healed_sound"].empty() ? "heal.wav" : cfg["healed_sound"].str();
41 }
42 
44 {
45  config merge() const
46  {
47  config result = attributes;
48  for(const config::const_all_children_iterator& i : children) {
49  result.add_child(i->key, i->cfg);
50  }
51 
52  return result;
53  }
54 
56  std::vector<config::const_all_children_iterator> children;
57 };
58 
59 typedef std::list<animation_branch> animation_branches;
60 
62 {
64  : itors(cfg.all_children_range()), branches(1), parent(nullptr)
65  {
66  branches.back().attributes.merge_attributes(cfg);
67  }
68 
70  : itors(cfg.all_children_range()), branches(p->branches), parent(p)
71  {
72  // If similar 'if' condition in parent branches, we need to
73  // cull the branches where there are partial matches.
74  // Hence the need to check if the condition has come up before.
75  // Also, the attributes are merged here between branches.
76  bool previously_hits_set = false;
77  bool previously_direction_set = false;
78  bool previously_terrain_set = false;
79  bool previously_value_set = false;
80  bool previously_value_2nd_set = false;
81 
82  const std::string s_cfg_hits = cfg["hits"];
83  const std::string s_cfg_direction = cfg["direction"];
84  const std::string s_cfg_terrain = cfg["terrain_types"];
85  const std::string s_cfg_value = cfg["value"];
86  const std::string s_cfg_value_2nd = cfg["value_2nd"];
87 
88  for(const auto& branch : branches) {
89  const std::string s_branch_hits = branch.attributes["hits"];
90  const std::string s_branch_direction = branch.attributes["direction"];
91  const std::string s_branch_terrain = branch.attributes["terrain_types"];
92  const std::string s_branch_value = branch.attributes["value"];
93  const std::string s_branch_value_2nd = branch.attributes["value_second"];
94 
95  if(!s_branch_hits.empty() && s_branch_hits == s_cfg_hits) {
96  previously_hits_set = true;
97  }
98 
99  if(!s_branch_direction.empty() && s_branch_direction == s_cfg_direction) {
100  previously_direction_set = true;
101  }
102 
103  if(!s_branch_terrain.empty() && s_branch_terrain == s_cfg_terrain) {
104  previously_terrain_set = true;
105  }
106 
107  if(!s_branch_value.empty() && s_branch_value == s_cfg_value) {
108  previously_value_set = true;
109  }
110 
111  if(!s_branch_value_2nd.empty() && s_branch_value_2nd == s_cfg_value_2nd) {
112  previously_value_2nd_set = true;
113  }
114  }
115 
116  // Merge all frames that have new matches and prune any impossible
117  // matches, e.g. hits='yes' and hits='no'
118  for(auto iter = branches.begin(); iter != branches.end(); /* nothing */) {
119  const std::string s_branch_hits = (*iter).attributes["hits"];
120  const std::string s_branch_direction = (*iter).attributes["direction"];
121  const std::string s_branch_terrain = (*iter).attributes["terrain_types"];
122  const std::string s_branch_value = (*iter).attributes["value"];
123  const std::string s_branch_value_2nd = (*iter).attributes["value_second"];
124 
125  const bool hits_match = (previously_hits_set && s_branch_hits != s_cfg_hits);
126  const bool direction_match = (previously_direction_set && s_branch_direction != s_cfg_direction);
127  const bool terrain_match = (previously_terrain_set && s_branch_terrain != s_cfg_terrain);
128  const bool value_match = (previously_value_set && s_branch_value != s_cfg_value);
129  const bool value_2nd_match = (previously_value_2nd_set && s_branch_value_2nd != s_cfg_value_2nd);
130 
131  if((!previously_hits_set || hits_match) &&
132  (!previously_direction_set || direction_match) &&
133  (!previously_terrain_set || terrain_match) &&
134  (!previously_value_set || value_match) &&
135  (!previously_value_2nd_set || value_2nd_match) &&
136  (hits_match || direction_match || terrain_match || value_match || value_2nd_match))
137  {
138  branches.erase(iter++);
139  } else {
140  (*iter).attributes.merge_attributes(cfg);
141  ++iter;
142  }
143  }
144 
145  // Then we prune all parent branches with similar matches as they
146  // now will not have the full frame list
147  for(auto iter = parent->branches.begin(); iter != parent->branches.end(); /* nothing */) {
148  const std::string s_branch_hits = (*iter).attributes["hits"];
149  const std::string s_branch_direction = (*iter).attributes["direction"];
150  const std::string s_branch_terrain = (*iter).attributes["terrain_types"];
151  const std::string s_branch_value = (*iter).attributes["value"];
152  const std::string s_branch_value_2nd = (*iter).attributes["value_second"];
153 
154  const bool hits_match = (previously_hits_set && s_branch_hits == s_cfg_hits);
155  const bool direction_match = (previously_direction_set && s_branch_direction == s_cfg_direction);
156  const bool terrain_match = (previously_terrain_set && s_branch_terrain == s_cfg_terrain);
157  const bool value_match = (previously_value_set && s_branch_value == s_cfg_value);
158  const bool value_2nd_match = (previously_value_2nd_set && s_branch_value_2nd == s_cfg_value_2nd);
159 
160  if((!previously_hits_set || hits_match) &&
161  (!previously_direction_set || direction_match) &&
162  (!previously_terrain_set || terrain_match) &&
163  (!previously_value_set || value_match) &&
164  (!previously_value_2nd_set || value_2nd_match) &&
165  (hits_match || direction_match || terrain_match || value_match || value_2nd_match))
166  {
167  parent->branches.erase(iter++);
168  } else {
169  ++iter;
170  }
171  }
172  }
173 
175 
178 };
179 
180 static void prepare_single_animation(const config& anim_cfg, animation_branches& expanded_anims)
181 {
182  /* The anim_cursor holds the current parsing through the config and the branches hold the data
183  * that will be interpreted as the actual animation. The branches store the config attributes
184  * for each block and the children of those branches make up all the 'frame', 'missile_frame',
185  * etc. individually (so 2 instances of 'frame' would be stored as 2 children).
186  */
187  std::list<animation_cursor> anim_cursors;
188  anim_cursors.emplace_back(anim_cfg);
189 
190  while(!anim_cursors.empty()) {
191  animation_cursor& ac = anim_cursors.back();
192 
193  // Reached end of sub-tag config block
194  if(ac.itors.empty()) {
195  if(!ac.parent) break;
196 
197  // Merge all the current branches into the parent.
198  ac.parent->branches.splice(ac.parent->branches.end(), ac.branches);
199  anim_cursors.pop_back();
200  continue;
201  }
202 
203  if(ac.itors.front().key != "if") {
204  // Append current config object to all the branches in scope.
205  for(animation_branch &ab : ac.branches) {
206  ab.children.push_back(ac.itors.begin());
207  }
208 
209  ac.itors.pop_front();
210  continue;
211  }
212 
213  int count = 0;
214  do {
215  // Copies the current branches to each cursor created for the conditional clauses.
216  // Merge attributes of the clause into them.
217  anim_cursors.emplace_back(ac.itors.front().cfg, &ac);
218  ac.itors.pop_front();
219  ++count;
220  } while (!ac.itors.empty() && ac.itors.front().key == "else");
221 
222  if(count > 1) {
223  // When else statements present, clear all branches before 'if'
224  ac.branches.clear();
225  }
226  }
227 
228 #if 0
229  // Debug aid
230  for(animation_branch& ab : anim_cursors.back().branches) {
231  std::cout << "--branch--\n" << ab.attributes;
232  for(config::all_children_iterator &ci : ab.children) {
233  std::cout << "--branchcfg--\n" << ci->cfg;
234  }
235  std::cout << "\n";
236  }
237 #endif
238 
239  // Create the config object describing each branch.
240  assert(anim_cursors.size() == 1);
241  animation_cursor& ac = anim_cursors.back();
242  expanded_anims.splice(expanded_anims.end(), ac.branches, ac.branches.begin(), ac.branches.end());
243 }
244 
245 static animation_branches prepare_animation(const config& cfg, const std::string& animation_tag)
246 {
247  animation_branches expanded_animations;
248  for(const config &anim : cfg.child_range(animation_tag)) {
249  prepare_single_animation(anim, expanded_animations);
250  }
251 
252  return expanded_animations;
253 }
254 
255 unit_animation::unit_animation(const std::chrono::milliseconds& start_time,
256  const unit_frame& frame, const std::string& event, const int variation, const frame_builder& builder)
257  : terrain_types_()
258  , unit_filter_()
259  , secondary_unit_filter_()
260  , directions_()
261  , frequency_(0)
262  , base_score_(variation)
263  , event_(utils::split(event))
264  , value_()
265  , primary_attack_filter_()
266  , secondary_attack_filter_()
267  , hits_()
268  , value2_()
269  , sub_anims_()
270  , unit_anim_(start_time,builder)
271  , src_()
272  , dst_()
273  , invalidated_(false)
274  , play_offscreen_(true)
275  , overlaped_hex_()
276 {
277  add_frame(frame.duration(),frame,!frame.does_not_change());
278 }
279 
280 unit_animation::unit_animation(const config& cfg,const std::string& frame_string )
281  : terrain_types_(t_translation::read_list(cfg["terrain_type"].str()))
282  , unit_filter_()
283  , secondary_unit_filter_()
284  , directions_()
285  , frequency_(cfg["frequency"].to_int())
286  , base_score_(cfg["base_score"].to_int())
287  , event_()
288  , value_()
289  , primary_attack_filter_()
290  , secondary_attack_filter_()
291  , hits_()
292  , value2_()
293  , sub_anims_()
294  , unit_anim_(cfg,frame_string)
295  , src_()
296  , dst_()
297  , invalidated_(false)
298  , play_offscreen_(true)
299  , overlaped_hex_()
300 {
301  //if(!cfg["debug"].empty()) printf("DEBUG WML: FINAL\n%s\n\n",cfg.debug().c_str());
302 
303  for(const auto [key, frame] : cfg.all_children_view()) {
304  if(key == frame_string) {
305  continue;
306  }
307 
308  if(key.find("_frame", key.size() - 6) == std::string::npos) {
309  continue;
310  }
311 
312  if(sub_anims_.find(key) != sub_anims_.end()) {
313  continue;
314  }
315 
316  sub_anims_[key] = particle(cfg, key.substr(0, key.size() - 5));
317  }
318 
319  event_ = utils::split(cfg["apply_to"]);
320 
321  const std::vector<std::string>& my_directions = utils::split(cfg["direction"]);
322  for(const auto& direction : my_directions) {
324  directions_.push_back(d);
325  }
326 
327  /*const filter_context* fc = game_display::get_singleton();
328  if(!fc) {
329  // This is a pointer to the gamestate. Would prefer to tie unit animations only to the display, but for now this
330  // is an acceptable fallback. It seems to be relevant because when a second game is created, it seems that the
331  // game_display is null at the time that units are being constructed, and hence at the time that this code is running.
332  // A different solution might be to delay the team_builder stage 2 call until after the gui is initialized. Note that
333  // the current set up could conceivably cause problems with the editor, iirc it doesn't initialize a filter context.
334  fc = resources::filter_con;
335  assert(fc);
336  }*/
337 
338  for(const config& filter : cfg.child_range("filter")) {
339  unit_filter_.push_back(filter);
340  }
341 
342  for(const config& filter : cfg.child_range("filter_second")) {
343  secondary_unit_filter_.push_back(filter);
344  }
345 
346  for(const std::string& v : utils::split(cfg["value"])) {
347  value_.push_back(utils::from_chars<int>(v).value_or(0));
348  }
349 
350  for(const auto& h : utils::split(cfg["hits"])) {
351  if(h == "yes" || h == strike_result::hit) {
352  hits_.push_back(strike_result::type::hit);
353  }
354 
355  if(h == "no" || h == strike_result::miss) {
356  hits_.push_back(strike_result::type::miss);
357  }
358 
359  if(h == "yes" || h == strike_result::kill ) {
360  hits_.push_back(strike_result::type::kill);
361  }
362  }
363 
364  for(const std::string& v2 : utils::split(cfg["value_second"])) {
365  value2_.push_back(utils::from_chars<int>(v2).value_or(0));
366  }
367 
368  for(const config& filter : cfg.child_range("filter_attack")) {
369  primary_attack_filter_.push_back(filter);
370  }
371 
372  for(const config& filter : cfg.child_range("filter_second_attack")) {
373  secondary_attack_filter_.push_back(filter);
374  }
375 
376  play_offscreen_ = cfg["offscreen"].to_bool(true);
377 }
378 
379 int unit_animation::matches(const map_location& loc, const map_location& second_loc,
380  const unit_const_ptr& my_unit, const std::string& event, const int value, strike_result::type hit, const const_attack_ptr& attack,
381  const const_attack_ptr& second_attack, int value2) const
382 {
383  int result = base_score_;
384  const display& disp = *display::get_singleton();
385 
386  if(!event.empty() && !event_.empty()) {
387  if(!utils::contains(event_, event)) {
388  return MATCH_FAIL;
389  }
390 
391  result++;
392  }
393 
394  if(!terrain_types_.empty()) {
396  return MATCH_FAIL;
397  }
398 
399  result++;
400  }
401 
402  if(!value_.empty()) {
403  if(!utils::contains(value_, value)) {
404  return MATCH_FAIL;
405  }
406 
407  result++;
408  }
409 
410  if(my_unit) {
411  if(!directions_.empty()) {
412  if(!utils::contains(directions_, my_unit->facing())) {
413  return MATCH_FAIL;
414  }
415 
416  result++;
417  }
418 
419  for(const auto& filter : unit_filter_) {
421  if(!f(*my_unit, loc)) return MATCH_FAIL;
422  ++result;
423  }
424 
425  if(!secondary_unit_filter_.empty()) {
426  unit_map::const_iterator unit = disp.context().units().find(second_loc);
427  if(!unit.valid()) {
428  return MATCH_FAIL;
429  }
430 
431  for(const config& c : secondary_unit_filter_) {
432  unit_filter f{ vconfig(c) };
433  if(!f(*unit, second_loc)) return MATCH_FAIL;
434  result++;
435  }
436  }
437  } else if(!unit_filter_.empty()) {
438  return MATCH_FAIL;
439  }
440 
441  if(frequency_ && !(randomness::rng::default_instance().get_random_int(0, frequency_-1))) {
442  return MATCH_FAIL;
443  }
444 
445  if(!hits_.empty()) {
446  if(!utils::contains(hits_, hit)) {
447  return MATCH_FAIL;
448  }
449 
450  result ++;
451  }
452 
453  if(!value2_.empty()) {
454  if(!utils::contains(value2_, value2)) {
455  return MATCH_FAIL;
456  }
457 
458  result ++;
459  }
460 
461  if(!attack) {
462  if(!primary_attack_filter_.empty()) {
463  return MATCH_FAIL;
464  }
465  }
466 
467  for(const auto& iter : primary_attack_filter_) {
468  if(!attack->matches_filter(iter)) return MATCH_FAIL;
469  result++;
470  }
471 
472  if(!second_attack) {
473  if(!secondary_attack_filter_.empty()) {
474  return MATCH_FAIL;
475  }
476  }
477 
478  for(const auto& iter : secondary_attack_filter_) {
479  if(!second_attack->matches_filter(iter)) return MATCH_FAIL;
480  result++;
481  }
482 
483  return result;
484 }
485 
486 void unit_animation::fill_initial_animations(std::vector<unit_animation>& animations, const config& cfg)
487 {
488  add_anims(animations, cfg);
489 
490  std::vector<unit_animation> animation_base;
491  for(const auto& anim : animations) {
492  if(utils::contains(anim.event_, "default")) {
493  animation_base.push_back(anim);
494  animation_base.back().base_score_ += unit_animation::DEFAULT_ANIM;
495  animation_base.back().event_.clear();
496  }
497  }
498 
499  const std::string default_image = cfg["image"];
500 
501  if(animation_base.empty()) {
502  animation_base.push_back(unit_animation(0ms, frame_builder().image(default_image).duration(1ms), "", unit_animation::DEFAULT_ANIM));
503  }
504 
505  animations.push_back(unit_animation(0ms, frame_builder().image(default_image).duration(1ms), "_disabled_", 0));
506  animations.push_back(unit_animation(0ms,
507  frame_builder().image(default_image).duration(300ms).blend("0.0~0.3:100,0.3~0.0:200", {255,255,255}),
508  "_disabled_selected_", 0));
509 
510  for(const auto& base : animation_base) {
511  animations.push_back(base);
512  animations.back().event_ = { "standing" };
513  animations.back().play_offscreen_ = false;
514 
515  animations.push_back(base);
516  animations.back().event_ = { "_ghosted_" };
517  animations.back().unit_anim_.override(0ms, animations.back().unit_anim_.get_animation_duration(),particle::UNSET,"0.9", "", {0,0,0}, "", "", "~GS()");
518 
519  animations.push_back(base);
520  animations.back().event_ = { "_disabled_ghosted_" };
521  animations.back().unit_anim_.override(0ms, 1ms, particle::UNSET, "0.4", "", {0,0,0}, "", "", "~GS()");
522 
523  animations.push_back(base);
524  animations.back().event_ = { "selected" };
525  animations.back().unit_anim_.override(0ms, 300ms, particle::UNSET, "", "0.0~0.3:100,0.3~0.0:200", {255,255,255});
526 
527  animations.push_back(base);
528  animations.back().event_ = { "recruited" };
529  animations.back().unit_anim_.override(0ms, 600ms, particle::NO_CYCLE, "0~1:600");
530 
531  animations.push_back(base);
532  animations.back().event_ = { "levelin" };
533  animations.back().unit_anim_.override(0ms, 600ms, particle::NO_CYCLE, "", "1~0:600", {255,255,255});
534 
535  animations.push_back(base);
536  animations.back().event_ = { "levelout" };
537  animations.back().unit_anim_.override(0ms, 600ms, particle::NO_CYCLE, "", "0~1:600,1", {255,255,255});
538 
539  animations.push_back(base);
540  animations.back().event_ = { "pre_movement" };
541  animations.back().unit_anim_.override(0ms, 1ms, particle::NO_CYCLE);
542 
543  animations.push_back(base);
544  animations.back().event_ = { "post_movement" };
545  animations.back().unit_anim_.override(0ms, 1ms, particle::NO_CYCLE);
546 
547  animations.push_back(base);
548  animations.back().event_ = { "movement" };
549  animations.back().unit_anim_.override(0ms, 200ms,
550  particle::NO_CYCLE, "", "", {0,0,0}, "0~1:200", std::to_string(get_abs_frame_layer(drawing_layer::unit_move_default)));
551 
552  animations.push_back(base);
553  animations.back().event_ = { "defend" };
554  animations.back().unit_anim_.override(0ms, animations.back().unit_anim_.get_animation_duration(),
555  particle::NO_CYCLE, "", "0.0,0.5:75,0.0:75,0.5:75,0.0", {255,0,0});
556  animations.back().hits_.push_back(strike_result::type::hit);
557  animations.back().hits_.push_back(strike_result::type::kill);
558 
559  animations.push_back(base);
560  animations.back().event_ = { "defend" };
561 
562  animations.push_back(base);
563  animations.back().event_ = { "attack" };
564  animations.back().unit_anim_.override(-150ms, 300ms, particle::NO_CYCLE, "", "", {0,0,0}, "0~0.6:150,0.6~0:150", std::to_string(get_abs_frame_layer(drawing_layer::unit_move_default)));
565  animations.back().primary_attack_filter_.emplace_back("range", "melee");
566 
567  animations.push_back(base);
568  animations.back().event_ = { "attack" };
569  animations.back().unit_anim_.override(-150ms, 150ms, particle::NO_CYCLE);
570  animations.back().primary_attack_filter_.emplace_back("range", "ranged");
571 
572  animations.push_back(base);
573  animations.back().event_ = { "death" };
574  animations.back().unit_anim_.override(0ms, 600ms, particle::NO_CYCLE, "1~0:600");
575  animations.back().sub_anims_["_death_sound"] = particle();
576  animations.back().sub_anims_["_death_sound"].add_frame(1ms, frame_builder().sound(cfg["die_sound"]), true);
577 
578  animations.push_back(base);
579  animations.back().event_ = { "victory" };
580  animations.back().unit_anim_.override(0ms, animations.back().unit_anim_.get_animation_duration(), particle::CYCLE);
581 
582  animations.push_back(base);
583  animations.back().unit_anim_.override(0ms, 150ms, particle::NO_CYCLE, "1~0:150");
584  animations.back().event_ = { "pre_teleport" };
585 
586  animations.push_back(base);
587  animations.back().unit_anim_.override(0ms, 150ms, particle::NO_CYCLE, "0~1:150,1");
588  animations.back().event_ = { "post_teleport" };
589 
590  animations.push_back(base);
591  animations.back().event_ = { "healing" };
592 
593  animations.push_back(base);
594  animations.back().event_ = { "healed" };
595  animations.back().unit_anim_.override(0ms, 300ms, particle::NO_CYCLE, "", "0:30,0.5:30,0:30,0.5:30,0:30,0.5:30,0:30,0.5:30,0:30", {255,255,255});
596 
597  const std::string healed_sound = get_heal_sound(cfg);
598 
599  animations.back().sub_anims_["_healed_sound"].add_frame(1ms, frame_builder().sound(healed_sound), true);
600 
601  animations.push_back(base);
602  animations.back().event_ = { "poisoned" };
603  animations.back().unit_anim_.override(0ms, 300ms, particle::NO_CYCLE, "", "0:30,0.5:30,0:30,0.5:30,0:30,0.5:30,0:30,0.5:30,0:30", {0,255,0});
604  animations.back().sub_anims_["_poison_sound"] = particle();
605  animations.back().sub_anims_["_poison_sound"].add_frame(1ms, frame_builder().sound(game_config::sounds::status::poisoned), true);
606  }
607 }
608 
609 static void add_simple_anim(std::vector<unit_animation>& animations,
610  const config& cfg, char const* tag_name, char const* apply_to,
612  bool offscreen = true)
613 {
614  for(const animation_branch& ab : prepare_animation(cfg, tag_name)) {
615  config anim = ab.merge();
616  anim["apply_to"] = apply_to;
617 
618  if(!offscreen) {
619  config::attribute_value& v = anim["offscreen"];
620  if(v.empty()) v = false;
621  }
622 
623  config::attribute_value& v = anim["layer"];
624  if(v.empty()) v = get_abs_frame_layer(layer);
625 
626  animations.emplace_back(anim);
627  }
628 }
629 
630 void unit_animation::add_anims( std::vector<unit_animation> & animations, const config & cfg)
631 {
632  for(const animation_branch& ab : prepare_animation(cfg, "animation")) {
633  animations.emplace_back(ab.merge());
634  }
635 
636  constexpr int default_layer = get_abs_frame_layer(drawing_layer::unit_default);
637  constexpr int move_layer = get_abs_frame_layer(drawing_layer::unit_move_default);
638  constexpr int missile_layer = get_abs_frame_layer(drawing_layer::unit_missile_default);
639 
640  add_simple_anim(animations, cfg, "resistance_anim", "resistance");
641  add_simple_anim(animations, cfg, "leading_anim", "leading");
642  add_simple_anim(animations, cfg, "teaching_anim", "teaching");
643  add_simple_anim(animations, cfg, "recruit_anim", "recruited");
644  add_simple_anim(animations, cfg, "recruiting_anim", "recruiting");
645  add_simple_anim(animations, cfg, "idle_anim", "idling", drawing_layer::unit_default, false);
646  add_simple_anim(animations, cfg, "levelin_anim", "levelin");
647  add_simple_anim(animations, cfg, "levelout_anim", "levelout");
648 
649  for(const animation_branch& ab : prepare_animation(cfg, "standing_anim")) {
650  config anim = ab.merge();
651  anim["apply_to"] = "standing";
652  anim["cycles"] = true;
653 
654  // Add cycles to all frames within a standing animation block
655  for(config::const_all_children_iterator ci : ab.children) {
656  std::string sub_frame_name = ci->key;
657  std::size_t pos = sub_frame_name.find("_frame");
658  if(pos != std::string::npos) {
659  anim[sub_frame_name.substr(0, pos) + "_cycles"] = true;
660  }
661  }
662 
663  if(anim["layer"].empty()) {
664  anim["layer"] = default_layer;
665  }
666 
667  if(anim["offscreen"].empty()) {
668  anim["offscreen"] = false;
669  }
670 
671  animations.emplace_back(anim);
672  }
673 
674  // Standing animations are also used as default animations
675  for(const animation_branch& ab : prepare_animation(cfg, "standing_anim")) {
676  config anim = ab.merge();
677  anim["apply_to"] = "default";
678  anim["cycles"] = true;
679 
680  for(config::const_all_children_iterator ci : ab.children) {
681  std::string sub_frame_name = ci->key;
682  std::size_t pos = sub_frame_name.find("_frame");
683  if(pos != std::string::npos) {
684  anim[sub_frame_name.substr(0, pos) + "_cycles"] = true;
685  }
686  }
687 
688  if(anim["layer"].empty()) {
689  anim["layer"] = default_layer;
690  }
691 
692  if(anim["offscreen"].empty()) {
693  anim["offscreen"] = false;
694  }
695 
696  animations.emplace_back(anim);
697  }
698 
699  for(const animation_branch& ab : prepare_animation(cfg, "healing_anim")) {
700  config anim = ab.merge();
701  anim["apply_to"] = "healing";
702  anim["value"] = anim["damage"];
703 
704  if(anim["layer"].empty()) {
705  anim["layer"] = default_layer;
706  }
707 
708  animations.emplace_back(anim);
709  }
710 
711  for(const animation_branch& ab : prepare_animation(cfg, "healed_anim")) {
712  config anim = ab.merge();
713  anim["apply_to"] = "healed";
714  anim["value"] = anim["healing"];
715 
716  if(anim["layer"].empty()) {
717  anim["layer"] = default_layer;
718  }
719 
720  animations.emplace_back(anim);
721  animations.back().sub_anims_["_healed_sound"] = particle();
722 
723  const std::string healed_sound = get_heal_sound(cfg);
724  animations.back().sub_anims_["_healed_sound"].add_frame(1ms,frame_builder().sound(healed_sound),true);
725  }
726 
727  for(const animation_branch &ab : prepare_animation(cfg, "poison_anim")) {
728  config anim = ab.merge();
729  anim["apply_to"] = "poisoned";
730  anim["value"] = anim["damage"];
731 
732  if(anim["layer"].empty()) {
733  anim["layer"] = default_layer;
734  }
735 
736  animations.emplace_back(anim);
737  animations.back().sub_anims_["_poison_sound"] = particle();
738  animations.back().sub_anims_["_poison_sound"].add_frame(1ms,frame_builder().sound(game_config::sounds::status::poisoned),true);
739  }
740 
741  add_simple_anim(animations, cfg, "pre_movement_anim", "pre_movement", drawing_layer::unit_move_default);
742 
743  for(const animation_branch& ab : prepare_animation(cfg, "movement_anim")) {
744  config anim = ab.merge();
745  anim["apply_to"] = "movement";
746 
747  if(anim["offset"].empty()) {
748  anim["offset"] = "0~1:200,0~1:200,0~1:200,0~1:200,0~1:200,0~1:200,0~1:200,0~1:200,0~1:200,0~1:200,0~1:200,0~1:200,0~1:200,0~1:200,0~1:200,0~1:200,0~1:200,0~1:200,0~1:200,0~1:200,0~1:200,0~1:200,0~1:200,0~1:200,0~1:200,0~1:200,0~1:200,0~1:200,0~1:200,0~1:200,0~1:200,0~1:200,0~1:200,0~1:200,";
749  }
750 
751  if(anim["layer"].empty()) {
752  anim["layer"] = move_layer;
753  }
754 
755  animations.emplace_back(anim);
756  }
757 
758  add_simple_anim(animations, cfg, "post_movement_anim", "post_movement", drawing_layer::unit_move_default);
759 
760  for(const animation_branch& ab : prepare_animation(cfg, "defend")) {
761  config anim = ab.merge();
762  anim["apply_to"] = "defend";
763 
764  if(anim["layer"].empty()) {
765  anim["layer"] = default_layer;
766  }
767 
768  if(!anim["damage"].empty() && anim["value"].empty()) {
769  anim["value"] = anim["damage"];
770  }
771 
772  if(anim["hits"].empty()) {
773  anim["hits"] = false;
774  animations.emplace_back(anim);
775  animations.back().base_score_--; //so default doesn't interfere with 'if' block
776 
777  anim["hits"] = true;
778  animations.emplace_back(anim);
779  animations.back().base_score_--;
780 
781  image::locator image_loc = animations.back().get_last_frame().end_parameters().image;
782  animations.back().add_frame(225ms, frame_builder()
783  .image(image_loc.get_filename()+image_loc.get_modifications())
784  .duration(225ms)
785  .blend("0.0,0.5:75,0.0:75,0.5:75,0.0", {255,0,0}));
786  } else {
787  for(const std::string& hit_type : utils::split(anim["hits"])) {
788  config tmp = anim;
789  tmp["hits"] = hit_type;
790 
791  animations.emplace_back(tmp);
792 
793  image::locator image_loc = animations.back().get_last_frame().end_parameters().image;
794  if(hit_type == "yes" || hit_type == strike_result::hit || hit_type == strike_result::kill) {
795  animations.back().add_frame(225ms, frame_builder()
796  .image(image_loc.get_filename() + image_loc.get_modifications())
797  .duration(225ms)
798  .blend("0.0,0.5:75,0.0:75,0.5:75,0.0", {255,0,0}));
799  }
800  }
801  }
802  }
803 
804  add_simple_anim(animations, cfg, "draw_weapon_anim", "draw_weapon", drawing_layer::unit_move_default);
805  add_simple_anim(animations, cfg, "sheath_weapon_anim", "sheath_weapon", drawing_layer::unit_move_default);
806 
807  for(const animation_branch& ab : prepare_animation(cfg, "attack_anim")) {
808  config anim = ab.merge();
809  anim["apply_to"] = "attack";
810 
811  if(anim["layer"].empty()) {
812  anim["layer"] = move_layer;
813  }
814 
815  config::const_child_itors missile_fs = anim.child_range("missile_frame");
816  if(anim["offset"].empty() && missile_fs.empty()) {
817  anim["offset"] ="0~0.6,0.6~0";
818  }
819 
820  if(!missile_fs.empty()) {
821  if(anim["missile_offset"].empty()) {
822  anim["missile_offset"] = "0~0.8";
823  }
824 
825  if(anim["missile_layer"].empty()) {
826  anim["missile_layer"] = missile_layer;
827  }
828 
829  config tmp;
830  tmp["duration"] = 1;
831 
832  anim.add_child("missile_frame", tmp);
833  anim.add_child_at("missile_frame", tmp, 0);
834  }
835 
836  animations.emplace_back(anim);
837  }
838 
839  for(const animation_branch& ab : prepare_animation(cfg, "death")) {
840  config anim = ab.merge();
841  anim["apply_to"] = "death";
842 
843  if(anim["layer"].empty()) {
844  anim["layer"] = default_layer;
845  }
846 
847  animations.emplace_back(anim);
848  image::locator image_loc = animations.back().get_last_frame().end_parameters().image;
849 
850  animations.back().add_frame(600ms, frame_builder()
851  .image(image_loc.get_filename()+image_loc.get_modifications())
852  .duration(600ms)
853  .highlight("1~0:600"));
854 
855  if(!cfg["die_sound"].empty()) {
856  animations.back().sub_anims_["_death_sound"] = particle();
857  animations.back().sub_anims_["_death_sound"].add_frame(1ms,frame_builder().sound(cfg["die_sound"]),true);
858  }
859  }
860 
861  add_simple_anim(animations, cfg, "victory_anim", "victory");
862 
863  for(const animation_branch& ab : prepare_animation(cfg, "extra_anim")) {
864  config anim = ab.merge();
865  anim["apply_to"] = anim["flag"];
866 
867  if(anim["layer"].empty()) {
868  anim["layer"] = default_layer;
869  }
870 
871  animations.emplace_back(anim);
872  }
873 
874  for(const animation_branch& ab : prepare_animation(cfg, "teleport_anim")) {
875  config anim = ab.merge();
876  if(anim["layer"].empty()) {
877  anim["layer"] = default_layer;
878  }
879 
880  anim["apply_to"] = "pre_teleport";
881  animations.emplace_back(anim);
882  animations.back().unit_anim_.set_end_time(0ms);
883 
884  anim["apply_to"] ="post_teleport";
885  animations.emplace_back(anim);
886  animations.back().unit_anim_.remove_frames_until(0ms);
887  }
888 }
889 
890 void unit_animation::particle::override(const std::chrono::milliseconds& start_time
891  , const std::chrono::milliseconds& duration
892  , const cycle_state cycles
893  , const std::string& highlight
894  , const std::string& blend_ratio
895  , color_t blend_color
896  , const std::string& offset
897  , const std::string& layer
898  , const std::string& modifiers)
899 {
900  set_begin_time(start_time);
901  parameters_.override(duration,highlight,blend_ratio,blend_color,offset,layer,modifiers);
902 
903  if(cycles == CYCLE) {
904  cycles_=true;
905  } else if(cycles==NO_CYCLE) {
906  cycles_=false;
907  }
908 
909  if(get_animation_duration() < duration) {
911  } else if(get_animation_duration() > duration) {
912  set_end_time(duration);
913  }
914 }
915 
917 {
918  if(animated<unit_frame>::need_update()) return true;
919  if(get_current_frame().need_update()) return true;
920  if(parameters_.need_update()) return true;
921  return false;
922 }
923 
925 {
926  return get_current_frame_begin_time() != last_frame_begin_time_;
927 }
928 
929 unit_animation::particle::particle(const config& cfg, const std::string& frame_string)
930  : animated<unit_frame>()
931  , accelerate(true)
932  , parameters_()
933  , halo_id_()
934  , last_frame_begin_time_(0)
935  , cycles_(false)
936 {
937  starting_frame_time_ = std::chrono::milliseconds::max();
938 
939  config::const_child_itors range = cfg.child_range(frame_string + "frame");
940  if(!range.empty() && cfg[frame_string + "start_time"].empty()) {
941  for(const config& frame : range) {
942  starting_frame_time_ = std::min(starting_frame_time_, chrono::parse_duration<std::chrono::milliseconds>(frame["begin"]));
943  }
944  } else {
945  starting_frame_time_ = chrono::parse_duration<std::chrono::milliseconds>(cfg[frame_string + "start_time"]);
946  }
947 
948  for(const config& frame : range) {
949  unit_frame tmp_frame(frame);
950  add_frame(tmp_frame.duration(), tmp_frame, !tmp_frame.does_not_change());
951  }
952 
953  cycles_ = cfg[frame_string + "cycles"].to_bool(false);
955 
957  force_change();
958  }
959 }
960 
962 {
963  if(unit_anim_.need_update()) return true;
964  for(const auto& anim : sub_anims_) {
965  if(anim.second.need_update()) return true;
966  }
967 
968  return false;
969 }
970 
972 {
973  if(!play_offscreen_) {
974  return false;
975  }
976 
977  if(unit_anim_.need_minimal_update()) return true;
978 
979  for(const auto& anim : sub_anims_) {
980  if(anim.second.need_minimal_update()) return true;
981  }
982 
983  return false;
984 }
985 
987 {
988  if(!unit_anim_.animation_finished()) return false;
989  for(const auto& anim : sub_anims_) {
990  if(!anim.second.animation_finished()) return false;
991  }
992 
993  return true;
994 }
995 
997 {
998  if(!unit_anim_.animation_finished_potential()) return false;
999  for(const auto& anim : sub_anims_) {
1000  if(!anim.second.animation_finished_potential()) return false;
1001  }
1002 
1003  return true;
1004 }
1005 
1007 {
1008  double acceleration = unit_anim_.accelerate ? display::get_singleton()->turbo_speed() : 1.0;
1009  unit_anim_.update_last_draw_time(acceleration);
1010  for(auto& anim : sub_anims_) {
1011  anim.second.update_last_draw_time(acceleration);
1012  }
1013 }
1014 
1015 std::chrono::milliseconds unit_animation::get_end_time() const
1016 {
1017  auto result = unit_anim_.get_end_time();
1018  for(const auto& anim : sub_anims_) {
1019  result = std::max(result, anim.second.get_end_time());
1020  }
1021 
1022  return result;
1023 }
1024 
1025 std::chrono::milliseconds unit_animation::get_begin_time() const
1026 {
1027  auto result = unit_anim_.get_begin_time();
1028  for(const auto& anim : sub_anims_) {
1029  result = std::min(result, anim.second.get_begin_time());
1030  }
1031 
1032  return result;
1033 }
1034 
1035 void unit_animation::start_animation(const std::chrono::milliseconds& start_time
1036  , const map_location& src
1037  , const map_location& dst
1038  , const std::string& text
1039  , const color_t text_color
1040  , const bool accelerate)
1041 {
1042  unit_anim_.accelerate = accelerate;
1043  src_ = src;
1044  dst_ = dst;
1045 
1046  unit_anim_.start_animation(start_time);
1047 
1048  if(!text.empty()) {
1049  particle crude_build;
1050  crude_build.add_frame(1ms, frame_builder());
1051  crude_build.add_frame(1ms, frame_builder().text(text, text_color), true);
1052  sub_anims_["_add_text"] = crude_build;
1053  }
1054 
1055  for(auto& anim : sub_anims_) {
1056  anim.second.accelerate = accelerate;
1057  anim.second.start_animation(start_time);
1058  }
1059 }
1060 
1062 {
1063  src_ = src;
1064  dst_ = dst;
1065 }
1066 
1068 {
1070 
1071  for(auto& anim : sub_anims_) {
1072  anim.second.pause_animation();
1073  }
1074 }
1075 
1077 {
1079 
1080  for(auto& anim : sub_anims_) {
1081  anim.second.restart_animation();
1082  }
1083 }
1084 
1086 {
1087  invalidated_ = false;
1088  overlaped_hex_.clear();
1089 
1090  value.primary_frame = true;
1091  unit_anim_.redraw(value,src_,dst_, halo_man);
1092 
1093  value.primary_frame = false;
1094  for(auto& anim : sub_anims_) {
1095  anim.second.redraw(value, src_, dst_, halo_man);
1096  }
1097 }
1098 
1100 {
1102 
1103  for(auto& anim : sub_anims_) {
1104  anim.second.clear_halo();
1105  }
1106 }
1107 
1109 {
1110  if(invalidated_) return false;
1111 
1112  display* disp = display::get_singleton();
1113  const bool complete_redraw = disp->tile_nearly_on_screen(src_) || disp->tile_nearly_on_screen(dst_);
1114 
1115  if(overlaped_hex_.empty()) {
1116  if(complete_redraw) {
1117  value.primary_frame = true;
1119  value.primary_frame = false;
1120 
1121  for(auto& anim : sub_anims_) {
1122  std::set<map_location> tmp = anim.second.get_overlaped_hex(value, src_, dst_);
1123  overlaped_hex_.insert(tmp.begin(), tmp.end());
1124  }
1125  } else {
1126  // Offscreen animations only invalidate their own hex, no propagation,
1127  // but we still need this to play sounds
1128  overlaped_hex_.insert(src_);
1129  }
1130  }
1131 
1132  if(complete_redraw) {
1133  if( need_update()) {
1134  disp->invalidate(overlaped_hex_);
1135  invalidated_ = true;
1136  return true;
1137  } else {
1139  return invalidated_;
1140  }
1141  } else {
1142  if(need_minimal_update()) {
1143  disp->invalidate(overlaped_hex_);
1144  invalidated_ = true;
1145  return true;
1146  } else {
1147  return false;
1148  }
1149  }
1150 }
1151 
1152 std::string unit_animation::debug() const
1153 {
1154  std::ostringstream outstream;
1155  outstream << *this;
1156  return outstream.str();
1157 }
1158 
1159 std::ostream& operator<<(std::ostream& outstream, const unit_animation& u_animation)
1160 {
1161  std::string events_string = utils::join(u_animation.event_);
1162  outstream << "[" << events_string << "]\n";
1163 
1164  outstream << "\tstart_time=" << u_animation.get_begin_time().count() << '\n';
1165 
1166  if(u_animation.hits_.size() > 0) {
1167  std::vector<std::string> hits;
1168  std::transform(u_animation.hits_.begin(), u_animation.hits_.end(), std::back_inserter(hits), strike_result::get_string);
1169  outstream << "\thits=" << utils::join(hits) << '\n';
1170  }
1171 
1172  if(u_animation.directions_.size() > 0) {
1173  std::vector<std::string> dirs;
1174  std::transform(u_animation.directions_.begin(), u_animation.directions_.end(), std::back_inserter(dirs), map_location::write_direction);
1175  outstream << "\tdirections=" << utils::join(dirs) << '\n';
1176  }
1177 
1178  if(u_animation.terrain_types_.size() > 0) {
1179  outstream << "\tterrain=" << utils::join(u_animation.terrain_types_) << '\n';
1180  }
1181 
1182  if(u_animation.frequency_ > 0) outstream << "frequency=" << u_animation.frequency_ << '\n';
1183 
1184  if(u_animation.unit_filter_.size() > 0) {
1185  outstream << "[filter]\n";
1186  for(const config& cfg : u_animation.unit_filter_) {
1187  outstream << cfg.debug();
1188  }
1189 
1190  outstream << "[/filter]\n";
1191  }
1192 
1193  if(u_animation.secondary_unit_filter_.size() > 0) {
1194  outstream << "[filter_second]\n";
1195  for(const config& cfg : u_animation.secondary_unit_filter_) {
1196  outstream << cfg.debug();
1197  }
1198 
1199  outstream << "[/filter_second]\n";
1200  }
1201 
1202  if(u_animation.primary_attack_filter_.size() > 0) {
1203  outstream << "[filter_attack]\n";
1204  for(const config& cfg : u_animation.primary_attack_filter_) {
1205  outstream << cfg.debug();
1206  }
1207 
1208  outstream << "[/filter_attack]\n";
1209  }
1210 
1211  if(u_animation.secondary_attack_filter_.size() > 0) {
1212  outstream << "[filter_second_attack]\n";
1213  for(const config& cfg : u_animation.secondary_attack_filter_) {
1214  outstream << cfg.debug();
1215  }
1216 
1217  outstream << "[/filter_second_attack]\n";
1218  }
1219 
1220  for(std::size_t i = 0; i < u_animation.unit_anim_.get_frames_count(); i++) {
1221  outstream << "\t[frame]\n";
1222  for(const std::string& frame_string : u_animation.unit_anim_.get_frame(i).debug_strings()) {
1223  outstream << "\t\t" << frame_string <<"\n";
1224  }
1225  outstream << "\t[/frame]\n";
1226  }
1227 
1228  for(std::pair<std::string, unit_animation::particle> p : u_animation.sub_anims_) {
1229  for(std::size_t i = 0; i < p.second.get_frames_count(); i++) {
1230  std::string sub_frame_name = p.first;
1231  std::size_t pos = sub_frame_name.find("_frame");
1232  if(pos != std::string::npos) sub_frame_name = sub_frame_name.substr(0, pos);
1233 
1234  outstream << "\t" << sub_frame_name << "_start_time=" << p.second.get_begin_time().count() << '\n';
1235  outstream << "\t[" << p.first << "]\n";
1236 
1237  for(const std::string& frame_string : p.second.get_frame(i).debug_strings()) {
1238  outstream << "\t\t" << frame_string << '\n';
1239  }
1240 
1241  outstream << "\t[/" << p.first << "]\n";
1242  }
1243  }
1244 
1245  outstream << "[/" << events_string << "]\n";
1246  return outstream;
1247 }
1248 
1250 {
1251  const unit_frame& current_frame = get_current_frame();
1252  const auto animation_time = get_animation_time();
1253  const frame_parameters default_val = parameters_.parameters(animation_time - get_begin_time());
1254 
1255  // Everything is relative to the first frame in an attack/defense/etc. block.
1256  // so we need to check if this particular frame is due to be shown at this time
1257  bool in_scope_of_frame = (animation_time >= get_current_frame_begin_time() ? true: false);
1258  if(animation_time > get_current_frame_end_time()) in_scope_of_frame = false;
1259 
1260  // Sometimes even if the frame is not due to be shown, a frame image still must be shown.
1261  // i.e. in a defense animation that is shorter than an attack animation.
1262  // the halos should not persist though and use the 'in_scope_of_frame' variable.
1263 
1264  // For sound frames we want the first time variable set only after the frame has started.
1265  if(get_current_frame_begin_time() != last_frame_begin_time_ && animation_time >= get_current_frame_begin_time()) {
1266  last_frame_begin_time_ = get_current_frame_begin_time();
1267  current_frame.redraw(get_current_frame_time(), true, in_scope_of_frame, src, dst, halo_id_, halo_man, default_val, value);
1268  } else {
1269  current_frame.redraw(get_current_frame_time(), false, in_scope_of_frame, src, dst, halo_id_, halo_man, default_val, value);
1270  }
1271 }
1272 
1274 {
1275  halo_id_.reset();
1276 }
1277 
1279 {
1280  const unit_frame& current_frame = get_current_frame();
1281  const frame_parameters default_val = parameters_.parameters(get_animation_time() - get_begin_time());
1282  return current_frame.get_overlaped_hex(get_current_frame_time(), src, dst, default_val,value);
1283 }
1284 
1286 {
1287  halo_id_.reset();
1288 }
1289 
1290 void unit_animation::particle::start_animation(const std::chrono::milliseconds& start_time)
1291 {
1292  halo_id_.reset();
1293  parameters_.override(get_animation_duration());
1294  animated<unit_frame>::start_animation(start_time,cycles_);
1295  last_frame_begin_time_ = get_begin_time() - 1ms;
1296 }
1297 
1299  , const std::string& event
1300  , const map_location &src
1301  , const map_location &dst
1302  , const int value
1303  , bool with_bars
1304  , const std::string& text
1305  , const color_t text_color
1306  , const strike_result::type hit_type
1307  , const const_attack_ptr& attack
1308  , const const_attack_ptr& second_attack
1309  , int value2)
1310 {
1311  if(!animated_unit) return;
1312 
1313  const unit_animation* anim =
1314  animated_unit->anim_comp().choose_animation(src, event, dst, value, hit_type, attack, second_attack, value2);
1315  if(!anim) return;
1316 
1317  start_time_ = std::max(start_time_, anim->get_begin_time());
1318  animated_units_.AGGREGATE_EMPLACE(std::move(animated_unit), anim, text, text_color, src, with_bars);
1319 }
1320 
1322  , const unit_animation* anim
1323  , const map_location &src
1324  , bool with_bars
1325  , const std::string& text
1326  , const color_t text_color)
1327 {
1328  if(!animated_unit || !anim) return;
1329 
1330  start_time_ = std::max(start_time_, anim->get_begin_time());
1331  animated_units_.AGGREGATE_EMPLACE(std::move(animated_unit), anim, text, text_color, src, with_bars);
1332 }
1333 
1335  , const std::string& event
1336  , const map_location &src
1337  , const map_location &dst
1338  , const int value
1339  , const strike_result::type hit_type
1340  , const const_attack_ptr& attack
1341  , const const_attack_ptr& second_attack
1342  , int value2) const
1343 {
1344  return (animated_unit && animated_unit->anim_comp().choose_animation(src, event, dst, value, hit_type, attack, second_attack, value2));
1345 }
1346 
1348  , const std::string& event
1349  , const map_location &src
1350  , const map_location & dst
1351  , const int value
1352  , bool with_bars
1353  , const std::string& text
1354  , const color_t text_color
1355  , const strike_result::type hit_type
1356  , const const_attack_ptr& attack
1357  , const const_attack_ptr& second_attack
1358  , int value2)
1359 {
1360  if(!animated_unit) return;
1361 
1362  if(animated_unit->anim_comp().get_animation() &&
1363  !animated_unit->anim_comp().get_animation()->animation_finished_potential() &&
1364  animated_unit->anim_comp().get_animation()->matches(
1365  src, dst, animated_unit, event, value, hit_type, attack, second_attack, value2) > unit_animation::MATCH_FAIL)
1366  {
1367  animated_units_.AGGREGATE_EMPLACE(animated_unit, nullptr, text, text_color, src, with_bars);
1368  } else {
1369  add_animation(animated_unit,event,src,dst,value,with_bars,text,text_color,hit_type,attack,second_attack,value2);
1370  }
1371 }
1372 
1374 {
1375  auto begin_time = std::chrono::milliseconds::max();
1376 
1377  for(const auto& anim : animated_units_) {
1378  if(anim.my_unit->anim_comp().get_animation()) {
1379  if(anim.animation) {
1380  begin_time = std::min(begin_time, anim.animation->get_begin_time());
1381  } else {
1382  begin_time = std::min(begin_time, anim.my_unit->anim_comp().get_animation()->get_begin_time());
1383  }
1384  }
1385  }
1386 
1387  for(auto& anim : animated_units_) {
1388  if(anim.animation) {
1389  anim.my_unit->anim_comp().start_animation(begin_time, anim.animation, anim.with_bars, anim.text, anim.text_color);
1390  anim.animation = nullptr;
1391  } else {
1392  anim.my_unit->anim_comp().get_animation()->update_parameters(anim.src, anim.src.get_direction(anim.my_unit->facing()));
1393  }
1394  }
1395 }
1396 
1398 {
1399  bool finished = true;
1400  for(const auto& anim : animated_units_) {
1401  finished &= anim.my_unit->anim_comp().get_animation()->animation_finished_potential();
1402  }
1403 
1404  return finished;
1405 }
1406 
1407 void unit_animator::wait_until(const std::chrono::milliseconds& animation_time) const
1408 {
1409  if(animated_units_.empty()) {
1410  return;
1411  }
1412  // important to set a max animation time so that the time does not go past this value for movements.
1413  // fix for bug #1565
1414  animated_units_[0].my_unit->anim_comp().get_animation()->set_max_animation_time(animation_time);
1415 
1416  display* disp = display::get_singleton();
1417  double speed = disp->turbo_speed();
1418 
1420 
1421  using std::chrono::steady_clock;
1422  auto end_tick = animated_units_[0].my_unit->anim_comp().get_animation()->time_to_tick(animation_time);
1423 
1424  while(steady_clock::now() < end_tick - std::min(std::chrono::floor<std::chrono::milliseconds>(20ms / speed), 20ms)) {
1425  auto rest = std::chrono::floor<std::chrono::milliseconds>((animation_time - get_animation_time()) * speed);
1426  std::this_thread::sleep_for(std::clamp(rest, 0ms, 10ms));
1427 
1429  end_tick = animated_units_[0].my_unit->anim_comp().get_animation()->time_to_tick(animation_time);
1430  }
1431 
1432  auto rest = std::max<steady_clock::duration>(0ms, end_tick - steady_clock::now() + 5ms);
1433  std::this_thread::sleep_for(rest);
1434 
1436  animated_units_[0].my_unit->anim_comp().get_animation()->set_max_animation_time(0ms);
1437 }
1438 
1440 {
1441  bool finished = false;
1442  while(!finished) {
1444 
1445  std::this_thread::sleep_for(10ms);
1446 
1447  finished = true;
1448  for(const auto& anim : animated_units_) {
1449  finished &= anim.my_unit->anim_comp().get_animation()->animation_finished_potential();
1450  }
1451  }
1452 }
1453 
1454 std::chrono::milliseconds unit_animator::get_animation_time() const
1455 {
1456  if(animated_units_.empty()) {
1457  return 0ms;
1458  }
1459  return animated_units_[0].my_unit->anim_comp().get_animation()->get_animation_time() ;
1460 }
1461 
1462 std::chrono::milliseconds unit_animator::get_animation_time_potential() const
1463 {
1464  if(animated_units_.empty()) {
1465  return 0ms;
1466  }
1467  return animated_units_[0].my_unit->anim_comp().get_animation()->get_animation_time_potential() ;
1468 }
1469 
1470 std::chrono::milliseconds unit_animator::get_end_time() const
1471 {
1472  auto end_time = std::chrono::milliseconds::min();
1473  for(const auto& anim : animated_units_) {
1474  if(anim.my_unit->anim_comp().get_animation()) {
1475  end_time = std::max(end_time, anim.my_unit->anim_comp().get_animation()->get_end_time());
1476  }
1477  }
1478 
1479  return end_time;
1480 }
1481 
1483 {
1484  for(const auto& anim : animated_units_) {
1485  if(anim.my_unit->anim_comp().get_animation()) {
1486  anim.my_unit->anim_comp().get_animation()->pause_animation();
1487  }
1488  }
1489 }
1490 
1492 {
1493  for(const auto& anim : animated_units_) {
1494  if(anim.my_unit->anim_comp().get_animation()) {
1495  anim.my_unit->anim_comp().get_animation()->restart_animation();
1496  }
1497  }
1498 }
1499 
1501 {
1502  for(const auto& anim : animated_units_) {
1503  anim.my_unit->anim_comp().set_standing();
1504  }
1505 }
map_location loc
Definition: move.cpp:172
void new_animation_frame()
Definition: animated.cpp:29
std::list< animation_branch > animation_branches
Definition: animation.cpp:59
static void prepare_single_animation(const config &anim_cfg, animation_branches &expanded_anims)
Definition: animation.cpp:180
static std::string get_heal_sound(const config &cfg)
Definition: animation.cpp:38
static void add_simple_anim(std::vector< unit_animation > &animations, const config &cfg, char const *tag_name, char const *apply_to, drawing_layer layer=drawing_layer::unit_default, bool offscreen=true)
Definition: animation.cpp:609
static animation_branches prepare_animation(const config &cfg, const std::string &animation_tag)
Definition: animation.cpp:245
std::unique_ptr< PangoAttribute, void(*)(PangoAttribute *)> value_
Definition: attributes.cpp:60
const T & get_frame(std::size_t n) const
std::chrono::milliseconds get_begin_time() const
void start_animation(const std::chrono::milliseconds &start_time, bool cycles=false)
Starts an animation cycle.
bool animation_finished_potential() const
void add_frame(const std::chrono::milliseconds &duration, const unit_frame &value, bool force_change=false)
Adds a frame to an animation.
void pause_animation()
Definition: animated.hpp:53
void update_last_draw_time(double acceleration=0)
void set_end_time(const std::chrono::milliseconds &ending_time)
const unit_frame & get_last_frame() const
bool animation_finished() const
Returns true if the current animation was finished.
std::chrono::milliseconds starting_frame_time_
Definition: animated.hpp:117
void restart_animation()
Definition: animated.hpp:58
std::size_t get_frames_count() const
void set_begin_time(const std::chrono::milliseconds &new_begin_time)
std::chrono::milliseconds get_animation_duration() const
std::chrono::milliseconds get_end_time() const
bool cycles() const
Definition: animated.hpp:75
Variant for storing WML attributes.
bool empty() const
Tests for an attribute that either was never set or was set to "".
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:157
config & add_child(std::string_view key)
Definition: config.cpp:435
all_children_iterator erase(const all_children_iterator &i)
Definition: config.cpp:617
boost::iterator_range< const_all_children_iterator > const_all_children_itors
Definition: config.hpp:762
auto all_children_view() const
In-order iteration over all children.
Definition: config.hpp:795
child_itors child_range(std::string_view key)
Definition: config.cpp:267
std::string debug() const
Definition: config.cpp:1213
config & add_child_at(std::string_view key, const config &val, std::size_t index)
Definition: config.cpp:464
boost::iterator_range< const_child_iterator > const_child_itors
Definition: config.hpp:281
bool empty() const
Definition: config.cpp:822
virtual void play_slice()
virtual const gamemap & map() const =0
virtual const unit_map & units() const =0
Sort-of-Singleton that many classes, both GUI and non-GUI, use to access the game data.
Definition: display.hpp:88
bool invalidate(const map_location &loc)
Function to invalidate a specific tile for redrawing.
Definition: display.cpp:2960
double turbo_speed() const
Definition: display.cpp:2004
bool propagate_invalidation(const std::set< map_location > &locs)
If this set is partially invalidated, invalidate all its hexes.
Definition: display.cpp:2981
bool tile_nearly_on_screen(const map_location &loc) const
Checks if location loc or one of the adjacent tiles is visible on screen.
Definition: display.cpp:1767
const display_context & context() const
Definition: display.hpp:184
static display * get_singleton()
Returns the display object if a display object exists.
Definition: display.hpp:102
Easily build frame parameters with the serialized constructors.
Definition: frame.hpp:84
Keep most parameters in a separate class to simplify the handling of the large number of parameters b...
Definition: frame.hpp:148
void override(const std::chrono::milliseconds &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:336
bool does_not_change() const
Definition: frame.cpp:253
terrain_code get_terrain(const map_location &loc) const
Looks up terrain at a particular location.
Definition: map.cpp:301
Generic locator abstracting the location of an image.
Definition: picture.hpp:59
const std::string & get_filename() const
Definition: picture.hpp:82
const std::string & get_modifications() const
Definition: picture.hpp:87
static rng & default_instance()
Definition: random.cpp:73
bool need_update() const
Definition: animation.cpp:916
std::set< map_location > get_overlaped_hex(const frame_parameters &value, const map_location &src, const map_location &dst)
Definition: animation.cpp:1278
void start_animation(const std::chrono::milliseconds &start_time)
Definition: animation.cpp:1290
frame_parsed_parameters parameters_
Definition: animation.hpp:159
bool need_minimal_update() const
Definition: animation.cpp:924
particle(const std::chrono::milliseconds &start_time=std::chrono::milliseconds{0}, const frame_builder &builder=frame_builder())
Definition: animation.hpp:124
void override(const std::chrono::milliseconds &start_time, const std::chrono::milliseconds &duration, const cycle_state cycles, 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: animation.cpp:890
void redraw(const frame_parameters &value, const map_location &src, const map_location &dst, halo::manager &halo_man)
Definition: animation.cpp:1249
bool invalidate(frame_parameters &value)
Definition: animation.cpp:1108
bool play_offscreen_
Definition: animation.hpp:184
std::vector< config > primary_attack_filter_
Definition: animation.hpp:173
std::vector< map_location::direction > directions_
Definition: animation.hpp:168
std::vector< int > value2_
Definition: animation.hpp:176
bool need_update() const
Definition: animation.cpp:961
map_location src_
Definition: animation.hpp:180
bool need_minimal_update() const
Definition: animation.cpp:971
void clear_haloes()
Definition: animation.cpp:1099
bool animation_finished_potential() const
Definition: animation.cpp:996
std::vector< config > secondary_unit_filter_
Definition: animation.hpp:167
map_location dst_
Definition: animation.hpp:181
t_translation::ter_list terrain_types_
Definition: animation.hpp:165
void add_frame(const std::chrono::milliseconds &duration, const unit_frame &value, bool force_change=false)
Definition: animation.hpp:46
void start_animation(const std::chrono::milliseconds &start_time, const map_location &src=map_location::null_location(), const map_location &dst=map_location::null_location(), const std::string &text="", const color_t text_color={0, 0, 0}, const bool accelerate=true)
Definition: animation.cpp:1035
std::chrono::milliseconds get_begin_time() const
Definition: animation.cpp:1025
std::vector< std::string > event_
Definition: animation.hpp:171
int matches(const map_location &loc, const map_location &second_loc, const unit_const_ptr &my_unit, const std::string &event="", const int value=0, strike_result::type hit=strike_result::type::invalid, const const_attack_ptr &attack=nullptr, const const_attack_ptr &second_attack=nullptr, int value2=0) const
Definition: animation.cpp:379
std::vector< int > value_
Definition: animation.hpp:172
std::string debug() const
Definition: animation.cpp:1152
void update_last_draw_time()
Definition: animation.cpp:1006
std::map< std::string, particle > sub_anims_
Definition: animation.hpp:177
particle unit_anim_
Definition: animation.hpp:178
void update_parameters(const map_location &src, const map_location &dst)
Definition: animation.cpp:1061
std::chrono::milliseconds get_end_time() const
Definition: animation.cpp:1015
auto get_current_frame_begin_time() const
Definition: animation.hpp:94
void pause_animation()
Definition: animation.cpp:1067
void restart_animation()
Definition: animation.cpp:1076
unit_animation()=delete
void redraw(frame_parameters &value, halo::manager &halo_man)
Definition: animation.cpp:1085
std::set< map_location > overlaped_hex_
Definition: animation.hpp:185
auto get_animation_time() const
Definition: animation.hpp:69
friend std::ostream & operator<<(std::ostream &outstream, const unit_animation &u_animation)
Definition: animation.cpp:1159
static void fill_initial_animations(std::vector< unit_animation > &animations, const config &cfg)
Definition: animation.cpp:486
std::vector< config > secondary_attack_filter_
Definition: animation.hpp:174
std::vector< config > unit_filter_
Definition: animation.hpp:166
static void add_anims(std::vector< unit_animation > &animations, const config &cfg)
Definition: animation.cpp:630
std::vector< strike_result::type > hits_
Definition: animation.hpp:175
bool animation_finished() const
Definition: animation.cpp:986
std::chrono::milliseconds get_animation_time_potential() const
Definition: animation.cpp:1462
void pause_animation()
Definition: animation.cpp:1482
std::chrono::milliseconds get_animation_time() const
Definition: animation.cpp:1454
bool has_animation(const unit_const_ptr &animated_unit, const std::string &event, const map_location &src=map_location::null_location(), const map_location &dst=map_location::null_location(), const int value=0, const strike_result::type hit_type=strike_result::type::invalid, const const_attack_ptr &attack=nullptr, const const_attack_ptr &second_attack=nullptr, int value2=0) const
has_animation : return an boolean value if animated unit present and have animation specified,...
Definition: animation.cpp:1334
void wait_for_end() const
Definition: animation.cpp:1439
void replace_anim_if_invalid(const unit_const_ptr &animated_unit, const std::string &event, const map_location &src=map_location::null_location(), const map_location &dst=map_location::null_location(), const int value=0, bool with_bars=false, const std::string &text="", const color_t text_color={0, 0, 0}, const strike_result::type hit_type=strike_result::type::invalid, const const_attack_ptr &attack=nullptr, const const_attack_ptr &second_attack=nullptr, int value2=0)
Definition: animation.cpp:1347
void add_animation(unit_const_ptr animated_unit, const unit_animation *animation, const map_location &src=map_location::null_location(), bool with_bars=false, const std::string &text="", const color_t text_color={0, 0, 0})
Definition: animation.cpp:1321
bool would_end() const
Definition: animation.cpp:1397
void start_animations()
Definition: animation.cpp:1373
void set_all_standing()
Definition: animation.cpp:1500
std::chrono::milliseconds get_end_time() const
Definition: animation.cpp:1470
void restart_animation()
Definition: animation.cpp:1491
void wait_until(const std::chrono::milliseconds &animation_time) const
Definition: animation.cpp:1407
Describes a unit's animation sequence.
Definition: frame.hpp:208
void redraw(const std::chrono::milliseconds &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:630
const std::chrono::milliseconds & duration() const
Definition: frame.hpp:230
bool does_not_change() const
Definition: frame.hpp:235
std::vector< std::string > debug_strings() const
Definition: frame.hpp:245
std::set< map_location > get_overlaped_hex(const std::chrono::milliseconds &frame_time, const map_location &src, const map_location &dst, const frame_parameters &animation_val, const frame_parameters &engine_val) const
Definition: frame.cpp:796
unit_iterator find(std::size_t id)
Definition: map.cpp:302
This class represents a single unit of a specific type.
Definition: unit.hpp:132
A variable-expanding proxy for the config class.
Definition: variable.hpp:45
map_display and display: classes which take care of displaying the map and game-data on the screen.
drawing_layer
@ unit_missile_default
Default layer for missile frames.
@ unit_default
Default layer for drawing units.
@ unit_move_default
Default layer for drawing moving units.
const config * cfg
constexpr int get_abs_frame_layer(drawing_layer layer)
Definition: frame.hpp:37
std::size_t i
Definition: function.cpp:1032
static bool sound()
Functions to load and save images from/to disk.
play_controller * controller
Definition: resources.cpp:21
bool terrain_matches(const terrain_code &src, const terrain_code &dest)
Tests whether a specific terrain matches an expression, for matching rules see above.
ter_list read_list(std::string_view str, const ter_layer filler)
Reads a list of terrains from a string, when reading the.
constexpr auto transform
Definition: ranges.hpp:41
constexpr auto filter
Definition: ranges.hpp:38
bool contains(const Container &container, const Value &value)
Returns true iff value is found in container.
Definition: general.hpp:87
std::string join(const T &v, const std::string &s=",")
Generates a new string joining container items in a list.
std::vector< std::string > split(const config_attribute_value &val)
std::shared_ptr< const unit > unit_const_ptr
Definition: ptr.hpp:27
std::shared_ptr< const attack_type > const_attack_ptr
Definition: ptr.hpp:34
rect dst
Location on the final composed sheet.
rect src
Non-transparent portion of the surface to compose.
config merge() const
Definition: animation.cpp:45
std::vector< config::const_all_children_iterator > children
Definition: animation.cpp:56
animation_branches branches
Definition: animation.cpp:176
animation_cursor(const config &cfg)
Definition: animation.cpp:63
animation_cursor(const config &cfg, animation_cursor *p)
Definition: animation.cpp:69
config::const_all_children_itors itors
Definition: animation.cpp:174
animation_cursor * parent
Definition: animation.cpp:177
The basic class for representing 8-bit RGB or RGBA colour values.
Definition: color.hpp:61
All parameters from a frame at a given instant.
Definition: frame.hpp:44
boost::tribool primary_frame
Definition: frame.hpp:75
Encapsulates the map of the game.
Definition: location.hpp:46
static std::string write_direction(direction dir)
Definition: location.cpp:154
direction
Valid directions which can be moved in our hexagonal world.
Definition: location.hpp:48
static direction parse_direction(const std::string &str)
Definition: location.cpp:79
static std::string get_string(enum_type key)
Converts a enum to its string equivalent.
Definition: enum_base.hpp:46
mock_char c
mock_party p
#define d
#define h
#define f