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