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