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 
27 #include "ai.h"
28 #include "artifact.h"
29 #include "campaign_data.h"
30 #include "campaign_savedata.h"
31 #include "castle.h"
32 #include "game.h"
33 #include "game_over.h"
34 #include "ground.h"
35 #include "heroes.h"
36 #include "logging.h"
37 #include "maps_actions.h"
38 #include "maps_objects.h"
39 #include "mp2.h"
40 #include "pairs.h"
41 #include "race.h"
42 #include "resource.h"
43 #include "save_format_version.h"
44 #include "serialize.h"
45 #include "settings.h"
46 #include "tools.h"
47 #include "world.h"
48 
49 namespace
50 {
isTileBlockedForSettingMonster(const MapsTiles & mapTiles,const int32_t tileId,const int32_t radius,const std::set<int32_t> & excludeTiles)51     bool isTileBlockedForSettingMonster( const MapsTiles & mapTiles, const int32_t tileId, const int32_t radius, const std::set<int32_t> & excludeTiles )
52     {
53         const MapsIndexes & indexes = Maps::getAroundIndexes( tileId, radius );
54 
55         for ( const int32_t indexId : indexes ) {
56             if ( excludeTiles.count( indexId ) > 0 ) {
57                 return true;
58             }
59 
60             const Maps::Tiles & indexedTile = mapTiles[indexId];
61             if ( indexedTile.isWater() ) {
62                 continue;
63             }
64 
65             const MP2::MapObjectType objectType = indexedTile.GetObject( true );
66             if ( objectType == MP2::OBJ_CASTLE || objectType == MP2::OBJ_HEROES || objectType == MP2::OBJ_MONSTER ) {
67                 return true;
68             }
69         }
70 
71         return false;
72     }
73 
findSuitableNeighbouringTile(const MapsTiles & mapTiles,const int32_t tileId,const bool allDirections)74     int32_t findSuitableNeighbouringTile( const MapsTiles & mapTiles, const int32_t tileId, const bool allDirections )
75     {
76         std::vector<int32_t> suitableIds;
77 
78         const MapsIndexes & indexes = Maps::getAroundIndexes( tileId );
79 
80         for ( const int32_t indexId : indexes ) {
81             // If allDirections is false, we should only consider tiles below the current object
82             if ( !allDirections && indexId < tileId + world.w() - 2 ) {
83                 continue;
84             }
85 
86             const Maps::Tiles & indexedTile = mapTiles[indexId];
87 
88             if ( indexedTile.isWater() || !indexedTile.isClearGround() ) {
89                 continue;
90             }
91 
92             // If the candidate tile is a coast tile, it is suitable only if there are other coast tiles nearby
93             if ( indexedTile.GetObject( false ) == MP2::OBJ_COAST ) {
94                 const MapsIndexes coastTiles = Maps::ScanAroundObject( indexId, MP2::OBJ_COAST );
95 
96                 if ( coastTiles.empty() ) {
97                     continue;
98                 }
99             }
100 
101             suitableIds.emplace_back( indexId );
102         }
103 
104         if ( suitableIds.empty() ) {
105             return -1;
106         }
107 
108         return Rand::Get( suitableIds );
109     }
110 
getNeighbouringEmptyTileCount(const MapsTiles & mapTiles,const int32_t tileId)111     int32_t getNeighbouringEmptyTileCount( const MapsTiles & mapTiles, const int32_t tileId )
112     {
113         int32_t count = 0;
114 
115         const MapsIndexes & indexes = Maps::getAroundIndexes( tileId );
116 
117         for ( const int32_t indexId : indexes ) {
118             const Maps::Tiles & indexedTile = mapTiles[indexId];
119             if ( indexedTile.isWater() || !indexedTile.isClearGround() ) {
120                 continue;
121             }
122 
123             ++count;
124         }
125 
126         return count;
127     }
128 }
129 
130 namespace GameStatic
131 {
132     extern u32 uniq;
133 }
134 
~ListActions()135 ListActions::~ListActions()
136 {
137     clear();
138 }
139 
clear(void)140 void ListActions::clear( void )
141 {
142     for ( iterator it = begin(); it != end(); ++it )
143         delete *it;
144     std::list<ActionSimple *>::clear();
145 }
146 
~MapObjects()147 MapObjects::~MapObjects()
148 {
149     clear();
150 }
151 
clear(void)152 void MapObjects::clear( void )
153 {
154     for ( iterator it = begin(); it != end(); ++it )
155         delete ( *it ).second;
156     std::map<u32, MapObjectSimple *>::clear();
157 }
158 
add(MapObjectSimple * obj)159 void MapObjects::add( MapObjectSimple * obj )
160 {
161     if ( obj ) {
162         std::map<u32, MapObjectSimple *> & currentMap = *this;
163         if ( currentMap[obj->GetUID()] )
164             delete currentMap[obj->GetUID()];
165         currentMap[obj->GetUID()] = obj;
166     }
167 }
168 
get(u32 uid)169 MapObjectSimple * MapObjects::get( u32 uid )
170 {
171     iterator it = find( uid );
172     return it != end() ? ( *it ).second : nullptr;
173 }
174 
get(const fheroes2::Point & pos)175 std::list<MapObjectSimple *> MapObjects::get( const fheroes2::Point & pos )
176 {
177     std::list<MapObjectSimple *> res;
178     for ( iterator it = begin(); it != end(); ++it )
179         if ( ( *it ).second && ( *it ).second->isPosition( pos ) )
180             res.push_back( ( *it ).second );
181     return res;
182 }
183 
remove(u32 uid)184 void MapObjects::remove( u32 uid )
185 {
186     iterator it = find( uid );
187     if ( it != end() )
188         delete ( *it ).second;
189     erase( it );
190 }
191 
Get(s32 index)192 CapturedObject & CapturedObjects::Get( s32 index )
193 {
194     std::map<s32, CapturedObject> & my = *this;
195     return my[index];
196 }
197 
SetColor(s32 index,int col)198 void CapturedObjects::SetColor( s32 index, int col )
199 {
200     Get( index ).SetColor( col );
201 }
202 
Set(s32 index,int obj,int col)203 void CapturedObjects::Set( s32 index, int obj, int col )
204 {
205     CapturedObject & co = Get( index );
206 
207     if ( co.GetColor() != col && co.guardians.isValid() )
208         co.guardians.Reset();
209 
210     co.Set( obj, col );
211 }
212 
GetCount(int obj,int col) const213 u32 CapturedObjects::GetCount( int obj, int col ) const
214 {
215     u32 result = 0;
216 
217     const ObjectColor objcol( obj, col );
218 
219     for ( const_iterator it = begin(); it != end(); ++it ) {
220         if ( objcol == ( *it ).second.objcol )
221             ++result;
222     }
223 
224     return result;
225 }
226 
GetCountMines(int type,int col) const227 u32 CapturedObjects::GetCountMines( int type, int col ) const
228 {
229     u32 result = 0;
230 
231     const ObjectColor objcol1( MP2::OBJ_MINES, col );
232     const ObjectColor objcol2( MP2::OBJ_HEROES, col );
233 
234     for ( const_iterator it = begin(); it != end(); ++it ) {
235         const ObjectColor & objcol = ( *it ).second.objcol;
236 
237         if ( objcol == objcol1 || objcol == objcol2 ) {
238             // scan for find mines
239             const uint8_t index = world.GetTiles( ( *it ).first ).GetObjectSpriteIndex();
240 
241             // index sprite EXTRAOVR
242             if ( 0 == index && Resource::ORE == type )
243                 ++result;
244             else if ( 1 == index && Resource::SULFUR == type )
245                 ++result;
246             else if ( 2 == index && Resource::CRYSTAL == type )
247                 ++result;
248             else if ( 3 == index && Resource::GEMS == type )
249                 ++result;
250             else if ( 4 == index && Resource::GOLD == type )
251                 ++result;
252         }
253     }
254 
255     return result;
256 }
257 
GetColor(s32 index) const258 int CapturedObjects::GetColor( s32 index ) const
259 {
260     const_iterator it = find( index );
261     return it != end() ? ( *it ).second.GetColor() : Color::NONE;
262 }
263 
ClearFog(int colors)264 void CapturedObjects::ClearFog( int colors )
265 {
266     // clear abroad objects
267     for ( const_iterator it = begin(); it != end(); ++it ) {
268         const ObjectColor & objcol = ( *it ).second.objcol;
269 
270         if ( objcol.isColor( colors ) ) {
271             int scoute = 0;
272 
273             switch ( objcol.first ) {
274             case MP2::OBJ_MINES:
275             case MP2::OBJ_ALCHEMYLAB:
276             case MP2::OBJ_SAWMILL:
277                 scoute = 2;
278                 break;
279 
280             default:
281                 break;
282             }
283 
284             if ( scoute )
285                 Maps::ClearFog( ( *it ).first, scoute, colors );
286         }
287     }
288 }
289 
ResetColor(int color)290 void CapturedObjects::ResetColor( int color )
291 {
292     for ( iterator it = begin(); it != end(); ++it ) {
293         ObjectColor & objcol = ( *it ).second.objcol;
294 
295         if ( objcol.isColor( color ) ) {
296             const MP2::MapObjectType objectType = static_cast<MP2::MapObjectType>( objcol.first );
297 
298             objcol.second = objectType == MP2::OBJ_CASTLE ? Color::UNUSED : Color::NONE;
299             world.GetTiles( ( *it ).first ).CaptureFlags32( objectType, objcol.second );
300         }
301     }
302 }
303 
tributeCapturedObjects(const int playerColorId,const int objectType,Funds & funds,int & objectCount)304 void CapturedObjects::tributeCapturedObjects( const int playerColorId, const int objectType, Funds & funds, int & objectCount )
305 {
306     funds = Funds();
307     objectCount = 0;
308 
309     for ( iterator it = begin(); it != end(); ++it ) {
310         const ObjectColor & objcol = ( *it ).second.objcol;
311 
312         if ( objcol.isObject( objectType ) && objcol.isColor( playerColorId ) ) {
313             Maps::Tiles & tile = world.GetTiles( ( *it ).first );
314 
315             funds += Funds( tile.QuantityResourceCount() );
316             ++objectCount;
317             tile.QuantityReset();
318         }
319     }
320 }
321 
322 World & world = World::Get();
323 
Get(void)324 World & World::Get( void )
325 {
326     static World insideWorld;
327 
328     return insideWorld;
329 }
330 
Defaults(void)331 void World::Defaults( void )
332 {
333     // playing kingdom
334     vec_kingdoms.Init();
335 
336     // Map seed is random and persisted on saves
337     // this has to be generated before initializing heroes, as campaign-specific heroes start at a higher level and thus have to simulate level ups
338     _seed = Rand::Get( std::numeric_limits<uint32_t>::max() );
339 
340     week_next = Week::RandomWeek( *this, false, _weekSeed );
341 
342     // initialize all heroes
343     vec_heroes.Init();
344 
345     vec_castles.Init();
346 }
347 
Reset(void)348 void World::Reset( void )
349 {
350     width = 0;
351     height = 0;
352 
353     // maps tiles
354     vec_tiles.clear();
355 
356     // kingdoms
357     vec_kingdoms.clear();
358 
359     // event day
360     vec_eventsday.clear();
361 
362     // rumors
363     vec_rumors.clear();
364 
365     // castles
366     vec_castles.Clear();
367 
368     // heroes
369     vec_heroes.clear();
370 
371     // extra
372     map_captureobj.clear();
373     map_actions.clear();
374     map_objects.clear();
375 
376     ultimate_artifact.Reset();
377 
378     day = 0;
379     week = 0;
380     month = 0;
381 
382     week_current = Week( WeekName::TORTOISE );
383     week_next = Week::RandomWeek( *this, false, _weekSeed );
384 
385     heroes_cond_wins = Heroes::UNKNOWN;
386     heroes_cond_loss = Heroes::UNKNOWN;
387 
388     _seed = 0;
389 }
390 
391 /* new maps */
NewMaps(int32_t sw,int32_t sh)392 void World::NewMaps( int32_t sw, int32_t sh )
393 {
394     Reset();
395 
396     width = sw;
397     height = sh;
398 
399     Maps::FileInfo fi;
400 
401     fi.size_w = static_cast<uint16_t>( width );
402     fi.size_h = static_cast<uint16_t>( height );
403 
404     Settings & conf = Settings::Get();
405 
406     if ( conf.isPriceOfLoyaltySupported() ) {
407         fi._version = GameVersion::PRICE_OF_LOYALTY;
408     }
409 
410     conf.SetCurrentFileInfo( fi );
411 
412     Defaults();
413 
414     vec_tiles.resize( static_cast<size_t>( width ) * height );
415 
416     // init all tiles
417     for ( size_t i = 0; i < vec_tiles.size(); ++i ) {
418         MP2::mp2tile_t mp2tile;
419 
420         mp2tile.surfaceType = static_cast<uint16_t>( Rand::Get( 16, 19 ) ); // index sprite ground, see ground32.til
421         mp2tile.objectName1 = 0; // object sprite level 1
422         mp2tile.level1IcnImageIndex = 0xff; // index sprite level 1
423         mp2tile.quantity1 = 0;
424         mp2tile.quantity2 = 0;
425         mp2tile.objectName2 = 0; // object sprite level 2
426         mp2tile.level2IcnImageIndex = 0xff; // index sprite level 2
427         mp2tile.flags = static_cast<uint8_t>( Rand::Get( 0, 3 ) ); // shape reflect % 4, 0 none, 1 vertical, 2 horizontal, 3 any
428         mp2tile.mapObjectType = MP2::OBJ_ZERO;
429         mp2tile.nextAddonIndex = 0;
430         mp2tile.level1ObjectUID = 0; // means that there's no object on this tile.
431         mp2tile.level2ObjectUID = 0;
432 
433         vec_tiles[i].Init( static_cast<int32_t>( i ), mp2tile );
434     }
435 }
436 
InitKingdoms(void)437 void World::InitKingdoms( void )
438 {
439     vec_kingdoms.Init();
440 }
441 
GetTiles(const int32_t x,const int32_t y) const442 const Maps::Tiles & World::GetTiles( const int32_t x, const int32_t y ) const
443 {
444 #ifdef WITH_DEBUG
445     return vec_tiles.at( y * width + x );
446 #else
447     return vec_tiles[y * width + x];
448 #endif
449 }
450 
GetTiles(const int32_t x,const int32_t y)451 Maps::Tiles & World::GetTiles( const int32_t x, const int32_t y )
452 {
453 #ifdef WITH_DEBUG
454     return vec_tiles.at( y * width + x );
455 #else
456     return vec_tiles[y * width + x];
457 #endif
458 }
459 
GetTiles(const int32_t tileId) const460 const Maps::Tiles & World::GetTiles( const int32_t tileId ) const
461 {
462 #ifdef WITH_DEBUG
463     return vec_tiles.at( tileId );
464 #else
465     return vec_tiles[tileId];
466 #endif
467 }
468 
GetTiles(const int32_t tileId)469 Maps::Tiles & World::GetTiles( const int32_t tileId )
470 {
471 #ifdef WITH_DEBUG
472     return vec_tiles.at( tileId );
473 #else
474     return vec_tiles[tileId];
475 #endif
476 }
477 
getSize() const478 size_t World::getSize() const
479 {
480     return vec_tiles.size();
481 }
482 
483 /* get kingdom */
GetKingdom(int color)484 Kingdom & World::GetKingdom( int color )
485 {
486     return vec_kingdoms.GetKingdom( color );
487 }
488 
GetKingdom(int color) const489 const Kingdom & World::GetKingdom( int color ) const
490 {
491     return vec_kingdoms.GetKingdom( color );
492 }
493 
getCastle(const fheroes2::Point & tilePosition)494 Castle * World::getCastle( const fheroes2::Point & tilePosition )
495 {
496     return vec_castles.Get( tilePosition );
497 }
498 
getCastle(const fheroes2::Point & tilePosition) const499 const Castle * World::getCastle( const fheroes2::Point & tilePosition ) const
500 {
501     return vec_castles.Get( tilePosition );
502 }
503 
getCastleEntrance(const fheroes2::Point & tilePosition) const504 const Castle * World::getCastleEntrance( const fheroes2::Point & tilePosition ) const
505 {
506     if ( !isValidCastleEntrance( tilePosition ) ) {
507         return nullptr;
508     }
509 
510     return vec_castles.Get( tilePosition );
511 }
512 
getCastleEntrance(const fheroes2::Point & tilePosition)513 Castle * World::getCastleEntrance( const fheroes2::Point & tilePosition )
514 {
515     if ( !isValidCastleEntrance( tilePosition ) ) {
516         return nullptr;
517     }
518 
519     return vec_castles.Get( tilePosition );
520 }
521 
isValidCastleEntrance(const fheroes2::Point & tilePosition) const522 bool World::isValidCastleEntrance( const fheroes2::Point & tilePosition ) const
523 {
524     return Maps::isValidAbsPoint( tilePosition.x, tilePosition.y ) && ( GetTiles( tilePosition.x, tilePosition.y ).GetObject( false ) == MP2::OBJ_CASTLE );
525 }
526 
GetHeroes(int id)527 Heroes * World::GetHeroes( int id )
528 {
529     return vec_heroes.Get( id );
530 }
531 
GetHeroes(int id) const532 const Heroes * World::GetHeroes( int id ) const
533 {
534     return vec_heroes.Get( id );
535 }
536 
537 /* get heroes from index maps */
GetHeroes(const fheroes2::Point & center)538 Heroes * World::GetHeroes( const fheroes2::Point & center )
539 {
540     return vec_heroes.Get( center );
541 }
542 
GetHeroes(const fheroes2::Point & center) const543 const Heroes * World::GetHeroes( const fheroes2::Point & center ) const
544 {
545     return vec_heroes.Get( center );
546 }
547 
GetFreemanHeroes(int race) const548 Heroes * World::GetFreemanHeroes( int race ) const
549 {
550     return vec_heroes.GetFreeman( race );
551 }
552 
GetFreemanHeroesSpecial(int heroID) const553 Heroes * World::GetFreemanHeroesSpecial( int heroID ) const
554 {
555     return vec_heroes.GetFreemanSpecial( heroID );
556 }
557 
FromJailHeroes(s32 index)558 Heroes * World::FromJailHeroes( s32 index )
559 {
560     return vec_heroes.FromJail( index );
561 }
562 
GetHeroes(const Castle & castle) const563 CastleHeroes World::GetHeroes( const Castle & castle ) const
564 {
565     return CastleHeroes( vec_heroes.GetGuest( castle ), vec_heroes.GetGuard( castle ) );
566 }
567 
RescanAllHeroesPathPassable() const568 void World::RescanAllHeroesPathPassable() const
569 {
570     std::for_each( vec_heroes.begin(), vec_heroes.end(), []( Heroes * hero ) { hero->RescanPathPassable(); } );
571 }
572 
GetDay(void) const573 int World::GetDay( void ) const
574 {
575     return LastDay() ? DAYOFWEEK : day % DAYOFWEEK;
576 }
577 
GetWeek(void) const578 int World::GetWeek( void ) const
579 {
580     return LastWeek() ? WEEKOFMONTH : week % WEEKOFMONTH;
581 }
582 
GetMonth(void) const583 int World::GetMonth( void ) const
584 {
585     return month;
586 }
587 
CountDay(void) const588 u32 World::CountDay( void ) const
589 {
590     return day;
591 }
592 
CountWeek(void) const593 u32 World::CountWeek( void ) const
594 {
595     return week;
596 }
597 
BeginWeek(void) const598 bool World::BeginWeek( void ) const
599 {
600     return 1 == ( day % DAYOFWEEK );
601 }
602 
BeginMonth(void) const603 bool World::BeginMonth( void ) const
604 {
605     return 1 == ( week % WEEKOFMONTH ) && BeginWeek();
606 }
607 
LastDay(void) const608 bool World::LastDay( void ) const
609 {
610     return ( 0 == ( day % DAYOFWEEK ) );
611 }
612 
LastWeek(void) const613 bool World::LastWeek( void ) const
614 {
615     return ( 0 == ( week % WEEKOFMONTH ) );
616 }
617 
GetWeekType(void) const618 const Week & World::GetWeekType( void ) const
619 {
620     return week_current;
621 }
622 
pickRumor()623 void World::pickRumor()
624 {
625     if ( vec_rumors.empty() ) {
626         _rumor = nullptr;
627         assert( 0 );
628         return;
629     }
630 
631     if ( vec_rumors.size() == 1 ) {
632         _rumor = &vec_rumors.front();
633         assert( 0 );
634         return;
635     }
636 
637     const std::string * current = _rumor;
638     while ( current == _rumor ) {
639         // vec_rumors always contain values
640         _rumor = &Rand::Get( vec_rumors );
641     }
642 }
643 
644 /* new day */
NewDay(void)645 void World::NewDay( void )
646 {
647     ++day;
648 
649     if ( BeginWeek() ) {
650         ++week;
651 
652         pickRumor();
653 
654         if ( BeginMonth() ) {
655             ++month;
656         }
657     }
658 
659     // first the routine of the new month
660     if ( BeginMonth() ) {
661         NewMonth();
662 
663         vec_kingdoms.NewMonth();
664         vec_castles.NewMonth();
665         vec_heroes.NewMonth();
666     }
667 
668     // then the routine of the new week
669     if ( BeginWeek() ) {
670         NewWeek();
671 
672         vec_kingdoms.NewWeek();
673         vec_castles.NewWeek();
674         vec_heroes.NewWeek();
675     }
676 
677     // and finally the routine of the new day
678     vec_kingdoms.NewDay();
679     vec_castles.NewDay();
680     vec_heroes.NewDay();
681 
682     // remove deprecated events
683     assert( day > 0 );
684 
685     vec_eventsday.remove_if( [this]( const EventDate & v ) { return v.isDeprecated( day - 1 ); } );
686 }
687 
NewWeek(void)688 void World::NewWeek( void )
689 {
690     // update week seed: it depends on the current day and the state of the map
691     _weekSeed = _seed;
692     fheroes2::hashCombine( _weekSeed, day );
693     for ( const Maps::Tiles & tile : vec_tiles ) {
694         fheroes2::hashCombine( _weekSeed, tile.GetQuantity1() );
695     }
696 
697     // update week type
698     week_next = Week::RandomWeek( *this, LastWeek(), _weekSeed );
699     week_current = week_next;
700 
701     // update objects
702     if ( week > 1 ) {
703         for ( Maps::Tiles & tile : vec_tiles ) {
704             if ( MP2::isWeekLife( ( tile ).GetObject( false ) ) || tile.GetObject() == MP2::OBJ_MONSTER ) {
705                 tile.QuantityUpdate( false );
706             }
707         }
708     }
709 
710     // add events
711     if ( Settings::Get().ExtWorldExtObjectsCaptured() ) {
712         vec_kingdoms.AddTributeEvents( map_captureobj, day, MP2::OBJ_WATERWHEEL );
713         vec_kingdoms.AddTributeEvents( map_captureobj, day, MP2::OBJ_WINDMILL );
714         vec_kingdoms.AddTributeEvents( map_captureobj, day, MP2::OBJ_MAGICGARDEN );
715     }
716 }
717 
NewMonth(void)718 void World::NewMonth( void )
719 {
720     if ( month > 1 && week_current.GetType() == WeekName::MONSTERS ) {
721         MonthOfMonstersAction( Monster( week_current.GetMonster() ) );
722     }
723 }
724 
MonthOfMonstersAction(const Monster & mons)725 void World::MonthOfMonstersAction( const Monster & mons )
726 {
727     if ( !mons.isValid() ) {
728         return;
729     }
730 
731     // Find all tiles which are useful for monsters such as resources, artifacts, mines, other capture objects. Exclude heroes, monsters and castles.
732     std::vector<int32_t> primaryTargetTiles;
733 
734     // Sometimes monsters appear on roads so find all road tiles.
735     std::vector<int32_t> secondaryTargetTiles;
736 
737     // Lastly monster occasionally appear on empty tiles.
738     std::vector<int32_t> tetriaryTargetTiles;
739 
740     std::set<int32_t> excludeTiles;
741 
742     for ( const Maps::Tiles & tile : vec_tiles ) {
743         if ( tile.isWater() ) {
744             // Monsters are not placed on water.
745             continue;
746         }
747 
748         const int32_t tileId = tile.GetIndex();
749         const MP2::MapObjectType objectType = tile.GetObject( true );
750 
751         if ( objectType == MP2::OBJ_CASTLE || objectType == MP2::OBJ_HEROES || objectType == MP2::OBJ_MONSTER ) {
752             excludeTiles.emplace( tileId );
753             continue;
754         }
755 
756         if ( MP2::isActionObject( objectType ) ) {
757             if ( isTileBlockedForSettingMonster( vec_tiles, tileId, 3, excludeTiles ) ) {
758                 continue;
759             }
760 
761             const int32_t tileToSet = findSuitableNeighbouringTile( vec_tiles, tileId, ( tile.GetPassable() == DIRECTION_ALL ) );
762             if ( tileToSet >= 0 ) {
763                 primaryTargetTiles.emplace_back( tileToSet );
764                 excludeTiles.emplace( tileId );
765             }
766         }
767         else if ( tile.isRoad() ) {
768             if ( isTileBlockedForSettingMonster( vec_tiles, tileId, 4, excludeTiles ) ) {
769                 continue;
770             }
771 
772             if ( getNeighbouringEmptyTileCount( vec_tiles, tileId ) < 2 ) {
773                 continue;
774             }
775 
776             const int32_t tileToSet = findSuitableNeighbouringTile( vec_tiles, tileId, true );
777             if ( tileToSet >= 0 ) {
778                 secondaryTargetTiles.emplace_back( tileToSet );
779                 excludeTiles.emplace( tileId );
780             }
781         }
782         else if ( tile.isClearGround() ) {
783             if ( isTileBlockedForSettingMonster( vec_tiles, tileId, 4, excludeTiles ) ) {
784                 continue;
785             }
786 
787             if ( getNeighbouringEmptyTileCount( vec_tiles, tileId ) < 4 ) {
788                 continue;
789             }
790 
791             const int32_t tileToSet = findSuitableNeighbouringTile( vec_tiles, tileId, true );
792             if ( tileToSet >= 0 ) {
793                 tetriaryTargetTiles.emplace_back( tileToSet );
794                 excludeTiles.emplace( tileId );
795             }
796         }
797     }
798 
799     // Shuffle all found tile IDs.
800     Rand::Shuffle( primaryTargetTiles );
801     Rand::Shuffle( secondaryTargetTiles );
802     Rand::Shuffle( tetriaryTargetTiles );
803 
804     // Calculate the number of monsters to be placed.
805     uint32_t monstersToBePlaced = 0;
806     if ( primaryTargetTiles.size() < static_cast<size_t>( height ) ) {
807         monstersToBePlaced = static_cast<uint32_t>( height );
808     }
809     else {
810         monstersToBePlaced
811             = Rand::GetWithSeed( static_cast<uint32_t>( primaryTargetTiles.size() * 75 / 100 ), static_cast<uint32_t>( primaryTargetTiles.size() * 125 / 100 ), _seed );
812     }
813 
814     // 85% of positions are for primary targets
815     // 10% of positions are for roads
816     // 5% of positions are for empty tiles
817     uint32_t primaryTileCount = monstersToBePlaced * 85 / 100;
818     if ( primaryTileCount > primaryTargetTiles.size() ) {
819         primaryTileCount = static_cast<uint32_t>( primaryTargetTiles.size() );
820     }
821 
822     for ( uint32_t i = 0; i < primaryTileCount; ++i ) {
823         Maps::Tiles::PlaceMonsterOnTile( vec_tiles[primaryTargetTiles[i]], mons, 0 /* random */ );
824     }
825 
826     uint32_t secondaryTileCount = monstersToBePlaced * 10 / 100;
827     if ( secondaryTileCount > secondaryTargetTiles.size() ) {
828         secondaryTileCount = static_cast<uint32_t>( secondaryTargetTiles.size() );
829     }
830 
831     for ( uint32_t i = 0; i < secondaryTileCount; ++i ) {
832         Maps::Tiles::PlaceMonsterOnTile( vec_tiles[secondaryTargetTiles[i]], mons, 0 /* random */ );
833     }
834 
835     uint32_t tetriaryTileCount = monstersToBePlaced * 5 / 100;
836     if ( tetriaryTileCount > tetriaryTargetTiles.size() ) {
837         tetriaryTileCount = static_cast<uint32_t>( tetriaryTargetTiles.size() );
838     }
839 
840     for ( uint32_t i = 0; i < tetriaryTileCount; ++i ) {
841         Maps::Tiles::PlaceMonsterOnTile( vec_tiles[tetriaryTargetTiles[i]], mons, 0 /* random */ );
842     }
843 }
844 
GetRumors(void)845 const std::string & World::GetRumors( void )
846 {
847     if ( !_rumor ) {
848         pickRumor();
849     }
850     return *_rumor;
851 }
852 
GetTeleportEndPoints(s32 center) const853 MapsIndexes World::GetTeleportEndPoints( s32 center ) const
854 {
855     MapsIndexes result;
856 
857     const Maps::Tiles & entrance = GetTiles( center );
858     if ( _allTeleporters.size() > 1 && entrance.GetObject( false ) == MP2::OBJ_STONELITHS ) {
859         for ( MapsIndexes::const_iterator it = _allTeleporters.begin(); it != _allTeleporters.end(); ++it ) {
860             const Maps::Tiles & tile = GetTiles( *it );
861             if ( *it != center && tile.GetObjectSpriteIndex() == entrance.GetObjectSpriteIndex() && tile.GetObject() != MP2::OBJ_HEROES
862                  && tile.isWater() == entrance.isWater() ) {
863                 result.push_back( *it );
864             }
865         }
866     }
867 
868     return result;
869 }
870 
871 /* return random teleport destination */
NextTeleport(s32 index) const872 s32 World::NextTeleport( s32 index ) const
873 {
874     const MapsIndexes teleports = GetTeleportEndPoints( index );
875     if ( teleports.empty() ) {
876         DEBUG_LOG( DBG_GAME, DBG_WARN, "not found" );
877         return index;
878     }
879 
880     return Rand::Get( teleports );
881 }
882 
GetWhirlpoolEndPoints(s32 center) const883 MapsIndexes World::GetWhirlpoolEndPoints( s32 center ) const
884 {
885     if ( MP2::OBJ_WHIRLPOOL == GetTiles( center ).GetObject( false ) ) {
886         std::map<s32, MapsIndexes> uniq_whirlpools;
887 
888         for ( MapsIndexes::const_iterator it = _whirlpoolTiles.begin(); it != _whirlpoolTiles.end(); ++it ) {
889             const Maps::Tiles & tile = GetTiles( *it );
890             if ( tile.GetHeroes() != nullptr ) {
891                 continue;
892             }
893 
894             uniq_whirlpools[tile.GetObjectUID()].push_back( *it );
895         }
896 
897         if ( 2 > uniq_whirlpools.size() ) {
898             DEBUG_LOG( DBG_GAME, DBG_WARN, "is empty" );
899             return MapsIndexes();
900         }
901 
902         const uint32_t currentUID = GetTiles( center ).GetObjectUID();
903         MapsIndexes uniqs;
904         uniqs.reserve( uniq_whirlpools.size() );
905 
906         for ( std::map<s32, MapsIndexes>::const_iterator it = uniq_whirlpools.begin(); it != uniq_whirlpools.end(); ++it ) {
907             const u32 uniq = ( *it ).first;
908             if ( uniq == currentUID )
909                 continue;
910             uniqs.push_back( uniq );
911         }
912 
913         return uniq_whirlpools[Rand::Get( uniqs )];
914     }
915 
916     return MapsIndexes();
917 }
918 
919 /* return random whirlpools destination */
NextWhirlpool(s32 index) const920 s32 World::NextWhirlpool( s32 index ) const
921 {
922     const MapsIndexes whilrpools = GetWhirlpoolEndPoints( index );
923     if ( whilrpools.empty() ) {
924         DEBUG_LOG( DBG_GAME, DBG_WARN, "is full" );
925         return index;
926     }
927 
928     return Rand::Get( whilrpools );
929 }
930 
931 /* return message from sign */
932 
933 /* return count captured object */
CountCapturedObject(int obj,int col) const934 u32 World::CountCapturedObject( int obj, int col ) const
935 {
936     return map_captureobj.GetCount( obj, col );
937 }
938 
939 /* return count captured mines */
CountCapturedMines(int type,int color) const940 u32 World::CountCapturedMines( int type, int color ) const
941 {
942     switch ( type ) {
943     case Resource::WOOD:
944         return CountCapturedObject( MP2::OBJ_SAWMILL, color );
945     case Resource::MERCURY:
946         return CountCapturedObject( MP2::OBJ_ALCHEMYLAB, color );
947     default:
948         break;
949     }
950 
951     return map_captureobj.GetCountMines( type, color );
952 }
953 
954 /* capture object */
CaptureObject(s32 index,int color)955 void World::CaptureObject( s32 index, int color )
956 {
957     const MP2::MapObjectType objectType = GetTiles( index ).GetObject( false );
958     map_captureobj.Set( index, objectType, color );
959 
960     Castle * castle = getCastleEntrance( Maps::GetPoint( index ) );
961     if ( castle && castle->GetColor() != color )
962         castle->ChangeColor( color );
963 
964     if ( color & ( Color::ALL | Color::UNUSED ) )
965         GetTiles( index ).CaptureFlags32( objectType, color );
966 }
967 
968 /* return color captured object */
ColorCapturedObject(s32 index) const969 int World::ColorCapturedObject( s32 index ) const
970 {
971     return map_captureobj.GetColor( index );
972 }
973 
GetListActions(s32 index)974 ListActions * World::GetListActions( s32 index )
975 {
976     MapActions::iterator it = map_actions.find( index );
977     return it != map_actions.end() ? &( *it ).second : nullptr;
978 }
979 
GetCapturedObject(s32 index)980 CapturedObject & World::GetCapturedObject( s32 index )
981 {
982     return map_captureobj.Get( index );
983 }
984 
ResetCapturedObjects(int color)985 void World::ResetCapturedObjects( int color )
986 {
987     map_captureobj.ResetColor( color );
988 }
989 
ClearFog(int colors)990 void World::ClearFog( int colors )
991 {
992     colors = Players::GetPlayerFriends( colors );
993 
994     // clear abroad castles
995     vec_castles.Scoute( colors );
996 
997     // clear abroad heroes
998     vec_heroes.Scoute( colors );
999 
1000     map_captureobj.ClearFog( colors );
1001 }
1002 
GetUltimateArtifact(void) const1003 const UltimateArtifact & World::GetUltimateArtifact( void ) const
1004 {
1005     return ultimate_artifact;
1006 }
1007 
DiggingForUltimateArtifact(const fheroes2::Point & center)1008 bool World::DiggingForUltimateArtifact( const fheroes2::Point & center )
1009 {
1010     Maps::Tiles & tile = GetTiles( center.x, center.y );
1011 
1012     // puts hole sprite
1013     uint8_t obj = 0;
1014     uint32_t idx = 0;
1015 
1016     switch ( tile.GetGround() ) {
1017     case Maps::Ground::WASTELAND:
1018         obj = 0xE4;
1019         idx = 70;
1020         break; // ICN::OBJNCRCK
1021     case Maps::Ground::DIRT:
1022         obj = 0xE0;
1023         idx = 140;
1024         break; // ICN::OBJNDIRT
1025     case Maps::Ground::DESERT:
1026         obj = 0xDC;
1027         idx = 68;
1028         break; // ICN::OBJNDSRT
1029     case Maps::Ground::LAVA:
1030         obj = 0xD8;
1031         idx = 26;
1032         break; // ICN::OBJNLAVA
1033     case Maps::Ground::GRASS:
1034     default:
1035         obj = 0xC0;
1036         idx = 9;
1037         break; // ICN::OBJNGRA2
1038     }
1039     tile.AddonsPushLevel1( Maps::TilesAddon( 0, GetUniq(), obj, idx ) );
1040 
1041     // reset
1042     if ( ultimate_artifact.isPosition( tile.GetIndex() ) && !ultimate_artifact.isFound() ) {
1043         ultimate_artifact.markAsFound();
1044         return true;
1045     }
1046 
1047     return false;
1048 }
1049 
AddEventDate(const EventDate & event)1050 void World::AddEventDate( const EventDate & event )
1051 {
1052     vec_eventsday.push_back( event );
1053 }
1054 
GetEventsDate(int color) const1055 EventsDate World::GetEventsDate( int color ) const
1056 {
1057     EventsDate res;
1058 
1059     for ( EventsDate::const_iterator it = vec_eventsday.begin(); it != vec_eventsday.end(); ++it )
1060         if ( ( *it ).isAllow( color, day ) )
1061             res.push_back( *it );
1062 
1063     return res;
1064 }
1065 
DateString(void) const1066 std::string World::DateString( void ) const
1067 {
1068     std::string output( "month: " );
1069     output += std::to_string( GetMonth() );
1070     output += ", week: ";
1071     output += std::to_string( GetWeek() );
1072     output += ", day: ";
1073     output += std::to_string( GetDay() );
1074 
1075     return output;
1076 }
1077 
CountObeliskOnMaps(void)1078 u32 World::CountObeliskOnMaps( void )
1079 {
1080     const size_t res = std::count_if( vec_tiles.begin(), vec_tiles.end(), []( const Maps::Tiles & tile ) { return MP2::OBJ_OBELISK == tile.GetObject( false ); } );
1081     return res > 0 ? static_cast<uint32_t>( res ) : 6;
1082 }
1083 
ActionForMagellanMaps(int color)1084 void World::ActionForMagellanMaps( int color )
1085 {
1086     const int alliedColors = Players::GetPlayerFriends( color );
1087 
1088     for ( Maps::Tiles & tile : vec_tiles ) {
1089         if ( tile.isWater() ) {
1090             tile.ClearFog( alliedColors );
1091         }
1092     }
1093 }
1094 
GetMapEvent(const fheroes2::Point & pos)1095 MapEvent * World::GetMapEvent( const fheroes2::Point & pos )
1096 {
1097     std::list<MapObjectSimple *> res = map_objects.get( pos );
1098     return !res.empty() ? static_cast<MapEvent *>( res.front() ) : nullptr;
1099 }
1100 
GetMapObject(u32 uid)1101 MapObjectSimple * World::GetMapObject( u32 uid )
1102 {
1103     return uid ? map_objects.get( uid ) : nullptr;
1104 }
1105 
RemoveMapObject(const MapObjectSimple * obj)1106 void World::RemoveMapObject( const MapObjectSimple * obj )
1107 {
1108     if ( obj )
1109         map_objects.remove( obj->GetUID() );
1110 }
1111 
UpdateRecruits(Recruits & recruits) const1112 void World::UpdateRecruits( Recruits & recruits ) const
1113 {
1114     if ( vec_heroes.HaveTwoFreemans() )
1115         while ( recruits.GetID1() == recruits.GetID2() )
1116             recruits.SetHero2( GetFreemanHeroes() );
1117     else
1118         recruits.SetHero2( nullptr );
1119 }
1120 
GetHeroesCondWins(void) const1121 const Heroes * World::GetHeroesCondWins( void ) const
1122 {
1123     return GetHeroes( heroes_cond_wins );
1124 }
1125 
GetHeroesCondLoss(void) const1126 const Heroes * World::GetHeroesCondLoss( void ) const
1127 {
1128     return GetHeroes( heroes_cond_loss );
1129 }
1130 
KingdomIsWins(const Kingdom & kingdom,uint32_t wins) const1131 bool World::KingdomIsWins( const Kingdom & kingdom, uint32_t wins ) const
1132 {
1133     const Settings & conf = Settings::Get();
1134 
1135     switch ( wins ) {
1136     case GameOver::WINS_ALL:
1137         return kingdom.GetColor() == vec_kingdoms.GetNotLossColors();
1138 
1139     case GameOver::WINS_TOWN: {
1140         const Castle * town = getCastleEntrance( conf.WinsMapsPositionObject() );
1141         // check comp also wins
1142         return ( kingdom.isControlHuman() || conf.WinsCompAlsoWins() ) && ( town && town->GetColor() == kingdom.GetColor() );
1143     }
1144 
1145     case GameOver::WINS_HERO: {
1146         const Heroes * hero = GetHeroesCondWins();
1147         return ( hero && Heroes::UNKNOWN != heroes_cond_wins && hero->isFreeman() && hero->GetKillerColor() == kingdom.GetColor() );
1148     }
1149 
1150     case GameOver::WINS_ARTIFACT: {
1151         const KingdomHeroes & heroes = kingdom.GetHeroes();
1152         if ( conf.WinsFindUltimateArtifact() ) {
1153             return std::any_of( heroes.begin(), heroes.end(), []( const Heroes * hero ) { return hero->HasUltimateArtifact(); } );
1154         }
1155         else {
1156             const Artifact art = conf.WinsFindArtifactID();
1157             return std::any_of( heroes.begin(), heroes.end(), [&art]( const Heroes * hero ) { return hero->hasArtifact( art ); } );
1158         }
1159     }
1160 
1161     case GameOver::WINS_SIDE: {
1162         return !( Game::GetActualKingdomColors() & ~Players::GetPlayerFriends( kingdom.GetColor() ) );
1163     }
1164 
1165     case GameOver::WINS_GOLD:
1166         // check comp also wins
1167         return ( ( kingdom.isControlHuman() || conf.WinsCompAlsoWins() ) && 0 < kingdom.GetFunds().Get( Resource::GOLD )
1168                  && static_cast<u32>( kingdom.GetFunds().Get( Resource::GOLD ) ) >= conf.WinsAccumulateGold() );
1169 
1170     default:
1171         break;
1172     }
1173 
1174     return false;
1175 }
1176 
isAnyKingdomVisited(const MP2::MapObjectType objectType,const int32_t dstIndex) const1177 bool World::isAnyKingdomVisited( const MP2::MapObjectType objectType, const int32_t dstIndex ) const
1178 {
1179     const Colors colors( Game::GetKingdomColors() );
1180     for ( const int color : colors ) {
1181         const Kingdom & kingdom = world.GetKingdom( color );
1182         if ( kingdom.isVisited( dstIndex, objectType ) ) {
1183             return true;
1184         }
1185     }
1186     return false;
1187 }
1188 
KingdomIsLoss(const Kingdom & kingdom,uint32_t loss) const1189 bool World::KingdomIsLoss( const Kingdom & kingdom, uint32_t loss ) const
1190 {
1191     const Settings & conf = Settings::Get();
1192 
1193     switch ( loss ) {
1194     case GameOver::LOSS_ALL:
1195         return kingdom.isLoss();
1196 
1197     case GameOver::LOSS_TOWN: {
1198         const Castle * town = getCastleEntrance( conf.LossMapsPositionObject() );
1199         return ( town && town->GetColor() != kingdom.GetColor() );
1200     }
1201 
1202     case GameOver::LOSS_HERO: {
1203         const Heroes * hero = GetHeroesCondLoss();
1204         return ( hero && Heroes::UNKNOWN != heroes_cond_loss && hero->isFreeman() );
1205     }
1206 
1207     case GameOver::LOSS_TIME:
1208         return ( CountDay() > conf.LossCountDays() && kingdom.isControlHuman() );
1209 
1210     default:
1211         break;
1212     }
1213 
1214     return false;
1215 }
1216 
CheckKingdomWins(const Kingdom & kingdom) const1217 uint32_t World::CheckKingdomWins( const Kingdom & kingdom ) const
1218 {
1219     const Settings & conf = Settings::Get();
1220 
1221     if ( conf.isCampaignGameType() ) {
1222         const Campaign::ScenarioVictoryCondition victoryCondition = Campaign::getCurrentScenarioVictoryCondition();
1223         if ( victoryCondition == Campaign::ScenarioVictoryCondition::CAPTURE_DRAGON_CITY ) {
1224             const bool visited = kingdom.isVisited( MP2::OBJ_DRAGONCITY ) || kingdom.isVisited( MP2::OBJN_DRAGONCITY );
1225             if ( visited ) {
1226                 return GameOver::WINS_SIDE;
1227             }
1228 
1229             return GameOver::COND_NONE;
1230         }
1231     }
1232 
1233     const std::array<uint32_t, 6> wins
1234         = { GameOver::WINS_ALL, GameOver::WINS_TOWN, GameOver::WINS_HERO, GameOver::WINS_ARTIFACT, GameOver::WINS_SIDE, GameOver::WINS_GOLD };
1235 
1236     for ( const uint32_t cond : wins ) {
1237         if ( ( ( conf.ConditionWins() & cond ) == cond ) && KingdomIsWins( kingdom, cond ) ) {
1238             return cond;
1239         }
1240     }
1241 
1242     return GameOver::COND_NONE;
1243 }
1244 
CheckKingdomLoss(const Kingdom & kingdom) const1245 uint32_t World::CheckKingdomLoss( const Kingdom & kingdom ) const
1246 {
1247     const Settings & conf = Settings::Get();
1248 
1249     // first, check if the other players have not completed WINS_TOWN, WINS_HERO, WINS_ARTIFACT or WINS_GOLD yet
1250     const std::array<std::pair<uint32_t, uint32_t>, 4> enemy_wins = { std::make_pair<uint32_t, uint32_t>( GameOver::WINS_TOWN, GameOver::LOSS_ENEMY_WINS_TOWN ),
1251                                                                       std::make_pair<uint32_t, uint32_t>( GameOver::WINS_HERO, GameOver::LOSS_ENEMY_WINS_HERO ),
1252                                                                       std::make_pair<uint32_t, uint32_t>( GameOver::WINS_ARTIFACT, GameOver::LOSS_ENEMY_WINS_ARTIFACT ),
1253                                                                       std::make_pair<uint32_t, uint32_t>( GameOver::WINS_GOLD, GameOver::LOSS_ENEMY_WINS_GOLD ) };
1254 
1255     for ( const auto & item : enemy_wins ) {
1256         if ( conf.ConditionWins() & item.first ) {
1257             const int color = vec_kingdoms.FindWins( item.first );
1258 
1259             if ( color && color != kingdom.GetColor() ) {
1260                 return item.second;
1261             }
1262         }
1263     }
1264 
1265     if ( conf.isCampaignGameType() && kingdom.isControlHuman() ) {
1266         const Campaign::ScenarioLossCondition lossCondition = Campaign::getCurrentScenarioLossCondition();
1267         if ( lossCondition == Campaign::ScenarioLossCondition::LOSE_ALL_SORCERESS_VILLAGES ) {
1268             const KingdomCastles & castles = kingdom.GetCastles();
1269             bool hasSorceressVillage = false;
1270 
1271             for ( size_t i = 0; i < castles.size(); ++i ) {
1272                 if ( castles[i]->isCastle() || castles[i]->GetRace() != Race::SORC )
1273                     continue;
1274 
1275                 hasSorceressVillage = true;
1276                 break;
1277             }
1278 
1279             if ( !hasSorceressVillage )
1280                 return GameOver::LOSS_ALL;
1281         }
1282     }
1283 
1284     const std::array<uint32_t, 4> loss = { GameOver::LOSS_ALL, GameOver::LOSS_TOWN, GameOver::LOSS_HERO, GameOver::LOSS_TIME };
1285 
1286     for ( const uint32_t cond : loss ) {
1287         if ( ( ( conf.ConditionLoss() & cond ) == cond ) && KingdomIsLoss( kingdom, cond ) ) {
1288             return cond;
1289         }
1290     }
1291 
1292     return GameOver::COND_NONE;
1293 }
1294 
GetUniq(void)1295 u32 World::GetUniq( void )
1296 {
1297     return ++GameStatic::uniq;
1298 }
1299 
getDistance(const Heroes & hero,int targetIndex)1300 uint32_t World::getDistance( const Heroes & hero, int targetIndex )
1301 {
1302     _pathfinder.reEvaluateIfNeeded( hero );
1303     return _pathfinder.getDistance( targetIndex );
1304 }
1305 
getPath(const Heroes & hero,int targetIndex)1306 std::list<Route::Step> World::getPath( const Heroes & hero, int targetIndex )
1307 {
1308     _pathfinder.reEvaluateIfNeeded( hero );
1309     return _pathfinder.buildPath( targetIndex );
1310 }
1311 
resetPathfinder()1312 void World::resetPathfinder()
1313 {
1314     _pathfinder.reset();
1315     AI::Get().resetPathfinder();
1316 }
1317 
PostLoad(const bool setTilePassabilities)1318 void World::PostLoad( const bool setTilePassabilities )
1319 {
1320     if ( setTilePassabilities ) {
1321         // update tile passable
1322         for ( Maps::Tiles & tile : vec_tiles ) {
1323             tile.updateEmpty();
1324             tile.setInitialPassability();
1325         }
1326 
1327         // Once the original passabilities are set we know all neighbours. Now we have to update passabilities based on neighbours.
1328         for ( Maps::Tiles & tile : vec_tiles ) {
1329             tile.updatePassability();
1330         }
1331     }
1332 
1333     // cache data that's accessed often
1334     _allTeleporters = Maps::GetObjectPositions( MP2::OBJ_STONELITHS, true );
1335     _whirlpoolTiles = Maps::GetObjectPositions( MP2::OBJ_WHIRLPOOL, true );
1336 
1337     resetPathfinder();
1338     ComputeStaticAnalysis();
1339 }
1340 
GetMapSeed() const1341 uint32_t World::GetMapSeed() const
1342 {
1343     return _seed;
1344 }
1345 
operator <<(StreamBase & msg,const CapturedObject & obj)1346 StreamBase & operator<<( StreamBase & msg, const CapturedObject & obj )
1347 {
1348     return msg << obj.objcol << obj.guardians << obj.split;
1349 }
1350 
operator >>(StreamBase & msg,CapturedObject & obj)1351 StreamBase & operator>>( StreamBase & msg, CapturedObject & obj )
1352 {
1353     return msg >> obj.objcol >> obj.guardians >> obj.split;
1354 }
1355 
operator <<(StreamBase & msg,const MapObjects & objs)1356 StreamBase & operator<<( StreamBase & msg, const MapObjects & objs )
1357 {
1358     msg << static_cast<u32>( objs.size() );
1359     for ( MapObjects::const_iterator it = objs.begin(); it != objs.end(); ++it )
1360         if ( ( *it ).second ) {
1361             const MapObjectSimple & obj = *( *it ).second;
1362             msg << ( *it ).first << obj.GetType();
1363 
1364             switch ( obj.GetType() ) {
1365             case MP2::OBJ_EVENT:
1366                 msg << static_cast<const MapEvent &>( obj );
1367                 break;
1368 
1369             case MP2::OBJ_SPHINX:
1370                 msg << static_cast<const MapSphinx &>( obj );
1371                 break;
1372 
1373             case MP2::OBJ_SIGN:
1374                 msg << static_cast<const MapSign &>( obj );
1375                 break;
1376 
1377             default:
1378                 msg << obj;
1379                 break;
1380             }
1381         }
1382 
1383     return msg;
1384 }
1385 
operator >>(StreamBase & msg,MapObjects & objs)1386 StreamBase & operator>>( StreamBase & msg, MapObjects & objs )
1387 {
1388     u32 size = 0;
1389     msg >> size;
1390 
1391     objs.clear();
1392 
1393     for ( u32 ii = 0; ii < size; ++ii ) {
1394         s32 index;
1395         int type;
1396         msg >> index >> type;
1397 
1398         switch ( type ) {
1399         case MP2::OBJ_EVENT: {
1400             MapEvent * ptr = new MapEvent();
1401             msg >> *ptr;
1402             objs[index] = ptr;
1403             break;
1404         }
1405 
1406         case MP2::OBJ_SPHINX: {
1407             MapSphinx * ptr = new MapSphinx();
1408             msg >> *ptr;
1409             objs[index] = ptr;
1410             break;
1411         }
1412 
1413         case MP2::OBJ_SIGN: {
1414             MapSign * ptr = new MapSign();
1415             msg >> *ptr;
1416             objs[index] = ptr;
1417             break;
1418         }
1419 
1420         default: {
1421             MapObjectSimple * ptr = new MapObjectSimple();
1422             msg >> *ptr;
1423             objs[index] = ptr;
1424             break;
1425         }
1426         }
1427     }
1428 
1429     return msg;
1430 }
1431 
operator <<(StreamBase & msg,const World & w)1432 StreamBase & operator<<( StreamBase & msg, const World & w )
1433 {
1434     // TODO: before 0.9.4 Size was uint16_t type
1435     const uint16_t width = static_cast<uint16_t>( w.width );
1436     const uint16_t height = static_cast<uint16_t>( w.height );
1437 
1438     return msg << width << height << w.vec_tiles << w.vec_heroes << w.vec_castles << w.vec_kingdoms << w.vec_rumors << w.vec_eventsday << w.map_captureobj
1439                << w.ultimate_artifact << w.day << w.week << w.month << w.week_current << w.week_next << w.heroes_cond_wins << w.heroes_cond_loss << w.map_actions
1440                << w.map_objects << w._seed;
1441 }
1442 
operator >>(StreamBase & msg,World & w)1443 StreamBase & operator>>( StreamBase & msg, World & w )
1444 {
1445     // TODO: before 0.9.4 Size was uint16_t type
1446     uint16_t width = 0;
1447     uint16_t height = 0;
1448 
1449     msg >> width >> height;
1450     w.width = width;
1451     w.height = height;
1452 
1453     msg >> w.vec_tiles >> w.vec_heroes >> w.vec_castles >> w.vec_kingdoms >> w.vec_rumors >> w.vec_eventsday >> w.map_captureobj >> w.ultimate_artifact >> w.day >> w.week
1454         >> w.month >> w.week_current >> w.week_next >> w.heroes_cond_wins >> w.heroes_cond_loss >> w.map_actions >> w.map_objects >> w._seed;
1455 
1456     w.PostLoad( false );
1457 
1458     return msg;
1459 }
1460 
LoadFromMP2(StreamBuf st)1461 void EventDate::LoadFromMP2( StreamBuf st )
1462 {
1463     // id
1464     if ( 0 == st.get() ) {
1465         // resource
1466         resource.wood = st.getLE32();
1467         resource.mercury = st.getLE32();
1468         resource.ore = st.getLE32();
1469         resource.sulfur = st.getLE32();
1470         resource.crystal = st.getLE32();
1471         resource.gems = st.getLE32();
1472         resource.gold = st.getLE32();
1473 
1474         st.skip( 2 );
1475 
1476         // allow computer
1477         computer = ( st.getLE16() != 0 );
1478 
1479         // day of first occurent
1480         first = st.getLE16();
1481 
1482         // subsequent occurrences
1483         subsequent = st.getLE16();
1484 
1485         st.skip( 6 );
1486 
1487         colors = 0;
1488         // blue
1489         if ( st.get() )
1490             colors |= Color::BLUE;
1491         // green
1492         if ( st.get() )
1493             colors |= Color::GREEN;
1494         // red
1495         if ( st.get() )
1496             colors |= Color::RED;
1497         // yellow
1498         if ( st.get() )
1499             colors |= Color::YELLOW;
1500         // orange
1501         if ( st.get() )
1502             colors |= Color::ORANGE;
1503         // purple
1504         if ( st.get() )
1505             colors |= Color::PURPLE;
1506 
1507         // message
1508         message = st.toString();
1509         DEBUG_LOG( DBG_GAME, DBG_INFO,
1510                    "event"
1511                        << ": " << message );
1512     }
1513     else {
1514         DEBUG_LOG( DBG_GAME, DBG_WARN, "unknown id" );
1515     }
1516 }
1517 
isDeprecated(u32 date) const1518 bool EventDate::isDeprecated( u32 date ) const
1519 {
1520     return 0 == subsequent && first < date;
1521 }
1522 
isAllow(int col,u32 date) const1523 bool EventDate::isAllow( int col, u32 date ) const
1524 {
1525     return ( ( first == date || ( subsequent && ( first < date && 0 == ( ( date - first ) % subsequent ) ) ) ) && ( col & colors ) );
1526 }
1527 
operator <<(StreamBase & msg,const EventDate & obj)1528 StreamBase & operator<<( StreamBase & msg, const EventDate & obj )
1529 {
1530     return msg << obj.resource << obj.computer << obj.first << obj.subsequent << obj.colors << obj.message << obj.title;
1531 }
1532 
operator >>(StreamBase & msg,EventDate & obj)1533 StreamBase & operator>>( StreamBase & msg, EventDate & obj )
1534 {
1535     msg >> obj.resource >> obj.computer >> obj.first >> obj.subsequent >> obj.colors >> obj.message;
1536 
1537     static_assert( LAST_SUPPORTED_FORMAT_VERSION < FORMAT_VERSION_096_RELEASE, "Remove the check below." );
1538     if ( Game::GetLoadVersion() >= FORMAT_VERSION_096_RELEASE ) {
1539         msg >> obj.title;
1540     }
1541 
1542     return msg;
1543 }
1544