Преглед изворни кода

Merge branch 'develop' into FileInfo

Zyx-2000 пре 9 година
родитељ
комит
c3e8c8bc1c
50 измењених фајлова са 852 додато и 415 уклоњено
  1. 20 12
      AI/VCAI/Goals.cpp
  2. 0 1
      AI/VCAI/VCAI.cbp
  3. 5 4
      AI/VCAI/VCAI.cpp
  4. 2 0
      ChangeLog
  5. 5 0
      client/CMT.cpp
  6. 40 15
      client/CPlayerInterface.cpp
  7. 3 1
      client/CPlayerInterface.h
  8. 8 6
      client/Client.cpp
  9. 0 1
      client/VCMI_client.cbp
  10. 15 15
      client/battle/CBattleInterface.cpp
  11. 61 81
      client/widgets/CArtifactHolder.cpp
  12. 4 2
      client/widgets/CArtifactHolder.h
  13. 30 5
      client/widgets/CComponent.cpp
  14. 2 2
      client/widgets/CGarrisonInt.cpp
  15. 22 17
      client/windows/CAdvmapInterface.cpp
  16. 8 4
      client/windows/CHeroWindow.cpp
  17. 5 5
      client/windows/CTradeWindow.cpp
  18. 2 1
      config/defaultMods.json
  19. 88 8
      lib/CArtHandler.cpp
  20. 19 8
      lib/CArtHandler.h
  21. 42 38
      lib/CGameInfoCallback.cpp
  22. 2 3
      lib/CGameInfoCallback.h
  23. 7 6
      lib/CGameState.cpp
  24. 21 0
      lib/CModHandler.cpp
  25. 13 3
      lib/CModHandler.h
  26. 1 1
      lib/Connection.h
  27. 11 2
      lib/JsonNode.cpp
  28. 10 9
      lib/JsonNode.h
  29. 43 6
      lib/NetPacksLib.cpp
  30. 0 1
      lib/VCMI_lib.cbp
  31. 14 0
      lib/filesystem/AdapterLoaders.cpp
  32. 3 9
      lib/filesystem/AdapterLoaders.h
  33. 16 0
      lib/filesystem/ISimpleResourceLoader.h
  34. 4 3
      lib/logging/CLogger.h
  35. 50 24
      lib/mapObjects/CGHeroInstance.cpp
  36. 18 7
      lib/mapObjects/CGHeroInstance.h
  37. 4 4
      lib/mapObjects/CGPandoraBox.cpp
  38. 10 4
      lib/mapObjects/CObjectHandler.cpp
  39. 19 6
      lib/mapObjects/CQuest.cpp
  40. 16 2
      lib/mapObjects/CQuest.h
  41. 1 1
      lib/mapObjects/CRewardableObject.cpp
  42. 2 2
      lib/mapObjects/JsonRandom.cpp
  43. 89 36
      lib/mapObjects/MiscObjects.cpp
  44. 5 0
      lib/mapObjects/MiscObjects.h
  45. 2 0
      lib/rmg/CMapGenerator.cpp
  46. 7 9
      lib/rmg/CRmgTemplateZone.cpp
  47. 101 48
      server/CGameHandler.cpp
  48. 2 1
      server/CQuery.cpp
  49. 0 1
      server/VCMI_server.cbp
  50. 0 1
      vcmi.workspace

+ 20 - 12
AI/VCAI/Goals.cpp

@@ -154,8 +154,8 @@ bool Goals::AbstractGoal::operator== (AbstractGoal &g)
 
 //TODO: find out why the following are not generated automatically on MVS?
 
-namespace Goals 
-{ 
+namespace Goals
+{
 	template <>
 	void CGoal<Win>::accept (VCAI * ai)
 	{
@@ -275,7 +275,7 @@ TSubgoal Win::whatToDoToAchieve()
 					// 0.85 -> radius now 2 tiles
 					// 0.95 -> 1 tile radius, position is fully known
 					// AFAIK H3 AI does something like this
-					int3 grailPos = cb->getGrailPos(ratio);
+					int3 grailPos = cb->getGrailPos(&ratio);
 					if(ratio > 0.99)
 					{
 						return sptr (Goals::DigAtTile(grailPos));
@@ -367,7 +367,7 @@ TSubgoal FindObj::whatToDoToAchieve()
 
 std::string GetObj::completeMessage() const
 {
-	return "hero " + hero.get()->name + " captured Object ID = " + boost::lexical_cast<std::string>(objid); 
+	return "hero " + hero.get()->name + " captured Object ID = " + boost::lexical_cast<std::string>(objid);
 }
 
 TSubgoal GetObj::whatToDoToAchieve()
@@ -409,7 +409,7 @@ bool GetObj::fulfillsMe (TSubgoal goal)
 
 std::string VisitHero::completeMessage() const
 {
-	return "hero " + hero.get()->name + " visited hero " + boost::lexical_cast<std::string>(objid); 
+	return "hero " + hero.get()->name + " visited hero " + boost::lexical_cast<std::string>(objid);
 }
 
 TSubgoal VisitHero::whatToDoToAchieve()
@@ -435,10 +435,18 @@ TSubgoal VisitHero::whatToDoToAchieve()
 
 bool VisitHero::fulfillsMe (TSubgoal goal)
 {
-	if (goal->goalType == Goals::VISIT_TILE && cb->getObj(ObjectInstanceID(objid))->visitablePos() == goal->tile)
-		return true;
-	else
+	if (goal->goalType != Goals::VISIT_TILE)
+	{
 		return false;
+	}
+	auto obj = cb->getObj(ObjectInstanceID(objid));
+	if (!obj)
+	{
+		logAi->errorStream() << boost::format("Hero %s: VisitHero::fulfillsMe at %s: object %d not found")
+			% hero.name % goal->tile % objid;
+		return false;
+	}
+	return obj->visitablePos() == goal->tile;
 }
 
 TSubgoal GetArtOfType::whatToDoToAchieve()
@@ -458,7 +466,7 @@ TSubgoal ClearWayTo::whatToDoToAchieve()
 		return sptr (Goals::Explore());
 	}
 
-	return (fh->chooseSolution(getAllPossibleSubgoals()));	
+	return (fh->chooseSolution(getAllPossibleSubgoals()));
 }
 
 TGoalVec ClearWayTo::getAllPossibleSubgoals()
@@ -862,7 +870,7 @@ TSubgoal GatherTroops::whatToDoToAchieve()
 		{
 			auto creatures = vstd::tryAt(t->town->creatures, creature->level - 1);
 			if(!creatures)
-				continue; 
+				continue;
 
 			int upgradeNumber = vstd::find_pos(*creatures, creature->idNumber);
 			if(upgradeNumber < 0)
@@ -957,7 +965,7 @@ TGoalVec Conquer::getAllPossibleSubgoals()
 	std::vector<const CGObjectInstance *> objs;
 	for (auto obj : ai->visitableObjs)
 	{
-		if (conquerable(obj)) 
+		if (conquerable(obj))
 			objs.push_back (obj);
 	}
 
@@ -1035,7 +1043,7 @@ TGoalVec GatherArmy::getAllPossibleSubgoals()
 {
 	//get all possible towns, heroes and dwellings we may use
 	TGoalVec ret;
-	
+
 	//TODO: include evaluation of monsters gather in calculation
 	for (auto t : cb->getTownsInfo())
 	{

+ 0 - 1
AI/VCAI/VCAI.cbp

@@ -86,7 +86,6 @@
 		<Unit filename="Goals.cpp" />
 		<Unit filename="Goals.h" />
 		<Unit filename="StdInc.h">
-			<Option compile="1" />
 			<Option weight="0" />
 		</Unit>
 		<Unit filename="VCAI.cpp" />

+ 5 - 4
AI/VCAI/VCAI.cpp

@@ -1987,7 +1987,6 @@ bool VCAI::moveHeroToTile(int3 dst, HeroPtr h)
 			if(teleportChannelProbingList.size())
 				doChannelProbing();
 		}
-		ret = !i;
 	}
 	if (h)
 	{
@@ -2002,6 +2001,8 @@ bool VCAI::moveHeroToTile(int3 dst, HeroPtr h)
 		completeGoal (sptr(Goals::VisitTile(dst).sethero(h))); //we stepped on some tile, anyway
 		completeGoal (sptr(Goals::ClearWayTo(dst).sethero(h)));
 
+		ret = (dst == h->visitablePos());
+
 		if(!ret) //reserve object we are heading towards
 		{
 			auto obj = vstd::frontOrNull(cb->getVisitableObjs(dst));
@@ -2020,7 +2021,7 @@ bool VCAI::moveHeroToTile(int3 dst, HeroPtr h)
 }
 void VCAI::tryRealize(Goals::Explore & g)
 {
-	throw cannotFulfillGoalException("EXPLORE is not a elementar goal!");
+	throw cannotFulfillGoalException("EXPLORE is not an elementar goal!");
 }
 
 void VCAI::tryRealize(Goals::RecruitHero & g)
@@ -2853,7 +2854,7 @@ BattleState AIStatus::getBattle()
 }
 
 void AIStatus::addQuery(QueryID ID, std::string description)
-{	
+{
 	if(ID == QueryID(-1))
 	{
         logAi->debugStream() << boost::format("The \"query\" has an id %d, it'll be ignored as non-query. Description: %s") % ID % description;
@@ -2878,7 +2879,7 @@ void AIStatus::removeQuery(QueryID ID)
 
 	std::string description = remainingQueries[ID];
 	remainingQueries.erase(ID);
-	
+
 	cv.notify_all();
     logAi->debugStream() << boost::format("Removing query %d - %s. Total queries count: %d") % ID % description % remainingQueries.size();
 }

+ 2 - 0
ChangeLog

@@ -8,6 +8,8 @@ GENERAL:
 - Angel Wings
 - Boots of Levitation
 * Implemented rumors in tavern window
+* New cheat code:
+- vcmiglaurung - gives 5000 crystal dragons into each slot
 
 ADVETURE AI:
 * Fixed AI trying to go through underground rock

+ 5 - 0
client/CMT.cpp

@@ -441,6 +441,11 @@ int main(int argc, char** argv)
 	CCS->musich->setVolume(settings["general"]["music"].Float());
     logGlobal->infoStream()<<"Initializing screen and sound handling: "<<pomtime.getDiff();
 
+#ifdef __APPLE__
+	// Ctrl+click should be treated as a right click on Mac OS X
+	SDL_SetHint(SDL_HINT_MAC_CTRL_CLICK_EMULATE_RIGHT_CLICK, "1");
+#endif
+
 #ifndef VCMI_NO_THREADED_LOAD
 	//we can properly play intro only in the main thread, so we have to move loading to the separate thread
 	boost::thread loading(init);

+ 40 - 15
client/CPlayerInterface.cpp

@@ -119,7 +119,7 @@ CPlayerInterface::CPlayerInterface(PlayerColor Player)
 	isAutoFightOn = false;
 
 	duringMovement = false;
-	ignoreEvents = false;	
+	ignoreEvents = false;
 }
 
 CPlayerInterface::~CPlayerInterface()
@@ -824,16 +824,16 @@ BattleAction CPlayerInterface::activeStack(const CStack * stack) //called when i
 	//tidy up
 	BattleAction ret = *(b->givenCommand->data);
 	vstd::clear_pointer(b->givenCommand->data);
-	
+
 	if(ret.actionType == Battle::CANCEL)
 	{
 		if(stackId != ret.stackNumber)
 			logGlobal->error("Not current active stack action canceled");
-		logGlobal->traceStream() << "Canceled command for " << stackName;			
+		logGlobal->traceStream() << "Canceled command for " << stackName;
 	}
 	else
 		logGlobal->traceStream() << "Giving command for " << stackName;
-		
+
 	return ret;
 }
 
@@ -1335,7 +1335,7 @@ void CPlayerInterface::moveHero( const CGHeroInstance *h, CGPath path )
 	if(showingDialog->get() || !dialogs.empty())
 		return;
 
-	duringMovement = true;
+	setMovementStatus(true);
 
 	if (adventureInt && adventureInt->isHeroSleeping(h))
 	{
@@ -1347,8 +1347,6 @@ void CPlayerInterface::moveHero( const CGHeroInstance *h, CGPath path )
 	}
 
 	boost::thread moveHeroTask(std::bind(&CPlayerInterface::doMoveHero,this,h,path));
-
-
 }
 
 bool CPlayerInterface::shiftPressed() const
@@ -1559,6 +1557,7 @@ void CPlayerInterface::centerView (int3 pos, int focusTime)
 {
 	EVENT_HANDLER_CALLED_BY_CLIENT;
 	waitWhileDialog();
+	CCS->curh->hide();
 	adventureInt->centerOn (pos);
 	if(focusTime)
 	{
@@ -1569,6 +1568,7 @@ void CPlayerInterface::centerView (int3 pos, int focusTime)
 			SDL_Delay(focusTime);
 		}
 	}
+	CCS->curh->show();
 }
 
 void CPlayerInterface::objectRemoved( const CGObjectInstance *obj )
@@ -1610,11 +1610,11 @@ void CPlayerInterface::update()
 {
 	// Make sure that gamestate won't change when GUI objects may obtain its parts on event processing or drawing request
 	boost::shared_lock<boost::shared_mutex> gsLock(cb->getGsMutex());
-	
-	// While mutexes were locked away we may be have stopped being the active interface	
+
+	// While mutexes were locked away we may be have stopped being the active interface
 	if(LOCPLINT != this)
 		return;
-	
+
 	//if there are any waiting dialogs, show them
 	if((howManyPeople <= 1 || makingTurn) && !dialogs.empty() && !showingDialog->get())
 	{
@@ -2170,7 +2170,7 @@ void CPlayerInterface::showPuzzleMap()
 
 	//TODO: interface should not know the real position of Grail...
 	double ratio = 0;
-	int3 grailPos = cb->getGrailPos(ratio);
+	int3 grailPos = cb->getGrailPos(&ratio);
 
 	GH.pushInt(new CPuzzleWindow(grailPos, ratio));
 }
@@ -2195,7 +2195,7 @@ void CPlayerInterface::advmapSpellCast(const CGHeroInstance * caster, int spellI
 		int level = caster->getSpellSchoolLevel(spell);
 		adventureInt->worldViewOptions.showAllTerrain = (level>2);
 	}
-	
+
 	auto castSoundPath = spell->getCastSound();
 	if (!castSoundPath.empty())
 		CCS->soundh->playSound(castSoundPath);
@@ -2510,9 +2510,20 @@ void CPlayerInterface::stacksRebalanced(const StackLocation &src, const StackLoc
 	garrisonsChanged(objects);
 }
 
+void CPlayerInterface::askToAssembleArtifact(const ArtifactLocation &al)
+{
+	auto hero = dynamic_cast<const CGHeroInstance*>(al.relatedObj());
+	if(hero)
+	{
+		CArtPlace::askToAssemble(hero->getArt(al.slot), al.slot, hero);
+	}
+}
+
 void CPlayerInterface::artifactPut(const ArtifactLocation &al)
 {
 	EVENT_HANDLER_CALLED_BY_CLIENT;
+	adventureInt->infoBar.showSelection();
+	askToAssembleArtifact(al);
 }
 
 void CPlayerInterface::artifactRemoved(const ArtifactLocation &al)
@@ -2537,6 +2548,7 @@ void CPlayerInterface::artifactMoved(const ArtifactLocation &src, const Artifact
 		if(artWin)
 			artWin->artifactMoved(src, dst);
 	}
+	askToAssembleArtifact(dst);
 }
 
 void CPlayerInterface::artifactAssembled(const ArtifactLocation &al)
@@ -2632,6 +2644,19 @@ bool CPlayerInterface::capturedAllEvents()
 	return false;
 }
 
+void CPlayerInterface::setMovementStatus(bool value)
+{
+	duringMovement = value;
+	if(value)
+	{
+		CCS->curh->hide();
+	}
+	else
+	{
+		CCS->curh->show();
+	}
+}
+
 void CPlayerInterface::doMoveHero(const CGHeroInstance * h, CGPath path)
 {
 	int i = 1;
@@ -2776,15 +2801,15 @@ void CPlayerInterface::doMoveHero(const CGHeroInstance * h, CGPath path)
 		adventureInt->updateNextHero(h);
 	}
 
-	duringMovement = false;
+	setMovementStatus(false);
 }
 
 void CPlayerInterface::showWorldViewEx(const std::vector<ObjectPosInfo>& objectPositions)
 {
 	EVENT_HANDLER_CALLED_BY_CLIENT;
 	//TODO: showWorldViewEx
-	
+
 	std::copy(objectPositions.begin(), objectPositions.end(), std::back_inserter(adventureInt->worldViewOptions.iconPositions));
-	
+
 	viewWorldMap();
 }

+ 3 - 1
client/CPlayerInterface.h

@@ -197,7 +197,7 @@ public:
 	void showComp(const Component &comp, std::string message) override; //display component in the advmapint infobox
 	void saveGame(COSer & h, const int version) override; //saving
 	void loadGame(CISer & h, const int version) override; //loading
-	void showWorldViewEx(const std::vector<ObjectPosInfo> & objectPositions) override;	
+	void showWorldViewEx(const std::vector<ObjectPosInfo> & objectPositions) override;
 
 	//for battles
 	void actionFinished(const BattleAction& action) override;//occurs AFTER action taken by active stack or by the hero
@@ -295,6 +295,8 @@ private:
 	bool ignoreEvents;
 
 	void doMoveHero(const CGHeroInstance *h, CGPath path);
+	void setMovementStatus(bool value);
+	void askToAssembleArtifact(const ArtifactLocation &al);
 };
 
 extern CPlayerInterface * LOCPLINT;

