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