瀏覽代碼

* Client is able to await for answers for multiple queries at the same time
* Hackish solution allowing AI undertaking actions from event-handling thread
* Fixed crash when death stare or acid breath activated on stack that was just killed
* minor fixes

Michał W. Urbańczyk 13 年之前
父節點
當前提交
13f26fc3cb
共有 12 個文件被更改,包括 176 次插入65 次删除
  1. 34 5
      AI/VCAI/VCAI.cpp
  2. 3 0
      AI/VCAI/VCAI.h
  3. 5 11
      CCallback.cpp
  4. 18 7
      client/Client.cpp
  5. 55 1
      client/Client.h
  6. 1 3
      client/NetPacksClient.cpp
  7. 2 2
      lib/Connection.cpp
  8. 1 1
      lib/Connection.h
  9. 2 1
      lib/NetPacks.h
  10. 44 18
      server/CGameHandler.cpp
  11. 1 0
      server/CGameHandler.h
  12. 10 16
      server/VCMI_server.vcxproj

+ 34 - 5
AI/VCAI/VCAI.cpp

@@ -793,7 +793,7 @@ void VCAI::heroGotLevel(const CGHeroInstance *hero, int pskill, std::vector<ui16
 	NET_EVENT_HANDLER;
 	LOG_ENTRY;
 	status.addQuery();
-	callback(0);
+	requestActionASAP(boost::bind(callback, 0));
 }
 
 void VCAI::showBlockingDialog(const std::string &text, const std::vector<Component> &components, ui32 askID, const int soundID, bool selection, bool cancel)
@@ -809,7 +809,10 @@ void VCAI::showBlockingDialog(const std::string &text, const std::vector<Compone
 	if(!selection && cancel) //yes&no -> always answer yes, we are a brave AI :)
 		sel = 1;
 
-	cb->selectionMade(sel, askID);
+	requestActionASAP([&]()
+	{
+		cb->selectionMade(sel, askID);
+	});
 }
 
 void VCAI::showGarrisonDialog(const CArmedInstance *up, const CGHeroInstance *down, bool removableUnits, boost::function<void()> &onEnd)
@@ -819,8 +822,11 @@ void VCAI::showGarrisonDialog(const CArmedInstance *up, const CGHeroInstance *do
 	status.addQuery();
 
 	//you can't request action from action-response thread
-	//pickBestCreatures (down, up);
-	onEnd();
+	requestActionASAP([=,this]()
+	{
+		pickBestCreatures (down, up);
+		onEnd();
+	});
 }
 
 void VCAI::serialize(COSer<CSaveFile> &h, const int version)
@@ -1352,6 +1358,7 @@ bool VCAI::moveHeroToTile(int3 dst, const CGHeroInstance * h)
 			if(h->tempOwner != playerID) //we lost hero
 			{
 				remove_if_present(lockedHeroes, h);
+				throw std::runtime_error("Hero was lost!"); //we need to throw, otherwise hero will be assigned to sth again
 				break;
 			}
 
@@ -1653,7 +1660,15 @@ void VCAI::performTypicalActions()
 		INDENT;
 		makePossibleUpgrades(h);
 		cb->setSelection(h);
-		wander(h);
+		try
+		{
+			wander(h);
+		}
+		catch(std::exception &e)
+		{
+			BNLOG("Cannot use this hero anymore, received exception: %s", e.what());
+			continue;
+		}
 	}
 }
 
@@ -1787,6 +1802,20 @@ void VCAI::finish()
 		makingTurn->interrupt();
 }
 
+void VCAI::requestActionASAP(boost::function<void()> whatToDo)
+{
+	boost::barrier b(2);
+	boost::thread newThread([&b,this,whatToDo]()
+	{
+		setThreadName(-1, "VCAI::requestActionASAP::helper");
+		SET_GLOBAL_STATE(this);
+		boost::shared_lock<boost::shared_mutex> gsLock(cb->getGsMutex());
+		b.wait();
+		whatToDo();
+	});
+	b.wait();
+}
+
 AIStatus::AIStatus()
 {
 	battle = NO_BATTLE;

+ 3 - 0
AI/VCAI/VCAI.h

@@ -284,6 +284,9 @@ public:
 	const CGHeroInstance *primaryHero() const;
 	TResources estimateIncome() const;
 	bool containsSavedRes(const TResources &cost) const;
+
+	//special function that can be called ONLY from game events handling thread and will send request ASAP
+	void requestActionASAP(boost::function<void()> whatToDo); 
 };
 
 

