Browse Source

Merge branch 'develop' into SpellsRefactoring4

AlexVinS 10 years ago
parent
commit
eebf65e88f
46 changed files with 879 additions and 681 deletions
  1. 2 2
      AI/StupidAI/StupidAI.cpp
  2. 2 2
      AI/StupidAI/StupidAI.h
  3. 5 2
      AI/VCAI/AIUtility.cpp
  4. 111 105
      AI/VCAI/Fuzzy.cpp
  5. 13 10
      AI/VCAI/Fuzzy.h
  6. 14 3
      AI/VCAI/Goals.cpp
  7. 2 2
      AI/VCAI/VCAI.cpp
  8. 2 2
      AI/VCAI/VCAI.h
  9. 2 2
      client/CPlayerInterface.cpp
  10. 2 2
      client/CPlayerInterface.h
  11. 4 0
      client/CPreGame.cpp
  12. 22 18
      client/Client.cpp
  13. 7 2
      client/Client.h
  14. 1 0
      client/VCMI_client.vcxproj
  15. 1 1
      client/widgets/AdventureMapClasses.cpp
  16. 5 0
      client/widgets/Buttons.cpp
  17. 1 0
      client/widgets/Buttons.h
  18. 8 3
      client/widgets/TextControls.cpp
  19. 3 2
      lib/CGameInfoCallback.cpp
  20. 4 4
      lib/CGameInterface.cpp
  21. 6 6
      lib/CGameInterface.h
  22. 5 1
      lib/CGameState.cpp
  23. 45 54
      lib/Connection.cpp
  24. 314 246
      lib/Connection.h
  25. 8 8
      lib/IGameCallback.cpp
  26. 1 0
      lib/VCMI_lib.vcxproj
  27. 23 0
      lib/mapObjects/CommonConstructors.cpp
  28. 7 1
      lib/mapObjects/CommonConstructors.h
  29. 2 1
      lib/mapObjects/MiscObjects.cpp
  30. 5 14
      lib/registerTypes/RegisterTypes.cpp
  31. 4 7
      lib/registerTypes/RegisterTypes.h
  32. 2 7
      lib/registerTypes/TypesClientPacks1.cpp
  33. 4 7
      lib/registerTypes/TypesClientPacks2.cpp
  34. 3 7
      lib/registerTypes/TypesMapObjects1.cpp
  35. 2 7
      lib/registerTypes/TypesMapObjects2.cpp
  36. 2 7
      lib/registerTypes/TypesMapObjects3.cpp
  37. 3 7
      lib/registerTypes/TypesPregamePacks.cpp
  38. 2 7
      lib/registerTypes/TypesServerPacks.cpp
  39. 1 1
      lib/rmg/CMapGenerator.cpp
  40. 0 7
      lib/rmg/CRmgTemplateStorage.cpp
  41. 113 70
      lib/rmg/CRmgTemplateZone.cpp
  42. 5 5
      lib/rmg/CRmgTemplateZone.h
  43. 103 38
      lib/rmg/CZonePlacer.cpp
  44. 1 1
      server/CGameHandler.cpp
  45. 1 0
      test/Test.vcxproj
  46. 6 10
      vcmibuilder

+ 2 - 2
AI/StupidAI/StupidAI.cpp

@@ -321,13 +321,13 @@ BattleAction CStupidAI::goTowards(const CStack * stack, BattleHex destination)
 	}
 }
 
-void CStupidAI::saveGame(COSer<CSaveFile> &h, const int version)
+void CStupidAI::saveGame(COSer & h, const int version)
 {
 	//TODO to be implemented with saving/loading during the battles
 	assert(0);
 }
 
-void CStupidAI::loadGame(CISer<CLoadFile> &h, const int version)
+void CStupidAI::loadGame(CISer & h, const int version)
 {
 	//TODO to be implemented with saving/loading during the battles
 	assert(0);

+ 2 - 2
AI/StupidAI/StupidAI.h

@@ -36,8 +36,8 @@ public:
 
 	BattleAction goTowards(const CStack * stack, BattleHex hex );
 
-	virtual void saveGame(COSer<CSaveFile> &h, const int version) override;
-	virtual void loadGame(CISer<CLoadFile> &h, const int version) override;
+	virtual void saveGame(COSer & h, const int version) override;
+	virtual void loadGame(CISer & h, const int version) override;
 
 };
 

+ 5 - 2
AI/VCAI/AIUtility.cpp

@@ -234,8 +234,11 @@ ui64 evaluateDanger(crint3 tile, const CGHeroInstance *visitor)
 		{
 			//TODO: don't downcast objects AI shouldn't know about!
 			auto armedObj = dynamic_cast<const CArmedInstance*>(dangerousObject);
-			if(armedObj)
-				objectDanger *= fh->getTacticalAdvantage(visitor, armedObj); //this line tends to go infinite for allied towns (?)
+			if (armedObj)
+			{
+				float tacticalAdvantage = fh->getTacticalAdvantage(visitor, armedObj);
+				objectDanger *= tacticalAdvantage; //this line tends to go infinite for allied towns (?)
+			}
 		}
 		if (dangerousObject->ID == Obj::SUBTERRANEAN_GATE)
 		{ //check guard on the other side of the gate

+ 111 - 105
AI/VCAI/Fuzzy.cpp

@@ -20,9 +20,11 @@
 */
 
 #define MIN_AI_STRENGHT (0.5f) //lower when combat AI gets smarter
-#define UNGUARDED_OBJECT (100.0f) //we consider unguarded objects 10 times weaker than us
+#define UNGUARDED_OBJECT (100.0f) //we consider unguarded objects 100 times weaker than us
 
 struct BankConfig;
+class IObjectInfo;
+class CBankInfo;
 class Engine;
 class InputVariable;
 class CGTownInstance;
@@ -35,6 +37,22 @@ FuzzyHelper *fh;
 extern boost::thread_specific_ptr<CCallback> cb;
 extern boost::thread_specific_ptr<VCAI> ai;
 
+engineBase::engineBase()
+{
+	engine.addRuleBlock(&rules);
+}
+
+void engineBase::configure()
+{
+	engine.configure("Minimum", "Maximum", "Minimum", "AlgebraicSum", "Centroid");
+	logAi->infoStream() << engine.toString();
+}
+
+void engineBase::addRule(const std::string &txt)
+{
+	rules.addRule(fl::Rule::parse(txt, &engine));
+}
+
 struct armyStructure
 {
 	float walkers, shooters, flyers;
@@ -72,45 +90,18 @@ armyStructure evaluateArmyStructure (const CArmedInstance * army)
 	as.shooters = shootersStrenght / totalStrenght;
 	as.flyers = flyersStrenght / totalStrenght;
 	as.maxSpeed = maxSpeed;
+	assert(as.walkers || as.flyers || as.shooters);
 	return as;
 }
 
 FuzzyHelper::FuzzyHelper()
 {
-	initBank();
 	initTacticalAdvantage();
+	ta.configure();
 	initVisitTile();
-	
-	engine.configure("AlgebraicProduct", "AlgebraicSum",
-			"AlgebraicProduct", "AlgebraicSum", "Centroid");
-
-	logAi->infoStream() << engine.toString();
+	vt.configure();
 }
 
-void FuzzyHelper::initBank()
-{
-	try
-	{
-		//Trivial bank estimation
-		bankInput = new fl::InputVariable("BankInput");
-		bankDanger = new fl::OutputVariable("BankDanger");
-		bankInput->addTerm(new fl::Rectangle("SET", 0.5 - 5 * fl::fuzzylite::macheps(),
-				0.5 + 5 * fl::fuzzylite::macheps()));
-
-		engine.addInputVariable(bankInput);
-		engine.addOutputVariable(bankDanger);
-		engine.addRuleBlock(&bankBlock);
-		for (int i = 0; i < 4; ++i)
-		{
-			bankDanger->addTerm(new fl::Triangle("Bank" + boost::lexical_cast<std::string>(i), 0, 1));
-			bankBlock.addRule(fl::Rule::parse("if BankInput is SET then BankDanger is Bank" + boost::lexical_cast<std::string>(i), &engine));
-		}
-	}
-	catch (fl::Exception & fe)
-	{
-		logAi->errorStream() << "initBank : " << fe.getWhat();
-	}
-}
 
 void FuzzyHelper::initTacticalAdvantage()
 {
@@ -132,9 +123,10 @@ void FuzzyHelper::initTacticalAdvantage()
 
 		for (auto val : helper)
 		{
-			engine.addInputVariable(val);
+			ta.engine.addInputVariable(val);
 			val->addTerm(new fl::Ramp("FEW", 0.6, 0.0));
 			val->addTerm(new fl::Ramp("MANY", 0.4, 1));
+			val->setRange(0.0, 1.0);
 		}
 
 		ta.ourSpeed = new fl::InputVariable("OurSpeed");
@@ -144,57 +136,66 @@ void FuzzyHelper::initTacticalAdvantage()
 
 		for (auto val : helper)
 		{
-			engine.addInputVariable(val);
+			ta.engine.addInputVariable(val);
 			val->addTerm(new fl::Ramp("LOW", 6.5, 3));
 			val->addTerm(new fl::Triangle("MEDIUM", 5.5, 10.5));
 			val->addTerm(new fl::Ramp("HIGH", 8.5, 16));
+			val->setRange(0, 25);
 		}
 
 		ta.castleWalls = new fl::InputVariable("CastleWalls");
-		engine.addInputVariable(ta.castleWalls);
-		ta.castleWalls->addTerm(new fl::Rectangle("NONE", CGTownInstance::NONE - 5.0 * fl::fuzzylite::macheps(),
-				CGTownInstance::NONE + 5.0 * fl::fuzzylite::macheps()));
+		ta.engine.addInputVariable(ta.castleWalls);
 		{
-			fl::scalar a = CGTownInstance::FORT, d = 2.5;
-			fl::scalar b = a + (d - a) * 1.0 / 5.0;
-			fl::scalar c = a + (d - a) * 4.0 / 5.0;
-			ta.castleWalls->addTerm(new fl::Trapezoid("MEDIUM", a, b, c, d));
+			fl::Rectangle* none = new fl::Rectangle("NONE", CGTownInstance::NONE, CGTownInstance::NONE + (CGTownInstance::FORT - CGTownInstance::NONE) * 0.5f);
+			ta.castleWalls->addTerm(none);
+                    
+			fl::Trapezoid* medium = new fl::Trapezoid("MEDIUM", (CGTownInstance::FORT - CGTownInstance::NONE) * 0.5f, CGTownInstance::FORT,
+				CGTownInstance::CITADEL, CGTownInstance::CITADEL + (CGTownInstance::CASTLE - CGTownInstance::CITADEL) * 0.5f);
+			ta.castleWalls->addTerm(medium);
+
+			fl::Ramp* high = new fl::Ramp("HIGH", CGTownInstance::CITADEL - 0.1, CGTownInstance::CASTLE);
+			ta.castleWalls->addTerm(high);
+
+			ta.castleWalls->setRange(CGTownInstance::NONE, CGTownInstance::CASTLE);
 		}
 
-		ta.castleWalls->addTerm(new fl::Ramp("HIGH", CGTownInstance::CITADEL - 0.1, CGTownInstance::CASTLE));
+		
 
 		ta.bankPresent = new fl::InputVariable("Bank");
-		engine.addInputVariable(ta.bankPresent);
-		ta.bankPresent->addTerm(new fl::Rectangle("FALSE", 0.0 - 5.0 * fl::fuzzylite::macheps(),
-				0.0 + 5.0 * fl::fuzzylite::macheps()));
-		ta.bankPresent->addTerm(new fl::Rectangle("TRUE", 1.0 - 5.0 * fl::fuzzylite::macheps(),
-				0.0 + 5.0 * fl::fuzzylite::macheps()));
+		ta.engine.addInputVariable(ta.bankPresent);
+		{
+			fl::Rectangle* termFalse = new fl::Rectangle("FALSE", 0.0, 0.5f);
+			ta.bankPresent->addTerm(termFalse);
+			fl::Rectangle* termTrue = new fl::Rectangle("TRUE", 0.5f, 1);
+			ta.bankPresent->addTerm(termTrue);
+			ta.bankPresent->setRange(0, 1);
+		}
 
 		ta.threat = new fl::OutputVariable("Threat");
-		engine.addOutputVariable(ta.threat);
+		ta.engine.addOutputVariable(ta.threat);
 		ta.threat->addTerm(new fl::Ramp("LOW", 1, MIN_AI_STRENGHT));
 		ta.threat->addTerm(new fl::Triangle("MEDIUM", 0.8, 1.2));
 		ta.threat->addTerm(new fl::Ramp("HIGH", 1, 1.5));
+		ta.threat->setRange(MIN_AI_STRENGHT, 1.5);
 
-		engine.addRuleBlock(&ta.tacticalAdvantage);
-
-		ta.tacticalAdvantage.addRule(fl::Rule::parse("if OurShooters is MANY and EnemySpeed is LOW then Threat is LOW", &engine));
-		ta.tacticalAdvantage.addRule(fl::Rule::parse("if OurShooters is MANY and EnemyShooters is FEW then Threat is LOW", &engine));
-		ta.tacticalAdvantage.addRule(fl::Rule::parse("if OurSpeed is LOW and EnemyShooters is MANY then Threat is HIGH", &engine));
-		ta.tacticalAdvantage.addRule(fl::Rule::parse("if OurSpeed is HIGH and EnemyShooters is MANY then Threat is LOW", &engine));
+		ta.addRule("if OurShooters is MANY and EnemySpeed is LOW then Threat is LOW");
+		ta.addRule("if OurShooters is MANY and EnemyShooters is FEW then Threat is LOW");
+		ta.addRule("if OurSpeed is LOW and EnemyShooters is MANY then Threat is HIGH");
+		ta.addRule("if OurSpeed is HIGH and EnemyShooters is MANY then Threat is LOW");
 
-		ta.tacticalAdvantage.addRule(fl::Rule::parse("if OurWalkers is FEW and EnemyShooters is MANY then Threat is somewhat LOW", &engine));
-		ta.tacticalAdvantage.addRule(fl::Rule::parse("if OurShooters is MANY and EnemySpeed is HIGH then Threat is somewhat HIGH", &engine));
+		ta.addRule("if OurWalkers is FEW and EnemyShooters is MANY then Threat is somewhat LOW");
+		ta.addRule("if OurShooters is MANY and EnemySpeed is HIGH then Threat is somewhat HIGH");
 		//just to cover all cases
-		ta.tacticalAdvantage.addRule(fl::Rule::parse("if EnemySpeed is MEDIUM then Threat is MEDIUM", &engine));
-		ta.tacticalAdvantage.addRule(fl::Rule::parse("if EnemySpeed is LOW and OurShooters is FEW then Threat is MEDIUM", &engine));
+		ta.addRule("if OurShooters is FEW and EnemySpeed is HIGH then Threat is MEDIUM");
+		ta.addRule("if EnemySpeed is MEDIUM then Threat is MEDIUM");
+		ta.addRule("if EnemySpeed is LOW and OurShooters is FEW then Threat is MEDIUM");
 
-		ta.tacticalAdvantage.addRule(fl::Rule::parse("if Bank is TRUE and OurShooters is MANY then Threat is somewhat HIGH", &engine));
-		ta.tacticalAdvantage.addRule(fl::Rule::parse("if Bank is TRUE and EnemyShooters is MANY then Threat is LOW", &engine));
+		ta.addRule("if Bank is TRUE and OurShooters is MANY then Threat is somewhat HIGH");
+		ta.addRule("if Bank is TRUE and EnemyShooters is MANY then Threat is LOW");
 
-		ta.tacticalAdvantage.addRule(fl::Rule::parse("if CastleWalls is HIGH and OurWalkers is MANY then Threat is very HIGH", &engine));
-		ta.tacticalAdvantage.addRule(fl::Rule::parse("if CastleWalls is HIGH and OurFlyers is MANY and OurShooters is MANY then Threat is MEDIUM", &engine));
-		ta.tacticalAdvantage.addRule(fl::Rule::parse("if CastleWalls is MEDIUM and OurShooters is MANY and EnemyWalkers is MANY then Threat is LOW", &engine));
+		ta.addRule("if CastleWalls is HIGH and OurWalkers is MANY then Threat is very HIGH");
+		ta.addRule("if CastleWalls is HIGH and OurFlyers is MANY and OurShooters is MANY then Threat is MEDIUM");
+		ta.addRule("if CastleWalls is MEDIUM and OurShooters is MANY and EnemyWalkers is MANY then Threat is LOW");
 
 	}
 	catch (fl::Exception & pe)
@@ -205,34 +206,20 @@ void FuzzyHelper::initTacticalAdvantage()
 
 ui64 FuzzyHelper::estimateBankDanger (const CBank * bank)
 {
-	auto info = VLC->objtypeh->getHandlerFor(bank->ID, bank->subID)->getObjectInfo(bank->appearance);
+	//this one is not fuzzy anymore, just calculate weighted average
 
-	ui64 val = std::numeric_limits<ui64>::max();
-	try
-	{
-		fl::Triangle* bank0 = dynamic_cast<fl::Triangle*> (bankDanger->getTerm("Bank0"));
-		fl::Triangle* bank1 = dynamic_cast<fl::Triangle*> (bankDanger->getTerm("Bank1"));
-		if (bank0 && bank1)
-		{
-			bank0->setVertexA(info->minGuards().totalStrength * 0.5f);
-			bank0->setVertexC(info->minGuards().totalStrength * 1.5f);
-			bank1->setVertexA(info->maxGuards().totalStrength * 0.5f);
-			bank1->setVertexC(info->maxGuards().totalStrength * 1.5f);
-		}
+	auto objectInfo = VLC->objtypeh->getHandlerFor(bank->ID, bank->subID)->getObjectInfo(bank->appearance);
 
-		//comparison purposes
-		//int averageValue = (evaluateBankConfig (VLC->objh->banksInfo[ID][0]) + evaluateBankConfig (VLC->objh->banksInfo[ID][3])) * 0.5;
-		//dynamic_cast<fl::SingletonTerm*>(bankInput->term("SET"))->setValue(0.5);
-		bankInput->setInputValue(0.5);
-		//engine.process(BANK_DANGER);//TODO: Process Bank_Dange only
-		engine.process(); //TODO: Process Bank_Dange only
-		val = bankDanger->getOutputValue(); //some expected value of this bank
-	}
-	catch (fl::Exception & fe)
+	CBankInfo * bankInfo = dynamic_cast<CBankInfo *> (objectInfo.get());
+
+	ui64 totalStrength = 0;
+	ui8 totalChance = 0;
+	for (auto config : bankInfo->getPossibleGuards())
 	{
-		logAi->errorStream() << "estimateBankDanger " << ": " << fe.getWhat();
+		totalStrength += config.second.totalStrength * config.first;
+		totalChance += config.first;
 	}
-	return val;
+	return totalStrength / totalChance;
 
 }
 
@@ -269,13 +256,26 @@ float FuzzyHelper::getTacticalAdvantage (const CArmedInstance *we, const CArmedI
 			ta.castleWalls->setInputValue(0);
 
 		//engine.process(TACTICAL_ADVANTAGE);//TODO: Process only Tactical_Advantage
-		engine.process();
+		ta.engine.process();
 		output = ta.threat->getOutputValue();
 	}
 	catch (fl::Exception & fe)
 	{
 		logAi->errorStream() << "getTacticalAdvantage " << ": " << fe.getWhat();
 	}
+	logAi->traceStream() << "getTacticalAdvantage output: " << output;
+	if (output < 0 || (output != output))
+	{
+		fl::InputVariable* tab[] = {ta.bankPresent, ta.castleWalls, ta.ourWalkers, ta.ourShooters, ta.ourFlyers, ta.ourSpeed, ta.enemyWalkers, ta.enemyShooters, ta.enemyFlyers, ta.enemySpeed};
+		std::string names[] = {"bankPresent", "castleWalls", "ourWalkers", "ourShooters", "ourFlyers", "ourSpeed", "enemyWalkers", "enemyShooters", "enemyFlyers", "enemySpeed" };
+		std::stringstream log("Warning! Fuzzy engine doesn't cover this set of parameters: ");
+
+		for (int i = 0; i < boost::size(tab); i++)
+			log << names[i] << ": " << tab[i]->getInputValue() << " ";
+		logAi->errorStream() << log.str();
+		assert(false);
+	}
+
 	return output;
 }
 
