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
25 #include "artifact.h"
26 #include "campaign_data.h"
27 #include "campaign_savedata.h"
28 #include "castle.h"
29 #include "game_over.h"
30 #include "heroes.h"
31 #include "icn.h"
32 #include "kingdom.h"
33 #include "logging.h"
34 #include "maps_objects.h"
35 #include "maps_tiles.h"
36 #include "mp2.h"
37 #include "mp2_helper.h"
38 #include "race.h"
39 #include "rand.h"
40 #include "serialize.h"
41 #include "settings.h"
42 #include "tools.h"
43 #include "translations.h"
44 #include "world.h"
45
46 namespace
47 {
48 const int32_t ultimateArtifactOffset = 9;
49
getUltimateArtifact()50 Artifact getUltimateArtifact()
51 {
52 if ( Settings::Get().isCampaignGameType() ) {
53 const Campaign::ScenarioVictoryCondition victoryCondition = Campaign::getCurrentScenarioVictoryCondition();
54 if ( victoryCondition == Campaign::ScenarioVictoryCondition::OBTAIN_ULTIMATE_CROWN ) {
55 return Artifact::ULTIMATE_CROWN;
56 }
57 else if ( victoryCondition == Campaign::ScenarioVictoryCondition::OBTAIN_SPHERE_NEGATION ) {
58 return Artifact::SPHERE_NEGATION;
59 }
60 }
61
62 return Artifact::Rand( Artifact::ART_ULTIMATE );
63 }
64
fixCastleNames(AllCastles & castles)65 void fixCastleNames( AllCastles & castles )
66 {
67 // Find castles with no names.
68 std::vector<Castle *> castleWithNoName;
69 std::set<std::string> castleNames;
70
71 for ( Castle * castle : castles ) {
72 if ( castle == nullptr ) {
73 // How do we have an empty pointer in this container?
74 assert( 0 );
75 continue;
76 }
77
78 const std::string & name = castle->GetName();
79
80 if ( name.empty() ) {
81 castleWithNoName.emplace_back( castle );
82 }
83 else {
84 castleNames.emplace( name );
85 }
86 }
87
88 if ( castleWithNoName.empty() ) {
89 return;
90 }
91
92 for ( Castle * castle : castleWithNoName ) {
93 castle->setName( castleNames );
94 castleNames.emplace( castle->GetName() );
95 }
96 }
97 }
98
99 namespace GameStatic
100 {
101 extern u32 uniq;
102 }
103
LoadMapMP2(const std::string & filename)104 bool World::LoadMapMP2( const std::string & filename )
105 {
106 Reset();
107 Defaults();
108
109 StreamFile fs;
110 if ( !fs.open( filename, "rb" ) ) {
111 DEBUG_LOG( DBG_GAME | DBG_ENGINE, DBG_WARN, "file not found " << filename.c_str() );
112 return false;
113 }
114
115 // check (mp2, mx2) ID
116 if ( fs.getBE32() != 0x5C000000 )
117 return false;
118
119 // endof
120 const size_t endof_mp2 = fs.size();
121 fs.seek( endof_mp2 - 4 );
122
123 // read uniq
124 GameStatic::uniq = fs.getLE32();
125
126 // offset data
127 fs.seek( MP2::MP2OFFSETDATA - 2 * 4 );
128
129 // width
130 switch ( fs.getLE32() ) {
131 case Maps::SMALL:
132 width = Maps::SMALL;
133 break;
134 case Maps::MEDIUM:
135 width = Maps::MEDIUM;
136 break;
137 case Maps::LARGE:
138 width = Maps::LARGE;
139 break;
140 case Maps::XLARGE:
141 width = Maps::XLARGE;
142 break;
143 default:
144 width = 0;
145 break;
146 }
147
148 // height
149 switch ( fs.getLE32() ) {
150 case Maps::SMALL:
151 height = Maps::SMALL;
152 break;
153 case Maps::MEDIUM:
154 height = Maps::MEDIUM;
155 break;
156 case Maps::LARGE:
157 height = Maps::LARGE;
158 break;
159 case Maps::XLARGE:
160 height = Maps::XLARGE;
161 break;
162 default:
163 height = 0;
164 break;
165 }
166
167 if ( width == 0 || height == 0 || width != height ) {
168 DEBUG_LOG( DBG_GAME, DBG_WARN, "incrrect maps size" );
169 return false;
170 }
171
172 const int32_t worldSize = width * height;
173
174 // seek to ADDONS block
175 fs.skip( worldSize * MP2::SIZEOFMP2TILE );
176
177 // read all addons
178 std::vector<MP2::mp2addon_t> vec_mp2addons( fs.getLE32() /* count mp2addon_t */ );
179
180 for ( MP2::mp2addon_t & mp2addon : vec_mp2addons ) {
181 MP2::loadAddon( fs, mp2addon );
182 }
183
184 const size_t endof_addons = fs.tell();
185 DEBUG_LOG( DBG_GAME, DBG_INFO, "read all tiles addons, tellg: " << endof_addons );
186
187 // offset data
188 fs.seek( MP2::MP2OFFSETDATA );
189
190 vec_tiles.resize( worldSize );
191
192 // In the future we need to check 3 things which could point that this map is The Price of Loyalty version:
193 // - new object types
194 // - new artifact types on map
195 // - new artifact types in hero's bag
196
197 MapsIndexes vec_object; // index maps for OBJ_CASTLE, OBJ_HEROES, OBJ_SIGN, OBJ_BOTTLE, OBJ_EVENT
198 vec_object.reserve( 100 );
199
200 // read all tiles
201 for ( int32_t i = 0; i < worldSize; ++i ) {
202 Maps::Tiles & tile = vec_tiles[i];
203
204 MP2::mp2tile_t mp2tile;
205 MP2::loadTile( fs, mp2tile );
206
207 tile.Init( i, mp2tile );
208
209 // Read extra information if it's present.
210 size_t addonIndex = mp2tile.nextAddonIndex;
211 while ( addonIndex > 0 ) {
212 if ( vec_mp2addons.size() <= addonIndex ) {
213 DEBUG_LOG( DBG_GAME, DBG_WARN, "index out of range" );
214 break;
215 }
216 tile.AddonsPushLevel1( vec_mp2addons[addonIndex] );
217 tile.AddonsPushLevel2( vec_mp2addons[addonIndex] );
218 addonIndex = vec_mp2addons[addonIndex].nextAddonIndex;
219 }
220
221 tile.AddonsSort();
222
223 switch ( mp2tile.mapObjectType ) {
224 case MP2::OBJ_RNDTOWN:
225 case MP2::OBJ_RNDCASTLE:
226 case MP2::OBJ_CASTLE:
227 case MP2::OBJ_HEROES:
228 case MP2::OBJ_SIGN:
229 case MP2::OBJ_BOTTLE:
230 case MP2::OBJ_EVENT:
231 case MP2::OBJ_SPHINX:
232 case MP2::OBJ_JAIL:
233 vec_object.push_back( i );
234 break;
235 default:
236 break;
237 }
238 }
239
240 DEBUG_LOG( DBG_GAME, DBG_INFO, "read all tiles, tellg: " << fs.tell() );
241
242 // after addons
243 fs.seek( endof_addons );
244
245 // cood castles
246 // 72 x 3 byte (cx, cy, id)
247 for ( int32_t i = 0; i < 72; ++i ) {
248 const uint32_t cx = fs.get();
249 const uint32_t cy = fs.get();
250 const uint32_t id = fs.get();
251
252 // empty block
253 if ( 0xFF == cx && 0xFF == cy )
254 continue;
255
256 switch ( id ) {
257 case 0x00: // tower: knight
258 case 0x80: // castle: knight
259 vec_castles.AddCastle( new Castle( cx, cy, Race::KNGT ) );
260 break;
261
262 case 0x01: // tower: barbarian
263 case 0x81: // castle: barbarian
264 vec_castles.AddCastle( new Castle( cx, cy, Race::BARB ) );
265 break;
266
267 case 0x02: // tower: sorceress
268 case 0x82: // castle: sorceress
269 vec_castles.AddCastle( new Castle( cx, cy, Race::SORC ) );
270 break;
271
272 case 0x03: // tower: warlock
273 case 0x83: // castle: warlock
274 vec_castles.AddCastle( new Castle( cx, cy, Race::WRLK ) );
275 break;
276
277 case 0x04: // tower: wizard
278 case 0x84: // castle: wizard
279 vec_castles.AddCastle( new Castle( cx, cy, Race::WZRD ) );
280 break;
281
282 case 0x05: // tower: necromancer
283 case 0x85: // castle: necromancer
284 vec_castles.AddCastle( new Castle( cx, cy, Race::NECR ) );
285 break;
286
287 case 0x06: // tower: random
288 case 0x86: // castle: random
289 vec_castles.AddCastle( new Castle( cx, cy, Race::NONE ) );
290 break;
291
292 default:
293 DEBUG_LOG( DBG_GAME, DBG_WARN,
294 "castle block: "
295 << "unknown id: " << id << ", maps index: " << cx + cy * width );
296 break;
297 }
298 // preload in to capture objects cache
299 map_captureobj.Set( Maps::GetIndexFromAbsPoint( cx, cy ), MP2::OBJ_CASTLE, Color::NONE );
300 }
301
302 DEBUG_LOG( DBG_GAME, DBG_INFO, "read coord castles, tellg: " << fs.tell() );
303 fs.seek( endof_addons + ( 72 * 3 ) );
304
305 // cood resource kingdoms
306 // 144 x 3 byte (cx, cy, id)
307 for ( int32_t i = 0; i < 144; ++i ) {
308 const uint32_t cx = fs.get();
309 const uint32_t cy = fs.get();
310 const uint32_t id = fs.get();
311
312 // empty block
313 if ( 0xFF == cx && 0xFF == cy )
314 continue;
315
316 switch ( id ) {
317 // mines: wood
318 case 0x00:
319 map_captureobj.Set( Maps::GetIndexFromAbsPoint( cx, cy ), MP2::OBJ_SAWMILL, Color::NONE );
320 break;
321 // mines: mercury
322 case 0x01:
323 map_captureobj.Set( Maps::GetIndexFromAbsPoint( cx, cy ), MP2::OBJ_ALCHEMYLAB, Color::NONE );
324 break;
325 // mines: ore
326 case 0x02:
327 // mines: sulfur
328 case 0x03:
329 // mines: crystal
330 case 0x04:
331 // mines: gems
332 case 0x05:
333 // mines: gold
334 case 0x06:
335 map_captureobj.Set( Maps::GetIndexFromAbsPoint( cx, cy ), MP2::OBJ_MINES, Color::NONE );
336 break;
337 // lighthouse
338 case 0x64:
339 map_captureobj.Set( Maps::GetIndexFromAbsPoint( cx, cy ), MP2::OBJ_LIGHTHOUSE, Color::NONE );
340 break;
341 // dragon city
342 case 0x65:
343 map_captureobj.Set( Maps::GetIndexFromAbsPoint( cx, cy ), MP2::OBJ_DRAGONCITY, Color::NONE );
344 break;
345 // abandoned mines
346 case 0x67:
347 map_captureobj.Set( Maps::GetIndexFromAbsPoint( cx, cy ), MP2::OBJ_ABANDONEDMINE, Color::NONE );
348 break;
349 default:
350 DEBUG_LOG( DBG_GAME, DBG_WARN,
351 "kingdom block: "
352 << "unknown id: " << id << ", maps index: " << cx + cy * width );
353 break;
354 }
355 }
356
357 DEBUG_LOG( DBG_GAME, DBG_INFO, "read coord other resource, tellg: " << fs.tell() );
358 fs.seek( endof_addons + ( 72 * 3 ) + ( 144 * 3 ) );
359
360 // byte: num obelisks (01 default)
361 fs.skip( 1 );
362
363 // count final mp2 blocks
364 u32 countblock = 0;
365 while ( true ) {
366 const u32 l = fs.get();
367 const u32 h = fs.get();
368
369 if ( 0 == h && 0 == l )
370 break;
371 else {
372 countblock = 256 * h + l - 1;
373 }
374 }
375
376 // castle or heroes or (events, rumors, etc)
377 for ( u32 ii = 0; ii < countblock; ++ii ) {
378 s32 findobject = -1;
379
380 // read block
381 size_t sizeblock = fs.getLE16();
382 std::vector<u8> pblock = fs.getRaw( sizeblock );
383
384 for ( MapsIndexes::const_iterator it_index = vec_object.begin(); it_index != vec_object.end() && findobject < 0; ++it_index ) {
385 const Maps::Tiles & tile = vec_tiles[*it_index];
386
387 // orders(quantity2, quantity1)
388 u32 orders = ( tile.GetQuantity2() ? tile.GetQuantity2() : 0 );
389 orders <<= 8;
390 orders |= tile.GetQuantity1();
391
392 if ( orders && !( orders % 0x08 ) && ( ii + 1 == orders / 0x08 ) )
393 findobject = *it_index;
394 }
395
396 if ( 0 <= findobject ) {
397 const Maps::Tiles & tile = vec_tiles[findobject];
398
399 switch ( tile.GetObject() ) {
400 case MP2::OBJ_CASTLE:
401 // add castle
402 if ( MP2::SIZEOFMP2CASTLE != pblock.size() ) {
403 DEBUG_LOG( DBG_GAME, DBG_WARN,
404 "read castle: "
405 << "incorrect size block: " << pblock.size() );
406 }
407 else {
408 Castle * castle = getCastleEntrance( Maps::GetPoint( findobject ) );
409 if ( castle ) {
410 castle->LoadFromMP2( pblock );
411 map_captureobj.SetColor( tile.GetIndex(), castle->GetColor() );
412 }
413 else {
414 DEBUG_LOG( DBG_GAME, DBG_WARN,
415 "load castle: "
416 << "not found, index: " << findobject );
417 }
418 }
419 break;
420 case MP2::OBJ_RNDTOWN:
421 case MP2::OBJ_RNDCASTLE:
422 // add rnd castle
423 if ( MP2::SIZEOFMP2CASTLE != pblock.size() ) {
424 DEBUG_LOG( DBG_GAME, DBG_WARN,
425 "read castle: "
426 << "incorrect size block: " << pblock.size() );
427 }
428 else {
429 // Random castle's entrance tile is marked as OBJ_RNDCASTLE or OBJ_RNDTOWN instead of OBJ_CASTLE.
430 Castle * castle = getCastle( Maps::GetPoint( findobject ) );
431 if ( castle ) {
432 castle->LoadFromMP2( pblock );
433 Maps::UpdateCastleSprite( castle->GetCenter(), castle->GetRace(), castle->isCastle(), true );
434 Maps::ReplaceRandomCastleObjectId( castle->GetCenter() );
435 map_captureobj.SetColor( tile.GetIndex(), castle->GetColor() );
436 }
437 else {
438 DEBUG_LOG( DBG_GAME, DBG_WARN,
439 "load castle: "
440 << "not found, index: " << findobject );
441 }
442 }
443 break;
444 case MP2::OBJ_JAIL:
445 // add jail
446 if ( MP2::SIZEOFMP2HEROES != pblock.size() ) {
447 DEBUG_LOG( DBG_GAME, DBG_WARN,
448 "read heroes: "
449 << "incorrect size block: " << pblock.size() );
450 }
451 else {
452 int race = Race::KNGT;
453 switch ( pblock[0x3c] ) {
454 case 1:
455 race = Race::BARB;
456 break;
457 case 2:
458 race = Race::SORC;
459 break;
460 case 3:
461 race = Race::WRLK;
462 break;
463 case 4:
464 race = Race::WZRD;
465 break;
466 case 5:
467 race = Race::NECR;
468 break;
469 default:
470 break;
471 }
472
473 Heroes * hero = GetFreemanHeroes( race );
474
475 if ( hero ) {
476 hero->LoadFromMP2( findobject, Color::NONE, hero->GetRace(), StreamBuf( pblock ) );
477 hero->SetModes( Heroes::JAIL );
478 }
479 }
480 break;
481 case MP2::OBJ_HEROES:
482 // add heroes
483 if ( MP2::SIZEOFMP2HEROES != pblock.size() ) {
484 DEBUG_LOG( DBG_GAME, DBG_WARN,
485 "read heroes: "
486 << "incorrect size block: " << pblock.size() );
487 }
488 else {
489 std::pair<int, int> colorRace = Maps::Tiles::ColorRaceFromHeroSprite( tile.GetObjectSpriteIndex() );
490 const Kingdom & kingdom = GetKingdom( colorRace.first );
491
492 if ( colorRace.second == Race::RAND && colorRace.first != Color::NONE )
493 colorRace.second = kingdom.GetRace();
494
495 // check heroes max count
496 if ( kingdom.AllowRecruitHero( false, 0 ) ) {
497 Heroes * hero = nullptr;
498
499 if ( pblock[17] && pblock[18] < Heroes::BAX )
500 hero = vec_heroes.Get( pblock[18] );
501
502 if ( !hero || !hero->isFreeman() )
503 hero = vec_heroes.GetFreeman( colorRace.second );
504
505 if ( hero )
506 hero->LoadFromMP2( findobject, colorRace.first, colorRace.second, StreamBuf( pblock ) );
507 }
508 else {
509 DEBUG_LOG( DBG_GAME, DBG_WARN, "load heroes maximum" );
510 }
511 }
512 break;
513 case MP2::OBJ_SIGN:
514 case MP2::OBJ_BOTTLE:
515 // add sign or buttle
516 if ( MP2::SIZEOFMP2SIGN - 1 < pblock.size() && 0x01 == pblock[0] ) {
517 MapSign * obj = new MapSign();
518 obj->LoadFromMP2( findobject, StreamBuf( pblock ) );
519 map_objects.add( obj );
520 }
521 break;
522 case MP2::OBJ_EVENT:
523 // add event maps
524 if ( MP2::SIZEOFMP2EVENT - 1 < pblock.size() && 0x01 == pblock[0] ) {
525 MapEvent * obj = new MapEvent();
526 obj->LoadFromMP2( findobject, StreamBuf( pblock ) );
527 map_objects.add( obj );
528 }
529 break;
530 case MP2::OBJ_SPHINX:
531 // add riddle sphinx
532 if ( MP2::SIZEOFMP2RIDDLE - 1 < pblock.size() && 0x00 == pblock[0] ) {
533 MapSphinx * obj = new MapSphinx();
534 obj->LoadFromMP2( findobject, StreamBuf( pblock ) );
535 map_objects.add( obj );
536 }
537 break;
538 default:
539 break;
540 }
541 }
542 // other events
543 else if ( 0x00 == pblock[0] ) {
544 // add event day
545 if ( MP2::SIZEOFMP2EVENT - 1 < pblock.size() && 1 == pblock[42] ) {
546 vec_eventsday.emplace_back();
547 vec_eventsday.back().LoadFromMP2( StreamBuf( pblock ) );
548 }
549 // add rumors
550 else if ( MP2::SIZEOFMP2RUMOR - 1 < pblock.size() ) {
551 if ( pblock[8] ) {
552 vec_rumors.push_back( StreamBuf( &pblock[8], pblock.size() - 8 ).toString() );
553 DEBUG_LOG( DBG_GAME, DBG_INFO, "add rumors: " << vec_rumors.back() );
554 }
555 }
556 }
557 // debug
558 else {
559 DEBUG_LOG( DBG_GAME, DBG_WARN, "read maps: unknown block addons, size: " << pblock.size() );
560 }
561 }
562
563 fixCastleNames( vec_castles );
564
565 // clear artifact flags to correctly generate random artifacts
566 fheroes2::ResetArtifactStats();
567
568 const Settings & conf = Settings::Get();
569
570 // do not let the player get a random artifact that allows him to win the game
571 if ( ( conf.ConditionWins() & GameOver::WINS_ARTIFACT ) == GameOver::WINS_ARTIFACT && !conf.WinsFindUltimateArtifact() ) {
572 const Artifact art = conf.WinsFindArtifactID();
573
574 fheroes2::ExcludeArtifactFromRandom( art.GetID() );
575 }
576
577 ProcessNewMap();
578
579 DEBUG_LOG( DBG_GAME, DBG_INFO, "end load" );
580 return true;
581 }
582
ProcessNewMap()583 void World::ProcessNewMap()
584 {
585 // modify other objects
586 for ( size_t i = 0; i < vec_tiles.size(); ++i ) {
587 Maps::Tiles & tile = vec_tiles[i];
588 Maps::Tiles::fixTileObjectType( tile );
589
590 switch ( tile.GetObject() ) {
591 case MP2::OBJ_WITCHSHUT:
592 case MP2::OBJ_SHRINE1:
593 case MP2::OBJ_SHRINE2:
594 case MP2::OBJ_SHRINE3:
595 case MP2::OBJ_STONELITHS:
596 case MP2::OBJ_FOUNTAIN:
597 case MP2::OBJ_EVENT:
598 case MP2::OBJ_BOAT:
599 case MP2::OBJ_RNDARTIFACT:
600 case MP2::OBJ_RNDARTIFACT1:
601 case MP2::OBJ_RNDARTIFACT2:
602 case MP2::OBJ_RNDARTIFACT3:
603 case MP2::OBJ_RNDRESOURCE:
604 case MP2::OBJ_WATERCHEST:
605 case MP2::OBJ_TREASURECHEST:
606 case MP2::OBJ_ARTIFACT:
607 case MP2::OBJ_RESOURCE:
608 case MP2::OBJ_MAGICGARDEN:
609 case MP2::OBJ_WATERWHEEL:
610 case MP2::OBJ_WINDMILL:
611 case MP2::OBJ_WAGON:
612 case MP2::OBJ_SKELETON:
613 case MP2::OBJ_LEANTO:
614 case MP2::OBJ_CAMPFIRE:
615 case MP2::OBJ_FLOTSAM:
616 case MP2::OBJ_SHIPWRECKSURVIROR:
617 case MP2::OBJ_DERELICTSHIP:
618 case MP2::OBJ_SHIPWRECK:
619 case MP2::OBJ_GRAVEYARD:
620 case MP2::OBJ_PYRAMID:
621 case MP2::OBJ_DAEMONCAVE:
622 case MP2::OBJ_ABANDONEDMINE:
623 case MP2::OBJ_ALCHEMYLAB:
624 case MP2::OBJ_SAWMILL:
625 case MP2::OBJ_MINES:
626 case MP2::OBJ_TREEKNOWLEDGE:
627 case MP2::OBJ_BARRIER:
628 case MP2::OBJ_TRAVELLERTENT:
629 case MP2::OBJ_MONSTER:
630 case MP2::OBJ_RNDMONSTER:
631 case MP2::OBJ_RNDMONSTER1:
632 case MP2::OBJ_RNDMONSTER2:
633 case MP2::OBJ_RNDMONSTER3:
634 case MP2::OBJ_RNDMONSTER4:
635 case MP2::OBJ_ANCIENTLAMP:
636 case MP2::OBJ_WATCHTOWER:
637 case MP2::OBJ_EXCAVATION:
638 case MP2::OBJ_CAVE:
639 case MP2::OBJ_TREEHOUSE:
640 case MP2::OBJ_ARCHERHOUSE:
641 case MP2::OBJ_GOBLINHUT:
642 case MP2::OBJ_DWARFCOTT:
643 case MP2::OBJ_HALFLINGHOLE:
644 case MP2::OBJ_PEASANTHUT:
645 case MP2::OBJ_THATCHEDHUT:
646 case MP2::OBJ_RUINS:
647 case MP2::OBJ_TREECITY:
648 case MP2::OBJ_WAGONCAMP:
649 case MP2::OBJ_DESERTTENT:
650 case MP2::OBJ_TROLLBRIDGE:
651 case MP2::OBJ_DRAGONCITY:
652 case MP2::OBJ_CITYDEAD:
653 tile.QuantityUpdate();
654 break;
655
656 case MP2::OBJ_WATERALTAR:
657 case MP2::OBJ_AIRALTAR:
658 case MP2::OBJ_FIREALTAR:
659 case MP2::OBJ_EARTHALTAR:
660 case MP2::OBJ_BARROWMOUNDS:
661 tile.QuantityReset();
662 tile.QuantityUpdate();
663 break;
664
665 case MP2::OBJ_HEROES: {
666 // remove map editor sprite
667 if ( MP2::GetICNObject( tile.GetObjectTileset() ) == ICN::MINIHERO )
668 tile.Remove( tile.GetObjectUID() );
669
670 tile.SetHeroes( GetHeroes( Maps::GetPoint( static_cast<int32_t>( i ) ) ) );
671 break;
672 }
673
674 default:
675 break;
676 }
677 }
678
679 // add heroes to kingdoms
680 vec_kingdoms.AddHeroes( vec_heroes );
681
682 // add castles to kingdoms
683 vec_kingdoms.AddCastles( vec_castles );
684
685 const Settings & conf = Settings::Get();
686
687 // update wins, loss conditions
688 if ( GameOver::WINS_HERO & conf.ConditionWins() ) {
689 const Heroes * hero = GetHeroes( conf.WinsMapsPositionObject() );
690 heroes_cond_wins = hero ? hero->GetID() : Heroes::UNKNOWN;
691 }
692 if ( GameOver::LOSS_HERO & conf.ConditionLoss() ) {
693 Heroes * hero = GetHeroes( conf.LossMapsPositionObject() );
694 if ( hero ) {
695 heroes_cond_loss = hero->GetID();
696 hero->SetModes( Heroes::NOTDISMISS | Heroes::NOTDEFAULTS );
697 }
698 }
699
700 // Set Ultimate Artifact.
701 fheroes2::Point ultimate_pos;
702 MapsTiles::iterator it = std::find_if( vec_tiles.begin(), vec_tiles.end(), []( const Maps::Tiles & tile ) { return tile.isObject( MP2::OBJ_RNDULTIMATEARTIFACT ); } );
703 if ( vec_tiles.end() == it ) {
704 // generate position for ultimate
705 MapsIndexes pools;
706 pools.reserve( vec_tiles.size() / 2 );
707
708 for ( size_t i = 0; i < vec_tiles.size(); ++i ) {
709 const Maps::Tiles & tile = vec_tiles[i];
710 const int32_t x = tile.GetIndex() % width;
711 if ( x < ultimateArtifactOffset || x >= width - ultimateArtifactOffset )
712 continue;
713
714 const int32_t y = tile.GetIndex() / width;
715 if ( y < ultimateArtifactOffset || y >= height - ultimateArtifactOffset )
716 continue;
717
718 if ( tile.GoodForUltimateArtifact() )
719 pools.emplace_back( tile.GetIndex() );
720 }
721
722 if ( !pools.empty() ) {
723 const int32_t pos = Rand::Get( pools );
724 ultimate_artifact.Set( pos, getUltimateArtifact() );
725 ultimate_pos = Maps::GetPoint( pos );
726 }
727 }
728 else {
729 // remove ultimate artifact sprite
730 it->Remove( it->GetObjectUID() );
731 it->setAsEmpty();
732 ultimate_artifact.Set( it->GetIndex(), getUltimateArtifact() );
733 ultimate_pos = ( *it ).GetCenter();
734 }
735
736 PostLoad( true );
737
738 // play with hero
739 vec_kingdoms.ApplyPlayWithStartingHero();
740
741 // play with debug hero
742 if ( IS_DEVEL() ) {
743 // get first castle position
744 Kingdom & kingdom = GetKingdom( Color::GetFirst( Players::HumanColors() ) );
745
746 if ( !kingdom.GetCastles().empty() ) {
747 const Castle * castle = kingdom.GetCastles().front();
748 const fheroes2::Point & cp = castle->GetCenter();
749 Heroes * hero = vec_heroes.Get( Heroes::DEBUG_HERO );
750
751 if ( hero && !world.GetTiles( cp.x, cp.y + 1 ).GetHeroes() ) {
752 hero->Recruit( castle->GetColor(), fheroes2::Point( cp.x, cp.y + 1 ) );
753 }
754 }
755 }
756
757 vec_rumors.emplace_back( _( "The ultimate artifact is really the %{name}." ) );
758 StringReplace( vec_rumors.back(), "%{name}", ultimate_artifact.GetName() );
759
760 vec_rumors.emplace_back( _( "The ultimate artifact may be found in the %{name} regions of the world." ) );
761
762 if ( height / 3 > ultimate_pos.y ) {
763 if ( width / 3 > ultimate_pos.x )
764 StringReplace( vec_rumors.back(), "%{name}", _( "north-west" ) );
765 else if ( 2 * width / 3 > ultimate_pos.x )
766 StringReplace( vec_rumors.back(), "%{name}", _( "north" ) );
767 else
768 StringReplace( vec_rumors.back(), "%{name}", _( "north-east" ) );
769 }
770 else if ( 2 * height / 3 > ultimate_pos.y ) {
771 if ( width / 3 > ultimate_pos.x )
772 StringReplace( vec_rumors.back(), "%{name}", _( "west" ) );
773 else if ( 2 * width / 3 > ultimate_pos.x )
774 StringReplace( vec_rumors.back(), "%{name}", _( "center" ) );
775 else
776 StringReplace( vec_rumors.back(), "%{name}", _( "east" ) );
777 }
778 else {
779 if ( width / 3 > ultimate_pos.x )
780 StringReplace( vec_rumors.back(), "%{name}", _( "south-west" ) );
781 else if ( 2 * width / 3 > ultimate_pos.x )
782 StringReplace( vec_rumors.back(), "%{name}", _( "south" ) );
783 else
784 StringReplace( vec_rumors.back(), "%{name}", _( "south-east" ) );
785 }
786
787 vec_rumors.emplace_back( _( "The truth is out there." ) );
788 vec_rumors.emplace_back( _( "The dark side is stronger." ) );
789 vec_rumors.emplace_back( _( "The end of the world is near." ) );
790 vec_rumors.emplace_back( _( "The bones of Lord Slayer are buried in the foundation of the arena." ) );
791 vec_rumors.emplace_back( _( "A Black Dragon will take out a Titan any day of the week." ) );
792 vec_rumors.emplace_back( _( "He told her: Yada yada yada... and then she said: Blah, blah, blah..." ) );
793 vec_rumors.emplace_back( _( "An unknown force is being ressurected..." ) );
794
795 vec_rumors.emplace_back( _( "Check the newest version of game at\nhttps://github.com/ihhub/\nfheroes2/releases" ) );
796 }
797