Bläddra i källkod

Merge pull request #2259 from IvanSavenko/campaign_refactoring

Fix accumulated issues with campaigns
Ivan Savenko 2 år sedan
förälder
incheckning
9cd246ab8b
100 ändrade filer med 3093 tillägg och 2675 borttagningar
  1. 1 1
      AI/Nullkiller/AIGateway.cpp
  2. 1 1
      AI/Nullkiller/AIUtility.cpp
  3. 0 1
      AI/Nullkiller/Analyzers/HeroManager.cpp
  4. 0 1
      AI/Nullkiller/Engine/PriorityEvaluator.cpp
  5. 1 1
      AI/Nullkiller/Goals/CompleteQuest.h
  6. 0 1
      AI/Nullkiller/Pathfinding/Actions/BattleAction.h
  7. 0 1
      AI/Nullkiller/Pathfinding/Actions/BuyArmyAction.h
  8. 1 1
      AI/Nullkiller/Pathfinding/Actions/QuestAction.h
  9. 1 0
      AI/VCAI/FuzzyHelper.cpp
  10. 1 0
      AI/VCAI/Goals/CompleteQuest.h
  11. 1 1
      AI/VCAI/VCAI.cpp
  12. 1 1
      CCallback.cpp
  13. 11 2
      client/CMT.cpp
  14. 0 2
      client/CMakeLists.txt
  15. 27 3
      client/CPlayerInterface.cpp
  16. 43 9
      client/CServerHandler.cpp
  17. 6 6
      client/CServerHandler.h
  18. 1 1
      client/Client.cpp
  19. 1 1
      client/Client.h
  20. 5 5
      client/ClientCommandManager.cpp
  21. 1 2
      client/NetPacksClient.cpp
  22. 14 2
      client/adventureMap/AdventureMapShortcuts.cpp
  23. 1 0
      client/battle/BattleInterface.cpp
  24. 1 1
      client/battle/BattleInterfaceClasses.cpp
  25. 0 1
      client/battle/BattleStacksController.cpp
  26. 1 0
      client/battle/BattleWindow.cpp
  27. 14 6
      client/eventsSDL/InputHandler.cpp
  28. 4 4
      client/eventsSDL/InputHandler.h
  29. 0 91
      client/eventsSDL/UserEventHandler.cpp
  30. 0 20
      client/eventsSDL/UserEventHandler.h
  31. 5 8
      client/gui/CGuiHandler.cpp
  32. 3 14
      client/gui/CGuiHandler.h
  33. 59 55
      client/lobby/CBonusSelection.cpp
  34. 7 5
      client/lobby/CBonusSelection.h
  35. 2 2
      client/lobby/CLobbyScreen.cpp
  36. 7 9
      client/lobby/SelectionTab.cpp
  37. 10 3
      client/mainmenu/CCampaignScreen.cpp
  38. 2 0
      client/mainmenu/CCampaignScreen.h
  39. 14 8
      client/mainmenu/CMainMenu.cpp
  40. 2 2
      client/mainmenu/CMainMenu.h
  41. 1 3
      client/mainmenu/CPrologEpilogVideo.cpp
  42. 4 3
      client/mainmenu/CPrologEpilogVideo.h
  43. 0 1
      client/render/Graphics.cpp
  44. 1 2
      client/widgets/CGarrisonInt.cpp
  45. 1 1
      client/widgets/MiscWidgets.cpp
  46. 3 3
      client/windows/CCastleInterface.cpp
  47. 1 1
      client/windows/CCreatureWindow.cpp
  48. 1 1
      client/windows/CQuestLog.cpp
  49. 0 1
      client/windows/CTradeWindow.cpp
  50. 3 1
      client/windows/GUIClasses.cpp
  51. 1 1
      client/windows/InfoWindows.cpp
  52. 38 9
      client/windows/settings/SettingsMainWindow.cpp
  53. 0 1
      client/windows/settings/SettingsMainWindow.h
  54. 19 5
      cmake_modules/VCMI_lib.cmake
  55. 0 1
      lib/CCreatureHandler.cpp
  56. 0 1
      lib/CCreatureSet.cpp
  57. 3 1
      lib/CGameInfoCallback.cpp
  58. 0 1
      lib/CGameInterface.h
  59. 0 215
      lib/CGameStateFwd.h
  60. 3 1
      lib/CPlayerState.cpp
  61. 1 0
      lib/CPlayerState.h
  62. 2 0
      lib/GameConstants.cpp
  63. 3 0
      lib/GameConstants.h
  64. 3 2
      lib/IGameCallback.cpp
  65. 8 7
      lib/NetPacks.h
  66. 3 20
      lib/NetPacksLib.cpp
  67. 3 3
      lib/NetPacksLobby.h
  68. 3 3
      lib/StartInfo.cpp
  69. 5 4
      lib/StartInfo.h
  70. 1 0
      lib/battle/CBattleInfoEssentials.cpp
  71. 1 1
      lib/battle/CPlayerBattleCallback.cpp
  72. 57 0
      lib/campaign/CampaignConstants.h
  73. 615 0
      lib/campaign/CampaignHandler.cpp
  74. 45 0
      lib/campaign/CampaignHandler.h
  75. 30 0
      lib/campaign/CampaignScenarioPrologEpilog.h
  76. 435 0
      lib/campaign/CampaignState.cpp
  77. 307 0
      lib/campaign/CampaignState.h
  78. 70 814
      lib/gameState/CGameState.cpp
  79. 18 95
      lib/gameState/CGameState.h
  80. 592 0
      lib/gameState/CGameStateCampaign.cpp
  81. 67 0
      lib/gameState/CGameStateCampaign.h
  82. 85 0
      lib/gameState/EVictoryLossCheckResult.h
  83. 183 0
      lib/gameState/InfoAboutArmy.cpp
  84. 99 0
      lib/gameState/InfoAboutArmy.h
  85. 55 0
      lib/gameState/QuestInfo.h
  86. 47 0
      lib/gameState/SThievesGuildInfo.h
  87. 1 1
      lib/mapObjects/CArmedInstance.cpp
  88. 1 1
      lib/mapObjects/CBank.cpp
  89. 1 1
      lib/mapObjects/CGDwelling.cpp
  90. 11 4
      lib/mapObjects/CGHeroInstance.cpp
  91. 8 4
      lib/mapObjects/CGHeroInstance.h
  92. 0 1
      lib/mapObjects/CGMarket.cpp
  93. 1 1
      lib/mapObjects/CGObjectInstance.cpp
  94. 1 1
      lib/mapObjects/CGTownBuilding.cpp
  95. 1 1
      lib/mapObjects/CGTownInstance.cpp
  96. 0 1
      lib/mapObjects/CQuest.cpp
  97. 1 1
      lib/mapObjects/CRewardableObject.cpp
  98. 1 1
      lib/mapObjects/MiscObjects.cpp
  99. 0 876
      lib/mapping/CCampaignHandler.cpp
  100. 0 307
      lib/mapping/CCampaignHandler.h

+ 1 - 1
AI/Nullkiller/AIGateway.cpp

@@ -15,7 +15,7 @@
 #include "../../lib/CConfigHandler.h"
 #include "../../lib/CHeroHandler.h"
 #include "../../lib/GameSettings.h"
-#include "../../lib/CGameState.h"
+#include "../../lib/gameState/CGameState.h"
 #include "../../lib/NetPacks.h"
 #include "../../lib/serializer/CTypeList.h"
 #include "../../lib/serializer/BinarySerializer.h"

+ 1 - 1
AI/Nullkiller/AIUtility.cpp

@@ -17,7 +17,7 @@
 #include "../../lib/CHeroHandler.h"
 #include "../../lib/mapObjects/MapObjects.h"
 #include "../../lib/mapping/CMapDefines.h"
-
+#include "../../lib/gameState/QuestInfo.h"
 #include "../../lib/GameSettings.h"
 
 #include <vcmi/CreatureService.h>

+ 0 - 1
AI/Nullkiller/Analyzers/HeroManager.cpp

@@ -13,7 +13,6 @@
 #include "../../../lib/mapObjects/MapObjects.h"
 #include "../../../lib/CHeroHandler.h"
 #include "../../../lib/GameSettings.h"
