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