浏览代码

Merged fixes from the branch.

Michał W. Urbańczyk 12 年之前
父节点
当前提交
0abbe71b25

+ 10 - 14
AI/EmptyAI/EmptyAI.vcxproj

@@ -68,40 +68,38 @@
   </ImportGroup>
   <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
     <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+    <Import Project="..\..\VCMI_global_debug.props" />
     <Import Project="..\..\VCMI_global.props" />
   </ImportGroup>
   <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="PropertySheets">
     <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+    <Import Project="..\..\VCMI_global_debug.props" />
     <Import Project="..\..\VCMI_global.props" />
   </ImportGroup>
   <ImportGroup Condition="'$(Configuration)|$(Platform)'=='RD|Win32'" Label="PropertySheets">
     <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+    <Import Project="..\..\VCMI_global_release.props" />
     <Import Project="..\..\VCMI_global.props" />
   </ImportGroup>
   <ImportGroup Condition="'$(Configuration)|$(Platform)'=='RD|x64'" Label="PropertySheets">
     <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+    <Import Project="..\..\VCMI_global_release.props" />
     <Import Project="..\..\VCMI_global.props" />
   </ImportGroup>
   <PropertyGroup Label="UserMacros" />
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
-    <OutDir>..\..\..\AI</OutDir>
+    <OutDir>$(VCMI_Out)\AI\</OutDir>
     <IncludePath>$(IncludePath)</IncludePath>
     <LibraryPath>$(LibraryPath)</LibraryPath>
   </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
-    <OutDir>$(SolutionDir)\AI\</OutDir>
-    <IncludePath>$(IncludePath)</IncludePath>
-    <LibraryPath>$(LibraryPath)</LibraryPath>
+    <OutDir>$(VCMI_Out)\AI\</OutDir>
   </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='RD|Win32'">
-    <OutDir>$(SolutionDir)$(Configuration)\bin\AI\</OutDir>
-    <IncludePath>$(IncludePath)</IncludePath>
-    <LibraryPath>$(LibraryPath)</LibraryPath>
+    <OutDir>$(VCMI_Out)\AI\</OutDir>
   </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='RD|x64'">
-    <OutDir>$(SolutionDir)$(Configuration)\bin\AI\</OutDir>
-    <IncludePath>$(IncludePath)</IncludePath>
-    <LibraryPath>$(LibraryPath)</LibraryPath>
+    <OutDir>$(VCMI_Out)\AI\</OutDir>
   </PropertyGroup>
   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
     <ClCompile>
@@ -116,7 +114,6 @@
     <Link>
       <GenerateDebugInformation>true</GenerateDebugInformation>
       <AdditionalDependencies>VCMI_lib.lib;%(AdditionalDependencies)</AdditionalDependencies>
-      <AdditionalLibraryDirectories>../..;../../../libs;../../..;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
       <OutputFile>$(OutDir)EmptyAI.dll</OutputFile>
     </Link>
   </ItemDefinitionGroup>
@@ -132,7 +129,6 @@
     <Link>
       <GenerateDebugInformation>true</GenerateDebugInformation>
       <AdditionalDependencies>VCMI_lib.lib;%(AdditionalDependencies)</AdditionalDependencies>
-      <AdditionalLibraryDirectories>$(OutDir)..;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
       <OutputFile>$(OutDir)EmptyAI.dll</OutputFile>
     </Link>
   </ItemDefinitionGroup>
@@ -146,13 +142,13 @@
       <PrecompiledHeader>Use</PrecompiledHeader>
       <PrecompiledHeaderFile>StdInc.h</PrecompiledHeaderFile>
       <PreprocessorDefinitions>_WINDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <AdditionalOptions>/Zm130 %(AdditionalOptions)</AdditionalOptions>
     </ClCompile>
     <Link>
       <GenerateDebugInformation>true</GenerateDebugInformation>
       <EnableCOMDATFolding>true</EnableCOMDATFolding>
       <OptimizeReferences>true</OptimizeReferences>
       <AdditionalDependencies>VCMI_lib.lib;%(AdditionalDependencies)</AdditionalDependencies>
-      <AdditionalLibraryDirectories>G:\Programowanie\VCMI\RD;../../../libs;../../;E:\vcmi\rep - assembla\trunk;E:\C++\lua bin;E:\C++\boost_1_43_0\lib;E:\C++\SDL_mixer-1.2.7\lib;E:\C++\SDL_ttf-2.0.8\lib;E:\C++\zlib 1.2.3 binaries\lib;E:\C++\SDL-1.2.11\devlibs - visual\SDL-1.2.11\lib;F:\C++\SDL_Image 1.2.5\SDL_image-1.2.5\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
       <OutputFile>$(OutDir)EmptyAI.dll</OutputFile>
     </Link>
   </ItemDefinitionGroup>
