1 /*************************************************************************** 2 * Free Heroes of Might and Magic II: https://github.com/ihhub/fheroes2 * 3 * Copyright (C) 2020 * 4 * * 5 * This program is free software; you can redistribute it and/or modify * 6 * it under the terms of the GNU General Public License as published by * 7 * the Free Software Foundation; either version 2 of the License, or * 8 * (at your option) any later version. * 9 * * 10 * This program is distributed in the hope that it will be useful, * 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of * 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 13 * GNU General Public License for more details. * 14 * * 15 * You should have received a copy of the GNU General Public License * 16 * along with this program; if not, write to the * 17 * Free Software Foundation, Inc., * 18 * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * 19 ***************************************************************************/ 20 21 #include <map> 22 23 #include "agg.h" 24 #include "battle_animation.h" 25 #include "battle_cell.h" 26 #include "bin_info.h" 27 #include "logging.h" 28 #include "monster.h" 29 #include "serialize.h" 30 31 namespace 32 { 33 template <typename T> getValue(const uint8_t * data,const size_t base,const size_t offset=0)34 T getValue( const uint8_t * data, const size_t base, const size_t offset = 0 ) 35 { 36 return fheroes2::getLEValue<T>( reinterpret_cast<const char *>( data ), base, offset ); 37 } 38 } 39 40 namespace Bin_Info 41 { 42 class MonsterAnimCache 43 { 44 public: 45 AnimationReference createAnimReference( int monsterID ) const; 46 MonsterAnimInfo getAnimInfo( int monsterID ); 47 48 private: 49 std::map<int, MonsterAnimInfo> _animMap; 50 }; 51 52 const size_t CORRECT_FRM_LENGTH = 821; 53 54 // When base unit and its upgrade use the same FRM file (e.g. Archer and Ranger) 55 // We modify animation speed value to make them go faster 56 const double MOVE_SPEED_UPGRADE = 0.12; 57 const double SHOOT_SPEED_UPGRADE = 0.08; 58 const double RANGER_SHOOT_SPEED = 0.78; 59 60 std::map<int, AnimationReference> animRefs; 61 MonsterAnimCache _infoCache; 62 GetFilename(int monsterId)63 const char * GetFilename( int monsterId ) 64 { 65 return fheroes2::getMonsterData( monsterId ).binFileName; 66 } 67 MonsterAnimInfo(int monsterID,const std::vector<u8> & bytes)68 MonsterAnimInfo::MonsterAnimInfo( int monsterID, const std::vector<u8> & bytes ) 69 : moveSpeed( 450 ) 70 , shootSpeed( 0 ) 71 , flightSpeed( 0 ) 72 , troopCountOffsetLeft( 0 ) 73 , troopCountOffsetRight( 0 ) 74 , idleAnimationCount( 0 ) 75 , idleAnimationDelay( 0 ) 76 { 77 if ( bytes.size() != Bin_Info::CORRECT_FRM_LENGTH ) { 78 return; 79 } 80 81 const uint8_t * data = bytes.data(); 82 83 eyePosition = fheroes2::Point( getValue<int16_t>( data, 1 ), getValue<int16_t>( data, 3 ) ); 84 85 for ( size_t moveID = 0; moveID < 7; ++moveID ) { 86 std::vector<int> moveOffset; 87 88 moveOffset.reserve( 16 ); 89 90 for ( int frame = 0; frame < 16; ++frame ) { 91 moveOffset.push_back( static_cast<int>( getValue<int8_t>( data, 5 + moveID * 16, frame ) ) ); 92 } 93 94 frameXOffset.push_back( moveOffset ); 95 } 96 97 // Idle animations data 98 idleAnimationCount = data[117]; 99 if ( idleAnimationCount > 5u ) 100 idleAnimationCount = 5u; // here we need to reset our object 101 for ( uint32_t i = 0; i < idleAnimationCount; ++i ) 102 idlePriority.push_back( getValue<float>( data, 118, i ) ); 103 104 for ( uint32_t i = 0; i < idleAnimationCount; ++i ) 105 unusedIdleDelays.push_back( getValue<uint32_t>( data, 138, i ) ); 106 107 idleAnimationDelay = getValue<uint32_t>( data, 158 ); 108 109 // Monster speed data 110 moveSpeed = getValue<uint32_t>( data, 162 ); 111 shootSpeed = getValue<uint32_t>( data, 166 ); 112 flightSpeed = getValue<uint32_t>( data, 170 ); 113 114 // Projectile data 115 for ( size_t i = 0; i < 3; ++i ) { 116 projectileOffset.emplace_back( getValue<int16_t>( data, 174 + ( i * 4 ) ), getValue<int16_t>( data, 176 + ( i * 4 ) ) ); 117 } 118 119 // Elves and Grand Elves have incorrect start Y position for lower shooting attack 120 if ( monsterID == Monster::ELF || monsterID == Monster::GRAND_ELF ) { 121 if ( projectileOffset[2].y == -1 ) 122 projectileOffset[2].y = -32; 123 } 124 125 uint8_t projectileCount = data[186]; 126 if ( projectileCount > 12u ) 127 projectileCount = 12u; // here we need to reset our object 128 for ( uint8_t i = 0; i < projectileCount; ++i ) 129 projectileAngles.push_back( getValue<float>( data, 187, i ) ); 130 131 // Positional offsets for sprites & drawing 132 troopCountOffsetLeft = getValue<int32_t>( data, 235 ); 133 troopCountOffsetRight = getValue<int32_t>( data, 239 ); 134 135 // Load animation sequences themselves 136 for ( int idx = MOVE_START; idx <= SHOOT3_END; ++idx ) { 137 std::vector<int> anim; 138 uint8_t count = data[243 + idx]; 139 if ( count > 16 ) 140 count = 16; // here we need to reset our object 141 for ( uint8_t frame = 0; frame < count; ++frame ) { 142 anim.push_back( static_cast<int>( data[277 + idx * 16 + frame] ) ); 143 } 144 animationFrames.push_back( anim ); 145 } 146 147 if ( monsterID == Monster::WOLF ) { // Wolves have incorrect frame for lower attack animation 148 if ( animationFrames[ATTACK3].size() == 3 && animationFrames[ATTACK3][0] == 16 ) { 149 animationFrames[ATTACK3][0] = 2; 150 } 151 if ( animationFrames[ATTACK3_END].size() == 3 && animationFrames[ATTACK3_END][2] == 16 ) { 152 animationFrames[ATTACK3_END][2] = 2; 153 } 154 } 155 156 // Modify AnimInfo for upgraded monsters without own FRM file 157 int speedDiff = 0; 158 switch ( monsterID ) { 159 case Monster::RANGER: 160 case Monster::VETERAN_PIKEMAN: 161 case Monster::MASTER_SWORDSMAN: 162 case Monster::CHAMPION: 163 case Monster::CRUSADER: 164 case Monster::ORC_CHIEF: 165 case Monster::OGRE_LORD: 166 case Monster::WAR_TROLL: 167 case Monster::BATTLE_DWARF: 168 case Monster::GRAND_ELF: 169 case Monster::GREATER_DRUID: 170 case Monster::MINOTAUR_KING: 171 case Monster::STEEL_GOLEM: 172 case Monster::ARCHMAGE: 173 case Monster::MUTANT_ZOMBIE: 174 case Monster::ROYAL_MUMMY: 175 case Monster::VAMPIRE_LORD: 176 case Monster::POWER_LICH: 177 speedDiff = static_cast<int>( Monster( monsterID ).GetSpeed() ) - Monster( monsterID - 1 ).GetSpeed(); 178 break; 179 case Monster::EARTH_ELEMENT: 180 case Monster::AIR_ELEMENT: 181 case Monster::WATER_ELEMENT: 182 speedDiff = static_cast<int>( Monster( monsterID ).GetSpeed() ) - Monster( Monster::FIRE_ELEMENT ).GetSpeed(); 183 break; 184 default: 185 break; 186 } 187 188 if ( std::abs( speedDiff ) > 0 ) { 189 moveSpeed = static_cast<uint32_t>( ( 1 - MOVE_SPEED_UPGRADE * speedDiff ) * moveSpeed ); 190 // Ranger is special since he gets double attack on upgrade 191 if ( monsterID == Monster::RANGER ) { 192 shootSpeed = static_cast<uint32_t>( shootSpeed * RANGER_SHOOT_SPEED ); 193 } 194 else { 195 shootSpeed = static_cast<uint32_t>( ( 1 - SHOOT_SPEED_UPGRADE * speedDiff ) * shootSpeed ); 196 } 197 } 198 199 if ( frameXOffset[MOVE_STOP][0] == 0 && frameXOffset[MOVE_TILE_END][0] != 0 ) 200 frameXOffset[MOVE_STOP][0] = frameXOffset[MOVE_TILE_END][0]; 201 202 for ( int idx = MOVE_START; idx <= MOVE_ONE; ++idx ) 203 frameXOffset[idx].resize( animationFrames[idx].size(), 0 ); 204 205 if ( frameXOffset[MOVE_STOP].size() == 1 && frameXOffset[MOVE_STOP][0] == 0 ) { 206 if ( frameXOffset[MOVE_TILE_END].size() == 1 && frameXOffset[MOVE_TILE_END][0] != 0 ) 207 frameXOffset[MOVE_STOP][0] = frameXOffset[MOVE_TILE_END][0]; 208 else if ( frameXOffset[MOVE_TILE_START].size() == 1 && frameXOffset[MOVE_TILE_START][0] != 0 ) 209 frameXOffset[MOVE_STOP][0] = 44 + frameXOffset[MOVE_TILE_START][0]; 210 else 211 frameXOffset[MOVE_STOP][0] = frameXOffset[MOVE_MAIN].back(); 212 } 213 214 if ( monsterID == Monster::IRON_GOLEM || monsterID == Monster::STEEL_GOLEM ) { 215 if ( frameXOffset[MOVE_START].size() == 4 ) { // the original golem info 216 frameXOffset[MOVE_START][0] = 0; 217 frameXOffset[MOVE_START][1] = CELLW * 1 / 8; 218 frameXOffset[MOVE_START][2] = CELLW * 2 / 8; 219 frameXOffset[MOVE_START][3] = CELLW * 3 / 8; 220 for ( size_t id = 0; id < frameXOffset[MOVE_MAIN].size(); ++id ) 221 frameXOffset[MOVE_MAIN][id] += CELLW / 2; 222 } 223 } 224 225 if ( monsterID == Monster::SWORDSMAN || monsterID == Monster::MASTER_SWORDSMAN ) { 226 if ( frameXOffset[MOVE_START].size() == 2 && frameXOffset[MOVE_STOP].size() == 1 ) { // the original swordsman info 227 frameXOffset[MOVE_START][0] = 0; 228 frameXOffset[MOVE_START][1] = CELLW * 1 / 8; 229 for ( size_t id = 0; id < frameXOffset[MOVE_MAIN].size(); ++id ) 230 frameXOffset[MOVE_MAIN][id] += CELLW / 4; 231 232 frameXOffset[MOVE_STOP][0] = CELLW; 233 } 234 } 235 } 236 getAnimInfo(int monsterID)237 MonsterAnimInfo MonsterAnimCache::getAnimInfo( int monsterID ) 238 { 239 std::map<int, MonsterAnimInfo>::iterator mapIterator = _animMap.find( monsterID ); 240 if ( mapIterator != _animMap.end() ) { 241 return mapIterator->second; 242 } 243 else { 244 const MonsterAnimInfo info( monsterID, AGG::LoadBINFRM( Bin_Info::GetFilename( monsterID ) ) ); 245 if ( info.isValid() ) { 246 _animMap[monsterID] = info; 247 return info; 248 } 249 else { 250 DEBUG_LOG( DBG_ENGINE, DBG_WARN, "missing BIN FRM data: " << Bin_Info::GetFilename( monsterID ) << ", index: " << monsterID ); 251 } 252 } 253 return MonsterAnimInfo(); 254 } 255 isValid() const256 bool MonsterAnimInfo::isValid() const 257 { 258 if ( animationFrames.size() != SHOOT3_END + 1 ) 259 return false; 260 261 // Absolute minimal set up 262 const int essentialAnimations[7] = {MOVE_MAIN, STATIC, DEATH, WINCE_UP, ATTACK1, ATTACK2, ATTACK3}; 263 264 for ( int i = 0; i < 7; ++i ) { 265 if ( animationFrames.at( essentialAnimations[i] ).empty() ) 266 return false; 267 } 268 269 if ( idlePriority.size() != static_cast<size_t>( idleAnimationCount ) ) 270 return false; 271 272 return true; 273 } 274 hasAnim(int animID) const275 bool MonsterAnimInfo::hasAnim( int animID ) const 276 { 277 return animationFrames.size() == SHOOT3_END + 1 && !animationFrames.at( animID ).empty(); 278 } 279 getProjectileID(const double angle) const280 size_t MonsterAnimInfo::getProjectileID( const double angle ) const 281 { 282 const std::vector<float> & angles = projectileAngles; 283 if ( angles.empty() ) 284 return 0; 285 286 for ( size_t id = 0u; id < angles.size() - 1; ++id ) { 287 if ( angle >= static_cast<double>( angles[id] + angles[id + 1] ) / 2.0 ) 288 return id; 289 } 290 return angles.size() - 1; 291 } 292 createAnimReference(int monsterID) const293 AnimationReference MonsterAnimCache::createAnimReference( int monsterID ) const 294 { 295 return AnimationReference( monsterID ); 296 } 297 GetMonsterInfo(uint32_t monsterID)298 MonsterAnimInfo GetMonsterInfo( uint32_t monsterID ) 299 { 300 return _infoCache.getAnimInfo( monsterID ); 301 } 302 InitBinInfo()303 void InitBinInfo() 304 { 305 for ( int i = Monster::UNKNOWN; i < Monster::WATER_ELEMENT + 1; ++i ) 306 animRefs[i] = _infoCache.createAnimReference( i ); 307 } 308 } 309