+ 8 - 6
client/Client.cpp

@@ -219,18 +219,20 @@ void CClient::endGame( bool closeConnection /*= true*/ )
 	GH.curInt = nullptr;
 	{
 		boost::unique_lock<boost::recursive_mutex> un(*LOCPLINT->pim);
-        logNetwork->infoStream() << "Ending current game!";
+		logNetwork->infoStream() << "Ending current game!";
 		if(GH.topInt())
+		{
 			GH.topInt()->deactivate();
+		}
 		GH.listInt.clear();
 		GH.objsToBlit.clear();
 		GH.statusbar = nullptr;
-        logNetwork->infoStream() << "Removed GUI.";
+		logNetwork->infoStream() << "Removed GUI.";
 
 		vstd::clear_pointer(const_cast<CGameInfo*>(CGI)->mh);
 		vstd::clear_pointer(gs);
 
-        logNetwork->infoStream() << "Deleted mapHandler and gameState.";
+		logNetwork->infoStream() << "Deleted mapHandler and gameState.";
 		LOCPLINT = nullptr;
 	}
 
@@ -238,9 +240,9 @@ void CClient::endGame( bool closeConnection /*= true*/ )
 	battleints.clear();
 	callbacks.clear();
 	battleCallbacks.clear();
-    logNetwork->infoStream() << "Deleted playerInts.";
-
-    logNetwork->infoStream() << "Client stopped.";
+	CGObelisk::reset();
+	logNetwork->infoStream() << "Deleted playerInts.";
+	logNetwork->infoStream() << "Client stopped.";
 }
 
 #if 1

+ 0 - 1
client/VCMI_client.cbp

@@ -127,7 +127,6 @@
 		<Unit filename="NetPacksClient.cpp" />
 		<Unit filename="SDLMain.h" />
 		<Unit filename="StdInc.h">
-			<Option compile="1" />
 			<Option weight="0" />
 		</Unit>
 		<Unit filename="VCMI_client.rc">

+ 15 - 15
client/battle/CBattleInterface.cpp

@@ -1017,7 +1017,7 @@ void CBattleInterface::stackRemoved(int stackID)
 			setActiveStack(nullptr);
 		}
 	}
-	
+
 	delete creAnims[stackID];
 	creAnims.erase(stackID);
 	creDir.erase(stackID);
@@ -1201,18 +1201,18 @@ void CBattleInterface::stackIsCatapulting(const CatapultAttack & ca)
 		for(auto attackInfo : ca.attackedParts)
 		{
 			addNewAnim(new CShootingAnimation(this, stack, attackInfo.destinationTile, nullptr, true, attackInfo.damageDealt));
-		}		
+		}
 	}
 	else
 	{
-		//no attacker stack, assume spell-related (earthquake) - only hit animation 
+		//no attacker stack, assume spell-related (earthquake) - only hit animation
 		for(auto attackInfo : ca.attackedParts)
 		{
 			Point destPos = CClickableHex::getXYUnitAnim(attackInfo.destinationTile, nullptr, this) + Point(99, 120);
-	
+
 			addNewAnim(new CSpellEffectAnimation(this, "SGEXPL.DEF", destPos.x, destPos.y));
 		}
-	}	
+	}
 
 	waitForAnims();
 
@@ -1323,9 +1323,9 @@ void CBattleInterface::spellCast( const BattleSpellCast * sc )
 
 	//displaying message in console
 	std::vector<std::string> logLines;
-	
+
 	spell.prepareBattleLog(curInt->cb.get(), sc, logLines);
-	
+
 	for(auto line : logLines)
 		console->addText(line);
 
@@ -1427,15 +1427,15 @@ void CBattleInterface::displayEffect(ui32 effect, int destTile, bool areaEffect)
 }
 
 void CBattleInterface::displaySpellAnimation(const CSpell::TAnimation & animation, BattleHex destinationTile, bool areaEffect)
-{	
+{
 	if(animation.pause > 0)
 	{
-		addNewAnim(new CDummyAnimation(this, animation.pause));	
+		addNewAnim(new CDummyAnimation(this, animation.pause));
 	}
 	else
 	{
-		addNewAnim(new CSpellEffectAnimation(this, animation.resourceName, destinationTile, false, animation.verticalPosition == VerticalPosition::BOTTOM));	
-	}	
+		addNewAnim(new CSpellEffectAnimation(this, animation.resourceName, destinationTile, false, animation.verticalPosition == VerticalPosition::BOTTOM));
+	}
 }
 
 void CBattleInterface::displaySpellCast(SpellID spellID, BattleHex destinationTile, bool areaEffect)
@@ -1444,11 +1444,11 @@ void CBattleInterface::displaySpellCast(SpellID spellID, BattleHex destinationTi
 
 	if(spell == nullptr)
 		return;
-		
+
 	for(const CSpell::TAnimation & animation : spell->animationInfo.cast)
 	{
 		displaySpellAnimation(animation, destinationTile, areaEffect);
-	}	
+	}
 }
 
 void CBattleInterface::displaySpellEffect(SpellID spellID, BattleHex destinationTile, bool areaEffect)
@@ -1933,7 +1933,7 @@ void CBattleInterface::bTacticNextStack(const CStack *current /*= nullptr*/)
 	waitForAnims();
 
 	TStacks stacksOfMine = tacticianInterface->cb->battleGetStacks(CBattleCallback::ONLY_MINE);
-	stacksOfMine.erase(std::remove_if(stacksOfMine.begin(), stacksOfMine.end(), &immobile), stacksOfMine.end());
+	vstd::erase_if(stacksOfMine, &immobile);
 	auto it = vstd::find(stacksOfMine, current);
 	if(it != stacksOfMine.end() && ++it != stacksOfMine.end())
 		stackActivated(*it);
@@ -2072,7 +2072,7 @@ void CBattleInterface::handleHex(BattleHex myNumber, int eventType)
 			case ANY_CREATURE:
 				if (shere && shere->alive() && isCastingPossibleHere (sactive, shere, myNumber))
 					legalAction = true;
-				break;				
+				break;
 			case HOSTILE_CREATURE_SPELL:
 				if (shere && shere->alive() && !ourStack && isCastingPossibleHere (sactive, shere, myNumber))
 					legalAction = true;

+ 61 - 81
client/widgets/CArtifactHolder.cpp

@@ -32,7 +32,7 @@
  */
 
 CArtPlace::CArtPlace(Point position, const CArtifactInstance * Art):
-    locked(false), picked(false), marked(false), ourArt(Art)
+	locked(false), picked(false), marked(false), ourArt(Art)
 {
 	pos += position;
 	pos.w = pos.h = 44;
@@ -180,7 +180,7 @@ void CArtPlace::clickLeft(tribool down, bool previousState)
 						if(srcInBackpack && srcInSameHero)
 						{
 							if(!ourArt								//cannot move from backpack to AFTER backpack -> combined with vstd::amin above it will guarantee that dest is at most the last artifact
-							  || ourOwner->commonInfo->src.slotID < ourOwner->commonInfo->dst.slotID) //rearranging arts in backpack after taking src artifact, the dest id will be shifted
+								|| ourOwner->commonInfo->src.slotID < ourOwner->commonInfo->dst.slotID) //rearranging arts in backpack after taking src artifact, the dest id will be shifted
 								vstd::advance(ourOwner->commonInfo->dst.slotID, -1);
 						}
 						if(srcInSameHero && ourOwner->commonInfo->dst.slotID == ourOwner->commonInfo->src.slotID) //we came to src == dst
@@ -214,6 +214,32 @@ void CArtPlace::clickLeft(tribool down, bool previousState)
 	}
 }
 
+bool CArtPlace::askToAssemble(const CArtifactInstance *art, ArtifactPosition slot,
+                              const CGHeroInstance *hero)
+{
+	std::vector<const CArtifact *> assemblyPossibilities = art->assemblyPossibilities(hero);
+
+	// If the artifact can be assembled, display dialog.
+	for(const CArtifact *combination : assemblyPossibilities)
+	{
+		LOCPLINT->showArtifactAssemblyDialog(
+			art->artType->id,
+			combination->id,
+			true,
+			std::bind(&CCallback::assembleArtifacts, LOCPLINT->cb.get(), hero, slot, true, combination->id),
+			0);
+
+		if(assemblyPossibilities.size() > 2)
+		{
+			logGlobal->warnStream() << boost::format(
+				"More than one possibility of assembling on %s... taking only first")
+				% art->artType->Name();
+		}
+		return true;
+	}
+	return false;
+}
+
 void CArtPlace::clickRight(tribool down, bool previousState)
 {
 	if(down && ourArt && !locked && text.size() && !picked)  //if there is no description or it's a lock, do nothing ;]
@@ -225,20 +251,8 @@ void CArtPlace::clickRight(tribool down, bool previousState)
 				std::vector<const CArtifact *> assemblyPossibilities = ourArt->assemblyPossibilities(ourOwner->curHero);
 
 				// If the artifact can be assembled, display dialog.
-				for(const CArtifact *combination : assemblyPossibilities)
+				if (askToAssemble(ourArt, slotID, ourOwner->curHero))
 				{
-					LOCPLINT->showArtifactAssemblyDialog(
-						ourArt->artType->id,
-						combination->id,
-						true,
-						std::bind(&CCallback::assembleArtifacts, LOCPLINT->cb.get(), ourOwner->curHero, slotID, true, combination->id),
-						0);
-
-					if(assemblyPossibilities.size() > 2)
-					{
-                        logGlobal->warnStream() << "More than one possibility of assembling... taking only first";
-						break;
-					}
 					return;
 				}
 
@@ -303,10 +317,10 @@ void CArtPlace::deselect ()
 		for(int i = 0; i < GameConstants::BACKPACK_START; i++)
 		{
 			auto place = ourOwner->getArtPlace(i);
-			
+
 			if(nullptr != place)//getArtPlace may return null
 				place->pickSlot(false);
-		}			
+		}
 	}
 
 	CCS->curh->dragAndDropCursor(nullptr);
@@ -372,70 +386,36 @@ void CArtPlace::setArtifact(const CArtifactInstance *art)
 		image->disable();
 		text = std::string();
 		hoverText = CGI->generaltexth->allTexts[507];
+		return;
 	}
-	else
-	{
-		image->enable();
-		image->setFrame(locked ? ArtifactID::ART_LOCK : art->artType->iconIndex);
 
-		std::string artDesc = ourArt->artType->Description();
-		if (vstd::contains (artDesc, '{'))
-			text = artDesc;
-		else
-			text = '{' + ourArt->artType->Name() + "}\n\n" + artDesc; //workaround for new artifacts with single name, turns it to H3-style
+	image->enable();
+	image->setFrame(locked ? ArtifactID::ART_LOCK : art->artType->iconIndex);
 
-		if(art->artType->id == ArtifactID::SPELL_SCROLL)
-		{
-			// we expect scroll description to be like this: This scroll contains the [spell name] spell which is added into your spell book for as long as you carry the scroll.
-			// so we want to replace text in [...] with a spell name
-			// however other language versions don't have name placeholder at all, so we have to be careful
-			int spellID = art->getGivenSpellID();
-			size_t nameStart = text.find_first_of('[');
-			size_t nameEnd = text.find_first_of(']', nameStart);
-			if(spellID >= 0)
-			{
-				if(nameStart != std::string::npos  &&  nameEnd != std::string::npos)
-					text = text.replace(nameStart, nameEnd - nameStart + 1, CGI->spellh->objects[spellID]->name);
+	text = art->getEffectiveDescription(ourOwner->curHero);
 
-				//add spell component info (used to provide a pic in r-click popup)
-				baseType = CComponent::spell;
-				type = spellID;
-				bonusValue = 0;
-			}
-		}
-		else
+	if(art->artType->id == ArtifactID::SPELL_SCROLL)
+	{
+		int spellID = art->getGivenSpellID();
+		if(spellID >= 0)
 		{
-			baseType = CComponent::artifact;
-			type = art->artType->id;
+			//add spell component info (used to provide a pic in r-click popup)
+			baseType = CComponent::spell;
+			type = spellID;
 			bonusValue = 0;
 		}
-		if (art->artType->constituents) //display info about components of combined artifact
-		{
-			//TODO
-		}
-		else if (art->artType->constituentOf.size()) //display info about set
-		{
-			std::string artList;
-			auto combinedArt = art->artType->constituentOf[0];
-			text += "\n\n";
-			text += "{" + combinedArt->Name() + "}";
-			int wornArtifacts = 0;
-			for (auto a : *combinedArt->constituents) //TODO: can the artifact be a part of more than one set?
-			{
-				artList += "\n" + a->Name();
-				if (ourOwner->curHero->hasArt(a->id, true))
-					wornArtifacts++;
-			}
-			text += " (" + boost::str(boost::format("%d") % wornArtifacts) +  " / " +
-				boost::str(boost::format("%d") % combinedArt->constituents->size()) + ")" + artList;
-			//TODO: fancy colors and fonts for this text
-		}
-
-		if (locked) // Locks should appear as empty.
-			hoverText = CGI->generaltexth->allTexts[507];
-		else
-			hoverText = boost::str(boost::format(CGI->generaltexth->heroscrn[1]) % ourArt->artType->Name());
 	}
+	else
+	{
+		baseType = CComponent::artifact;
+		type = art->artType->id;
+		bonusValue = 0;
+	}
+
+	if (locked) // Locks should appear as empty.
+		hoverText = CGI->generaltexth->allTexts[507];
+	else
+		hoverText = boost::str(boost::format(CGI->generaltexth->heroscrn[1]) % ourArt->artType->Name());
 }
 
 void CArtifactsOfHero::SCommonPart::reset()
@@ -454,12 +434,12 @@ void CArtifactsOfHero::setHero(const CGHeroInstance * hero)
 		backpackPos = 0;
 
 	// Fill the slots for worn artifacts and backpack.
-	
+
 	for(auto p : artWorn)
 	{
 		setSlotData(p.second, p.first);
 	}
-	
+
 	scrollBackpack(0);
 }
 
@@ -587,8 +567,8 @@ void CArtifactsOfHero::setSlotData(CArtPlace* artPlace, ArtifactPosition slotID)
 
 	if(const ArtSlotInfo *asi = curHero->getSlot(slotID))
 	{
-		artPlace->setArtifact(asi->artifact);
 		artPlace->lockSlot(asi->locked);
+		artPlace->setArtifact(asi->artifact);
 	}
 	else
 		artPlace->setArtifact(nullptr);
@@ -797,7 +777,7 @@ void CArtifactsOfHero::artifactMoved(const ArtifactLocation &src, const Artifact
 	}
 	else if(src.slot >= GameConstants::BACKPACK_START &&
 	        src.slot <  commonInfo->src.slotID &&
-			src.isHolder(commonInfo->src.AOH->curHero)) //artifact taken from before currently picked one
+			    src.isHolder(commonInfo->src.AOH->curHero)) //artifact taken from before currently picked one
 	{
 		//int fixedSlot = src.hero->getArtPos(commonInfo->src.art);
 		vstd::advance(commonInfo->src.slotID, -1);
@@ -811,14 +791,14 @@ void CArtifactsOfHero::artifactMoved(const ArtifactLocation &src, const Artifact
 	}
 
 	updateParentWindow();
- 	int shift = 0;
+	int shift = 0;
 // 	if(dst.slot >= Arts::BACKPACK_START && dst.slot - Arts::BACKPACK_START < backpackPos)
 // 		shift++;
 //
- 	if(src.slot < GameConstants::BACKPACK_START  &&  dst.slot - GameConstants::BACKPACK_START < backpackPos)
+	if(src.slot < GameConstants::BACKPACK_START  &&  dst.slot - GameConstants::BACKPACK_START < backpackPos)
 		shift++;
 	if(dst.slot < GameConstants::BACKPACK_START  &&  src.slot - GameConstants::BACKPACK_START < backpackPos)
- 		shift--;
+		shift--;
 
 	if( (isCurHeroSrc && src.slot >= GameConstants::BACKPACK_START)
 	 || (isCurHeroDst && dst.slot >= GameConstants::BACKPACK_START) )
@@ -853,7 +833,7 @@ CArtPlace * CArtifactsOfHero::getArtPlace(int slot)
 		for(CArtPlace *ap : backpack)
 			if(ap->slotID == slot)
 				return ap;
-		return nullptr;				
+		return nullptr;
 	}
 }
 

+ 4 - 2
client/widgets/CArtifactHolder.h

@@ -74,15 +74,17 @@ public:
 
 	void setMeAsDest(bool backpackAsVoid = true);
 	void setArtifact(const CArtifactInstance *art);
