From 5e7e4efd19f8c25d6dd30b552878f9996eca4941 Mon Sep 17 00:00:00 2001 From: Ihar Hubchyk Date: Mon, 7 Feb 2022 22:44:32 +0800 Subject: [PATCH] Add betrayal scenario support for original campaign (#4968) --- src/fheroes2/campaign/campaign_data.cpp | 372 ++++++++++------- src/fheroes2/campaign/campaign_data.h | 13 +- src/fheroes2/campaign/campaign_savedata.cpp | 56 ++- src/fheroes2/campaign/campaign_savedata.h | 30 +- .../campaign/campaign_scenariodata.cpp | 46 ++- src/fheroes2/campaign/campaign_scenariodata.h | 57 ++- src/fheroes2/game/game_campaign.cpp | 381 +++++++++++++----- src/fheroes2/game/game_io.cpp | 10 +- src/fheroes2/game/game_newgame.cpp | 14 +- src/fheroes2/kingdom/week.cpp | 4 +- src/fheroes2/system/save_format_version.h | 3 +- src/fheroes2/world/world.cpp | 4 +- 12 files changed, 653 insertions(+), 337 deletions(-) diff --git a/src/fheroes2/campaign/campaign_data.cpp b/src/fheroes2/campaign/campaign_data.cpp index 5c9e16f449d..1bad739e796 100644 --- a/src/fheroes2/campaign/campaign_data.cpp +++ b/src/fheroes2/campaign/campaign_data.cpp @@ -25,6 +25,8 @@ #include "resource.h" #include "spell.h" #include "translations.h" + +#include #include namespace @@ -51,6 +53,8 @@ namespace case 8: obtainableAwards.emplace_back( 4, Campaign::CampaignAwardData::TYPE_DEFEAT_ENEMY_HERO, Heroes::CORLAGON, 0, 9 ); break; + default: + break; } return obtainableAwards; @@ -77,6 +81,8 @@ namespace case 9: obtainableAwards.emplace_back( 6, Campaign::CampaignAwardData::TYPE_CARRY_OVER_FORCES, 0 ); break; + default: + break; } return obtainableAwards; @@ -105,6 +111,8 @@ namespace // seems that Kraeger is a custom name for Dainwin in this case obtainableAwards.emplace_back( 5, Campaign::CampaignAwardData::TYPE_DEFEAT_ENEMY_HERO, Heroes::DAINWIN, _( "Kraeger defeated" ) ); break; + default: + break; } return obtainableAwards; @@ -121,6 +129,8 @@ namespace case 2: obtainableAwards.emplace_back( 1, Campaign::CampaignAwardData::TYPE_GET_ARTIFACT, Artifact::SPHERE_NEGATION ); break; + default: + break; } return obtainableAwards; @@ -143,6 +153,8 @@ namespace case 6: obtainableAwards.emplace_back( 3, Campaign::CampaignAwardData::TYPE_CREATURE_ALLIANCE, Monster::ELF, _( "Elven Alliance" ) ); break; + default: + break; } return obtainableAwards; @@ -150,11 +162,13 @@ namespace Campaign::CampaignData getRolandCampaignData() { - const std::string rolandCampaignScenarioNames[10] - = { gettext_noop( "Force of Arms" ), gettext_noop( "Annexation" ), gettext_noop( "Save the Dwarves" ), gettext_noop( "Carator Mines" ), - gettext_noop( "Turning Point" ), gettext_noop( "Defender" ), gettext_noop( "The Gauntlet" ), gettext_noop( "The Crown" ), - gettext_noop( "Corlagon's Defense" ), gettext_noop( "Final Justice" ) }; - const std::string rolandCampaignDescription[10] = { + const int scenarioCount = 11; + + const std::array scenarioName + = { gettext_noop( "Force of Arms" ), gettext_noop( "Annexation" ), gettext_noop( "Save the Dwarves" ), gettext_noop( "Carator Mines" ), + gettext_noop( "Turning Point" ), gettext_noop( "Defender" ), gettext_noop( "The Gauntlet" ), gettext_noop( "The Crown" ), + gettext_noop( "Corlagon's Defense" ), gettext_noop( "Final Justice" ), gettext_noop( "Betrayal" ) }; + const std::array scenarioDescription = { gettext_noop( "Roland needs you to defeat the lords near his castle to begin his war of rebellion against his brother. They are not allied with each other, so they will spend" " most of their time fighting with one another. Victory is yours when you have defeated all of their castles and heroes." ), @@ -172,70 +186,82 @@ namespace gettext_noop( "Find the Crown before Archibald's heroes find it. Roland will need the Crown for the final battle against Archibald." ), gettext_noop( "Three allied enemies stand before you and victory, including Lord Corlagon. Roland is in a castle to the northwest, and you will lose if he falls to the enemy. Remember that capturing Lord Corlagon will ensure that he will not fight against you in the final scenario." ), + gettext_noop( "This is the final battle. Both you and your enemy are armed to the teeth, and all are allied against you. Capture Archibald to end the war!" ), gettext_noop( - "This is the final battle. Both you and your enemy are armed to the teeth, and all are allied against you. Capture Archibald to end the war!" ) }; + "Switching sides leaves you with three castles against the enemy's one. This battle will be the easiest one you will face for the rest of the war...traitor." ) }; std::vector scenarioDatas; - scenarioDatas.reserve( 10 ); + scenarioDatas.reserve( scenarioCount ); - scenarioDatas.emplace_back( 0, std::vector{ 1 }, Campaign::ScenarioBonusData::getCampaignBonusData( 0, 0 ), "CAMPG01.H2C", rolandCampaignScenarioNames[0], - rolandCampaignDescription[0], + std::vector scenarioInfo; + scenarioInfo.reserve( scenarioCount ); + for ( int i = 0; i < scenarioCount; ++i ) { + scenarioInfo.emplace_back( Campaign::ROLAND_CAMPAIGN, i ); + } + + scenarioDatas.emplace_back( scenarioInfo[0], std::vector{ scenarioInfo[1] }, "CAMPG01.H2C", scenarioName[0], scenarioDescription[0], Campaign::VideoSequence{ { "GOOD01V.SMK", Video::VideoAction::IGNORE_VIDEO }, { "GOOD01.SMK", Video::VideoAction::PLAY_TILL_AUDIO_END } }, emptyPlayback ); - scenarioDatas.emplace_back( 1, std::vector{ 2, 3 }, Campaign::ScenarioBonusData::getCampaignBonusData( 0, 1 ), "CAMPG02.H2C", rolandCampaignScenarioNames[1], - rolandCampaignDescription[1], + scenarioDatas.emplace_back( scenarioInfo[1], std::vector{ scenarioInfo[2], scenarioInfo[3] }, "CAMPG02.H2C", scenarioName[1], + scenarioDescription[1], Campaign::VideoSequence{ { "GOOD02W.SMK", Video::VideoAction::IGNORE_VIDEO }, { "GOOD02.SMK", Video::VideoAction::PLAY_TILL_AUDIO_END } }, Campaign::VideoSequence{ { "GOOD03QW.SMK", Video::VideoAction::IGNORE_VIDEO }, { "GOOD03.SMK", Video::VideoAction::PLAY_TILL_AUDIO_END } } ); - scenarioDatas.emplace_back( 2, std::vector{ 3 }, Campaign::ScenarioBonusData::getCampaignBonusData( 0, 2 ), "CAMPG03.H2C", rolandCampaignScenarioNames[2], - rolandCampaignDescription[2], emptyPlayback, + scenarioDatas.emplace_back( scenarioInfo[2], std::vector{ scenarioInfo[3] }, "CAMPG03.H2C", scenarioName[2], scenarioDescription[2], + emptyPlayback, Campaign::VideoSequence{ { "GOOD04W.SMK", Video::VideoAction::IGNORE_VIDEO }, { "GOOD04.SMK", Video::VideoAction::PLAY_TILL_AUDIO_END } }, Campaign::ScenarioVictoryCondition::STANDARD, Campaign::ScenarioLossCondition::LOSE_ALL_SORCERESS_VILLAGES ); - scenarioDatas.emplace_back( 3, std::vector{ 4 }, Campaign::ScenarioBonusData::getCampaignBonusData( 0, 3 ), "CAMPG04.H2C", rolandCampaignScenarioNames[3], - rolandCampaignDescription[3], emptyPlayback, + scenarioDatas.emplace_back( scenarioInfo[3], std::vector{ scenarioInfo[4], scenarioInfo[10] }, "CAMPG04.H2C", scenarioName[3], + scenarioDescription[3], emptyPlayback, Campaign::VideoSequence{ { "GOOD05V.SMK", Video::VideoAction::IGNORE_VIDEO }, { "GOOD05.SMK", Video::VideoAction::PLAY_TILL_AUDIO_END } } ); - scenarioDatas.emplace_back( 4, std::vector{ 5 }, Campaign::ScenarioBonusData::getCampaignBonusData( 0, 4 ), "CAMPG05.H2C", rolandCampaignScenarioNames[4], - rolandCampaignDescription[4], emptyPlayback, emptyPlayback ); - scenarioDatas.emplace_back( 5, std::vector{ 6, 7 }, Campaign::ScenarioBonusData::getCampaignBonusData( 0, 5 ), "CAMPG06.H2C", rolandCampaignScenarioNames[5], - rolandCampaignDescription[5], + scenarioDatas.emplace_back( scenarioInfo[4], std::vector{ scenarioInfo[5] }, "CAMPG05.H2C", scenarioName[4], scenarioDescription[4], + emptyPlayback, Campaign::VideoSequence{ { "GOOD06AV.SMK", Video::VideoAction::IGNORE_VIDEO }, - { "GOOD06.SMK", Video::VideoAction::PLAY_TILL_AUDIO_END } }, + { "GOOD06.SMK", Video::VideoAction::PLAY_TILL_AUDIO_END } } ); + scenarioDatas.emplace_back( scenarioInfo[5], std::vector{ scenarioInfo[6], scenarioInfo[7] }, "CAMPG06.H2C", scenarioName[5], + scenarioDescription[5], emptyPlayback, Campaign::VideoSequence{ { "GOOD07QW.SMK", Video::VideoAction::IGNORE_VIDEO }, { "GOOD07.SMK", Video::VideoAction::PLAY_TILL_AUDIO_END } } ); - scenarioDatas.emplace_back( 6, std::vector{ 8 }, Campaign::ScenarioBonusData::getCampaignBonusData( 0, 6 ), "CAMPG07.H2C", rolandCampaignScenarioNames[6], - rolandCampaignDescription[6], emptyPlayback, emptyPlayback ); - scenarioDatas.emplace_back( 7, std::vector{ 8 }, Campaign::ScenarioBonusData::getCampaignBonusData( 0, 7 ), "CAMPG08.H2C", rolandCampaignScenarioNames[7], - rolandCampaignDescription[7], emptyPlayback, emptyPlayback, Campaign::ScenarioVictoryCondition::OBTAIN_ULTIMATE_CROWN ); - scenarioDatas.emplace_back( 8, std::vector{ 9 }, Campaign::ScenarioBonusData::getCampaignBonusData( 0, 8 ), "CAMPG09.H2C", rolandCampaignScenarioNames[8], - rolandCampaignDescription[8], + scenarioDatas.emplace_back( scenarioInfo[6], std::vector{ scenarioInfo[8] }, "CAMPG07.H2C", scenarioName[6], scenarioDescription[6], + emptyPlayback, emptyPlayback ); + scenarioDatas.emplace_back( scenarioInfo[7], std::vector{ scenarioInfo[8] }, "CAMPG08.H2C", scenarioName[7], scenarioDescription[7], + emptyPlayback, emptyPlayback, Campaign::ScenarioVictoryCondition::OBTAIN_ULTIMATE_CROWN ); + scenarioDatas.emplace_back( scenarioInfo[8], std::vector{ scenarioInfo[9] }, "CAMPG09.H2C", scenarioName[8], scenarioDescription[8], Campaign::VideoSequence{ { "GOOD09W.SMK", Video::VideoAction::IGNORE_VIDEO }, { "GOOD09.SMK", Video::VideoAction::PLAY_TILL_AUDIO_END } }, emptyPlayback ); - scenarioDatas.emplace_back( 9, std::vector{}, Campaign::ScenarioBonusData::getCampaignBonusData( 0, 9 ), "CAMPG10.H2C", rolandCampaignScenarioNames[9], - rolandCampaignDescription[9], + scenarioDatas.emplace_back( scenarioInfo[9], std::vector{}, "CAMPG10.H2C", scenarioName[9], scenarioDescription[9], Campaign::VideoSequence{ { "GOOD10W.SMK", Video::VideoAction::IGNORE_VIDEO }, { "GOOD10.SMK", Video::VideoAction::PLAY_TILL_AUDIO_END } }, Campaign::VideoSequence{ { "LIBRARYW.SMK", Video::VideoAction::IGNORE_VIDEO }, { "LIBRARY.SMK", Video::VideoAction::PLAY_TILL_AUDIO_END } } ); + // At the end of the Betrayal scenario we should start an Archibald scenario. + scenarioDatas.emplace_back( scenarioInfo[10], std::vector{ Campaign::ScenarioInfoId( Campaign::ARCHIBALD_CAMPAIGN, 5 ) }, + "CAMPG05B.H2C", scenarioName[10], scenarioDescription[10], emptyPlayback, + Campaign::VideoSequence{ { "EVIL06BW.SMK", Video::VideoAction::IGNORE_VIDEO }, + { "EVIL06.SMK", Video::VideoAction::PLAY_TILL_AUDIO_END } } ); + Campaign::CampaignData campaignData; campaignData.setCampaignID( Campaign::ROLAND_CAMPAIGN ); campaignData.setCampaignDescription( "Roland Campaign" ); - campaignData.setCampaignScenarios( scenarioDatas ); + campaignData.setCampaignScenarios( std::move( scenarioDatas ) ); return campaignData; } Campaign::CampaignData getArchibaldCampaignData() { - const std::string archibaldCampaignScenarioNames[11] + const int scenarioCount = 12; + + const std::array scenarioName = { gettext_noop( "First Blood" ), gettext_noop( "Barbarian Wars" ), gettext_noop( "Necromancers" ), gettext_noop( "Slay the Dwarves" ), gettext_noop( "Turning Point" ), gettext_noop( "Rebellion" ), gettext_noop( "Dragon Master" ), gettext_noop( "Country Lords" ), - gettext_noop( "The Crown" ), gettext_noop( "Greater Glory" ), gettext_noop( "Apocalypse" ) }; - const std::string archibaldCampaignDescription[11] = { + gettext_noop( "The Crown" ), gettext_noop( "Greater Glory" ), gettext_noop( "Apocalypse" ), gettext_noop( "Betrayal" ) }; + const std::array scenarioDescription = { gettext_noop( "King Archibald requires you to defeat the three enemies in this region. They are not allied with one another, so they will spend most of their energy fighting" " amongst themselves. You will win when you own all of the enemy castles and there are no more heroes left to fight." ), @@ -257,69 +283,87 @@ namespace gettext_noop( "Gather as large an army as possible and capture the enemy castle within 8 weeks. You are opposed by only one enemy, but must travel a long way to get to the enemy castle. Any troops you have in your army at the end of this scenario will be with you in the final battle." ), gettext_noop( - "This is the final battle. Both you and your enemy are armed to the teeth, and all are allied against you. Capture Roland to win the war, and be sure not to lose Archibald in the fight!" ) }; + "This is the final battle. Both you and your enemy are armed to the teeth, and all are allied against you. Capture Roland to win the war, and be sure not to lose Archibald in the fight!" ), + gettext_noop( + "Switching sides leaves you with three castles against the enemy's one. This battle will be the easiest one you will face for the rest of the war...traitor." ) }; + std::vector scenarioDatas; - scenarioDatas.reserve( 11 ); + scenarioDatas.reserve( scenarioCount ); + + std::vector scenarioInfo; + scenarioInfo.reserve( scenarioCount ); + for ( int i = 0; i < scenarioCount; ++i ) { + scenarioInfo.emplace_back( Campaign::ARCHIBALD_CAMPAIGN, i ); + } - scenarioDatas.emplace_back( 0, std::vector{ 1 }, Campaign::ScenarioBonusData::getCampaignBonusData( 1, 0 ), "CAMPE01.H2C", archibaldCampaignScenarioNames[0], - archibaldCampaignDescription[0], + scenarioDatas.emplace_back( scenarioInfo[0], std::vector{ scenarioInfo[1] }, "CAMPE01.H2C", scenarioName[0], scenarioDescription[0], Campaign::VideoSequence{ { "EVIL01V.SMK", Video::VideoAction::IGNORE_VIDEO }, { "EVIL01.SMK", Video::VideoAction::PLAY_TILL_AUDIO_END } }, emptyPlayback ); - scenarioDatas.emplace_back( 1, std::vector{ 2, 3 }, Campaign::ScenarioBonusData::getCampaignBonusData( 1, 1 ), "CAMPE02.H2C", - archibaldCampaignScenarioNames[1], archibaldCampaignDescription[1], + scenarioDatas.emplace_back( scenarioInfo[1], std::vector{ scenarioInfo[2], scenarioInfo[3] }, "CAMPE02.H2C", scenarioName[1], + scenarioDescription[1], Campaign::VideoSequence{ { "EVIL02W.SMK", Video::VideoAction::IGNORE_VIDEO }, { "EVIL02.SMK", Video::VideoAction::PLAY_TILL_AUDIO_END } }, Campaign::VideoSequence{ { "EVIL03QW.SMK", Video::VideoAction::IGNORE_VIDEO }, { "EVIL03.SMK", Video::VideoAction::PLAY_TILL_AUDIO_END } } ); - scenarioDatas.emplace_back( 2, std::vector{ 4 }, Campaign::ScenarioBonusData::getCampaignBonusData( 1, 2 ), "CAMPE03.H2C", archibaldCampaignScenarioNames[2], - archibaldCampaignDescription[2], emptyPlayback, + scenarioDatas.emplace_back( scenarioInfo[2], std::vector{ scenarioInfo[4], scenarioInfo[11] }, "CAMPE03.H2C", scenarioName[2], + scenarioDescription[2], emptyPlayback, Campaign::VideoSequence{ { "EVIL05AV.SMK", Video::VideoAction::IGNORE_VIDEO }, + { "EVIL05.SMK", Video::VideoAction::PLAY_TILL_AUDIO_END }, + { "SBETRAYV.SMK", Video::VideoAction::IGNORE_VIDEO }, { "EVIL05.SMK", Video::VideoAction::PLAY_TILL_AUDIO_END } } ); - scenarioDatas.emplace_back( 3, std::vector{ 4 }, Campaign::ScenarioBonusData::getCampaignBonusData( 1, 3 ), "CAMPE04.H2C", archibaldCampaignScenarioNames[3], - archibaldCampaignDescription[3], emptyPlayback, - Campaign::VideoSequence{ { "EVIL05AV.SMK", Video::VideoAction::IGNORE_VIDEO }, + scenarioDatas.emplace_back( scenarioInfo[3], std::vector{ scenarioInfo[4], scenarioInfo[11] }, "CAMPE04.H2C", scenarioName[3], + scenarioDescription[3], emptyPlayback, + Campaign::VideoSequence{ { "EVIL05BV.SMK", Video::VideoAction::IGNORE_VIDEO }, + { "EVIL05.SMK", Video::VideoAction::PLAY_TILL_AUDIO_END }, + { "SBETRAYV.SMK", Video::VideoAction::IGNORE_VIDEO }, { "EVIL05.SMK", Video::VideoAction::PLAY_TILL_AUDIO_END } } ); - scenarioDatas.emplace_back( 4, std::vector{ 5 }, Campaign::ScenarioBonusData::getCampaignBonusData( 1, 4 ), "CAMPE05.H2C", archibaldCampaignScenarioNames[4], - archibaldCampaignDescription[4], emptyPlayback, emptyPlayback ); - scenarioDatas.emplace_back( 5, std::vector{ 6, 7 }, Campaign::ScenarioBonusData::getCampaignBonusData( 1, 5 ), "CAMPE06.H2C", - archibaldCampaignScenarioNames[5], archibaldCampaignDescription[5], + scenarioDatas.emplace_back( scenarioInfo[4], std::vector{ scenarioInfo[5] }, "CAMPE05.H2C", scenarioName[4], scenarioDescription[4], + emptyPlayback, Campaign::VideoSequence{ { "EVIL06AW.SMK", Video::VideoAction::IGNORE_VIDEO }, - { "EVIL06.SMK", Video::VideoAction::PLAY_TILL_AUDIO_END } }, + { "EVIL06.SMK", Video::VideoAction::PLAY_TILL_AUDIO_END } } ); + scenarioDatas.emplace_back( scenarioInfo[5], std::vector{ scenarioInfo[6], scenarioInfo[7] }, "CAMPE06.H2C", scenarioName[5], + scenarioDescription[5], emptyPlayback, Campaign::VideoSequence{ { "EVIL07W.SMK", Video::VideoAction::IGNORE_VIDEO }, { "EVIL07.SMK", Video::VideoAction::PLAY_TILL_AUDIO_END } } ); - scenarioDatas.emplace_back( 6, std::vector{ 7 }, Campaign::ScenarioBonusData::getCampaignBonusData( 1, 6 ), "CAMPE07.H2C", archibaldCampaignScenarioNames[6], - archibaldCampaignDescription[6], emptyPlayback, Campaign::VideoSequence{ { "EVIL08.SMK", Video::VideoAction::PLAY_TILL_VIDEO_END } }, + scenarioDatas.emplace_back( scenarioInfo[6], std::vector{ scenarioInfo[7] }, "CAMPE07.H2C", scenarioName[6], scenarioDescription[6], + emptyPlayback, Campaign::VideoSequence{ { "EVIL08.SMK", Video::VideoAction::PLAY_TILL_VIDEO_END } }, Campaign::ScenarioVictoryCondition::CAPTURE_DRAGON_CITY ); - scenarioDatas.emplace_back( 7, std::vector{ 8, 9 }, Campaign::ScenarioBonusData::getCampaignBonusData( 1, 7 ), "CAMPE08.H2C", - archibaldCampaignScenarioNames[7], archibaldCampaignDescription[7], emptyPlayback, + scenarioDatas.emplace_back( scenarioInfo[7], std::vector{ scenarioInfo[8], scenarioInfo[9] }, "CAMPE08.H2C", scenarioName[7], + scenarioDescription[7], emptyPlayback, Campaign::VideoSequence{ { "EVIL09W.SMK", Video::VideoAction::IGNORE_VIDEO }, { "EVIL09.SMK", Video::VideoAction::PLAY_TILL_AUDIO_END } } ); - scenarioDatas.emplace_back( 8, std::vector{ 10 }, Campaign::ScenarioBonusData::getCampaignBonusData( 1, 8 ), "CAMPE09.H2C", - archibaldCampaignScenarioNames[8], archibaldCampaignDescription[8], emptyPlayback, emptyPlayback, - Campaign::ScenarioVictoryCondition::OBTAIN_ULTIMATE_CROWN ); - scenarioDatas.emplace_back( 9, std::vector{ 10 }, Campaign::ScenarioBonusData::getCampaignBonusData( 1, 9 ), "CAMPE10.H2C", - archibaldCampaignScenarioNames[9], archibaldCampaignDescription[9], emptyPlayback, emptyPlayback ); - scenarioDatas.emplace_back( 10, std::vector{}, Campaign::ScenarioBonusData::getCampaignBonusData( 1, 10 ), "CAMPE11.H2C", archibaldCampaignScenarioNames[10], - archibaldCampaignDescription[10], + scenarioDatas.emplace_back( scenarioInfo[8], std::vector{ scenarioInfo[10] }, "CAMPE09.H2C", scenarioName[8], scenarioDescription[8], + emptyPlayback, emptyPlayback, Campaign::ScenarioVictoryCondition::OBTAIN_ULTIMATE_CROWN ); + scenarioDatas.emplace_back( scenarioInfo[9], std::vector{ scenarioInfo[10] }, "CAMPE10.H2C", scenarioName[9], scenarioDescription[9], + emptyPlayback, emptyPlayback ); + scenarioDatas.emplace_back( scenarioInfo[10], std::vector{}, "CAMPE11.H2C", scenarioName[10], scenarioDescription[10], Campaign::VideoSequence{ { "EVIL11W.SMK", Video::VideoAction::IGNORE_VIDEO }, { "EVIL10.SMK", Video::VideoAction::PLAY_TILL_AUDIO_END } }, Campaign::VideoSequence{ { "PRISON.SMK", Video::VideoAction::PLAY_TILL_VIDEO_END } } ); + // At the end of the Betrayal scenario we should start a Roland scenario. + scenarioDatas.emplace_back( scenarioInfo[11], std::vector{ Campaign::ScenarioInfoId( Campaign::ROLAND_CAMPAIGN, 5 ) }, "CAMPE05B.H2C", + scenarioName[11], scenarioDescription[11], emptyPlayback, + Campaign::VideoSequence{ { "GOOD06BV.SMK", Video::VideoAction::IGNORE_VIDEO }, + { "GOOD06.SMK", Video::VideoAction::PLAY_TILL_AUDIO_END } } ); + Campaign::CampaignData campaignData; campaignData.setCampaignID( Campaign::ARCHIBALD_CAMPAIGN ); campaignData.setCampaignDescription( "Archibald Campaign" ); - campaignData.setCampaignScenarios( scenarioDatas ); + campaignData.setCampaignScenarios( std::move( scenarioDatas ) ); return campaignData; } Campaign::CampaignData getPriceOfLoyaltyCampaignData() { - const std::string priceOfLoyaltyCampaignScenarioNames[8] + const int scenarioCount = 8; + + const std::array scenarioName = { gettext_noop( "Uprising" ), gettext_noop( "Island of Chaos" ), gettext_noop( "Arrow's Flight" ), gettext_noop( "The Abyss" ), gettext_noop( "The Giant's Pass" ), gettext_noop( "Aurora Borealis" ), gettext_noop( "Betrayal's End" ), gettext_noop( "Corruption's Heart" ) }; - const std::string priceOfLoyaltyCampaignDescription[8] = { + const std::array scenarioDescription = { gettext_noop( "Subdue the unruly local lords in order to provide the Empire with facilities to operate in this region." ), gettext_noop( "Eliminate all oposition in this area. Then the first piece of the artifact will be yours." ), gettext_noop( @@ -336,55 +380,59 @@ namespace std::vector scenarioDatas; scenarioDatas.reserve( 8 ); - const int campaignID = Campaign::PRICE_OF_LOYALTY_CAMPAIGN; - scenarioDatas.emplace_back( 0, std::vector{ 1 }, Campaign::ScenarioBonusData::getCampaignBonusData( campaignID, 0 ), "CAMP1_01.HXC", - priceOfLoyaltyCampaignScenarioNames[0], priceOfLoyaltyCampaignDescription[0], + std::vector scenarioInfo; + scenarioInfo.reserve( scenarioCount ); + for ( int i = 0; i < scenarioCount; ++i ) { + scenarioInfo.emplace_back( Campaign::PRICE_OF_LOYALTY_CAMPAIGN, i ); + } + + scenarioDatas.emplace_back( scenarioInfo[0], std::vector{ scenarioInfo[1] }, "CAMP1_01.HXC", scenarioName[0], scenarioDescription[0], Campaign::VideoSequence{ { "MIXPOL1.SMK", Video::VideoAction::IGNORE_VIDEO }, { "POL1.SMK", Video::VideoAction::PLAY_TILL_AUDIO_END } }, emptyPlayback ); - scenarioDatas.emplace_back( 1, std::vector{ 2, 3 }, Campaign::ScenarioBonusData::getCampaignBonusData( campaignID, 1 ), "CAMP1_02.HXC", - priceOfLoyaltyCampaignScenarioNames[1], priceOfLoyaltyCampaignDescription[1], + scenarioDatas.emplace_back( scenarioInfo[1], std::vector{ scenarioInfo[2], scenarioInfo[3] }, "CAMP1_02.HXC", scenarioName[1], + scenarioDescription[1], Campaign::VideoSequence{ { "MIXPOL2.SMK", Video::VideoAction::IGNORE_VIDEO }, { "POL2.SMK", Video::VideoAction::PLAY_TILL_AUDIO_END } }, Campaign::VideoSequence{ { "MIXPOL3.SMK", Video::VideoAction::IGNORE_VIDEO }, { "POL3.SMK", Video::VideoAction::PLAY_TILL_AUDIO_END } } ); - scenarioDatas.emplace_back( 2, std::vector{ 4 }, Campaign::ScenarioBonusData::getCampaignBonusData( campaignID, 2 ), "CAMP1_03.HXC", - priceOfLoyaltyCampaignScenarioNames[2], priceOfLoyaltyCampaignDescription[2], emptyPlayback, emptyPlayback ); - scenarioDatas.emplace_back( 3, std::vector{ 5 }, Campaign::ScenarioBonusData::getCampaignBonusData( campaignID, 3 ), "CAMP1_04.HXC", - priceOfLoyaltyCampaignScenarioNames[3], priceOfLoyaltyCampaignDescription[3], emptyPlayback, emptyPlayback ); - scenarioDatas.emplace_back( 4, std::vector{ 5 }, Campaign::ScenarioBonusData::getCampaignBonusData( campaignID, 4 ), "CAMP1_05.HXC", - priceOfLoyaltyCampaignScenarioNames[4], priceOfLoyaltyCampaignDescription[4], + scenarioDatas.emplace_back( scenarioInfo[2], std::vector{ scenarioInfo[4] }, "CAMP1_03.HXC", scenarioName[2], scenarioDescription[2], + emptyPlayback, emptyPlayback ); + scenarioDatas.emplace_back( scenarioInfo[3], std::vector{ scenarioInfo[5] }, "CAMP1_04.HXC", scenarioName[3], scenarioDescription[3], + emptyPlayback, emptyPlayback ); + scenarioDatas.emplace_back( scenarioInfo[4], std::vector{ scenarioInfo[5] }, "CAMP1_05.HXC", scenarioName[4], scenarioDescription[4], Campaign::VideoSequence{ { "MIXPOL4.SMK", Video::VideoAction::IGNORE_VIDEO }, { "POL4.SMK", Video::VideoAction::PLAY_TILL_AUDIO_END } }, emptyPlayback ); - scenarioDatas.emplace_back( 5, std::vector{ 6, 7 }, Campaign::ScenarioBonusData::getCampaignBonusData( campaignID, 5 ), "CAMP1_06.HXC", - priceOfLoyaltyCampaignScenarioNames[5], priceOfLoyaltyCampaignDescription[5], + scenarioDatas.emplace_back( scenarioInfo[5], std::vector{ scenarioInfo[6], scenarioInfo[7] }, "CAMP1_06.HXC", scenarioName[5], + scenarioDescription[5], Campaign::VideoSequence{ { "MIXPOL5.SMK", Video::VideoAction::IGNORE_VIDEO }, { "POL5.SMK", Video::VideoAction::PLAY_TILL_AUDIO_END } }, Campaign::VideoSequence{ { "MIXPOL6.SMK", Video::VideoAction::IGNORE_VIDEO }, { "POL6.SMK", Video::VideoAction::PLAY_TILL_AUDIO_END } } ); - scenarioDatas.emplace_back( 6, std::vector{ 7 }, Campaign::ScenarioBonusData::getCampaignBonusData( campaignID, 6 ), "CAMP1_07.HXC", - priceOfLoyaltyCampaignScenarioNames[6], priceOfLoyaltyCampaignDescription[6], emptyPlayback, + scenarioDatas.emplace_back( scenarioInfo[6], std::vector{ scenarioInfo[7] }, "CAMP1_07.HXC", scenarioName[6], scenarioDescription[6], + emptyPlayback, Campaign::VideoSequence{ { "MIXPOL7.SMK", Video::VideoAction::IGNORE_VIDEO }, { "POL7.SMK", Video::VideoAction::PLAY_TILL_AUDIO_END } } ); - scenarioDatas.emplace_back( 7, std::vector{}, Campaign::ScenarioBonusData::getCampaignBonusData( campaignID, 7 ), "CAMP1_08.HXC", - priceOfLoyaltyCampaignScenarioNames[7], priceOfLoyaltyCampaignDescription[7], emptyPlayback, + scenarioDatas.emplace_back( scenarioInfo[7], std::vector{}, "CAMP1_08.HXC", scenarioName[7], scenarioDescription[7], emptyPlayback, Campaign::VideoSequence{ { "MIXPOL8.SMK", Video::VideoAction::IGNORE_VIDEO }, { "POL8.SMK", Video::VideoAction::PLAY_TILL_AUDIO_END } } ); Campaign::CampaignData campaignData; - campaignData.setCampaignID( campaignID ); - campaignData.setCampaignScenarios( scenarioDatas ); + campaignData.setCampaignID( Campaign::PRICE_OF_LOYALTY_CAMPAIGN ); + campaignData.setCampaignScenarios( std::move( scenarioDatas ) ); return campaignData; } Campaign::CampaignData getDescendantsCampaignData() { - const std::string descendantsCampaignScenarioNames[8] + const int scenarioCount = 8; + + const std::array scenarioName = { gettext_noop( "Conquer and Unify" ), gettext_noop( "Border Towns" ), gettext_noop( "The Wayward Son" ), gettext_noop( "Crazy Uncle Ivan" ), gettext_noop( "The Southern War" ), gettext_noop( "Ivory Gates" ), gettext_noop( "The Elven Lands" ), gettext_noop( "The Epic Battle" ) }; - const std::string descendantsCampaignDescription[8] = { + const std::array scenarioDescription = { gettext_noop( "Conquer and unite all the enemy tribes. Don't lose the hero Jarkonas, the forefather of all descendants." ), gettext_noop( "Your rival, the Kingdom of Harondale, is attacking weak towns on your border! Recover from their first strike and crush them completely!" ), gettext_noop( @@ -399,53 +447,57 @@ namespace std::vector scenarioDatas; scenarioDatas.reserve( 8 ); - const int campaignID = Campaign::DESCENDANTS_CAMPAIGN; - scenarioDatas.emplace_back( 0, std::vector{ 1 }, Campaign::ScenarioBonusData::getCampaignBonusData( campaignID, 0 ), "CAMP2_01.HXC", - descendantsCampaignScenarioNames[0], descendantsCampaignDescription[0], + std::vector scenarioInfo; + scenarioInfo.reserve( scenarioCount ); + for ( int i = 0; i < scenarioCount; ++i ) { + scenarioInfo.emplace_back( Campaign::DESCENDANTS_CAMPAIGN, i ); + } + + scenarioDatas.emplace_back( scenarioInfo[0], std::vector{ scenarioInfo[1] }, "CAMP2_01.HXC", scenarioName[0], scenarioDescription[0], Campaign::VideoSequence{ { "MIXDES9.SMK", Video::VideoAction::IGNORE_VIDEO }, { "DES9.SMK", Video::VideoAction::PLAY_TILL_AUDIO_END } }, emptyPlayback ); - scenarioDatas.emplace_back( 1, std::vector{ 2, 3 }, Campaign::ScenarioBonusData::getCampaignBonusData( campaignID, 1 ), "CAMP2_02.HXC", - descendantsCampaignScenarioNames[1], descendantsCampaignDescription[1], + scenarioDatas.emplace_back( scenarioInfo[1], std::vector{ scenarioInfo[2], scenarioInfo[3] }, "CAMP2_02.HXC", scenarioName[1], + scenarioDescription[1], Campaign::VideoSequence{ { "MIXDES10.SMK", Video::VideoAction::IGNORE_VIDEO }, { "DES10.SMK", Video::VideoAction::PLAY_TILL_AUDIO_END } }, Campaign::VideoSequence{ { "MIXDES11.SMK", Video::VideoAction::IGNORE_VIDEO }, { "DES11.SMK", Video::VideoAction::PLAY_TILL_AUDIO_END } } ); - scenarioDatas.emplace_back( 2, std::vector{ 4 }, Campaign::ScenarioBonusData::getCampaignBonusData( campaignID, 2 ), "CAMP2_03.HXC", - descendantsCampaignScenarioNames[2], descendantsCampaignDescription[2], emptyPlayback, emptyPlayback ); - scenarioDatas.emplace_back( 3, std::vector{ 4 }, Campaign::ScenarioBonusData::getCampaignBonusData( campaignID, 3 ), "CAMP2_04.HXC", - descendantsCampaignScenarioNames[3], descendantsCampaignDescription[3], emptyPlayback, emptyPlayback ); - scenarioDatas.emplace_back( 4, std::vector{ 5, 6 }, Campaign::ScenarioBonusData::getCampaignBonusData( campaignID, 4 ), "CAMP2_05.HXC", - descendantsCampaignScenarioNames[4], descendantsCampaignDescription[4], + scenarioDatas.emplace_back( scenarioInfo[2], std::vector{ scenarioInfo[4] }, "CAMP2_03.HXC", scenarioName[2], scenarioDescription[2], + emptyPlayback, emptyPlayback ); + scenarioDatas.emplace_back( scenarioInfo[3], std::vector{ scenarioInfo[4] }, "CAMP2_04.HXC", scenarioName[3], scenarioDescription[3], + emptyPlayback, emptyPlayback ); + scenarioDatas.emplace_back( scenarioInfo[4], std::vector{ scenarioInfo[5], scenarioInfo[6] }, "CAMP2_05.HXC", scenarioName[4], + scenarioDescription[4], Campaign::VideoSequence{ { "MIXDES12.SMK", Video::VideoAction::IGNORE_VIDEO }, { "DES12.SMK", Video::VideoAction::PLAY_TILL_AUDIO_END } }, emptyPlayback ); - scenarioDatas.emplace_back( 5, std::vector{ 7 }, Campaign::ScenarioBonusData::getCampaignBonusData( campaignID, 5 ), "CAMP2_06.HXC", - descendantsCampaignScenarioNames[5], descendantsCampaignDescription[5], + scenarioDatas.emplace_back( scenarioInfo[5], std::vector{ scenarioInfo[7] }, "CAMP2_06.HXC", scenarioName[5], scenarioDescription[5], Campaign::VideoSequence{ { "MIXDES13.SMK", Video::VideoAction::IGNORE_VIDEO }, { "DES13.SMK", Video::VideoAction::PLAY_TILL_AUDIO_END } }, emptyPlayback ); - scenarioDatas.emplace_back( 6, std::vector{ 7 }, Campaign::ScenarioBonusData::getCampaignBonusData( campaignID, 6 ), "CAMP2_07.HXC", - descendantsCampaignScenarioNames[6], descendantsCampaignDescription[6], emptyPlayback, emptyPlayback ); - scenarioDatas.emplace_back( 7, std::vector{}, Campaign::ScenarioBonusData::getCampaignBonusData( campaignID, 7 ), "CAMP2_08.HXC", - descendantsCampaignScenarioNames[7], descendantsCampaignDescription[7], + scenarioDatas.emplace_back( scenarioInfo[6], std::vector{ scenarioInfo[7] }, "CAMP2_07.HXC", scenarioName[6], scenarioDescription[6], + emptyPlayback, emptyPlayback ); + scenarioDatas.emplace_back( scenarioInfo[7], std::vector{}, "CAMP2_08.HXC", scenarioName[7], scenarioDescription[7], Campaign::VideoSequence{ { "MIXDES14.SMK", Video::VideoAction::IGNORE_VIDEO }, { "DES14.SMK", Video::VideoAction::PLAY_TILL_AUDIO_END } }, Campaign::VideoSequence{ { "MIXDES15.SMK", Video::VideoAction::IGNORE_VIDEO }, { "DES15.SMK", Video::VideoAction::PLAY_TILL_AUDIO_END } } ); Campaign::CampaignData campaignData; - campaignData.setCampaignID( campaignID ); - campaignData.setCampaignScenarios( scenarioDatas ); + campaignData.setCampaignID( Campaign::DESCENDANTS_CAMPAIGN ); + campaignData.setCampaignScenarios( std::move( scenarioDatas ) ); return campaignData; } Campaign::CampaignData getWizardsIsleCampaignData() { - const std::string wizardsIsleCampaignScenarioNames[4] + const int scenarioCount = 4; + + const std::array scenarioName = { gettext_noop( "The Shrouded Isles" ), gettext_noop( "The Eternal Scrolls" ), gettext_noop( "Power's End" ), gettext_noop( "Fount of Wizardry" ) }; - const std::string wizardsIsleCampaignDescription[4] = { + const std::array scenarioDescription = { gettext_noop( "Your mission is to vanquish the warring mages in the magical Shrouded Isles. The completion of this task will give you a fighting chance against your rivals." ), gettext_noop( "The location of the great library has been discovered! You must make your way to it, and reclaim the city of Chronos in which it lies." ), @@ -455,40 +507,45 @@ namespace std::vector scenarioDatas; scenarioDatas.reserve( 4 ); - const int campaignID = Campaign::WIZARDS_ISLE_CAMPAIGN; - scenarioDatas.emplace_back( 0, std::vector{ 1 }, Campaign::ScenarioBonusData::getCampaignBonusData( campaignID, 0 ), "CAMP3_01.HXC", - wizardsIsleCampaignScenarioNames[0], wizardsIsleCampaignDescription[0], + std::vector scenarioInfo; + scenarioInfo.reserve( scenarioCount ); + for ( int i = 0; i < scenarioCount; ++i ) { + scenarioInfo.emplace_back( Campaign::WIZARDS_ISLE_CAMPAIGN, i ); + } + + scenarioDatas.emplace_back( scenarioInfo[0], std::vector{ scenarioInfo[1] }, "CAMP3_01.HXC", scenarioName[0], scenarioDescription[0], Campaign::VideoSequence{ { "MIXWIZ16.SMK", Video::VideoAction::IGNORE_VIDEO }, { "WIZ16.SMK", Video::VideoAction::PLAY_TILL_AUDIO_END } }, emptyPlayback ); - scenarioDatas.emplace_back( 1, std::vector{ 2, 3 }, Campaign::ScenarioBonusData::getCampaignBonusData( campaignID, 1 ), "CAMP3_02.HXC", - wizardsIsleCampaignScenarioNames[1], wizardsIsleCampaignDescription[1], + scenarioDatas.emplace_back( scenarioInfo[1], std::vector{ scenarioInfo[2], scenarioInfo[3] }, "CAMP3_02.HXC", scenarioName[1], + scenarioDescription[1], Campaign::VideoSequence{ { "MIXWIZ17.SMK", Video::VideoAction::IGNORE_VIDEO }, { "WIZ17.SMK", Video::VideoAction::PLAY_TILL_AUDIO_END } }, Campaign::VideoSequence{ { "MIXWIZ18.SMK", Video::VideoAction::IGNORE_VIDEO }, { "WIZ18.SMK", Video::VideoAction::PLAY_TILL_AUDIO_END } } ); - scenarioDatas.emplace_back( 2, std::vector{ 3 }, Campaign::ScenarioBonusData::getCampaignBonusData( campaignID, 2 ), "CAMP3_03.HXC", - wizardsIsleCampaignScenarioNames[2], wizardsIsleCampaignDescription[2], emptyPlayback, + scenarioDatas.emplace_back( scenarioInfo[2], std::vector{ scenarioInfo[3] }, "CAMP3_03.HXC", scenarioName[2], scenarioDescription[2], + emptyPlayback, Campaign::VideoSequence{ { "MIXWIZ19.SMK", Video::VideoAction::IGNORE_VIDEO }, { "WIZ19.SMK", Video::VideoAction::PLAY_TILL_AUDIO_END } }, Campaign::ScenarioVictoryCondition::OBTAIN_SPHERE_NEGATION ); - scenarioDatas.emplace_back( 3, std::vector{}, Campaign::ScenarioBonusData::getCampaignBonusData( campaignID, 3 ), "CAMP3_04.HXC", - wizardsIsleCampaignScenarioNames[3], wizardsIsleCampaignDescription[3], emptyPlayback, + scenarioDatas.emplace_back( scenarioInfo[3], std::vector{}, "CAMP3_04.HXC", scenarioName[3], scenarioDescription[3], emptyPlayback, Campaign::VideoSequence{ { "MIXWIZ20.SMK", Video::VideoAction::IGNORE_VIDEO }, { "WIZ20.SMK", Video::VideoAction::PLAY_TILL_AUDIO_END } } ); Campaign::CampaignData campaignData; - campaignData.setCampaignID( campaignID ); - campaignData.setCampaignScenarios( scenarioDatas ); + campaignData.setCampaignID( Campaign::WIZARDS_ISLE_CAMPAIGN ); + campaignData.setCampaignScenarios( std::move( scenarioDatas ) ); return campaignData; } Campaign::CampaignData getVoyageHomeCampaignData() { - const std::string voyageHomeCampaignScenarioNames[4] + const int scenarioCount = 4; + + const std::array scenarioName = { gettext_noop( "Stranded" ), gettext_noop( "Pirate Isles" ), gettext_noop( "King and Country" ), gettext_noop( "Blood is Thicker" ) }; - const std::string voyageHomeCampaignDescription[4] = { + const std::array scenarioDescription = { gettext_noop( "Capture the town on the island off the southeast shore in order to construct a boat and travel back towards the mainland. Do not lose the hero Gallavant." ), gettext_noop( "Find and defeat Martine, the pirate leader, who resides in Pirates Cove. Do not lose Gallavant or your quest will be over." ), @@ -497,30 +554,32 @@ namespace std::vector scenarioDatas; scenarioDatas.reserve( 4 ); - const int campaignID = Campaign::VOYAGE_HOME_CAMPAIGN; - scenarioDatas.emplace_back( 0, std::vector{ 1 }, Campaign::ScenarioBonusData::getCampaignBonusData( campaignID, 0 ), "CAMP4_01.HXC", - voyageHomeCampaignScenarioNames[0], voyageHomeCampaignDescription[0], + std::vector scenarioInfo; + scenarioInfo.reserve( scenarioCount ); + for ( int i = 0; i < scenarioCount; ++i ) { + scenarioInfo.emplace_back( Campaign::VOYAGE_HOME_CAMPAIGN, i ); + } + + scenarioDatas.emplace_back( scenarioInfo[0], std::vector{ scenarioInfo[1] }, "CAMP4_01.HXC", scenarioName[0], scenarioDescription[0], Campaign::VideoSequence{ { "MIXVOY21.SMK", Video::VideoAction::IGNORE_VIDEO }, { "VOY21.SMK", Video::VideoAction::PLAY_TILL_AUDIO_END } }, emptyPlayback ); - scenarioDatas.emplace_back( 1, std::vector{ 2, 3 }, Campaign::ScenarioBonusData::getCampaignBonusData( campaignID, 1 ), "CAMP4_02.HXC", - voyageHomeCampaignScenarioNames[1], voyageHomeCampaignDescription[1], + scenarioDatas.emplace_back( scenarioInfo[1], std::vector{ scenarioInfo[2], scenarioInfo[3] }, "CAMP4_02.HXC", scenarioName[1], + scenarioDescription[1], Campaign::VideoSequence{ { "MIXVOY22.SMK", Video::VideoAction::IGNORE_VIDEO }, { "VOY22.SMK", Video::VideoAction::PLAY_TILL_AUDIO_END } }, Campaign::VideoSequence{ { "MIXVOY23.SMK", Video::VideoAction::IGNORE_VIDEO }, { "VOY23.SMK", Video::VideoAction::PLAY_TILL_AUDIO_END } } ); - scenarioDatas.emplace_back( 2, std::vector{ 3 }, Campaign::ScenarioBonusData::getCampaignBonusData( campaignID, 2 ), "CAMP4_03.HXC", - voyageHomeCampaignScenarioNames[2], voyageHomeCampaignDescription[2], emptyPlayback, + scenarioDatas.emplace_back( scenarioInfo[2], std::vector{}, "CAMP4_03.HXC", scenarioName[2], scenarioDescription[2], emptyPlayback, Campaign::VideoSequence{ { "MIXVOY24.SMK", Video::VideoAction::IGNORE_VIDEO }, { "VOY24.SMK", Video::VideoAction::PLAY_TILL_AUDIO_END } } ); - scenarioDatas.emplace_back( 3, std::vector{}, Campaign::ScenarioBonusData::getCampaignBonusData( campaignID, 3 ), "CAMP4_04.HXC", - voyageHomeCampaignScenarioNames[3], voyageHomeCampaignDescription[3], emptyPlayback, + scenarioDatas.emplace_back( scenarioInfo[3], std::vector{}, "CAMP4_04.HXC", scenarioName[3], scenarioDescription[3], emptyPlayback, Campaign::VideoSequence{ { "MIXVOY25.SMK", Video::VideoAction::IGNORE_VIDEO }, { "VOY25.SMK", Video::VideoAction::PLAY_TILL_AUDIO_END } } ); Campaign::CampaignData campaignData; - campaignData.setCampaignID( campaignID ); - campaignData.setCampaignScenarios( scenarioDatas ); + campaignData.setCampaignID( Campaign::VOYAGE_HOME_CAMPAIGN ); + campaignData.setCampaignScenarios( std::move( scenarioDatas ) ); return campaignData; } @@ -555,36 +614,35 @@ namespace Campaign return std::vector(); } - const std::vector & CampaignData::getScenariosAfter( const int scenarioID ) const + const std::vector & CampaignData::getScenariosAfter( const ScenarioInfoId & scenarioInfo ) { - for ( size_t i = 0; i < _scenarios.size(); ++i ) { - if ( _scenarios[i].getScenarioID() == scenarioID ) - return _scenarios[i].getNextMaps(); - } + const CampaignData & campaignData = getCampaignData( scenarioInfo.campaignId ); + const std::vector & scenarios = campaignData.getAllScenarios(); + assert( scenarioInfo.scenarioId >= 0 && static_cast( scenarioInfo.scenarioId ) < scenarios.size() ); - return _scenarios[scenarioID].getNextMaps(); + return scenarios[scenarioInfo.scenarioId].getNextScenarios(); } - std::vector CampaignData::getStartingScenarios() const + std::vector CampaignData::getStartingScenarios() const { - std::vector startingScenarios; + std::vector startingScenarios; for ( size_t i = 0; i < _scenarios.size(); ++i ) { const int scenarioID = _scenarios[i].getScenarioID(); - if ( isStartingScenario( scenarioID ) ) - startingScenarios.emplace_back( scenarioID ); + if ( isStartingScenario( { _campaignID, scenarioID } ) ) + startingScenarios.emplace_back( _campaignID, scenarioID ); } return startingScenarios; } - bool CampaignData::isStartingScenario( const int scenarioID ) const + bool CampaignData::isStartingScenario( const ScenarioInfoId & scenarioInfo ) const { // starting scenario = a scenario that is never included as a nextMap for ( size_t i = 0; i < _scenarios.size(); ++i ) { - const std::vector & nextMaps = _scenarios[i].getNextMaps(); + const std::vector & nextMaps = _scenarios[i].getNextScenarios(); - if ( std::find( nextMaps.begin(), nextMaps.end(), scenarioID ) != nextMaps.end() ) + if ( std::find( nextMaps.begin(), nextMaps.end(), scenarioInfo ) != nextMaps.end() ) return false; } @@ -601,10 +659,18 @@ namespace Campaign return true; } - bool CampaignData::isLastScenario( const int scenarioID ) const + bool CampaignData::isLastScenario( const Campaign::ScenarioInfoId & scenarioInfoId ) const { assert( !_scenarios.empty() ); - return scenarioID == _scenarios.back().getScenarioID(); + for ( const ScenarioData & scenario : _scenarios ) { + if ( scenario.getScenarioInfoId() == scenarioInfoId ) { + if ( scenario.getNextScenarios().empty() ) { + return true; + } + } + } + + return false; } void CampaignData::setCampaignID( const int campaignID ) @@ -612,9 +678,9 @@ namespace Campaign _campaignID = campaignID; } - void CampaignData::setCampaignScenarios( const std::vector & scenarios ) + void CampaignData::setCampaignScenarios( std::vector && scenarios ) { - _scenarios = scenarios; + _scenarios = std::move( scenarios ); } void CampaignData::setCampaignDescription( const std::string & campaignDescription ) @@ -708,21 +774,21 @@ namespace Campaign } } - std::vector CampaignAwardData::getCampaignAwardData( const int campaignID, const int scenarioID ) + std::vector CampaignAwardData::getCampaignAwardData( const ScenarioInfoId & scenarioInfo ) { - assert( campaignID >= 0 && scenarioID >= 0 ); + assert( scenarioInfo.campaignId >= 0 && scenarioInfo.scenarioId >= 0 ); - switch ( campaignID ) { + switch ( scenarioInfo.campaignId ) { case ROLAND_CAMPAIGN: - return getRolandCampaignAwardData( scenarioID ); + return getRolandCampaignAwardData( scenarioInfo.scenarioId ); case ARCHIBALD_CAMPAIGN: - return getArchibaldCampaignAwardData( scenarioID ); + return getArchibaldCampaignAwardData( scenarioInfo.scenarioId ); case PRICE_OF_LOYALTY_CAMPAIGN: - return getPriceOfLoyaltyCampaignAwardData( scenarioID ); + return getPriceOfLoyaltyCampaignAwardData( scenarioInfo.scenarioId ); case DESCENDANTS_CAMPAIGN: - return getDescendantsCampaignAwardData( scenarioID ); + return getDescendantsCampaignAwardData( scenarioInfo.scenarioId ); case WIZARDS_ISLE_CAMPAIGN: - return getWizardsIsleCampaignAwardData( scenarioID ); + return getWizardsIsleCampaignAwardData( scenarioInfo.scenarioId ); case VOYAGE_HOME_CAMPAIGN: // No campaign award for voyage home! return {}; diff --git a/src/fheroes2/campaign/campaign_data.h b/src/fheroes2/campaign/campaign_data.h index b4b06c2ef5c..e5bc8e855ad 100644 --- a/src/fheroes2/campaign/campaign_data.h +++ b/src/fheroes2/campaign/campaign_data.h @@ -40,16 +40,15 @@ namespace Campaign return _scenarios; } - const std::vector & getScenariosAfter( const int scenarioID ) const; - std::vector getStartingScenarios() const; + static const std::vector & getScenariosAfter( const ScenarioInfoId & scenarioInfo ); + std::vector getStartingScenarios() const; bool isAllCampaignMapsPresent() const; - bool isLastScenario( const int scenarioID ) const; - bool isStartingScenario( const int scenarioID ) const; + bool isLastScenario( const Campaign::ScenarioInfoId & scenarioInfoId ) const; void setCampaignID( const int campaignID ); void setCampaignDescription( const std::string & campaignDescription ); - void setCampaignScenarios( const std::vector & scenarios ); + void setCampaignScenarios( std::vector && scenarios ); static const CampaignData & getCampaignData( const int campaignID ); @@ -57,6 +56,8 @@ namespace Campaign int _campaignID; std::string _campaignDescription; std::vector _scenarios; + + bool isStartingScenario( const ScenarioInfoId & scenarioInfo ) const; }; struct CampaignAwardData @@ -91,7 +92,7 @@ namespace Campaign std::string ToString() const; - static std::vector getCampaignAwardData( const int campaignID, const int scenarioID ); + static std::vector getCampaignAwardData( const ScenarioInfoId & scenarioInfo ); static std::vector getExtraCampaignAwardData( const int campaignID ); static const char * getAllianceJoiningMessage( const int monsterId ); diff --git a/src/fheroes2/campaign/campaign_savedata.cpp b/src/fheroes2/campaign/campaign_savedata.cpp index e93369a3f5b..5a7eb1dcb6f 100644 --- a/src/fheroes2/campaign/campaign_savedata.cpp +++ b/src/fheroes2/campaign/campaign_savedata.cpp @@ -21,6 +21,7 @@ #include "campaign_savedata.h" #include "army.h" #include "campaign_data.h" +#include "save_format_version.h" #include "serialize.h" #include #include @@ -28,10 +29,11 @@ namespace Campaign { CampaignSaveData::CampaignSaveData() - : _currentScenarioID( 0 ) - , _campaignID( 0 ) + : _currentScenarioInfoId( -1, -1 ) , _daysPassed( 0 ) - {} + { + // Do nothing. + } CampaignSaveData & Campaign::CampaignSaveData::Get() { @@ -54,21 +56,17 @@ namespace Campaign _currentScenarioBonus = bonus; } - void CampaignSaveData::setCurrentScenarioID( const int scenarioID ) + void CampaignSaveData::setCurrentScenarioInfoId( const ScenarioInfoId & scenarioInfoId ) { - _currentScenarioID = scenarioID; - } - - void CampaignSaveData::setCampaignID( const int campaignID ) - { - _campaignID = campaignID; + assert( scenarioInfoId.campaignId >= 0 && scenarioInfoId.scenarioId >= 0 ); + _currentScenarioInfoId = scenarioInfoId; } void CampaignSaveData::addCurrentMapToFinished() { - const bool isNotDuplicate = std::find( _finishedMaps.begin(), _finishedMaps.end(), _currentScenarioID ) == _finishedMaps.end(); + const bool isNotDuplicate = std::find( _finishedMaps.begin(), _finishedMaps.end(), _currentScenarioInfoId ) == _finishedMaps.end(); if ( isNotDuplicate ) - _finishedMaps.emplace_back( _currentScenarioID ); + _finishedMaps.emplace_back( _currentScenarioInfoId ); } void CampaignSaveData::addDaysPassed( const uint32_t days ) @@ -81,8 +79,7 @@ namespace Campaign _finishedMaps.clear(); _obtainedCampaignAwards.clear(); _carryOverTroops.clear(); - _currentScenarioID = 0; - _campaignID = 0; + _currentScenarioInfoId = { -1, -1 }; _daysPassed = 0; } @@ -95,7 +92,7 @@ namespace Campaign } } - int CampaignSaveData::getLastCompletedScenarioID() const + const ScenarioInfoId & CampaignSaveData::getLastCompletedScenarioInfoID() const { assert( !_finishedMaps.empty() ); return _finishedMaps.back(); @@ -106,7 +103,7 @@ namespace Campaign std::vector obtainedAwards; for ( size_t i = 0; i < _finishedMaps.size(); ++i ) { - const std::vector awards = Campaign::CampaignAwardData::getCampaignAwardData( _campaignID, _finishedMaps[i] ); + const std::vector awards = Campaign::CampaignAwardData::getCampaignAwardData( _finishedMaps[i] ); for ( size_t j = 0; j < awards.size(); ++j ) { if ( std::find( _obtainedCampaignAwards.begin(), _obtainedCampaignAwards.end(), awards[j]._id ) != _obtainedCampaignAwards.end() ) @@ -114,7 +111,7 @@ namespace Campaign } } - const std::vector extraAwards = Campaign::CampaignAwardData::getExtraCampaignAwardData( _campaignID ); + const std::vector extraAwards = Campaign::CampaignAwardData::getExtraCampaignAwardData( _currentScenarioInfoId.campaignId ); for ( const Campaign::CampaignAwardData & award : extraAwards ) { if ( std::find( _obtainedCampaignAwards.begin(), _obtainedCampaignAwards.end(), award._id ) != _obtainedCampaignAwards.end() ) obtainedAwards.emplace_back( award ); @@ -125,14 +122,31 @@ namespace Campaign StreamBase & operator<<( StreamBase & msg, const CampaignSaveData & data ) { - return msg << data._currentScenarioID << data._currentScenarioBonus << data._finishedMaps << data._campaignID << data._daysPassed << data._obtainedCampaignAwards - << data._carryOverTroops; + return msg << data._currentScenarioInfoId.campaignId << data._currentScenarioInfoId.scenarioId << data._currentScenarioBonus << data._finishedMaps + << data._daysPassed << data._obtainedCampaignAwards << data._carryOverTroops; } StreamBase & operator>>( StreamBase & msg, CampaignSaveData & data ) { - return msg >> data._currentScenarioID >> data._currentScenarioBonus >> data._finishedMaps >> data._campaignID >> data._daysPassed >> data._obtainedCampaignAwards - >> data._carryOverTroops; + return msg >> data._currentScenarioInfoId.campaignId >> data._currentScenarioInfoId.scenarioId >> data._currentScenarioBonus >> data._finishedMaps + >> data._daysPassed >> data._obtainedCampaignAwards >> data._carryOverTroops; + } + + void CampaignSaveData::loadOldSaveSata( StreamBase & msg, CampaignSaveData & data ) + { + static_assert( LAST_SUPPORTED_FORMAT_VERSION < FORMAT_VERSION_0912_RELEASE, "Remove this method." ); + + msg >> data._currentScenarioInfoId.scenarioId >> data._currentScenarioBonus; + + std::vector finishedMaps; + msg >> finishedMaps; + + msg >> data._currentScenarioInfoId.campaignId >> data._daysPassed >> data._obtainedCampaignAwards >> data._carryOverTroops; + + data._finishedMaps.clear(); + for ( const int mapId : finishedMaps ) { + data._finishedMaps.emplace_back( data._currentScenarioInfoId.campaignId, mapId ); + } } ScenarioVictoryCondition getCurrentScenarioVictoryCondition() diff --git a/src/fheroes2/campaign/campaign_savedata.h b/src/fheroes2/campaign/campaign_savedata.h index b74eebaadd5..5867aad0dd2 100644 --- a/src/fheroes2/campaign/campaign_savedata.h +++ b/src/fheroes2/campaign/campaign_savedata.h @@ -35,23 +35,28 @@ namespace Campaign public: CampaignSaveData(); - const std::vector & getFinishedMaps() const + const std::vector & getFinishedMaps() const { return _finishedMaps; } int getCampaignID() const { - return _campaignID; + return _currentScenarioInfoId.campaignId; } int getCurrentScenarioID() const { - return _currentScenarioID; + return _currentScenarioInfoId.scenarioId; + } + + const ScenarioInfoId & getCurrentScenarioInfoId() const + { + return _currentScenarioInfoId; } // Make sure that this is not the first scenario in the campaign. Please call isStarting to verify this. - int getLastCompletedScenarioID() const; + const ScenarioInfoId & getLastCompletedScenarioInfoID() const; bool isStarting() const { @@ -71,8 +76,7 @@ namespace Campaign std::vector getObtainedCampaignAwards() const; void setCurrentScenarioBonus( const ScenarioBonusData & bonus ); - void setCurrentScenarioID( const int scenarioID ); - void setCampaignID( const int campaignID ); + void setCurrentScenarioInfoId( const ScenarioInfoId & scenarioInfoId ); void addCurrentMapToFinished(); void addCampaignAward( const int awardID ); void setCarryOverTroops( const Troops & troops ); @@ -80,17 +84,25 @@ namespace Campaign void addDaysPassed( const uint32_t days ); void removeCampaignAward( const int awardID ); + void removeAllAwards() + { + _obtainedCampaignAwards.clear(); + } + static CampaignSaveData & Get(); + static void loadOldSaveSata( StreamBase & msg, CampaignSaveData & data ); + private: friend StreamBase & operator<<( StreamBase & msg, const CampaignSaveData & data ); friend StreamBase & operator>>( StreamBase & msg, CampaignSaveData & data ); - std::vector _finishedMaps; + std::vector _finishedMaps; std::vector _obtainedCampaignAwards; std::vector _carryOverTroops; - int _currentScenarioID; - int _campaignID; + + ScenarioInfoId _currentScenarioInfoId; + uint32_t _daysPassed; ScenarioBonusData _currentScenarioBonus; }; diff --git a/src/fheroes2/campaign/campaign_scenariodata.cpp b/src/fheroes2/campaign/campaign_scenariodata.cpp index fac84d18eed..c638939dbf3 100644 --- a/src/fheroes2/campaign/campaign_scenariodata.cpp +++ b/src/fheroes2/campaign/campaign_scenariodata.cpp @@ -80,6 +80,11 @@ namespace bonus.emplace_back( Campaign::ScenarioBonusData::ARTIFACT, Artifact::HIDEOUS_MASK, 1 ); bonus.emplace_back( Campaign::ScenarioBonusData::ARTIFACT, Artifact::FIZBIN_MISFORTUNE, 1 ); break; + case 10: + bonus.emplace_back( Campaign::ScenarioBonusData::STARTING_RACE, Race::NECR, 1 ); + bonus.emplace_back( Campaign::ScenarioBonusData::STARTING_RACE, Race::WRLK, 1 ); + bonus.emplace_back( Campaign::ScenarioBonusData::STARTING_RACE, Race::BARB, 1 ); + break; default: assert( 0 ); break; @@ -140,6 +145,11 @@ namespace bonus.emplace_back( Campaign::ScenarioBonusData::ARTIFACT, Artifact::HIDEOUS_MASK, 1 ); bonus.emplace_back( Campaign::ScenarioBonusData::ARTIFACT, Artifact::FIZBIN_MISFORTUNE, 1 ); break; + case 11: + bonus.emplace_back( Campaign::ScenarioBonusData::STARTING_RACE, Race::WZRD, 1 ); + bonus.emplace_back( Campaign::ScenarioBonusData::STARTING_RACE, Race::SORC, 1 ); + bonus.emplace_back( Campaign::ScenarioBonusData::STARTING_RACE, Race::KNGT, 1 ); + break; default: assert( 0 ); break; @@ -396,6 +406,16 @@ namespace namespace Campaign { + StreamBase & operator<<( StreamBase & msg, const ScenarioInfoId & data ) + { + return msg << data.campaignId << data.scenarioId; + } + + StreamBase & operator>>( StreamBase & msg, ScenarioInfoId & data ) + { + return msg >> data.campaignId >> data.scenarioId; + } + ScenarioBonusData::ScenarioBonusData() : _type( 0 ) , _subType( 0 ) @@ -446,22 +466,22 @@ namespace Campaign return useAmount ? std::to_string( _amount ) + " " + objectName : objectName; } - std::vector ScenarioBonusData::getCampaignBonusData( const int campaignID, const int scenarioID ) + std::vector ScenarioBonusData::getCampaignBonusData( const ScenarioInfoId & scenarioInfo ) { - assert( scenarioID >= 0 ); - switch ( campaignID ) { + assert( scenarioInfo.scenarioId >= 0 ); + switch ( scenarioInfo.campaignId ) { case Campaign::ROLAND_CAMPAIGN: - return getRolandCampaignBonusData( scenarioID ); + return getRolandCampaignBonusData( scenarioInfo.scenarioId ); case Campaign::ARCHIBALD_CAMPAIGN: - return getArchibaldCampaignBonusData( scenarioID ); + return getArchibaldCampaignBonusData( scenarioInfo.scenarioId ); case Campaign::PRICE_OF_LOYALTY_CAMPAIGN: - return getPriceOfLoyaltyCampaignBonusData( scenarioID ); + return getPriceOfLoyaltyCampaignBonusData( scenarioInfo.scenarioId ); case Campaign::VOYAGE_HOME_CAMPAIGN: - return getVoyageHomeCampaignBonusData( scenarioID ); + return getVoyageHomeCampaignBonusData( scenarioInfo.scenarioId ); case Campaign::WIZARDS_ISLE_CAMPAIGN: - return getWizardsIsleCampaignBonusData( scenarioID ); + return getWizardsIsleCampaignBonusData( scenarioInfo.scenarioId ); case Campaign::DESCENDANTS_CAMPAIGN: - return getDescendantsCampaignBonusData( scenarioID ); + return getDescendantsCampaignBonusData( scenarioInfo.scenarioId ); default: // Did you add a new campaign? Add the corresponding case above. assert( 0 ); @@ -482,13 +502,13 @@ namespace Campaign return msg >> data._type >> data._subType >> data._amount; } - ScenarioData::ScenarioData( int scenarioID, const std::vector & nextMaps, const std::vector & bonuses, const std::string & fileName, + ScenarioData::ScenarioData( const ScenarioInfoId & scenarioInfo, std::vector && nextScenarios, const std::string & fileName, const std::string & scenarioName, const std::string & description, const VideoSequence & startScenarioVideoPlayback, const VideoSequence & endScenarioVideoPlayback, const ScenarioVictoryCondition victoryCondition, const ScenarioLossCondition lossCondition ) - : _scenarioID( scenarioID ) - , _nextMaps( nextMaps ) - , _bonuses( bonuses ) + : _scenarioInfo( scenarioInfo ) + , _nextScenarios( std::move( nextScenarios ) ) + , _bonuses( ScenarioBonusData::getCampaignBonusData( scenarioInfo ) ) , _fileName( StringLower( fileName ) ) , _scenarioName( scenarioName ) , _description( description ) diff --git a/src/fheroes2/campaign/campaign_scenariodata.h b/src/fheroes2/campaign/campaign_scenariodata.h index 6a1d10029b8..e3bbff0cfe0 100644 --- a/src/fheroes2/campaign/campaign_scenariodata.h +++ b/src/fheroes2/campaign/campaign_scenariodata.h @@ -50,6 +50,35 @@ namespace Campaign LOSE_ALL_SORCERESS_VILLAGES = 1 }; + struct ScenarioInfoId + { + ScenarioInfoId() = default; + + ScenarioInfoId( const int campaignId_, const int scenarioId_ ) + : campaignId( campaignId_ ) + , scenarioId( scenarioId_ ) + { + // Do nothing. + } + + bool operator==( const ScenarioInfoId & info ) const + { + return campaignId == info.campaignId && scenarioId == info.scenarioId; + } + + bool operator!=( const ScenarioInfoId & info ) const + { + return !operator==( info ); + } + + friend StreamBase & operator<<( StreamBase & msg, const ScenarioInfoId & data ); + friend StreamBase & operator>>( StreamBase & msg, ScenarioInfoId & data ); + + int campaignId{ -1 }; + + int scenarioId{ -1 }; + }; + struct ScenarioBonusData { public: @@ -77,7 +106,7 @@ namespace Campaign std::string ToString() const; - static std::vector getCampaignBonusData( const int campaignID, const int scenarioID ); + static std::vector getCampaignBonusData( const ScenarioInfoId & scenarioInfo ); }; struct ScenarioIntroVideoInfo @@ -92,14 +121,14 @@ namespace Campaign { public: ScenarioData() = delete; - ScenarioData( int scenarioID, const std::vector & nextMaps, const std::vector & bonuses, const std::string & fileName, - const std::string & scenarioName, const std::string & description, const VideoSequence & startScenarioVideoPlayback, - const VideoSequence & endScenarioVideoPlayback, const ScenarioVictoryCondition victoryCondition = ScenarioVictoryCondition::STANDARD, + ScenarioData( const ScenarioInfoId & scenarioInfo, std::vector && nextScenarios, const std::string & fileName, const std::string & scenarioName, + const std::string & description, const VideoSequence & startScenarioVideoPlayback, const VideoSequence & endScenarioVideoPlayback, + const ScenarioVictoryCondition victoryCondition = ScenarioVictoryCondition::STANDARD, const ScenarioLossCondition lossCondition = ScenarioLossCondition::STANDARD ); - const std::vector & getNextMaps() const + const std::vector & getNextScenarios() const { - return _nextMaps; + return _nextScenarios; } const std::vector & getBonuses() const @@ -109,7 +138,17 @@ namespace Campaign int getScenarioID() const { - return _scenarioID; + return _scenarioInfo.scenarioId; + } + + int getCampaignId() const + { + return _scenarioInfo.campaignId; + } + + const ScenarioInfoId & getScenarioInfoId() const + { + return _scenarioInfo; } const char * getScenarioName() const; @@ -140,8 +179,8 @@ namespace Campaign Maps::FileInfo loadMap() const; private: - int _scenarioID; - std::vector _nextMaps; + ScenarioInfoId _scenarioInfo; + std::vector _nextScenarios; std::vector _bonuses; std::string _fileName; // Note: There are inconsistencies with the content of the map file in regards to the map name and description, so we'll be getting them from somewhere else diff --git a/src/fheroes2/game/game_campaign.cpp b/src/fheroes2/game/game_campaign.cpp index 1dcd0a091a2..50682a2b32f 100644 --- a/src/fheroes2/game/game_campaign.cpp +++ b/src/fheroes2/game/game_campaign.cpp @@ -41,13 +41,27 @@ namespace { - std::vector getCampaignIconOffsets( const int campaignId ) + enum ScenarioIcon : uint32_t + { + SCENARIO_ICON_CLEARED = 0, + SCENARIO_ICON_AVAILABLE = 1, + SCENARIO_ICON_UNAVAILABLE = 2, + }; + + const int betrayalScenarioId = 4; + + std::vector getCampaignIconOffsets( const int campaignId, const bool archibaldLowerBetrayalBranch = false ) { switch ( campaignId ) { case Campaign::ROLAND_CAMPAIGN: - return { { 0, 1 }, { 2, 1 }, { 3, 0 }, { 4, 1 }, { 6, 1 }, { 8, 1 }, { 10, 2 }, { 10, 0 }, { 12, 1 }, { 14, 1 } }; + return { { 0, 1 }, { 2, 1 }, { 3, 0 }, { 4, 1 }, { 6, 1 }, { 8, 1 }, { 10, 2 }, { 10, 0 }, { 12, 1 }, { 14, 1 }, { 6, 2 } }; case Campaign::ARCHIBALD_CAMPAIGN: - return { { 0, 1 }, { 2, 1 }, { 4, 0 }, { 4, 2 }, { 6, 1 }, { 8, 1 }, { 9, 0 }, { 10, 1 }, { 12, 0 }, { 12, 2 }, { 14, 1 } }; + if ( archibaldLowerBetrayalBranch ) { + return { { 0, 1 }, { 2, 1 }, { 4, 0 }, { 4, 2 }, { 6, 1 }, { 8, 1 }, { 9, 0 }, { 10, 1 }, { 12, 0 }, { 12, 2 }, { 14, 1 }, { 6, 2 } }; + } + else { + return { { 0, 1 }, { 2, 1 }, { 4, 0 }, { 4, 2 }, { 6, 1 }, { 8, 1 }, { 9, 0 }, { 10, 1 }, { 12, 0 }, { 12, 2 }, { 14, 1 }, { 6, 0 } }; + } case Campaign::PRICE_OF_LOYALTY_CAMPAIGN: return { { 0, 0 }, { 2, 0 }, { 4, 1 }, { 4, 0 }, { 6, 1 }, { 7, 0 }, { 9, 1 }, { 10, 0 } }; case Campaign::DESCENDANTS_CAMPAIGN: @@ -63,122 +77,245 @@ namespace } } - enum ScenarioIcon : uint32_t + bool isBetrayalScenario( const Campaign::ScenarioInfoId & scenarioInfoId ) { - SCENARIO_ICON_CLEARED = 0, - SCENARIO_ICON_AVAILABLE = 1, - SCENARIO_ICON_UNAVAILABLE = 2, - }; + // Betrayal scenario is a special scenario of switching between campaigns. Next scenarios after this must have different campaign ID. + for ( const Campaign::ScenarioInfoId & nextScenario : Campaign::CampaignData::getScenariosAfter( scenarioInfoId ) ) { + if ( nextScenario.campaignId != scenarioInfoId.campaignId ) { + return true; + } + } - void DrawCampaignScenarioIcon( const int icnId, const int iconIdx, const fheroes2::Point & offset, const int posX, const int posY ) - { - const fheroes2::Sprite & icon = fheroes2::AGG::GetICN( icnId, iconIdx ); - fheroes2::Blit( icon, fheroes2::Display::instance(), offset.x + posX, offset.y + posY ); + return false; } - void DrawCampaignScenarioIcons( fheroes2::ButtonGroup & buttonGroup, const Campaign::CampaignData & campaignData, const fheroes2::Point & top, - const int chosenScenarioId ) + void getScenarioIconId( const Campaign::ScenarioInfoId & scenarioInfoId, int & iconsId, uint32_t & iconStatusOffset, uint32_t & selectedIconIdx ) { - fheroes2::Display & display = fheroes2::Display::instance(); + iconsId = ICN::UNKNOWN; + iconStatusOffset = 0; + selectedIconIdx = 0; - int campaignTrack = ICN::UNKNOWN; - int iconsId = ICN::UNKNOWN; - uint32_t iconStatusOffset = 0; - uint32_t selectedIconIdx = 0; - - switch ( campaignData.getCampaignID() ) { + switch ( scenarioInfoId.campaignId ) { case Campaign::ROLAND_CAMPAIGN: - campaignTrack = ICN::CTRACK00; iconsId = ICN::CAMPXTRG; iconStatusOffset = 10; - selectedIconIdx = 14; - break; + selectedIconIdx = isBetrayalScenario( scenarioInfoId ) ? 17 : 14; + return; case Campaign::ARCHIBALD_CAMPAIGN: - campaignTrack = ICN::CTRACK03; iconsId = ICN::CAMPXTRE; iconStatusOffset = 10; - selectedIconIdx = 17; - break; + selectedIconIdx = isBetrayalScenario( scenarioInfoId ) ? 14 : 17; + return; case Campaign::PRICE_OF_LOYALTY_CAMPAIGN: - campaignTrack = ICN::X_TRACK1; iconsId = ICN::X_CMPEXT; iconStatusOffset = 0; selectedIconIdx = 4; - break; + return; case Campaign::DESCENDANTS_CAMPAIGN: - campaignTrack = ICN::X_TRACK2; iconsId = ICN::X_CMPEXT; iconStatusOffset = 0; selectedIconIdx = 7; - break; + return; case Campaign::WIZARDS_ISLE_CAMPAIGN: - campaignTrack = ICN::X_TRACK3; iconsId = ICN::X_CMPEXT; iconStatusOffset = 0; selectedIconIdx = 10; - break; + return; case Campaign::VOYAGE_HOME_CAMPAIGN: - campaignTrack = ICN::X_TRACK4; iconsId = ICN::X_CMPEXT; iconStatusOffset = 0; selectedIconIdx = 13; - break; + return; default: // Implementing a new campaign? Add a new case! assert( 0 ); - break; + return; } + } - const fheroes2::Sprite & track = fheroes2::AGG::GetICN( campaignTrack, 0 ); - const fheroes2::Point trackOffset( top.x + track.x(), top.y + track.y() ); - fheroes2::Blit( track, display, trackOffset.x, trackOffset.y ); + void DrawCampaignScenarioIcon( const int icnId, const uint32_t iconIdx, const fheroes2::Point & offset, const int posX, const int posY ) + { + const fheroes2::Sprite & icon = fheroes2::AGG::GetICN( icnId, iconIdx ); + fheroes2::Blit( icon, fheroes2::Display::instance(), offset.x + posX, offset.y + posY ); + } - const std::vector & iconOffsets = getCampaignIconOffsets( campaignData.getCampaignID() ); + void addScenarioButton( fheroes2::ButtonGroup & buttonGroup, const int buttonId, const Campaign::ScenarioInfoId & scenarioInfo, + const std::vector & availableMaps, const std::vector & clearedMaps, + const fheroes2::Point & trackOffset, const bool archibaldLowerBetrayalBranch ) + { const int deltaY = 42; const int deltaX = 37; - const std::vector & scenarios = campaignData.getAllScenarios(); - const Campaign::CampaignSaveData & saveData = Campaign::CampaignSaveData::Get(); + const std::vector & iconOffsets = getCampaignIconOffsets( scenarioInfo.campaignId, archibaldLowerBetrayalBranch ); + assert( scenarioInfo.scenarioId >= 0 && static_cast( scenarioInfo.scenarioId ) < iconOffsets.size() ); + if ( scenarioInfo.scenarioId < 0 || static_cast( scenarioInfo.scenarioId ) >= iconOffsets.size() ) { + return; + } + + fheroes2::Point offset = iconOffsets[scenarioInfo.scenarioId]; + offset.x *= deltaX; + offset.y *= deltaY; + + offset.x -= 2; + offset.y -= 2; + + int iconsId = -1; + uint32_t iconStatusOffset = 0; + uint32_t selectedIconIdx = 0; + + getScenarioIconId( scenarioInfo, iconsId, iconStatusOffset, selectedIconIdx ); + + // available scenario (one of which should be selected) + if ( std::find( availableMaps.begin(), availableMaps.end(), scenarioInfo ) != availableMaps.end() ) { + const fheroes2::Sprite & availableIcon = fheroes2::AGG::GetICN( iconsId, iconStatusOffset + SCENARIO_ICON_AVAILABLE ); + const fheroes2::Sprite & selectedIcon = fheroes2::AGG::GetICN( iconsId, selectedIconIdx ); + buttonGroup.createButton( trackOffset.x + offset.x, trackOffset.y + offset.y, availableIcon, selectedIcon, buttonId ); + } + // cleared scenario + else if ( std::find( clearedMaps.begin(), clearedMaps.end(), scenarioInfo ) != clearedMaps.end() ) { + if ( isBetrayalScenario( scenarioInfo ) ) { + assert( static_cast( betrayalScenarioId ) < iconOffsets.size() ); + offset = iconOffsets[betrayalScenarioId]; + offset.x *= deltaX; + offset.y *= deltaY; + + offset.x -= 2; + offset.y -= 2; + } + + DrawCampaignScenarioIcon( iconsId, iconStatusOffset + SCENARIO_ICON_CLEARED, trackOffset, offset.x, offset.y ); + } + else if ( !isBetrayalScenario( scenarioInfo ) ) { + DrawCampaignScenarioIcon( iconsId, iconStatusOffset + SCENARIO_ICON_UNAVAILABLE, trackOffset, offset.x, offset.y ); + } + } + + void DrawCampaignScenarioIcons( fheroes2::ButtonGroup & buttonGroup, const Campaign::CampaignData & campaignData, const fheroes2::Point & top, + const int chosenScenarioId ) + { std::vector prevScenarioNextMaps; - const std::vector & clearedMaps = saveData.getFinishedMaps(); - std::vector availableMaps; + const Campaign::CampaignSaveData & saveData = Campaign::CampaignSaveData::Get(); + const std::vector & clearedMaps = saveData.getFinishedMaps(); + + std::vector availableMaps; if ( chosenScenarioId >= 0 ) { - availableMaps.emplace_back( chosenScenarioId ); + availableMaps.emplace_back( saveData.getCampaignID(), chosenScenarioId ); } else { - availableMaps = saveData.isStarting() ? campaignData.getStartingScenarios() : campaignData.getScenariosAfter( saveData.getLastCompletedScenarioID() ); + availableMaps + = saveData.isStarting() ? campaignData.getStartingScenarios() : Campaign::CampaignData::getScenariosAfter( saveData.getLastCompletedScenarioInfoID() ); } - assert( iconOffsets.size() == scenarios.size() ); - - for ( size_t i = 0; i < scenarios.size(); ++i ) { - const int scenarioID = scenarios[i].getScenarioID(); + bool isBetrayalScenarioNext = false; + for ( const Campaign::ScenarioInfoId & scenarioInfo : availableMaps ) { + if ( isBetrayalScenario( scenarioInfo ) ) { + isBetrayalScenarioNext = true; + break; + } + } - assert( scenarioID >= 0 && static_cast( scenarioID ) < iconOffsets.size() ); - if ( scenarioID < 0 || static_cast( scenarioID ) >= iconOffsets.size() ) { - continue; + bool isBetrayalScenarioCompleted = false; + if ( !isBetrayalScenarioNext ) { + for ( const Campaign::ScenarioInfoId & scenarioInfo : clearedMaps ) { + if ( isBetrayalScenario( scenarioInfo ) ) { + isBetrayalScenarioCompleted = true; + break; + } } + } - fheroes2::Point offset = iconOffsets[scenarioID]; - offset.x *= deltaX; - offset.y *= deltaY; + int campaignTrack = ICN::UNKNOWN; - offset.x -= 2; - offset.y -= 2; + bool archibaldLowerBetrayalBranch = false; - // available scenario (one of which should be selected) - if ( std::find( availableMaps.begin(), availableMaps.end(), scenarioID ) != availableMaps.end() ) { - const fheroes2::Sprite & availableIcon = fheroes2::AGG::GetICN( iconsId, iconStatusOffset + SCENARIO_ICON_AVAILABLE ); - const fheroes2::Sprite & selectedIcon = fheroes2::AGG::GetICN( iconsId, selectedIconIdx ); - buttonGroup.createButton( trackOffset.x + offset.x, trackOffset.y + offset.y, availableIcon, selectedIcon, static_cast( i ) ); + switch ( campaignData.getCampaignID() ) { + case Campaign::ROLAND_CAMPAIGN: + if ( isBetrayalScenarioNext ) { + campaignTrack = ICN::CTRACK04; } - // cleared scenario - else if ( std::find( clearedMaps.begin(), clearedMaps.end(), static_cast( i ) ) != clearedMaps.end() ) { - DrawCampaignScenarioIcon( iconsId, iconStatusOffset + SCENARIO_ICON_CLEARED, trackOffset, offset.x, offset.y ); + else if ( isBetrayalScenarioCompleted ) { + campaignTrack = ICN::CTRACK02; } else { - DrawCampaignScenarioIcon( iconsId, iconStatusOffset + SCENARIO_ICON_UNAVAILABLE, trackOffset, offset.x, offset.y ); + campaignTrack = ICN::CTRACK00; + } + break; + case Campaign::ARCHIBALD_CAMPAIGN: + if ( isBetrayalScenarioNext ) { + // Archibald campaign is unique in terms of scenario branching so this is the only way to make it work. + assert( !clearedMaps.empty() ); + if ( clearedMaps.back().scenarioId == 2 ) { + campaignTrack = ICN::CTRACK05; + } + else { + assert( clearedMaps.back().scenarioId == 3 ); + campaignTrack = ICN::CTRACK06; + archibaldLowerBetrayalBranch = true; + } + } + else if ( isBetrayalScenarioCompleted ) { + campaignTrack = ICN::CTRACK01; + } + else { + campaignTrack = ICN::CTRACK03; + } + break; + case Campaign::PRICE_OF_LOYALTY_CAMPAIGN: + campaignTrack = ICN::X_TRACK1; + break; + case Campaign::DESCENDANTS_CAMPAIGN: + campaignTrack = ICN::X_TRACK2; + break; + case Campaign::WIZARDS_ISLE_CAMPAIGN: + campaignTrack = ICN::X_TRACK3; + break; + case Campaign::VOYAGE_HOME_CAMPAIGN: + campaignTrack = ICN::X_TRACK4; + break; + default: + // Implementing a new campaign? Add a new case! + assert( 0 ); + return; + } + + const fheroes2::Sprite & track = fheroes2::AGG::GetICN( campaignTrack, 0 ); + const fheroes2::Point trackOffset( top.x + track.x(), top.y + track.y() ); + fheroes2::Blit( track, fheroes2::Display::instance(), trackOffset.x, trackOffset.y ); + + if ( isBetrayalScenarioCompleted ) { + assert( campaignData.getCampaignID() == Campaign::ROLAND_CAMPAIGN || campaignData.getCampaignID() == Campaign::ARCHIBALD_CAMPAIGN ); + + const bool isRolandCurrentCampaign = ( campaignData.getCampaignID() == Campaign::ROLAND_CAMPAIGN ); + + const Campaign::CampaignData & beforeCampaignData + = Campaign::CampaignData::getCampaignData( isRolandCurrentCampaign ? Campaign::ARCHIBALD_CAMPAIGN : Campaign::ROLAND_CAMPAIGN ); + const Campaign::CampaignData & currentCampaignData + = Campaign::CampaignData::getCampaignData( isRolandCurrentCampaign ? Campaign::ROLAND_CAMPAIGN : Campaign::ARCHIBALD_CAMPAIGN ); + + int scenarioCounter = 0; + + for ( const Campaign::ScenarioData & scenarioData : beforeCampaignData.getAllScenarios() ) { + if ( scenarioData.getScenarioID() <= betrayalScenarioId || isBetrayalScenario( scenarioData.getScenarioInfoId() ) ) { + const Campaign::ScenarioInfoId scenarioInfo{ scenarioData.getCampaignId(), scenarioData.getScenarioID() }; + addScenarioButton( buttonGroup, scenarioCounter, scenarioInfo, availableMaps, clearedMaps, trackOffset, archibaldLowerBetrayalBranch ); + ++scenarioCounter; + } + } + + for ( const Campaign::ScenarioData & scenarioData : currentCampaignData.getAllScenarios() ) { + if ( scenarioData.getScenarioID() > betrayalScenarioId ) { + const Campaign::ScenarioInfoId scenarioInfo{ scenarioData.getCampaignId(), scenarioData.getScenarioID() }; + addScenarioButton( buttonGroup, scenarioCounter, scenarioInfo, availableMaps, clearedMaps, trackOffset, archibaldLowerBetrayalBranch ); + ++scenarioCounter; + } + } + } + else { + const std::vector & scenarios = campaignData.getAllScenarios(); + for ( size_t i = 0; i < scenarios.size(); ++i ) { + const Campaign::ScenarioInfoId scenarioInfo{ scenarios[i].getCampaignId(), scenarios[i].getScenarioID() }; + addScenarioButton( buttonGroup, static_cast( i ), scenarioInfo, availableMaps, clearedMaps, trackOffset, archibaldLowerBetrayalBranch ); } } } @@ -189,7 +326,13 @@ namespace TextBox mapName( scenario.getScenarioName(), Font::BIG, 200 ); mapName.Blit( top.x + 197, top.y + 97 - mapName.h() / 2 ); - Text campaignMapId( std::to_string( scenario.getScenarioID() + 1 ), Font::BIG ); + int scenarioId = scenario.getScenarioID() + 1; + if ( isBetrayalScenario( scenario.getScenarioInfoId() ) ) { + assert( scenario.getCampaignId() == Campaign::ARCHIBALD_CAMPAIGN || scenario.getCampaignId() == Campaign::ROLAND_CAMPAIGN ); + scenarioId = betrayalScenarioId + 1; + } + + Text campaignMapId( std::to_string( scenarioId ), Font::BIG ); campaignMapId.Blit( top.x + 172 - campaignMapId.w() / 2, top.y + 97 - campaignMapId.h() / 2 ); TextBox mapDescription( scenario.getDescription(), Font::BIG, 356 ); @@ -202,11 +345,16 @@ namespace } } - void drawObtainedCampaignAwards( const std::vector & obtainedAwards, const fheroes2::Point & top ) + void drawObtainedCampaignAwards( const Campaign::CampaignSaveData & campaignSaveData, const fheroes2::Point & top ) { + if ( isBetrayalScenario( campaignSaveData.getCurrentScenarioInfoId() ) ) { + return; + } + const int textAwardWidth = 180; // 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 + const std::vector obtainedAwards = campaignSaveData.getObtainedCampaignAwards(); const size_t awardCount = obtainedAwards.size(); const size_t indexEnd = awardCount <= 4 ? awardCount : 4; const int yOffset = awardCount > 3 ? 16 : 22; @@ -234,11 +382,11 @@ namespace army.GetTroop( i )->Set( troops[i] ); } - void setHeroAndArmyBonus( Heroes * hero, const int campaignID, const uint32_t currentScenarioID ) + void setHeroAndArmyBonus( Heroes * hero, const Campaign::ScenarioInfoId & scenarioInfoId ) { - switch ( campaignID ) { + switch ( scenarioInfoId.campaignId ) { case Campaign::ARCHIBALD_CAMPAIGN: { - if ( currentScenarioID != 6 ) { + if ( scenarioInfoId.scenarioId != 6 ) { assert( 0 ); // no other scenario has this bonus return; } @@ -266,7 +414,7 @@ namespace } } - void SetScenarioBonus( const int campaignID, const uint32_t currentScenarioID, const Campaign::ScenarioBonusData & scenarioBonus ) + void SetScenarioBonus( const Campaign::ScenarioInfoId & scenarioInfoId, const Campaign::ScenarioBonusData & scenarioBonus ) { const Players & sortedPlayers = Settings::Get().GetPlayers(); for ( const Player * player : sortedPlayers ) { @@ -312,7 +460,7 @@ namespace case Campaign::ScenarioBonusData::STARTING_RACE_AND_ARMY: assert( bestHero != nullptr ); if ( bestHero != nullptr ) { - setHeroAndArmyBonus( bestHero, campaignID, currentScenarioID ); + setHeroAndArmyBonus( bestHero, scenarioInfoId ); } break; case Campaign::ScenarioBonusData::SKILL_PRIMARY: @@ -336,13 +484,13 @@ namespace // apply only the ones that are applied at the start (artifact, spell, carry-over troops) // the rest will be applied based on the situation required - void applyObtainedCampaignAwards( const uint32_t currentScenarioID, const std::vector & awards ) + void applyObtainedCampaignAwards( const Campaign::ScenarioInfoId & currentScenarioInfoId, const std::vector & awards ) { const Players & sortedPlayers = Settings::Get().GetPlayers(); Kingdom & humanKingdom = world.GetKingdom( Players::HumanColors() ); for ( size_t i = 0; i < awards.size(); ++i ) { - if ( currentScenarioID < awards[i]._startScenarioID ) + if ( currentScenarioInfoId.scenarioId < static_cast( awards[i]._startScenarioID ) ) continue; switch ( awards[i]._type ) { @@ -380,12 +528,13 @@ namespace return; } - const int lastCompletedScenarioID = saveData.getLastCompletedScenarioID(); - const Campaign::CampaignData & campaignData = Campaign::CampaignData::getCampaignData( saveData.getCampaignID() ); + const Campaign::ScenarioInfoId & lastCompletedScenarioInfoId = saveData.getLastCompletedScenarioInfoID(); + + const Campaign::CampaignData & campaignData = Campaign::CampaignData::getCampaignData( lastCompletedScenarioInfoId.campaignId ); const std::vector & scenarios = campaignData.getAllScenarios(); - assert( lastCompletedScenarioID >= 0 && static_cast( lastCompletedScenarioID ) < scenarios.size() ); - const Campaign::ScenarioData & completedScenario = scenarios[lastCompletedScenarioID]; + assert( lastCompletedScenarioInfoId.scenarioId >= 0 && static_cast( lastCompletedScenarioInfoId.scenarioId ) < scenarios.size() ); + const Campaign::ScenarioData & completedScenario = scenarios[lastCompletedScenarioInfoId.scenarioId]; if ( !completedScenario.getEndScenarioVideoPlayback().empty() ) { AGG::ResetMixer(); @@ -402,12 +551,13 @@ namespace { const Campaign::CampaignSaveData & saveData = Campaign::CampaignSaveData::Get(); - const int chosenScenarioID = saveData.getCurrentScenarioID(); - const Campaign::CampaignData & campaignData = Campaign::CampaignData::getCampaignData( saveData.getCampaignID() ); + const Campaign::ScenarioInfoId & currentScenarioInfoId = saveData.getCurrentScenarioInfoId(); + + const Campaign::CampaignData & campaignData = Campaign::CampaignData::getCampaignData( currentScenarioInfoId.campaignId ); const std::vector & scenarios = campaignData.getAllScenarios(); - assert( chosenScenarioID >= 0 && static_cast( chosenScenarioID ) < scenarios.size() ); - const Campaign::ScenarioData & scenario = scenarios[chosenScenarioID]; + assert( currentScenarioInfoId.scenarioId >= 0 && static_cast( currentScenarioInfoId.scenarioId ) < scenarios.size() ); + const Campaign::ScenarioData & scenario = scenarios[currentScenarioInfoId.scenarioId]; if ( !scenario.getStartScenarioVideoPlayback().empty() ) { AGG::ResetMixer(); @@ -515,11 +665,7 @@ fheroes2::GameMode Game::CompleteCampaignScenario( const bool isLoadingSaveFile Game::SaveCompletedCampaignScenario(); } - const int lastCompletedScenarioID = saveData.getLastCompletedScenarioID(); - const Campaign::CampaignData & campaignData = Campaign::CampaignData::getCampaignData( saveData.getCampaignID() ); - - const std::vector obtainableAwards - = Campaign::CampaignAwardData::getCampaignAwardData( saveData.getCampaignID(), lastCompletedScenarioID ); + const std::vector obtainableAwards = Campaign::CampaignAwardData::getCampaignAwardData( saveData.getLastCompletedScenarioInfoID() ); // TODO: Check for awards that have to be obtained with 'freak' conditions for ( size_t i = 0; i < obtainableAwards.size(); ++i ) { @@ -574,8 +720,9 @@ fheroes2::GameMode Game::CompleteCampaignScenario( const bool isLoadingSaveFile playPreviosScenarioVideo(); - // TODO: do proper calc based on all scenarios cleared? - if ( campaignData.isLastScenario( lastCompletedScenarioID ) ) { + const Campaign::ScenarioInfoId & lastCompletedScenarioInfo = saveData.getLastCompletedScenarioInfoID(); + const Campaign::CampaignData & campaignData = Campaign::CampaignData::getCampaignData( saveData.getCampaignID() ); + if ( campaignData.isLastScenario( lastCompletedScenarioInfo ) ) { Game::ShowCredits(); AGG::ResetMixer(); @@ -583,8 +730,8 @@ fheroes2::GameMode Game::CompleteCampaignScenario( const bool isLoadingSaveFile return fheroes2::GameMode::HIGHSCORES; } - const int firstNextMap = campaignData.getScenariosAfter( lastCompletedScenarioID ).front(); - saveData.setCurrentScenarioID( firstNextMap ); + const Campaign::ScenarioInfoId firstNextMap = Campaign::CampaignData::getScenariosAfter( lastCompletedScenarioInfo ).front(); + saveData.setCurrentScenarioInfoId( firstNextMap ); return fheroes2::GameMode::SELECT_CAMPAIGN_SCENARIO; } @@ -602,9 +749,10 @@ fheroes2::GameMode Game::SelectCampaignScenario( const fheroes2::GameMode prevMo const Campaign::CampaignData & campaignData = Campaign::CampaignData::getCampaignData( chosenCampaignID ); - const int chosenScenarioID = campaignSaveData.getCurrentScenarioID(); + const Campaign::ScenarioInfoId & currentScenarioInfoId = campaignSaveData.getCurrentScenarioInfoId(); + const std::vector & scenarios = campaignData.getAllScenarios(); - const Campaign::ScenarioData & scenario = scenarios[chosenScenarioID]; + const Campaign::ScenarioData & scenario = scenarios[currentScenarioInfoId.scenarioId]; if ( !allowToRestart ) { playCurrentScenarioVideo(); @@ -699,26 +847,26 @@ fheroes2::GameMode Game::SelectCampaignScenario( const fheroes2::GameMode prevMo textDaysSpent.Blit( top.x + 582 - textDaysSpent.w() / 2, top.y + 31 ); DrawCampaignScenarioDescription( scenario, top ); - drawObtainedCampaignAwards( campaignSaveData.getObtainedCampaignAwards(), top ); + drawObtainedCampaignAwards( campaignSaveData, top ); - std::vector selectableScenarios; + std::vector selectableScenarios; if ( allowToRestart ) { - selectableScenarios.emplace_back( chosenScenarioID ); + selectableScenarios.emplace_back( currentScenarioInfoId ); } else { - selectableScenarios - = campaignSaveData.isStarting() ? campaignData.getStartingScenarios() : campaignData.getScenariosAfter( campaignSaveData.getLastCompletedScenarioID() ); + selectableScenarios = campaignSaveData.isStarting() ? campaignData.getStartingScenarios() + : Campaign::CampaignData::getScenariosAfter( campaignSaveData.getLastCompletedScenarioInfoID() ); } const uint32_t selectableScenariosCount = static_cast( selectableScenarios.size() ); fheroes2::ButtonGroup selectableScenarioButtons; - const int highlightedScenarioId = allowToRestart ? chosenScenarioID : -1; + const int highlightedScenarioId = allowToRestart ? currentScenarioInfoId.scenarioId : -1; DrawCampaignScenarioIcons( selectableScenarioButtons, campaignData, top, highlightedScenarioId ); for ( uint32_t i = 0; i < selectableScenariosCount; ++i ) { - if ( chosenScenarioID == selectableScenarios[i] ) + if ( currentScenarioInfoId == selectableScenarios[i] ) selectableScenarioButtons.button( i ).press(); selectableScenarioButtons.button( i ).draw(); @@ -756,8 +904,8 @@ fheroes2::GameMode Game::SelectCampaignScenario( const fheroes2::GameMode prevMo } for ( uint32_t i = 0; i < selectableScenariosCount; ++i ) { - if ( chosenScenarioID != selectableScenarios[i] && le.MousePressLeft( selectableScenarioButtons.button( i ).area() ) ) { - campaignSaveData.setCurrentScenarioID( selectableScenarios[i] ); + if ( currentScenarioInfoId != selectableScenarios[i] && le.MousePressLeft( selectableScenarioButtons.button( i ).area() ) ) { + campaignSaveData.setCurrentScenarioInfoId( selectableScenarios[i] ); return fheroes2::GameMode::SELECT_CAMPAIGN_SCENARIO; } } @@ -765,16 +913,27 @@ fheroes2::GameMode Game::SelectCampaignScenario( const fheroes2::GameMode prevMo if ( le.MouseClickLeft( buttonCancel.area() ) || HotKeyPressEvent( EVENT_DEFAULT_EXIT ) ) { return prevMode; } - if ( ( buttonOk.isEnabled() && ( le.MouseClickLeft( buttonOk.area() ) || HotKeyPressEvent( EVENT_DEFAULT_READY ) ) ) - || ( buttonRestart.isEnabled() && le.MouseClickLeft( buttonRestart.area() ) ) ) { + + const bool restartButtonClicked = ( buttonRestart.isEnabled() && le.MouseClickLeft( buttonRestart.area() ) ); + + if ( ( buttonOk.isEnabled() && ( le.MouseClickLeft( buttonOk.area() ) || HotKeyPressEvent( EVENT_DEFAULT_READY ) ) ) || restartButtonClicked ) { + if ( restartButtonClicked + && Dialog::Message( _( "Restart" ), _( "Are you sure you want to restart this scenario?" ), Font::BIG, Dialog::YES | Dialog::NO ) == Dialog::NO ) { + continue; + } + const Maps::FileInfo mapInfo = scenario.loadMap(); conf.SetCurrentFileInfo( mapInfo ); // starting faction scenario bonus has to be called before players.SetStartGame() if ( scenarioBonus._type == Campaign::ScenarioBonusData::STARTING_RACE || scenarioBonus._type == Campaign::ScenarioBonusData::STARTING_RACE_AND_ARMY ) { // but the army has to be set after starting the game, so first only set the race - SetScenarioBonus( campaignSaveData.getCampaignID(), chosenScenarioID, - { Campaign::ScenarioBonusData::STARTING_RACE, scenarioBonus._subType, scenarioBonus._amount } ); + SetScenarioBonus( currentScenarioInfoId, { Campaign::ScenarioBonusData::STARTING_RACE, scenarioBonus._subType, scenarioBonus._amount } ); + } + + // Betrayal scenario eliminates all obtained awards. + if ( isBetrayalScenario( currentScenarioInfoId ) ) { + campaignSaveData.removeAllAwards(); } Players & players = conf.GetPlayers(); @@ -796,13 +955,13 @@ fheroes2::GameMode Game::SelectCampaignScenario( const fheroes2::GameMode prevMo // meanwhile, the others should be called after players.SetStartGame() if ( scenarioBonus._type != Campaign::ScenarioBonusData::STARTING_RACE ) { - SetScenarioBonus( campaignSaveData.getCampaignID(), chosenScenarioID, scenarioBonus ); + SetScenarioBonus( currentScenarioInfoId, scenarioBonus ); } - applyObtainedCampaignAwards( chosenScenarioID, campaignSaveData.getObtainedCampaignAwards() ); + applyObtainedCampaignAwards( currentScenarioInfoId, campaignSaveData.getObtainedCampaignAwards() ); campaignSaveData.setCurrentScenarioBonus( scenarioBonus ); - campaignSaveData.setCurrentScenarioID( chosenScenarioID ); + campaignSaveData.setCurrentScenarioInfoId( currentScenarioInfoId ); return fheroes2::GameMode::START_GAME; } diff --git a/src/fheroes2/game/game_io.cpp b/src/fheroes2/game/game_io.cpp index 4bdb27fbb32..ed31513e004 100644 --- a/src/fheroes2/game/game_io.cpp +++ b/src/fheroes2/game/game_io.cpp @@ -227,9 +227,15 @@ fheroes2::GameMode Game::Load( const std::string & fn ) if ( conf.isCampaignGameType() ) { Campaign::CampaignSaveData & saveData = Campaign::CampaignSaveData::Get(); - fz >> saveData; + static_assert( LAST_SUPPORTED_FORMAT_VERSION < FORMAT_VERSION_0912_RELEASE, "Remove the usage of loadOldSaveSata method." ); + if ( binver < FORMAT_VERSION_0912_RELEASE ) { + Campaign::CampaignSaveData::loadOldSaveSata( fz, saveData ); + } + else { + fz >> saveData; + } - if ( !saveData.isStarting() && saveData.getCurrentScenarioID() == saveData.getLastCompletedScenarioID() ) { + if ( !saveData.isStarting() && saveData.getCurrentScenarioInfoId() == saveData.getLastCompletedScenarioInfoID() ) { // This is the end of the current scenario. We should show next scenario selection. returnValue = fheroes2::GameMode::COMPLETE_CAMPAIGN_SCENARIO_FROM_LOAD_FILE; } diff --git a/src/fheroes2/game/game_newgame.cpp b/src/fheroes2/game/game_newgame.cpp index 28c7dfc3100..7a87c254f85 100644 --- a/src/fheroes2/game/game_newgame.cpp +++ b/src/fheroes2/game/game_newgame.cpp @@ -203,8 +203,7 @@ fheroes2::GameMode Game::NewSuccessionWarsCampaign() Campaign::CampaignSaveData & campaignSaveData = Campaign::CampaignSaveData::Get(); campaignSaveData.reset(); - campaignSaveData.setCampaignID( chosenCampaign ); - campaignSaveData.setCurrentScenarioID( 0 ); + campaignSaveData.setCurrentScenarioInfoId( { chosenCampaign, 0 } ); AGG::PlayMusic( MUS::VICTORY, true, true ); @@ -216,8 +215,7 @@ fheroes2::GameMode Game::NewPriceOfLoyaltyCampaign() // TODO: Properly choose the campaign instead of this hackish way Campaign::CampaignSaveData & campaignSaveData = Campaign::CampaignSaveData::Get(); campaignSaveData.reset(); - campaignSaveData.setCampaignID( Campaign::PRICE_OF_LOYALTY_CAMPAIGN ); - campaignSaveData.setCurrentScenarioID( 0 ); + campaignSaveData.setCurrentScenarioInfoId( { Campaign::PRICE_OF_LOYALTY_CAMPAIGN, 0 } ); std::array, 4> videos{ getVideo( "IVYPOL.SMK" ), getVideo( "IVYVOY.SMK" ), getVideo( "IVYWIZ.SMK" ), getVideo( "IVYDES.SMK" ) }; @@ -266,22 +264,22 @@ fheroes2::GameMode Game::NewPriceOfLoyaltyCampaign() LocalEvent & le = LocalEvent::Get(); while ( le.HandleEvents( highlightCampaignId < videos.size() ? Game::isCustomDelayNeeded( customDelay ) : true ) ) { if ( le.MouseClickLeft( activeCampaignArea[0] ) ) { - campaignSaveData.setCampaignID( Campaign::PRICE_OF_LOYALTY_CAMPAIGN ); + campaignSaveData.setCurrentScenarioInfoId( { Campaign::PRICE_OF_LOYALTY_CAMPAIGN, 0 } ); gameChoice = fheroes2::GameMode::SELECT_CAMPAIGN_SCENARIO; break; } if ( le.MouseClickLeft( activeCampaignArea[1] ) ) { - campaignSaveData.setCampaignID( Campaign::VOYAGE_HOME_CAMPAIGN ); + campaignSaveData.setCurrentScenarioInfoId( { Campaign::VOYAGE_HOME_CAMPAIGN, 0 } ); gameChoice = fheroes2::GameMode::SELECT_CAMPAIGN_SCENARIO; break; } if ( le.MouseClickLeft( activeCampaignArea[2] ) ) { - campaignSaveData.setCampaignID( Campaign::WIZARDS_ISLE_CAMPAIGN ); + campaignSaveData.setCurrentScenarioInfoId( { Campaign::WIZARDS_ISLE_CAMPAIGN, 0 } ); gameChoice = fheroes2::GameMode::SELECT_CAMPAIGN_SCENARIO; break; } if ( le.MouseClickLeft( activeCampaignArea[3] ) ) { - campaignSaveData.setCampaignID( Campaign::DESCENDANTS_CAMPAIGN ); + campaignSaveData.setCurrentScenarioInfoId( { Campaign::DESCENDANTS_CAMPAIGN, 0 } ); gameChoice = fheroes2::GameMode::SELECT_CAMPAIGN_SCENARIO; break; } diff --git a/src/fheroes2/kingdom/week.cpp b/src/fheroes2/kingdom/week.cpp index a5f335a876c..27c508f34fb 100644 --- a/src/fheroes2/kingdom/week.cpp +++ b/src/fheroes2/kingdom/week.cpp @@ -295,7 +295,7 @@ Week Week::RandomWeek( const World & worldInstance, const bool isNewMonth, const StreamBase & operator>>( StreamBase & stream, Week & week ) { - static_assert( LAST_SUPPORTED_FORMAT_VERSION < FORMAT_VERSION_0912_RELEASE, "Remove this operator." ); + static_assert( LAST_SUPPORTED_FORMAT_VERSION < FORMAT_VERSION_PRE3_0912_RELEASE, "Remove this operator." ); int32_t weekType; int32_t monster; @@ -307,7 +307,7 @@ StreamBase & operator>>( StreamBase & stream, Week & week ) StreamBase & operator<<( StreamBase & stream, const Week & week ) { - static_assert( LAST_SUPPORTED_FORMAT_VERSION < FORMAT_VERSION_0912_RELEASE, "Remove this operator." ); + static_assert( LAST_SUPPORTED_FORMAT_VERSION < FORMAT_VERSION_PRE3_0912_RELEASE, "Remove this operator." ); return stream << static_cast( week.GetType() ) << static_cast( week.GetMonster() ); } diff --git a/src/fheroes2/system/save_format_version.h b/src/fheroes2/system/save_format_version.h index a764a9a3b0d..8c26a753f49 100644 --- a/src/fheroes2/system/save_format_version.h +++ b/src/fheroes2/system/save_format_version.h @@ -25,7 +25,8 @@ enum SaveFileFormat : uint16_t { // TODO: if you're adding a new version you must assign it to CURRENT_FORMAT_VERSION located at the bottom. - FORMAT_VERSION_0912_RELEASE = 9802, + FORMAT_VERSION_0912_RELEASE = 9803, + FORMAT_VERSION_PRE3_0912_RELEASE = 9802, FORMAT_VERSION_PRE2_0912_RELEASE = 9801, FORMAT_VERSION_PRE1_0912_RELEASE = 9800, FORMAT_VERSION_097_RELEASE = 9701, diff --git a/src/fheroes2/world/world.cpp b/src/fheroes2/world/world.cpp index 6d07b195219..aec8d94731b 100644 --- a/src/fheroes2/world/world.cpp +++ b/src/fheroes2/world/world.cpp @@ -1420,8 +1420,8 @@ StreamBase & operator>>( StreamBase & msg, World & w ) 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 >> w.month; - static_assert( LAST_SUPPORTED_FORMAT_VERSION < FORMAT_VERSION_0912_RELEASE, "Remove the check below." ); - if ( Game::GetLoadVersion() < FORMAT_VERSION_0912_RELEASE ) { + static_assert( LAST_SUPPORTED_FORMAT_VERSION < FORMAT_VERSION_PRE3_0912_RELEASE, "Remove the check below." ); + if ( Game::GetLoadVersion() < FORMAT_VERSION_PRE3_0912_RELEASE ) { Week dummyWeek; msg >> dummyWeek >> dummyWeek;