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