Selaa lähdekoodia

Merge remote-tracking branch 'vcmi/develop' into lobby

Ivan Savenko 1 vuosi sitten
vanhempi
sitoutus
322c5faf63
100 muutettua tiedostoa jossa 608 lisäystä ja 392 poistoa
  1. 11 3
      .github/workflows/github.yml
  2. 2 2
      AI/EmptyAI/CEmptyAI.cpp
  3. 2 2
      AI/EmptyAI/CEmptyAI.h
  4. 27 16
      AI/Nullkiller/AIGateway.cpp
  5. 4 4
      AI/Nullkiller/AIGateway.h
  6. 2 2
      AI/Nullkiller/AIUtility.h
  7. 4 0
      AI/Nullkiller/Engine/Nullkiller.cpp
  8. 1 0
      AI/Nullkiller/Engine/Nullkiller.h
  9. 19 2
      AI/Nullkiller/Engine/PriorityEvaluator.cpp
  10. 1 1
      AI/Nullkiller/Goals/CGoal.h
  11. 21 4
      AI/Nullkiller/Pathfinding/AINodeStorage.cpp
  12. 11 2
      AI/Nullkiller/Pathfinding/AINodeStorage.h
  13. 8 7
      AI/Nullkiller/Pathfinding/Actions/AdventureSpellCastMovementActions.cpp
  14. 2 1
      AI/Nullkiller/Pathfinding/Actions/AdventureSpellCastMovementActions.h
  15. 12 0
      AI/Nullkiller/Pathfinding/Rules/AILayerTransitionRule.cpp
  16. 8 10
      AI/StupidAI/StupidAI.cpp
  17. 2 2
      AI/VCAI/AIUtility.h
  18. 1 1
      AI/VCAI/Goals/AbstractGoal.h
  19. 1 1
      AI/VCAI/Goals/CGoal.h
  20. 2 2
      AI/VCAI/ResourceManager.h
  21. 6 8
      AI/VCAI/VCAI.cpp
  22. 5 5
      AI/VCAI/VCAI.h
  23. 2 2
      CCallback.cpp
  24. 2 2
      CCallback.h
  25. 26 1
      ChangeLog.md
  26. 3 0
      Mods/vcmi/config/vcmi/english.json
  27. 2 0
      Mods/vcmi/config/vcmi/german.json
  28. 12 13
      client/CMT.cpp
  29. 4 4
      client/CPlayerInterface.cpp
  30. 2 2
      client/CPlayerInterface.h
  31. 3 1
      client/CServerHandler.cpp
  32. 10 13
      client/Client.cpp
  33. 2 2
      client/Client.h
  34. 20 9
      client/NetPacksClient.cpp
  35. 2 2
      client/PlayerLocalState.h
  36. 5 5
      client/battle/BattleActionsController.cpp
  37. 4 4
      client/battle/BattleInterface.cpp
  38. 4 2
      client/battle/BattleStacksController.cpp
  39. 14 29
      client/eventsSDL/InputSourceText.cpp
  40. 4 0
      client/lobby/CBonusSelection.cpp
  41. 1 0
      client/lobby/CBonusSelection.h
  42. 14 2
      client/lobby/CLobbyScreen.cpp
  43. 6 0
      client/lobby/OptionsTabBase.cpp
  44. 2 1
      client/mainmenu/CPrologEpilogVideo.cpp
  45. 2 0
      client/renderSDL/SDLImage.cpp
  46. 10 23
      client/widgets/TextControls.cpp
  47. 6 30
      client/widgets/TextControls.h
  48. 1 1
      client/windows/CMapOverview.cpp
  49. 74 7
      client/windows/GUIClasses.cpp
  50. 28 1
      client/windows/GUIClasses.h
  51. 11 9
      cmake_modules/VCMI_lib.cmake
  52. 3 3
      config/bonuses.json
  53. 1 1
      config/difficulty.json
  54. 3 1
      config/gameConfig.json
  55. 2 0
      config/widgets/extraOptionsTab.json
  56. 6 0
      debian/changelog
  57. 1 1
      docs/Readme.md
  58. 1 0
      launcher/eu.vcmi.VCMI.metainfo.xml
  59. 3 0
      launcher/modManager/cmodlist.cpp
  60. 1 1
      launcher/modManager/cmodlistview_moc.cpp
  61. 75 62
      launcher/translation/spanish.ts
  62. 2 2
      lib/CArtHandler.h
  63. 3 3
      lib/CArtifactInstance.h
  64. 7 4
      lib/CBonusTypeHandler.cpp
  65. 2 2
      lib/CBonusTypeHandler.h
  66. 5 5
      lib/CCreatureSet.h
  67. 2 4
      lib/CGameInterface.cpp
  68. 4 4
      lib/CGameInterface.h
  69. 2 2
      lib/CGeneralTextHandler.h
  70. 4 4
      lib/CPlayerState.h
  71. 1 1
      lib/CRandomGenerator.h
  72. 1 1
      lib/CStack.h
  73. 1 1
      lib/Color.h
  74. 1 1
      lib/ConstTransitivePtr.h
  75. 1 1
      lib/ExtraOptionsInfo.h
  76. 1 0
      lib/GameSettings.cpp
  77. 2 1
      lib/GameSettings.h
  78. 1 1
      lib/JsonNode.h
  79. 2 2
      lib/JsonRandom.cpp
  80. 2 2
      lib/LogicalExpression.h
  81. 1 1
      lib/MetaString.h
  82. 1 1
      lib/Point.h
  83. 1 1
      lib/Rect.h
  84. 1 1
      lib/ResourceSet.h
  85. 1 1
      lib/ScriptHandler.h
  86. 6 6
      lib/StartInfo.h
  87. 1 1
      lib/TerrainHandler.h
  88. 1 1
      lib/TurnTimerInfo.h
  89. 2 2
      lib/battle/BattleAction.h
  90. 1 1
      lib/battle/BattleHex.h
  91. 2 5
      lib/battle/BattleInfo.h
  92. 3 2
      lib/battle/CBattleInfoCallback.cpp
  93. 1 1
      lib/battle/CBattleInfoCallback.h
  94. 2 2
      lib/battle/CObstacleInstance.h
  95. 1 1
      lib/battle/SideInBattle.h
  96. 1 1
      lib/battle/SiegeInfo.h
  97. 1 1
      lib/bonuses/Bonus.h
  98. 1 1
      lib/bonuses/BonusList.h
  99. 1 1
      lib/bonuses/CBonusSystemNode.h
  100. 11 11
      lib/bonuses/Limiters.h

+ 11 - 3
.github/workflows/github.yml

@@ -268,18 +268,26 @@ jobs:
       run: |
         ctest --preset ${{matrix.preset}}
 
+    - name: Kill XProtect to work around CPack issue on macOS 
+      if: ${{ startsWith(matrix.platform, 'mac') }}
+      run: |
+        # Cf. https://github.com/actions/runner-images/issues/7522#issuecomment-1556766641
+        echo Killing...; sudo pkill -9 XProtect >/dev/null || true;
+        echo "Waiting..."; counter=0; while pgrep XProtect && ((counter < 20)); do sleep 3; ((counter++)); done
+        pgrep XProtect || true
+
     - name: Pack
       id: cpack
       if: ${{ matrix.pack == 1 }}
       run: |
         cd '${{github.workspace}}/out/build/${{matrix.preset}}'
         CPACK_PATH=`which -a cpack | grep -m1 -v -i chocolatey`
-        "$CPACK_PATH" -C ${{matrix.pack_type}} ${{ matrix.cpack_args }}
+        counter=0; until "$CPACK_PATH" -C ${{matrix.pack_type}} ${{ matrix.cpack_args }} || ((counter > 20)); do sleep 3; ((counter++)); done
         test -f '${{github.workspace}}/CI/${{matrix.platform}}/post_pack.sh' \
           && '${{github.workspace}}/CI/${{matrix.platform}}/post_pack.sh' '${{github.workspace}}' "$(ls '${{ env.VCMI_PACKAGE_FILE_NAME }}'.*)"
         rm -rf _CPack_Packages
 
-    - name: Create android package
+    - name: Create Android package
       if: ${{ startsWith(matrix.platform, 'android') }}
       run: |
         cd android
@@ -414,7 +422,7 @@ jobs:
         name: Android JNI android-64
         path: ${{ github.workspace }}/android/vcmi-app/src/main/jniLibs/
  
-    - name: Create android package
+    - name: Create Android package
       run: |
         cd android
         ./gradlew bundleRelease --info

+ 2 - 2
AI/EmptyAI/CEmptyAI.cpp

@@ -14,11 +14,11 @@
 #include "../../lib/CStack.h"
 #include "../../lib/battle/BattleAction.h"
 
-void CEmptyAI::saveGame(BinarySerializer & h, const int version)
+void CEmptyAI::saveGame(BinarySerializer & h)
 {
 }
 
-void CEmptyAI::loadGame(BinaryDeserializer & h, const int version)
+void CEmptyAI::loadGame(BinaryDeserializer & h)
 {
 }
 

+ 2 - 2
AI/EmptyAI/CEmptyAI.h

@@ -19,8 +19,8 @@ class CEmptyAI : public CGlobalAI
 	std::shared_ptr<CCallback> cb;
 
 public:
-	virtual void saveGame(BinarySerializer & h, const int version) override;
-	virtual void loadGame(BinaryDeserializer & h, const int version) override;
+	virtual void saveGame(BinarySerializer & h) override;
+	virtual void loadGame(BinaryDeserializer & h) override;
 
 	void initGameInterface(std::shared_ptr<Environment> ENV, std::shared_ptr<CCallback> CB) override;
 	void yourTurn(QueryID queryID) override;

+ 27 - 16
AI/Nullkiller/AIGateway.cpp

@@ -586,11 +586,18 @@ void AIGateway::heroGotLevel(const CGHeroInstance * hero, PrimarySkill pskill, s
 
 	requestActionASAP([=]()
 	{ 
+		int sel = 0;
+
 		if(hPtr.validAndSet())
 		{
+			std::unique_lock<std::mutex> lockGuard(nullkiller->aiStateMutex);
+
 			nullkiller->heroManager->update();
-			answerQuery(queryID, nullkiller->heroManager->selectBestSkill(hPtr, skills));
+
+			sel = nullkiller->heroManager->selectBestSkill(hPtr, skills);
 		}
+
+		answerQuery(queryID, sel);
 	});
 }
 