+ 5 - 11
CCallback.cpp

@@ -59,7 +59,7 @@ void CCallback::selectionMade(int selection, int asker)
 {
 	QueryReply pack(asker,selection);
 	pack.player = player;
-	cl->serv->sendPackToServer(pack, player);
+	sendRequest(&pack);
 }
 void CCallback::recruitCreatures(const CGObjectInstance *obj, ui32 ID, ui32 amount, si32 level/*=-1*/)
 {
@@ -222,21 +222,15 @@ int CBattleCallback::battleMakeAction(BattleAction* action)
 
 void CBattleCallback::sendRequest(const CPack* request)
 {
-
-	//TODO should be part of CClient (client owns connection, not CB)
-	//but it would have to be very tricky cause template/serialization issues
-	if(waitTillRealize)
-		cl->waitingRequest.set(typeList.getTypeID(request));
-
-	cl->serv->sendPackToServer(*request, player);
-
+	int requestID = cl->sendRequest(request, player);
 	if(waitTillRealize)
 	{
+		tlog5 << boost::format("We'll wait till request %d is answered.\n") % requestID;
 		unique_ptr<vstd::unlock_shared_guard> unlocker; //optional, if flag set
 		if(unlockGsWhenWaiting)
-			unlocker = make_unique<vstd::unlock_shared_guard>(vstd::makeUnlockSharedGuard(getGsMutex()));
+			unlocker = make_unique<vstd::unlock_shared_guard>(getGsMutex());
 
-		cl->waitingRequest.waitWhileTrue();
+		cl->waitingRequest.waitWhileContains(requestID);
 	}
 }
 

+ 18 - 7
client/Client.cpp

@@ -96,13 +96,11 @@ void CClient::init()
 }
 
 CClient::CClient(void)
-:waitingRequest(0)
 {
 	init();
 }
 
 CClient::CClient(CConnection *con, StartInfo *si)
-:waitingRequest(0)
 {
 	init();
 	newGame(con,si);
@@ -121,7 +119,7 @@ void CClient::waitForMoveAndSend(int color)
 		assert(vstd::contains(battleints, color));
 		BattleAction ba = battleints[color]->activeStack(gs->curB->getStack(gs->curB->activeStack, false));
 		MakeAction temp_action(ba);
-		serv->sendPackToServer(temp_action, color);
+		sendRequest(&temp_action, color);
 		return;
 	}
 	catch(boost::thread_interrupted&)
@@ -175,7 +173,7 @@ void CClient::save(const std::string & fname)
 	}
 
 	SaveGame save_game(fname);
-	serv->sendPackToServer((CPackForClient&)save_game, getCurrentPlayer());
+	sendRequest((CPackForClient*)&save_game, 255);
 }
 
 void CClient::endGame( bool closeConnection /*= true*/ )
@@ -530,7 +528,7 @@ void CClient::stopConnection()
 		tlog0 << "Connection has been requested to be closed.\n";
 		boost::unique_lock<boost::mutex>(*serv->wmx);
 		CloseServer close_server;
-		serv->sendPackToServer(close_server, 255);
+		sendRequest(&close_server, 255);
 		tlog0 << "Sent closing signal to the server\n";
 	}
 
@@ -599,7 +597,7 @@ void CClient::commitPackage( CPackForClient *pack )
 	CommitPackage cp;
 	cp.freePack = false;
 	cp.packToCommit = pack;
-	serv->sendPackToServer(cp, 255);
+	sendRequest(&cp, 255);
 }
 
 int CClient::getLocalPlayer() const
