The Battle for Wesnoth  1.19.0-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 "display.hpp"
19 #include "map/map.hpp"
20 #include "play_controller.hpp"
21 #include "resources.hpp"
22 #include "color.hpp"
23 #include "units/unit.hpp"
25 #include "units/filter.hpp"
26 #include "variable.hpp"
27 #include "random.hpp"
28 
29 #include <algorithm>
30 
31 static std::string get_heal_sound(const config& cfg)
32 {
33  return cfg["healed_sound"].empty() ? "heal.wav" : cfg["healed_sound"].str();
34 }
35 
37 {
39  : attributes()
40  , children()
41  {}
42 
43  config merge() const
44  {
45  config result = attributes;
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 
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"])
284  , base_score_(cfg["base_score"])
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 config::any_child fr : cfg.all_children_range()) {
302  if(fr.key == frame_string) {
303  continue;
304  }
305 
306  if(fr.key.find("_frame", fr.key.size() - 6) == std::string::npos) {
307  continue;
308  }
309 
310  if(sub_anims_.find(fr.key) != sub_anims_.end()) {
311  continue;
312  }
313 
314  sub_anims_[fr.key] = particle(cfg, fr.key.substr(0, fr.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  unit_const_ptr my_unit, const std::string& event, const int value, strike_result::type hit, const_attack_ptr attack,
379  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.get_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(0, frame_builder().image(default_image).duration(1), "", unit_animation::DEFAULT_ANIM));
501  }
502 
503  animations.push_back(unit_animation(0, frame_builder().image(default_image).duration(1), "_disabled_", 0));
504  animations.push_back(unit_animation(0,
505  frame_builder().image(default_image).duration(300).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(0, 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(0, 1, 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(0, 300, 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(0, 600, particle::NO_CYCLE, "0~1:600");
528 
529  animations.push_back(base);
530  animations.back().event_ = { "levelin" };
531  animations.back().unit_anim_.override(0, 600, 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(0, 600, 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(0, 1, particle::NO_CYCLE);
540 
541  animations.push_back(base);
542  animations.back().event_ = { "post_movement" };
543  animations.back().unit_anim_.override(0, 1, particle::NO_CYCLE);
544 
545  animations.push_back(base);
546  animations.back().event_ = { "movement" };
547  animations.back().unit_anim_.override(0, 200,
548  particle::NO_CYCLE, "", "", {0,0,0}, "0~1:200", std::to_string(display::LAYER_UNIT_MOVE_DEFAULT - display::LAYER_UNIT_FIRST));
549 
550  animations.push_back(base);
551  animations.back().event_ = { "defend" };
552  animations.back().unit_anim_.override(0, 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(-150, 300, particle::NO_CYCLE, "", "", {0,0,0}, "0~0.6:150,0.6~0:150", std::to_string(display::LAYER_UNIT_MOVE_DEFAULT-display::LAYER_UNIT_FIRST));
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(-150, 150, 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(0, 600, particle::NO_CYCLE, "1~0:600");
573  animations.back().sub_anims_["_death_sound"] = particle();
574  animations.back().sub_anims_["_death_sound"].add_frame(1, frame_builder().sound(cfg["die_sound"]), true);
575 
576  animations.push_back(base);
577  animations.back().event_ = { "victory" };
578  animations.back().unit_anim_.override(0, animations.back().unit_anim_.get_animation_duration(), particle::CYCLE);
579 
580  animations.push_back(base);
581  animations.back().unit_anim_.override(0, 150, particle::NO_CYCLE, "1~0:150");
582  animations.back().event_ = { "pre_teleport" };
583 
584  animations.push_back(base);
585  animations.back().unit_anim_.override(0, 150, 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(0, 300, 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(1, frame_builder().sound(healed_sound), true);
598 
599  animations.push_back(base);
600  animations.back().event_ = { "poisoned" };
601  animations.back().unit_anim_.override(0, 300, 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(1, 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 = layer - display::LAYER_UNIT_FIRST;
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  const int default_layer = display::LAYER_UNIT_DEFAULT - display::LAYER_UNIT_FIRST;
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", display::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(1,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(1,frame_builder().sound(game_config::sounds::status::poisoned),true);
737  }
738 
739  add_simple_anim(animations, cfg, "pre_movement_anim", "pre_movement", display::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", display::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(225, frame_builder()
781  .image(image_loc.get_filename()+image_loc.get_modifications())
782  .duration(225)
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(225, frame_builder()
794  .image(image_loc.get_filename() + image_loc.get_modifications())
795  .duration(225)
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", display::LAYER_UNIT_MOVE_DEFAULT);
803  add_simple_anim(animations, cfg, "sheath_weapon_anim", "sheath_weapon", display::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(600, frame_builder()
849  .image(image_loc.get_filename()+image_loc.get_modifications())
850  .duration(600)
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(1,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(0);
881 
882  anim["apply_to"] ="post_teleport";
883  animations.emplace_back(anim);
884  animations.back().unit_anim_.remove_frames_until(0);
885  }
886 }
887 
889  , int 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_ = INT_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_, frame["begin"].to_int());
941  }
942  } else {
943  starting_frame_time_ = 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 
1014 {
1015  int result = unit_anim_.get_end_time();
1016  for(const auto& anim : sub_anims_) {
1017  result = std::max<int>(result, anim.second.get_end_time());
1018  }
1019 
1020  return result;
1021 }
1022 
1024 {
1025  int result = unit_anim_.get_begin_time();
1026  for(const auto& anim : sub_anims_) {
1027  result = std::min<int>(result, anim.second.get_begin_time());
1028  }
1029 
1030  return result;
1031 }
1032 
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(1, frame_builder());
1049  crude_build.add_frame(1, 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() << '\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() << '\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 int 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 
1276 std::set<map_location> unit_animation::particle::get_overlaped_hex(const frame_parameters& value, const map_location& src, const map_location& dst)
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 
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() -1;
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_attack_ptr attack
1306  , const_attack_ptr second_attack
1307  , int value2)
1308 {
1309  if(!animated_unit) return;
1310 
1311  anim_elem tmp;
1312  tmp.my_unit = std::move(animated_unit);
1313  tmp.text = text;
1314  tmp.text_color = text_color;
1315  tmp.src = src;
1316  tmp.with_bars= with_bars;
1317  tmp.animation = tmp.my_unit->anim_comp().choose_animation(src, event, dst, value, hit_type, attack, second_attack, value2);
1318 
1319  if(!tmp.animation) return;
1320 
1321  start_time_ = std::max<int>(start_time_, tmp.animation->get_begin_time());
1322  animated_units_.push_back(std::move(tmp));
1323 }
1324 
1326  , const unit_animation* anim
1327  , const map_location &src
1328  , bool with_bars
1329  , const std::string& text
1330  , const color_t text_color)
1331 {
1332  if(!animated_unit) return;
1333 
1334  anim_elem tmp;
1335  tmp.my_unit = std::move(animated_unit);
1336  tmp.text = text;
1337  tmp.text_color = text_color;
1338  tmp.src = src;
1339  tmp.with_bars = with_bars;
1340  tmp.animation = anim;
1341 
1342  if(!tmp.animation) return;
1343 
1344  start_time_ = std::max<int>(start_time_, tmp.animation->get_begin_time());
1345  animated_units_.push_back(std::move(tmp));
1346 }
1347 
1349  , const std::string& event
1350  , const map_location &src
1351  , const map_location &dst
1352  , const int value
1353  , const strike_result::type hit_type
1354  , const_attack_ptr attack
1355  , const_attack_ptr second_attack
1356  , int value2) const
1357 {
1358  return (animated_unit && animated_unit->anim_comp().choose_animation(src, event, dst, value, hit_type, attack, second_attack, value2));
1359 }
1360 
1362  , const std::string& event
1363  , const map_location &src
1364  , const map_location & dst
1365  , const int value
1366  , bool with_bars
1367  , const std::string& text
1368  , const color_t text_color
1369  , const strike_result::type hit_type
1370  , const_attack_ptr attack
1371  , const_attack_ptr second_attack
1372  , int value2)
1373 {
1374  if(!animated_unit) return;
1375 
1376  if(animated_unit->anim_comp().get_animation() &&
1377  !animated_unit->anim_comp().get_animation()->animation_finished_potential() &&
1378  animated_unit->anim_comp().get_animation()->matches(
1379  src, dst, animated_unit, event, value, hit_type, attack, second_attack, value2) > unit_animation::MATCH_FAIL)
1380  {
1381  anim_elem tmp;
1382  tmp.my_unit = animated_unit;
1383  tmp.text = text;
1384  tmp.text_color = text_color;
1385  tmp.src = src;
1386  tmp.with_bars= with_bars;
1387  tmp.animation = nullptr;
1388 
1389  animated_units_.push_back(std::move(tmp));
1390  } else {
1391  add_animation(animated_unit,event,src,dst,value,with_bars,text,text_color,hit_type,attack,second_attack,value2);
1392  }
1393 }
1394 
1396 {
1397  int begin_time = INT_MAX;
1398 
1399  for(const auto& anim : animated_units_) {
1400  if(anim.my_unit->anim_comp().get_animation()) {
1401  if(anim.animation) {
1402  begin_time = std::min<int>(begin_time, anim.animation->get_begin_time());
1403  } else {
1404  begin_time = std::min<int>(begin_time, anim.my_unit->anim_comp().get_animation()->get_begin_time());
1405  }
1406  }
1407  }
1408 
1409  for(auto& anim : animated_units_) {
1410  if(anim.animation) {
1411  anim.my_unit->anim_comp().start_animation(begin_time, anim.animation, anim.with_bars, anim.text, anim.text_color);
1412  anim.animation = nullptr;
1413  } else {
1414  anim.my_unit->anim_comp().get_animation()->update_parameters(anim.src, anim.src.get_direction(anim.my_unit->facing()));
1415  }
1416  }
1417 }
1418 
1420 {
1421  bool finished = true;
1422  for(const auto& anim : animated_units_) {
1423  finished &= anim.my_unit->anim_comp().get_animation()->animation_finished_potential();
1424  }
1425 
1426  return finished;
1427 }
1428 
1429 void unit_animator::wait_until(int animation_time) const
1430 {
1431  if(animated_units_.empty()) {
1432  return;
1433  }
1434  // important to set a max animation time so that the time does not go past this value for movements.
1435  // fix for bug #1565
1436  animated_units_[0].my_unit->anim_comp().get_animation()->set_max_animation_time(animation_time);
1437 
1438  display* disp = display::get_singleton();
1439  double speed = disp->turbo_speed();
1440 
1442 
1443  int end_tick = animated_units_[0].my_unit->anim_comp().get_animation()->time_to_tick(animation_time);
1444  while(SDL_GetTicks() < unsigned(end_tick - std::min(int(20 / speed), 20))) {
1445  if(!game_config::no_delay) {
1446  SDL_Delay(std::clamp(int((animation_time - get_animation_time()) * speed), 0, 10));
1447  }
1449  end_tick = animated_units_[0].my_unit->anim_comp().get_animation()->time_to_tick(animation_time);
1450  }
1451 
1452  if(!game_config::no_delay) {
1453  SDL_Delay(std::max<int>(0, end_tick - SDL_GetTicks() + 5));
1454  }
1455 
1457  animated_units_[0].my_unit->anim_comp().get_animation()->set_max_animation_time(0);
1458 }
1459 
1461 {
1462  if(game_config::no_delay) return;
1463 
1464  bool finished = false;
1465  while(!finished) {
1467 
1468  SDL_Delay(10);
1469 
1470  finished = true;
1471  for(const auto& anim : animated_units_) {
1472  finished &= anim.my_unit->anim_comp().get_animation()->animation_finished_potential();
1473  }
1474  }
1475 }
1476 
1478 {
1479  if(animated_units_.empty()) {
1480  return 0;
1481  }
1482  return animated_units_[0].my_unit->anim_comp().get_animation()->get_animation_time() ;
1483 }
1484 
1486 {
1487  if(animated_units_.empty()) {
1488  return 0;
1489  }
1490  return animated_units_[0].my_unit->anim_comp().get_animation()->get_animation_time_potential() ;
1491 }
1492 
1494 {
1495  int end_time = INT_MIN;
1496  for(const auto& anim : animated_units_) {
1497  if(anim.my_unit->anim_comp().get_animation()) {
1498  end_time = std::max<int>(end_time, anim.my_unit->anim_comp().get_animation()->get_end_time());
1499  }
1500  }
1501 
1502  return end_time;
1503 }
1504 
1506 {
1507  for(const auto& anim : animated_units_) {
1508  if(anim.my_unit->anim_comp().get_animation()) {
1509  anim.my_unit->anim_comp().get_animation()->pause_animation();
1510  }
1511  }
1512 }
1513 
1515 {
1516  for(const auto& anim : animated_units_) {
1517  if(anim.my_unit->anim_comp().get_animation()) {
1518  anim.my_unit->anim_comp().get_animation()->restart_animation();
1519  }
1520  }
1521 }
1522 
1524 {
1525  for(const auto& anim : animated_units_) {
1526  anim.my_unit->anim_comp().set_standing();
1527  }
1528 }
void new_animation_frame()
Definition: animated.cpp:31
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:31
static animation_branches prepare_animation(const config &cfg, const std::string &animation_tag)
Definition: animation.cpp:243
static void add_simple_anim(std::vector< unit_animation > &animations, const config &cfg, char const *tag_name, char const *apply_to, display::drawing_layer layer=display::LAYER_UNIT_DEFAULT, bool offscreen=true)
Definition: animation.cpp:607
const T & get_frame(std::size_t n) const
void set_end_time(int ending_time)
int get_end_time() const
bool animation_finished_potential() const
int get_begin_time() const
void pause_animation()
Definition: animated.hpp:50
void update_last_draw_time(double acceleration=0)
void start_animation(int start_time, bool cycles=false)
Starts an animation cycle.
int get_animation_duration() const
const unit_frame & get_last_frame() const
void set_begin_time(int new_begin_time)
bool animation_finished() const
Returns true if the current animation was finished.
void restart_animation()
Definition: animated.hpp:55
std::size_t get_frames_count() const
void add_frame(int duration, const unit_frame &value, bool force_change=false)
Adds a frame to an animation.
bool cycles() const
Definition: animated.hpp:71
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:159
boost::iterator_range< const_all_children_iterator > const_all_children_itors
Definition: config.hpp:804
config & add_child_at(config_key_type key, const config &val, std::size_t index)
Definition: config.cpp:467
const_all_children_itors all_children_range() const
In-order iteration over all children.
Definition: config.cpp:887
child_itors child_range(config_key_type key)
Definition: config.cpp:273
std::string debug() const
Definition: config.cpp:1244
boost::iterator_range< const_child_iterator > const_child_itors
Definition: config.hpp:283
bool empty() const
Definition: config.cpp:852
config & add_child(config_key_type key)
Definition: config.cpp:441
virtual void play_slice(bool is_delay_enabled=true)
Sort-of-Singleton that many classes, both GUI and non-GUI, use to access the game data.
Definition: display.hpp:88
const unit_map & get_units() const
Definition: display.hpp:149
bool invalidate(const map_location &loc)
Function to invalidate a specific tile for redrawing.
Definition: display.cpp:3145
drawing_layer
The layers to render something on.
Definition: display.hpp:820
@ LAYER_UNIT_FIRST
Reserve layers to be selected for WML.
Definition: display.hpp:829
@ LAYER_UNIT_MISSILE_DEFAULT
default layer for missile frames
Definition: display.hpp:845
@ LAYER_UNIT_DEFAULT
default layer for drawing units
Definition: display.hpp:831
@ LAYER_UNIT_MOVE_DEFAULT
default layer for drawing moving units
Definition: display.hpp:840
double turbo_speed() const
Definition: display.cpp:2188
bool propagate_invalidation(const std::set< map_location > &locs)
If this set is partially invalidated, invalidate all its hexes.
Definition: display.cpp:3166
const gamemap & get_map() const
Definition: display.hpp:106
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:1933
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:78
Keep most parameters in a separate class to simplify the handling of the large number of parameters b...
Definition: frame.hpp:142
void override(int duration, const std::string &highlight="", const std::string &blend_ratio="", color_t blend_color={0, 0, 0}, const std::string &offset="", const std::string &layer="", const std::string &modifiers="")
Definition: frame.cpp:322
bool does_not_change() const
Definition: frame.cpp:268
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:63
const std::string & get_filename() const
Definition: picture.hpp:85
const std::string & get_modifications() const
Definition: picture.hpp:90
static rng & default_instance()
Definition: random.cpp:73
bool need_update() const
Definition: animation.cpp:914
void start_animation(int start_time)
Definition: animation.cpp:1288
std::set< map_location > get_overlaped_hex(const frame_parameters &value, const map_location &src, const map_location &dst)
Definition: animation.cpp:1276
frame_parsed_parameters parameters_
Definition: animation.hpp:161
bool need_minimal_update() const
Definition: animation.cpp:922
void override(int start_time, int 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
particle(int start_time=0, const frame_builder &builder=frame_builder())
Definition: animation.hpp:126
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:186
std::vector< config > primary_attack_filter_
Definition: animation.hpp:175
std::vector< int > value2_
Definition: animation.hpp:178
bool need_update() const
Definition: animation.cpp:959
map_location src_
Definition: animation.hpp:182
bool need_minimal_update() const
Definition: animation.cpp:969
void start_animation(int 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
void clear_haloes()
Definition: animation.cpp:1097
int get_animation_time() const
Definition: animation.hpp:70
bool animation_finished_potential() const
Definition: animation.cpp:994
std::vector< config > secondary_unit_filter_
Definition: animation.hpp:169
void add_frame(int duration, const unit_frame &value, bool force_change=false)
Definition: animation.hpp:47
int matches(const map_location &loc, const map_location &second_loc, unit_const_ptr my_unit, const std::string &event="", const int value=0, strike_result::type hit=strike_result::type::invalid, const_attack_ptr attack=nullptr, const_attack_ptr second_attack=nullptr, int value2=0) const
Definition: animation.cpp:377
map_location dst_
Definition: animation.hpp:183
t_translation::ter_list terrain_types_
Definition: animation.hpp:167
std::vector< std::string > event_
Definition: animation.hpp:173
int get_current_frame_begin_time() const
Definition: animation.hpp:95
std::vector< int > value_
Definition: animation.hpp:174
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:179
particle unit_anim_
Definition: animation.hpp:180
void update_parameters(const map_location &src, const map_location &dst)
Definition: animation.cpp:1059
void pause_animation()
Definition: animation.cpp:1065
void restart_animation()
Definition: animation.cpp:1074
int get_begin_time() const
Definition: animation.cpp:1023
unit_animation()=delete
int get_end_time() const
Definition: animation.cpp:1013
void redraw(frame_parameters &value, halo::manager &halo_man)
Definition: animation.cpp:1083
std::set< map_location > overlaped_hex_
Definition: animation.hpp:187
std::vector< map_location::DIRECTION > directions_
Definition: animation.hpp:170
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:176
std::vector< config > unit_filter_
Definition: animation.hpp:168
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:177
bool animation_finished() const
Definition: animation.cpp:984
void replace_anim_if_invalid(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_attack_ptr attack=nullptr, const_attack_ptr second_attack=nullptr, int value2=0)
Definition: animation.cpp:1361
void wait_until(int animation_time) const
Definition: animation.cpp:1429
void pause_animation()
Definition: animation.cpp:1505
int get_animation_time() const
Definition: animation.cpp:1477
bool has_animation(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_attack_ptr attack=nullptr, 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:1348
void wait_for_end() const
Definition: animation.cpp:1460
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:1325
bool would_end() const
Definition: animation.cpp:1419
void start_animations()
Definition: animation.cpp:1395
void set_all_standing()
Definition: animation.cpp:1523
int get_animation_time_potential() const
Definition: animation.cpp:1485
void restart_animation()
Definition: animation.cpp:1514
int get_end_time() const
Definition: animation.cpp:1493
Describes a unit's animation sequence.
Definition: frame.hpp:201
int duration() const
Definition: frame.hpp:222
void redraw(const int frame_time, bool on_start_time, bool in_scope_of_frame, const map_location &src, const map_location &dst, halo::handle &halo_id, halo::manager &halo_man, const frame_parameters &animation_val, const frame_parameters &engine_val) const
Definition: frame.cpp:609
bool does_not_change() const
Definition: frame.hpp:227
std::vector< std::string > debug_strings() const
Definition: frame.hpp:237
std::set< map_location > get_overlaped_hex(const int frame_time, const map_location &src, const map_location &dst, const frame_parameters &animation_val, const frame_parameters &engine_val) const
Definition: frame.cpp:778
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
std::size_t i
Definition: function.cpp:968
Functions to load and save images from/to disk.
play_controller * controller
Definition: resources.cpp:21
Audio output for sound and music.
Definition: sound.cpp:40
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)
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
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:36
boost::tribool primary_frame
Definition: frame.hpp:69
Encapsulates the map of the game.
Definition: location.hpp:38
static DIRECTION parse_direction(const std::string &str)
Definition: location.cpp:65
DIRECTION
Valid directions which can be moved in our hexagonal world.
Definition: location.hpp:40
static std::string write_direction(DIRECTION dir)
Definition: location.cpp:140
static std::string get_string(enum_type key)
Converts a enum to its string equivalent.
Definition: enum_base.hpp:46
unit_const_ptr my_unit
Definition: animation.hpp:283
const unit_animation * animation
Definition: animation.hpp:284
mock_char c
mock_party p
#define d
#define h
#define f