+	static bool askToAssemble(const CArtifactInstance *art, ArtifactPosition slot,
+	                          const CGHeroInstance *hero);
 };
 
 /// Contains artifacts of hero. Distincts which artifacts are worn or backpacked
 class CArtifactsOfHero : public CIntObject
 {
 	const CGHeroInstance * curHero;
-	
+
 	std::map<ArtifactPosition, CArtPlace *> artWorn;
- 
+
 	std::vector<CArtPlace *> backpack; //hero's visible backpack (only 5 elements!)
 	int backpackPos; //number of first art visible in backpack (in hero's vector)
 

+ 30 - 5
client/widgets/CComponent.cpp

@@ -7,6 +7,7 @@
 #include "../CMessage.h"
 #include "../CGameInfo.h"
 #include "../widgets/Images.h"
+#include "../widgets/CArtifactHolder.h"
 #include "../windows/CAdvmapInterface.h"
 
 #include "../../lib/CArtHandler.h"
@@ -144,14 +145,26 @@ size_t CComponent::getIndex()
 
 std::string CComponent::getDescription()
 {
-	switch (compType)
+	switch(compType)
 	{
 	case primskill:  return (subtype < 4)? CGI->generaltexth->arraytxt[2+subtype] //Primary skill
 										 : CGI->generaltexth->allTexts[149]; //mana
 	case secskill:   return CGI->generaltexth->skillInfoTexts[subtype][val-1];
 	case resource:   return CGI->generaltexth->allTexts[242];
 	case creature:   return "";
-	case artifact:   return CGI->arth->artifacts[subtype]->Description();
+	case artifact:
+	{
+		std::unique_ptr<CArtifactInstance> art;
+		if (subtype != ArtifactID::SPELL_SCROLL)
+		{
+			art.reset(CArtifactInstance::createNewArtifactInstance(subtype));
+		}
+		else
+		{
+			art.reset(CArtifactInstance::createScroll(static_cast<SpellID>(val)));
+		}
+		return art->getEffectiveDescription();
+	}
 	case experience: return CGI->generaltexth->allTexts[241];
 	case spell:      return CGI->spellh->objects[subtype]->getLevelInfo(val).description;
 	case morale:     return CGI->generaltexth->heroscrn[ 4 - (val>0) + (val<0)];
@@ -166,7 +179,7 @@ std::string CComponent::getDescription()
 
 std::string CComponent::getSubtitle()
 {
-	if (!perDay)
+	if(!perDay)
 		return getSubtitleInternal();
 
 	std::string ret = CGI->generaltexth->allTexts[3];
@@ -186,19 +199,31 @@ std::string CComponent::getSubtitleInternal()
 	case artifact:   return CGI->arth->artifacts[subtype]->Name();
 	case experience:
 		{
-			if (subtype == 1) //+1 level - tree of knowledge
+			if(subtype == 1) //+1 level - tree of knowledge
 			{
 				std::string level = CGI->generaltexth->allTexts[442];
 				boost::replace_first(level, "1", boost::lexical_cast<std::string>(val));
 				return level;
 			}
 			else
+			{
 				return boost::lexical_cast<std::string>(val); //amount of experience OR level required for seer hut;
+			}
 		}
 	case spell:      return CGI->spellh->objects[subtype]->name;
 	case morale:     return "";
 	case luck:       return "";
-	case building:   return CGI->townh->factions[subtype]->town->buildings[BuildingID(val)]->Name();
+	case building:
+		{
+			auto building = CGI->townh->factions[subtype]->town->buildings[BuildingID(val)];
+			if(!building)
+			{
+				logGlobal->errorStream() << boost::format("Town of faction %s has no building #%d")
+					% CGI->townh->factions[subtype]->town->faction->name % val;
+				return (boost::format("Missing building #%d") % val).str();
+			}
+			return building->Name();
+		}
 	case hero:       return "";
 	case flag:       return CGI->generaltexth->capColors[subtype];
 	}

+ 2 - 2
client/widgets/CGarrisonInt.cpp

@@ -73,11 +73,11 @@ void CGarrisonSlot::hover (bool on)
 			{
 				if(upg == EGarrisonType::UP)
 				{
-					temp = CGI->generaltexth->tcommands[32]; //Select %s (visiting)
+					temp = CGI->generaltexth->tcommands[12]; //Select %s (in garrison)
 				}
 				else if(owner->armedObjs[0] && (owner->armedObjs[0]->ID == Obj::TOWN || owner->armedObjs[0]->ID == Obj::HERO))
 				{
-					temp = CGI->generaltexth->tcommands[12]; //Select %s (in garrison)
+					temp = CGI->generaltexth->tcommands[32]; //Select %s (visiting)
 				}
 				else
 				{

+ 22 - 17
client/windows/CAdvmapInterface.cpp

@@ -758,14 +758,15 @@ void CAdvMapInt::updateSleepWake(const CGHeroInstance *h)
 
 void CAdvMapInt::updateMoveHero(const CGHeroInstance *h, tribool hasPath)
 {
-	//default value is for everywhere but CPlayerInterface::moveHero, because paths are not updated from there immediately
-	if (hasPath == boost::indeterminate)
-		 hasPath = LOCPLINT->paths[h].nodes.size() ? true : false;
-	if (!h)
+	if(!h)
 	{
 		moveHero->block(true);
 		return;
 	}
+	//default value is for everywhere but CPlayerInterface::moveHero, because paths are not updated from there immediately
+	if(boost::logic::indeterminate(hasPath))
+		hasPath = LOCPLINT->paths[h].nodes.size() ? true : false;
+
 	moveHero->block(!hasPath || (h->movement == 0));
 }
 
@@ -1420,13 +1421,13 @@ void CAdvMapInt::tileLClicked(const int3 &mapPos)
 	bool canSelect = topBlocking && topBlocking->ID == Obj::HERO && topBlocking->tempOwner == LOCPLINT->playerID;
 	canSelect |= topBlocking && topBlocking->ID == Obj::TOWN && LOCPLINT->cb->getPlayerRelations(LOCPLINT->playerID, topBlocking->tempOwner);
 
-	if (selection->ID != Obj::HERO) //hero is not selected (presumably town)
+	if(selection->ID != Obj::HERO) //hero is not selected (presumably town)
 	{
 		assert(!terrain.currentPath); //path can be active only when hero is selected
 		if(selection == topBlocking) //selected town clicked
 			LOCPLINT->openTownWindow(static_cast<const CGTownInstance*>(topBlocking));
-		else if ( canSelect )
-				select(static_cast<const CArmedInstance*>(topBlocking), false);
+		else if(canSelect)
+			select(static_cast<const CArmedInstance*>(topBlocking), false);
 		return;
 	}
 	else if(const CGHeroInstance * currentHero = curHero()) //hero is selected
@@ -1444,22 +1445,26 @@ void CAdvMapInt::tileLClicked(const int3 &mapPos)
 		}
 		else //still here? we need to move hero if we clicked end of already selected path or calculate a new path otherwise
 		{
-			if (terrain.currentPath  &&  terrain.currentPath->endPos() == mapPos)//we'll be moving
+			if(terrain.currentPath && terrain.currentPath->endPos() == mapPos)//we'll be moving
 			{
-				if (CGI->mh->canStartHeroMovement())
-					LOCPLINT->moveHero(currentHero,*terrain.currentPath);
+				if(CGI->mh->canStartHeroMovement())
+					LOCPLINT->moveHero(currentHero, *terrain.currentPath);
 				return;
 			}
-			else/* if(mp.z == currentHero->pos.z)*/ //remove old path and find a new one if we clicked on the map level on which hero is present
+			else //remove old path and find a new one if we clicked on accessible tile
 			{
 				CGPath &path = LOCPLINT->paths[currentHero];
-				terrain.currentPath = &path;
-				bool gotPath = LOCPLINT->cb->getPathsInfo(currentHero)->getPath(path, mapPos); //try getting path, erase if failed
-				updateMoveHero(currentHero);
-				if (!gotPath)
-					LOCPLINT->eraseCurrentPathOf(currentHero);
+				CGPath newpath;
+				bool gotPath = LOCPLINT->cb->getPathsInfo(currentHero)->getPath(newpath, mapPos); //try getting path, erase if failed
+				if(gotPath && newpath.nodes.size())
+					path = newpath;
+
+				if(path.nodes.size())
+					terrain.currentPath = &path;
 				else
-					return;
+					LOCPLINT->eraseCurrentPathOf(currentHero);
+
+				updateMoveHero(currentHero);
 			}
 		}
 	} //end of hero is selected "case"

+ 8 - 4
client/windows/CHeroWindow.cpp

@@ -28,6 +28,7 @@
 #include "../lib/CHeroHandler.h"
 #include "../lib/mapObjects/CGHeroInstance.h"
 #include "../lib/NetPacksBase.h"
+#include "../mapHandler.h"
 
 /*
  * CHeroWindow.cpp, part of VCMI engine
@@ -275,6 +276,9 @@ void CHeroWindow::update(const CGHeroInstance * hero, bool redrawNeeded /*= fals
 	if(!LOCPLINT->cb->howManyTowns() && LOCPLINT->cb->howManyHeroes() == 1)
 		noDismiss = true;
 
+	if(curHero->isMissionCritical())
+		noDismiss = true;
+
 	dismissButton->block(!!curHero->visitedTown || noDismiss);
 
 	if(curHero->getSecSkillLevel(SecondarySkill::TACTICS) == 0)
@@ -343,10 +347,10 @@ void CHeroWindow::commanderWindow()
 void CHeroWindow::showAll(SDL_Surface * to)
 {
 	CIntObject::showAll(to);
-	 
+
 	//printing hero's name
 	printAtMiddleLoc(curHero->name, 190, 38, FONT_BIG, Colors::YELLOW, to);
-	 
+
 	//printing hero's level
 	std::string secondLine= CGI->generaltexth->allTexts[342];
 	boost::algorithm::replace_first(secondLine,"%d",boost::lexical_cast<std::string>(curHero->level));
@@ -360,14 +364,14 @@ void CHeroWindow::showAll(SDL_Surface * to)
 	 	primarySkill << primSkillAreas[m]->bonusValue;
 	 	printAtMiddleLoc(primarySkill.str(), 53 + 70 * m, 166, FONT_SMALL, Colors::WHITE, to);
 	}
-	 
+
 	//secondary skills
 	for(size_t v=0; v<std::min(secSkillAreas.size(), curHero->secSkills.size()); ++v)
 	{
 	 	printAtLoc(CGI->generaltexth->levels[curHero->secSkills[v].second-1], (v%2) ? 212 : 68, 280 + 48 * (v/2), FONT_SMALL, Colors::WHITE, to);
 	 	printAtLoc(CGI->generaltexth->skillName[curHero->secSkills[v].first], (v%2) ? 212 : 68, 300 + 48 * (v/2), FONT_SMALL, Colors::WHITE, to);
 	}
-	 
+
 	//printing special ability
 	printAtLoc(curHero->type->specName, 69, 205, FONT_SMALL, Colors::WHITE, to);
 	std::ostringstream expstr;

+ 5 - 5
client/windows/CTradeWindow.cpp

@@ -269,7 +269,7 @@ void CTradeWindow::CTradeableItem::clickRight(tribool down, bool previousState)
 		case ARTIFACT_TYPE:
 		case ARTIFACT_PLACEHOLDER:
 			if(id >= 0)
-				adventureInt->handleRightClick(CGI->arth->artifacts[id]->Description(), down);
+				adventureInt->handleRightClick(hlp->getEffectiveDescription(), down);
 			break;
 		}
 	}
@@ -500,14 +500,14 @@ void CTradeWindow::getPositionsFor(std::vector<Rect> &poss, bool Left, EType typ
 		int h, w, x, y, dx, dy;
 		int leftToRightOffset;
 		getBaseForPositions(type, dx, dy, x, y, h, w, !Left, leftToRightOffset);
-		
-		const std::vector<Rect> tmp = 
+
+		const std::vector<Rect> tmp =
 		{
 			genRect(h, w, x, y), genRect(h, w, x + dx, y), genRect(h, w, x + 2*dx, y),
 			genRect(h, w, x, y + dy), genRect(h, w, x + dx, y + dy), genRect(h, w, x + 2*dx, y + dy),
-			genRect(h, w, x + dx, y + 2*dy)			
+			genRect(h, w, x + dx, y + 2*dy)
 		};
-		
+
 		vstd::concatenate(poss, tmp);
 
 		if(!Left)

+ 2 - 1
config/defaultMods.json

@@ -23,7 +23,8 @@
 		"ALL_CREATURES_GET_DOUBLE_MONTHS" : false,
 		"NEGATIVE_LUCK" : false,
 		"MAX_HEROES_AVAILABLE_PER_PLAYER" : 16,
-		"MAX_HEROES_ON_MAP_PER_PLAYER" : 8	
+		"MAX_HEROES_ON_MAP_PER_PLAYER" : 8,
+		"WINNING_HERO_WITH_NO_TROOPS_RETREATS": true
 
 	},
 	"modules":

+ 88 - 8
lib/CArtHandler.cpp

@@ -280,16 +280,16 @@ ArtifactPosition CArtHandler::stringToSlot(std::string slotName)
 
 void CArtHandler::addSlot(CArtifact * art, const std::string & slotID)
 {
-	static const std::vector<ArtifactPosition> miscSlots = 
+	static const std::vector<ArtifactPosition> miscSlots =
 	{
 		ArtifactPosition::MISC1, ArtifactPosition::MISC2, ArtifactPosition::MISC3, ArtifactPosition::MISC4, ArtifactPosition::MISC5
 	};
-	
+
 	static const std::vector<ArtifactPosition> ringSlots =
 	{
 		ArtifactPosition::LEFT_RING, ArtifactPosition::RIGHT_RING
 	};
-	
+
 	if (slotID == "MISC")
 	{
 		vstd::concatenate(art->possibleSlots[ArtBearer::HERO], miscSlots);
@@ -323,7 +323,7 @@ void CArtHandler::loadSlots(CArtifact * art, const JsonNode & node)
 CArtifact::EartClass CArtHandler::stringToClass(std::string className)
 {
 	static const std::map<std::string, CArtifact::EartClass> artifactClassMap =
-	{	
+	{
 		{"TREASURE", CArtifact::ART_TREASURE},
 		{"MINOR", CArtifact::ART_MINOR},
 		{"MAJOR", CArtifact::ART_MAJOR},
@@ -738,9 +738,14 @@ std::string CArtifactInstance::nodeName() const
 }
 
 CArtifactInstance * CArtifactInstance::createScroll( const CSpell *s)
+{
+	return createScroll(s->id);
+}
+
+CArtifactInstance *CArtifactInstance::createScroll(SpellID sid)
 {
 	auto ret = new CArtifactInstance(VLC->arth->artifacts[ArtifactID::SPELL_SCROLL]);
-	auto b = new Bonus(Bonus::PERMANENT, Bonus::SPELL, Bonus::ARTIFACT_INSTANCE, -1, ArtifactID::SPELL_SCROLL, s->id);
+	auto b = new Bonus(Bonus::PERMANENT, Bonus::SPELL, Bonus::ARTIFACT_INSTANCE, -1, ArtifactID::SPELL_SCROLL, sid);
 	ret->addNewBonus(b);
 	return ret;
 }
@@ -752,6 +757,48 @@ void CArtifactInstance::init()
 	setNodeType(ARTIFACT_INSTANCE);
 }
 
+std::string CArtifactInstance::getEffectiveDescription(
+	const CGHeroInstance *hero) const
+{
+	std::string text = artType->Description();
+	if (!vstd::contains(text, '{'))
+		text = '{' + artType->Name() + "}\n\n" + text; //workaround for new artifacts with single name, turns it to H3-style
+
+	if(artType->id == ArtifactID::SPELL_SCROLL)
+	{
+		// we expect scroll description to be like this: This scroll contains the [spell name] spell which is added into your spell book for as long as you carry the scroll.
+		// so we want to replace text in [...] with a spell name
+		// however other language versions don't have name placeholder at all, so we have to be careful
+		int spellID = getGivenSpellID();
+		size_t nameStart = text.find_first_of('[');
+		size_t nameEnd = text.find_first_of(']', nameStart);
+		if(spellID >= 0)
+		{
+			if(nameStart != std::string::npos  &&  nameEnd != std::string::npos)
+				text = text.replace(nameStart, nameEnd - nameStart + 1, VLC->spellh->objects[spellID]->name);
+		}
+	}
+	else if (hero && artType->constituentOf.size()) //display info about set
+	{
+		std::string artList;
+		auto combinedArt = artType->constituentOf[0];
+		text += "\n\n";
+		text += "{" + combinedArt->Name() + "}";
+		int wornArtifacts = 0;
+		for (auto a : *combinedArt->constituents) //TODO: can the artifact be a part of more than one set?
+		{
+			artList += "\n" + a->Name();
+			if (hero->hasArt(a->id, true))
+				wornArtifacts++;
+		}
+		text += " (" + boost::str(boost::format("%d") % wornArtifacts) +  " / " +
+			boost::str(boost::format("%d") % combinedArt->constituents->size()) + ")" + artList;
+		//TODO: fancy colors and fonts for this text
+	}
+
+	return text;
+}
+
 ArtifactPosition CArtifactInstance::firstAvailableSlot(const CArtifactSet *h) const
 {
 	for(auto slot : artType->possibleSlots.at(h->bearerType()))
@@ -900,7 +947,7 @@ SpellID CArtifactInstance::getGivenSpellID() const
 	const Bonus * b = getBonusLocalFirst(Selector::type(Bonus::SPELL));
 	if(!b)
 	{
-        logGlobal->warnStream() << "Warning: " << nodeName() << " doesn't bear any spell!";
+		logGlobal->warnStream() << "Warning: " << nodeName() << " doesn't bear any spell!";
 		return SpellID::NONE;
 	}
 	return SpellID(b->subtype);
@@ -1152,9 +1199,42 @@ const CArtifactInstance * CArtifactSet::getArtByInstanceId( ArtifactInstanceID a
 	return nullptr;
 }
 
-bool CArtifactSet::hasArt(ui32 aid, bool onlyWorn /*= false*/) const
+bool CArtifactSet::hasArt(ui32 aid, bool onlyWorn /*= false*/,
+                          bool searchBackpackAssemblies /*= false*/) const
+{
+	return getArtPos(aid, onlyWorn) != ArtifactPosition::PRE_FIRST ||
+	       (searchBackpackAssemblies && getHiddenArt(aid));
+}
+
+std::pair<const CCombinedArtifactInstance *, const CArtifactInstance *>
+CArtifactSet::searchForConstituent(int aid) const
+{
+	for(auto & slot : artifactsInBackpack)
+	{
+		auto art = slot.artifact;
+		if(art->canBeDisassembled())
+		{
+			auto ass = static_cast<CCombinedArtifactInstance *>(art.get());
+			for(auto& ci : ass->constituentsInfo)
+			{
+				if(ci.art->artType->id == aid)
+				{
+					return {ass, ci.art};
+				}
+			}
+		}
+	}
+	return {nullptr, nullptr};
+}
+
+const CArtifactInstance *CArtifactSet::getHiddenArt(int aid) const
+{
+	return searchForConstituent(aid).second;
+}
+
+const CCombinedArtifactInstance *CArtifactSet::getAssemblyByConstituent(int aid) const
 {
-	return getArtPos(aid, onlyWorn) != ArtifactPosition::PRE_FIRST;
+	return searchForConstituent(aid).first;
 }
 
 const ArtSlotInfo * CArtifactSet::getSlot(ArtifactPosition pos) const

+ 19 - 8
lib/CArtHandler.h

@@ -119,6 +119,7 @@ public:
 	void deserializationFix();
 	void setType(CArtifact *Art);
 
+	std::string getEffectiveDescription(const CGHeroInstance *hero = nullptr) const;
 	ArtifactPosition firstAvailableSlot(const CArtifactSet *h) const;
 	ArtifactPosition firstBackpackSlot(const CArtifactSet *h) const;
 	SpellID getGivenSpellID() const; //to be used with scrolls (and similar arts), -1 if none
@@ -128,7 +129,9 @@ public:
 	virtual bool canBeDisassembled() const;
 	virtual void putAt(ArtifactLocation al);
 	virtual void removeFrom(ArtifactLocation al);
-	virtual bool isPart(const CArtifactInstance *supposedPart) const; //checks if this a part of this artifact: artifact instance is a part of itself, additionally truth is returned for consituents of combined arts
+	/// Checks if this a part of this artifact: artifact instance is a part
+	/// of itself, additionally truth is returned for constituents of combined arts
+	virtual bool isPart(const CArtifactInstance *supposedPart) const;
 
 	std::vector<const CArtifact *> assemblyPossibilities(const CArtifactSet *h) const;
 	void move(ArtifactLocation src, ArtifactLocation dst);
@@ -141,6 +144,7 @@ public:
 	}
 
 	static CArtifactInstance *createScroll(const CSpell *s);
+	static CArtifactInstance *createScroll(SpellID sid);
 	static CArtifactInstance *createNewArtifactInstance(CArtifact *Art);
 	static CArtifactInstance *createNewArtifactInstance(int aid);
 };