@@ -343,63 +343,67 @@ void FuzzyHelper::initVisitTile()
 {
 	try
 	{
-
 		vt.strengthRatio = new fl::InputVariable("strengthRatio"); //hero must be strong enough to defeat guards
 		vt.heroStrength = new fl::InputVariable("heroStrength"); //we want to use weakest possible hero
 		vt.turnDistance = new fl::InputVariable("turnDistance"); //we want to use hero who is near
 		vt.missionImportance = new fl::InputVariable("lockedMissionImportance"); //we may want to preempt hero with low-priority mission
 		vt.value = new fl::OutputVariable("Value");
+		vt.value->setMinimum(0);
+		vt.value->setMaximum(5);
 
 		std::vector<fl::InputVariable*> helper = {vt.strengthRatio, vt.heroStrength, vt.turnDistance, vt.missionImportance};
 		for (auto val : helper)
 		{
-			engine.addInputVariable(val);
+			vt.engine.addInputVariable(val);
 		}
-		engine.addOutputVariable(vt.value);
+		vt.engine.addOutputVariable(vt.value);
 
 		vt.strengthRatio->addTerm(new fl::Ramp("LOW", SAFE_ATTACK_CONSTANT, 0));
 		vt.strengthRatio->addTerm(new fl::Ramp("HIGH", SAFE_ATTACK_CONSTANT, SAFE_ATTACK_CONSTANT * 3));
+		vt.strengthRatio->setRange(0, SAFE_ATTACK_CONSTANT * 3 );
 
 		//strength compared to our main hero
 		vt.heroStrength->addTerm(new fl::Ramp("LOW", 0.2, 0));
 		vt.heroStrength->addTerm(new fl::Triangle("MEDIUM", 0.2, 0.8));
 		vt.heroStrength->addTerm(new fl::Ramp("HIGH", 0.5, 1));
+		vt.heroStrength->setRange(0.0, 1.0);
 
 		vt.turnDistance->addTerm(new fl::Ramp("SMALL", 0.5, 0));
 		vt.turnDistance->addTerm(new fl::Triangle("MEDIUM", 0.1, 0.8));
 		vt.turnDistance->addTerm(new fl::Ramp("LONG", 0.5, 3));
+		vt.turnDistance->setRange(0.0, 3.0);
 
 		vt.missionImportance->addTerm(new fl::Ramp("LOW", 2.5, 0));
 		vt.missionImportance->addTerm(new fl::Triangle("MEDIUM", 2, 3));
 		vt.missionImportance->addTerm(new fl::Ramp("HIGH", 2.5, 5));
+		vt.missionImportance->setRange(0.0, 5.0);
 
 		//an issue: in 99% cases this outputs center of mass (2.5) regardless of actual input :/
 		 //should be same as "mission Importance" to keep consistency
 		vt.value->addTerm(new fl::Ramp("LOW", 2.5, 0));
 		vt.value->addTerm(new fl::Triangle("MEDIUM", 2, 3)); //can't be center of mass :/
 		vt.value->addTerm(new fl::Ramp("HIGH", 2.5, 5));
-
-		engine.addRuleBlock (&vt.rules);
+		vt.value->setRange(0.0,5.0);
 
 		//use unarmed scouts if possible
-		vt.rules.addRule(fl::Rule::parse("if strengthRatio is HIGH and heroStrength is LOW then Value is very HIGH", &engine));
+		vt.addRule("if strengthRatio is HIGH and heroStrength is LOW then Value is very HIGH");
 		//we may want to use secondary hero(es) rather than main hero
-		vt.rules.addRule(fl::Rule::parse("if strengthRatio is HIGH and heroStrength is MEDIUM then Value is somewhat HIGH", &engine));
-		vt.rules.addRule(fl::Rule::parse("if strengthRatio is HIGH and heroStrength is HIGH then Value is somewhat LOW", &engine));
+		vt.addRule("if strengthRatio is HIGH and heroStrength is MEDIUM then Value is somewhat HIGH");
+		vt.addRule("if strengthRatio is HIGH and heroStrength is HIGH then Value is somewhat LOW");
 		//don't assign targets to heroes who are too weak, but prefer targets of our main hero (in case we need to gather army)
-		vt.rules.addRule(fl::Rule::parse("if strengthRatio is LOW and heroStrength is LOW then Value is very LOW", &engine));
+		vt.addRule("if strengthRatio is LOW and heroStrength is LOW then Value is very LOW");
 		//attempt to arm secondary heroes is not stupid
-		vt.rules.addRule(fl::Rule::parse("if strengthRatio is LOW and heroStrength is MEDIUM then Value is somewhat HIGH", &engine));
-		vt.rules.addRule(fl::Rule::parse("if strengthRatio is LOW and heroStrength is HIGH then Value is LOW", &engine));
+		vt.addRule("if strengthRatio is LOW and heroStrength is MEDIUM then Value is somewhat HIGH");
+		vt.addRule("if strengthRatio is LOW and heroStrength is HIGH then Value is LOW");
 
 		//do not cancel important goals
-		vt.rules.addRule(fl::Rule::parse("if lockedMissionImportance is HIGH then Value is very LOW", &engine));
-		vt.rules.addRule(fl::Rule::parse("if lockedMissionImportance is MEDIUM then Value is somewhat LOW", &engine));
-		vt.rules.addRule(fl::Rule::parse("if lockedMissionImportance is LOW then Value is HIGH", &engine));
+		vt.addRule("if lockedMissionImportance is HIGH then Value is very LOW");
+		vt.addRule("if lockedMissionImportance is MEDIUM then Value is somewhat LOW");
+		vt.addRule("if lockedMissionImportance is LOW then Value is HIGH");
 		//pick nearby objects if it's easy, avoid long walks
-		vt.rules.addRule(fl::Rule::parse("if turnDistance is SMALL then Value is HIGH", &engine));
-		vt.rules.addRule(fl::Rule::parse("if turnDistance is MEDIUM then Value is MEDIUM", &engine));
-		vt.rules.addRule(fl::Rule::parse("if turnDistance is LONG then Value is LOW", &engine));
+		vt.addRule("if turnDistance is SMALL then Value is HIGH");
+		vt.addRule("if turnDistance is MEDIUM then Value is MEDIUM");
+		vt.addRule("if turnDistance is LONG then Value is LOW");
 	}
 	catch (fl::Exception & fe)
 	{
@@ -442,7 +446,7 @@ float FuzzyHelper::evaluate (Goals::VisitTile & g)
 		vt.turnDistance->setInputValue(turns);
 		vt.missionImportance->setInputValue(missionImportance);
 
-		engine.process();
+		vt.engine.process();
 		//engine.process(VISIT_TILE); //TODO: Process only Visit_Tile
 		g.priority = vt.value->getOutputValue();
 	}
@@ -450,6 +454,7 @@ float FuzzyHelper::evaluate (Goals::VisitTile & g)
 	{
 		logAi->errorStream() << "evaluate VisitTile " << ": " << fe.getWhat();
 	}
+	assert (g.priority >= 0);
 	return g.priority;
 
 }
@@ -493,6 +498,7 @@ float FuzzyHelper::evaluate (Goals::ClearWayTo & g)
 	}
 	else
 		return -1;
+
 }
 
 float FuzzyHelper::evaluate (Goals::BuildThis & g)

+ 13 - 10
AI/VCAI/Fuzzy.h

@@ -16,17 +16,22 @@ class VCAI;
 class CArmedInstance;
 class CBank;
 
-class FuzzyHelper
+class engineBase
 {
-	friend class VCAI;
-
+public:
 	fl::Engine engine;
+	fl::RuleBlock rules;
 
-	fl::InputVariable* bankInput;
-	fl::OutputVariable* bankDanger;
-	fl::RuleBlock bankBlock;
+	engineBase();
+	void configure();
+	void addRule(const std::string &txt);
+};
+
+class FuzzyHelper
+{
+	friend class VCAI;
 
-	class TacticalAdvantage
+	class TacticalAdvantage : public engineBase
 	{
 	public:
 		fl::InputVariable * ourWalkers, * ourShooters, * ourFlyers, * enemyWalkers, * enemyShooters, * enemyFlyers;
@@ -34,11 +39,10 @@ class FuzzyHelper
 		fl::InputVariable * bankPresent;
 		fl::InputVariable * castleWalls;
 		fl::OutputVariable * threat;
-		fl::RuleBlock tacticalAdvantage;
 		~TacticalAdvantage();
 	} ta;
 
-	class EvalVisitTile
+	class EvalVisitTile : public engineBase
 	{
 	public:
 		fl::InputVariable * strengthRatio;
@@ -55,7 +59,6 @@ public:
 	//blocks should be initialized in this order, which may be confusing :/
 
 	FuzzyHelper();
-	void initBank();
 	void initTacticalAdvantage();
 	void initVisitTile();
 

+ 14 - 3
AI/VCAI/Goals.cpp

@@ -369,6 +369,9 @@ TSubgoal GetObj::whatToDoToAchieve()
 	const CGObjectInstance * obj = cb->getObj(ObjectInstanceID(objid));
 	if(!obj)
 		return sptr (Goals::Explore());
+	if (obj->tempOwner == ai->playerID) //we can't capture our own object -> move to Win codition
+		throw cannotFulfillGoalException("Cannot capture my own object " + obj->getObjectName());
+
 	int3 pos = obj->visitablePos();
 	if (hero)
 	{
@@ -377,8 +380,11 @@ TSubgoal GetObj::whatToDoToAchieve()
 	}
 	else
 	{
-		if (ai->isAccessible(obj->pos))
-			return sptr (Goals::VisitTile(pos).sethero(hero)); //we must visit object with same hero, if any
+		for (auto h : cb->getHeroesInfo())
+		{
+			if (ai->isAccessibleForHero(pos, h))
+				return sptr(Goals::VisitTile(pos).sethero(h)); //we must visit object with same hero, if any
+		}
 	}
 	return sptr (Goals::ClearWayTo(pos).sethero(hero));
 }
@@ -709,7 +715,12 @@ TGoalVec VisitTile::getAllPossibleSubgoals()
 	{
 		auto obj = frontOrNull(cb->getVisitableObjs(tile));
 		if (obj && obj->ID == Obj::HERO && obj->tempOwner == ai->playerID) //our own hero stands on that tile
-			ret.push_back (sptr(Goals::VisitTile(tile).sethero(dynamic_cast<const CGHeroInstance *>(obj)).setisElementar(true)));
+		{
+			if (hero.get(true) && hero->id == obj->id) //if it's assigned hero, visit tile. If it's different hero, we can't visit tile now
+				ret.push_back(sptr(Goals::VisitTile(tile).sethero(dynamic_cast<const CGHeroInstance *>(obj)).setisElementar(true)));
+			else
+				throw cannotFulfillGoalException("Tile is already occupied by another hero "); //FIXME: we should give up this tile earlier
+		}
 		else
 			ret.push_back (sptr(Goals::ClearWayTo(tile)));
 	}

+ 2 - 2
AI/VCAI/VCAI.cpp

@@ -598,7 +598,7 @@ void VCAI::showGarrisonDialog(const CArmedInstance *up, const CGHeroInstance *do
 	});
 }
 
-void VCAI::saveGame(COSer<CSaveFile> &h, const int version)
+void VCAI::saveGame(COSer & h, const int version)
 {
 	LOG_TRACE_PARAMS(logAi, "version '%i'", version);
 	NET_EVENT_HANDLER;
@@ -609,7 +609,7 @@ void VCAI::saveGame(COSer<CSaveFile> &h, const int version)
 	serializeInternal(h, version);
 }
 