@@ -166,13 +162,13 @@
       <PrecompiledHeader>Use</PrecompiledHeader>
       <PrecompiledHeaderFile>StdInc.h</PrecompiledHeaderFile>
       <PreprocessorDefinitions>_WINDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <AdditionalOptions>/Zm130 %(AdditionalOptions)</AdditionalOptions>
     </ClCompile>
     <Link>
       <GenerateDebugInformation>true</GenerateDebugInformation>
       <EnableCOMDATFolding>true</EnableCOMDATFolding>
       <OptimizeReferences>true</OptimizeReferences>
       <AdditionalDependencies>VCMI_lib.lib;%(AdditionalDependencies)</AdditionalDependencies>
-      <AdditionalLibraryDirectories>$(OutDir)..;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
       <OutputFile>$(OutDir)EmptyAI.dll</OutputFile>
     </Link>
   </ItemDefinitionGroup>

+ 33 - 7
AI/VCAI/VCAI.cpp

@@ -493,12 +493,15 @@ void VCAI::showThievesGuildWindow (const CGObjectInstance * obj)
 	NET_EVENT_HANDLER;
 }
 
-void VCAI::playerBlocked(int reason)
+void VCAI::playerBlocked(int reason, bool start)
 {
-	LOG_TRACE_PARAMS(logAi, "reason '%i'", reason);
+	LOG_TRACE_PARAMS(logAi, "reason '%i', start '%i'", reason % start);
 	NET_EVENT_HANDLER;
-	if (reason == PlayerBlocked::UPCOMING_BATTLE)
+	if (start && reason == PlayerBlocked::UPCOMING_BATTLE)
 		status.setBattle(UPCOMING_BATTLE);
+
+	if(reason == PlayerBlocked::ONGOING_MOVEMENT)
+		status.setMove(start);
 }
 
 void VCAI::showPuzzleMap()
@@ -571,15 +574,17 @@ void VCAI::artifactDisassembled(const ArtifactLocation &al)
 
 void VCAI::heroVisit(const CGHeroInstance *visitor, const CGObjectInstance *visitedObj, bool start)
 {
-	LOG_TRACE_PARAMS(logAi, "start '%i'", start);
+	LOG_TRACE_PARAMS(logAi, "start '%i'; obj '%s'", start % (visitedObj ? visitedObj->hoverName : std::string("n/a")));
 	NET_EVENT_HANDLER;
-	if (start)
+	if(start)
 	{
 		markObjectVisited (visitedObj);
 		erase_if_present(reservedObjs, visitedObj); //unreserve objects
 		erase_if_present(reservedHeroesMap[visitor], visitedObj);
 		completeGoal (CGoal(GET_OBJ).sethero(visitor)); //we don't need to visit in anymore
 	}
+
+	status.heroVisit(visitedObj, start);
 }
 
 void VCAI::availableArtifactsChanged(const CGBlackMarket *bm /*= nullptr*/)
@@ -2629,6 +2634,7 @@ AIStatus::AIStatus()
 {
 	battle = NO_BATTLE;
 	havingTurn = false;
+	ongoingHeroMovement = false;
 }
 
 AIStatus::~AIStatus()
@@ -2701,8 +2707,8 @@ void AIStatus::madeTurn()
 void AIStatus::waitTillFree()
 {
 	boost::unique_lock<boost::mutex> lock(mx);
-	while(battle != NO_BATTLE || remainingQueries.size())
-		cv.wait(lock);
+	while(battle != NO_BATTLE || remainingQueries.size() || objectsBeingVisited.size() || ongoingHeroMovement)
+		cv.timed_wait(lock, boost::posix_time::milliseconds(100));
 }
 
 bool AIStatus::haveTurn()
@@ -2738,6 +2744,26 @@ void AIStatus::receivedAnswerConfirmation(int answerRequestID, int result)
 	}
 }
 