@@ -625,7 +623,7 @@ void CClient::commenceTacticPhaseForInt(CBattleGameInterface *battleInt)
 		if(gs && !!gs->curB && gs->curB->tacticDistance) //while awaiting for end of tactics phase, many things can happen (end of battle... or game)
 		{
 			MakeAction ma(BattleAction::makeEndOFTacticPhase(battleInt->playerID));
-			serv->sendPackToServer(ma, battleInt->playerID);
+			sendRequest(&ma, battleInt->playerID);
 		}
 	} HANDLE_EXCEPTION
 }
@@ -636,6 +634,19 @@ void CClient::invalidatePaths(const CGHeroInstance *h /*= NULL*/)
 		pathInfo->isValid = false;
 }
 
+int CClient::sendRequest(const CPack *request, int player)
+{
+	static ui32 requestCounter = 0;
+
+	ui32 requestID = requestCounter++;
+	tlog5 << boost::format("Sending a request \"%s\". It'll have an ID=%d.\n") 
+				% typeid(*request).name() % requestID;
+
+	waitingRequest.pushBack(requestID);
+	serv->sendPackToServer(*request, player, requestID);
+	return requestID;
+}
+
 template void CClient::serialize( CISer<CLoadFile> &h, const int version );
 template void CClient::serialize( COSer<CSaveFile> &h, const int version );
 

+ 55 - 1
client/Client.h

@@ -56,6 +56,58 @@ public:
 	~CServerHandler();
 };
 
+template<typename T>
+class ThreadSafeVector
+{
+	typedef std::vector<T> TVector;
+	typedef boost::unique_lock<boost::mutex> TLock;
+	TVector items;
+	boost::mutex mx;
+	boost::condition_variable cond;
+
+public:
+
+	void pushBack(const T &item)
+	{
+		TLock lock(mx);
+		items.push_back(item);
+		cond.notify_all();
+	}
+
+// 	//to access list, caller must present a lock used to lock mx
+// 	TVector &getList(TLock &lockedLock)
+// 	{
+// 		assert(lockedLock.owns_lock() && lockedLock.mutex() == &mx);
+// 		return items;
+// 	}
+
+	TLock getLock()
+	{
+		return TLock(mx);
+	}
+
+	void waitWhileContains(const T &item)
+	{
+		auto lock = getLock();
+		while(vstd::contains(items, item))
+			cond.wait(lock);
+	}
+
+	bool tryRemovingElement(const T&item) //returns false if element was not present
+	{
+		auto lock = getLock();
+		auto itr = vstd::find(items, item);
+		if(itr == items.end()) //not in container
+		{
+			return false;
+		}
+
+		items.erase(itr);
+		cond.notify_all();
+		return true;
+	}
+};
+
 /// Class which handles client - server logic
 class CClient : public IGameCallback
 {
@@ -75,7 +127,7 @@ public:
 
 	CScriptingModule *erm;
 
-	CondSh<int> waitingRequest;
+	ThreadSafeVector<int> waitingRequest;
 
 	std::queue<CPack *> packs;
 	boost::mutex packsM;
@@ -162,6 +214,8 @@ public:
 	friend class CBattleCallback; //handling players actions
 	friend void processCommand(const std::string &message, CClient *&client); //handling console
 	
+	int sendRequest(const CPack *request, int player); //returns ID given to that request
+
 	void handlePack( CPack * pack ); //applies the given pack and deletes it
 	void battleStarted(const BattleInfo * info);
 	void commenceTacticPhaseForInt(CBattleGameInterface *battleInt); //will be called as separate thread

+ 1 - 3
client/NetPacksClient.cpp

@@ -717,9 +717,7 @@ void EndAction::applyCl( CClient *cl )
 void PackageApplied::applyCl( CClient *cl )
 {
 	INTERFACE_CALL_IF_PRESENT(player, requestRealized, this);
-	if(cl->waitingRequest.get() == packType)
-		cl->waitingRequest.setn(false);
-	else if(cl->waitingRequest.get())
+	if(!cl->waitingRequest.tryRemovingElement(requestID))
 		tlog3 << "Surprising server message!\n";
 }
 

+ 2 - 2
lib/Connection.cpp

@@ -240,11 +240,11 @@ CPack * CConnection::retreivePack()
 	return ret;
 }
 
