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