1 /***************************************************************************
2 * Free Heroes of Might and Magic II: https://github.com/ihhub/fheroes2 *
3 * Copyright (C) 2020 *
4 * *
5 * This program is free software; you can redistribute it and/or modify *
6 * it under the terms of the GNU General Public License as published by *
7 * the Free Software Foundation; either version 2 of the License, or *
8 * (at your option) any later version. *
9 * *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
14 * *
15 * You should have received a copy of the GNU General Public License *
16 * along with this program; if not, write to the *
17 * Free Software Foundation, Inc., *
18 * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
19 ***************************************************************************/
20
21 #include <cassert>
22
23 #include "agg.h"
24 #include "agg_image.h"
25 #include "battle.h"
26 #include "campaign_data.h"
27 #include "campaign_savedata.h"
28 #include "campaign_scenariodata.h"
29 #include "cursor.h"
30 #include "dialog.h"
31 #include "game.h"
32 #include "game_credits.h"
33 #include "game_io.h"
34 #include "game_video.h"
35 #include "icn.h"
36 #include "race.h"
37 #include "settings.h"
38 #include "text.h"
39 #include "translations.h"
40 #include "world.h"
41
42 namespace
43 {
getCampaignIconOffsets(const int campaignId)44 std::vector<fheroes2::Point> getCampaignIconOffsets( const int campaignId )
45 {
46 switch ( campaignId ) {
47 case Campaign::ROLAND_CAMPAIGN:
48 return { { 0, 1 }, { 2, 1 }, { 3, 0 }, { 4, 1 }, { 6, 1 }, { 8, 1 }, { 10, 2 }, { 10, 0 }, { 12, 1 }, { 14, 1 } };
49 case Campaign::ARCHIBALD_CAMPAIGN:
50 return { { 0, 1 }, { 2, 1 }, { 4, 0 }, { 4, 2 }, { 6, 1 }, { 8, 1 }, { 9, 0 }, { 10, 1 }, { 12, 0 }, { 12, 2 }, { 14, 1 } };
51 case Campaign::PRICE_OF_LOYALTY_CAMPAIGN:
52 return { { 0, 0 }, { 2, 0 }, { 4, 1 }, { 4, 0 }, { 6, 1 }, { 7, 0 }, { 9, 1 }, { 10, 0 } };
53 case Campaign::DESCENDANTS_CAMPAIGN:
54 return { { 0, 1 }, { 2, 1 }, { 4, 0 }, { 4, 2 }, { 6, 1 }, { 8, 2 }, { 8, 0 }, { 10, 1 } };
55 case Campaign::WIZARDS_ISLE_CAMPAIGN:
56 return { { 0, 0 }, { 2, 0 }, { 4, 1 }, { 6, 0 } };
57 case Campaign::VOYAGE_HOME_CAMPAIGN:
58 return { { 0, 0 }, { 2, 0 }, { 4, 0 }, { 4, 1 } };
59 default:
60 // Implementing a new campaign? Add a new case!
61 assert( 0 );
62 return {};
63 }
64 }
65
66 enum ScenarioIcon : uint32_t
67 {
68 SCENARIO_ICON_CLEARED = 0,
69 SCENARIO_ICON_AVAILABLE = 1,
70 SCENARIO_ICON_UNAVAILABLE = 2,
71 };
72
DrawCampaignScenarioIcon(const int icnId,const int iconIdx,const fheroes2::Point & offset,const int posX,const int posY)73 void DrawCampaignScenarioIcon( const int icnId, const int iconIdx, const fheroes2::Point & offset, const int posX, const int posY )
74 {
75 const fheroes2::Sprite & icon = fheroes2::AGG::GetICN( icnId, iconIdx );
76 fheroes2::Blit( icon, fheroes2::Display::instance(), offset.x + posX, offset.y + posY );
77 }
78
DrawCampaignScenarioIcons(fheroes2::ButtonGroup & buttonGroup,const Campaign::CampaignData & campaignData,const fheroes2::Point & top,const int chosenScenarioId)79 void DrawCampaignScenarioIcons( fheroes2::ButtonGroup & buttonGroup, const Campaign::CampaignData & campaignData, const fheroes2::Point & top,
80 const int chosenScenarioId )
81 {
82 fheroes2::Display & display = fheroes2::Display::instance();
83
84 int campaignTrack = ICN::UNKNOWN;
85 int iconsId = ICN::UNKNOWN;
86 uint32_t iconStatusOffset = 0;
87 uint32_t selectedIconIdx = 0;
88
89 switch ( campaignData.getCampaignID() ) {
90 case Campaign::ROLAND_CAMPAIGN:
91 campaignTrack = ICN::CTRACK00;
92 iconsId = ICN::CAMPXTRG;
93 iconStatusOffset = 10;
94 selectedIconIdx = 14;
95 break;
96 case Campaign::ARCHIBALD_CAMPAIGN:
97 campaignTrack = ICN::CTRACK03;
98 iconsId = ICN::CAMPXTRE;
99 iconStatusOffset = 10;
100 selectedIconIdx = 17;
101 break;
102 case Campaign::PRICE_OF_LOYALTY_CAMPAIGN:
103 campaignTrack = ICN::X_TRACK1;
104 iconsId = ICN::X_CMPEXT;
105 iconStatusOffset = 0;
106 selectedIconIdx = 4;
107 break;
108 case Campaign::DESCENDANTS_CAMPAIGN:
109 campaignTrack = ICN::X_TRACK2;
110 iconsId = ICN::X_CMPEXT;
111 iconStatusOffset = 0;
112 selectedIconIdx = 7;
113 break;
114 case Campaign::WIZARDS_ISLE_CAMPAIGN:
115 campaignTrack = ICN::X_TRACK3;
116 iconsId = ICN::X_CMPEXT;
117 iconStatusOffset = 0;
118 selectedIconIdx = 10;
119 break;
120 case Campaign::VOYAGE_HOME_CAMPAIGN:
121 campaignTrack = ICN::X_TRACK4;
122 iconsId = ICN::X_CMPEXT;
123 iconStatusOffset = 0;
124 selectedIconIdx = 13;
125 break;
126 default:
127 // Implementing a new campaign? Add a new case!
128 assert( 0 );
129 break;
130 }
131
132 const fheroes2::Sprite & track = fheroes2::AGG::GetICN( campaignTrack, 0 );
133 const fheroes2::Point trackOffset( top.x + track.x(), top.y + track.y() );
134 fheroes2::Blit( track, display, trackOffset.x, trackOffset.y );
135
136 const std::vector<fheroes2::Point> & iconOffsets = getCampaignIconOffsets( campaignData.getCampaignID() );
137 const int deltaY = 42;
138 const int deltaX = 37;
139
140 const std::vector<Campaign::ScenarioData> & scenarios = campaignData.getAllScenarios();
141 const Campaign::CampaignSaveData & saveData = Campaign::CampaignSaveData::Get();
142
143 std::vector<int> prevScenarioNextMaps;
144 const std::vector<int> & clearedMaps = saveData.getFinishedMaps();
145 std::vector<int> availableMaps;
146 if ( chosenScenarioId >= 0 ) {
147 availableMaps.emplace_back( chosenScenarioId );
148 }
149 else {
150 availableMaps = saveData.isStarting() ? campaignData.getStartingScenarios() : campaignData.getScenariosAfter( saveData.getLastCompletedScenarioID() );
151 }
152
153 assert( iconOffsets.size() == scenarios.size() );
154
155 for ( size_t i = 0; i < scenarios.size(); ++i ) {
156 const int scenarioID = scenarios[i].getScenarioID();
157
158 assert( scenarioID >= 0 && static_cast<size_t>( scenarioID ) < iconOffsets.size() );
159 if ( scenarioID < 0 || static_cast<size_t>( scenarioID ) >= iconOffsets.size() ) {
160 continue;
161 }
162
163 fheroes2::Point offset = iconOffsets[scenarioID];
164 offset.x *= deltaX;
165 offset.y *= deltaY;
166
167 offset.x -= 2;
168 offset.y -= 2;
169
170 // available scenario (one of which should be selected)
171 if ( std::find( availableMaps.begin(), availableMaps.end(), scenarioID ) != availableMaps.end() ) {
172 const fheroes2::Sprite & availableIcon = fheroes2::AGG::GetICN( iconsId, iconStatusOffset + SCENARIO_ICON_AVAILABLE );
173 const fheroes2::Sprite & selectedIcon = fheroes2::AGG::GetICN( iconsId, selectedIconIdx );
174 buttonGroup.createButton( trackOffset.x + offset.x, trackOffset.y + offset.y, availableIcon, selectedIcon, static_cast<int>( i ) );
175 }
176 // cleared scenario
177 else if ( std::find( clearedMaps.begin(), clearedMaps.end(), static_cast<int>( i ) ) != clearedMaps.end() ) {
178 DrawCampaignScenarioIcon( iconsId, iconStatusOffset + SCENARIO_ICON_CLEARED, trackOffset, offset.x, offset.y );
179 }
180 else {
181 DrawCampaignScenarioIcon( iconsId, iconStatusOffset + SCENARIO_ICON_UNAVAILABLE, trackOffset, offset.x, offset.y );
182 }
183 }
184 }
185
DrawCampaignScenarioDescription(const Campaign::ScenarioData & scenario,const fheroes2::Point & top)186 void DrawCampaignScenarioDescription( const Campaign::ScenarioData & scenario, const fheroes2::Point & top )
187 {
188 const std::vector<Campaign::ScenarioBonusData> & bonuses = scenario.getBonuses();
189 TextBox mapName( scenario.getScenarioName(), Font::BIG, 200 );
190 mapName.Blit( top.x + 197, top.y + 97 - mapName.h() / 2 );
191
192 Text campaignMapId( std::to_string( scenario.getScenarioID() + 1 ), Font::BIG );
193 campaignMapId.Blit( top.x + 172 - campaignMapId.w() / 2, top.y + 97 - campaignMapId.h() / 2 );
194
195 TextBox mapDescription( scenario.getDescription(), Font::BIG, 356 );
196 mapDescription.Blit( top.x + 34, top.y + 132 );
197
198 const int textChoiceWidth = 150;
199 for ( size_t i = 0; i < bonuses.size(); ++i ) {
200 Text choice( bonuses[i].ToString(), Font::BIG );
201 choice.Blit( top.x + 425, top.y + 209 + 22 * static_cast<int>( i ) - choice.h() / 2, textChoiceWidth );
202 }
203 }
204
drawObtainedCampaignAwards(const std::vector<Campaign::CampaignAwardData> & obtainedAwards,const fheroes2::Point & top)205 void drawObtainedCampaignAwards( const std::vector<Campaign::CampaignAwardData> & obtainedAwards, const fheroes2::Point & top )
206 {
207 const int textAwardWidth = 180;
208
209 // if there are more than 3 awards, we need to reduce the offset between text so that it doesn't overflow out of the text box
210 const size_t awardCount = obtainedAwards.size();
211 const size_t indexEnd = awardCount <= 4 ? awardCount : 4;
212 const int yOffset = awardCount > 3 ? 16 : 22;
213
214 Text award;
215 for ( size_t i = 0; i < indexEnd; ++i ) {
216 if ( i < 3 )
217 award.Set( obtainedAwards[i].ToString(), Font::BIG );
218 else // if we have exactly 4 obtained awards, display the fourth award, otherwise show "and more..."
219 award.Set( awardCount == 4 ? obtainedAwards[i].ToString() : std::string( _( "and more..." ) ), Font::BIG );
220
221 if ( award.w() > textAwardWidth ) {
222 award.Blit( top.x + 425, top.y + 100 + yOffset * static_cast<int>( i ) - award.h() / 2, textAwardWidth );
223 }
224 else {
225 award.Blit( top.x + 425 + ( textAwardWidth - award.w() ) / 2, top.y + 100 + yOffset * static_cast<int>( i ) - award.h() / 2 );
226 }
227 }
228 }
229
replaceArmy(Army & army,const std::vector<Troop> & troops)230 void replaceArmy( Army & army, const std::vector<Troop> & troops )
231 {
232 army.Clean();
233 for ( size_t i = 0; i < troops.size(); ++i )
234 army.GetTroop( i )->Set( troops[i] );
235 }
236
setHeroAndArmyBonus(Heroes * hero,const int campaignID,const uint32_t currentScenarioID)237 void setHeroAndArmyBonus( Heroes * hero, const int campaignID, const uint32_t currentScenarioID )
238 {
239 switch ( campaignID ) {
240 case Campaign::ARCHIBALD_CAMPAIGN: {
241 if ( currentScenarioID != 6 ) {
242 assert( 0 ); // no other scenario has this bonus
243 return;
244 }
245 switch ( hero->GetRace() ) {
246 case Race::NECR:
247 replaceArmy( hero->GetArmy(), { { Monster::SKELETON, 50 }, { Monster::ROYAL_MUMMY, 18 }, { Monster::VAMPIRE_LORD, 8 } } );
248 break;
249 case Race::WRLK:
250 replaceArmy( hero->GetArmy(), { { Monster::CENTAUR, 40 }, { Monster::GARGOYLE, 24 }, { Monster::GRIFFIN, 18 } } );
251 break;
252 case Race::BARB:
253 replaceArmy( hero->GetArmy(), { { Monster::ORC_CHIEF, 12 }, { Monster::OGRE, 18 }, { Monster::GOBLIN, 40 } } );
254 break;
255 default:
256 assert( 0 ); // bonus changed?
257 }
258 const uint32_t exp = hero->GetExperience();
259 if ( exp < 5000 ) {
260 hero->IncreaseExperience( 5000 - exp, true );
261 }
262 break;
263 }
264 default:
265 assert( 0 ); // some new campaign that uses this bonus?
266 }
267 }
268
SetScenarioBonus(const int campaignID,const uint32_t currentScenarioID,const Campaign::ScenarioBonusData & scenarioBonus)269 void SetScenarioBonus( const int campaignID, const uint32_t currentScenarioID, const Campaign::ScenarioBonusData & scenarioBonus )
270 {
271 const Players & sortedPlayers = Settings::Get().GetPlayers();
272 for ( const Player * player : sortedPlayers ) {
273 if ( player == nullptr ) {
274 continue;
275 }
276
277 if ( !player->isControlHuman() )
278 continue;
279
280 Kingdom & kingdom = world.GetKingdom( player->GetColor() );
281 Heroes * bestHero = kingdom.GetBestHero();
282
283 switch ( scenarioBonus._type ) {
284 case Campaign::ScenarioBonusData::RESOURCES:
285 kingdom.AddFundsResource( Funds( scenarioBonus._subType, scenarioBonus._amount ) );
286 break;
287 case Campaign::ScenarioBonusData::ARTIFACT: {
288 assert( bestHero != nullptr );
289 if ( bestHero != nullptr ) {
290 bestHero->PickupArtifact( Artifact( scenarioBonus._subType ) );
291 }
292 break;
293 }
294 case Campaign::ScenarioBonusData::TROOP:
295 assert( bestHero != nullptr );
296 if ( bestHero != nullptr ) {
297 bestHero->GetArmy().JoinTroop( Troop( Monster( scenarioBonus._subType ), scenarioBonus._amount ) );
298 }
299 break;
300 case Campaign::ScenarioBonusData::SPELL: {
301 KingdomHeroes & heroes = kingdom.GetHeroes();
302 assert( !heroes.empty() );
303 if ( !heroes.empty() ) {
304 // TODO: make sure that the correct hero receives the spell. Right now it's a semi-hacky way to do this.
305 heroes.back()->AppendSpellToBook( scenarioBonus._subType, true );
306 }
307 break;
308 }
309 case Campaign::ScenarioBonusData::STARTING_RACE:
310 Players::SetPlayerRace( player->GetColor(), scenarioBonus._subType );
311 break;
312 case Campaign::ScenarioBonusData::STARTING_RACE_AND_ARMY:
313 assert( bestHero != nullptr );
314 if ( bestHero != nullptr ) {
315 setHeroAndArmyBonus( bestHero, campaignID, currentScenarioID );
316 }
317 break;
318 case Campaign::ScenarioBonusData::SKILL_PRIMARY:
319 assert( bestHero != nullptr );
320 if ( bestHero != nullptr ) {
321 for ( uint32_t i = 0; i < scenarioBonus._amount; ++i )
322 bestHero->IncreasePrimarySkill( scenarioBonus._subType );
323 }
324 break;
325 case Campaign::ScenarioBonusData::SKILL_SECONDARY:
326 assert( bestHero != nullptr );
327 if ( bestHero != nullptr ) {
328 bestHero->LearnSkill( Skill::Secondary( scenarioBonus._subType, scenarioBonus._amount ) );
329 }
330 break;
331 default:
332 assert( 0 );
333 }
334 }
335 }
336
337 // apply only the ones that are applied at the start (artifact, spell, carry-over troops)
338 // the rest will be applied based on the situation required
applyObtainedCampaignAwards(const uint32_t currentScenarioID,const std::vector<Campaign::CampaignAwardData> & awards)339 void applyObtainedCampaignAwards( const uint32_t currentScenarioID, const std::vector<Campaign::CampaignAwardData> & awards )
340 {
341 const Players & sortedPlayers = Settings::Get().GetPlayers();
342 Kingdom & humanKingdom = world.GetKingdom( Players::HumanColors() );
343
344 for ( size_t i = 0; i < awards.size(); ++i ) {
345 if ( currentScenarioID < awards[i]._startScenarioID )
346 continue;
347
348 switch ( awards[i]._type ) {
349 case Campaign::CampaignAwardData::TYPE_GET_ARTIFACT:
350 humanKingdom.GetBestHero()->PickupArtifact( Artifact( awards[i]._subType ) );
351 break;
352 case Campaign::CampaignAwardData::TYPE_GET_SPELL:
353 humanKingdom.GetBestHero()->AppendSpellToBook( awards[i]._subType, true );
354 break;
355 case Campaign::CampaignAwardData::TYPE_DEFEAT_ENEMY_HERO:
356 for ( const Player * player : sortedPlayers ) {
357 Kingdom & kingdom = world.GetKingdom( player->GetColor() );
358 const KingdomHeroes & heroes = kingdom.GetHeroes();
359
360 for ( size_t j = 0; j < heroes.size(); ++j ) {
361 if ( heroes[j]->GetID() == static_cast<int>( awards[i]._subType ) ) {
362 heroes[j]->SetKillerColor( humanKingdom.GetColor() );
363 heroes[j]->SetFreeman( Battle::RESULT_LOSS );
364 break;
365 }
366 }
367 }
368 break;
369 case Campaign::CampaignAwardData::TYPE_CARRY_OVER_FORCES:
370 replaceArmy( humanKingdom.GetBestHero()->GetArmy(), Campaign::CampaignSaveData::Get().getCarryOverTroops() );
371 break;
372 }
373 }
374 }
375
playPreviosScenarioVideo()376 void playPreviosScenarioVideo()
377 {
378 const Campaign::CampaignSaveData & saveData = Campaign::CampaignSaveData::Get();
379 if ( saveData.isStarting() ) {
380 return;
381 }
382
383 const int lastCompletedScenarioID = saveData.getLastCompletedScenarioID();
384 const Campaign::CampaignData & campaignData = Campaign::CampaignData::getCampaignData( saveData.getCampaignID() );
385
386 const std::vector<Campaign::ScenarioData> & scenarios = campaignData.getAllScenarios();
387 assert( lastCompletedScenarioID >= 0 && static_cast<size_t>( lastCompletedScenarioID ) < scenarios.size() );
388 const Campaign::ScenarioData & completedScenario = scenarios[lastCompletedScenarioID];
389
390 if ( !completedScenario.getEndScenarioVideoPlayback().empty() ) {
391 AGG::ResetMixer();
392
393 for ( const Campaign::ScenarioIntroVideoInfo & videoInfo : completedScenario.getEndScenarioVideoPlayback() ) {
394 Video::ShowVideo( videoInfo.fileName, videoInfo.action );
395 }
396
397 AGG::ResetMixer();
398 }
399 }
400
playCurrentScenarioVideo()401 void playCurrentScenarioVideo()
402 {
403 const Campaign::CampaignSaveData & saveData = Campaign::CampaignSaveData::Get();
404
405 const int chosenScenarioID = saveData.getCurrentScenarioID();
406 const Campaign::CampaignData & campaignData = Campaign::CampaignData::getCampaignData( saveData.getCampaignID() );
407
408 const std::vector<Campaign::ScenarioData> & scenarios = campaignData.getAllScenarios();
409 assert( chosenScenarioID >= 0 && static_cast<size_t>( chosenScenarioID ) < scenarios.size() );
410 const Campaign::ScenarioData & scenario = scenarios[chosenScenarioID];
411
412 if ( !scenario.getStartScenarioVideoPlayback().empty() ) {
413 AGG::ResetMixer();
414
415 for ( const Campaign::ScenarioIntroVideoInfo & videoInfo : scenario.getStartScenarioVideoPlayback() ) {
416 Video::ShowVideo( videoInfo.fileName, videoInfo.action );
417 }
418
419 AGG::ResetMixer();
420 }
421 }
422
getCampaignButtonId(const int campaignId)423 int getCampaignButtonId( const int campaignId )
424 {
425 switch ( campaignId ) {
426 case Campaign::ROLAND_CAMPAIGN:
427 return ICN::CAMPXTRG;
428 case Campaign::ARCHIBALD_CAMPAIGN:
429 return ICN::CAMPXTRE;
430 case Campaign::PRICE_OF_LOYALTY_CAMPAIGN:
431 case Campaign::DESCENDANTS_CAMPAIGN:
432 case Campaign::WIZARDS_ISLE_CAMPAIGN:
433 case Campaign::VOYAGE_HOME_CAMPAIGN:
434 return ICN::X_CMPBTN;
435 default:
436 // Implementing a new campaign? Add a new case!
437 assert( 0 );
438 return ICN::UNKNOWN;
439 }
440 }
441
drawCampaignNameHeader(const int campaignId,fheroes2::Image & output,const fheroes2::Point & offset)442 void drawCampaignNameHeader( const int campaignId, fheroes2::Image & output, const fheroes2::Point & offset )
443 {
444 // Add extra image header if supported
445 uint32_t campaignNameHeader = ICN::UNKNOWN;
446
447 switch ( campaignId ) {
448 case Campaign::PRICE_OF_LOYALTY_CAMPAIGN:
449 campaignNameHeader = 15;
450 break;
451 case Campaign::DESCENDANTS_CAMPAIGN:
452 campaignNameHeader = 16;
453 break;
454 case Campaign::WIZARDS_ISLE_CAMPAIGN:
455 campaignNameHeader = 17;
456 break;
457 case Campaign::VOYAGE_HOME_CAMPAIGN:
458 campaignNameHeader = 18;
459 break;
460 default:
461 return;
462 }
463
464 const fheroes2::Sprite & header = fheroes2::AGG::GetICN( ICN::X_CMPEXT, campaignNameHeader );
465 fheroes2::Blit( header, output, offset.x + 24, offset.y + 25 );
466 }
467
playCampaignMusic(const int campaignId)468 void playCampaignMusic( const int campaignId )
469 {
470 switch ( campaignId ) {
471 case Campaign::ROLAND_CAMPAIGN:
472 case Campaign::PRICE_OF_LOYALTY_CAMPAIGN:
473 case Campaign::DESCENDANTS_CAMPAIGN:
474 case Campaign::WIZARDS_ISLE_CAMPAIGN:
475 case Campaign::VOYAGE_HOME_CAMPAIGN:
476 AGG::PlayMusic( MUS::ROLAND_CAMPAIGN_SCREEN, true );
477 break;
478 case Campaign::ARCHIBALD_CAMPAIGN:
479 AGG::PlayMusic( MUS::ARCHIBALD_CAMPAIGN_SCREEN, true );
480 break;
481 default:
482 // Implementing a new campaign? Add a new case!
483 assert( 0 );
484 break;
485 }
486 }
487 }
488
isSuccessionWarsCampaignPresent()489 bool Game::isSuccessionWarsCampaignPresent()
490 {
491 return Campaign::CampaignData::getCampaignData( Campaign::ROLAND_CAMPAIGN ).isAllCampaignMapsPresent()
492 && Campaign::CampaignData::getCampaignData( Campaign::ARCHIBALD_CAMPAIGN ).isAllCampaignMapsPresent();
493 }
494
isPriceOfLoyaltyCampaignPresent()495 bool Game::isPriceOfLoyaltyCampaignPresent()
496 {
497 // We need to check game resources as well.
498 if ( fheroes2::AGG::GetICN( ICN::X_LOADCM, 0 ).empty() || fheroes2::AGG::GetICN( ICN::X_IVY, 0 ).empty() ) {
499 return false;
500 }
501
502 return Campaign::CampaignData::getCampaignData( Campaign::PRICE_OF_LOYALTY_CAMPAIGN ).isAllCampaignMapsPresent()
503 && Campaign::CampaignData::getCampaignData( Campaign::VOYAGE_HOME_CAMPAIGN ).isAllCampaignMapsPresent()
504 && Campaign::CampaignData::getCampaignData( Campaign::WIZARDS_ISLE_CAMPAIGN ).isAllCampaignMapsPresent()
505 && Campaign::CampaignData::getCampaignData( Campaign::DESCENDANTS_CAMPAIGN ).isAllCampaignMapsPresent();
506 }
507
CompleteCampaignScenario(const bool isLoadingSaveFile)508 fheroes2::GameMode Game::CompleteCampaignScenario( const bool isLoadingSaveFile )
509 {
510 Campaign::CampaignSaveData & saveData = Campaign::CampaignSaveData::Get();
511
512 if ( !isLoadingSaveFile ) {
513 saveData.addCurrentMapToFinished();
514 saveData.addDaysPassed( world.CountDay() );
515 Game::SaveCompletedCampaignScenario();
516 }
517
518 const int lastCompletedScenarioID = saveData.getLastCompletedScenarioID();
519 const Campaign::CampaignData & campaignData = Campaign::CampaignData::getCampaignData( saveData.getCampaignID() );
520
521 const std::vector<Campaign::CampaignAwardData> obtainableAwards
522 = Campaign::CampaignAwardData::getCampaignAwardData( saveData.getCampaignID(), lastCompletedScenarioID );
523
524 // TODO: Check for awards that have to be obtained with 'freak' conditions
525 for ( size_t i = 0; i < obtainableAwards.size(); ++i ) {
526 const uint32_t awardType = obtainableAwards[i]._type;
527
528 if ( awardType == Campaign::CampaignAwardData::AwardType::TYPE_CARRY_OVER_FORCES ) {
529 Kingdom & humanKingdom = world.GetKingdom( Players::HumanColors() );
530
531 const Heroes * lastBattleWinHero = humanKingdom.GetLastBattleWinHero();
532
533 if ( lastBattleWinHero )
534 saveData.setCarryOverTroops( lastBattleWinHero->GetArmy() );
535 }
536
537 saveData.addCampaignAward( obtainableAwards[i]._id );
538
539 // after adding an artifact award, check whether the artifacts can be assembled into something else
540 if ( awardType == Campaign::CampaignAwardData::AwardType::TYPE_GET_ARTIFACT ) {
541 const std::vector<Campaign::CampaignAwardData> obtainedAwards = saveData.getObtainedCampaignAwards();
542 std::map<uint32_t, int> artifactAwardIDs;
543 BagArtifacts bagArtifacts;
544
545 for ( const Campaign::CampaignAwardData & awardData : obtainedAwards ) {
546 if ( awardData._type != Campaign::CampaignAwardData::AwardType::TYPE_GET_ARTIFACT )
547 continue;
548
549 artifactAwardIDs.emplace( awardData._subType, awardData._id );
550 bagArtifacts.PushArtifact( awardData._subType );
551 saveData.removeCampaignAward( awardData._id );
552 }
553
554 // add the assembled artifact's campaign award to artifactAwards
555 for ( const Campaign::CampaignAwardData & awardData : Campaign::CampaignAwardData::getExtraCampaignAwardData( saveData.getCampaignID() ) ) {
556 if ( awardData._type != Campaign::CampaignAwardData::AwardType::TYPE_GET_ARTIFACT )
557 continue;
558
559 artifactAwardIDs.emplace( awardData._subType, awardData._id );
560 }
561
562 bagArtifacts.assembleArtifactSetIfPossible();
563
564 for ( const Artifact & artifact : bagArtifacts ) {
565 if ( !artifact.isValid() )
566 continue;
567
568 const auto foundArtifact = artifactAwardIDs.find( artifact.GetID() );
569 if ( foundArtifact != artifactAwardIDs.end() )
570 saveData.addCampaignAward( foundArtifact->second );
571 }
572 }
573 }
574
575 playPreviosScenarioVideo();
576
577 // TODO: do proper calc based on all scenarios cleared?
578 if ( campaignData.isLastScenario( lastCompletedScenarioID ) ) {
579 Game::ShowCredits();
580
581 AGG::ResetMixer();
582 Video::ShowVideo( "WIN.SMK", Video::VideoAction::WAIT_FOR_USER_INPUT );
583 return fheroes2::GameMode::HIGHSCORES;
584 }
585
586 const int firstNextMap = campaignData.getScenariosAfter( lastCompletedScenarioID ).front();
587 saveData.setCurrentScenarioID( firstNextMap );
588 return fheroes2::GameMode::SELECT_CAMPAIGN_SCENARIO;
589 }
590
SelectCampaignScenario(const fheroes2::GameMode prevMode,const bool allowToRestart)591 fheroes2::GameMode Game::SelectCampaignScenario( const fheroes2::GameMode prevMode, const bool allowToRestart )
592 {
593 fheroes2::Display & display = fheroes2::Display::instance();
594 display.fill( 0 );
595 Settings & conf = Settings::Get();
596
597 // setup cursor
598 const CursorRestorer cursorRestorer( true, Cursor::POINTER );
599
600 Campaign::CampaignSaveData & campaignSaveData = Campaign::CampaignSaveData::Get();
601 const int chosenCampaignID = campaignSaveData.getCampaignID();
602
603 const Campaign::CampaignData & campaignData = Campaign::CampaignData::getCampaignData( chosenCampaignID );
604
605 const int chosenScenarioID = campaignSaveData.getCurrentScenarioID();
606 const std::vector<Campaign::ScenarioData> & scenarios = campaignData.getAllScenarios();
607 const Campaign::ScenarioData & scenario = scenarios[chosenScenarioID];
608
609 if ( !allowToRestart ) {
610 playCurrentScenarioVideo();
611 }
612
613 playCampaignMusic( chosenCampaignID );
614
615 int backgroundIconID = ICN::UNKNOWN;
616
617 switch ( chosenCampaignID ) {
618 case Campaign::ROLAND_CAMPAIGN:
619 backgroundIconID = ICN::CAMPBKGG;
620 break;
621 case Campaign::ARCHIBALD_CAMPAIGN:
622 backgroundIconID = ICN::CAMPBKGE;
623 break;
624 // PoL campaigns use the same background, but different headers. TODO: Implement the headers
625 case Campaign::PRICE_OF_LOYALTY_CAMPAIGN:
626 case Campaign::DESCENDANTS_CAMPAIGN:
627 case Campaign::WIZARDS_ISLE_CAMPAIGN:
628 case Campaign::VOYAGE_HOME_CAMPAIGN:
629 backgroundIconID = ICN::X_CMPBKG;
630 break;
631 default:
632 // Implementing a new campaign? Add a new case!
633 assert( 0 );
634 break;
635 }
636
637 const fheroes2::Sprite & backgroundImage = fheroes2::AGG::GetICN( backgroundIconID, 0 );
638 const fheroes2::Point top( ( display.width() - backgroundImage.width() ) / 2, ( display.height() - backgroundImage.height() ) / 2 );
639
640 fheroes2::Blit( backgroundImage, display, top.x, top.y );
641 drawCampaignNameHeader( chosenCampaignID, display, top );
642
643 const int buttonIconID = getCampaignButtonId( chosenCampaignID );
644 fheroes2::Button buttonViewIntro( top.x + 22, top.y + 431, buttonIconID, 0, 1 );
645 fheroes2::Button buttonRestart( top.x + 195, top.y + 431, buttonIconID, 2, 3 );
646 fheroes2::Button buttonOk( top.x + 367, top.y + 431, buttonIconID, 4, 5 );
647 fheroes2::Button buttonCancel( top.x + 511, top.y + 431, buttonIconID, 6, 7 );
648
649 // create scenario bonus choice buttons
650 fheroes2::ButtonGroup buttonChoices;
651 fheroes2::OptionButtonGroup optionButtonGroup;
652
653 Campaign::ScenarioBonusData scenarioBonus;
654 const std::vector<Campaign::ScenarioBonusData> & bonusChoices = scenario.getBonuses();
655
656 const fheroes2::Point optionButtonOffset( 590, 199 );
657 const int32_t optionButtonStep = 22;
658
659 const fheroes2::Sprite & pressedButton = fheroes2::AGG::GetICN( ICN::CAMPXTRG, allowToRestart ? 9 : 8 );
660 fheroes2::Sprite releaseButton( pressedButton.width(), pressedButton.height(), pressedButton.x(), pressedButton.y() );
661 fheroes2::Copy( backgroundImage, optionButtonOffset.x + pressedButton.x(), optionButtonOffset.y + pressedButton.y(), releaseButton, 0, 0, releaseButton.width(),
662 releaseButton.height() );
663
664 const uint32_t bonusChoiceCount = static_cast<uint32_t>( scenario.getBonuses().size() );
665 for ( uint32_t i = 0; i < bonusChoiceCount; ++i ) {
666 buttonChoices.createButton( optionButtonOffset.x + top.x, optionButtonOffset.y + optionButtonStep * i + top.y, releaseButton, pressedButton, i );
667 optionButtonGroup.addButton( &buttonChoices.button( i ) );
668 }
669
670 // in case there's no bonus for the map
671 if ( bonusChoiceCount > 0 ) {
672 scenarioBonus = bonusChoices[0];
673 buttonChoices.button( 0 ).press();
674 }
675
676 buttonViewIntro.draw();
677
678 if ( !scenario.isMapFilePresent() ) {
679 buttonOk.disable();
680 }
681
682 if ( allowToRestart ) {
683 buttonOk.disable();
684 buttonOk.hide();
685 buttonRestart.draw();
686 }
687 else {
688 buttonRestart.disable();
689 buttonRestart.hide();
690 buttonOk.draw();
691 }
692
693 buttonCancel.draw();
694
695 for ( uint32_t i = 0; i < bonusChoiceCount; ++i )
696 buttonChoices.button( i ).draw();
697
698 Text textDaysSpent( std::to_string( campaignSaveData.getDaysPassed() ), Font::BIG );
699 textDaysSpent.Blit( top.x + 582 - textDaysSpent.w() / 2, top.y + 31 );
700
701 DrawCampaignScenarioDescription( scenario, top );
702 drawObtainedCampaignAwards( campaignSaveData.getObtainedCampaignAwards(), top );
703
704 std::vector<int> selectableScenarios;
705 if ( allowToRestart ) {
706 selectableScenarios.emplace_back( chosenScenarioID );
707 }
708 else {
709 selectableScenarios
710 = campaignSaveData.isStarting() ? campaignData.getStartingScenarios() : campaignData.getScenariosAfter( campaignSaveData.getLastCompletedScenarioID() );
711 }
712
713 const uint32_t selectableScenariosCount = static_cast<uint32_t>( selectableScenarios.size() );
714
715 fheroes2::ButtonGroup selectableScenarioButtons;
716
717 const int highlightedScenarioId = allowToRestart ? chosenScenarioID : -1;
718 DrawCampaignScenarioIcons( selectableScenarioButtons, campaignData, top, highlightedScenarioId );
719
720 for ( uint32_t i = 0; i < selectableScenariosCount; ++i ) {
721 if ( chosenScenarioID == selectableScenarios[i] )
722 selectableScenarioButtons.button( i ).press();
723
724 selectableScenarioButtons.button( i ).draw();
725 }
726
727 LocalEvent & le = LocalEvent::Get();
728
729 display.render();
730
731 while ( le.HandleEvents() ) {
732 le.MousePressLeft( buttonCancel.area() ) ? buttonCancel.drawOnPress() : buttonCancel.drawOnRelease();
733 le.MousePressLeft( buttonOk.area() ) ? buttonOk.drawOnPress() : buttonOk.drawOnRelease();
734 le.MousePressLeft( buttonViewIntro.area() ) ? buttonViewIntro.drawOnPress() : buttonViewIntro.drawOnRelease();
735
736 if ( allowToRestart ) {
737 le.MousePressLeft( buttonRestart.area() ) ? buttonRestart.drawOnPress() : buttonRestart.drawOnRelease();
738 }
739 else {
740 for ( uint32_t i = 0; i < bonusChoiceCount; ++i ) {
741 if ( le.MousePressLeft( buttonChoices.button( i ).area() ) ) {
742 buttonChoices.button( i ).press();
743 optionButtonGroup.draw();
744 scenarioBonus = bonusChoices[i];
745
746 break;
747 }
748 }
749 }
750
751 for ( uint32_t i = 0; i < selectableScenariosCount; ++i ) {
752 if ( chosenScenarioID != selectableScenarios[i] && le.MousePressLeft( selectableScenarioButtons.button( i ).area() ) ) {
753 campaignSaveData.setCurrentScenarioID( selectableScenarios[i] );
754 return fheroes2::GameMode::SELECT_CAMPAIGN_SCENARIO;
755 }
756 }
757
758 if ( le.MouseClickLeft( buttonCancel.area() ) || HotKeyPressEvent( EVENT_DEFAULT_EXIT ) ) {
759 return prevMode;
760 }
761 if ( ( buttonOk.isEnabled() && ( le.MouseClickLeft( buttonOk.area() ) || HotKeyPressEvent( EVENT_DEFAULT_READY ) ) )
762 || ( buttonRestart.isEnabled() && le.MouseClickLeft( buttonRestart.area() ) ) ) {
763 const Maps::FileInfo mapInfo = scenario.loadMap();
764 conf.SetCurrentFileInfo( mapInfo );
765
766 // starting faction scenario bonus has to be called before players.SetStartGame()
767 if ( scenarioBonus._type == Campaign::ScenarioBonusData::STARTING_RACE || scenarioBonus._type == Campaign::ScenarioBonusData::STARTING_RACE_AND_ARMY ) {
768 // but the army has to be set after starting the game, so first only set the race
769 SetScenarioBonus( campaignSaveData.getCampaignID(), chosenScenarioID,
770 { Campaign::ScenarioBonusData::STARTING_RACE, scenarioBonus._subType, scenarioBonus._amount } );
771 }
772
773 Players & players = conf.GetPlayers();
774 players.SetStartGame();
775 if ( conf.ExtGameUseFade() )
776 fheroes2::FadeDisplay();
777
778 fheroes2::ImageRestorer restorer( display );
779 Game::ShowMapLoadingText();
780 conf.SetGameType( Game::TYPE_CAMPAIGN );
781
782 if ( !world.LoadMapMP2( mapInfo.file ) ) {
783 Dialog::Message( _( "Campaign Scenario loading failure" ), _( "Please make sure that campaign files are correct and present." ), Font::BIG, Dialog::OK );
784 conf.SetCurrentFileInfo( Maps::FileInfo() );
785 continue;
786 }
787
788 restorer.reset();
789
790 // meanwhile, the others should be called after players.SetStartGame()
791 if ( scenarioBonus._type != Campaign::ScenarioBonusData::STARTING_RACE ) {
792 SetScenarioBonus( campaignSaveData.getCampaignID(), chosenScenarioID, scenarioBonus );
793 }
794
795 applyObtainedCampaignAwards( chosenScenarioID, campaignSaveData.getObtainedCampaignAwards() );
796
797 campaignSaveData.setCurrentScenarioBonus( scenarioBonus );
798 campaignSaveData.setCurrentScenarioID( chosenScenarioID );
799
800 return fheroes2::GameMode::START_GAME;
801 }
802 else if ( le.MouseClickLeft( buttonViewIntro.area() ) ) {
803 AGG::ResetMixer();
804 fheroes2::ImageRestorer restorer( display, top.x, top.y, backgroundImage.width(), backgroundImage.height() );
805 playPreviosScenarioVideo();
806 playCurrentScenarioVideo();
807
808 playCampaignMusic( chosenCampaignID );
809 }
810 }
811
812 return prevMode;
813 }
814