@@ -172,7 +176,7 @@ public:
 
 	void createConstituents();
 	void addAsConstituent(CArtifactInstance *art, ArtifactPosition slot);
-	CArtifactInstance *figureMainConstituent(const ArtifactLocation al); //main constituent is replcaed with us (combined art), not lock
+	CArtifactInstance *figureMainConstituent(const ArtifactLocation al); //main constituent is replaced with us (combined art), not lock
 
 	CCombinedArtifactInstance();
 
@@ -265,10 +269,8 @@ struct DLL_LINKAGE ArtSlotInfo
 	ConstTransitivePtr<CArtifactInstance> artifact;
 	ui8 locked; //if locked, then artifact points to the combined artifact
 
-	ArtSlotInfo()
-	{
-		locked = false;
-	}
+	ArtSlotInfo() : locked(false) {}
+
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
 		h & artifact & locked;
@@ -288,10 +290,16 @@ public:
 	const ArtSlotInfo *getSlot(ArtifactPosition pos) const;
 	const CArtifactInstance* getArt(ArtifactPosition pos, bool excludeLocked = true) const; //nullptr - no artifact
 	CArtifactInstance* getArt(ArtifactPosition pos, bool excludeLocked = true); //nullptr - no artifact
-	ArtifactPosition getArtPos(int aid, bool onlyWorn = true) const; //looks for equipped artifact with given ID and returns its slot ID or -1 if none(if more than one such artifact lower ID is returned)
+	/// Looks for equipped artifact with given ID and returns its slot ID or -1 if none
+	/// (if more than one such artifact lower ID is returned)
+	ArtifactPosition getArtPos(int aid, bool onlyWorn = true) const;
 	ArtifactPosition getArtPos(const CArtifactInstance *art) const;
 	const CArtifactInstance *getArtByInstanceId(ArtifactInstanceID artInstId) const;
-	bool hasArt(ui32 aid, bool onlyWorn = false) const; //checks if hero possess artifact of given id (either in backack or worn)
+	/// Search for constituents of assemblies in backpack which do not have an ArtifactPosition
+	const CArtifactInstance *getHiddenArt(int aid) const;
+	const CCombinedArtifactInstance *getAssemblyByConstituent(int aid) const;
+	/// Checks if hero possess artifact of given id (either in backack or worn)
+	bool hasArt(ui32 aid, bool onlyWorn = false, bool searchBackpackAssemblies = false) const;
 	bool isPositionFree(ArtifactPosition pos, bool onlyLockCheck = false) const;
 	si32 getArtTypeId(ArtifactPosition pos) const;
 
@@ -304,4 +312,7 @@ public:
 	}
 
 	void artDeserializationFix(CBonusSystemNode *node);
+
+protected:
+	std::pair<const CCombinedArtifactInstance *, const CArtifactInstance *> searchForConstituent(int aid) const;
 };

+ 42 - 38
lib/CGameInfoCallback.cpp

@@ -66,6 +66,10 @@ const PlayerState * CGameInfoCallback::getPlayer(PlayerColor color, bool verbose
 {
 	//funtion written from scratch since it's accessed A LOT by AI
 
+	if(!color.isValidPlayer())
+	{
+		return nullptr;
+	}
 	auto player = gs->players.find(color);
 	if (player != gs->players.end())
 	{
@@ -229,13 +233,13 @@ bool CGameInfoCallback::getTownInfo(const CGObjectInstance * town, InfoAboutTown
 	{
 		if(!detailed && nullptr != selectedObject)
 		{
-			const CGHeroInstance * selectedHero = dynamic_cast<const CGHeroInstance *>(selectedObject);		
+			const CGHeroInstance * selectedHero = dynamic_cast<const CGHeroInstance *>(selectedObject);
 			if(nullptr != selectedHero)
-				detailed = selectedHero->hasVisions(town, 1);			
+				detailed = selectedHero->hasVisions(town, 1);
 		}
-		
+
 		dest.initFromTown(static_cast<const CGTownInstance *>(town), detailed);
-	}		
+	}
 	else if(town->ID == Obj::GARRISON || town->ID == Obj::GARRISON2)
 		dest.initFromArmy(static_cast<const CArmedInstance *>(town), detailed);
 	else
@@ -268,28 +272,28 @@ bool CGameInfoCallback::getHeroInfo(const CGObjectInstance * hero, InfoAboutHero
 	ERROR_RET_VAL_IF(!isVisible(h->getPosition(false)), "That hero is not visible!", false);
 
 	bool accessFlag = hasAccess(h->tempOwner);
-	
+
 	if(!accessFlag && nullptr != selectedObject)
 	{
-		const CGHeroInstance * selectedHero = dynamic_cast<const CGHeroInstance *>(selectedObject);		
+		const CGHeroInstance * selectedHero = dynamic_cast<const CGHeroInstance *>(selectedObject);
 		if(nullptr != selectedHero)
-			accessFlag = selectedHero->hasVisions(hero, 1);			
+			accessFlag = selectedHero->hasVisions(hero, 1);
 	}
-	
+
 	dest.initFromHero(h, accessFlag);
-	
+
 	//DISGUISED bonus implementation
-	
+
 	if(getPlayerRelations(getLocalPlayer(), hero->tempOwner) == PlayerRelations::ENEMIES)
 	{
-		//todo: bonus cashing	
+		//todo: bonus cashing
 		int disguiseLevel = h->valOfBonuses(Selector::typeSubtype(Bonus::DISGUISED, 0));
-		
-		auto doBasicDisguise = [disguiseLevel](InfoAboutHero & info)		
+
+		auto doBasicDisguise = [disguiseLevel](InfoAboutHero & info)
 		{
 			int maxAIValue = 0;
 			const CCreature * mostStrong = nullptr;
-			
+
 			for(auto & elem : info.army)
 			{
 				if(elem.second.type->AIValue > maxAIValue)
@@ -298,7 +302,7 @@ bool CGameInfoCallback::getHeroInfo(const CGObjectInstance * hero, InfoAboutHero
 					mostStrong = elem.second.type;
 				}
 			}
-			
+
 			if(nullptr == mostStrong)//just in case
 				logGlobal->errorStream() << "CGameInfoCallback::getHeroInfo: Unable to select most strong stack" << disguiseLevel;
 			else
@@ -307,25 +311,25 @@ bool CGameInfoCallback::getHeroInfo(const CGObjectInstance * hero, InfoAboutHero
 					elem.second.type = mostStrong;
 				}
 		};
-		
-		auto doAdvancedDisguise = [accessFlag, &doBasicDisguise](InfoAboutHero & info)		
+
+		auto doAdvancedDisguise = [accessFlag, &doBasicDisguise](InfoAboutHero & info)
 		{
 			doBasicDisguise(info);
-			
+
 			for(auto & elem : info.army)
 				elem.second.count = 0;
 		};
-		
-		auto doExpertDisguise = [this,h](InfoAboutHero & info)		
+
+		auto doExpertDisguise = [this,h](InfoAboutHero & info)
 		{
 			for(auto & elem : info.army)
 				elem.second.count = 0;
-			
+
 			const auto factionIndex = getStartInfo(false)->playerInfos.at(h->tempOwner).castle;
-			
+
 			int maxAIValue = 0;
 			const CCreature * mostStrong = nullptr;
-			
+
 			for(auto creature : VLC->creh->creatures)
 			{
 				if(creature->faction == factionIndex && creature->AIValue > maxAIValue)
@@ -334,35 +338,35 @@ bool CGameInfoCallback::getHeroInfo(const CGObjectInstance * hero, InfoAboutHero
 					mostStrong = creature;
 				}
 			}
-			
+
 			if(nullptr != mostStrong) //possible, faction may have no creatures at all
 				for(auto & elem : info.army)
 					elem.second.type = mostStrong;
-		};				
-		
-		
+		};
+
+
 		switch (disguiseLevel)
 		{
 		case 0:
 			//no bonus at all - do nothing
-			break;		
+			break;
 		case 1:
 			doBasicDisguise(dest);
-			break;		
+			break;
 		case 2:
 			doAdvancedDisguise(dest);
-			break;		
+			break;
 		case 3:
 			doExpertDisguise(dest);
-			break;		
+			break;
 		default:
 			//invalid value
 			logGlobal->errorStream() << "CGameInfoCallback::getHeroInfo: Invalid DISGUISED bonus value " << disguiseLevel;
 			break;
 		}
-		
+
 	}
-	
+
 	return true;
 }
 
@@ -486,7 +490,7 @@ std::shared_ptr<boost::multi_array<TerrainTile*, 3>> CGameInfoCallback::getAllVi
 
 
 	boost::multi_array<TerrainTile*, 3> tileArray(boost::extents[width][height][levels]);
-	
+
 	for (size_t x = 0; x < width; x++)
 		for (size_t y = 0; y < height; y++)
 			for (size_t z = 0; z < levels; z++)
@@ -723,15 +727,16 @@ int CPlayerSpecificInfoCallback::getHeroSerial(const CGHeroInstance * hero, bool
 	return -1;
 }
 
-int3 CPlayerSpecificInfoCallback::getGrailPos( double &outKnownRatio )
+int3 CPlayerSpecificInfoCallback::getGrailPos( double *outKnownRatio )
 {
 	if (!player || CGObelisk::obeliskCount == 0)
 	{
-		outKnownRatio = 0.0;
+		*outKnownRatio = 0.0;
 	}
 	else
 	{
-		outKnownRatio = static_cast<double>(CGObelisk::visited[gs->getPlayerTeam(*player)->id]) / CGObelisk::obeliskCount;
+		*outKnownRatio = static_cast<double>(CGObelisk::visited[gs->getPlayerTeam(*player)->id])
+			/ CGObelisk::obeliskCount;
 	}
 	return gs->map->grailPos;
 }
@@ -964,4 +969,3 @@ void IGameEventRealizer::setObjProperty(ObjectInstanceID objid, int prop, si64 v
 	sob.val = static_cast<ui32>(val);
 	commitPackage(&sob);
 }
-

+ 2 - 3
lib/CGameInfoCallback.h

@@ -125,7 +125,7 @@ class DLL_LINKAGE CPlayerSpecificInfoCallback : public CGameInfoCallback
 public:
 	int howManyTowns() const;
 	int howManyHeroes(bool includeGarrisoned = true) const;
-	int3 getGrailPos(double &outKnownRatio);
+	int3 getGrailPos(double *outKnownRatio);
 	boost::optional<PlayerColor> getMyColor() const;
 
 	std::vector <const CGTownInstance *> getTownsInfo(bool onlyOur = true) const; //true -> only owned; false -> all visible
@@ -139,7 +139,7 @@ public:
 
 	int getResourceAmount(Res::ERes type) const;
 	TResources getResourceAmount() const;
-	const std::vector< std::vector< std::vector<ui8> > > & getVisibilityMap()const; //returns visibility map 
+	const std::vector< std::vector< std::vector<ui8> > > & getVisibilityMap()const; //returns visibility map
 	const PlayerSettings * getPlayerSettings(PlayerColor color) const;
 };
 
@@ -154,4 +154,3 @@ public:
 
 	virtual void showInfoDialog(const std::string &msg, PlayerColor player);
 };
-

+ 7 - 6
lib/CGameState.cpp

@@ -1219,8 +1219,8 @@ CGameState::CrossoverHeroesList CGameState::getCrossoverHeroesFromPreviousScenar
 				// remove heroes which didn't reached the end of the scenario, but were available at the start
 				for(auto hero : lostCrossoverHeroes)
 				{
-					crossoverHeroes.heroesFromAnyPreviousScenarios.erase(range::remove_if(crossoverHeroes.heroesFromAnyPreviousScenarios,
-						CGObjectInstanceBySubIdFinder(hero)), crossoverHeroes.heroesFromAnyPreviousScenarios.end());
+					vstd::erase_if(crossoverHeroes.heroesFromAnyPreviousScenarios,
+					               CGObjectInstanceBySubIdFinder(hero));
 				}
 
 				// now add heroes which completed the scenario