+void AIStatus::heroVisit(const CGObjectInstance *obj, bool started)
+{
+	boost::unique_lock<boost::mutex> lock(mx);
+	if(started)
+		objectsBeingVisited.push_back(obj);
+	else
+	{
+		assert(objectsBeingVisited.size() == 1);
+		objectsBeingVisited.clear();
+	}
+	cv.notify_all();
+}
+
+void AIStatus::setMove(bool ongoing)
+{
+	boost::unique_lock<boost::mutex> lock(mx);
+	ongoingHeroMovement = ongoing;
+	cv.notify_all();
+}
+
 int3 whereToExplore(HeroPtr h)
 {
 	//TODO it's stupid and ineffective, write sth better

+ 5 - 1
AI/VCAI/VCAI.h

@@ -75,6 +75,8 @@ class AIStatus
 	BattleState battle;
 	std::map<QueryID, std::string> remainingQueries;
 	std::map<int, QueryID> requestToQueryID; //IDs of answer-requests sent to server => query ids (so we can match answer confirmation from server to the query)
+	std::vector<const CGObjectInstance*> objectsBeingVisited;
+	bool ongoingHeroMovement;
 
 	bool havingTurn;
 
@@ -82,6 +84,7 @@ public:
 	AIStatus();
 	~AIStatus();
 	void setBattle(BattleState BS);
+	void setMove(bool ongoing);
 	BattleState getBattle();
 	void addQuery(QueryID ID, std::string description);
 	void removeQuery(QueryID ID);
@@ -92,6 +95,7 @@ public:
 	bool haveTurn();
 	void attemptedAnsweringQuery(QueryID queryID, int answerRequestID);
 	void receivedAnswerConfirmation(int answerRequestID, int result);
+	void heroVisit(const CGObjectInstance *obj, bool started);
 
 
 	template <typename Handler> void serialize(Handler &h, const int version)
@@ -327,7 +331,7 @@ public:
 	virtual void artifactAssembled(const ArtifactLocation &al) override;
 	virtual void showTavernWindow(const CGObjectInstance *townOrTavern) override;
 	virtual void showThievesGuildWindow (const CGObjectInstance * obj) override;
-	virtual void playerBlocked(int reason) override;
+	virtual void playerBlocked(int reason, bool start) override;
 	virtual void showPuzzleMap() override;
 	virtual void showShipyardDialog(const IShipyard *obj) override;
 	virtual void gameOver(PlayerColor player, bool victory) override;

+ 8 - 0
client/CPlayerInterface.cpp

@@ -601,6 +601,13 @@ void CPlayerInterface::buildChanged(const CGTownInstance *town, BuildingID build
 	castleInt->townlist->update(town);
 }
 
+void CPlayerInterface::battleStartBefore(const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2)
+{
+	//Don't wait for dialogs when we are non-active hot-seat player
+	if(LOCPLINT == this)
+		waitForAllDialogs();
+}
+
 void CPlayerInterface::battleStart(const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool side)
 {
 	EVENT_HANDLER_CALLED_BY_CLIENT;
@@ -990,6 +997,7 @@ void CPlayerInterface::showInfoDialog(const std::string &text, CComponent * comp
 
 void CPlayerInterface::showInfoDialog(const std::string &text, const std::vector<CComponent*> & components, int soundID, bool delComps)
 {
+	LOG_TRACE_PARAMS(logGlobal, "player=%s, text=%s, is LOCPLINT=%d", playerID % text % (this==LOCPLINT));
 	waitWhileDialog();
 
 	if (settings["session"]["autoSkip"].Bool() && !LOCPLINT->shiftPressed())

+ 1 - 0
client/CPlayerInterface.h

@@ -203,6 +203,7 @@ public:
 	void battleStacksEffectsSet(const SetStackEffect & sse) override; //called when a specific effect is set to stacks
 	void battleTriggerEffect(const BattleTriggerEffect & bte) override; //various one-shot effect
 	void battleStacksAttacked(const std::vector<BattleStackAttacked> & bsa) override;
+	void battleStartBefore(const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2) override; //called by engine just before battle starts; side=0 - left, side=1 - right
 	void battleStart(const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool side) override; //called by engine when battle starts; side=0 - left, side=1 - right
 	void battleStacksHealedRes(const std::vector<std::pair<ui32, ui32> > & healedStacks, bool lifeDrain, bool tentHeal, si32 lifeDrainFrom) override; //called when stacks are healed / resurrected
 	void battleNewStackAppeared(const CStack * stack) override; //not called at the beginning of a battle or by resurrection; called eg. when elemental is summoned

+ 13 - 2
client/NetPacksClient.cpp

@@ -246,7 +246,7 @@ void DisassembledArtifact::applyCl( CClient *cl )
 void HeroVisit::applyCl( CClient *cl )
 {
 	assert(hero);
-	INTERFACE_CALL_IF_PRESENT(hero->tempOwner, heroVisit, hero, obj, starting);
+	INTERFACE_CALL_IF_PRESENT(player, heroVisit, hero, obj, starting);
 }
 
 void NewTurn::applyCl( CClient *cl )
@@ -600,6 +600,17 @@ void ExchangeDialog::applyCl(CClient *cl)
 	INTERFACE_CALL_IF_PRESENT(heroes[0]->tempOwner, heroExchangeStarted, heroes[0]->id, heroes[1]->id, queryID);
 }
 
+void BattleStart::applyFirstCl( CClient *cl )
+{
+	//Cannot use the usual macro because curB is not set yet
+	CALL_ONLY_THAT_BATTLE_INTERFACE(info->sides[0].color, battleStartBefore, info->sides[0].armyObject, info->sides[1].armyObject,
+		info->tile, info->sides[0].hero, info->sides[1].hero);
+	CALL_ONLY_THAT_BATTLE_INTERFACE(info->sides[1].color, battleStartBefore, info->sides[0].armyObject, info->sides[1].armyObject,
+		info->tile, info->sides[0].hero, info->sides[1].hero);
+	BATTLE_INTERFACE_CALL_RECEIVERS(battleStartBefore, info->sides[0].armyObject, info->sides[1].armyObject,
+		info->tile, info->sides[0].hero, info->sides[1].hero);
+}
+
 void BattleStart::applyCl( CClient *cl )
 {
 	cl->battleStarted(info);
@@ -780,7 +791,7 @@ void SystemMessage::applyCl( CClient *cl )
 
 void PlayerBlocked::applyCl( CClient *cl )
 {
-	INTERFACE_CALL_IF_PRESENT(player,playerBlocked,reason);
+	INTERFACE_CALL_IF_PRESENT(player,playerBlocked,reason, startOrEnd==BLOCKADE_STARTED);
 }
 
 void YourTurn::applyCl( CClient *cl )

+ 3 - 1
client/battle/CBattleAnimations.cpp

@@ -85,7 +85,9 @@ CBattleStackAnimation::CBattleStackAnimation(CBattleInterface * owner, const CSt
     : CBattleAnimation(owner),
       myAnim(owner->creAnims[stack->ID]),
       stack(stack)
-{}
+{
+	assert(myAnim);
+}
 
 void CAttackAnimation::nextFrame()
 {

+ 2 - 2
client/battle/CBattleInterface.cpp

@@ -153,7 +153,7 @@ CBattleInterface::CBattleInterface(const CCreatureSet * army1, const CCreatureSe
 	//initializing armies
 	this->army1 = army1;
 	this->army2 = army2;
-	std::vector<const CStack*> stacks = curInt->cb->battleGetAllStacks();
+	std::vector<const CStack*> stacks = curInt->cb->battleGetAllStacks(true);
 	for(const CStack *s : stacks)
 	{
 		newStack(s);
@@ -3652,7 +3652,7 @@ void CBattleInterface::showPiecesOfWall(SDL_Surface * to, std::vector<int> piece
 
 			const CStack *turret = nullptr;
 
-			for(auto & stack : curInt->cb->battleGetAllStacks())
+			for(auto & stack : curInt->cb->battleGetAllStacks(true))
 			{
 				if(stack->position == stackPos)
 				{

+ 8 - 5
lib/CBattleCallback.cpp

@@ -177,11 +177,14 @@ bool CBattleInfoEssentials::battleHasNativeStack(ui8 side) const
 	return false;
 }
 
-TStacks CBattleInfoEssentials::battleGetAllStacks() const /*returns all stacks, alive or dead or undead or mechanical :) */
+TStacks CBattleInfoEssentials::battleGetAllStacks(bool includeTurrets /*= false*/) const /*returns all stacks, alive or dead or undead or mechanical :) */
 {
 	TStacks ret;
 	RETURN_IF_NOT_BATTLE(ret);
 	boost::copy(getBattle()->stacks, std::back_inserter(ret));
+	if(!includeTurrets)
+		vstd::erase_if(ret, [](const CStack *stack) { return stack->type->idNumber == CreatureID::ARROW_TOWERS; });
+
 	return ret;
 }
 
@@ -245,7 +248,7 @@ const CStack* CBattleInfoEssentials::battleGetStackByID(int ID, bool onlyAlive)
 {
 	RETURN_IF_NOT_BATTLE(nullptr);
 
-	for(auto s : battleGetAllStacks())
+	for(auto s : battleGetAllStacks(true))
 		if(s->ID == ID  &&  (!onlyAlive || s->alive()))
 			return s;
 
@@ -512,7 +515,7 @@ SpellID CBattleInfoCallback::battleGetRandomStackSpell(const CStack * stack, ERa
 const CStack* CBattleInfoCallback::battleGetStackByPos(BattleHex pos, bool onlyAlive) const
 {
 	RETURN_IF_NOT_BATTLE(nullptr);
-	for(auto s : battleGetAllStacks())
+	for(auto s : battleGetAllStacks(true))
 		if(vstd::contains(s->getHexes(), pos) && (!onlyAlive || s->alive()))
 				return s;
 
@@ -594,7 +597,7 @@ void CBattleInfoCallback::battleGetStackQueue(std::vector<const CStack *> &out,
 			return;
 	}
 
-	auto allStacks = battleGetAllStacks();
+	auto allStacks = battleGetAllStacks(true);
 	if(!vstd::contains_if(allStacks, [](const CStack *stack) { return stack->willMove(100000); })) //little evil, but 100000 should be enough for all effects to disappear
 	{
 		//No stack will be able to move, battle is over.
@@ -602,7 +605,7 @@ void CBattleInfoCallback::battleGetStackQueue(std::vector<const CStack *> &out,
 		return;
 	}
 
-	for(auto s : battleGetAllStacks())
+	for(auto s : battleGetAllStacks(true))
 	{
 		if((turn <= 0 && !s->willMove()) //we are considering current round and stack won't move
 			|| (turn > 0 && !s->canMove(turn)) //stack won't be able to move in later rounds

+ 1 - 1
lib/CBattleCallback.h

@@ -168,7 +168,7 @@ public:
 	ETerrainType battleTerrainType() const;
 	BFieldType battleGetBattlefieldType() const;
 	std::vector<shared_ptr<const CObstacleInstance> > battleGetAllObstacles(boost::optional<BattlePerspective::BattlePerspective> perspective = boost::none) const; //returns all obstacles on the battlefield
-	TStacks battleGetAllStacks() const; //returns all stacks, alive or dead or undead or mechanical :)
+	TStacks battleGetAllStacks(bool includeTurrets = false) const; //returns all stacks, alive or dead or undead or mechanical :)
 	bool battleHasNativeStack(ui8 side) const;
 	si8 battleGetWallState(int partOfWall) const; //for determining state of a part of the wall; format: parameter [0] - keep, [1] - bottom tower, [2] - bottom wall, [3] - below gate, [4] - over gate, [5] - upper wall, [6] - uppert tower, [7] - gate; returned value: 1 - intact, 2 - damaged, 3 - destroyed; 0 - no battle
 	int battleGetMoatDmg() const; //what dmg unit will suffer if ending turn in the moat

+ 4 - 0
lib/CGameState.cpp

@@ -1369,6 +1369,10 @@ void CGameState::init(StartInfo * si)
 				}
 			}
 
+		//Early check for #1444-like problems
+		for(auto building : vti->builtBuildings)
+			assert(vti->town->buildings[building]);
+
 		//town events
 		for(CCastleEvent &ev : vti->events)
 		{

+ 0 - 1
lib/CObjectHandler.cpp

@@ -2749,7 +2749,6 @@ void CGTownInstance::battleFinished(const CGHeroInstance *hero, const BattleResu
 	}
 }
 
-
 bool CGVisitableOPH::wasVisited (const CGHeroInstance * h) const
 {
 	return vstd::contains(visitors, h->id);

+ 11 - 0
lib/CObjectHandler.h

@@ -634,6 +634,17 @@ public:
 
 		h & town & townAndVis;
 		BONUS_TREE_DESERIALIZATION_FIX
+
+		vstd::erase_if(builtBuildings, [this](BuildingID building) -> bool
+		{
+			if(!town->buildings.count(building) ||  !town->buildings.at(building))
+			{
+				logGlobal->errorStream() << boost::format("#1444-like issue in CGTownInstance::serialize. From town %s at %s removing the bogus builtBuildings item %s")
+					% name % pos % building;
+				return true;
+			}
+			return false;
+		});
 	}
 	//////////////////////////////////////////////////////////////////////////
 

+ 1 - 1
lib/CTownHandler.h

@@ -205,7 +205,7 @@ public:
 		//Fix #1444 corrupted save
 		while(auto badElem = vstd::tryFindIf(buildings, findNull))
 		{
-			std::cout << "#1444-like bug encountered, fixing buildings list by removing bogus entry " << badElem->first << " from " << faction->name << std::endl;
+			logGlobal->errorStream() << "#1444-like bug encountered in CTown::serialize, fixing buildings list by removing bogus entry " << badElem->first << " from " << faction->name;
 			buildings.erase(badElem->first);
 		}
 	}

+ 2 - 1
lib/IGameEventsReceiver.h

@@ -60,6 +60,7 @@ public:
 	virtual void battleSpellCast(const BattleSpellCast *sc){};
 	virtual void battleStacksEffectsSet(const SetStackEffect & sse){};//called when a specific effect is set to stacks
 	virtual void battleTriggerEffect(const BattleTriggerEffect & bte){}; //called for various one-shot effects
+	virtual void battleStartBefore(const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2) {}; //called just before battle start
 	virtual void battleStart(const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool side){}; //called by engine when battle starts; side=0 - left, side=1 - right
 	virtual void battleStacksHealedRes(const std::vector<std::pair<ui32, ui32> > & healedStacks, bool lifeDrain, bool tentHeal, si32 lifeDrainFrom){}; //called when stacks are healed / resurrected first element of pair - stack id, second - healed hp
 	virtual void battleNewStackAppeared(const CStack * stack){}; //not called at the beginning of a battle or by resurrection; called eg. when elemental is summoned
@@ -126,7 +127,7 @@ public:
 	virtual void requestRealized(PackageApplied *pa){};
 	virtual void objectPropertyChanged(const SetObjectProperty * sop){}; //eg. mine has been flagged
 	virtual void objectRemoved(const CGObjectInstance *obj){}; //eg. collected resource, picked artifact, beaten hero
-	virtual void playerBlocked(int reason){}; //reason: 0 - upcoming battle
+	virtual void playerBlocked(int reason, bool start){}; //reason: 0 - upcoming battle
 	virtual void gameOver(PlayerColor player, bool victory){}; //player lost or won the game
 	virtual void playerStartsTurn(PlayerColor player){};
 	virtual void showComp(const Component &comp, std::string message) {}; //display component in the advmapint infobox

+ 9 - 4
lib/NetPacks.h

@@ -230,14 +230,16 @@ struct PlayerBlocked : public CPackForClient //96
 	PlayerBlocked(){type = 96;};
 	void applyCl(CClient *cl);
 
-	enum EReason { UPCOMING_BATTLE };
-
+	enum EReason { UPCOMING_BATTLE, ONGOING_MOVEMENT };
+	enum EMode { BLOCKADE_STARTED, BLOCKADE_ENDED };
+	
 	EReason reason;
+	EMode startOrEnd;
 	PlayerColor player;
 
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
-		h & reason & player;
+		h & reason & startOrEnd & player;
 	}
 };
 
@@ -1090,6 +1092,7 @@ struct HeroVisit : CPackForClient //531
 {
 	const CGHeroInstance *hero;
 	const CGObjectInstance *obj;
+	PlayerColor player; //if hero was killed during the visit, its color is already reset
 	bool starting; //false -> ending
 
 	void applyCl(CClient *cl);
@@ -1097,7 +1100,7 @@ struct HeroVisit : CPackForClient //531
 
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
-		h & hero & obj & starting;
+		h & hero & obj & player & starting;
 	}
 };
 
@@ -1360,6 +1363,8 @@ struct BattleInfo;
 struct BattleStart : public CPackForClient//3000
 {
 	BattleStart(){type = 3000;};
+
+	void applyFirstCl(CClient *cl);
 	void applyCl(CClient *cl);
 	DLL_LINKAGE void applyGs(CGameState *gs);
 

+ 2 - 0
lib/NetPacksLib.cpp

@@ -13,6 +13,7 @@
 #include "CCreatureHandler.h"
 #include "CGameState.h"
 #include "BattleState.h"
+#include "CTownHandler.h"
 
 #undef min
 #undef max
@@ -448,6 +449,7 @@ DLL_LINKAGE void NewStructures::applyGs( CGameState *gs )
 	CGTownInstance *t = gs->getTown(tid);
 	for(const auto & id : bid)
 	{
+		assert(t->town->buildings[id]);
 		t->builtBuildings.insert(id);
 	}
 	t->builded = builded;

+ 27 - 21
server/CGameHandler.cpp

@@ -3253,13 +3253,18 @@ static EndAction end_action;
 
 bool CGameHandler::makeBattleAction( BattleAction &ba )
 {
-    logGlobal->errorStream() << "\tMaking action of type " << ba.actionType;
 	bool ok = true;
-
-
+	
 	const CStack *stack = battleGetStackByID(ba.stackNumber); //may be nullptr if action is not about stack
+	const CStack *destinationStack = ba.actionType == Battle::WALK_AND_ATTACK ? gs->curB->battleGetStackByPos(ba.additionalInfo)
+								   : ba.actionType == Battle::SHOOT			  ? gs->curB->battleGetStackByPos(ba.destinationTile)
+																			  : nullptr;
 	const bool isAboutActiveStack = stack && (stack == battleActiveStack());
 
+	logGlobal->traceStream() << boost::format(
+		"Making action: type=%d; side=%d; stack=%s; dst=%s; additionalInfo=%d; stackAtDst=%s")
+		% ba.actionType % (int)ba.side % (stack ? stack->getName() : std::string("none")) 
+		% ba.destinationTile % ba.additionalInfo % (destinationStack ? destinationStack->getName() : std::string("none"));
 
 	switch(ba.actionType)
 	{
@@ -3370,8 +3375,7 @@ bool CGameHandler::makeBattleAction( BattleAction &ba )
 			StartAction start_action(ba);
 			sendAndApply(&start_action); //start movement and attack
 
-			const CStack *stackAtEnd = gs->curB->battleGetStackByPos(ba.additionalInfo);
-			if(!stack || !stackAtEnd)
+			if(!stack || !destinationStack)
 			{
 				sendAndApply(&end_action);
 				break;
@@ -3380,7 +3384,7 @@ bool CGameHandler::makeBattleAction( BattleAction &ba )
 			BattleHex startingPos = stack->position;
 			int distance = moveStack(ba.stackNumber, ba.destinationTile);
 
-            logGlobal->traceStream() << stack->nodeName() << " will attack " << stackAtEnd->nodeName();
+            logGlobal->traceStream() << stack->nodeName() << " will attack " << destinationStack->nodeName();
 
 			if(stack->position != ba.destinationTile //we wasn't able to reach destination tile
 				&& !(stack->doubleWide()
@@ -3396,12 +3400,12 @@ bool CGameHandler::makeBattleAction( BattleAction &ba )
 				break;
 			}
 
-			if(stackAtEnd && stack->ID == stackAtEnd->ID) //we should just move, it will be handled by following check
+			if(destinationStack && stack->ID == destinationStack->ID) //we should just move, it will be handled by following check
 			{
-				stackAtEnd = nullptr;
+				destinationStack = nullptr;
 			}
 
-			if(!stackAtEnd)
+			if(!destinationStack)
 			{
 				complain(boost::str(boost::format("walk and attack error: no stack at additionalInfo tile (%d)!\n") % ba.additionalInfo));
 				ok = false;
@@ -3409,7 +3413,7 @@ bool CGameHandler::makeBattleAction( BattleAction &ba )
 				break;
 			}
 
-			if( !CStack::isMeleeAttackPossible(stack, stackAtEnd) )
+			if( !CStack::isMeleeAttackPossible(stack, destinationStack) )
 			{
 				complain("Attack cannot be performed!");
 				sendAndApply(&end_action);
@@ -3426,10 +3430,10 @@ bool CGameHandler::makeBattleAction( BattleAction &ba )
 			{
 				if (stack &&
 					stack->alive() && //move can cause death, eg. by walking into the moat
-					stackAtEnd->alive())
+					destinationStack->alive())
 				{
 					BattleAttack bat;
-					prepareAttack(bat, stack, stackAtEnd, (i ? 0 : distance),  ba.additionalInfo); //no distance travelled on second attack
+					prepareAttack(bat, stack, destinationStack, (i ? 0 : distance),  ba.additionalInfo); //no distance travelled on second attack
 					//prepareAttack(bat, stack, stackAtEnd, 0, ba.additionalInfo); 
 					handleAttackBeforeCasting(bat); //only before first attack
 					sendAndApply(&bat);
@@ -3437,13 +3441,13 @@ bool CGameHandler::makeBattleAction( BattleAction &ba )
 				}
 
 				//counterattack
-				if (stackAtEnd
+				if (destinationStack
 					&& !stack->hasBonusOfType(Bonus::BLOCKS_RETALIATION)
-					&& stackAtEnd->ableToRetaliate()
+					&& destinationStack->ableToRetaliate()
 					&& stack->alive()) //attacker may have died (fire shield)
 				{
 					BattleAttack bat;
-					prepareAttack(bat, stackAtEnd, stack, 0, stack->position);
+					prepareAttack(bat, destinationStack, stack, 0, stack->position);
 					bat.flags |= BattleAttack::COUNTER;
 					sendAndApply(&bat);
 					handleAfterAttackCasting(bat);
@@ -3462,7 +3466,6 @@ bool CGameHandler::makeBattleAction( BattleAction &ba )
 		}
 	case Battle::SHOOT:
 		{
-			const CStack *destStack= gs->curB->battleGetStackByPos(ba.destinationTile);
 			if( !gs->curB->battleCanShoot(stack, ba.destinationTile) )
 			{
 				complain("Cannot shoot!");
@@ -3475,7 +3478,7 @@ bool CGameHandler::makeBattleAction( BattleAction &ba )
 			{
 				BattleAttack bat;
 				bat.flags |= BattleAttack::SHOT;
-				prepareAttack(bat, stack, destStack, 0, ba.destinationTile);
+				prepareAttack(bat, stack, destinationStack, 0, ba.destinationTile);
 				handleAttackBeforeCasting(bat);
 				sendAndApply(&bat);
 				handleAfterAttackCasting(bat);
@@ -3485,14 +3488,14 @@ bool CGameHandler::makeBattleAction( BattleAction &ba )
 
 			const CGHeroInstance * attackingHero = gs->curB->battleGetFightingHero(ba.side);
 
-			if( destStack->alive()
+			if( destinationStack->alive()
 			    && (stack->getCreature()->idNumber == CreatureID::BALLISTA)
 			    && (attackingHero->getSecSkillLevel(SecondarySkill::ARTILLERY) >= SecSkillLevel::ADVANCED)
 			   )
 			{
 				BattleAttack bat2;
 				bat2.flags |= BattleAttack::SHOT;
-				prepareAttack(bat2, stack, destStack, 0, ba.destinationTile);
+				prepareAttack(bat2, stack, destinationStack, 0, ba.destinationTile);
 				sendAndApply(&bat2);
 			}
 			//allow more than one additional attack
@@ -3503,13 +3506,13 @@ bool CGameHandler::makeBattleAction( BattleAction &ba )
 			{
 				if(
 					stack->alive()
-					&& destStack->alive()
+					&& destinationStack->alive()
 					&& stack->shots
 					)
 				{
 					BattleAttack bat;
 					bat.flags |= BattleAttack::SHOT;
-					prepareAttack(bat, stack, destStack, 0, ba.destinationTile);
+					prepareAttack(bat, stack, destinationStack, 0, ba.destinationTile);
 					sendAndApply(&bat);
 					handleAfterAttackCasting(bat);
 				}
@@ -4983,6 +4986,7 @@ void CGameHandler::objectVisited( const CGObjectInstance * obj, const CGHeroInst
 	HeroVisit hv;
 	hv.obj = obj;
 	hv.hero = h;
+	hv.player = h->tempOwner;
 	hv.starting = true;
 	sendAndApply(&hv);
 
@@ -4996,6 +5000,7 @@ void CGameHandler::objectVisitEnded(const CObjectVisitQuery &query)
 	logGlobal->traceStream() << query.visitingHero->nodeName() << " visit ends.\n";
 
 	HeroVisit hv;
+	hv.player = query.players.front();
 	hv.obj = nullptr; //not necessary, moreover may have been deleted in the meantime
 	hv.hero = query.visitingHero;
 	assert(hv.hero);
@@ -5059,6 +5064,7 @@ void CGameHandler::engageIntoBattle( PlayerColor player )
 	PlayerBlocked pb;
 	pb.player = player;
 	pb.reason = PlayerBlocked::UPCOMING_BATTLE;
+	pb.startOrEnd = PlayerBlocked::BLOCKADE_STARTED;
 	sendAndApply(&pb);
 }
 

+ 24 - 0
server/CQuery.cpp

@@ -86,6 +86,11 @@ void CQuery::onExposure(CGameHandler *gh, QueryPtr topQuery)
 	gh->queries.popQuery(*this);
 }
 
+void CQuery::onAdding(CGameHandler *gh, PlayerColor color)
+{
+
+}
+
 CObjectVisitQuery::CObjectVisitQuery(const CGObjectInstance *Obj, const CGHeroInstance *Hero, int3 Tile)
 	: visitedObject(Obj), visitingHero(Hero), tile(Tile), removeObjectAfterVisit(false)
 {
@@ -169,6 +174,7 @@ void Queries::addQuery(QueryPtr query)
 void Queries::addQuery(PlayerColor player, QueryPtr query)
 {
 	LOG_TRACE_PARAMS(logGlobal, "player='%s', query='%s'", player % query);
+	query->onAdding(gh, player);
 	queries[player].push_back(query);
 }
 
@@ -372,3 +378,21 @@ void CHeroMovementQuery::onExposure(CGameHandler *gh, QueryPtr topQuery)
 
 	gh->queries.popIfTop(*this);
 }
+
+void CHeroMovementQuery::onRemoval(CGameHandler *gh, PlayerColor color)
+{
+	PlayerBlocked pb;
+	pb.player = color;
+	pb.reason = PlayerBlocked::ONGOING_MOVEMENT;
+	pb.startOrEnd = PlayerBlocked::BLOCKADE_ENDED;
+	gh->sendAndApply(&pb);
+}
+
+void CHeroMovementQuery::onAdding(CGameHandler *gh, PlayerColor color)
+{
+	PlayerBlocked pb;
+	pb.player = color;
+	pb.reason = PlayerBlocked::ONGOING_MOVEMENT;
+	pb.startOrEnd = PlayerBlocked::BLOCKADE_STARTED;
+	gh->sendAndApply(&pb);
+}

+ 3 - 0
server/CQuery.h

@@ -36,6 +36,7 @@ public:
 	virtual bool blocksPack(const CPack *pack) const; //query can block attempting actions by player. Eg. he can't move hero during the battle.
 
 	virtual bool endsByPlayerAnswer() const; //query is removed after player gives answer (like dialogs)
+	virtual void onAdding(CGameHandler *gh, PlayerColor color); //called just before query is pushed on stack
 	virtual void onRemoval(CGameHandler *gh, PlayerColor color); //called after query is removed from stack
 	virtual void onExposure(CGameHandler *gh, QueryPtr topQuery);//called when query immediately above is removed and this is exposed (becomes top)
 	virtual std::string toString() const;
@@ -98,6 +99,8 @@ public:
 	virtual void onExposure(CGameHandler *gh, QueryPtr topQuery);
 
 	CHeroMovementQuery(const TryMoveHero &Tmh, const CGHeroInstance *Hero, bool VisitDestAfterVictory = false);
+	virtual void onAdding(CGameHandler *gh, PlayerColor color) override;
+	virtual void onRemoval(CGameHandler *gh, PlayerColor color) override;
 };
 
 class CDialogQuery : public CQuery