-void VCAI::loadGame(CISer<CLoadFile> &h, const int version)
+void VCAI::loadGame(CISer & h, const int version)
 {
 	LOG_TRACE_PARAMS(logAi, "version '%i'", version);
 	NET_EVENT_HANDLER;

+ 2 - 2
AI/VCAI/VCAI.h

@@ -190,8 +190,8 @@ public:
 	virtual void commanderGotLevel (const CCommanderInstance * commander, std::vector<ui32> skills, QueryID queryID) override; //TODO
 	virtual void showBlockingDialog(const std::string &text, const std::vector<Component> &components, QueryID askID, const int soundID, bool selection, bool cancel) override; //Show a dialog, player must take decision. If selection then he has to choose between one of given components, if cancel he is allowed to not choose. After making choice, CCallback::selectionMade should be called with number of selected component (1 - n) or 0 for cancel (if allowed) and askID.
 	virtual 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
-	virtual void saveGame(COSer<CSaveFile> &h, const int version) override; //saving
-	virtual void loadGame(CISer<CLoadFile> &h, const int version) override; //loading
+	virtual void saveGame(COSer & h, const int version) override; //saving
+	virtual void loadGame(CISer & h, const int version) override; //loading
 	virtual void finish() override;
 
 	virtual void availableCreaturesChanged(const CGDwelling *town) override;

+ 2 - 2
client/CPlayerInterface.cpp

@@ -1278,13 +1278,13 @@ template <typename Handler> void CPlayerInterface::serializeTempl( Handler &h, c
 	h & spellbookSettings;
 }
 
-void CPlayerInterface::saveGame( COSer<CSaveFile> &h, const int version )
+void CPlayerInterface::saveGame( COSer & h, const int version )
 {
 	EVENT_HANDLER_CALLED_BY_CLIENT;
 	serializeTempl(h,version);
 }
 
-void CPlayerInterface::loadGame( CISer<CLoadFile> &h, const int version )
+void CPlayerInterface::loadGame( CISer & h, const int version )
 {
 	EVENT_HANDLER_CALLED_BY_CLIENT;
 	serializeTempl(h,version);

+ 2 - 2
client/CPlayerInterface.h

@@ -193,8 +193,8 @@ public:
 	void gameOver(PlayerColor player, const EVictoryLossCheckResult & victoryLossCheckResult) override;
 	void playerStartsTurn(PlayerColor player) override; //called before yourTurn on active itnerface
 	void showComp(const Component &comp, std::string message) override; //display component in the advmapint infobox
-	void saveGame(COSer<CSaveFile> &h, const int version) override; //saving
-	void loadGame(CISer<CLoadFile> &h, const int version) override; //loading
+	void saveGame(COSer & h, const int version) override; //saving
+	void loadGame(CISer & h, const int version) override; //loading
 
 	//for battles
 	void actionFinished(const BattleAction& action) override;//occurs AFTER action taken by active stack or by the hero

+ 4 - 0
client/CPreGame.cpp

@@ -2171,6 +2171,10 @@ void InfoCard::changeSelection( const CMapInfo *to )
 		else
 			mapDescription->setText(to->mapHeader->description);
 
+		mapDescription->label->scrollTextTo(0);
+		if (mapDescription->slider)
+			mapDescription->slider->moveToMin();
+
 		if(SEL->screenType != CMenuScreen::newGame && SEL->screenType != CMenuScreen::campaignList) {
 			//difficulty->block(true);
 			difficulty->setSelected(SEL->sInfo.difficulty);

+ 22 - 18
client/Client.cpp

@@ -331,7 +331,7 @@ void CClient::loadGame(const std::string & fname, const bool server, const std::
          std::cout << x << std::endl;
     std::cout << "ENDCLIENTPLAYERS\n";
 
-    serialize(*loader,0,clientPlayers);
+    serialize(loader->serializer,0,clientPlayers);
     *serv << ui32(clientPlayers.size());
     for(auto & elem : clientPlayers)
         *serv << ui8(elem.getNum());
@@ -493,11 +493,10 @@ void CClient::newGame( CConnection *con, StartInfo *si )
 // 	}
 }
 
-template <typename Handler>
-void CClient::serialize( Handler &h, const int version )
+void CClient::serialize(COSer & h, const int version)
 {
-	h & hotSeat;
-	if(h.saving)
+	assert(h.saving);
+	h & hotSeat;	
 	{
 		ui8 players = playerint.size();
 		h & players;
@@ -507,11 +506,15 @@ void CClient::serialize( Handler &h, const int version )
 			LOG_TRACE_PARAMS(logGlobal, "Saving player %s interface", i->first);
 			assert(i->first == i->second->playerID);
 			h & i->first & i->second->dllName & i->second->human;
-			i->second->saveGame(dynamic_cast<COSer<CSaveFile>&>(h), version); 
-			//evil cast that i still like better than sfinae-magic. If I had a "static if"...
+			i->second->saveGame(h, version); 			
 		}
 	}
-	else
+}
+
+void CClient::serialize(CISer & h, const int version)
+{
+	assert(!h.saving);
+	h & hotSeat;
 	{
 		ui8 players = 0; //fix for uninitialized warning
 		h & players;
@@ -551,7 +554,7 @@ void CClient::serialize( Handler &h, const int version )
 			nInt->playerID = pid;
 
 			installNewPlayerInterface(nInt, pid);
-			nInt->loadGame(dynamic_cast<CISer<CLoadFile>&>(h), version); //another evil cast, check above
+			nInt->loadGame(h, version); //another evil cast, check above
 		}
 
 		if(!vstd::contains(battleints, PlayerColor::NEUTRAL))
@@ -559,11 +562,10 @@ void CClient::serialize( Handler &h, const int version )
 	}
 }
 
-template <typename Handler>
-void CClient::serialize( Handler &h, const int version, const std::set<PlayerColor>& playerIDs)
+void CClient::serialize(COSer & h, const int version, const std::set<PlayerColor> & playerIDs)
 {
+	assert(h.saving);
 	h & hotSeat;
-	if(h.saving)
 	{
 		ui8 players = playerint.size();
 		h & players;
@@ -573,11 +575,15 @@ void CClient::serialize( Handler &h, const int version, const std::set<PlayerCol
 			LOG_TRACE_PARAMS(logGlobal, "Saving player %s interface", i->first);
 			assert(i->first == i->second->playerID);
 			h & i->first & i->second->dllName & i->second->human;
-			i->second->saveGame(dynamic_cast<COSer<CSaveFile>&>(h), version); 
-			//evil cast that i still like better than sfinae-magic. If I had a "static if"...
+			i->second->saveGame(h, version); 			
 		}
 	}
-	else
+}
+
+void CClient::serialize(CISer & h, const int version, const std::set<PlayerColor> & playerIDs)
+{
+	assert(!h.saving);
+	h & hotSeat;
 	{
 		ui8 players = 0; //fix for uninitialized warning
 		h & players;
@@ -620,7 +626,7 @@ void CClient::serialize( Handler &h, const int version, const std::set<PlayerCol
             if(playerIDs.count(pid))
                  installNewPlayerInterface(nInt, pid);
 
-            nInt->loadGame(dynamic_cast<CISer<CLoadFile>&>(h), version); //another evil cast, check above            
+            nInt->loadGame(h, version);       
 		}
 
 		if(playerIDs.count(PlayerColor::NEUTRAL))
@@ -901,8 +907,6 @@ std::string CClient::aiNameForPlayer(const PlayerSettings &ps, bool battleAI)
 	return goodAI;
 }
 
-template void CClient::serialize( CISer<CLoadFile> &h, const int version );
-template void CClient::serialize( COSer<CSaveFile> &h, const int version );
 
 void CServerHandler::startServer()
 {

+ 7 - 2
client/Client.h

@@ -32,6 +32,8 @@ struct SharedMem;
 class CClient;
 class CScriptingModule;
 struct CPathsInfo;
+class CISer;
+class COSer;
 namespace boost { class thread; }
 
 /// structure to handle running server and connecting to it
@@ -236,7 +238,10 @@ public:
 
 	//////////////////////////////////////////////////////////////////////////
 
-	template <typename Handler> void serialize(Handler &h, const int version);
-	template <typename Handler> void serialize(Handler &h, const int version, const std::set<PlayerColor>& playerIDs);
+	void serialize(COSer &h, const int version);
+	void serialize(CISer &h, const int version);
+	
+	void serialize(COSer &h, const int version, const std::set<PlayerColor>& playerIDs);
+	void serialize(CISer &h, const int version, const std::set<PlayerColor>& playerIDs);
 	void battleFinished();
 };

+ 1 - 0
client/VCMI_client.vcxproj

@@ -153,6 +153,7 @@
       <AdditionalLibraryDirectories>$(FFMPEGDIR)\lib;.;..\..\libs;..</AdditionalLibraryDirectories>
       <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
       <OptimizeReferences>true</OptimizeReferences>
+      <AdditionalOptions>/LTCG %(AdditionalOptions)</AdditionalOptions>
     </Link>
     <CustomBuildStep>
       <Command>

+ 1 - 1
client/widgets/AdventureMapClasses.cpp

@@ -479,7 +479,7 @@ void CMinimapInstance::showAll(SDL_Surface *to)
 	blitAtLoc(minimap, 0, 0, to);
 
 	//draw heroes
-	std::vector <const CGHeroInstance *> heroes = LOCPLINT->cb->getHeroesInfo(false);
+	std::vector <const CGHeroInstance *> heroes = LOCPLINT->cb->getHeroesInfo(false); //TODO: do we really need separate function for drawing heroes? 
 	for(auto & hero : heroes)
 	{
 		int3 position = hero->getPosition(false);

+ 5 - 0
client/widgets/Buttons.cpp

@@ -744,6 +744,11 @@ void CSlider::keyPressed(const SDL_KeyboardEvent & key)
 	moveTo(moveDest);
 }
 
+void CSlider::moveToMin()
+{
+	moveTo(0);
+}
+
 void CSlider::moveToMax()
 {
 	moveTo(amount);

+ 1 - 0
client/widgets/Buttons.h

@@ -230,6 +230,7 @@ public:
 	void moveRight();
 	void moveTo(int value);
 	void moveBy(int amount);
+	void moveToMin();
 	void moveToMax();
 
 	/// Amount modifier

+ 8 - 3
client/widgets/TextControls.cpp

@@ -280,15 +280,20 @@ void CTextBox::resize(Point newSize)
 
 void CTextBox::setText(const std::string &text)
 {
+	label->pos.w = pos.w; // reset to default before textSize.y check
 	label->setText(text);
-	if (label->textSize.y <= label->pos.h && slider)
+	if(label->textSize.y <= label->pos.h && slider)
 	{
 		// slider is no longer needed
 		vstd::clear_pointer(slider);
-		label->pos.w = pos.w;
+	}
+	else if(slider)
+	{
+		// decrease width again if slider still used
+		label->pos.w = pos.w - 32;
 		label->setText(text);
 	}
-	else if (label->textSize.y > label->pos.h && !slider)
+	else if(label->textSize.y > label->pos.h)
 	{
 		// create slider and update widget
 		label->pos.w = pos.w - 32;

+ 3 - 2
lib/CGameInfoCallback.cpp

@@ -109,7 +109,7 @@ const CGObjectInstance* CGameInfoCallback::getObj(ObjectInstanceID objid, bool v
 		return nullptr;
 	}
 
-	if(!isVisible(ret, player))
+	if(!isVisible(ret, player) && ret->tempOwner != player)
 	{
 		if(verbose)
             logGlobal->errorStream() << "Cannot get object with id " << oid << ". Object is not visible.";
@@ -518,7 +518,8 @@ std::vector < const CGHeroInstance *> CPlayerSpecificInfoCallback::getHeroesInfo
 	std::vector < const CGHeroInstance *> ret;
 	for(auto hero : gs->map->heroesOnMap)
 	{
-		if( !player || (hero->tempOwner == *player) ||
+		// !player || // - why would we even get access to hero not owned by any player?
+		if((hero->tempOwner == *player) ||
 			(isVisible(hero->getPosition(false), player) && !onlyOur)	)
 		{
 			ret.push_back(hero);

+ 4 - 4
lib/CGameInterface.cpp

@@ -243,7 +243,7 @@ void CAdventureAI::yourTacticPhase(int distance)
 	battleAI->yourTacticPhase(distance);
 }
 
-void CAdventureAI::saveGame(COSer<CSaveFile> &h, const int version) /*saving */
+void CAdventureAI::saveGame(COSer & h, const int version) /*saving */
 {
 	LOG_TRACE_PARAMS(logAi, "version '%i'", version);
 	CGlobalAI::saveGame(h, version);
@@ -256,7 +256,7 @@ void CAdventureAI::saveGame(COSer<CSaveFile> &h, const int version) /*saving */
 	}
 }
 
-void CAdventureAI::loadGame(CISer<CLoadFile> &h, const int version) /*loading */
+void CAdventureAI::loadGame(CISer & h, const int version) /*loading */
 {
 	LOG_TRACE_PARAMS(logAi, "version '%i'", version);
 	CGlobalAI::loadGame(h, version);
@@ -273,10 +273,10 @@ void CAdventureAI::loadGame(CISer<CLoadFile> &h, const int version) /*loading */
 	}
 }
 
-void CBattleGameInterface::saveGame(COSer<CSaveFile> &h, const int version)
+void CBattleGameInterface::saveGame(COSer & h, const int version)
 {
 }
 
-void CBattleGameInterface::loadGame(CISer<CLoadFile> &h, const int version)
+void CBattleGameInterface::loadGame(CISer  & h, const int version)
 {
 }

+ 6 - 6
lib/CGameInterface.h

@@ -50,8 +50,8 @@ class CPathsInfo;
 class CCreature;
 class CLoadFile;
 class CSaveFile;
-template <typename Serializer> class CISer;
-template <typename Serializer> class COSer;
+class CISer;
+class COSer;
 struct ArtifactLocation;
 class CScriptingModule;
 
@@ -69,8 +69,8 @@ public:
 	virtual BattleAction activeStack(const CStack * stack)=0; //called when it's turn of that stack
 	virtual void yourTacticPhase(int distance){}; //called when interface has opportunity to use Tactics skill -> use cb->battleMakeTacticAction from this function
 
-	virtual void saveGame(COSer<CSaveFile> &h, const int version);
-	virtual void loadGame(CISer<CLoadFile> &h, const int version);
+	virtual void saveGame(COSer &h, const int version);
+	virtual void loadGame(CISer &h, const int version);
 
 };
 
@@ -142,6 +142,6 @@ public:
 	virtual void battleEnd(const BattleResult *br);
 	virtual void battleStacksHealedRes(const std::vector<std::pair<ui32, ui32> > & healedStacks, bool lifeDrain, bool tentHeal, si32 lifeDrainFrom);
 
-	virtual void saveGame(COSer<CSaveFile> &h, const int version); //saving
-	virtual void loadGame(CISer<CLoadFile> &h, const int version); //loading
+	virtual void saveGame(COSer & h, const int version); //saving
+	virtual void loadGame(CISer & h, const int version); //loading
 };

+ 5 - 1
lib/CGameState.cpp

@@ -878,7 +878,7 @@ void CGameState::initDuel()
 		else
 		{
 			CLoadFile lf(scenarioOps->mapname);
-			lf >> dp;
+			lf.serializer >> dp;
 		}
 	}
 	catch(...)
@@ -2240,6 +2240,10 @@ bool CGameState::isVisible( const CGObjectInstance *obj, boost::optional<PlayerC
 	if(!player)
 		return true;
 
+	//we should always see our own heroes - but sometimes not visible heroes cause crash :?
+	if (player == obj->tempOwner)
+		return true;
+
 	if(*player == PlayerColor::NEUTRAL) //-> TODO ??? needed?
 		return false;
 	//object is visible when at least one blocked tile is visible

+ 45 - 54
lib/Connection.cpp

@@ -18,14 +18,9 @@
 using namespace boost;
 using namespace boost::asio::ip;
 
-extern template void registerTypes<CISer<CConnection> >(CISer<CConnection>& s);
-extern template void registerTypes<COSer<CConnection> >(COSer<CConnection>& s);
-extern template void registerTypes<CISer<CMemorySerializer> >(CISer<CMemorySerializer>& s);
-extern template void registerTypes<COSer<CMemorySerializer> >(COSer<CMemorySerializer>& s);
-extern template void registerTypes<CSaveFile>(CSaveFile & s);
-extern template void registerTypes<CLoadFile>(CLoadFile & s);
+extern template void registerTypes<CISer>(CISer & s);
+extern template void registerTypes<COSer>(COSer & s);
 extern template void registerTypes<CTypeList>(CTypeList & s);
-extern template void registerTypes<CLoadIntegrityValidator>(CLoadIntegrityValidator & s);
 
 CTypeList typeList;
 
@@ -50,8 +45,8 @@ void CConnection::init()
 
 	enableSmartPointerSerializatoin();
 	disableStackSendingByID();
-	registerTypes(static_cast<CISer<CConnection>&>(*this));
-	registerTypes(static_cast<COSer<CConnection>&>(*this));
+	registerTypes(iser);
+	registerTypes(oser);
 #ifdef LIL_ENDIAN
 	myEndianess = true;
 #else
@@ -60,8 +55,8 @@ void CConnection::init()
 	connected = true;
 	std::string pom;
 	//we got connection
-	(*this) << std::string("Aiya!\n") << name << myEndianess; //identify ourselves
-	(*this) >> pom >> pom >> contactEndianess;
+	oser << std::string("Aiya!\n") << name << myEndianess; //identify ourselves
+	iser >> pom >> pom >> contactEndianess;
     logNetwork->infoStream() << "Established connection with "<<pom;
 	wmx = new boost::mutex;
 	rmx = new boost::mutex;
@@ -73,7 +68,7 @@ void CConnection::init()
 }
 
 CConnection::CConnection(std::string host, std::string port, std::string Name)
-:io_service(new asio::io_service), name(Name)
+:iser(this), oser(this), io_service(new asio::io_service), name(Name) 
 {
 	int i;
 	boost::system::error_code error = asio::error::host_not_found;
@@ -128,12 +123,12 @@ connerror1:
 	throw std::runtime_error("Can't establish connection :(");
 }
 CConnection::CConnection(TSocket * Socket, std::string Name )
-	:socket(Socket),io_service(&Socket->get_io_service()), name(Name)//, send(this), rec(this)
+	:iser(this), oser(this), socket(Socket),io_service(&Socket->get_io_service()), name(Name)//, send(this), rec(this)
 {
 	init();
 }
 CConnection::CConnection(TAcceptor * acceptor, boost::asio::io_service *Io_service, std::string Name)
-: name(Name)//, send(this), rec(this)
+: iser(this), oser(this), name(Name)//, send(this), rec(this)
 {
 	boost::system::error_code error = asio::error::host_not_found;
 	socket = new tcp::socket(*io_service);
@@ -229,7 +224,7 @@ CPack * CConnection::retreivePack()
 	CPack *ret = nullptr;
 	boost::unique_lock<boost::mutex> lock(*rmx);
     logNetwork->traceStream() << "Listening... ";
-	*this >> ret;
+	iser >> ret;
 	logNetwork->traceStream() << "\treceived server message of type " << typeid(*ret).name() << ", data: " << ret;
 	return ret;
 }
@@ -238,37 +233,33 @@ void CConnection::sendPackToServer(const CPack &pack, PlayerColor player, ui32 r
 {
 	boost::unique_lock<boost::mutex> lock(*wmx);
     logNetwork->traceStream() << "Sending to server a pack of type " << typeid(pack).name();
-	*this << player << requestID << &pack; //packs has to be sent as polymorphic pointers!
+	oser << player << requestID << &pack; //packs has to be sent as polymorphic pointers!
 }
 
 void CConnection::disableStackSendingByID()
 {
-	CISer<CConnection>::sendStackInstanceByIds = false;
-	COSer<CConnection>::sendStackInstanceByIds = false;
+	CSerializer::sendStackInstanceByIds = false;	
 }
 
 void CConnection::enableStackSendingByID()
 {
-	CISer<CConnection>::sendStackInstanceByIds = true;
-	COSer<CConnection>::sendStackInstanceByIds = true;
+	CSerializer::sendStackInstanceByIds = true;	
 }
 
 void CConnection::disableSmartPointerSerialization()
 {
-	CISer<CConnection>::smartPointerSerialization = false;
-	COSer<CConnection>::smartPointerSerialization = false;
+	iser.smartPointerSerialization = oser.smartPointerSerialization = false;
 }
 
 void CConnection::enableSmartPointerSerializatoin()
 {
-	CISer<CConnection>::smartPointerSerialization = true;
-	COSer<CConnection>::smartPointerSerialization = true;
+	iser.smartPointerSerialization = oser.smartPointerSerialization = true;
 }
 
 void CConnection::prepareForSendingHeroes()
 {
-	loadedPointers.clear();
-	savedPointers.clear();
+	iser.loadedPointers.clear();
+	oser.savedPointers.clear();
 	disableSmartVectorMemberSerialization();
 	enableSmartPointerSerializatoin();
 	disableStackSendingByID();
@@ -276,25 +267,25 @@ void CConnection::prepareForSendingHeroes()
 
 void CConnection::enterPregameConnectionMode()
 {
-	loadedPointers.clear();
-	savedPointers.clear();
+	iser.loadedPointers.clear();
+	oser.savedPointers.clear();
 	disableSmartVectorMemberSerialization();
 	disableSmartPointerSerialization();
 }
 
 void CConnection::disableSmartVectorMemberSerialization()
 {
-	smartVectorMembersSerialization = false;
+	CSerializer::smartVectorMembersSerialization = false;
 }
 
 void CConnection::enableSmartVectorMemberSerializatoin()
 {
-	smartVectorMembersSerialization = true;
+	CSerializer::smartVectorMembersSerialization = true;
 }
 
-CSaveFile::CSaveFile( const std::string &fname )
+CSaveFile::CSaveFile( const std::string &fname ): serializer(this) 
 {
-	registerTypes(*this);
+	registerTypes(serializer);
 	openNextFile(fname);
 }
 
@@ -320,7 +311,7 @@ void CSaveFile::openNextFile(const std::string &fname)
 			THROW_FORMAT("Error: cannot open to write %s!", fname);
 
 		sfile->write("VCMI",4); //write magic identifier
-		*this << version; //write format version
+		serializer << version; //write format version
 	}
 	catch(...)
 	{
@@ -350,9 +341,9 @@ void CSaveFile::putMagicBytes( const std::string &text )
 	write(text.c_str(), text.length());
 }
 
-CLoadFile::CLoadFile(const boost::filesystem::path & fname, int minimalVersion /*= version*/)
+CLoadFile::CLoadFile(const boost::filesystem::path & fname, int minimalVersion /*= version*/): serializer(this)
 {
-	registerTypes(*this);
+	registerTypes(serializer);
 	openNextFile(fname, minimalVersion);
 }
 
@@ -368,7 +359,7 @@ int CLoadFile::read(void * data, unsigned size)
 
 void CLoadFile::openNextFile(const boost::filesystem::path & fname, int minimalVersion)
 {
-	assert(!reverseEndianess);
+	assert(!serializer.reverseEndianess);
 	assert(minimalVersion <= version);
 
 	try
@@ -386,22 +377,22 @@ void CLoadFile::openNextFile(const boost::filesystem::path & fname, int minimalV
 		if(std::memcmp(buffer,"VCMI",4))
 			THROW_FORMAT("Error: not a VCMI file(%s)!", fName);
 
-		*this >> fileVersion;	
-		if(fileVersion < minimalVersion)
+		serializer >> serializer.fileVersion;	
+		if(serializer.fileVersion < minimalVersion)
 			THROW_FORMAT("Error: too old file format (%s)!", fName);
 
-		if(fileVersion > version)
+		if(serializer.fileVersion > version)
 		{
-			logGlobal->warnStream() << boost::format("Warning format version mismatch: found %d when current is %d! (file %s)\n") % fileVersion % version % fName;
+			logGlobal->warnStream() << boost::format("Warning format version mismatch: found %d when current is %d! (file %s)\n") % serializer.fileVersion % version % fName;
 
-			auto versionptr = (char*)&fileVersion;
+			auto versionptr = (char*)&serializer.fileVersion;
 			std::reverse(versionptr, versionptr + 4);
-			logGlobal->warnStream() << "Version number reversed is " << fileVersion << ", checking...";
+			logGlobal->warnStream() << "Version number reversed is " << serializer.fileVersion << ", checking...";
 
-			if(fileVersion == version)
+			if(serializer.fileVersion == version)
 			{
 				logGlobal->warnStream() << fname << " seems to have different endianness! Entering reversing mode.";
-				reverseEndianess = true;
+				serializer.reverseEndianess = true;
 			}
 			else
 				THROW_FORMAT("Error: too new file format (%s)!", fName);
@@ -427,7 +418,7 @@ void CLoadFile::clear()
 {
 	sfile = nullptr;
 	fName.clear();
-	fileVersion = 0;
+	serializer.fileVersion = 0;
 }
 
 void CLoadFile::checkMagicBytes( const std::string &text )
@@ -579,14 +570,14 @@ void CSerializer::addStdVecItems(CGameState *gs, LibClasses *lib)
 }
 
 CLoadIntegrityValidator::CLoadIntegrityValidator( const std::string &primaryFileName, const std::string &controlFileName, int minimalVersion /*= version*/ )
-	: foundDesync(false)
+	: serializer(this), foundDesync(false)
 {
-	registerTypes(*this);
+	registerTypes(serializer);
 	primaryFile = make_unique<CLoadFile>(primaryFileName, minimalVersion);
 	controlFile = make_unique<CLoadFile>(controlFileName, minimalVersion);
 
-	assert(primaryFile->fileVersion == controlFile->fileVersion);
-	fileVersion = primaryFile->fileVersion;
+	assert(primaryFile->serializer.fileVersion == controlFile->serializer.fileVersion);
+	serializer.fileVersion = primaryFile->serializer.fileVersion;
 }
 
 int CLoadIntegrityValidator::read( void * data, unsigned size )
@@ -615,8 +606,8 @@ int CLoadIntegrityValidator::read( void * data, unsigned size )
 
 unique_ptr<CLoadFile> CLoadIntegrityValidator::decay()
 {
-	primaryFile->loadedPointers = this->loadedPointers;
-	primaryFile->loadedPointersTypes = this->loadedPointersTypes;
+	primaryFile->serializer.loadedPointers = this->serializer.loadedPointers;
+	primaryFile->serializer.loadedPointersTypes = this->serializer.loadedPointersTypes;
 	return std::move(primaryFile);
 }
 
@@ -647,10 +638,10 @@ int CMemorySerializer::write(const void * data, unsigned size)
 	return size;
 }
 
-CMemorySerializer::CMemorySerializer()
+CMemorySerializer::CMemorySerializer(): iser(this), oser(this)
 {
 	readPos = 0;
-	registerTypes(static_cast<CISer<CMemorySerializer>&>(*this));
-	registerTypes(static_cast<COSer<CMemorySerializer>&>(*this));
+	registerTypes(iser);
+	registerTypes(oser);
 }
 

+ 314 - 246
lib/Connection.h

@@ -30,6 +30,8 @@
 const ui32 version = 751;
 const ui32 minSupportedVersion = version;
 
+class CISer;
+class COSer;
 class CConnection;
 class CGObjectInstance;
 class CStackInstance;
@@ -266,140 +268,6 @@ public:
 
 extern DLL_LINKAGE CTypeList typeList;
 
-
-template<typename Ser>
-struct SaveBoolean
-{
-	static void invoke(Ser &s, const bool &data)
-	{
-		s.saveBoolean(data);
-	}
-};
-template<typename Ser>
-struct LoadBoolean
-{
-	static void invoke(Ser &s, bool &data)
-	{
-		s.loadBoolean(data);
-	}
-};
-
-template<typename Ser>
-struct SaveBooleanVector
-{
-	static void invoke(Ser &s, const std::vector<bool> &data)
-	{
-		s.saveBooleanVector(data);
-	}
-};
-template<typename Ser>
-struct LoadBooleanVector
-{
-	static void invoke(Ser &s, std::vector<bool> &data)
-	{
-		s.loadBooleanVector(data);
-	}
-};
-
-template<typename Ser,typename T>
-struct SavePrimitive
-{
-	static void invoke(Ser &s, const T &data)
-	{
-		s.savePrimitive(data);
-	}
-};
-template<typename Ser,typename T>
-struct SaveSerializable
-{
-	static void invoke(Ser &s, const T &data)
-	{
-		s.saveSerializable(data);
-	}
-};
-
-template<typename Ser,typename T>
-struct SaveEnum
-{
-	static void invoke(Ser &s, const T &data)
-	{
-		s.saveEnum(data);
-	}
-};
-template<typename Ser,typename T>
-struct LoadEnum
-{
-	static void invoke(Ser &s, T &data)
-	{
-		s.loadEnum(data);
-	}
-};
-template<typename Ser,typename T>
-struct LoadPrimitive
-{
-	static void invoke(Ser &s, T &data)
-	{
-		s.loadPrimitive(data);
-	}
-};
-template<typename Ser,typename T>
-struct SavePointer
-{
-	static void invoke(Ser &s, const T &data)
-	{
-		s.savePointer(data);
-	}
-};
-template<typename Ser,typename T>
-struct LoadPointer
-{
-	static void invoke(Ser &s, T &data)
-	{
-		s.loadPointer(data);
-	}
-};
-template<typename Ser,typename T>
-struct SaveArray
-{
-	static void invoke(Ser &s, const T &data)
-	{
-		s.saveArray(data);
-	}
-};
-template<typename Ser,typename T>
-struct LoadArray
-{
-	static void invoke(Ser &s, T &data)
-	{
-		s.loadArray(data);
-	}
-};
-template<typename Ser,typename T>
-struct LoadSerializable
-{
-	static void invoke(Ser &s, T &data)
-	{
-		s.loadSerializable(data);
-	}
-};
-
-template<typename Ser,typename T>
-struct SaveWrong
-{
-	static void invoke(Ser &s, const T &data)
-	{
-		throw std::runtime_error("Wrong save serialization call!");
-	}
-};
-template<typename Ser,typename T>
-struct LoadWrong
-{
-	static void invoke(Ser &s, const T &data)
-	{
-		throw std::runtime_error("Wrong load serialization call!");
-	}
-};
-
 template<typename Variant, typename Source>
 struct VariantLoaderHelper
 {
@@ -570,28 +438,30 @@ public:
 	void addStdVecItems(CGameState *gs, LibClasses *lib = VLC);
 };
 
-class DLL_LINKAGE CSaverBase : public virtual CSerializer
+class IBinaryWriter : public virtual CSerializer
 {
+public:
+	virtual int write(const void * data, unsigned size) = 0;
 };
 
-class CBasicPointerSaver
+class DLL_LINKAGE CSaverBase 
 {
+protected:
+	IBinaryWriter * writer;
 public:
-	virtual void savePtr(CSaverBase &ar, const void *data) const =0;
-	virtual ~CBasicPointerSaver(){}
+	CSaverBase(IBinaryWriter * w): writer(w){};
+	
+	inline int write(const void * data, unsigned size)
+	{
+		return writer->write(data, size);
+	};
 };
 
-template <typename Serializer, typename T> class CPointerSaver : public CBasicPointerSaver
+class CBasicPointerSaver
 {
 public:
-	void savePtr(CSaverBase &ar, const void *data) const
-	{
-		Serializer &s = static_cast<Serializer&>(ar);
-		const T *ptr = static_cast<const T*>(data);
-
-		//T is most derived known type, it's time to call actual serialize
-		const_cast<T&>(*ptr).serialize(s,version);
-	}
+	virtual void savePtr(CSaverBase &ar, const void *data) const =0;
+	virtual ~CBasicPointerSaver(){}
 };
 
 template <typename T> //metafunction returning CGObjectInstance if T is its derivate or T elsewise
@@ -658,6 +528,7 @@ struct SaveIfStackInstance
 		return false;
 	}
 };
+
 template<typename Ser>
 struct SaveIfStackInstance<Ser, CStackInstance *>
 {
@@ -676,6 +547,7 @@ struct SaveIfStackInstance<Ser, CStackInstance *>
 		return true;
 	}
 };
+
 template<typename Ser,typename T>
 struct LoadIfStackInstance
 {
@@ -711,16 +583,101 @@ struct LoadIfStackInstance<Ser, CStackInstance *>
 
 
 /// The class which manages saving objects.
-template <typename Serializer> class DLL_LINKAGE COSer : public CSaverBase
+class DLL_LINKAGE COSer : public CSaverBase
 {
 public:
+	
+	struct SaveBoolean
+	{
+		static void invoke(COSer &s, const bool &data)
+		{
+			s.saveBoolean(data);
+		}
+	};	
+	
+	struct SaveBooleanVector
+	{
+		static void invoke(COSer &s, const std::vector<bool> &data)
+		{
+			s.saveBooleanVector(data);
+		}
+	};
+
+	template<typename T>
+	struct SavePrimitive
+	{
+		static void invoke(COSer &s, const T &data)
+		{
+			s.savePrimitive(data);
+		}
+	};
+
+	template<typename T>
+	struct SaveSerializable
+	{
+		static void invoke(COSer &s, const T &data)
+		{
+			s.saveSerializable(data);
+		}
+	};
+
+	template<typename T>
+	struct SaveEnum
+	{
+		static void invoke(COSer &s, const T &data)
+		{
+			s.saveEnum(data);
+		}
+	};
+		
+	template<typename T>
+	struct SavePointer
+	{
+		static void invoke(COSer &s, const T &data)
+		{
+			s.savePointer(data);
+		}
+	};
+	
+	template<typename T>
+	struct SaveArray
+	{
+		static void invoke(COSer &s, const T &data)
+		{
+			s.saveArray(data);
+		}
+	};
+
+	template<typename T>
+	struct SaveWrong
+	{
+		static void invoke(COSer &s, const T &data)
+		{
+			throw std::runtime_error("Wrong save serialization call!");
+		}
+	};	
+	
+	template <typename T> 
+	class CPointerSaver : public CBasicPointerSaver
+	{
+	public:
+		void savePtr(CSaverBase &ar, const void *data) const override
+		{
+			COSer &s = static_cast<COSer&>(ar);
+			const T *ptr = static_cast<const T*>(data);
+
+			//T is most derived known type, it's time to call actual serialize
+			const_cast<T&>(*ptr).serialize(s,version);
+		}
+	};	
+			
 	bool saving;
 	std::map<ui16,CBasicPointerSaver*> savers; // typeID => CPointerSaver<serializer,type>
 
 	std::map<const void*, ui32> savedPointers;
 	bool smartPointerSerialization;
 
-	COSer()
+	COSer(IBinaryWriter * w): CSaverBase(w)
 	{
 		saving=true;
 		smartPointerSerialization = true;
@@ -738,7 +695,7 @@ public:
 	{
 		auto ID = typeList.getTypeID(t);
 		if(!savers.count(ID))
-			savers[ID] = new CPointerSaver<COSer<Serializer>, T>;
+			savers[ID] = new CPointerSaver<T>;
 	}
 
 	template<typename Base, typename Derived> void registerType(const Base * b = nullptr, const Derived * d = nullptr)
@@ -748,31 +705,23 @@ public:
 		addSaver(d);
 	}
 
-    Serializer * This()
-	{
-		return static_cast<Serializer*>(this);
-	}
-
 	template<class T>
-	Serializer & operator<<(const T &t)
+	COSer & operator<<(const T &t)
 	{
-		this->This()->save(t);
-		return * this->This();
+		this->save(t);
+		return * this;
 	}
 
 	template<class T>
 	COSer & operator&(const T & t)
 	{
-		return * this->This() << t;
+		return * this << t;
 	}
 
-
-
-	int write(const void * data, unsigned size);
 	template <typename T>
 	void savePrimitive(const T &data)
 	{
-		this->This()->write(&data,sizeof(data));
+		this->write(&data,sizeof(data));
 	}
 
 	template <typename T>
@@ -786,23 +735,23 @@ public:
 		if(!hlp)
 			return;
 
- 		if(smartVectorMembersSerialization)
+ 		if(writer->smartVectorMembersSerialization)
 		{
 			typedef typename boost::remove_const<typename boost::remove_pointer<T>::type>::type TObjectType;
 			typedef typename VectorisedTypeFor<TObjectType>::type VType;
 			typedef typename VectorizedIDType<TObjectType>::type IDType;
- 			if(const auto *info = getVectorisedTypeInfo<VType, IDType>())
+ 			if(const auto *info = writer->getVectorisedTypeInfo<VType, IDType>())
  			{
-				IDType id = getIdFromVectorItem<VType>(*info, data);
+				IDType id = writer->getIdFromVectorItem<VType>(*info, data);
 				*this << id;
 				if(id != IDType(-1)) //vector id is enough
  					return;
  			}
  		}
 
-		if(sendStackInstanceByIds)
+		if(writer->sendStackInstanceByIds)
 		{
-			const bool gotSaved = SaveIfStackInstance<Serializer,T>::invoke(*This(), data);
+			const bool gotSaved = SaveIfStackInstance<COSer,T>::invoke(*this, data);
 			if(gotSaved)
 				return;
 		}
@@ -830,7 +779,7 @@ public:
 		ui16 tid = typeList.getTypeID(data);
 		*this << tid;
 
-		This()->savePointerHlp(tid, data);
+		this->savePointerHlp(tid, data);
 	}
 
 	//that part of ptr serialization was extracted to allow customization of its behavior in derived classes
@@ -856,27 +805,27 @@ public:
 		typedef
 			//if
 			typename mpl::eval_if< mpl::equal_to<SerializationLevel<T>,mpl::int_<Boolean> >,
-			mpl::identity<SaveBoolean<Serializer> >,
+			mpl::identity<SaveBoolean>,
 			//else if
 			typename mpl::eval_if< mpl::equal_to<SerializationLevel<T>,mpl::int_<BooleanVector> >,
-			mpl::identity<SaveBooleanVector<Serializer> >,
+			mpl::identity<SaveBooleanVector>,
 			//else if
 			typename mpl::eval_if< mpl::equal_to<SerializationLevel<T>,mpl::int_<Primitive> >,
-			mpl::identity<SavePrimitive<Serializer,T> >,
+			mpl::identity<SavePrimitive<T> >,
 			//else if
 			typename mpl::eval_if<mpl::equal_to<SerializationLevel<T>,mpl::int_<Enum> >,
-			mpl::identity<SaveEnum<Serializer,T> >,
+			mpl::identity<SaveEnum<T> >,
 			//else if
 			typename mpl::eval_if<mpl::equal_to<SerializationLevel<T>,mpl::int_<Pointer> >,
-			mpl::identity<SavePointer<Serializer,T> >,
+			mpl::identity<SavePointer<T> >,
 			//else if
 			typename mpl::eval_if<mpl::equal_to<SerializationLevel<T>,mpl::int_<Array> >,
-			mpl::identity<SaveArray<Serializer,T> >,
+			mpl::identity<SaveArray<T> >,
 			//else if
 			typename mpl::eval_if<mpl::equal_to<SerializationLevel<T>,mpl::int_<Serializable> >,
-			mpl::identity<SaveSerializable<Serializer,T> >,
+			mpl::identity<SaveSerializable<T> >,
 			//else
-			mpl::identity<SaveWrong<Serializer,T> >
+			mpl::identity<SaveWrong<T> >
 			>
 			>
 			>
@@ -884,7 +833,7 @@ public:
 			>
 			>
 			>::type typex;
-		typex::invoke(* this->This(), data);
+		typex::invoke(* this, data);
 	}
 	template <typename T>
 	void saveSerializable(const T &data)
@@ -947,7 +896,7 @@ public:
 	void saveSerializable(const std::string &data)
 	{
 		*this << ui32(data.length());
-		this->This()->write(data.c_str(),data.size());
+		this->write(data.c_str(),data.size());
 	}
 	template <typename T1, typename T2>
 	void saveSerializable(const std::pair<T1,T2> &data)
@@ -974,7 +923,7 @@ public:
 		si32 which = data.which();
 		*this << which;
 
-		VariantVisitorSaver<Serializer> visitor(*this->This());
+		VariantVisitorSaver<COSer> visitor(*this);
 		boost::apply_visitor(visitor, data);
 	}
 	template <typename T>
@@ -1009,10 +958,24 @@ public:
 	}
 };
 