@@ -1665,8 +1665,9 @@ void CGameState::initStartingBonus()
 				int res = VLC->townh->factions[scenarioOps->playerInfos[elem.first].castle]->town->primaryRes;
 				if(res == Res::WOOD_AND_ORE)
 				{
-					elem.second.resources[Res::WOOD] += rand.nextInt(5, 10);
-					elem.second.resources[Res::ORE] += rand.nextInt(5, 10);
+					int amount = rand.nextInt(5, 10);
+					elem.second.resources[Res::WOOD] += amount;
+					elem.second.resources[Res::ORE] += amount;
 				}
 				else
 				{
@@ -2267,7 +2268,7 @@ EVictoryLossCheckResult CGameState::checkForVictoryAndLoss(PlayerColor player) c
 
 	for (const TriggeredEvent & event : map->triggeredEvents)
 	{
-		if ((event.trigger.test(evaluateEvent)))
+		if (event.trigger.test(evaluateEvent))
 		{
 			if (event.effect.type == EventEffect::VICTORY)
 				return EVictoryLossCheckResult::victory(event.onFulfill, event.effect.toOtherMessage);
@@ -2284,7 +2285,7 @@ EVictoryLossCheckResult CGameState::checkForVictoryAndLoss(PlayerColor player) c
 	return EVictoryLossCheckResult();
 }
 
-bool CGameState::checkForVictory( PlayerColor player, const EventCondition & condition ) const
+bool CGameState::checkForVictory(PlayerColor player, const EventCondition & condition) const
 {
 	const PlayerState *p = CGameInfoCallback::getPlayer(player);
 	switch (condition.condition)

+ 21 - 0
lib/CModHandler.cpp

@@ -551,21 +551,42 @@ CModHandler::CModHandler()
 
 void CModHandler::loadConfigFromFile (std::string name)
 {
+	std::string paths;
+	for(auto& p : CResourceHandler::get()->getResourceNames(ResourceID("config/" + name)))
+	{
+		paths += p.string() + ", ";
+	}
+	paths = paths.substr(0, paths.size() - 2);
+	logGlobal->debugStream() << "Loading hardcoded features settings from [" << paths << "], result:";
 	settings.data = JsonUtils::assembleFromFiles("config/" + name);
 	const JsonNode & hardcodedFeatures = settings.data["hardcodedFeatures"];
 	settings.MAX_HEROES_AVAILABLE_PER_PLAYER = hardcodedFeatures["MAX_HEROES_AVAILABLE_PER_PLAYER"].Float();
+	logGlobal->debugStream() << "\tMAX_HEROES_AVAILABLE_PER_PLAYER\t" << settings.MAX_HEROES_AVAILABLE_PER_PLAYER;
 	settings.MAX_HEROES_ON_MAP_PER_PLAYER = hardcodedFeatures["MAX_HEROES_ON_MAP_PER_PLAYER"].Float();
+	logGlobal->debugStream() << "\tMAX_HEROES_ON_MAP_PER_PLAYER\t" << settings.MAX_HEROES_ON_MAP_PER_PLAYER;
 	settings.CREEP_SIZE = hardcodedFeatures["CREEP_SIZE"].Float();
+	logGlobal->debugStream() << "\tCREEP_SIZE\t" << settings.CREEP_SIZE;
 	settings.WEEKLY_GROWTH = hardcodedFeatures["WEEKLY_GROWTH_PERCENT"].Float();
+	logGlobal->debugStream() << "\tWEEKLY_GROWTH\t" << settings.WEEKLY_GROWTH;
 	settings.NEUTRAL_STACK_EXP = hardcodedFeatures["NEUTRAL_STACK_EXP_DAILY"].Float();
+	logGlobal->debugStream() << "\tNEUTRAL_STACK_EXP\t" << settings.NEUTRAL_STACK_EXP;
 	settings.MAX_BUILDING_PER_TURN = hardcodedFeatures["MAX_BUILDING_PER_TURN"].Float();
+	logGlobal->debugStream() << "\tMAX_BUILDING_PER_TURN\t" << settings.MAX_BUILDING_PER_TURN;
 	settings.DWELLINGS_ACCUMULATE_CREATURES = hardcodedFeatures["DWELLINGS_ACCUMULATE_CREATURES"].Bool();
+	logGlobal->debugStream() << "\tDWELLINGS_ACCUMULATE_CREATURES\t" << settings.DWELLINGS_ACCUMULATE_CREATURES;
 	settings.ALL_CREATURES_GET_DOUBLE_MONTHS = hardcodedFeatures["ALL_CREATURES_GET_DOUBLE_MONTHS"].Bool();
+	logGlobal->debugStream() << "\tALL_CREATURES_GET_DOUBLE_MONTHS\t" << settings.ALL_CREATURES_GET_DOUBLE_MONTHS;
+	settings.WINNING_HERO_WITH_NO_TROOPS_RETREATS = hardcodedFeatures["WINNING_HERO_WITH_NO_TROOPS_RETREATS"].Bool();
+	logGlobal->debugStream() << "\tWINNING_HERO_WITH_NO_TROOPS_RETREATS\t" << settings.WINNING_HERO_WITH_NO_TROOPS_RETREATS;
 	const JsonNode & gameModules = settings.data["modules"];
 	modules.STACK_EXP = gameModules["STACK_EXPERIENCE"].Bool();
+  logGlobal->debugStream() << "\tSTACK_EXP\t" << modules.STACK_EXP;
 	modules.STACK_ARTIFACT = gameModules["STACK_ARTIFACTS"].Bool();
+	logGlobal->debugStream() << "\tSTACK_ARTIFACT\t" << modules.STACK_ARTIFACT;
 	modules.COMMANDERS = gameModules["COMMANDERS"].Bool();
+	logGlobal->debugStream() << "\tCOMMANDERS\t" << modules.COMMANDERS;
 	modules.MITHRIL = gameModules["MITHRIL"].Bool();
+	logGlobal->debugStream() << "\tMITHRIL\t" << modules.MITHRIL;
 }
 
 // currentList is passed by value to get current list of depending mods

+ 13 - 3
lib/CModHandler.h

@@ -73,7 +73,7 @@ public:
 	/// Function callback will be called during ID resolution phase of loading
 	void requestIdentifier(std::string scope, std::string type, std::string name, const std::function<void(si32)> & callback);
 	///fullName = [remoteScope:]type.name
-	void requestIdentifier(std::string scope, std::string fullName, const std::function<void(si32)> & callback);	
+	void requestIdentifier(std::string scope, std::string fullName, const std::function<void(si32)> & callback);
 	void requestIdentifier(std::string type, const JsonNode & name, const std::function<void(si32)> & callback);
 	void requestIdentifier(const JsonNode & name, const std::function<void(si32)> & callback);
 
@@ -253,17 +253,27 @@ public:
 
 		int CREEP_SIZE; // neutral stacks won't grow beyond this number
 		int WEEKLY_GROWTH; //percent
-		int NEUTRAL_STACK_EXP; 
+		int NEUTRAL_STACK_EXP;
 		int MAX_BUILDING_PER_TURN;
 		bool DWELLINGS_ACCUMULATE_CREATURES;
 		bool ALL_CREATURES_GET_DOUBLE_MONTHS;
 		int MAX_HEROES_AVAILABLE_PER_PLAYER;
 		int MAX_HEROES_ON_MAP_PER_PLAYER;
+		bool WINNING_HERO_WITH_NO_TROOPS_RETREATS;
 
 		template <typename Handler> void serialize(Handler &h, const int version)
 		{
 			h & data & CREEP_SIZE & WEEKLY_GROWTH & NEUTRAL_STACK_EXP & MAX_BUILDING_PER_TURN;
-			h & DWELLINGS_ACCUMULATE_CREATURES & ALL_CREATURES_GET_DOUBLE_MONTHS & MAX_HEROES_AVAILABLE_PER_PLAYER & MAX_HEROES_ON_MAP_PER_PLAYER;
+			h & DWELLINGS_ACCUMULATE_CREATURES & ALL_CREATURES_GET_DOUBLE_MONTHS &
+			MAX_HEROES_AVAILABLE_PER_PLAYER & MAX_HEROES_ON_MAP_PER_PLAYER;
+			if(version >= 756)
+			{
+				h & WINNING_HERO_WITH_NO_TROOPS_RETREATS;
+			}
+			else if(!h.saving)
+			{
+				WINNING_HERO_WITH_NO_TROOPS_RETREATS = true;
+			}
 		}
 	} settings;
 

+ 1 - 1
lib/Connection.h

@@ -27,7 +27,7 @@
 #include "mapping/CCampaignHandler.h" //for CCampaignState
 #include "rmg/CMapGenerator.h" // for CMapGenOptions
 
-const ui32 version = 755;
+const ui32 version = 757;
 const ui32 minSupportedVersion = 753;
 
 class CISer;

+ 11 - 2
lib/JsonNode.cpp

@@ -56,6 +56,15 @@ JsonNode::JsonNode(ResourceID && fileURI):
 	*this = parser.parse(fileURI.getName());
 }
 
+JsonNode::JsonNode(const ResourceID & fileURI):
+	type(DATA_NULL)
+{
+	auto file = CResourceHandler::get()->load(fileURI)->readAll();
+
+	JsonParser parser(reinterpret_cast<char*>(file.first.get()), file.second);
+	*this = parser.parse(fileURI.getName());
+}
+
 JsonNode::JsonNode(ResourceID && fileURI, bool &isValidSyntax):
 	type(DATA_NULL)
 {
@@ -328,7 +337,7 @@ void JsonUtils::parseTypedBonusShort(const JsonVector& source, Bonus *dest)
 	resolveIdentifier(source[2],dest->subtype);
 	dest->additionalInfo = source[3].Float();
 	dest->duration = Bonus::PERMANENT; //TODO: handle flags (as integer)
-	dest->turnsRemain = 0;	
+	dest->turnsRemain = 0;
 }
 
 
@@ -343,7 +352,7 @@ Bonus * JsonUtils::parseBonus (const JsonVector &ability_vec) //TODO: merge with
 		return b;
 	}
 	b->type = it->second;
-	
+
 	parseTypedBonusShort(ability_vec, b);
 	return b;
 }

+ 10 - 9
lib/JsonNode.h

@@ -7,7 +7,7 @@
  * Full text of license available in license.txt file, in main folder
  *
  */
- 
+
 #pragma once
 
 class JsonNode;
@@ -55,6 +55,7 @@ public:
 	explicit JsonNode(const char * data, size_t datasize);
 	//Create tree from JSON file
  	explicit JsonNode(ResourceID && fileURI);
+ 	explicit JsonNode(const ResourceID & fileURI);
 	explicit JsonNode(ResourceID && fileURI, bool & isValidSyntax);
 	//Copy c-tor
 	JsonNode(const JsonNode &copy);
@@ -125,9 +126,9 @@ namespace JsonUtils
 	/**
 	 * @brief parse short bonus format, excluding type
 	 * @note sets duration to Permament
-	 */	
+	 */
 	DLL_LINKAGE void parseTypedBonusShort(const JsonVector &source, Bonus *dest);
-	
+
 	///
 	DLL_LINKAGE Bonus * parseBonus (const JsonVector &ability_vec);
 	DLL_LINKAGE Bonus * parseBonus (const JsonNode &bonus);
@@ -144,7 +145,7 @@ namespace JsonUtils
 	 * @note this function will destroy data in source
 	 */
 	DLL_LINKAGE void merge(JsonNode & dest, JsonNode & source);
-	
+
 	/**
 	 * @brief recursively merges source into dest, replacing identical fields
 	 * struct : recursively calls this function
@@ -152,12 +153,12 @@ namespace JsonUtils
 	 * values : value in source will replace value in dest
 	 * null   : if value in source is present but set to null it will delete entry in dest
 	 * @note this function will preserve data stored in source by creating copy
-	 */ 
+	 */
 	DLL_LINKAGE void mergeCopy(JsonNode & dest, JsonNode source);
-	
+
     /** @brief recursively merges descendant into copy of base node
      * Result emulates inheritance semantic
-     * 
+     *
      *
      */
 	DLL_LINKAGE void inherit(JsonNode & descendant, const JsonNode & base);
@@ -200,7 +201,7 @@ namespace JsonDetail
 {
 	// conversion helpers for JsonNode::convertTo (partial template function instantiation is illegal in c++)
 
-	template <typename T, int arithm> 
+	template <typename T, int arithm>
 	struct JsonConvImpl;
 
 	template <typename T>
@@ -229,7 +230,7 @@ namespace JsonDetail
 			///this should be triggered only for numeric types and enums
 			static_assert(boost::mpl::or_<std::is_arithmetic<Type>, std::is_enum<Type>, boost::is_class<Type> >::value, "Unsupported type for JsonNode::convertTo()!");
 			return JsonConvImpl<Type, boost::mpl::or_<std::is_enum<Type>, boost::is_class<Type> >::value >::convertImpl(node);
-				
+
 		}
 	};
 

+ 43 - 6
lib/NetPacksLib.cpp

@@ -340,7 +340,7 @@ DLL_LINKAGE void RemoveBonus::applyGs( CGameState *gs )
 		if(b->source == source && b->sid == id)
 		{
 			bonus = *b; //backup bonus (to show to interfaces later)
-			node->removeBonus(b);			
+			node->removeBonus(b);
 			break;
 		}
 	}
@@ -362,6 +362,10 @@ DLL_LINKAGE void RemoveObject::applyGs( CGameState *gs )
 		p->heroes -= h;
 		h->detachFrom(h->whereShouldBeAttached(gs));
 		h->tempOwner = PlayerColor::NEUTRAL; //no one owns beaten hero
+		vstd::erase_if(h->artifactsInBackpack, [](const ArtSlotInfo& asi)
+		{
+			return asi.artifact->artType->id == ArtifactID::GRAIL;
+		});
 
 		if(h->visitedTown)
 		{
@@ -590,7 +594,11 @@ DLL_LINKAGE void HeroRecruited::applyGs( CGameState *gs )
 
 	h->setOwner(player);
 	h->pos = tile;
-	h->movement =  h->maxMovePoints(true);
+	bool fresh = !h->isInitialized();
+	if(fresh)
+	{ // this is a fresh hero who hasn't appeared yet
+		h->movement = h->maxMovePoints(true);
+	}
 
 	gs->hpool.heroesPool.erase(hid);
 	if(h->id == ObjectInstanceID())
@@ -604,7 +612,10 @@ DLL_LINKAGE void HeroRecruited::applyGs( CGameState *gs )
 	gs->map->heroesOnMap.push_back(h);
 	p->heroes.push_back(h);
 	h->attachTo(p);
-	h->initObj();
+	if(fresh)
+	{
+		h->initObj();
+	}
 	gs->map->addBlockVisTiles(h);
 
 	if(t)
@@ -754,7 +765,7 @@ DLL_LINKAGE const CArtifactInstance *ArtifactLocation::getArt() const
 			return s->artifact;
 		else
 		{
-            logNetwork->warnStream() << "ArtifactLocation::getArt: That location is locked!";
+			logNetwork->warnStream() << "ArtifactLocation::getArt: This location is locked!";
 			return nullptr;
 		}
 	}
@@ -914,6 +925,32 @@ DLL_LINKAGE void PutArtifact::applyGs( CGameState *gs )
 
 DLL_LINKAGE void EraseArtifact::applyGs( CGameState *gs )
 {
+	auto slot = al.getSlot();
+	if(slot->locked)
+	{
+		logGlobal->debugStream() << "Erasing locked artifact: " << slot->artifact->artType->Name();
+		DisassembledArtifact dis;
+		dis.al.artHolder = al.artHolder;
+		auto aset = al.getHolderArtSet();
+		bool found = false;
+		for(auto& p : aset->artifactsWorn)
+		{
+			auto art = p.second.artifact;
+			if(art->canBeDisassembled() && art->isPart(slot->artifact))
+			{
+				dis.al.slot = aset->getArtPos(art);
+				found = true;
+				break;
+			}
+		}
+		assert(found && "Failed to determine the assembly this locked artifact belongs to");
+		logGlobal->debugStream() << "Found the corresponding assembly: " << dis.al.getSlot()->artifact->artType->Name();
+		dis.applyGs(gs);
+	}
+	else
+	{
+		logGlobal->debugStream() << "Erasing artifact " << slot->artifact->artType->Name();
+	}
 	al.removeArtifact();
 }
 
@@ -1262,7 +1299,7 @@ DLL_LINKAGE void BattleStackAttacked::applyGs( CGameState *gs )
 	{
 		//"hide" killed creatures instead so we keep info about it
 		at->state.insert(EBattleStackState::DEAD_CLONE);
-		
+
 		for(CStack * s : gs->curB->stacks)
 		{
 			if(s->cloneID == at->ID)
@@ -1375,7 +1412,7 @@ void actualizeEffect(CStack * s, const Bonus & ef)
 			stackBonus->turnsRemain = std::max(stackBonus->turnsRemain, ef.turnsRemain);
 		}
 	}
-	CBonusSystemNode::treeHasChanged();	
+	CBonusSystemNode::treeHasChanged();
 }
 
 void actualizeEffect(CStack * s, const std::vector<Bonus> & ef)

+ 0 - 1
lib/VCMI_lib.cbp

@@ -201,7 +201,6 @@
 		<Unit filename="ScopeGuard.h" />
 		<Unit filename="StartInfo.h" />
 		<Unit filename="StdInc.h">
-			<Option compile="1" />
 			<Option weight="0" />
 		</Unit>
 		<Unit filename="StringConstants.h" />

+ 14 - 0
lib/filesystem/AdapterLoaders.cpp

@@ -87,6 +87,20 @@ boost::optional<boost::filesystem::path> CFilesystemList::getResourceName(const
 	return boost::optional<boost::filesystem::path>();
 }
 
+std::set<boost::filesystem::path> CFilesystemList::getResourceNames(const ResourceID & resourceName) const
+{
+	std::set<boost::filesystem::path> paths;
+	for(auto& loader : getResourcesWithName(resourceName))
+	{
+		auto rn = loader->getResourceName(resourceName);
+		if(rn)
+		{
+			paths.insert(*rn);
+		}
+	}
+	return std::move(paths);
+}
+
 std::unordered_set<ResourceID> CFilesystemList::getFilteredFiles(std::function<bool(const ResourceID &)> filter) const
 {
 	std::unordered_set<ResourceID> ret;

+ 3 - 9
lib/filesystem/AdapterLoaders.h

@@ -58,15 +58,8 @@ class DLL_LINKAGE CFilesystemList : public ISimpleResourceLoader
 	std::set<ISimpleResourceLoader *> writeableLoaders;
 
 	//FIXME: this is only compile fix, should be removed in the end
-	CFilesystemList(CFilesystemList &)
-    {
-		//class is not copyable
-    }
-    CFilesystemList &operator=(CFilesystemList &)
-    {
-        //class is not copyable
-        return *this;
-    }
+	CFilesystemList(CFilesystemList &) = delete;
+	CFilesystemList &operator=(CFilesystemList &) = delete;
 
 public:
 	CFilesystemList();
@@ -77,6 +70,7 @@ public:
 	bool existsResource(const ResourceID & resourceName) const override;
 	std::string getMountPoint() const override;
 	boost::optional<boost::filesystem::path> getResourceName(const ResourceID & resourceName) const override;
+	std::set<boost::filesystem::path> getResourceNames(const ResourceID & resourceName) const override;
 	std::unordered_set<ResourceID> getFilteredFiles(std::function<bool(const ResourceID &)> filter) const override;
 	bool createResource(std::string filename, bool update = false) override;
 	std::vector<const ISimpleResourceLoader *> getResourcesWithName(const ResourceID & resourceName) const override;

+ 16 - 0
lib/filesystem/ISimpleResourceLoader.h

@@ -53,6 +53,22 @@ public:
 		return boost::optional<boost::filesystem::path>();
 	}
 
