The Battle for Wesnoth  1.17.23+dev
animation.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2006 - 2023
3  by Jeremy Rosen <jeremy.rosen@enst-bretagne.fr>
4  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
5 
6  This program is free software; you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation; either version 2 of the License, or
9  (at your option) any later version.
10  This program is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY.
12 
13  See the COPYING file for more details.
14 */
15 
16 #include "units/animation.hpp"
17 
18 #include "display.hpp"
19 #include "filter_context.hpp"
20 #include "map/map.hpp"
21 #include "play_controller.hpp"
22 #include "resources.hpp"
23 #include "color.hpp"
24 #include "units/unit.hpp"
26 #include "units/filter.hpp"
27 #include "variable.hpp"
28 #include "random.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 {
40  : attributes()
41  , children()
42  {}
43 
44  config merge() const
45  {
46  config result = attributes;
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 
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"])
285  , base_score_(cfg["base_score"])
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 config::any_child fr : cfg.all_children_range()) {
303  if(fr.key == frame_string) {
304  continue;
305  }
306 
307  if(fr.key.find("_frame", fr.key.size() - 6) == std::string::npos) {
308  continue;
309  }
310 
311  if(sub_anims_.find(fr.key) != sub_anims_.end()) {
312  continue;
313  }
314 
315  sub_anims_[fr.key] = particle(cfg, fr.key.substr(0, fr.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  unit_const_ptr my_unit, const std::string& event, const int value, strike_result::type hit, const_attack_ptr attack,
380  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(std::find(event_.begin(), event_.end(), event) == event_.end()) {
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(std::find(value_.begin(), value_.end(), value) == value_.end()) {
403  return MATCH_FAIL;
404  }
405 
406  result++;
407  }
408 
409  if(my_unit) {
410  if(!directions_.empty()) {
411  if(std::find(directions_.begin(), directions_.end(), my_unit->facing()) == directions_.end()) {
412  return MATCH_FAIL;
413  }
414 
415  result++;
416  }
417 
418  for(const auto& filter : unit_filter_) {
419  unit_filter f{ vconfig(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.get_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(std::find(hits_.begin(),hits_.end(),hit) == hits_.end()) {
446  return MATCH_FAIL;
447  }
448 
449  result ++;
450  }
451 
452  if(!value2_.empty()) {
453  if(std::find(value2_.begin(),value2_.end(),value2) == value2_.end()) {
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(std::find(anim.event_.begin(), anim.event_.end(), "default") != anim.event_.end()) {
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(0, frame_builder().image(default_image).duration(1), "", unit_animation::DEFAULT_ANIM));
502  }
503 
504  animations.push_back(unit_animation(0, frame_builder().image(default_image).duration(1), "_disabled_", 0));
505  animations.push_back(unit_animation(0,
506  frame_builder().image(default_image).duration(300).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(0, 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(0, 1, 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(0, 300, 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(0, 600, particle::NO_CYCLE, "0~1:600");
529 
530  animations.push_back(base);
531  animations.back().event_ = { "levelin" };
532  animations.back().unit_anim_.override(0, 600, 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(0, 600, 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(0, 1, particle::NO_CYCLE);
541 
542  animations.push_back(base);
543  animations.back().event_ = { "post_movement" };
544  animations.back().unit_anim_.override(0, 1, particle::NO_CYCLE);
545 
546  animations.push_back(base);
547  animations.back().event_ = { "movement" };
548  animations.back().unit_anim_.override(0, 200,
549  particle::NO_CYCLE, "", "", {0,0,0}, "0~1:200", std::to_string(display::LAYER_UNIT_MOVE_DEFAULT - display::LAYER_UNIT_FIRST));
550 
551  animations.push_back(base);
552  animations.back().event_ = { "defend" };
553  animations.back().unit_anim_.override(0, 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(-150, 300, particle::NO_CYCLE, "", "", {0,0,0}, "0~0.6:150,0.6~0:150", std::to_string(display::LAYER_UNIT_MOVE_DEFAULT-display::LAYER_UNIT_FIRST));
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(-150, 150, 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(0, 600, particle::NO_CYCLE, "1~0:600");
574  animations.back().sub_anims_["_death_sound"] = particle();
575  animations.back().sub_anims_["_death_sound"].add_frame(1, frame_builder().sound(cfg["die_sound"]), true);
576 
577  animations.push_back(base);
578  animations.back().event_ = { "victory" };
579  animations.back().unit_anim_.override(0, animations.back().unit_anim_.get_animation_duration(), particle::CYCLE);
580 
581  animations.push_back(base);
582  animations.back().unit_anim_.override(0, 150, particle::NO_CYCLE, "1~0:150");
583  animations.back().event_ = { "pre_teleport" };
584 
585  animations.push_back(base);
586  animations.back().unit_anim_.override(0, 150, 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(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});
595 
596  const std::string healed_sound = get_heal_sound(cfg);
597 
598  animations.back().sub_anims_["_healed_sound"].add_frame(1, frame_builder().sound(healed_sound), true);
599 
600  animations.push_back(base);
601  animations.back().event_ = { "poisoned" };
602  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});
603  animations.back().sub_anims_["_poison_sound"] = particle();
604  animations.back().sub_anims_["_poison_sound"].add_frame(1, 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 = layer - display::LAYER_UNIT_FIRST;
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  const int default_layer = display::LAYER_UNIT_DEFAULT - display::LAYER_UNIT_FIRST;
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", display::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(1,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(1,frame_builder().sound(game_config::sounds::status::poisoned),true);
738  }
739 
740  add_simple_anim(animations, cfg, "pre_movement_anim", "pre_movement", display::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", display::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(225, frame_builder()
782  .image(image_loc.get_filename()+image_loc.get_modifications())
783  .duration(225)
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(225, frame_builder()
795  .image(image_loc.get_filename() + image_loc.get_modifications())
796  .duration(225)
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", display::LAYER_UNIT_MOVE_DEFAULT);
804  add_simple_anim(animations, cfg, "sheath_weapon_anim", "sheath_weapon", display::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(600, frame_builder()
850  .image(image_loc.get_filename()+image_loc.get_modifications())
851  .duration(600)
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(1,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(0);
882 
883  anim["apply_to"] ="post_teleport";
884  animations.emplace_back(anim);
885  animations.back().unit_anim_.remove_frames_until(0);
886  }
887 }
888 
890  , int 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_ = INT_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_, frame["begin"].to_int());
942  }
943  } else {
944  starting_frame_time_ = 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 
1015 {
1016  int result = unit_anim_.get_end_time();
1017  for(const auto& anim : sub_anims_) {
1018  result = std::max<int>(result, anim.second.get_end_time());
1019  }
1020 
1021  return result;
1022 }
1023 
1025 {
1026  int result = unit_anim_.get_begin_time();
1027  for(const auto& anim : sub_anims_) {
1028  result = std::min<int>(result, anim.second.get_begin_time());
1029  }
1030 
1031  return result;
1032 }
1033 
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(1, frame_builder());
1050  crude_build.add_frame(1, 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() << '\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() << '\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 int 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 
1277 std::set<map_location> unit_animation::particle::get_overlaped_hex(const frame_parameters& value, const map_location& src, const map_location& dst)
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 
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() -1;
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_attack_ptr attack
1307  , const_attack_ptr second_attack
1308  , int value2)
1309 {
1310  if(!animated_unit) return;
1311 
1312  anim_elem tmp;
1313  tmp.my_unit = std::move(animated_unit);
1314  tmp.text = text;
1315  tmp.text_color = text_color;
1316  tmp.src = src;
1317  tmp.with_bars= with_bars;
1318  tmp.animation = tmp.my_unit->anim_comp().choose_animation(src, event, dst, value, hit_type, attack, second_attack, value2);
1319 
1320  if(!tmp.animation) return;
1321 
1322  start_time_ = std::max<int>(start_time_, tmp.animation->get_begin_time());
1323  animated_units_.push_back(std::move(tmp));
1324 }
1325 
1327  , const unit_animation* anim
1328  , const map_location &src
1329  , bool with_bars
1330  , const std::string& text
1331  , const color_t text_color)
1332 {
1333  if(!animated_unit) return;
1334 
1335  anim_elem tmp;
1336  tmp.my_unit = std::move(animated_unit);
1337  tmp.text = text;
1338  tmp.text_color = text_color;
1339  tmp.src = src;
1340  tmp.with_bars = with_bars;
1341  tmp.animation = anim;
1342 
1343  if(!tmp.animation) return;
1344 
1345  start_time_ = std::max<int>(start_time_, tmp.animation->get_begin_time());
1346  animated_units_.push_back(std::move(tmp));
1347 }
1348 
1350  , const std::string& event
1351  , const map_location &src
1352  , const map_location &dst
1353  , const int value
1354  , const strike_result::type hit_type
1355  , const_attack_ptr attack
1356  , const_attack_ptr second_attack
1357  , int value2) const
1358 {
1359  return (animated_unit && animated_unit->anim_comp().choose_animation(src, event, dst, value, hit_type, attack, second_attack, value2));
1360 }
1361 
1363  , const std::string& event
1364  , const map_location &src
1365  , const map_location & dst
1366  , const int value
1367  , bool with_bars
1368  , const std::string& text
1369  , const color_t text_color
1370  , const strike_result::type hit_type
1371  , const_attack_ptr attack
1372  , const_attack_ptr second_attack
1373  , int value2)
1374 {
1375  if(!animated_unit) return;
1376 
1377  if(animated_unit->anim_comp().get_animation() &&
1378  !animated_unit->anim_comp().get_animation()->animation_finished_potential() &&
1379  animated_unit->anim_comp().get_animation()->matches(
1380  src, dst, animated_unit, event, value, hit_type, attack, second_attack, value2) > unit_animation::MATCH_FAIL)
1381  {
1382  anim_elem tmp;
1383  tmp.my_unit = animated_unit;
1384  tmp.text = text;
1385  tmp.text_color = text_color;
1386  tmp.src = src;
1387  tmp.with_bars= with_bars;
1388  tmp.animation = nullptr;
1389 
1390  animated_units_.push_back(std::move(tmp));
1391  } else {
1392  add_animation(animated_unit,event,src,dst,value,with_bars,text,text_color,hit_type,attack,second_attack,value2);
1393  }
1394 }
1395 
1397 {
1398  int begin_time = INT_MAX;
1399 
1400  for(const auto& anim : animated_units_) {
1401  if(anim.my_unit->anim_comp().get_animation()) {
1402  if(anim.animation) {
1403  begin_time = std::min<int>(begin_time, anim.animation->get_begin_time());
1404  } else {
1405  begin_time = std::min<int>(begin_time, anim.my_unit->anim_comp().get_animation()->get_begin_time());
1406  }
1407  }
1408  }
1409 
1410  for(auto& anim : animated_units_) {
1411  if(anim.animation) {
1412  anim.my_unit->anim_comp().start_animation(begin_time, anim.animation, anim.with_bars, anim.text, anim.text_color);
1413  anim.animation = nullptr;
1414  } else {
1415  anim.my_unit->anim_comp().get_animation()->update_parameters(anim.src, anim.src.get_direction(anim.my_unit->facing()));
1416  }
1417  }
1418 }
1419 
1421 {
1422  bool finished = true;
1423  for(const auto& anim : animated_units_) {
1424  finished &= anim.my_unit->anim_comp().get_animation()->animation_finished_potential();
1425  }
1426 
1427  return finished;
1428 }
1429 
1430 void unit_animator::wait_until(int animation_time) const
1431 {
1432  if(animated_units_.empty()) {
1433  return;
1434  }
1435  // important to set a max animation time so that the time does not go past this value for movements.
1436  // fix for bug #1565
1437  animated_units_[0].my_unit->anim_comp().get_animation()->set_max_animation_time(animation_time);
1438 
1439  display* disp = display::get_singleton();
1440  double speed = disp->turbo_speed();
1441 
1443 
1444  int end_tick = animated_units_[0].my_unit->anim_comp().get_animation()->time_to_tick(animation_time);
1445  while(SDL_GetTicks() < unsigned(end_tick - std::min(int(20 / speed), 20))) {
1446  if(!game_config::no_delay) {
1447  SDL_Delay(std::clamp(int((animation_time - get_animation_time()) * speed), 0, 10));
1448  }
1450  end_tick = animated_units_[0].my_unit->anim_comp().get_animation()->time_to_tick(animation_time);
1451  }
1452 
1453  if(!game_config::no_delay) {
1454  SDL_Delay(std::max<int>(0, end_tick - SDL_GetTicks() + 5));
1455  }
1456 
1458  animated_units_[0].my_unit->anim_comp().get_animation()->set_max_animation_time(0);
1459 }
1460 
1462 {
1463  if(game_config::no_delay) return;
1464 
1465  bool finished = false;
1466  while(!finished) {
1468 
1469  SDL_Delay(10);
1470 
1471  finished = true;
1472  for(const auto& anim : animated_units_) {
1473  finished &= anim.my_unit->anim_comp().get_animation()->animation_finished_potential();
1474  }
1475  }
1476 }
1477 
1479 {
1480  if(animated_units_.empty()) {
1481  return 0;
1482  }
1483  return animated_units_[0].my_unit->anim_comp().get_animation()->get_animation_time() ;
1484 }
1485 
1487 {
1488  if(animated_units_.empty()) {
1489  return 0;
1490  }
1491  return animated_units_[0].my_unit->anim_comp().get_animation()->get_animation_time_potential() ;
1492 }
1493 
1495 {
1496  int end_time = INT_MIN;
1497  for(const auto& anim : animated_units_) {
1498  if(anim.my_unit->anim_comp().get_animation()) {
1499  end_time = std::max<int>(end_time, anim.my_unit->anim_comp().get_animation()->get_end_time());
1500  }
1501  }
1502 
1503  return end_time;
1504 }
1505 
1507 {
1508  for(const auto& anim : animated_units_) {
1509  if(anim.my_unit->anim_comp().get_animation()) {
1510  anim.my_unit->anim_comp().get_animation()->pause_animation();
1511  }
1512  }
1513 }
1514 
1516 {
1517  for(const auto& anim : animated_units_) {
1518  if(anim.my_unit->anim_comp().get_animation()) {
1519  anim.my_unit->anim_comp().get_animation()->restart_animation();
1520  }
1521  }
1522 }
1523 
1525 {
1526  for(const auto& anim : animated_units_) {
1527  anim.my_unit->anim_comp().set_standing();
1528  }
1529 }
void new_animation_frame()
Definition: animated.cpp:31
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:32
static animation_branches prepare_animation(const config &cfg, const std::string &animation_tag)
Definition: animation.cpp:244
static void add_simple_anim(std::vector< unit_animation > &animations, const config &cfg, char const *tag_name, char const *apply_to, display::drawing_layer layer=display::LAYER_UNIT_DEFAULT, bool offscreen=true)
Definition: animation.cpp:608
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:52
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:57
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:73
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:161
boost::iterator_range< const_all_children_iterator > const_all_children_itors
Definition: config.hpp:806
config & add_child_at(config_key_type key, const config &val, std::size_t index)
Definition: config.cpp:471
const_all_children_itors all_children_range() const
In-order iteration over all children.
Definition: config.cpp:891
child_itors child_range(config_key_type key)
Definition: config.cpp:277
std::string debug() const
Definition: config.cpp:1248
boost::iterator_range< const_child_iterator > const_child_itors
Definition: config.hpp:285
bool empty() const
Definition: config.cpp:856
config & add_child(config_key_type key)
Definition: config.cpp:445
virtual void play_slice(bool is_delay_enabled=true)
Sort-of-Singleton that many classes, both GUI and non-GUI, use to access the game data.
Definition: display.hpp:87
const unit_map & get_units() const
Definition: display.hpp:148
bool invalidate(const map_location &loc)
Function to invalidate a specific tile for redrawing.
Definition: display.cpp:3147
drawing_layer
The layers to render something on.
Definition: display.hpp:808
@ LAYER_UNIT_FIRST
Reserve layers to be selected for WML.
Definition: display.hpp:817
@ LAYER_UNIT_MISSILE_DEFAULT
default layer for missile frames
Definition: display.hpp:833
@ LAYER_UNIT_DEFAULT
default layer for drawing units
Definition: display.hpp:819
@ LAYER_UNIT_MOVE_DEFAULT
default layer for drawing moving units
Definition: display.hpp:828
double turbo_speed() const
Definition: display.cpp:2238
bool propagate_invalidation(const std::set< map_location > &locs)
If this set is partially invalidated, invalidate all its hexes.
Definition: display.cpp:3168
const gamemap & get_map() const
Definition: display.hpp:105
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:1983
static display * get_singleton()
Returns the display object if a display object exists.
Definition: display.hpp:101
Keep most parameters in a separate class to simplify the handling of the large number of parameters b...
Definition: frame.hpp:143
void override(int duration, const std::string &highlight="", const std::string &blend_ratio="", color_t blend_color={0, 0, 0}, const std::string &offset="", const std::string &layer="", const std::string &modifiers="")
Definition: frame.cpp:322
bool does_not_change() const
Definition: frame.cpp:268
terrain_code get_terrain(const map_location &loc) const
Looks up terrain at a particular location.
Definition: map.cpp:302
Generic locator abstracting the location of an image.
Definition: picture.hpp:64
const std::string & get_filename() const
Definition: picture.hpp:86
const std::string & get_modifications() const
Definition: picture.hpp:91
static rng & default_instance()
Definition: random.cpp:74
bool need_update() const
Definition: animation.cpp:915
void start_animation(int start_time)
Definition: animation.cpp:1289
std::set< map_location > get_overlaped_hex(const frame_parameters &value, const map_location &src, const map_location &dst)
Definition: animation.cpp:1277
frame_parsed_parameters parameters_
Definition: animation.hpp:162
bool need_minimal_update() const
Definition: animation.cpp:923
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:889
particle(int start_time=0, const frame_builder &builder=frame_builder())
Definition: animation.hpp:127
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:187
std::vector< config > primary_attack_filter_
Definition: animation.hpp:176
std::vector< int > value2_
Definition: animation.hpp:179
bool need_update() const
Definition: animation.cpp:960
map_location src_
Definition: animation.hpp:183
bool need_minimal_update() const
Definition: animation.cpp:970
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:1034
void clear_haloes()
Definition: animation.cpp:1098
int get_animation_time() const
Definition: animation.hpp:71
bool animation_finished_potential() const
Definition: animation.cpp:995
std::vector< config > secondary_unit_filter_
Definition: animation.hpp:170
void add_frame(int duration, const unit_frame &value, bool force_change=false)
Definition: animation.hpp:48
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:378
map_location dst_
Definition: animation.hpp:184
t_translation::ter_list terrain_types_
Definition: animation.hpp:168
std::vector< std::string > event_
Definition: animation.hpp:174
int get_current_frame_begin_time() const
Definition: animation.hpp:96
std::vector< int > value_
Definition: animation.hpp:175
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:180
particle unit_anim_
Definition: animation.hpp:181
void update_parameters(const map_location &src, const map_location &dst)
Definition: animation.cpp:1060
void pause_animation()
Definition: animation.cpp:1066
void restart_animation()
Definition: animation.cpp:1075
int get_begin_time() const
Definition: animation.cpp:1024
unit_animation()=delete
int get_end_time() const
Definition: animation.cpp:1014
void redraw(frame_parameters &value, halo::manager &halo_man)
Definition: animation.cpp:1084
std::set< map_location > overlaped_hex_
Definition: animation.hpp:188
std::vector< map_location::DIRECTION > directions_
Definition: animation.hpp:171
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:177
std::vector< config > unit_filter_
Definition: animation.hpp:169
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:178
bool animation_finished() const
Definition: animation.cpp:985
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:1362
void wait_until(int animation_time) const
Definition: animation.cpp:1430
void pause_animation()
Definition: animation.cpp:1506
int get_animation_time() const
Definition: animation.cpp:1478
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:1349
void wait_for_end() const
Definition: animation.cpp:1461
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:1326
bool would_end() const
Definition: animation.cpp:1420
void start_animations()
Definition: animation.cpp:1396
void set_all_standing()
Definition: animation.cpp:1524
int get_animation_time_potential() const
Definition: animation.cpp:1486
void restart_animation()
Definition: animation.cpp:1515
int get_end_time() const
Definition: animation.cpp:1494
Describes a unit's animation sequence.
Definition: frame.hpp:202
int duration() const
Definition: frame.hpp:223
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:569
bool does_not_change() const
Definition: frame.hpp:228
std::vector< std::string > debug_strings() const
Definition: frame.hpp:238
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:738
unit_iterator find(std::size_t id)
Definition: map.cpp:301
This class represents a single unit of a specific type.
Definition: unit.hpp:135
A variable-expanding proxy for the config class.
Definition: variable.hpp:45
std::size_t i
Definition: function.cpp:968
Functions to load and save images from/to disk.
play_controller * controller
Definition: resources.cpp:22
Audio output for sound and music.
Definition: sound.cpp:42
bool terrain_matches(const terrain_code &src, const terrain_code &dest)
Tests whether a specific terrain matches an expression, for matching rules see above.
ter_list read_list(std::string_view str, const ter_layer filler)
Reads a list of terrains from a string, when reading the.
std::string join(const T &v, const std::string &s=",")
Generates a new string joining container items in a list.
std::vector< std::string > split(const config_attribute_value &val)
std::shared_ptr< const unit > unit_const_ptr
Definition: ptr.hpp:27
std::shared_ptr< const attack_type > const_attack_ptr
Definition: ptr.hpp:34
config merge() const
Definition: animation.cpp: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:59
All parameters from a frame at a given instant.
Definition: frame.hpp:36
boost::tribool primary_frame
Definition: frame.hpp:69
Encapsulates the map of the game.
Definition: location.hpp:38
static DIRECTION parse_direction(const std::string &str)
Definition: location.cpp:66
DIRECTION
Valid directions which can be moved in our hexagonal world.
Definition: location.hpp:40
static std::string write_direction(DIRECTION dir)
Definition: location.cpp:141
static std::string get_string(enum_type key)
Converts a enum to its string equivalent.
Definition: enum_base.hpp:46
unit_const_ptr my_unit
Definition: animation.hpp:284
const unit_animation * animation
Definition: animation.hpp:285
mock_char c
mock_party p
#define d
#define h
#define f