+class IBinaryReader : public virtual CSerializer
+{
+public:
+	virtual int read(void * data, unsigned size) = 0;
+};
 
-
-class DLL_LINKAGE CLoaderBase : public virtual CSerializer
-{};
+class DLL_LINKAGE CLoaderBase 
+{
+protected:
+	IBinaryReader * reader;
+public:
+	CLoaderBase(IBinaryReader * r): reader(r){};
+	
+	inline int read(void * data, unsigned size)
+	{
+		return reader->read(data, size);
+	};
+};
 
 class CBasicPointerLoader
 {
@@ -1040,28 +1003,99 @@ struct ClassObjectCreator<T, typename std::enable_if<std::is_abstract<T>::value>
 	}
 };
 
-template <typename Serializer, typename T> class CPointerLoader : public CBasicPointerLoader
+
+/// The class which manages loading of objects.
+class DLL_LINKAGE CISer : public CLoaderBase
 {
 public:
-	const std::type_info * loadPtr(CLoaderBase &ar, void *data, ui32 pid) const //data is pointer to the ACTUAL POINTER
+	struct LoadBoolean
+	{
+		static void invoke(CISer &s, bool &data)
+		{
+			s.loadBoolean(data);
+		}
+	};
+	
+	struct LoadBooleanVector
+	{
+		static void invoke(CISer &s, std::vector<bool> &data)
+		{
+			s.loadBooleanVector(data);
+		}
+	};	
+	
+	template<typename T>
+	struct LoadEnum
 	{
-		Serializer &s = static_cast<Serializer&>(ar);
-		T *&ptr = *static_cast<T**>(data);
+		static void invoke(CISer &s, T &data)
+		{
+			s.loadEnum(data);
+		}
+	};
 
-		//create new object under pointer
-		typedef typename boost::remove_pointer<T>::type npT;
-		ptr = ClassObjectCreator<npT>::invoke(); //does new npT or throws for abstract classes
-		s.ptrAllocated(ptr, pid);
-		//T is most derived known type, it's time to call actual serialize
-		ptr->serialize(s,version);
-		return &typeid(T);
-	}
-};
+	template<typename T>
+	struct LoadPrimitive
+	{
+		static void invoke(CISer &s, T &data)
+		{
+			s.loadPrimitive(data);
+		}
+	};	
 