-void CConnection::sendPackToServer(const CPack &pack, ui8 player)
+void CConnection::sendPackToServer(const CPack &pack, ui8 player, ui32 requestID)
 {
 	boost::unique_lock<boost::mutex> lock(*wmx);
 	tlog5 << "Sending to server a pack of type " << typeid(pack).name() << std::endl;
-	*this << player << &pack; //packs has to be sent as polymorphic pointers!
+	*this << player << requestID << &pack; //packs has to be sent as polymorphic pointers!
 }
 
 CSaveFile::CSaveFile( const std::string &fname )

+ 1 - 1
lib/Connection.h

@@ -916,7 +916,7 @@ public:
 	~CConnection(void);
 
 	CPack *retreivePack(); //gets from server next pack (allocates it with new)
-	void sendPackToServer(const CPack &pack, ui8 player);
+	void sendPackToServer(const CPack &pack, ui8 player, ui32 requestID);
 };
 
 DLL_LINKAGE std::ostream &operator<<(std::ostream &str, const CConnection &cpc);

+ 2 - 1
lib/NetPacks.h

@@ -172,11 +172,12 @@ struct PackageApplied : public CPackForClient //94
 
 	ui8 result; //0 - something went wrong, request hasn't been realized; 1 - OK
 	ui32 packType; //type id of applied package
+	ui32 requestID; //an ID given by client to the request that was applied
 	ui8 player;
 
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
-		h & result & packType & player;
+		h & result & packType & requestID & player;
 	}
 };
 

+ 44 - 18
server/CGameHandler.cpp

@@ -627,29 +627,36 @@ void CGameHandler::handleConnection(std::set<int> players, CConnection &c)
 {
 	setThreadName(-1, "CGameHandler::handleConnection");
 	srand(time(NULL));
-	CPack *pack = NULL;
-	ui8 player = 255;
+
 	try
 	{
 		while(1)//server should never shut connection first //was: while(!end2)
 		{
+			CPack *pack = NULL;
+			ui8 player = 255;
+			si32 requestID = -999;
+			int packType = 0;
+
 			{
 				boost::unique_lock<boost::mutex> lock(*c.rmx);
-				c >> player >> pack; //get the package
-				tlog5 << "Received client message of type " << typeid(*pack).name() << std::endl;
+				c >> player >> requestID >> pack; //get the package
+				packType = typeList.getTypeID(pack); //get the id of type
+				
+				tlog5 << boost::format("Received client message (request %d by player %d) of type with ID=%d (%s).\n")
+					% requestID % player % packType % typeid(*pack).name();
 			}
 
-			int packType = typeList.getTypeID(pack); //get the id of type
+			//prepare struct informing that action was applied
+			PackageApplied applied;
+			applied.player = player;
+			applied.result = false;
+			applied.packType = packType;
+			applied.requestID = requestID;
+
 			CBaseForGHApply *apply = applier->apps[packType]; //and appropriae applier object
-			if(packType != typeList.getTypeID<QueryReply>() 
-				&&   (packType != typeList.getTypeID<ArrangeStacks>() || !isAllowedArrangePack((ArrangeStacks*)pack)) // for dialogs like garrison
-				&&   states.getQueriesCount(player))
+			if(isBlockedByQueries(pack, packType, player))
 			{
 				complain(boost::str(boost::format("Player %d has to answer queries  before attempting any further actions (count=%d)!") % (int)player % states.getQueriesCount(player)));
-				PackageApplied applied;
-				applied.player = player;
-				applied.result = false;
-				applied.packType = packType;
 				{
 					boost::unique_lock<boost::mutex> lock(*c.wmx);
 					c << &applied;
@@ -661,10 +668,7 @@ void CGameHandler::handleConnection(std::set<int> players, CConnection &c)
 				tlog5 << "Message successfully applied (result=" << result << ")!\n";
 
 				//send confirmation that we've applied the package
-				PackageApplied applied;
-				applied.player = player;
 				applied.result = result;
-				applied.packType = packType;
 				{
 					boost::unique_lock<boost::mutex> lock(*c.wmx);
 					c << &applied;
@@ -674,9 +678,8 @@ void CGameHandler::handleConnection(std::set<int> players, CConnection &c)
 			{
 				tlog1 << "Message cannot be applied, cannot find applier (unregistered type)!\n";
 			}
-			delete pack;
-			pack = NULL;
-			player = 255;
+
+			vstd::clear_pointer(pack);
 		}
 	}
 	catch(boost::system::system_error &e) //for boost errors just log, not crash - probably client shut down connection
@@ -4793,6 +4796,12 @@ void CGameHandler::handleAfterAttackCasting( const BattleAttack & bat )
 	const CStack * attacker = gs->curB->getStack(bat.stackAttacking);
 	attackCasting(bat, Bonus::SPELL_AFTER_ATTACK, attacker);
 
+	if(bat.bsa[0].newAmount <= 0)
+	{
+		//don't try death stare or acid breath on dead stack (crash!)
+		return;
+	}
+
 	if (attacker->hasBonusOfType(Bonus::DEATH_STARE)) // spell id 79
 	{
 		int staredCreatures = 0;
@@ -4815,6 +4824,7 @@ void CGameHandler::handleAfterAttackCasting( const BattleAttack & bat )
 				!attacker->attackerOwned, attacker->owner, NULL, NULL, staredCreatures, ECastingMode::AFTER_ATTACK_CASTING, attacker);
 		}
 	}
+
 	int acidDamage = 0;
 	TBonusListPtr acidBreath = attacker->getBonuses(Selector::type(Bonus::ACID_BREATH));
 	BOOST_FOREACH(const Bonus *b, *acidBreath)
@@ -5621,6 +5631,22 @@ void CGameHandler::spawnWanderingMonsters(int creatureID)
 	}
 }
 