-#include "../../../lib/CGameState.h"
 
 namespace NKAI
 {

+ 0 - 1
AI/Nullkiller/Engine/PriorityEvaluator.cpp

@@ -16,7 +16,6 @@
 #include "../../../lib/mapObjectConstructors/CBankInstanceConstructor.h"
 #include "../../../lib/mapObjects/MapObjects.h"
 #include "../../../lib/CCreatureHandler.h"
-#include "../../../lib/CGameStateFwd.h"
 #include "../../../lib/VCMI_Lib.h"
 #include "../../../lib/StartInfo.h"
 #include "../../../CCallback.h"

+ 1 - 1
AI/Nullkiller/Goals/CompleteQuest.h

@@ -12,7 +12,7 @@
 #include "../AIUtility.h"
 #include "../../../CCallback.h"
 #include "../Goals/CGoal.h"
-#include "../../../lib/CGameState.h"
+#include "../../../lib/gameState/QuestInfo.h"
 
 namespace NKAI
 {

+ 0 - 1
AI/Nullkiller/Pathfinding/Actions/BattleAction.h

@@ -11,7 +11,6 @@
 #pragma once
 
 #include "SpecialAction.h"
-#include "../../../../lib/CGameState.h"
 
 namespace NKAI
 {

+ 0 - 1
AI/Nullkiller/Pathfinding/Actions/BuyArmyAction.h

@@ -11,7 +11,6 @@
 #pragma once
 
 #include "SpecialAction.h"
-#include "../../../../lib/CGameState.h"
 
 namespace NKAI
 {

+ 1 - 1
AI/Nullkiller/Pathfinding/Actions/QuestAction.h

@@ -11,7 +11,7 @@
 #pragma once
 
 #include "SpecialAction.h"
-#include "../../../../lib/CGameState.h"
+#include "../../../../lib/gameState/QuestInfo.h"
 
 namespace NKAI
 {

+ 1 - 0
AI/VCAI/FuzzyHelper.cpp

@@ -19,6 +19,7 @@
 #include "../../lib/mapObjects/CBank.h"
 #include "../../lib/mapObjects/CGCreature.h"
 #include "../../lib/mapObjects/CGDwelling.h"
+#include "../../lib/gameState/InfoAboutArmy.h"
 
 FuzzyHelper * fh;
 

+ 1 - 0
AI/VCAI/Goals/CompleteQuest.h

@@ -11,6 +11,7 @@
 
 #include "CGoal.h"
 #include "../../../lib/VCMI_Lib.h"
+#include "../../../lib/gameState/QuestInfo.h"
 
 namespace Goals
 {

+ 1 - 1
AI/VCAI/VCAI.cpp

@@ -20,7 +20,7 @@
 #include "../../lib/CConfigHandler.h"
 #include "../../lib/CHeroHandler.h"
 #include "../../lib/GameSettings.h"
-#include "../../lib/CGameState.h"
+#include "../../lib/gameState/CGameState.h"
 #include "../../lib/NetPacksBase.h"
 #include "../../lib/NetPacks.h"
 #include "../../lib/bonuses/CBonusSystemNode.h"

+ 1 - 1
CCallback.cpp

@@ -11,7 +11,7 @@
 #include "CCallback.h"
 
 #include "lib/CCreatureHandler.h"
-#include "lib/CGameState.h"
+#include "lib/gameState/CGameState.h"
 #include "client/CPlayerInterface.h"
 #include "client/Client.h"
 #include "lib/mapping/CMap.h"

+ 11 - 2
client/CMT.cpp

@@ -29,6 +29,7 @@
 #include "../lib/filesystem/Filesystem.h"
 #include "../lib/CGeneralTextHandler.h"
 #include "../lib/VCMIDirs.h"
+#include "../lib/VCMI_Lib.h"
 #include "../lib/CConfigHandler.h"
 
 #include "../lib/logging/CBasicLogConfigurator.h"
@@ -450,8 +451,16 @@ static void mainLoop()
 {
 	SettingsListener resChanged = settings.listen["video"]["resolution"];
 	SettingsListener fsChanged = settings.listen["video"]["fullscreen"];
-	resChanged([](const JsonNode &newState){  GH.pushUserEvent(EUserEvent::FULLSCREEN_TOGGLED); });
-	fsChanged([](const JsonNode &newState){  GH.pushUserEvent(EUserEvent::FULLSCREEN_TOGGLED); });
+
+	auto functor = [](const JsonNode &newState){
+		GH.dispatchMainThread([](){
+			boost::unique_lock<boost::recursive_mutex> lock(*CPlayerInterface::pim);
+			GH.onScreenResize();
+		});
+	};
+
+	resChanged(functor);
+	fsChanged(functor);
 
 	inGuiThread.reset(new bool(true));
 

+ 0 - 2
client/CMakeLists.txt

@@ -29,7 +29,6 @@ set(client_SRCS
 
 	eventsSDL/NotificationHandler.cpp
 	eventsSDL/InputHandler.cpp
-	eventsSDL/UserEventHandler.cpp
 	eventsSDL/InputSourceKeyboard.cpp
 	eventsSDL/InputSourceMouse.cpp
 	eventsSDL/InputSourceText.cpp
@@ -173,7 +172,6 @@ set(client_HEADERS
 
 	eventsSDL/NotificationHandler.h
 	eventsSDL/InputHandler.h
-	eventsSDL/UserEventHandler.h
 	eventsSDL/InputSourceKeyboard.h
 	eventsSDL/InputSourceMouse.h
 	eventsSDL/InputSourceText.h

+ 27 - 3
client/CPlayerInterface.cpp

@@ -23,6 +23,7 @@
 #include "../CCallback.h"
 #include "windows/CCastleInterface.h"
 #include "eventsSDL/InputHandler.h"
+#include "mainmenu/CMainMenu.h"
 #include "gui/CursorHandler.h"
 #include "windows/CKingdomInterface.h"
 #include "CGameInfo.h"
@@ -75,7 +76,7 @@
 #include "../lib/TerrainHandler.h"
 #include "CServerHandler.h"
 // FIXME: only needed for CGameState::mutex
-#include "../lib/CGameState.h"
+#include "../lib/gameState/CGameState.h"
 #include "eventsSDL/NotificationHandler.h"
 #include "adventureMap/CInGameConsole.h"
 
@@ -1744,7 +1745,16 @@ void CPlayerInterface::requestReturningToMainMenu(bool won)
 	if(won && cb->getStartInfo()->campState)
 		CSH->startCampaignScenario(cb->getStartInfo()->campState);
 	else
-		GH.pushUserEvent(EUserEvent::RETURN_TO_MAIN_MENU);
+	{
+		GH.dispatchMainThread(
+			[]()
+			{
+				CSH->endGameplay();
+				GH.defActionsDef = 63;
+				CMM->menu->switchToTab("main");
+			}
+		);
+	}
 }
 
 void CPlayerInterface::askToAssembleArtifact(const ArtifactLocation &al)
@@ -1840,7 +1850,21 @@ void CPlayerInterface::waitForAllDialogs(bool unlockPim)
 
 void CPlayerInterface::proposeLoadingGame()
 {
-	showYesNoDialog(CGI->generaltexth->allTexts[68], [](){ GH.pushUserEvent(EUserEvent::RETURN_TO_MENU_LOAD); }, nullptr);
+	showYesNoDialog(
+		CGI->generaltexth->allTexts[68],
+		[]()
+		{
+			GH.dispatchMainThread(
+				[]()
+				{
+					CSH->endGameplay();
+					GH.defActionsDef = 63;
+					CMM->menu->switchToTab("load");
+				}
+			);
+		},
+		nullptr
+	);
 }
 
 bool CPlayerInterface::capturedAllEvents()

+ 43 - 9
client/CServerHandler.cpp

@@ -21,6 +21,7 @@
 #include "windows/InfoWindows.h"
 
 #include "mainmenu/CMainMenu.h"
+#include "mainmenu/CPrologEpilogVideo.h"
 
 #ifdef VCMI_ANDROID
 #include "../lib/CAndroidVMHelper.h"
@@ -41,7 +42,7 @@
 #include "../lib/NetPackVisitor.h"
 #include "../lib/StartInfo.h"
 #include "../lib/VCMIDirs.h"
-#include "../lib/mapping/CCampaignHandler.h"
+#include "../lib/campaign/CampaignState.h"
 #include "../lib/mapping/CMapInfo.h"
 #include "../lib/mapObjects/MiscObjects.h"
 #include "../lib/rmg/CMapGenOptions.h"
@@ -321,6 +322,7 @@ void CServerHandler::applyPacksOnLobbyScreen()
 	boost::unique_lock<boost::recursive_mutex> lock(*mx);
 	while(!packsForLobbyScreen.empty())
 	{
+		boost::unique_lock<boost::recursive_mutex> guiLock(*CPlayerInterface::pim);
 		CPackForLobby * pack = packsForLobbyScreen.front();
 		packsForLobbyScreen.pop_front();
 		CBaseForLobbyApply * apply = applier->getApplier(typeList.getTypeID(pack)); //find the applier
@@ -442,7 +444,7 @@ void CServerHandler::sendClientDisconnecting()
 	sendLobbyPack(lcd);
 }
 
-void CServerHandler::setCampaignState(std::shared_ptr<CCampaignState> newCampaign)
+void CServerHandler::setCampaignState(std::shared_ptr<CampaignState> newCampaign)
 {
 	state = EClientState::LOBBY_CAMPAIGN;
 	LobbySetCampaign lsc;
@@ -450,7 +452,7 @@ void CServerHandler::setCampaignState(std::shared_ptr<CCampaignState> newCampaig
 	sendLobbyPack(lsc);
 }
 
-void CServerHandler::setCampaignMap(int mapId) const
+void CServerHandler::setCampaignMap(CampaignScenarioID mapId) const
 {
 	if(state == EClientState::GAMEPLAY) // FIXME: UI shouldn't sent commands in first place
 		return;
@@ -660,12 +662,38 @@ void CServerHandler::endGameplay(bool closeConnection, bool restart)
 	saveSession->Bool() = false;
 }
 
-void CServerHandler::startCampaignScenario(std::shared_ptr<CCampaignState> cs)
+void CServerHandler::startCampaignScenario(std::shared_ptr<CampaignState> cs)
 {
-	if(cs)
-		GH.pushUserEvent(EUserEvent::CAMPAIGN_START_SCENARIO, CMemorySerializer::deepCopy(*cs.get()).release());
-	else
-		GH.pushUserEvent(EUserEvent::CAMPAIGN_START_SCENARIO, CMemorySerializer::deepCopy(*si->campState.get()).release());
+	std::shared_ptr<CampaignState> ourCampaign = cs;
+
+	if (!cs)
+		ourCampaign = si->campState;
+
+	GH.dispatchMainThread([ourCampaign]()
+	{
+		CSH->campaignServerRestartLock.set(true);
+		CSH->endGameplay();
+
+		auto & epilogue = ourCampaign->scenario(*ourCampaign->lastScenario()).epilog;
+		auto finisher = [=]()
+		{
+			if(!ourCampaign->isCampaignFinished())
+			{
+				GH.windows().pushWindow(CMM);
+				GH.windows().pushWindow(CMM->menu);
+				CMM->openCampaignLobby(ourCampaign);
+			}
+		};
+		if(epilogue.hasPrologEpilog)
+		{
+			GH.windows().createAndPushWindow<CPrologEpilogVideo>(epilogue, finisher);
+		}
+		else
+		{
+			CSH->campaignServerRestartLock.waitUntil(false);
+			finisher();
+		}
+	});
 }
 
 void CServerHandler::showServerError(std::string txt)
@@ -842,7 +870,13 @@ void CServerHandler::threadHandleConnection()
 			if(client)
 			{
 				state = EClientState::DISCONNECTING;
-				GH.pushUserEvent(EUserEvent::RETURN_TO_MAIN_MENU);
+
+				GH.dispatchMainThread([]()
+				{
+					CSH->endGameplay();
+					GH.defActionsDef = 63;
+					CMM->menu->switchToTab("main");
+				});
 			}
 			else
 			{

+ 6 - 6
client/CServerHandler.h

@@ -57,8 +57,8 @@ public:
 
 	virtual void sendClientConnecting() const = 0;
 	virtual void sendClientDisconnecting() = 0;
-	virtual void setCampaignState(std::shared_ptr<CCampaignState> newCampaign) = 0;
-	virtual void setCampaignMap(int mapId) const = 0;
+	virtual void setCampaignState(std::shared_ptr<CampaignState> newCampaign) = 0;
+	virtual void setCampaignMap(CampaignScenarioID mapId) const = 0;
 	virtual void setCampaignBonus(int bonusId) const = 0;
 	virtual void setMapInfo(std::shared_ptr<CMapInfo> to, std::shared_ptr<CMapGenOptions> mapGenOpts = {}) const = 0;
 	virtual void setPlayer(PlayerColor color) const = 0;
@@ -92,7 +92,7 @@ public:
 	// FIXME: Bunch of crutches to glue it all together
 
 	// For starting non-custom campaign and continue to next mission
-	std::shared_ptr<CCampaignState> campaignStateToSend;
+	std::shared_ptr<CampaignState> campaignStateToSend;
 
 	ui8 screenType; // To create lobby UI only after server is setup
 	ui8 loadMode; // For saves filtering in SelectionTab
@@ -135,8 +135,8 @@ public:
 	// Lobby server API for UI
 	void sendClientConnecting() const override;
 	void sendClientDisconnecting() override;
-	void setCampaignState(std::shared_ptr<CCampaignState> newCampaign) override;
-	void setCampaignMap(int mapId) const override;
+	void setCampaignState(std::shared_ptr<CampaignState> newCampaign) override;
+	void setCampaignMap(CampaignScenarioID mapId) const override;
 	void setCampaignBonus(int bonusId) const override;
 	void setMapInfo(std::shared_ptr<CMapInfo> to, std::shared_ptr<CMapGenOptions> mapGenOpts = {}) const override;
 	void setPlayer(PlayerColor color) const override;
@@ -150,7 +150,7 @@ public:
 
 	void startGameplay(VCMI_LIB_WRAP_NAMESPACE(CGameState) * gameState = nullptr);
 	void endGameplay(bool closeConnection = true, bool restart = false);
-	void startCampaignScenario(std::shared_ptr<CCampaignState> cs = {});
+	void startCampaignScenario(std::shared_ptr<CampaignState> cs = {});
 	void showServerError(std::string txt);
 
 	// TODO: LobbyState must be updated within game so we should always know how many player interfaces our client handle

+ 1 - 1
client/Client.cpp

@@ -23,7 +23,7 @@
 
 #include "../CCallback.h"
 #include "../lib/CConfigHandler.h"
-#include "../lib/CGameState.h"
+#include "../lib/gameState/CGameState.h"
 #include "../lib/CThreadHelper.h"
 #include "../lib/VCMIDirs.h"
 #include "../lib/battle/BattleInfo.h"

+ 1 - 1
client/Client.h

@@ -23,7 +23,7 @@ VCMI_LIB_NAMESPACE_BEGIN
 
 struct CPack;
 struct CPackForServer;
-class CCampaignState;
+class CampaignState;
 class IGameEventsReceiver;
 class IBattleEventsReceiver;
 class CBattleGameInterface;

+ 5 - 5
client/ClientCommandManager.cpp

@@ -20,12 +20,12 @@
 #include "../lib/NetPacks.h"
 #include "ClientNetPackVisitors.h"
 #include "../lib/CConfigHandler.h"
-#include "../lib/CGameState.h"
+#include "../lib/gameState/CGameState.h"
 #include "../lib/CPlayerState.h"
 #include "../lib/StringConstants.h"
+#include "../lib/campaign/CampaignHandler.h"
 #include "../lib/mapping/CMapService.h"
 #include "../lib/mapping/CMap.h"
-#include "../lib/mapping/CCampaignHandler.h"
 #include "windows/CCastleInterface.h"
 #include "render/CAnimation.h"
 #include "../CCallback.h"
@@ -209,9 +209,9 @@ void ClientCommandManager::handleConvertTextCommand()
 	logGlobal->info("Loading campaigns for export");
 	for (auto const & campaignName : campaignList)
 	{
-		CCampaignState state(CCampaignHandler::getCampaign(campaignName.getName()));
-		for (auto const & part : state.camp->mapPieces)
-			delete state.getMap(part.first);
+		auto state = CampaignHandler::getCampaign(campaignName.getName());
+		for (auto const & part : state->allScenarios())
+			state->getMap(part);
 	}
 
 	VLC->generaltexth->dumpAllTexts();

+ 1 - 2
client/NetPacksClient.cpp

@@ -37,8 +37,7 @@
 #include "../lib/StartInfo.h"
 #include "../lib/CConfigHandler.h"
 #include "../lib/mapObjects/CGMarket.h"
-#include "../lib/mapping/CCampaignHandler.h"
-#include "../lib/CGameState.h"
+#include "../lib/gameState/CGameState.h"
 #include "../lib/CStack.h"
 #include "../lib/battle/BattleInfo.h"
 #include "../lib/GameConstants.h"

+ 14 - 2
client/adventureMap/AdventureMapShortcuts.cpp

@@ -13,6 +13,7 @@
 
 #include "../CGameInfo.h"
 #include "../CPlayerInterface.h"
+#include "../CServerHandler.h"
 #include "../PlayerLocalState.h"
 #include "../gui/CGuiHandler.h"
 #include "../gui/Shortcut.h"
@@ -293,8 +294,19 @@ void AdventureMapShortcuts::viewPuzzleMap()
 
 void AdventureMapShortcuts::restartGame()
 {
-	LOCPLINT->showYesNoDialog(CGI->generaltexth->translate("vcmi.adventureMap.confirmRestartGame"),
-		[](){ GH.pushUserEvent(EUserEvent::RESTART_GAME); }, nullptr);
+	LOCPLINT->showYesNoDialog(
+		CGI->generaltexth->translate("vcmi.adventureMap.confirmRestartGame"),
+		[]()
+		{
+			GH.dispatchMainThread(
+				[]()
+				{
+					CSH->sendRestartGame();
+				}
+			);
+		},
+		nullptr
+	);
 }
 
 void AdventureMapShortcuts::visitObject()

+ 1 - 0
client/battle/BattleInterface.cpp

@@ -38,6 +38,7 @@
 #include "../../lib/CGeneralTextHandler.h"
 #include "../../lib/CHeroHandler.h"
 #include "../../lib/CondSh.h"
+#include "../../lib/gameState/InfoAboutArmy.h"
 #include "../../lib/mapObjects/CGTownInstance.h"
 #include "../../lib/NetPacks.h"
 #include "../../lib/UnlockGuard.h"

+ 1 - 1
client/battle/BattleInterfaceClasses.cpp

@@ -41,7 +41,7 @@
 #include "../../lib/CStack.h"
 #include "../../lib/CConfigHandler.h"
 #include "../../lib/CCreatureHandler.h"
-#include "../../lib/CGameState.h"
+#include "../../lib/gameState/InfoAboutArmy.h"
 #include "../../lib/CGeneralTextHandler.h"
 #include "../../lib/CTownHandler.h"
 #include "../../lib/CHeroHandler.h"

+ 0 - 1
client/battle/BattleStacksController.cpp

@@ -32,7 +32,6 @@
 #include "../../CCallback.h"
 #include "../../lib/spells/ISpellMechanics.h"
 #include "../../lib/battle/BattleHex.h"
-#include "../../lib/CGameState.h"
 #include "../../lib/CStack.h"
 #include "../../lib/CondSh.h"
 #include "../../lib/TextOperations.h"

+ 1 - 0
client/battle/BattleWindow.cpp

@@ -33,6 +33,7 @@
 
 #include "../../CCallback.h"
 #include "../../lib/CGeneralTextHandler.h"
+#include "../../lib/gameState/InfoAboutArmy.h"
 #include "../../lib/mapObjects/CGHeroInstance.h"
 #include "../../lib/CStack.h"
 #include "../../lib/CConfigHandler.h"

+ 14 - 6
client/eventsSDL/InputHandler.cpp

@@ -16,7 +16,6 @@
 #include "InputSourceKeyboard.h"
 #include "InputSourceTouch.h"
 #include "InputSourceText.h"
-#include "UserEventHandler.h"
 
 #include "../gui/CGuiHandler.h"
 #include "../gui/CursorHandler.h"
@@ -35,7 +34,6 @@ InputHandler::InputHandler()
 	, keyboardHandler(std::make_unique<InputSourceKeyboard>())
 	, fingerHandler(std::make_unique<InputSourceTouch>())
 	, textHandler(std::make_unique<InputSourceText>())
-	, userHandler(std::make_unique<UserEventHandler>())
 {
 }
 
@@ -127,7 +125,7 @@ void InputHandler::preprocessEvent(const SDL_Event & ev)
 	}
 	else if(ev.type == SDL_USEREVENT)
 	{
-		userHandler->handleUserEvent(ev.user);
+		handleUserEvent(ev.user);
 
 		return;
 	}
@@ -244,15 +242,25 @@ bool InputHandler::hasTouchInputDevice() const
 	return fingerHandler->hasTouchInputDevice();
 }
 
-void InputHandler::pushUserEvent(EUserEvent usercode, void * userdata)
+void InputHandler::dispatchMainThread(const std::function<void()> & functor)
 {
+	auto heapFunctor = new std::function<void()>(functor);
+
 	SDL_Event event;
 	event.type = SDL_USEREVENT;
-	event.user.code = static_cast<int32_t>(usercode);
-	event.user.data1 = userdata;
+	event.user.code = 0;
+	event.user.data1 = static_cast <void*>(heapFunctor);
+	event.user.data2 = nullptr;
 	SDL_PushEvent(&event);
 }
 
+void InputHandler::handleUserEvent(const SDL_UserEvent & current)
+{
+	auto heapFunctor = static_cast<std::function<void()>*>(current.data1);
+
+	(*heapFunctor)();
+}
+
 const Point & InputHandler::getCursorPosition() const
 {
 	return cursorPosition;

+ 4 - 4
client/eventsSDL/InputHandler.h

@@ -15,12 +15,12 @@
 enum class EUserEvent;
 enum class MouseButton;
 union SDL_Event;
+struct SDL_UserEvent;
 
 class InputSourceMouse;
 class InputSourceKeyboard;
 class InputSourceTouch;
 class InputSourceText;
-class UserEventHandler;
 
 class InputHandler
 {
@@ -31,12 +31,12 @@ class InputHandler
 
 	void preprocessEvent(const SDL_Event & event);
 	void handleCurrentEvent(const SDL_Event & current);
+	void handleUserEvent(const SDL_UserEvent & current);
 
 	std::unique_ptr<InputSourceMouse> mouseHandler;
 	std::unique_ptr<InputSourceKeyboard> keyboardHandler;
 	std::unique_ptr<InputSourceTouch> fingerHandler;
 	std::unique_ptr<InputSourceText> textHandler;
-	std::unique_ptr<UserEventHandler> userHandler;
 
 public:
 	InputHandler();
@@ -66,8 +66,8 @@ public:
 	/// returns true if system has active touchscreen
 	bool hasTouchInputDevice() const;
 
-	/// Generates new user event that will be processed on next frame
-	void pushUserEvent(EUserEvent usercode, void * userdata);
+	/// Calls provided functor in main thread on next execution frame
+	void dispatchMainThread(const std::function<void()> & functor);
 
 	/// Returns current position of cursor, in VCMI logical screen coordinates
 	const Point & getCursorPosition() const;

+ 0 - 91
client/eventsSDL/UserEventHandler.cpp

@@ -1,91 +0,0 @@
-/*
-* EventHandlerSDLUser.cpp, part of VCMI engine
-*
-* Authors: listed in file AUTHORS in main folder
-*
-* License: GNU General Public License v2.0 or later
-* Full text of license available in license.txt file, in main folder
-*
-*/
-
-#include "StdInc.h"
-#include "UserEventHandler.h"
-
-#include "../CMT.h"
-#include "../CPlayerInterface.h"
-#include "../CServerHandler.h"
-#include "../gui/CGuiHandler.h"
-#include "../gui/WindowHandler.h"
-#include "../mainmenu/CMainMenu.h"
-#include "../mainmenu/CPrologEpilogVideo.h"
-#include "../gui/EventDispatcher.h"
-
-#include <SDL_events.h>
-
-void UserEventHandler::handleUserEvent(const SDL_UserEvent & user)
-{
-	switch(static_cast<EUserEvent>(user.code))
-	{
-		case EUserEvent::FORCE_QUIT:
-		{
-			handleQuit(false);
-			return;
-		}
-		break;
-		case EUserEvent::RETURN_TO_MAIN_MENU:
-		{
-			CSH->endGameplay();
-			GH.defActionsDef = 63;
-			CMM->menu->switchToTab("main");
-		}
-		break;
-		case EUserEvent::RESTART_GAME:
-		{
-			CSH->sendRestartGame();
-		}
-		break;
-		case EUserEvent::CAMPAIGN_START_SCENARIO:
-		{
-			CSH->campaignServerRestartLock.set(true);
-			CSH->endGameplay();
-			auto ourCampaign = std::shared_ptr<CCampaignState>(reinterpret_cast<CCampaignState *>(user.data1));
-			auto & epilogue = ourCampaign->camp->scenarios[ourCampaign->mapsConquered.back()].epilog;
-			auto finisher = [=]()
-			{
-				if(!ourCampaign->mapsRemaining.empty())
-				{
-					GH.windows().pushWindow(CMM);
-					GH.windows().pushWindow(CMM->menu);
-					CMM->openCampaignLobby(ourCampaign);
-				}
-			};
-			if(epilogue.hasPrologEpilog)
-			{
-				GH.windows().createAndPushWindow<CPrologEpilogVideo>(epilogue, finisher);
-			}
-			else
-			{
-				CSH->campaignServerRestartLock.waitUntil(false);
-				finisher();
-			}
-		}
-		break;
-		case EUserEvent::RETURN_TO_MENU_LOAD:
-			CSH->endGameplay();
-			GH.defActionsDef = 63;
-			CMM->menu->switchToTab("load");
-			break;
-		case EUserEvent::FULLSCREEN_TOGGLED:
-		{
-			boost::unique_lock<boost::recursive_mutex> lock(*CPlayerInterface::pim);
-			GH.onScreenResize();
-			break;
-		}
-		case EUserEvent::FAKE_MOUSE_MOVE:
-			GH.events().dispatchMouseMoved(Point(0, 0), GH.getCursorPosition());
-			break;
-		default:
-			logGlobal->error("Unknown user event. Code %d", user.code);
-			break;
-	}
-}

+ 0 - 20
client/eventsSDL/UserEventHandler.h

@@ -1,20 +0,0 @@
-/*
-* EventHandlerSDLUser.h, part of VCMI engine
-*
-* Authors: listed in file AUTHORS in main folder
-*
-* License: GNU General Public License v2.0 or later
-* Full text of license available in license.txt file, in main folder
-*
-*/
-
-#pragma once
-
-struct SDL_UserEvent;
-
-/// Class for handling events of type SDL_UserEvent
-class UserEventHandler
-{
-public:
-	void handleUserEvent(const SDL_UserEvent & current);
-};

+ 5 - 8
client/gui/CGuiHandler.cpp

@@ -87,7 +87,9 @@ void CGuiHandler::handleEvents()
 
 void CGuiHandler::fakeMouseMove()
 {
-	pushUserEvent(EUserEvent::FAKE_MOUSE_MOVE);
+	dispatchMainThread([](){
+		GH.events().dispatchMouseMoved(Point(0, 0), GH.getCursorPosition());
+	});
 }
 
 void CGuiHandler::startTextInput(const Rect & whereInput)
@@ -203,14 +205,9 @@ bool CGuiHandler::amIGuiThread()
 	return inGuiThread.get() && *inGuiThread;
 }
 
-void CGuiHandler::pushUserEvent(EUserEvent usercode)
+void CGuiHandler::dispatchMainThread(const std::function<void()> & functor)
 {
-	inputHandlerInstance->pushUserEvent(usercode, nullptr);
-}
-
-void CGuiHandler::pushUserEvent(EUserEvent usercode, void * userdata)
-{
-	inputHandlerInstance->pushUserEvent(usercode, userdata);
+	inputHandlerInstance->dispatchMainThread(functor);
 }
 
 IScreenHandler & CGuiHandler::screenHandler()

+ 3 - 14
client/gui/CGuiHandler.h

@@ -27,18 +27,6 @@ class WindowHandler;
 class EventDispatcher;
 class InputHandler;
 
-// TODO: event handling need refactoring. Perhaps convert into delayed function call?
-enum class EUserEvent
-{
-	RETURN_TO_MAIN_MENU,
-	RESTART_GAME,
-	RETURN_TO_MENU_LOAD,
-	FULLSCREEN_TOGGLED,
-	CAMPAIGN_START_SCENARIO,
-	FORCE_QUIT,
-	FAKE_MOUSE_MOVE,
-};
-
 // Handles GUI logic and drawing
 class CGuiHandler
 {
@@ -108,8 +96,9 @@ public:
 	void drawFPSCounter(); // draws the FPS to the upper left corner of the screen
 
 	bool amIGuiThread();
-	void pushUserEvent(EUserEvent usercode);
-	void pushUserEvent(EUserEvent usercode, void * userdata);
+
+	/// Calls provided functor in main thread on next execution frame
+	void dispatchMainThread(const std::function<void()> & functor);
 
 	CondSh<bool> * terminate_cond; // confirm termination
 };

+ 59 - 55
client/lobby/CBonusSelection.cpp

@@ -47,14 +47,14 @@
 #include "../../lib/CCreatureHandler.h"
 #include "../../lib/StartInfo.h"
 
-#include "../../lib/mapping/CCampaignHandler.h"
+#include "../../lib/campaign/CampaignState.h"
 #include "../../lib/mapping/CMapService.h"
 #include "../../lib/mapping/CMapInfo.h"
 
 #include "../../lib/mapObjects/CGHeroInstance.h"
 
 
-std::shared_ptr<CCampaignState> CBonusSelection::getCampaign()
+std::shared_ptr<CampaignState> CBonusSelection::getCampaign()
 {
 	return CSH->si->campState;
 }
@@ -64,7 +64,7 @@ CBonusSelection::CBonusSelection()
 {
 	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
 
-	std::string bgName = getCampaign()->camp->header.campaignRegions.campPrefix + "_BG.BMP";
+	std::string bgName = getCampaign()->getRegions().getBackgroundName();
 	setBackground(bgName);
 
 	panelBackground = std::make_shared<CPicture>("CAMPBRF.BMP", 456, 6);
@@ -78,11 +78,11 @@ CBonusSelection::CBonusSelection()
 	iconsMapSizes = std::make_shared<CAnimImage>("SCNRMPSZ", 4, 0, 735, 26);
 
 	labelCampaignDescription = std::make_shared<CLabel>(481, 63, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::YELLOW, CGI->generaltexth->allTexts[38]);
-	campaignDescription = std::make_shared<CTextBox>(getCampaign()->camp->header.description, Rect(480, 86, 286, 117), 1);
+	campaignDescription = std::make_shared<CTextBox>(getCampaign()->getDescription(), Rect(480, 86, 286, 117), 1);
 
 	mapName = std::make_shared<CLabel>(481, 219, FONT_BIG, ETextAlignment::TOPLEFT, Colors::YELLOW, CSH->mi->getName());
 	labelMapDescription = std::make_shared<CLabel>(481, 253, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::YELLOW, CGI->generaltexth->allTexts[496]);
-	mapDescription = std::make_shared<CTextBox>("", Rect(480, 280, 286, 117), 1);
+	mapDescription = std::make_shared<CTextBox>("", Rect(480, 278, 292, 108), 1);
 
 	labelChooseBonus = std::make_shared<CLabel>(511, 432, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[71]);
 	groupBonuses = std::make_shared<CToggleGroup>(std::bind(&IServerAPI::setCampaignBonus, CSH, _1));
@@ -99,26 +99,29 @@ CBonusSelection::CBonusSelection()
 		difficultyIcons[b] = std::make_shared<CAnimImage>("GSPBUT" + std::to_string(b + 3) + ".DEF", 0, 0, 709, 455);
 	}
 
-	if(getCampaign()->camp->header.difficultyChoosenByPlayer)
+	if(getCampaign()->playerSelectedDifficulty())
 	{
 		buttonDifficultyLeft = std::make_shared<CButton>(Point(694, 508), "SCNRBLF.DEF", CButton::tooltip(), std::bind(&CBonusSelection::decreaseDifficulty, this));
 		buttonDifficultyRight = std::make_shared<CButton>(Point(738, 508), "SCNRBRT.DEF", CButton::tooltip(), std::bind(&CBonusSelection::increaseDifficulty, this));
 	}
 
-	for(int g = 0; g < getCampaign()->camp->scenarios.size(); ++g)
+	for(auto scenarioID : getCampaign()->allScenarios())
 	{
-		if(getCampaign()->camp->conquerable(g))
-			regions.push_back(std::make_shared<CRegion>(g, true, true, getCampaign()->camp->header.campaignRegions));
-		else if(getCampaign()->camp->scenarios[g].conquered) //display as striped
-			regions.push_back(std::make_shared<CRegion>(g, false, false, getCampaign()->camp->header.campaignRegions));
+		if(getCampaign()->isAvailable(scenarioID))
+			regions.push_back(std::make_shared<CRegion>(scenarioID, true, true, getCampaign()->getRegions()));
+		else if(getCampaign()->isConquered(scenarioID)) //display as striped
+			regions.push_back(std::make_shared<CRegion>(scenarioID, false, false, getCampaign()->getRegions()));
 	}
+
+	if (!getCampaign()->getMusic().empty())
+		CCS->musich->playMusic( "Music/" + getCampaign()->getMusic(), true, false);
 }
 
 void CBonusSelection::createBonusesIcons()
 {
 	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
-	const CCampaignScenario & scenario = getCampaign()->camp->scenarios[CSH->campaignMap];
-	const std::vector<CScenarioTravel::STravelBonus> & bonDescs = scenario.travelOptions.bonusesToChoose;
+	const CampaignScenario & scenario = getCampaign()->scenario(CSH->campaignMap);
+	const std::vector<CampaignBonus> & bonDescs = scenario.travelOptions.bonusesToChoose;
 	groupBonuses = std::make_shared<CToggleGroup>(std::bind(&IServerAPI::setCampaignBonus, CSH, _1));
 
 	static const char * bonusPics[] =
@@ -138,23 +141,24 @@ void CBonusSelection::createBonusesIcons()
 
 	for(int i = 0; i < bonDescs.size(); i++)
 	{
-		std::string picName = bonusPics[bonDescs[i].type];
+		int bonusType = static_cast<size_t>(bonDescs[i].type);
+		std::string picName = bonusPics[bonusType];
 		size_t picNumber = bonDescs[i].info2;
 
 		std::string desc;
 		switch(bonDescs[i].type)
 		{
-		case CScenarioTravel::STravelBonus::SPELL:
+		case CampaignBonusType::SPELL:
 			desc = CGI->generaltexth->allTexts[715];
 			boost::algorithm::replace_first(desc, "%s", CGI->spells()->getByIndex(bonDescs[i].info2)->getNameTranslated());
 			break;
-		case CScenarioTravel::STravelBonus::MONSTER:
+		case CampaignBonusType::MONSTER:
 			picNumber = bonDescs[i].info2 + 2;
 			desc = CGI->generaltexth->allTexts[717];
 			boost::algorithm::replace_first(desc, "%d", std::to_string(bonDescs[i].info3));
 			boost::algorithm::replace_first(desc, "%s", CGI->creatures()->getByIndex(bonDescs[i].info2)->getNamePluralTranslated());
 			break;
-		case CScenarioTravel::STravelBonus::BUILDING:
+		case CampaignBonusType::BUILDING:
 		{
 			int faction = -1;
 			for(auto & elem : CSH->si->playerInfos)
@@ -169,7 +173,7 @@ void CBonusSelection::createBonusesIcons()
 			assert(faction != -1);
 
 			BuildingID buildID;
-			if(getCampaign()->camp->header.version == CampaignVersion::VCMI)
+			if(getCampaign()->formatVCMI())
 				buildID = BuildingID(bonDescs[i].info1);
 			else
 				buildID = CBuildingHandler::campToERMU(bonDescs[i].info1, faction, std::set<BuildingID>());
@@ -181,15 +185,15 @@ void CBonusSelection::createBonusesIcons()
 
 			break;
 		}
-		case CScenarioTravel::STravelBonus::ARTIFACT:
+		case CampaignBonusType::ARTIFACT:
 			desc = CGI->generaltexth->allTexts[715];
 			boost::algorithm::replace_first(desc, "%s", CGI->artifacts()->getByIndex(bonDescs[i].info2)->getNameTranslated());
 			break;
-		case CScenarioTravel::STravelBonus::SPELL_SCROLL:
+		case CampaignBonusType::SPELL_SCROLL:
 			desc = CGI->generaltexth->allTexts[716];
 			boost::algorithm::replace_first(desc, "%s", CGI->spells()->getByIndex(bonDescs[i].info2)->getNameTranslated());
 			break;
-		case CScenarioTravel::STravelBonus::PRIMARY_SKILL:
+		case CampaignBonusType::PRIMARY_SKILL:
 		{
 			int leadingSkill = -1;
 			std::vector<std::pair<int, int>> toPrint; //primary skills to be listed <num, val>
@@ -222,7 +226,7 @@ void CBonusSelection::createBonusesIcons()
 			boost::algorithm::replace_first(desc, "%s", substitute);
 			break;
 		}
-		case CScenarioTravel::STravelBonus::SECONDARY_SKILL:
+		case CampaignBonusType::SECONDARY_SKILL:
 			desc = CGI->generaltexth->allTexts[718];
 
 			boost::algorithm::replace_first(desc, "%s", CGI->generaltexth->levels[bonDescs[i].info3 - 1]); //skill level
@@ -230,7 +234,7 @@ void CBonusSelection::createBonusesIcons()
 			picNumber = bonDescs[i].info2 * 3 + bonDescs[i].info3 - 1;
 
 			break;
-		case CScenarioTravel::STravelBonus::RESOURCE:
+		case CampaignBonusType::RESOURCE:
 		{
 			int serialResID = 0;
 			switch(bonDescs[i].info1)
@@ -267,19 +271,19 @@ void CBonusSelection::createBonusesIcons()
 			boost::algorithm::replace_first(desc, "%s", replacement);
 			break;
 		}
-		case CScenarioTravel::STravelBonus::HEROES_FROM_PREVIOUS_SCENARIO:
+		case CampaignBonusType::HEROES_FROM_PREVIOUS_SCENARIO:
 		{
-			auto superhero = getCampaign()->camp->scenarios[bonDescs[i].info2].strongestHero(PlayerColor(bonDescs[i].info1));
+			auto superhero = getCampaign()->strongestHero(static_cast<CampaignScenarioID>(bonDescs[i].info2), PlayerColor(bonDescs[i].info1));
 			if(!superhero)
 				logGlobal->warn("No superhero! How could it be transferred?");
 			picNumber = superhero ? superhero->portrait : 0;
 			desc = CGI->generaltexth->allTexts[719];
 
-			boost::algorithm::replace_first(desc, "%s", getCampaign()->camp->scenarios[bonDescs[i].info2].scenarioName);
+			boost::algorithm::replace_first(desc, "%s", getCampaign()->scenario(static_cast<CampaignScenarioID>(bonDescs[i].info2)).scenarioName);
 			break;
 		}
 
-		case CScenarioTravel::STravelBonus::HERO:
+		case CampaignBonusType::HERO:
 
 			desc = CGI->generaltexth->allTexts[718];
 			boost::algorithm::replace_first(desc, "%s", CGI->generaltexth->capColors[bonDescs[i].info1]); //hero's color
@@ -310,10 +314,8 @@ void CBonusSelection::createBonusesIcons()
 		groupBonuses->addToggle(i, bonusButton);
 	}
 
-	if(vstd::contains(getCampaign()->chosenCampaignBonuses, CSH->campaignMap))
-	{
-		groupBonuses->setSelected(getCampaign()->chosenCampaignBonuses[CSH->campaignMap]);
-	}
+	if(getCampaign()->getBonusID(CSH->campaignMap))
+		groupBonuses->setSelected(*getCampaign()->getBonusID(CSH->campaignMap));
 }
 
 void CBonusSelection::updateAfterStateChange()
@@ -322,7 +324,7 @@ void CBonusSelection::updateAfterStateChange()
 	{
 		buttonRestart->disable();
 		buttonStart->enable();
-		if(!getCampaign()->mapsConquered.empty())
+		if(!getCampaign()->conqueredScenarios().empty())
 			buttonBack->block(true);
 		else
 			buttonBack->block(false);
@@ -339,7 +341,7 @@ void CBonusSelection::updateAfterStateChange()
 	}
 	if(CSH->campaignBonus == -1)
 	{
-		buttonStart->block(getCampaign()->camp->scenarios[CSH->campaignMap].travelOptions.bonusesToChoose.size());
+		buttonStart->block(getCampaign()->scenario(CSH->campaignMap).travelOptions.bonusesToChoose.size());
 	}
 	else if(buttonStart->isBlocked())
 	{
@@ -352,6 +354,7 @@ void CBonusSelection::updateAfterStateChange()
 	if(!CSH->mi)
 		return;
 	iconsMapSizes->setFrame(CSH->mi->getMapSizeIconId());
+	mapName->setText(CSH->mi->getName());
 	mapDescription->setText(CSH->mi->getDescription());
 	for(size_t i = 0; i < difficultyIcons.size(); i++)
 	{
@@ -391,11 +394,11 @@ void CBonusSelection::startMap()
 	{
 		auto exitCb = [=]()
 		{
-			logGlobal->info("Starting scenario %d", CSH->campaignMap);
+			logGlobal->info("Starting scenario %d", static_cast<int>(CSH->campaignMap));
 			CSH->sendStartGame();
 		};
 
-		const CCampaignScenario & scenario = getCampaign()->camp->scenarios[CSH->campaignMap];
+		const CampaignScenario & scenario = getCampaign()->scenario(CSH->campaignMap);
 		if(scenario.prolog.hasPrologEpilog)
 		{
 			GH.windows().createAndPushWindow<CPrologEpilogVideo>(scenario.prolog, exitCb);
@@ -428,10 +431,19 @@ void CBonusSelection::startMap()
 void CBonusSelection::restartMap()
 {
 	close();
-	LOCPLINT->showYesNoDialog(CGI->generaltexth->allTexts[67], [=]()
-	{
-		GH.pushUserEvent(EUserEvent::RESTART_GAME);
-	}, 0);
+	LOCPLINT->showYesNoDialog(
+		CGI->generaltexth->allTexts[67],
+		[=]()
+		{
+			GH.dispatchMainThread(
+				[]()
+				{
+					CSH->sendRestartGame();
+				}
+			);
+		},
+		0
+	);
 }
 
 void CBonusSelection::increaseDifficulty()
@@ -446,31 +458,23 @@ void CBonusSelection::decreaseDifficulty()
 		CSH->setDifficulty(CSH->si->difficulty - 1);
 }
 
-CBonusSelection::CRegion::CRegion(int id, bool accessible, bool selectable, const CampaignRegions & campDsc)
+CBonusSelection::CRegion::CRegion(CampaignScenarioID id, bool accessible, bool selectable, const CampaignRegions & campDsc)
 	: CIntObject(LCLICK | SHOW_POPUP), idOfMapAndRegion(id), accessible(accessible), selectable(selectable)
 {
 	OBJ_CONSTRUCTION;
-	static const std::string colors[2][8] =
-	{
-		{"R", "B", "N", "G", "O", "V", "T", "P"},
-		{"Re", "Bl", "Br", "Gr", "Or", "Vi", "Te", "Pi"}
-	};
 
-	const CampaignRegions::RegionDescription & desc = campDsc.regions[idOfMapAndRegion];
-	pos.x += desc.xpos;
-	pos.y += desc.ypos;
+	pos += campDsc.getPosition(id);
 
-	std::string prefix = campDsc.campPrefix + desc.infix + "_";
-	std::string suffix = colors[campDsc.colorSuffixLength - 1][CSH->si->campState->camp->scenarios[idOfMapAndRegion].regionColor];
-	graphicsNotSelected = std::make_shared<CPicture>(prefix + "En" + suffix + ".BMP");
+	auto color = CSH->si->campState->scenario(idOfMapAndRegion).regionColor;
+
+	graphicsNotSelected = std::make_shared<CPicture>(campDsc.getAvailableName(id, color));
 	graphicsNotSelected->disable();
-	graphicsSelected = std::make_shared<CPicture>(prefix + "Se" + suffix + ".BMP");
+	graphicsSelected = std::make_shared<CPicture>(campDsc.getSelectedName(id, color));
 	graphicsSelected->disable();
-	graphicsStriped = std::make_shared<CPicture>(prefix + "Co" + suffix + ".BMP");
+	graphicsStriped = std::make_shared<CPicture>(campDsc.getConqueredName(id, color));
 	graphicsStriped->disable();
 	pos.w = graphicsNotSelected->pos.w;
 	pos.h = graphicsNotSelected->pos.h;
-
 }
 
 void CBonusSelection::CRegion::updateState()
@@ -510,7 +514,7 @@ void CBonusSelection::CRegion::clickLeft(tribool down, bool previousState)
 void CBonusSelection::CRegion::showPopupWindow()
 {
 	// FIXME: For some reason "down" is only ever contain indeterminate_value
-	auto text = CSH->si->campState->camp->scenarios[idOfMapAndRegion].regionText;
+	auto text = CSH->si->campState->scenario(idOfMapAndRegion).regionText;
 	if(!graphicsNotSelected->getSurface()->isTransparent(GH.getCursorPosition() - pos.topLeft()) && text.size())
 	{
 		CRClickPopup::createAndPush(text);

+ 7 - 5
client/lobby/CBonusSelection.h

@@ -11,10 +11,12 @@
 
 #include "../windows/CWindowObject.h"
 
+#include "../lib/campaign/CampaignConstants.h"
+
 VCMI_LIB_NAMESPACE_BEGIN
 
-class CCampaignState;
-struct CampaignRegions;
+class CampaignState;
+class CampaignRegions;
 
 VCMI_LIB_NAMESPACE_END
 
@@ -30,7 +32,7 @@ class ISelectionScreenInfo;
 class CBonusSelection : public CWindowObject
 {
 public:
-	std::shared_ptr<CCampaignState> getCampaign();
+	std::shared_ptr<CampaignState> getCampaign();
 	CBonusSelection();
 
 	class CRegion
@@ -39,11 +41,11 @@ public:
 		std::shared_ptr<CPicture> graphicsNotSelected;
 		std::shared_ptr<CPicture> graphicsSelected;
 		std::shared_ptr<CPicture> graphicsStriped;
-		int idOfMapAndRegion;
+		CampaignScenarioID idOfMapAndRegion;
 		bool accessible; // false if region should be striped
 		bool selectable; // true if region should be selectable
 	public:
-		CRegion(int id, bool accessible, bool selectable, const CampaignRegions & campDsc);
+		CRegion(CampaignScenarioID id, bool accessible, bool selectable, const CampaignRegions & campDsc);
 		void updateState();
 		void clickLeft(tribool down, bool previousState) override;
 		void showPopupWindow() override;

+ 2 - 2
client/lobby/CLobbyScreen.cpp

@@ -27,8 +27,8 @@
 #include "../../lib/CModHandler.h"
 #include "../../lib/NetPacksLobby.h"
 #include "../../lib/CGeneralTextHandler.h"
+#include "../../lib/campaign/CampaignHandler.h"
 #include "../../lib/mapping/CMapInfo.h"
-#include "../../lib/mapping/CCampaignHandler.h"
 #include "../../lib/rmg/CMapGenOptions.h"
 
 CLobbyScreen::CLobbyScreen(ESelectionScreen screenType)
@@ -119,7 +119,7 @@ void CLobbyScreen::startCampaign()
 {
 	if(CSH->mi)
 	{
-		auto ourCampaign = std::make_shared<CCampaignState>(CCampaignHandler::getCampaign(CSH->mi->fileURI));
+		auto ourCampaign = CampaignHandler::getCampaign(CSH->mi->fileURI);
 		CSH->setCampaignState(ourCampaign);
 	}
 }

+ 7 - 9
client/lobby/SelectionTab.cpp

@@ -36,10 +36,10 @@
 #include "../../lib/CModHandler.h"
 #include "../../lib/GameSettings.h"
 #include "../../lib/filesystem/Filesystem.h"
+#include "../../lib/campaign/CampaignState.h"
 #include "../../lib/mapping/CMapInfo.h"
 #include "../../lib/mapping/CMapHeader.h"
 #include "../../lib/mapping/MapFormat.h"
-#include "../../lib/mapping/CCampaignHandler.h"
 #include "../../lib/serializer/Connection.h"
 
 bool mapSorter::operator()(const std::shared_ptr<CMapInfo> aaa, const std::shared_ptr<CMapInfo> bbb)
@@ -104,13 +104,11 @@ bool mapSorter::operator()(const std::shared_ptr<CMapInfo> aaa, const std::share
 		switch(sortBy)
 		{
 		case _numOfMaps: //by number of maps in campaign
-			return aaa->campaignHeader->numberOfScenarios <
-				   bbb->campaignHeader->numberOfScenarios;
-			break;
+			return aaa->campaign->scenariosCount() < bbb->campaign->scenariosCount();
 		case _name: //by name
-			return boost::ilexicographical_compare(aaa->campaignHeader->name, bbb->campaignHeader->name);
+			return boost::ilexicographical_compare(aaa->campaign->getName(), bbb->campaign->getName());
 		default:
-			return boost::ilexicographical_compare(aaa->campaignHeader->name, bbb->campaignHeader->name);
+			return boost::ilexicographical_compare(aaa->campaign->getName(), bbb->campaign->getName());
 		}
 	}
 }
@@ -623,7 +621,7 @@ void SelectionTab::parseCampaigns(const std::unordered_set<ResourceID> & files)
 		//allItems[i].date = std::asctime(std::localtime(&files[i].date));
 		info->fileURI = file.getName();
 		info->campaignInit();
-		if(info->campaignHeader)
+		if(info->campaign)
 			allItems.push_back(info);
 	}
 }
@@ -677,7 +675,7 @@ void SelectionTab::ListItem::updateItem(std::shared_ptr<CMapInfo> info, bool sel
 	}
 
 	auto color = selected ? Colors::YELLOW : Colors::WHITE;
-	if(info->campaignHeader)
+	if(info->campaign)
 	{
 		labelAmountOfPlayers->disable();
 		labelMapSizeLetter->disable();
@@ -686,7 +684,7 @@ void SelectionTab::ListItem::updateItem(std::shared_ptr<CMapInfo> info, bool sel
 		iconLossCondition->disable();
 		labelNumberOfCampaignMaps->enable();
 		std::ostringstream ostr(std::ostringstream::out);
-		ostr << info->campaignHeader->numberOfScenarios;
+		ostr << info->campaign->scenariosCount();
 		labelNumberOfCampaignMaps->setText(ostr.str());
 		labelNumberOfCampaignMaps->setColor(color);
 	}

+ 10 - 3
client/mainmenu/CCampaignScreen.cpp

@@ -42,7 +42,7 @@
 #include "../../lib/CHeroHandler.h"
 #include "../../lib/CCreatureHandler.h"
 
-#include "../../lib/mapping/CCampaignHandler.h"
+#include "../../lib/campaign/CampaignHandler.h"
 #include "../../lib/mapping/CMapService.h"
 
 #include "../../lib/mapObjects/CGHeroInstance.h"
@@ -73,6 +73,13 @@ CCampaignScreen::CCampaignScreen(const JsonNode & config)
 		campButtons.push_back(std::make_shared<CCampaignButton>(node));
 }
 
+void CCampaignScreen::activate()
+{
+	CCS->musich->playMusic("Music/MainMenu", true, false);
+
+	CWindowObject::activate();
+}
+
 std::shared_ptr<CButton> CCampaignScreen::createExitButton(const JsonNode & button)
 {
 	std::pair<std::string, std::string> help;
@@ -96,8 +103,8 @@ CCampaignScreen::CCampaignButton::CCampaignButton(const JsonNode & config)
 
 	status = config["open"].Bool() ? CCampaignScreen::ENABLED : CCampaignScreen::DISABLED;
 
-	CCampaignHeader header = CCampaignHandler::getHeader(campFile);
-	hoverText = header.name;
+	auto header = CampaignHandler::getHeader(campFile);
+	hoverText = header->getName();
 
 	if(status != CCampaignScreen::DISABLED)
 	{

+ 2 - 0
client/mainmenu/CCampaignScreen.h

@@ -58,4 +58,6 @@ public:
 	enum CampaignSet {ROE, AB, SOD, WOG};
 
 	CCampaignScreen(const JsonNode & config);
+
+	void activate() override;
 };

+ 14 - 8
client/mainmenu/CMainMenu.cpp

@@ -42,6 +42,7 @@
 
 #include "../../lib/CGeneralTextHandler.h"
 #include "../../lib/JsonNode.h"
+#include "../../lib/campaign/CampaignHandler.h"
 #include "../../lib/serializer/Connection.h"
 #include "../../lib/serializer/CTypeList.h"
 #include "../../lib/filesystem/Filesystem.h"
@@ -54,7 +55,6 @@
 #include "../../lib/GameConstants.h"
 #include "../../lib/CRandomGenerator.h"
 #include "../../lib/CondSh.h"
-#include "../../lib/mapping/CCampaignHandler.h"
 
 #if defined(SINGLE_PROCESS_APP) && defined(VCMI_ANDROID)
 #include "../../server/CVCMIServer.h"
@@ -68,7 +68,10 @@ ISelectionScreenInfo * SEL;
 
 static void do_quit()
 {
-	GH.pushUserEvent(EUserEvent::FORCE_QUIT);
+	GH.dispatchMainThread([]()
+	{
+		handleQuit(false);
+	});
 }
 
 CMenuScreen::CMenuScreen(const JsonNode & configNode)
@@ -354,11 +357,11 @@ void CMainMenu::openLobby(ESelectionScreen screenType, bool host, const std::vec
 
 void CMainMenu::openCampaignLobby(const std::string & campaignFileName)
 {
-	auto ourCampaign = std::make_shared<CCampaignState>(CCampaignHandler::getCampaign(campaignFileName));
+	auto ourCampaign = CampaignHandler::getCampaign(campaignFileName);
 	openCampaignLobby(ourCampaign);
 }
 
-void CMainMenu::openCampaignLobby(std::shared_ptr<CCampaignState> campaign)
+void CMainMenu::openCampaignLobby(std::shared_ptr<CampaignState> campaign)
 {
 	CSH->resetStateForLobby(StartInfo::CAMPAIGN);
 	CSH->screenType = ESelectionScreen::campaignList;
@@ -562,10 +565,13 @@ void CSimpleJoinScreen::connectThread(const std::string & addr, ui16 port)
 	else
 		CSH->justConnectToServer(addr, port);
 
-	if(GH.windows().isTopWindow(this))
-	{
-		close();
-	}
+	// async call to prevent thread race
+	GH.dispatchMainThread([this](){
+		if(GH.windows().isTopWindow(this))
+		{
+			close();
+		}
+	});
 }
 
 CLoadingScreen::CLoadingScreen(std::function<void()> loader)

+ 2 - 2
client/mainmenu/CMainMenu.h

@@ -14,7 +14,7 @@
 
 VCMI_LIB_NAMESPACE_BEGIN
 
-class CCampaignState;
+class CampaignState;
 
 VCMI_LIB_NAMESPACE_END
 
@@ -147,7 +147,7 @@ public:
 	void update() override;
 	static void openLobby(ESelectionScreen screenType, bool host, const std::vector<std::string> * names, ELoadMode loadMode);
 	static void openCampaignLobby(const std::string & campaignFileName);
-	static void openCampaignLobby(std::shared_ptr<CCampaignState> campaign);
+	static void openCampaignLobby(std::shared_ptr<CampaignState> campaign);
 	void openCampaignScreen(std::string name);
 
 	static std::shared_ptr<CMainMenu> create();

+ 1 - 3
client/mainmenu/CPrologEpilogVideo.cpp

@@ -18,10 +18,8 @@
 #include "../widgets/TextControls.h"
 #include "../render/Canvas.h"
 
-#include "../../lib/mapping/CCampaignHandler.h"
 
-
-CPrologEpilogVideo::CPrologEpilogVideo(CCampaignScenario::SScenarioPrologEpilog _spe, std::function<void()> callback)
+CPrologEpilogVideo::CPrologEpilogVideo(CampaignScenarioPrologEpilog _spe, std::function<void()> callback)
 	: CWindowObject(BORDERED), spe(_spe), positionCounter(0), voiceSoundHandle(-1), exitCb(callback)
 {
 	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;

+ 4 - 3
client/mainmenu/CPrologEpilogVideo.h

@@ -9,13 +9,14 @@
  */
 #pragma once
 #include "../windows/CWindowObject.h"
-#include "../../lib/mapping/CCampaignHandler.h"
+
+#include "../../lib/campaign/CampaignScenarioPrologEpilog.h"
 
 class CMultiLineLabel;
 
 class CPrologEpilogVideo : public CWindowObject
 {
-	CCampaignScenario::SScenarioPrologEpilog spe;
+	CampaignScenarioPrologEpilog spe;
 	int positionCounter;
 	int voiceSoundHandle;
 	std::function<void()> exitCb;
@@ -23,7 +24,7 @@ class CPrologEpilogVideo : public CWindowObject
 	std::shared_ptr<CMultiLineLabel> text;
 
 public:
-	CPrologEpilogVideo(CCampaignScenario::SScenarioPrologEpilog _spe, std::function<void()> callback);
+	CPrologEpilogVideo(CampaignScenarioPrologEpilog _spe, std::function<void()> callback);
 
 	void clickLeft(tribool down, bool previousState) override;
 	void show(Canvas & to) override;

+ 0 - 1
client/render/Graphics.cpp

@@ -32,7 +32,6 @@
 #include "../lib/VCMI_Lib.h"
 #include "../CCallback.h"
 #include "../lib/CGeneralTextHandler.h"
-#include "../lib/CGameState.h"
 #include "../lib/JsonNode.h"
 #include "../lib/vcmi_endian.h"
 #include "../lib/CStopWatch.h"

+ 1 - 2
client/widgets/CGarrisonInt.cpp

@@ -27,8 +27,7 @@
 #include "../../lib/CCreatureHandler.h"
 #include "../../lib/mapObjects/CGHeroInstance.h"
 #include "../../lib/TextOperations.h"
-
-#include "../../lib/CGameState.h"
+#include "../../lib/gameState/CGameState.h"
 
 void CGarrisonSlot::setHighlight(bool on)
 {

+ 1 - 1
client/widgets/MiscWidgets.cpp

@@ -25,7 +25,7 @@
 #include "../../CCallback.h"
 
 #include "../../lib/CConfigHandler.h"
-#include "../../lib/CGameState.h"
+#include "../../lib/gameState/InfoAboutArmy.h"
 #include "../../lib/CGeneralTextHandler.h"
 #include "../../lib/CModHandler.h"
 #include "../../lib/GameSettings.h"

+ 3 - 3
client/windows/CCastleInterface.cpp

@@ -45,7 +45,7 @@
 #include "../../lib/CTownHandler.h"
 #include "../../lib/GameConstants.h"
 #include "../../lib/StartInfo.h"
-#include "../../lib/mapping/CCampaignHandler.h"
+#include "../../lib/campaign/CampaignState.h"
 #include "../../lib/mapObjects/CGHeroInstance.h"
 #include "../../lib/mapObjects/CGTownInstance.h"
 
@@ -922,8 +922,8 @@ void CCastleBuildings::enterMagesGuild()
 	{
 		const StartInfo *si = LOCPLINT->cb->getStartInfo();
 		// it would be nice to find a way to move this hack to config/mapOverrides.json
-		if(si && si->campState && si->campState->camp &&                // We're in campaign,
-			(si->campState->camp->header.filename == "DATA/YOG.H3C") && // which is "Birth of a Barbarian",
+		if(si && si->campState && si->campState &&                // We're in campaign,
+			(si->campState->getFilename() == "DATA/YOG.H3C") && // which is "Birth of a Barbarian",
 			(hero->subID == 45))                                        // and the hero is Yog (based on Solmyr)
 		{
 			// "Yog has given up magic in all its forms..."

+ 1 - 1
client/windows/CCreatureWindow.cpp

@@ -32,7 +32,7 @@
 #include "../../lib/CModHandler.h"
 #include "../../lib/GameSettings.h"
 #include "../../lib/CHeroHandler.h"
-#include "../../lib/CGameState.h"
+#include "../../lib/gameState/CGameState.h"
 #include "../../lib/TextOperations.h"
 
 class CCreatureArtifactInstance;

+ 1 - 1
client/windows/CQuestLog.cpp

@@ -26,7 +26,7 @@
 #include "../../CCallback.h"
 #include "../../lib/CArtHandler.h"
 #include "../../lib/CConfigHandler.h"
-#include "../../lib/CGameState.h"
+#include "../../lib/gameState/QuestInfo.h"
 #include "../../lib/CGeneralTextHandler.h"
 #include "../../lib/MetaString.h"
 #include "../../lib/mapObjects/CQuest.h"

+ 0 - 1
client/windows/CTradeWindow.cpp

@@ -32,7 +32,6 @@
 #include "../../lib/CCreatureHandler.h"
 #include "../../lib/CGeneralTextHandler.h"
 #include "../../lib/CHeroHandler.h"
-#include "../../lib/CGameState.h"
 #include "../../lib/mapObjects/CGHeroInstance.h"
 #include "../../lib/mapObjects/CGTownInstance.h"
 #include "../../lib/mapObjects/CGMarket.h"

+ 3 - 1
client/windows/GUIClasses.cpp

@@ -53,11 +53,13 @@
 #include "../lib/ArtifactUtils.h"
 #include "../lib/mapObjects/CGTownInstance.h"
 #include "../lib/mapObjects/ObjectTemplate.h"
+#include "../lib/gameState/CGameState.h"
+#include "../lib/gameState/InfoAboutArmy.h"
+#include "../lib/gameState/SThievesGuildInfo.h"
 #include "../lib/CArtHandler.h"
 #include "../lib/CBuildingHandler.h"
 #include "../lib/CConfigHandler.h"
 #include "../lib/CCreatureHandler.h"
-#include "../lib/CGameState.h"
 #include "../lib/CGeneralTextHandler.h"
 #include "../lib/CHeroHandler.h"
 #include "../lib/CModHandler.h"

+ 1 - 1
client/windows/InfoWindows.cpp

@@ -32,13 +32,13 @@
 
 #include "../../CCallback.h"
 
-#include "../../lib/CGameState.h"
 #include "../../lib/CConfigHandler.h"
 #include "../../lib/CondSh.h"
 #include "../../lib/CGeneralTextHandler.h" //for Unicode related stuff
 #include "../../lib/mapObjects/CGHeroInstance.h"
 #include "../../lib/mapObjects/CGTownInstance.h"
 #include "../../lib/mapObjects/MiscObjects.h"
+#include "../../lib/gameState/InfoAboutArmy.h"
 
 #include <SDL_surface.h>
 

+ 38 - 9
client/windows/settings/SettingsMainWindow.cpp

@@ -16,6 +16,7 @@
 #include "GeneralOptionsTab.h"
 #include "OtherOptionsTab.h"
 
+#include "CMT.h"
 #include "CGameInfo.h"
 #include "CGeneralTextHandler.h"
 #include "CPlayerInterface.h"
@@ -112,7 +113,18 @@ void SettingsMainWindow::close()
 
 void SettingsMainWindow::quitGameButtonCallback()
 {
-	LOCPLINT->showYesNoDialog(CGI->generaltexth->allTexts[578], [this](){ closeAndPushEvent(EUserEvent::FORCE_QUIT); }, 0);
+	LOCPLINT->showYesNoDialog(
+		CGI->generaltexth->allTexts[578],
+		[this]()
+		{
+			close();
+			GH.dispatchMainThread( []()
+			{
+				handleQuit(false);
+			});
+		},
+		0
+	);
 }
 
 void SettingsMainWindow::backButtonCallback()
@@ -122,7 +134,20 @@ void SettingsMainWindow::backButtonCallback()
 
 void SettingsMainWindow::mainMenuButtonCallback()
 {
-	LOCPLINT->showYesNoDialog(CGI->generaltexth->allTexts[578], [this](){ closeAndPushEvent(EUserEvent::RETURN_TO_MAIN_MENU); }, 0);
+	LOCPLINT->showYesNoDialog(
+		CGI->generaltexth->allTexts[578],
+		[this]()
+		{
+			close();
+			GH.dispatchMainThread( []()
+			{
+				CSH->endGameplay();
+				GH.defActionsDef = 63;
+				CMM->menu->switchToTab("main");
+			});
+		},
+		0
+	);
 }
 
 void SettingsMainWindow::loadGameButtonCallback()
@@ -139,13 +164,17 @@ void SettingsMainWindow::saveGameButtonCallback()
 
 void SettingsMainWindow::restartGameButtonCallback()
 {
-	LOCPLINT->showYesNoDialog(CGI->generaltexth->allTexts[67], [this](){ closeAndPushEvent(EUserEvent::RESTART_GAME); }, 0);
-}
-
-void SettingsMainWindow::closeAndPushEvent(EUserEvent code)
-{
-	close();
-	GH.pushUserEvent(code);
+	LOCPLINT->showYesNoDialog(
+		CGI->generaltexth->allTexts[67],
+		[this]()
+		{
+			close();
+			GH.dispatchMainThread([](){
+				CSH->sendRestartGame();
+			});
+		},
+		0
+	);
 }
 
 void SettingsMainWindow::showAll(Canvas & to)

+ 0 - 1
client/windows/settings/SettingsMainWindow.h

@@ -30,7 +30,6 @@ private:
 	void openTab(size_t index);
 
 	void close(); //TODO: copypaste of WindowBase::close(), consider changing Windowbase to IWindowbase with default close() implementation and changing WindowBase inheritance to CIntObject + IWindowBase
-	void closeAndPushEvent(EUserEvent code);
 
 	void loadGameButtonCallback();
 	void saveGameButtonCallback();

+ 19 - 5
cmake_modules/VCMI_lib.cmake

@@ -39,6 +39,9 @@ macro(add_main_lib TARGET_NAME LIBRARY_TYPE)
 		${MAIN_LIB_DIR}/bonuses/Propagators.cpp
 		${MAIN_LIB_DIR}/bonuses/Updaters.cpp
 
+		${MAIN_LIB_DIR}/campaign/CampaignHandler.cpp
+		${MAIN_LIB_DIR}/campaign/CampaignState.cpp
+
 		${MAIN_LIB_DIR}/events/ApplyDamage.cpp
 		${MAIN_LIB_DIR}/events/GameResumed.cpp
 		${MAIN_LIB_DIR}/events/ObjectVisitEnded.cpp
@@ -62,6 +65,10 @@ macro(add_main_lib TARGET_NAME LIBRARY_TYPE)
 		${MAIN_LIB_DIR}/filesystem/MinizipExtensions.cpp
 		${MAIN_LIB_DIR}/filesystem/ResourceID.cpp
 
+		${MAIN_LIB_DIR}/gameState/CGameState.cpp
+		${MAIN_LIB_DIR}/gameState/CGameStateCampaign.cpp
+		${MAIN_LIB_DIR}/gameState/InfoAboutArmy.cpp
+
 		${MAIN_LIB_DIR}/logging/CBasicLogConfigurator.cpp
 		${MAIN_LIB_DIR}/logging/CLogger.cpp
 
@@ -93,7 +100,6 @@ macro(add_main_lib TARGET_NAME LIBRARY_TYPE)
 		${MAIN_LIB_DIR}/mapObjects/MiscObjects.cpp
 		${MAIN_LIB_DIR}/mapObjects/ObjectTemplate.cpp
 
-		${MAIN_LIB_DIR}/mapping/CCampaignHandler.cpp
 		${MAIN_LIB_DIR}/mapping/CDrawRoadsOperation.cpp
 		${MAIN_LIB_DIR}/mapping/CMap.cpp
 		${MAIN_LIB_DIR}/mapping/CMapHeader.cpp
@@ -222,7 +228,6 @@ macro(add_main_lib TARGET_NAME LIBRARY_TYPE)
 		${MAIN_LIB_DIR}/CCreatureSet.cpp
 		${MAIN_LIB_DIR}/CGameInfoCallback.cpp
 		${MAIN_LIB_DIR}/CGameInterface.cpp
-		${MAIN_LIB_DIR}/CGameState.cpp
 		${MAIN_LIB_DIR}/CGeneralTextHandler.cpp
 		${MAIN_LIB_DIR}/CHeroHandler.cpp
 		${MAIN_LIB_DIR}/CModHandler.cpp
@@ -350,6 +355,11 @@ macro(add_main_lib TARGET_NAME LIBRARY_TYPE)
 		${MAIN_LIB_DIR}/bonuses/Propagators.h
 		${MAIN_LIB_DIR}/bonuses/Updaters.h
 
+		${MAIN_LIB_DIR}/campaign/CampaignConstants.h
+		${MAIN_LIB_DIR}/campaign/CampaignHandler.h
+		${MAIN_LIB_DIR}/campaign/CampaignScenarioPrologEpilog.h
+		${MAIN_LIB_DIR}/campaign/CampaignState.h
+
 		${MAIN_LIB_DIR}/events/ApplyDamage.h
 		${MAIN_LIB_DIR}/events/GameResumed.h
 		${MAIN_LIB_DIR}/events/ObjectVisitEnded.h
@@ -378,6 +388,13 @@ macro(add_main_lib TARGET_NAME LIBRARY_TYPE)
 		${MAIN_LIB_DIR}/filesystem/MinizipExtensions.h
 		${MAIN_LIB_DIR}/filesystem/ResourceID.h
 
+		${MAIN_LIB_DIR}/gameState/CGameState.h
+		${MAIN_LIB_DIR}/gameState/CGameStateCampaign.h
+		${MAIN_LIB_DIR}/gameState/EVictoryLossCheckResult.h
+		${MAIN_LIB_DIR}/gameState/InfoAboutArmy.h
+		${MAIN_LIB_DIR}/gameState/SThievesGuildInfo.h
+		${MAIN_LIB_DIR}/gameState/QuestInfo.h
+
 		${MAIN_LIB_DIR}/logging/CBasicLogConfigurator.h
 		${MAIN_LIB_DIR}/logging/CLogger.h
 
@@ -414,7 +431,6 @@ macro(add_main_lib TARGET_NAME LIBRARY_TYPE)
 		${MAIN_LIB_DIR}/mapObjects/MiscObjects.h
 		${MAIN_LIB_DIR}/mapObjects/ObjectTemplate.h
 
-		${MAIN_LIB_DIR}/mapping/CCampaignHandler.h
 		${MAIN_LIB_DIR}/mapping/CDrawRoadsOperation.h
 		${MAIN_LIB_DIR}/mapping/CMapDefines.h
 		${MAIN_LIB_DIR}/mapping/CMapEditManager.h
@@ -542,8 +558,6 @@ macro(add_main_lib TARGET_NAME LIBRARY_TYPE)
 		${MAIN_LIB_DIR}/CCreatureSet.h
 		${MAIN_LIB_DIR}/CGameInfoCallback.h
 		${MAIN_LIB_DIR}/CGameInterface.h
-		${MAIN_LIB_DIR}/CGameStateFwd.h
-		${MAIN_LIB_DIR}/CGameState.h
 		${MAIN_LIB_DIR}/CGeneralTextHandler.h
 		${MAIN_LIB_DIR}/CHeroHandler.h
 		${MAIN_LIB_DIR}/CModHandler.h

+ 0 - 1
lib/CCreatureHandler.cpp

@@ -14,7 +14,6 @@
 #include "ResourceSet.h"
 #include "filesystem/Filesystem.h"
 #include "VCMI_Lib.h"
-#include "CGameState.h"
 #include "CTownHandler.h"
 #include "CModHandler.h"
 #include "GameSettings.h"

+ 0 - 1
lib/CCreatureSet.cpp

@@ -18,7 +18,6 @@
 #include "GameSettings.h"
 #include "mapObjects/CGHeroInstance.h"
 #include "IGameCallback.h"
-#include "CGameState.h"
 #include "CGeneralTextHandler.h"
 #include "spells/CSpellHandler.h"
 #include "CHeroHandler.h"

+ 3 - 1
lib/CGameInfoCallback.cpp

@@ -10,7 +10,9 @@
 #include "StdInc.h"
 #include "CGameInfoCallback.h"
 
-#include "CGameState.h" // PlayerState
+#include "gameState/CGameState.h"
+#include "gameState/InfoAboutArmy.h"
+#include "gameState/SThievesGuildInfo.h"
 #include "CGeneralTextHandler.h"
 #include "StartInfo.h" // for StartInfo
 #include "battle/BattleInfo.h" // for BattleInfo

+ 0 - 1
lib/CGameInterface.h

@@ -11,7 +11,6 @@
 
 #include "battle/BattleAction.h"
 #include "IGameEventsReceiver.h"
-#include "CGameStateFwd.h"
 
 #include "spells/ViewSpellInt.h"
 

+ 0 - 215
lib/CGameStateFwd.h

@@ -1,215 +0,0 @@
-/*
- * CGameStateFwd.h, part of VCMI engine
- *
- * Authors: listed in file AUTHORS in main folder
- *
- * License: GNU General Public License v2.0 or later
- * Full text of license available in license.txt file, in main folder
- *
- */
-#pragma once
-
-#include "CCreatureSet.h"
-#include "MetaString.h"
-#include "int3.h"
-
-VCMI_LIB_NAMESPACE_BEGIN
-
-class CQuest;
-class CGObjectInstance;
-class CHeroClass;
-class CTown;
-
-//numbers of creatures are exact numbers if detailed else they are quantity ids (1 - a few, 2 - several and so on; additionally 0 - unknown)
-struct ArmyDescriptor : public std::map<SlotID, CStackBasicDescriptor>
-{
-	bool isDetailed;
-	DLL_LINKAGE ArmyDescriptor(const CArmedInstance *army, bool detailed); //not detailed -> quantity ids as count
-	DLL_LINKAGE ArmyDescriptor();
-
-	DLL_LINKAGE int getStrength() const;
-};
-
-struct DLL_LINKAGE InfoAboutArmy
-{
-	PlayerColor owner;
-	std::string name;
-
-	ArmyDescriptor army;
-
-	InfoAboutArmy();
-	InfoAboutArmy(const CArmedInstance *Army, bool detailed);
-
-	void initFromArmy(const CArmedInstance *Army, bool detailed);
-};
-
-struct DLL_LINKAGE InfoAboutHero : public InfoAboutArmy
-{
-private:
-	void assign(const InfoAboutHero & iah);
-
-public:
-	struct DLL_LINKAGE Details
-	{
-		std::vector<si32> primskills;
-		si32 mana, manaLimit, luck, morale;
-	};
-
-	Details * details = nullptr;
-
-	const CHeroClass *hclass;
-	int portrait;
-
-	enum EInfoLevel
-	{
-		BASIC,
-		DETAILED,
-		INBATTLE
-	};
-
-	InfoAboutHero();
-	InfoAboutHero(const InfoAboutHero & iah);
-	InfoAboutHero(const CGHeroInstance *h, EInfoLevel infoLevel);
-	~InfoAboutHero();
-
-	InfoAboutHero & operator=(const InfoAboutHero & iah);
-
-	void initFromHero(const CGHeroInstance *h, EInfoLevel infoLevel);
-};
-
-/// Struct which holds a int information about a town
-struct DLL_LINKAGE InfoAboutTown : public InfoAboutArmy
-{
-	struct DLL_LINKAGE Details
-	{
-		si32 hallLevel, goldIncome;
-		bool customRes;
-		bool garrisonedHero;
-
-	} *details;
-
-	const CTown *tType;
-
-	si32 built;
-	si32 fortLevel; //0 - none
-
-	InfoAboutTown();
-	InfoAboutTown(const CGTownInstance *t, bool detailed);
-	~InfoAboutTown();
-	void initFromTown(const CGTownInstance *t, bool detailed);
-};
-
-class DLL_LINKAGE EVictoryLossCheckResult
-{
-public:
-	static EVictoryLossCheckResult victory(MetaString toSelf, MetaString toOthers)
-	{
-		return EVictoryLossCheckResult(VICTORY, toSelf, toOthers);
-	}
-
-	static EVictoryLossCheckResult defeat(MetaString toSelf, MetaString toOthers)
-	{
-		return EVictoryLossCheckResult(DEFEAT, toSelf, toOthers);
-	}
-
-	EVictoryLossCheckResult():
-	intValue(0)
-	{
-	}
-
-	bool operator==(EVictoryLossCheckResult const & other) const
-	{
-		return intValue == other.intValue;
-	}
-
-	bool operator!=(EVictoryLossCheckResult const & other) const
-	{
-		return intValue != other.intValue;
-	}
-
-	bool victory() const
-	{
-		return intValue == VICTORY;
-	}
-	bool loss() const
-	{
-		return intValue == DEFEAT;
-	}
-
-	EVictoryLossCheckResult invert()
-	{
-		return EVictoryLossCheckResult(-intValue, messageToOthers, messageToSelf);
-	}
-
-	MetaString messageToSelf;
-	MetaString messageToOthers;
-
-	template <typename Handler> void serialize(Handler &h, const int version)
-	{
-		h & intValue;
-		h & messageToSelf;
-		h & messageToOthers;
-	}
-private:
-	enum EResult
-	{
-		DEFEAT = -1,
-		INGAME =  0,
-		VICTORY= +1
-	};
-
-	EVictoryLossCheckResult(si32 intValue, MetaString toSelf, MetaString toOthers):
-		messageToSelf(toSelf),
-		messageToOthers(toOthers),
-		intValue(intValue)
-	{
-	}
-
-	si32 intValue; // uses EResultult
-};
-
-/*static std::ostream & operator<<(std::ostream & os, const EVictoryLossCheckResult & victoryLossCheckResult)
-{
-	os << victoryLossCheckResult.messageToSelf;
-	return os;
-}*/
-
-struct DLL_LINKAGE QuestInfo //universal interface for human and AI
-{
-	const CQuest * quest;
-	const CGObjectInstance * obj; //related object, most likely Seer Hut
-	int3 tile;
-
-	QuestInfo()
-		: quest(nullptr), obj(nullptr), tile(-1,-1,-1)
-	{};
-	QuestInfo (const CQuest * Quest, const CGObjectInstance * Obj, int3 Tile) :
-		quest (Quest), obj (Obj), tile (Tile){};
-
-	QuestInfo (const QuestInfo &qi) : quest(qi.quest), obj(qi.obj), tile(qi.tile)
-	{};
-
-	const QuestInfo& operator= (const QuestInfo &qi)
-	{
-		quest = qi.quest;
-		obj = qi.obj;
-		tile = qi.tile;
-		return *this;
-	}
-
-	bool operator== (const QuestInfo & qi) const
-	{
-		return (quest == qi.quest && obj == qi.obj);
-	}
-
-	//std::vector<std::string> > texts //allow additional info for quest log?
-
-	template <typename Handler> void serialize(Handler &h, const int version)
-	{
-		h & quest;
-		h & obj;
-		h & tile;
-	}
-};
-
-VCMI_LIB_NAMESPACE_END

+ 3 - 1
lib/CPlayerState.cpp

@@ -10,7 +10,7 @@
 #include "StdInc.h"
 
 #include "CPlayerState.h"
-#include "CGameStateFwd.h"
+#include "gameState/QuestInfo.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 
@@ -40,6 +40,8 @@ PlayerState::PlayerState(PlayerState && other) noexcept:
 	std::swap(quests, other.quests);
 }
 
+PlayerState::~PlayerState() = default;
+
 std::string PlayerState::nodeName() const
 {
 	return "Player " + color.getStrCap(false);

+ 1 - 0
lib/CPlayerState.h

@@ -43,6 +43,7 @@ public:
 
 	PlayerState();
 	PlayerState(PlayerState && other) noexcept;
+	~PlayerState();
 
 	std::string nodeName() const override;
 

+ 2 - 0
lib/GameConstants.cpp

@@ -38,6 +38,8 @@
 
 VCMI_LIB_NAMESPACE_BEGIN
 
+const HeroTypeID HeroTypeID::NONE = HeroTypeID(-1);
+
 const SlotID SlotID::COMMANDER_SLOT_PLACEHOLDER = SlotID(-2);
 const SlotID SlotID::SUMMONED_SLOT_PLACEHOLDER = SlotID(-3);
 const SlotID SlotID::WAR_MACHINES_SLOT = SlotID(-4);

+ 3 - 0
lib/GameConstants.h

@@ -329,6 +329,8 @@ class HeroTypeID : public BaseForID<HeroTypeID, si32>
 	///json serialization helpers
 	static si32 decode(const std::string & identifier);
 	static std::string encode(const si32 index);
+
+	DLL_LINKAGE static const HeroTypeID NONE;
 };
 
 class SlotID : public BaseForID<SlotID, si32>
@@ -1223,6 +1225,7 @@ class SpellID
 public:
 	enum ESpellID
 	{
+		SPELLBOOK_PRESET = -3,
 		PRESET = -2,
 		NONE = -1,
 		SUMMON_BOAT=0, SCUTTLE_BOAT=1, VISIONS=2, VIEW_EARTH=3, DISGUISE=4, VIEW_AIR=5,

+ 3 - 2
lib/IGameCallback.cpp

@@ -32,9 +32,10 @@
 #include "mapObjectConstructors/CObjectClassesHandler.h"
 #include "mapObjects/CObjectHandler.h"
 #include "mapObjects/ObjectTemplate.h"
-#include "mapping/CCampaignHandler.h"
+#include "campaign/CampaignState.h"
 #include "StartInfo.h"
-#include "CGameState.h"
+#include "gameState/CGameState.h"
+#include "gameState/CGameStateCampaign.h"
 #include "mapping/CMap.h"
 #include "CPlayerState.h"
 #include "GameSettings.h"

+ 8 - 7
lib/NetPacks.h

@@ -11,16 +11,17 @@
 
 #include "NetPacksBase.h"
 
-#include "battle/BattleAction.h"
-#include "mapObjects/CGHeroInstance.h"
 #include "ConstTransitivePtr.h"
-#include "int3.h"
-#include "ResourceSet.h"
-#include "CGameStateFwd.h"
-#include "mapping/CMapDefines.h"
-#include "battle/CObstacleInstance.h"
 #include "MetaString.h"
+#include "ResourceSet.h"
+#include "int3.h"
 
+#include "battle/BattleAction.h"
+#include "battle/CObstacleInstance.h"
+#include "gameState/EVictoryLossCheckResult.h"
+#include "gameState/QuestInfo.h"
+#include "mapObjects/CGHeroInstance.h"
+#include "mapping/CMapDefines.h"
 #include "spells/ViewSpellInt.h"
 
 class CClient;

+ 3 - 20
lib/NetPacksLib.cpp

@@ -19,7 +19,7 @@
 #include "mapping/CMap.h"
 #include "spells/CSpellHandler.h"
 #include "CCreatureHandler.h"
-#include "CGameState.h"
+#include "gameState/CGameState.h"
 #include "CStack.h"
 #include "battle/BattleInfo.h"
 #include "CTownHandler.h"
@@ -31,7 +31,7 @@
 #include "mapObjects/CGMarket.h"
 #include "mapObjectConstructors/AObjectTypeHandler.h"
 #include "mapObjectConstructors/CObjectClassesHandler.h"
-#include "mapping/CCampaignHandler.h"
+#include "campaign/CampaignState.h"
 #include "GameSettings.h"
 
 
@@ -1066,30 +1066,13 @@ void PlayerEndsGame::applyGs(CGameState * gs) const
 		p->status = EPlayerStatus::WINNER;
 
 		// TODO: Campaign-specific code might as well go somewhere else
+		// keep all heroes from the winning player
 		if(p->human && gs->scenarioOps->campState)
 		{
 			std::vector<CGHeroInstance *> crossoverHeroes;
 			for (CGHeroInstance * hero : gs->map->heroesOnMap)
-			{
 				if (hero->tempOwner == player)
-				{
-					// keep all heroes from the winning player
-					crossoverHeroes.push_back(hero);
-				}
-				else if (vstd::contains(gs->scenarioOps->campState->getCurrentScenario().keepHeroes, HeroTypeID(hero->subID)))
-				{
-					// keep hero whether lost or won (like Xeron in AB campaign)
 					crossoverHeroes.push_back(hero);
-				}
-			}
-			// keep lost heroes which are in heroes pool
-			for (auto & heroPair : gs->hpool.heroesPool)
-			{
-				if (vstd::contains(gs->scenarioOps->campState->getCurrentScenario().keepHeroes, HeroTypeID(heroPair.first)))
-				{
-					crossoverHeroes.push_back(heroPair.second.get());
-				}
-			}
 
 			gs->scenarioOps->campState->setCurrentMapAsConquered(crossoverHeroes);
 		}

+ 3 - 3
lib/NetPacksLobby.h

@@ -18,7 +18,7 @@ class CVCMIServer;
 
 VCMI_LIB_NAMESPACE_BEGIN
 
-class CCampaignState;
+class CampaignState;
 class CMapInfo;
 struct StartInfo;
 class CMapGenOptions;
@@ -175,7 +175,7 @@ struct DLL_LINKAGE LobbySetMap : public CLobbyPackToServer
 
 struct DLL_LINKAGE LobbySetCampaign : public CLobbyPackToServer
 {
-	std::shared_ptr<CCampaignState> ourCampaign;
+	std::shared_ptr<CampaignState> ourCampaign;
 
 	virtual void visitTyped(ICPackVisitor & visitor) override;
 
@@ -187,7 +187,7 @@ struct DLL_LINKAGE LobbySetCampaign : public CLobbyPackToServer
 
 struct DLL_LINKAGE LobbySetCampaignMap : public CLobbyPackToServer
 {
-	int mapId = -1;
+	CampaignScenarioID mapId = CampaignScenarioID::NONE;
 
 	virtual void visitTyped(ICPackVisitor & visitor) override;
 

+ 3 - 3
lib/StartInfo.cpp

@@ -15,7 +15,7 @@
 #include "VCMI_Lib.h"
 #include "rmg/CMapGenOptions.h"
 #include "mapping/CMapInfo.h"
-#include "mapping/CCampaignHandler.h"
+#include "campaign/CampaignState.h"
 #include "mapping/CMapHeader.h"
 #include "mapping/CMapService.h"
 
@@ -62,8 +62,8 @@ PlayerSettings * StartInfo::getPlayersSettings(const ui8 connectedPlayerId)
 
 std::string StartInfo::getCampaignName() const
 {
-	if(campState->camp->header.name.length())
-		return campState->camp->header.name;
+	if(!campState->getName().empty())
+		return campState->getName();
 	else
 		return VLC->generaltexth->allTexts[508];
 }

+ 5 - 4
lib/StartInfo.h

@@ -10,11 +10,12 @@
 #pragma once
 
 #include "GameConstants.h"
+#include "campaign/CampaignConstants.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 
 class CMapGenOptions;
-class CCampaignState;
+class CampaignState;
 class CMapInfo;
 struct PlayerInfo;
 class PlayerColor;
@@ -87,7 +88,7 @@ struct DLL_LINKAGE StartInfo
 	bool createRandomMap() const { return mapGenOptions != nullptr; }
 	std::shared_ptr<CMapGenOptions> mapGenOptions;
 
-	std::shared_ptr<CCampaignState> campState;
+	std::shared_ptr<CampaignState> campState;
 
 	PlayerSettings & getIthPlayersSettings(const PlayerColor & no);
 	const PlayerSettings & getIthPlayersSettings(const PlayerColor & no) const;
@@ -138,10 +139,10 @@ struct DLL_LINKAGE LobbyState
 	int hostClientId;
 	// TODO: Campaign-only and we don't really need either of them.
 	// Before start both go into CCampaignState that is part of StartInfo
-	int campaignMap;
+	CampaignScenarioID campaignMap;
 	int campaignBonus;
 
-	LobbyState() : si(new StartInfo()), hostClientId(-1), campaignMap(-1), campaignBonus(-1) {}
+	LobbyState() : si(new StartInfo()), hostClientId(-1), campaignMap(CampaignScenarioID::NONE), campaignBonus(-1) {}
 
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{

+ 1 - 0
lib/battle/CBattleInfoEssentials.cpp

@@ -13,6 +13,7 @@
 #include "BattleInfo.h"
 #include "../NetPacks.h"
 #include "../mapObjects/CGTownInstance.h"
+#include "../gameState/InfoAboutArmy.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 

+ 1 - 1
lib/battle/CPlayerBattleCallback.cpp

@@ -10,7 +10,7 @@
 #include "StdInc.h"
 #include "CPlayerBattleCallback.h"
 #include "../CStack.h"
-#include "../CGameState.h"
+#include "../gameState/InfoAboutArmy.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 

+ 57 - 0
lib/campaign/CampaignConstants.h

@@ -0,0 +1,57 @@
+/*
+ * CampaignConstants.h, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+#pragma once
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+enum class CampaignVersion : uint8_t
+{
+	NONE = 0,
+	RoE = 4,
+	AB = 5,
+	SoD = 6,
+	WoG = 6,
+	//		Chr = 7, // Heroes Chronicles, likely identical to SoD, untested
+
+	VCMI = 1,
+	VCMI_MIN = 1,
+	VCMI_MAX = 1,
+};
+
+enum class CampaignStartOptions: int8_t
+{
+	NONE = 0,
+	START_BONUS,
+	HERO_CROSSOVER,
+	HERO_OPTIONS
+};
+
+enum class CampaignBonusType : int8_t
+{
+	NONE = -1,
+	SPELL,
+	MONSTER,
+	BUILDING,
+	ARTIFACT,
+	SPELL_SCROLL,
+	PRIMARY_SKILL,
+	SECONDARY_SKILL,
+	RESOURCE,
+	HEROES_FROM_PREVIOUS_SCENARIO,
+	HERO
+};
+
+enum class CampaignScenarioID : int8_t
+{
+	NONE = -1,
+	// no members - fake enum to create integer type that is not implicitly convertible to int
+};
+
+VCMI_LIB_NAMESPACE_END

+ 615 - 0
lib/campaign/CampaignHandler.cpp

@@ -0,0 +1,615 @@
+/*
+ * CCampaignHandler.cpp, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+#include "StdInc.h"
+#include "CampaignHandler.h"
+
+#include "CampaignState.h"
+
+#include "../filesystem/Filesystem.h"
+#include "../filesystem/CCompressedStream.h"
+#include "../filesystem/CMemoryStream.h"
+#include "../filesystem/CBinaryReader.h"
+#include "../VCMI_Lib.h"
+#include "../CGeneralTextHandler.h"
+#include "../TextOperations.h"
+#include "../CModHandler.h"
+#include "../Languages.h"
+#include "../StringConstants.h"
+#include "../mapping/CMapHeader.h"
+#include "../mapping/CMapService.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+void CampaignHandler::readCampaign(Campaign * ret, const std::vector<ui8> & input, std::string filename, std::string modName, std::string encoding)
+{
+	if (input.front() < uint8_t(' ')) // binary format
+	{
+		CMemoryStream stream(input.data(), input.size());
+		CBinaryReader reader(&stream);
+
+		readHeaderFromMemory(*ret, reader, filename, modName, encoding);
+
+		for(int g = 0; g < ret->numberOfScenarios; ++g)
+		{
+			auto scenarioID = static_cast<CampaignScenarioID>(ret->scenarios.size());
+			ret->scenarios[scenarioID] = readScenarioFromMemory(reader, *ret);
+		}
+	}
+	else // text format (json)
+	{
+		JsonNode jsonCampaign((const char*)input.data(), input.size());
+		readHeaderFromJson(*ret, jsonCampaign, filename, modName, encoding);
+
+		for(auto & scenario : jsonCampaign["scenarios"].Vector())
+		{
+			auto scenarioID = static_cast<CampaignScenarioID>(ret->scenarios.size());
+			ret->scenarios[scenarioID] = readScenarioFromJson(scenario);
+		}
+	}
+}
+
+std::unique_ptr<Campaign> CampaignHandler::getHeader( const std::string & name)
+{
+	ResourceID resourceID(name, EResType::CAMPAIGN);
+	std::string modName = VLC->modh->findResourceOrigin(resourceID);
+	std::string language = VLC->modh->getModLanguage(modName);
+	std::string encoding = Languages::getLanguageOptions(language).encoding;
+	
+	auto ret = std::make_unique<Campaign>();
+	auto fileStream = CResourceHandler::get(modName)->load(resourceID);
+	std::vector<ui8> cmpgn = getFile(std::move(fileStream), true)[0];
+
+	readCampaign(ret.get(), cmpgn, resourceID.getName(), modName, encoding);
+
+	return ret;
+}
+
+std::shared_ptr<CampaignState> CampaignHandler::getCampaign( const std::string & name )
+{
+	ResourceID resourceID(name, EResType::CAMPAIGN);
+	std::string modName = VLC->modh->findResourceOrigin(resourceID);
+	std::string language = VLC->modh->getModLanguage(modName);
+	std::string encoding = Languages::getLanguageOptions(language).encoding;
+	
+	auto ret = std::make_unique<CampaignState>();
+	
+	auto fileStream = CResourceHandler::get(modName)->load(resourceID);
+
+	std::vector<std::vector<ui8>> files = getFile(std::move(fileStream), false);
+
+	readCampaign(ret.get(), files[0], resourceID.getName(), modName, encoding);
+
+	//first entry is campaign header. start loop from 1
+	for(int scenarioIndex = 0, fileIndex = 1; fileIndex < files.size() && scenarioIndex < ret->numberOfScenarios; scenarioIndex++)
+	{
+		auto scenarioID = static_cast<CampaignScenarioID>(scenarioIndex);
+
+		if(!ret->scenarios[scenarioID].isNotVoid()) //skip void scenarios
+			continue;
+
+		std::string scenarioName = resourceID.getName();
+		boost::to_lower(scenarioName);
+		scenarioName += ':' + std::to_string(fileIndex - 1);
+
+		//set map piece appropriately, convert vector to string
+		ret->mapPieces[scenarioID] = files[fileIndex];
+
+		auto hdr = ret->getMapHeader(scenarioID);
+		ret->scenarios[scenarioID].scenarioName = hdr->name;
+		fileIndex++;
+	}
+
+	return ret;
+}
+
+static std::string convertMapName(std::string input)
+{
+	boost::algorithm::to_lower(input);
+	boost::algorithm::trim(input);
+
+	size_t slashPos = input.find_last_of("/");
+
+	if (slashPos != std::string::npos)
+		return input.substr(slashPos + 1);
+
+	return input;
+}
+
+std::string CampaignHandler::readLocalizedString(CBinaryReader & reader, std::string filename, std::string modName, std::string encoding, std::string identifier)
+{
+	TextIdentifier stringID( "campaign", convertMapName(filename), identifier);
+
+	std::string input = TextOperations::toUnicode(reader.readBaseString(), encoding);
+
+	if (input.empty())
+		return "";
+
+	VLC->generaltexth->registerString(modName, stringID, input);
+	return VLC->generaltexth->translate(stringID.get());
+}
+
+void CampaignHandler::readHeaderFromJson(CampaignHeader & ret, JsonNode & reader, std::string filename, std::string modName, std::string encoding)
+{
+	ret.version = static_cast<CampaignVersion>(reader["version"].Integer());
+	if(ret.version < CampaignVersion::VCMI_MIN || ret.version > CampaignVersion::VCMI_MAX)
+	{
+		logGlobal->info("VCMP Loading: Unsupported campaign %s version %d", filename, static_cast<int>(ret.version));
+		return;
+	}
+	
+	ret.version = CampaignVersion::VCMI;
+	ret.campaignRegions = CampaignRegions::fromJson(reader["regions"]);
+	ret.numberOfScenarios = reader["scenarios"].Vector().size();
+	ret.name = reader["name"].String();
+	ret.description = reader["description"].String();
+	ret.difficultyChoosenByPlayer = reader["allowDifficultySelection"].Bool();
+	ret.music = reader["music"].String();
+	ret.filename = filename;
+	ret.modName = modName;
+	ret.encoding = encoding;
+}
+
+CampaignScenario CampaignHandler::readScenarioFromJson(JsonNode & reader)
+{
+	auto prologEpilogReader = [](JsonNode & identifier) -> CampaignScenarioPrologEpilog
+	{
+		CampaignScenarioPrologEpilog ret;
+		ret.hasPrologEpilog = !identifier.isNull();
+		if(ret.hasPrologEpilog)
+		{
+			ret.prologVideo = identifier["video"].String();
+			ret.prologMusic = identifier["music"].String();
+			ret.prologText = identifier["text"].String();
+		}
+		return ret;
+	};
+
+	CampaignScenario ret;
+	ret.mapName = reader["map"].String();
+	for(auto & g : reader["preconditions"].Vector())
+		ret.preconditionRegions.insert(static_cast<CampaignScenarioID>(g.Integer()));
+
+	ret.regionColor = reader["color"].Integer();
+	ret.difficulty = reader["difficulty"].Integer();
+	ret.regionText = reader["regionText"].String();
+	ret.prolog = prologEpilogReader(reader["prolog"]);
+	ret.epilog = prologEpilogReader(reader["epilog"]);
+
+	ret.travelOptions = readScenarioTravelFromJson(reader);
+
+	return ret;
+}
+
+CampaignTravel CampaignHandler::readScenarioTravelFromJson(JsonNode & reader)
+{
+	CampaignTravel ret;
+
+	std::map<std::string, CampaignStartOptions> startOptionsMap = {
+		{"none", CampaignStartOptions::NONE},
+		{"bonus", CampaignStartOptions::START_BONUS},
+		{"crossover", CampaignStartOptions::HERO_CROSSOVER},
+		{"hero", CampaignStartOptions::HERO_OPTIONS}
+	};
+	
+	std::map<std::string, CampaignBonusType> bonusTypeMap = {
+		{"spell", CampaignBonusType::SPELL},
+		{"creature", CampaignBonusType::MONSTER},
+		{"building", CampaignBonusType::BUILDING},
+		{"artifact", CampaignBonusType::ARTIFACT},
+		{"scroll", CampaignBonusType::SPELL_SCROLL},
+		{"primarySkill", CampaignBonusType::PRIMARY_SKILL},
+		{"secondarySkill", CampaignBonusType::SECONDARY_SKILL},
+		{"resource", CampaignBonusType::RESOURCE},
+		//{"prevHero", CScenarioTravel::STravelBonus::EBonusType::HEROES_FROM_PREVIOUS_SCENARIO},
+		//{"hero", CScenarioTravel::STravelBonus::EBonusType::HERO},
+	};
+	
+	std::map<std::string, ui32> primarySkillsMap = {
+		{"attack", 0},
+		{"defence", 8},
+		{"spellpower", 16},
+		{"knowledge", 24},
+	};
+	
+	std::map<std::string, ui16> heroSpecialMap = {
+		{"strongest", 0xFFFD},
+		{"generated", 0xFFFE},
+		{"random", 0xFFFF}
+	};
+	
+	std::map<std::string, ui8> resourceTypeMap = {
+		//FD - wood+ore
+		//FE - mercury+sulfur+crystal+gem
+		{"wood", 0},
+		{"mercury", 1},
+		{"ore", 2},
+		{"sulfur", 3},
+		{"crystal", 4},
+		{"gems", 5},
+		{"gold", 6},
+		{"common", 0xFD},
+		{"rare", 0xFE}
+	};
+	
+	for(auto & k : reader["heroKeeps"].Vector())
+	{
+		if(k.String() == "experience") ret.whatHeroKeeps.experience = true;
+		if(k.String() == "primarySkills") ret.whatHeroKeeps.primarySkills = true;
+		if(k.String() == "secondarySkills") ret.whatHeroKeeps.secondarySkills = true;
+		if(k.String() == "spells") ret.whatHeroKeeps.spells = true;
+		if(k.String() == "artifacts") ret.whatHeroKeeps.artifacts = true;
+	}
+	
+	for(auto & k : reader["keepCreatures"].Vector())
+	{
+		if(auto identifier = VLC->modh->identifiers.getIdentifier(CModHandler::scopeMap(), "creature", k.String()))
+			ret.monstersKeptByHero.insert(CreatureID(identifier.value()));
+		else
+			logGlobal->warn("VCMP Loading: keepCreatures contains unresolved identifier %s", k.String());
+	}
+	for(auto & k : reader["keepArtifacts"].Vector())
+	{
+		if(auto identifier = VLC->modh->identifiers.getIdentifier(CModHandler::scopeMap(), "artifact", k.String()))
+			ret.artifactsKeptByHero.insert(ArtifactID(identifier.value()));
+		else
+			logGlobal->warn("VCMP Loading: keepArtifacts contains unresolved identifier %s", k.String());
+	}
+
+	ret.startOptions = startOptionsMap[reader["startOptions"].String()];
+	switch(ret.startOptions)
+	{
+	case CampaignStartOptions::NONE:
+		//no bonuses. Seems to be OK
+		break;
+	case CampaignStartOptions::START_BONUS: //reading of bonuses player can choose
+		{
+			ret.playerColor = reader["playerColor"].Integer();
+			for(auto & bjson : reader["bonuses"].Vector())
+			{
+				CampaignBonus bonus;
+				bonus.type = bonusTypeMap[bjson["what"].String()];
+				switch (bonus.type)
+				{
+					case CampaignBonusType::RESOURCE:
+						bonus.info1 = resourceTypeMap[bjson["type"].String()];
+						bonus.info2 = bjson["amount"].Integer();
+						break;
+						
+					case CampaignBonusType::BUILDING:
+						bonus.info1 = vstd::find_pos(EBuildingType::names, bjson["type"].String());
+						if(bonus.info1 == -1)
+							logGlobal->warn("VCMP Loading: unresolved building identifier %s", bjson["type"].String());
+						break;
+						
+					default:
+						if(int heroId = heroSpecialMap[bjson["hero"].String()])
+							bonus.info1 = heroId;
+						else
+							if(auto identifier = VLC->modh->identifiers.getIdentifier(CModHandler::scopeMap(), "hero", bjson["hero"].String()))
+								bonus.info1 = identifier.value();
+							else
+								logGlobal->warn("VCMP Loading: unresolved hero identifier %s", bjson["hero"].String());
+	
+						bonus.info3 = bjson["amount"].Integer();
+						
+						switch(bonus.type)
+						{
+							case CampaignBonusType::SPELL:
+							case CampaignBonusType::MONSTER:
+							case CampaignBonusType::SECONDARY_SKILL:
+							case CampaignBonusType::ARTIFACT:
+								if(auto identifier  = VLC->modh->identifiers.getIdentifier(CModHandler::scopeMap(), bjson["what"].String(), bjson["type"].String()))
+									bonus.info2 = identifier.value();
+								else
+									logGlobal->warn("VCMP Loading: unresolved %s identifier %s", bjson["what"].String(), bjson["type"].String());
+								break;
+								
+							case CampaignBonusType::SPELL_SCROLL:
+								if(auto Identifier = VLC->modh->identifiers.getIdentifier(CModHandler::scopeMap(), "spell", bjson["type"].String()))
+									bonus.info2 = Identifier.value();
+								else
+									logGlobal->warn("VCMP Loading: unresolved spell scroll identifier %s", bjson["type"].String());
+								break;
+								
+							case CampaignBonusType::PRIMARY_SKILL:
+								for(auto & ps : primarySkillsMap)
+									bonus.info2 |= bjson[ps.first].Integer() << ps.second;
+								break;
+								
+							default:
+								bonus.info2 = bjson["type"].Integer();
+						}
+						break;
+				}
+				ret.bonusesToChoose.push_back(bonus);
+			}
+			break;
+		}
+	case CampaignStartOptions::HERO_CROSSOVER: //reading of players (colors / scenarios ?) player can choose
+		{
+			for(auto & bjson : reader["bonuses"].Vector())
+			{
+				CampaignBonus bonus;
+				bonus.type = CampaignBonusType::HEROES_FROM_PREVIOUS_SCENARIO;
+				bonus.info1 = bjson["playerColor"].Integer(); //player color
+				bonus.info2 = bjson["scenario"].Integer(); //from what scenario
+				ret.bonusesToChoose.push_back(bonus);
+			}
+			break;
+		}
+	case CampaignStartOptions::HERO_OPTIONS: //heroes player can choose between
+		{
+			for(auto & bjson : reader["bonuses"].Vector())
+			{
+				CampaignBonus bonus;
+				bonus.type = CampaignBonusType::HERO;
+				bonus.info1 = bjson["playerColor"].Integer(); //player color
+				
+				if(int heroId = heroSpecialMap[bjson["hero"].String()])
+					bonus.info2 = heroId;
+				else
+					if (auto identifier = VLC->modh->identifiers.getIdentifier(CModHandler::scopeMap(), "hero", bjson["hero"].String()))
+						bonus.info2 = identifier.value();
+					else
+						logGlobal->warn("VCMP Loading: unresolved hero identifier %s", bjson["hero"].String());
+			
+				ret.bonusesToChoose.push_back(bonus);
+			}
+			break;
+		}
+	default:
+		{
+			logGlobal->warn("VCMP Loading: Unsupported start options value");
+			break;
+		}
+	}
+
+	return ret;
+}
+
+
+void CampaignHandler::readHeaderFromMemory( CampaignHeader & ret, CBinaryReader & reader, std::string filename, std::string modName, std::string encoding )
+{
+	ret.version = static_cast<CampaignVersion>(reader.readUInt32());
+	ui8 campId = reader.readUInt8() - 1;//change range of it from [1, 20] to [0, 19]
+	ret.loadLegacyData(campId);
+	ret.name = readLocalizedString(reader, filename, modName, encoding, "name");
+	ret.description = readLocalizedString(reader, filename, modName, encoding, "description");
+	if (ret.version > CampaignVersion::RoE)
+		ret.difficultyChoosenByPlayer = reader.readInt8();
+	else
+		ret.difficultyChoosenByPlayer = false;
+
+	ret.music = prologMusicName(reader.readInt8());
+	ret.filename = filename;
+	ret.modName = modName;
+	ret.encoding = encoding;
+}
+
+CampaignScenario CampaignHandler::readScenarioFromMemory( CBinaryReader & reader, const CampaignHeader & header)
+{
+	auto prologEpilogReader = [&](const std::string & identifier) -> CampaignScenarioPrologEpilog
+	{
+		CampaignScenarioPrologEpilog ret;
+		ret.hasPrologEpilog = reader.readUInt8();
+		if(ret.hasPrologEpilog)
+		{
+			ret.prologVideo = CampaignHandler::prologVideoName(reader.readUInt8());
+			ret.prologMusic = CampaignHandler::prologMusicName(reader.readUInt8());
+			ret.prologText = readLocalizedString(reader, header.filename, header.modName, header.encoding, identifier);
+		}
+		return ret;
+	};
+
+	CampaignScenario ret;
+	ret.mapName = reader.readBaseString();
+	reader.readUInt32(); //packedMapSize - not used
+	if(header.numberOfScenarios > 8) //unholy alliance
+	{
+		ret.loadPreconditionRegions(reader.readUInt16());
+	}
+	else
+	{
+		ret.loadPreconditionRegions(reader.readUInt8());
+	}
+	ret.regionColor = reader.readUInt8();
+	ret.difficulty = reader.readUInt8();
+	ret.regionText = readLocalizedString(reader, header.filename, header.modName, header.encoding, ret.mapName + ".region");
+	ret.prolog = prologEpilogReader(ret.mapName + ".prolog");
+	ret.epilog = prologEpilogReader(ret.mapName + ".epilog");
+
+	ret.travelOptions = readScenarioTravelFromMemory(reader, header.version);
+
+	return ret;
+}
+
+template<typename Identifier>
+static void readContainer(std::set<Identifier> & container, CBinaryReader & reader, int sizeBytes)
+{
+	for(int iId = 0, byte = 0; iId < sizeBytes * 8; ++iId)
+	{
+		if(iId % 8 == 0)
+			byte = reader.readUInt8();
+		if(byte & (1 << iId % 8))
+			container.insert(Identifier(iId));
+	}
+}
+
+CampaignTravel CampaignHandler::readScenarioTravelFromMemory(CBinaryReader & reader, CampaignVersion version )
+{
+	CampaignTravel ret;
+
+	ui8 whatHeroKeeps = reader.readUInt8();
+	ret.whatHeroKeeps.experience = whatHeroKeeps & 1;
+	ret.whatHeroKeeps.primarySkills = whatHeroKeeps & 2;
+	ret.whatHeroKeeps.secondarySkills = whatHeroKeeps & 4;
+	ret.whatHeroKeeps.spells = whatHeroKeeps & 8;
+	ret.whatHeroKeeps.artifacts = whatHeroKeeps & 16;
+	
+	readContainer(ret.monstersKeptByHero, reader, 19);
+	readContainer(ret.artifactsKeptByHero, reader, version < CampaignVersion::SoD ? 17 : 18);
+
+	ret.startOptions = static_cast<CampaignStartOptions>(reader.readUInt8());
+
+	switch(ret.startOptions)
+	{
+	case CampaignStartOptions::NONE:
+		//no bonuses. Seems to be OK
+		break;
+	case CampaignStartOptions::START_BONUS: //reading of bonuses player can choose
+		{
+			ret.playerColor = reader.readUInt8();
+			ui8 numOfBonuses = reader.readUInt8();
+			for (int g=0; g<numOfBonuses; ++g)
+			{
+				CampaignBonus bonus;
+				bonus.type = static_cast<CampaignBonusType>(reader.readUInt8());
+				//hero: FFFD means 'most powerful' and FFFE means 'generated'
+				switch(bonus.type)
+				{
+				case CampaignBonusType::SPELL:
+					{
+						bonus.info1 = reader.readUInt16(); //hero
+						bonus.info2 = reader.readUInt8(); //spell ID
+						break;
+					}
+				case CampaignBonusType::MONSTER:
+					{
+						bonus.info1 = reader.readUInt16(); //hero
+						bonus.info2 = reader.readUInt16(); //monster type
+						bonus.info3 = reader.readUInt16(); //monster count
+						break;
+					}
+				case CampaignBonusType::BUILDING:
+					{
+						bonus.info1 = reader.readUInt8(); //building ID (0 - town hall, 1 - city hall, 2 - capitol, etc)
+						break;
+					}
+				case CampaignBonusType::ARTIFACT:
+					{
+						bonus.info1 = reader.readUInt16(); //hero
+						bonus.info2 = reader.readUInt16(); //artifact ID
+						break;
+					}
+				case CampaignBonusType::SPELL_SCROLL:
+					{
+						bonus.info1 = reader.readUInt16(); //hero
+						bonus.info2 = reader.readUInt8(); //spell ID
+						break;
+					}
+				case CampaignBonusType::PRIMARY_SKILL:
+					{
+						bonus.info1 = reader.readUInt16(); //hero
+						bonus.info2 = reader.readUInt32(); //bonuses (4 bytes for 4 skills)
+						break;
+					}
+				case CampaignBonusType::SECONDARY_SKILL:
+					{
+						bonus.info1 = reader.readUInt16(); //hero
+						bonus.info2 = reader.readUInt8(); //skill ID
+						bonus.info3 = reader.readUInt8(); //skill level
+						break;
+					}
+				case CampaignBonusType::RESOURCE:
+					{
+						bonus.info1 = reader.readUInt8(); //type
+						//FD - wood+ore
+						//FE - mercury+sulfur+crystal+gem
+						bonus.info2 = reader.readUInt32(); //count
+						break;
+					}
+				default:
+					logGlobal->warn("Corrupted h3c file");
+					break;
+				}
+				ret.bonusesToChoose.push_back(bonus);
+			}
+			break;
+		}
+	case CampaignStartOptions::HERO_CROSSOVER: //reading of players (colors / scenarios ?) player can choose
+		{
+			ui8 numOfBonuses = reader.readUInt8();
+			for (int g=0; g<numOfBonuses; ++g)
+			{
+				CampaignBonus bonus;
+				bonus.type = CampaignBonusType::HEROES_FROM_PREVIOUS_SCENARIO;
+				bonus.info1 = reader.readUInt8(); //player color
+				bonus.info2 = reader.readUInt8(); //from what scenario
+
+				ret.bonusesToChoose.push_back(bonus);
+			}
+			break;
+		}
+	case CampaignStartOptions::HERO_OPTIONS: //heroes player can choose between
+		{
+			ui8 numOfBonuses = reader.readUInt8();
+			for (int g=0; g<numOfBonuses; ++g)
+			{
+				CampaignBonus bonus;
+				bonus.type = CampaignBonusType::HERO;
+				bonus.info1 = reader.readUInt8(); //player color
+				bonus.info2 = reader.readUInt16(); //hero, FF FF - random
+
+				ret.bonusesToChoose.push_back(bonus);
+			}
+			break;
+		}
+	default:
+		{
+			logGlobal->warn("Corrupted h3c file");
+			break;
+		}
+	}
+
+	return ret;
+}
+
+std::vector< std::vector<ui8> > CampaignHandler::getFile(std::unique_ptr<CInputStream> file, bool headerOnly)
+{
+	CCompressedStream stream(std::move(file), true);
+
+	std::vector< std::vector<ui8> > ret;
+	do
+	{
+		std::vector<ui8> block(stream.getSize());
+		stream.read(block.data(), block.size());
+		ret.push_back(block);
+		ret.back().shrink_to_fit();
+	}
+	while (!headerOnly && stream.getNextBlock());
+
+	return ret;
+}
+
+std::string CampaignHandler::prologVideoName(ui8 index)
+{
+	JsonNode config(ResourceID(std::string("CONFIG/campaignMedia"), EResType::TEXT));
+	auto vids = config["videos"].Vector();
+	if(index < vids.size())
+		return vids[index].String();
+	return "";
+}
+
+std::string CampaignHandler::prologMusicName(ui8 index)
+{
+	std::vector<std::string> music;
+	return VLC->generaltexth->translate("core.cmpmusic." + std::to_string(static_cast<int>(index)));
+}
+
+std::string CampaignHandler::prologVoiceName(ui8 index)
+{
+	JsonNode config(ResourceID(std::string("CONFIG/campaignMedia"), EResType::TEXT));
+	auto audio = config["voice"].Vector();
+	if(index < audio.size())
+		return audio[index].String();
+	return "";
+}
+
+VCMI_LIB_NAMESPACE_END

+ 45 - 0
lib/campaign/CampaignHandler.h

@@ -0,0 +1,45 @@
+/*
+ * CampaignHandler.h, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+#pragma once
+
+#include "CampaignState.h" // Convenience include - not required for build, but required for any user of CampaignHandler
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+class DLL_LINKAGE CampaignHandler
+{
+	static std::string readLocalizedString(CBinaryReader & reader, std::string filename, std::string modName, std::string encoding, std::string identifier);
+
+	static void readCampaign(Campaign * target, const std::vector<ui8> & stream, std::string filename, std::string modName, std::string encoding);
+
+	//parsers for VCMI campaigns (*.vcmp)
+	static void readHeaderFromJson(CampaignHeader & target, JsonNode & reader, std::string filename, std::string modName, std::string encoding);
+	static CampaignScenario readScenarioFromJson(JsonNode & reader);
+	static CampaignTravel readScenarioTravelFromJson(JsonNode & reader);
+
+	//parsers for original H3C campaigns
+	static void readHeaderFromMemory(CampaignHeader & target, CBinaryReader & reader, std::string filename, std::string modName, std::string encoding);
+	static CampaignScenario readScenarioFromMemory(CBinaryReader & reader, const CampaignHeader & header);
+	static CampaignTravel readScenarioTravelFromMemory(CBinaryReader & reader, CampaignVersion version);
+	/// returns h3c split in parts. 0 = h3c header, 1-end - maps (binary h3m)
+	/// headerOnly - only header will be decompressed, returned vector wont have any maps
+	static std::vector<std::vector<ui8>> getFile(std::unique_ptr<CInputStream> file, bool headerOnly);
+
+	static std::string prologVideoName(ui8 index);
+	static std::string prologMusicName(ui8 index);
+	static std::string prologVoiceName(ui8 index);
+
+public:
+	static std::unique_ptr<Campaign> getHeader( const std::string & name); //name - name of appropriate file
+
+	static std::shared_ptr<CampaignState> getCampaign(const std::string & name); //name - name of appropriate file
+};
+
+VCMI_LIB_NAMESPACE_END

+ 30 - 0
lib/campaign/CampaignScenarioPrologEpilog.h

@@ -0,0 +1,30 @@
+/*
+ * CampaignScenarioPrologEpilog.h, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+#pragma once
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+struct DLL_LINKAGE CampaignScenarioPrologEpilog
+{
+	bool hasPrologEpilog = false;
+	std::string prologVideo; // from CmpMovie.txt
+	std::string prologMusic; // from CmpMusic.txt
+	std::string prologText;
+
+	template <typename Handler> void serialize(Handler &h, const int formatVersion)
+	{
+		h & hasPrologEpilog;
+		h & prologVideo;
+		h & prologMusic;
+		h & prologText;
+	}
+};
+
+VCMI_LIB_NAMESPACE_END

+ 435 - 0
lib/campaign/CampaignState.cpp

@@ -0,0 +1,435 @@
+/*
+ * CCampaignHandler.cpp, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+#include "StdInc.h"
+#include "CampaignState.h"
+
+#include "../JsonNode.h"
+#include "../filesystem/ResourceID.h"
+#include "../VCMI_Lib.h"
+#include "../CGeneralTextHandler.h"
+#include "../mapping/CMapService.h"
+#include "../mapping/CMapInfo.h"
+#include "../mapping/CMap.h"
+#include "../mapObjects/CGHeroInstance.h"
+#include "../serializer/JsonDeserializer.h"
+#include "../serializer/JsonSerializer.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+void CampaignScenario::loadPreconditionRegions(ui32 regions)
+{
+	for (int i=0; i<32; i++) //for each bit in region. h3c however can only hold up to 16
+	{
+		if ( (1 << i) & regions)
+			preconditionRegions.insert(static_cast<CampaignScenarioID>(i));
+	}
+}
+
+CampaignRegions::RegionDescription CampaignRegions::RegionDescription::fromJson(const JsonNode & node)
+{
+	CampaignRegions::RegionDescription rd;
+	rd.infix = node["infix"].String();
+	rd.xpos = static_cast<int>(node["x"].Float());
+	rd.ypos = static_cast<int>(node["y"].Float());
+	return rd;
+}
+
+CampaignRegions CampaignRegions::fromJson(const JsonNode & node)
+{
+	CampaignRegions cr;
+	cr.campPrefix = node["prefix"].String();
+	cr.colorSuffixLength = static_cast<int>(node["color_suffix_length"].Float());
+
+	for(const JsonNode & desc : node["desc"].Vector())
+		cr.regions.push_back(CampaignRegions::RegionDescription::fromJson(desc));
+
+	return cr;
+}
+
+CampaignRegions CampaignRegions::getLegacy(int campId)
+{
+	static std::vector<CampaignRegions> campDescriptions;
+	if(campDescriptions.empty()) //read once
+	{
+		const JsonNode config(ResourceID("config/campaign_regions.json"));
+		for(const JsonNode & campaign : config["campaign_regions"].Vector())
+			campDescriptions.push_back(CampaignRegions::fromJson(campaign));
+	}
+
+	return campDescriptions.at(campId);
+}
+
+std::string CampaignRegions::getBackgroundName() const
+{
+	return campPrefix + "_BG.BMP";
+}
+
+Point CampaignRegions::getPosition(CampaignScenarioID which) const
+{
+	auto const & region = regions[static_cast<int>(which)];
+	return Point(region.xpos, region.ypos);
+}
+
+std::string CampaignRegions::getNameFor(CampaignScenarioID which, int colorIndex, std::string type) const
+{
+	auto const & region = regions[static_cast<int>(which)];
+
+	static const std::string colors[2][8] =
+	{
+		{"R", "B", "N", "G", "O", "V", "T", "P"},
+		{"Re", "Bl", "Br", "Gr", "Or", "Vi", "Te", "Pi"}
+	};
+
+	std::string color = colors[colorSuffixLength - 1][colorIndex];
+
+	return campPrefix + region.infix + "_" + type + color + ".BMP";
+}
+
+std::string CampaignRegions::getAvailableName(CampaignScenarioID which, int color) const
+{
+	return getNameFor(which, color, "En");
+}
+
+std::string CampaignRegions::getSelectedName(CampaignScenarioID which, int color) const
+{
+	return getNameFor(which, color, "Se");
+}
+
+std::string CampaignRegions::getConqueredName(CampaignScenarioID which, int color) const
+{
+	return getNameFor(which, color, "Co");
+}
+
+
+bool CampaignBonus::isBonusForHero() const
+{
+	return type == CampaignBonusType::SPELL ||
+		   type == CampaignBonusType::MONSTER ||
+		   type == CampaignBonusType::ARTIFACT ||
+		   type == CampaignBonusType::SPELL_SCROLL ||
+		   type == CampaignBonusType::PRIMARY_SKILL ||
+		   type == CampaignBonusType::SECONDARY_SKILL;
+}
+
+void CampaignHeader::loadLegacyData(ui8 campId)
+{
+	campaignRegions = CampaignRegions::getLegacy(campId);
+	numberOfScenarios = VLC->generaltexth->getCampaignLength(campId);
+}
+
+bool CampaignHeader::playerSelectedDifficulty() const
+{
+	return difficultyChoosenByPlayer;
+}
+
+bool CampaignHeader::formatVCMI() const
+{
+	return version == CampaignVersion::VCMI;
+}
+
+std::string CampaignHeader::getDescription() const
+{
+	return description;
+}
+
+std::string CampaignHeader::getName() const
+{
+	return name;
+}
+
+std::string CampaignHeader::getFilename() const
+{
+	return filename;
+}
+
+std::string CampaignHeader::getModName() const
+{
+	return modName;
+}
+
+std::string CampaignHeader::getEncoding() const
+{
+	return encoding;
+}
+
+std::string CampaignHeader::getMusic() const
+{
+	return music;
+}
+
+const CampaignRegions & CampaignHeader::getRegions() const
+{
+	return campaignRegions;
+}
+
+bool CampaignState::isConquered(CampaignScenarioID whichScenario) const
+{
+	return vstd::contains(mapsConquered, whichScenario);
+}
+
+bool CampaignState::isAvailable(CampaignScenarioID whichScenario) const
+{
+	//check for void scenraio
+	if (!scenario(whichScenario).isNotVoid())
+	{
+		return false;
+	}
+
+	if (vstd::contains(mapsConquered, whichScenario))
+	{
+		return false;
+	}
+	//check preconditioned regions
+	for (auto const & it : scenario(whichScenario).preconditionRegions)
+	{
+		if (!vstd::contains(mapsConquered, it))
+			return false;
+	}
+	return true;
+}
+
+bool CampaignScenario::isNotVoid() const
+{
+	return !mapName.empty();
+}
+
+std::set<HeroTypeID> CampaignState::getReservedHeroes() const
+{
+	std::set<HeroTypeID> result;
+
+	for (auto const & scenarioID : allScenarios())
+	{
+		if (isConquered(scenarioID))
+			continue;
+
+		auto header = getMapHeader(scenarioID);
+
+		result.insert(header->reservedCampaignHeroes.begin(), header->reservedCampaignHeroes.end());
+	}
+
+	return result;
+}
+
+const CGHeroInstance * CampaignState::strongestHero(CampaignScenarioID scenarioId, const PlayerColor & owner) const
+{
+	std::function<bool(const JsonNode & node)> isOwned = [owner](const JsonNode & node)
+	{
+		auto * h = CampaignState::crossoverDeserialize(node, nullptr);
+		bool result = h->tempOwner == owner;
+		vstd::clear_pointer(h);
+		return result;
+	};
+	auto ownedHeroes = scenarioHeroPool.at(scenarioId) | boost::adaptors::filtered(isOwned);
+
+	if (ownedHeroes.empty())
+		return nullptr;
+
+	return CampaignState::crossoverDeserialize(ownedHeroes.front(), nullptr);
+}
+
+/// Returns heroes that can be instantiated as hero placeholders by power
+const std::vector<JsonNode> & CampaignState::getHeroesByPower(CampaignScenarioID scenarioId) const
+{
+	static const std::vector<JsonNode> emptyVector;
+
+	if (scenarioHeroPool.count(scenarioId))
+		return scenarioHeroPool.at(scenarioId);
+
+	return emptyVector;
+}
+
+/// Returns hero for instantiation as placeholder by type
+/// May return empty JsonNode if such hero was not found
+const JsonNode & CampaignState::getHeroByType(HeroTypeID heroID) const
+{
+	static const JsonNode emptyNode;
+
+	if (!getReservedHeroes().count(heroID))
+		return emptyNode;
+
+	if (!globalHeroPool.count(heroID))
+		return emptyNode;
+
+	return globalHeroPool.at(heroID);
+}
+
+void CampaignState::setCurrentMapAsConquered(std::vector<CGHeroInstance *> heroes)
+{
+	range::sort(heroes, [](const CGHeroInstance * a, const CGHeroInstance * b)
+	{
+		return a->getHeroStrength() > b->getHeroStrength();
+	});
+
+	logGlobal->info("Scenario %d of campaign %s (%s) has been completed", static_cast<int>(*currentMap), getFilename(), getName());
+
+	mapsConquered.push_back(*currentMap);
+	auto reservedHeroes = getReservedHeroes();
+
+	for (auto * hero : heroes)
+	{
+		HeroTypeID heroType(hero->subID);
+		JsonNode node = CampaignState::crossoverSerialize(hero);
+
+		if (reservedHeroes.count(heroType))
+		{
+			logGlobal->info("Hero crossover: %d (%s) exported to global pool", hero->subID, hero->getNameTranslated());
+			globalHeroPool[heroType] = node;
+		}
+		else
+		{
+			logGlobal->info("Hero crossover: %d (%s) exported to scenario pool", hero->subID, hero->getNameTranslated());
+			scenarioHeroPool[*currentMap].push_back(node);
+		}
+	}
+}
+
+std::optional<CampaignBonus> CampaignState::getBonus(CampaignScenarioID which) const
+{
+	auto bonuses = scenario(which).travelOptions.bonusesToChoose;
+	assert(chosenCampaignBonuses.count(*currentMap) || bonuses.empty());
+
+	if(bonuses.empty())
+		return std::optional<CampaignBonus>();
+
+	if (!getBonusID(which))
+		return std::optional<CampaignBonus>();
+
+	return bonuses[getBonusID(which).value()];
+}
+
+std::optional<ui8> CampaignState::getBonusID(CampaignScenarioID which) const
+{
+	if (!chosenCampaignBonuses.count(which))
+		return std::nullopt;
+
+	return chosenCampaignBonuses.at(which);
+}
+
+std::unique_ptr<CMap> CampaignState::getMap(CampaignScenarioID scenarioId) const
+{
+	// FIXME: there is certainly better way to handle maps inside campaigns
+	if(scenarioId == CampaignScenarioID::NONE)
+		scenarioId = currentMap.value();
+
+	CMapService mapService;
+	std::string scenarioName = getFilename().substr(0, getFilename().find('.'));
+	boost::to_lower(scenarioName);
+	scenarioName += ':' + std::to_string(static_cast<int>(scenarioId));
+	const auto & mapContent = mapPieces.find(scenarioId)->second;
+	return mapService.loadMap(mapContent.data(), mapContent.size(), scenarioName, getModName(), getEncoding());
+}
+
+std::unique_ptr<CMapHeader> CampaignState::getMapHeader(CampaignScenarioID scenarioId) const
+{
+	if(scenarioId == CampaignScenarioID::NONE)
+		scenarioId = currentMap.value();
+
+	CMapService mapService;
+	std::string scenarioName = getFilename().substr(0, getFilename().find('.'));
+	boost::to_lower(scenarioName);
+	scenarioName += ':' + std::to_string(static_cast<int>(scenarioId));
+	const auto & mapContent = mapPieces.find(scenarioId)->second;
+	return mapService.loadMapHeader(mapContent.data(), mapContent.size(), scenarioName, getModName(), getEncoding());
+}
+
+std::shared_ptr<CMapInfo> CampaignState::getMapInfo(CampaignScenarioID scenarioId) const
+{
+	if(scenarioId == CampaignScenarioID::NONE)
+		scenarioId = currentMap.value();
+
+	auto mapInfo = std::make_shared<CMapInfo>();
+	mapInfo->fileURI = getFilename();
+	mapInfo->mapHeader = getMapHeader(scenarioId);
+	mapInfo->countPlayers();
+	return mapInfo;
+}
+
+JsonNode CampaignState::crossoverSerialize(CGHeroInstance * hero)
+{
+	JsonNode node;
+	JsonSerializer handler(nullptr, node);
+	hero->serializeJsonOptions(handler);
+	return node;
+}
+
+CGHeroInstance * CampaignState::crossoverDeserialize(const JsonNode & node, CMap * map)
+{
+	JsonDeserializer handler(nullptr, const_cast<JsonNode&>(node));
+	auto * hero = new CGHeroInstance();
+	hero->ID = Obj::HERO;
+	hero->serializeJsonOptions(handler);
+	if (map)
+		hero->serializeJsonArtifacts(handler, "artifacts", map);
+	return hero;
+}
+
+void CampaignState::setCurrentMap(CampaignScenarioID which)
+{
+	assert(scenario(which).isNotVoid());
+
+	currentMap = which;
+}
+
+void CampaignState::setCurrentMapBonus(ui8 which)
+{
+	chosenCampaignBonuses[*currentMap] = which;
+}
+
+std::optional<CampaignScenarioID> CampaignState::currentScenario() const
+{
+	return currentMap;
+}
+
+std::optional<CampaignScenarioID> CampaignState::lastScenario() const
+{
+	if (mapsConquered.empty())
+		return std::nullopt;
+	return mapsConquered.back();
+}
+
+std::set<CampaignScenarioID> CampaignState::conqueredScenarios() const
+{
+	std::set<CampaignScenarioID> result;
+	result.insert(mapsConquered.begin(), mapsConquered.end());
+	return result;
+}
+
+std::set<CampaignScenarioID> Campaign::allScenarios() const
+{
+	std::set<CampaignScenarioID> result;
+
+	for (auto const & entry : scenarios)
+	{
+		if (entry.second.isNotVoid())
+			result.insert(entry.first);
+	}
+
+	return result;
+}
+
+int Campaign::scenariosCount() const
+{
+	return allScenarios().size();
+}
+
+const CampaignScenario & Campaign::scenario(CampaignScenarioID which) const
+{
+	assert(scenarios.count(which));
+	assert(scenarios.at(which).isNotVoid());
+
+	return scenarios.at(which);
+}
+
+bool CampaignState::isCampaignFinished() const
+{
+	return conqueredScenarios() == allScenarios();
+}
+
+VCMI_LIB_NAMESPACE_END

+ 307 - 0
lib/campaign/CampaignState.h

@@ -0,0 +1,307 @@
+/*
+ * CampaignState.h, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+#pragma once
+
+#include "../../lib/GameConstants.h"
+#include "CampaignConstants.h"
+#include "CampaignScenarioPrologEpilog.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+struct StartInfo;
+class CGHeroInstance;
+class CBinaryReader;
+class CInputStream;
+class CMap;
+class CMapHeader;
+class CMapInfo;
+class JsonNode;
+class Point;
+
+class DLL_LINKAGE CampaignRegions
+{
+	std::string campPrefix;
+	int colorSuffixLength;
+
+	struct DLL_LINKAGE RegionDescription
+	{
+		std::string infix;
+		int xpos, ypos;
+
+		template <typename Handler> void serialize(Handler &h, const int formatVersion)
+		{
+			h & infix;
+			h & xpos;
+			h & ypos;
+		}
+
+		static CampaignRegions::RegionDescription fromJson(const JsonNode & node);
+	};
+
+	std::vector<RegionDescription> regions;
+
+	std::string getNameFor(CampaignScenarioID which, int color, std::string type) const;
+
+public:
+	std::string getBackgroundName() const;
+	Point getPosition(CampaignScenarioID which) const;
+	std::string getAvailableName(CampaignScenarioID which, int color) const;
+	std::string getSelectedName(CampaignScenarioID which, int color) const;
+	std::string getConqueredName(CampaignScenarioID which, int color) const;
+
+	template <typename Handler> void serialize(Handler &h, const int formatVersion)
+	{
+		h & campPrefix;
+		h & colorSuffixLength;
+		h & regions;
+	}
+
+	static CampaignRegions fromJson(const JsonNode & node);
+	static CampaignRegions getLegacy(int campId);
+};
+
+class DLL_LINKAGE CampaignHeader : public boost::noncopyable
+{
+	friend class CampaignHandler;
+
+	CampaignVersion version = CampaignVersion::NONE;
+	CampaignRegions campaignRegions;
+	std::string name;
+	std::string description;
+	std::string music;
+	std::string filename;
+	std::string modName;
+	std::string encoding;
+
+	int numberOfScenarios = 0;
+	bool difficultyChoosenByPlayer = false;
+
+	void loadLegacyData(ui8 campId);
+
+public:
+	bool playerSelectedDifficulty() const;
+	bool formatVCMI() const;
+
+	std::string getDescription() const;
+	std::string getName() const;
+	std::string getFilename() const;
+	std::string getModName() const;
+	std::string getEncoding() const;
+	std::string getMusic() const;
+
+	const CampaignRegions & getRegions() const;
+
+	template <typename Handler> void serialize(Handler &h, const int formatVersion)
+	{
+		h & version;
+		h & campaignRegions;
+		h & numberOfScenarios;
+		h & name;
+		h & description;
+		h & difficultyChoosenByPlayer;
+		h & filename;
+		h & modName;
+		h & music;
+		h & encoding;
+	}
+};
+
+struct DLL_LINKAGE CampaignBonus
+{
+	CampaignBonusType type = CampaignBonusType::NONE;
+
+	//purpose depends on type
+	int32_t info1 = 0;
+	int32_t info2 = 0;
+	int32_t info3 = 0;
+
+	bool isBonusForHero() const;
+
+	template <typename Handler> void serialize(Handler &h, const int formatVersion)
+	{
+		h & type;
+		h & info1;
+		h & info2;
+		h & info3;
+	}
+};
+
+struct DLL_LINKAGE CampaignTravel
+{
+	struct DLL_LINKAGE WhatHeroKeeps
+	{
+		bool experience = false;
+		bool primarySkills = false;
+		bool secondarySkills = false;
+		bool spells = false;
+		bool artifacts = false;
+
+		template <typename Handler> void serialize(Handler &h, const int formatVersion)
+		{
+			h & experience;
+			h & primarySkills;
+			h & secondarySkills;
+			h & spells;
+			h & artifacts;
+		}
+	};
+
+	std::set<CreatureID> monstersKeptByHero;
+	std::set<ArtifactID> artifactsKeptByHero;
+	std::vector<CampaignBonus> bonusesToChoose;
+
+	WhatHeroKeeps whatHeroKeeps;
+	CampaignStartOptions startOptions = CampaignStartOptions::NONE; //1 - start bonus, 2 - traveling hero, 3 - hero options
+	PlayerColor playerColor = PlayerColor::NEUTRAL; //only for startOptions == 1
+
+	template <typename Handler> void serialize(Handler &h, const int formatVersion)
+	{
+		h & whatHeroKeeps;
+		h & monstersKeptByHero;
+		h & artifactsKeptByHero;
+		h & startOptions;
+		h & playerColor;
+		h & bonusesToChoose;
+	}
+};
+
+struct DLL_LINKAGE CampaignScenario
+{
+	std::string mapName; //*.h3m
+	std::string scenarioName; //from header. human-readble
+	std::set<CampaignScenarioID> preconditionRegions; //what we need to conquer to conquer this one (stored as bitfield in h3c)
+	ui8 regionColor = 0;
+	ui8 difficulty = 0;
+
+	std::string regionText;
+	CampaignScenarioPrologEpilog prolog;
+	CampaignScenarioPrologEpilog epilog;
+
+	CampaignTravel travelOptions;
+
+	void loadPreconditionRegions(ui32 regions);
+	bool isNotVoid() const;
+
+	template <typename Handler> void serialize(Handler &h, const int formatVersion)
+	{
+		h & mapName;
+		h & scenarioName;
+		h & preconditionRegions;
+		h & regionColor;
+		h & difficulty;
+		h & regionText;
+		h & prolog;
+		h & epilog;
+		h & travelOptions;
+	}
+};
+
+/// Class that represents loaded campaign information
+class DLL_LINKAGE Campaign : public CampaignHeader
+{
+	friend class CampaignHandler;
+
+	std::map<CampaignScenarioID, CampaignScenario> scenarios;
+
+public:
+	const CampaignScenario & scenario(CampaignScenarioID which) const;
+	std::set<CampaignScenarioID> allScenarios() const;
+	int scenariosCount() const;
+
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & static_cast<CampaignHeader&>(*this);
+		h & scenarios;
+	}
+};
+
+/// Class that represent campaign that is being played at
+/// Contains campaign itself as well as current state of the campaign
+class DLL_LINKAGE CampaignState : public Campaign
+{
+	friend class CampaignHandler;
+	using ScenarioPoolType = std::vector<JsonNode>;
+	using CampaignPoolType = std::map<CampaignScenarioID, ScenarioPoolType>;
+	using GlobalPoolType = std::map<HeroTypeID, JsonNode>;
+
+	/// List of all maps completed by player, in order of their completion
+	std::vector<CampaignScenarioID> mapsConquered;
+
+	std::map<CampaignScenarioID, std::vector<uint8_t> > mapPieces; //binary h3ms, scenario number -> map data
+	std::map<CampaignScenarioID, ui8> chosenCampaignBonuses;
+	std::optional<CampaignScenarioID> currentMap;
+
+	/// Heroes from specific scenario, ordered by descending strength
+	CampaignPoolType scenarioHeroPool;
+
+	/// Pool of heroes currently reserved for usage in campaign
+	GlobalPoolType globalHeroPool;
+
+public:
+	CampaignState() = default;
+
+	/// Returns last completed scenario, if any
+	std::optional<CampaignScenarioID> lastScenario() const;
+
+	std::optional<CampaignScenarioID> currentScenario() const;
+	std::set<CampaignScenarioID> conqueredScenarios() const;
+
+	/// Returns bonus selected for specific scenario
+	std::optional<CampaignBonus> getBonus(CampaignScenarioID which) const;
+
+	/// Returns index of selected bonus for specified scenario
+	std::optional<ui8> getBonusID(CampaignScenarioID which) const;
+
+	/// Returns true if selected scenario can be selected and started by player
+	bool isAvailable(CampaignScenarioID whichScenario) const;
+
+	/// Returns true if selected scenario has been already completed by player
+	bool isConquered(CampaignScenarioID whichScenario) const;
+
+	/// Returns true if all available scenarios have been completed and campaign is finished
+	bool isCampaignFinished() const;
+
+	std::unique_ptr<CMap> getMap(CampaignScenarioID scenarioId) const;
+	std::unique_ptr<CMapHeader> getMapHeader(CampaignScenarioID scenarioId) const;
+	std::shared_ptr<CMapInfo> getMapInfo(CampaignScenarioID scenarioId) const;
+
+	void setCurrentMap(CampaignScenarioID which);
+	void setCurrentMapBonus(ui8 which);
+	void setCurrentMapAsConquered(std::vector<CGHeroInstance*> heroes);
+
+	/// Returns list of heroes that must be reserved for campaign and can only be used for hero placeholders
+	std::set<HeroTypeID> getReservedHeroes() const;
+
+	/// Returns strongest hero from specified scenario, or null if none found
+	const CGHeroInstance * strongestHero(CampaignScenarioID scenarioId, const PlayerColor & owner) const;
+
+	/// Returns heroes that can be instantiated as hero placeholders by power
+	const std::vector<JsonNode> & getHeroesByPower(CampaignScenarioID scenarioId) const;
+
+	/// Returns hero for instantiation as placeholder by type
+	/// May return empty JsonNode if such hero was not found
+	const JsonNode & getHeroByType(HeroTypeID heroID) const;
+
+	static JsonNode crossoverSerialize(CGHeroInstance * hero);
+	static CGHeroInstance * crossoverDeserialize(const JsonNode & node, CMap * map);
+
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & static_cast<Campaign&>(*this);
+		h & scenarioHeroPool;
+		h & globalHeroPool;
+		h & mapPieces;
+		h & mapsConquered;
+		h & currentMap;
+		h & chosenCampaignBonuses;
+	}
+};
+
+VCMI_LIB_NAMESPACE_END

+ 70 - 814
lib/CGameState.cpp → lib/gameState/CGameState.cpp

@@ -10,36 +10,40 @@
 #include "StdInc.h"
 #include "CGameState.h"
 
-#include "mapping/CCampaignHandler.h"
-#include "ArtifactUtils.h"
-#include "CArtHandler.h"
-#include "CBuildingHandler.h"
-#include "CGeneralTextHandler.h"
-#include "CTownHandler.h"
-#include "spells/CSpellHandler.h"
-#include "CHeroHandler.h"
-#include "CModHandler.h"
-#include "GameSettings.h"
-#include "TerrainHandler.h"
-#include "CSkillHandler.h"
-#include "mapping/CMap.h"
-#include "mapping/CMapService.h"
-#include "mapObjectConstructors/CObjectClassesHandler.h"
-#include "StartInfo.h"
-#include "NetPacks.h"
-#include "pathfinder/CPathfinder.h"
-#include "pathfinder/PathfinderOptions.h"
-#include "registerTypes/RegisterTypes.h"
-#include "battle/BattleInfo.h"
-#include "JsonNode.h"
-#include "filesystem/Filesystem.h"
-#include "GameConstants.h"
-#include "rmg/CMapGenerator.h"
-#include "CStopWatch.h"
-#include "mapping/CMapEditManager.h"
-#include "serializer/CTypeList.h"
-#include "serializer/CMemorySerializer.h"
-#include "VCMIDirs.h"
+#include "EVictoryLossCheckResult.h"
+#include "InfoAboutArmy.h"
+#include "CGameStateCampaign.h"
+#include "SThievesGuildInfo.h"
+
+#include "../ArtifactUtils.h"
+#include "../CBuildingHandler.h"
+#include "../CGeneralTextHandler.h"
+#include "../CHeroHandler.h"
+#include "../CPlayerState.h"
+#include "../CStopWatch.h"
+#include "../GameSettings.h"
+#include "../StartInfo.h"
+#include "../TerrainHandler.h"
+#include "../VCMIDirs.h"
+#include "../VCMI_Lib.h"
+#include "../battle/BattleInfo.h"
+#include "../campaign/CampaignState.h"
+#include "../filesystem/ResourceID.h"
+#include "../mapObjectConstructors/AObjectTypeHandler.h"
+#include "../mapObjectConstructors/CObjectClassesHandler.h"
+#include "../mapObjectConstructors/DwellingInstanceConstructor.h"
+#include "../mapObjects/CGHeroInstance.h"
+#include "../mapObjects/CGTownInstance.h"
+#include "../mapping/CMap.h"
+#include "../mapping/CMapEditManager.h"
+#include "../mapping/CMapService.h"
+#include "../pathfinder/CPathfinder.h"
+#include "../pathfinder/PathfinderOptions.h"
+#include "../registerTypes/RegisterTypes.h"
+#include "../rmg/CMapGenerator.h"
+#include "../serializer/CMemorySerializer.h"
+#include "../serializer/CTypeList.h"
+#include "../spells/CSpellHandler.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 
@@ -173,37 +177,18 @@ CGHeroInstance * CGameState::HeroesPool::pickHeroFor(bool native,
 	return ret;
 }
 
-void CGameState::CrossoverHeroesList::addHeroToBothLists(CGHeroInstance * hero)
-{
-	heroesFromPreviousScenario.push_back(hero);
-	heroesFromAnyPreviousScenarios.push_back(hero);
-}
-
-void CGameState::CrossoverHeroesList::removeHeroFromBothLists(CGHeroInstance * hero)
-{
-	heroesFromPreviousScenario -= hero;
-	heroesFromAnyPreviousScenarios -= hero;
-}
-
-CGameState::CampaignHeroReplacement::CampaignHeroReplacement(CGHeroInstance * hero, const ObjectInstanceID & heroPlaceholderId):
-	hero(hero),
-	heroPlaceholderId(heroPlaceholderId)
-{
-
-}
-
-int CGameState::pickNextHeroType(const PlayerColor & owner)
+HeroTypeID CGameState::pickNextHeroType(const PlayerColor & owner)
 {
 	const PlayerSettings &ps = scenarioOps->getIthPlayersSettings(owner);
 	if(ps.hero >= 0 && !isUsedHero(HeroTypeID(ps.hero))) //we haven't used selected hero
 	{
-		return ps.hero;
+		return HeroTypeID(ps.hero);
 	}
 
 	return pickUnusedHeroTypeRandomly(owner);
 }
 
-int CGameState::pickUnusedHeroTypeRandomly(const PlayerColor & owner)
+HeroTypeID CGameState::pickUnusedHeroTypeRandomly(const PlayerColor & owner)
 {
 	//list of available heroes for this faction and others
 	std::vector<HeroTypeID> factionHeroes;
@@ -221,23 +206,23 @@ int CGameState::pickUnusedHeroTypeRandomly(const PlayerColor & owner)
 	// select random hero native to "our" faction
 	if(!factionHeroes.empty())
 	{
-		return RandomGeneratorUtil::nextItem(factionHeroes, getRandomGenerator())->getNum();
+		return *RandomGeneratorUtil::nextItem(factionHeroes, getRandomGenerator());
 	}
 
 	logGlobal->warn("Cannot find free hero of appropriate faction for player %s - trying to get first available...", owner.getStr());
 	if(!otherHeroes.empty())
 	{
-		return RandomGeneratorUtil::nextItem(otherHeroes, getRandomGenerator())->getNum();
+		return *RandomGeneratorUtil::nextItem(otherHeroes, getRandomGenerator());
 	}
 
 	logGlobal->error("No free allowed heroes!");
 	auto notAllowedHeroesButStillBetterThanCrash = getUnusedAllowedHeroes(true);
 	if(!notAllowedHeroesButStillBetterThanCrash.empty())
-		return notAllowedHeroesButStillBetterThanCrash.begin()->getNum();
+		return *notAllowedHeroesButStillBetterThanCrash.begin();
 
 	logGlobal->error("No free heroes at all!");
 	assert(0); //current code can't handle this situation
-	return -1; // no available heroes at all
+	return HeroTypeID::NONE; // no available heroes at all
 }
 
 std::pair<Obj,int> CGameState::pickObject (CGObjectInstance *obj)
@@ -526,7 +511,9 @@ void CGameState::init(const IMapService * mapService, StartInfo * si, bool allow
 
 	initGlobalBonuses();
 	initPlayerStates();
-	placeCampaignHeroes();
+	if (campaign)
+		campaign->placeCampaignHeroes();
+	removeHeroPlaceholders();
 	initGrailPosition();
 	initRandomFactionsForPlayers();
 	randomizeMapObjects();
@@ -697,8 +684,8 @@ void CGameState::initNewGame(const IMapService * mapService, bool allowSavingRan
 
 void CGameState::initCampaign()
 {
-	logGlobal->info("Open campaign map file: %d", scenarioOps->campState->currentMap.value());
-	map = scenarioOps->campState->getMap();
+	campaign = std::make_unique<CGameStateCampaign>(this);
+	map = campaign->getCurrentMap().release();
 }
 
 void CGameState::checkMapChecksum()
@@ -834,108 +821,6 @@ void CGameState::initPlayerStates()
 	}
 }
 
-void CGameState::placeCampaignHeroes()
-{
-	if (scenarioOps->campState)
-	{
-		// place bonus hero
-		auto campaignBonus = scenarioOps->campState->getBonusForCurrentMap();
-		bool campaignGiveHero = campaignBonus && campaignBonus->type == CScenarioTravel::STravelBonus::HERO;
-
-		if(campaignGiveHero)
-		{
-			auto playerColor = PlayerColor(campaignBonus->info1);
-			auto it = scenarioOps->playerInfos.find(playerColor);
-			if(it != scenarioOps->playerInfos.end())
-			{
-				auto heroTypeId = campaignBonus->info2;
-				if(heroTypeId == 0xffff) // random bonus hero
-				{
-					heroTypeId = pickUnusedHeroTypeRandomly(playerColor);
-				}
-
-				placeStartingHero(playerColor, HeroTypeID(heroTypeId), map->players[playerColor.getNum()].posOfMainTown);
-			}
-		}
-
-		// replace heroes placeholders
-		auto crossoverHeroes = getCrossoverHeroesFromPreviousScenarios();
-
-		if(!crossoverHeroes.heroesFromAnyPreviousScenarios.empty())
-		{
-			logGlobal->debug("\tGenerate list of hero placeholders");
-			auto campaignHeroReplacements = generateCampaignHeroesToReplace(crossoverHeroes);
-
-			logGlobal->debug("\tPrepare crossover heroes");
-			prepareCrossoverHeroes(campaignHeroReplacements, scenarioOps->campState->getCurrentScenario().travelOptions);
-
-			// remove same heroes on the map which will be added through crossover heroes
-			// INFO: we will remove heroes because later it may be possible that the API doesn't allow having heroes
-			// with the same hero type id
-			std::vector<CGHeroInstance *> removedHeroes;
-
-			for(auto & campaignHeroReplacement : campaignHeroReplacements)
-			{
-				auto * hero = getUsedHero(HeroTypeID(campaignHeroReplacement.hero->subID));
-				if(hero)
-				{
-					removedHeroes.push_back(hero);
-					map->heroesOnMap -= hero;
-					map->objects[hero->id.getNum()] = nullptr;
-					map->removeBlockVisTiles(hero, true);
-				}
-			}
-
-			logGlobal->debug("\tReplace placeholders with heroes");
-			replaceHeroesPlaceholders(campaignHeroReplacements);
-
-			// now add removed heroes again with unused type ID
-			for(auto * hero : removedHeroes)
-			{
-				si32 heroTypeId = 0;
-				if(hero->ID == Obj::HERO)
-				{
-					heroTypeId = pickUnusedHeroTypeRandomly(hero->tempOwner);
-				}
-				else if(hero->ID == Obj::PRISON)
-				{
-					auto unusedHeroTypeIds = getUnusedAllowedHeroes();
-					if(!unusedHeroTypeIds.empty())
-					{
-						heroTypeId = (*RandomGeneratorUtil::nextItem(unusedHeroTypeIds, getRandomGenerator())).getNum();
-					}
-					else
-					{
-						logGlobal->error("No free hero type ID found to replace prison.");
-						assert(0);
-					}
-				}
-				else
-				{
-					assert(0); // should not happen
-				}
-
-				hero->subID = heroTypeId;
-				hero->portrait = hero->subID;
-				map->getEditManager()->insertObject(hero);
-			}
-		}
-	}
-
-	// remove hero placeholders on map
-	for(auto obj : map->objects)
-	{
-		if(obj && obj->ID == Obj::HERO_PLACEHOLDER)
-		{
-			auto heroPlaceholder = dynamic_cast<CGHeroPlaceholder *>(obj.get());
-			map->removeBlockVisTiles(heroPlaceholder, true);
-			map->instanceNames.erase(obj->instanceName);
-			map->objects[heroPlaceholder->id.getNum()] = nullptr;
-			delete heroPlaceholder;
-		}
-	}
-}
-
 void CGameState::placeStartingHero(const PlayerColor & playerColor, const HeroTypeID & heroTypeId, int3 townPos)
 {
 	for(auto town : map->towns)
@@ -952,191 +837,6 @@ void CGameState::placeStartingHero(const PlayerColor & playerColor, const HeroTy
 	map->getEditManager()->insertObject(hero);
 }
 
-CGameState::CrossoverHeroesList CGameState::getCrossoverHeroesFromPreviousScenarios() const
-{
-	CrossoverHeroesList crossoverHeroes;
-
-	auto campaignState = scenarioOps->campState;
-	auto bonus = campaignState->getBonusForCurrentMap();
-	if(bonus && bonus->type == CScenarioTravel::STravelBonus::HEROES_FROM_PREVIOUS_SCENARIO)
-	{
-		std::vector<CGHeroInstance *> heroes;
-		for(auto & node : campaignState->camp->scenarios[bonus->info2].crossoverHeroes)
-		{
-			auto * h = CCampaignState::crossoverDeserialize(node);
-			heroes.push_back(h);
-		}
-		crossoverHeroes.heroesFromAnyPreviousScenarios = crossoverHeroes.heroesFromPreviousScenario = heroes;
-	}
-	else
-	{
-		if(!campaignState->mapsConquered.empty())
-		{
-			std::vector<CGHeroInstance *> heroes = {};
-
-			crossoverHeroes.heroesFromAnyPreviousScenarios = crossoverHeroes.heroesFromPreviousScenario = heroes;
-			crossoverHeroes.heroesFromPreviousScenario = heroes;
-
-			for(auto mapNr : campaignState->mapsConquered)
-			{
-				// create a list of deleted heroes
-				auto & scenario = campaignState->camp->scenarios[mapNr];
-				auto lostCrossoverHeroes = scenario.getLostCrossoverHeroes();
-
-				// remove heroes which didn't reached the end of the scenario, but were available at the start
-				for(auto * hero : lostCrossoverHeroes)
-				{
-					//					auto hero = CCampaignState::crossoverDeserialize(node);
-					vstd::erase_if(crossoverHeroes.heroesFromAnyPreviousScenarios, [hero](CGHeroInstance * h)
-					{
-						return hero->subID == h->subID;
-					});
-				}
-
-				// now add heroes which completed the scenario
-				for(auto node : scenario.crossoverHeroes)
-				{
-					auto * hero = CCampaignState::crossoverDeserialize(node);
-					// add new heroes and replace old heroes with newer ones
-					auto it = range::find_if(crossoverHeroes.heroesFromAnyPreviousScenarios, [hero](CGHeroInstance * h)
-					{
-						return hero->subID == h->subID;
-					});
-
-					if(it != crossoverHeroes.heroesFromAnyPreviousScenarios.end())
-					{
-						// replace old hero with newer one
-						crossoverHeroes.heroesFromAnyPreviousScenarios[it - crossoverHeroes.heroesFromAnyPreviousScenarios.begin()] = hero;
-					}
-					else
-					{
-						// add new hero
-						crossoverHeroes.heroesFromAnyPreviousScenarios.push_back(hero);
-					}
-
-					if(mapNr == campaignState->mapsConquered.back())
-					{
-						crossoverHeroes.heroesFromPreviousScenario.push_back(hero);
-					}
-				}
-			}
-		}
-	}
-
-	return crossoverHeroes;
-}
-
-void CGameState::prepareCrossoverHeroes(std::vector<CGameState::CampaignHeroReplacement> & campaignHeroReplacements, const CScenarioTravel & travelOptions)
-{
-	// create heroes list for convenience iterating
-	std::vector<CGHeroInstance *> crossoverHeroes;
-	crossoverHeroes.reserve(campaignHeroReplacements.size());
-	for(auto & campaignHeroReplacement : campaignHeroReplacements)
-	{
-		crossoverHeroes.push_back(campaignHeroReplacement.hero);
-	}
-
-	// TODO replace magic numbers with named constants
-	// TODO this logic (what should be kept) should be part of CScenarioTravel and be exposed via some clean set of methods
-	if(!travelOptions.whatHeroKeeps.experience)
-	{
-		//trimming experience
-		for(CGHeroInstance * cgh : crossoverHeroes)
-		{
-			cgh->initExp(getRandomGenerator());
-		}
-	}
-
-	if(!travelOptions.whatHeroKeeps.primarySkills)
-	{
-		//trimming prim skills
-		for(CGHeroInstance * cgh : crossoverHeroes)
-		{
-			for(int g=0; g<GameConstants::PRIMARY_SKILLS; ++g)
-			{
-				auto sel = Selector::type()(BonusType::PRIMARY_SKILL)
-					.And(Selector::subtype()(g))
-					.And(Selector::sourceType()(BonusSource::HERO_BASE_SKILL));
-
-				cgh->getBonusLocalFirst(sel)->val = cgh->type->heroClass->primarySkillInitial[g];
-			}
-		}
-	}
-
-	if(!travelOptions.whatHeroKeeps.secondarySkills)
-	{
-		//trimming sec skills
-		for(CGHeroInstance * cgh : crossoverHeroes)
-		{
-			cgh->secSkills = cgh->type->secSkillsInit;
-			cgh->recreateSecondarySkillsBonuses();
-		}
-	}
-
-	if(!travelOptions.whatHeroKeeps.spells)
-	{
-		for(CGHeroInstance * cgh : crossoverHeroes)
-		{
-			cgh->removeSpellbook();
-		}
-	}
-
-	if(!travelOptions.whatHeroKeeps.artifacts)
-	{
-		//trimming artifacts
-		for(CGHeroInstance * hero : crossoverHeroes)
-		{
-			size_t totalArts = GameConstants::BACKPACK_START + hero->artifactsInBackpack.size();
-			for (size_t i = 0; i < totalArts; i++ )
-			{
-				auto artifactPosition = ArtifactPosition((si32)i);
-				if(artifactPosition == ArtifactPosition::SPELLBOOK) continue; // do not handle spellbook this way
-
-				const ArtSlotInfo *info = hero->getSlot(artifactPosition);
-				if(!info)
-					continue;
-
-				// TODO: why would there be nullptr artifacts?
-				const CArtifactInstance *art = info->artifact;
-				if(!art)
-					continue;
-
-				bool takeable = travelOptions.artifactsKeptByHero.count(art->artType->getId());
-
-				ArtifactLocation al(hero, artifactPosition);
-				if(!takeable  &&  !al.getSlot()->locked)  //don't try removing locked artifacts -> it crashes #1719
-					al.removeArtifact();
-			}
-		}
-	}
-
-	//trimming creatures
-	for(CGHeroInstance * cgh : crossoverHeroes)
-	{
-		auto shouldSlotBeErased = [&](const std::pair<SlotID, CStackInstance *> & j) -> bool
-		{
-			CreatureID::ECreatureID crid = j.second->getCreatureID().toEnum();
-			return !travelOptions.monstersKeptByHero.count(crid);
-		};
-
-		auto stacksCopy = cgh->stacks; //copy of the map, so we can iterate iover it and remove stacks
-		for(auto &slotPair : stacksCopy)
-			if(shouldSlotBeErased(slotPair))
-				cgh->eraseStack(slotPair.first);
-	}
-
-	// Removing short-term bonuses
-	for(CGHeroInstance * cgh : crossoverHeroes)
-	{
-		cgh->removeBonusesRecursive(CSelector(Bonus::OneDay)
-			.Or(CSelector(Bonus::OneWeek))
-			.Or(CSelector(Bonus::NTurns))
-			.Or(CSelector(Bonus::NDays))
-			.Or(CSelector(Bonus::OneBattle)));
-	}
-
-}
-
 void CGameState::placeStartingHeroes()
 {
 	logGlobal->debug("\tGiving starting hero");
@@ -1148,14 +848,8 @@ void CGameState::placeStartingHeroes()
 		if(playerInfo.generateHeroAtMainTown && playerInfo.hasMainTown)
 		{
 			// Do not place a starting hero if the hero was already placed due to a campaign bonus
-			if(scenarioOps->campState)
-			{
-				if(auto campaignBonus = scenarioOps->campState->getBonusForCurrentMap())
-				{
-					if(campaignBonus->type == CScenarioTravel::STravelBonus::HERO && playerColor == PlayerColor(campaignBonus->info1))
-						continue;
-				}
-			}
+			if (campaign && campaign->playerHasStartingHero(playerColor))
+				continue;
 
 			int heroTypeId = pickNextHeroType(playerColor);
 			if(playerSettingPair.second.hero == -1)
@@ -1166,6 +860,22 @@ void CGameState::placeStartingHeroes()
 	}
 }
 
+void CGameState::removeHeroPlaceholders()
+{
+	// remove any hero placeholders that remain on map after (potential) campaign heroes placement
+	for(auto obj : map->objects)
+	{
+		if(obj && obj->ID == Obj::HERO_PLACEHOLDER)
+		{
+			auto heroPlaceholder = dynamic_cast<CGHeroPlaceholder *>(obj.get());
+			map->removeBlockVisTiles(heroPlaceholder, true);
+			map->instanceNames.erase(obj->instanceName);
+			map->objects[heroPlaceholder->id.getNum()] = nullptr;
+			delete heroPlaceholder;
+		}
+	}
+}
+
 void CGameState::initStartingResources()
 {
 	logGlobal->debug("\tSetting up resources");
@@ -1186,55 +896,8 @@ void CGameState::initStartingResources()
 			p.resources = startresAI;
 	}
 
-	auto getHumanPlayerInfo = [&]() -> std::vector<const PlayerSettings *>
-	{
-		std::vector<const PlayerSettings *> ret;
-		for(const auto & playerInfo : scenarioOps->playerInfos)
-		{
-			if(playerInfo.second.isControlledByHuman())
-				ret.push_back(&playerInfo.second);
-		}
-
-		return ret;
-	};
-
-	//give start resource bonus in case of campaign
-	if (scenarioOps->mode == StartInfo::CAMPAIGN)
-	{
-		auto chosenBonus = scenarioOps->campState->getBonusForCurrentMap();
-		if(chosenBonus && chosenBonus->type == CScenarioTravel::STravelBonus::RESOURCE)
-		{
-			std::vector<const PlayerSettings *> people = getHumanPlayerInfo(); //players we will give resource bonus
-			for(const PlayerSettings *ps : people)
-			{
-				std::vector<int> res; //resources we will give
-				switch (chosenBonus->info1)
-				{
-				case 0: case 1: case 2: case 3: case 4: case 5: case 6:
-					res.push_back(chosenBonus->info1);
-					break;
-				case 0xFD: //wood+ore
-					res.push_back(GameResID(EGameResID::WOOD)); 
-					res.push_back(GameResID(EGameResID::ORE));
-					break;
-				case 0xFE:  //rare
-					res.push_back(GameResID(EGameResID::MERCURY));
-					res.push_back(GameResID(EGameResID::SULFUR));
-					res.push_back(GameResID(EGameResID::CRYSTAL));
-					res.push_back(GameResID(EGameResID::GEMS));
-					break;
-				default:
-					assert(0);
-					break;
-				}
-				//increasing resource quantity
-				for (auto & re : res)
-				{
-					players[ps->color].resources[re] += chosenBonus->info2;
-				}
-			}
-		}
-	}
+	if (campaign)
+		campaign->initStartingResources();
 }
 
 void CGameState::initHeroes()
@@ -1312,115 +975,8 @@ void CGameState::initHeroes()
 		hpool.pavailable[elem.heroId] = elem.players;
 	}
 
-	if (scenarioOps->mode == StartInfo::CAMPAIGN) //give campaign bonuses for specific / best hero
-	{
-		auto chosenBonus = scenarioOps->campState->getBonusForCurrentMap();
-		if (chosenBonus && chosenBonus->isBonusForHero() && chosenBonus->info1 != 0xFFFE) //exclude generated heroes
-		{
-			//find human player
-			PlayerColor humanPlayer=PlayerColor::NEUTRAL;
-			for (auto & elem : players)
-			{
-				if(elem.second.human)
-				{
-					humanPlayer = elem.first;
-					break;
-				}
-			}
-			assert(humanPlayer != PlayerColor::NEUTRAL);
-
-			std::vector<ConstTransitivePtr<CGHeroInstance> > & heroes = players[humanPlayer].heroes;
-
-			if (chosenBonus->info1 == 0xFFFD) //most powerful
-			{
-				int maxB = -1;
-				for (int b=0; b<heroes.size(); ++b)
-				{
-					if (maxB == -1 || heroes[b]->getTotalStrength() > heroes[maxB]->getTotalStrength())
-					{
-						maxB = b;
-					}
-				}
-				if(maxB < 0)
-					logGlobal->warn("Cannot give bonus to hero cause there are no heroes!");
-				else
-					giveCampaignBonusToHero(heroes[maxB]);
-			}
-			else //specific hero
-			{
-				for (auto & heroe : heroes)
-				{
-					if (heroe->subID == chosenBonus->info1)
-					{
-						giveCampaignBonusToHero(heroe);
-						break;
-					}
-				}
-			}
-		}
-	}
-}
-
-void CGameState::giveCampaignBonusToHero(CGHeroInstance * hero)
-{
-	const std::optional<CScenarioTravel::STravelBonus> & curBonus = scenarioOps->campState->getBonusForCurrentMap();
-	if(!curBonus)
-		return;
-
-	if(curBonus->isBonusForHero())
-	{
-		//apply bonus
-		switch (curBonus->type)
-		{
-		case CScenarioTravel::STravelBonus::SPELL:
-			hero->addSpellToSpellbook(SpellID(curBonus->info2));
-			break;
-		case CScenarioTravel::STravelBonus::MONSTER:
-			{
-				for(int i=0; i<GameConstants::ARMY_SIZE; i++)
-				{
-					if(hero->slotEmpty(SlotID(i)))
-					{
-						hero->addToSlot(SlotID(i), CreatureID(curBonus->info2), curBonus->info3);
-						break;
-					}
-				}
-			}
-			break;
-		case CScenarioTravel::STravelBonus::ARTIFACT:
-			if(!gs->giveHeroArtifact(hero, static_cast<ArtifactID>(curBonus->info2)))
-				logGlobal->error("Cannot give starting artifact - no free slots!");
-			break;
-		case CScenarioTravel::STravelBonus::SPELL_SCROLL:
-			{
-				CArtifactInstance * scroll = ArtifactUtils::createScroll(SpellID(curBonus->info2));
-				const auto slot = ArtifactUtils::getArtAnyPosition(hero, scroll->getTypeId());
-				if(ArtifactUtils::isSlotEquipment(slot) || ArtifactUtils::isSlotBackpack(slot))
-					scroll->putAt(ArtifactLocation(hero, slot));
-				else
-					logGlobal->error("Cannot give starting scroll - no free slots!");
-			}
-			break;
-		case CScenarioTravel::STravelBonus::PRIMARY_SKILL:
-			{
-				const ui8* ptr = reinterpret_cast<const ui8*>(&curBonus->info2);
-				for (int g=0; g<GameConstants::PRIMARY_SKILLS; ++g)
-				{
-					int val = ptr[g];
-					if (val == 0)
-					{
-						continue;
-					}
-					auto bb = std::make_shared<Bonus>(BonusDuration::PERMANENT, BonusType::PRIMARY_SKILL, BonusSource::CAMPAIGN_BONUS, val, *scenarioOps->campState->currentMap, g);
-					hero->addNewBonus(bb);
-				}
-			}
-			break;
-		case CScenarioTravel::STravelBonus::SECONDARY_SKILL:
-			hero->setSecSkillLevel(SecondarySkill(curBonus->info2), curBonus->info3, true);
-			break;
-		}
-	}
+	if (campaign)
+		campaign->initHeroes();
 }
 
 void CGameState::initFogOfWar()
@@ -1503,36 +1059,8 @@ void CGameState::initTowns()
 {
 	logGlobal->debug("\tTowns");
 
-	//campaign bonuses for towns
-	if (scenarioOps->mode == StartInfo::CAMPAIGN)
-	{
-		auto chosenBonus = scenarioOps->campState->getBonusForCurrentMap();
-
-		if (chosenBonus && chosenBonus->type == CScenarioTravel::STravelBonus::BUILDING)
-		{
-			for (int g=0; g<map->towns.size(); ++g)
-			{
-				PlayerState * owner = getPlayerState(map->towns[g]->getOwner());
-				if (owner)
-				{
-					PlayerInfo & pi = map->players[owner->color.getNum()];
-
-					if (owner->human && //human-owned
-						map->towns[g]->pos == pi.posOfMainTown)
-					{
-						BuildingID buildingId;
-						if(scenarioOps->campState->camp->header.version == CampaignVersion::VCMI)
-							buildingId = BuildingID(chosenBonus->info1);
-						else
-							buildingId = CBuildingHandler::campToERMU(chosenBonus->info1, map->towns[g]->subID, map->towns[g]->builtBuildings);
-
-						map->towns[g]->builtBuildings.insert(buildingId);
-						break;
-					}
-				}
-			}
-		}
-	}
+	if (campaign)
+		campaign->initTowns();
 
 	CGTownInstance::universitySkills.clear();
 	for ( int i=0; i<4; i++)
@@ -2646,114 +2174,6 @@ std::set<HeroTypeID> CGameState::getUnusedAllowedHeroes(bool alsoIncludeNotAllow
 	return ret;
 }
 
-std::vector<CGameState::CampaignHeroReplacement> CGameState::generateCampaignHeroesToReplace(CrossoverHeroesList & crossoverHeroes)
-{
-	std::vector<CampaignHeroReplacement> campaignHeroReplacements;
-
-	//selecting heroes by type
-	for(auto obj : map->objects)
-	{
-		if(obj && obj->ID == Obj::HERO_PLACEHOLDER)
-		{
-			auto * heroPlaceholder = dynamic_cast<CGHeroPlaceholder *>(obj.get());
-			if(heroPlaceholder->subID != 0xFF) //select by type
-			{
-				auto it = range::find_if(crossoverHeroes.heroesFromAnyPreviousScenarios, [heroPlaceholder](CGHeroInstance * hero)
-				{
-					return hero->subID == heroPlaceholder->subID;
-				});
-
-				if(it != crossoverHeroes.heroesFromAnyPreviousScenarios.end())
-				{
-					auto * hero = *it;
-					crossoverHeroes.removeHeroFromBothLists(hero);
-					campaignHeroReplacements.emplace_back(CMemorySerializer::deepCopy(*hero).release(), heroPlaceholder->id);
-				}
-			}
-		}
-	}
-
-	//selecting heroes by power
-	range::sort(crossoverHeroes.heroesFromPreviousScenario, [](const CGHeroInstance * a, const CGHeroInstance * b)
-	{
-		return a->getHeroStrength() > b->getHeroStrength();
-	}); //sort, descending strength
-
-	// sort hero placeholders descending power
-	std::vector<CGHeroPlaceholder *> heroPlaceholders;
-	for(auto obj : map->objects)
-	{
-		if(obj && obj->ID == Obj::HERO_PLACEHOLDER)
-		{
-			auto * heroPlaceholder = dynamic_cast<CGHeroPlaceholder *>(obj.get());
-			if(heroPlaceholder->subID == 0xFF) //select by power
-			{
-				heroPlaceholders.push_back(heroPlaceholder);
-			}
-		}
-	}
-	range::sort(heroPlaceholders, [](const CGHeroPlaceholder * a, const CGHeroPlaceholder * b)
-	{
-		return a->power > b->power;
-	});
-
-	for(int i = 0; i < heroPlaceholders.size(); ++i)
-	{
-		auto * heroPlaceholder = heroPlaceholders[i];
-		if(crossoverHeroes.heroesFromPreviousScenario.size() > i)
-		{
-			auto * hero = crossoverHeroes.heroesFromPreviousScenario[i];
-			campaignHeroReplacements.emplace_back(CMemorySerializer::deepCopy(*hero).release(), heroPlaceholder->id);
-		}
-	}
-
-	return campaignHeroReplacements;
-}
-
-void CGameState::replaceHeroesPlaceholders(const std::vector<CGameState::CampaignHeroReplacement> & campaignHeroReplacements)
-{
-	for(const auto & campaignHeroReplacement : campaignHeroReplacements)
-	{
-		auto * heroPlaceholder = dynamic_cast<CGHeroPlaceholder *>(getObjInstance(campaignHeroReplacement.heroPlaceholderId));
-
-		CGHeroInstance *heroToPlace = campaignHeroReplacement.hero;
-		heroToPlace->id = campaignHeroReplacement.heroPlaceholderId;
-		heroToPlace->tempOwner = heroPlaceholder->tempOwner;
-		heroToPlace->pos = heroPlaceholder->pos;
-		heroToPlace->type = VLC->heroh->objects[heroToPlace->subID];
-		heroToPlace->appearance = VLC->objtypeh->getHandlerFor(Obj::HERO,
-															   heroToPlace->type->heroClass->getIndex())->getTemplates().front();
-
-		for(auto &&i : heroToPlace->stacks)
-			i.second->type = VLC->creh->objects[i.second->getCreatureID()];
-
-		auto fixArtifact = [&](CArtifactInstance * art)
-		{
-			art->artType = VLC->arth->objects[art->artType->getId()];
-			gs->map->artInstances.emplace_back(art);
-			art->id = ArtifactInstanceID((si32)gs->map->artInstances.size() - 1);
-		};
-
-		for(auto &&i : heroToPlace->artifactsWorn)
-			fixArtifact(i.second.artifact);
-		for(auto &&i : heroToPlace->artifactsInBackpack)
-			fixArtifact(i.artifact);
-
-		map->removeBlockVisTiles(heroPlaceholder, true);
-		map->objects[heroPlaceholder->id.getNum()] = nullptr;
-		map->instanceNames.erase(heroPlaceholder->instanceName);
-
-		map->heroesOnMap.emplace_back(heroToPlace);
-		map->objects[heroToPlace->id.getNum()] = heroToPlace;
-		map->addBlockVisTiles(heroToPlace);
-		map->instanceNames[heroToPlace->instanceName] = heroToPlace;
-
-		delete heroPlaceholder;
-
-		scenarioOps->campState->getCurrentScenario().placedCrossoverHeroes.push_back(CCampaignState::crossoverSerialize(heroToPlace));
-	}
-}
-
 bool CGameState::isUsedHero(const HeroTypeID & hid) const
 {
 	return getUsedHero(hid);
@@ -2801,170 +2221,6 @@ bool RumorState::update(int id, int extra)
 	return true;
 }
 
-InfoAboutArmy::InfoAboutArmy():
-	owner(PlayerColor::NEUTRAL)
-{}
-
-InfoAboutArmy::InfoAboutArmy(const CArmedInstance *Army, bool detailed)
-{
-	initFromArmy(Army, detailed);
-}
-
-void InfoAboutArmy::initFromArmy(const CArmedInstance *Army, bool detailed)
-{
-	army = ArmyDescriptor(Army, detailed);
-	owner = Army->tempOwner;
-	name = Army->getObjectName();
-}
-
-void InfoAboutHero::assign(const InfoAboutHero & iah)
-{
-	vstd::clear_pointer(details);
-	InfoAboutArmy::operator = (iah);
-
-	details = (iah.details ? new Details(*iah.details) : nullptr);
-	hclass = iah.hclass;
-	portrait = iah.portrait;
-}
-
-InfoAboutHero::InfoAboutHero(): portrait(-1) {}
-
-InfoAboutHero::InfoAboutHero(const InfoAboutHero & iah): InfoAboutArmy(iah)
-{
-	assign(iah);
-}
-
-InfoAboutHero::InfoAboutHero(const CGHeroInstance * h, InfoAboutHero::EInfoLevel infoLevel):
-	portrait(-1)
-{
-	initFromHero(h, infoLevel);
-}
-
-InfoAboutHero::~InfoAboutHero()
-{
-	vstd::clear_pointer(details);
-}
-
-InfoAboutHero & InfoAboutHero::operator=(const InfoAboutHero & iah)
-{
-	assign(iah);
-	return *this;
-}
-
-void InfoAboutHero::initFromHero(const CGHeroInstance *h, InfoAboutHero::EInfoLevel infoLevel)
-{
-	vstd::clear_pointer(details);
-	if(!h)
-		return;
-
-	bool detailed = ( (infoLevel == EInfoLevel::DETAILED) || (infoLevel == EInfoLevel::INBATTLE) );
-
-	initFromArmy(h, detailed);
-
-	hclass = h->type->heroClass;
-	name = h->getNameTranslated();
-	portrait = h->portrait;
-
-	if(detailed)
-	{
-		//include details about hero
-		details = new Details();
-		details->luck = h->luckVal();
-		details->morale = h->moraleVal();
-		details->mana = h->mana;
-		details->primskills.resize(GameConstants::PRIMARY_SKILLS);
-
-		for (int i = 0; i < GameConstants::PRIMARY_SKILLS ; i++)
-		{
-			details->primskills[i] = h->getPrimSkillLevel(static_cast<PrimarySkill::PrimarySkill>(i));
-		}
-		if (infoLevel == EInfoLevel::INBATTLE)
-			details->manaLimit = h->manaLimit();
-		else
-			details->manaLimit = -1; //we do not want to leak max mana info outside battle so set to meaningless value
-	}
-}
-
-InfoAboutTown::InfoAboutTown():
-	details(nullptr),
-	tType(nullptr),
-	built(0),
-	fortLevel(0)
-{
-
-}
-
-InfoAboutTown::InfoAboutTown(const CGTownInstance *t, bool detailed):
-	details(nullptr),
-	tType(nullptr),
-	built(0),
-	fortLevel(0)
-{
-	initFromTown(t, detailed);
-}
-
-InfoAboutTown::~InfoAboutTown()
-{
-	vstd::clear_pointer(details);
-}
-
-void InfoAboutTown::initFromTown(const CGTownInstance *t, bool detailed)
-{
-	initFromArmy(t, detailed);
-	army = ArmyDescriptor(t->getUpperArmy(), detailed);
-	built = t->builded;
-	fortLevel = t->fortLevel();
-	name = t->getNameTranslated();
-	tType = t->getTown();
-
-	vstd::clear_pointer(details);
-
-	if(detailed)
-	{
-		//include details about hero
-		details = new Details();
-		TResources income = t->dailyIncome();
-		details->goldIncome = income[EGameResID::GOLD];
-		details->customRes = t->hasBuilt(BuildingID::RESOURCE_SILO);
-		details->hallLevel = t->hallLevel();
-		details->garrisonedHero = t->garrisonHero;
-	}
-}
-
-ArmyDescriptor::ArmyDescriptor(const CArmedInstance *army, bool detailed)
-	: isDetailed(detailed)
-{
-	for(const auto & elem : army->Slots())
-	{
-		if(detailed)
-			(*this)[elem.first] = *elem.second;
-		else
-			(*this)[elem.first] = CStackBasicDescriptor(elem.second->type, (int)elem.second->getQuantityID());
-	}
-}
-
-ArmyDescriptor::ArmyDescriptor()
-	: isDetailed(false)
-{
-
-}
-
-int ArmyDescriptor::getStrength() const
-{
-	ui64 ret = 0;
-	if(isDetailed)
-	{
-		for(const auto & elem : *this)
-			ret += elem.second.type->getAIValue() * elem.second.count;
-	}
-	else
-	{
-		for(const auto & elem : *this)
-			ret += elem.second.type->getAIValue() * CCreature::estimateCreatureCount(elem.second.count);
-	}
-	return static_cast<int>(ret);
-}
-
 TeamState::TeamState()
 {
 	setNodeType(TEAM);

+ 18 - 95
lib/CGameState.h → lib/gameState/CGameState.h

@@ -9,17 +9,8 @@
  */
 #pragma once
 
-#include "CCreatureHandler.h"
-#include "VCMI_Lib.h"
-
-#include "bonuses/Bonus.h"
-#include "CCreatureSet.h"
-#include "ConstTransitivePtr.h"
+#include "bonuses/CBonusSystemNode.h"
 #include "IGameCallback.h"
-#include "ResourceSet.h"
-#include "int3.h"
-#include "CRandomGenerator.h"
-#include "CGameStateFwd.h"
 
 namespace boost
 {
@@ -28,70 +19,21 @@ class shared_mutex;
 
 VCMI_LIB_NAMESPACE_BEGIN
 
-class CTown;
-class IGameCallback;
-class CCreatureSet;
-class CQuest;
-class CGHeroInstance;
-class CGTownInstance;
-class CArmedInstance;
-class CGDwelling;
-class CObjectScript;
-class CGObjectInstance;
-class CCreature;
+class EVictoryLossCheckResult;
+class Services;
+class IMapService;
 class CMap;
-struct StartInfo;
-struct SetObjectProperty;
-class MetaString;
 struct CPack;
-class CSpell;
-struct TerrainTile;
 class CHeroClass;
-class CCampaign;
-class CCampaignState;
-class IModableArt;
-class CGGarrison;
-struct QuestInfo;
-class CQuest;
-class CCampaignScenario;
 struct EventCondition;
-class CScenarioTravel;
-class IMapService;
-
+struct CampaignTravel;
+class CStackInstance;
+class CGameStateCampaign;
+struct SThievesGuildInfo;
 
 template<typename T> class CApplier;
 class CBaseForGSApply;
 
-struct DLL_LINKAGE SThievesGuildInfo
-{
-	std::vector<PlayerColor> playerColors; //colors of players that are in-game
-
-	std::vector< std::vector< PlayerColor > > numOfTowns, numOfHeroes, gold, woodOre, mercSulfCrystGems, obelisks, artifacts, army, income; // [place] -> [colours of players]
-
-	std::map<PlayerColor, InfoAboutHero> colorToBestHero; //maps player's color to his best heros'
-
-    std::map<PlayerColor, EAiTactic::EAiTactic> personality; // color to personality // ai tactic
-	std::map<PlayerColor, si32> bestCreature; // color to ID // id or -1 if not known
-
-//	template <typename Handler> void serialize(Handler &h, const int version)
-//	{
-//		h & playerColors;
-//		h & numOfTowns;
-//		h & numOfHeroes;
-//		h & gold;
-//		h & woodOre;
-//		h & mercSulfCrystGems;
-//		h & obelisks;
-//		h & artifacts;
-//		h & army;
-//		h & income;
-//		h & colorToBestHero;
-//		h & personality;
-//		h & bestCreature;
-//	}
-
-};
-
 struct DLL_LINKAGE RumorState
 {
 	enum ERumorType : ui8
@@ -125,7 +67,7 @@ struct UpgradeInfo
 {
 	CreatureID oldID; //creature to be upgraded
 	std::vector<CreatureID> newID; //possible upgrades
-	std::vector<TResources> cost; // cost[upgrade_serial] -> set of pairs<resource_ID,resource_amount>; cost is for single unit (not entire stack)
+	std::vector<ResourceSet> cost; // cost[upgrade_serial] -> set of pairs<resource_ID,resource_amount>; cost is for single unit (not entire stack)
 	UpgradeInfo(){oldID = CreatureID::NONE;};
 };
 
@@ -135,6 +77,7 @@ DLL_LINKAGE std::ostream & operator<<(std::ostream & os, const EVictoryLossCheck
 
 class DLL_LINKAGE CGameState : public CNonConstInfoCallback
 {
+	friend class CGameStateCampaign;
 public:
 	struct DLL_LINKAGE HeroesPool
 	{
@@ -230,29 +173,15 @@ public:
 		h & globalEffects;
 		h & rand;
 		h & rumor;
+		h & campaign;
 
 		BONUS_TREE_DESERIALIZATION_FIX
 	}
 
 private:
-	struct CrossoverHeroesList
-	{
-		std::vector<CGHeroInstance *> heroesFromPreviousScenario, heroesFromAnyPreviousScenarios;
-		void addHeroToBothLists(CGHeroInstance * hero);
-		void removeHeroFromBothLists(CGHeroInstance * hero);
-	};
-
-	struct CampaignHeroReplacement
-	{
-		CampaignHeroReplacement(CGHeroInstance * hero, const ObjectInstanceID & heroPlaceholderId);
-		CGHeroInstance * hero;
-		ObjectInstanceID heroPlaceholderId;
-	};
-
 	// ----- initialization -----
 	void preInitAuto();
 	void initNewGame(const IMapService * mapService, bool allowSavingRandomMap);
-	void initCampaign();
 	void checkMapChecksum();
 	void initGlobalBonuses();
 	void initGrailPosition();
@@ -260,27 +189,18 @@ private:
 	void randomizeMapObjects();
 	void randomizeObject(CGObjectInstance *cur);
 	void initPlayerStates();
-	void placeCampaignHeroes();
-	CrossoverHeroesList getCrossoverHeroesFromPreviousScenarios() const;
-
-	/// returns heroes and placeholders in where heroes will be put
-	std::vector<CampaignHeroReplacement> generateCampaignHeroesToReplace(CrossoverHeroesList & crossoverHeroes);
-
-	/// gets prepared and copied hero instances with crossover heroes from prev. scenario and travel options from current scenario
-	void prepareCrossoverHeroes(std::vector<CampaignHeroReplacement> & campaignHeroReplacements, const CScenarioTravel & travelOptions);
-
-	void replaceHeroesPlaceholders(const std::vector<CampaignHeroReplacement> & campaignHeroReplacements);
 	void placeStartingHeroes();
 	void placeStartingHero(const PlayerColor & playerColor, const HeroTypeID & heroTypeId, int3 townPos);
+	void removeHeroPlaceholders();
 	void initStartingResources();
 	void initHeroes();
 	void placeHeroesInTowns();
-	void giveCampaignBonusToHero(CGHeroInstance * hero);
 	void initFogOfWar();
 	void initStartingBonus();
 	void initTowns();
 	void initMapObjects();
 	void initVisitingAndGarrisonedHeroes();
+	void initCampaign();
 
 	// ----- bonus system handling -----
 
@@ -295,8 +215,8 @@ private:
 	bool isUsedHero(const HeroTypeID & hid) const; //looks in heroes and prisons
 	std::set<HeroTypeID> getUnusedAllowedHeroes(bool alsoIncludeNotAllowed = false) const;
 	std::pair<Obj,int> pickObject(CGObjectInstance *obj); //chooses type of object to be randomized, returns <type, subtype>
-	int pickUnusedHeroTypeRandomly(const PlayerColor & owner); // picks a unused hero type randomly
-	int pickNextHeroType(const PlayerColor & owner); // picks next free hero type of the H3 hero init sequence -> chosen starting hero, then unused hero type randomly
+	HeroTypeID pickUnusedHeroTypeRandomly(const PlayerColor & owner); // picks a unused hero type randomly
+	HeroTypeID pickNextHeroType(const PlayerColor & owner); // picks next free hero type of the H3 hero init sequence -> chosen starting hero, then unused hero type randomly
 	UpgradeInfo fillUpgradeInfo(const CStackInstance &stack) const;
 
 	// ---- data -----
@@ -304,6 +224,9 @@ private:
 	CRandomGenerator rand;
 	Services * services;
 
+	/// Ponter to campaign state manager. Nullptr for single scenarios
+	std::unique_ptr<CGameStateCampaign> campaign;
+
 	friend class IGameCallback;
 	friend class CMapHandler;
 	friend class CGameHandler;

+ 592 - 0
lib/gameState/CGameStateCampaign.cpp

@@ -0,0 +1,592 @@
+/*
+ * CGameStateCampaign.cpp, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+#include "StdInc.h"
+#include "CGameStateCampaign.h"
+
+#include "CGameState.h"
+#include "QuestInfo.h"
+
+#include "../campaign/CampaignState.h"
+#include "../mapping/CMapEditManager.h"
+#include "../mapObjects/CGHeroInstance.h"
+#include "../registerTypes/RegisterTypes.h"
+#include "../mapObjectConstructors/AObjectTypeHandler.h"
+#include "../mapObjectConstructors/CObjectClassesHandler.h"
+#include "../StartInfo.h"
+#include "../CBuildingHandler.h"
+#include "../CHeroHandler.h"
+#include "../mapping/CMap.h"
+#include "../ArtifactUtils.h"
+#include "../CPlayerState.h"
+#include "../serializer/CMemorySerializer.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+CampaignHeroReplacement::CampaignHeroReplacement(CGHeroInstance * hero, const ObjectInstanceID & heroPlaceholderId):
+	hero(hero),
+	heroPlaceholderId(heroPlaceholderId)
+{
+}
+
+CGameStateCampaign::CGameStateCampaign(CGameState * owner):
+	gameState(owner)
+{
+	assert(gameState->scenarioOps->mode == StartInfo::CAMPAIGN);
+	assert(gameState->scenarioOps->campState != nullptr);
+}
+
+std::optional<CampaignBonus> CGameStateCampaign::currentBonus() const
+{
+	auto campaignState = gameState->scenarioOps->campState;
+	return campaignState->getBonus(*campaignState->currentScenario());
+}
+
+std::optional<CampaignScenarioID> CGameStateCampaign::getHeroesSourceScenario() const
+{
+	auto campaignState = gameState->scenarioOps->campState;
+	auto bonus = currentBonus();
+
+	if(bonus && bonus->type == CampaignBonusType::HEROES_FROM_PREVIOUS_SCENARIO)
+		return static_cast<CampaignScenarioID>(bonus->info2);;
+
+	return campaignState->lastScenario();
+}
+
+void CGameStateCampaign::trimCrossoverHeroesParameters(std::vector<CampaignHeroReplacement> & campaignHeroReplacements, const CampaignTravel & travelOptions)
+{
+	// create heroes list for convenience iterating
+	std::vector<CGHeroInstance *> crossoverHeroes;
+	crossoverHeroes.reserve(campaignHeroReplacements.size());
+	for(auto & campaignHeroReplacement : campaignHeroReplacements)
+	{
+		crossoverHeroes.push_back(campaignHeroReplacement.hero);
+	}
+
+	// TODO this logic (what should be kept) should be part of CScenarioTravel and be exposed via some clean set of methods
+	if(!travelOptions.whatHeroKeeps.experience)
+	{
+		//trimming experience
+		for(CGHeroInstance * cgh : crossoverHeroes)
+		{
+			cgh->initExp(gameState->getRandomGenerator());
+		}
+	}
+
+	if(!travelOptions.whatHeroKeeps.primarySkills)
+	{
+		//trimming prim skills
+		for(CGHeroInstance * cgh : crossoverHeroes)
+		{
+			for(int g=0; g<GameConstants::PRIMARY_SKILLS; ++g)
+			{
+				auto sel = Selector::type()(BonusType::PRIMARY_SKILL)
+					.And(Selector::subtype()(g))
+					.And(Selector::sourceType()(BonusSource::HERO_BASE_SKILL));
+
+				cgh->getBonusLocalFirst(sel)->val = cgh->type->heroClass->primarySkillInitial[g];
+			}
+		}
+	}
+
+	if(!travelOptions.whatHeroKeeps.secondarySkills)
+	{
+		//trimming sec skills
+		for(CGHeroInstance * cgh : crossoverHeroes)
+		{
+			cgh->secSkills = cgh->type->secSkillsInit;
+			cgh->recreateSecondarySkillsBonuses();
+		}
+	}
+
+	if(!travelOptions.whatHeroKeeps.spells)
+	{
+		for(CGHeroInstance * cgh : crossoverHeroes)
+		{
+			cgh->removeSpellbook();
+		}
+	}
+
+	if(!travelOptions.whatHeroKeeps.artifacts)
+	{
+		//trimming artifacts
+		for(CGHeroInstance * hero : crossoverHeroes)
+		{
+			size_t totalArts = GameConstants::BACKPACK_START + hero->artifactsInBackpack.size();
+			for (size_t i = 0; i < totalArts; i++ )
+			{
+				auto artifactPosition = ArtifactPosition((si32)i);
+				if(artifactPosition == ArtifactPosition::SPELLBOOK)
+					continue; // do not handle spellbook this way
+
+				const ArtSlotInfo *info = hero->getSlot(artifactPosition);
+				if(!info)
+					continue;
+
+				// TODO: why would there be nullptr artifacts?
+				const CArtifactInstance *art = info->artifact;
+				if(!art)
+					continue;
+
+				bool takeable = travelOptions.artifactsKeptByHero.count(art->artType->getId());
+
+				ArtifactLocation al(hero, artifactPosition);
+				if(!takeable  &&  !al.getSlot()->locked)  //don't try removing locked artifacts -> it crashes #1719
+					al.removeArtifact();
+			}
+		}
+	}
+
+	//trimming creatures
+	for(CGHeroInstance * cgh : crossoverHeroes)
+	{
+		auto shouldSlotBeErased = [&](const std::pair<SlotID, CStackInstance *> & j) -> bool
+		{
+			CreatureID::ECreatureID crid = j.second->getCreatureID().toEnum();
+			return !travelOptions.monstersKeptByHero.count(crid);
+		};
+
+		auto stacksCopy = cgh->stacks; //copy of the map, so we can iterate iover it and remove stacks
+		for(auto &slotPair : stacksCopy)
+			if(shouldSlotBeErased(slotPair))
+				cgh->eraseStack(slotPair.first);
+	}
+
+	// Removing short-term bonuses
+	for(CGHeroInstance * cgh : crossoverHeroes)
+	{
+		cgh->removeBonusesRecursive(CSelector(Bonus::OneDay)
+			.Or(CSelector(Bonus::OneWeek))
+			.Or(CSelector(Bonus::NTurns))
+			.Or(CSelector(Bonus::NDays))
+			.Or(CSelector(Bonus::OneBattle)));
+	}
+}
+
+void CGameStateCampaign::placeCampaignHeroes()
+{
+	// place bonus hero
+	auto campaignState = gameState->scenarioOps->campState;
+	auto campaignBonus = campaignState->getBonus(*campaignState->currentScenario());
+	bool campaignGiveHero = campaignBonus && campaignBonus->type == CampaignBonusType::HERO;
+
+	if(campaignGiveHero)
+	{
+		auto playerColor = PlayerColor(campaignBonus->info1);
+		auto it = gameState->scenarioOps->playerInfos.find(playerColor);
+		if(it != gameState->scenarioOps->playerInfos.end())
+		{
+			auto heroTypeId = campaignBonus->info2;
+			if(heroTypeId == 0xffff) // random bonus hero
+			{
+				heroTypeId = gameState->pickUnusedHeroTypeRandomly(playerColor);
+			}
+
+			gameState->placeStartingHero(playerColor, HeroTypeID(heroTypeId), gameState->map->players[playerColor.getNum()].posOfMainTown);
+		}
+	}
+
+	logGlobal->debug("\tGenerate list of hero placeholders");
+	auto campaignHeroReplacements = generateCampaignHeroesToReplace();
+
+	logGlobal->debug("\tPrepare crossover heroes");
+	trimCrossoverHeroesParameters(campaignHeroReplacements, campaignState->scenario(*campaignState->currentScenario()).travelOptions);
+
+	// remove same heroes on the map which will be added through crossover heroes
+	// INFO: we will remove heroes because later it may be possible that the API doesn't allow having heroes
+	// with the same hero type id
+	std::vector<CGHeroInstance *> removedHeroes;
+
+	std::set<HeroTypeID> heroesToRemove = campaignState->getReservedHeroes();
+
+	for(auto & campaignHeroReplacement : campaignHeroReplacements)
+		heroesToRemove.insert(HeroTypeID(campaignHeroReplacement.hero->subID));
+
+	for(auto & heroID : heroesToRemove)
+	{
+		auto * hero = gameState->getUsedHero(heroID);
+		if(hero)
+		{
+			removedHeroes.push_back(hero);
+			gameState->map->heroesOnMap -= hero;
+			gameState->map->objects[hero->id.getNum()] = nullptr;
+			gameState->map->removeBlockVisTiles(hero, true);
+		}
+	}
+
+	logGlobal->debug("\tReplace placeholders with heroes");
+	replaceHeroesPlaceholders(campaignHeroReplacements);
+
+	// now add removed heroes again with unused type ID
+	for(auto * hero : removedHeroes)
+	{
+		si32 heroTypeId = 0;
+		if(hero->ID == Obj::HERO)
+		{
+			heroTypeId = gameState->pickUnusedHeroTypeRandomly(hero->tempOwner);
+		}
+		else if(hero->ID == Obj::PRISON)
+		{
+			auto unusedHeroTypeIds = gameState->getUnusedAllowedHeroes();
+			if(!unusedHeroTypeIds.empty())
+			{
+				heroTypeId = (*RandomGeneratorUtil::nextItem(unusedHeroTypeIds, gameState->getRandomGenerator())).getNum();
+			}
+			else
+			{
+				logGlobal->error("No free hero type ID found to replace prison.");
+				assert(0);
+			}
+		}
+		else
+		{
+			assert(0); // should not happen
+		}
+
+		hero->subID = heroTypeId;
+		hero->portrait = hero->subID;
+		gameState->map->getEditManager()->insertObject(hero);
+	}
+}
+
+void CGameStateCampaign::giveCampaignBonusToHero(CGHeroInstance * hero)
+{
+	auto curBonus = currentBonus();
+	if(!curBonus)
+		return;
+
+	assert(curBonus->isBonusForHero());
+
+	//apply bonus
+	switch(curBonus->type)
+	{
+		case CampaignBonusType::SPELL:
+		{
+			hero->addSpellToSpellbook(SpellID(curBonus->info2));
+			break;
+		}
+		case CampaignBonusType::MONSTER:
+		{
+			for(int i = 0; i < GameConstants::ARMY_SIZE; i++)
+			{
+				if(hero->slotEmpty(SlotID(i)))
+				{
+					hero->addToSlot(SlotID(i), CreatureID(curBonus->info2), curBonus->info3);
+					break;
+				}
+			}
+			break;
+		}
+		case CampaignBonusType::ARTIFACT:
+		{
+			if(!gameState->giveHeroArtifact(hero, static_cast<ArtifactID>(curBonus->info2)))
+				logGlobal->error("Cannot give starting artifact - no free slots!");
+			break;
+		}
+		case CampaignBonusType::SPELL_SCROLL:
+		{
+			CArtifactInstance * scroll = ArtifactUtils::createScroll(SpellID(curBonus->info2));
+			const auto slot = ArtifactUtils::getArtAnyPosition(hero, scroll->getTypeId());
+			if(ArtifactUtils::isSlotEquipment(slot) || ArtifactUtils::isSlotBackpack(slot))
+				scroll->putAt(ArtifactLocation(hero, slot));
+			else
+				logGlobal->error("Cannot give starting scroll - no free slots!");
+			break;
+		}
+		case CampaignBonusType::PRIMARY_SKILL:
+		{
+			const ui8 * ptr = reinterpret_cast<const ui8 *>(&curBonus->info2);
+			for(int g = 0; g < GameConstants::PRIMARY_SKILLS; ++g)
+			{
+				int val = ptr[g];
+				if(val == 0)
+				{
+					continue;
+				}
+				auto bb = std::make_shared<Bonus>(
+					BonusDuration::PERMANENT, BonusType::PRIMARY_SKILL, BonusSource::CAMPAIGN_BONUS, val, static_cast<int>(*gameState->scenarioOps->campState->currentScenario()), g
+				);
+				hero->addNewBonus(bb);
+			}
+			break;
+		}
+		case CampaignBonusType::SECONDARY_SKILL:
+		{
+			hero->setSecSkillLevel(SecondarySkill(curBonus->info2), curBonus->info3, true);
+			break;
+		}
+	}
+}
+
+void CGameStateCampaign::replaceHeroesPlaceholders(const std::vector<CampaignHeroReplacement> & campaignHeroReplacements)
+{
+	for(const auto & campaignHeroReplacement : campaignHeroReplacements)
+	{
+		auto * heroPlaceholder = dynamic_cast<CGHeroPlaceholder *>(gameState->getObjInstance(campaignHeroReplacement.heroPlaceholderId));
+
+		CGHeroInstance *heroToPlace = campaignHeroReplacement.hero;
+		heroToPlace->id = campaignHeroReplacement.heroPlaceholderId;
+		heroToPlace->tempOwner = heroPlaceholder->tempOwner;
+		heroToPlace->pos = heroPlaceholder->pos;
+		heroToPlace->type = VLC->heroh->objects[heroToPlace->subID];
+		heroToPlace->appearance = VLC->objtypeh->getHandlerFor(Obj::HERO, heroToPlace->type->heroClass->getIndex())->getTemplates().front();
+
+		gameState->map->removeBlockVisTiles(heroPlaceholder, true);
+		gameState->map->objects[heroPlaceholder->id.getNum()] = nullptr;
+		gameState->map->instanceNames.erase(heroPlaceholder->instanceName);
+
+		gameState->map->heroesOnMap.emplace_back(heroToPlace);
+		gameState->map->objects[heroToPlace->id.getNum()] = heroToPlace;
+		gameState->map->addBlockVisTiles(heroToPlace);
+		gameState->map->instanceNames[heroToPlace->instanceName] = heroToPlace;
+
+		delete heroPlaceholder;
+	}
+}
+
+std::vector<CampaignHeroReplacement> CGameStateCampaign::generateCampaignHeroesToReplace()
+{
+	auto campaignState = gameState->scenarioOps->campState;
+
+	std::vector<CampaignHeroReplacement> campaignHeroReplacements;
+	std::vector<CGHeroPlaceholder *> placeholdersByPower;
+	std::vector<CGHeroPlaceholder *> placeholdersByType;
+
+	// find all placeholders on map
+	for(auto obj : gameState->map->objects)
+	{
+		if(!obj)
+			continue;
+
+		if (obj->ID != Obj::HERO_PLACEHOLDER)
+			continue;
+
+		auto * heroPlaceholder = dynamic_cast<CGHeroPlaceholder *>(obj.get());
+
+		// only 1 field must be set
+		assert(heroPlaceholder->powerRank != heroPlaceholder->heroType);
+
+		if(heroPlaceholder->powerRank)
+			placeholdersByPower.push_back(heroPlaceholder);
+
+		if(heroPlaceholder->heroType)
+			placeholdersByType.push_back(heroPlaceholder);
+	}
+
+	//selecting heroes by type
+	for (auto const * placeholder : placeholdersByType)
+	{
+		auto const & node = campaignState->getHeroByType(*placeholder->heroType);
+		if (node.isNull())
+		{
+			logGlobal->info("Hero crossover: Unable to replace placeholder for %d (%s)!", placeholder->heroType->getNum(), VLC->heroTypes()->getById(*placeholder->heroType)->getNameTranslated());
+			continue;
+		}
+
+		CGHeroInstance * hero = CampaignState::crossoverDeserialize(node, gameState->map);
+
+		logGlobal->info("Hero crossover: Loading placeholder for %d (%s)", hero->subID, hero->getNameTranslated());
+
+		campaignHeroReplacements.emplace_back(hero, placeholder->id);
+	}
+
+	auto lastScenario = getHeroesSourceScenario();
+
+	if (!placeholdersByPower.empty() && lastScenario)
+	{
+		// sort hero placeholders descending power
+		boost::range::sort(placeholdersByPower, [](const CGHeroPlaceholder * a, const CGHeroPlaceholder * b)
+		{
+			return *a->powerRank > *b->powerRank;
+		});
+
+		auto const & nodeList = campaignState->getHeroesByPower(lastScenario.value());
+		auto nodeListIter = nodeList.begin();
+
+		for (auto const * placeholder : placeholdersByPower)
+		{
+			if (nodeListIter == nodeList.end())
+				break;
+
+			CGHeroInstance * hero = CampaignState::crossoverDeserialize(*nodeListIter, gameState->map);
+			nodeListIter++;
+
+			logGlobal->info("Hero crossover: Loading placeholder as %d (%s)", hero->subID, hero->getNameTranslated());
+
+			campaignHeroReplacements.emplace_back(hero, placeholder->id);
+		}
+	}
+	return campaignHeroReplacements;
+}
+
+void CGameStateCampaign::initHeroes()
+{
+	auto chosenBonus = currentBonus();
+	if (chosenBonus && chosenBonus->isBonusForHero() && chosenBonus->info1 != 0xFFFE) //exclude generated heroes
+	{
+		//find human player
+		PlayerColor humanPlayer=PlayerColor::NEUTRAL;
+		for (auto & elem : gameState->players)
+		{
+			if(elem.second.human)
+			{
+				humanPlayer = elem.first;
+				break;
+			}
+		}
+		assert(humanPlayer != PlayerColor::NEUTRAL);
+
+		std::vector<ConstTransitivePtr<CGHeroInstance> > & heroes = gameState->players[humanPlayer].heroes;
+
+		if (chosenBonus->info1 == 0xFFFD) //most powerful
+		{
+			int maxB = -1;
+			for (int b=0; b<heroes.size(); ++b)
+			{
+				if (maxB == -1 || heroes[b]->getTotalStrength() > heroes[maxB]->getTotalStrength())
+				{
+					maxB = b;
+				}
+			}
+			if(maxB < 0)
+				logGlobal->warn("Cannot give bonus to hero cause there are no heroes!");
+			else
+				giveCampaignBonusToHero(heroes[maxB]);
+		}
+		else //specific hero
+		{
+			for (auto & heroe : heroes)
+			{
+				if (heroe->subID == chosenBonus->info1)
+				{
+					giveCampaignBonusToHero(heroe);
+					break;
+				}
+			}
+		}
+	}
+}
+
+void CGameStateCampaign::initStartingResources()
+{
+	auto getHumanPlayerInfo = [&]() -> std::vector<const PlayerSettings *>
+	{
+		std::vector<const PlayerSettings *> ret;
+		for(const auto & playerInfo : gameState->scenarioOps->playerInfos)
+		{
+			if(playerInfo.second.isControlledByHuman())
+				ret.push_back(&playerInfo.second);
+		}
+
+		return ret;
+	};
+
+	auto chosenBonus = currentBonus();
+	if(chosenBonus && chosenBonus->type == CampaignBonusType::RESOURCE)
+	{
+		std::vector<const PlayerSettings *> people = getHumanPlayerInfo(); //players we will give resource bonus
+		for(const PlayerSettings *ps : people)
+		{
+			std::vector<int> res; //resources we will give
+			switch (chosenBonus->info1)
+			{
+				case 0: case 1: case 2: case 3: case 4: case 5: case 6:
+					res.push_back(chosenBonus->info1);
+					break;
+				case 0xFD: //wood+ore
+					res.push_back(GameResID(EGameResID::WOOD));
+					res.push_back(GameResID(EGameResID::ORE));
+					break;
+				case 0xFE:  //rare
+					res.push_back(GameResID(EGameResID::MERCURY));
+					res.push_back(GameResID(EGameResID::SULFUR));
+					res.push_back(GameResID(EGameResID::CRYSTAL));
+					res.push_back(GameResID(EGameResID::GEMS));
+					break;
+				default:
+					assert(0);
+					break;
+			}
+			//increasing resource quantity
+			for (auto & re : res)
+			{
+				gameState->players[ps->color].resources[re] += chosenBonus->info2;
+			}
+		}
+	}
+}
+
+void CGameStateCampaign::initTowns()
+{
+	auto chosenBonus = currentBonus();
+
+	if (!chosenBonus)
+		return;
+
+	if (chosenBonus->type != CampaignBonusType::BUILDING)
+		return;
+
+	for (int g=0; g<gameState->map->towns.size(); ++g)
+	{
+		CGTownInstance * town = gameState->map->towns[g];
+
+		PlayerState * owner = gameState->getPlayerState(town->getOwner());
+		if (!owner)
+			continue;
+
+		PlayerInfo & pi = gameState->map->players[owner->color.getNum()];
+
+		if (!owner->human)
+			continue;
+
+		if (town->pos != pi.posOfMainTown)
+			continue;
+
+		BuildingID newBuilding;
+		if(gameState->scenarioOps->campState->formatVCMI())
+			newBuilding = BuildingID(chosenBonus->info1);
+		else
+			newBuilding = CBuildingHandler::campToERMU(chosenBonus->info1, town->subID, town->builtBuildings);
+
+		// Build granted building & all prerequisites - e.g. Mages Guild Lvl 3 should also give Mages Guild Lvl 1 & 2
+		while(true)
+		{
+			if (newBuilding == BuildingID::NONE)
+				break;
+
+			if (town->builtBuildings.count(newBuilding) != 0)
+				break;
+
+			town->builtBuildings.insert(newBuilding);
+
+			auto building = town->town->buildings.at(newBuilding);
+			newBuilding = building->upgrade;
+		}
+		break;
+	}
+}
+
+bool CGameStateCampaign::playerHasStartingHero(PlayerColor playerColor) const
+{
+	auto campaignBonus = currentBonus();
+
+	if (!campaignBonus)
+		return false;
+
+	if(campaignBonus->type == CampaignBonusType::HERO && playerColor == PlayerColor(campaignBonus->info1))
+		return true;
+	return false;
+}
+
+std::unique_ptr<CMap> CGameStateCampaign::getCurrentMap() const
+{
+	return gameState->scenarioOps->campState->getMap(CampaignScenarioID::NONE);
+}
+
+VCMI_LIB_NAMESPACE_END

+ 67 - 0
lib/gameState/CGameStateCampaign.h

@@ -0,0 +1,67 @@
+/*
+ * CGameStateCampaign.h, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+#pragma once
+
+#include "../GameConstants.h"
+#include "../campaign/CampaignConstants.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+struct CampaignBonus;
+struct CampaignTravel;
+class CGHeroInstance;
+class CGameState;
+class CMap;
+
+struct CampaignHeroReplacement
+{
+	CampaignHeroReplacement(CGHeroInstance * hero, const ObjectInstanceID & heroPlaceholderId);
+	CGHeroInstance * hero;
+	ObjectInstanceID heroPlaceholderId;
+};
+
+class CGameStateCampaign
+{
+	CGameState * gameState;
+
+	/// Returns ID of scenario from which hero placeholders should be selected
+	std::optional<CampaignScenarioID> getHeroesSourceScenario() const;
+
+	/// returns heroes and placeholders in where heroes will be put
+	std::vector<CampaignHeroReplacement> generateCampaignHeroesToReplace();
+
+	std::optional<CampaignBonus> currentBonus() const;
+
+	/// Trims hero parameters that should not transfer between scenarios according to travelOptions flags
+	void trimCrossoverHeroesParameters(std::vector<CampaignHeroReplacement> & campaignHeroReplacements, const CampaignTravel & travelOptions);
+
+	void replaceHeroesPlaceholders(const std::vector<CampaignHeroReplacement> & campaignHeroReplacements);
+
+	void giveCampaignBonusToHero(CGHeroInstance * hero);
+
+public:
+	CGameStateCampaign() = default;
+	CGameStateCampaign(CGameState * owner);
+
+	void placeCampaignHeroes();
+	void initStartingResources();
+	void initHeroes();
+	void initTowns();
+
+	bool playerHasStartingHero(PlayerColor player) const;
+	std::unique_ptr<CMap> getCurrentMap() const;
+
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & gameState;
+	}
+};
+
+VCMI_LIB_NAMESPACE_END

+ 85 - 0
lib/gameState/EVictoryLossCheckResult.h

@@ -0,0 +1,85 @@
+/*
+ * EVictoryLossCheckResult.h, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+#pragma once
+
+#include "MetaString.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+class DLL_LINKAGE EVictoryLossCheckResult
+{
+public:
+	static EVictoryLossCheckResult victory(MetaString toSelf, MetaString toOthers)
+	{
+		return EVictoryLossCheckResult(VICTORY, toSelf, toOthers);
+	}
+
+	static EVictoryLossCheckResult defeat(MetaString toSelf, MetaString toOthers)
+	{
+		return EVictoryLossCheckResult(DEFEAT, toSelf, toOthers);
+	}
+
+	EVictoryLossCheckResult():
+	intValue(0)
+	{
+	}
+
+	bool operator==(EVictoryLossCheckResult const & other) const
+	{
+		return intValue == other.intValue;
+	}
+
+	bool operator!=(EVictoryLossCheckResult const & other) const
+	{
+		return intValue != other.intValue;
+	}
+
+	bool victory() const
+	{
+		return intValue == VICTORY;
+	}
+	bool loss() const
+	{
+		return intValue == DEFEAT;
+	}
+
+	EVictoryLossCheckResult invert()
+	{
+		return EVictoryLossCheckResult(-intValue, messageToOthers, messageToSelf);
+	}
+
+	MetaString messageToSelf;
+	MetaString messageToOthers;
+
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & intValue;
+		h & messageToSelf;
+		h & messageToOthers;
+	}
+private:
+	enum EResult
+	{
+		DEFEAT = -1,
+		INGAME =  0,
+		VICTORY= +1
+	};
+
+	EVictoryLossCheckResult(si32 intValue, MetaString toSelf, MetaString toOthers):
+		messageToSelf(toSelf),
+		messageToOthers(toOthers),
+		intValue(intValue)
+	{
+	}
+
+	si32 intValue; // uses EResultult
+};
+
+VCMI_LIB_NAMESPACE_END

+ 183 - 0
lib/gameState/InfoAboutArmy.cpp

@@ -0,0 +1,183 @@
+/*
+ * InfoAboutArmy.cpp, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+#include "StdInc.h"
+#include "InfoAboutArmy.h"
+
+#include "../mapObjects/CGHeroInstance.h"
+#include "../mapObjects/CGTownInstance.h"
+#include "../CHeroHandler.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+ArmyDescriptor::ArmyDescriptor(const CArmedInstance *army, bool detailed)
+	: isDetailed(detailed)
+{
+	for(const auto & elem : army->Slots())
+	{
+		if(detailed)
+			(*this)[elem.first] = *elem.second;
+		else
+			(*this)[elem.first] = CStackBasicDescriptor(elem.second->type, (int)elem.second->getQuantityID());
+	}
+}
+
+ArmyDescriptor::ArmyDescriptor()
+	: isDetailed(false)
+{
+
+}
+
+int ArmyDescriptor::getStrength() const
+{
+	ui64 ret = 0;
+	if(isDetailed)
+	{
+		for(const auto & elem : *this)
+			ret += elem.second.type->getAIValue() * elem.second.count;
+	}
+	else
+	{
+		for(const auto & elem : *this)
+			ret += elem.second.type->getAIValue() * CCreature::estimateCreatureCount(elem.second.count);
+	}
+	return static_cast<int>(ret);
+}
+
+InfoAboutArmy::InfoAboutArmy():
+	owner(PlayerColor::NEUTRAL)
+{}
+
+InfoAboutArmy::InfoAboutArmy(const CArmedInstance *Army, bool detailed)
+{
+	initFromArmy(Army, detailed);
+}
+
+void InfoAboutArmy::initFromArmy(const CArmedInstance *Army, bool detailed)
+{
+	army = ArmyDescriptor(Army, detailed);
+	owner = Army->tempOwner;
+	name = Army->getObjectName();
+}
+
+void InfoAboutHero::assign(const InfoAboutHero & iah)
+{
+	vstd::clear_pointer(details);
+	InfoAboutArmy::operator = (iah);
+
+	details = (iah.details ? new Details(*iah.details) : nullptr);
+	hclass = iah.hclass;
+	portrait = iah.portrait;
+}
+
+InfoAboutHero::InfoAboutHero(): portrait(-1) {}
+
+InfoAboutHero::InfoAboutHero(const InfoAboutHero & iah): InfoAboutArmy(iah)
+{
+	assign(iah);
+}
+
+InfoAboutHero::InfoAboutHero(const CGHeroInstance * h, InfoAboutHero::EInfoLevel infoLevel):
+	portrait(-1)
+{
+	initFromHero(h, infoLevel);
+}
+
+InfoAboutHero::~InfoAboutHero()
+{
+	vstd::clear_pointer(details);
+}
+
+InfoAboutHero & InfoAboutHero::operator=(const InfoAboutHero & iah)
+{
+	assign(iah);
+	return *this;
+}
+
+void InfoAboutHero::initFromHero(const CGHeroInstance *h, InfoAboutHero::EInfoLevel infoLevel)
+{
+	vstd::clear_pointer(details);
+	if(!h)
+		return;
+
+	bool detailed = ( (infoLevel == EInfoLevel::DETAILED) || (infoLevel == EInfoLevel::INBATTLE) );
+
+	initFromArmy(h, detailed);
+
+	hclass = h->type->heroClass;
+	name = h->getNameTranslated();
+	portrait = h->portrait;
+
+	if(detailed)
+	{
+		//include details about hero
+		details = new Details();
+		details->luck = h->luckVal();
+		details->morale = h->moraleVal();
+		details->mana = h->mana;
+		details->primskills.resize(GameConstants::PRIMARY_SKILLS);
+
+		for (int i = 0; i < GameConstants::PRIMARY_SKILLS ; i++)
+		{
+			details->primskills[i] = h->getPrimSkillLevel(static_cast<PrimarySkill::PrimarySkill>(i));
+		}
+		if (infoLevel == EInfoLevel::INBATTLE)
+			details->manaLimit = h->manaLimit();
+		else
+			details->manaLimit = -1; //we do not want to leak max mana info outside battle so set to meaningless value
+	}
+}
+
+InfoAboutTown::InfoAboutTown():
+	details(nullptr),
+	tType(nullptr),
+	built(0),
+	fortLevel(0)
+{
+
+}
+
+InfoAboutTown::InfoAboutTown(const CGTownInstance *t, bool detailed):
+	details(nullptr),
+	tType(nullptr),
+	built(0),
+	fortLevel(0)
+{
+	initFromTown(t, detailed);
+}
+
+InfoAboutTown::~InfoAboutTown()
+{
+	vstd::clear_pointer(details);
+}
+
+void InfoAboutTown::initFromTown(const CGTownInstance *t, bool detailed)
+{
+	initFromArmy(t, detailed);
+	army = ArmyDescriptor(t->getUpperArmy(), detailed);
+	built = t->builded;
+	fortLevel = t->fortLevel();
+	name = t->getNameTranslated();
+	tType = t->getTown();
+
+	vstd::clear_pointer(details);
+
+	if(detailed)
+	{
+		//include details about hero
+		details = new Details();
+		TResources income = t->dailyIncome();
+		details->goldIncome = income[EGameResID::GOLD];
+		details->customRes = t->hasBuilt(BuildingID::RESOURCE_SILO);
+		details->hallLevel = t->hallLevel();
+		details->garrisonedHero = t->garrisonHero;
+	}
+}
+
+VCMI_LIB_NAMESPACE_END

+ 99 - 0
lib/gameState/InfoAboutArmy.h

@@ -0,0 +1,99 @@
+/*
+ * InfoAboutArmy.h, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+#pragma once
+
+#include "CCreatureSet.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+class CGTownInstance;
+class CHeroClass;
+class CTown;
+
+//numbers of creatures are exact numbers if detailed else they are quantity ids (1 - a few, 2 - several and so on; additionally 0 - unknown)
+struct ArmyDescriptor : public std::map<SlotID, CStackBasicDescriptor>
+{
+	bool isDetailed;
+	DLL_LINKAGE ArmyDescriptor(const CArmedInstance *army, bool detailed); //not detailed -> quantity ids as count
+	DLL_LINKAGE ArmyDescriptor();
+
+	DLL_LINKAGE int getStrength() const;
+};
+
+struct DLL_LINKAGE InfoAboutArmy
+{
+	PlayerColor owner;
+	std::string name;
+
+	ArmyDescriptor army;
+
+	InfoAboutArmy();
+	InfoAboutArmy(const CArmedInstance *Army, bool detailed);
+
+	void initFromArmy(const CArmedInstance *Army, bool detailed);
+};
+
+struct DLL_LINKAGE InfoAboutHero : public InfoAboutArmy
+{
+private:
+	void assign(const InfoAboutHero & iah);
+
+public:
+	struct DLL_LINKAGE Details
+	{
+		std::vector<si32> primskills;
+		si32 mana, manaLimit, luck, morale;
+	};
+
+	Details * details = nullptr;
+
+	const CHeroClass *hclass;
+	int portrait;
+
+	enum EInfoLevel
+	{
+		BASIC,
+		DETAILED,
+		INBATTLE
+	};
+
+	InfoAboutHero();
+	InfoAboutHero(const InfoAboutHero & iah);
+	InfoAboutHero(const CGHeroInstance *h, EInfoLevel infoLevel);
+	~InfoAboutHero();
+
+	InfoAboutHero & operator=(const InfoAboutHero & iah);
+
+	void initFromHero(const CGHeroInstance *h, EInfoLevel infoLevel);
+};
+
+/// Struct which holds a int information about a town
+struct DLL_LINKAGE InfoAboutTown : public InfoAboutArmy
+{
+	struct DLL_LINKAGE Details
+	{
+		si32 hallLevel, goldIncome;
+		bool customRes;
+		bool garrisonedHero;
+
+	} *details;
+
+	const CTown *tType;
+
+	si32 built;
+	si32 fortLevel; //0 - none
+
+	InfoAboutTown();
+	InfoAboutTown(const CGTownInstance *t, bool detailed);
+	~InfoAboutTown();
+	void initFromTown(const CGTownInstance *t, bool detailed);
+};
+
+VCMI_LIB_NAMESPACE_END

+ 55 - 0
lib/gameState/QuestInfo.h

@@ -0,0 +1,55 @@
+/*
+ * QuestInfo.h, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+#pragma once
+
+#include "int3.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+class CQuest;
+class CGObjectInstance;
+
+struct DLL_LINKAGE QuestInfo //universal interface for human and AI
+{
+	const CQuest * quest;
+	const CGObjectInstance * obj; //related object, most likely Seer Hut
+	int3 tile;
+
+	QuestInfo()
+		: quest(nullptr), obj(nullptr), tile(-1,-1,-1)
+	{};
+	QuestInfo (const CQuest * Quest, const CGObjectInstance * Obj, int3 Tile) :
+		quest (Quest), obj (Obj), tile (Tile){};
+
+	QuestInfo (const QuestInfo &qi) : quest(qi.quest), obj(qi.obj), tile(qi.tile)
+	{};
+
+	const QuestInfo& operator= (const QuestInfo &qi)
+	{
+		quest = qi.quest;
+		obj = qi.obj;
+		tile = qi.tile;
+		return *this;
+	}
+
+	bool operator== (const QuestInfo & qi) const
+	{
+		return (quest == qi.quest && obj == qi.obj);
+	}
+
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & quest;
+		h & obj;
+		h & tile;
+	}
+};
+
+VCMI_LIB_NAMESPACE_END

+ 47 - 0
lib/gameState/SThievesGuildInfo.h

@@ -0,0 +1,47 @@
+/*
+ * SThievesGuildInfo.h, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+#pragma once
+
+#include "../GameConstants.h"
+#include "InfoAboutArmy.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+struct DLL_LINKAGE SThievesGuildInfo
+{
+	std::vector<PlayerColor> playerColors; //colors of players that are in-game
+
+	std::vector< std::vector< PlayerColor > > numOfTowns, numOfHeroes, gold, woodOre, mercSulfCrystGems, obelisks, artifacts, army, income; // [place] -> [colours of players]
+
+	std::map<PlayerColor, InfoAboutHero> colorToBestHero; //maps player's color to his best heros'
+
+	std::map<PlayerColor, EAiTactic::EAiTactic> personality; // color to personality // ai tactic
+	std::map<PlayerColor, si32> bestCreature; // color to ID // id or -1 if not known
+
+//	template <typename Handler> void serialize(Handler &h, const int version)
+//	{
+//		h & playerColors;
+//		h & numOfTowns;
+//		h & numOfHeroes;
+//		h & gold;
+//		h & woodOre;
+//		h & mercSulfCrystGems;
+//		h & obelisks;
+//		h & artifacts;
+//		h & army;
+//		h & income;
+//		h & colorToBestHero;
+//		h & personality;
+//		h & bestCreature;
+//	}
+
+};
+
+VCMI_LIB_NAMESPACE_END

+ 1 - 1
lib/mapObjects/CArmedInstance.cpp

@@ -14,7 +14,7 @@
 #include "../CTownHandler.h"
 #include "../CCreatureHandler.h"
 #include "../CGeneralTextHandler.h"
-#include "../CGameState.h"
+#include "../gameState/CGameState.h"
 #include "../CPlayerState.h"
 
 VCMI_LIB_NAMESPACE_BEGIN

+ 1 - 1
lib/mapObjects/CBank.cpp

@@ -20,7 +20,7 @@
 #include "../mapObjectConstructors/CObjectClassesHandler.h"
 #include "../mapObjectConstructors/CBankInstanceConstructor.h"
 #include "../IGameCallback.h"
-#include "../CGameState.h"
+#include "../gameState/CGameState.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 

+ 1 - 1
lib/mapObjects/CGDwelling.cpp

@@ -15,7 +15,7 @@
 #include "../mapObjectConstructors/CObjectClassesHandler.h"
 #include "../CTownHandler.h"
 #include "../IGameCallback.h"
-#include "../CGameState.h"
+#include "../gameState/CGameState.h"
 #include "../CPlayerState.h"
 #include "../NetPacks.h"
 #include "../GameSettings.h"

+ 11 - 4
lib/mapObjects/CGHeroInstance.cpp

@@ -26,7 +26,7 @@
 #include "../spells/CSpellHandler.h"
 #include "../CSkillHandler.h"
 #include "../IGameCallback.h"
-#include "../CGameState.h"
+#include "../gameState/CGameState.h"
 #include "../CCreatureHandler.h"
 #include "../CTownHandler.h"
 #include "../mapping/CMap.h"
@@ -287,16 +287,23 @@ void CGHeroInstance::initHero(CRandomGenerator & rand)
 	if (ID == Obj::HERO)
 		appearance = VLC->objtypeh->getHandlerFor(Obj::HERO, type->heroClass->getIndex())->getTemplates().front();
 
-	if(!vstd::contains(spells, SpellID::PRESET)) //hero starts with a spell
+	if(!vstd::contains(spells, SpellID::PRESET))
 	{
+		// hero starts with default spells
 		for(const auto & spellID : type->spells)
 			spells.insert(spellID);
 	}
 	else //remove placeholder
 		spells -= SpellID::PRESET;
 
-	if(!getArt(ArtifactPosition::MACH4) && !getArt(ArtifactPosition::SPELLBOOK) && type->haveSpellBook) //no catapult means we haven't read pre-existent set -> use default rules for spellbook
-		putArtifact(ArtifactPosition::SPELLBOOK, ArtifactUtils::createNewArtifactInstance(ArtifactID::SPELLBOOK));
+	if(!vstd::contains(spells, SpellID::SPELLBOOK_PRESET))
+	{
+		// hero starts with default spellbook presence status
+		if(!getArt(ArtifactPosition::SPELLBOOK) && type->haveSpellBook)
+			putArtifact(ArtifactPosition::SPELLBOOK, ArtifactUtils::createNewArtifactInstance(ArtifactID::SPELLBOOK));
+	}
+	else
+		spells -= SpellID::SPELLBOOK_PRESET;
 
 	if(!getArt(ArtifactPosition::MACH4))
 		putArtifact(ArtifactPosition::MACH4, ArtifactUtils::createNewArtifactInstance(ArtifactID::CATAPULT)); //everyone has a catapult

+ 8 - 4
lib/mapObjects/CGHeroInstance.h

@@ -28,13 +28,17 @@ enum class EHeroGender : uint8_t;
 class CGHeroPlaceholder : public CGObjectInstance
 {
 public:
-	//subID stores id of hero type. If it's 0xff then following field is used
-	ui8 power;
+	/// if this is placeholder by power, then power rank of desired hero
+	std::optional<ui8> powerRank;
+
+	/// if this is placeholder by type, then hero type of desired hero
+	std::optional<HeroTypeID> heroType;
 
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
 		h & static_cast<CGObjectInstance&>(*this);
-		h & power;
+		h & powerRank;
+		h & heroType;
 	}
 };
 
@@ -42,7 +46,7 @@ public:
 class DLL_LINKAGE CGHeroInstance : public CArmedInstance, public IBoatGenerator, public CArtifactSet, public spells::Caster, public AFactionMember, public ICreatureUpgrader
 {
 	// We serialize heroes into JSON for crossover
-	friend class CCampaignState;
+	friend class CampaignState;
 	friend class CMapLoaderH3M;
 	friend class CMapFormatJson;
 

+ 0 - 1
lib/mapObjects/CGMarket.cpp

@@ -15,7 +15,6 @@
 #include "../CGeneralTextHandler.h"
 #include "../IGameCallback.h"
 #include "../CCreatureHandler.h"
-#include "../CGameState.h"
 #include "CGTownInstance.h"
 #include "../GameSettings.h"
 #include "../CSkillHandler.h"

+ 1 - 1
lib/mapObjects/CGObjectInstance.cpp

@@ -14,7 +14,7 @@
 #include "CGHeroInstance.h"
 #include "ObjectTemplate.h"
 
-#include "../CGameState.h"
+#include "../gameState/CGameState.h"
 #include "../CGeneralTextHandler.h"
 #include "../IGameCallback.h"
 #include "../NetPacks.h"

+ 1 - 1
lib/mapObjects/CGTownBuilding.cpp

@@ -14,7 +14,7 @@
 #include "../CGeneralTextHandler.h"
 #include "../NetPacks.h"
 #include "../IGameCallback.h"
-#include "../CGameState.h"
+#include "../gameState/CGameState.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 

+ 1 - 1
lib/mapObjects/CGTownInstance.cpp

@@ -19,7 +19,7 @@
 #include "../CGeneralTextHandler.h"
 #include "../CModHandler.h"
 #include "../IGameCallback.h"
-#include "../CGameState.h"
+#include "../gameState/CGameState.h"
 #include "../mapping/CMap.h"
 #include "../CPlayerState.h"
 #include "../TerrainHandler.h"

+ 0 - 1
lib/mapObjects/CQuest.cpp

@@ -20,7 +20,6 @@
 #include "../CHeroHandler.h"
 #include "CGCreature.h"
 #include "../IGameCallback.h"
-#include "../CGameState.h"
 #include "../mapObjectConstructors/CObjectClassesHandler.h"
 #include "../serializer/JsonSerializeFormat.h"
 #include "../CModHandler.h"

+ 1 - 1
lib/mapObjects/CRewardableObject.cpp

@@ -10,7 +10,7 @@
 
 #include "StdInc.h"
 #include "CRewardableObject.h"
-#include "../CGameState.h"
+#include "../gameState/CGameState.h"
 #include "../CGeneralTextHandler.h"
 #include "../CPlayerState.h"
 #include "../IGameCallback.h"

+ 1 - 1
lib/mapObjects/MiscObjects.cpp

@@ -19,7 +19,7 @@
 #include "../CSkillHandler.h"
 #include "../spells/CSpellHandler.h"
 #include "../IGameCallback.h"
-#include "../CGameState.h"
+#include "../gameState/CGameState.h"
 #include "../mapping/CMap.h"
 #include "../CPlayerState.h"
 #include "../serializer/JsonSerializeFormat.h"

+ 0 - 876
lib/mapping/CCampaignHandler.cpp

@@ -1,876 +0,0 @@
-/*
- * CCampaignHandler.cpp, part of VCMI engine
- *
- * Authors: listed in file AUTHORS in main folder
- *
- * License: GNU General Public License v2.0 or later
- * Full text of license available in license.txt file, in main folder
- *
- */
-#include "StdInc.h"
-#include "CCampaignHandler.h"
-
-#include "../filesystem/Filesystem.h"
-#include "../filesystem/CCompressedStream.h"
-#include "../filesystem/CMemoryStream.h"
-#include "../filesystem/CBinaryReader.h"
-#include "../VCMI_Lib.h"
-#include "../vcmi_endian.h"
-#include "../CGeneralTextHandler.h"
-#include "../TextOperations.h"
-#include "../StartInfo.h"
-#include "../CModHandler.h"
-#include "../CArtHandler.h" //for hero crossover
-#include "../mapObjects/CGHeroInstance.h"//for hero crossover
-#include "../CHeroHandler.h"
-#include "../Languages.h"
-#include "../StringConstants.h"
-#include "CMapService.h"
-#include "CMap.h"
-#include "CMapInfo.h"
-
-// For hero crossover
-#include "serializer/CSerializer.h"
-#include "serializer/JsonDeserializer.h"
-#include "serializer/JsonSerializer.h"
-
-VCMI_LIB_NAMESPACE_BEGIN
-
-CampaignRegions::RegionDescription CampaignRegions::RegionDescription::fromJson(const JsonNode & node)
-{
-	CampaignRegions::RegionDescription rd;
-	rd.infix = node["infix"].String();
-	rd.xpos = static_cast<int>(node["x"].Float());
-	rd.ypos = static_cast<int>(node["y"].Float());
-	return rd;
-}
-
-CampaignRegions CampaignRegions::fromJson(const JsonNode & node)
-{
-	CampaignRegions cr;
-	cr.campPrefix = node["prefix"].String();
-	cr.colorSuffixLength = static_cast<int>(node["color_suffix_length"].Float());
-
-	for(const JsonNode & desc : node["desc"].Vector())
-		cr.regions.push_back(CampaignRegions::RegionDescription::fromJson(desc));
-	
-	return cr;
-}
-
-CampaignRegions CampaignRegions::getLegacy(int campId)
-{
-	static std::vector<CampaignRegions> campDescriptions;
-	if(campDescriptions.empty()) //read once
-	{
-		const JsonNode config(ResourceID("config/campaign_regions.json"));
-		for(const JsonNode & campaign : config["campaign_regions"].Vector())
-			campDescriptions.push_back(CampaignRegions::fromJson(campaign));
-	}
-	
-	return campDescriptions.at(campId);
-}
-
-
-bool CScenarioTravel::STravelBonus::isBonusForHero() const
-{
-	return type == SPELL || type == MONSTER || type == ARTIFACT || type == SPELL_SCROLL || type == PRIMARY_SKILL
-		|| type == SECONDARY_SKILL;
-}
-
-void CCampaignHeader::loadLegacyData(ui8 campId)
-{
-	campaignRegions = CampaignRegions::getLegacy(campId);
-	numberOfScenarios = VLC->generaltexth->getCampaignLength(campId);
-}
-
-CCampaignHeader CCampaignHandler::getHeader( const std::string & name)
-{
-	ResourceID resourceID(name, EResType::CAMPAIGN);
-	std::string modName = VLC->modh->findResourceOrigin(resourceID);
-	std::string language = VLC->modh->getModLanguage(modName);
-	std::string encoding = Languages::getLanguageOptions(language).encoding;
-	
-	auto fileStream = CResourceHandler::get(modName)->load(resourceID);
-	std::vector<ui8> cmpgn = getFile(std::move(fileStream), true)[0];
-	JsonNode jsonCampaign((const char*)cmpgn.data(), cmpgn.size());
-	if(jsonCampaign.isNull())
-	{
-		//legacy OH3 campaign (*.h3c)
-		CMemoryStream stream(cmpgn.data(), cmpgn.size());
-		CBinaryReader reader(&stream);
-		return readHeaderFromMemory(reader, resourceID.getName(), modName, encoding);
-	}
-	
-	//VCMI (*.vcmp)
-	return readHeaderFromJson(jsonCampaign, resourceID.getName(), modName, encoding);
-}
-
-std::unique_ptr<CCampaign> CCampaignHandler::getCampaign( const std::string & name )
-{
-	ResourceID resourceID(name, EResType::CAMPAIGN);
-	std::string modName = VLC->modh->findResourceOrigin(resourceID);
-	std::string language = VLC->modh->getModLanguage(modName);
-	std::string encoding = Languages::getLanguageOptions(language).encoding;
-	
-	auto ret = std::make_unique<CCampaign>();
-	
-	auto fileStream = CResourceHandler::get(modName)->load(resourceID);
-
-	std::vector<std::vector<ui8>> files = getFile(std::move(fileStream), false);
-
-	if (files[0].front() < uint8_t(' ')) // binary format
-	{
-		CMemoryStream stream(files[0].data(), files[0].size());
-		CBinaryReader reader(&stream);
-		ret->header = readHeaderFromMemory(reader, resourceID.getName(), modName, encoding);
-		
-		for(int g = 0; g < ret->header.numberOfScenarios; ++g)
-			ret->scenarios.emplace_back(readScenarioFromMemory(reader, ret->header));
-	}
-	else // text format (json)
-	{
-		JsonNode jsonCampaign((const char*)files[0].data(), files[0].size());
-		ret->header = readHeaderFromJson(jsonCampaign, resourceID.getName(), modName, encoding);
-		for(auto & scenario : jsonCampaign["scenarios"].Vector())
-			ret->scenarios.emplace_back(readScenarioFromJson(scenario));
-	}
-	
-	//first entry is campaign header. start loop from 1
-	for(int scenarioID = 0, g = 1; g < files.size() && scenarioID < ret->header.numberOfScenarios; ++g)
-	{
-		while(!ret->scenarios[scenarioID].isNotVoid()) //skip void scenarios
-			scenarioID++;
-
-		std::string scenarioName = resourceID.getName();
-		boost::to_lower(scenarioName);
-		scenarioName += ':' + std::to_string(g - 1);
-
-		//set map piece appropriately, convert vector to string
-		ret->mapPieces[scenarioID].assign(reinterpret_cast<const char*>(files[g].data()), files[g].size());
-		CMapService mapService;
-		auto hdr = mapService.loadMapHeader(
-			reinterpret_cast<const ui8 *>(ret->mapPieces[scenarioID].c_str()),
-			static_cast<int>(ret->mapPieces[scenarioID].size()),
-			scenarioName,
-			modName,
-			encoding);
-		ret->scenarios[scenarioID].scenarioName = hdr->name;
-		scenarioID++;
-	}
-
-	// handle campaign specific discrepancies
-	if(resourceID.getName() == "DATA/AB")
-	{
-		ret->scenarios[6].keepHeroes.emplace_back(155); // keep hero Xeron for map 7 To Kill A Hero
-	}
-	else if(resourceID.getName() == "DATA/FINAL")
-	{
-		// keep following heroes for map 8 Final H
-		ret->scenarios[7].keepHeroes.emplace_back(148); // Gelu
-		ret->scenarios[7].keepHeroes.emplace_back(27); // Gem
-		ret->scenarios[7].keepHeroes.emplace_back(102); // Crag Hack
-		ret->scenarios[7].keepHeroes.emplace_back(96); // Yog
-	}
-
-	return ret;
-}
-
-static std::string convertMapName(std::string input)
-{
-	boost::algorithm::to_lower(input);
-	boost::algorithm::trim(input);
-
-	size_t slashPos = input.find_last_of("/");
-
-	if (slashPos != std::string::npos)
-		return input.substr(slashPos + 1);
-
-	return input;
-}
-
-std::string CCampaignHandler::readLocalizedString(CBinaryReader & reader, std::string filename, std::string modName, std::string encoding, std::string identifier)
-{
-	TextIdentifier stringID( "campaign", convertMapName(filename), identifier);
-
-	std::string input = TextOperations::toUnicode(reader.readBaseString(), encoding);
-
-	if (input.empty())
-		return "";
-
-	VLC->generaltexth->registerString(modName, stringID, input);
-	return VLC->generaltexth->translate(stringID.get());
-}
-
-CCampaignHeader CCampaignHandler::readHeaderFromJson(JsonNode & reader, std::string filename, std::string modName, std::string encoding)
-{
-	CCampaignHeader ret;
-
-	ret.version = reader["version"].Integer();
-	if(ret.version < CampaignVersion::VCMI_MIN || ret.version > CampaignVersion::VCMI_MAX)
-	{
-		logGlobal->info("VCMP Loading: Unsupported campaign %s version %d", filename, ret.version);
-		return ret;
-	}
-	
-	ret.version = CampaignVersion::VCMI;
-	ret.campaignRegions = CampaignRegions::fromJson(reader["regions"]);
-	ret.numberOfScenarios = reader["scenarios"].Vector().size();
-	ret.name = reader["name"].String();
-	ret.description = reader["description"].String();
-	ret.difficultyChoosenByPlayer = reader["allowDifficultySelection"].Bool();
-	//skip ret.music because it's unused in vcmi
-	ret.filename = filename;
-	ret.modName = modName;
-	ret.encoding = encoding;
-	ret.valid = true;
-	return ret;
-}
-
-CCampaignScenario CCampaignHandler::readScenarioFromJson(JsonNode & reader)
-{
-	auto prologEpilogReader = [](JsonNode & identifier) -> CCampaignScenario::SScenarioPrologEpilog
-	{
-		CCampaignScenario::SScenarioPrologEpilog ret;
-		ret.hasPrologEpilog = !identifier.isNull();
-		if(ret.hasPrologEpilog)
-		{
-			ret.prologVideo = identifier["video"].String();
-			ret.prologMusic = identifier["music"].String();
-			ret.prologText = identifier["text"].String();
-		}
-		return ret;
-	};
-
-	CCampaignScenario ret;
-	ret.conquered = false;
-	ret.mapName = reader["map"].String();
-	for(auto & g : reader["preconditions"].Vector())
-		ret.preconditionRegions.insert(g.Integer());
-
-	ret.regionColor = reader["color"].Integer();
-	ret.difficulty = reader["difficulty"].Integer();
-	ret.regionText = reader["regionText"].String();
-	ret.prolog = prologEpilogReader(reader["prolog"]);
-	ret.epilog = prologEpilogReader(reader["epilog"]);
-
-	ret.travelOptions = readScenarioTravelFromJson(reader);
-
-	return ret;
-}
-
-CScenarioTravel CCampaignHandler::readScenarioTravelFromJson(JsonNode & reader)
-{
-	CScenarioTravel ret;
-
-	std::map<std::string, ui8> startOptionsMap = {
-		{"none", 0},
-		{"bonus", 1},
-		{"crossover", 2},
-		{"hero", 3}
-	};
-	
-	std::map<std::string, CScenarioTravel::STravelBonus::EBonusType> bonusTypeMap = {
-		{"spell", CScenarioTravel::STravelBonus::EBonusType::SPELL},
-		{"creature", CScenarioTravel::STravelBonus::EBonusType::MONSTER},
-		{"building", CScenarioTravel::STravelBonus::EBonusType::BUILDING},
-		{"artifact", CScenarioTravel::STravelBonus::EBonusType::ARTIFACT},
-		{"scroll", CScenarioTravel::STravelBonus::EBonusType::SPELL_SCROLL},
-		{"primarySkill", CScenarioTravel::STravelBonus::EBonusType::PRIMARY_SKILL},
-		{"secondarySkill", CScenarioTravel::STravelBonus::EBonusType::SECONDARY_SKILL},
-		{"resource", CScenarioTravel::STravelBonus::EBonusType::RESOURCE},
-		//{"prevHero", CScenarioTravel::STravelBonus::EBonusType::HEROES_FROM_PREVIOUS_SCENARIO},
-		//{"hero", CScenarioTravel::STravelBonus::EBonusType::HERO},
-	};
-	
-	std::map<std::string, ui32> primarySkillsMap = {
-		{"attack", 0},
-		{"defence", 8},
-		{"spellpower", 16},
-		{"knowledge", 24},
-	};
-	
-	std::map<std::string, ui16> heroSpecialMap = {
-		{"strongest", 0xFFFD},
-		{"generated", 0xFFFE},
-		{"random", 0xFFFF}
-	};
-	
-	std::map<std::string, ui8> resourceTypeMap = {
-		//FD - wood+ore
-		//FE - mercury+sulfur+crystal+gem
-		{"wood", 0},
-		{"mercury", 1},
-		{"ore", 2},
-		{"sulfur", 3},
-		{"crystal", 4},
-		{"gems", 5},
-		{"gold", 6},
-		{"common", 0xFD},
-		{"rare", 0xFE}
-	};
-	
-	for(auto & k : reader["heroKeeps"].Vector())
-	{
-		if(k.String() == "experience") ret.whatHeroKeeps.experience = true;
-		if(k.String() == "primarySkills") ret.whatHeroKeeps.primarySkills = true;
-		if(k.String() == "secondarySkills") ret.whatHeroKeeps.secondarySkills = true;
-		if(k.String() == "spells") ret.whatHeroKeeps.spells = true;
-		if(k.String() == "artifacts") ret.whatHeroKeeps.artifacts = true;
-	}
-	
-	for(auto & k : reader["keepCreatures"].Vector())
-	{
-		if(auto identifier = VLC->modh->identifiers.getIdentifier(CModHandler::scopeMap(), "creature", k.String()))
-			ret.monstersKeptByHero.insert(CreatureID(identifier.value()));
-		else
-			logGlobal->warn("VCMP Loading: keepCreatures contains unresolved identifier %s", k.String());
-	}
-	for(auto & k : reader["keepArtifacts"].Vector())
-	{
-		if(auto identifier = VLC->modh->identifiers.getIdentifier(CModHandler::scopeMap(), "artifact", k.String()))
-			ret.artifactsKeptByHero.insert(ArtifactID(identifier.value()));
-		else
-			logGlobal->warn("VCMP Loading: keepArtifacts contains unresolved identifier %s", k.String());
-	}
-
-	ret.startOptions = startOptionsMap[reader["startOptions"].String()];
-	switch(ret.startOptions)
-	{
-	case 0:
-		//no bonuses. Seems to be OK
-		break;
-	case 1: //reading of bonuses player can choose
-		{
-			ret.playerColor = reader["playerColor"].Integer();
-			for(auto & bjson : reader["bonuses"].Vector())
-			{
-				CScenarioTravel::STravelBonus bonus;
-				bonus.type = bonusTypeMap[bjson["what"].String()];
-				switch (bonus.type)
-				{
-					case CScenarioTravel::STravelBonus::EBonusType::RESOURCE:
-						bonus.info1 = resourceTypeMap[bjson["type"].String()];
-						bonus.info2 = bjson["amount"].Integer();
-						break;
-						
-					case CScenarioTravel::STravelBonus::EBonusType::BUILDING:
-						bonus.info1 = vstd::find_pos(EBuildingType::names, bjson["type"].String());
-						if(bonus.info1 == -1)
-							logGlobal->warn("VCMP Loading: unresolved building identifier %s", bjson["type"].String());
-						break;
-						
-					default:
-						if(int heroId = heroSpecialMap[bjson["hero"].String()])
-							bonus.info1 = heroId;
-						else
-							if(auto identifier = VLC->modh->identifiers.getIdentifier(CModHandler::scopeMap(), "hero", bjson["hero"].String()))
-								bonus.info1 = identifier.value();
-							else
-								logGlobal->warn("VCMP Loading: unresolved hero identifier %s", bjson["hero"].String());
-	
-						bonus.info3 = bjson["amount"].Integer();
-						
-						switch(bonus.type)
-						{
-							case CScenarioTravel::STravelBonus::EBonusType::SPELL:
-							case CScenarioTravel::STravelBonus::EBonusType::MONSTER:
-							case CScenarioTravel::STravelBonus::EBonusType::SECONDARY_SKILL:
-							case CScenarioTravel::STravelBonus::EBonusType::ARTIFACT:
-								if(auto identifier  = VLC->modh->identifiers.getIdentifier(CModHandler::scopeMap(), bjson["what"].String(), bjson["type"].String()))
-									bonus.info2 = identifier.value();
-								else
-									logGlobal->warn("VCMP Loading: unresolved %s identifier %s", bjson["what"].String(), bjson["type"].String());
-								break;
-								
-							case CScenarioTravel::STravelBonus::EBonusType::SPELL_SCROLL:
-								if(auto Identifier = VLC->modh->identifiers.getIdentifier(CModHandler::scopeMap(), "spell", bjson["type"].String()))
-									bonus.info2 = Identifier.value();
-								else
-									logGlobal->warn("VCMP Loading: unresolved spell scroll identifier %s", bjson["type"].String());
-								break;
-								
-							case CScenarioTravel::STravelBonus::EBonusType::PRIMARY_SKILL:
-								for(auto & ps : primarySkillsMap)
-									bonus.info2 |= bjson[ps.first].Integer() << ps.second;
-								break;
-								
-							default:
-								bonus.info2 = bjson["type"].Integer();
-						}
-						break;
-				}
-				ret.bonusesToChoose.push_back(bonus);
-			}
-			break;
-		}
-	case 2: //reading of players (colors / scenarios ?) player can choose
-		{
-			for(auto & bjson : reader["bonuses"].Vector())
-			{
-				CScenarioTravel::STravelBonus bonus;
-				bonus.type = CScenarioTravel::STravelBonus::HEROES_FROM_PREVIOUS_SCENARIO;
-				bonus.info1 = bjson["playerColor"].Integer(); //player color
-				bonus.info2 = bjson["scenario"].Integer(); //from what scenario
-				ret.bonusesToChoose.push_back(bonus);
-			}
-			break;
-		}
-	case 3: //heroes player can choose between
-		{
-			for(auto & bjson : reader["bonuses"].Vector())
-			{
-				CScenarioTravel::STravelBonus bonus;
-				bonus.type = CScenarioTravel::STravelBonus::HERO;
-				bonus.info1 = bjson["playerColor"].Integer(); //player color
-				
-				if(int heroId = heroSpecialMap[bjson["hero"].String()])
-					bonus.info2 = heroId;
-				else
-					if (auto identifier = VLC->modh->identifiers.getIdentifier(CModHandler::scopeMap(), "hero", bjson["hero"].String()))
-						bonus.info2 = identifier.value();
-					else
-						logGlobal->warn("VCMP Loading: unresolved hero identifier %s", bjson["hero"].String());
-			
-				ret.bonusesToChoose.push_back(bonus);
-			}
-			break;
-		}
-	default:
-		{
-			logGlobal->warn("VCMP Loading: Unsupported start options value");
-			break;
-		}
-	}
-
-	return ret;
-}
-
-
-CCampaignHeader CCampaignHandler::readHeaderFromMemory( CBinaryReader & reader, std::string filename, std::string modName, std::string encoding )
-{
-	CCampaignHeader ret;
-
-	ret.version = reader.readUInt32();
-	ui8 campId = reader.readUInt8() - 1;//change range of it from [1, 20] to [0, 19]
-	ret.loadLegacyData(campId);
-	ret.name = readLocalizedString(reader, filename, modName, encoding, "name");
-	ret.description = readLocalizedString(reader, filename, modName, encoding, "description");
-	if (ret.version > CampaignVersion::RoE)
-		ret.difficultyChoosenByPlayer = reader.readInt8();
-	else
-		ret.difficultyChoosenByPlayer = false;
-	reader.readInt8(); //music - skip as unused
-	ret.filename = filename;
-	ret.modName = modName;
-	ret.encoding = encoding;
-	ret.valid = true;
-	return ret;
-}
-
-CCampaignScenario CCampaignHandler::readScenarioFromMemory( CBinaryReader & reader, const CCampaignHeader & header)
-{
-	auto prologEpilogReader = [&](const std::string & identifier) -> CCampaignScenario::SScenarioPrologEpilog
-	{
-		CCampaignScenario::SScenarioPrologEpilog ret;
-		ret.hasPrologEpilog = reader.readUInt8();
-		if(ret.hasPrologEpilog)
-		{
-			ret.prologVideo = CCampaignHandler::prologVideoName(reader.readUInt8());
-			ret.prologMusic = CCampaignHandler::prologMusicName(reader.readUInt8());
-			ret.prologText = readLocalizedString(reader, header.filename, header.modName, header.encoding, identifier);
-		}
-		return ret;
-	};
-
-	CCampaignScenario ret;
-	ret.conquered = false;
-	ret.mapName = reader.readBaseString();
-	reader.readUInt32(); //packedMapSize - not used
-	if(header.numberOfScenarios > 8) //unholy alliance
-	{
-		ret.loadPreconditionRegions(reader.readUInt16());
-	}
-	else
-	{
-		ret.loadPreconditionRegions(reader.readUInt8());
-	}
-	ret.regionColor = reader.readUInt8();
-	ret.difficulty = reader.readUInt8();
-	ret.regionText = readLocalizedString(reader, header.filename, header.modName, header.encoding, ret.mapName + ".region");
-	ret.prolog = prologEpilogReader(ret.mapName + ".prolog");
-	ret.epilog = prologEpilogReader(ret.mapName + ".epilog");
-
-	ret.travelOptions = readScenarioTravelFromMemory(reader, header.version);
-
-	return ret;
-}
-
-void CCampaignScenario::loadPreconditionRegions(ui32 regions)
-{
-	for (int i=0; i<32; i++) //for each bit in region. h3c however can only hold up to 16
-	{
-		if ( (1 << i) & regions)
-			preconditionRegions.insert(i);
-	}
-}
-
-CScenarioTravel CCampaignHandler::readScenarioTravelFromMemory(CBinaryReader & reader, int version )
-{
-	CScenarioTravel ret;
-
-	ui8 whatHeroKeeps = reader.readUInt8();
-	ret.whatHeroKeeps.experience = whatHeroKeeps & 1;
-	ret.whatHeroKeeps.primarySkills = whatHeroKeeps & 2;
-	ret.whatHeroKeeps.secondarySkills = whatHeroKeeps & 4;
-	ret.whatHeroKeeps.spells = whatHeroKeeps & 8;
-	ret.whatHeroKeeps.artifacts = whatHeroKeeps & 16;
-	
-	//TODO: replace with template lambda form C++20 and make typed containers
-	auto bitMaskToId = [&reader](std::set<int> & container, int size)
-	{
-		for(int iId = 0, byte = 0; iId < size * 8; ++iId)
-		{
-			if(iId % 8 == 0)
-				byte = reader.readUInt8();
-			if(byte & (1 << iId % 8))
-				container.insert(iId);
-		}
-	};
-	
-	bitMaskToId(ret.monstersKeptByHero, 19);
-	bitMaskToId(ret.artifactsKeptByHero, version < CampaignVersion::SoD ? 17 : 18);
-
-	ret.startOptions = reader.readUInt8();
-
-	switch(ret.startOptions)
-	{
-	case 0:
-		//no bonuses. Seems to be OK
-		break;
-	case 1: //reading of bonuses player can choose
-		{
-			ret.playerColor = reader.readUInt8();
-			ui8 numOfBonuses = reader.readUInt8();
-			for (int g=0; g<numOfBonuses; ++g)
-			{
-				CScenarioTravel::STravelBonus bonus;
-				bonus.type = static_cast<CScenarioTravel::STravelBonus::EBonusType>(reader.readUInt8());
-				//hero: FFFD means 'most powerful' and FFFE means 'generated'
-				switch(bonus.type)
-				{
-				case CScenarioTravel::STravelBonus::SPELL:
-					{
-						bonus.info1 = reader.readUInt16(); //hero
-						bonus.info2 = reader.readUInt8(); //spell ID
-						break;
-					}
-				case CScenarioTravel::STravelBonus::MONSTER:
-					{
-						bonus.info1 = reader.readUInt16(); //hero
-						bonus.info2 = reader.readUInt16(); //monster type
-						bonus.info3 = reader.readUInt16(); //monster count
-						break;
-					}
-				case CScenarioTravel::STravelBonus::BUILDING:
-					{
-						bonus.info1 = reader.readUInt8(); //building ID (0 - town hall, 1 - city hall, 2 - capitol, etc)
-						break;
-					}
-				case CScenarioTravel::STravelBonus::ARTIFACT:
-					{
-						bonus.info1 = reader.readUInt16(); //hero
-						bonus.info2 = reader.readUInt16(); //artifact ID
-						break;
-					}
-				case CScenarioTravel::STravelBonus::SPELL_SCROLL:
-					{
-						bonus.info1 = reader.readUInt16(); //hero
-						bonus.info2 = reader.readUInt8(); //spell ID
-						break;
-					}
-				case CScenarioTravel::STravelBonus::PRIMARY_SKILL:
-					{
-						bonus.info1 = reader.readUInt16(); //hero
-						bonus.info2 = reader.readUInt32(); //bonuses (4 bytes for 4 skills)
-						break;
-					}
-				case CScenarioTravel::STravelBonus::SECONDARY_SKILL:
-					{
-						bonus.info1 = reader.readUInt16(); //hero
-						bonus.info2 = reader.readUInt8(); //skill ID
-						bonus.info3 = reader.readUInt8(); //skill level
-						break;
-					}
-				case CScenarioTravel::STravelBonus::RESOURCE:
-					{
-						bonus.info1 = reader.readUInt8(); //type
-						//FD - wood+ore
-						//FE - mercury+sulfur+crystal+gem
-						bonus.info2 = reader.readUInt32(); //count
-						break;
-					}
-				default:
-					logGlobal->warn("Corrupted h3c file");
-					break;
-				}
-				ret.bonusesToChoose.push_back(bonus);
-			}
-			break;
-		}
-	case 2: //reading of players (colors / scenarios ?) player can choose
-		{
-			ui8 numOfBonuses = reader.readUInt8();
-			for (int g=0; g<numOfBonuses; ++g)
-			{
-				CScenarioTravel::STravelBonus bonus;
-				bonus.type = CScenarioTravel::STravelBonus::HEROES_FROM_PREVIOUS_SCENARIO;
-				bonus.info1 = reader.readUInt8(); //player color
-				bonus.info2 = reader.readUInt8(); //from what scenario
-
-				ret.bonusesToChoose.push_back(bonus);
-			}
-			break;
-		}
-	case 3: //heroes player can choose between
-		{
-			ui8 numOfBonuses = reader.readUInt8();
-			for (int g=0; g<numOfBonuses; ++g)
-			{
-				CScenarioTravel::STravelBonus bonus;
-				bonus.type = CScenarioTravel::STravelBonus::HERO;
-				bonus.info1 = reader.readUInt8(); //player color
-				bonus.info2 = reader.readUInt16(); //hero, FF FF - random
-
-				ret.bonusesToChoose.push_back(bonus);
-			}
-			break;
-		}
-	default:
-		{
-			logGlobal->warn("Corrupted h3c file");
-			break;
-		}
-	}
-
-	return ret;
-}
-
-std::vector< std::vector<ui8> > CCampaignHandler::getFile(std::unique_ptr<CInputStream> file, bool headerOnly)
-{
-	CCompressedStream stream(std::move(file), true);
-
-	std::vector< std::vector<ui8> > ret;
-	do
-	{
-		std::vector<ui8> block(stream.getSize());
-		stream.read(block.data(), block.size());
-		ret.push_back(block);
-	}
-	while (!headerOnly && stream.getNextBlock());
-
-	return ret;
-}
-
-bool CCampaign::conquerable( int whichScenario ) const
-{
-	//check for void scenraio
-	if (!scenarios[whichScenario].isNotVoid())
-	{
-		return false;
-	}
-
-	if (scenarios[whichScenario].conquered)
-	{
-		return false;
-	}
-	//check preconditioned regions
-	for (int g=0; g<scenarios.size(); ++g)
-	{
-		if( vstd::contains(scenarios[whichScenario].preconditionRegions, g) && !scenarios[g].conquered)
-			return false; //prerequisite does not met
-
-	}
-	return true;
-}
-
-bool CCampaignScenario::isNotVoid() const
-{
-	return !mapName.empty();
-}
-
-const CGHeroInstance * CCampaignScenario::strongestHero(const PlayerColor & owner)
-{
-	std::function<bool(JsonNode & node)> isOwned = [owner](JsonNode & node)
-	{
-		auto * h = CCampaignState::crossoverDeserialize(node);
-		bool result = h->tempOwner == owner;
-		vstd::clear_pointer(h);
-		return result;
-	};
-	auto ownedHeroes = crossoverHeroes | boost::adaptors::filtered(isOwned);
-
-	auto i = vstd::maxElementByFun(ownedHeroes, [](JsonNode & node)
-	{
-		auto * h = CCampaignState::crossoverDeserialize(node);
-		double result = h->getHeroStrength();
-		vstd::clear_pointer(h);
-		return result;
-	});
-	return i == ownedHeroes.end() ? nullptr : CCampaignState::crossoverDeserialize(*i);
-}
-
-std::vector<CGHeroInstance *> CCampaignScenario::getLostCrossoverHeroes()
-{
-	std::vector<CGHeroInstance *> lostCrossoverHeroes;
-	if(conquered)
-	{
-		for(auto node2 : placedCrossoverHeroes)
-		{
-			auto * hero = CCampaignState::crossoverDeserialize(node2);
-			auto it = range::find_if(crossoverHeroes, [hero](JsonNode node)
-			{
-				auto * h = CCampaignState::crossoverDeserialize(node);
-				bool result = hero->subID == h->subID;
-				vstd::clear_pointer(h);
-				return result;
-			});
-			if(it == crossoverHeroes.end())
-			{
-				lostCrossoverHeroes.push_back(hero);
-			}
-		}
-	}
-	return lostCrossoverHeroes;
-}
-
-void CCampaignState::setCurrentMapAsConquered(const std::vector<CGHeroInstance *> & heroes)
-{
-	camp->scenarios[*currentMap].crossoverHeroes.clear();
-	for(CGHeroInstance * hero : heroes)
-	{
-		camp->scenarios[*currentMap].crossoverHeroes.push_back(crossoverSerialize(hero));
-	}
-
-	mapsConquered.push_back(*currentMap);
-	mapsRemaining -= *currentMap;
-	camp->scenarios[*currentMap].conquered = true;
-}
-
-std::optional<CScenarioTravel::STravelBonus> CCampaignState::getBonusForCurrentMap() const
-{
-	auto bonuses = getCurrentScenario().travelOptions.bonusesToChoose;
-	assert(chosenCampaignBonuses.count(*currentMap) || bonuses.size() == 0);
-
-	if(bonuses.empty())
-		return std::optional<CScenarioTravel::STravelBonus>();
-	else
-		return bonuses[currentBonusID()];
-}
-
-const CCampaignScenario & CCampaignState::getCurrentScenario() const
-{
-	return camp->scenarios[*currentMap];
-}
-
-CCampaignScenario & CCampaignState::getCurrentScenario()
-{
-	return camp->scenarios[*currentMap];
-}
-
-ui8 CCampaignState::currentBonusID() const
-{
-	return chosenCampaignBonuses.at(*currentMap);
-}
-
-CCampaignState::CCampaignState( std::unique_ptr<CCampaign> _camp ) : camp(std::move(_camp))
-{
-	for(int i = 0; i < camp->scenarios.size(); i++)
-	{
-		if(vstd::contains(camp->mapPieces, i)) //not all maps must be present in a campaign
-			mapsRemaining.push_back(i);
-	}
-}
-
-CMap * CCampaignState::getMap(int scenarioId) const
-{
-	// FIXME: there is certainly better way to handle maps inside campaigns
-	if(scenarioId == -1)
-		scenarioId = currentMap.value();
-	
-	CMapService mapService;
-	std::string scenarioName = camp->header.filename.substr(0, camp->header.filename.find('.'));
-	boost::to_lower(scenarioName);
-	scenarioName += ':' + std::to_string(scenarioId);
-	std::string & mapContent = camp->mapPieces.find(scenarioId)->second;
-	const auto * buffer = reinterpret_cast<const ui8 *>(mapContent.data());
-	return mapService.loadMap(buffer, static_cast<int>(mapContent.size()), scenarioName, camp->header.modName, camp->header.encoding).release();
-}
-
-std::unique_ptr<CMapHeader> CCampaignState::getHeader(int scenarioId) const
-{
-	if(scenarioId == -1)
-		scenarioId = currentMap.value();
-	
-	CMapService mapService;
-	std::string scenarioName = camp->header.filename.substr(0, camp->header.filename.find('.'));
-	boost::to_lower(scenarioName);
-	scenarioName += ':' + std::to_string(scenarioId);
-	std::string & mapContent = camp->mapPieces.find(scenarioId)->second;
-	const auto * buffer = reinterpret_cast<const ui8 *>(mapContent.data());
-	return mapService.loadMapHeader(buffer, static_cast<int>(mapContent.size()), scenarioName, camp->header.modName, camp->header.encoding);
-}
-
-std::shared_ptr<CMapInfo> CCampaignState::getMapInfo(int scenarioId) const
-{
-	if(scenarioId == -1)
-		scenarioId = currentMap.value();
-
-	auto mapInfo = std::make_shared<CMapInfo>();
-	mapInfo->fileURI = camp->header.filename;
-	mapInfo->mapHeader = getHeader(scenarioId);
-	mapInfo->countPlayers();
-	return mapInfo;
-}
-
-JsonNode CCampaignState::crossoverSerialize(CGHeroInstance * hero)
-{
-	JsonNode node;
-	JsonSerializer handler(nullptr, node);
-	hero->serializeJsonOptions(handler);
-	return node;
-}
-
-CGHeroInstance * CCampaignState::crossoverDeserialize(JsonNode & node)
-{
-	JsonDeserializer handler(nullptr, node);
-	auto * hero = new CGHeroInstance();
-	hero->ID = Obj::HERO;
-	hero->serializeJsonOptions(handler);
-	return hero;
-}
-
-std::string CCampaignHandler::prologVideoName(ui8 index)
-{
-	JsonNode config(ResourceID(std::string("CONFIG/campaignMedia"), EResType::TEXT));
-	auto vids = config["videos"].Vector();
-	if(index < vids.size())
-		return vids[index].String();
-	return "";
-}
-
-std::string CCampaignHandler::prologMusicName(ui8 index)
-{
-	std::vector<std::string> music;
-	return VLC->generaltexth->translate("core.cmpmusic." + std::to_string(static_cast<int>(index)));
-}
-
-std::string CCampaignHandler::prologVoiceName(ui8 index)
-{
-	JsonNode config(ResourceID(std::string("CONFIG/campaignMedia"), EResType::TEXT));
-	auto audio = config["voice"].Vector();
-	if(index < audio.size())
-		return audio[index].String();
-	return "";
-}
-
-VCMI_LIB_NAMESPACE_END

+ 0 - 307
lib/mapping/CCampaignHandler.h

@@ -1,307 +0,0 @@
-/*
- * CCampaignHandler.h, part of VCMI engine
- *
- * Authors: listed in file AUTHORS in main folder
- *
- * License: GNU General Public License v2.0 or later
- * Full text of license available in license.txt file, in main folder
- *
- */
-#pragma once
-
-#include "../../lib/GameConstants.h"
-
-VCMI_LIB_NAMESPACE_BEGIN
-
-struct StartInfo;
-class CGHeroInstance;
-class CBinaryReader;
-class CInputStream;
-class CMap;
-class CMapHeader;
-class CMapInfo;
-class JsonNode;
-
-namespace CampaignVersion
-{
-	enum Version
-	{
-		RoE = 4,
-		AB = 5,
-		SoD = 6,
-		WoG = 6,
-//		Chr = 7, // Heroes Chronicles, likely identical to SoD, untested
-		VCMI = 1
-	};
-
-	const int VCMI_MIN = 1;
-	const int VCMI_MAX = 1;
-}
-
-struct DLL_LINKAGE CampaignRegions
-{
-	std::string campPrefix;
-	int colorSuffixLength;
-
-	struct DLL_LINKAGE RegionDescription
-	{
-		std::string infix;
-		int xpos, ypos;
-		
-		template <typename Handler> void serialize(Handler &h, const int formatVersion)
-		{
-			h & infix;
-			h & xpos;
-			h & ypos;
-		}
-		
-		static CampaignRegions::RegionDescription fromJson(const JsonNode & node);
-	};
-
-	std::vector<RegionDescription> regions;
-	
-	template <typename Handler> void serialize(Handler &h, const int formatVersion)
-	{
-		h & campPrefix;
-		h & colorSuffixLength;
-		h & regions;
-	}
-	
-	static CampaignRegions fromJson(const JsonNode & node);
-	static CampaignRegions getLegacy(int campId);
-};
-
-class DLL_LINKAGE CCampaignHeader
-{
-public:
-	si32 version = 0; //4 - RoE, 5 - AB, 6 - SoD, WoG and HotA
-	CampaignRegions campaignRegions;
-	int numberOfScenarios = 0;
-	std::string name, description;
-	bool difficultyChoosenByPlayer = false;
-	bool valid = false;
-
-	std::string filename;
-	std::string modName;
-	std::string encoding;
-	
-	void loadLegacyData(ui8 campId);
-
-	template <typename Handler> void serialize(Handler &h, const int formatVersion)
-	{
-		h & version;
-		h & campaignRegions;
-		h & numberOfScenarios;
-		h & name;
-		h & description;
-		h & difficultyChoosenByPlayer;
-		h & filename;
-		h & modName;
-		h & encoding;
-		h & valid;
-	}
-};
-
-class DLL_LINKAGE CScenarioTravel
-{
-public:
-	
-	struct DLL_LINKAGE WhatHeroKeeps
-	{
-		bool experience = false;
-		bool primarySkills = false;
-		bool secondarySkills = false;
-		bool spells = false;
-		bool artifacts = false;
-		
-		template <typename Handler> void serialize(Handler &h, const int formatVersion)
-		{
-			h & experience;
-			h & primarySkills;
-			h & secondarySkills;
-			h & spells;
-			h & artifacts;
-		}
-	};
-	
-	WhatHeroKeeps whatHeroKeeps;
-	
-	//TODO: use typed containers
-	std::set<int> monstersKeptByHero;
-	std::set<int> artifactsKeptByHero;
-
-	ui8 startOptions = 0; //1 - start bonus, 2 - traveling hero, 3 - hero options
-
-	ui8 playerColor = 0; //only for startOptions == 1
-
-	struct DLL_LINKAGE STravelBonus
-	{
-		enum EBonusType {SPELL, MONSTER, BUILDING, ARTIFACT, SPELL_SCROLL, PRIMARY_SKILL, SECONDARY_SKILL, RESOURCE,
-			HEROES_FROM_PREVIOUS_SCENARIO, HERO};
-		EBonusType type = EBonusType::SPELL; //uses EBonusType
-		si32 info1 = 0, info2 = 0, info3 = 0; //purpose depends on type
-
-		bool isBonusForHero() const;
-
-		template <typename Handler> void serialize(Handler &h, const int formatVersion)
-		{
-			h & type;
-			h & info1;
-			h & info2;
-			h & info3;
-		}
-	};
-
-	std::vector<STravelBonus> bonusesToChoose;
-
-	template <typename Handler> void serialize(Handler &h, const int formatVersion)
-	{
-		h & whatHeroKeeps;
-		h & monstersKeptByHero;
-		h & artifactsKeptByHero;
-		h & startOptions;
-		h & playerColor;
-		h & bonusesToChoose;
-	}
-
-};
-
-class DLL_LINKAGE CCampaignScenario
-{
-public:
-	struct DLL_LINKAGE SScenarioPrologEpilog
-	{
-		bool hasPrologEpilog = false;
-		std::string prologVideo; // from CmpMovie.txt
-		std::string prologMusic; // from CmpMusic.txt
-		std::string prologText;
-
-		template <typename Handler> void serialize(Handler &h, const int formatVersion)
-		{
-			h & hasPrologEpilog;
-			h & prologVideo;
-			h & prologMusic;
-			h & prologText;
-		}
-	};
-
-	std::string mapName; //*.h3m
-	std::string scenarioName; //from header. human-readble
-	std::set<ui8> preconditionRegions; //what we need to conquer to conquer this one (stored as bitfield in h3c)
-	ui8 regionColor = 0;
-	ui8 difficulty = 0;
-	bool conquered = false;
-
-	std::string regionText;
-	SScenarioPrologEpilog prolog, epilog;
-
-	CScenarioTravel travelOptions;
-	std::vector<HeroTypeID> keepHeroes; // contains list of heroes which should be kept for next scenario (doesn't matter if they lost)
-	std::vector<JsonNode> crossoverHeroes; // contains all heroes with the same state when the campaign scenario was finished
-	std::vector<JsonNode> placedCrossoverHeroes; // contains all placed crossover heroes defined by hero placeholders when the scenario was started
-
-	void loadPreconditionRegions(ui32 regions);
-	bool isNotVoid() const;
-	// FIXME: due to usage of JsonNode I can't make these methods const
-	const CGHeroInstance * strongestHero(const PlayerColor & owner);
-	std::vector<CGHeroInstance *> getLostCrossoverHeroes(); /// returns a list of crossover heroes which started the scenario, but didn't complete it
-
-	template <typename Handler> void serialize(Handler &h, const int formatVersion)
-	{
-		h & mapName;
-		h & scenarioName;
-		h & preconditionRegions;
-		h & regionColor;
-		h & difficulty;
-		h & conquered;
-		h & regionText;
-		h & prolog;
-		h & epilog;
-		h & travelOptions;
-		h & crossoverHeroes;
-		h & placedCrossoverHeroes;
-		h & keepHeroes;
-	}
-};
-
-class DLL_LINKAGE CCampaign
-{
-public:
-	CCampaignHeader header;
-	std::vector<CCampaignScenario> scenarios;
-	std::map<int, std::string > mapPieces; //binary h3ms, scenario number -> map data
-
-	template <typename Handler> void serialize(Handler &h, const int formatVersion)
-	{
-		h & header;
-		h & scenarios;
-		h & mapPieces;
-	}
-
-	bool conquerable(int whichScenario) const;
-};
-
-class DLL_LINKAGE CCampaignState
-{
-public:
-	std::unique_ptr<CCampaign> camp;
-	std::string fileEncoding;
-	std::vector<ui8> mapsConquered, mapsRemaining;
-	std::optional<si32> currentMap;
-
-	std::map<ui8, ui8> chosenCampaignBonuses;
-
-	void setCurrentMapAsConquered(const std::vector<CGHeroInstance*> & heroes);
-	std::optional<CScenarioTravel::STravelBonus> getBonusForCurrentMap() const;
-	const CCampaignScenario & getCurrentScenario() const;
-	CCampaignScenario & getCurrentScenario();
-	ui8 currentBonusID() const;
-
-	CMap * getMap(int scenarioId = -1) const;
-	std::unique_ptr<CMapHeader> getHeader(int scenarioId = -1) const;
-	std::shared_ptr<CMapInfo> getMapInfo(int scenarioId = -1) const;
-
-	static JsonNode crossoverSerialize(CGHeroInstance * hero);
-	static CGHeroInstance * crossoverDeserialize(JsonNode & node);
-
-	CCampaignState() = default;
-	CCampaignState(std::unique_ptr<CCampaign> _camp);
-
-	template <typename Handler> void serialize(Handler &h, const int version)
-	{
-		h & camp;
-		h & mapsRemaining;
-		h & mapsConquered;
-		h & currentMap;
-		h & chosenCampaignBonuses;
-	}
-};
-
-class DLL_LINKAGE CCampaignHandler
-{
-	static std::string readLocalizedString(CBinaryReader & reader, std::string filename, std::string modName, std::string encoding, std::string identifier);
-	
-	//parsers for VCMI campaigns (*.vcmp)
-	static CCampaignHeader readHeaderFromJson(JsonNode & reader, std::string filename, std::string modName, std::string encoding);
-	static CCampaignScenario readScenarioFromJson(JsonNode & reader);
-	static CScenarioTravel readScenarioTravelFromJson(JsonNode & reader);
-
-	//parsers for original H3C campaigns
-	static CCampaignHeader readHeaderFromMemory(CBinaryReader & reader, std::string filename, std::string modName, std::string encoding);
-	static CCampaignScenario readScenarioFromMemory(CBinaryReader & reader, const CCampaignHeader & header);
-	static CScenarioTravel readScenarioTravelFromMemory(CBinaryReader & reader, int version);
-	/// returns h3c split in parts. 0 = h3c header, 1-end - maps (binary h3m)
-	/// headerOnly - only header will be decompressed, returned vector wont have any maps
-	static std::vector<std::vector<ui8>> getFile(std::unique_ptr<CInputStream> file, bool headerOnly);
-
-	static std::string prologVideoName(ui8 index);
-	static std::string prologMusicName(ui8 index);
-	static std::string prologVoiceName(ui8 index);
-
-public:
-	static CCampaignHeader getHeader( const std::string & name); //name - name of appropriate file
-
-	static std::unique_ptr<CCampaign> getCampaign(const std::string & name); //name - name of appropriate file
-};
-
-VCMI_LIB_NAMESPACE_END

Vissa filer visades inte eftersom för många filer har ändrats