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