The Battle for Wesnoth 1.21.0-dev
 
Loading...
Searching...
No Matches
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"
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
36using namespace std::chrono_literals;
37
38static 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;
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
59typedef 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
180static 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
245static 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
255unit_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
280unit_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
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()) {
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_) {
420 unit_filter f{ vconfig(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_) {
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
486void 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,
564 particle::NO_CYCLE, "", "", {0,0,0}, "0~1:200", std::to_string(get_abs_frame_layer(drawing_layer::unit_move_default)));
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
623static 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
644void 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
904void 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
943unit_animation::particle::particle(const config& cfg, const std::string& frame_string)
945 , parameters_()
946 , halo_id_()
947 , last_frame_begin_time_(0)
948 , cycles_(false)
949{
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
1024std::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
1034std::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
1044void 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{
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
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()) {
1144 invalidated_ = true;
1145 return true;
1146 } else {
1148 return invalidated_;
1149 }
1150 } else {
1151 if(need_minimal_update()) {
1153 invalidated_ = true;
1154 return true;
1155 } else {
1156 return false;
1157 }
1158 }
1159}
1160
1161std::string unit_animation::debug() const
1162{
1163 std::ostringstream outstream;
1164 outstream << *this;
1165 return outstream.str();
1166}
1167
1168std::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
1299void 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
1416void 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);
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
1463std::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
1471std::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)
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)
static animation_branches prepare_animation(const config &cfg, const std::string &animation_tag)
std::unique_ptr< PangoAttribute, void(*)(PangoAttribute *)> value_
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.
const T & get_frame(std::size_t n) const
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...
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.
const unit_frame & get_last_frame() const
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:2927
double turbo_speed() const
Definition display.cpp:1965
bool propagate_invalidation(const std::set< map_location > &locs)
If this set is partially invalidated, invalidate all its hexes.
Definition display.cpp:2948
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:1728
static display * get_singleton()
Returns the display object if a display object exists.
Definition display.hpp:102
const display_context & context() const
Definition display.hpp:187
Easily build frame parameters with the serialized constructors.
Definition frame.hpp:84
frame_builder & highlight(const std::string &highlight)
Definition frame.cpp:160
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
std::set< map_location > get_overlaped_hex(const frame_parameters &value, const map_location &src, const map_location &dst)
void start_animation(const std::chrono::milliseconds &start_time)
frame_parsed_parameters parameters_
bool need_minimal_update() const
particle(const std::chrono::milliseconds &start_time=std::chrono::milliseconds{0}, const frame_builder &builder=frame_builder())
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="")
void redraw(const frame_parameters &value, const map_location &src, const map_location &dst, halo::manager &halo_man)
bool invalidate(frame_parameters &value)
std::vector< config > primary_attack_filter_
std::vector< map_location::direction > directions_
void resume_animation()
std::vector< int > value2_
bool need_update() const
map_location src_
bool need_minimal_update() const
std::vector< config > secondary_unit_filter_
void try_advance_to_current_frame()
map_location dst_
t_translation::ter_list terrain_types_
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)
std::chrono::milliseconds get_begin_time() const
std::vector< std::string > event_
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
std::vector< int > value_
std::string debug() const
std::map< std::string, particle > sub_anims_
particle unit_anim_
void update_parameters(const map_location &src, const map_location &dst)
friend std::ostream & operator<<(std::ostream &outstream, const unit_animation &u_animation)
std::chrono::milliseconds get_end_time() const
auto get_current_frame_begin_time() const
Definition animation.hpp:83
void pause_animation()
auto get_elapsed_time() const
Definition animation.hpp:63
unit_animation()=delete
void redraw(frame_parameters &value, halo::manager &halo_man)
std::set< map_location > overlaped_hex_
static void fill_initial_animations(std::vector< unit_animation > &animations, const config &cfg)
std::vector< config > secondary_attack_filter_
std::vector< config > unit_filter_
static void add_anims(std::vector< unit_animation > &animations, const config &cfg)
std::vector< strike_result::type > hits_
bool animation_finished() const
std::chrono::milliseconds get_elapsed_time() const
void pause_animation()
void resume_animation()
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,...
void wait_for_end() const
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)
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})
bool would_end() const
void start_animations()
void set_all_standing()
std::chrono::milliseconds get_end_time() const
void wait_until(const std::chrono::milliseconds &animation_time) const
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
bool does_not_change() const
Definition frame.hpp:235
const std::chrono::milliseconds & duration() const
Definition frame.hpp:230
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
std::vector< std::string > debug_strings() const
Definition frame.hpp:245
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
play_controller * controller
Definition resources.cpp:21
Audio output for sound and music.
bool terrain_matches(const terrain_code &src, const terrain_code &dest)
Tests whether a specific terrain matches an expression, for matching rules see above.
bool contains(const Container &container, const Value &value)
Returns true iff value is found in container.
Definition general.hpp:88
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::shared_ptr< const unit > unit_const_ptr
Definition ptr.hpp:27
std::shared_ptr< const attack_type > const_attack_ptr
Definition ptr.hpp:34
std::unique_ptr< MIX_Audio, decltype(&MIX_DestroyAudio)> value
Definition sound.cpp:139
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
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
animation_cursor * parent
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
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_party p
#define i
#define d
#define h
#define f
#define c