@@ -661,14 +668,18 @@ void AIGateway::showBlockingDialog(const std::string & text, const std::vector<C
 		if(selection) //select from multiple components -> take the last one (they're indexed [1-size])
 			sel = components.size();
 
-		// TODO: Find better way to understand it is Chest of Treasures
-		if(hero.validAndSet()
-			&& components.size() == 2
-			&& components.front().type == ComponentType::RESOURCE
-			&& (nullkiller->heroManager->getHeroRole(hero) != HeroRole::MAIN
-			|| nullkiller->buildAnalyzer->getGoldPreasure() > MAX_GOLD_PEASURE))
 		{
-			sel = 1; // for now lets pick gold from a chest.
+				std::unique_lock<std::mutex> mxLock(nullkiller->aiStateMutex);
+
+				// TODO: Find better way to understand it is Chest of Treasures
+				if(hero.validAndSet()
+					&& components.size() == 2
+					&& components.front().type == ComponentType::RESOURCE
+					&& (nullkiller->heroManager->getHeroRole(hero) != HeroRole::MAIN
+						|| nullkiller->buildAnalyzer->getGoldPreasure() > MAX_GOLD_PEASURE))
+				{
+					sel = 1; // for now lets pick gold from a chest.
+				}
 		}
 
 		answerQuery(askID, sel);
@@ -747,27 +758,25 @@ void AIGateway::showMapObjectSelectDialog(QueryID askID, const Component & icon,
 	requestActionASAP([=](){ answerQuery(askID, selectedObject.getNum()); });
 }
 
-void AIGateway::saveGame(BinarySerializer & h, const int version)
+void AIGateway::saveGame(BinarySerializer & h)
 {
-	LOG_TRACE_PARAMS(logAi, "version '%i'", version);
 	NET_EVENT_HANDLER;
 	nullkiller->memory->removeInvisibleObjects(myCb.get());
 
-	CAdventureAI::saveGame(h, version);
-	serializeInternal(h, version);
+	CAdventureAI::saveGame(h);
+	serializeInternal(h);
 }
 
-void AIGateway::loadGame(BinaryDeserializer & h, const int version)
+void AIGateway::loadGame(BinaryDeserializer & h)
 {
-	LOG_TRACE_PARAMS(logAi, "version '%i'", version);
 	//NET_EVENT_HANDLER;
 
 	#if 0
 	//disabled due to issue 2890
 	registerGoals(h);
 	#endif // 0
-	CAdventureAI::loadGame(h, version);
-	serializeInternal(h, version);
+	CAdventureAI::loadGame(h);
+	serializeInternal(h);
 }
 
 bool AIGateway::makePossibleUpgrades(const CArmedInstance * obj)
@@ -859,6 +868,8 @@ void AIGateway::performObjectInteraction(const CGObjectInstance * obj, HeroPtr h
 		{
 			makePossibleUpgrades(h.get());
 
+			std::unique_lock<std::mutex>  lockGuard(nullkiller->aiStateMutex);
+
 			if(!h->visitedTown->garrisonHero || !nullkiller->isHeroLocked(h->visitedTown->garrisonHero))
 				moveCreaturesToHero(h->visitedTown);
 

+ 4 - 4
AI/Nullkiller/AIGateway.h

@@ -62,7 +62,7 @@ public:
 	void heroVisit(const CGObjectInstance * obj, bool started);
 
 
-	template<typename Handler> void serialize(Handler & h, const int version)
+	template<typename Handler> void serialize(Handler & h)
 	{
 		h & battle;
 		h & remainingQueries;
@@ -119,8 +119,8 @@ public:
 	void showGarrisonDialog(const CArmedInstance * up, const CGHeroInstance * down, bool removableUnits, QueryID queryID) override; //all stacks operations between these objects become allowed, interface has to call onEnd when done
 	void showTeleportDialog(const CGHeroInstance * hero, TeleportChannelID channel, TTeleportExitsList exits, bool impassable, QueryID askID) override;
 	void showMapObjectSelectDialog(QueryID askID, const Component & icon, const MetaString & title, const MetaString & description, const std::vector<ObjectInstanceID> & objects) override;
-	void saveGame(BinarySerializer & h, const int version) override; //saving
-	void loadGame(BinaryDeserializer & h, const int version) override; //loading
+	void saveGame(BinarySerializer & h) override; //saving
+	void loadGame(BinaryDeserializer & h) override; //loading
 	void finish() override;
 
 	void availableCreaturesChanged(const CGDwelling * town) override;
@@ -203,7 +203,7 @@ public:
 	//special function that can be called ONLY from game events handling thread and will send request ASAP
 	void requestActionASAP(std::function<void()> whatToDo);
 
-	template<typename Handler> void serializeInternal(Handler & h, const int version)
+	template<typename Handler> void serializeInternal(Handler & h)
 	{
 		h & nullkiller->memory->knownTeleportChannels;
 		h & nullkiller->memory->knownSubterraneanGates;

+ 2 - 2
AI/Nullkiller/AIUtility.h

@@ -115,7 +115,7 @@ public:
 	bool validAndSet() const;
 
 
-	template<typename Handler> void serialize(Handler & h, const int version)
+	template<typename Handler> void serialize(Handler & h)
 	{
 		h & this->h;
 		h & hid;
@@ -147,7 +147,7 @@ struct ObjectIdRef
 	bool operator<(const ObjectIdRef & rhs) const;
 
 
-	template<typename Handler> void serialize(Handler & h, const int version)
+	template<typename Handler> void serialize(Handler & h)
 	{
 		h & id;
 	}

+ 4 - 0
AI/Nullkiller/Engine/Nullkiller.cpp

@@ -115,6 +115,8 @@ Goals::TTask Nullkiller::choseBestTask(Goals::TSubgoal behavior, int decompositi
 
 void Nullkiller::resetAiState()
 {
+	std::unique_lock<std::mutex> lockGuard(aiStateMutex);
+
 	lockedResources = TResources();
 	scanDepth = ScanDepth::MAIN_FULL;
 	playerID = ai->playerID;
@@ -127,6 +129,8 @@ void Nullkiller::updateAiState(int pass, bool fast)
 {
 	boost::this_thread::interruption_point();
 
+	std::unique_lock<std::mutex> lockGuard(aiStateMutex);
+
 	auto start = std::chrono::high_resolution_clock::now();
 
 	activeHero = nullptr;

+ 1 - 0
AI/Nullkiller/Engine/Nullkiller.h

@@ -73,6 +73,7 @@ public:
 	std::unique_ptr<ArmyFormation> armyFormation;
 	PlayerColor playerID;
 	std::shared_ptr<CCallback> cb;
+	std::mutex aiStateMutex;
 
 	Nullkiller();
 	void init(std::shared_ptr<CCallback> cb, PlayerColor playerID);

+ 19 - 2
AI/Nullkiller/Engine/PriorityEvaluator.cpp

@@ -137,6 +137,18 @@ TResources getCreatureBankResources(const CGObjectInstance * target, const CGHer
 	return sum > 1 ? result / sum : result;
 }
 
+uint64_t getResourcesGoldReward(const TResources & res)
+{
+	int nonGoldResources = res[EGameResID::GEMS]
+		+ res[EGameResID::SULFUR]
+		+ res[EGameResID::WOOD]
+		+ res[EGameResID::ORE]
+		+ res[EGameResID::CRYSTAL]
+		+ res[EGameResID::MERCURY];
+
+	return res[EGameResID::GOLD] + 100 * nonGoldResources;
+}
+
 uint64_t getCreatureBankArmyReward(const CGObjectInstance * target, const CGHeroInstance * hero)
 {
 	auto objectInfo = target->getObjectHandler()->getObjectInfo(target->appearance);
@@ -491,7 +503,7 @@ float RewardEvaluator::getStrategicalValue(const CGObjectInstance * target) cons
 			//Evaluate resources used for construction. Gold is evaluated separately.
 			if (it->resType != EGameResID::GOLD)
 			{
-				sum += 0.1f * getResourceRequirementStrength(it->resType);
+				sum += 0.1f * it->resVal * getResourceRequirementStrength(it->resType);
 			}
 		}
 		return sum;
@@ -529,6 +541,9 @@ float RewardEvaluator::getStrategicalValue(const CGObjectInstance * target) cons
 			? getEnemyHeroStrategicalValue(dynamic_cast<const CGHeroInstance *>(target))
 			: 0;
 
+	case Obj::KEYMASTER:
+		return 0.6f;
+
 	default:
 		return 0;
 	}
@@ -588,6 +603,8 @@ float RewardEvaluator::getSkillReward(const CGObjectInstance * target, const CGH
 	case Obj::PANDORAS_BOX:
 		//Can contains experience, spells, or skills (only on custom maps)
 		return 2.5f;
+	case Obj::PYRAMID:
+		return 3.0f;
 	case Obj::HERO:
 		return ai->cb->getPlayerRelations(target->tempOwner, ai->playerID) == PlayerRelations::ENEMIES
 			? enemyHeroEliminationSkillRewardRatio * dynamic_cast<const CGHeroInstance *>(target)->level
@@ -660,7 +677,7 @@ int32_t RewardEvaluator::getGoldReward(const CGObjectInstance * target, const CG
 	case Obj::WAGON:
 		return 100;
 	case Obj::CREATURE_BANK:
-		return getCreatureBankResources(target, hero)[EGameResID::GOLD];
+		return getResourcesGoldReward(getCreatureBankResources(target, hero));
 	case Obj::CRYPT:
 	case Obj::DERELICT_SHIP:
 		return 3000;

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

@@ -37,7 +37,7 @@ namespace Goals
 		{
 			return new T(static_cast<T const &>(*this)); //casting enforces template instantiation
 		}
-		template<typename Handler> void serialize(Handler & h, const int version)
+		template<typename Handler> void serialize(Handler & h)
 		{
 			h & static_cast<AbstractGoal &>(*this);
 			//h & goalType & isElementar & isAbstract & priority;

+ 21 - 4
AI/Nullkiller/Pathfinding/AINodeStorage.cpp

@@ -35,6 +35,8 @@ const uint64_t MIN_ARMY_STRENGTH_FOR_CHAIN = 5000;
 const uint64_t MIN_ARMY_STRENGTH_FOR_NEXT_ACTOR = 1000;
 const uint64_t CHAIN_MAX_DEPTH = 4;
 
+const bool DO_NOT_SAVE_TO_COMMITED_TILES = false;
+
 AISharedStorage::AISharedStorage(int3 sizes)
 {
 	if(!shared){
@@ -234,6 +236,7 @@ void AINodeStorage::resetTile(const int3 & coord, EPathfindingLayer layer, EPath
 		heroNode.specialAction.reset();
 		heroNode.armyLoss = 0;
 		heroNode.chainOther = nullptr;
+		heroNode.dayFlags = DayFlags::NONE;
 		heroNode.update(coord, layer, accessibility);
 	}
 }
@@ -265,7 +268,8 @@ void AINodeStorage::commit(
 	EPathNodeAction action, 
 	int turn, 
 	int movementLeft, 
-	float cost) const
+	float cost,
+	bool saveToCommited) const
 {
 	destination->action = action;
 	destination->setCost(cost);
@@ -291,10 +295,15 @@ void AINodeStorage::commit(
 		destination->actor->armyValue);
 #endif
 
-	if(destination->turns <= heroChainTurn)
+	if(saveToCommited && destination->turns <= heroChainTurn)
 	{
 		commitedTiles.insert(destination->coord);
 	}
+
+	if(destination->turns == source->turns)
+	{
+		destination->dayFlags = source->dayFlags;
+	}
 }
 
 std::vector<CGPathNode *> AINodeStorage::calculateNeighbours(
@@ -778,7 +787,14 @@ void HeroChainCalculationTask::addHeroChain(const std::vector<ExchangeCandidate>
 			continue;
 		}
 
-		storage.commit(exchangeNode, carrier, carrier->action, chainInfo.turns, chainInfo.moveRemains, chainInfo.getCost());
+		storage.commit(
+			exchangeNode,
+			carrier,
+			carrier->action,
+			chainInfo.turns,
+			chainInfo.moveRemains, 
+			chainInfo.getCost(),
+			DO_NOT_SAVE_TO_COMMITED_TILES);
 
 		if(carrier->specialAction || carrier->chainOther)
 		{
@@ -1070,7 +1086,8 @@ struct TowmPortalFinder
 				EPathNodeAction::TELEPORT_NORMAL,
 				bestNode->turns,
 				bestNode->moveRemains - movementNeeded,
-				movementCost);
+				movementCost,
+				DO_NOT_SAVE_TO_COMMITED_TILES);
 
 			node->theNodeBefore = bestNode;
 			node->addSpecialAction(std::make_shared<AIPathfinding::TownPortalAction>(targetTown));

+ 11 - 2
AI/Nullkiller/Pathfinding/AINodeStorage.h

@@ -41,11 +41,19 @@ namespace AIPathfinding
 	const int CHAIN_MAX_DEPTH = 4;
 }
 
+enum DayFlags : ui8
+{
+	NONE = 0,
+	FLY_CAST = 1,
+	WATER_WALK_CAST = 2
+};
+
 struct AIPathNode : public CGPathNode
 {
 	uint64_t danger;
 	uint64_t armyLoss;
-	int32_t manaCost;
+	int16_t manaCost;
+	DayFlags dayFlags;
 	const AIPathNode * chainOther;
 	std::shared_ptr<const SpecialAction> specialAction;
 	const ChainActor * actor;
@@ -200,7 +208,8 @@ public:
 		EPathNodeAction action,
 		int turn,
 		int movementLeft,
-		float cost) const;
+		float cost,
+		bool saveToCommited = true) const;
 
 	inline const AIPathNode * getAINode(const CGPathNode * node) const
 	{

+ 8 - 7
AI/Nullkiller/Pathfinding/Actions/AdventureSpellCastMovementActions.cpp

@@ -22,18 +22,18 @@ namespace NKAI
 
 namespace AIPathfinding
 {
-	AdventureCastAction::AdventureCastAction(SpellID spellToCast, const CGHeroInstance * hero)
-		:spellToCast(spellToCast), hero(hero)
+	AdventureCastAction::AdventureCastAction(SpellID spellToCast, const CGHeroInstance * hero, DayFlags flagsToAdd)
+		:spellToCast(spellToCast), hero(hero), flagsToAdd(flagsToAdd)
 	{
 		manaCost = hero->getSpellCost(spellToCast.toSpell());
 	}
 
 	WaterWalkingAction::WaterWalkingAction(const CGHeroInstance * hero)
-		:AdventureCastAction(SpellID::WATER_WALK, hero)
+		:AdventureCastAction(SpellID::WATER_WALK, hero, DayFlags::WATER_WALK_CAST)
 	{ }
 
 	AirWalkingAction::AirWalkingAction(const CGHeroInstance * hero)
-		: AdventureCastAction(SpellID::FLY, hero)
+		: AdventureCastAction(SpellID::FLY, hero, DayFlags::FLY_CAST)
 	{
 	}
 
@@ -41,11 +41,12 @@ namespace AIPathfinding
 		const CGHeroInstance * hero,
 		CDestinationNodeInfo & destination,
 		const PathNodeInfo & source,
-		AIPathNode * dstMode,
+		AIPathNode * dstNode,
 		const AIPathNode * srcNode) const
 	{
-		dstMode->manaCost = srcNode->manaCost + manaCost;
-		dstMode->theNodeBefore = source.node;
+		dstNode->manaCost = srcNode->manaCost + manaCost;
+		dstNode->theNodeBefore = source.node;
+		dstNode->dayFlags = static_cast<DayFlags>(dstNode->dayFlags | flagsToAdd);
 	}
 
 	void AdventureCastAction::execute(const CGHeroInstance * hero) const

+ 2 - 1
AI/Nullkiller/Pathfinding/Actions/AdventureSpellCastMovementActions.h

@@ -24,9 +24,10 @@ namespace AIPathfinding
 		SpellID spellToCast;
 		const CGHeroInstance * hero;
 		int manaCost;
+		DayFlags flagsToAdd;
 
 	public:
-		AdventureCastAction(SpellID spellToCast, const CGHeroInstance * hero);
+		AdventureCastAction(SpellID spellToCast, const CGHeroInstance * hero, DayFlags flagsToAdd = DayFlags::NONE);
 
 		virtual void execute(const CGHeroInstance * hero) const override;
 

+ 12 - 0
AI/Nullkiller/Pathfinding/Rules/AILayerTransitionRule.cpp

@@ -61,6 +61,12 @@ namespace AIPathfinding
 
 		if(source.node->layer == EPathfindingLayer::LAND && destination.node->layer == EPathfindingLayer::WATER)
 		{
+			if(nodeStorage->getAINode(source.node)->dayFlags & DayFlags::WATER_WALK_CAST)
+			{
+				destination.blocked = false;
+				return;
+			}
+
 			auto action = waterWalkingActions.find(nodeStorage->getHero(source.node));
 
 			if(action != waterWalkingActions.end() && tryUseSpecialAction(destination, source, action->second, EPathNodeAction::NORMAL))
@@ -73,6 +79,12 @@ namespace AIPathfinding
 
 		if(source.node->layer == EPathfindingLayer::LAND && destination.node->layer == EPathfindingLayer::AIR)
 		{
+			if(nodeStorage->getAINode(source.node)->dayFlags & DayFlags::FLY_CAST)
+			{
+				destination.blocked = false;
+				return;
+			}
+
 			auto action = airWalkingActions.find(nodeStorage->getHero(source.node));
 
 			if(action != airWalkingActions.end() && tryUseSpecialAction(destination, source, action->second, EPathNodeAction::NORMAL))

+ 8 - 10
AI/StupidAI/StupidAI.cpp

@@ -16,8 +16,6 @@
 #include "../../lib/battle/BattleAction.h"
 #include "../../lib/battle/BattleInfo.h"
 
-static std::shared_ptr<CBattleCallback> cbc;
-
 CStupidAI::CStupidAI()
 	: side(-1)
 	, wasWaitingForRealize(false)
@@ -41,7 +39,7 @@ void CStupidAI::initBattleInterface(std::shared_ptr<Environment> ENV, std::share
 {
 	print("init called, saving ptr to IBattleCallback");
 	env = ENV;
-	cbc = cb = CB;
+	cb = CB;
 
 	wasWaitingForRealize = CB->waitTillRealize;
 	wasUnlockingGs = CB->unlockGsWhenWaiting;
@@ -73,11 +71,11 @@ public:
 	std::vector<BattleHex> attackFrom; //for melee fight
 	EnemyInfo(const CStack * _s) : s(_s), adi(0), adr(0)
 	{}
-	void calcDmg(const BattleID & battleID, const CStack * ourStack)
+	void calcDmg(std::shared_ptr<CBattleCallback> cb, const BattleID & battleID, const CStack * ourStack)
 	{
 		// FIXME: provide distance info for Jousting bonus
 		DamageEstimation retal;
-		DamageEstimation dmg = cbc->getBattle(battleID)->battleEstimateDamage(ourStack, s, 0, &retal);
+		DamageEstimation dmg = cb->getBattle(battleID)->battleEstimateDamage(ourStack, s, 0, &retal);
 		adi = static_cast<int>((dmg.damage.min + dmg.damage.max) / 2);
 		adr = static_cast<int>((retal.damage.min + retal.damage.max) / 2);
 	}
@@ -93,14 +91,14 @@ bool isMoreProfitable(const EnemyInfo &ei1, const EnemyInfo& ei2)
 	return (ei1.adi-ei1.adr) < (ei2.adi - ei2.adr);
 }
 
-static bool willSecondHexBlockMoreEnemyShooters(const BattleID & battleID, const BattleHex &h1, const BattleHex &h2)
+static bool willSecondHexBlockMoreEnemyShooters(std::shared_ptr<CBattleCallback> cb, const BattleID & battleID, const BattleHex &h1, const BattleHex &h2)
 {
 	int shooters[2] = {0}; //count of shooters on hexes
 
 	for(int i = 0; i < 2; i++)
 	{
 		for (auto & neighbour : (i ? h2 : h1).neighbouringTiles())
-			if(const auto * s = cbc->getBattle(battleID)->battleGetUnitByPos(neighbour))
+			if(const auto * s = cb->getBattle(battleID)->battleGetUnitByPos(neighbour))
 				if(s->isShooter())
 					shooters[i]++;
 	}
@@ -172,10 +170,10 @@ void CStupidAI::activeStack(const BattleID & battleID, const CStack * stack)
 	}
 
 	for ( auto & enemy : enemiesReachable )
-		enemy.calcDmg(battleID, stack);
+		enemy.calcDmg(cb, battleID, stack);
 
 	for ( auto & enemy : enemiesShootable )
-		enemy.calcDmg(battleID, stack);
+		enemy.calcDmg(cb, battleID, stack);
 
 	if(enemiesShootable.size())
 	{
@@ -186,7 +184,7 @@ void CStupidAI::activeStack(const BattleID & battleID, const CStack * stack)
 	else if(enemiesReachable.size())
 	{
 		const EnemyInfo &ei= *std::max_element(enemiesReachable.begin(), enemiesReachable.end(), &isMoreProfitable);
-		BattleHex targetHex = *std::max_element(ei.attackFrom.begin(), ei.attackFrom.end(), [&](auto a, auto b) { return willSecondHexBlockMoreEnemyShooters(battleID, a, b);});
+		BattleHex targetHex = *std::max_element(ei.attackFrom.begin(), ei.attackFrom.end(), [&](auto a, auto b) { return willSecondHexBlockMoreEnemyShooters(cb, battleID, a, b);});
 
 		cb->battleMakeUnitAction(battleID, BattleAction::makeMeleeAttack(stack, ei.s->getPosition(), targetHex));
 		return;

+ 2 - 2
AI/VCAI/AIUtility.h

@@ -70,7 +70,7 @@ public:
 	bool validAndSet() const;
 
 
-	template<typename Handler> void serialize(Handler & h, const int version)
+	template<typename Handler> void serialize(Handler & h)
 	{
 		h & this->h;
 		h & hid;
@@ -102,7 +102,7 @@ struct ObjectIdRef
 	bool operator<(const ObjectIdRef & rhs) const;
 
 
-	template<typename Handler> void serialize(Handler & h, const int version)
+	template<typename Handler> void serialize(Handler & h)
 	{
 		h & id;
 	}

+ 1 - 1
AI/VCAI/Goals/AbstractGoal.h

@@ -176,7 +176,7 @@ namespace Goals
 			return !(*this == g);
 		}
 
-		template<typename Handler> void serialize(Handler & h, const int version)
+		template<typename Handler> void serialize(Handler & h)
 		{
 			h & goalType;
 			h & isElementar;

+ 1 - 1
AI/VCAI/Goals/CGoal.h

@@ -69,7 +69,7 @@ namespace Goals
 
 			return ptr;
 		}
-		template<typename Handler> void serialize(Handler & h, const int version)
+		template<typename Handler> void serialize(Handler & h)
 		{
 			h & static_cast<AbstractGoal &>(*this);
 			//h & goalType & isElementar & isAbstract & priority;

+ 2 - 2
AI/VCAI/ResourceManager.h

@@ -28,7 +28,7 @@ struct DLL_EXPORT ResourceObjective
 	Goals::TSubgoal goal; //what for (build, gather army etc...)
 
 	 //TODO: register?
-	template<typename Handler> void serializeInternal(Handler & h, const int version)
+	template<typename Handler> void serializeInternal(Handler & h)
 	{
 		h & resources;
 		//h & goal; //FIXME: goal serialization is broken
@@ -105,7 +105,7 @@ private:
 	void dumpToLog() const;
 
 	//TODO: register?
-	template<typename Handler> void serializeInternal(Handler & h, const int version)
+	template<typename Handler> void serializeInternal(Handler & h)
 	{
 		h & saving;
 		h & queue;

+ 6 - 8
AI/VCAI/VCAI.cpp

@@ -746,9 +746,8 @@ void VCAI::showMapObjectSelectDialog(QueryID askID, const Component & icon, cons
 	requestActionASAP([=](){ answerQuery(askID, selectedObject.getNum()); });
 }
 
-void VCAI::saveGame(BinarySerializer & h, const int version)
+void VCAI::saveGame(BinarySerializer & h)
 {
-	LOG_TRACE_PARAMS(logAi, "version '%i'", version);
 	NET_EVENT_HANDLER;
 	validateVisitableObjs();
 
@@ -756,21 +755,20 @@ void VCAI::saveGame(BinarySerializer & h, const int version)
 	//disabled due to issue 2890
 	registerGoals(h);
 	#endif // 0
-	CAdventureAI::saveGame(h, version);
-	serializeInternal(h, version);
+	CAdventureAI::saveGame(h);
+	serializeInternal(h);
 }
 
-void VCAI::loadGame(BinaryDeserializer & h, const int version)
+void VCAI::loadGame(BinaryDeserializer & h)
 {
-	LOG_TRACE_PARAMS(logAi, "version '%i'", version);
 	//NET_EVENT_HANDLER;
 
 	#if 0
 	//disabled due to issue 2890
 	registerGoals(h);
 	#endif // 0
-	CAdventureAI::loadGame(h, version);
-	serializeInternal(h, version);
+	CAdventureAI::loadGame(h);
+	serializeInternal(h);
 }
 
 void makePossibleUpgrades(const CArmedInstance * obj)

+ 5 - 5
AI/VCAI/VCAI.h

@@ -68,7 +68,7 @@ public:
 	void heroVisit(const CGObjectInstance * obj, bool started);
 
 
-	template<typename Handler> void serialize(Handler & h, const int version)
+	template<typename Handler> void serialize(Handler & h)
 	{
 		h & battle;
 		h & remainingQueries;
@@ -152,8 +152,8 @@ public:
 	void showGarrisonDialog(const CArmedInstance * up, const CGHeroInstance * down, bool removableUnits, QueryID queryID) override; //all stacks operations between these objects become allowed, interface has to call onEnd when done
 	void showTeleportDialog(const CGHeroInstance * hero, TeleportChannelID channel, TTeleportExitsList exits, bool impassable, QueryID askID) override;
 	void showMapObjectSelectDialog(QueryID askID, const Component & icon, const MetaString & title, const MetaString & description, const std::vector<ObjectInstanceID> & objects) override;
-	void saveGame(BinarySerializer & h, const int version) override; //saving
-	void loadGame(BinaryDeserializer & h, const int version) override; //loading
+	void saveGame(BinarySerializer & h) override; //saving
+	void loadGame(BinaryDeserializer & h) override; //loading
 	void finish() override;
 
 	void availableCreaturesChanged(const CGDwelling * town) override;
@@ -301,7 +301,7 @@ public:
 	}
 	#endif
 
-	template<typename Handler> void serializeInternal(Handler & h, const int version)
+	template<typename Handler> void serializeInternal(Handler & h)
 	{
 		h & knownTeleportChannels;
 		h & knownSubterraneanGates;
@@ -341,7 +341,7 @@ public:
 							//we have to explicitly ignore invalid goal class type id
 							h & typeId;
 							Goals::AbstractGoal ignored2;
-							ignored2.serialize(h, version);
+							ignored2.serialize(h);
 						}
 					}
 				}

+ 2 - 2
CCallback.cpp

@@ -261,12 +261,12 @@ void CCallback::setFormation(const CGHeroInstance * hero, EArmyFormation mode)
 	sendRequest(&pack);
 }
 
-void CCallback::recruitHero(const CGObjectInstance *townOrTavern, const CGHeroInstance *hero)
+void CCallback::recruitHero(const CGObjectInstance *townOrTavern, const CGHeroInstance *hero, const HeroTypeID & nextHero)
 {
 	assert(townOrTavern);
 	assert(hero);
 
-	HireHero pack(hero->getHeroType(), townOrTavern->id);
+	HireHero pack(hero->getHeroType(), townOrTavern->id, nextHero);
 	pack.player = *player;
 	sendRequest(&pack);
 }

+ 2 - 2
CCallback.h

@@ -73,7 +73,7 @@ public:
 	virtual void castSpell(const CGHeroInstance *hero, SpellID spellID, const int3 &pos = int3(-1, -1, -1))=0; //cast adventure map spell
 
 	//town
-	virtual void recruitHero(const CGObjectInstance *townOrTavern, const CGHeroInstance *hero)=0;
+	virtual void recruitHero(const CGObjectInstance *townOrTavern, const CGHeroInstance *hero, const HeroTypeID & nextHero=HeroTypeID::NONE)=0;
 	virtual bool buildBuilding(const CGTownInstance *town, BuildingID buildingID)=0;
 	virtual void recruitCreatures(const CGDwelling *obj, const CArmedInstance * dst, CreatureID ID, ui32 amount, si32 level=-1)=0;
 	virtual bool upgradeCreature(const CArmedInstance *obj, SlotID stackPos, CreatureID newID=CreatureID::NONE)=0; //if newID==-1 then best possible upgrade will be made
@@ -185,7 +185,7 @@ public:
 	void trade(const IMarket * market, EMarketMode mode, TradeItemSell id1, TradeItemBuy id2, ui32 val1, const CGHeroInstance * hero = nullptr) override;
 	void trade(const IMarket * market, EMarketMode mode, const std::vector<TradeItemSell> & id1, const std::vector<TradeItemBuy> & id2, const std::vector<ui32> & val1, const CGHeroInstance * hero = nullptr) override;
 	void setFormation(const CGHeroInstance * hero, EArmyFormation mode) override;
-	void recruitHero(const CGObjectInstance *townOrTavern, const CGHeroInstance *hero) override;
+	void recruitHero(const CGObjectInstance *townOrTavern, const CGHeroInstance *hero, const HeroTypeID & nextHero=HeroTypeID::NONE) override;
 	void save(const std::string &fname) override;
 	void sendMessage(const std::string &mess, const CGObjectInstance * currentObject = nullptr) override;
 	void gamePause(bool pause) override;

+ 26 - 1
ChangeLog.md

@@ -1,4 +1,4 @@
-# 1.4.3 -> 1.5.0
+# 1.4.5 -> 1.5.0
 
 ### General
 * Added Chinese translation to map editor
@@ -10,6 +10,31 @@
 * Added status bar to the backpack window
 * Quick backpack window is now only available when enabled Interface enhancements
 
+# 1.4.4 -> 1.4.5
+
+### Stability
+* Fixed crash on creature spellcasting
+* Fixed crash on unit entering magical obstacles such as quicksands
+* Fixed freeze on map loading on some systems
+* Fixed crash on attempt to start campaign with unsupported map
+* Fixed crash on opening creature information window with invalid SPELL_IMMUNITY bonus
+
+### Random Maps Generator
+* Fixed placement of guards sometimes resulting into open connection into third zone
+* Fixed rare crash on multithreaded access during placement of artifacts or wandering monsters
+
+### Map Editor
+* Fixed inspector using wrong editor for some values
+
+### AI
+* Fixed bug leading to AI not attacking wandering monsters in some cases
+* Fixed crash on using StupidAI for autocombat or for enemy players 
+
+# 1.4.3 -> 1.4.4
+
+### General
+* Fixed crash on generation of random maps
+
 # 1.4.2 -> 1.4.3
 
 ### General

+ 3 - 0
Mods/vcmi/config/vcmi/english.json

@@ -91,6 +91,7 @@
 	"vcmi.lobby.room.type" : "Room Type",
 	"vcmi.lobby.room.mode" : "Game Mode",
 
+	"vcmi.client.errors.invalidMap" : "{Invalid map or campaign}\n\nFailed to start game! Selected map or campaign might be invalid or corrupted. Reason:\n%s",
 	"vcmi.client.errors.missingCampaigns" : "{Missing data files}\n\nCampaigns data files were not found! You may be using incomplete or corrupted Heroes 3 data files. Please reinstall game data.",
 	"vcmi.server.errors.existingProcess" : "Another VCMI server process is running. Please terminate it before starting a new game.",
 	"vcmi.server.errors.modsToEnable"    : "{Following mods are required}",
@@ -251,6 +252,8 @@
 	"vcmi.heroWindow.openBackpack.hover" : "Open artifact backpack window",
 	"vcmi.heroWindow.openBackpack.help"  : "Opens window that allows easier artifact backpack management.",
 
+	"vcmi.tavernWindow.inviteHero"  : "Invite hero",
+
 	"vcmi.commanderWindow.artifactMessage" : "Do you want to return this artifact to the hero?",
 
 	"vcmi.creatureWindow.showBonuses.hover"    : "Switch to bonuses view",

+ 2 - 0
Mods/vcmi/config/vcmi/german.json

@@ -232,6 +232,8 @@
 	"vcmi.heroWindow.openBackpack.hover" : "Artefakt-Rucksack-Fenster öffnen",
 	"vcmi.heroWindow.openBackpack.help"  : "Öffnet ein Fenster, das die Verwaltung des Artefakt-Rucksacks erleichtert",
 
+	"vcmi.tavernWindow.inviteHero"  : "Helden einladen",
+
 	"vcmi.commanderWindow.artifactMessage" : "Möchtet Ihr diesen Artefakt dem Helden zurückgeben?",
 
 	"vcmi.creatureWindow.showBonuses.hover"    : "Wechsle zur Bonus-Ansicht",

+ 12 - 13
client/CMT.cpp

@@ -412,9 +412,9 @@ static void mainLoop()
 	{
 		if(CSH->client)
 			CSH->endGameplay();
-	}
 
-	GH.windows().clear();
+		GH.windows().clear();
+	}
 
 	CMM.reset();
 
@@ -474,25 +474,24 @@ void handleQuit(bool ask)
 	// FIXME: avoids crash if player attempts to close game while opening is still playing
 	// use cursor handler as indicator that loading is not done yet
 	// proper solution would be to abort init thread (or wait for it to finish)
+	if(!ask)
+	{
+		quitApplication();
+		return;
+	}
+
 	if (!CCS->curh)
 	{
 		quitRequestedDuringOpeningPlayback = true;
 		return;
 	}
 
-	if(ask)
-	{
-		CCS->curh->set(Cursor::Map::POINTER);
+	CCS->curh->set(Cursor::Map::POINTER);
 
-		if (LOCPLINT)
-			LOCPLINT->showYesNoDialog(CGI->generaltexth->allTexts[69], quitApplication, nullptr);
-		else
-			CInfoWindow::showYesNoDialog(CGI->generaltexth->allTexts[69], {}, quitApplication, {}, PlayerColor(1));
-	}
+	if (LOCPLINT)
+		LOCPLINT->showYesNoDialog(CGI->generaltexth->allTexts[69], quitApplication, nullptr);
 	else
-	{
-		quitApplication();
-	}
+		CInfoWindow::showYesNoDialog(CGI->generaltexth->allTexts[69], {}, quitApplication, {}, PlayerColor(1));
 }
 
 void handleFatalError(const std::string & message, bool terminate)

+ 4 - 4
client/CPlayerInterface.cpp

@@ -1150,16 +1150,16 @@ void CPlayerInterface::heroBonusChanged( const CGHeroInstance *hero, const Bonus
 	}
 }
 
-void CPlayerInterface::saveGame( BinarySerializer & h, const int version )
+void CPlayerInterface::saveGame( BinarySerializer & h )
 {
 	EVENT_HANDLER_CALLED_BY_CLIENT;
-	localState->serialize(h, version);
+	localState->serialize(h);
 }
 
-void CPlayerInterface::loadGame( BinaryDeserializer & h, const int version )
+void CPlayerInterface::loadGame( BinaryDeserializer & h )
 {
 	EVENT_HANDLER_CALLED_BY_CLIENT;
-	localState->serialize(h, version);
+	localState->serialize(h);
 	firstCall = -1;
 }
 

+ 2 - 2
client/CPlayerInterface.h

@@ -145,8 +145,8 @@ protected: // Call-ins from server, should not be called directly, but only via
 	void gameOver(PlayerColor player, const EVictoryLossCheckResult & victoryLossCheckResult) override;
 	void playerStartsTurn(PlayerColor player) override; //called before yourTurn on active itnerface
 	void playerEndsTurn(PlayerColor player) override;
-	void saveGame(BinarySerializer & h, const int version) override; //saving
-	void loadGame(BinaryDeserializer & h, const int version) override; //loading
+	void saveGame(BinarySerializer & h) override; //saving
+	void loadGame(BinaryDeserializer & h) override; //loading
 	void showWorldViewEx(const std::vector<ObjectPosInfo> & objectPositions, bool showTerrain) override;
 
 	//for battles

+ 3 - 1
client/CServerHandler.cpp

@@ -610,7 +610,9 @@ bool CServerHandler::validateGameStart(bool allowOnlyAI) const
 void CServerHandler::sendStartGame(bool allowOnlyAI) const
 {
 	verifyStateBeforeStart(allowOnlyAI ? true : settings["session"]["onlyai"].Bool());
-	GH.windows().createAndPushWindow<CLoadingScreen>();
+
+	if(!settings["session"]["headless"].Bool())
+		GH.windows().createAndPushWindow<CLoadingScreen>();
 	
 	LobbyStartGame lsg;
 	if(client)

+ 10 - 13
client/Client.cpp

@@ -226,7 +226,7 @@ void CClient::loadGame(CGameState * initializedGameState)
 			throw std::runtime_error("Cannot open client part of " + CSH->si->mapname);
 
 		std::unique_ptr<CLoadFile> loader (new CLoadFile(clientSaveName));
-		serialize(loader->serializer, loader->serializer.fileVersion);
+		serialize(loader->serializer, loader->serializer.version);
 
 		logNetwork->info("Client data loaded.");
 	}
@@ -239,7 +239,7 @@ void CClient::loadGame(CGameState * initializedGameState)
 	initPlayerInterfaces();
 }
 
-void CClient::serialize(BinarySerializer & h, const int version)
+void CClient::serialize(BinarySerializer & h)
 {
 	assert(h.saving);
 	ui8 players = static_cast<ui8>(playerint.size());
@@ -252,20 +252,17 @@ void CClient::serialize(BinarySerializer & h, const int version)
 		h & i->first;
 		h & i->second->dllName;
 		h & i->second->human;
-		i->second->saveGame(h, version);
+		i->second->saveGame(h);
 	}
 
 #if SCRIPTING_ENABLED
-	if(version >= 800)
-	{
-		JsonNode scriptsState;
-		clientScripts->serializeState(h.saving, scriptsState);
-		h & scriptsState;
-	}
+	JsonNode scriptsState;
+	clientScripts->serializeState(h.saving, scriptsState);
+	h & scriptsState;
 #endif
 }
 
-void CClient::serialize(BinaryDeserializer & h, const int version)
+void CClient::serialize(BinaryDeserializer & h)
 {
 	assert(!h.saving);
 	ui8 players = 0;
@@ -321,7 +318,7 @@ void CClient::serialize(BinaryDeserializer & h, const int version)
 
 		// loadGame needs to be called after initGameInterface to load paths correctly
 		// initGameInterface is called in installNewPlayerInterface
-		nInt->loadGame(h, version);
+		nInt->loadGame(h);
 
 		if (shouldResetInterface)
 		{
@@ -412,7 +409,7 @@ void CClient::initPlayerEnvironments()
 			hasHumanPlayer = true;
 	}
 
-	if(!hasHumanPlayer)
+	if(!hasHumanPlayer && !settings["session"]["headless"].Bool())
 	{
 		Settings session = settings.write["session"];
 		session["spectate"].Bool() = true;
@@ -437,7 +434,7 @@ void CClient::initPlayerInterfaces()
 		if(!vstd::contains(playerint, color))
 		{
 			logNetwork->info("Preparing interface for player %s", color.toString());
-			if(playerInfo.second.isControlledByAI())
+			if(playerInfo.second.isControlledByAI() || settings["session"]["onlyai"].Bool())
 			{
 				bool alliedToHuman = false;
 				for(auto & allyInfo : gs->scenarioOps->playerInfos)

+ 2 - 2
client/Client.h

@@ -131,8 +131,8 @@ public:
 
 	void newGame(CGameState * gameState);
 	void loadGame(CGameState * gameState);
-	void serialize(BinarySerializer & h, const int version);
-	void serialize(BinaryDeserializer & h, const int version);
+	void serialize(BinarySerializer & h);
+	void serialize(BinaryDeserializer & h);
 
 	void save(const std::string & fname);
 	void endGame();

+ 20 - 9
client/NetPacksClient.cpp

@@ -157,6 +157,9 @@ void ApplyClientNetPackVisitor::visitSetMana(SetMana & pack)
 	const CGHeroInstance *h = cl.getHero(pack.hid);
 	callInterfaceIfPresent(cl, h->tempOwner, &IGameEventsReceiver::heroManaPointsChanged, h);
 
+	if(settings["session"]["headless"].Bool())
+		return;
+
 	for (auto window : GH.windows().findWindows<BattleWindow>())
 		window->heroManaPointsChanged(h);
 }
@@ -467,7 +470,8 @@ void ApplyFirstClientNetPackVisitor::visitRemoveObject(RemoveObject & pack)
 			i->second->objectRemoved(o, pack.initiator);
 	}
 
-	CGI->mh->waitForOngoingAnimations();
+	if(CGI->mh)
+		CGI->mh->waitForOngoingAnimations();
 }
 
 void ApplyClientNetPackVisitor::visitRemoveObject(RemoveObject & pack)
@@ -553,9 +557,11 @@ void ApplyClientNetPackVisitor::visitNewStructures(NewStructures & pack)
 	}
 
 	// invalidate section of map view with our object and force an update
-	CGI->mh->onObjectInstantRemove(town, town->getOwner());
-	CGI->mh->onObjectInstantAdd(town, town->getOwner());
-
+	if(CGI->mh)
+	{
+		CGI->mh->onObjectInstantRemove(town, town->getOwner());
+		CGI->mh->onObjectInstantAdd(town, town->getOwner());
+	}
 }
 void ApplyClientNetPackVisitor::visitRazeStructures(RazeStructures & pack)
 {
@@ -566,8 +572,11 @@ void ApplyClientNetPackVisitor::visitRazeStructures(RazeStructures & pack)
 	}
 
 	// invalidate section of map view with our object and force an update
-	CGI->mh->onObjectInstantRemove(town, town->getOwner());
-	CGI->mh->onObjectInstantAdd(town, town->getOwner());
+	if(CGI->mh)
+	{
+		CGI->mh->onObjectInstantRemove(town, town->getOwner());
+		CGI->mh->onObjectInstantAdd(town, town->getOwner());
+	}
 }
 
 void ApplyClientNetPackVisitor::visitSetAvailableCreatures(SetAvailableCreatures & pack)
@@ -651,7 +660,7 @@ void ApplyFirstClientNetPackVisitor::visitSetObjectProperty(SetObjectProperty &
 	}
 
 	// invalidate section of map view with our object and force an update with new flag color
-	if (pack.what == ObjProperty::OWNER)
+	if (pack.what == ObjProperty::OWNER && CGI->mh)
 	{
 		auto object = gs.getObjInstance(pack.id);
 		CGI->mh->onObjectInstantRemove(object, object->getOwner());
@@ -668,7 +677,7 @@ void ApplyClientNetPackVisitor::visitSetObjectProperty(SetObjectProperty & pack)
 	}
 
 	// invalidate section of map view with our object and force an update with new flag color
-	if (pack.what == ObjProperty::OWNER)
+	if (pack.what == ObjProperty::OWNER && CGI->mh)
 	{
 		auto object = gs.getObjInstance(pack.id);
 		CGI->mh->onObjectInstantAdd(object, object->getOwner());
@@ -1023,7 +1032,9 @@ void ApplyClientNetPackVisitor::visitNewObject(NewObject & pack)
 		if(gs.isVisible(obj, i->first))
 			i->second->newObject(obj);
 	}
-	CGI->mh->waitForOngoingAnimations();
+
+	if(CGI->mh)
+		CGI->mh->waitForOngoingAnimations();
 }
 
 void ApplyClientNetPackVisitor::visitSetAvailableArtifacts(SetAvailableArtifacts & pack)

+ 2 - 2
client/PlayerLocalState.h

@@ -47,7 +47,7 @@ public:
 		int spellbookLastTabAdvmap = 4;
 
 		template<typename Handler>
-		void serialize(Handler & h, const int version)
+		void serialize(Handler & h)
 		{
 			h & spellbookLastPageBattle;
 			h & spellbookLastPageAdvmap;
@@ -94,7 +94,7 @@ public:
 	void setSelection(const CArmedInstance *sel);
 
 	template<typename Handler>
-	void serialize(Handler & h, int version)
+	void serialize(Handler & h)
 	{
 		//WARNING: this code is broken and not used. See CClient::loadGame
 		std::map<const CGHeroInstance *, int3> pathsMap; //hero -> dest

+ 5 - 5
client/battle/BattleActionsController.cpp

@@ -750,7 +750,7 @@ void BattleActionsController::actionRealize(PossiblePlayerBattleAction action, B
 
 			if (!spellcastingModeActive())
 			{
-				if (action.spell().toSpell())
+				if (action.spell().hasValue())
 				{
 					owner.giveCommand(EActionType::MONSTER_SPELL, targetHex, action.spell());
 				}
@@ -887,17 +887,17 @@ void BattleActionsController::tryActivateStackSpellcasting(const CStack *casterS
 	{
 		// faerie dragon can cast only one, randomly selected spell until their next move
 		//TODO: faerie dragon type spell should be selected by server
-		const auto * spellToCast = owner.getBattle()->getRandomCastedSpell(CRandomGenerator::getDefault(), casterStack).toSpell();
+		const auto spellToCast = owner.getBattle()->getRandomCastedSpell(CRandomGenerator::getDefault(), casterStack);
 
-		if (spellToCast)
-			creatureSpells.push_back(spellToCast);
+		if (spellToCast.hasValue())
+			creatureSpells.push_back(spellToCast.toSpell());
 	}
 
 	TConstBonusListPtr bl = casterStack->getBonuses(Selector::type()(BonusType::SPELLCASTER));
 
 	for(const auto & bonus : *bl)
 	{
-		if (bonus->additionalInfo[0] <= 0)
+		if (bonus->additionalInfo[0] <= 0 && bonus->subtype.as<SpellID>().hasValue())
 			creatureSpells.push_back(bonus->subtype.as<SpellID>().toSpell());
 	}
 }

+ 4 - 4
client/battle/BattleInterface.cpp

@@ -352,13 +352,13 @@ void BattleInterface::spellCast(const BattleSpellCast * sc)
 	CCS->curh->set(Cursor::Combat::BLOCKED);
 
 	const SpellID spellID = sc->spellID;
-	const CSpell * spell = spellID.toSpell();
-	auto targetedTile = sc->tile;
 
-	assert(spell);
-	if(!spell)
+	if(!spellID.hasValue())
 		return;
 
+	const CSpell * spell = spellID.toSpell();
+	auto targetedTile = sc->tile;
+
 	const AudioPath & castSoundPath = spell->getCastSound();
 
 	if (!castSoundPath.empty())

+ 4 - 2
client/battle/BattleStacksController.cpp

@@ -30,6 +30,8 @@
 #include "../render/Colors.h"
 #include "../render/Canvas.h"
 #include "../render/IRenderHandler.h"
+#include "../render/Graphics.h"
+#include "../render/IFont.h"
 
 #include "../../CCallback.h"
 #include "../../lib/spells/ISpellMechanics.h"
@@ -327,10 +329,10 @@ void BattleStacksController::showStackAmountBox(Canvas & canvas, const CStack *
 			boxPosition = owner.fieldController->hexPositionLocal(frontPos).center() + Point(-8, -14);
 	}
 
-	Point textPosition = amountBG->dimensions()/2 + boxPosition + Point(0, 1);
+	Point textPosition = Point(amountBG->dimensions().x/2 + boxPosition.x, boxPosition.y + graphics->fonts[EFonts::FONT_TINY]->getLineHeight() - 6);
 
 	canvas.draw(amountBG, boxPosition);
-	canvas.drawText(textPosition, EFonts::FONT_TINY, Colors::WHITE, ETextAlignment::CENTER, TextOperations::formatMetric(stack->getCount(), 4));
+	canvas.drawText(textPosition, EFonts::FONT_TINY, Colors::WHITE, ETextAlignment::TOPCENTER, TextOperations::formatMetric(stack->getCount(), 4));
 }
 
 void BattleStacksController::showStack(Canvas & canvas, const CStack * stack)

+ 14 - 29
client/eventsSDL/InputSourceText.cpp

@@ -21,10 +21,6 @@
 
 #include <SDL_events.h>
 
-#ifdef VCMI_APPLE
-#	include <dispatch/dispatch.h>
-#endif
-
 void InputSourceText::handleEventTextInput(const SDL_TextInputEvent & text)
 {
 	GH.events().dispatchTextInput(text.text);
@@ -37,38 +33,27 @@ void InputSourceText::handleEventTextEditing(const SDL_TextEditingEvent & text)
 
 void InputSourceText::startTextInput(const Rect & whereInput)
 {
-
-#ifdef VCMI_APPLE
-	dispatch_async(dispatch_get_main_queue(), ^{
-#endif
-
-	Rect rectInScreenCoordinates = GH.screenHandler().convertLogicalPointsToWindow(whereInput);
-	SDL_Rect textInputRect = CSDL_Ext::toSDL(rectInScreenCoordinates);
-
-	SDL_SetTextInputRect(&textInputRect);
-
-	if (SDL_IsTextInputActive() == SDL_FALSE)
+	GH.dispatchMainThread([whereInput]()
 	{
-		SDL_StartTextInput();
-	}
+		Rect rectInScreenCoordinates = GH.screenHandler().convertLogicalPointsToWindow(whereInput);
+		SDL_Rect textInputRect = CSDL_Ext::toSDL(rectInScreenCoordinates);
+
+		SDL_SetTextInputRect(&textInputRect);
 
-#ifdef VCMI_APPLE
+		if (SDL_IsTextInputActive() == SDL_FALSE)
+		{
+			SDL_StartTextInput();
+		}
 	});
-#endif
 }
 
 void InputSourceText::stopTextInput()
 {
-#ifdef VCMI_APPLE
-	dispatch_async(dispatch_get_main_queue(), ^{
-#endif
-
-	if (SDL_IsTextInputActive() == SDL_TRUE)
+	GH.dispatchMainThread([]()
 	{
-		SDL_StopTextInput();
-	}
-
-#ifdef VCMI_APPLE
+		if (SDL_IsTextInputActive() == SDL_TRUE)
+		{
+			SDL_StopTextInput();
+		}
 	});
-#endif
 }

+ 4 - 0
client/lobby/CBonusSelection.cpp

@@ -72,6 +72,7 @@ CBonusSelection::CBonusSelection()
 
 	buttonStart = std::make_shared<CButton>(Point(475, 536), AnimationPath::builtin("CBBEGIB.DEF"), CButton::tooltip(), std::bind(&CBonusSelection::startMap, this), EShortcut::GLOBAL_ACCEPT);
 	buttonRestart = std::make_shared<CButton>(Point(475, 536), AnimationPath::builtin("CBRESTB.DEF"), CButton::tooltip(), std::bind(&CBonusSelection::restartMap, this), EShortcut::GLOBAL_ACCEPT);
+	buttonVideo = std::make_shared<CButton>(Point(705, 214), AnimationPath::builtin("CBVIDEB.DEF"), CButton::tooltip(), [this](){ GH.windows().createAndPushWindow<CPrologEpilogVideo>(getCampaign()->scenario(CSH->campaignMap).prolog, [this](){ redraw(); }); });
 	buttonBack = std::make_shared<CButton>(Point(624, 536), AnimationPath::builtin("CBCANCB.DEF"), CButton::tooltip(), std::bind(&CBonusSelection::goBack, this), EShortcut::GLOBAL_CANCEL);
 
 	campaignName = std::make_shared<CLabel>(481, 28, FONT_BIG, ETextAlignment::TOPLEFT, Colors::YELLOW, CSH->si->getCampaignName());
@@ -309,6 +310,7 @@ void CBonusSelection::updateAfterStateChange()
 	if(CSH->state != EClientState::GAMEPLAY)
 	{
 		buttonRestart->disable();
+		buttonVideo->disable();
 		buttonStart->enable();
 		if(!getCampaign()->conqueredScenarios().empty())
 			buttonBack->block(true);
@@ -319,6 +321,7 @@ void CBonusSelection::updateAfterStateChange()
 	{
 		buttonStart->disable();
 		buttonRestart->enable();
+		buttonVideo->enable();
 		buttonBack->block(false);
 		if(buttonDifficultyLeft)
 			buttonDifficultyLeft->disable();
@@ -401,6 +404,7 @@ void CBonusSelection::startMap()
 	//block buttons immediately
 	buttonStart->block(true);
 	buttonRestart->block(true);
+	buttonVideo->block(true);
 	buttonBack->block(true);
 
 	if(LOCPLINT) // we're currently ingame, so ask for starting new map and end game

+ 1 - 0
client/lobby/CBonusSelection.h

@@ -65,6 +65,7 @@ public:
 	std::shared_ptr<CButton> buttonStart;
 	std::shared_ptr<CButton> buttonRestart;
 	std::shared_ptr<CButton> buttonBack;
+	std::shared_ptr<CButton> buttonVideo;
 	std::shared_ptr<CLabel> campaignName;
 	std::shared_ptr<CLabel> labelCampaignDescription;
 	std::shared_ptr<CTextBox> campaignDescription;

+ 14 - 2
client/lobby/CLobbyScreen.cpp

@@ -135,11 +135,23 @@ void CLobbyScreen::toggleTab(std::shared_ptr<CIntObject> tab)
 
 void CLobbyScreen::startCampaign()
 {
-	if(CSH->mi)
-	{
+	if(!CSH->mi)
+		return;
+
+	try {
 		auto ourCampaign = CampaignHandler::getCampaign(CSH->mi->fileURI);
 		CSH->setCampaignState(ourCampaign);
 	}
+	catch (const std::runtime_error &e)
+	{
+		// handle possible exception on map loading. For example campaign that contains map in unsupported format
+		// for example, wog campaigns or hota campaigns without hota map support mod
+		MetaString message;
+		message.appendTextID("vcmi.client.errors.invalidMap");
+		message.replaceRawString(e.what());
+
+		CInfoWindow::showInfoDialog(message.toString(), {});
+	}
 }
 
 void CLobbyScreen::startScenario(bool allowOnlyAI)

+ 6 - 0
client/lobby/OptionsTabBase.cpp

@@ -407,8 +407,14 @@ void OptionsTabBase::recreate()
 	}
 
 	if(auto buttonCheatAllowed = widget<CToggleButton>("buttonCheatAllowed"))
+	{
 		buttonCheatAllowed->setSelectedSilent(SEL->getStartInfo()->extraOptionsInfo.cheatsAllowed);
+		buttonCheatAllowed->block(SEL->screenType == ESelectionScreen::loadGame);
+	}
 
 	if(auto buttonUnlimitedReplay = widget<CToggleButton>("buttonUnlimitedReplay"))
+	{
 		buttonUnlimitedReplay->setSelectedSilent(SEL->getStartInfo()->extraOptionsInfo.unlimitedReplay);
+		buttonUnlimitedReplay->block(SEL->screenType == ESelectionScreen::loadGame);
+	}
 }

+ 2 - 1
client/mainmenu/CPrologEpilogVideo.cpp

@@ -64,5 +64,6 @@ void CPrologEpilogVideo::clickPressed(const Point & cursorPosition)
 	close();
 	CCS->soundh->stopSound(voiceSoundHandle);
 	CCS->soundh->stopSound(videoSoundHandle);
-	exitCb();
+	if(exitCb)
+		exitCb();
 }

+ 2 - 0
client/renderSDL/SDLImage.cpp

@@ -205,6 +205,8 @@ void SDLImage::exportBitmap(const boost::filesystem::path& path) const
 
 void SDLImage::playerColored(PlayerColor player)
 {
+	if (!surf)
+		return;
 	graphics->blueToPlayersAdv(surf, player);
 }
 

+ 10 - 23
client/widgets/TextControls.cpp

@@ -553,8 +553,7 @@ Point CGStatusBar::getBorderSize()
 
 CTextInput::CTextInput(const Rect & Pos, EFonts font, const CFunctionList<void(const std::string &)> & CB, ETextAlignment alignment, bool giveFocusToInput)
 	: CLabel(Pos.x, Pos.y, font, alignment),
-	cb(CB),
-	CFocusable(std::make_shared<CKeyboardFocusListener>(this))
+	cb(CB)
 {
 	setRedrawParent(true);
 	pos.h = Pos.h;
@@ -570,7 +569,7 @@ CTextInput::CTextInput(const Rect & Pos, EFonts font, const CFunctionList<void(c
 }
 
 CTextInput::CTextInput(const Rect & Pos, const Point & bgOffset, const ImagePath & bgName, const CFunctionList<void(const std::string &)> & CB)
-	:cb(CB), 	CFocusable(std::make_shared<CKeyboardFocusListener>(this))
+	:cb(CB)
 {
 	pos += Pos.topLeft();
 	pos.h = Pos.h;
@@ -587,7 +586,6 @@ CTextInput::CTextInput(const Rect & Pos, const Point & bgOffset, const ImagePath
 }
 
 CTextInput::CTextInput(const Rect & Pos, std::shared_ptr<IImage> srf)
-	:CFocusable(std::make_shared<CKeyboardFocusListener>(this))
 {
 	pos += Pos.topLeft();
 	OBJ_CONSTRUCTION;
@@ -603,20 +601,15 @@ CTextInput::CTextInput(const Rect & Pos, std::shared_ptr<IImage> srf)
 #endif
 }
 
-std::atomic<int> CKeyboardFocusListener::usageIndex(0);
+std::atomic<int> CFocusable::usageIndex(0);
 
-CKeyboardFocusListener::CKeyboardFocusListener(CTextInput * textInput)
-	:textInput(textInput)
+void CFocusable::focusGot()
 {
-}
-
-void CKeyboardFocusListener::focusGot()
-{
-	GH.startTextInput(textInput->pos);
+	GH.startTextInput(pos);
 	usageIndex++;
 }
 
-void CKeyboardFocusListener::focusLost()
+void CFocusable::focusLost()
 {
 	if(0 == --usageIndex)
 	{
@@ -769,12 +762,6 @@ void CTextInput::numberFilter(std::string & text, const std::string & oldText, i
 }
 
 CFocusable::CFocusable()
-	:CFocusable(std::make_shared<IFocusListener>())
-{
-}
-
-CFocusable::CFocusable(std::shared_ptr<IFocusListener> focusListener)
-	: focusListener(focusListener)
 {
 	focus = false;
 	focusables.push_back(this);
@@ -785,7 +772,7 @@ CFocusable::~CFocusable()
 	if(hasFocus())
 	{
 		inputWithFocus = nullptr;
-		focusListener->focusLost();
+		focusLost();
 	}
 
 	focusables -= this;
@@ -799,13 +786,13 @@ bool CFocusable::hasFocus() const
 void CFocusable::giveFocus()
 {
 	focus = true;
-	focusListener->focusGot();
+	focusGot();
 	redraw();
 
 	if(inputWithFocus)
 	{
 		inputWithFocus->focus = false;
-		inputWithFocus->focusListener->focusLost();
+		inputWithFocus->focusLost();
 		inputWithFocus->redraw();
 	}
 
@@ -837,7 +824,7 @@ void CFocusable::removeFocus()
 	if(this == inputWithFocus)
 	{
 		focus = false;
-		focusListener->focusLost();
+		focusLost();
 		redraw();
 
 		inputWithFocus = nullptr;

+ 6 - 30
client/widgets/TextControls.h

@@ -163,25 +163,12 @@ public:
 	void clear() override;
 	void setEnteringMode(bool on) override;
 	void setEnteredText(const std::string & text) override;
-
-};
-
-class CFocusable;
-
-class IFocusListener
-{
-public:
-	virtual void focusGot() {};
-	virtual void focusLost() {};
-	virtual ~IFocusListener() = default;
 };
 
 /// UIElement which can get input focus
 class CFocusable : public virtual CIntObject
 {
-private:
-	std::shared_ptr<IFocusListener> focusListener;
-
+	static std::atomic<int> usageIndex;
 public:
 	bool focus; //only one focusable control can have focus at one moment
 
@@ -190,38 +177,27 @@ public:
 	void removeFocus(); //remove focus
 	bool hasFocus() const;
 
+	void focusGot();
+	void focusLost();
+
 	static std::list<CFocusable *> focusables; //all existing objs
 	static CFocusable * inputWithFocus; //who has focus now
 
 	CFocusable();
-	CFocusable(std::shared_ptr<IFocusListener> focusListener);
 	~CFocusable();
 };
 
-class CTextInput;
-class CKeyboardFocusListener : public IFocusListener
-{
-private:
-	static std::atomic<int> usageIndex;
-	CTextInput * textInput;
-
-public:
-	CKeyboardFocusListener(CTextInput * textInput);
-	void focusGot() override;
-	void focusLost() override;
-};
-
 /// Text input box where players can enter text
 class CTextInput : public CLabel, public CFocusable
 {
 	std::string newText;
 	std::string helpBox; //for right-click help
-	
+
 protected:
 	std::string visibleText() override;
 
 public:
-	
+
 	CFunctionList<void(const std::string &)> cb;
 	CFunctionList<void(std::string &, const std::string &)> filters;
 	void setText(const std::string & nText) override;

+ 1 - 1
client/windows/CMapOverview.cpp

@@ -161,7 +161,7 @@ CMapOverviewWidget::CMapOverviewWidget(CMapOverview& parent):
 		std::unique_ptr<CMap> campaignMap = nullptr;
 		if(p.tabType != ESelectionScreen::newGame && config["variables"]["mapPreviewForSaves"].Bool())
 		{
-			CLoadFile lf(*CResourceHandler::get()->getResourceName(ResourcePath(p.resource.getName(), EResType::SAVEGAME)), MINIMAL_SERIALIZATION_VERSION);
+			CLoadFile lf(*CResourceHandler::get()->getResourceName(ResourcePath(p.resource.getName(), EResType::SAVEGAME)), ESerializationVersion::MINIMAL);
 			lf.checkMagicBytes(SAVEGAME_MAGIC);
 
 			auto mapHeader = std::make_unique<CMapHeader>();

+ 74 - 7
client/windows/GUIClasses.cpp

@@ -17,6 +17,8 @@
 #include "InfoWindows.h"
 
 #include "../CGameInfo.h"
+#include "../CServerHandler.h"
+#include "../Client.h"
 #include "../CMusicHandler.h"
 #include "../CPlayerInterface.h"
 #include "../CVideoHandler.h"
@@ -48,6 +50,7 @@
 #include "../lib/mapObjects/ObjectTemplate.h"
 #include "../lib/gameState/CGameState.h"
 #include "../lib/gameState/SThievesGuildInfo.h"
+#include "../lib/gameState/TavernHeroesPool.h"
 #include "../lib/CGeneralTextHandler.h"
 #include "../lib/CHeroHandler.h"
 #include "../lib/GameSettings.h"
@@ -443,7 +446,8 @@ CLevelWindow::~CLevelWindow()
 CTavernWindow::CTavernWindow(const CGObjectInstance * TavernObj, const std::function<void()> & onWindowClosed)
 	: CStatusbarWindow(PLAYER_COLORED, ImagePath::builtin("TPTAVERN")),
 	onWindowClosed(onWindowClosed),
-	tavernObj(TavernObj)
+	tavernObj(TavernObj),
+	heroToInvite(nullptr)
 {
 	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
 
@@ -459,8 +463,8 @@ CTavernWindow::CTavernWindow(const CGObjectInstance * TavernObj, const std::func
 
 	oldSelected = -1;
 
-	h1 = std::make_shared<HeroPortrait>(selected, 0, 72, 299, h[0]);
-	h2 = std::make_shared<HeroPortrait>(selected, 1, 162, 299, h[1]);
+	h1 = std::make_shared<HeroPortrait>(selected, 0, 72, 299, h[0], [this]() { if(!recruit->isBlocked()) recruitb(); });
+	h2 = std::make_shared<HeroPortrait>(selected, 1, 162, 299, h[1], [this]() { if(!recruit->isBlocked()) recruitb(); });
 
 	title = std::make_shared<CLabel>(197, 32, FONT_BIG, ETextAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->jktexts[37]);
 	cost = std::make_shared<CLabel>(320, 328, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, std::to_string(GameConstants::HERO_GOLD_COST));
@@ -507,6 +511,34 @@ CTavernWindow::CTavernWindow(const CGObjectInstance * TavernObj, const std::func
 		CCS->videoh->open(townObj->town->clientInfo.tavernVideo);
 	else
 		CCS->videoh->open(VideoPath::builtin("TAVERN.BIK"));
+
+	addInvite();
+}
+
+void CTavernWindow::addInvite()
+{
+	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
+
+	if(!VLC->settings()->getBoolean(EGameSettings::HEROES_TAVERN_INVITE))
+		return;
+
+	const auto & heroesPool = CSH->client->gameState()->heroesPool;
+	for(auto & elem : heroesPool->unusedHeroesFromPool())
+	{
+		bool heroAvailable = heroesPool->isHeroAvailableFor(elem.first, tavernObj->getOwner());
+		if(heroAvailable)
+			inviteableHeroes[elem.first] = elem.second;
+	}
+
+	if(!inviteableHeroes.empty())
+	{
+		if(!heroToInvite)
+			heroToInvite = (*RandomGeneratorUtil::nextItem(inviteableHeroes, CRandomGenerator::getDefault())).second;
+
+		inviteHero = std::make_shared<CLabel>(170, 444, EFonts::FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->translate("vcmi.tavernWindow.inviteHero"));
+		inviteHeroImage = std::make_shared<CAnimImage>(AnimationPath::builtin("PortraitsSmall"), (*CGI->heroh)[heroToInvite->getHeroType()]->imageIndex, 0, 245, 428);
+		inviteHeroImageArea = std::make_shared<LRClickableArea>(Rect(245, 428, 48, 32), [this](){ GH.windows().createAndPushWindow<HeroSelector>(inviteableHeroes, [this](CGHeroInstance* h){ heroToInvite = h; addInvite(); }); }, [this](){ GH.windows().createAndPushWindow<CRClickPopupInt>(std::make_shared<CHeroWindow>(heroToInvite)); });
+	}
 }
 
 void CTavernWindow::recruitb()
@@ -514,7 +546,7 @@ void CTavernWindow::recruitb()
 	const CGHeroInstance *toBuy = (selected ? h2 : h1)->h;
 	const CGObjectInstance *obj = tavernObj;
 
-	LOCPLINT->cb->recruitHero(obj, toBuy);
+	LOCPLINT->cb->recruitHero(obj, toBuy, heroToInvite ? heroToInvite->getHeroType() : HeroTypeID::NONE);
 	close();
 }
 
@@ -569,15 +601,23 @@ void CTavernWindow::HeroPortrait::clickPressed(const Point & cursorPosition)
 		*_sel = _id;
 }
 
+void CTavernWindow::HeroPortrait::clickDouble(const Point & cursorPosition)
+{
+	clickPressed(cursorPosition);
+	
+	if(onChoose)
+		onChoose();
+}
+
 void CTavernWindow::HeroPortrait::showPopupWindow(const Point & cursorPosition)
 {
 	if(h)
 		GH.windows().createAndPushWindow<CRClickPopupInt>(std::make_shared<CHeroWindow>(h));
 }
 
-CTavernWindow::HeroPortrait::HeroPortrait(int & sel, int id, int x, int y, const CGHeroInstance * H)
-	: CIntObject(LCLICK | SHOW_POPUP | HOVER),
-	h(H), _sel(&sel), _id(id)
+CTavernWindow::HeroPortrait::HeroPortrait(int & sel, int id, int x, int y, const CGHeroInstance * H, std::function<void()> OnChoose)
+	: CIntObject(LCLICK | DOUBLECLICK | SHOW_POPUP | HOVER),
+	h(H), _sel(&sel), _id(id), onChoose(OnChoose)
 {
 	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
 	h = H;
@@ -615,6 +655,33 @@ void CTavernWindow::HeroPortrait::hover(bool on)
 		GH.statusbar()->clear();
 }
 
+CTavernWindow::HeroSelector::HeroSelector(std::map<HeroTypeID, CGHeroInstance*> InviteableHeroes, std::function<void(CGHeroInstance*)> OnChoose)
+	: CWindowObject(BORDERED), inviteableHeroes(InviteableHeroes), onChoose(OnChoose)
+{
+	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
+
+	pos = Rect(0, 0, 16 * 48, (inviteableHeroes.size() / 16 + (inviteableHeroes.size() % 16 != 0)) * 32);
+	background = std::make_shared<CFilledTexture>(ImagePath::builtin("DIBOXBCK"), Rect(0, 0, pos.w, pos.h));
+
+	int x = 0;
+	int y = 0;
+	for(auto & h : inviteableHeroes)
+	{
+		portraits.push_back(std::make_shared<CAnimImage>(AnimationPath::builtin("PortraitsSmall"), (*CGI->heroh)[h.first]->imageIndex, 0, x * 48, y * 32));
+		portraitAreas.push_back(std::make_shared<LRClickableArea>(Rect(x * 48, y * 32, 48, 32), [this, h](){ close(); onChoose(inviteableHeroes[h.first]); }, [this, h](){ GH.windows().createAndPushWindow<CRClickPopupInt>(std::make_shared<CHeroWindow>(inviteableHeroes[h.first])); }));
+
+		if(x > 0 && x % 15 == 0)
+		{
+			x = 0;
+			y++;
+		}
+		else
+			x++;
+	}
+
+	center();
+}
+
 static const std::string QUICK_EXCHANGE_MOD_PREFIX = "quick-exchange";
 static const std::string QUICK_EXCHANGE_BG = QUICK_EXCHANGE_MOD_PREFIX + "/TRADEQE";
 

+ 28 - 1
client/windows/GUIClasses.h

@@ -36,6 +36,8 @@ class CTextBox;
 class CGarrisonInt;
 class CGarrisonSlot;
 class CHeroArea;
+class CAnimImage;
+class CFilledTexture;
 
 enum class EUserEvent;
 
@@ -206,10 +208,13 @@ public:
 		std::string description; // "XXX is a level Y ZZZ with N artifacts"
 		const CGHeroInstance * h;
 
+		std::function<void()> onChoose;
+
 		void clickPressed(const Point & cursorPosition) override;
+		void clickDouble(const Point & cursorPosition) override;
 		void showPopupWindow(const Point & cursorPosition) override;
 		void hover (bool on) override;
-		HeroPortrait(int & sel, int id, int x, int y, const CGHeroInstance * H);
+		HeroPortrait(int & sel, int id, int x, int y, const CGHeroInstance * H, std::function<void()> OnChoose = nullptr);
 
 	private:
 		int *_sel;
@@ -218,6 +223,21 @@ public:
 		std::shared_ptr<CAnimImage> portrait;
 	};
 
+	class HeroSelector : public CWindowObject
+	{
+	public:
+		std::shared_ptr<CFilledTexture> background;
+
+		HeroSelector(std::map<HeroTypeID, CGHeroInstance*> InviteableHeroes, std::function<void(CGHeroInstance*)> OnChoose);
+
+	private:
+		std::map<HeroTypeID, CGHeroInstance*> inviteableHeroes;
+		std::function<void(CGHeroInstance*)> onChoose;
+
+		std::vector<std::shared_ptr<CAnimImage>> portraits;
+		std::vector<std::shared_ptr<LRClickableArea>> portraitAreas;
+	};
+
 	//recruitable heroes
 	std::shared_ptr<HeroPortrait> h1;
 	std::shared_ptr<HeroPortrait> h2; //recruitable heroes
@@ -237,6 +257,13 @@ public:
 	std::shared_ptr<CTextBox> heroDescription;
 
 	std::shared_ptr<CTextBox> rumor;
+	
+	std::shared_ptr<CLabel> inviteHero;
+	std::shared_ptr<CAnimImage> inviteHeroImage;
+	std::shared_ptr<LRClickableArea> inviteHeroImageArea;
+	std::map<HeroTypeID, CGHeroInstance*> inviteableHeroes;
+	CGHeroInstance* heroToInvite;
+	void addInvite();
 
 	CTavernWindow(const CGObjectInstance * TavernObj, const std::function<void()> & onWindowClosed);
 	~CTavernWindow();

+ 11 - 9
cmake_modules/VCMI_lib.cmake

@@ -2,7 +2,7 @@ macro(add_main_lib TARGET_NAME LIBRARY_TYPE)
 	if(NOT DEFINED MAIN_LIB_DIR)
 		set(MAIN_LIB_DIR "${CMAKE_SOURCE_DIR}/lib")
 	endif()
-	
+
 	set(lib_SRCS
 		${MAIN_LIB_DIR}/StdInc.cpp
 
@@ -268,13 +268,13 @@ macro(add_main_lib TARGET_NAME LIBRARY_TYPE)
 		${MAIN_LIB_DIR}/VCMI_Lib.cpp
 	)
 
-	# Version.cpp is a generated file
+    # Version.cpp is a generated file
 	if(ENABLE_GITVERSION)
 		list(APPEND lib_SRCS ${CMAKE_BINARY_DIR}/Version.cpp)
 		set_source_files_properties(${CMAKE_BINARY_DIR}/Version.cpp
 			PROPERTIES GENERATED TRUE
 		)
-	endif()
+    endif()
 
 	set(lib_HEADERS
 		${MAIN_LIB_DIR}/../include/vstd/CLoggerBase.h
@@ -567,6 +567,7 @@ macro(add_main_lib TARGET_NAME LIBRARY_TYPE)
 		${MAIN_LIB_DIR}/serializer/JsonSerializer.h
 		${MAIN_LIB_DIR}/serializer/JsonUpdater.h
 		${MAIN_LIB_DIR}/serializer/Cast.h
+		${MAIN_LIB_DIR}/serializer/ESerializationVersion.h
 
 		${MAIN_LIB_DIR}/spells/AbilityCaster.h
 		${MAIN_LIB_DIR}/spells/AdventureSpellMechanics.h
@@ -628,6 +629,7 @@ macro(add_main_lib TARGET_NAME LIBRARY_TYPE)
 		${MAIN_LIB_DIR}/CStopWatch.h
 		${MAIN_LIB_DIR}/CThreadHelper.h
 		${MAIN_LIB_DIR}/CTownHandler.h
+		${MAIN_LIB_DIR}/ExtraOptionsInfo.h
 		${MAIN_LIB_DIR}/FunctionList.h
 		${MAIN_LIB_DIR}/GameCallbackHolder.h
 		${MAIN_LIB_DIR}/GameConstants.h
@@ -663,7 +665,7 @@ macro(add_main_lib TARGET_NAME LIBRARY_TYPE)
 		${MAIN_LIB_DIR}/VCMI_Lib.h
 	)
 
-	assign_source_group(${lib_SRCS} ${lib_HEADERS})
+    assign_source_group(${lib_SRCS} ${lib_HEADERS})
 
 	add_library(${TARGET_NAME} ${LIBRARY_TYPE} ${lib_SRCS} ${lib_HEADERS})
 	set_target_properties(${TARGET_NAME} PROPERTIES COMPILE_DEFINITIONS "VCMI_DLL=1")
@@ -671,7 +673,7 @@ macro(add_main_lib TARGET_NAME LIBRARY_TYPE)
 		minizip::minizip ZLIB::ZLIB
 		${SYSTEM_LIBS} Boost::boost Boost::thread Boost::filesystem Boost::program_options Boost::locale Boost::date_time
 	)
-	if(APPLE_IOS)
+    if(APPLE_IOS)
 		target_link_libraries(${TARGET_NAME} PUBLIC iOS_utils)
 	endif()
 
@@ -682,13 +684,13 @@ macro(add_main_lib TARGET_NAME LIBRARY_TYPE)
 		PUBLIC ${MAIN_LIB_DIR}
 	)
 
-	if(WIN32)
+    if(WIN32)
 		set_target_properties(${TARGET_NAME}
 			PROPERTIES
-				OUTPUT_NAME "VCMI_lib"
+			    OUTPUT_NAME "VCMI_lib"
 				PROJECT_LABEL "VCMI_lib"
 		)
-	endif()
+    endif()
 
 	vcmi_set_output_dir(${TARGET_NAME} "")
 
@@ -702,7 +704,7 @@ macro(add_main_lib TARGET_NAME LIBRARY_TYPE)
 			COMMAND ${CMAKE_COMMAND} -P ${CMAKE_SOURCE_DIR}/cmake_modules/create_link.cmake ${MAIN_LIB_DIR}/../config ${CMAKE_BINARY_DIR}/bin/${CMAKE_CFG_INTDIR}/config
 			COMMAND ${CMAKE_COMMAND} -P ${CMAKE_SOURCE_DIR}/cmake_modules/create_link.cmake ${MAIN_LIB_DIR}/../Mods ${CMAKE_BINARY_DIR}/bin/${CMAKE_CFG_INTDIR}/Mods
 		)
-	endif()
+    endif()
 
 	# Update version before vcmi compiling
 	if(TARGET update_version)

+ 3 - 3
config/bonuses.json

@@ -149,7 +149,7 @@
 	{
 		"graphics":
 		{
-			"icon":  "zvs/Lib1.res/E_RDEF"
+			"icon":  "zvs/Lib1.res/E_RATT"
 		}
 	},
 
@@ -197,7 +197,7 @@
 	{
 		"graphics":
 		{
-			"icon":  ""
+			"icon":  "zvs/Lib1.res/Ferocity"
 		}
 	},
 
@@ -448,7 +448,7 @@
 	{
 		"graphics":
 		{
-			"icon": ""
+			"icon": "zvs/Lib1.res/Revenge"
 		}
 	},
 

+ 1 - 1
config/difficulty.json

@@ -28,7 +28,7 @@
 		},
 		"king":
 		{
-			"resources": { "wood" : 0, "mercury": 0, "ore": 0	, "sulfur": 0, "crystal": 0, "gems": 0, "gold": 0, "mithril": 0 },
+			"resources": { "wood" : 0, "mercury": 0, "ore": 0, "sulfur": 0, "crystal": 0, "gems": 0, "gold": 0, "mithril": 0 },
 			"globalBonuses": [],
 			"battleBonuses": []
 		}

+ 3 - 1
config/gameConfig.json

@@ -289,7 +289,9 @@
 			// Chances for a hero with default army to receive corresponding stack out of his predefined starting troops
 			"startingStackChances": [ 100, 88, 25],
 			// number of artifacts that can fit in a backpack. -1 is unlimited.
-			"backpackSize"		: -1
+			"backpackSize"		: -1,
+			// if heroes are invitable in tavern
+			"tavernInvite"            : false
 		},
 
 		"towns":

+ 2 - 0
config/widgets/extraOptionsTab.json

@@ -64,12 +64,14 @@
 					"name": "buttonCheatAllowed",
 					"image": "lobby/checkbox",
 					"callback" : "setCheatAllowed",
+					"help" : "vcmi.optionsTab.cheatAllowed",
 					"selected" : true
 				},
 				{
 					"name": "buttonUnlimitedReplay",
 					"image": "lobby/checkbox",
 					"callback" : "setUnlimitedReplay",
+					"help" : "vcmi.optionsTab.unlimitedReplay",
 					"selected" : true
 				}
 			]

+ 6 - 0
debian/changelog

@@ -4,6 +4,12 @@ vcmi (1.5.0) jammy; urgency=medium
 
  -- Ivan Savenko <[email protected]>  Fri, 1 Mar 2024 12:00:00 +0200
 
+vcmi (1.4.5) jammy; urgency=medium
+
+  * New upstream release
+
+  -- Ivan Savenko <[email protected]>  Tue, 23 Jan 2024 12:00:00 +0200
+
 vcmi (1.4.4) jammy; urgency=medium
 
   * New upstream release

+ 1 - 1
docs/Readme.md

@@ -2,7 +2,7 @@
 [![Github Downloads](https://img.shields.io/github/downloads/vcmi/vcmi/1.4.0/total)](https://github.com/vcmi/vcmi/releases/tag/1.4.0)
 [![Github Downloads](https://img.shields.io/github/downloads/vcmi/vcmi/1.4.1/total)](https://github.com/vcmi/vcmi/releases/tag/1.4.1)
 [![Github Downloads](https://img.shields.io/github/downloads/vcmi/vcmi/1.4.2/total)](https://github.com/vcmi/vcmi/releases/tag/1.4.2)
-[![Github Downloads](https://img.shields.io/github/downloads/vcmi/vcmi/1.4.3/total)](https://github.com/vcmi/vcmi/releases/tag/1.4.3)
+[![Github Downloads](https://img.shields.io/github/downloads/vcmi/vcmi/1.4.4/total)](https://github.com/vcmi/vcmi/releases/tag/1.4.4)
 [![Github Downloads](https://img.shields.io/github/downloads/vcmi/vcmi/total)](https://github.com/vcmi/vcmi/releases)
 
 # VCMI Project

+ 1 - 0
launcher/eu.vcmi.VCMI.metainfo.xml

@@ -77,6 +77,7 @@
 	</screenshots>
 	<releases>
 		<release version="1.5.0" date="2024-03-01" type="development"/>
+		<release version="1.4.5" date="2024-01-23" type="stable"/>
 		<release version="1.4.4" date="2024-01-20" type="stable"/>
 		<release version="1.4.3" date="2024-01-19" type="stable"/>
 		<release version="1.4.2" date="2023-12-25" type="stable"/>

+ 3 - 0
launcher/modManager/cmodlist.cpp

@@ -38,6 +38,9 @@ bool CModEntry::isEnabled() const
 	if(!isInstalled())
 		return false;
 
+	if (!isVisible())
+		return false;
+
 	return modSettings["active"].toBool();
 }
 

+ 1 - 1
launcher/modManager/cmodlistview_moc.cpp

@@ -521,7 +521,7 @@ QStringList CModListView::findDependentMods(QString mod, bool excludeDisabled)
 	{
 		auto current = modModel->getMod(modName);
 
-		if(!current.isInstalled())
+		if(!current.isInstalled() || !current.isVisible())
 			continue;
 
 		if(current.getDependencies().contains(mod, Qt::CaseInsensitive))

+ 75 - 62
launcher/translation/spanish.ts

@@ -6,84 +6,84 @@
     <message>
         <location filename="../aboutProject/aboutproject_moc.ui" line="22"/>
         <source>VCMI on Discord</source>
-        <translation type="unfinished">VCMI en Discord</translation>
+        <translation>VCMI en Discord</translation>
     </message>
     <message>
         <location filename="../aboutProject/aboutproject_moc.ui" line="29"/>
         <source>Have a question? Found a bug? Want to help? Join us!</source>
-        <translation type="unfinished">¿Tienes alguna pregunta? ¿Encontraste algún error? ¿Quieres ayudar? ¡Únete a nosotros!</translation>
+        <translation>¿Tienes alguna pregunta? ¿Encontraste algún error? ¿Quieres ayudar? ¡Únete a nosotros!</translation>
     </message>
     <message>
         <location filename="../aboutProject/aboutproject_moc.ui" line="36"/>
         <source>VCMI on Github</source>
-        <translation type="unfinished">VCMI en Github</translation>
+        <translation>VCMI en Github</translation>
     </message>
     <message>
         <location filename="../aboutProject/aboutproject_moc.ui" line="55"/>
         <source>Our Community</source>
-        <translation type="unfinished"></translation>
+        <translation>Nuestra comunidad</translation>
     </message>
     <message>
         <location filename="../aboutProject/aboutproject_moc.ui" line="62"/>
         <source>VCMI on Slack</source>
-        <translation type="unfinished">VCMI en Slack</translation>
+        <translation>VCMI en Slack</translation>
     </message>
     <message>
         <location filename="../aboutProject/aboutproject_moc.ui" line="98"/>
         <source>Build Information</source>
-        <translation type="unfinished"></translation>
+        <translation>Información de la versión</translation>
     </message>
     <message>
         <location filename="../aboutProject/aboutproject_moc.ui" line="115"/>
         <source>User data directory</source>
-        <translation type="unfinished">Directorio de datos del usuario</translation>
+        <translation>Directorio de datos del usuario</translation>
     </message>
     <message>
         <location filename="../aboutProject/aboutproject_moc.ui" line="122"/>
         <location filename="../aboutProject/aboutproject_moc.ui" line="129"/>
         <location filename="../aboutProject/aboutproject_moc.ui" line="193"/>
         <source>Open</source>
-        <translation type="unfinished">Abrir</translation>
+        <translation>Abrir</translation>
     </message>
     <message>
         <location filename="../aboutProject/aboutproject_moc.ui" line="136"/>
         <source>Check for updates</source>
-        <translation type="unfinished"></translation>
+        <translation>Comprobar actualizaciones</translation>
     </message>
     <message>
         <location filename="../aboutProject/aboutproject_moc.ui" line="156"/>
         <source>Game version</source>
-        <translation type="unfinished"></translation>
+        <translation>Versión del juego</translation>
     </message>
     <message>
         <location filename="../aboutProject/aboutproject_moc.ui" line="163"/>
         <source>Log files directory</source>
-        <translation type="unfinished">Directorio de archivos de registro</translation>
+        <translation>Directorio de archivos de registro</translation>
     </message>
     <message>
         <location filename="../aboutProject/aboutproject_moc.ui" line="176"/>
         <source>Data Directories</source>
-        <translation type="unfinished">Directorios de datos</translation>
+        <translation>Directorios de datos</translation>
     </message>
     <message>
         <location filename="../aboutProject/aboutproject_moc.ui" line="213"/>
         <source>Game data directory</source>
-        <translation type="unfinished"></translation>
+        <translation>Directorio de los datos del juego</translation>
     </message>
     <message>
         <location filename="../aboutProject/aboutproject_moc.ui" line="220"/>
         <source>Operating System</source>
-        <translation type="unfinished"></translation>
+        <translation>Sistema operativo</translation>
     </message>
     <message>
         <location filename="../aboutProject/aboutproject_moc.ui" line="273"/>
         <source>Project homepage</source>
-        <translation type="unfinished"></translation>
+        <translation>Página web del proyecto</translation>
     </message>
     <message>
         <location filename="../aboutProject/aboutproject_moc.ui" line="286"/>
         <source>Report a bug</source>
-        <translation type="unfinished"></translation>
+        <translation>Informar de un error</translation>
     </message>
 </context>
 <context>
@@ -121,7 +121,7 @@
     <message>
         <location filename="../modManager/cmodlistmodel_moc.cpp" line="48"/>
         <source>Maps</source>
-        <translation type="unfinished"></translation>
+        <translation>Mapas</translation>
     </message>
     <message>
         <location filename="../modManager/cmodlistmodel_moc.cpp" line="49"/>
@@ -180,7 +180,7 @@
     <message>
         <location filename="../modManager/cmodlistmodel_moc.cpp" line="62"/>
         <source>Compatibility</source>
-        <translation type="unfinished">Compatibilidad</translation>
+        <translation>Compatibilidad</translation>
     </message>
     <message>
         <location filename="../modManager/cmodlistmodel_moc.cpp" line="63"/>
@@ -319,7 +319,7 @@
     <message>
         <location filename="../modManager/cmodlistview_moc.cpp" line="292"/>
         <source>Size</source>
-        <translation type="unfinished"></translation>
+        <translation>Tamaño</translation>
     </message>
     <message>
         <location filename="../modManager/cmodlistview_moc.cpp" line="294"/>
@@ -410,12 +410,12 @@
     <message>
         <location filename="../modManager/cmodlistview_moc.cpp" line="638"/>
         <source>Downloading %s%. %p% (%v MB out of %m MB) finished</source>
-        <translation type="unfinished"></translation>
+        <translation>Descargando %s%. %p% (%v MB de %m MB) completado</translation>
     </message>
     <message>
         <location filename="../modManager/cmodlistview_moc.cpp" line="665"/>
         <source>Download failed</source>
-        <translation type="unfinished"></translation>
+        <translation>Descarga fallida</translation>
     </message>
     <message>
         <location filename="../modManager/cmodlistview_moc.cpp" line="666"/>
@@ -424,30 +424,37 @@
 Encountered errors:
 
 </source>
-        <translation type="unfinished"></translation>
+        <translation>No se han podido descargar todos los ficheros.
+
+Errores encontrados:
+
+</translation>
     </message>
     <message>
         <location filename="../modManager/cmodlistview_moc.cpp" line="667"/>
         <source>
 
 Install successfully downloaded?</source>
-        <translation type="unfinished"></translation>
+        <translation>
+
+Instalar lo correctamente descargado?</translation>
     </message>
     <message>
         <location filename="../modManager/cmodlistview_moc.cpp" line="812"/>
         <source>Installing mod %1</source>
-        <translation type="unfinished"></translation>
+        <translation>Instalando mod %1</translation>
     </message>
     <message>
         <location filename="../modManager/cmodlistview_moc.cpp" line="874"/>
         <source>Operation failed</source>
-        <translation type="unfinished"></translation>
+        <translation>Operación fallida</translation>
     </message>
     <message>
         <location filename="../modManager/cmodlistview_moc.cpp" line="875"/>
         <source>Encountered errors:
 </source>
-        <translation type="unfinished"></translation>
+        <translation>Errores encontrados:
+</translation>
     </message>
     <message>
         <location filename="../modManager/cmodlistview_moc.cpp" line="910"/>
@@ -482,94 +489,94 @@ Install successfully downloaded?</source>
     <message>
         <location filename="../settingsView/csettingsview_moc.ui" line="230"/>
         <source>Interface Scaling</source>
-        <translation type="unfinished"></translation>
+        <translation>Escala de la interfaz</translation>
     </message>
     <message>
         <location filename="../settingsView/csettingsview_moc.ui" line="394"/>
         <source>Neutral AI in battles</source>
-        <translation type="unfinished"></translation>
+        <translation>IA neutral en batallas</translation>
     </message>
     <message>
         <location filename="../settingsView/csettingsview_moc.ui" line="169"/>
         <source>Enemy AI in battles</source>
-        <translation type="unfinished"></translation>
+        <translation>IA enemiga en batallas</translation>
     </message>
     <message>
         <location filename="../settingsView/csettingsview_moc.ui" line="176"/>
         <source>Additional repository</source>
-        <translation type="unfinished"></translation>
+        <translation>Repositorio adicional</translation>
     </message>
     <message>
         <location filename="../settingsView/csettingsview_moc.ui" line="210"/>
         <source>Adventure Map Allies</source>
-        <translation type="unfinished"></translation>
+        <translation>Aliados en el Mapa de aventuras</translation>
     </message>
     <message>
         <location filename="../settingsView/csettingsview_moc.ui" line="552"/>
         <source>Adventure Map Enemies</source>
-        <translation type="unfinished"></translation>
+        <translation>Enemigos en el Mapa de aventuras</translation>
     </message>
     <message>
         <location filename="../settingsView/csettingsview_moc.ui" line="428"/>
         <source>Windowed</source>
-        <translation type="unfinished"></translation>
+        <translation>Ventana</translation>
     </message>
     <message>
         <location filename="../settingsView/csettingsview_moc.ui" line="433"/>
         <source>Borderless fullscreen</source>
-        <translation type="unfinished"></translation>
+        <translation>Ventana completa sin bordes</translation>
     </message>
     <message>
         <location filename="../settingsView/csettingsview_moc.ui" line="438"/>
         <source>Exclusive fullscreen</source>
-        <translation type="unfinished"></translation>
+        <translation>Pantalla completa</translation>
     </message>
     <message>
         <location filename="../settingsView/csettingsview_moc.ui" line="593"/>
         <source>Autosave limit (0 = off)</source>
-        <translation type="unfinished"></translation>
+        <translation>Límite de autosaves (0 = sin límite)</translation>
     </message>
     <message>
         <location filename="../settingsView/csettingsview_moc.ui" line="401"/>
         <source>Friendly AI in battles</source>
-        <translation type="unfinished"></translation>
+        <translation>IA amistosa en batallas</translation>
     </message>
     <message>
         <location filename="../settingsView/csettingsview_moc.ui" line="370"/>
         <source>Framerate Limit</source>
-        <translation type="unfinished"></translation>
+        <translation>Límite de fotogramas</translation>
     </message>
     <message>
         <location filename="../settingsView/csettingsview_moc.ui" line="460"/>
         <source>Autosave prefix</source>
-        <translation type="unfinished"></translation>
+        <translation>Prefijo autoguardado</translation>
     </message>
     <message>
         <location filename="../settingsView/csettingsview_moc.ui" line="484"/>
         <source>empty = map name prefix</source>
-        <translation type="unfinished"></translation>
+        <translation>Vacio = prefijo del mapa</translation>
     </message>
     <message>
         <location filename="../settingsView/csettingsview_moc.ui" line="607"/>
         <source>Refresh now</source>
-        <translation type="unfinished"></translation>
+        <translation>Actualizar</translation>
     </message>
     <message>
         <location filename="../settingsView/csettingsview_moc.ui" line="614"/>
         <source>Default repository</source>
-        <translation type="unfinished"></translation>
+        <translation>Repositorio por defecto</translation>
     </message>
     <message>
         <location filename="../settingsView/csettingsview_moc.ui" line="624"/>
         <source>Renderer</source>
-        <translation type="unfinished"></translation>
+        <translation>Render</translation>
     </message>
     <message>
         <location filename="../settingsView/csettingsview_moc.ui" line="246"/>
         <location filename="../settingsView/csettingsview_moc.ui" line="476"/>
         <location filename="../settingsView/csettingsview_moc.ui" line="544"/>
         <source>On</source>
-        <translation>Encendido</translation>
+        <translation>Activado</translation>
     </message>
     <message>
         <location filename="../settingsView/csettingsview_moc.ui" line="115"/>
@@ -584,7 +591,7 @@ Install successfully downloaded?</source>
     <message>
         <location filename="../settingsView/csettingsview_moc.ui" line="349"/>
         <source>Reserved screen area</source>
-        <translation type="unfinished"></translation>
+        <translation>Área de pantalla reservada</translation>
     </message>
     <message>
         <location filename="../settingsView/csettingsview_moc.ui" line="155"/>
@@ -614,7 +621,7 @@ Install successfully downloaded?</source>
     <message>
         <location filename="../settingsView/csettingsview_moc.ui" line="562"/>
         <source>VSync</source>
-        <translation type="unfinished"></translation>
+        <translation>Sincronización vertical</translation>
     </message>
     <message>
         <location filename="../settingsView/csettingsview_moc.ui" line="135"/>
@@ -640,7 +647,13 @@ Windowed - game will run inside a window that covers part of your screen
 Borderless Windowed Mode - game will run in a window that covers entirely of your screen, using same resolution as your screen.
 
 Fullscreen Exclusive Mode - game will cover entirety of your screen and will use selected resolution.</source>
-        <translation type="unfinished"></translation>
+        <translation>Selecciona el modo de visualización del juego
+
+En ventana - el juego se ejecutará dentro de una ventana que forma parte de tu pantalla.
+
+Ventana sin bordes - el juego se ejecutará en una ventana que cubre completamente tu pantalla, usando la misma resolución.
+
+Pantalla completa - el juego cubrirá la totalidad de la pantalla y utilizará la resolución seleccionada.</translation>
     </message>
     <message>
         <location filename="../settingsView/csettingsview_moc.ui" line="522"/>
@@ -764,12 +777,12 @@ Fullscreen Exclusive Mode - game will cover entirety of your screen and will use
     <message>
         <location filename="../firstLaunch/firstlaunch_moc.ui" line="702"/>
         <source>Interface Improvements</source>
-        <translation type="unfinished"></translation>
+        <translation>Mejora de la interfaz</translation>
     </message>
     <message>
         <location filename="../firstLaunch/firstlaunch_moc.ui" line="772"/>
         <source>Install mod that provides various interface improvements, such as better interface for random maps and selectable actions in battles</source>
-        <translation type="unfinished"></translation>
+        <translation>Instalar mod que proporciona varias mejoras en la interfaz, como mejor interacción en los mapas aleatorios y más opciones en las batallas</translation>
     </message>
     <message>
         <location filename="../firstLaunch/firstlaunch_moc.ui" line="127"/>
@@ -882,12 +895,12 @@ Ten en cuenta que para usar VCMI debes ser dueño de los archivos de datos origi
     <message>
         <location filename="../firstLaunch/firstlaunch_moc.cpp" line="143"/>
         <source>Heroes III installation found!</source>
-        <translation type="unfinished"></translation>
+        <translation>Instalación de Heroes III encontrada!</translation>
     </message>
     <message>
         <location filename="../firstLaunch/firstlaunch_moc.cpp" line="143"/>
         <source>Copy data to VCMI folder?</source>
-        <translation type="unfinished"></translation>
+        <translation>Copiar datos a la carpeta VCMI?</translation>
     </message>
 </context>
 <context>
@@ -903,7 +916,7 @@ Ten en cuenta que para usar VCMI debes ser dueño de los archivos de datos origi
     <message>
         <location filename="../languages.cpp" line="23"/>
         <source>Czech</source>
-        <translation type="unfinished"></translation>
+        <translation>Czech (Checo)</translation>
     </message>
     <message>
         <location filename="../languages.cpp" line="24"/>
@@ -918,7 +931,7 @@ Ten en cuenta que para usar VCMI debes ser dueño de los archivos de datos origi
     <message>
         <location filename="../languages.cpp" line="26"/>
         <source>Finnish</source>
-        <translation type="unfinished"></translation>
+        <translation>Finnish (Finlandés)</translation>
     </message>
     <message>
         <location filename="../languages.cpp" line="27"/>
@@ -933,12 +946,12 @@ Ten en cuenta que para usar VCMI debes ser dueño de los archivos de datos origi
     <message>
         <location filename="../languages.cpp" line="29"/>
         <source>Hungarian</source>
-        <translation type="unfinished"></translation>
+        <translation>Hungarian (Húngaro)</translation>
     </message>
     <message>
         <location filename="../languages.cpp" line="30"/>
         <source>Italian</source>
-        <translation type="unfinished"></translation>
+        <translation>Italian (Italiano)</translation>
     </message>
     <message>
         <location filename="../languages.cpp" line="31"/>
@@ -953,7 +966,7 @@ Ten en cuenta que para usar VCMI debes ser dueño de los archivos de datos origi
     <message>
         <location filename="../languages.cpp" line="33"/>
         <source>Portuguese</source>
-        <translation type="unfinished"></translation>
+        <translation>Portuguese (Portugués)</translation>
     </message>
     <message>
         <location filename="../languages.cpp" line="34"/>
@@ -968,12 +981,12 @@ Ten en cuenta que para usar VCMI debes ser dueño de los archivos de datos origi
     <message>
         <location filename="../languages.cpp" line="36"/>
         <source>Swedish</source>
-        <translation type="unfinished"></translation>
+        <translation>Swedish (Sueco)</translation>
     </message>
     <message>
         <location filename="../languages.cpp" line="37"/>
         <source>Turkish</source>
-        <translation type="unfinished"></translation>
+        <translation>Turkish (Turco)</translation>
     </message>
     <message>
         <location filename="../languages.cpp" line="38"/>
@@ -983,7 +996,7 @@ Ten en cuenta que para usar VCMI debes ser dueño de los archivos de datos origi
     <message>
         <location filename="../languages.cpp" line="39"/>
         <source>Vietnamese</source>
-        <translation type="unfinished"></translation>
+        <translation>Vietnamese (Vietnamita)</translation>
     </message>
     <message>
         <location filename="../languages.cpp" line="40"/>
@@ -993,12 +1006,12 @@ Ten en cuenta que para usar VCMI debes ser dueño de los archivos de datos origi
     <message>
         <location filename="../languages.cpp" line="41"/>
         <source>Other (Cyrillic Script)</source>
-        <translation type="unfinished"></translation>
+        <translation>Otro (Escritura cirílica)</translation>
     </message>
     <message>
         <location filename="../languages.cpp" line="42"/>
         <source>Other (West European)</source>
-        <translation type="unfinished"></translation>
+        <translation>Otro (Europa del Este)</translation>
     </message>
     <message>
         <location filename="../languages.cpp" line="64"/>
@@ -1021,7 +1034,7 @@ Ten en cuenta que para usar VCMI debes ser dueño de los archivos de datos origi
     <message>
         <location filename="../mainwindow_moc.ui" line="157"/>
         <source>Help</source>
-        <translation type="unfinished"></translation>
+        <translation>Ayuda</translation>
     </message>
     <message>
         <location filename="../mainwindow_moc.ui" line="226"/>

+ 2 - 2
lib/CArtHandler.h

@@ -179,7 +179,7 @@ struct DLL_LINKAGE ArtSlotInfo
 	ArtSlotInfo() : locked(false) {}
 	const CArtifactInstance * getArt() const;
 
-	template <typename Handler> void serialize(Handler & h, const int version)
+	template <typename Handler> void serialize(Handler & h)
 	{
 		h & artifact;
 		h & locked;
@@ -223,7 +223,7 @@ public:
 	virtual void removeArtifact(ArtifactPosition slot);
 	virtual ~CArtifactSet();
 
-	template <typename Handler> void serialize(Handler &h, const int version)
+	template <typename Handler> void serialize(Handler &h)
 	{
 		h & artifactsInBackpack;
 		h & artifactsWorn;

+ 3 - 3
lib/CArtifactInstance.h

@@ -27,7 +27,7 @@ public:
 	{
 		ConstTransitivePtr<CArtifactInstance> art;
 		ArtifactPosition slot;
-		template <typename Handler> void serialize(Handler & h, const int version)
+		template <typename Handler> void serialize(Handler & h)
 		{
 			h & art;
 			h & slot;
@@ -41,7 +41,7 @@ public:
 	const std::vector<PartInfo> & getPartsInfo() const;
 	void addPlacementMap(CArtifactSet::ArtPlacementMap & placementMap);
 
-	template <typename Handler> void serialize(Handler & h, const int version)
+	template <typename Handler> void serialize(Handler & h)
 	{
 		h & partsInfo;
 	}
@@ -93,7 +93,7 @@ public:
 	void move(CArtifactSet & srcSet, const ArtifactPosition srcSlot, CArtifactSet & dstSet, const ArtifactPosition dstSlot);
 	
 	void deserializationFix();
-	template <typename Handler> void serialize(Handler & h, const int version)
+	template <typename Handler> void serialize(Handler & h)
 	{
 		h & static_cast<CBonusSystemNode&>(*this);
 		h & static_cast<CCombinedArtifactInstance&>(*this);

+ 7 - 4
lib/CBonusTypeHandler.cpp

@@ -76,10 +76,10 @@ std::string CBonusTypeHandler::bonusToString(const std::shared_ptr<Bonus> & bonu
 	if (text.find("${val}") != std::string::npos)
 		boost::algorithm::replace_all(text, "${val}", std::to_string(bearer->valOfBonuses(Selector::typeSubtype(bonus->type, bonus->subtype))));
 
-	if (text.find("${subtype.creature}") != std::string::npos && bonus->subtype.as<CreatureID>() != CreatureID::NONE)
+	if (text.find("${subtype.creature}") != std::string::npos && bonus->subtype.as<CreatureID>().hasValue())
 		boost::algorithm::replace_all(text, "${subtype.creature}", bonus->subtype.as<CreatureID>().toCreature()->getNamePluralTranslated());
 
-	if (text.find("${subtype.spell}") != std::string::npos && bonus->subtype.as<SpellID>() != SpellID::NONE)
+	if (text.find("${subtype.spell}") != std::string::npos && bonus->subtype.as<SpellID>().hasValue())
 		boost::algorithm::replace_all(text, "${subtype.spell}", bonus->subtype.as<SpellID>().toSpell()->getNameTranslated());
 
 	return text;
@@ -95,8 +95,11 @@ ImagePath CBonusTypeHandler::bonusToGraphics(const std::shared_ptr<Bonus> & bonu
 	case BonusType::SPELL_IMMUNITY:
 	{
 		fullPath = true;
-		const CSpell * sp = bonus->subtype.as<SpellID>().toSpell();
-		fileName = sp->getIconImmune();
+		if (bonus->subtype.as<SpellID>().hasValue())
+		{
+			const CSpell * sp = bonus->subtype.as<SpellID>().toSpell();
+			fileName = sp->getIconImmune();
+		}
 		break;
 	}
 	case BonusType::SPELL_DAMAGE_REDUCTION: //Spell damage reduction for all schools

+ 2 - 2
lib/CBonusTypeHandler.h

@@ -27,7 +27,7 @@ public:
 	std::string getNameTextID() const;
 	std::string getDescriptionTextID() const;
 
-	template <typename Handler> void serialize(Handler & h, const int version)
+	template <typename Handler> void serialize(Handler & h)
 	{
 		h & icon;
 		h & identifier;
@@ -53,7 +53,7 @@ public:
 	std::string bonusToString(const std::shared_ptr<Bonus> & bonus, const IBonusBearer * bearer, bool description) const override;
 	ImagePath bonusToGraphics(const std::shared_ptr<Bonus> & bonus) const override;
 
-	template <typename Handler> void serialize(Handler & h, const int version)
+	template <typename Handler> void serialize(Handler & h)
 	{
 		//for now always use up to date configuration
 		//once modded bonus type will be implemented, serialize only them

+ 5 - 5
lib/CCreatureSet.h

@@ -46,7 +46,7 @@ public:
 	
 	friend bool operator== (const CStackBasicDescriptor & l, const CStackBasicDescriptor & r);
 
-	template <typename Handler> void serialize(Handler &h, const int version)
+	template <typename Handler> void serialize(Handler &h)
 	{
 		if(h.saving)
 		{
@@ -85,7 +85,7 @@ public:
 	const CArmedInstance * const & armyObj; //stack must be part of some army, army must be part of some object
 	TExpType experience;//commander needs same amount of exp as hero
 
-	template <typename Handler> void serialize(Handler &h, const int version)
+	template <typename Handler> void serialize(Handler &h)
 	{
 		h & static_cast<CBonusSystemNode&>(*this);
 		h & static_cast<CStackBasicDescriptor&>(*this);
@@ -157,7 +157,7 @@ public:
 	int getLevel() const override;
 	ArtBearer::ArtBearer bearerType() const override; //from CArtifactSet
 
-	template <typename Handler> void serialize(Handler &h, const int version)
+	template <typename Handler> void serialize(Handler &h)
 	{
 		h & static_cast<CStackInstance&>(*this);
 		h & alive;
@@ -197,7 +197,7 @@ public:
 	bool setCreature(SlotID slot, CreatureID cre, TQuantity count) override;
 	operator bool() const;
 
-	template <typename Handler> void serialize(Handler &h, const int version)
+	template <typename Handler> void serialize(Handler &h)
 	{
 		h & army;
 	}
@@ -280,7 +280,7 @@ public:
 	bool contains(const CStackInstance *stack) const;
 	bool canBeMergedWith(const CCreatureSet &cs, bool allowMergingStacks = true) const;
 
-	template <typename Handler> void serialize(Handler &h, const int version)
+	template <typename Handler> void serialize(Handler &h)
 	{
 		h & stacks;
 		h & formation;

+ 2 - 4
lib/CGameInterface.cpp

@@ -243,9 +243,8 @@ void CAdventureAI::yourTacticPhase(const BattleID & battleID, int distance)
 	battleAI->yourTacticPhase(battleID, distance);
 }
 
-void CAdventureAI::saveGame(BinarySerializer & h, const int version) /*saving */
+void CAdventureAI::saveGame(BinarySerializer & h) /*saving */
 {
-	LOG_TRACE_PARAMS(logAi, "version '%i'", version);
 	bool hasBattleAI = static_cast<bool>(battleAI);
 	h & hasBattleAI;
 	if(hasBattleAI)
@@ -254,9 +253,8 @@ void CAdventureAI::saveGame(BinarySerializer & h, const int version) /*saving */
 	}
 }
 
-void CAdventureAI::loadGame(BinaryDeserializer & h, const int version) /*loading */
+void CAdventureAI::loadGame(BinaryDeserializer & h) /*loading */
 {
-	LOG_TRACE_PARAMS(logAi, "version '%i'", version);
 	bool hasBattleAI = false;
 	h & hasBattleAI;
 	if(hasBattleAI)

+ 4 - 4
lib/CGameInterface.h

@@ -111,8 +111,8 @@ public:
 
 	virtual std::optional<BattleAction> makeSurrenderRetreatDecision(const BattleID & battleID, const BattleStateInfoForRetreat & battleState) = 0;
 
-	virtual void saveGame(BinarySerializer & h, const int version) = 0;
-	virtual void loadGame(BinaryDeserializer & h, const int version) = 0;
+	virtual void saveGame(BinarySerializer & h) = 0;
+	virtual void loadGame(BinaryDeserializer & h) = 0;
 };
 
 class DLL_LINKAGE CDynLibHandler
@@ -162,8 +162,8 @@ public:
 	virtual void battleEnd(const BattleID & battleID, const BattleResult *br, QueryID queryID) override;
 	virtual void battleUnitsChanged(const BattleID & battleID, const std::vector<UnitChanges> & units) override;
 
-	virtual void saveGame(BinarySerializer & h, const int version) override;
-	virtual void loadGame(BinaryDeserializer & h, const int version) override;
+	virtual void saveGame(BinarySerializer & h) override;
+	virtual void loadGame(BinaryDeserializer & h) override;
 };
 
 VCMI_LIB_NAMESPACE_END

+ 2 - 2
lib/CGeneralTextHandler.h

@@ -135,7 +135,7 @@ protected:
 		std::string modContext;
 		
 		template <typename Handler>
-		void serialize(Handler & h, const int Version)
+		void serialize(Handler & h)
 		{
 			h & baseValue;
 			h & baseLanguage;
@@ -193,7 +193,7 @@ public:
 	void jsonSerialize(JsonNode & dest) const;
 	
 	template <typename Handler>
-	void serialize(Handler & h, const int Version)
+	void serialize(Handler & h)
 	{
 		std::string key;
 		auto sz = stringsLocalizations.size();

+ 4 - 4
lib/CPlayerState.h

@@ -40,10 +40,10 @@ struct DLL_LINKAGE PlayerState : public CBonusSystemNode, public Player
 				return subID < other.subID;
 		}
 
-		template <typename Handler> void serialize(Handler &h, const int version)
+		template <typename Handler> void serialize(Handler &h)
 		{
 			h & id;
-			subID.serializeIdentifier(h, id, version);
+			subID.serializeIdentifier(h, id);
 		}
 	};
 
@@ -89,7 +89,7 @@ public:
 		return heroes.empty() && towns.empty();
 	}
 
-	template <typename Handler> void serialize(Handler &h, const int version)
+	template <typename Handler> void serialize(Handler &h)
 	{
 		h & color;
 		h & human;
@@ -123,7 +123,7 @@ public:
 
 	TeamState();
 
-	template <typename Handler> void serialize(Handler &h, const int version)
+	template <typename Handler> void serialize(Handler &h)
 	{
 		h & id;
 		h & players;

+ 1 - 1
lib/CRandomGenerator.h

@@ -87,7 +87,7 @@ private:
 
 public:
 	template <typename Handler>
-	void serialize(Handler & h, const int version)
+	void serialize(Handler & h)
 	{
 		if(h.saving)
 		{

+ 1 - 1
lib/CStack.h

@@ -93,7 +93,7 @@ public:
 		return this->owner;
 	}
 
-	template <typename Handler> void serialize(Handler & h, const int version)
+	template <typename Handler> void serialize(Handler & h)
 	{
 		//this assumes that stack objects is newly created
 		//stackState is not serialized here

+ 1 - 1
lib/Color.h

@@ -50,7 +50,7 @@ public:
 	{}
 
 	template <typename Handler>
-	void serialize(Handler &h, const int version)
+	void serialize(Handler &h)
 	{
 		h & r;
 		h & g;

+ 1 - 1
lib/ConstTransitivePtr.h

@@ -69,7 +69,7 @@ public:
 		ptr = nullptr;
 	}
 
-	template <typename Handler> void serialize(Handler &h, const int version)
+	template <typename Handler> void serialize(Handler &h)
 	{
 		h & ptr;
 	}

+ 1 - 1
lib/ExtraOptionsInfo.h

@@ -20,7 +20,7 @@ struct DLL_LINKAGE ExtraOptionsInfo
 	bool operator == (const ExtraOptionsInfo & other) const;
 
 	template <typename Handler>
-	void serialize(Handler &h, const int version)
+	void serialize(Handler &h)
 	{
 		h & cheatsAllowed;
 		h & unlimitedReplay;

+ 1 - 0
lib/GameSettings.cpp

@@ -74,6 +74,7 @@ void GameSettings::load(const JsonNode & input)
 		{EGameSettings::HEROES_RETREAT_ON_WIN_WITHOUT_TROOPS,   "heroes",    "retreatOnWinWithoutTroops"  },
 		{EGameSettings::HEROES_STARTING_STACKS_CHANCES,         "heroes",    "startingStackChances"       },
 		{EGameSettings::HEROES_BACKPACK_CAP,                    "heroes",    "backpackSize"               },
+		{EGameSettings::HEROES_TAVERN_INVITE,                   "heroes",    "tavernInvite"               },
 		{EGameSettings::MAP_FORMAT_RESTORATION_OF_ERATHIA,      "mapFormat", "restorationOfErathia"       },
 		{EGameSettings::MAP_FORMAT_ARMAGEDDONS_BLADE,           "mapFormat", "armageddonsBlade"           },
 		{EGameSettings::MAP_FORMAT_SHADOW_OF_DEATH,             "mapFormat", "shadowOfDeath"              },

+ 2 - 1
lib/GameSettings.h

@@ -38,6 +38,7 @@ enum class EGameSettings
 	HEROES_RETREAT_ON_WIN_WITHOUT_TROOPS,
 	HEROES_STARTING_STACKS_CHANCES,
 	HEROES_BACKPACK_CAP,
+	HEROES_TAVERN_INVITE,
 	MARKETS_BLACK_MARKET_RESTOCK_PERIOD,
 	BANKS_SHOW_GUARDS_COMPOSITION,
 	MODULE_COMMANDERS,
@@ -96,7 +97,7 @@ public:
 	const JsonNode & getValue(EGameSettings option) const override;
 
 	template<typename Handler>
-	void serialize(Handler & h, const int version)
+	void serialize(Handler & h)
 	{
 		h & gameSettings;
 	}

+ 1 - 1
lib/JsonNode.h

@@ -118,7 +118,7 @@ public:
 
 	std::string toJson(bool compact = false) const;
 
-	template <typename Handler> void serialize(Handler &h, const int version)
+	template <typename Handler> void serialize(Handler &h)
 	{
 		h & meta;
 		h & flags;

+ 2 - 2
lib/JsonRandom.cpp

@@ -80,7 +80,7 @@ VCMI_LIB_NAMESPACE_BEGIN
 	IdentifierType JsonRandom::decodeKey(const std::string & modScope, const std::string & value, const Variables & variables)
 	{
 		if (value.empty() || value[0] != '@')
-			return IdentifierType(*VLC->identifiers()->getIdentifier(modScope, IdentifierType::entityType(), value));
+			return IdentifierType(VLC->identifiers()->getIdentifier(modScope, IdentifierType::entityType(), value).value_or(-1));
 		else
 			return loadVariable(IdentifierType::entityType(), value, variables, IdentifierType::NONE);
 	}
@@ -89,7 +89,7 @@ VCMI_LIB_NAMESPACE_BEGIN
 	IdentifierType JsonRandom::decodeKey(const JsonNode & value, const Variables & variables)
 	{
 		if (value.String().empty() || value.String()[0] != '@')
-			return IdentifierType(*VLC->identifiers()->getIdentifier(IdentifierType::entityType(), value));
+			return IdentifierType(VLC->identifiers()->getIdentifier(IdentifierType::entityType(), value).value_or(-1));
 		else
 			return loadVariable(IdentifierType::entityType(), value.String(), variables, IdentifierType::NONE);
 	}

+ 2 - 2
lib/LogicalExpression.h

@@ -57,7 +57,7 @@ namespace LogicalExpressionDetail
 			}
 
 			template <typename Handler>
-			void serialize(Handler & h, const int version)
+			void serialize(Handler & h)
 			{
 				h & expressions;
 			}
@@ -614,7 +614,7 @@ public:
 	}
 
 	template <typename Handler>
-	void serialize(Handler & h, const int version)
+	void serialize(Handler & h)
 	{
 		h & data;
 	}

+ 1 - 1
lib/MetaString.h

@@ -125,7 +125,7 @@ public:
 	
 	void serializeJson(JsonSerializeFormat & handler);
 
-	template <typename Handler> void serialize(Handler & h, const int version)
+	template <typename Handler> void serialize(Handler & h)
 	{
 		h & exactStrings;
 		h & localStrings;

+ 1 - 1
lib/Point.h

@@ -122,7 +122,7 @@ public:
 	}
 
 	template <typename Handler>
-	void serialize(Handler &h, const int version)
+	void serialize(Handler &h)
 	{
 		h & x;
 		h & y;

+ 1 - 1
lib/Rect.h

@@ -163,7 +163,7 @@ public:
 	DLL_LINKAGE Rect include(const Rect & other) const;
 
 	template <typename Handler>
-	void serialize(Handler &h, const int version)
+	void serialize(Handler &h)
 	{
 		h & x;
 		h & y;

+ 1 - 1
lib/ResourceSet.h

@@ -180,7 +180,7 @@ public:
 // 			return true;
 // 		}
 
-	template <typename Handler> void serialize(Handler &h, const int version)
+	template <typename Handler> void serialize(Handler &h)
 	{
 		h & container;
 	}

+ 1 - 1
lib/ScriptHandler.h

@@ -108,7 +108,7 @@ public:
 
 	void run(std::shared_ptr<Pool> pool) const override;
 
-	template <typename Handler> void serialize(Handler & h, const int version)
+	template <typename Handler> void serialize(Handler & h)
 	{
 		JsonNode state;
 		if(h.saving)

+ 6 - 6
lib/StartInfo.h

@@ -41,7 +41,7 @@ struct DLL_LINKAGE SimturnsInfo
 	}
 
 	template <typename Handler>
-	void serialize(Handler &h, const int version)
+	void serialize(Handler &h)
 	{
 		h & requiredTurns;
 		h & optionalTurns;
@@ -76,7 +76,7 @@ struct DLL_LINKAGE PlayerSettings
 	std::set<ui8> connectedPlayerIDs; //Empty - AI, or connectrd player ids
 	bool compOnly; //true if this player is a computer only player; required for RMG
 	template <typename Handler>
-	void serialize(Handler &h, const int version)
+	void serialize(Handler &h)
 	{
 		h & castle;
 		h & hero;
@@ -137,7 +137,7 @@ struct DLL_LINKAGE StartInfo
 	std::string getCampaignName() const;
 
 	template <typename Handler>
-	void serialize(Handler &h, const int version)
+	void serialize(Handler &h)
 	{
 		h & mode;
 		h & difficulty;
@@ -149,7 +149,7 @@ struct DLL_LINKAGE StartInfo
 		h & fileURI;
 		h & simturnsInfo;
 		h & turnTimerInfo;
-		if(version >= 832)
+		if(h.version >= Handler::Version::HAS_EXTRA_OPTIONS)
 			h & extraOptionsInfo;
 		else
 			extraOptionsInfo = ExtraOptionsInfo();
@@ -170,7 +170,7 @@ struct ClientPlayer
 	int connection;
 	std::string name;
 
-	template <typename Handler> void serialize(Handler &h, const int version)
+	template <typename Handler> void serialize(Handler &h)
 	{
 		h & connection;
 		h & name;
@@ -190,7 +190,7 @@ struct DLL_LINKAGE LobbyState
 
 	LobbyState() : si(new StartInfo()), hostClientId(-1), campaignMap(CampaignScenarioID::NONE), campaignBonus(-1) {}
 
-	template <typename Handler> void serialize(Handler &h, const int version)
+	template <typename Handler> void serialize(Handler &h)
 	{
 		h & si;
 		h & mi;

+ 1 - 1
lib/TerrainHandler.h

@@ -26,7 +26,7 @@ struct DLL_LINKAGE TerrainPaletteAnimation
 	/// total numbers of colors to cycle
 	int32_t length;
 
-	template <typename Handler> void serialize(Handler& h, const int version)
+	template <typename Handler> void serialize(Handler& h)
 	{
 		h & start;
 		h & length;

+ 1 - 1
lib/TurnTimerInfo.h

@@ -34,7 +34,7 @@ struct DLL_LINKAGE TurnTimerInfo
 	bool operator == (const TurnTimerInfo & other) const;
 
 	template <typename Handler>
-	void serialize(Handler &h, const int version)
+	void serialize(Handler &h)
 	{
 		h & turnTimer;
 		h & baseTimer;

+ 2 - 2
lib/battle/BattleAction.h

@@ -55,7 +55,7 @@ public:
 	battle::Target getTarget(const CBattleInfoCallback * cb) const;
 	void setTarget(const battle::Target & target_);
 
-	template <typename Handler> void serialize(Handler & h, const int version)
+	template <typename Handler> void serialize(Handler & h)
 	{
 		h & side;
 		h & stackNumber;
@@ -70,7 +70,7 @@ private:
 		int32_t unitValue;
 		BattleHex hexValue;
 
-		template <typename Handler> void serialize(Handler & h, const int version)
+		template <typename Handler> void serialize(Handler & h)
 		{
 			h & unitValue;
 			h & hexValue;

+ 1 - 1
lib/battle/BattleHex.h

@@ -105,7 +105,7 @@ struct DLL_LINKAGE BattleHex //TODO: decide if this should be changed to class f
 	static BattleHex getClosestTile(ui8 side, BattleHex initialPos, std::set<BattleHex> & possibilities); //TODO: vector or set? copying one to another is bad
 
 	template <typename Handler>
-	void serialize(Handler &h, const int version)
+	void serialize(Handler &h)
 	{
 		h & hex;
 	}

+ 2 - 5
lib/battle/BattleInfo.h

@@ -50,7 +50,7 @@ public:
 	ui8 tacticsSide; //which side is requested to play tactics phase
 	ui8 tacticDistance; //how many hexes we can go forward (1 = only hexes adjacent to margin line)
 
-	template <typename Handler> void serialize(Handler &h, const int version)
+	template <typename Handler> void serialize(Handler &h)
 	{
 		h & battleID;
 		h & sides;
@@ -66,10 +66,7 @@ public:
 		h & tacticsSide;
 		h & tacticDistance;
 		h & static_cast<CBonusSystemNode&>(*this);
-		if (version > 824)
-			h & replayAllowed;
-		else
-			replayAllowed = false;
+		h & replayAllowed;
 	}
 
 	//////////////////////////////////////////////////////////////////////////

+ 3 - 2
lib/battle/CBattleInfoCallback.cpp

@@ -867,9 +867,10 @@ bool CBattleInfoCallback::handleObstacleTriggersForUnit(SpellCastEnvironment & s
 			auto shouldReveal = !spellObstacle->hidden || !battleIsObstacleVisibleForSide(*obstacle, (BattlePerspective::BattlePerspective)side);
 			const auto * hero = battleGetFightingHero(spellObstacle->casterSide);
 			auto caster = spells::ObstacleCasterProxy(getBattle()->getSidePlayer(spellObstacle->casterSide), hero, *spellObstacle);
-			const auto * sp = obstacle->getTrigger().toSpell();
-			if(obstacle->triggersEffects() && sp)
+
+			if(obstacle->triggersEffects() && obstacle->getTrigger().hasValue())
 			{
+				const auto * sp = obstacle->getTrigger().toSpell();
 				auto cast = spells::BattleCast(this, &caster, spells::Mode::PASSIVE, sp);
 				spells::detail::ProblemImpl ignored;
 				auto target = spells::Target(1, spells::Destination(&unit));

+ 1 - 1
lib/battle/CBattleInfoCallback.h

@@ -36,7 +36,7 @@ struct DLL_LINKAGE AttackableTiles
 {
 	std::set<BattleHex> hostileCreaturePositions;
 	std::set<BattleHex> friendlyCreaturePositions; //for Dragon Breath
-	template <typename Handler> void serialize(Handler &h, const int version)
+	template <typename Handler> void serialize(Handler &h)
 	{
 		h & hostileCreaturePositions;
 		h & friendlyCreaturePositions;

+ 2 - 2
lib/battle/CObstacleInstance.h

@@ -63,7 +63,7 @@ struct DLL_LINKAGE CObstacleInstance
 	
 	virtual void serializeJson(JsonSerializeFormat & handler);
 
-	template <typename Handler> void serialize(Handler &h, const int version)
+	template <typename Handler> void serialize(Handler &h)
 	{
 		h & ID;
 		h & pos;
@@ -118,7 +118,7 @@ struct DLL_LINKAGE SpellCreatedObstacle : CObstacleInstance
 
 	void serializeJson(JsonSerializeFormat & handler) override;
 
-	template <typename Handler> void serialize(Handler &h, const int version)
+	template <typename Handler> void serialize(Handler &h)
 	{
 		h & static_cast<CObstacleInstance&>(*this);
 		h & turnsRemaining;

+ 1 - 1
lib/battle/SideInBattle.h

@@ -28,7 +28,7 @@ struct DLL_LINKAGE SideInBattle
 	void init(const CGHeroInstance * Hero, const CArmedInstance * Army);
 
 
-	template <typename Handler> void serialize(Handler &h, const int version)
+	template <typename Handler> void serialize(Handler &h)
 	{
 		h & color;
 		h & hero;

+ 1 - 1
lib/battle/SiegeInfo.h

@@ -23,7 +23,7 @@ struct DLL_LINKAGE SiegeInfo
 	// return EWallState decreased by value of damage points
 	static EWallState applyDamage(EWallState state, unsigned int value);
 
-	template <typename Handler> void serialize(Handler &h, const int version)
+	template <typename Handler> void serialize(Handler &h)
 	{
 		h & wallState;
 		h & gateState;

+ 1 - 1
lib/bonuses/Bonus.h

@@ -86,7 +86,7 @@ struct DLL_LINKAGE Bonus : public std::enable_shared_from_this<Bonus>
 	Bonus(BonusDuration::Type Duration, BonusType Type, BonusSource Src, si32 Val, BonusSourceID sourceID, BonusSubtypeID subtype, BonusValueType ValType);
 	Bonus() = default;
 
-	template <typename Handler> void serialize(Handler &h, const int version)
+	template <typename Handler> void serialize(Handler &h)
 	{
 		h & duration;
 		h & type;

+ 1 - 1
lib/bonuses/BonusList.h

@@ -91,7 +91,7 @@ public:
 	void insert(TInternalContainer::iterator position, TInternalContainer::size_type n, const std::shared_ptr<Bonus> & x);
 
 	template <typename Handler>
-	void serialize(Handler &h, const int version)
+	void serialize(Handler &h)
 	{
 		h & static_cast<TInternalContainer&>(bonuses);
 	}

+ 1 - 1
lib/bonuses/CBonusSystemNode.h

@@ -130,7 +130,7 @@ public:
 		return PlayerColor::NEUTRAL;
 	}
 
-	template <typename Handler> void serialize(Handler &h, const int version)
+	template <typename Handler> void serialize(Handler &h)
 	{
 		h & nodeType;
 		h & exportedBonuses;

+ 11 - 11
lib/bonuses/Limiters.h

@@ -38,7 +38,7 @@ public:
 	virtual std::string toString() const;
 	virtual JsonNode toJsonNode() const;
 
-	template <typename Handler> void serialize(Handler &h, const int version)
+	template <typename Handler> void serialize(Handler &h)
 	{
 	}
 };
@@ -53,7 +53,7 @@ public:
 	void add(const TLimiterPtr & limiter);
 	JsonNode toJsonNode() const override;
 
-	template <typename Handler> void serialize(Handler & h, const int version)
+	template <typename Handler> void serialize(Handler & h)
 	{
 		h & static_cast<ILimiter&>(*this);
 		h & limiters;
@@ -104,7 +104,7 @@ public:
 	std::string toString() const override;
 	JsonNode toJsonNode() const override;
 
-	template <typename Handler> void serialize(Handler &h, const int version)
+	template <typename Handler> void serialize(Handler &h)
 	{
 		h & static_cast<ILimiter&>(*this);
 		h & creature;
@@ -132,7 +132,7 @@ public:
 	std::string toString() const override;
 	JsonNode toJsonNode() const override;
 
-	template <typename Handler> void serialize(Handler &h, const int version)
+	template <typename Handler> void serialize(Handler &h)
 	{
 		h & static_cast<ILimiter&>(*this);
 		h & type;
@@ -156,7 +156,7 @@ public:
 	std::string toString() const override;
 	JsonNode toJsonNode() const override;
 
-	template <typename Handler> void serialize(Handler &h, const int version)
+	template <typename Handler> void serialize(Handler &h)
 	{
 		h & static_cast<ILimiter&>(*this);
 		h & terrainType;
@@ -175,7 +175,7 @@ public:
 	std::string toString() const override;
 	JsonNode toJsonNode() const override;
 
-	template <typename Handler> void serialize(Handler &h, const int version)
+	template <typename Handler> void serialize(Handler &h)
 	{
 		h & static_cast<ILimiter&>(*this);
 		h & minLevel;
@@ -193,7 +193,7 @@ public:
 	std::string toString() const override;
 	JsonNode toJsonNode() const override;
 
-	template <typename Handler> void serialize(Handler &h, const int version)
+	template <typename Handler> void serialize(Handler &h)
 	{
 		h & static_cast<ILimiter&>(*this);
 		h & faction;
@@ -210,7 +210,7 @@ public:
 	std::string toString() const override;
 	JsonNode toJsonNode() const override;
 
-	template <typename Handler> void serialize(Handler &h, const int version)
+	template <typename Handler> void serialize(Handler &h)
 	{
 		h & static_cast<ILimiter&>(*this);
 		h & alignment;
@@ -225,7 +225,7 @@ public:
 
 	EDecision limit(const BonusLimitationContext &context) const override;
 
-	template <typename Handler> void serialize(Handler &h, const int version)
+	template <typename Handler> void serialize(Handler &h)
 	{
 		h & static_cast<ILimiter&>(*this);
 		h & owner;
@@ -242,7 +242,7 @@ public:
 	RankRangeLimiter(ui8 Min, ui8 Max = 255);
 	EDecision limit(const BonusLimitationContext &context) const override;
 
-	template <typename Handler> void serialize(Handler &h, const int version)
+	template <typename Handler> void serialize(Handler &h)
 	{
 		h & static_cast<ILimiter&>(*this);
 		h & minRank;
@@ -259,7 +259,7 @@ public:
 	EDecision limit(const BonusLimitationContext &context) const override;
 	JsonNode toJsonNode() const override;
 
-	template <typename Handler> void serialize(Handler &h, const int version)
+	template <typename Handler> void serialize(Handler &h)
 	{
 		h & static_cast<ILimiter&>(*this);
 		h & applicableHexes;

Kaikkia tiedostoja ei voida näyttää, sillä liian monta tiedostoa muuttui tässä diffissä