1 /***************************************************************************
2  *   Copyright (C) 2009 by Andrey Afletdinov <fheroes2@gmail.com>          *
3  *                                                                         *
4  *   Part of the Free Heroes2 Engine:                                      *
5  *   http://sourceforge.net/projects/fheroes2                              *
6  *                                                                         *
7  *   This program is free software; you can redistribute it and/or modify  *
8  *   it under the terms of the GNU General Public License as published by  *
9  *   the Free Software Foundation; either version 2 of the License, or     *
10  *   (at your option) any later version.                                   *
11  *                                                                         *
12  *   This program is distributed in the hope that it will be useful,       *
13  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
14  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
15  *   GNU General Public License for more details.                          *
16  *                                                                         *
17  *   You should have received a copy of the GNU General Public License     *
18  *   along with this program; if not, write to the                         *
19  *   Free Software Foundation, Inc.,                                       *
20  *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
21  ***************************************************************************/
22 
23 #include <algorithm>
24 #include <array>
25 #include <cassert>
26 #include <iostream>
27 
28 #include "agg_image.h"
29 #include "castle.h"
30 #include "game.h"
31 #include "ground.h"
32 #include "heroes.h"
33 #include "icn.h"
34 #ifdef WITH_DEBUG
35 #include "game_interface.h"
36 #else
37 #include "interface_gamearea.h"
38 #endif
39 #include "logging.h"
40 #include "maps.h"
41 #include "maps_tiles.h"
42 #include "monster.h"
43 #include "monster_anim.h"
44 #include "mounts.h"
45 #include "mp2.h"
46 #include "objcrck.h"
47 #include "objdirt.h"
48 #include "objdsrt.h"
49 #include "objgras.h"
50 #include "objlava.h"
51 #include "objmult.h"
52 #include "objsnow.h"
53 #include "objswmp.h"
54 #include "objtown.h"
55 #include "objwatr.h"
56 #include "objxloc.h"
57 #include "race.h"
58 #include "save_format_version.h"
59 #include "serialize.h"
60 #include "settings.h"
61 #include "spell.h"
62 #ifdef WITH_DEBUG
63 #include "text.h"
64 #endif
65 #include "til.h"
66 #include "trees.h"
67 #include "world.h"
68 
69 namespace
70 {
isValidShadowSprite(const int icn,const uint8_t icnIndex)71     bool isValidShadowSprite( const int icn, const uint8_t icnIndex )
72     {
73         if ( icn == 0 ) {
74             // Special case when no objects exist.
75             return false;
76         }
77 
78         switch ( icn ) {
79         case ICN::MTNDSRT:
80         case ICN::MTNGRAS:
81         case ICN::MTNLAVA:
82         case ICN::MTNMULT:
83         case ICN::MTNSNOW:
84         case ICN::MTNSWMP:
85             return ObjMnts1::isShadow( icnIndex );
86         case ICN::MTNCRCK:
87         case ICN::MTNDIRT:
88             return ObjMnts2::isShadow( icnIndex );
89         case ICN::TREDECI:
90         case ICN::TREEVIL:
91         case ICN::TREFALL:
92         case ICN::TREFIR:
93         case ICN::TREJNGL:
94         case ICN::TRESNOW:
95             return ObjTree::isShadow( icnIndex );
96         case ICN::OBJNCRCK:
97             return ObjCrck::isShadow( icnIndex );
98         case ICN::OBJNDIRT:
99             return ObjDirt::isShadow( icnIndex );
100         case ICN::OBJNDSRT:
101             return ObjDsrt::isShadow( icnIndex );
102         case ICN::OBJNGRA2:
103             return ObjGra2::isShadow( icnIndex );
104         case ICN::OBJNGRAS:
105             return ObjGras::isShadow( icnIndex );
106         case ICN::OBJNMUL2:
107             return ObjMul2::isShadow( icnIndex );
108         case ICN::OBJNMULT:
109             return ObjMult::isShadow( icnIndex );
110         case ICN::OBJNSNOW:
111             return ObjSnow::isShadow( icnIndex );
112         case ICN::OBJNSWMP:
113             return ObjSwmp::isShadow( icnIndex );
114         case ICN::OBJNWAT2:
115             return ObjWat2::isShadow( icnIndex );
116         case ICN::OBJNWATR:
117             return ObjWatr::isShadow( icnIndex );
118         case ICN::OBJNARTI:
119         case ICN::OBJNRSRC:
120             return 0 == ( icnIndex % 2 );
121         case ICN::OBJNTWRD:
122             return icnIndex > 31;
123         case ICN::X_LOC1:
124             return ObjXlc1::isShadow( icnIndex );
125         case ICN::X_LOC2:
126             return ObjXlc2::isShadow( icnIndex );
127         case ICN::X_LOC3:
128             return ObjXlc3::isShadow( icnIndex );
129         case ICN::OBJNTOWN:
130             return ObjTown::isShadow( icnIndex );
131         case ICN::OBJNLAVA:
132             return ObjLava::isShadow( icnIndex );
133         case ICN::OBJNLAV2:
134             return ObjLav2::isShadow( icnIndex );
135         case ICN::OBJNLAV3:
136             return ObjLav3::isShadow( icnIndex );
137         case ICN::OBJNTWSH:
138             return true;
139         case ICN::STREAM:
140         case ICN::OBJNTWBA:
141         case ICN::OBJNXTRA:
142         case ICN::ROAD:
143         case ICN::EXTRAOVR:
144         case ICN::MONS32:
145         case ICN::BOAT32:
146         case ICN::FLAG32:
147         case ICN::MINIHERO:
148             return false;
149         default:
150             break;
151         }
152 
153         // Did you add a new type of objects into the game?
154         assert( 0 );
155         return false;
156     }
157 
isValidReefsSprite(const int icn,const uint8_t icnIndex)158     bool isValidReefsSprite( const int icn, const uint8_t icnIndex )
159     {
160         return icn == ICN::X_LOC2 && ObjXlc2::isReefs( icnIndex );
161     }
162 
163 #if defined( VERIFY_SHADOW_SPRITES )
164     // Define VERIFY_SHADOW_SPRITES macro to be able to use these functions.
isShadowImage(const fheroes2::Image & image)165     bool isShadowImage( const fheroes2::Image & image )
166     {
167         // The image can't be empty.
168         assert( !image.empty() );
169         if ( image.empty() )
170             return false;
171 
172         const uint8_t * data = image.transform();
173         const uint8_t * dataEnd = data + image.width() * image.height();
174 
175         size_t transformCounter = 0;
176 
177         for ( ; data != dataEnd; ++data ) {
178             if ( *data == 0 ) {
179                 return false;
180             }
181             else if ( *data != 1 ) {
182                 ++transformCounter;
183             }
184         }
185 
186         if ( transformCounter == 0 ) {
187             assert( image.width() == 1 && image.height() == 1 );
188             return true;
189         }
190 
191         return true;
192     }
193 
194     // Use this function to verify the correctness of data being returned by isValidShadowSprite function.
findAllShadowImages()195     void findAllShadowImages()
196     {
197         static bool completed = false;
198         if ( completed ) {
199             return;
200         }
201 
202         const std::vector<int32_t> icnIds
203             = { ICN::MTNDSRT,  ICN::MTNGRAS,  ICN::MTNLAVA,  ICN::MTNMULT,  ICN::MTNSNOW,  ICN::MTNSWMP,  ICN::MTNCRCK,  ICN::MTNDIRT,  ICN::TREDECI,
204                 ICN::TREEVIL,  ICN::TREFALL,  ICN::TREFIR,   ICN::TREJNGL,  ICN::TRESNOW,  ICN::OBJNCRCK, ICN::OBJNDIRT, ICN::OBJNDSRT, ICN::OBJNGRA2,
205                 ICN::OBJNGRAS, ICN::OBJNMUL2, ICN::OBJNMULT, ICN::OBJNSNOW, ICN::OBJNSWMP, ICN::OBJNWAT2, ICN::OBJNWATR, ICN::OBJNARTI, ICN::OBJNRSRC,
206                 ICN::OBJNTWRD, ICN::OBJNTWSH, ICN::STREAM,   ICN::OBJNTWBA, ICN::ROAD,     ICN::EXTRAOVR, ICN::X_LOC1,   ICN::X_LOC2,   ICN::X_LOC3,
207                 ICN::OBJNTOWN, ICN::OBJNLAVA, ICN::OBJNLAV2, ICN::OBJNLAV3, ICN::MONS32 };
208 
209         for ( const int32_t icnId : icnIds ) {
210             const uint32_t maxIndex = fheroes2::AGG::GetICNCount( icnId );
211             assert( maxIndex != 0 );
212 
213             std::string output;
214 
215             for ( uint32_t i = 0; i < maxIndex; i++ ) {
216                 const uint32_t startIndex = ICN::AnimationFrame( icnId, i, 0, true );
217                 const bool hasAnimation = startIndex != 0;
218                 bool isImageShadow = isShadowImage( fheroes2::AGG::GetICN( icnId, i ) );
219                 if ( isImageShadow && hasAnimation ) {
220                     for ( uint32_t indexOffset = 1;; ++indexOffset ) {
221                         const uint32_t animationIndex = ICN::AnimationFrame( icnId, i, indexOffset, true );
222                         if ( startIndex == animationIndex ) {
223                             break;
224                         }
225 
226                         if ( !isShadowImage( fheroes2::AGG::GetICN( icnId, animationIndex ) ) ) {
227                             isImageShadow = false;
228                             break;
229                         }
230                     }
231                 }
232 
233                 if ( isValidShadowSprite( icnId, i ) != isImageShadow ) {
234                     output += std::to_string( i );
235                     output += ", ";
236                 }
237             }
238 
239             if ( output.empty() ) {
240                 continue;
241             }
242 
243             VERBOSE_LOG( ICN::GetString( icnId ) << ": " << output );
244         }
245 
246         completed = true;
247     }
248 #endif
249 
contains(const int base,const int value)250     bool contains( const int base, const int value )
251     {
252         return ( base & value ) == value;
253     }
254 
255 #ifdef WITH_DEBUG
PassableViewSurface(const int passable)256     const fheroes2::Image & PassableViewSurface( const int passable )
257     {
258         static std::map<int, fheroes2::Image> imageMap;
259 
260         auto iter = imageMap.find( passable );
261         if ( iter != imageMap.end() ) {
262             return iter->second;
263         }
264 
265         const int32_t size = 31;
266         const uint8_t red = 0xBA;
267         const uint8_t green = 0x5A;
268 
269         fheroes2::Image sf( size, size );
270         sf.reset();
271 
272         if ( 0 == passable || Direction::CENTER == passable ) {
273             fheroes2::DrawBorder( sf, red );
274         }
275         else if ( DIRECTION_ALL == passable ) {
276             fheroes2::DrawBorder( sf, green );
277         }
278         else {
279             const uint8_t topLeftColor = ( ( passable & Direction::TOP_LEFT ) != 0 ) ? green : red;
280             const uint8_t bottomRightColor = ( ( passable & Direction::BOTTOM_RIGHT ) != 0 ) ? green : red;
281             const uint8_t topRightColor = ( ( passable & Direction::TOP_RIGHT ) != 0 ) ? green : red;
282             const uint8_t bottomLeftColor = ( ( passable & Direction::BOTTOM_LEFT ) != 0 ) ? green : red;
283             const uint8_t topColor = ( ( passable & Direction::TOP ) != 0 ) ? green : red;
284             const uint8_t bottomColor = ( ( passable & Direction::BOTTOM ) != 0 ) ? green : red;
285             const uint8_t leftColor = ( ( passable & Direction::LEFT ) != 0 ) ? green : red;
286             const uint8_t rightColor = ( ( passable & Direction::RIGHT ) != 0 ) ? green : red;
287 
288             uint8_t * image = sf.image();
289             uint8_t * transform = sf.transform();
290 
291             // Horizontal
292             for ( int32_t i = 0; i < 10; ++i ) {
293                 *( image + i ) = topLeftColor;
294                 *( transform + i ) = 0;
295 
296                 *( image + i + ( size - 1 ) * size ) = bottomLeftColor;
297                 *( transform + i + ( size - 1 ) * size ) = 0;
298             }
299 
300             for ( int32_t i = 10; i < 21; ++i ) {
301                 *( image + i ) = topColor;
302                 *( transform + i ) = 0;
303 
304                 *( image + i + ( size - 1 ) * size ) = bottomColor;
305                 *( transform + i + ( size - 1 ) * size ) = 0;
306             }
307 
308             for ( int32_t i = 21; i < size; ++i ) {
309                 *( image + i ) = topRightColor;
310                 *( transform + i ) = 0;
311 
312                 *( image + i + ( size - 1 ) * size ) = bottomRightColor;
313                 *( transform + i + ( size - 1 ) * size ) = 0;
314             }
315 
316             // Vertical
317             for ( int32_t i = 0; i < 10; ++i ) {
318                 *( image + i * size ) = topLeftColor;
319                 *( transform + i * size ) = 0;
320 
321                 *( image + size - 1 + i * size ) = topRightColor;
322                 *( transform + size - 1 + i * size ) = 0;
323             }
324 
325             for ( int32_t i = 10; i < 21; ++i ) {
326                 *( image + i * size ) = leftColor;
327                 *( transform + i * size ) = 0;
328 
329                 *( image + size - 1 + i * size ) = rightColor;
330                 *( transform + size - 1 + i * size ) = 0;
331             }
332 
333             for ( int32_t i = 21; i < size; ++i ) {
334                 *( image + i * size ) = bottomLeftColor;
335                 *( transform + i * size ) = 0;
336 
337                 *( image + size - 1 + i * size ) = bottomRightColor;
338                 *( transform + size - 1 + i * size ) = 0;
339             }
340         }
341 
342         return imageMap.emplace( passable, std::move( sf ) ).first->second;
343     }
344 #endif
345 
isShortObject(const MP2::MapObjectType objectType)346     bool isShortObject( const MP2::MapObjectType objectType )
347     {
348         // Some objects allow middle moves even being attached to the bottom.
349         // These object actually don't have any sprites on tiles above them within addon 2 level objects.
350         // TODO: find a better way to do not hardcode values here.
351 
352         switch ( objectType ) {
353         case MP2::OBJ_HALFLINGHOLE:
354         case MP2::OBJN_HALFLINGHOLE:
355         case MP2::OBJ_LEANTO:
356         case MP2::OBJ_WATERLAKE:
357         case MP2::OBJ_TARPIT:
358         case MP2::OBJ_MERCENARYCAMP:
359         case MP2::OBJN_MERCENARYCAMP:
360         case MP2::OBJ_STANDINGSTONES:
361         case MP2::OBJ_SHRINE1:
362         case MP2::OBJ_SHRINE2:
363         case MP2::OBJ_SHRINE3:
364         case MP2::OBJ_MAGICGARDEN:
365         case MP2::OBJ_RUINS:
366         case MP2::OBJN_RUINS:
367         case MP2::OBJ_SIGN:
368         case MP2::OBJ_IDOL:
369         case MP2::OBJ_STONELITHS:
370         case MP2::OBJN_STONELITHS:
371         case MP2::OBJ_WAGON:
372         case MP2::OBJ_WAGONCAMP:
373         case MP2::OBJN_WAGONCAMP:
374         case MP2::OBJ_GOBLINHUT:
375         case MP2::OBJ_FAERIERING:
376         case MP2::OBJN_FAERIERING:
377         case MP2::OBJ_BARRIER:
378         case MP2::OBJ_MAGICWELL:
379         case MP2::OBJ_NOTHINGSPECIAL:
380             return true;
381         default:
382             break;
383         }
384 
385         return false;
386     }
387 
isDetachedObjectType(const MP2::MapObjectType objectType)388     bool isDetachedObjectType( const MP2::MapObjectType objectType )
389     {
390         // Some objects do not take into account other objects below them.
391         switch ( objectType ) {
392         case MP2::OBJ_CASTLE:
393         case MP2::OBJ_WAGONCAMP:
394         case MP2::OBJ_FAERIERING:
395         case MP2::OBJ_MINES:
396         case MP2::OBJ_SAWMILL:
397         case MP2::OBJ_WATERALTAR:
398         case MP2::OBJ_AIRALTAR:
399         case MP2::OBJ_FIREALTAR:
400         case MP2::OBJ_EARTHALTAR:
401             return true;
402         default:
403             break;
404         }
405 
406         return false;
407     }
408 
isCombinedObject(const MP2::MapObjectType objectType)409     bool isCombinedObject( const MP2::MapObjectType objectType )
410     {
411         // Trees allow bottom and top movements but they don't allow the same for other trees.
412         switch ( objectType ) {
413         case MP2::OBJ_TREES:
414         case MP2::OBJ_CRATER:
415             return true;
416         default:
417             break;
418         }
419 
420         return false;
421     }
422 }
423 
TilesAddon()424 Maps::TilesAddon::TilesAddon()
425     : uniq( 0 )
426     , level( 0 )
427     , object( 0 )
428     , index( 0 )
429 {}
430 
TilesAddon(const uint8_t lv,const uint32_t uid,const uint8_t obj,const uint32_t index_)431 Maps::TilesAddon::TilesAddon( const uint8_t lv, const uint32_t uid, const uint8_t obj, const uint32_t index_ )
432     : uniq( uid )
433     , level( lv )
434     , object( obj )
435     , index( index_ )
436 {}
437 
String(int lvl) const438 std::string Maps::TilesAddon::String( int lvl ) const
439 {
440     std::ostringstream os;
441     os << "----------------" << lvl << "--------" << std::endl
442        << "uniq            : " << uniq << std::endl
443        << "tileset         : " << static_cast<int>( object ) << ", (" << ICN::GetString( MP2::GetICNObject( object ) ) << ")" << std::endl
444        << "index           : " << static_cast<int>( index ) << std::endl
445        << "level           : " << static_cast<int>( level ) << ", (" << static_cast<int>( level % 4 ) << ")" << std::endl
446        << "shadow          : " << isShadow( *this ) << std::endl;
447     return os.str();
448 }
449 
TilesAddon(const Maps::TilesAddon & ta)450 Maps::TilesAddon::TilesAddon( const Maps::TilesAddon & ta )
451     : uniq( ta.uniq )
452     , level( ta.level )
453     , object( ta.object )
454     , index( ta.index )
455 {}
456 
PredicateSortRules1(const Maps::TilesAddon & ta1,const Maps::TilesAddon & ta2)457 bool Maps::TilesAddon::PredicateSortRules1( const Maps::TilesAddon & ta1, const Maps::TilesAddon & ta2 )
458 {
459     return ( ( ta1.level % 4 ) > ( ta2.level % 4 ) );
460 }
461 
GetLoyaltyObject(const uint8_t tileset,const uint8_t icnIndex)462 MP2::MapObjectType Maps::Tiles::GetLoyaltyObject( const uint8_t tileset, const uint8_t icnIndex )
463 {
464     switch ( MP2::GetICNObject( tileset ) ) {
465     case ICN::X_LOC1:
466         if ( icnIndex == 3 )
467             return MP2::OBJ_ALCHEMYTOWER;
468         else if ( icnIndex < 3 )
469             return MP2::OBJN_ALCHEMYTOWER;
470         else if ( 70 == icnIndex )
471             return MP2::OBJ_ARENA;
472         else if ( 3 < icnIndex && icnIndex < 72 )
473             return MP2::OBJN_ARENA;
474         else if ( 77 == icnIndex )
475             return MP2::OBJ_BARROWMOUNDS;
476         else if ( 71 < icnIndex && icnIndex < 78 )
477             return MP2::OBJN_BARROWMOUNDS;
478         else if ( 94 == icnIndex )
479             return MP2::OBJ_EARTHALTAR;
480         else if ( 77 < icnIndex && icnIndex < 112 )
481             return MP2::OBJN_EARTHALTAR;
482         else if ( 118 == icnIndex )
483             return MP2::OBJ_AIRALTAR;
484         else if ( 111 < icnIndex && icnIndex < 120 )
485             return MP2::OBJN_AIRALTAR;
486         else if ( 127 == icnIndex )
487             return MP2::OBJ_FIREALTAR;
488         else if ( 119 < icnIndex && icnIndex < 129 )
489             return MP2::OBJN_FIREALTAR;
490         else if ( 135 == icnIndex )
491             return MP2::OBJ_WATERALTAR;
492         else if ( 128 < icnIndex && icnIndex < 137 )
493             return MP2::OBJN_WATERALTAR;
494         break;
495 
496     case ICN::X_LOC2:
497         if ( icnIndex == 4 )
498             return MP2::OBJ_STABLES;
499         else if ( icnIndex < 4 )
500             return MP2::OBJN_STABLES;
501         else if ( icnIndex == 9 )
502             return MP2::OBJ_JAIL;
503         else if ( 4 < icnIndex && icnIndex < 10 )
504             return MP2::OBJN_JAIL;
505         else if ( icnIndex == 37 )
506             return MP2::OBJ_MERMAID;
507         else if ( 9 < icnIndex && icnIndex < 47 )
508             return MP2::OBJN_MERMAID;
509         else if ( icnIndex == 101 )
510             return MP2::OBJ_SIRENS;
511         else if ( 46 < icnIndex && icnIndex < 111 )
512             return MP2::OBJN_SIRENS;
513         else if ( ObjXlc2::isReefs( icnIndex ) )
514             return MP2::OBJ_REEFS;
515         break;
516 
517     case ICN::X_LOC3:
518         if ( icnIndex == 30 )
519             return MP2::OBJ_HUTMAGI;
520         else if ( icnIndex < 32 )
521             return MP2::OBJN_HUTMAGI;
522         else if ( icnIndex == 50 )
523             return MP2::OBJ_EYEMAGI;
524         else if ( 31 < icnIndex && icnIndex < 59 )
525             return MP2::OBJN_EYEMAGI;
526         break;
527 
528     default:
529         break;
530     }
531 
532     return MP2::OBJ_ZERO;
533 }
534 
isRoad() const535 bool Maps::TilesAddon::isRoad() const
536 {
537     switch ( MP2::GetICNObject( object ) ) {
538     // road sprite
539     case ICN::ROAD:
540         if ( 1 == index || 8 == index || 10 == index || 11 == index || 15 == index || 22 == index || 23 == index || 24 == index || 25 == index || 27 == index )
541             return false;
542         else
543             return true;
544 
545     // castle or town gate
546     case ICN::OBJNTOWN:
547     case ICN::OBJNTWRD:
548         if ( 13 == index || 29 == index || 45 == index || 61 == index || 77 == index || 93 == index || 109 == index || 125 == index || 141 == index || 157 == index
549              || 173 == index || 189 == index )
550             return true;
551         break;
552 
553     default:
554         break;
555     }
556 
557     return false;
558 }
559 
hasSpriteAnimation() const560 bool Maps::TilesAddon::hasSpriteAnimation() const
561 {
562     return object & 1;
563 }
564 
isResource(const TilesAddon & ta)565 bool Maps::TilesAddon::isResource( const TilesAddon & ta )
566 {
567     // OBJNRSRC
568     return ICN::OBJNRSRC == MP2::GetICNObject( ta.object ) && ( ta.index % 2 );
569 }
570 
isArtifact(const TilesAddon & ta)571 bool Maps::TilesAddon::isArtifact( const TilesAddon & ta )
572 {
573     // OBJNARTI (skip ultimate)
574     return ( ICN::OBJNARTI == MP2::GetICNObject( ta.object ) && ( ta.index > 0x10 ) && ( ta.index % 2 ) );
575 }
576 
ColorFromBarrierSprite(const uint8_t tileset,const uint8_t icnIndex)577 int Maps::Tiles::ColorFromBarrierSprite( const uint8_t tileset, const uint8_t icnIndex )
578 {
579     // 60, 66, 72, 78, 84, 90, 96, 102
580     return ICN::X_LOC3 == MP2::GetICNObject( tileset ) && 60 <= icnIndex && 102 >= icnIndex ? ( ( icnIndex - 60 ) / 6 ) + 1 : 0;
581 }
582 
ColorFromTravellerTentSprite(const uint8_t tileset,const uint8_t icnIndex)583 int Maps::Tiles::ColorFromTravellerTentSprite( const uint8_t tileset, const uint8_t icnIndex )
584 {
585     // 110, 114, 118, 122, 126, 130, 134, 138
586     return ICN::X_LOC3 == MP2::GetICNObject( tileset ) && 110 <= icnIndex && 138 >= icnIndex ? ( ( icnIndex - 110 ) / 4 ) + 1 : 0;
587 }
588 
isFlag32(const TilesAddon & ta)589 bool Maps::TilesAddon::isFlag32( const TilesAddon & ta )
590 {
591     return ICN::FLAG32 == MP2::GetICNObject( ta.object );
592 }
593 
isShadow(const TilesAddon & ta)594 bool Maps::TilesAddon::isShadow( const TilesAddon & ta )
595 {
596     return Tiles::isShadowSprite( ta.object, ta.index );
597 }
598 
isShadowSprite(const int icn,const uint8_t icnIndex)599 bool Maps::Tiles::isShadowSprite( const int icn, const uint8_t icnIndex )
600 {
601     return isValidShadowSprite( icn, icnIndex );
602 }
603 
isShadowSprite(const uint8_t tileset,const uint8_t icnIndex)604 bool Maps::Tiles::isShadowSprite( const uint8_t tileset, const uint8_t icnIndex )
605 {
606     return isShadowSprite( MP2::GetICNObject( tileset ), icnIndex );
607 }
608 
UpdateAbandoneMineLeftSprite(uint8_t & tileset,uint8_t & index,const int resource)609 void Maps::Tiles::UpdateAbandoneMineLeftSprite( uint8_t & tileset, uint8_t & index, const int resource )
610 {
611     if ( ICN::OBJNGRAS == MP2::GetICNObject( tileset ) && 6 == index ) {
612         tileset = 128; // MTNGRAS
613         index = 82;
614     }
615     else if ( ICN::OBJNDIRT == MP2::GetICNObject( tileset ) && 8 == index ) {
616         tileset = 104; // MTNDIRT
617         index = 112;
618     }
619     else if ( ICN::EXTRAOVR == MP2::GetICNObject( tileset ) && 5 == index ) {
620         switch ( resource ) {
621         case Resource::ORE:
622             index = 0;
623             break;
624         case Resource::SULFUR:
625             index = 1;
626             break;
627         case Resource::CRYSTAL:
628             index = 2;
629             break;
630         case Resource::GEMS:
631             index = 3;
632             break;
633         case Resource::GOLD:
634             index = 4;
635             break;
636         default:
637             break;
638         }
639     }
640 }
641 
UpdateAbandoneMineRightSprite(uint8_t & tileset,uint8_t & index)642 void Maps::Tiles::UpdateAbandoneMineRightSprite( uint8_t & tileset, uint8_t & index )
643 {
644     if ( ICN::OBJNDIRT == MP2::GetICNObject( tileset ) && index == 9 ) {
645         tileset = 104;
646         index = 113;
647     }
648     else if ( ICN::OBJNGRAS == MP2::GetICNObject( tileset ) && index == 7 ) {
649         tileset = 128;
650         index = 83;
651     }
652 }
653 
ColorRaceFromHeroSprite(const uint32_t heroSpriteIndex)654 std::pair<int, int> Maps::Tiles::ColorRaceFromHeroSprite( const uint32_t heroSpriteIndex )
655 {
656     std::pair<int, int> res;
657 
658     if ( 7 > heroSpriteIndex )
659         res.first = Color::BLUE;
660     else if ( 14 > heroSpriteIndex )
661         res.first = Color::GREEN;
662     else if ( 21 > heroSpriteIndex )
663         res.first = Color::RED;
664     else if ( 28 > heroSpriteIndex )
665         res.first = Color::YELLOW;
666     else if ( 35 > heroSpriteIndex )
667         res.first = Color::ORANGE;
668     else
669         res.first = Color::PURPLE;
670 
671     switch ( heroSpriteIndex % 7 ) {
672     case 0:
673         res.second = Race::KNGT;
674         break;
675     case 1:
676         res.second = Race::BARB;
677         break;
678     case 2:
679         res.second = Race::SORC;
680         break;
681     case 3:
682         res.second = Race::WRLK;
683         break;
684     case 4:
685         res.second = Race::WZRD;
686         break;
687     case 5:
688         res.second = Race::NECR;
689         break;
690     case 6:
691         res.second = Race::RAND;
692         break;
693     }
694 
695     return res;
696 }
697 
698 /* Maps::Addons */
Remove(u32 uniq)699 void Maps::Addons::Remove( u32 uniq )
700 {
701     remove_if( [uniq]( const TilesAddon & v ) { return v.isUniq( uniq ); } );
702 }
703 
PackTileSpriteIndex(u32 index,u32 shape)704 u32 PackTileSpriteIndex( u32 index, u32 shape ) /* index max: 0x3FFF, shape value: 0, 1, 2, 3 */
705 {
706     return ( shape << 14 ) | ( 0x3FFF & index );
707 }
708 
Tiles()709 Maps::Tiles::Tiles()
710     : _index( 0 )
711     , pack_sprite_index( 0 )
712     , uniq( 0 )
713     , objectTileset( 0 )
714     , objectIndex( 255 )
715     , mp2_object( 0 )
716     , tilePassable( DIRECTION_ALL )
717     , fog_colors( Color::ALL )
718     , quantity1( 0 )
719     , quantity2( 0 )
720     , quantity3( 0 )
721 {}
722 
Init(s32 index,const MP2::mp2tile_t & mp2)723 void Maps::Tiles::Init( s32 index, const MP2::mp2tile_t & mp2 )
724 {
725     tilePassable = DIRECTION_ALL;
726 
727     _level = mp2.quantity1 & 0x03;
728     quantity1 = mp2.quantity1;
729     quantity2 = mp2.quantity2;
730     quantity3 = 0;
731     fog_colors = Color::ALL;
732 
733     SetTile( mp2.surfaceType, mp2.flags );
734     SetIndex( index );
735     SetObject( static_cast<MP2::MapObjectType>( mp2.mapObjectType ) );
736 
737     addons_level1.clear();
738     addons_level2.clear();
739 
740     // those bitfields are set by map editor regardless if map object is there
741     tileIsRoad = ( ( mp2.objectName1 >> 1 ) & 1 ) && ( MP2::GetICNObject( mp2.objectName1 ) == ICN::ROAD );
742 
743     // If an object has priority 2 (shadow) or 3 (ground) then we put it as an addon.
744     if ( mp2.mapObjectType == MP2::OBJ_ZERO && ( _level >> 1 ) & 1 ) {
745         AddonsPushLevel1( mp2 );
746     }
747     else {
748         objectTileset = mp2.objectName1;
749         objectIndex = mp2.level1IcnImageIndex;
750         uniq = mp2.level1ObjectUID;
751     }
752     AddonsPushLevel2( mp2 );
753 }
754 
GetHeroes(void) const755 Heroes * Maps::Tiles::GetHeroes( void ) const
756 {
757     return MP2::OBJ_HEROES == mp2_object && heroID ? world.GetHeroes( heroID - 1 ) : nullptr;
758 }
759 
SetHeroes(Heroes * hero)760 void Maps::Tiles::SetHeroes( Heroes * hero )
761 {
762     if ( hero ) {
763         hero->SetMapsObject( static_cast<MP2::MapObjectType>( mp2_object ) );
764         heroID = hero->GetID() + 1;
765         SetObject( MP2::OBJ_HEROES );
766     }
767     else {
768         hero = GetHeroes();
769 
770         if ( hero ) {
771             SetObject( hero->GetMapsObject() );
772             hero->SetMapsObject( MP2::OBJ_ZERO );
773         }
774         else {
775             setAsEmpty();
776         }
777 
778         heroID = 0;
779     }
780 }
781 
GetCenter(void) const782 fheroes2::Point Maps::Tiles::GetCenter( void ) const
783 {
784     return Maps::GetPoint( _index );
785 }
786 
GetObject(bool ignoreObjectUnderHero) const787 MP2::MapObjectType Maps::Tiles::GetObject( bool ignoreObjectUnderHero /* true */ ) const
788 {
789     if ( !ignoreObjectUnderHero && MP2::OBJ_HEROES == mp2_object ) {
790         const Heroes * hero = GetHeroes();
791         return hero ? hero->GetMapsObject() : MP2::OBJ_ZERO;
792     }
793 
794     return static_cast<MP2::MapObjectType>( mp2_object );
795 }
796 
SetObject(const MP2::MapObjectType objectType)797 void Maps::Tiles::SetObject( const MP2::MapObjectType objectType )
798 {
799     mp2_object = objectType;
800     world.resetPathfinder();
801 }
802 
setBoat(int direction)803 void Maps::Tiles::setBoat( int direction )
804 {
805     if ( objectTileset != 0 && objectIndex != 255 ) {
806         AddonsPushLevel1( TilesAddon( 0, uniq, objectTileset, objectIndex ) );
807     }
808     SetObject( MP2::OBJ_BOAT );
809     objectTileset = ICN::BOAT32;
810 
811     // Left-side sprites have to flipped, add 128 to index
812     switch ( direction ) {
813     case Direction::TOP:
814         objectIndex = 0;
815         break;
816     case Direction::TOP_RIGHT:
817         objectIndex = 9;
818         break;
819     case Direction::RIGHT:
820         objectIndex = 18;
821         break;
822     case Direction::BOTTOM_RIGHT:
823         objectIndex = 27;
824         break;
825     case Direction::BOTTOM:
826         objectIndex = 36;
827         break;
828     case Direction::BOTTOM_LEFT:
829         objectIndex = 27 + 128;
830         break;
831     case Direction::LEFT:
832         objectIndex = 18 + 128;
833         break;
834     case Direction::TOP_LEFT:
835         objectIndex = 9 + 128;
836         break;
837     default:
838         objectIndex = 18;
839         break;
840     }
841 }
842 
getBoatDirection() const843 int Maps::Tiles::getBoatDirection() const
844 {
845     // Check if it really is a boat
846     if ( objectTileset != ICN::BOAT32 )
847         return Direction::UNKNOWN;
848 
849     // Left-side sprites have to flipped, add 128 to index
850     switch ( objectIndex ) {
851     case 0:
852         return Direction::TOP;
853     case 9:
854         return Direction::TOP_RIGHT;
855     case 18:
856         return Direction::RIGHT;
857     case 27:
858         return Direction::BOTTOM_RIGHT;
859     case 36:
860         return Direction::BOTTOM;
861     case 27 + 128:
862         return Direction::BOTTOM_LEFT;
863     case 18 + 128:
864         return Direction::LEFT;
865     case 9 + 128:
866         return Direction::TOP_LEFT;
867     default:
868         break;
869     }
870 
871     return Direction::UNKNOWN;
872 }
873 
resetObjectSprite()874 void Maps::Tiles::resetObjectSprite()
875 {
876     objectTileset = 0;
877     objectIndex = 255;
878 }
879 
SetTile(u32 sprite_index,u32 shape)880 void Maps::Tiles::SetTile( u32 sprite_index, u32 shape )
881 {
882     pack_sprite_index = PackTileSpriteIndex( sprite_index, shape );
883 }
884 
TileSpriteIndex(void) const885 u32 Maps::Tiles::TileSpriteIndex( void ) const
886 {
887     return pack_sprite_index & 0x3FFF;
888 }
889 
TileSpriteShape(void) const890 u32 Maps::Tiles::TileSpriteShape( void ) const
891 {
892     return pack_sprite_index >> 14;
893 }
894 
GetTileSurface(void) const895 const fheroes2::Image & Maps::Tiles::GetTileSurface( void ) const
896 {
897     return fheroes2::AGG::GetTIL( TIL::GROUND32, TileSpriteIndex(), TileSpriteShape() );
898 }
899 
getOriginalPassability() const900 int Maps::Tiles::getOriginalPassability() const
901 {
902     const MP2::MapObjectType objectType = GetObject( false );
903 
904     if ( MP2::isActionObject( objectType ) ) {
905         return MP2::getActionObjectDirection( objectType );
906     }
907 
908     if ( ( objectTileset == 0 || objectIndex == 255 ) || ( ( _level >> 1 ) & 1 ) || isShadow() ) {
909         // No object exists. Make it fully passable.
910         return DIRECTION_ALL;
911     }
912 
913     if ( isValidReefsSprite( MP2::GetICNObject( objectTileset ), objectIndex ) ) {
914         return 0;
915     }
916 
917     for ( const TilesAddon & addon : addons_level1 ) {
918         if ( isValidReefsSprite( MP2::GetICNObject( addon.object ), addon.index ) ) {
919             return 0;
920         }
921     }
922 
923     // Objects have fixed passability.
924     return DIRECTION_CENTER_ROW | DIRECTION_BOTTOM_ROW;
925 }
926 
setInitialPassability()927 void Maps::Tiles::setInitialPassability()
928 {
929     tilePassable = getOriginalPassability();
930 }
931 
updatePassability()932 void Maps::Tiles::updatePassability()
933 {
934     if ( !Maps::isValidDirection( _index, Direction::LEFT ) ) {
935         tilePassable &= ~( Direction::LEFT | Direction::TOP_LEFT | Direction::BOTTOM_LEFT );
936     }
937     if ( !Maps::isValidDirection( _index, Direction::RIGHT ) ) {
938         tilePassable &= ~( Direction::RIGHT | Direction::TOP_RIGHT | Direction::BOTTOM_RIGHT );
939     }
940     if ( !Maps::isValidDirection( _index, Direction::TOP ) ) {
941         tilePassable &= ~( Direction::TOP | Direction::TOP_LEFT | Direction::TOP_RIGHT );
942     }
943     if ( !Maps::isValidDirection( _index, Direction::BOTTOM ) ) {
944         tilePassable &= ~( Direction::BOTTOM | Direction::BOTTOM_LEFT | Direction::BOTTOM_RIGHT );
945     }
946 
947     const MP2::MapObjectType objectType = GetObject( false );
948     const bool isActionObject = MP2::isActionObject( objectType );
949     if ( !isActionObject && objectTileset > 0 && objectIndex < 255 && ( ( _level >> 1 ) & 1 ) == 0 && !isShadow() ) {
950         // This is a non-action object.
951         if ( Maps::isValidDirection( _index, Direction::BOTTOM ) ) {
952             const Tiles & bottomTile = world.GetTiles( Maps::GetDirectionIndex( _index, Direction::BOTTOM ) );
953 
954             // If a bottom tile has the same object ID then this tile is inaccessible.
955             std::vector<uint32_t> tileUIDs;
956             if ( objectTileset > 0 && objectIndex < 255 && uniq != 0 && ( ( _level >> 1 ) & 1 ) == 0 ) {
957                 tileUIDs.emplace_back( uniq );
958             }
959 
960             for ( const TilesAddon & addon : addons_level1 ) {
961                 if ( addon.uniq != 0 && ( ( addon.level >> 1 ) & 1 ) == 0 ) {
962                     tileUIDs.emplace_back( addon.uniq );
963                 }
964             }
965 
966             for ( const uint32_t objectId : tileUIDs ) {
967                 if ( bottomTile.doesObjectExist( objectId ) ) {
968                     tilePassable = 0;
969                     return;
970                 }
971             }
972 
973             // If an object locates on land and the bottom tile is water mark the current tile as impassible. It's done for cases that a hero won't be able to
974             // disembark on the tile.
975             if ( !isWater() && bottomTile.isWater() ) {
976                 tilePassable = 0;
977                 return;
978             }
979 
980             const bool isBottomTileObject = ( ( bottomTile._level >> 1 ) & 1 ) == 0;
981 
982             if ( !isDetachedObject() && isBottomTileObject && bottomTile.objectTileset > 0 && bottomTile.objectIndex < 255 ) {
983                 const MP2::MapObjectType bottomTileObjectType = bottomTile.GetObject( false );
984                 const bool isBottomTileActionObject = MP2::isActionObject( bottomTileObjectType );
985                 const MP2::MapObjectType correctedObjectType = MP2::getBaseActionObjectType( bottomTileObjectType );
986 
987                 if ( isBottomTileActionObject ) {
988                     if ( ( MP2::getActionObjectDirection( bottomTileObjectType ) & Direction::TOP ) == 0 ) {
989                         if ( isShortObject( bottomTileObjectType ) ) {
990                             tilePassable &= ~Direction::BOTTOM;
991                         }
992                         else {
993                             tilePassable = 0;
994                             return;
995                         }
996                     }
997                 }
998                 else if ( bottomTile.mp2_object != 0 && correctedObjectType != bottomTileObjectType && MP2::isActionObject( correctedObjectType )
999                           && isShortObject( correctedObjectType ) && ( bottomTile.getOriginalPassability() & Direction::TOP ) == 0 ) {
1000                     tilePassable &= ~Direction::BOTTOM;
1001                 }
1002                 else if ( isShortObject( bottomTileObjectType )
1003                           || ( !bottomTile.containsTileSet( getValidTileSets() ) && ( isCombinedObject( objectType ) || isCombinedObject( bottomTileObjectType ) ) ) ) {
1004                     tilePassable &= ~Direction::BOTTOM;
1005                 }
1006                 else {
1007                     tilePassable = 0;
1008                     return;
1009                 }
1010             }
1011         }
1012         else {
1013             tilePassable = 0;
1014             return;
1015         }
1016     }
1017 
1018     // Left side.
1019     if ( ( tilePassable & Direction::TOP_LEFT ) && Maps::isValidDirection( _index, Direction::LEFT ) ) {
1020         const Tiles & leftTile = world.GetTiles( Maps::GetDirectionIndex( _index, Direction::LEFT ) );
1021         const bool leftTileTallObject = leftTile.isTallObject();
1022         if ( leftTileTallObject && ( leftTile.getOriginalPassability() & Direction::TOP ) == 0 ) {
1023             tilePassable &= ~Direction::TOP_LEFT;
1024         }
1025     }
1026 
1027     // Right side.
1028     if ( ( tilePassable & Direction::TOP_RIGHT ) && Maps::isValidDirection( _index, Direction::RIGHT ) ) {
1029         const Tiles & rightTile = world.GetTiles( Maps::GetDirectionIndex( _index, Direction::RIGHT ) );
1030         const bool rightTileTallObject = rightTile.isTallObject();
1031         if ( rightTileTallObject && ( rightTile.getOriginalPassability() & Direction::TOP ) == 0 ) {
1032             tilePassable &= ~Direction::TOP_RIGHT;
1033         }
1034     }
1035 }
1036 
doesObjectExist(const uint32_t uid) const1037 bool Maps::Tiles::doesObjectExist( const uint32_t uid ) const
1038 {
1039     if ( uniq == uid && ( ( _level >> 1 ) & 1 ) == 0 ) {
1040         return true;
1041     }
1042 
1043     for ( const TilesAddon & addon : addons_level1 ) {
1044         if ( addon.uniq == uid && ( ( addon.level >> 1 ) & 1 ) == 0 ) {
1045             return true;
1046         }
1047     }
1048 
1049     return false;
1050 }
1051 
GetRegion() const1052 uint32_t Maps::Tiles::GetRegion() const
1053 {
1054     return _region;
1055 }
1056 
UpdateRegion(uint32_t newRegionID)1057 void Maps::Tiles::UpdateRegion( uint32_t newRegionID )
1058 {
1059     if ( tilePassable ) {
1060         _region = newRegionID;
1061     }
1062 }
1063 
GetObjectUID() const1064 u32 Maps::Tiles::GetObjectUID() const
1065 {
1066     return uniq;
1067 }
1068 
GetPassable(void) const1069 int Maps::Tiles::GetPassable( void ) const
1070 {
1071     return tilePassable;
1072 }
1073 
isClearGround() const1074 bool Maps::Tiles::isClearGround() const
1075 {
1076     const MP2::MapObjectType objectType = GetObject( true );
1077 
1078     switch ( objectType ) {
1079     case MP2::OBJ_ZERO:
1080     case MP2::OBJ_COAST:
1081         return true;
1082 
1083     default:
1084         break;
1085     }
1086 
1087     if ( objectTileset == 0 || objectIndex == 255 || ( ( _level >> 1 ) & 1 ) == 1 ) {
1088         if ( MP2::isActionObject( objectType, isWater() ) ) {
1089             return false;
1090         }
1091         // No objects are here.
1092         return true;
1093     }
1094 
1095     return false;
1096 }
1097 
AddonsPushLevel1(const MP2::mp2tile_t & mt)1098 void Maps::Tiles::AddonsPushLevel1( const MP2::mp2tile_t & mt )
1099 {
1100     if ( mt.objectName1 != 0 && mt.level1IcnImageIndex != 0xFF ) {
1101         addons_level1.emplace_back( mt.quantity1, mt.level1ObjectUID, mt.objectName1, mt.level1IcnImageIndex );
1102     }
1103 
1104     // MP2 "objectName" is a bitfield
1105     // 6 bits is ICN tileset id, 1 bit isRoad flag, 1 bit hasAnimation flag
1106     if ( ( ( mt.objectName1 >> 1 ) & 1 ) && ( MP2::GetICNObject( mt.objectName1 ) == ICN::ROAD ) )
1107         tileIsRoad = true;
1108 }
1109 
AddonsPushLevel1(const MP2::mp2addon_t & ma)1110 void Maps::Tiles::AddonsPushLevel1( const MP2::mp2addon_t & ma )
1111 {
1112     if ( ma.objectNameN1 && ma.indexNameN1 < 0xFF ) {
1113         addons_level1.emplace_back( ma.quantityN, ma.level1ObjectUID, ma.objectNameN1, ma.indexNameN1 );
1114     }
1115 }
1116 
AddonsPushLevel1(const TilesAddon & ta)1117 void Maps::Tiles::AddonsPushLevel1( const TilesAddon & ta )
1118 {
1119     addons_level1.emplace_back( ta );
1120 }
1121 
AddonsPushLevel2(const MP2::mp2tile_t & mt)1122 void Maps::Tiles::AddonsPushLevel2( const MP2::mp2tile_t & mt )
1123 {
1124     if ( mt.objectName2 && mt.level2IcnImageIndex != 0xFF ) {
1125         addons_level2.emplace_back( mt.quantity1, mt.level2ObjectUID, mt.objectName2, mt.level2IcnImageIndex );
1126     }
1127 }
1128 
AddonsPushLevel2(const MP2::mp2addon_t & ma)1129 void Maps::Tiles::AddonsPushLevel2( const MP2::mp2addon_t & ma )
1130 {
1131     if ( ma.objectNameN2 && ma.indexNameN2 < 0xFF ) {
1132         addons_level2.emplace_back( ma.quantityN, ma.level2ObjectUID, ma.objectNameN2, ma.indexNameN2 );
1133     }
1134 }
1135 
AddonsSort()1136 void Maps::Tiles::AddonsSort()
1137 {
1138     // Push everything to the container and sort it by level.
1139     if ( objectTileset != 0 && objectIndex < 255 ) {
1140         addons_level1.emplace_front( _level, uniq, objectTileset, objectIndex );
1141     }
1142 
1143     // Some original maps have issues with identifying tiles as roads. This code fixes it. It's not an ideal solution but works fine in most of cases.
1144     if ( !tileIsRoad ) {
1145         for ( const TilesAddon & addon : addons_level1 ) {
1146             if ( addon.isRoad() ) {
1147                 tileIsRoad = true;
1148                 break;
1149             }
1150         }
1151     }
1152 
1153     addons_level1.sort( TilesAddon::PredicateSortRules1 );
1154 
1155     if ( !addons_level1.empty() ) {
1156         const TilesAddon & highestPriorityAddon = addons_level1.back();
1157         uniq = highestPriorityAddon.uniq;
1158         objectTileset = highestPriorityAddon.object;
1159         objectIndex = highestPriorityAddon.index;
1160         _level = highestPriorityAddon.level & 0x03;
1161 
1162         addons_level1.pop_back();
1163     }
1164 
1165     // Level 2 objects don't have any rendering priorities so they should be rendered first in queue first to render.
1166 }
1167 
GetGround(void) const1168 int Maps::Tiles::GetGround( void ) const
1169 {
1170     const u32 index = TileSpriteIndex();
1171 
1172     // list grounds from GROUND32.TIL
1173     if ( 30 > index )
1174         return Maps::Ground::WATER;
1175     else if ( 92 > index )
1176         return Maps::Ground::GRASS;
1177     else if ( 146 > index )
1178         return Maps::Ground::SNOW;
1179     else if ( 208 > index )
1180         return Maps::Ground::SWAMP;
1181     else if ( 262 > index )
1182         return Maps::Ground::LAVA;
1183     else if ( 321 > index )
1184         return Maps::Ground::DESERT;
1185     else if ( 361 > index )
1186         return Maps::Ground::DIRT;
1187     else if ( 415 > index )
1188         return Maps::Ground::WASTELAND;
1189 
1190     return Maps::Ground::BEACH;
1191 }
1192 
isWater(void) const1193 bool Maps::Tiles::isWater( void ) const
1194 {
1195     return 30 > TileSpriteIndex();
1196 }
1197 
RedrawTile(fheroes2::Image & dst,const fheroes2::Rect & visibleTileROI,const Interface::GameArea & area) const1198 void Maps::Tiles::RedrawTile( fheroes2::Image & dst, const fheroes2::Rect & visibleTileROI, const Interface::GameArea & area ) const
1199 {
1200     const fheroes2::Point & mp = Maps::GetPoint( _index );
1201 
1202     if ( !( visibleTileROI & mp ) )
1203         return;
1204 
1205     area.DrawTile( dst, GetTileSurface(), mp );
1206 }
1207 
RedrawEmptyTile(fheroes2::Image & dst,const fheroes2::Point & mp,const fheroes2::Rect & visibleTileROI,const Interface::GameArea & area)1208 void Maps::Tiles::RedrawEmptyTile( fheroes2::Image & dst, const fheroes2::Point & mp, const fheroes2::Rect & visibleTileROI, const Interface::GameArea & area )
1209 {
1210     if ( !( visibleTileROI & mp ) ) {
1211         return;
1212     }
1213 
1214     if ( mp.y == -1 && mp.x >= 0 && mp.x < world.w() ) { // top first row
1215         area.DrawTile( dst, fheroes2::AGG::GetTIL( TIL::STON, 20 + ( mp.x % 4 ), 0 ), mp );
1216     }
1217     else if ( mp.x == world.w() && mp.y >= 0 && mp.y < world.h() ) { // right first row
1218         area.DrawTile( dst, fheroes2::AGG::GetTIL( TIL::STON, 24 + ( mp.y % 4 ), 0 ), mp );
1219     }
1220     else if ( mp.y == world.h() && mp.x >= 0 && mp.x < world.w() ) { // bottom first row
1221         area.DrawTile( dst, fheroes2::AGG::GetTIL( TIL::STON, 28 + ( mp.x % 4 ), 0 ), mp );
1222     }
1223     else if ( mp.x == -1 && mp.y >= 0 && mp.y < world.h() ) { // left first row
1224         area.DrawTile( dst, fheroes2::AGG::GetTIL( TIL::STON, 32 + ( mp.y % 4 ), 0 ), mp );
1225     }
1226     else {
1227         area.DrawTile( dst, fheroes2::AGG::GetTIL( TIL::STON, ( std::abs( mp.y ) % 4 ) * 4 + std::abs( mp.x ) % 4, 0 ), mp );
1228     }
1229 }
1230 
RedrawAddon(fheroes2::Image & dst,const Addons & addon,const fheroes2::Rect & visibleTileROI,bool isPuzzleDraw,const Interface::GameArea & area) const1231 void Maps::Tiles::RedrawAddon( fheroes2::Image & dst, const Addons & addon, const fheroes2::Rect & visibleTileROI, bool isPuzzleDraw,
1232                                const Interface::GameArea & area ) const
1233 {
1234     if ( addon.empty() ) {
1235         return;
1236     }
1237 
1238     const fheroes2::Point & mp = Maps::GetPoint( _index );
1239 
1240     if ( !( visibleTileROI & mp ) )
1241         return;
1242 
1243     for ( Addons::const_iterator it = addon.begin(); it != addon.end(); ++it ) {
1244         const u8 index = ( *it ).index;
1245         const int icn = MP2::GetICNObject( ( *it ).object );
1246 
1247         if ( ICN::UNKNOWN != icn && ICN::MINIHERO != icn && ICN::MONS32 != icn && ( !isPuzzleDraw || !MP2::isHiddenForPuzzle( it->object, index ) ) ) {
1248             const fheroes2::Sprite & sprite = fheroes2::AGG::GetICN( icn, index );
1249             area.BlitOnTile( dst, sprite, sprite.x(), sprite.y(), mp );
1250 
1251             // possible animation
1252             const uint32_t animationIndex = ICN::AnimationFrame( icn, index, Game::MapsAnimationFrame(), quantity2 != 0 );
1253             if ( animationIndex ) {
1254                 area.BlitOnTile( dst, fheroes2::AGG::GetICN( icn, animationIndex ), mp );
1255             }
1256         }
1257     }
1258 }
1259 
RedrawBottom(fheroes2::Image & dst,const fheroes2::Rect & visibleTileROI,bool isPuzzleDraw,const Interface::GameArea & area) const1260 void Maps::Tiles::RedrawBottom( fheroes2::Image & dst, const fheroes2::Rect & visibleTileROI, bool isPuzzleDraw, const Interface::GameArea & area ) const
1261 {
1262     RedrawAddon( dst, addons_level1, visibleTileROI, isPuzzleDraw, area );
1263 }
1264 
RedrawPassable(fheroes2::Image & dst,const fheroes2::Rect & visibleTileROI,const Interface::GameArea & area) const1265 void Maps::Tiles::RedrawPassable( fheroes2::Image & dst, const fheroes2::Rect & visibleTileROI, const Interface::GameArea & area ) const
1266 {
1267 #ifdef WITH_DEBUG
1268     const fheroes2::Point & mp = Maps::GetPoint( _index );
1269 
1270     if ( ( visibleTileROI & mp ) && ( 0 == tilePassable || DIRECTION_ALL != tilePassable ) ) {
1271         area.BlitOnTile( dst, PassableViewSurface( tilePassable ), 0, 0, mp );
1272     }
1273 #else
1274     (void)dst;
1275     (void)visibleTileROI;
1276     (void)area;
1277 #endif
1278 }
1279 
RedrawObjects(fheroes2::Image & dst,bool isPuzzleDraw,const Interface::GameArea & area) const1280 void Maps::Tiles::RedrawObjects( fheroes2::Image & dst, bool isPuzzleDraw, const Interface::GameArea & area ) const
1281 {
1282     const MP2::MapObjectType objectType = GetObject();
1283 
1284     // monsters and boats will be drawn later, on top of everything else
1285     // hero object is accepted here since it replaces what was there originally
1286     if ( objectType != MP2::OBJ_BOAT && objectType != MP2::OBJ_MONSTER && ( !isPuzzleDraw || !MP2::isHiddenForPuzzle( objectTileset, objectIndex ) ) ) {
1287         const int icn = MP2::GetICNObject( objectTileset );
1288 
1289         if ( ICN::UNKNOWN != icn ) {
1290             const fheroes2::Point & mp = Maps::GetPoint( _index );
1291 
1292             const fheroes2::Sprite & sprite = fheroes2::AGG::GetICN( icn, objectIndex );
1293             area.BlitOnTile( dst, sprite, sprite.x(), sprite.y(), mp );
1294 
1295             // possible animation
1296             const uint32_t animationIndex = ICN::AnimationFrame( icn, objectIndex, Game::MapsAnimationFrame(), quantity2 != 0 );
1297             if ( animationIndex ) {
1298                 const fheroes2::Sprite & animationSprite = fheroes2::AGG::GetICN( icn, animationIndex );
1299 
1300                 area.BlitOnTile( dst, animationSprite, mp );
1301             }
1302         }
1303     }
1304 }
1305 
RedrawMonster(fheroes2::Image & dst,const fheroes2::Rect & visibleTileROI,const Interface::GameArea & area) const1306 void Maps::Tiles::RedrawMonster( fheroes2::Image & dst, const fheroes2::Rect & visibleTileROI, const Interface::GameArea & area ) const
1307 {
1308     const fheroes2::Point & mp = Maps::GetPoint( _index );
1309 
1310     if ( !( visibleTileROI & mp ) )
1311         return;
1312 
1313     const Monster & monster = QuantityMonster();
1314     const std::pair<uint32_t, uint32_t> spriteIndicies = GetMonsterSpriteIndices( *this, monster.GetSpriteIndex() );
1315 
1316     const fheroes2::Sprite & sprite = fheroes2::AGG::GetICN( ICN::MINIMON, spriteIndicies.first );
1317     area.BlitOnTile( dst, sprite, sprite.x() + 16, sprite.y() + 30, mp );
1318 
1319     if ( spriteIndicies.second ) {
1320         const fheroes2::Sprite & animatedSprite = fheroes2::AGG::GetICN( ICN::MINIMON, spriteIndicies.second );
1321         area.BlitOnTile( dst, animatedSprite, animatedSprite.x() + 16, animatedSprite.y() + 30, mp );
1322     }
1323 }
1324 
RedrawBoatShadow(fheroes2::Image & dst,const fheroes2::Rect & visibleTileROI,const Interface::GameArea & area) const1325 void Maps::Tiles::RedrawBoatShadow( fheroes2::Image & dst, const fheroes2::Rect & visibleTileROI, const Interface::GameArea & area ) const
1326 {
1327     const fheroes2::Point & mp = Maps::GetPoint( _index );
1328 
1329     if ( !( visibleTileROI & mp ) )
1330         return;
1331 
1332     const uint32_t spriteIndex = ( objectIndex == 255 ) ? 18 : objectIndex;
1333 
1334     const Game::ObjectFadeAnimation::FadeTask & fadeTask = Game::ObjectFadeAnimation::GetFadeTask();
1335     const uint8_t alpha
1336         = ( MP2::OBJ_BOAT == fadeTask.object && ( ( fadeTask.fadeOut && fadeTask.fromIndex == _index ) || ( fadeTask.fadeIn && fadeTask.toIndex == _index ) ) )
1337               ? fadeTask.alpha
1338               : 255;
1339 
1340     const fheroes2::Sprite & sprite = fheroes2::AGG::GetICN( ICN::BOATSHAD, spriteIndex % 128 );
1341     area.BlitOnTile( dst, sprite, sprite.x(), TILEWIDTH + sprite.y() - 11, mp, ( spriteIndex > 128 ), alpha );
1342 }
1343 
RedrawBoat(fheroes2::Image & dst,const fheroes2::Rect & visibleTileROI,const Interface::GameArea & area) const1344 void Maps::Tiles::RedrawBoat( fheroes2::Image & dst, const fheroes2::Rect & visibleTileROI, const Interface::GameArea & area ) const
1345 {
1346     const fheroes2::Point & mp = Maps::GetPoint( _index );
1347 
1348     if ( !( visibleTileROI & mp ) )
1349         return;
1350 
1351     const uint32_t spriteIndex = ( objectIndex == 255 ) ? 18 : objectIndex;
1352 
1353     const Game::ObjectFadeAnimation::FadeTask & fadeTask = Game::ObjectFadeAnimation::GetFadeTask();
1354     const uint8_t alpha
1355         = ( MP2::OBJ_BOAT == fadeTask.object && ( ( fadeTask.fadeOut && fadeTask.fromIndex == _index ) || ( fadeTask.fadeIn && fadeTask.toIndex == _index ) ) )
1356               ? fadeTask.alpha
1357               : 255;
1358 
1359     const fheroes2::Sprite & sprite = fheroes2::AGG::GetICN( ICN::BOAT32, spriteIndex % 128 );
1360     area.BlitOnTile( dst, sprite, sprite.x(), TILEWIDTH + sprite.y() - 11, mp, ( spriteIndex > 128 ), alpha );
1361 }
1362 
SkipRedrawTileBottom4Hero(const uint8_t tileset,const uint8_t icnIndex,const int passable)1363 bool Interface::SkipRedrawTileBottom4Hero( const uint8_t tileset, const uint8_t icnIndex, const int passable )
1364 {
1365     const int icn = MP2::GetICNObject( tileset );
1366     switch ( icn ) {
1367     case ICN::UNKNOWN:
1368     case ICN::MINIHERO:
1369     case ICN::MONS32:
1370         return true;
1371 
1372     // whirlpool
1373     case ICN::OBJNWATR:
1374         return ( icnIndex >= 202 && icnIndex <= 225 ) || icnIndex == 69;
1375 
1376     // river delta
1377     case ICN::OBJNMUL2:
1378         return icnIndex < 14;
1379 
1380     case ICN::OBJNTWSH:
1381     case ICN::OBJNTWBA:
1382     case ICN::ROAD:
1383     case ICN::STREAM:
1384         return true;
1385 
1386     case ICN::OBJNCRCK:
1387         return ( icnIndex == 58 || icnIndex == 59 || icnIndex == 64 || icnIndex == 65 || icnIndex == 188 || icnIndex == 189 || ( passable & DIRECTION_TOP_ROW ) );
1388 
1389     case ICN::OBJNDIRT:
1390     case ICN::OBJNDSRT:
1391     case ICN::OBJNGRA2:
1392     case ICN::OBJNGRAS:
1393     case ICN::OBJNLAVA:
1394     case ICN::OBJNSNOW:
1395     case ICN::OBJNSWMP:
1396         return ( passable & DIRECTION_TOP_ROW ) != 0;
1397 
1398     default:
1399         break;
1400     }
1401 
1402     return Maps::Tiles::isShadowSprite( icn, icnIndex );
1403 }
1404 
RedrawBottom4Hero(fheroes2::Image & dst,const fheroes2::Rect & visibleTileROI,const Interface::GameArea & area) const1405 void Maps::Tiles::RedrawBottom4Hero( fheroes2::Image & dst, const fheroes2::Rect & visibleTileROI, const Interface::GameArea & area ) const
1406 {
1407     const fheroes2::Point & mp = Maps::GetPoint( _index );
1408 
1409     if ( !( visibleTileROI & mp ) )
1410         return;
1411 
1412     for ( Addons::const_iterator it = addons_level1.begin(); it != addons_level1.end(); ++it ) {
1413         const uint8_t object = it->object;
1414         const uint8_t index = it->index;
1415         if ( !Interface::SkipRedrawTileBottom4Hero( object, index, tilePassable ) ) {
1416             const int icn = MP2::GetICNObject( object );
1417 
1418             area.BlitOnTile( dst, fheroes2::AGG::GetICN( icn, index ), mp );
1419 
1420             // possible anime
1421             if ( it->object & 1 ) {
1422                 area.BlitOnTile( dst, fheroes2::AGG::GetICN( icn, ICN::AnimationFrame( icn, index, Game::MapsAnimationFrame(), quantity2 != 0 ) ), mp );
1423             }
1424         }
1425     }
1426 }
1427 
RedrawTop(fheroes2::Image & dst,const fheroes2::Rect & visibleTileROI,const bool isPuzzleDraw,const Interface::GameArea & area) const1428 void Maps::Tiles::RedrawTop( fheroes2::Image & dst, const fheroes2::Rect & visibleTileROI, const bool isPuzzleDraw, const Interface::GameArea & area ) const
1429 {
1430     const fheroes2::Point & mp = Maps::GetPoint( _index );
1431 
1432     if ( !( visibleTileROI & mp ) )
1433         return;
1434 
1435     const MP2::MapObjectType objectType = GetObject( false );
1436     // animate objects
1437     if ( objectType == MP2::OBJ_ABANDONEDMINE ) {
1438         area.BlitOnTile( dst, fheroes2::AGG::GetICN( ICN::OBJNHAUN, Game::MapsAnimationFrame() % 15 ), mp );
1439     }
1440     else if ( objectType == MP2::OBJ_MINES ) {
1441         const uint8_t spellID = quantity3;
1442         if ( spellID == Spell::HAUNT ) {
1443             area.BlitOnTile( dst, fheroes2::AGG::GetICN( ICN::OBJNHAUN, Game::MapsAnimationFrame() % 15 ), mp );
1444         }
1445         else if ( spellID >= Spell::SETEGUARDIAN && spellID <= Spell::SETWGUARDIAN ) {
1446             area.BlitOnTile( dst, fheroes2::AGG::GetICN( ICN::OBJNXTRA, spellID - Spell::SETEGUARDIAN ), TILEWIDTH, 0, mp );
1447         }
1448     }
1449 
1450     RedrawAddon( dst, addons_level2, visibleTileROI, isPuzzleDraw, area );
1451 }
1452 
RedrawTopFromBottom(fheroes2::Image & dst,const Interface::GameArea & area) const1453 void Maps::Tiles::RedrawTopFromBottom( fheroes2::Image & dst, const Interface::GameArea & area ) const
1454 {
1455     if ( !Maps::isValidDirection( _index, Direction::BOTTOM ) ) {
1456         return;
1457     }
1458     const Maps::Tiles & tile = world.GetTiles( Maps::GetDirectionIndex( _index, Direction::BOTTOM ) );
1459     const fheroes2::Point & mp = Maps::GetPoint( tile._index );
1460     for ( const Maps::TilesAddon & addon : tile.addons_level2 ) {
1461         const int icn = MP2::GetICNObject( addon.object );
1462         if ( icn == ICN::FLAG32 ) {
1463             area.BlitOnTile( dst, fheroes2::AGG::GetICN( icn, addon.index ), mp );
1464         }
1465     }
1466 }
1467 
RedrawTop4Hero(fheroes2::Image & dst,const fheroes2::Rect & visibleTileROI,bool skip_ground,const Interface::GameArea & area) const1468 void Maps::Tiles::RedrawTop4Hero( fheroes2::Image & dst, const fheroes2::Rect & visibleTileROI, bool skip_ground, const Interface::GameArea & area ) const
1469 {
1470     const fheroes2::Point & mp = Maps::GetPoint( _index );
1471 
1472     if ( ( visibleTileROI & mp ) && !addons_level2.empty() ) {
1473         for ( Addons::const_iterator it = addons_level2.begin(); it != addons_level2.end(); ++it ) {
1474             if ( skip_ground && MP2::isActionObject( static_cast<MP2::MapObjectType>( ( *it ).object ) ) )
1475                 continue;
1476 
1477             const uint8_t object = ( *it ).object;
1478             const uint8_t index = ( *it ).index;
1479             const int icn = MP2::GetICNObject( object );
1480 
1481             if ( ICN::HighlyObjectSprite( icn, index ) ) {
1482                 area.BlitOnTile( dst, fheroes2::AGG::GetICN( icn, index ), mp );
1483 
1484                 // possible anime
1485                 if ( object & 1 ) {
1486                     area.BlitOnTile( dst, fheroes2::AGG::GetICN( icn, ICN::AnimationFrame( icn, index, Game::MapsAnimationFrame() ) ), mp );
1487                 }
1488             }
1489         }
1490     }
1491 }
1492 
FindAddonLevel1(u32 uniq1)1493 Maps::TilesAddon * Maps::Tiles::FindAddonLevel1( u32 uniq1 )
1494 {
1495     Addons::iterator it = std::find_if( addons_level1.begin(), addons_level1.end(), [uniq1]( const TilesAddon & v ) { return v.isUniq( uniq1 ); } );
1496 
1497     return it != addons_level1.end() ? &( *it ) : nullptr;
1498 }
1499 
FindAddonLevel2(u32 uniq2)1500 Maps::TilesAddon * Maps::Tiles::FindAddonLevel2( u32 uniq2 )
1501 {
1502     Addons::iterator it = std::find_if( addons_level2.begin(), addons_level2.end(), [uniq2]( const TilesAddon & v ) { return v.isUniq( uniq2 ); } );
1503 
1504     return it != addons_level2.end() ? &( *it ) : nullptr;
1505 }
1506 
String(void) const1507 std::string Maps::Tiles::String( void ) const
1508 {
1509     std::ostringstream os;
1510 
1511     os << "----------------:>>>>>>>>" << std::endl
1512        << "Tile index      : " << _index << ", "
1513        << "point: (" << GetCenter().x << ", " << GetCenter().y << ")" << std::endl
1514        << "uniq            : " << uniq << std::endl
1515        << "mp2 object      : " << GetObject() << ", (" << MP2::StringObject( GetObject() ) << ")" << std::endl
1516        << "tileset         : " << static_cast<int>( objectTileset ) << ", (" << ICN::GetString( MP2::GetICNObject( objectTileset ) ) << ")" << std::endl
1517        << "object index    : " << static_cast<int>( objectIndex ) << ", (animated: " << hasSpriteAnimation() << ")" << std::endl
1518        << "level           : " << static_cast<int>( _level ) << std::endl
1519        << "region          : " << _region << std::endl
1520        << "ground          : " << Ground::String( GetGround() ) << ", (isRoad: " << tileIsRoad << ")" << std::endl
1521        << "shadow          : " << isShadowSprite( objectTileset, objectIndex ) << std::endl
1522        << "passable        : " << ( tilePassable ? Direction::String( tilePassable ) : "false" );
1523 
1524     os << std::endl
1525        << "quantity 1      : " << static_cast<int>( quantity1 ) << std::endl
1526        << "quantity 2      : " << static_cast<int>( quantity2 ) << std::endl
1527        << "quantity 3      : " << static_cast<int>( quantity3 ) << std::endl;
1528 
1529     for ( Addons::const_iterator it = addons_level1.begin(); it != addons_level1.end(); ++it )
1530         os << ( *it ).String( 1 );
1531 
1532     for ( Addons::const_iterator it = addons_level2.begin(); it != addons_level2.end(); ++it )
1533         os << ( *it ).String( 2 );
1534 
1535     os << "----------------I--------" << std::endl;
1536 
1537     // extra obj info
1538     switch ( GetObject() ) {
1539         // dwelling
1540     case MP2::OBJ_RUINS:
1541     case MP2::OBJ_TREECITY:
1542     case MP2::OBJ_WAGONCAMP:
1543     case MP2::OBJ_DESERTTENT:
1544     case MP2::OBJ_TROLLBRIDGE:
1545     case MP2::OBJ_DRAGONCITY:
1546     case MP2::OBJ_CITYDEAD:
1547     case MP2::OBJ_WATCHTOWER:
1548     case MP2::OBJ_EXCAVATION:
1549     case MP2::OBJ_CAVE:
1550     case MP2::OBJ_TREEHOUSE:
1551     case MP2::OBJ_ARCHERHOUSE:
1552     case MP2::OBJ_GOBLINHUT:
1553     case MP2::OBJ_DWARFCOTT:
1554     case MP2::OBJ_HALFLINGHOLE:
1555     case MP2::OBJ_PEASANTHUT:
1556     case MP2::OBJ_THATCHEDHUT:
1557     case MP2::OBJ_MONSTER:
1558         os << "count           : " << MonsterCount() << std::endl;
1559         break;
1560 
1561     case MP2::OBJ_HEROES: {
1562         const Heroes * hero = GetHeroes();
1563         if ( hero )
1564             os << hero->String();
1565         break;
1566     }
1567 
1568     case MP2::OBJN_CASTLE:
1569     case MP2::OBJ_CASTLE: {
1570         const Castle * castle = world.getCastle( GetCenter() );
1571         if ( castle )
1572             os << castle->String();
1573         break;
1574     }
1575 
1576     default: {
1577         const MapsIndexes & v = Maps::GetTilesUnderProtection( _index );
1578         if ( !v.empty() ) {
1579             os << "protection      : ";
1580             for ( MapsIndexes::const_iterator it = v.begin(); it != v.end(); ++it )
1581                 os << *it << ", ";
1582             os << std::endl;
1583         }
1584         break;
1585     }
1586     }
1587 
1588     if ( MP2::isCaptureObject( GetObject( false ) ) ) {
1589         const CapturedObject & co = world.GetCapturedObject( _index );
1590 
1591         os << "capture color   : " << Color::String( co.objcol.second ) << std::endl;
1592         if ( co.guardians.isValid() ) {
1593             os << "capture guard   : " << co.guardians.GetName() << std::endl << "capture caunt   : " << co.guardians.GetCount() << std::endl;
1594         }
1595     }
1596 
1597     os << "----------------:<<<<<<<<" << std::endl;
1598     return os.str();
1599 }
1600 
FixObject(void)1601 void Maps::Tiles::FixObject( void )
1602 {
1603     if ( MP2::OBJ_ZERO == mp2_object ) {
1604         if ( std::any_of( addons_level1.begin(), addons_level1.end(), TilesAddon::isArtifact ) )
1605             SetObject( MP2::OBJ_ARTIFACT );
1606         else if ( std::any_of( addons_level1.begin(), addons_level1.end(), TilesAddon::isResource ) )
1607             SetObject( MP2::OBJ_RESOURCE );
1608     }
1609 }
1610 
GoodForUltimateArtifact() const1611 bool Maps::Tiles::GoodForUltimateArtifact() const
1612 {
1613     if ( isWater() || !isPassable( Direction::CENTER, false, true, 0 ) ) {
1614         return false;
1615     }
1616 
1617     if ( objectTileset == 0 || isShadowSprite( objectTileset, objectIndex ) ) {
1618         return addons_level1.size() == static_cast<size_t>( std::count_if( addons_level1.begin(), addons_level1.end(), TilesAddon::isShadow ) );
1619     }
1620 
1621     return false;
1622 }
1623 
validateWaterRules(bool fromWater) const1624 bool Maps::Tiles::validateWaterRules( bool fromWater ) const
1625 {
1626     const bool tileIsWater = isWater();
1627     if ( fromWater )
1628         return mp2_object == MP2::OBJ_COAST || ( tileIsWater && mp2_object != MP2::OBJ_BOAT );
1629 
1630     // if we're not in water but tile is; allow movement in three cases
1631     if ( tileIsWater )
1632         return mp2_object == MP2::OBJ_SHIPWRECK || mp2_object == MP2::OBJ_HEROES || mp2_object == MP2::OBJ_BOAT;
1633 
1634     return true;
1635 }
1636 
isPassable(int direct,bool fromWater,bool skipfog,const int heroColor) const1637 bool Maps::Tiles::isPassable( int direct, bool fromWater, bool skipfog, const int heroColor ) const
1638 {
1639     if ( !skipfog && isFog( heroColor ) )
1640         return false;
1641 
1642     if ( !validateWaterRules( fromWater ) )
1643         return false;
1644 
1645     return ( direct & tilePassable ) != 0;
1646 }
1647 
SetObjectPassable(bool pass)1648 void Maps::Tiles::SetObjectPassable( bool pass )
1649 {
1650     switch ( GetObject( false ) ) {
1651     case MP2::OBJ_TROLLBRIDGE:
1652         if ( pass )
1653             tilePassable |= Direction::TOP_LEFT;
1654         else
1655             tilePassable &= ~Direction::TOP_LEFT;
1656         break;
1657 
1658     default:
1659         break;
1660     }
1661 }
1662 
1663 /* check road */
isRoad() const1664 bool Maps::Tiles::isRoad() const
1665 {
1666     return tileIsRoad || mp2_object == MP2::OBJ_CASTLE;
1667 }
1668 
isStream(void) const1669 bool Maps::Tiles::isStream( void ) const
1670 {
1671     for ( auto it = addons_level1.begin(); it != addons_level1.end(); ++it ) {
1672         const int icn = MP2::GetICNObject( it->object );
1673         if ( icn == ICN::STREAM || ( icn == ICN::OBJNMUL2 && it->index < 14 ) )
1674             return true;
1675     }
1676     const int tileICN = MP2::GetICNObject( objectTileset );
1677     return tileICN == ICN::STREAM || ( tileICN == ICN::OBJNMUL2 && objectIndex < 14 );
1678 }
1679 
isShadow() const1680 bool Maps::Tiles::isShadow() const
1681 {
1682     return isShadowSprite( objectTileset, objectIndex )
1683            && addons_level1.size() == static_cast<size_t>( std::count_if( addons_level1.begin(), addons_level1.end(), TilesAddon::isShadow ) );
1684 }
1685 
hasSpriteAnimation() const1686 bool Maps::Tiles::hasSpriteAnimation() const
1687 {
1688     return objectTileset & 1;
1689 }
1690 
isObject(const MP2::MapObjectType objectType) const1691 bool Maps::Tiles::isObject( const MP2::MapObjectType objectType ) const
1692 {
1693     return objectType == mp2_object;
1694 }
1695 
GetObjectTileset() const1696 uint8_t Maps::Tiles::GetObjectTileset() const
1697 {
1698     return objectTileset;
1699 }
1700 
GetObjectSpriteIndex() const1701 uint8_t Maps::Tiles::GetObjectSpriteIndex() const
1702 {
1703     return objectIndex;
1704 }
1705 
FindFlags(void)1706 Maps::TilesAddon * Maps::Tiles::FindFlags( void )
1707 {
1708     Addons::iterator it = std::find_if( addons_level1.begin(), addons_level1.end(), TilesAddon::isFlag32 );
1709 
1710     if ( it == addons_level1.end() ) {
1711         it = std::find_if( addons_level2.begin(), addons_level2.end(), TilesAddon::isFlag32 );
1712         return addons_level2.end() != it ? &( *it ) : nullptr;
1713     }
1714 
1715     return addons_level1.end() != it ? &( *it ) : nullptr;
1716 }
1717 
removeFlags()1718 void Maps::Tiles::removeFlags()
1719 {
1720     addons_level1.remove_if( TilesAddon::isFlag32 );
1721     addons_level2.remove_if( TilesAddon::isFlag32 );
1722 }
1723 
CaptureFlags32(const MP2::MapObjectType objectType,int col)1724 void Maps::Tiles::CaptureFlags32( const MP2::MapObjectType objectType, int col )
1725 {
1726     u32 index = 0;
1727 
1728     switch ( col ) {
1729     case Color::BLUE:
1730         index = 0;
1731         break;
1732     case Color::GREEN:
1733         index = 1;
1734         break;
1735     case Color::RED:
1736         index = 2;
1737         break;
1738     case Color::YELLOW:
1739         index = 3;
1740         break;
1741     case Color::ORANGE:
1742         index = 4;
1743         break;
1744     case Color::PURPLE:
1745         index = 5;
1746         break;
1747     default:
1748         index = 6;
1749         break;
1750     }
1751 
1752     switch ( objectType ) {
1753     case MP2::OBJ_WINDMILL:
1754         index += 42;
1755         CorrectFlags32( col, index, false );
1756         break;
1757     case MP2::OBJ_WATERWHEEL:
1758         index += 14;
1759         CorrectFlags32( col, index, false );
1760         break;
1761     case MP2::OBJ_MAGICGARDEN:
1762         index += 42;
1763         CorrectFlags32( col, index, false );
1764         break;
1765 
1766     case MP2::OBJ_MINES:
1767         index += 14;
1768         CorrectFlags32( col, index, true );
1769         break;
1770     case MP2::OBJ_LIGHTHOUSE:
1771         index += 42;
1772         CorrectFlags32( col, index, false );
1773         break;
1774 
1775     case MP2::OBJ_ALCHEMYLAB: {
1776         index += 21;
1777         if ( Maps::isValidDirection( _index, Direction::TOP ) ) {
1778             Maps::Tiles & tile = world.GetTiles( Maps::GetDirectionIndex( _index, Direction::TOP ) );
1779             tile.CorrectFlags32( col, index, true );
1780         }
1781         break;
1782     }
1783 
1784     case MP2::OBJ_SAWMILL: {
1785         index += 28;
1786         if ( Maps::isValidDirection( _index, Direction::TOP_RIGHT ) ) {
1787             Maps::Tiles & tile = world.GetTiles( Maps::GetDirectionIndex( _index, Direction::TOP_RIGHT ) );
1788             tile.CorrectFlags32( col, index, true );
1789         }
1790         break;
1791     }
1792 
1793     case MP2::OBJ_CASTLE: {
1794         index *= 2;
1795         if ( Maps::isValidDirection( _index, Direction::LEFT ) ) {
1796             Maps::Tiles & tile = world.GetTiles( Maps::GetDirectionIndex( _index, Direction::LEFT ) );
1797             tile.CorrectFlags32( col, index, true );
1798         }
1799 
1800         index += 1;
1801         if ( Maps::isValidDirection( _index, Direction::RIGHT ) ) {
1802             Maps::Tiles & tile = world.GetTiles( Maps::GetDirectionIndex( _index, Direction::RIGHT ) );
1803             tile.CorrectFlags32( col, index, true );
1804         }
1805         break;
1806     }
1807 
1808     default:
1809         break;
1810     }
1811 }
1812 
CorrectFlags32(const int col,const u32 index,const bool up)1813 void Maps::Tiles::CorrectFlags32( const int col, const u32 index, const bool up )
1814 {
1815     if ( col == Color::NONE ) {
1816         removeFlags();
1817         return;
1818     }
1819 
1820     TilesAddon * taddon = FindFlags();
1821 
1822     // replace flag
1823     if ( taddon )
1824         taddon->index = index;
1825     else if ( up )
1826         // or new flag
1827         addons_level2.emplace_back( TilesAddon::UPPER, World::GetUniq(), 0x38, index );
1828     else
1829         // or new flag
1830         addons_level1.emplace_back( TilesAddon::UPPER, World::GetUniq(), 0x38, index );
1831 }
1832 
fixTileObjectType(Tiles & tile)1833 void Maps::Tiles::fixTileObjectType( Tiles & tile )
1834 {
1835     const MP2::MapObjectType originalObjectType = tile.GetObject( false );
1836     const int originalICN = MP2::GetICNObject( tile.objectTileset );
1837 
1838     // Left tile of a skeleton on Desert should be mark as non-action tile.
1839     if ( originalObjectType == MP2::OBJ_SKELETON && originalICN == ICN::OBJNDSRT && tile.objectIndex == 83 ) {
1840         tile.SetObject( MP2::OBJN_SKELETON );
1841 
1842         // There is no need to check the rest of things as we fixed this object.
1843         return;
1844     }
1845 
1846     // Original Editor marks Reefs as Stones. We're fixing this issue by changing the type of the object without changing the content of a tile.
1847     // This is also required in order to properly calculate Reefs' passbility.
1848     if ( originalObjectType == MP2::OBJ_STONES && isValidReefsSprite( originalICN, tile.objectIndex ) ) {
1849         tile.SetObject( MP2::OBJ_REEFS );
1850 
1851         // There is no need to check the rest of things as we fixed this object.
1852         return;
1853     }
1854 
1855     // Fix The Price of Loyalty objects even if the map is The Succession Wars type.
1856     switch ( originalObjectType ) {
1857     case MP2::OBJ_UNKNW_79:
1858     case MP2::OBJ_UNKNW_7A:
1859     case MP2::OBJ_UNKNW_F9:
1860     case MP2::OBJ_UNKNW_FA: {
1861         MP2::MapObjectType objectType = Maps::Tiles::GetLoyaltyObject( tile.objectTileset, tile.objectIndex );
1862         if ( objectType != MP2::OBJ_ZERO ) {
1863             tile.SetObject( objectType );
1864             break;
1865         }
1866 
1867         // Add-ons of level 1 shouldn't even exist if no top object. However, let's play safe and verify it as well.
1868         for ( const TilesAddon & addon : tile.addons_level1 ) {
1869             objectType = Maps::Tiles::GetLoyaltyObject( addon.object, addon.index );
1870             if ( objectType != MP2::OBJ_ZERO )
1871                 break;
1872         }
1873 
1874         if ( objectType != MP2::OBJ_ZERO ) {
1875             tile.SetObject( objectType );
1876             break;
1877         }
1878 
1879         for ( const TilesAddon & addon : tile.addons_level2 ) {
1880             objectType = Maps::Tiles::GetLoyaltyObject( addon.object, addon.index );
1881             if ( objectType != MP2::OBJ_ZERO )
1882                 break;
1883         }
1884 
1885         if ( objectType != MP2::OBJ_ZERO ) {
1886             tile.SetObject( objectType );
1887             break;
1888         }
1889 
1890         DEBUG_LOG( DBG_GAME, DBG_WARN,
1891                    "Invalid object type index " << tile._index << ": type " << MP2::StringObject( originalObjectType ) << ", icn ID "
1892                                                 << static_cast<int>( tile.objectIndex ) );
1893         break;
1894     }
1895 
1896     default:
1897         break;
1898     }
1899 }
1900 
1901 /* true: if protection or has guardians */
CaptureObjectIsProtection(void) const1902 bool Maps::Tiles::CaptureObjectIsProtection( void ) const
1903 {
1904     const MP2::MapObjectType objectType = GetObject( false );
1905 
1906     if ( MP2::isCaptureObject( objectType ) ) {
1907         if ( MP2::OBJ_CASTLE == objectType ) {
1908             Castle * castle = world.getCastleEntrance( GetCenter() );
1909             if ( castle )
1910                 return castle->GetArmy().isValid();
1911         }
1912         else
1913             return QuantityTroop().isValid();
1914     }
1915 
1916     return false;
1917 }
1918 
Remove(u32 uniqID)1919 void Maps::Tiles::Remove( u32 uniqID )
1920 {
1921     if ( !addons_level1.empty() )
1922         addons_level1.Remove( uniqID );
1923     if ( !addons_level2.empty() )
1924         addons_level2.Remove( uniqID );
1925 
1926     if ( uniq == uniqID ) {
1927         resetObjectSprite();
1928         uniq = 0;
1929     }
1930 }
1931 
ReplaceObjectSprite(uint32_t uniqID,uint8_t rawTileset,uint8_t newTileset,uint8_t indexToReplace,uint8_t newIndex)1932 void Maps::Tiles::ReplaceObjectSprite( uint32_t uniqID, uint8_t rawTileset, uint8_t newTileset, uint8_t indexToReplace, uint8_t newIndex )
1933 {
1934     for ( Addons::iterator it = addons_level1.begin(); it != addons_level1.end(); ++it ) {
1935         if ( it->uniq == uniqID && ( it->object >> 2 ) == rawTileset && it->index == indexToReplace ) {
1936             it->object = newTileset;
1937             it->index = newIndex;
1938         }
1939     }
1940     for ( Addons::iterator it2 = addons_level2.begin(); it2 != addons_level2.end(); ++it2 ) {
1941         if ( it2->uniq == uniqID && ( it2->object >> 2 ) == rawTileset && it2->index == indexToReplace ) {
1942             it2->object = newTileset;
1943             it2->index = newIndex;
1944         }
1945     }
1946 
1947     if ( uniq == uniqID && ( objectTileset >> 2 ) == rawTileset && objectIndex == indexToReplace ) {
1948         objectTileset = newTileset;
1949         objectIndex = newIndex;
1950     }
1951 }
1952 
UpdateObjectSprite(uint32_t uniqID,uint8_t rawTileset,uint8_t newTileset,int indexChange)1953 void Maps::Tiles::UpdateObjectSprite( uint32_t uniqID, uint8_t rawTileset, uint8_t newTileset, int indexChange )
1954 {
1955     for ( Addons::iterator it = addons_level1.begin(); it != addons_level1.end(); ++it ) {
1956         if ( it->uniq == uniqID && ( it->object >> 2 ) == rawTileset ) {
1957             it->object = newTileset;
1958             it->index = it->index + indexChange;
1959         }
1960     }
1961     for ( Addons::iterator it2 = addons_level2.begin(); it2 != addons_level2.end(); ++it2 ) {
1962         if ( it2->uniq == uniqID && ( it2->object >> 2 ) == rawTileset ) {
1963             it2->object = newTileset;
1964             it2->index = it2->index + indexChange;
1965         }
1966     }
1967 
1968     if ( uniq == uniqID && ( objectTileset >> 2 ) == rawTileset ) {
1969         objectTileset = newTileset;
1970         objectIndex += indexChange;
1971     }
1972 }
1973 
RemoveObjectSprite(void)1974 void Maps::Tiles::RemoveObjectSprite( void )
1975 {
1976     switch ( GetObject() ) {
1977     case MP2::OBJ_MONSTER:
1978         Remove( uniq );
1979         break;
1980     case MP2::OBJ_JAIL:
1981         RemoveJailSprite();
1982         tilePassable = DIRECTION_ALL;
1983         break;
1984     case MP2::OBJ_ARTIFACT: {
1985         const uint32_t uidArtifact = getObjectIdByICNType( ICN::OBJNARTI );
1986         Remove( uidArtifact );
1987 
1988         if ( Maps::isValidDirection( _index, Direction::LEFT ) )
1989             world.GetTiles( Maps::GetDirectionIndex( _index, Direction::LEFT ) ).Remove( uidArtifact );
1990         break;
1991     }
1992     case MP2::OBJ_TREASURECHEST:
1993     case MP2::OBJ_RESOURCE: {
1994         const uint32_t uidResource = getObjectIdByICNType( ICN::OBJNRSRC );
1995         Remove( uidResource );
1996 
1997         if ( Maps::isValidDirection( _index, Direction::LEFT ) )
1998             world.GetTiles( Maps::GetDirectionIndex( _index, Direction::LEFT ) ).Remove( uidResource );
1999         break;
2000     }
2001     case MP2::OBJ_BARRIER:
2002         tilePassable = DIRECTION_ALL;
2003         // fall-through
2004     default:
2005         // remove shadow sprite from left cell
2006         if ( Maps::isValidDirection( _index, Direction::LEFT ) )
2007             world.GetTiles( Maps::GetDirectionIndex( _index, Direction::LEFT ) ).Remove( uniq );
2008 
2009         Remove( uniq );
2010         break;
2011     }
2012 }
2013 
RemoveJailSprite(void)2014 void Maps::Tiles::RemoveJailSprite( void )
2015 {
2016     // remove left sprite
2017     if ( Maps::isValidDirection( _index, Direction::LEFT ) ) {
2018         const s32 left = Maps::GetDirectionIndex( _index, Direction::LEFT );
2019         world.GetTiles( left ).Remove( uniq );
2020 
2021         // remove left left sprite
2022         if ( Maps::isValidDirection( left, Direction::LEFT ) )
2023             world.GetTiles( Maps::GetDirectionIndex( left, Direction::LEFT ) ).Remove( uniq );
2024     }
2025 
2026     // remove top sprite
2027     if ( Maps::isValidDirection( _index, Direction::TOP ) ) {
2028         const s32 top = Maps::GetDirectionIndex( _index, Direction::TOP );
2029         Maps::Tiles & topTile = world.GetTiles( top );
2030         topTile.Remove( uniq );
2031 
2032         if ( topTile.GetObject() == MP2::OBJ_JAIL ) {
2033             topTile.setAsEmpty();
2034             topTile.FixObject();
2035         }
2036 
2037         // remove top left sprite
2038         if ( Maps::isValidDirection( top, Direction::LEFT ) ) {
2039             Maps::Tiles & leftTile = world.GetTiles( Maps::GetDirectionIndex( top, Direction::LEFT ) );
2040             leftTile.Remove( uniq );
2041 
2042             if ( leftTile.GetObject() == MP2::OBJ_JAIL ) {
2043                 leftTile.setAsEmpty();
2044                 leftTile.FixObject();
2045             }
2046         }
2047     }
2048 
2049     Remove( uniq );
2050 }
2051 
UpdateAbandoneMineSprite(Tiles & tile)2052 void Maps::Tiles::UpdateAbandoneMineSprite( Tiles & tile )
2053 {
2054     if ( tile.uniq ) {
2055         const int type = tile.QuantityResourceCount().first;
2056 
2057         Tiles::UpdateAbandoneMineLeftSprite( tile.objectTileset, tile.objectIndex, type );
2058         for ( Addons::iterator it = tile.addons_level1.begin(); it != tile.addons_level1.end(); ++it )
2059             Tiles::UpdateAbandoneMineLeftSprite( it->object, it->index, type );
2060 
2061         if ( Maps::isValidDirection( tile._index, Direction::RIGHT ) ) {
2062             Tiles & tile2 = world.GetTiles( Maps::GetDirectionIndex( tile._index, Direction::RIGHT ) );
2063             TilesAddon * mines = tile2.FindAddonLevel1( tile.uniq );
2064 
2065             if ( mines )
2066                 Tiles::UpdateAbandoneMineRightSprite( mines->object, mines->index );
2067 
2068             if ( tile2.GetObject() == MP2::OBJN_ABANDONEDMINE ) {
2069                 tile2.SetObject( MP2::OBJN_MINES );
2070                 Tiles::UpdateAbandoneMineRightSprite( tile2.objectTileset, tile2.objectIndex );
2071             }
2072         }
2073     }
2074 
2075     if ( Maps::isValidDirection( tile._index, Direction::LEFT ) ) {
2076         Tiles & tile2 = world.GetTiles( Maps::GetDirectionIndex( tile._index, Direction::LEFT ) );
2077         if ( tile2.GetObject() == MP2::OBJN_ABANDONEDMINE )
2078             tile2.SetObject( MP2::OBJN_MINES );
2079     }
2080 
2081     if ( Maps::isValidDirection( tile._index, Direction::TOP ) ) {
2082         Tiles & tile2 = world.GetTiles( Maps::GetDirectionIndex( tile._index, Direction::TOP ) );
2083         if ( tile2.GetObject() == MP2::OBJN_ABANDONEDMINE )
2084             tile2.SetObject( MP2::OBJN_MINES );
2085 
2086         if ( Maps::isValidDirection( tile2._index, Direction::LEFT ) ) {
2087             Tiles & tile3 = world.GetTiles( Maps::GetDirectionIndex( tile2._index, Direction::LEFT ) );
2088             if ( tile3.GetObject() == MP2::OBJN_ABANDONEDMINE )
2089                 tile3.SetObject( MP2::OBJN_MINES );
2090         }
2091 
2092         if ( Maps::isValidDirection( tile2._index, Direction::RIGHT ) ) {
2093             Tiles & tile3 = world.GetTiles( Maps::GetDirectionIndex( tile2._index, Direction::RIGHT ) );
2094             if ( tile3.GetObject() == MP2::OBJN_ABANDONEDMINE )
2095                 tile3.SetObject( MP2::OBJN_MINES );
2096         }
2097     }
2098 }
2099 
UpdateRNDArtifactSprite(Tiles & tile)2100 void Maps::Tiles::UpdateRNDArtifactSprite( Tiles & tile )
2101 {
2102     Artifact art;
2103 
2104     switch ( tile.GetObject() ) {
2105     case MP2::OBJ_RNDARTIFACT:
2106         art = Artifact::Rand( Artifact::ART_LEVEL123 );
2107         break;
2108     case MP2::OBJ_RNDARTIFACT1:
2109         art = Artifact::Rand( Artifact::ART_LEVEL1 );
2110         break;
2111     case MP2::OBJ_RNDARTIFACT2:
2112         art = Artifact::Rand( Artifact::ART_LEVEL2 );
2113         break;
2114     case MP2::OBJ_RNDARTIFACT3:
2115         art = Artifact::Rand( Artifact::ART_LEVEL3 );
2116         break;
2117     default:
2118         return;
2119     }
2120 
2121     if ( !art.isValid() ) {
2122         DEBUG_LOG( DBG_GAME, DBG_WARN, "unknown artifact" );
2123         return;
2124     }
2125 
2126     tile.SetObject( MP2::OBJ_ARTIFACT );
2127 
2128     uint32_t uidArtifact = tile.getObjectIdByICNType( ICN::OBJNARTI );
2129     if ( uidArtifact == 0 ) {
2130         uidArtifact = tile.uniq;
2131     }
2132 
2133     updateTileById( tile, uidArtifact, art.IndexSprite() );
2134 
2135     // replace artifact shadow
2136     if ( Maps::isValidDirection( tile._index, Direction::LEFT ) ) {
2137         updateTileById( world.GetTiles( Maps::GetDirectionIndex( tile._index, Direction::LEFT ) ), uidArtifact, art.IndexSprite() - 1 );
2138     }
2139 }
2140 
UpdateRNDResourceSprite(Tiles & tile)2141 void Maps::Tiles::UpdateRNDResourceSprite( Tiles & tile )
2142 {
2143     tile.SetObject( MP2::OBJ_RESOURCE );
2144 
2145     const uint32_t resourceSprite = Resource::GetIndexSprite( Resource::Rand( true ) );
2146 
2147     uint32_t uidResource = tile.getObjectIdByICNType( ICN::OBJNRSRC );
2148     if ( uidResource == 0 ) {
2149         uidResource = tile.uniq;
2150     }
2151 
2152     updateTileById( tile, uidResource, resourceSprite );
2153 
2154     // Replace shadow of the resource.
2155     if ( Maps::isValidDirection( tile._index, Direction::LEFT ) ) {
2156         updateTileById( world.GetTiles( Maps::GetDirectionIndex( tile._index, Direction::LEFT ) ), uidResource, resourceSprite - 1 );
2157     }
2158 }
2159 
GetMonsterSpriteIndices(const Tiles & tile,uint32_t monsterIndex)2160 std::pair<uint32_t, uint32_t> Maps::Tiles::GetMonsterSpriteIndices( const Tiles & tile, uint32_t monsterIndex )
2161 {
2162     const int tileIndex = tile._index;
2163     int attackerIndex = -1;
2164 
2165     // scan for a hero around
2166     for ( const int32_t idx : ScanAroundObject( tileIndex, MP2::OBJ_HEROES, false ) ) {
2167         const Heroes * hero = world.GetTiles( idx ).GetHeroes();
2168         assert( hero != nullptr );
2169 
2170         // hero is going to attack monsters on this tile
2171         if ( hero->GetAttackedMonsterTileIndex() == tileIndex ) {
2172             attackerIndex = idx;
2173             break;
2174         }
2175     }
2176 
2177     std::pair<uint32_t, uint32_t> spriteIndices( monsterIndex * 9, 0 );
2178 
2179     // draw an attacking sprite if there is an attacking hero nearby
2180     if ( attackerIndex != -1 ) {
2181         spriteIndices.first += 7;
2182 
2183         switch ( Maps::GetDirection( tileIndex, attackerIndex ) ) {
2184         case Direction::TOP_LEFT:
2185         case Direction::LEFT:
2186         case Direction::BOTTOM_LEFT:
2187             spriteIndices.first += 1;
2188             break;
2189         default:
2190             break;
2191         }
2192     }
2193     else {
2194         const fheroes2::Point & mp = Maps::GetPoint( tileIndex );
2195         const std::array<uint8_t, 15> & monsterAnimationSequence = fheroes2::getMonsterAnimationSequence();
2196         spriteIndices.second = monsterIndex * 9 + 1 + monsterAnimationSequence[( Game::MapsAnimationFrame() + mp.x * mp.y ) % monsterAnimationSequence.size()];
2197     }
2198     return spriteIndices;
2199 }
2200 
ClearFog(int colors)2201 void Maps::Tiles::ClearFog( int colors )
2202 {
2203     fog_colors &= ~colors;
2204 }
2205 
isFogAllAround(const int color) const2206 bool Maps::Tiles::isFogAllAround( const int color ) const
2207 {
2208     const int32_t center = GetIndex();
2209     const fheroes2::Point mp = Maps::GetPoint( center );
2210     const int32_t width = world.w();
2211     const int32_t height = world.h();
2212 
2213     // Verify all tiles around the current one with radius of 2 to cover moving hero case as well.
2214     for ( int32_t y = -2; y < 3; ++y ) {
2215         const int32_t offsetY = mp.y + y;
2216         if ( offsetY < 0 || offsetY >= height )
2217             continue;
2218 
2219         const int32_t centerY = center + y * width;
2220 
2221         for ( int32_t x = -2; x < 3; ++x ) {
2222             if ( x == 0 && y == 0 )
2223                 continue;
2224 
2225             const int32_t offsetX = mp.x + x;
2226             if ( offsetX < 0 || offsetX >= width )
2227                 continue;
2228 
2229             if ( !world.GetTiles( centerY + x ).isFog( color ) ) {
2230                 return false;
2231             }
2232         }
2233     }
2234 
2235     return true;
2236 }
2237 
GetFogDirections(int color) const2238 int Maps::Tiles::GetFogDirections( int color ) const
2239 {
2240     int around = 0;
2241     const Directions & directions = Direction::All();
2242 
2243     for ( Directions::const_iterator it = directions.begin(); it != directions.end(); ++it )
2244         if ( !Maps::isValidDirection( _index, *it ) || world.GetTiles( Maps::GetDirectionIndex( _index, *it ) ).isFog( color ) )
2245             around |= *it;
2246 
2247     if ( isFog( color ) )
2248         around |= Direction::CENTER;
2249 
2250     return around;
2251 }
2252 
RedrawFogs(fheroes2::Image & dst,int color,const Interface::GameArea & area) const2253 void Maps::Tiles::RedrawFogs( fheroes2::Image & dst, int color, const Interface::GameArea & area ) const
2254 {
2255     const fheroes2::Point & mp = Maps::GetPoint( _index );
2256 
2257     const int around = GetFogDirections( color );
2258 
2259     // TIL::CLOF32
2260     if ( DIRECTION_ALL == around ) {
2261         const fheroes2::Image & sf = fheroes2::AGG::GetTIL( TIL::CLOF32, ( mp.x + mp.y ) % 4, 0 );
2262         area.DrawTile( dst, sf, mp );
2263     }
2264     else {
2265         u32 index = 0;
2266         bool revert = false;
2267 
2268         if ( ( around & Direction::CENTER ) && !( around & ( Direction::TOP | Direction::BOTTOM | Direction::LEFT | Direction::RIGHT ) ) ) {
2269             index = 10;
2270         }
2271         else if ( ( contains( around, Direction::CENTER | Direction::TOP ) ) && !( around & ( Direction::BOTTOM | Direction::LEFT | Direction::RIGHT ) ) ) {
2272             index = 6;
2273         }
2274         else if ( ( contains( around, Direction::CENTER | Direction::RIGHT ) ) && !( around & ( Direction::TOP | Direction::BOTTOM | Direction::LEFT ) ) ) {
2275             index = 7;
2276         }
2277         else if ( ( contains( around, Direction::CENTER | Direction::LEFT ) ) && !( around & ( Direction::TOP | Direction::BOTTOM | Direction::RIGHT ) ) ) {
2278             index = 7;
2279             revert = true;
2280         }
2281         else if ( ( contains( around, Direction::CENTER | Direction::BOTTOM ) ) && !( around & ( Direction::TOP | Direction::LEFT | Direction::RIGHT ) ) ) {
2282             index = 8;
2283         }
2284         else if ( ( contains( around, DIRECTION_CENTER_COL ) ) && !( around & ( Direction::LEFT | Direction::RIGHT ) ) ) {
2285             index = 9;
2286         }
2287         else if ( ( contains( around, DIRECTION_CENTER_ROW ) ) && !( around & ( Direction::TOP | Direction::BOTTOM ) ) ) {
2288             index = 29;
2289         }
2290         else if ( around == ( DIRECTION_ALL & ( ~Direction::TOP_RIGHT ) ) ) {
2291             index = 15;
2292         }
2293         else if ( around == ( DIRECTION_ALL & ( ~Direction::TOP_LEFT ) ) ) {
2294             index = 15;
2295             revert = true;
2296         }
2297         else if ( around == ( DIRECTION_ALL & ( ~Direction::BOTTOM_RIGHT ) ) ) {
2298             index = 22;
2299         }
2300         else if ( around == ( DIRECTION_ALL & ( ~Direction::BOTTOM_LEFT ) ) ) {
2301             index = 22;
2302             revert = true;
2303         }
2304         else if ( around == ( DIRECTION_ALL & ( ~( Direction::TOP_RIGHT | Direction::BOTTOM_RIGHT ) ) ) ) {
2305             index = 16;
2306         }
2307         else if ( around == ( DIRECTION_ALL & ( ~( Direction::TOP_LEFT | Direction::BOTTOM_LEFT ) ) ) ) {
2308             index = 16;
2309             revert = true;
2310         }
2311         else if ( around == ( DIRECTION_ALL & ( ~( Direction::TOP_RIGHT | Direction::BOTTOM_LEFT ) ) ) ) {
2312             index = 17;
2313         }
2314         else if ( around == ( DIRECTION_ALL & ( ~( Direction::TOP_LEFT | Direction::BOTTOM_RIGHT ) ) ) ) {
2315             index = 17;
2316             revert = true;
2317         }
2318         else if ( around == ( DIRECTION_ALL & ( ~( Direction::TOP_LEFT | Direction::TOP_RIGHT ) ) ) ) {
2319             index = 18;
2320         }
2321         else if ( around == ( DIRECTION_ALL & ( ~( Direction::BOTTOM_LEFT | Direction::BOTTOM_RIGHT ) ) ) ) {
2322             index = 23;
2323         }
2324         else if ( around == ( DIRECTION_ALL & ( ~DIRECTION_TOP_RIGHT_CORNER ) ) ) {
2325             index = 13;
2326         }
2327         else if ( around == ( DIRECTION_ALL & ( ~DIRECTION_TOP_LEFT_CORNER ) ) ) {
2328             index = 13;
2329             revert = true;
2330         }
2331         else if ( around == ( DIRECTION_ALL & ( ~DIRECTION_BOTTOM_RIGHT_CORNER ) ) ) {
2332             index = 14;
2333         }
2334         else if ( around == ( DIRECTION_ALL & ( ~DIRECTION_BOTTOM_LEFT_CORNER ) ) ) {
2335             index = 14;
2336             revert = true;
2337         }
2338         else if ( contains( around, Direction::CENTER | Direction::LEFT | Direction::BOTTOM_LEFT | Direction::BOTTOM )
2339                   && !( around & ( Direction::TOP | Direction::RIGHT ) ) ) {
2340             index = 11;
2341         }
2342         else if ( contains( around, Direction::CENTER | Direction::RIGHT | Direction::BOTTOM_RIGHT | Direction::BOTTOM )
2343                   && !( around & ( Direction::TOP | Direction::LEFT ) ) ) {
2344             index = 11;
2345             revert = true;
2346         }
2347         else if ( contains( around, Direction::CENTER | Direction::LEFT | Direction::TOP_LEFT | Direction::TOP )
2348                   && !( around & ( Direction::BOTTOM | Direction::RIGHT ) ) ) {
2349             index = 12;
2350         }
2351         else if ( contains( around, Direction::CENTER | Direction::RIGHT | Direction::TOP_RIGHT | Direction::TOP )
2352                   && !( around & ( Direction::BOTTOM | Direction::LEFT ) ) ) {
2353             index = 12;
2354             revert = true;
2355         }
2356         else if ( contains( around, DIRECTION_CENTER_ROW | Direction::BOTTOM | Direction::TOP | Direction::TOP_LEFT )
2357                   && !( around & ( Direction::BOTTOM_LEFT | Direction::BOTTOM_RIGHT | Direction::TOP_RIGHT ) ) ) {
2358             index = 19;
2359         }
2360         else if ( contains( around, DIRECTION_CENTER_ROW | Direction::BOTTOM | Direction::TOP | Direction::TOP_RIGHT )
2361                   && !( around & ( Direction::BOTTOM_LEFT | Direction::BOTTOM_RIGHT | Direction::TOP_LEFT ) ) ) {
2362             index = 19;
2363             revert = true;
2364         }
2365         else if ( contains( around, DIRECTION_CENTER_ROW | Direction::BOTTOM | Direction::TOP | Direction::BOTTOM_LEFT )
2366                   && !( around & ( Direction::TOP_RIGHT | Direction::BOTTOM_RIGHT | Direction::TOP_LEFT ) ) ) {
2367             index = 20;
2368         }
2369         else if ( contains( around, DIRECTION_CENTER_ROW | Direction::BOTTOM | Direction::TOP | Direction::BOTTOM_RIGHT )
2370                   && !( around & ( Direction::TOP_RIGHT | Direction::BOTTOM_LEFT | Direction::TOP_LEFT ) ) ) {
2371             index = 20;
2372             revert = true;
2373         }
2374         else if ( contains( around, DIRECTION_CENTER_ROW | Direction::BOTTOM | Direction::TOP )
2375                   && !( around & ( Direction::TOP_RIGHT | Direction::BOTTOM_RIGHT | Direction::BOTTOM_LEFT | Direction::TOP_LEFT ) ) ) {
2376             index = 22;
2377         }
2378         else if ( contains( around, DIRECTION_CENTER_ROW | Direction::BOTTOM | Direction::BOTTOM_LEFT ) && !( around & ( Direction::TOP | Direction::BOTTOM_RIGHT ) ) ) {
2379             index = 24;
2380         }
2381         else if ( contains( around, DIRECTION_CENTER_ROW | Direction::BOTTOM | Direction::BOTTOM_RIGHT ) && !( around & ( Direction::TOP | Direction::BOTTOM_LEFT ) ) ) {
2382             index = 24;
2383             revert = true;
2384         }
2385         else if ( contains( around, DIRECTION_CENTER_COL | Direction::LEFT | Direction::TOP_LEFT ) && !( around & ( Direction::RIGHT | Direction::BOTTOM_LEFT ) ) ) {
2386             index = 25;
2387         }
2388         else if ( contains( around, DIRECTION_CENTER_COL | Direction::RIGHT | Direction::TOP_RIGHT ) && !( around & ( Direction::LEFT | Direction::BOTTOM_RIGHT ) ) ) {
2389             index = 25;
2390             revert = true;
2391         }
2392         else if ( contains( around, DIRECTION_CENTER_COL | Direction::BOTTOM_LEFT | Direction::LEFT ) && !( around & ( Direction::RIGHT | Direction::TOP_LEFT ) ) ) {
2393             index = 26;
2394         }
2395         else if ( contains( around, DIRECTION_CENTER_COL | Direction::BOTTOM_RIGHT | Direction::RIGHT ) && !( around & ( Direction::LEFT | Direction::TOP_RIGHT ) ) ) {
2396             index = 26;
2397             revert = true;
2398         }
2399         else if ( contains( around, DIRECTION_CENTER_ROW | Direction::TOP_LEFT | Direction::TOP ) && !( around & ( Direction::BOTTOM | Direction::TOP_RIGHT ) ) ) {
2400             index = 30;
2401         }
2402         else if ( contains( around, DIRECTION_CENTER_ROW | Direction::TOP_RIGHT | Direction::TOP ) && !( around & ( Direction::BOTTOM | Direction::TOP_LEFT ) ) ) {
2403             index = 30;
2404             revert = true;
2405         }
2406         else if ( contains( around, Direction::CENTER | Direction::BOTTOM | Direction::LEFT )
2407                   && !( around & ( Direction::TOP | Direction::RIGHT | Direction::BOTTOM_LEFT ) ) ) {
2408             index = 27;
2409         }
2410         else if ( contains( around, Direction::CENTER | Direction::BOTTOM | Direction::RIGHT )
2411                   && !( around & ( Direction::TOP | Direction::TOP_LEFT | Direction::LEFT | Direction::BOTTOM_RIGHT ) ) ) {
2412             index = 27;
2413             revert = true;
2414         }
2415         else if ( contains( around, Direction::CENTER | Direction::LEFT | Direction::TOP )
2416                   && !( around & ( Direction::TOP_LEFT | Direction::RIGHT | Direction::BOTTOM | Direction::BOTTOM_RIGHT ) ) ) {
2417             index = 28;
2418         }
2419         else if ( contains( around, Direction::CENTER | Direction::RIGHT | Direction::TOP )
2420                   && !( around & ( Direction::TOP_RIGHT | Direction::LEFT | Direction::BOTTOM | Direction::BOTTOM_LEFT ) ) ) {
2421             index = 28;
2422             revert = true;
2423         }
2424         else if ( contains( around, DIRECTION_CENTER_ROW | Direction::TOP ) && !( around & ( Direction::BOTTOM | Direction::TOP_LEFT | Direction::TOP_RIGHT ) ) ) {
2425             index = 31;
2426         }
2427         else if ( contains( around, DIRECTION_CENTER_COL | Direction::RIGHT ) && !( around & ( Direction::LEFT | Direction::TOP_RIGHT | Direction::BOTTOM_RIGHT ) ) ) {
2428             index = 32;
2429         }
2430         else if ( contains( around, DIRECTION_CENTER_COL | Direction::LEFT ) && !( around & ( Direction::RIGHT | Direction::TOP_LEFT | Direction::BOTTOM_LEFT ) ) ) {
2431             index = 32;
2432             revert = true;
2433         }
2434         else if ( contains( around, DIRECTION_CENTER_ROW | Direction::BOTTOM ) && !( around & ( Direction::TOP | Direction::BOTTOM_LEFT | Direction::BOTTOM_RIGHT ) ) ) {
2435             index = 33;
2436         }
2437         else if ( contains( around, DIRECTION_CENTER_ROW | DIRECTION_BOTTOM_ROW ) && !( around & Direction::TOP ) ) {
2438             index = ( _index % 2 ) ? 0 : 1;
2439         }
2440         else if ( contains( around, DIRECTION_CENTER_ROW | DIRECTION_TOP_ROW ) && !( around & Direction::BOTTOM ) ) {
2441             index = ( _index % 2 ) ? 4 : 5;
2442         }
2443         else if ( contains( around, DIRECTION_CENTER_COL | DIRECTION_LEFT_COL ) && !( around & Direction::RIGHT ) ) {
2444             index = ( _index % 2 ) ? 2 : 3;
2445         }
2446         else if ( contains( around, DIRECTION_CENTER_COL | DIRECTION_RIGHT_COL ) && !( around & Direction::LEFT ) ) {
2447             index = ( _index % 2 ) ? 2 : 3;
2448             revert = true;
2449         }
2450         else {
2451             // unknown
2452             DEBUG_LOG( DBG_GAME, DBG_WARN, "Invalid direction for fog: " << around );
2453             const fheroes2::Image & sf = fheroes2::AGG::GetTIL( TIL::CLOF32, ( mp.x + mp.y ) % 4, 0 );
2454             area.DrawTile( dst, sf, mp );
2455             return;
2456         }
2457 
2458         const fheroes2::Sprite & sprite = fheroes2::AGG::GetICN( ICN::CLOP32, index );
2459         area.BlitOnTile( dst, sprite, ( revert ? sprite.x() + TILEWIDTH - sprite.width() : sprite.x() ), sprite.y(), mp, revert );
2460     }
2461 }
2462 
updateTileById(Maps::Tiles & tile,const uint32_t uid,const uint8_t newIndex)2463 void Maps::Tiles::updateTileById( Maps::Tiles & tile, const uint32_t uid, const uint8_t newIndex )
2464 {
2465     Maps::TilesAddon * addon = tile.FindAddonLevel1( uid );
2466     if ( addon != nullptr ) {
2467         addon->index = newIndex;
2468     }
2469     else if ( tile.uniq == uid ) {
2470         tile.objectIndex = newIndex;
2471     }
2472 }
2473 
updateEmpty()2474 void Maps::Tiles::updateEmpty()
2475 {
2476     if ( mp2_object == MP2::OBJ_ZERO ) {
2477         setAsEmpty();
2478     }
2479 }
2480 
setAsEmpty()2481 void Maps::Tiles::setAsEmpty()
2482 {
2483     // If an object is removed we should validate if this tile a potential candidate to be a coast.
2484     // Check if this tile is not water and it have neighbouring water tiles.
2485     if ( isWater() ) {
2486         SetObject( MP2::OBJ_ZERO );
2487         return;
2488     }
2489 
2490     bool isCoast = false;
2491 
2492     const Indexes tileIndices = Maps::getAroundIndexes( _index, 1 );
2493     for ( const int tileIndex : tileIndices ) {
2494         if ( tileIndex < 0 ) {
2495             // Invalid tile index.
2496             continue;
2497         }
2498 
2499         if ( world.GetTiles( tileIndex ).isWater() ) {
2500             isCoast = true;
2501             break;
2502         }
2503     }
2504 
2505     SetObject( isCoast ? MP2::OBJ_COAST : MP2::OBJ_ZERO );
2506 }
2507 
getObjectIdByICNType(const int icnId) const2508 uint32_t Maps::Tiles::getObjectIdByICNType( const int icnId ) const
2509 {
2510     if ( MP2::GetICNObject( objectTileset ) == icnId ) {
2511         return uniq;
2512     }
2513 
2514     for ( const TilesAddon & addon : addons_level1 ) {
2515         if ( MP2::GetICNObject( addon.object ) == icnId ) {
2516             return addon.uniq;
2517         }
2518     }
2519 
2520     return 0;
2521 }
2522 
getValidTileSets() const2523 std::vector<uint8_t> Maps::Tiles::getValidTileSets() const
2524 {
2525     std::vector<uint8_t> tileSets;
2526 
2527     if ( objectTileset != 0 ) {
2528         tileSets.emplace_back( objectTileset >> 2 );
2529     }
2530 
2531     for ( const TilesAddon & addon : addons_level1 ) {
2532         if ( addon.object != 0 ) {
2533             tileSets.emplace_back( addon.object >> 2 );
2534         }
2535     }
2536 
2537     for ( const TilesAddon & addon : addons_level2 ) {
2538         if ( addon.object != 0 ) {
2539             tileSets.emplace_back( addon.object >> 2 );
2540         }
2541     }
2542 
2543     return tileSets;
2544 }
2545 
containsTileSet(const std::vector<uint8_t> & tileSets) const2546 bool Maps::Tiles::containsTileSet( const std::vector<uint8_t> & tileSets ) const
2547 {
2548     for ( const uint8_t tileSetId : tileSets ) {
2549         if ( ( objectTileset >> 2 ) == tileSetId ) {
2550             return true;
2551         }
2552 
2553         for ( const TilesAddon & addon : addons_level1 ) {
2554             if ( ( addon.object >> 2 ) == tileSetId ) {
2555                 return true;
2556             }
2557         }
2558 
2559         for ( const TilesAddon & addon : addons_level2 ) {
2560             if ( ( addon.object >> 2 ) == tileSetId ) {
2561                 return true;
2562             }
2563         }
2564     }
2565 
2566     return false;
2567 }
2568 
isTallObject() const2569 bool Maps::Tiles::isTallObject() const
2570 {
2571     // TODO: possibly cache the output of the method as right now it's in average twice.
2572     if ( !Maps::isValidDirection( _index, Direction::TOP ) ) {
2573         // Nothing above so this object can't be tall.
2574         return false;
2575     }
2576 
2577     std::vector<uint32_t> tileUIDs;
2578     if ( objectTileset > 0 && objectIndex < 255 && uniq != 0 && ( ( _level >> 1 ) & 1 ) == 0 ) {
2579         tileUIDs.emplace_back( uniq );
2580     }
2581 
2582     for ( const TilesAddon & addon : addons_level1 ) {
2583         if ( addon.uniq != 0 && ( ( addon.level >> 1 ) & 1 ) == 0 ) {
2584             tileUIDs.emplace_back( addon.uniq );
2585         }
2586     }
2587 
2588     for ( const TilesAddon & addon : addons_level2 ) {
2589         if ( addon.uniq != 0 && ( ( addon.level >> 1 ) & 1 ) == 0 ) {
2590             tileUIDs.emplace_back( addon.uniq );
2591         }
2592     }
2593 
2594     const Tiles & topTile = world.GetTiles( Maps::GetDirectionIndex( _index, Direction::TOP ) );
2595     for ( const uint32_t tileUID : tileUIDs ) {
2596         if ( topTile.uniq == tileUID && !topTile.isShadowSprite( topTile.objectTileset, topTile.objectIndex ) ) {
2597             return true;
2598         }
2599 
2600         for ( const TilesAddon & addon : topTile.addons_level1 ) {
2601             if ( addon.uniq == tileUID && !TilesAddon::isShadow( addon ) ) {
2602                 return true;
2603             }
2604         }
2605 
2606         for ( const TilesAddon & addon : topTile.addons_level2 ) {
2607             if ( addon.uniq == tileUID && !TilesAddon::isShadow( addon ) ) {
2608                 return true;
2609             }
2610         }
2611     }
2612 
2613     return false;
2614 }
2615 
getIndexOfMainTile(const Maps::Tiles & tile)2616 int32_t Maps::Tiles::getIndexOfMainTile( const Maps::Tiles & tile )
2617 {
2618     const MP2::MapObjectType objectType = tile.GetObject( false );
2619     const MP2::MapObjectType correctedObjectType = MP2::getBaseActionObjectType( objectType );
2620 
2621     if ( correctedObjectType == objectType ) {
2622         // Nothing to do.
2623         return tile._index;
2624     }
2625 
2626     // This is non-main tile of an action object. We have to find the main tile.
2627     // Since we don't want to care about the size of every object in the game we should find tiles in a certain radius.
2628     const int32_t radiusOfSearch = 3;
2629 
2630     // It's unknown whether object type belongs to bottom layer or ground. Create a list of UIDs starting from bottom layer.
2631     std::vector<uint32_t> uids;
2632     const Maps::Addons & level2Addons = tile.getLevel2Addons();
2633     const Maps::Addons & level1Addons = tile.getLevel1Addons();
2634 
2635     for ( auto iter = level2Addons.rbegin(); iter != level2Addons.rend(); ++iter ) {
2636         if ( iter->uniq != 0 ) {
2637             uids.emplace_back( iter->uniq );
2638         }
2639     }
2640 
2641     if ( tile.GetObjectUID() != 0 ) {
2642         uids.emplace_back( tile.GetObjectUID() );
2643     }
2644 
2645     for ( auto iter = level1Addons.rbegin(); iter != level1Addons.rend(); ++iter ) {
2646         if ( iter->uniq != 0 ) {
2647             uids.emplace_back( iter->uniq );
2648         }
2649     }
2650 
2651     const int32_t tileIndex = tile.GetIndex();
2652     const int32_t mapWidth = world.w();
2653 
2654     assert( correctedObjectType > objectType );
2655 
2656     for ( int32_t y = -radiusOfSearch; y <= radiusOfSearch; ++y ) {
2657         for ( int32_t x = -radiusOfSearch; x <= radiusOfSearch; ++x ) {
2658             const int32_t index = tileIndex + y * mapWidth + x;
2659             if ( Maps::isValidAbsIndex( index ) ) {
2660                 const Maps::Tiles & foundTile = world.GetTiles( index );
2661                 if ( std::find( uids.begin(), uids.end(), foundTile.GetObjectUID() ) != uids.end() && foundTile.GetObject( false ) == correctedObjectType ) {
2662                     return foundTile._index;
2663                 }
2664             }
2665         }
2666     }
2667 
2668     // Most likely we have a broken object put by an editor.
2669     DEBUG_LOG( DBG_GAME, DBG_WARN, "Tile " << tileIndex << " of type " << MP2::StringObject( objectType ) << " has no parent tile." );
2670     return -1;
2671 }
2672 
isDetachedObject() const2673 bool Maps::Tiles::isDetachedObject() const
2674 {
2675     const MP2::MapObjectType objectType = GetObject( false );
2676     if ( isDetachedObjectType( objectType ) ) {
2677         return true;
2678     }
2679 
2680     const MP2::MapObjectType correctedObjectType = MP2::getBaseActionObjectType( objectType );
2681     if ( !isDetachedObjectType( correctedObjectType ) ) {
2682         return false;
2683     }
2684 
2685     const int32_t mainTileIndex = Maps::Tiles::getIndexOfMainTile( *this );
2686     if ( mainTileIndex == -1 ) {
2687         return false;
2688     }
2689 
2690     const uint32_t objectUID = world.GetTiles( mainTileIndex ).GetObjectUID();
2691     if ( uniq == objectUID ) {
2692         return ( ( _level >> 1 ) & 1 ) == 0;
2693     }
2694 
2695     for ( const TilesAddon & addon : addons_level1 ) {
2696         if ( addon.uniq == objectUID ) {
2697             return ( ( addon.level >> 1 ) & 1 ) == 0;
2698         }
2699     }
2700 
2701     return false;
2702 }
2703 
operator <<(StreamBase & msg,const TilesAddon & ta)2704 StreamBase & Maps::operator<<( StreamBase & msg, const TilesAddon & ta )
2705 {
2706     return msg << ta.level << ta.uniq << ta.object << ta.index;
2707 }
2708 
operator >>(StreamBase & msg,TilesAddon & ta)2709 StreamBase & Maps::operator>>( StreamBase & msg, TilesAddon & ta )
2710 {
2711     msg >> ta.level >> ta.uniq >> ta.object >> ta.index;
2712 
2713     return msg;
2714 }
2715 
operator <<(StreamBase & msg,const Tiles & tile)2716 StreamBase & Maps::operator<<( StreamBase & msg, const Tiles & tile )
2717 {
2718     return msg << tile._index << tile.pack_sprite_index << tile.tilePassable << tile.uniq << tile.objectTileset << tile.objectIndex << tile.mp2_object << tile.fog_colors
2719                << tile.quantity1 << tile.quantity2 << tile.quantity3 << tile.heroID << tile.tileIsRoad << tile.addons_level1 << tile.addons_level2 << tile._level;
2720 }
2721 
operator >>(StreamBase & msg,Tiles & tile)2722 StreamBase & Maps::operator>>( StreamBase & msg, Tiles & tile )
2723 {
2724     msg >> tile._index >> tile.pack_sprite_index >> tile.tilePassable >> tile.uniq >> tile.objectTileset >> tile.objectIndex >> tile.mp2_object >> tile.fog_colors
2725         >> tile.quantity1 >> tile.quantity2 >> tile.quantity3 >> tile.heroID >> tile.tileIsRoad >> tile.addons_level1 >> tile.addons_level2 >> tile._level;
2726 
2727     return msg;
2728 }
2729