+	/**
+	 * Gets all full names of matching resources, e.g. names of files in filesystem.
+	 *
+	 * @return std::set with names.
+	 */
+	virtual std::set<boost::filesystem::path> getResourceNames(const ResourceID & resourceName) const
+	{
+		std::set<boost::filesystem::path> result;
+		auto rn = getResourceName(resourceName);
+		if(rn)
+		{
+			result.insert(*rn);
+		}
+		return result;
+	}
+
 	/**
 	 * Get list of files that matches filter function
 	 *

+ 4 - 3
lib/logging/CLogger.h

@@ -12,6 +12,7 @@
 #pragma once
 
 #include "../CConsoleHandler.h"
+#include "../filesystem/FileStream.h"
 
 class CLogger;
 struct LogRecord;
@@ -147,7 +148,7 @@ private:
 
 /// Macros for tracing the control flow of the application conveniently. If the LOG_TRACE macro is used it should be
 /// the first statement in the function. Logging traces via this macro have almost no impact when the trace is disabled.
-/// 
+///
 #define RAII_TRACE(logger, onEntry, onLeave)			\
 	std::unique_ptr<CTraceLogger> ctl00;						\
 	if(logger->isTraceEnabled())						\
@@ -217,7 +218,7 @@ public:
 	CLogFormatter(CLogFormatter && move);
 
 	CLogFormatter(const std::string & pattern);
-	
+
 	CLogFormatter & operator=(const CLogFormatter & copy);
 	CLogFormatter & operator=(CLogFormatter && move);
 
@@ -302,7 +303,7 @@ public:
 	void write(const LogRecord & record) override;
 
 private:
-	boost::filesystem::ofstream file;
+	FileStream file;
 	CLogFormatter formatter;
 	mutable boost::mutex mx;
 };

+ 50 - 24
lib/mapObjects/CGHeroInstance.cpp

@@ -23,6 +23,7 @@
 #include "../CCreatureHandler.h"
 #include "../BattleState.h"
 #include "../CTownHandler.h"
+#include "../mapping/CMap.h"
 #include "CGTownInstance.h"
 
 ///helpers
@@ -220,7 +221,9 @@ CGHeroInstance::CGHeroInstance()
 	setNodeType(HERO);
 	ID = Obj::HERO;
 	tacticFormationEnabled = inTownGarrison = false;
-	mana = movement = portrait = -1;
+	mana = UNINITIALIZED_MANA;
+	movement = UNINITIALIZED_MOVEMENT;
+	portrait = UNINITIALIZED_PORTRAIT;
 	isStanding = true;
 	moveDir = 4;
 	level = 1;
@@ -868,18 +871,18 @@ TExpType CGHeroInstance::calculateXp(TExpType exp) const
 ui8 CGHeroInstance::getSpellSchoolLevel(const CSpell * spell, int *outSelectedSchool) const
 {
 	si16 skill = -1; //skill level
-	
+
 	spell->forEachSchool([&, this](const SpellSchoolInfo & cnf, bool & stop)
 	{
 		int thisSchool = std::max<int>(getSecSkillLevel(cnf.skill),	valOfBonuses(Bonus::MAGIC_SCHOOL_SKILL, 1 << ((ui8)cnf.id))); //FIXME: Bonus shouldn't be additive (Witchking Artifacts : Crown of Skies)
-		if(thisSchool > skill)									
-		{														
-			skill = thisSchool;									
-			if(outSelectedSchool)								
-				*outSelectedSchool = (ui8)cnf.id;				
-		}																
+		if(thisSchool > skill)
+		{
+			skill = thisSchool;
+			if(outSelectedSchool)
+				*outSelectedSchool = (ui8)cnf.id;
+		}
 	});
-	
+
 	vstd::amax(skill, valOfBonuses(Bonus::MAGIC_SCHOOL_SKILL, 0)); //any school bonus
 	vstd::amax(skill, valOfBonuses(Bonus::SPELL, spell->id.toEnum())); //given by artifact or other effect
 
@@ -904,7 +907,7 @@ ui32 CGHeroInstance::getSpellBonus(const CSpell * spell, ui32 base, const CStack
 	if (affectedStack && affectedStack->getCreature()->level) //Hero specials like Solmyr, Deemer
 		base *= (100. + ((valOfBonuses(Bonus::SPECIAL_SPELL_LEV,  spell->id.toEnum()) * level) / affectedStack->getCreature()->level)) / 100.0;
 
-	return base;	
+	return base;
 }
 
 int CGHeroInstance::getEffectLevel(const CSpell * spell) const
@@ -912,7 +915,7 @@ int CGHeroInstance::getEffectLevel(const CSpell * spell) const
 	if(hasBonusOfType(Bonus::MAXED_SPELL, spell->id))
 		return 3;//todo: recheck specialty from where this bonus is. possible bug
 	else
-		return getSpellSchoolLevel(spell);		
+		return getSpellSchoolLevel(spell);
 }
 
 int CGHeroInstance::getEffectPower(const CSpell * spell) const
@@ -922,7 +925,7 @@ int CGHeroInstance::getEffectPower(const CSpell * spell) const
 
 int CGHeroInstance::getEnchantPower(const CSpell * spell) const
 {
-	return getPrimSkillLevel(PrimarySkill::SPELL_POWER) + valOfBonuses(Bonus::SPELL_DURATION);	
+	return getPrimSkillLevel(PrimarySkill::SPELL_POWER) + valOfBonuses(Bonus::SPELL_DURATION);
 }
 
 int CGHeroInstance::getEffectValue(const CSpell * spell) const
@@ -1107,8 +1110,8 @@ void CGHeroInstance::getOutOffsets(std::vector<int3> &offsets) const
 {
 	// FIXME: Offsets need to be fixed once we get rid of convertPosition
 	// Check issue 515 for details
-	offsets = 
-	{ 
+	offsets =
+	{
 		int3(-1,1,0), int3(-1,-1,0), int3(-2,0,0), int3(0,0,0), int3(0,1,0), int3(-2,1,0), int3(0,-1,0), int3(-2,-1,0)
 	};
 }
@@ -1436,20 +1439,43 @@ void CGHeroInstance::levelUpAutomatically()
 bool CGHeroInstance::hasVisions(const CGObjectInstance * target, const int subtype) const
 {
 	//VISIONS spell support
-	
-	const std::string cached = boost::to_string((boost::format("type_%d__subtype_%d") % Bonus::VISIONS % subtype)); 
-	
+
+	const std::string cached = boost::to_string((boost::format("type_%d__subtype_%d") % Bonus::VISIONS % subtype));
+
 	const int visionsMultiplier = valOfBonuses(Selector::typeSubtype(Bonus::VISIONS,subtype), cached);
-	
+
 	int visionsRange =  visionsMultiplier * getPrimSkillLevel(PrimarySkill::SPELL_POWER);
-		
-	if (visionsMultiplier > 0) 	
+
+	if (visionsMultiplier > 0)
 		vstd::amax(visionsRange, 3); //minimum range is 3 tiles, but only if VISIONS bonus present
-	
+
 	const int distance = target->pos.dist2d(getPosition(false));
-	
+
 	//logGlobal->debug(boost::to_string(boost::format("Visions: dist %d, mult %d, range %d") % distance % visionsMultiplier % visionsRange));
-	
-	return (distance < visionsRange) && (target->pos.z == pos.z);	
+
+	return (distance < visionsRange) && (target->pos.z == pos.z);
 }
 
+bool CGHeroInstance::isMissionCritical() const
+{
+	for(const TriggeredEvent & event : IObjectInterface::cb->getMapHeader()->triggeredEvents)
+	{
+		if(event.trigger.test([&](const EventCondition & condition)
+		{
+			if (condition.condition == EventCondition::CONTROL && condition.object)
+			{
+				auto hero = dynamic_cast<const CGHeroInstance*>(condition.object);
+				return (hero != this);
+			}
+			else if(condition.condition == EventCondition::IS_HUMAN)
+			{
+				return true;
+			}
+			return false;
+		}))
+		{
+			return true;
+		}
+	}
+	return false;
+}

+ 18 - 7
lib/mapObjects/CGHeroInstance.h

@@ -20,6 +20,7 @@
 class CHero;
 class CGBoat;
 class CGTownInstance;
+class CMap;
 struct TerrainTile;
 struct TurnInfo;
 
@@ -64,6 +65,9 @@ public:
 	ConstTransitivePtr<CCommanderInstance> commander;
 	const CGBoat *boat; //set to CGBoat when sailing
 
+	static const ui32 UNINITIALIZED_PORTRAIT = -1;
+	static const ui32 UNINITIALIZED_MANA = -1;
+	static const ui32 UNINITIALIZED_MOVEMENT = -1;
 
 	//std::vector<const CArtifact*> artifacts; //hero's artifacts from bag
 	//std::map<ui16, const CArtifact*> artifWorn; //map<position,artifact_id>; positions: 0 - head; 1 - shoulders; 2 - neck; 3 - right hand; 4 - left hand; 5 - torso; 6 - right ring; 7 - left ring; 8 - feet; 9 - misc1; 10 - misc2; 11 - misc3; 12 - misc4; 13 - mach1; 14 - mach2; 15 - mach3; 16 - mach4; 17 - spellbook; 18 - misc5
@@ -124,6 +128,11 @@ public:
 		}
 	} skillsInfo;
 
+	inline bool isInitialized() const
+	{ // has this hero been on the map at least once?
+		return movement != UNINITIALIZED_MOVEMENT && mana != UNINITIALIZED_MANA;
+	}
+
 	//int3 getSightCenter() const; //"center" tile from which the sight distance is calculated
 	int getSightRadious() const override; //sight distance (should be used if player-owned structure)
 	//////////////////////////////////////////////////////////////////////////
@@ -177,7 +186,7 @@ public:
 	double getHeroStrength() const; // includes fighting and magic strength
 	ui64 getTotalStrength() const; // includes fighting strength and army strength
 	TExpType calculateXp(TExpType exp) const; //apply learning skill
-	
+
 	bool canCastThisSpell(const CSpell * spell) const; //determines if this hero can cast given spell; takes into account existing spell in spellbook, existing spellbook and artifact bonuses
 	CStackBasicDescriptor calculateNecromancy (const BattleResult &battleResult) const;
 	void showNecromancyDialog(const CStackBasicDescriptor &raisedStack) const;
@@ -201,23 +210,25 @@ public:
 	void Updatespecialty();
 	void recreateSecondarySkillsBonuses();
 	void updateSkill(SecondarySkill which, int val);
-	
+
 	bool hasVisions(const CGObjectInstance * target, const int subtype) const;
+	/// If this hero perishes, the scenario is failed
+	bool isMissionCritical() const;
 
 	CGHeroInstance();
 	virtual ~CGHeroInstance();
-	
+
 	///ArtBearer
 	ArtBearer::ArtBearer bearerType() const override;
 
 	///IBonusBearer
 	CBonusSystemNode *whereShouldBeAttached(CGameState *gs) override;
 	std::string nodeName() const override;
-	
+
 	///ISpellCaster
 	ui8 getSpellSchoolLevel(const CSpell * spell, int *outSelectedSchool = nullptr) const override;
 	ui32 getSpellBonus(const CSpell * spell, ui32 base, const CStack * affectedStack) const override;
-	
+
 	///default spell school level for effect calculation
 	int getEffectLevel(const CSpell * spell) const override;
 
@@ -229,9 +240,9 @@ public:
 
 	///damage/heal override(ignores spell configuration, effect level and effect power)
 	int getEffectValue(const CSpell * spell) const override;
-	
+
 	const PlayerColor getOwner() const override;
-	
+
 	void deserializationFix();
 
 	void initObj() override;

+ 4 - 4
lib/mapObjects/CGPandoraBox.cpp

@@ -295,10 +295,10 @@ void CGPandoraBox::getText( InfoWindow &iw, bool &afterBattle, int val, int nega
 
 void CGPandoraBox::battleFinished(const CGHeroInstance *hero, const BattleResult &result) const
 {
-	if(result.winner)
-		return;
-
-	giveContentsUpToExp(hero);
+	if(result.winner == 0)
+	{
+		giveContentsUpToExp(hero);
+	}
 }
 
 void CGPandoraBox::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const

+ 10 - 4
lib/mapObjects/CObjectHandler.cpp

@@ -169,7 +169,7 @@ std::set<int3> CGObjectInstance::getBlockedPos() const
 	{
 		for(int h=0; h<getHeight(); ++h)
 		{
-			if (appearance.isBlockedAt(w, h))
+			if(appearance.isBlockedAt(w, h))
 				ret.insert(int3(pos.x - w, pos.y - h, pos.z));
 		}
 	}
@@ -191,7 +191,13 @@ void CGObjectInstance::setType(si32 ID, si32 subID)
 	//recalculate blockvis tiles - new appearance might have different blockmap than before
 	cb->gameState()->map->removeBlockVisTiles(this, true);
 	auto handler = VLC->objtypeh->getHandlerFor(ID, subID);
-	if (!handler->getTemplates(tile.terType).empty())
+	if(!handler)
+	{
+		logGlobal->errorStream() << boost::format(
+			  "Unknown object type %d:%d at %s") % ID % subID % visitablePos();
+		return;
+	}
+	if(!handler->getTemplates(tile.terType).empty())
 		appearance = handler->getTemplates(tile.terType)[0];
 	else
 		appearance = handler->getTemplates()[0]; // get at least some appearance since alternative is crash
@@ -328,9 +334,9 @@ int3 IBoatGenerator::bestLocation() const
 
 	for (auto & offset : offsets)
 	{
-		if (const TerrainTile *tile = IObjectInterface::cb->getTile(o->pos + offset, false)) //tile is in the map
+		if(const TerrainTile *tile = IObjectInterface::cb->getTile(o->pos + offset, false)) //tile is in the map
 		{
-			if (tile->terType == ETerrainType::WATER  &&  (!tile->blocked || tile->blockingObjects.front()->ID == Obj::BOAT)) //and is water and is not blocked or is blocked by boat
+			if(tile->terType == ETerrainType::WATER  &&  (!tile->blocked || tile->blockingObjects.front()->ID == Obj::BOAT)) //and is water and is not blocked or is blocked by boat
 				return o->pos + offset;
 		}
 	}

+ 19 - 6
lib/mapObjects/CQuest.cpp

@@ -70,7 +70,7 @@ bool CQuest::checkQuest (const CGHeroInstance * h) const
 		case MISSION_ART:
 			for (auto & elem : m5arts)
 			{
-				if (h->hasArt(elem))
+				if (h->hasArt(elem, false, true))
 					continue;
 				return false; //if the artifact was not found
 			}
@@ -416,6 +416,7 @@ void CGSeerHut::init()
 {
 	seerName = *RandomGeneratorUtil::nextItem(VLC->generaltexth->seerNames, cb->gameState()->getRandomGenerator());
 	quest->textOption = cb->gameState()->getRandomGenerator().nextInt(2);
+	quest->completedOption = cb->gameState()->getRandomGenerator().nextInt(1, 5);
 }
 
 void CGSeerHut::initObj()
@@ -435,7 +436,7 @@ void CGSeerHut::initObj()
 	else
 	{
 		quest->progress = CQuest::COMPLETE;
-		quest->firstVisitText = VLC->generaltexth->seerEmpty[quest->textOption];
+		quest->firstVisitText = VLC->generaltexth->seerEmpty[quest->completedOption];
 	}
 }
 
@@ -533,7 +534,7 @@ void CGSeerHut::newTurn() const
 {
 	if (quest->lastDay >= 0 && quest->lastDay < cb->getDate()-1) //time is up
 	{
-		cb->setObjProperty (id, 10, CQuest::COMPLETE);
+		cb->setObjProperty (id, CGSeerHut::OBJPROP_VISITED, CQuest::COMPLETE);
 	}
 }
 
@@ -550,7 +551,7 @@ void CGSeerHut::onHeroVisit( const CGHeroInstance * h ) const
 		if (firstVisit)
 		{
 			isCustom = quest->isCustomFirst;
-			cb->setObjProperty (id, 10, CQuest::IN_PROGRESS);
+			cb->setObjProperty (id, CGSeerHut::OBJPROP_VISITED, CQuest::IN_PROGRESS);
 
 			AddQuest aq;
 			aq.quest = QuestInfo (quest, this, visitablePos());
@@ -582,7 +583,7 @@ void CGSeerHut::onHeroVisit( const CGHeroInstance * h ) const
 	}
 	else
 	{
-		iw.text << VLC->generaltexth->seerEmpty[quest->textOption];
+		iw.text << VLC->generaltexth->seerEmpty[quest->completedOption];
 		if (ID == Obj::SEER_HUT)
 			iw.text.addReplacement(seerName);
 		cb->showInfoDialog(&iw);
@@ -630,6 +631,18 @@ void CGSeerHut::finishQuest(const CGHeroInstance * h, ui32 accept) const
 			case CQuest::MISSION_ART:
 				for (auto & elem : quest->m5arts)
 				{
+					if(!h->hasArt(elem))
+					{
+						// first we need to disassemble this backpack artifact
+						auto assembly = h->getAssemblyByConstituent(elem);
+						assert(assembly);
+						for(auto & ci : assembly->constituentsInfo)
+						{
+							cb->giveHeroNewArtifact(h, ci.art->artType, ArtifactPosition::PRE_FIRST);
+						}
+						// remove the assembly
+						cb->removeArtifact(ArtifactLocation(h, h->getArtPos(assembly)));
+					}
 					cb->removeArtifact(ArtifactLocation(h, h->getArtPos(elem, false)));
 				}
 				break;
@@ -645,7 +658,7 @@ void CGSeerHut::finishQuest(const CGHeroInstance * h, ui32 accept) const
 			default:
 				break;
 		}
-		cb->setObjProperty (id, 10, CQuest::COMPLETE); //mission complete
+		cb->setObjProperty (id, CGSeerHut::OBJPROP_VISITED, CQuest::COMPLETE); //mission complete
 		completeQuest(h); //make sure to remove QuestGuard at the very end
 	}
 }

+ 16 - 2
lib/mapObjects/CQuest.h

@@ -37,8 +37,11 @@ public:
 	std::vector<CStackBasicDescriptor> m6creatures; //pair[cre id, cre count], CreatureSet info irrelevant
 	std::vector<ui32> m7resources; //TODO: use resourceset?
 
-	//following field are used only for kill creature/hero missions, the original objects became inaccessible after their removal, so we need to store info needed for messages / hover text
+	// following fields are used only for kill creature/hero missions, the original
+	// objects became inaccessible after their removal, so we need to store info
+	// needed for messages / hover text
 	ui8 textOption;
+	ui8 completedOption;
 	CStackBasicDescriptor stackToKill;
 	ui8 stackDirection;
 	std::string heroName; //backup of hero name
@@ -66,7 +69,16 @@ public:
 	{
 		h & qid & missionType & progress & lastDay & m13489val & m2stats & m5arts & m6creatures & m7resources
 			& textOption & stackToKill & stackDirection & heroName & heroPortrait
-			& firstVisitText & nextVisitText & completedText & isCustomFirst & isCustomNext & isCustomComplete;
+			& firstVisitText & nextVisitText & completedText & isCustomFirst
+			& isCustomNext & isCustomComplete;
+		if(version >= 757)
+		{
+			h & completedOption;
+		}
+		else if(!h.saving)
+		{
+			completedOption = 1;
+		}
 	}
 };
 
@@ -118,6 +130,8 @@ public:
 		h & rewardType & rID & rVal & seerName;
 	}
 protected:
+	static constexpr int OBJPROP_VISITED = 10;
+
 	void setPropertyDer(ui8 what, ui32 val) override;
 };
 

+ 1 - 1
lib/mapObjects/CRewardableObject.cpp

@@ -496,6 +496,7 @@ void CGPickable::initObj()
 					info.resize(1);
 					info[0].message.addTxt(MetaString::ADVOB_TXT, 51);
 					info[0].reward.removeObject = true;
+					break;
 			case 1:
 				{
 					info.resize(1);
@@ -682,7 +683,6 @@ void CGBonusingObject::initObj()
 			info[i].message.addTxt(MetaString::ADVOB_TXT, 62);
 			soundID = soundBase::experience;
 		}
-		onVisited.addTxt(MetaString::ADVOB_TXT, 63);
 		info.back().limiter.dayOfWeek = 7;
 		configureBonus(info.back(), Bonus::MORALE, 1, 68); // on last day of week
 		configureBonus(info.back(), Bonus::LUCK,   1, 68);

+ 2 - 2
lib/mapObjects/JsonRandom.cpp

@@ -133,10 +133,10 @@ namespace JsonRandom
 		if (value["type"].getType() == JsonNode::DATA_STRING)
 			return SpellID(VLC->modh->identifiers.getIdentifier("spell", value["type"]).get());
 
-		spells.erase(std::remove_if(spells.begin(), spells.end(), [=](SpellID spell)
+		vstd::erase_if(spells, [=](SpellID spell)
 		{
 			return VLC->spellh->objects[spell]->level != si32(value["level"].Float());
-		}), spells.end());
+		});
 
 		return SpellID(*RandomGeneratorUtil::nextItem(spells, rng));
 	}

+ 89 - 36
lib/mapObjects/MiscObjects.cpp

@@ -24,7 +24,7 @@
 #include "../CPlayerState.h"
 
 std::map <si32, std::vector<ObjectInstanceID> > CGMagi::eyelist;
-ui8 CGObelisk::obeliskCount; //how many obelisks are on map
+ui8 CGObelisk::obeliskCount = 0; //how many obelisks are on map
 std::map<TeamID, ui8> CGObelisk::visited; //map: team_id => how many obelisks has been visited
 
 ///helpers
@@ -60,7 +60,7 @@ static std::string & visitedTxt(const bool visited)
 
 void CPlayersVisited::setPropertyDer( ui8 what, ui32 val )
 {
-	if(what == 10)
+	if(what == CPlayersVisited::OBJPROP_VISITED)
 		players.insert(PlayerColor(val));
 }
 
@@ -103,16 +103,16 @@ std::string CGCreature::getHoverText(const CGHeroInstance * hero) const
 {
 	std::string hoverName;
 	if(hero->hasVisions(this, 0))
-	{		
+	{
 		MetaString ms;
 		ms << stacks.begin()->second->count;
 		ms << " " ;
 		ms.addTxt(MetaString::CRE_PL_NAMES,subID);
-		
+
 		ms << "\n";
-		
+
 		int decision = takenAction(hero, true);
-		
+
 		switch (decision)
 		{
 		case FIGHT:
@@ -123,19 +123,19 @@ std::string CGCreature::getHoverText(const CGHeroInstance * hero) const
 			break;
 		case JOIN_FOR_FREE:
 			ms.addTxt(MetaString::GENERAL_TXT,243);
-			break;					
+			break;
 		default: //decision = cost in gold
 			VLC->generaltexth->allTexts[244];
-			ms << boost::to_string(boost::format(VLC->generaltexth->allTexts[244]) % decision);			
+			ms << boost::to_string(boost::format(VLC->generaltexth->allTexts[244]) % decision);
 			break;
-		}		
+		}
 
-		ms.toString(hoverName);		
+		ms.toString(hoverName);
 	}
 	else
 	{
-		hoverName = getHoverText(hero->tempOwner);	
-	}	
+		hoverName = getHoverText(hero->tempOwner);
+	}
 
 	const JsonNode & texts = VLC->generaltexth->localizedTexts["adventureMap"]["monsterThreat"];
 
@@ -392,6 +392,7 @@ void CGCreature::joinDecision(const CGHeroInstance *h, int cost, ui32 accept) co
 		if(cost)
 			cb->giveResource(h->tempOwner,Res::GOLD,-cost);
 
+		giveReward(h);
 		cb->tryJoiningArmy(this, h, true, true);
 	}
 }
@@ -452,9 +453,14 @@ void CGCreature::flee( const CGHeroInstance * h ) const
 
 void CGCreature::battleFinished(const CGHeroInstance *hero, const BattleResult &result) const
 {
-
-	if(result.winner==0)
+	if(result.winner == 0)
+	{
+		giveReward(hero);
+		cb->removeObject(this);
+	}
+	else if(result.winner > 1) // draw
 	{
+		// guarded reward is lost forever on draw
 		cb->removeObject(this);
 	}
 	else
@@ -462,11 +468,11 @@ void CGCreature::battleFinished(const CGHeroInstance *hero, const BattleResult &
 		//merge stacks into one
 		TSlots::const_iterator i;
 		CCreature * cre = VLC->creh->creatures[formation.basicType];
-		for (i = stacks.begin(); i != stacks.end(); i++)
+		for(i = stacks.begin(); i != stacks.end(); i++)
 		{
-			if (cre->isMyUpgrade(i->second->type))
+			if(cre->isMyUpgrade(i->second->type))
 			{
-				cb->changeStackType (StackLocation(this, i->first), cre); //un-upgrade creatures
+				cb->changeStackType(StackLocation(this, i->first), cre); //un-upgrade creatures
 			}
 		}
 
@@ -474,16 +480,16 @@ void CGCreature::battleFinished(const CGHeroInstance *hero, const BattleResult &
 		if(!hasStackAtSlot(SlotID(0)))
 			cb->moveStack(StackLocation(this, stacks.begin()->first), StackLocation(this, SlotID(0)), stacks.begin()->second->count);
 
-		while (stacks.size() > 1) //hopefully that's enough
+		while(stacks.size() > 1) //hopefully that's enough
 		{
 			// TODO it's either overcomplicated (if we assume there'll be only one stack) or buggy (if we allow multiple stacks... but that'll also cause troubles elsewhere)
 			i = stacks.end();
 			i--;
 			SlotID slot = getSlotFor(i->second->type);
-			if (slot == i->first) //no reason to move stack to its own slot
+			if(slot == i->first) //no reason to move stack to its own slot
 				break;
 			else
-				cb->moveStack (StackLocation(this, i->first), StackLocation(this, slot), i->second->count);
+				cb->moveStack(StackLocation(this, i->first), StackLocation(this, slot), i->second->count);
 		}
 
 		cb->setObjProperty(id, ObjProperty::MONSTER_POWER, stacks.begin()->second->count * 1000); //remember casualties
@@ -557,6 +563,35 @@ int CGCreature::getNumberOfStacks(const CGHeroInstance *hero) const
 	return split;
 }
 
+void CGCreature::giveReward(const CGHeroInstance * h) const
+{
+	InfoWindow iw;
+	iw.player = h->tempOwner;
+
+	if(resources.size())
+	{
+		cb->giveResources(h->tempOwner, resources);
+		for(int i = 0; i < resources.size(); i++)
+		{
+			if(resources[i] > 0)
+				iw.components.push_back(Component(Component::RESOURCE, i, resources[i], 0));
+		}
+	}
+
+	if(gainedArtifact != ArtifactID::NONE)
+	{
+		cb->giveHeroNewArtifact(h, VLC->arth->artifacts[gainedArtifact], ArtifactPosition::FIRST_AVAILABLE);
+		iw.components.push_back(Component(Component::ARTIFACT, gainedArtifact, 0, 0));
+	}
+
+	if(iw.components.size())
+	{
+		iw.text.addTxt(MetaString::ADVOB_TXT, 183); // % has found treasure
+		iw.text.addReplacement(h->name);
+		cb->showInfoDialog(&iw);
+	}
+}
+
 void CGMine::onHeroVisit( const CGHeroInstance * h ) const
 {
 	int relations = cb->gameState()->getPlayerRelations(h->tempOwner, tempOwner);
@@ -1279,7 +1314,7 @@ void CGWitchHut::onHeroVisit( const CGHeroInstance * h ) const
 	iw.soundID = soundBase::gazebo;
 	iw.player = h->getOwner();
 	if(!wasVisited(h->tempOwner))
-		cb->setObjProperty(id, 10, h->tempOwner.getNum());
+		cb->setObjProperty(id, CGWitchHut::OBJPROP_VISITED, h->tempOwner.getNum());
 	ui32 txt_id;
 	if(h->getSecSkillLevel(SecondarySkill(ability))) //you already know this skill
 	{
@@ -1389,7 +1424,7 @@ void CGShrine::onHeroVisit( const CGHeroInstance * h ) const
 	}
 
 	if(!wasVisited(h->tempOwner))
-		cb->setObjProperty(id, 10, h->tempOwner.getNum());
+		cb->setObjProperty(id, CGShrine::OBJPROP_VISITED, h->tempOwner.getNum());
 
 	InfoWindow iw;
 	iw.soundID = soundBase::temple;
@@ -1636,6 +1671,7 @@ void CGMagi::onHeroVisit(const CGHeroInstance * h) const
 				cb->sendAndApply(&cv);
 			}
 			cv.pos = h->getPosition(false);
+			cv.focusTime = 0;
 			cb->sendAndApply(&cv);
 		}
 	}
@@ -1714,7 +1750,7 @@ void CGShipyard::getOutOffsets( std::vector<int3> &offsets ) const
 		int3(-3,0,0), int3(1,0,0), //AB
 		int3(-3,1,0), int3(1,1,0), int3(-2,1,0), int3(0,1,0), int3(-1,1,0), //CDEFG
 		int3(-3,-1,0), int3(1,-1,0), int3(-2,-1,0), int3(0,-1,0), int3(-1,-1,0) //HIJKL
-	}; 
+	};
 }
 
 void CGShipyard::onHeroVisit( const CGHeroInstance * h ) const
@@ -1790,7 +1826,7 @@ void CCartographer::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answ
 		//water = 0; land = 1; underground = 2;
 		cb->getAllTiles (fw.tiles, hero->tempOwner, subID - 1, !subID + 1); //reveal appropriate tiles
 		cb->sendAndApply (&fw);
-		cb->setObjProperty (id, 10, hero->tempOwner.getNum());
+		cb->setObjProperty (id, CCartographer::OBJPROP_VISITED, hero->tempOwner.getNum());
 	}
 }
 
@@ -1812,11 +1848,16 @@ void CGObelisk::onHeroVisit( const CGHeroInstance * h ) const
 		iw.text.addTxt(MetaString::ADVOB_TXT, 96);
 		cb->sendAndApply(&iw);
 
-		cb->setObjProperty(id, 20, h->tempOwner.getNum()); //increment general visited obelisks counter
+		// increment general visited obelisks counter
+		cb->setObjProperty(id, CGObelisk::OBJPROP_INC, team.getNum());
 
 		openWindow(OpenWindow::PUZZLE_MAP, h->tempOwner.getNum());
 
-		cb->setObjProperty(id, 10, h->tempOwner.getNum()); //mark that particular obelisk as visited
+		// mark that particular obelisk as visited for all players in the team
+		for (auto & color : ts->players)
+		{
+			cb->setObjProperty(id, CGObelisk::OBJPROP_VISITED, color.getNum());
+		}
 	}
 	else
 	{
@@ -1831,6 +1872,12 @@ void CGObelisk::initObj()
 	obeliskCount++;
 }
 
+void CGObelisk::reset()
+{
+	obeliskCount = 0;
+	visited.clear();
+}
+
 std::string CGObelisk::getHoverText(PlayerColor player) const
 {
 	return getObjectName() + " " + visitedTxt(wasVisited(player));
@@ -1838,20 +1885,26 @@ std::string CGObelisk::getHoverText(PlayerColor player) const
 
 void CGObelisk::setPropertyDer( ui8 what, ui32 val )
 {
-	CPlayersVisited::setPropertyDer(what, val);
 	switch(what)
 	{
-	case 20:
-		assert(val < PlayerColor::PLAYER_LIMIT_I);
-		visited[TeamID(val)]++;
+		case CGObelisk::OBJPROP_INC:
+			{
+				assert(val < PlayerColor::PLAYER_LIMIT_I);
+				auto progress = ++visited[TeamID(val)];
+				logGlobal->debugStream() << boost::format("Player %d: obelisk progress %d / %d")
+					% val % static_cast<int>(progress) % static_cast<int>(obeliskCount);
 
-		if(visited[TeamID(val)] > obeliskCount)
-		{
-            logGlobal->errorStream() << "Error: Visited " << visited[TeamID(val)] << "\t\t" << obeliskCount;
-			assert(0);
-		}
+				if(progress > obeliskCount)
+				{
+					logGlobal->errorStream() << "Error: Visited " << progress << "\t\t" << obeliskCount;
+					assert(0);
+				}
 
-		break;
+				break;
+			}
+		default:
+			CPlayersVisited::setPropertyDer(what, val);
+			break;
 	}
 }
 

+ 5 - 0
lib/mapObjects/MiscObjects.h

@@ -28,6 +28,8 @@ public:
 		h & static_cast<CGObjectInstance&>(*this);
 		h & players;
 	}
+
+	static constexpr int OBJPROP_VISITED = 10;
 };
 
 class DLL_LINKAGE CGCreature : public CArmedInstance //creatures on map
@@ -90,6 +92,7 @@ private:
 	void joinDecision(const CGHeroInstance *h, int cost, ui32 accept) const;
 
 	int takenAction(const CGHeroInstance *h, bool allowJoin=true) const; //action on confrontation: -2 - fight, -1 - flee, >=0 - will join for given value of gold (may be 0)
+	void giveReward(const CGHeroInstance * h) const;
 
 };
 
@@ -450,12 +453,14 @@ class DLL_LINKAGE CGDenOfthieves : public CGObjectInstance
 class DLL_LINKAGE CGObelisk : public CPlayersVisited
 {
 public:
+	static constexpr int OBJPROP_INC = 20;
 	static ui8 obeliskCount; //how many obelisks are on map
 	static std::map<TeamID, ui8> visited; //map: team_id => how many obelisks has been visited
 
 	void onHeroVisit(const CGHeroInstance * h) const override;
 	void initObj() override;
 	std::string getHoverText(PlayerColor player) const override;
+	static void reset();
 
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{

+ 2 - 0
lib/rmg/CMapGenerator.cpp

@@ -20,6 +20,8 @@ void CMapGenerator::foreach_neighbour(const int3 &pos, std::function<void(int3&
 	for(const int3 &dir : int3::getDirs())
 	{
 		int3 n = pos + dir;
+		/*important notice: perform any translation before this function is called,
+		so the actual map position is checked*/
 		if(map->isInTheMap(n))
 			foo(n);
 	}