-/// The class which manages loading of objects.
-template <typename Serializer> class DLL_LINKAGE CISer : public CLoaderBase
-{
-public:
+	template<typename T>
+	struct LoadPointer
+	{
+		static void invoke(CISer &s, T &data)
+		{
+			s.loadPointer(data);
+		}
+	};	
+	
+	template<typename T>
+	struct LoadArray
+	{
+		static void invoke(CISer &s, T &data)
+		{
+			s.loadArray(data);
+		}
+	};
+
+	template<typename T>
+	struct LoadSerializable
+	{
+		static void invoke(CISer &s, T &data)
+		{
+			s.loadSerializable(data);
+		}
+	};
+
+	template<typename T>
+	struct LoadWrong
+	{
+		static void invoke(CISer &s, const T &data)
+		{
+			throw std::runtime_error("Wrong load serialization call!");
+		}
+	};	
+	
+	template <typename T> class CPointerLoader : public CBasicPointerLoader
+	{
+	public:
+		const std::type_info * loadPtr(CLoaderBase &ar, void *data, ui32 pid) const override //data is pointer to the ACTUAL POINTER
+		{
+			CISer &s = static_cast<CISer&>(ar);
+			T *&ptr = *static_cast<T**>(data);
+
+			//create new object under pointer
+			typedef typename boost::remove_pointer<T>::type npT;
+			ptr = ClassObjectCreator<npT>::invoke(); //does new npT or throws for abstract classes
+			s.ptrAllocated(ptr, pid);
+			//T is most derived known type, it's time to call actual serialize
+			ptr->serialize(s,version);
+			return &typeid(T);
+		}
+	};		
+	
 	bool saving;
 	std::map<ui16,CBasicPointerLoader*> loaders; // typeID => CPointerSaver<serializer,type>
 	si32 fileVersion;
@@ -1073,7 +1107,7 @@ public:
 
 	bool smartPointerSerialization;
 
-	CISer()
+	CISer(IBinaryReader * r): CLoaderBase(r)
 	{
 		saving = false;
 		fileVersion = 0;
@@ -1094,7 +1128,7 @@ public:
 	{
 		auto ID = typeList.getTypeID(t);
 		if(!loaders.count(ID))
-			loaders[ID] = new CPointerLoader<CISer<Serializer>, T>;
+			loaders[ID] = new CPointerLoader<T>;
 	}
 
 	template<typename Base, typename Derived> void registerType(const Base * b = nullptr, const Derived * d = nullptr)
@@ -1104,22 +1138,17 @@ public:
 		addLoader(d);
 	}
 
-    Serializer * This()
-	{
-		return static_cast<Serializer*>(this);
-	}
-
 	template<class T>
-	Serializer & operator>>(T &t)
+	CISer & operator>>(T &t)
 	{
-		this->This()->load(t);
-		return * this->This();
+		this->load(t);
+		return * this;
 	}
 
 	template<class T>
 	CISer & operator&(T & t)
 	{
-		return * this->This() >> t;
+		return * this >> t;
 	}
 
 	int write(const void * data, unsigned size);
@@ -1129,27 +1158,27 @@ public:
 		typedef
 			//if
 			typename mpl::eval_if< mpl::equal_to<SerializationLevel<T>,mpl::int_<Boolean> >,
-			mpl::identity<LoadBoolean<Serializer> >,
+			mpl::identity<LoadBoolean>,
 			//else if
 			typename mpl::eval_if< mpl::equal_to<SerializationLevel<T>,mpl::int_<BooleanVector> >,
-			mpl::identity<LoadBooleanVector<Serializer> >,
+			mpl::identity<LoadBooleanVector>,
 			//else if
 			typename mpl::eval_if< mpl::equal_to<SerializationLevel<T>,mpl::int_<Primitive> >,
-			mpl::identity<LoadPrimitive<Serializer,T> >,
+			mpl::identity<LoadPrimitive<T> >,
 			//else if
 			typename mpl::eval_if<mpl::equal_to<SerializationLevel<T>,mpl::int_<Enum> >,
-			mpl::identity<LoadEnum<Serializer,T> >,
+			mpl::identity<LoadEnum<T> >,
 			//else if
 			typename mpl::eval_if<mpl::equal_to<SerializationLevel<T>,mpl::int_<Pointer> >,
-			mpl::identity<LoadPointer<Serializer,T> >,
+			mpl::identity<LoadPointer<T> >,
 			//else if
 			typename mpl::eval_if<mpl::equal_to<SerializationLevel<T>,mpl::int_<Array> >,
-			mpl::identity<LoadArray<Serializer,T> >,
+			mpl::identity<LoadArray<T> >,
 			//else if
 			typename mpl::eval_if<mpl::equal_to<SerializationLevel<T>,mpl::int_<Serializable> >,
-			mpl::identity<LoadSerializable<Serializer,T> >,
+			mpl::identity<LoadSerializable<T> >,
 			//else
-			mpl::identity<LoadWrong<Serializer,T> >
+			mpl::identity<LoadWrong<T> >
 			>
 			>
 			>
@@ -1157,20 +1186,20 @@ public:
 			>
 			>
 			>::type typex;
-		typex::invoke(* this->This(), data);
+		typex::invoke(* this, data);
 	}
 	template <typename T>
 	void loadPrimitive(T &data)
 	{
 		if(0) //for testing #989
 		{
- 			this->This()->read(&data,sizeof(data));
+ 			this->read(&data,sizeof(data));
 		}
 		else
 		{
 			unsigned length = sizeof(data);
 			char* dataPtr = (char*)&data;
-			this->This()->read(dataPtr,length);
+			this->read(dataPtr,length);
 			if(reverseEndianess)
 				std::reverse(dataPtr, dataPtr + length);
 		}
@@ -1209,26 +1238,26 @@ public:
 			return;
 		}
 
-		if(smartVectorMembersSerialization)
+		if(reader->smartVectorMembersSerialization)
 		{
 			typedef typename boost::remove_const<typename boost::remove_pointer<T>::type>::type TObjectType; //eg: const CGHeroInstance * => CGHeroInstance
 			typedef typename VectorisedTypeFor<TObjectType>::type VType;									 //eg: CGHeroInstance -> CGobjectInstance
 			typedef typename VectorizedIDType<TObjectType>::type IDType;
-			if(const auto *info = getVectorisedTypeInfo<VType, IDType>())
+			if(const auto *info = reader->getVectorisedTypeInfo<VType, IDType>())
 			{
 				IDType id;
 				*this >> id;
 				if(id != IDType(-1))
 				{
-					data = static_cast<T>(getVectorItemFromId<VType, IDType>(*info, id));
+					data = static_cast<T>(reader->getVectorItemFromId<VType, IDType>(*info, id));
 					return;
 				}
 			}
 		}
 
-		if(sendStackInstanceByIds)
+		if(reader->sendStackInstanceByIds)
 		{
-			bool gotLoaded = LoadIfStackInstance<Serializer,T>::invoke(*This(), data);
+			bool gotLoaded = LoadIfStackInstance<CISer,T>::invoke(* this, data);
 			if(gotLoaded)
 				return;
 		}
@@ -1252,7 +1281,7 @@ public:
 		//get type id
 		ui16 tid;
 		*this >> tid;
-		This()->loadPointerHlp(tid, data, pid);
+		this->loadPointerHlp(tid, data, pid);
 	}
 
 	//that part of ptr deserialization was extracted to allow customization of its behavior in derived classes
@@ -1290,7 +1319,7 @@ public:
 	if(length > 500000)				\
 	{								\
         logGlobal->warnStream() << "Warning: very big length: " << length;\
-        reportState(logGlobal);			\
+        reader->reportState(logGlobal);			\
 	};
 
 
@@ -1437,7 +1466,7 @@ public:
 	{
 		READ_CHECK_U32(length);
 		data.resize(length);
-		this->This()->read((void*)data.c_str(),length);
+		this->read((void*)data.c_str(),length);
 	}
 
 	template <BOOST_VARIANT_ENUM_PARAMS(typename T)>
@@ -1506,52 +1535,71 @@ public:
 };
 
 class DLL_LINKAGE CSaveFile
-	: public COSer<CSaveFile>
+	:public IBinaryWriter
 {
-
 public:
+	
+	COSer serializer;
+	
 	std::string fName;
 	unique_ptr<std::ofstream> sfile;
 
 	CSaveFile(const std::string &fname); //throws!
 	~CSaveFile();
-	int write(const void * data, unsigned size);
+	int write(const void * data, unsigned size) override;
 
 	void openNextFile(const std::string &fname); //throws!
 	void clear();
     void reportState(CLogger * out);
 
 	void putMagicBytes(const std::string &text);
+	
+	template<class T>
+	CSaveFile & operator<<(const T &t)
+	{
+		serializer << t;
+		return * this;
+	}		
 };
 
 class DLL_LINKAGE CLoadFile
-	: public CISer<CLoadFile>
+	: public IBinaryReader
 {
-
 public:
+	CISer serializer;
+		
 	std::string fName;
 	unique_ptr<boost::filesystem::ifstream> sfile;
 
 	CLoadFile(const boost::filesystem::path & fname, int minimalVersion = version); //throws!
 	~CLoadFile();
-	int read(void * data, unsigned size); //throws!
+	int read(void * data, unsigned size) override; //throws!
 
 	void openNextFile(const boost::filesystem::path & fname, int minimalVersion); //throws!
 	void clear();
     void reportState(CLogger * out);
 
 	void checkMagicBytes(const std::string & text);
+	
+	template<class T>
+	CLoadFile & operator>>(T &t)
+	{
+		serializer >> t;
+		return * this;
+	}		
 };
 
