diff --git a/src/unit.cpp b/src/unit.cpp index 0d7ee007586..91cc743eb79 100644 --- a/src/unit.cpp +++ b/src/unit.cpp @@ -801,15 +801,24 @@ const std::string& unit::image() const switch(state_) { case STATE_NORMAL: return type_->image(); case STATE_DEFENDING_LONG: - return type_->image_defensive(attack_type::LONG_RANGE); - case STATE_DEFENDING_SHORT: - return type_->image_defensive(attack_type::SHORT_RANGE); + case STATE_DEFENDING_SHORT: { + const attack_type::RANGE range = (state_ == STATE_DEFENDING_LONG) ? attack_type::LONG_RANGE : attack_type::SHORT_RANGE; + const unit_animation* const anim = type_->defend_animation(getsHit_,range); + if(anim != NULL) { + const std::string* img = anim->get_frame(attackingMilliseconds_); + if(img != NULL) { + return *img; + } + } + + return type_->image_defensive(range); + } case STATE_ATTACKING: { if(attackType_ == NULL) return type_->image(); const std::string* const img = - attackType_->get_frame(attackingMilliseconds_); + attackType_->animation().get_frame(attackingMilliseconds_); if(img == NULL) return type_->image_fighting(attackType_->range()); @@ -825,10 +834,12 @@ const std::string& unit::image() const } } -void unit::set_defending(bool newval, attack_type::RANGE range) +void unit::set_defending(bool newval, bool hits, int ms, attack_type::RANGE range) { state_ = newval ? (range == attack_type::LONG_RANGE ? STATE_DEFENDING_LONG : STATE_DEFENDING_SHORT): STATE_NORMAL; + attackingMilliseconds_ = ms; + getsHit_ = hits; } void unit::set_attacking(bool newval, const attack_type* type, int ms) diff --git a/src/unit.hpp b/src/unit.hpp index a87013a0185..316abd15239 100644 --- a/src/unit.hpp +++ b/src/unit.hpp @@ -112,7 +112,7 @@ public: //(could be in the middle of an attack etc) const std::string& image() const; - void set_defending(bool newval, + void set_defending(bool newval, bool hits=false, int ms=0, attack_type::RANGE range=attack_type::LONG_RANGE); void set_attacking(bool newval, const attack_type* type=NULL, int ms=0); @@ -150,6 +150,7 @@ private: STATE state_; const attack_type* attackType_; int attackingMilliseconds_; + bool getsHit_; int hitpoints_; int maxHitpoints_, backupMaxHitpoints_; diff --git a/src/unit_display.cpp b/src/unit_display.cpp index 77220c49938..01e93108f02 100644 --- a/src/unit_display.cpp +++ b/src/unit_display.cpp @@ -203,8 +203,6 @@ bool unit_attack_ranged(display& disp, unit_map& units, const gamemap& map, const unit_map::iterator att = units.find(a); const unit_map::iterator def = units.find(b); - def->second.set_defending(true,attack_type::LONG_RANGE); - const gamemap::location leader_loc = under_leadership(units,a); unit_map::iterator leader = units.end(); if(leader_loc.valid()) { @@ -215,8 +213,8 @@ bool unit_attack_ranged(display& disp, unit_map& units, const gamemap& map, //the missile frames are based around the time when the missile impacts. //the 'real' frames are based around the time when the missile launches. - const int first_missile = minimum(-100,attack.get_first_frame(attack_type::MISSILE_FRAME)); - const int last_missile = attack.get_last_frame(attack_type::MISSILE_FRAME); + const int first_missile = minimum(-100,attack.animation().get_first_frame(unit_animation::MISSILE_FRAME)); + const int last_missile = attack.animation().get_last_frame(unit_animation::MISSILE_FRAME); const int real_last_missile = last_missile - first_missile; const int missile_impact = -first_missile; @@ -224,17 +222,17 @@ bool unit_attack_ranged(display& disp, unit_map& units, const gamemap& map, const int time_resolution = 20; const int acceleration = disp.turbo() ? 5:1; - const std::vector& sounds = attack.sound_effects(); - std::vector::const_iterator sfx_it = sounds.begin(); + const std::vector& sounds = attack.animation().sound_effects(); + std::vector::const_iterator sfx_it = sounds.begin(); const std::string& hit_sound = def->second.type().get_hit_sound(); bool played_hit_sound = (hit_sound == "" || hit_sound == "null"); const int play_hit_sound_at = 0; const bool hits = damage > 0; - const int begin_at = attack.get_first_frame(); + const int begin_at = attack.animation().get_first_frame(); const int end_at = maximum((damage+1)*time_resolution+missile_impact, - maximum(attack.get_last_frame(),real_last_missile)); + maximum(attack.animation().get_last_frame(),real_last_missile)); const double xsrc = disp.get_location_x(a); const double ysrc = disp.get_location_y(a); @@ -246,8 +244,7 @@ bool unit_attack_ranged(display& disp, unit_map& units, const gamemap& map, const bool vflip = b.y > a.y || b.y == a.y && is_even(a.x); const bool hflip = b.x < a.x; - const attack_type::FRAME_DIRECTION dir = - (a.x == b.x) ? attack_type::VERTICAL:attack_type::DIAGONAL; + const unit_animation::FRAME_DIRECTION dir = (a.x == b.x) ? unit_animation::VERTICAL:unit_animation::DIAGONAL; bool dead = false; const int drain_speed = 1*acceleration; @@ -266,6 +263,8 @@ bool unit_attack_ranged(display& disp, unit_map& units, const gamemap& map, for(int i = begin_at; i < end_at; i += time_resolution*acceleration) { events::pump(); + def->second.set_defending(true,hits,i - missile_impact,attack_type::LONG_RANGE); + //this is a while instead of an if, because there might be multiple //sounds playing simultaneously or close together while(!hide && sfx_it != sounds.end() && i >= sfx_it->time) { @@ -284,7 +283,7 @@ bool unit_attack_ranged(display& disp, unit_map& units, const gamemap& map, const std::string* new_halo = NULL; int new_halo_x = 0, new_halo_y = 0; - const std::string* unit_image = attack.get_frame(i,NULL,attack_type::UNIT_FRAME,attack_type::VERTICAL,&new_halo,&new_halo_x,&new_halo_y); + const std::string* unit_image = attack.animation().get_frame(i,NULL,unit_animation::UNIT_FRAME,unit_animation::VERTICAL,&new_halo,&new_halo_x,&new_halo_y); if(att->second.facing_left() == false) { new_halo_x *= -1; } @@ -354,8 +353,8 @@ bool unit_attack_ranged(display& disp, unit_map& units, const gamemap& map, const std::string* new_halo = NULL; int new_halo_x = 0, new_halo_y = 0; - const std::string* missile_image = attack.get_frame(missile_frame,NULL, - attack_type::MISSILE_FRAME,dir,&new_halo,&new_halo_x,&new_halo_y); + const std::string* missile_image = attack.animation().get_frame(missile_frame,NULL, + unit_animation::MISSILE_FRAME,dir,&new_halo,&new_halo_x,&new_halo_y); if(att->second.facing_left() == false) { new_halo_x *= -1; @@ -367,7 +366,7 @@ bool unit_attack_ranged(display& disp, unit_map& units, const gamemap& map, static const std::string default_missile(game_config::missile_n_image); static const std::string default_diag_missile(game_config::missile_ne_image); if(missile_image == NULL) { - if(dir == attack_type::VERTICAL) + if(dir == unit_animation::VERTICAL) missile_image = &default_missile; else missile_image = &default_diag_missile; @@ -523,8 +522,8 @@ bool unit_attack(display& disp, unit_map& units, const gamemap& map, } const bool hits = damage > 0; - const std::vector& sounds = attack.sound_effects(); - std::vector::const_iterator sfx_it = sounds.begin(); + const std::vector& sounds = attack.animation().sound_effects(); + std::vector::const_iterator sfx_it = sounds.begin(); const std::string& hit_sound = def->second.type().get_hit_sound(); bool played_hit_sound = (hit_sound == "" || hit_sound == "null"); @@ -544,9 +543,9 @@ bool unit_attack(display& disp, unit_map& units, const gamemap& map, leader->second.set_leading(true); } - const int begin_at = minimum(-200,attack.get_first_frame()); + const int begin_at = minimum(-200,attack.animation().get_first_frame()); const int end_at = maximum((damage+1)*time_resolution, - maximum(200,attack.get_last_frame())); + maximum(200,attack.animation().get_last_frame())); const double xsrc = disp.get_location_x(a); const double ysrc = disp.get_location_y(a); @@ -583,6 +582,8 @@ bool unit_attack(display& disp, unit_map& units, const gamemap& map, for(int i = begin_at; i < end_at; i += time_resolution*acceleration) { events::pump(); + def->second.set_defending(true,hits,i,attack_type::SHORT_RANGE); + //this is a while instead of an if, because there might be multiple //sounds playing simultaneously or close together while(!hide && sfx_it != sounds.end() && i >= sfx_it->time) { @@ -627,7 +628,6 @@ bool unit_attack(display& disp, unit_map& units, const gamemap& map, ++flash_num; } - disp.draw_tile(b.x,b.y,NULL,defender_alpha,defender_colour); if(leader_loc.valid()) { disp.draw_tile(leader_loc.x,leader_loc.y); @@ -637,8 +637,8 @@ bool unit_attack(display& disp, unit_map& units, const gamemap& map, int new_halo_x = 0, new_halo_y = 0; int xoffset = 0; - const std::string* unit_image = attack.get_frame(i,&xoffset,attack_type::UNIT_FRAME,attack_type::VERTICAL, - &new_halo_image,&new_halo_x,&new_halo_y); + const std::string* unit_image = attack.animation().get_frame(i,&xoffset,unit_animation::UNIT_FRAME,unit_animation::VERTICAL, + &new_halo_image,&new_halo_x,&new_halo_y); if(!attacker.facing_left()) { xoffset *= -1; diff --git a/src/unit_types.cpp b/src/unit_types.cpp index 1fe276c6e3e..cecf3cea3a1 100644 --- a/src/unit_types.cpp +++ b/src/unit_types.cpp @@ -28,28 +28,8 @@ #include #endif -attack_type::attack_type(const config& cfg) +unit_animation::unit_animation(const config& cfg) { - name_ = cfg["name"]; - type_ = cfg["type"]; - special_ = cfg["special"]; - backstab_ = special_ == "backstab"; - icon_ = cfg["icon"]; - if(icon_.empty()) - icon_ = "attacks/" + name_ + ".png"; - - range_ = cfg["range"] == "long" ? LONG_RANGE : SHORT_RANGE; - hexes_ = maximum(1,atoi(cfg["hexes"].c_str())); - damage_ = atol(cfg["damage"].c_str()); - num_attacks_ = atol(cfg["number"].c_str()); - - attack_weight_ = atof(cfg["attack_weight"].c_str()); - defense_weight_ = atof(cfg["defense_weight"].c_str()); - if ( ! attack_weight_ ) - attack_weight_ = 1.0; - if ( ! defense_weight_ ) - defense_weight_ = 1.0; - config::const_child_itors range = cfg.child_range("frame"); for(; range.first != range.second; ++range.first){ const int beg = atoi((**range.first)["begin"].c_str()); @@ -97,6 +77,92 @@ attack_type::attack_type(const config& cfg) } } +int unit_animation::get_first_frame(unit_animation::FRAME_TYPE type) const +{ + if(frames_[type].empty()) + return 0; + else + return minimum(frames_[type].front().start,0); +} + +int unit_animation::get_last_frame(unit_animation::FRAME_TYPE type) const +{ + if(frames_[type].empty()) + return 0; + else + return maximum(frames_[type].back().end,0); +} + +const std::string* unit_animation::get_frame(int milliseconds, int* xoff, + unit_animation::FRAME_TYPE type, + unit_animation::FRAME_DIRECTION dir, + const std::string** halo, int* halo_x, int* halo_y) const +{ + for(std::vector::const_iterator i = frames_[type].begin(); + i != frames_[type].end(); ++i) { + if(i->start > milliseconds) + return NULL; + + if(i->start <= milliseconds && i->end > milliseconds) { + if(xoff != NULL) { + *xoff = i->xoffset; + } + + if(halo != NULL) { + if(i->halo.empty()) { + *halo = NULL; + } else { + *halo = &i->halo; + } + + if(halo_x != NULL) { + *halo_x = i->halo_x; + } + + if(halo_y != NULL) { + *halo_y = i->halo_y; + } + } + + if(dir == DIAGONAL && i->image_diagonal != "") { + return &i->image_diagonal; + } else { + return &i->image; + } + } + } + + return NULL; +} + +const std::vector& unit_animation::sound_effects() const +{ + return sfx_; +} + +attack_type::attack_type(const config& cfg) : animation_(cfg) +{ + name_ = cfg["name"]; + type_ = cfg["type"]; + special_ = cfg["special"]; + backstab_ = special_ == "backstab"; + icon_ = cfg["icon"]; + if(icon_.empty()) + icon_ = "attacks/" + name_ + ".png"; + + range_ = cfg["range"] == "long" ? LONG_RANGE : SHORT_RANGE; + hexes_ = maximum(1,atoi(cfg["hexes"].c_str())); + damage_ = atol(cfg["damage"].c_str()); + num_attacks_ = atol(cfg["number"].c_str()); + + attack_weight_ = atof(cfg["attack_weight"].c_str()); + defense_weight_ = atof(cfg["defense_weight"].c_str()); + if ( ! attack_weight_ ) + attack_weight_ = 1.0; + if ( ! defense_weight_ ) + defense_weight_ = 1.0; +} + const std::string& attack_type::name() const { return name_; @@ -152,69 +218,6 @@ bool attack_type::backstab() const return backstab_; } -int attack_type::get_first_frame(attack_type::FRAME_TYPE type) const -{ - if(frames_[type].empty()) - return 0; - else - return minimum(frames_[type].front().start,0); -} - -int attack_type::get_last_frame(attack_type::FRAME_TYPE type) const -{ - if(frames_[type].empty()) - return 0; - else - return maximum(frames_[type].back().end,0); -} - -const std::string* attack_type::get_frame(int milliseconds, int* xoff, - attack_type::FRAME_TYPE type, - attack_type::FRAME_DIRECTION dir, - const std::string** halo, int* halo_x, int* halo_y) const -{ - for(std::vector::const_iterator i = frames_[type].begin(); - i != frames_[type].end(); ++i) { - if(i->start > milliseconds) - return NULL; - - if(i->start <= milliseconds && i->end > milliseconds) { - if(xoff != NULL) { - *xoff = i->xoffset; - } - - if(halo != NULL) { - if(i->halo.empty()) { - *halo = NULL; - } else { - *halo = &i->halo; - } - - if(halo_x != NULL) { - *halo_x = i->halo_x; - } - - if(halo_y != NULL) { - *halo_y = i->halo_y; - } - } - - if(dir == DIAGONAL && i->image_diagonal != "") { - return &i->image_diagonal; - } else { - return &i->image; - } - } - } - - return NULL; -} - -const std::vector& attack_type::sound_effects() const -{ - return sfx_; -} - bool attack_type::matches_filter(const config& cfg) const { const std::string& filter_range = cfg["range"]; @@ -549,6 +552,11 @@ unit_type::unit_type(const config& cfg, const movement_type_map& mv_types, } can_advance_ = advances_to().empty() == false; + + const config::child_list& defends = cfg_.get_children("defend"); + for(config::child_list::const_iterator d = defends.begin(); d != defends.end(); ++d) { + defensive_animations_.push_back(defensive_animation(**d)); + } } int unit_type::num_traits() const { return race_->num_traits(); } @@ -868,6 +876,39 @@ const std::string& unit_type::race() const return race_->name(); } +unit_type::defensive_animation::defensive_animation(const config& cfg) : hits(HIT_OR_MISS), range(SHORT_OR_LONG), animation(cfg) +{ + const std::string& hits_str = cfg["hits"]; + if(hits_str.empty() == false) { + hits = (hits_str == "yes") ? HIT : MISS; + } + + const std::string& range_str = cfg["range"]; + if(range_str.empty() == false) { + range = (range_str == "short") ? SHORT : LONG; + } +} + +bool unit_type::defensive_animation::matches(bool h, attack_type::RANGE r) const +{ + if(hits == HIT && h == false || hits == MISS && h == true || range == SHORT && r == attack_type::LONG_RANGE || range == LONG && r == attack_type::SHORT_RANGE) { + return false; + } else { + return true; + } +} + +const unit_animation* unit_type::defend_animation(bool hits, attack_type::RANGE range) const +{ + for(std::vector::const_iterator i = defensive_animations_.begin(); i != defensive_animations_.end(); ++i) { + if(i->matches(hits,range)) { + return &i->animation; + } + } + + return NULL; +} + game_data::game_data(const config& cfg) { static const std::vector dummy_traits; diff --git a/src/unit_types.hpp b/src/unit_types.hpp index 1c680d0c264..2b094159fed 100644 --- a/src/unit_types.hpp +++ b/src/unit_types.hpp @@ -21,26 +21,11 @@ #include #include -//the 'attack type' is the type of attack, how many times it strikes, -//and how much damage it does. -class attack_type +//a class to describe a unit's animation sequence +class unit_animation { public: - enum RANGE { SHORT_RANGE, LONG_RANGE }; - - attack_type(const config& cfg); - const std::string& name() const; - const std::string& type() const; - const std::string& special() const; - const std::string& icon() const; - RANGE range() const; - int hexes() const; - int damage() const; - int num_attacks() const; - double attack_weight() const; - double defense_weight() const; - - bool backstab() const; + unit_animation(const config& cfg); enum FRAME_TYPE { UNIT_FRAME, MISSILE_FRAME }; enum FRAME_DIRECTION { VERTICAL, DIAGONAL }; @@ -64,24 +49,7 @@ public: const std::vector& sound_effects() const; - bool matches_filter(const config& cfg) const; - bool apply_modification(const config& cfg,std::string* description); private: - std::string name_; - std::string type_; - std::string special_; - std::string icon_; - RANGE range_; - int hexes_; - int damage_; - int num_attacks_; - double attack_weight_; - double defense_weight_; - - //caches whether the unit can backstab. This is important - //because the AI queries it alot. - bool backstab_; - struct frame { frame(int i1, int i2, const std::string& img, const std::string& halo, int offset, int halo_x, int halo_y) : start(i1), end(i2), xoffset(offset), image(img), halo(halo), halo_x(halo_x), halo_y(halo_y) @@ -106,6 +74,49 @@ private: std::vector sfx_; }; +//the 'attack type' is the type of attack, how many times it strikes, +//and how much damage it does. +class attack_type +{ +public: + enum RANGE { SHORT_RANGE, LONG_RANGE }; + + attack_type(const config& cfg); + const std::string& name() const; + const std::string& type() const; + const std::string& special() const; + const std::string& icon() const; + RANGE range() const; + int hexes() const; + int damage() const; + int num_attacks() const; + double attack_weight() const; + double defense_weight() const; + + bool backstab() const; + + const unit_animation& animation() const { return animation_; } + + bool matches_filter(const config& cfg) const; + bool apply_modification(const config& cfg,std::string* description); +private: + unit_animation animation_; + std::string name_; + std::string type_; + std::string special_; + std::string icon_; + RANGE range_; + int hexes_; + int damage_; + int num_attacks_; + double attack_weight_; + double defense_weight_; + + //caches whether the unit can backstab. This is important + //because the AI queries it alot. + bool backstab_; +}; + class unit_movement_type; //the 'unit movement type' is the basic size of the unit - flying, small land, @@ -220,6 +231,8 @@ public: const std::string& race() const; + const unit_animation* defend_animation(bool hits, attack_type::RANGE range) const; + private: const config& cfg_; @@ -244,6 +257,18 @@ private: const std::vector& possibleTraits_; unit_race::GENDER gender_; + + struct defensive_animation + { + defensive_animation(const config& cfg); + bool matches(bool hits, attack_type::RANGE range) const; + + enum { HIT, MISS, HIT_OR_MISS } hits; + enum { SHORT, LONG, SHORT_OR_LONG } range; + unit_animation animation; + }; + + std::vector defensive_animations_; }; struct game_data