+ 7 - 9
lib/rmg/CRmgTemplateZone.cpp

@@ -1177,25 +1177,23 @@ bool CRmgTemplateZone::createTreasurePile(CMapGenerator* gen, int3 &pos, float m
 
 		int3 closestTile = int3(-1,-1,-1);
 		float minDistance = 1e10;
+
 		for (auto visitablePos : info.visitableFromBottomPositions) //objects that are not visitable from top must be accessible from bottom or side
 		{
 			int3 closestFreeTile = findClosestTile(freePaths, visitablePos);
 			if (closestFreeTile.dist2d(visitablePos) < minDistance)
 			{
-				closestTile = visitablePos + int3 (0, 1, 0); //start below object (y+1), possibly even outside the map (?)
+				closestTile = visitablePos + int3 (0, 1, 0); //start below object (y+1), possibly even outside the map, to not make path up through it
 				minDistance = closestFreeTile.dist2d(visitablePos);
 			}
 		}
-		if (!closestTile.valid())
+		for (auto visitablePos : info.visitableFromTopPositions) //all objects are accessible from any direction
 		{
-			for (auto visitablePos : info.visitableFromTopPositions) //all objects are accessible from any direction
+			int3 closestFreeTile = findClosestTile(freePaths, visitablePos);
+			if (closestFreeTile.dist2d(visitablePos) < minDistance)
 			{
-				int3 closestFreeTile = findClosestTile(freePaths, visitablePos);
-				if (closestFreeTile.dist2d(visitablePos) < minDistance)
-				{
-					closestTile = visitablePos;
-					minDistance = closestFreeTile.dist2d(visitablePos);
-				}
+				closestTile = visitablePos;
+				minDistance = closestFreeTile.dist2d(visitablePos);
 			}
 		}
 		assert (closestTile.valid());

+ 101 - 48
server/CGameHandler.cpp

@@ -124,6 +124,11 @@ static inline double distance(int3 a, int3 b)
 }
 static void giveExp(BattleResult &r)
 {
+	if(r.winner > 1)
+	{
+		// draw
+		return;
+	}
 	r.exp[0] = 0;
 	r.exp[1] = 0;
 	for(auto i = r.casualties[!r.winner].begin(); i!=r.casualties[!r.winner].end(); i++)
@@ -471,7 +476,7 @@ void CGameHandler::endBattle(int3 tile, const CGHeroInstance *hero1, const CGHer
 	const CArmedInstance *bEndArmy2 = gs->curB->sides.at(1).armyObject;
 	const BattleResult::EResult result = battleResult.get()->result;
 
-	auto findBattleQuery = [this] () -> std::shared_ptr<CBattleQuery>
+	auto findBattleQuery = [this]() -> std::shared_ptr<CBattleQuery>
 	{
 		for(auto &q : queries.allQueries())
 		{
@@ -526,66 +531,70 @@ void CGameHandler::endBattle(int3 tile, const CGHeroInstance *hero1, const CGHer
 	}
 
 
-	std::vector<ui32> arts; //display them in window
+	std::vector<const CArtifactInstance *> arts; //display them in window
 
-	if (result == BattleResult::NORMAL && finishingBattle->winnerHero)
+	if(result == BattleResult::NORMAL && finishingBattle->winnerHero)
 	{
-		if (finishingBattle->loserHero)
+		auto sendMoveArtifact = [&](const CArtifactInstance *art, MoveArtifact *ma)
 		{
-			auto artifactsWorn = finishingBattle->loserHero->artifactsWorn; //TODO: wrap it into a function, somehow (boost::variant -_-)
+			arts.push_back(art);
+			ma->dst = ArtifactLocation(finishingBattle->winnerHero, art->firstAvailableSlot(finishingBattle->winnerHero));
+			sendAndApply(ma);
+		};
+		if(finishingBattle->loserHero)
+		{
+			//TODO: wrap it into a function, somehow (boost::variant -_-)
+			auto artifactsWorn = finishingBattle->loserHero->artifactsWorn;
 			for (auto artSlot : artifactsWorn)
 			{
 				MoveArtifact ma;
-				ma.src = ArtifactLocation (finishingBattle->loserHero, artSlot.first);
+				ma.src = ArtifactLocation(finishingBattle->loserHero, artSlot.first);
 				const CArtifactInstance * art =  ma.src.getArt();
-				if (art && !art->artType->isBig() && art->artType->id != ArtifactID::SPELLBOOK) // don't move war machines or locked arts (spellbook)
+				if(art && !art->artType->isBig() &&
+				    art->artType->id != ArtifactID::SPELLBOOK)
+						// don't move war machines or locked arts (spellbook)
 				{
-					arts.push_back (art->artType->id);
-					ma.dst = ArtifactLocation (finishingBattle->winnerHero, art->firstAvailableSlot(finishingBattle->winnerHero));
-					sendAndApply(&ma);
+					sendMoveArtifact(art, &ma);
 				}
 			}
-			while (!finishingBattle->loserHero->artifactsInBackpack.empty())
+			while(!finishingBattle->loserHero->artifactsInBackpack.empty())
 			{
 				//we assume that no big artifacts can be found
 				MoveArtifact ma;
-				ma.src = ArtifactLocation (finishingBattle->loserHero,
+				ma.src = ArtifactLocation(finishingBattle->loserHero,
 					ArtifactPosition(GameConstants::BACKPACK_START)); //backpack automatically shifts arts to beginning
 				const CArtifactInstance * art =  ma.src.getArt();
-				arts.push_back (art->artType->id);
-				ma.dst = ArtifactLocation (finishingBattle->winnerHero, art->firstAvailableSlot(finishingBattle->winnerHero));
-				sendAndApply(&ma);
+				if(art->artType->id != ArtifactID::GRAIL) //grail may not be won
+				{
+					sendMoveArtifact(art, &ma);
+				}
 			}
-			if (finishingBattle->loserHero->commander) //TODO: what if commanders belong to no hero?
+			if(finishingBattle->loserHero->commander) //TODO: what if commanders belong to no hero?
 			{
 				artifactsWorn = finishingBattle->loserHero->commander->artifactsWorn;
-				for (auto artSlot : artifactsWorn)
+				for(auto artSlot : artifactsWorn)
 				{
 					MoveArtifact ma;
-					ma.src = ArtifactLocation (finishingBattle->loserHero->commander.get(), artSlot.first);
+					ma.src = ArtifactLocation(finishingBattle->loserHero->commander.get(), artSlot.first);
 					const CArtifactInstance * art =  ma.src.getArt();
 					if (art && !art->artType->isBig())
 					{
-						arts.push_back (art->artType->id);
-						ma.dst = ArtifactLocation (finishingBattle->winnerHero, art->firstAvailableSlot(finishingBattle->winnerHero));
-						sendAndApply(&ma);
+						sendMoveArtifact(art, &ma);
 					}
 				}
 			}
 		}
-		for (auto armySlot : gs->curB->battleGetArmyObject(!battleResult.data->winner)->stacks)
+		for(auto armySlot : gs->curB->battleGetArmyObject(!battleResult.data->winner)->stacks)
 		{
 			auto artifactsWorn = armySlot.second->artifactsWorn;
 			for (auto artSlot : artifactsWorn)
 			{
 				MoveArtifact ma;
-				ma.src = ArtifactLocation (armySlot.second, artSlot.first);
+				ma.src = ArtifactLocation(armySlot.second, artSlot.first);
 				const CArtifactInstance * art =  ma.src.getArt();
 				if (art && !art->artType->isBig())
 				{
-					arts.push_back (art->artType->id);
-					ma.dst = ArtifactLocation (finishingBattle->winnerHero, art->firstAvailableSlot(finishingBattle->winnerHero));
-					sendAndApply(&ma);
+					sendMoveArtifact(art, &ma);
 				}
 			}
 		}
@@ -593,23 +602,25 @@ void CGameHandler::endBattle(int3 tile, const CGHeroInstance *hero1, const CGHer
 
 	sendAndApply(battleResult.data); //after this point casualties objects are destroyed
 
-	if (arts.size()) //display loot
+	if(arts.size()) //display loot
 	{
 		InfoWindow iw;
 		iw.player = finishingBattle->winnerHero->tempOwner;
 
 		iw.text.addTxt (MetaString::GENERAL_TXT, 30); //You have captured enemy artifact
 
-		for (auto id : arts) //TODO; separate function to display loot for various ojects?
+		for(auto art : arts) //TODO; separate function to display loot for various ojects?
 		{
-			iw.components.push_back (Component (Component::ARTIFACT, id, 0, 0));
+			iw.components.push_back(Component(
+				Component::ARTIFACT, art->artType->id,
+				art->artType->id == ArtifactID::SPELL_SCROLL? art->getGivenSpellID() : 0, 0));
 			if(iw.components.size() >= 14)
 			{
 				sendAndApply(&iw);
 				iw.components.clear();
 			}
 		}
-		if (iw.components.size())
+		if(iw.components.size())
 		{
 			sendAndApply(&iw);
 		}
@@ -712,7 +723,7 @@ void CGameHandler::battleAfterLevelUp( const BattleResult &result )
 
 	setBattle(nullptr);
 
-	if(visitObjectAfterVictory && result.winner==0)
+	if(visitObjectAfterVictory && result.winner==0 && !finishingBattle->winnerHero->stacks.empty())
 	{
 		logGlobal->traceStream() << "post-victory visit";
 		visitObjectOnTile(*getTile(finishingBattle->winnerHero->getPosition()), finishingBattle->winnerHero);
@@ -734,13 +745,34 @@ void CGameHandler::battleAfterLevelUp( const BattleResult &result )
 			sah.army[0].setCreature(SlotID(0), finishingBattle->loserHero->type->initialArmy.at(0).creature, 1);
 		}
 
-		if(const CGHeroInstance *another =  getPlayer(finishingBattle->loser)->availableHeroes.at(1))
+		if(const CGHeroInstance *another =  getPlayer(finishingBattle->loser)->availableHeroes.at(0))
 			sah.hid[1] = another->subID;
 		else
 			sah.hid[1] = -1;
 
 		sendAndApply(&sah);
 	}
+	if(result.winner != 2 && finishingBattle->winnerHero && finishingBattle->winnerHero->stacks.empty())
+	{
+		RemoveObject ro(finishingBattle->winnerHero->id);
+		sendAndApply(&ro);
+
+		if (VLC->modh->settings.WINNING_HERO_WITH_NO_TROOPS_RETREATS)
+		{
+			SetAvailableHeroes sah;
+			sah.player = finishingBattle->victor;
+			sah.hid[0] = finishingBattle->winnerHero->subID;
+			sah.army[0].clear();
+			sah.army[0].setCreature(SlotID(0), finishingBattle->winnerHero->type->initialArmy.at(0).creature, 1);
+
+			if(const CGHeroInstance *another =  getPlayer(finishingBattle->victor)->availableHeroes.at(0))
+				sah.hid[1] = another->subID;
+			else
+				sah.hid[1] = -1;
+
+			sendAndApply(&sah);
+		}
+	}
 }
 
 void CGameHandler::prepareAttack(BattleAttack &bat, const CStack *att, const CStack *def, int distance, int targetHex)
@@ -926,13 +958,16 @@ void CGameHandler::handleConnection(std::set<PlayerColor> players, CConnection &
 			{
 				const bool result = apply->applyOnGH(this,&c,pack, player);
 				if(!result)
-					complain("Got false in applying... that request must have been fishy!");
-                logGlobal->traceStream() << "Message successfully applied (result=" << result << ")!";
+				{
+					complain((boost::format("Got false in applying %s... that request must have been fishy!")
+				            % typeid(*pack).name()).str());
+				}
+				logGlobal->traceStream() << "Message successfully applied (result=" << result << ")!";
 				sendPackageResponse(true);
 			}
 			else
 			{
-                logGlobal->errorStream() << "Message cannot be applied, cannot find applier (unregistered type)!";
+				logGlobal->errorStream() << "Message cannot be applied, cannot find applier (unregistered type)!";
 				sendPackageResponse(false);
 			}
 
@@ -2113,7 +2148,6 @@ void CGameHandler::stopHeroVisitCastle(const CGTownInstance * obj, const CGHeroI
 
 void CGameHandler::removeArtifact(const ArtifactLocation &al)
 {
-	assert(al.getArt());
 	EraseArtifact ea;
 	ea.al = al;
 	sendAndApply(&ea);
@@ -3038,7 +3072,7 @@ bool CGameHandler::assembleArtifacts (ObjectInstanceID heroID, ArtifactPosition
 		sendAndApply(&da);
 	}
 
-	return false;
+	return true;
 }
 
 bool CGameHandler::buyArtifact( ObjectInstanceID hid, ArtifactID aid )
@@ -3320,23 +3354,27 @@ bool CGameHandler::hireHero(const CGObjectInstance *obj, ui8 hid, PlayerColor pl
 //		|| (getHeroCount(player, false) >= GameConstants::MAX_HEROES_PER_PLAYER && complain("Cannot hire hero, only 8 wandering heroes are allowed!")))
 	if((p->resources.at(Res::GOLD) < GameConstants::HERO_GOLD_COST && complain("Not enough gold for buying hero!"))
 		|| ((!t) && (getHeroCount(player, false) >= VLC->modh->settings.MAX_HEROES_ON_MAP_PER_PLAYER && complain("Cannot hire hero, too many wandering heroes already!")))
-			|| ((t) && (getHeroCount(player, true) >= VLC->modh->settings.MAX_HEROES_AVAILABLE_PER_PLAYER && complain("Cannot hire hero, too many heroes garrizoned and wandering already!"))) )
-
-	return false;
+		|| ((t) && (getHeroCount(player, true) >= VLC->modh->settings.MAX_HEROES_AVAILABLE_PER_PLAYER && complain("Cannot hire hero, too many heroes garrizoned and wandering already!"))) )
+	{
+		return false;
+	}
 
 	if(t) //tavern in town
 	{
-		if(    (!t->hasBuilt(BuildingID::TAVERN)  && complain("No tavern!"))
-			|| (t->visitingHero  && complain("There is visiting hero - no place!")))
+		if((!t->hasBuilt(BuildingID::TAVERN) && complain("No tavern!"))
+			 || (t->visitingHero  && complain("There is visiting hero - no place!")))
+		{
 			return false;
+		}
 	}
 	else if(obj->ID == Obj::TAVERN)
 	{
-		if(getTile(obj->visitablePos())->visitableObjects.back() != obj  &&  complain("Tavern entry must be unoccupied!"))
+		if(getTile(obj->visitablePos())->visitableObjects.back() != obj && complain("Tavern entry must be unoccupied!"))
+		{
 			return false;
+		}
 	}
 
-
 	const CGHeroInstance *nh = p->availableHeroes.at(hid);
 	if (!nh)
 	{
@@ -3351,13 +3389,14 @@ bool CGameHandler::hireHero(const CGObjectInstance *obj, ui8 hid, PlayerColor pl
 	hr.tile = obj->visitablePos() + nh->getVisitableOffset();
 	sendAndApply(&hr);
 
-
 	std::map<ui32, ConstTransitivePtr<CGHeroInstance> > pool = gs->unusedHeroesFromPool();
 
 	const CGHeroInstance *theOtherHero = p->availableHeroes.at(!hid);
 	const CGHeroInstance *newHero = nullptr;
 	if (theOtherHero) //on XXL maps all heroes can be imprisoned :(
+	{
 		newHero = gs->hpool.pickHeroFor(false, player, getNativeTown(player), pool, gs->getRandomGenerator(), theOtherHero->type->heroClass);
+	}
 
 	SetAvailableHeroes sah;
 	sah.player = player;
@@ -3369,7 +3408,9 @@ bool CGameHandler::hireHero(const CGObjectInstance *obj, ui8 hid, PlayerColor pl
 		sah.army[hid].setCreature(SlotID(0), newHero->type->initialArmy[0].creature, 1);
 	}
 	else
+	{
 		sah.hid[hid] = -1;
+	}
 
 	sah.hid[!hid] = theOtherHero ? theOtherHero->subID : -1;
 	sendAndApply(&sah);
@@ -4026,6 +4067,16 @@ void CGameHandler::playerMessage( PlayerColor player, const std::string &message
 			if(!hero->hasStackAtSlot(SlotID(i)))
 				insertNewStack(StackLocation(hero, SlotID(i)), blackKnight, 10);
 	}
+	else if(message == "vcmiglaurung") //gives 5000 crystal dragons into each slot
+	{
+		CGHeroInstance *hero = gs->getHero(currObj);
+		const CCreature *crystalDragon = VLC->creh->creatures.at(133);
+		if(!hero) return;
+
+		for(int i = 0; i < GameConstants::ARMY_SIZE; i++)
+			if(!hero->hasStackAtSlot(SlotID(i)))
+				insertNewStack(StackLocation(hero, SlotID(i)), crystalDragon, 5000);
+	}
 	else if(message == "vcminoldor") //all war machines
 	{
 		CGHeroInstance *hero = gs->getHero(currObj);
@@ -5651,16 +5702,18 @@ void CGameHandler::giveHeroNewArtifact(const CGHeroInstance *h, const CArtifact
 
 void CGameHandler::setBattleResult(BattleResult::EResult resultType, int victoriusSide)
 {
-	if(battleResult.get())
+	boost::unique_lock<boost::mutex> guard(battleResult.mx);
+	if(battleResult.data)
 	{
-		complain("There is already set result?");
+		complain((boost::format("The battle result has been already set (to %d, asked to %d)")
+		          % battleResult.data->result % resultType).str());
 		return;
 	}
 	auto br = new BattleResult;
 	br->result = resultType;
 	br->winner = victoriusSide; //surrendering side loses
 	gs->curB->calculateCasualties(br->casualties);
-	battleResult.set(br);
+	battleResult.data = br;
 }
 
 void CGameHandler::commitPackage( CPackForClient *pack )

+ 2 - 1
server/CQuery.cpp

@@ -321,7 +321,8 @@ CBlockingDialogQuery::CBlockingDialogQuery(const BlockingDialog &bd)
 
 void CTeleportDialogQuery::notifyObjectAboutRemoval(const CObjectVisitQuery &objectVisit) const
 {
-	auto obj = dynamic_ptr_cast<const CGTeleport>(objectVisit.visitedObject);
+	// do not change to dynamic_ptr_cast - SIGSEGV!
+	auto obj = dynamic_cast<const CGTeleport*>(objectVisit.visitedObject);
 	obj->teleportDialogAnswered(objectVisit.visitingHero, *answer, td.exits);
 }
 

+ 0 - 1
server/VCMI_server.cbp

@@ -98,7 +98,6 @@
 		<Unit filename="CVCMIServer.h" />
 		<Unit filename="NetPacksServer.cpp" />
 		<Unit filename="StdInc.h">
-			<Option compile="1" />
 			<Option weight="0" />
 		</Unit>
 		<Extensions>

+ 0 - 1
vcmi.workspace

@@ -38,6 +38,5 @@
 		<Project filename="scripting/erm/ERM.cbp">
 			<Depends filename="lib/VCMI_lib.cbp" />
 		</Project>
-		<Project filename="launcher/VCMI_launcher.cbp" />
 	</Workspace>
 </CodeBlocks_workspace_file>