-class DLL_LINKAGE CLoadIntegrityValidator : public CISer<CLoadIntegrityValidator>
+class DLL_LINKAGE CLoadIntegrityValidator 
+	: public IBinaryReader
 {
 public:
+	CISer serializer;	
 	unique_ptr<CLoadFile> primaryFile, controlFile;
 	bool foundDesync;
 
 	CLoadIntegrityValidator(const std::string &primaryFileName, const std::string &controlFileName, int minimalVersion = version); //throws!
 
-	int read( void * data, unsigned size); //throws!
+	int read( void * data, unsigned size) override; //throws!
 	void checkMagicBytes(const std::string &text);
 
 	unique_ptr<CLoadFile> decay(); //returns primary file. CLoadIntegrityValidator stops being usable anymore
@@ -1561,7 +1609,7 @@ typedef boost::asio::basic_stream_socket < boost::asio::ip::tcp , boost::asio::s
 typedef boost::asio::basic_socket_acceptor<boost::asio::ip::tcp, boost::asio::socket_acceptor_service<boost::asio::ip::tcp> > TAcceptor;
 
 class DLL_LINKAGE CConnection
-	: public CISer<CConnection>, public COSer<CConnection>
+	: public IBinaryReader, public IBinaryWriter
 {
 	//CGameState *gs;
 	CConnection(void);
@@ -1569,6 +1617,9 @@ class DLL_LINKAGE CConnection
 	void init();
     void reportState(CLogger * out);
 public:
+	CISer iser;
+	COSer oser;
+	
 	boost::mutex *rmx, *wmx; // read/write mutexes
 	TSocket * socket;
 	bool logging;
@@ -1586,8 +1637,8 @@ public:
 	CConnection(TAcceptor * acceptor, boost::asio::io_service *Io_service, std::string Name);
 	CConnection(TSocket * Socket, std::string Name); //use immediately after accepting connection into socket
 
-	int write(const void * data, unsigned size);
-	int read(void * data, unsigned size);
+	int write(const void * data, unsigned size) override;
+	int read(void * data, unsigned size) override;
 	void close();
 	bool isOpen() const;
     template<class T>
@@ -1606,6 +1657,20 @@ public:
 
 	void prepareForSendingHeroes(); //disables sending vectorised, enables smart pointer serialization, clears saved/loaded ptr cache
 	void enterPregameConnectionMode();
+	
+	template<class T>
+	CConnection & operator>>(T &t)
+	{
+		iser >> t;
+		return * this;
+	}	
+	
+	template<class T>
+	CConnection & operator<<(const T &t)
+	{
+		oser << t;
+		return * this;
+	}
 };
 
 DLL_LINKAGE std::ostream &operator<<(std::ostream &str, const CConnection &cpc);
@@ -1613,14 +1678,17 @@ DLL_LINKAGE std::ostream &operator<<(std::ostream &str, const CConnection &cpc);
 
 // Serializer that stores objects in the dynamic buffer. Allows performing deep object copies.
 class DLL_LINKAGE CMemorySerializer
-	: public CISer<CMemorySerializer>, public COSer<CMemorySerializer>
+	: public IBinaryReader, public IBinaryWriter
 {
 	std::vector<ui8> buffer;
 
 	size_t readPos; //index of the next byte to be read
 public:
-	int read(void * data, unsigned size); //throws!
-	int write(const void * data, unsigned size);
+	CISer iser;
+	COSer oser;
+		
+	int read(void * data, unsigned size) override; //throws!
+	int write(const void * data, unsigned size) override;
 
 	CMemorySerializer();
 
@@ -1628,10 +1696,10 @@ public:
 	static unique_ptr<T> deepCopy(const T &data)
 	{
 		CMemorySerializer mem;
-		mem << &data;
+		mem.oser << &data;
 
 		unique_ptr<T> ret;
-		mem >> ret;
+		mem.iser >> ret;
 		return ret;
 	}
 };

+ 8 - 8
lib/IGameCallback.cpp

@@ -149,16 +149,16 @@ void CPrivilagedInfoCallback::loadCommonState(Loader &in)
 	StartInfo *si;
 
     logGlobal->infoStream() <<"\tReading header";
-	in >> dum;
+	in.serializer >> dum;
 
     logGlobal->infoStream() << "\tReading options";
-	in >> si;
+	in.serializer >> si;
 
     logGlobal->infoStream() <<"\tReading handlers";
-	in >> *VLC;
+	in.serializer >> *VLC;
 
     logGlobal->infoStream() <<"\tReading gamestate";
-	in >> gs;
+	in.serializer >> gs;
 }
 
 template<typename Saver>
@@ -167,13 +167,13 @@ void CPrivilagedInfoCallback::saveCommonState(Saver &out) const
     logGlobal->infoStream() << "Saving lib part of game...";
 	out.putMagicBytes(SAVEGAME_MAGIC);
     logGlobal->infoStream() <<"\tSaving header";
-	out << static_cast<CMapHeader&>(*gs->map);
+	out.serializer << static_cast<CMapHeader&>(*gs->map);
     logGlobal->infoStream() << "\tSaving options";
-	out << gs->scenarioOps;
+	out.serializer << gs->scenarioOps;
     logGlobal->infoStream() << "\tSaving handlers";
-	out << *VLC;
+	out.serializer << *VLC;
     logGlobal->infoStream() << "\tSaving gamestate";
-	out << gs;
+	out.serializer << gs;
 }
 
 // hardly memory usage for `-gdwarf-4` flag

+ 1 - 0
lib/VCMI_lib.vcxproj

@@ -147,6 +147,7 @@
       <LinkTimeCodeGeneration>
       </LinkTimeCodeGeneration>
       <AdditionalLibraryDirectories>..\..\libs</AdditionalLibraryDirectories>
+      <AdditionalOptions>/LTCG %(AdditionalOptions)</AdditionalOptions>
     </Link>
   </ItemDefinitionGroup>
   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='RD|x64'">

+ 23 - 0
lib/mapObjects/CommonConstructors.cpp

@@ -370,6 +370,29 @@ IObjectInfo::CArmyStructure CBankInfo::maxGuards() const
 	return *boost::range::max_element(armies);
 }
 
+TPossibleGuards CBankInfo::getPossibleGuards() const
+{
+	TPossibleGuards out;
+
+	for (const JsonNode & configEntry : config)
+	{
+		const JsonNode & guardsInfo = configEntry["guards"];
+		auto stacks = JsonRandom::evaluateCreatures(configEntry["guards"]);
+		IObjectInfo::CArmyStructure army;
+
+
+		for (auto stack : stacks)
+		{
+			army.totalStrength += stack.allowedCreatures.front()->AIValue * (stack.minAmount + stack.maxAmount) / 2;
+			//TODO: add fields for flyers, walkers etc...
+		}
+
+		ui8 chance = configEntry["chance"].Float();
+		out.push_back(std::make_pair(chance, army));
+	}
+	return out;
+}
+
 bool CBankInfo::givesResources() const
 {
 	for (const JsonNode & node : config)

+ 7 - 1
lib/mapObjects/CommonConstructors.h

@@ -152,14 +152,20 @@ struct BankConfig
 	}
 };
 
-class CBankInfo : public IObjectInfo
+typedef std::vector<std::pair<ui8, IObjectInfo::CArmyStructure>> TPossibleGuards;
+
+class DLL_LINKAGE CBankInfo : public IObjectInfo
 {
 	JsonVector config;
 public:
 	CBankInfo(JsonVector config);
 
+	TPossibleGuards getPossibleGuards() const;
+
+	//I have no idea what do these functions do or were supposed to do - War
 	CArmyStructure minGuards() const;
 	CArmyStructure maxGuards() const;
+
 	bool givesResources() const;
 	bool givesArtifacts() const;
 	bool givesCreatures() const;

+ 2 - 1
lib/mapObjects/MiscObjects.cpp

@@ -1445,7 +1445,8 @@ void CGShipyard::onHeroVisit( const CGHeroInstance * h ) const
 
 void CCartographer::onHeroVisit( const CGHeroInstance * h ) const
 {
-	if (!wasVisited (h->getOwner()) ) //if hero has not visited yet this cartographer
+	//if player has not bought map of this subtype yet and underground exist for stalagmite cartographer
+	if (!wasVisited(h->getOwner()) && (subID != 2 || cb->gameState()->map->twoLevel))
 	{
 		if (cb->getResource(h->tempOwner, Res::GOLD) >= 1000) //if he can afford a map
 		{

+ 5 - 14
lib/registerTypes/RegisterTypes.cpp

@@ -27,15 +27,11 @@
 // registerTypesServerPacks:  1.3 Gb
 // registerTypes4:  1.3 Gb
 
+
 #define DEFINE_EXTERNAL_METHOD(METHODNAME) \
-extern template DLL_LINKAGE void METHODNAME<CISer<CConnection>>(CISer<CConnection>& s); \
-extern template DLL_LINKAGE void METHODNAME<COSer<CConnection>>(COSer<CConnection>& s); \
-extern template DLL_LINKAGE void METHODNAME<CISer<CMemorySerializer>>(CISer<CMemorySerializer>& s); \
-extern template DLL_LINKAGE void METHODNAME<COSer<CMemorySerializer>>(COSer<CMemorySerializer>& s); \
-extern template DLL_LINKAGE void METHODNAME<CSaveFile>(CSaveFile & s); \
-extern template DLL_LINKAGE void METHODNAME<CLoadFile>(CLoadFile & s); \
+extern template DLL_LINKAGE void METHODNAME<CISer>(CISer & s); \
+extern template DLL_LINKAGE void METHODNAME<COSer>(COSer & s); \
 extern template DLL_LINKAGE void METHODNAME<CTypeList>(CTypeList & s); \
-extern template DLL_LINKAGE void METHODNAME<CLoadIntegrityValidator>(CLoadIntegrityValidator & s);
 
 //DEFINE_EXTERNAL_METHOD(registerTypesMapObjects)
 DEFINE_EXTERNAL_METHOD(registerTypesMapObjects1)
@@ -45,11 +41,6 @@ DEFINE_EXTERNAL_METHOD(registerTypesClientPacks2)
 DEFINE_EXTERNAL_METHOD(registerTypesServerPacks)
 DEFINE_EXTERNAL_METHOD(registerTypesPregamePacks)
 
-template void registerTypes<CISer<CConnection>>(CISer<CConnection>& s);
-template void registerTypes<COSer<CConnection>>(COSer<CConnection>& s);
-template void registerTypes<CISer<CMemorySerializer>>(CISer<CMemorySerializer>& s);
-template void registerTypes<COSer<CMemorySerializer>>(COSer<CMemorySerializer>& s);
-template void registerTypes<CSaveFile>(CSaveFile & s);
-template void registerTypes<CLoadFile>(CLoadFile & s);
+template void registerTypes<CISer>(CISer & s);
+template void registerTypes<COSer>(COSer & s);
 template void registerTypes<CTypeList>(CTypeList & s);
-template void registerTypes<CLoadIntegrityValidator>(CLoadIntegrityValidator & s);

+ 4 - 7
lib/registerTypes/RegisterTypes.h

@@ -367,13 +367,10 @@ void registerTypes(Serializer &s)
 }
 
 #ifndef INSTANTIATE_REGISTER_TYPES_HERE
-extern template DLL_LINKAGE void registerTypes<CISer<CConnection>>(CISer<CConnection>& s);
-extern template DLL_LINKAGE void registerTypes<COSer<CConnection>>(COSer<CConnection>& s);
-extern template DLL_LINKAGE void registerTypes<CSaveFile>(CSaveFile & s);
-extern template DLL_LINKAGE void registerTypes<CLoadFile>(CLoadFile & s);
+
+extern template DLL_LINKAGE void registerTypes<CISer>(CISer & s);
+extern template DLL_LINKAGE void registerTypes<COSer>(COSer & s);
 extern template DLL_LINKAGE void registerTypes<CTypeList>(CTypeList & s);
-extern template DLL_LINKAGE void registerTypes<CLoadIntegrityValidator>(CLoadIntegrityValidator & s);
-extern template DLL_LINKAGE void registerTypes<CISer<CMemorySerializer>>(CISer<CMemorySerializer> & s);
-extern template DLL_LINKAGE void registerTypes<COSer<CMemorySerializer>>(COSer<CMemorySerializer> & s);
+
 #endif
 

+ 2 - 7
lib/registerTypes/TypesClientPacks1.cpp

@@ -19,11 +19,6 @@
 #include "../mapObjects/CObjectClassesHandler.h"
 
 
-template void registerTypesClientPacks1<CISer<CConnection>>(CISer<CConnection>& s);
-template void registerTypesClientPacks1<COSer<CConnection>>(COSer<CConnection>& s);
-template void registerTypesClientPacks1<CISer<CMemorySerializer>>(CISer<CMemorySerializer>& s);
-template void registerTypesClientPacks1<COSer<CMemorySerializer>>(COSer<CMemorySerializer>& s);
-template void registerTypesClientPacks1<CSaveFile>(CSaveFile & s);
-template void registerTypesClientPacks1<CLoadFile>(CLoadFile & s);
+template void registerTypesClientPacks1<CISer>(CISer & s);
+template void registerTypesClientPacks1<COSer>(COSer & s);
 template void registerTypesClientPacks1<CTypeList>(CTypeList & s);
-template void registerTypesClientPacks1<CLoadIntegrityValidator>(CLoadIntegrityValidator & s);

+ 4 - 7
lib/registerTypes/TypesClientPacks2.cpp

@@ -19,11 +19,8 @@
 #include "../mapObjects/CObjectClassesHandler.h"
 
 
-template void registerTypesClientPacks2<CISer<CConnection>>(CISer<CConnection>& s);
-template void registerTypesClientPacks2<COSer<CConnection>>(COSer<CConnection>& s);
-template void registerTypesClientPacks2<CISer<CMemorySerializer>>(CISer<CMemorySerializer>& s);
-template void registerTypesClientPacks2<COSer<CMemorySerializer>>(COSer<CMemorySerializer>& s);
-template void registerTypesClientPacks2<CSaveFile>(CSaveFile & s);
-template void registerTypesClientPacks2<CLoadFile>(CLoadFile & s);
+template void registerTypesClientPacks2<CISer>(CISer & s);
+template void registerTypesClientPacks2<COSer>(COSer & s);
 template void registerTypesClientPacks2<CTypeList>(CTypeList & s);
-template void registerTypesClientPacks2<CLoadIntegrityValidator>(CLoadIntegrityValidator & s);
+
+

+ 3 - 7
lib/registerTypes/TypesMapObjects1.cpp

@@ -18,12 +18,8 @@
 #include "../NetPacks.h"
 #include "../mapObjects/CObjectClassesHandler.h"
 
-template void registerTypesMapObjects1<CISer<CConnection>>(CISer<CConnection>& s);
-template void registerTypesMapObjects1<COSer<CConnection>>(COSer<CConnection>& s);
-template void registerTypesMapObjects1<CISer<CMemorySerializer>>(CISer<CMemorySerializer>& s);
-template void registerTypesMapObjects1<COSer<CMemorySerializer>>(COSer<CMemorySerializer>& s);
-template void registerTypesMapObjects1<CSaveFile>(CSaveFile & s);
-template void registerTypesMapObjects1<CLoadFile>(CLoadFile & s);
+template void registerTypesMapObjects1<CISer>(CISer & s);
+template void registerTypesMapObjects1<COSer>(COSer & s);
 template void registerTypesMapObjects1<CTypeList>(CTypeList & s);
-template void registerTypesMapObjects1<CLoadIntegrityValidator>(CLoadIntegrityValidator & s);
+
 

+ 2 - 7
lib/registerTypes/TypesMapObjects2.cpp

@@ -19,12 +19,7 @@
 #include "../mapObjects/CObjectClassesHandler.h"
 
 
-template void registerTypesMapObjects2<CISer<CConnection>>(CISer<CConnection>& s);
-template void registerTypesMapObjects2<COSer<CConnection>>(COSer<CConnection>& s);
-template void registerTypesMapObjects2<CISer<CMemorySerializer>>(CISer<CMemorySerializer>& s);
-template void registerTypesMapObjects2<COSer<CMemorySerializer>>(COSer<CMemorySerializer>& s);
-template void registerTypesMapObjects2<CSaveFile>(CSaveFile & s);
-template void registerTypesMapObjects2<CLoadFile>(CLoadFile & s);
+template void registerTypesMapObjects2<CISer>(CISer & s);
+template void registerTypesMapObjects2<COSer>(COSer & s);
 template void registerTypesMapObjects2<CTypeList>(CTypeList & s);
-template void registerTypesMapObjects2<CLoadIntegrityValidator>(CLoadIntegrityValidator & s);
 

+ 2 - 7
lib/registerTypes/TypesMapObjects3.cpp

@@ -18,11 +18,6 @@
 #include "../NetPacks.h"
 #include "../mapObjects/CObjectClassesHandler.h"
 
-template void registerTypesMapObjectTypes<CISer<CConnection>>(CISer<CConnection>& s);
-template void registerTypesMapObjectTypes<COSer<CConnection>>(COSer<CConnection>& s);
-template void registerTypesMapObjectTypes<CISer<CMemorySerializer>>(CISer<CMemorySerializer>& s);
-template void registerTypesMapObjectTypes<COSer<CMemorySerializer>>(COSer<CMemorySerializer>& s);
-template void registerTypesMapObjectTypes<CSaveFile>(CSaveFile & s);
-template void registerTypesMapObjectTypes<CLoadFile>(CLoadFile & s);
+template void registerTypesMapObjectTypes<CISer>(CISer & s);
+template void registerTypesMapObjectTypes<COSer>(COSer & s);
 template void registerTypesMapObjectTypes<CTypeList>(CTypeList & s);
-template void registerTypesMapObjectTypes<CLoadIntegrityValidator>(CLoadIntegrityValidator & s);

+ 3 - 7
lib/registerTypes/TypesPregamePacks.cpp

@@ -18,11 +18,7 @@
 #include "../NetPacks.h"
 #include "../mapObjects/CObjectClassesHandler.h"
 
-template void registerTypesPregamePacks<CISer<CConnection>>(CISer<CConnection>& s);
-template void registerTypesPregamePacks<COSer<CConnection>>(COSer<CConnection>& s);
-template void registerTypesPregamePacks<CISer<CMemorySerializer>>(CISer<CMemorySerializer>& s);
-template void registerTypesPregamePacks<COSer<CMemorySerializer>>(COSer<CMemorySerializer>& s);
-template void registerTypesPregamePacks<CSaveFile>(CSaveFile & s);
-template void registerTypesPregamePacks<CLoadFile>(CLoadFile & s);
+template void registerTypesPregamePacks<CISer>(CISer & s);
+template void registerTypesPregamePacks<COSer>(COSer & s);
 template void registerTypesPregamePacks<CTypeList>(CTypeList & s);
-template void registerTypesPregamePacks<CLoadIntegrityValidator>(CLoadIntegrityValidator & s);
+

+ 2 - 7
lib/registerTypes/TypesServerPacks.cpp

@@ -18,11 +18,6 @@
 #include "../NetPacks.h"
 #include "../mapObjects/CObjectClassesHandler.h"
 
-template void registerTypesServerPacks<CISer<CConnection>>(CISer<CConnection>& s);
-template void registerTypesServerPacks<COSer<CConnection>>(COSer<CConnection>& s);
-template void registerTypesServerPacks<CISer<CMemorySerializer>>(CISer<CMemorySerializer>& s);
-template void registerTypesServerPacks<COSer<CMemorySerializer>>(COSer<CMemorySerializer>& s);
-template void registerTypesServerPacks<CSaveFile>(CSaveFile & s);
-template void registerTypesServerPacks<CLoadFile>(CLoadFile & s);
+template void registerTypesServerPacks<CISer>(CISer & s);
+template void registerTypesServerPacks<COSer>(COSer & s);
 template void registerTypesServerPacks<CTypeList>(CTypeList & s);
-template void registerTypesServerPacks<CLoadIntegrityValidator>(CLoadIntegrityValidator & s);

+ 1 - 1
lib/rmg/CMapGenerator.cpp

@@ -74,7 +74,7 @@ void CMapGenerator::initPrisonsRemaining()
 		if (isAllowed)
 			prisonsRemaining++;
 	}
-	prisonsRemaining = std::max<int> (0, prisonsRemaining - 16 * map->players.size()); //so at least 16 heroes will be available for every player
+	prisonsRemaining = std::max<int> (0, prisonsRemaining - 16 * mapGenOptions->getPlayerCount()); //so at least 16 heroes will be available for every player
 }
 
 std::unique_ptr<CMap> CMapGenerator::generate(CMapGenOptions * mapGenOptions, int randomSeed /*= std::time(nullptr)*/)

+ 0 - 7
lib/rmg/CRmgTemplateStorage.cpp

@@ -133,7 +133,6 @@ void CJsonRmgTemplateLoader::loadTemplates()
 				//treasures
 				if (!zoneNode["treasure"].isNull())
 				{
-					int totalDensity = 0;
 					//TODO: parse vector of different treasure settings
 					if (zoneNode["treasure"].getType() == JsonNode::DATA_STRUCT)
 					{
@@ -143,8 +142,6 @@ void CJsonRmgTemplateLoader::loadTemplates()
 							ti.min = treasureInfo["min"].Float();
 							ti.max = treasureInfo["max"].Float();
 							ti.density = treasureInfo["density"].Float(); //TODO: use me
-							totalDensity += ti.density;
-							ti.threshold = totalDensity;
 							zone->addTreasureInfo(ti);
 						}
 					}
@@ -156,12 +153,9 @@ void CJsonRmgTemplateLoader::loadTemplates()
 							ti.min = treasureInfo["min"].Float();
 							ti.max = treasureInfo["max"].Float();
 							ti.density = treasureInfo["density"].Float();
-							totalDensity += ti.density;
-							ti.threshold = totalDensity;
 							zone->addTreasureInfo(ti);
 						}
 					}
-					zone->setTotalDensity (totalDensity);
 				}
 
 				zones[zone->getId()] = zone;
@@ -191,7 +185,6 @@ void CJsonRmgTemplateLoader::loadTemplates()
 					{
 						zone->addTreasureInfo(treasureInfo);
 					}
-					zone->setTotalDensity (zones[zoneNode["treasureLikeZone"].Float()]->getTotalDensity());
 				}
 
 				if (!zoneNode["minesLikeZone"].isNull())

+ 113 - 70
lib/rmg/CRmgTemplateZone.cpp

@@ -138,8 +138,7 @@ CRmgTemplateZone::CRmgTemplateZone() :
 	matchTerrainToTown(true),
 	townType(ETownType::NEUTRAL),
 	terrainType (ETerrainType::GRASS),
-	zoneMonsterStrength(EMonsterStrength::ZONE_NORMAL),
-	totalDensity(0)
+	zoneMonsterStrength(EMonsterStrength::ZONE_NORMAL)
 {
 	terrainTypes = getDefaultTerrainTypes();
 }
@@ -303,16 +302,6 @@ void CRmgTemplateZone::setMonsterStrength (EMonsterStrength::EMonsterStrength va
 	zoneMonsterStrength = val;
 }
 
-void CRmgTemplateZone::setTotalDensity (ui16 val)
-{
-	totalDensity = val;
-}
-
-ui16 CRmgTemplateZone::getTotalDensity () const
-{
-	return totalDensity;
-}
-
 void CRmgTemplateZone::addTreasureInfo(CTreasureInfo & info)
 {
 	treasureInfo.push_back(info);
@@ -420,7 +409,11 @@ void CRmgTemplateZone::fractalize(CMapGenerator* gen)
 	std::set<int3> tilesToIgnore; //will be erased in this iteration
 
 	//the more treasure density, the greater distance between paths. Scaling is experimental.
+	int totalDensity = 0;
+	for (auto ti : treasureInfo)
+		totalDensity =+ ti.density;
 	const float minDistance = std::sqrt(totalDensity * 3);
+
 	for (auto tile : tileinfo)
 	{
 		if (gen->isFree(tile))
@@ -612,6 +605,10 @@ void CRmgTemplateZone::addRequiredObject(CGObjectInstance * obj, si32 strength)
 {
 	requiredObjects.push_back(std::make_pair(obj, strength));
 }
+void CRmgTemplateZone::addCloseObject(CGObjectInstance * obj, si32 strength)
+{
+	closeObjects.push_back(std::make_pair(obj, strength));
+}
 
 bool CRmgTemplateZone::addMonster(CMapGenerator* gen, int3 &pos, si32 strength, bool clearSurroundingTiles, bool zoneGuard)
 {
@@ -686,7 +683,7 @@ bool CRmgTemplateZone::addMonster(CMapGenerator* gen, int3 &pos, si32 strength,
 	return true;
 }
 
-bool CRmgTemplateZone::createTreasurePile (CMapGenerator* gen, int3 &pos, float minDistance)
+bool CRmgTemplateZone::createTreasurePile(CMapGenerator* gen, int3 &pos, float minDistance, const CTreasureInfo& treasureInfo)
 {
 	CTreasurePileInfo info;
 
@@ -695,25 +692,9 @@ bool CRmgTemplateZone::createTreasurePile (CMapGenerator* gen, int3 &pos, float
 	int3 guardPos (-1,-1,-1);
 	info.nextTreasurePos = pos;
 
-	//default values
-	int maxValue = 5000;
-	int minValue = 1500;
-
-	if (treasureInfo.size())
-	{
-		//roulette wheel
-		int r = gen->rand.nextInt (1, totalDensity);
+	int maxValue = treasureInfo.max;
+	int minValue = treasureInfo.min;
 
-		for (auto t : treasureInfo)
-		{
-			if (r <= t.threshold)
-			{
-				maxValue = t.max;
-				minValue = t.min;
-				break;
-			}
-		}
-	}
 	ui32 desiredValue = gen->rand.nextInt(minValue, maxValue);
 	//quantize value to let objects with value equal to max spawn too
 
@@ -1132,11 +1113,8 @@ bool CRmgTemplateZone::placeMines (CMapGenerator* gen)
 	static const Res::ERes woodOre[] = {Res::ERes::WOOD, Res::ERes::ORE};
 	static const Res::ERes preciousResources[] = {Res::ERes::GEMS, Res::ERes::CRYSTAL, Res::ERes::MERCURY, Res::ERes::SULFUR};
 
-
-	//TODO: factory / copy constructor?
 	for (const auto & res : woodOre)
 	{
-		//TODO: these 2 should be close to town (within 12 tiles radius)
 		for (int i = 0; i < mines[res]; i++)
 		{
 			auto mine = new CGMine();
@@ -1144,7 +1122,7 @@ bool CRmgTemplateZone::placeMines (CMapGenerator* gen)
 			mine->subID = static_cast<si32>(res);
 			mine->producedResource = res;
 			mine->producedQuantity = mine->defaultResProduction();
-			addRequiredObject(mine, 1500);
+			addCloseObject(mine, 1500);
 		}
 	}
 	for (const auto & res : preciousResources)
@@ -1189,35 +1167,91 @@ bool CRmgTemplateZone::createRequiredObjects(CMapGenerator* gen)
 		guardObject (gen, obj.first, obj.second, (obj.first->ID == Obj::MONOLITH_TWO_WAY), true);
 		//paths to required objects constitute main paths of zone. otherwise they just may lead to middle and create dead zones
 	}
+
+	for (const auto &obj : closeObjects)
+	{
+		std::vector<int3> tiles(possibleTiles.begin(), possibleTiles.end()); //new tiles vector after each object has been placed
+		
+		// smallest distance to zone center, greatest distance to nearest object
+		auto isCloser = [this, gen](const int3 & lhs, const int3 & rhs) -> bool
+		{
+			return (this->pos.dist2dSQ(lhs) * 0.5f - gen->getNearestObjectDistance(lhs)) < (this->pos.dist2dSQ(rhs) * 0.5f - gen->getNearestObjectDistance(rhs));
+		};
+
+		boost::sort (tiles, isCloser);
+
+		setTemplateForObject(gen, obj.first);
+		auto tilesBlockedByObject = obj.first->getBlockedOffsets();
+		bool result = false;
+
+		for (auto tile : tiles)
+		{
+			//object must be accessible from at least one surounding tile
+			if (!isAccessibleFromAnywhere(gen, obj.first->appearance, tile, tilesBlockedByObject))
+				continue;
+
+			//avoid borders
+			if (gen->isPossible(tile))
+			{
+				if (areAllTilesAvailable(gen, obj.first, tile, tilesBlockedByObject))
+				{
+					placeObject(gen, obj.first, tile);
+					guardObject(gen, obj.first, obj.second, (obj.first->ID == Obj::MONOLITH_TWO_WAY), true);
+					result = true;
+					break;
+				}
+			}
+		}
+		if (!result)
+		{
+			logGlobal->errorStream() << boost::format("Failed to fill zone %d due to lack of space") % id;
+			//TODO CLEANUP!
+			return false;
+		}
+	}
+
 	return true;
 }
 
 void CRmgTemplateZone::createTreasures(CMapGenerator* gen)
 {
-	//treasure density is proportional to map siz,e but must be scaled bakc to map size
-	//also, normalize it to zone count - higher count means relative smaller zones
+	auto valueComparator = [](const CTreasureInfo & lhs, const CTreasureInfo & rhs) -> bool
+	{
+		return lhs.max > rhs.max;
+	};
 
-	//this is squared distance for optimization purposes
-	const double minDistance = std::max<float>((600.f * size * size * gen->getZones().size()) /
-		(gen->mapGenOptions->getWidth() * gen->mapGenOptions->getHeight() * totalDensity * (gen->map->twoLevel ? 2 : 1)), 2);
-	//distance lower than 2 causes objects to overlap and crash
+	//place biggest treasures first at large distance, place smaller ones inbetween
+	boost::sort(treasureInfo, valueComparator);
 
-	do {
-		
-		//optimization - don't check tiles which are not allowed
-		vstd::erase_if (possibleTiles, [gen](const int3 &tile) -> bool
-		{
-			return !gen->isPossible(tile);
-		});
+	int totalDensity = 0;
+	for (auto t : treasureInfo)
+	{
+		totalDensity += t.density;
 
-		int3 pos;
-		if ( ! findPlaceForTreasurePile(gen, minDistance, pos))		
-		{
-			break;
-		}
-		createTreasurePile (gen, pos, minDistance);
+		//treasure density is inversely proportional to zone size but must be scaled back to map size
+		//also, normalize it to zone count - higher count means relatively smaller zones
+
+		//this is squared distance for optimization purposes
+		const double minDistance = std::max<float>((450.f * size * size * gen->getZones().size()) /
+			(gen->mapGenOptions->getWidth() * gen->mapGenOptions->getHeight() * totalDensity * (gen->map->twoLevel ? 2 : 1)), 2);
+		//distance lower than 2 causes objects to overlap and crash
 
-	} while(true);
+		do {
+			//optimization - don't check tiles which are not allowed
+			vstd::erase_if(possibleTiles, [gen](const int3 &tile) -> bool
+			{
+				return !gen->isPossible(tile);
+			});
+
+			int3 pos;
+			if (!findPlaceForTreasurePile(gen, minDistance, pos))
+			{
+				break;
+			}
+			createTreasurePile(gen, pos, minDistance, t);
+
+		} while (true);
+	}
 }
 
 void CRmgTemplateZone::createObstacles(CMapGenerator* gen)
@@ -1451,17 +1485,36 @@ bool CRmgTemplateZone::isAccessibleFromAnywhere (CMapGenerator* gen, ObjectTempl
 	return accessible;
 }
 
-bool CRmgTemplateZone::findPlaceForObject(CMapGenerator* gen, CGObjectInstance* obj, si32 min_dist, int3 &pos)
+void CRmgTemplateZone::setTemplateForObject(CMapGenerator* gen, CGObjectInstance* obj)
 {
-	//we need object apperance to deduce free tiles
 	if (obj->appearance.id == Obj::NO_OBJ)
 	{
 		auto templates = VLC->objtypeh->getHandlerFor(obj->ID, obj->subID)->getTemplates(gen->map->getTile(getPos()).terType);
 		if (templates.empty())
-			throw rmgException(boost::to_string(boost::format("Did not find graphics for object (%d,%d) at %s") %obj->ID %obj->subID %pos));
-	
+			throw rmgException(boost::to_string(boost::format("Did not find graphics for object (%d,%d) at %s") % obj->ID %obj->subID %pos));
+
 		obj->appearance = templates.front();
 	}
+}
+
+bool CRmgTemplateZone::areAllTilesAvailable(CMapGenerator* gen, CGObjectInstance* obj, int3& tile, std::set<int3>& tilesBlockedByObject) const
+{
+	for (auto blockingTile : tilesBlockedByObject)
+	{
+		int3 t = tile + blockingTile;
+		if (!gen->map->isInTheMap(t) || !gen->isPossible(t))
+		{
+			//if at least one tile is not possible, object can't be placed here
+			return false;
+		}
+	}
+	return true;
+}
+
+bool CRmgTemplateZone::findPlaceForObject(CMapGenerator* gen, CGObjectInstance* obj, si32 min_dist, int3 &pos)
+{
+	//we need object apperance to deduce free tile
+	setTemplateForObject(gen, obj);
 
 	//si32 min_dist = sqrt(tileinfo.size()/density);
 	int best_distance = 0;
@@ -1484,17 +1537,7 @@ bool CRmgTemplateZone::findPlaceForObject(CMapGenerator* gen, CGObjectInstance*
 		//avoid borders
 		if (gen->isPossible(tile) && (dist >= min_dist) && (dist > best_distance))
 		{
-			bool allTilesAvailable = true;
-			for (auto blockingTile : tilesBlockedByObject)
-			{
-				int3 t = tile + blockingTile;
-				if (!gen->map->isInTheMap(t) || !gen->isPossible(t))
-				{
-					allTilesAvailable = false; //if at least one tile is not possible, object can't be placed here
-					break;
-				}
-			}
-			if (allTilesAvailable)
+			if (areAllTilesAvailable(gen, obj, tile, tilesBlockedByObject))
 			{
 				best_distance = dist;
 				pos = tile;

+ 5 - 5
lib/rmg/CRmgTemplateZone.h

@@ -63,7 +63,6 @@ public:
 	ui32 min;
 	ui32 max;
 	ui16 density;
-	ui16 threshold; //how much must RNG roll to pick that zone
 };
 
 struct DLL_LINKAGE ObjectInfo
@@ -151,8 +150,9 @@ public:
 	void discardDistantTiles (CMapGenerator* gen, float distance);
 
 	void addRequiredObject(CGObjectInstance * obj, si32 guardStrength=0);
+	void addCloseObject(CGObjectInstance * obj, si32 guardStrength = 0);
 	bool addMonster(CMapGenerator* gen, int3 &pos, si32 strength, bool clearSurroundingTiles = true, bool zoneGuard = false);
-	bool createTreasurePile(CMapGenerator* gen, int3 &pos, float minDistance);
+	bool createTreasurePile(CMapGenerator* gen, int3 &pos, float minDistance, const CTreasureInfo& treasureInfo);
 	bool fill (CMapGenerator* gen);
 	bool placeMines (CMapGenerator* gen);
 	void initTownType (CMapGenerator* gen);
@@ -166,8 +166,6 @@ public:
 	bool crunchPath (CMapGenerator* gen, const int3 &src, const int3 &dst, TRmgTemplateZoneId zone, std::set<int3>* clearedTiles = nullptr);
 	std::vector<int3> getAccessibleOffsets (CMapGenerator* gen, CGObjectInstance* object);
 
-	void setTotalDensity (ui16 val);
-	ui16 getTotalDensity () const;
 	void addConnection(TRmgTemplateZoneId otherZone);
 	std::vector<TRmgTemplateZoneId> getConnections() const;
 	void addTreasureInfo(CTreasureInfo & info);
@@ -196,12 +194,12 @@ private:
 	ETerrainType terrainType;
 
 	EMonsterStrength::EMonsterStrength zoneMonsterStrength;
-	ui16 totalDensity;
 	std::vector<CTreasureInfo> treasureInfo;
 	std::vector<ObjectInfo> possibleObjects;
 
 	//content info
 	std::vector<std::pair<CGObjectInstance*, ui32>> requiredObjects;
+	std::vector<std::pair<CGObjectInstance*, ui32>> closeObjects;
 	std::vector<CGObjectInstance*> objects;
 
 	//placement info
@@ -218,6 +216,8 @@ private:
 	bool findPlaceForObject(CMapGenerator* gen, CGObjectInstance* obj, si32 min_dist, int3 &pos);
 	bool findPlaceForTreasurePile(CMapGenerator* gen, float min_dist, int3 &pos);
 	bool canObstacleBePlacedHere(CMapGenerator* gen, ObjectTemplate &temp, int3 &pos);
+	void setTemplateForObject(CMapGenerator* gen, CGObjectInstance* obj);
+	bool areAllTilesAvailable(CMapGenerator* gen, CGObjectInstance* obj, int3& tile, std::set<int3>& tilesBlockedByObject) const;
 	void checkAndPlaceObject(CMapGenerator* gen, CGObjectInstance* object, const int3 &pos);
 	void placeObject(CMapGenerator* gen, CGObjectInstance* object, const int3 &pos, bool updateDistance = true);
 	bool guardObject(CMapGenerator* gen, CGObjectInstance* object, si32 str, bool zoneGuard = false, bool addToFreePaths = false);

+ 103 - 38
lib/rmg/CZonePlacer.cpp

@@ -40,12 +40,6 @@ int3 CZonePlacer::cords (const float3 f) const
 
 void CZonePlacer::placeZones(const CMapGenOptions * mapGenOptions, CRandomGenerator * rand)
 {
-	//gravity-based algorithm
-
-	float gravityConstant = 1e-2;
-	float zoneScale = 0.5f; //zones starts small and then inflate
-	const float inflateModifier = 1.02;
-
 	logGlobal->infoStream() << "Starting zone placement";
 
 	int width = mapGenOptions->getWidth();
@@ -54,6 +48,13 @@ void CZonePlacer::placeZones(const CMapGenOptions * mapGenOptions, CRandomGenera
 	auto zones = gen->getZones();
 	bool underground = mapGenOptions->getHasTwoLevels();
 
+	//gravity-based algorithm
+
+	const float gravityConstant = 4e-3;
+	const float stiffnessConstant = 4e-3;
+	float zoneScale = 1.0f / std::sqrt(zones.size()); //zones starts small and then inflate. placing more zones is more difficult
+	const float inflateModifier = 1.02;
+
 	/*
 		let's assume we try to fit N circular zones with radius = size on a map
 
@@ -69,6 +70,10 @@ void CZonePlacer::placeZones(const CMapGenOptions * mapGenOptions, CRandomGenera
 	bool undergroundFlag = false;
 
 	std::vector<float> totalSize = { 0, 0 }; //make sure that sum of zone sizes on surface and uderground match size of the map
+
+	const float radius = 0.4f;
+	const float pi2 = 6.28f;
+
 	for (auto zone : zonesVector)
 	{
 		//even distribution for surface / underground zones. Surface zones always have priority.
@@ -87,7 +92,8 @@ void CZonePlacer::placeZones(const CMapGenOptions * mapGenOptions, CRandomGenera
 		}
 
 		totalSize[level] += (zone.second->getSize() * zone.second->getSize());
-		zone.second->setCenter (float3(rand->nextDouble(0.2, 0.8), rand->nextDouble(0.2, 0.8), level)); //start away from borders
+		float randomAngle = rand->nextDouble(0, pi2);
+		zone.second->setCenter(float3(0.5f + std::sin(randomAngle) * radius, 0.5f + std::cos(randomAngle) * radius, level)); //place zones around circle
 	}
 	//prescale zones
 	std::vector<float> prescaler = { 0, 0 };
@@ -101,6 +107,14 @@ void CZonePlacer::placeZones(const CMapGenOptions * mapGenOptions, CRandomGenera
 
 	//gravity-based algorithm. connected zones attract, intersceting zones and map boundaries push back
 
+	//remember best solution
+	float bestTotalDistance = 1e10;
+	float bestTotalOverlap = 1e10;
+	//float bestRatio = 1e10;
+	std::map<CRmgTemplateZone *, float3> bestSolution;
+
+	const int maxDistanceMovementRatio = zones.size() * zones.size(); //experimental - the more zones, the greater total distance expected
+
 	auto getDistance = [](float distance) -> float
 	{
 		return (distance ? distance * distance : 1e-6);
@@ -108,6 +122,7 @@ void CZonePlacer::placeZones(const CMapGenOptions * mapGenOptions, CRandomGenera
 
 	std::map <CRmgTemplateZone *, float3> forces;
 	std::map <CRmgTemplateZone *, float> distances;
+	std::map <CRmgTemplateZone *, float> overlaps;
 	while (zoneScale < 1) //until zones reach their desired size and fill the map tightly
 	{
 		for (auto zone : zones)
@@ -126,11 +141,13 @@ void CZonePlacer::placeZones(const CMapGenOptions * mapGenOptions, CRandomGenera
 				if (distance > minDistance)
 				{
 					//WARNING: compiler used to 'optimize' that line so it never actually worked
-					forceVector += (((otherZoneCenter - pos)*(pos.z != otherZoneCenter.z ? (distance - minDistance) : 1)/ getDistance(distance))); //positive value
-					totalDistance += distance;
+					forceVector += (((otherZoneCenter - pos)*(pos.z == otherZoneCenter.z ? (minDistance/distance) : 1)/ getDistance(distance))) * gravityConstant; //positive value
+					totalDistance += (distance - minDistance);
 				}
 			}
 			distances[zone.second] = totalDistance;
+
+			float totalOverlap = 0;
 			//separate overlaping zones
 			for (auto otherZone : zones)
 			{
@@ -143,7 +160,8 @@ void CZonePlacer::placeZones(const CMapGenOptions * mapGenOptions, CRandomGenera
 				float minDistance = (zone.second->getSize() + otherZone.second->getSize())/mapSize * zoneScale;
 				if (distance < minDistance)
 				{
-					forceVector -= (((otherZoneCenter - pos)*(minDistance - distance)) / getDistance(distance)); //negative value
+					forceVector -= (((otherZoneCenter - pos)*(minDistance/(distance ? distance : 1e-3))) / getDistance(distance)) * stiffnessConstant; //negative value
+					totalOverlap += (minDistance - distance) / (zoneScale * zoneScale); //overlapping of small zones hurts us more
 				}
 			}
 
@@ -151,11 +169,12 @@ void CZonePlacer::placeZones(const CMapGenOptions * mapGenOptions, CRandomGenera
 			//do not scale boundary distance - zones tend to get squashed
 			float size = zone.second->getSize() / mapSize;
 
-			auto pushAwayFromBoundary = [&forceVector, pos, &getDistance, size](float x, float y)
+			auto pushAwayFromBoundary = [&forceVector, pos, &getDistance, size, stiffnessConstant, &totalOverlap](float x, float y)
 			{
 				float3 boundary = float3 (x, y, pos.z);
 				float distance = pos.dist2d(boundary);
-				forceVector -= (boundary - pos) * (size - distance) / getDistance(distance); //negative value
+				totalOverlap += distance; //overlapping map boundaries is wrong as well
+				forceVector -= (boundary - pos) * (size - distance) / getDistance(distance) * stiffnessConstant; //negative value
 			};
 			if (pos.x < size)
 			{
@@ -173,9 +192,9 @@ void CZonePlacer::placeZones(const CMapGenOptions * mapGenOptions, CRandomGenera
 			{
 				pushAwayFromBoundary(pos.x, 1);
 			}
-
+			overlaps[zone.second] = totalOverlap;
 			forceVector.z = 0; //operator - doesn't preserve z coordinate :/
-			forces[zone.second] = forceVector * gravityConstant;
+			forces[zone.second] = forceVector;
 		}
 		//update positions
 		for (auto zone : forces)
@@ -185,53 +204,99 @@ void CZonePlacer::placeZones(const CMapGenOptions * mapGenOptions, CRandomGenera
 
 		//now perform drastic movement of zone that is completely not linked
 		float maxRatio = 0;
-		CRmgTemplateZone * distantZone = nullptr;
+		CRmgTemplateZone * misplacedZone = nullptr;
 
 		float totalDistance = 0;
+		float totalOverlap = 0;
 		for (auto zone : distances) //find most misplaced zone
 		{
 			totalDistance += zone.second;
-			float ratio = zone.second / forces[zone.first].mag(); //if distance to actual movement is long, the zone is misplaced
+			float overlap = overlaps[zone.first];
+			totalOverlap += overlap;
+			float ratio = (zone.second + overlap) / forces[zone.first].mag(); //if distance to actual movement is long, the zone is misplaced
 			if (ratio > maxRatio)
 			{
 				maxRatio = ratio;
-				distantZone = zone.first;
+				misplacedZone = zone.first;
 			}
 		}
-		logGlobal->traceStream() << boost::format("Total distance between zones in this iteration: %2.2f, Worst distance/movement ratio: %3.2f") % totalDistance % maxRatio;
+		logGlobal->traceStream() << boost::format("Total distance between zones in this iteration: %2.4f, Total overlap: %2.4f, Worst misplacement/movement ratio: %3.2f") % totalDistance % totalOverlap % maxRatio;
+
+		//save best solution before drastic jump
+		if (totalDistance + totalOverlap < bestTotalDistance + bestTotalOverlap)
+		{
+			bestTotalDistance = totalDistance;
+			bestTotalOverlap = totalOverlap;
+		//if (maxRatio < bestRatio)
+		//{
+		//	bestRatio = maxRatio;
+			for (auto zone : zones)
+				bestSolution[zone.second] = zone.second->getCenter();
+		}
 		
-		if (maxRatio > 100) //TODO: scale?
+		if (maxRatio > maxDistanceMovementRatio)
 		{
-			//find most distant zone that should be attracted and move inside it
-			
 			CRmgTemplateZone * targetZone = nullptr;
-			float maxDistance = 0;
-			float3 ourCenter = distantZone->getCenter();
-			for (auto con : distantZone->getConnections())
+			float3 ourCenter = misplacedZone->getCenter();
+
+			if (totalDistance > totalOverlap)
 			{
-				auto otherZone = zones[con];
-				float distance = otherZone->getCenter().dist2dSQ(ourCenter);
-				if (distance > maxDistance)
+				//find most distant zone that should be attracted and move inside it
+				float maxDistance = 0;
+				for (auto con : misplacedZone->getConnections())
 				{
-					maxDistance = distance;
-					targetZone = otherZone;
+					auto otherZone = zones[con];
+					float distance = otherZone->getCenter().dist2dSQ(ourCenter);
+					if (distance > maxDistance)
+					{
+						maxDistance = distance;
+						targetZone = otherZone;
+					}
 				}
+				float3 vec = targetZone->getCenter() - ourCenter;
+				float newDistanceBetweenZones = (std::max(misplacedZone->getSize(), targetZone->getSize())) * zoneScale / mapSize;
+				logGlobal->traceStream() << boost::format("Trying to move zone %d %s towards %d %s. Old distance %f") %
+					misplacedZone->getId() % ourCenter() % targetZone->getId() % targetZone->getCenter()() % maxDistance;
+				logGlobal->traceStream() << boost::format("direction is %s") % vec();
+
+				misplacedZone->setCenter(targetZone->getCenter() - vec.unitVector() * newDistanceBetweenZones); //zones should now overlap by half size
+				logGlobal->traceStream() << boost::format("New distance %f") % targetZone->getCenter().dist2d(misplacedZone->getCenter());
+			}
+			else
+			{
+				float maxOverlap = 0;
+				for (auto otherZone : zones)
+				{
+					float3 otherZoneCenter = otherZone.second->getCenter();
+
+					if (otherZone.second == misplacedZone || otherZoneCenter.z != ourCenter.z)
+						continue;
+
+					float distance = otherZoneCenter.dist2dSQ(ourCenter);
+					if (distance > maxOverlap)
+					{
+						maxOverlap = distance;
+						targetZone = otherZone.second;
+					}
+				}
+				float3 vec = ourCenter - targetZone->getCenter();
+				float newDistanceBetweenZones = (misplacedZone->getSize() + targetZone->getSize()) * zoneScale / mapSize;
+				logGlobal->traceStream() << boost::format("Trying to move zone %d %s away from %d %s. Old distance %f") %
+					misplacedZone->getId() % ourCenter() % targetZone->getId() % targetZone->getCenter()() % maxOverlap;
+				logGlobal->traceStream() << boost::format("direction is %s") % vec();
+
+				misplacedZone->setCenter(targetZone->getCenter() + vec.unitVector() * newDistanceBetweenZones); //zones should now be just separated
+				logGlobal->traceStream() << boost::format("New distance %f") % targetZone->getCenter().dist2d(misplacedZone->getCenter());
 			}
-			float3 vec = targetZone->getCenter() - ourCenter;
-			float newDistanceBetweenZones = (std::max (distantZone->getSize(),targetZone->getSize())) * zoneScale / mapSize;
-			logGlobal->traceStream() << boost::format("Trying to move zone %d %s towards %d %s. Old distance %f") %
-				distantZone->getId() % ourCenter() % targetZone->getId() % targetZone->getCenter()() % maxDistance;
-			logGlobal->traceStream() << boost::format("direction is %s") % vec();
-
-			distantZone->setCenter(targetZone->getCenter() - vec.unitVector() * newDistanceBetweenZones); //zones should now overlap by half size
-			logGlobal->traceStream() << boost::format("New distance %f") % targetZone->getCenter().dist2d(distantZone->getCenter());
 		}
 
 		zoneScale *= inflateModifier; //increase size of zones so they
 	}
+
+	logGlobal->traceStream() << boost::format("Best fitness reached: total distance %2.4f, total overlap %2.4f") % bestTotalDistance % bestTotalOverlap;
 	for (auto zone : zones) //finalize zone positions
 	{
-		zone.second->setPos(cords(zone.second->getCenter()));
+		zone.second->setPos (cords (bestSolution[zone.second]));
 		logGlobal->traceStream() << boost::format ("Placed zone %d at relative position %s and coordinates %s") % zone.first % zone.second->getCenter() % zone.second->getPos();
 	}
 }

+ 1 - 1
server/CGameHandler.cpp

@@ -2919,7 +2919,7 @@ bool CGameHandler::buyArtifact( ObjectInstanceID hid, ArtifactID aid )
 			return false;
 
 		giveResource(hero->getOwner(),Res::GOLD,-GameConstants::SPELLBOOK_GOLD_COST);
-		giveHeroNewArtifact(hero, VLC->arth->artifacts[0], ArtifactPosition::SPELLBOOK);
+		giveHeroNewArtifact(hero, VLC->arth->artifacts[ArtifactID::SPELLBOOK], ArtifactPosition::SPELLBOOK);
 		assert(hero->getArt(ArtifactPosition::SPELLBOOK));
 		giveSpells(town,hero);
 		return true;

+ 1 - 0
test/Test.vcxproj

@@ -142,6 +142,7 @@
       </LinkTimeCodeGeneration>
       <ShowProgress>NotSet</ShowProgress>
       <AdditionalLibraryDirectories>..\..\libs;..</AdditionalLibraryDirectories>
+      <AdditionalOptions>/LTCG %(AdditionalOptions)</AdditionalOptions>
     </Link>
   </ItemDefinitionGroup>
   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='RD|x64'">

+ 6 - 10
vcmibuilder

@@ -209,17 +209,13 @@ fi
 
 if [[ -n "$data_dir" ]]
 then
-	cp -r "$data_dir"/Data "$dest_dir" 
-	cp -r "$data_dir"/Maps "$dest_dir"
-
 	# this folder is named differently from time to time
-	# vcmi can handle any case but script can't
-	if [ -d "$data_dir"/MP3 ] 
-	then
-		cp -r "$data_dir"/MP3 "$dest_dir"
-	else
-		cp -r "$data_dir"/Mp3 "$dest_dir"
-	fi
+	# bash also has `shopt -s nocaseglob` but we don't want this to
+	# accidentally influence other parts of this script
+	# since the directory names are short, we use this pattern matching
+	cp -r "$data_dir"/[Dd][Aa][Tt][Aa] "$dest_dir" 
+	cp -r "$data_dir"/[Mm][Aa][Pp][Ss] "$dest_dir"
+	cp -r "$data_dir"/[Mm][Pp]3 "$dest_dir"
 fi
 
 if [[ -n "$download" ]]