+bool CGameHandler::isBlockedByQueries(const CPack *pack, int packType, ui8 player)
+{
+	//it's always legal to send query reply (we'll check later if it makes sense)
+	if(packType == typeList.getTypeID<QueryReply>())
+		return false;
+
+	if(packType == typeList.getTypeID<ArrangeStacks>() && isAllowedArrangePack((const ArrangeStacks*)pack))
+		return false;
+
+	//if there are no queries, nothing is blocking
+	if(states.getQueriesCount(player) == 0)
+		return false;
+
+	return true; //block package
+}
+
 CasualtiesAfterBattle::CasualtiesAfterBattle(const CArmedInstance *army, BattleInfo *bat)
 {
 	int color = army->tempOwner;

+ 1 - 0
server/CGameHandler.h

@@ -98,6 +98,7 @@ public:
 	std::map<ui32, boost::function<void()> > garrisonCallbacks; //query id => callback - for garrison dialogs
 	std::map<ui32, std::pair<si32,si32> > allowedExchanges;
 
+	bool isBlockedByQueries(const CPack *pack, int packType, ui8 player); 
 	bool isAllowedExchange(int id1, int id2);
 	bool isAllowedArrangePack(const ArrangeStacks *pack);
 	void giveSpells(const CGTownInstance *t, const CGHeroInstance *h);

+ 10 - 16
server/VCMI_server.vcxproj

@@ -62,17 +62,12 @@
   </ImportGroup>
   <PropertyGroup Label="UserMacros" />
   <PropertyGroup>
-    <_ProjectFileVersion>10.0.30319.1</_ProjectFileVersion>
-    <OutDir Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">..\..</OutDir>
-    <IntDir Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(Configuration)_VC9\</IntDir>
+    <_ProjectFileVersion>10.0.30128.1</_ProjectFileVersion>
+    <OutDir Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(SolutionDir)</OutDir>
     <OutDir Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(SolutionDir)</OutDir>
     <IntDir Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(Configuration)\</IntDir>
-    <OutDir Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(SolutionDir)$(Configuration)\</OutDir>
-    <IntDir Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(Configuration)_VC9\</IntDir>
-    <OutDir Condition="'$(Configuration)|$(Platform)'=='RD|Win32'">..\..\RD</OutDir>
-    <IntDir Condition="'$(Configuration)|$(Platform)'=='RD|Win32'">$(Configuration)_VC9\</IntDir>
     <IntDir Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(Configuration)\</IntDir>
-    <OutDir Condition="'$(Configuration)|$(Platform)'=='RD|Win32'">G:\Programowanie\VCMI\RD</OutDir>
+    <OutDir Condition="'$(Configuration)|$(Platform)'=='RD|Win32'">$(SolutionDir)$(Configuration)\bin\</OutDir>
     <OutDir Condition="'$(Configuration)|$(Platform)'=='RD|x64'">$(SolutionDir)$(Configuration)\bin\</OutDir>
     <IntDir Condition="'$(Configuration)|$(Platform)'=='RD|Win32'">$(Configuration)\</IntDir>
     <IntDir Condition="'$(Configuration)|$(Platform)'=='RD|x64'">$(Configuration)\</IntDir>
@@ -112,8 +107,8 @@
       <PrecompiledHeaderFile>StdInc.h</PrecompiledHeaderFile>
     </ClCompile>
     <Link>
-      <AdditionalDependencies>VCMI_lib.lib;zdll.lib;%(AdditionalDependencies)</AdditionalDependencies>
-      <AdditionalLibraryDirectories>../..;../../libs;../;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+      <AdditionalDependencies>VCMI_lib.lib;zlib.lib;%(AdditionalDependencies)</AdditionalDependencies>
+      <AdditionalLibraryDirectories>$(OutDir);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
       <GenerateDebugInformation>true</GenerateDebugInformation>
       <TargetMachine>MachineX86</TargetMachine>
     </Link>
@@ -160,14 +155,13 @@
     </ClCompile>
     <Link>
       <AdditionalDependencies>VCMI_lib.lib;zlib.lib;%(AdditionalDependencies)</AdditionalDependencies>
-      <AdditionalLibraryDirectories>G:\Programowanie\VCMI\libs;$(OutDir);G:\Programowanie\VCMI\RD;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+      <AdditionalLibraryDirectories>$(OutDir);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
       <GenerateDebugInformation>true</GenerateDebugInformation>
       <GenerateMapFile>true</GenerateMapFile>
       <OptimizeReferences>true</OptimizeReferences>
       <EnableCOMDATFolding>true</EnableCOMDATFolding>
       <TargetMachine>MachineX86</TargetMachine>
       <Profile>true</Profile>
-      <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
     </Link>
   </ItemDefinitionGroup>
   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='RD|x64'">
@@ -178,7 +172,7 @@
       <FavorSizeOrSpeed>Speed</FavorSizeOrSpeed>
       <OmitFramePointers>false</OmitFramePointers>
       <EnableFiberSafeOptimizations>true</EnableFiberSafeOptimizations>
-      <AdditionalIncludeDirectories>F:\Programowanie\VCMI\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories>%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <StringPooling>true</StringPooling>
       <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
       <FunctionLevelLinking>false</FunctionLevelLinking>
@@ -191,12 +185,13 @@
       <PrecompiledHeaderFile>StdInc.h</PrecompiledHeaderFile>
     </ClCompile>
     <Link>
-      <AdditionalDependencies>VCMI_lib.lib;zdll.lib;%(AdditionalDependencies)</AdditionalDependencies>
-      <AdditionalLibraryDirectories>F:\Programowanie\VCMI\libs;F:\Programowanie\VCMI\RD;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+      <AdditionalDependencies>VCMI_lib.lib;zlib.lib;%(AdditionalDependencies)</AdditionalDependencies>
+      <AdditionalLibraryDirectories>$(OutDir);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
       <GenerateDebugInformation>true</GenerateDebugInformation>
       <GenerateMapFile>true</GenerateMapFile>
       <OptimizeReferences>true</OptimizeReferences>
       <EnableCOMDATFolding>true</EnableCOMDATFolding>
+      <Profile>true</Profile>
     </Link>
   </ItemDefinitionGroup>
   <ItemGroup>
@@ -220,7 +215,6 @@
   <ItemGroup>
     <ProjectReference Include="..\lib\VCMI_lib.vcxproj">
       <Project>{b952ffc5-3039-4de1-9f08-90acda483d8f}</Project>
-      <ReferenceOutputAssembly>false</ReferenceOutputAssembly>
     </ProjectReference>
   </ItemGroup>
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />