Переглянути джерело

Added support for concurrent battles to gamestate and server

Ivan Savenko 2 роки тому
батько
коміт
fc4dfda00f

+ 7 - 3
lib/CGameInfoCallback.cpp

@@ -201,8 +201,10 @@ int32_t CGameInfoCallback::getSpellCost(const spells::Spell * sp, const CGHeroIn
 	//boost::shared_lock<boost::shared_mutex> lock(*gs->mx);
 	//boost::shared_lock<boost::shared_mutex> lock(*gs->mx);
 	ERROR_RET_VAL_IF(!canGetFullInfo(caster), "Cannot get info about caster!", -1);
 	ERROR_RET_VAL_IF(!canGetFullInfo(caster), "Cannot get info about caster!", -1);
 	//if there is a battle
 	//if there is a battle
-	if(gs->curB)
-		return gs->curB->battleGetSpellCost(sp, caster);
+	auto casterBattle = gs->getBattle(caster->getOwner());
+
+	if(casterBattle)
+		return casterBattle->battleGetSpellCost(sp, caster);
 
 
 	//if there is no battle
 	//if there is no battle
 	return caster->getSpellCost(sp);
 	return caster->getSpellCost(sp);
@@ -303,7 +305,9 @@ bool CGameInfoCallback::getHeroInfo(const CGObjectInstance * hero, InfoAboutHero
 
 
 	if (infoLevel == InfoAboutHero::EInfoLevel::BASIC)
 	if (infoLevel == InfoAboutHero::EInfoLevel::BASIC)
 	{
 	{
-		if(gs->curB && gs->curB->playerHasAccessToHeroInfo(*player, h)) //if it's battle we can get enemy hero full data
+		auto ourBattle = gs->getBattle(*player);
+
+		if(ourBattle && ourBattle->playerHasAccessToHeroInfo(*player, h)) //if it's battle we can get enemy hero full data
 			infoLevel = InfoAboutHero::EInfoLevel::INBATTLE;
 			infoLevel = InfoAboutHero::EInfoLevel::INBATTLE;
 		else
 		else
 			ERROR_RET_VAL_IF(!isVisible(h->visitablePos()), "That hero is not visible!", false);
 			ERROR_RET_VAL_IF(!isVisible(h->visitablePos()), "That hero is not visible!", false);

+ 1 - 1
lib/CGameInfoCallback.h

@@ -46,7 +46,7 @@ class CGDwelling;
 class CGTeleport;
 class CGTeleport;
 class CGTownInstance;
 class CGTownInstance;
 
 
-class DLL_LINKAGE IGameInfoCallback
+class DLL_LINKAGE IGameInfoCallback : boost::noncopyable
 {
 {
 public:
 public:
 	//TODO: all other public methods of CGameInfoCallback
 	//TODO: all other public methods of CGameInfoCallback

+ 49 - 0
lib/NetPacks.h

@@ -1483,12 +1483,14 @@ struct DLL_LINKAGE BattleStart : public CPackForClient
 {
 {
 	void applyGs(CGameState * gs) const;
 	void applyGs(CGameState * gs) const;
 
 
+	BattleID battleID = BattleID::NONE;
 	BattleInfo * info = nullptr;
 	BattleInfo * info = nullptr;
 
 
 	virtual void visitTyped(ICPackVisitor & visitor) override;
 	virtual void visitTyped(ICPackVisitor & visitor) override;
 
 
 	template <typename Handler> void serialize(Handler & h, const int version)
 	template <typename Handler> void serialize(Handler & h, const int version)
 	{
 	{
+		h & battleID;
 		h & info;
 		h & info;
 	}
 	}
 };
 };
@@ -1496,12 +1498,15 @@ struct DLL_LINKAGE BattleStart : public CPackForClient
 struct DLL_LINKAGE BattleNextRound : public CPackForClient
 struct DLL_LINKAGE BattleNextRound : public CPackForClient
 {
 {
 	void applyGs(CGameState * gs) const;
 	void applyGs(CGameState * gs) const;
+
+	BattleID battleID = BattleID::NONE;
 	si32 round = 0;
 	si32 round = 0;
 
 
 	virtual void visitTyped(ICPackVisitor & visitor) override;
 	virtual void visitTyped(ICPackVisitor & visitor) override;
 
 
 	template <typename Handler> void serialize(Handler & h, const int version)
 	template <typename Handler> void serialize(Handler & h, const int version)
 	{
 	{
+		h & battleID;
 		h & round;
 		h & round;
 	}
 	}
 };
 };
@@ -1510,6 +1515,7 @@ struct DLL_LINKAGE BattleSetActiveStack : public CPackForClient
 {
 {
 	void applyGs(CGameState * gs) const;
 	void applyGs(CGameState * gs) const;
 
 
+	BattleID battleID = BattleID::NONE;
 	ui32 stack = 0;
 	ui32 stack = 0;
 	ui8 askPlayerInterface = true;
 	ui8 askPlayerInterface = true;
 
 
@@ -1517,6 +1523,7 @@ struct DLL_LINKAGE BattleSetActiveStack : public CPackForClient
 
 
 	template <typename Handler> void serialize(Handler & h, const int version)
 	template <typename Handler> void serialize(Handler & h, const int version)
 	{
 	{
+		h & battleID;
 		h & stack;
 		h & stack;
 		h & askPlayerInterface;
 		h & askPlayerInterface;
 	}
 	}
@@ -1542,11 +1549,14 @@ struct DLL_LINKAGE BattleResultAccepted : public CPackForClient
 			h & exp;
 			h & exp;
 		}
 		}
 	};
 	};
+
+	BattleID battleID = BattleID::NONE;
 	std::array<HeroBattleResults, 2> heroResult;
 	std::array<HeroBattleResults, 2> heroResult;
 	ui8 winnerSide;
 	ui8 winnerSide;
 
 
 	template <typename Handler> void serialize(Handler & h, const int version)
 	template <typename Handler> void serialize(Handler & h, const int version)
 	{
 	{
+		h & battleID;
 		h & heroResult;
 		h & heroResult;
 		h & winnerSide;
 		h & winnerSide;
 	}
 	}
@@ -1556,6 +1566,7 @@ struct DLL_LINKAGE BattleResult : public Query
 {
 {
 	void applyFirstCl(CClient * cl);
 	void applyFirstCl(CClient * cl);
 
 
+	BattleID battleID = BattleID::NONE;
 	EBattleResult result = EBattleResult::NORMAL;
 	EBattleResult result = EBattleResult::NORMAL;
 	ui8 winner = 2; //0 - attacker, 1 - defender, [2 - draw (should be possible?)]
 	ui8 winner = 2; //0 - attacker, 1 - defender, [2 - draw (should be possible?)]
 	std::map<ui32, si32> casualties[2]; //first => casualties of attackers - map crid => number
 	std::map<ui32, si32> casualties[2]; //first => casualties of attackers - map crid => number
@@ -1566,6 +1577,7 @@ struct DLL_LINKAGE BattleResult : public Query
 
 
 	template <typename Handler> void serialize(Handler & h, const int version)
 	template <typename Handler> void serialize(Handler & h, const int version)
 	{
 	{
+		h & battleID;
 		h & queryID;
 		h & queryID;
 		h & result;
 		h & result;
 		h & winner;
 		h & winner;
@@ -1578,6 +1590,7 @@ struct DLL_LINKAGE BattleResult : public Query
 
 
 struct DLL_LINKAGE BattleLogMessage : public CPackForClient
 struct DLL_LINKAGE BattleLogMessage : public CPackForClient
 {
 {
+	BattleID battleID = BattleID::NONE;
 	std::vector<MetaString> lines;
 	std::vector<MetaString> lines;
 
 
 	void applyGs(CGameState * gs);
 	void applyGs(CGameState * gs);
@@ -1587,12 +1600,14 @@ struct DLL_LINKAGE BattleLogMessage : public CPackForClient
 
 
 	template <typename Handler> void serialize(Handler & h, const int version)
 	template <typename Handler> void serialize(Handler & h, const int version)
 	{
 	{
+		h & battleID;
 		h & lines;
 		h & lines;
 	}
 	}
 };
 };
 
 
 struct DLL_LINKAGE BattleStackMoved : public CPackForClient
 struct DLL_LINKAGE BattleStackMoved : public CPackForClient
 {
 {
+	BattleID battleID = BattleID::NONE;
 	ui32 stack = 0;
 	ui32 stack = 0;
 	std::vector<BattleHex> tilesToMove;
 	std::vector<BattleHex> tilesToMove;
 	int distance = 0;
 	int distance = 0;
@@ -1605,6 +1620,7 @@ struct DLL_LINKAGE BattleStackMoved : public CPackForClient
 
 
 	template <typename Handler> void serialize(Handler & h, const int version)
 	template <typename Handler> void serialize(Handler & h, const int version)
 	{
 	{
+		h & battleID;
 		h & stack;
 		h & stack;
 		h & tilesToMove;
 		h & tilesToMove;
 		h & distance;
 		h & distance;
@@ -1617,12 +1633,14 @@ struct DLL_LINKAGE BattleUnitsChanged : public CPackForClient
 	void applyGs(CGameState * gs);
 	void applyGs(CGameState * gs);
 	void applyBattle(IBattleState * battleState);
 	void applyBattle(IBattleState * battleState);
 
 
+	BattleID battleID = BattleID::NONE;
 	std::vector<UnitChanges> changedStacks;
 	std::vector<UnitChanges> changedStacks;
 
 
 	virtual void visitTyped(ICPackVisitor & visitor) override;
 	virtual void visitTyped(ICPackVisitor & visitor) override;
 
 
 	template <typename Handler> void serialize(Handler & h, const int version)
 	template <typename Handler> void serialize(Handler & h, const int version)
 	{
 	{
+		h & battleID;
 		h & changedStacks;
 		h & changedStacks;
 	}
 	}
 };
 };
@@ -1632,6 +1650,7 @@ struct BattleStackAttacked
 	DLL_LINKAGE void applyGs(CGameState * gs);
 	DLL_LINKAGE void applyGs(CGameState * gs);
 	DLL_LINKAGE void applyBattle(IBattleState * battleState);
 	DLL_LINKAGE void applyBattle(IBattleState * battleState);
 
 
+	BattleID battleID = BattleID::NONE;
 	ui32 stackAttacked = 0, attackerID = 0;
 	ui32 stackAttacked = 0, attackerID = 0;
 	ui32 killedAmount = 0;
 	ui32 killedAmount = 0;
 	int64_t damageAmount = 0;
 	int64_t damageAmount = 0;
@@ -1668,6 +1687,7 @@ struct BattleStackAttacked
 
 
 	template <typename Handler> void serialize(Handler & h, const int version)
 	template <typename Handler> void serialize(Handler & h, const int version)
 	{
 	{
+		h & battleID;
 		h & stackAttacked;
 		h & stackAttacked;
 		h & attackerID;
 		h & attackerID;
 		h & newState;
 		h & newState;
@@ -1687,6 +1707,7 @@ struct DLL_LINKAGE BattleAttack : public CPackForClient
 	void applyGs(CGameState * gs);
 	void applyGs(CGameState * gs);
 	BattleUnitsChanged attackerChanges;
 	BattleUnitsChanged attackerChanges;
 
 
+	BattleID battleID = BattleID::NONE;
 	std::vector<BattleStackAttacked> bsa;
 	std::vector<BattleStackAttacked> bsa;
 	ui32 stackAttacking = 0;
 	ui32 stackAttacking = 0;
 	ui32 flags = 0; //uses Eflags (below)
 	ui32 flags = 0; //uses Eflags (below)
@@ -1732,6 +1753,7 @@ struct DLL_LINKAGE BattleAttack : public CPackForClient
 
 
 	template <typename Handler> void serialize(Handler & h, const int version)
 	template <typename Handler> void serialize(Handler & h, const int version)
 	{
 	{
+		h & battleID;
 		h & bsa;
 		h & bsa;
 		h & stackAttacking;
 		h & stackAttacking;
 		h & flags;
 		h & flags;
@@ -1751,12 +1773,14 @@ struct DLL_LINKAGE StartAction : public CPackForClient
 	void applyFirstCl(CClient * cl);
 	void applyFirstCl(CClient * cl);
 	void applyGs(CGameState * gs);
 	void applyGs(CGameState * gs);
 
 
+	BattleID battleID = BattleID::NONE;
 	BattleAction ba;
 	BattleAction ba;
 
 
 	virtual void visitTyped(ICPackVisitor & visitor) override;
 	virtual void visitTyped(ICPackVisitor & visitor) override;
 
 
 	template <typename Handler> void serialize(Handler & h, const int version)
 	template <typename Handler> void serialize(Handler & h, const int version)
 	{
 	{
+		h & battleID;
 		h & ba;
 		h & ba;
 	}
 	}
 };
 };
@@ -1765,14 +1789,19 @@ struct DLL_LINKAGE EndAction : public CPackForClient
 {
 {
 	virtual void visitTyped(ICPackVisitor & visitor) override;
 	virtual void visitTyped(ICPackVisitor & visitor) override;
 
 
+	BattleID battleID = BattleID::NONE;
+
 	template <typename Handler> void serialize(Handler & h, const int version)
 	template <typename Handler> void serialize(Handler & h, const int version)
 	{
 	{
+		h & battleID;
 	}
 	}
 };
 };
 
 
 struct DLL_LINKAGE BattleSpellCast : public CPackForClient
 struct DLL_LINKAGE BattleSpellCast : public CPackForClient
 {
 {
 	void applyGs(CGameState * gs) const;
 	void applyGs(CGameState * gs) const;
+
+	BattleID battleID = BattleID::NONE;
 	bool activeCast = true;
 	bool activeCast = true;
 	ui8 side = 0; //which hero did cast spell: 0 - attacker, 1 - defender
 	ui8 side = 0; //which hero did cast spell: 0 - attacker, 1 - defender
 	SpellID spellID; //id of spell
 	SpellID spellID; //id of spell
@@ -1788,6 +1817,7 @@ struct DLL_LINKAGE BattleSpellCast : public CPackForClient
 
 
 	template <typename Handler> void serialize(Handler & h, const int version)
 	template <typename Handler> void serialize(Handler & h, const int version)
 	{
 	{
+		h & battleID;
 		h & side;
 		h & side;
 		h & spellID;
 		h & spellID;
 		h & manaGained;
 		h & manaGained;
@@ -1805,6 +1835,8 @@ struct DLL_LINKAGE SetStackEffect : public CPackForClient
 {
 {
 	void applyGs(CGameState * gs);
 	void applyGs(CGameState * gs);
 	void applyBattle(IBattleState * battleState);
 	void applyBattle(IBattleState * battleState);
+
+	BattleID battleID = BattleID::NONE;
 	std::vector<std::pair<ui32, std::vector<Bonus>>> toAdd;
 	std::vector<std::pair<ui32, std::vector<Bonus>>> toAdd;
 	std::vector<std::pair<ui32, std::vector<Bonus>>> toUpdate;
 	std::vector<std::pair<ui32, std::vector<Bonus>>> toUpdate;
 	std::vector<std::pair<ui32, std::vector<Bonus>>> toRemove;
 	std::vector<std::pair<ui32, std::vector<Bonus>>> toRemove;
@@ -1813,6 +1845,7 @@ struct DLL_LINKAGE SetStackEffect : public CPackForClient
 
 
 	template <typename Handler> void serialize(Handler & h, const int version)
 	template <typename Handler> void serialize(Handler & h, const int version)
 	{
 	{
+		h & battleID;
 		h & toAdd;
 		h & toAdd;
 		h & toUpdate;
 		h & toUpdate;
 		h & toRemove;
 		h & toRemove;
@@ -1824,23 +1857,27 @@ struct DLL_LINKAGE StacksInjured : public CPackForClient
 	void applyGs(CGameState * gs);
 	void applyGs(CGameState * gs);
 	void applyBattle(IBattleState * battleState);
 	void applyBattle(IBattleState * battleState);
 
 
+	BattleID battleID = BattleID::NONE;
 	std::vector<BattleStackAttacked> stacks;
 	std::vector<BattleStackAttacked> stacks;
 
 
 	virtual void visitTyped(ICPackVisitor & visitor) override;
 	virtual void visitTyped(ICPackVisitor & visitor) override;
 
 
 	template <typename Handler> void serialize(Handler & h, const int version)
 	template <typename Handler> void serialize(Handler & h, const int version)
 	{
 	{
+		h & battleID;
 		h & stacks;
 		h & stacks;
 	}
 	}
 };
 };
 
 
 struct DLL_LINKAGE BattleResultsApplied : public CPackForClient
 struct DLL_LINKAGE BattleResultsApplied : public CPackForClient
 {
 {
+	BattleID battleID = BattleID::NONE;
 	PlayerColor player1, player2;
 	PlayerColor player1, player2;
 	virtual void visitTyped(ICPackVisitor & visitor) override;
 	virtual void visitTyped(ICPackVisitor & visitor) override;
 
 
 	template <typename Handler> void serialize(Handler & h, const int version)
 	template <typename Handler> void serialize(Handler & h, const int version)
 	{
 	{
+		h & battleID;
 		h & player1;
 		h & player1;
 		h & player2;
 		h & player2;
 	}
 	}
@@ -1851,12 +1888,14 @@ struct DLL_LINKAGE BattleObstaclesChanged : public CPackForClient
 	void applyGs(CGameState * gs);
 	void applyGs(CGameState * gs);
 	void applyBattle(IBattleState * battleState);
 	void applyBattle(IBattleState * battleState);
 
 
+	BattleID battleID = BattleID::NONE;
 	std::vector<ObstacleChanges> changes;
 	std::vector<ObstacleChanges> changes;
 
 
 	virtual void visitTyped(ICPackVisitor & visitor) override;
 	virtual void visitTyped(ICPackVisitor & visitor) override;
 
 
 	template <typename Handler> void serialize(Handler & h, const int version)
 	template <typename Handler> void serialize(Handler & h, const int version)
 	{
 	{
+		h & battleID;
 		h & changes;
 		h & changes;
 	}
 	}
 };
 };
@@ -1883,6 +1922,7 @@ struct DLL_LINKAGE CatapultAttack : public CPackForClient
 	void applyGs(CGameState * gs);
 	void applyGs(CGameState * gs);
 	void applyBattle(IBattleState * battleState);
 	void applyBattle(IBattleState * battleState);
 
 
+	BattleID battleID = BattleID::NONE;
 	std::vector< AttackInfo > attackedParts;
 	std::vector< AttackInfo > attackedParts;
 	int attacker = -1; //if -1, then a spell caused this
 	int attacker = -1; //if -1, then a spell caused this
 
 
@@ -1890,6 +1930,7 @@ struct DLL_LINKAGE CatapultAttack : public CPackForClient
 
 
 	template <typename Handler> void serialize(Handler & h, const int version)
 	template <typename Handler> void serialize(Handler & h, const int version)
 	{
 	{
+		h & battleID;
 		h & attackedParts;
 		h & attackedParts;
 		h & attacker;
 		h & attacker;
 	}
 	}
@@ -1901,6 +1942,7 @@ struct DLL_LINKAGE BattleSetStackProperty : public CPackForClient
 
 
 	void applyGs(CGameState * gs) const;
 	void applyGs(CGameState * gs) const;
 
 
+	BattleID battleID = BattleID::NONE;
 	int stackID = 0;
 	int stackID = 0;
 	BattleStackProperty which = CASTS;
 	BattleStackProperty which = CASTS;
 	int val = 0;
 	int val = 0;
@@ -1908,6 +1950,7 @@ struct DLL_LINKAGE BattleSetStackProperty : public CPackForClient
 
 
 	template <typename Handler> void serialize(Handler & h, const int version)
 	template <typename Handler> void serialize(Handler & h, const int version)
 	{
 	{
+		h & battleID;
 		h & stackID;
 		h & stackID;
 		h & which;
 		h & which;
 		h & val;
 		h & val;
@@ -1923,6 +1966,7 @@ struct DLL_LINKAGE BattleTriggerEffect : public CPackForClient
 {
 {
 	void applyGs(CGameState * gs) const; //effect
 	void applyGs(CGameState * gs) const; //effect
 
 
+	BattleID battleID = BattleID::NONE;
 	int stackID = 0;
 	int stackID = 0;
 	int effect = 0; //use corresponding Bonus type
 	int effect = 0; //use corresponding Bonus type
 	int val = 0;
 	int val = 0;
@@ -1930,6 +1974,7 @@ struct DLL_LINKAGE BattleTriggerEffect : public CPackForClient
 
 
 	template <typename Handler> void serialize(Handler & h, const int version)
 	template <typename Handler> void serialize(Handler & h, const int version)
 	{
 	{
+		h & battleID;
 		h & stackID;
 		h & stackID;
 		h & effect;
 		h & effect;
 		h & val;
 		h & val;
@@ -1944,9 +1989,11 @@ struct DLL_LINKAGE BattleUpdateGateState : public CPackForClient
 {
 {
 	void applyGs(CGameState * gs) const;
 	void applyGs(CGameState * gs) const;
 
 
+	BattleID battleID = BattleID::NONE;
 	EGateState state = EGateState::NONE;
 	EGateState state = EGateState::NONE;
 	template <typename Handler> void serialize(Handler & h, const int version)
 	template <typename Handler> void serialize(Handler & h, const int version)
 	{
 	{
+		h & battleID;
 		h & state;
 		h & state;
 	}
 	}
 
 
@@ -2533,6 +2580,7 @@ struct DLL_LINKAGE MakeAction : public CPackForServer
 	{
 	{
 	}
 	}
 	BattleAction ba;
 	BattleAction ba;
+	BattleID battleID;
 
 
 	virtual void visitTyped(ICPackVisitor & visitor) override;
 	virtual void visitTyped(ICPackVisitor & visitor) override;
 
 
@@ -2540,6 +2588,7 @@ struct DLL_LINKAGE MakeAction : public CPackForServer
 	{
 	{
 		h & static_cast<CPackForServer &>(*this);
 		h & static_cast<CPackForServer &>(*this);
 		h & ba;
 		h & ba;
+		h & battleID;
 	}
 	}
 };
 };
 
 

+ 36 - 43
lib/NetPacksLib.cpp

@@ -34,11 +34,8 @@
 #include "campaign/CampaignState.h"
 #include "campaign/CampaignState.h"
 #include "GameSettings.h"
 #include "GameSettings.h"
 
 
-
 VCMI_LIB_NAMESPACE_BEGIN
 VCMI_LIB_NAMESPACE_BEGIN
 
 
-#define THROW_IF_NO_BATTLE if (!gs->curB) throw std::runtime_error("Trying to apply pack when no battle!");
-
 void CPack::visit(ICPackVisitor & visitor)
 void CPack::visit(ICPackVisitor & visitor)
 {
 {
 	visitBasic(visitor);
 	visitBasic(visitor);
@@ -962,7 +959,7 @@ void GiveBonus::applyGs(CGameState *gs)
 		break;
 		break;
 	case ETarget::BATTLE:
 	case ETarget::BATTLE:
 		assert(Bonus::OneBattle(&bonus));
 		assert(Bonus::OneBattle(&bonus));
-		cbsn = dynamic_cast<CBonusSystemNode*>(gs->curB.get());
+		cbsn = dynamic_cast<CBonusSystemNode*>(gs->getBattle(BattleID(id)));
 		break;
 		break;
 	}
 	}
 
 
@@ -2115,26 +2112,29 @@ void CommanderLevelUp::applyGs(CGameState * gs) const
 
 
 void BattleStart::applyGs(CGameState * gs) const
 void BattleStart::applyGs(CGameState * gs) const
 {
 {
-	gs->curB = info;
-	gs->curB->localInit();
+	assert(battleID == gs->nextBattleID);
+
+	gs->currentBattles.emplace_back(info);
+
+	info->battleID = gs->nextBattleID;
+	info->localInit();
+
+	vstd::next(gs->nextBattleID, 1);
 }
 }
 
 
 void BattleNextRound::applyGs(CGameState * gs) const
 void BattleNextRound::applyGs(CGameState * gs) const
 {
 {
-	THROW_IF_NO_BATTLE
-	gs->curB->nextRound(round);
+	gs->getBattle(battleID)->nextRound(round);
 }
 }
 
 
 void BattleSetActiveStack::applyGs(CGameState * gs) const
 void BattleSetActiveStack::applyGs(CGameState * gs) const
 {
 {
-	THROW_IF_NO_BATTLE
-	gs->curB->nextTurn(stack);
+	gs->getBattle(battleID)->nextTurn(stack);
 }
 }
 
 
 void BattleTriggerEffect::applyGs(CGameState * gs) const
 void BattleTriggerEffect::applyGs(CGameState * gs) const
 {
 {
-	THROW_IF_NO_BATTLE
-	CStack * st = gs->curB->getStack(stackID);
+	CStack * st = gs->getBattle(battleID)->getStack(stackID);
 	assert(st);
 	assert(st);
 	switch(static_cast<BonusType>(effect))
 	switch(static_cast<BonusType>(effect))
 	{
 	{
@@ -2173,8 +2173,8 @@ void BattleTriggerEffect::applyGs(CGameState * gs) const
 
 
 void BattleUpdateGateState::applyGs(CGameState * gs) const
 void BattleUpdateGateState::applyGs(CGameState * gs) const
 {
 {
-	if(gs->curB)
-		gs->curB->si.gateState = state;
+	if(gs->getBattle(battleID))
+		gs->getBattle(battleID)->si.gateState = state;
 }
 }
 
 
 void BattleResultAccepted::applyGs(CGameState * gs) const
 void BattleResultAccepted::applyGs(CGameState * gs) const
@@ -2214,7 +2214,13 @@ void BattleResultAccepted::applyGs(CGameState * gs) const
 		CBonusSystemNode::treeHasChanged();
 		CBonusSystemNode::treeHasChanged();
 	}
 	}
 
 
-	gs->curB.dellNull();
+	auto currentBattle = boost::range::find_if(gs->currentBattles, [&](const auto & battle)
+	{
+		return battle->battleID == battleID;
+	});
+
+	assert(currentBattle != gs->currentBattles.end());
+	gs->currentBattles.erase(currentBattle);
 }
 }
 
 
 void BattleLogMessage::applyGs(CGameState *gs)
 void BattleLogMessage::applyGs(CGameState *gs)
@@ -2229,8 +2235,7 @@ void BattleLogMessage::applyBattle(IBattleState * battleState)
 
 
 void BattleStackMoved::applyGs(CGameState *gs)
 void BattleStackMoved::applyGs(CGameState *gs)
 {
 {
-	THROW_IF_NO_BATTLE
-	applyBattle(gs->curB);
+	applyBattle(gs->getBattle(battleID));
 }
 }
 
 
 void BattleStackMoved::applyBattle(IBattleState * battleState)
 void BattleStackMoved::applyBattle(IBattleState * battleState)
@@ -2240,8 +2245,7 @@ void BattleStackMoved::applyBattle(IBattleState * battleState)
 
 
 void BattleStackAttacked::applyGs(CGameState * gs)
 void BattleStackAttacked::applyGs(CGameState * gs)
 {
 {
-	THROW_IF_NO_BATTLE
-	applyBattle(gs->curB);
+	applyBattle(gs->getBattle(battleID));
 }
 }
 
 
 void BattleStackAttacked::applyBattle(IBattleState * battleState)
 void BattleStackAttacked::applyBattle(IBattleState * battleState)
@@ -2251,8 +2255,7 @@ void BattleStackAttacked::applyBattle(IBattleState * battleState)
 
 
 void BattleAttack::applyGs(CGameState * gs)
 void BattleAttack::applyGs(CGameState * gs)
 {
 {
-	THROW_IF_NO_BATTLE
-	CStack * attacker = gs->curB->getStack(stackAttacking);
+	CStack * attacker = gs->getBattle(battleID)->getStack(stackAttacking);
 	assert(attacker);
 	assert(attacker);
 
 
 	attackerChanges.applyGs(gs);
 	attackerChanges.applyGs(gs);
@@ -2265,17 +2268,15 @@ void BattleAttack::applyGs(CGameState * gs)
 
 
 void StartAction::applyGs(CGameState *gs)
 void StartAction::applyGs(CGameState *gs)
 {
 {
-	THROW_IF_NO_BATTLE
-
-	CStack *st = gs->curB->getStack(ba.stackNumber);
+	CStack *st = gs->getBattle(battleID)->getStack(ba.stackNumber);
 
 
 	if(ba.actionType == EActionType::END_TACTIC_PHASE)
 	if(ba.actionType == EActionType::END_TACTIC_PHASE)
 	{
 	{
-		gs->curB->tacticDistance = 0;
+		gs->getBattle(battleID)->tacticDistance = 0;
 		return;
 		return;
 	}
 	}
 
 
-	if(gs->curB->tacticDistance)
+	if(gs->getBattle(battleID)->tacticDistance)
 	{
 	{
 		// moves in tactics phase do not affect creature status
 		// moves in tactics phase do not affect creature status
 		// (tactics stack queue is managed by client)
 		// (tactics stack queue is managed by client)
@@ -2310,27 +2311,24 @@ void StartAction::applyGs(CGameState *gs)
 	else
 	else
 	{
 	{
 		if(ba.actionType == EActionType::HERO_SPELL)
 		if(ba.actionType == EActionType::HERO_SPELL)
-			gs->curB->sides[ba.side].usedSpellsHistory.push_back(ba.spell);
+			gs->getBattle(battleID)->sides[ba.side].usedSpellsHistory.push_back(ba.spell);
 	}
 	}
 }
 }
 
 
 void BattleSpellCast::applyGs(CGameState * gs) const
 void BattleSpellCast::applyGs(CGameState * gs) const
 {
 {
-	THROW_IF_NO_BATTLE
-
 	if(castByHero)
 	if(castByHero)
 	{
 	{
 		if(side < 2)
 		if(side < 2)
 		{
 		{
-			gs->curB->sides[side].castSpellsCount++;
+			gs->getBattle(battleID)->sides[side].castSpellsCount++;
 		}
 		}
 	}
 	}
 }
 }
 
 
 void SetStackEffect::applyGs(CGameState *gs)
 void SetStackEffect::applyGs(CGameState *gs)
 {
 {
-	THROW_IF_NO_BATTLE
-	applyBattle(gs->curB);
+	applyBattle(gs->getBattle(battleID));
 }
 }
 
 
 void SetStackEffect::applyBattle(IBattleState * battleState)
 void SetStackEffect::applyBattle(IBattleState * battleState)
@@ -2348,8 +2346,7 @@ void SetStackEffect::applyBattle(IBattleState * battleState)
 
 
 void StacksInjured::applyGs(CGameState *gs)
 void StacksInjured::applyGs(CGameState *gs)
 {
 {
-	THROW_IF_NO_BATTLE
-	applyBattle(gs->curB);
+	applyBattle(gs->getBattle(battleID));
 }
 }
 
 
 void StacksInjured::applyBattle(IBattleState * battleState)
 void StacksInjured::applyBattle(IBattleState * battleState)
@@ -2360,8 +2357,7 @@ void StacksInjured::applyBattle(IBattleState * battleState)
 
 
 void BattleUnitsChanged::applyGs(CGameState *gs)
 void BattleUnitsChanged::applyGs(CGameState *gs)
 {
 {
-	THROW_IF_NO_BATTLE
-	applyBattle(gs->curB);
+	applyBattle(gs->getBattle(battleID));
 }
 }
 
 
 void BattleUnitsChanged::applyBattle(IBattleState * battleState)
 void BattleUnitsChanged::applyBattle(IBattleState * battleState)
@@ -2391,8 +2387,7 @@ void BattleUnitsChanged::applyBattle(IBattleState * battleState)
 
 
 void BattleObstaclesChanged::applyGs(CGameState * gs)
 void BattleObstaclesChanged::applyGs(CGameState * gs)
 {
 {
-	THROW_IF_NO_BATTLE;
-	applyBattle(gs->curB);
+	applyBattle(gs->getBattle(battleID));
 }
 }
 
 
 void BattleObstaclesChanged::applyBattle(IBattleState * battleState)
 void BattleObstaclesChanged::applyBattle(IBattleState * battleState)
@@ -2423,8 +2418,7 @@ CatapultAttack::~CatapultAttack() = default;
 
 
 void CatapultAttack::applyGs(CGameState * gs)
 void CatapultAttack::applyGs(CGameState * gs)
 {
 {
-	THROW_IF_NO_BATTLE
-	applyBattle(gs->curB);
+	applyBattle(gs->getBattle(battleID));
 }
 }
 
 
 void CatapultAttack::visitTyped(ICPackVisitor & visitor)
 void CatapultAttack::visitTyped(ICPackVisitor & visitor)
@@ -2450,8 +2444,7 @@ void CatapultAttack::applyBattle(IBattleState * battleState)
 
 
 void BattleSetStackProperty::applyGs(CGameState * gs) const
 void BattleSetStackProperty::applyGs(CGameState * gs) const
 {
 {
-	THROW_IF_NO_BATTLE
-	CStack * stack = gs->curB->getStack(stackID);
+	CStack * stack = gs->getBattle(battleID)->getStack(stackID);
 	switch(which)
 	switch(which)
 	{
 	{
 		case CASTS:
 		case CASTS:
@@ -2464,7 +2457,7 @@ void BattleSetStackProperty::applyGs(CGameState * gs) const
 		}
 		}
 		case ENCHANTER_COUNTER:
 		case ENCHANTER_COUNTER:
 		{
 		{
-			auto & counter = gs->curB->sides[gs->curB->whatSide(stack->unitOwner())].enchanterCounter;
+			auto & counter = gs->getBattle(battleID)->sides[gs->getBattle(battleID)->whatSide(stack->unitOwner())].enchanterCounter;
 			if(absolute)
 			if(absolute)
 				counter = val;
 				counter = val;
 			else
 			else

+ 1 - 1
lib/battle/BattleInfo.cpp

@@ -26,7 +26,7 @@
 VCMI_LIB_NAMESPACE_BEGIN
 VCMI_LIB_NAMESPACE_BEGIN
 
 
 ///BattleInfo
 ///BattleInfo
-std::pair< std::vector<BattleHex>, int > BattleInfo::getPath(BattleHex start, BattleHex dest, const battle::Unit * stack)
+std::pair< std::vector<BattleHex>, int > BattleInfo::getPath(BattleHex start, BattleHex dest, const battle::Unit * stack) const
 {
 {
 	auto reachability = getReachability(stack);
 	auto reachability = getReachability(stack);
 
 

+ 4 - 1
lib/battle/BattleInfo.h

@@ -26,6 +26,8 @@ class BattleField;
 class DLL_LINKAGE BattleInfo : public CBonusSystemNode, public CBattleInfoCallback, public IBattleState
 class DLL_LINKAGE BattleInfo : public CBonusSystemNode, public CBattleInfoCallback, public IBattleState
 {
 {
 public:
 public:
+	BattleID battleID = BattleID(0);
+
 	enum BattleSide
 	enum BattleSide
 	{
 	{
 		ATTACKER = 0,
 		ATTACKER = 0,
@@ -49,6 +51,7 @@ public:
 
 
 	template <typename Handler> void serialize(Handler &h, const int version)
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
 	{
+		h & battleID;
 		h & sides;
 		h & sides;
 		h & round;
 		h & round;
 		h & activeStack;
 		h & activeStack;
@@ -137,7 +140,7 @@ public:
 	using CBattleInfoEssentials::battleGetFightingHero;
 	using CBattleInfoEssentials::battleGetFightingHero;
 	CGHeroInstance * battleGetFightingHero(ui8 side) const;
 	CGHeroInstance * battleGetFightingHero(ui8 side) const;
 
 
-	std::pair< std::vector<BattleHex>, int > getPath(BattleHex start, BattleHex dest, const battle::Unit * stack); //returned value: pair<path, length>; length may be different than number of elements in path since flying creatures jump between distant hexes
+	std::pair< std::vector<BattleHex>, int > getPath(BattleHex start, BattleHex dest, const battle::Unit * stack) const; //returned value: pair<path, length>; length may be different than number of elements in path since flying creatures jump between distant hexes
 
 
 	void calculateCasualties(std::map<ui32,si32> * casualties) const; //casualties are array of maps size 2 (attacker, defeneder), maps are (crid => amount)
 	void calculateCasualties(std::map<ui32,si32> * casualties) const; //casualties are array of maps size 2 (attacker, defeneder), maps are (crid => amount)
 
 

+ 1 - 0
lib/constants/EntityIdentifiers.cpp

@@ -38,6 +38,7 @@
 
 
 VCMI_LIB_NAMESPACE_BEGIN
 VCMI_LIB_NAMESPACE_BEGIN
 
 
+const BattleID BattleID::NONE = BattleID(-1);
 const QueryID QueryID::NONE = QueryID(-1);
 const QueryID QueryID::NONE = QueryID(-1);
 const HeroTypeID HeroTypeID::NONE = HeroTypeID(-1);
 const HeroTypeID HeroTypeID::NONE = HeroTypeID(-1);
 const ObjectInstanceID ObjectInstanceID::NONE = ObjectInstanceID(-1);
 const ObjectInstanceID ObjectInstanceID::NONE = ObjectInstanceID(-1);

+ 6 - 0
lib/constants/EntityIdentifiers.h

@@ -180,6 +180,12 @@ public:
 	DLL_LINKAGE static const QueryID NONE;
 	DLL_LINKAGE static const QueryID NONE;
 };
 };
 
 
+class BattleID : public Identifier<QueryID>
+{
+public:
+	using Identifier<QueryID>::Identifier;
+	DLL_LINKAGE static const BattleID NONE;
+};
 class ObjectInstanceID : public Identifier<ObjectInstanceID>
 class ObjectInstanceID : public Identifier<ObjectInstanceID>
 {
 {
 public:
 public:

+ 33 - 4
lib/gameState/CGameState.cpp

@@ -395,7 +395,6 @@ CGameState::CGameState()
 
 
 CGameState::~CGameState()
 CGameState::~CGameState()
 {
 {
-	curB.dellNull();
 	map.dellNull();
 	map.dellNull();
 }
 }
 
 
@@ -1218,11 +1217,41 @@ void CGameState::initVisitingAndGarrisonedHeroes()
 	}
 	}
 }
 }
 
 
+const BattleInfo * CGameState::getBattle(const PlayerColor & player) const
+{
+	if (!player.isValidPlayer())
+		return nullptr;
+
+	for (const auto & battlePtr : currentBattles)
+		if (battlePtr->sides[0].color == player || battlePtr->sides[1].color == player)
+			return battlePtr.get();
+
+	return nullptr;
+}
+
+const BattleInfo * CGameState::getBattle(const BattleID & battle) const
+{
+	for (const auto & battlePtr : currentBattles)
+		if (battlePtr->battleID == battle)
+			return battlePtr.get();
+
+	return nullptr;
+}
+
+BattleInfo * CGameState::getBattle(const BattleID & battle)
+{
+	for (const auto & battlePtr : currentBattles)
+		if (battlePtr->battleID == battle)
+			return battlePtr.get();
+
+	return nullptr;
+}
+
 BattleField CGameState::battleGetBattlefieldType(int3 tile, CRandomGenerator & rand)
 BattleField CGameState::battleGetBattlefieldType(int3 tile, CRandomGenerator & rand)
 {
 {
-	if(!tile.valid() && curB)
-		tile = curB->tile;
-	else if(!tile.valid() && !curB)
+	assert(tile.valid());
+
+	if(!tile.valid())
 		return BattleField::NONE;
 		return BattleField::NONE;
 
 
 	const TerrainTile &t = map->getTile(tile);
 	const TerrainTile &t = map->getTile(tile);

+ 12 - 1
lib/gameState/CGameState.h

@@ -83,6 +83,11 @@ class DLL_LINKAGE CGameState : public CNonConstInfoCallback
 	friend class CGameStateCampaign;
 	friend class CGameStateCampaign;
 
 
 public:
 public:
+	/// List of currently ongoing battles
+	std::vector<std::unique_ptr<BattleInfo>> currentBattles;
+	/// ID that can be allocated to next battle
+	BattleID nextBattleID = BattleID(0);
+
 	//we have here all heroes available on this map that are not hired
 	//we have here all heroes available on this map that are not hired
 	std::unique_ptr<TavernHeroesPool> heroesPool;
 	std::unique_ptr<TavernHeroesPool> heroesPool;
 
 
@@ -98,7 +103,6 @@ public:
 	void updateOnLoad(StartInfo * si);
 	void updateOnLoad(StartInfo * si);
 
 
 	ConstTransitivePtr<StartInfo> scenarioOps, initialOpts; //second one is a copy of settings received from pregame (not randomized)
 	ConstTransitivePtr<StartInfo> scenarioOps, initialOpts; //second one is a copy of settings received from pregame (not randomized)
-	ConstTransitivePtr<BattleInfo> curB; //current battle
 	ui32 day; //total number of days in game
 	ui32 day; //total number of days in game
 	ConstTransitivePtr<CMap> map;
 	ConstTransitivePtr<CMap> map;
 	std::map<PlayerColor, PlayerState> players;
 	std::map<PlayerColor, PlayerState> players;
@@ -124,6 +128,13 @@ public:
 	std::vector<CGObjectInstance*> guardingCreatures (int3 pos) const;
 	std::vector<CGObjectInstance*> guardingCreatures (int3 pos) const;
 	void updateRumor();
 	void updateRumor();
 
 
+	/// Returns battle in which selected player is engaged, or nullptr if none.
+	/// Can NOT be used with neutral player, use battle by ID instead
+	const BattleInfo * getBattle(const PlayerColor & player) const;
+	/// Returns battle by its unique identifier, or nullptr if not found
+	const BattleInfo * getBattle(const BattleID & battle) const;
+	BattleInfo * getBattle(const BattleID & battle);
+
 	// ----- victory, loss condition checks -----
 	// ----- victory, loss condition checks -----
 
 
 	EVictoryLossCheckResult checkForVictoryAndLoss(const PlayerColor & player) const;
 	EVictoryLossCheckResult checkForVictoryAndLoss(const PlayerColor & player) const;

+ 2 - 0
server/CGameHandler.cpp

@@ -40,6 +40,7 @@
 #include "../lib/VCMI_Lib.h"
 #include "../lib/VCMI_Lib.h"
 #include "../lib/int3.h"
 #include "../lib/int3.h"
 
 
+#include "../lib/battle/BattleInfo.h"
 #include "../lib/filesystem/FileInfo.h"
 #include "../lib/filesystem/FileInfo.h"
 #include "../lib/filesystem/Filesystem.h"
 #include "../lib/filesystem/Filesystem.h"
 #include "../lib/gameState/CGameState.h"
 #include "../lib/gameState/CGameState.h"
@@ -1007,6 +1008,7 @@ void CGameHandler::run(bool resume)
 		clockLast += clockDuration;
 		clockLast += clockDuration;
 		turnTimerHandler.update(timePassed);
 		turnTimerHandler.update(timePassed);
 		boost::this_thread::sleep_for(boost::chrono::milliseconds(100));
 		boost::this_thread::sleep_for(boost::chrono::milliseconds(100));
+
 	}
 	}
 }
 }
 
 

+ 1 - 1
server/NetPacksServer.cpp

@@ -276,7 +276,7 @@ void ApplyGhNetPackVisitor::visitMakeAction(MakeAction & pack)
 {
 {
 	gh.throwIfWrongPlayer(&pack);
 	gh.throwIfWrongPlayer(&pack);
 
 
-	result = gh.battles->makePlayerBattleAction(pack.player, pack.ba);
+	result = gh.battles->makePlayerBattleAction(pack.battleID, pack.player, pack.ba);
 }
 }
 
 
 void ApplyGhNetPackVisitor::visitDigWithHero(DigWithHero & pack)
 void ApplyGhNetPackVisitor::visitDigWithHero(DigWithHero & pack)

+ 42 - 27
server/TurnTimerHandler.cpp

@@ -91,8 +91,8 @@ void TurnTimerHandler::update(int waitTime)
 			if(gs->isPlayerMakingTurn(player))
 			if(gs->isPlayerMakingTurn(player))
 				onPlayerMakingTurn(player, waitTime);
 				onPlayerMakingTurn(player, waitTime);
 		
 		
-		if(gs->curB)
-			onBattleLoop(waitTime);
+		for (auto & battle : gs->currentBattles)
+			onBattleLoop(battle->battleID, waitTime);
 	}
 	}
 }
 }
 
 
@@ -140,11 +140,11 @@ void TurnTimerHandler::onPlayerMakingTurn(PlayerColor player, int waitTime)
 	}
 	}
 }
 }
 
 
-bool TurnTimerHandler::isPvpBattle() const
+bool TurnTimerHandler::isPvpBattle(const BattleID & battleID) const
 {
 {
 	const auto * gs = gameHandler.gameState();
 	const auto * gs = gameHandler.gameState();
-	auto attacker = gs->curB->getSidePlayer(BattleSide::ATTACKER);
-	auto defender = gs->curB->getSidePlayer(BattleSide::DEFENDER);
+	auto attacker = gs->getBattle(battleID)->getSidePlayer(BattleSide::ATTACKER);
+	auto defender = gs->getBattle(battleID)->getSidePlayer(BattleSide::DEFENDER);
 	if(attacker.isValidPlayer() && defender.isValidPlayer())
 	if(attacker.isValidPlayer() && defender.isValidPlayer())
 	{
 	{
 		const auto * attackerState = gameHandler.getPlayerState(attacker);
 		const auto * attackerState = gameHandler.getPlayerState(attacker);
@@ -155,18 +155,18 @@ bool TurnTimerHandler::isPvpBattle() const
 	return false;
 	return false;
 }
 }
 
 
-void TurnTimerHandler::onBattleStart()
+void TurnTimerHandler::onBattleStart(const BattleID & battleID)
 {
 {
 	std::lock_guard<std::recursive_mutex> guard(mx);
 	std::lock_guard<std::recursive_mutex> guard(mx);
 	const auto * gs = gameHandler.gameState();
 	const auto * gs = gameHandler.gameState();
 	const auto * si = gameHandler.getStartInfo();
 	const auto * si = gameHandler.getStartInfo();
-	if(!si || !gs || !gs->curB)
+	if(!si || !gs)
 		return;
 		return;
 
 
-	auto attacker = gs->curB->getSidePlayer(BattleSide::ATTACKER);
-	auto defender = gs->curB->getSidePlayer(BattleSide::DEFENDER);
+	auto attacker = gs->getBattle(battleID)->getSidePlayer(BattleSide::ATTACKER);
+	auto defender = gs->getBattle(battleID)->getSidePlayer(BattleSide::DEFENDER);
 	
 	
-	bool pvpBattle = isPvpBattle();
+	bool pvpBattle = isPvpBattle(battleID);
 	
 	
 	for(auto i : {attacker, defender})
 	for(auto i : {attacker, defender})
 	{
 	{
@@ -183,18 +183,24 @@ void TurnTimerHandler::onBattleStart()
 	}
 	}
 }
 }
 
 
-void TurnTimerHandler::onBattleEnd()
+void TurnTimerHandler::onBattleEnd(const BattleID & battleID)
 {
 {
 	std::lock_guard<std::recursive_mutex> guard(mx);
 	std::lock_guard<std::recursive_mutex> guard(mx);
 	const auto * gs = gameHandler.gameState();
 	const auto * gs = gameHandler.gameState();
 	const auto * si = gameHandler.getStartInfo();
 	const auto * si = gameHandler.getStartInfo();
-	if(!si || !gs || !gs->curB)
+	if(!si || !gs)
+	{
+		assert(0);
 		return;
 		return;
+	}
 
 
-	auto attacker = gs->curB->getSidePlayer(BattleSide::ATTACKER);
-	auto defender = gs->curB->getSidePlayer(BattleSide::DEFENDER);
+	if (!si->turnTimerInfo.isBattleEnabled())
+		return;
 	
 	
-	bool pvpBattle = isPvpBattle();
+	auto attacker = gs->getBattle(battleID)->getSidePlayer(BattleSide::ATTACKER);
+	auto defender = gs->getBattle(battleID)->getSidePlayer(BattleSide::DEFENDER);
+	
+	bool pvpBattle = isPvpBattle(battleID);
 	
 	
 	for(auto i : {attacker, defender})
 	for(auto i : {attacker, defender})
 	{
 	{
@@ -216,15 +222,21 @@ void TurnTimerHandler::onBattleEnd()
 	}
 	}
 }
 }
 
 
-void TurnTimerHandler::onBattleNextStack(const CStack & stack)
+void TurnTimerHandler::onBattleNextStack(const BattleID & battleID, const CStack & stack)
 {
 {
 	std::lock_guard<std::recursive_mutex> guard(mx);
 	std::lock_guard<std::recursive_mutex> guard(mx);
 	const auto * gs = gameHandler.gameState();
 	const auto * gs = gameHandler.gameState();
 	const auto * si = gameHandler.getStartInfo();
 	const auto * si = gameHandler.getStartInfo();
-	if(!si || !gs || !gs->curB || !si->turnTimerInfo.isBattleEnabled())
+	if(!si || !gs || !gs->getBattle(battleID))
+	{
+		assert(0);
+		return;
+	}
+	
+	if (!si->turnTimerInfo.isBattleEnabled())
 		return;
 		return;
 	
 	
-	if(isPvpBattle())
+	if(isPvpBattle(battleID))
 	{
 	{
 		auto player = stack.getOwner();
 		auto player = stack.getOwner();
 		
 		
@@ -237,29 +249,32 @@ void TurnTimerHandler::onBattleNextStack(const CStack & stack)
 	}
 	}
 }
 }
 
 
-void TurnTimerHandler::onBattleLoop(int waitTime)
+void TurnTimerHandler::onBattleLoop(const BattleID & battleID, int waitTime)
 {
 {
 	std::lock_guard<std::recursive_mutex> guard(mx);
 	std::lock_guard<std::recursive_mutex> guard(mx);
 	const auto * gs = gameHandler.gameState();
 	const auto * gs = gameHandler.gameState();
 	const auto * si = gameHandler.getStartInfo();
 	const auto * si = gameHandler.getStartInfo();
-	if(!si || !gs || !gs->curB || !si->turnTimerInfo.isBattleEnabled())
+	if(!si || !gs || !si->turnTimerInfo.isBattleEnabled())
+	{
+		assert(0);
 		return;
 		return;
+	}
 	
 	
 	ui8 side = 0;
 	ui8 side = 0;
 	const CStack * stack = nullptr;
 	const CStack * stack = nullptr;
-	bool isTactisPhase = gs->curB->battleTacticDist() > 0;
+	bool isTactisPhase = gs->getBattle(battleID)->battleTacticDist() > 0;
 	
 	
 	if(isTactisPhase)
 	if(isTactisPhase)
-		side = gs->curB->battleGetTacticsSide();
+		side = gs->getBattle(battleID)->battleGetTacticsSide();
 	else
 	else
 	{
 	{
-		stack = gs->curB->battleGetStackByID(gs->curB->getActiveStackID());
+		stack = gs->getBattle(battleID)->battleGetStackByID(gs->getBattle(battleID)->getActiveStackID());
 		if(!stack || !stack->getOwner().isValidPlayer())
 		if(!stack || !stack->getOwner().isValidPlayer())
 			return;
 			return;
 		side = stack->unitSide();
 		side = stack->unitSide();
 	}
 	}
 	
 	
-	auto player = gs->curB->getSidePlayer(side);
+	auto player = gs->getBattle(battleID)->getSidePlayer(side);
 	if(!player.isValidPlayer())
 	if(!player.isValidPlayer())
 		return;
 		return;
 	
 	
@@ -270,7 +285,7 @@ void TurnTimerHandler::onBattleLoop(int waitTime)
 	auto & timer = timers[player];
 	auto & timer = timers[player];
 	if(timer.isActive && timer.isBattle && !timerCountDown(timer.creatureTimer, si->turnTimerInfo.creatureTimer, player, waitTime))
 	if(timer.isActive && timer.isBattle && !timerCountDown(timer.creatureTimer, si->turnTimerInfo.creatureTimer, player, waitTime))
 	{
 	{
-		if(isPvpBattle())
+		if(isPvpBattle(battleID))
 		{
 		{
 			if(timer.battleTimer > 0)
 			if(timer.battleTimer > 0)
 			{
 			{
@@ -289,7 +304,7 @@ void TurnTimerHandler::onBattleLoop(int waitTime)
 					doNothing.actionType = EActionType::DEFEND;
 					doNothing.actionType = EActionType::DEFEND;
 					doNothing.stackNumber = stack->unitId();
 					doNothing.stackNumber = stack->unitId();
 				}
 				}
-				gameHandler.battles->makePlayerBattleAction(player, doNothing);
+				gameHandler.battles->makePlayerBattleAction(battleID, player, doNothing);
 			}
 			}
 		}
 		}
 		else
 		else
@@ -311,7 +326,7 @@ void TurnTimerHandler::onBattleLoop(int waitTime)
 				BattleAction retreat;
 				BattleAction retreat;
 				retreat.side = side;
 				retreat.side = side;
 				retreat.actionType = EActionType::RETREAT; //harsh punishment
 				retreat.actionType = EActionType::RETREAT; //harsh punishment
-				gameHandler.battles->makePlayerBattleAction(player, retreat);
+				gameHandler.battles->makePlayerBattleAction(battleID, player, retreat);
 			}
 			}
 		}
 		}
 	}
 	}

+ 6 - 5
server/TurnTimerHandler.h

@@ -16,6 +16,7 @@ VCMI_LIB_NAMESPACE_BEGIN
 
 
 class CStack;
 class CStack;
 class PlayerColor;
 class PlayerColor;
+class BattleID;
 
 
 VCMI_LIB_NAMESPACE_END
 VCMI_LIB_NAMESPACE_END
 
 
@@ -33,10 +34,10 @@ class TurnTimerHandler
 	std::recursive_mutex mx;
 	std::recursive_mutex mx;
 	
 	
 	void onPlayerMakingTurn(PlayerColor player, int waitTime);
 	void onPlayerMakingTurn(PlayerColor player, int waitTime);
-	void onBattleLoop(int waitTime);
+	void onBattleLoop(const BattleID & battleID, int waitTime);
 	
 	
 	bool timerCountDown(int & timerToApply, int initialTimer, PlayerColor player, int waitTime);
 	bool timerCountDown(int & timerToApply, int initialTimer, PlayerColor player, int waitTime);
-	bool isPvpBattle() const;
+	bool isPvpBattle(const BattleID & battleID) const;
 	void sendTimerUpdate(PlayerColor player);
 	void sendTimerUpdate(PlayerColor player);
 	
 	
 public:
 public:
@@ -44,9 +45,9 @@ public:
 	
 	
 	void onGameplayStart(PlayerColor player);
 	void onGameplayStart(PlayerColor player);
 	void onPlayerGetTurn(PlayerColor player);
 	void onPlayerGetTurn(PlayerColor player);
-	void onBattleStart();
-	void onBattleNextStack(const CStack & stack);
-	void onBattleEnd();
+	void onBattleStart(const BattleID & battle);
+	void onBattleNextStack(const BattleID & battle, const CStack & stack);
+	void onBattleEnd(const BattleID & battleID);
 	void update(int waitTime);
 	void update(int waitTime);
 	void setTimerEnabled(PlayerColor player, bool enabled);
 	void setTimerEnabled(PlayerColor player, bool enabled);
 	void setEndTurnAllowed(PlayerColor player, bool enabled);
 	void setEndTurnAllowed(PlayerColor player, bool enabled);

+ 115 - 120
server/battles/BattleActionProcessor.cpp

@@ -37,42 +37,42 @@ void BattleActionProcessor::setGameHandler(CGameHandler * newGameHandler)
 	gameHandler = newGameHandler;
 	gameHandler = newGameHandler;
 }
 }
 
 
-bool BattleActionProcessor::doEmptyAction(const BattleAction & ba)
+bool BattleActionProcessor::doEmptyAction(const BattleInfo & battle, const BattleAction & ba)
 {
 {
 	return true;
 	return true;
 }
 }
 
 
-bool BattleActionProcessor::doEndTacticsAction(const BattleAction & ba)
+bool BattleActionProcessor::doEndTacticsAction(const BattleInfo & battle, const BattleAction & ba)
 {
 {
 	return true;
 	return true;
 }
 }
 
 
-bool BattleActionProcessor::doWaitAction(const BattleAction & ba)
+bool BattleActionProcessor::doWaitAction(const BattleInfo & battle, const BattleAction & ba)
 {
 {
 	const CStack * stack = gameHandler->battleGetStackByID(ba.stackNumber);
 	const CStack * stack = gameHandler->battleGetStackByID(ba.stackNumber);
 
 
-	if (!canStackAct(stack))
+	if (!canStackAct(battle, stack))
 		return false;
 		return false;
 
 
 	return true;
 	return true;
 }
 }
 
 
-bool BattleActionProcessor::doRetreatAction(const BattleAction & ba)
+bool BattleActionProcessor::doRetreatAction(const BattleInfo & battle, const BattleAction & ba)
 {
 {
-	if (!gameHandler->gameState()->curB->battleCanFlee(gameHandler->gameState()->curB->sides.at(ba.side).color))
+	if (!battle.battleCanFlee(battle.sides.at(ba.side).color))
 	{
 	{
 		gameHandler->complain("Cannot retreat!");
 		gameHandler->complain("Cannot retreat!");
 		return false;
 		return false;
 	}
 	}
 
 
-	owner->setBattleResult(EBattleResult::ESCAPE, !ba.side);
+	owner->setBattleResult(battle, EBattleResult::ESCAPE, !ba.side);
 	return true;
 	return true;
 }
 }
 
 
-bool BattleActionProcessor::doSurrenderAction(const BattleAction & ba)
+bool BattleActionProcessor::doSurrenderAction(const BattleInfo & battle, const BattleAction & ba)
 {
 {
-	PlayerColor player = gameHandler->gameState()->curB->sides.at(ba.side).color;
-	int cost = gameHandler->gameState()->curB->battleGetSurrenderCost(player);
+	PlayerColor player = battle.sides.at(ba.side).color;
+	int cost = battle.battleGetSurrenderCost(player);
 	if (cost < 0)
 	if (cost < 0)
 	{
 	{
 		gameHandler->complain("Cannot surrender!");
 		gameHandler->complain("Cannot surrender!");
@@ -86,13 +86,13 @@ bool BattleActionProcessor::doSurrenderAction(const BattleAction & ba)
 	}
 	}
 
 
 	gameHandler->giveResource(player, EGameResID::GOLD, -cost);
 	gameHandler->giveResource(player, EGameResID::GOLD, -cost);
-	owner->setBattleResult(EBattleResult::SURRENDER, !ba.side);
+	owner->setBattleResult(battle, EBattleResult::SURRENDER, !ba.side);
 	return true;
 	return true;
 }
 }
 
 
-bool BattleActionProcessor::doHeroSpellAction(const BattleAction & ba)
+bool BattleActionProcessor::doHeroSpellAction(const BattleInfo & battle, const BattleAction & ba)
 {
 {
-	const CGHeroInstance *h = gameHandler->gameState()->curB->battleGetFightingHero(ba.side);
+	const CGHeroInstance *h = battle.battleGetFightingHero(ba.side);
 	if (!h)
 	if (!h)
 	{
 	{
 		logGlobal->error("Wrong caster!");
 		logGlobal->error("Wrong caster!");
@@ -106,7 +106,7 @@ bool BattleActionProcessor::doHeroSpellAction(const BattleAction & ba)
 		return false;
 		return false;
 	}
 	}
 
 
-	spells::BattleCast parameters(gameHandler->gameState()->curB, h, spells::Mode::HERO, s);
+	spells::BattleCast parameters(&battle, h, spells::Mode::HERO, s);
 
 
 	spells::detail::ProblemImpl problem;
 	spells::detail::ProblemImpl problem;
 
 
@@ -122,17 +122,17 @@ bool BattleActionProcessor::doHeroSpellAction(const BattleAction & ba)
 		return false;
 		return false;
 	}
 	}
 
 
-	parameters.cast(gameHandler->spellEnv, ba.getTarget(gameHandler->gameState()->curB));
+	parameters.cast(gameHandler->spellEnv, ba.getTarget(&battle));
 
 
 	return true;
 	return true;
 }
 }
 
 
-bool BattleActionProcessor::doWalkAction(const BattleAction & ba)
+bool BattleActionProcessor::doWalkAction(const BattleInfo & battle, const BattleAction & ba)
 {
 {
 	const CStack * stack = gameHandler->battleGetStackByID(ba.stackNumber);
 	const CStack * stack = gameHandler->battleGetStackByID(ba.stackNumber);
-	battle::Target target = ba.getTarget(gameHandler->gameState()->curB);
+	battle::Target target = ba.getTarget(&battle);
 
 
-	if (!canStackAct(stack))
+	if (!canStackAct(battle, stack))
 		return false;
 		return false;
 
 
 	if(target.size() < 1)
 	if(target.size() < 1)
@@ -141,7 +141,7 @@ bool BattleActionProcessor::doWalkAction(const BattleAction & ba)
 		return false;
 		return false;
 	}
 	}
 
 
-	int walkedTiles = moveStack(ba.stackNumber, target.at(0).hexValue); //move
+	int walkedTiles = moveStack(battle, ba.stackNumber, target.at(0).hexValue); //move
 	if (!walkedTiles)
 	if (!walkedTiles)
 	{
 	{
 		gameHandler->complain("Stack failed movement!");
 		gameHandler->complain("Stack failed movement!");
@@ -150,11 +150,11 @@ bool BattleActionProcessor::doWalkAction(const BattleAction & ba)
 	return true;
 	return true;
 }
 }
 
 
-bool BattleActionProcessor::doDefendAction(const BattleAction & ba)
+bool BattleActionProcessor::doDefendAction(const BattleInfo & battle, const BattleAction & ba)
 {
 {
 	const CStack * stack = gameHandler->battleGetStackByID(ba.stackNumber);
 	const CStack * stack = gameHandler->battleGetStackByID(ba.stackNumber);
 
 
-	if (!canStackAct(stack))
+	if (!canStackAct(battle, stack))
 		return false;
 		return false;
 
 
 	//defensive stance, TODO: filter out spell boosts from bonus (stone skin etc.)
 	//defensive stance, TODO: filter out spell boosts from bonus (stone skin etc.)
@@ -199,12 +199,12 @@ bool BattleActionProcessor::doDefendAction(const BattleAction & ba)
 	return true;
 	return true;
 }
 }
 
 
-bool BattleActionProcessor::doAttackAction(const BattleAction & ba)
+bool BattleActionProcessor::doAttackAction(const BattleInfo & battle, const BattleAction & ba)
 {
 {
 	const CStack * stack = gameHandler->battleGetStackByID(ba.stackNumber);
 	const CStack * stack = gameHandler->battleGetStackByID(ba.stackNumber);
-	battle::Target target = ba.getTarget(gameHandler->gameState()->curB);
+	battle::Target target = ba.getTarget(&battle);
 
 
-	if (!canStackAct(stack))
+	if (!canStackAct(battle, stack))
 		return false;
 		return false;
 
 
 	if(target.size() < 2)
 	if(target.size() < 2)
@@ -215,7 +215,7 @@ bool BattleActionProcessor::doAttackAction(const BattleAction & ba)
 
 
 	BattleHex attackPos = target.at(0).hexValue;
 	BattleHex attackPos = target.at(0).hexValue;
 	BattleHex destinationTile = target.at(1).hexValue;
 	BattleHex destinationTile = target.at(1).hexValue;
-	const CStack * destinationStack = gameHandler->gameState()->curB->battleGetStackByPos(destinationTile, true);
+	const CStack * destinationStack = battle.battleGetStackByPos(destinationTile, true);
 
 
 	if(!destinationStack)
 	if(!destinationStack)
 	{
 	{
@@ -224,7 +224,7 @@ bool BattleActionProcessor::doAttackAction(const BattleAction & ba)
 	}
 	}
 
 
 	BattleHex startingPos = stack->getPosition();
 	BattleHex startingPos = stack->getPosition();
-	int distance = moveStack(ba.stackNumber, attackPos);
+	int distance = moveStack(battle, ba.stackNumber, attackPos);
 
 
 	logGlobal->trace("%s will attack %s", stack->nodeName(), destinationStack->nodeName());
 	logGlobal->trace("%s will attack %s", stack->nodeName(), destinationStack->nodeName());
 
 
@@ -256,7 +256,7 @@ bool BattleActionProcessor::doAttackAction(const BattleAction & ba)
 	int totalAttacks = stack->totalAttacks.getMeleeValue();
 	int totalAttacks = stack->totalAttacks.getMeleeValue();
 
 
 	//TODO: move to CUnitState
 	//TODO: move to CUnitState
-	const auto * attackingHero = gameHandler->gameState()->curB->battleGetFightingHero(ba.side);
+	const auto * attackingHero = battle.battleGetFightingHero(ba.side);
 	if(attackingHero)
 	if(attackingHero)
 	{
 	{
 		totalAttacks += attackingHero->valOfBonuses(BonusType::HERO_GRANTS_ATTACKS, stack->creatureIndex());
 		totalAttacks += attackingHero->valOfBonuses(BonusType::HERO_GRANTS_ATTACKS, stack->creatureIndex());
@@ -269,13 +269,13 @@ bool BattleActionProcessor::doAttackAction(const BattleAction & ba)
 		//first strike
 		//first strike
 		if(i == 0 && firstStrike && retaliation)
 		if(i == 0 && firstStrike && retaliation)
 		{
 		{
-			makeAttack(destinationStack, stack, 0, stack->getPosition(), true, false, true);
+			makeAttack(battle, destinationStack, stack, 0, stack->getPosition(), true, false, true);
 		}
 		}
 
 
 		//move can cause death, eg. by walking into the moat, first strike can cause death or paralysis/petrification
 		//move can cause death, eg. by walking into the moat, first strike can cause death or paralysis/petrification
 		if(stack->alive() && !stack->hasBonusOfType(BonusType::NOT_ACTIVE) && destinationStack->alive())
 		if(stack->alive() && !stack->hasBonusOfType(BonusType::NOT_ACTIVE) && destinationStack->alive())
 		{
 		{
-			makeAttack(stack, destinationStack, (i ? 0 : distance), destinationTile, i==0, false, false);//no distance travelled on second attack
+			makeAttack(battle, stack, destinationStack, (i ? 0 : distance), destinationTile, i==0, false, false);//no distance travelled on second attack
 		}
 		}
 
 
 		//counterattack
 		//counterattack
@@ -285,7 +285,7 @@ bool BattleActionProcessor::doAttackAction(const BattleAction & ba)
 			&& (i == 0 && !firstStrike)
 			&& (i == 0 && !firstStrike)
 			&& retaliation && destinationStack->ableToRetaliate())
 			&& retaliation && destinationStack->ableToRetaliate())
 		{
 		{
-			makeAttack(destinationStack, stack, 0, stack->getPosition(), true, false, true);
+			makeAttack(battle, destinationStack, stack, 0, stack->getPosition(), true, false, true);
 		}
 		}
 	}
 	}
 
 
@@ -296,18 +296,18 @@ bool BattleActionProcessor::doAttackAction(const BattleAction & ba)
 		&& startingPos == target.at(2).hexValue
 		&& startingPos == target.at(2).hexValue
 		&& stack->alive())
 		&& stack->alive())
 	{
 	{
-		moveStack(ba.stackNumber, startingPos);
+		moveStack(battle, ba.stackNumber, startingPos);
 		//NOTE: curStack->unitId() == ba.stackNumber (rev 1431)
 		//NOTE: curStack->unitId() == ba.stackNumber (rev 1431)
 	}
 	}
 	return true;
 	return true;
 }
 }
 
 
-bool BattleActionProcessor::doShootAction(const BattleAction & ba)
+bool BattleActionProcessor::doShootAction(const BattleInfo & battle, const BattleAction & ba)
 {
 {
 	const CStack * stack = gameHandler->battleGetStackByID(ba.stackNumber);
 	const CStack * stack = gameHandler->battleGetStackByID(ba.stackNumber);
-	battle::Target target = ba.getTarget(gameHandler->gameState()->curB);
+	battle::Target target = ba.getTarget(&battle);
 
 
-	if (!canStackAct(stack))
+	if (!canStackAct(battle, stack))
 		return false;
 		return false;
 
 
 	if(target.size() < 1)
 	if(target.size() < 1)
@@ -318,9 +318,9 @@ bool BattleActionProcessor::doShootAction(const BattleAction & ba)
 
 
 	auto destination = target.at(0).hexValue;
 	auto destination = target.at(0).hexValue;
 
 
-	const CStack * destinationStack = gameHandler->gameState()->curB->battleGetStackByPos(destination);
+	const CStack * destinationStack = battle.battleGetStackByPos(destination);
 
 
-	if (!gameHandler->gameState()->curB->battleCanShoot(stack, destination))
+	if (!battle.battleCanShoot(stack, destination))
 	{
 	{
 		gameHandler->complain("Cannot shoot!");
 		gameHandler->complain("Cannot shoot!");
 		return false;
 		return false;
@@ -332,23 +332,23 @@ bool BattleActionProcessor::doShootAction(const BattleAction & ba)
 		return false;
 		return false;
 	}
 	}
 
 
-	makeAttack(stack, destinationStack, 0, destination, true, true, false);
+	makeAttack(battle, stack, destinationStack, 0, destination, true, true, false);
 
 
 	//ranged counterattack
 	//ranged counterattack
 	if (destinationStack->hasBonusOfType(BonusType::RANGED_RETALIATION)
 	if (destinationStack->hasBonusOfType(BonusType::RANGED_RETALIATION)
 		&& !stack->hasBonusOfType(BonusType::BLOCKS_RANGED_RETALIATION)
 		&& !stack->hasBonusOfType(BonusType::BLOCKS_RANGED_RETALIATION)
 		&& destinationStack->ableToRetaliate()
 		&& destinationStack->ableToRetaliate()
-		&& gameHandler->gameState()->curB->battleCanShoot(destinationStack, stack->getPosition())
+		&& battle.battleCanShoot(destinationStack, stack->getPosition())
 		&& stack->alive()) //attacker may have died (fire shield)
 		&& stack->alive()) //attacker may have died (fire shield)
 	{
 	{
-		makeAttack(destinationStack, stack, 0, stack->getPosition(), true, true, true);
+		makeAttack(battle, destinationStack, stack, 0, stack->getPosition(), true, true, true);
 	}
 	}
 	//allow more than one additional attack
 	//allow more than one additional attack
 
 
 	int totalRangedAttacks = stack->totalAttacks.getRangedValue();
 	int totalRangedAttacks = stack->totalAttacks.getRangedValue();
 
 
 	//TODO: move to CUnitState
 	//TODO: move to CUnitState
-	const auto * attackingHero = gameHandler->gameState()->curB->battleGetFightingHero(ba.side);
+	const auto * attackingHero = battle.battleGetFightingHero(ba.side);
 	if(attackingHero)
 	if(attackingHero)
 	{
 	{
 		totalRangedAttacks += attackingHero->valOfBonuses(BonusType::HERO_GRANTS_ATTACKS, stack->creatureIndex());
 		totalRangedAttacks += attackingHero->valOfBonuses(BonusType::HERO_GRANTS_ATTACKS, stack->creatureIndex());
@@ -362,19 +362,19 @@ bool BattleActionProcessor::doShootAction(const BattleAction & ba)
 			&& stack->shots.canUse()
 			&& stack->shots.canUse()
 			)
 			)
 		{
 		{
-			makeAttack(stack, destinationStack, 0, destination, false, true, false);
+			makeAttack(battle, stack, destinationStack, 0, destination, false, true, false);
 		}
 		}
 	}
 	}
 
 
 	return true;
 	return true;
 }
 }
 
 
-bool BattleActionProcessor::doCatapultAction(const BattleAction & ba)
+bool BattleActionProcessor::doCatapultAction(const BattleInfo & battle, const BattleAction & ba)
 {
 {
-	const CStack * stack = gameHandler->gameState()->curB->battleGetStackByID(ba.stackNumber);
-	battle::Target target = ba.getTarget(gameHandler->gameState()->curB);
+	const CStack * stack = battle.battleGetStackByID(ba.stackNumber);
+	battle::Target target = ba.getTarget(&battle);
 
 
-	if (!canStackAct(stack))
+	if (!canStackAct(battle, stack))
 		return false;
 		return false;
 
 
 	std::shared_ptr<const Bonus> catapultAbility = stack->getBonusLocalFirst(Selector::type()(BonusType::CATAPULT));
 	std::shared_ptr<const Bonus> catapultAbility = stack->getBonusLocalFirst(Selector::type()(BonusType::CATAPULT));
@@ -385,7 +385,7 @@ bool BattleActionProcessor::doCatapultAction(const BattleAction & ba)
 	else
 	else
 	{
 	{
 		const CSpell * spell = SpellID(catapultAbility->subtype).toSpell();
 		const CSpell * spell = SpellID(catapultAbility->subtype).toSpell();
-		spells::BattleCast parameters(gameHandler->gameState()->curB, stack, spells::Mode::SPELL_LIKE_ATTACK, spell); //We can shot infinitely by catapult
+		spells::BattleCast parameters(&battle, stack, spells::Mode::SPELL_LIKE_ATTACK, spell); //We can shot infinitely by catapult
 		auto shotLevel = stack->valOfBonuses(Selector::typeSubtype(BonusType::CATAPULT_EXTRA_SHOTS, catapultAbility->subtype));
 		auto shotLevel = stack->valOfBonuses(Selector::typeSubtype(BonusType::CATAPULT_EXTRA_SHOTS, catapultAbility->subtype));
 		parameters.setSpellLevel(shotLevel);
 		parameters.setSpellLevel(shotLevel);
 		parameters.cast(gameHandler->spellEnv, target);
 		parameters.cast(gameHandler->spellEnv, target);
@@ -393,13 +393,13 @@ bool BattleActionProcessor::doCatapultAction(const BattleAction & ba)
 	return true;
 	return true;
 }
 }
 
 
-bool BattleActionProcessor::doUnitSpellAction(const BattleAction & ba)
+bool BattleActionProcessor::doUnitSpellAction(const BattleInfo & battle, const BattleAction & ba)
 {
 {
-	const CStack * stack = gameHandler->gameState()->curB->battleGetStackByID(ba.stackNumber);
-	battle::Target target = ba.getTarget(gameHandler->gameState()->curB);
+	const CStack * stack = battle.battleGetStackByID(ba.stackNumber);
+	battle::Target target = ba.getTarget(&battle);
 	SpellID spellID = ba.spell;
 	SpellID spellID = ba.spell;
 
 
-	if (!canStackAct(stack))
+	if (!canStackAct(battle, stack))
 		return false;
 		return false;
 
 
 	std::shared_ptr<const Bonus> randSpellcaster = stack->getBonus(Selector::type()(BonusType::RANDOM_SPELLCASTER));
 	std::shared_ptr<const Bonus> randSpellcaster = stack->getBonus(Selector::type()(BonusType::RANDOM_SPELLCASTER));
@@ -414,7 +414,7 @@ bool BattleActionProcessor::doUnitSpellAction(const BattleAction & ba)
 	else
 	else
 	{
 	{
 		const CSpell * spell = SpellID(spellID).toSpell();
 		const CSpell * spell = SpellID(spellID).toSpell();
-		spells::BattleCast parameters(gameHandler->gameState()->curB, stack, spells::Mode::CREATURE_ACTIVE, spell);
+		spells::BattleCast parameters(&battle, stack, spells::Mode::CREATURE_ACTIVE, spell);
 		int32_t spellLvl = 0;
 		int32_t spellLvl = 0;
 		if(spellcaster)
 		if(spellcaster)
 			vstd::amax(spellLvl, spellcaster->val);
 			vstd::amax(spellLvl, spellcaster->val);
@@ -426,12 +426,12 @@ bool BattleActionProcessor::doUnitSpellAction(const BattleAction & ba)
 	return true;
 	return true;
 }
 }
 
 
-bool BattleActionProcessor::doHealAction(const BattleAction & ba)
+bool BattleActionProcessor::doHealAction(const BattleInfo & battle, const BattleAction & ba)
 {
 {
-	const CStack * stack = gameHandler->gameState()->curB->battleGetStackByID(ba.stackNumber);
-	battle::Target target = ba.getTarget(gameHandler->gameState()->curB);
+	const CStack * stack = battle.battleGetStackByID(ba.stackNumber);
+	battle::Target target = ba.getTarget(&battle);
 
 
-	if (!canStackAct(stack))
+	if (!canStackAct(battle, stack))
 		return false;
 		return false;
 
 
 	if(target.size() < 1)
 	if(target.size() < 1)
@@ -446,7 +446,7 @@ bool BattleActionProcessor::doHealAction(const BattleAction & ba)
 	if(target.at(0).unitValue)
 	if(target.at(0).unitValue)
 		destStack = target.at(0).unitValue;
 		destStack = target.at(0).unitValue;
 	else
 	else
-		destStack = gameHandler->gameState()->curB->battleGetUnitByPos(target.at(0).hexValue);
+		destStack = battle.battleGetUnitByPos(target.at(0).hexValue);
 
 
 	if(stack == nullptr || destStack == nullptr || !healerAbility || healerAbility->subtype < 0)
 	if(stack == nullptr || destStack == nullptr || !healerAbility || healerAbility->subtype < 0)
 	{
 	{
@@ -455,7 +455,7 @@ bool BattleActionProcessor::doHealAction(const BattleAction & ba)
 	else
 	else
 	{
 	{
 		const CSpell * spell = SpellID(healerAbility->subtype).toSpell();
 		const CSpell * spell = SpellID(healerAbility->subtype).toSpell();
-		spells::BattleCast parameters(gameHandler->gameState()->curB, stack, spells::Mode::SPELL_LIKE_ATTACK, spell); //We can heal infinitely by first aid tent
+		spells::BattleCast parameters(&battle, stack, spells::Mode::SPELL_LIKE_ATTACK, spell); //We can heal infinitely by first aid tent
 		auto dest = battle::Destination(destStack, target.at(0).hexValue);
 		auto dest = battle::Destination(destStack, target.at(0).hexValue);
 		parameters.setSpellLevel(0);
 		parameters.setSpellLevel(0);
 		parameters.cast(gameHandler->spellEnv, {dest});
 		parameters.cast(gameHandler->spellEnv, {dest});
@@ -463,7 +463,7 @@ bool BattleActionProcessor::doHealAction(const BattleAction & ba)
 	return true;
 	return true;
 }
 }
 
 
-bool BattleActionProcessor::canStackAct(const CStack * stack)
+bool BattleActionProcessor::canStackAct(const BattleInfo & battle, const CStack * stack)
 {
 {
 	if (!stack)
 	if (!stack)
 	{
 	{
@@ -486,7 +486,7 @@ bool BattleActionProcessor::canStackAct(const CStack * stack)
 	}
 	}
 	else
 	else
 	{
 	{
-		if (stack->unitId() != gameHandler->gameState()->curB->getActiveStackID())
+		if (stack->unitId() != battle.getActiveStackID())
 		{
 		{
 			gameHandler->complain("Action has to be about active stack!");
 			gameHandler->complain("Action has to be about active stack!");
 			return false;
 			return false;
@@ -495,46 +495,46 @@ bool BattleActionProcessor::canStackAct(const CStack * stack)
 	return true;
 	return true;
 }
 }
 
 
-bool BattleActionProcessor::dispatchBattleAction(const BattleAction & ba)
+bool BattleActionProcessor::dispatchBattleAction(const BattleInfo & battle, const BattleAction & ba)
 {
 {
 	switch(ba.actionType)
 	switch(ba.actionType)
 	{
 	{
 		case EActionType::BAD_MORALE:
 		case EActionType::BAD_MORALE:
 		case EActionType::NO_ACTION:
 		case EActionType::NO_ACTION:
-			return doEmptyAction(ba);
+			return doEmptyAction(battle, ba);
 		case EActionType::END_TACTIC_PHASE:
 		case EActionType::END_TACTIC_PHASE:
-			return doEndTacticsAction(ba);
+			return doEndTacticsAction(battle, ba);
 		case EActionType::RETREAT:
 		case EActionType::RETREAT:
-			return doRetreatAction(ba);
+			return doRetreatAction(battle, ba);
 		case EActionType::SURRENDER:
 		case EActionType::SURRENDER:
-			return doSurrenderAction(ba);
+			return doSurrenderAction(battle, ba);
 		case EActionType::HERO_SPELL:
 		case EActionType::HERO_SPELL:
-			return doHeroSpellAction(ba);
+			return doHeroSpellAction(battle, ba);
 		case EActionType::WALK:
 		case EActionType::WALK:
-			return doWalkAction(ba);
+			return doWalkAction(battle, ba);
 		case EActionType::WAIT:
 		case EActionType::WAIT:
-			return doWaitAction(ba);
+			return doWaitAction(battle, ba);
 		case EActionType::DEFEND:
 		case EActionType::DEFEND:
-			return doDefendAction(ba);
+			return doDefendAction(battle, ba);
 		case EActionType::WALK_AND_ATTACK:
 		case EActionType::WALK_AND_ATTACK:
-			return doAttackAction(ba);
+			return doAttackAction(battle, ba);
 		case EActionType::SHOOT:
 		case EActionType::SHOOT:
-			return doShootAction(ba);
+			return doShootAction(battle, ba);
 		case EActionType::CATAPULT:
 		case EActionType::CATAPULT:
-			return doCatapultAction(ba);
+			return doCatapultAction(battle, ba);
 		case EActionType::MONSTER_SPELL:
 		case EActionType::MONSTER_SPELL:
-			return doUnitSpellAction(ba);
+			return doUnitSpellAction(battle, ba);
 		case EActionType::STACK_HEAL:
 		case EActionType::STACK_HEAL:
-			return doHealAction(ba);
+			return doHealAction(battle, ba);
 	}
 	}
 	gameHandler->complain("Unrecognized action type received!!");
 	gameHandler->complain("Unrecognized action type received!!");
 	return false;
 	return false;
 }
 }
 
 
-bool BattleActionProcessor::makeBattleActionImpl(const BattleAction &ba)
+bool BattleActionProcessor::makeBattleActionImpl(const BattleInfo & battle, const BattleAction &ba)
 {
 {
 	logGlobal->trace("Making action: %s", ba.toString());
 	logGlobal->trace("Making action: %s", ba.toString());
-	const CStack * stack = gameHandler->gameState()->curB->battleGetStackByID(ba.stackNumber);
+	const CStack * stack = battle.battleGetStackByID(ba.stackNumber);
 
 
 	// for these events client does not expects StartAction/EndAction wrapper
 	// for these events client does not expects StartAction/EndAction wrapper
 	if (!ba.isBattleEndAction())
 	if (!ba.isBattleEndAction())
@@ -543,7 +543,7 @@ bool BattleActionProcessor::makeBattleActionImpl(const BattleAction &ba)
 		gameHandler->sendAndApply(&startAction);
 		gameHandler->sendAndApply(&startAction);
 	}
 	}
 
 
-	bool result = dispatchBattleAction(ba);
+	bool result = dispatchBattleAction(battle, ba);
 
 
 	if (!ba.isBattleEndAction())
 	if (!ba.isBattleEndAction())
 	{
 	{
@@ -557,19 +557,19 @@ bool BattleActionProcessor::makeBattleActionImpl(const BattleAction &ba)
 	return result;
 	return result;
 }
 }
 
 
-int BattleActionProcessor::moveStack(int stack, BattleHex dest)
+int BattleActionProcessor::moveStack(const BattleInfo & battle, int stack, BattleHex dest)
 {
 {
 	int ret = 0;
 	int ret = 0;
 
 
 	const CStack *curStack = gameHandler->battleGetStackByID(stack);
 	const CStack *curStack = gameHandler->battleGetStackByID(stack);
-	const CStack *stackAtEnd = gameHandler->gameState()->curB->battleGetStackByPos(dest);
+	const CStack *stackAtEnd = battle.battleGetStackByPos(dest);
 
 
 	assert(curStack);
 	assert(curStack);
 	assert(dest < GameConstants::BFIELD_SIZE);
 	assert(dest < GameConstants::BFIELD_SIZE);
 
 
-	if (gameHandler->gameState()->curB->tacticDistance)
+	if (battle.tacticDistance)
 	{
 	{
-		assert(gameHandler->gameState()->curB->isInTacticRange(dest));
+		assert(battle.isInTacticRange(dest));
 	}
 	}
 
 
 	auto start = curStack->getPosition();
 	auto start = curStack->getPosition();
@@ -600,7 +600,7 @@ int BattleActionProcessor::moveStack(int stack, BattleHex dest)
 	}
 	}
 
 
 	bool canUseGate = false;
 	bool canUseGate = false;
-	auto dbState = gameHandler->gameState()->curB->si.gateState;
+	auto dbState = battle.si.gateState;
 	if(gameHandler->battleGetSiegeLevel() > 0 && curStack->unitSide() == BattleSide::DEFENDER &&
 	if(gameHandler->battleGetSiegeLevel() > 0 && curStack->unitSide() == BattleSide::DEFENDER &&
 		dbState != EGateState::DESTROYED &&
 		dbState != EGateState::DESTROYED &&
 		dbState != EGateState::BLOCKED)
 		dbState != EGateState::BLOCKED)
@@ -608,13 +608,13 @@ int BattleActionProcessor::moveStack(int stack, BattleHex dest)
 		canUseGate = true;
 		canUseGate = true;
 	}
 	}
 
 
-	std::pair< std::vector<BattleHex>, int > path = gameHandler->gameState()->curB->getPath(start, dest, curStack);
+	std::pair< std::vector<BattleHex>, int > path = battle.getPath(start, dest, curStack);
 
 
 	ret = path.second;
 	ret = path.second;
 
 
 	int creSpeed = curStack->speed(0, true);
 	int creSpeed = curStack->speed(0, true);
 
 
-	if (gameHandler->gameState()->curB->tacticDistance > 0 && creSpeed > 0)
+	if (battle.tacticDistance > 0 && creSpeed > 0)
 		creSpeed = GameConstants::BFIELD_SIZE;
 		creSpeed = GameConstants::BFIELD_SIZE;
 
 
 	bool hasWideMoat = vstd::contains_if(gameHandler->battleGetAllObstaclesOnPos(BattleHex(BattleHex::GATE_BRIDGE), false), [](const std::shared_ptr<const CObstacleInstance> & obst)
 	bool hasWideMoat = vstd::contains_if(gameHandler->battleGetAllObstaclesOnPos(BattleHex(BattleHex::GATE_BRIDGE), false), [](const std::shared_ptr<const CObstacleInstance> & obst)
@@ -826,7 +826,7 @@ int BattleActionProcessor::moveStack(int stack, BattleHex dest)
 					else if (curStack->getPosition() == gateMayCloseAtHex)
 					else if (curStack->getPosition() == gateMayCloseAtHex)
 					{
 					{
 						gateMayCloseAtHex = BattleHex();
 						gateMayCloseAtHex = BattleHex();
-						owner->updateGateState();
+						owner->updateGateState(battle);
 					}
 					}
 				}
 				}
 			}
 			}
@@ -848,10 +848,10 @@ int BattleActionProcessor::moveStack(int stack, BattleHex dest)
 	return ret;
 	return ret;
 }
 }
 
 
-void BattleActionProcessor::makeAttack(const CStack * attacker, const CStack * defender, int distance, BattleHex targetHex, bool first, bool ranged, bool counter)
+void BattleActionProcessor::makeAttack(const BattleInfo & battle, const CStack * attacker, const CStack * defender, int distance, BattleHex targetHex, bool first, bool ranged, bool counter)
 {
 {
 	if(first && !counter)
 	if(first && !counter)
-		handleAttackBeforeCasting(ranged, attacker, defender);
+		handleAttackBeforeCasting(battle, ranged, attacker, defender);
 
 
 	FireShieldInfo fireShield;
 	FireShieldInfo fireShield;
 	BattleAttack bat;
 	BattleAttack bat;
@@ -891,7 +891,7 @@ void BattleActionProcessor::makeAttack(const CStack * attacker, const CStack * d
 		bat.flags |= BattleAttack::DEATH_BLOW;
 		bat.flags |= BattleAttack::DEATH_BLOW;
 	}
 	}
 
 
-	const auto * owner = gameHandler->gameState()->curB->getHero(attacker->unitOwner());
+	const auto * owner = battle.getHero(attacker->unitOwner());
 	if(owner)
 	if(owner)
 	{
 	{
 		int chance = owner->valOfBonuses(BonusType::BONUS_DAMAGE_CHANCE, attacker->creatureIndex());
 		int chance = owner->valOfBonuses(BonusType::BONUS_DAMAGE_CHANCE, attacker->creatureIndex());
@@ -903,14 +903,14 @@ void BattleActionProcessor::makeAttack(const CStack * attacker, const CStack * d
 
 
 	// only primary target
 	// only primary target
 	if(defender->alive())
 	if(defender->alive())
-		drainedLife += applyBattleEffects(bat, attackerState, fireShield, defender, distance, false);
+		drainedLife += applyBattleEffects(battle, bat, attackerState, fireShield, defender, distance, false);
 
 
 	//multiple-hex normal attack
 	//multiple-hex normal attack
-	std::set<const CStack*> attackedCreatures = gameHandler->gameState()->curB->getAttackedCreatures(attacker, targetHex, bat.shot()); //creatures other than primary target
+	std::set<const CStack*> attackedCreatures = battle.getAttackedCreatures(attacker, targetHex, bat.shot()); //creatures other than primary target
 	for(const CStack * stack : attackedCreatures)
 	for(const CStack * stack : attackedCreatures)
 	{
 	{
 		if(stack != defender && stack->alive()) //do not hit same stack twice
 		if(stack != defender && stack->alive()) //do not hit same stack twice
-			drainedLife += applyBattleEffects(bat, attackerState, fireShield, stack, distance, true);
+			drainedLife += applyBattleEffects(battle, bat, attackerState, fireShield, stack, distance, true);
 	}
 	}
 
 
 	std::shared_ptr<const Bonus> bonus = attacker->getBonusLocalFirst(Selector::type()(BonusType::SPELL_LIKE_ATTACK));
 	std::shared_ptr<const Bonus> bonus = attacker->getBonusLocalFirst(Selector::type()(BonusType::SPELL_LIKE_ATTACK));
@@ -927,7 +927,7 @@ void BattleActionProcessor::makeAttack(const CStack * attacker, const CStack * d
 		battle::Target target;
 		battle::Target target;
 		target.emplace_back(defender, targetHex);
 		target.emplace_back(defender, targetHex);
 
 
-		spells::BattleCast event(gameHandler->gameState()->curB, attacker, spells::Mode::SPELL_LIKE_ATTACK, spell);
+		spells::BattleCast event(&battle, attacker, spells::Mode::SPELL_LIKE_ATTACK, spell);
 		event.setSpellLevel(bonus->val);
 		event.setSpellLevel(bonus->val);
 
 
 		auto attackedCreatures = spell->battleMechanics(&event)->getAffectedStacks(target);
 		auto attackedCreatures = spell->battleMechanics(&event)->getAffectedStacks(target);
@@ -938,7 +938,7 @@ void BattleActionProcessor::makeAttack(const CStack * attacker, const CStack * d
 		{
 		{
 			if(stack != defender && stack->alive()) //do not hit same stack twice
 			if(stack != defender && stack->alive()) //do not hit same stack twice
 			{
 			{
-				drainedLife += applyBattleEffects(bat, attackerState, fireShield, stack, distance, true);
+				drainedLife += applyBattleEffects(battle, bat, attackerState, fireShield, stack, distance, true);
 			}
 			}
 		}
 		}
 
 
@@ -1012,7 +1012,7 @@ void BattleActionProcessor::makeAttack(const CStack * attacker, const CStack * d
 			const CStack * actor = item.first;
 			const CStack * actor = item.first;
 			int64_t rawDamage = item.second;
 			int64_t rawDamage = item.second;
 
 
-			const CGHeroInstance * actorOwner = gameHandler->gameState()->curB->getHero(actor->unitOwner());
+			const CGHeroInstance * actorOwner = battle.getHero(actor->unitOwner());
 
 
 			if(actorOwner)
 			if(actorOwner)
 			{
 			{
@@ -1055,10 +1055,10 @@ void BattleActionProcessor::makeAttack(const CStack * attacker, const CStack * d
 
 
 	gameHandler->sendAndApply(&blm);
 	gameHandler->sendAndApply(&blm);
 
 
-	handleAfterAttackCasting(ranged, attacker, defender);
+	handleAfterAttackCasting(battle, ranged, attacker, defender);
 }
 }
 
 
-void BattleActionProcessor::attackCasting(bool ranged, BonusType attackMode, const battle::Unit * attacker, const battle::Unit * defender)
+void BattleActionProcessor::attackCasting(const BattleInfo & battle, bool ranged, BonusType attackMode, const battle::Unit * attacker, const battle::Unit * defender)
 {
 {
 	if(attacker->hasBonusOfType(attackMode))
 	if(attacker->hasBonusOfType(attackMode))
 	{
 	{
@@ -1104,7 +1104,7 @@ void BattleActionProcessor::attackCasting(bool ranged, BonusType attackMode, con
 			spells::Target target;
 			spells::Target target;
 			target.emplace_back(defender);
 			target.emplace_back(defender);
 
 
-			spells::BattleCast parameters(gameHandler->gameState()->curB, &caster, spells::Mode::PASSIVE, spell);
+			spells::BattleCast parameters(&battle, &caster, spells::Mode::PASSIVE, spell);
 
 
 			auto m = spell->battleMechanics(&parameters);
 			auto m = spell->battleMechanics(&parameters);
 
 
@@ -1126,17 +1126,17 @@ void BattleActionProcessor::attackCasting(bool ranged, BonusType attackMode, con
 	}
 	}
 }
 }
 
 
-void BattleActionProcessor::handleAttackBeforeCasting(bool ranged, const CStack * attacker, const CStack * defender)
+void BattleActionProcessor::handleAttackBeforeCasting(const BattleInfo & battle, bool ranged, const CStack * attacker, const CStack * defender)
 {
 {
-	attackCasting(ranged, BonusType::SPELL_BEFORE_ATTACK, attacker, defender); //no death stare / acid breath needed?
+	attackCasting(battle, ranged, BonusType::SPELL_BEFORE_ATTACK, attacker, defender); //no death stare / acid breath needed?
 }
 }
 
 
-void BattleActionProcessor::handleAfterAttackCasting(bool ranged, const CStack * attacker, const CStack * defender)
+void BattleActionProcessor::handleAfterAttackCasting(const BattleInfo & battle, bool ranged, const CStack * attacker, const CStack * defender)
 {
 {
 	if(!attacker->alive() || !defender->alive()) // can be already dead
 	if(!attacker->alive() || !defender->alive()) // can be already dead
 		return;
 		return;
 
 
-	attackCasting(ranged, BonusType::SPELL_AFTER_ATTACK, attacker, defender);
+	attackCasting(battle, ranged, BonusType::SPELL_AFTER_ATTACK, attacker, defender);
 
 
 	if(!defender->alive())
 	if(!defender->alive())
 	{
 	{
@@ -1169,7 +1169,7 @@ void BattleActionProcessor::handleAfterAttackCasting(bool ranged, const CStack *
 
 
 			spells::AbilityCaster caster(attacker, 0);
 			spells::AbilityCaster caster(attacker, 0);
 
 
-			spells::BattleCast parameters(gameHandler->gameState()->curB, &caster, spells::Mode::PASSIVE, spell);
+			spells::BattleCast parameters(&battle, &caster, spells::Mode::PASSIVE, spell);
 			spells::Target target;
 			spells::Target target;
 			target.emplace_back(defender);
 			target.emplace_back(defender);
 			parameters.setEffectValue(staredCreatures);
 			parameters.setEffectValue(staredCreatures);
@@ -1194,7 +1194,7 @@ void BattleActionProcessor::handleAfterAttackCasting(bool ranged, const CStack *
 
 
 		spells::AbilityCaster caster(attacker, 0);
 		spells::AbilityCaster caster(attacker, 0);
 
 
-		spells::BattleCast parameters(gameHandler->gameState()->curB, &caster, spells::Mode::PASSIVE, spell);
+		spells::BattleCast parameters(&battle, &caster, spells::Mode::PASSIVE, spell);
 		spells::Target target;
 		spells::Target target;
 		target.emplace_back(defender);
 		target.emplace_back(defender);
 
 
@@ -1221,7 +1221,7 @@ void BattleActionProcessor::handleAfterAttackCasting(bool ranged, const CStack *
 			return;
 			return;
 
 
 		battle::UnitInfo resurrectInfo;
 		battle::UnitInfo resurrectInfo;
-		resurrectInfo.id = gameHandler->gameState()->curB->battleNextUnitId();
+		resurrectInfo.id = battle.battleNextUnitId();
 		resurrectInfo.summoned = false;
 		resurrectInfo.summoned = false;
 		resurrectInfo.position = defender->getPosition();
 		resurrectInfo.position = defender->getPosition();
 		resurrectInfo.side = defender->unitSide();
 		resurrectInfo.side = defender->unitSide();
@@ -1286,7 +1286,7 @@ void BattleActionProcessor::handleAfterAttackCasting(bool ranged, const CStack *
 	}
 	}
 }
 }
 
 
-int64_t BattleActionProcessor::applyBattleEffects(BattleAttack & bat, std::shared_ptr<battle::CUnitState> attackerState, FireShieldInfo & fireShield, const CStack * def, int distance, bool secondary)
+int64_t BattleActionProcessor::applyBattleEffects(const BattleInfo & battle, BattleAttack & bat, std::shared_ptr<battle::CUnitState> attackerState, FireShieldInfo & fireShield, const CStack * def, int distance, bool secondary)
 {
 {
 	BattleStackAttacked bsa;
 	BattleStackAttacked bsa;
 	if(secondary)
 	if(secondary)
@@ -1302,8 +1302,8 @@ int64_t BattleActionProcessor::applyBattleEffects(BattleAttack & bat, std::share
 		bai.luckyStrike  = bat.lucky();
 		bai.luckyStrike  = bat.lucky();
 		bai.unluckyStrike  = bat.unlucky();
 		bai.unluckyStrike  = bat.unlucky();
 
 
-		auto range = gameHandler->gameState()->curB->calculateDmgRange(bai);
-		bsa.damageAmount = gameHandler->gameState()->curB->getActualDamage(range.damage, attackerState->getCount(), gameHandler->getRandomGenerator());
+		auto range = battle.calculateDmgRange(bai);
+		bsa.damageAmount = battle.getActualDamage(range.damage, attackerState->getCount(), gameHandler->getRandomGenerator());
 		CStack::prepareAttacked(bsa, gameHandler->getRandomGenerator(), bai.defender->acquireState()); //calculate casualties
 		CStack::prepareAttacked(bsa, gameHandler->getRandomGenerator(), bai.defender->acquireState()); //calculate casualties
 	}
 	}
 
 
@@ -1390,22 +1390,17 @@ void BattleActionProcessor::addGenericKilledLog(BattleLogMessage & blm, const CS
 	}
 	}
 }
 }
 
 
-bool BattleActionProcessor::makeAutomaticBattleAction(const BattleAction & ba)
+bool BattleActionProcessor::makeAutomaticBattleAction(const BattleInfo & battle, const BattleAction & ba)
 {
 {
-	return makeBattleActionImpl(ba);
+	return makeBattleActionImpl(battle, ba);
 }
 }
 
 
-bool BattleActionProcessor::makePlayerBattleAction(PlayerColor player, const BattleAction &ba)
+bool BattleActionProcessor::makePlayerBattleAction(const BattleInfo & battle, PlayerColor player, const BattleAction &ba)
 {
 {
-	const BattleInfo * battle = gameHandler->gameState()->curB;
-
-	if(!battle && gameHandler->complain("Can not make action - there is no battle ongoing!"))
-		return false;
-
 	if (ba.side != 0 && ba.side != 1 && gameHandler->complain("Can not make action - invalid battle side!"))
 	if (ba.side != 0 && ba.side != 1 && gameHandler->complain("Can not make action - invalid battle side!"))
 		return false;
 		return false;
 
 
-	if(battle->tacticDistance != 0)
+	if(battle.tacticDistance != 0)
 	{
 	{
 		if(!ba.isTacticsAction())
 		if(!ba.isTacticsAction())
 		{
 		{
@@ -1413,7 +1408,7 @@ bool BattleActionProcessor::makePlayerBattleAction(PlayerColor player, const Bat
 			return false;
 			return false;
 		}
 		}
 
 
-		if(player != battle->sides[ba.side].color)
+		if(player != battle.sides[ba.side].color)
 		{
 		{
 			gameHandler->complain("Can not make actions in battles you are not part of!");
 			gameHandler->complain("Can not make actions in battles you are not part of!");
 			return false;
 			return false;
@@ -1421,21 +1416,21 @@ bool BattleActionProcessor::makePlayerBattleAction(PlayerColor player, const Bat
 	}
 	}
 	else
 	else
 	{
 	{
-		if (ba.isUnitAction() && ba.stackNumber != battle->getActiveStackID())
+		if (ba.isUnitAction() && ba.stackNumber != battle.getActiveStackID())
 		{
 		{
 			gameHandler->complain("Can not make actions - stack is not active!");
 			gameHandler->complain("Can not make actions - stack is not active!");
 			return false;
 			return false;
 		}
 		}
 
 
-		auto active = battle->battleActiveUnit();
+		auto active = battle.battleActiveUnit();
 		if(!active && gameHandler->complain("No active unit in battle!"))
 		if(!active && gameHandler->complain("No active unit in battle!"))
 			return false;
 			return false;
 
 
-		auto unitOwner = battle->battleGetOwner(active);
+		auto unitOwner = battle.battleGetOwner(active);
 
 
 		if(player != unitOwner && gameHandler->complain("Can not make actions in battles you are not part of!"))
 		if(player != unitOwner && gameHandler->complain("Can not make actions in battles you are not part of!"))
 			return false;
 			return false;
 	}
 	}
 
 
-	return makeBattleActionImpl(ba);
+	return makeBattleActionImpl(battle, ba);
 }
 }

+ 27 - 26
server/battles/BattleActionProcessor.h

@@ -14,6 +14,7 @@ VCMI_LIB_NAMESPACE_BEGIN
 struct BattleLogMessage;
 struct BattleLogMessage;
 struct BattleAttack;
 struct BattleAttack;
 class BattleAction;
 class BattleAction;
+class BattleInfo;
 struct BattleHex;
 struct BattleHex;
 class CStack;
 class CStack;
 class PlayerColor;
 class PlayerColor;
@@ -38,42 +39,42 @@ class BattleActionProcessor : boost::noncopyable
 	BattleProcessor * owner;
 	BattleProcessor * owner;
 	CGameHandler * gameHandler;
 	CGameHandler * gameHandler;
 
 
-	int moveStack(int stack, BattleHex dest); //returned value - travelled distance
-	void makeAttack(const CStack * attacker, const CStack * defender, int distance, BattleHex targetHex, bool first, bool ranged, bool counter);
+	int moveStack(const BattleInfo & battle, int stack, BattleHex dest); //returned value - travelled distance
+	void makeAttack(const BattleInfo & battle, const CStack * attacker, const CStack * defender, int distance, BattleHex targetHex, bool first, bool ranged, bool counter);
 
 
-	void handleAttackBeforeCasting(bool ranged, const CStack * attacker, const CStack * defender);
-	void handleAfterAttackCasting(bool ranged, const CStack * attacker, const CStack * defender);
-	void attackCasting(bool ranged, BonusType attackMode, const battle::Unit * attacker, const battle::Unit * defender);
+	void handleAttackBeforeCasting(const BattleInfo & battle, bool ranged, const CStack * attacker, const CStack * defender);
+	void handleAfterAttackCasting(const BattleInfo & battle, bool ranged, const CStack * attacker, const CStack * defender);
+	void attackCasting(const BattleInfo & battle, bool ranged, BonusType attackMode, const battle::Unit * attacker, const battle::Unit * defender);
 
 
 	// damage, drain life & fire shield; returns amount of drained life
 	// damage, drain life & fire shield; returns amount of drained life
-	int64_t applyBattleEffects(BattleAttack & bat, std::shared_ptr<battle::CUnitState> attackerState, FireShieldInfo & fireShield, const CStack * def, int distance, bool secondary);
+	int64_t applyBattleEffects(const BattleInfo & battle, BattleAttack & bat, std::shared_ptr<battle::CUnitState> attackerState, FireShieldInfo & fireShield, const CStack * def, int distance, bool secondary);
 
 
 	void sendGenericKilledLog(const CStack * defender, int32_t killed, bool multiple);
 	void sendGenericKilledLog(const CStack * defender, int32_t killed, bool multiple);
 	void addGenericKilledLog(BattleLogMessage & blm, const CStack * defender, int32_t killed, bool multiple);
 	void addGenericKilledLog(BattleLogMessage & blm, const CStack * defender, int32_t killed, bool multiple);
 
 
-	bool canStackAct(const CStack * stack);
-
-	bool doEmptyAction(const BattleAction & ba);
-	bool doEndTacticsAction(const BattleAction & ba);
-	bool doRetreatAction(const BattleAction & ba);
-	bool doSurrenderAction(const BattleAction & ba);
-	bool doHeroSpellAction(const BattleAction & ba);
-	bool doWalkAction(const BattleAction & ba);
-	bool doWaitAction(const BattleAction & ba);
-	bool doDefendAction(const BattleAction & ba);
-	bool doAttackAction(const BattleAction & ba);
-	bool doShootAction(const BattleAction & ba);
-	bool doCatapultAction(const BattleAction & ba);
-	bool doUnitSpellAction(const BattleAction & ba);
-	bool doHealAction(const BattleAction & ba);
-
-	bool dispatchBattleAction(const BattleAction & ba);
-	bool makeBattleActionImpl(const BattleAction & ba);
+	bool canStackAct(const BattleInfo & battle, const CStack * stack);
+
+	bool doEmptyAction(const BattleInfo & battle, const BattleAction & ba);
+	bool doEndTacticsAction(const BattleInfo & battle, const BattleAction & ba);
+	bool doRetreatAction(const BattleInfo & battle, const BattleAction & ba);
+	bool doSurrenderAction(const BattleInfo & battle, const BattleAction & ba);
+	bool doHeroSpellAction(const BattleInfo & battle, const BattleAction & ba);
+	bool doWalkAction(const BattleInfo & battle, const BattleAction & ba);
+	bool doWaitAction(const BattleInfo & battle, const BattleAction & ba);
+	bool doDefendAction(const BattleInfo & battle, const BattleAction & ba);
+	bool doAttackAction(const BattleInfo & battle, const BattleAction & ba);
+	bool doShootAction(const BattleInfo & battle, const BattleAction & ba);
+	bool doCatapultAction(const BattleInfo & battle, const BattleAction & ba);
+	bool doUnitSpellAction(const BattleInfo & battle, const BattleAction & ba);
+	bool doHealAction(const BattleInfo & battle, const BattleAction & ba);
+
+	bool dispatchBattleAction(const BattleInfo & battle, const BattleAction & ba);
+	bool makeBattleActionImpl(const BattleInfo & battle, const BattleAction & ba);
 
 
 public:
 public:
 	explicit BattleActionProcessor(BattleProcessor * owner);
 	explicit BattleActionProcessor(BattleProcessor * owner);
 	void setGameHandler(CGameHandler * newGameHandler);
 	void setGameHandler(CGameHandler * newGameHandler);
 
 
-	bool makeAutomaticBattleAction(const BattleAction & ba);
-	bool makePlayerBattleAction(PlayerColor player, const BattleAction & ba);
+	bool makeAutomaticBattleAction(const BattleInfo & battle, const BattleAction & ba);
+	bool makePlayerBattleAction(const BattleInfo & battle, PlayerColor player, const BattleAction & ba);
 };
 };

+ 88 - 95
server/battles/BattleFlowProcessor.cpp

@@ -35,7 +35,7 @@ void BattleFlowProcessor::setGameHandler(CGameHandler * newGameHandler)
 	gameHandler = newGameHandler;
 	gameHandler = newGameHandler;
 }
 }
 
 
-void BattleFlowProcessor::summonGuardiansHelper(std::vector<BattleHex> & output, const BattleHex & targetPosition, ui8 side, bool targetIsTwoHex) //return hexes for summoning two hex monsters in output, target = unit to guard
+void BattleFlowProcessor::summonGuardiansHelper(const BattleInfo & battle, std::vector<BattleHex> & output, const BattleHex & targetPosition, ui8 side, bool targetIsTwoHex) //return hexes for summoning two hex monsters in output, target = unit to guard
 {
 {
 	int x = targetPosition.getX();
 	int x = targetPosition.getX();
 	int y = targetPosition.getY();
 	int y = targetPosition.getY();
@@ -110,34 +110,33 @@ void BattleFlowProcessor::summonGuardiansHelper(std::vector<BattleHex> & output,
 	}
 	}
 }
 }
 
 
-void BattleFlowProcessor::tryPlaceMoats()
+void BattleFlowProcessor::tryPlaceMoats(const BattleInfo & battle)
 {
 {
 	//Moat should be initialized here, because only here we can use spellcasting
 	//Moat should be initialized here, because only here we can use spellcasting
-	if (gameHandler->gameState()->curB->town && gameHandler->gameState()->curB->town->fortLevel() >= CGTownInstance::CITADEL)
+	if (battle.town && battle.town->fortLevel() >= CGTownInstance::CITADEL)
 	{
 	{
-		const auto * h = gameHandler->gameState()->curB->battleGetFightingHero(BattleSide::DEFENDER);
+		const auto * h = battle.battleGetFightingHero(BattleSide::DEFENDER);
 		const auto * actualCaster = h ? static_cast<const spells::Caster*>(h) : nullptr;
 		const auto * actualCaster = h ? static_cast<const spells::Caster*>(h) : nullptr;
-		auto moatCaster = spells::SilentCaster(gameHandler->gameState()->curB->getSidePlayer(BattleSide::DEFENDER), actualCaster);
-		auto cast = spells::BattleCast(gameHandler->gameState()->curB, &moatCaster, spells::Mode::PASSIVE, gameHandler->gameState()->curB->town->town->moatAbility.toSpell());
+		auto moatCaster = spells::SilentCaster(battle.getSidePlayer(BattleSide::DEFENDER), actualCaster);
+		auto cast = spells::BattleCast(&battle, &moatCaster, spells::Mode::PASSIVE, battle.town->town->moatAbility.toSpell());
 		auto target = spells::Target();
 		auto target = spells::Target();
 		cast.cast(gameHandler->spellEnv, target);
 		cast.cast(gameHandler->spellEnv, target);
 	}
 	}
 }
 }
 
 
-void BattleFlowProcessor::onBattleStarted()
+void BattleFlowProcessor::onBattleStarted(const BattleInfo & battle)
 {
 {
-	gameHandler->setBattle(gameHandler->gameState()->curB);
-	assert(gameHandler->gameState()->curB);
+	gameHandler->setBattle(&battle);
 
 
-	tryPlaceMoats();
+	tryPlaceMoats(battle);
 	
 	
-	gameHandler->turnTimerHandler.onBattleStart();
+	gameHandler->turnTimerHandler.onBattleStart(battle.battleID);
 
 
-	if (gameHandler->gameState()->curB->tacticDistance == 0)
-		onTacticsEnded();
+	if (battle.tacticDistance == 0)
+		onTacticsEnded(battle);
 }
 }
 
 
-void BattleFlowProcessor::trySummonGuardians(const CStack * stack)
+void BattleFlowProcessor::trySummonGuardians(const BattleInfo & battle, const CStack * stack)
 {
 {
 	if (!stack->hasBonusOfType(BonusType::SUMMON_GUARDIANS))
 	if (!stack->hasBonusOfType(BonusType::SUMMON_GUARDIANS))
 		return;
 		return;
@@ -156,14 +155,14 @@ void BattleFlowProcessor::trySummonGuardians(const CStack * stack)
 	if (!guardianIsBig)
 	if (!guardianIsBig)
 		targetHexes = stack->getSurroundingHexes();
 		targetHexes = stack->getSurroundingHexes();
 	else
 	else
-		summonGuardiansHelper(targetHexes, stack->getPosition(), stack->unitSide(), targetIsBig);
+		summonGuardiansHelper(battle, targetHexes, stack->getPosition(), stack->unitSide(), targetIsBig);
 
 
 	for(auto hex : targetHexes)
 	for(auto hex : targetHexes)
 	{
 	{
 		if(accessibility.accessible(hex, guardianIsBig, stack->unitSide())) //without this multiple creatures can occupy one hex
 		if(accessibility.accessible(hex, guardianIsBig, stack->unitSide())) //without this multiple creatures can occupy one hex
 		{
 		{
 			battle::UnitInfo info;
 			battle::UnitInfo info;
-			info.id = gameHandler->gameState()->curB->battleNextUnitId();
+			info.id = battle.battleNextUnitId();
 			info.count =  std::max(1, (int)(stack->getCount() * 0.01 * summonInfo->val));
 			info.count =  std::max(1, (int)(stack->getCount() * 0.01 * summonInfo->val));
 			info.type = creatureData;
 			info.type = creatureData;
 			info.side = stack->unitSide();
 			info.side = stack->unitSide();
@@ -178,11 +177,11 @@ void BattleFlowProcessor::trySummonGuardians(const CStack * stack)
 	}
 	}
 }
 }
 
 
-void BattleFlowProcessor::castOpeningSpells()
+void BattleFlowProcessor::castOpeningSpells(const BattleInfo & battle)
 {
 {
 	for (int i = 0; i < 2; ++i)
 	for (int i = 0; i < 2; ++i)
 	{
 	{
-		auto h = gameHandler->gameState()->curB->battleGetFightingHero(i);
+		auto h = battle.battleGetFightingHero(i);
 		if (!h)
 		if (!h)
 			continue;
 			continue;
 
 
@@ -194,7 +193,7 @@ void BattleFlowProcessor::castOpeningSpells()
 
 
 			const CSpell * spell = SpellID(b->subtype).toSpell();
 			const CSpell * spell = SpellID(b->subtype).toSpell();
 
 
-			spells::BattleCast parameters(gameHandler->gameState()->curB, &caster, spells::Mode::PASSIVE, spell);
+			spells::BattleCast parameters(&battle, &caster, spells::Mode::PASSIVE, spell);
 			parameters.setSpellLevel(3);
 			parameters.setSpellLevel(3);
 			parameters.setEffectDuration(b->val);
 			parameters.setEffectDuration(b->val);
 			parameters.massive = true;
 			parameters.massive = true;
@@ -203,55 +202,55 @@ void BattleFlowProcessor::castOpeningSpells()
 	}
 	}
 }
 }
 
 
-void BattleFlowProcessor::onTacticsEnded()
+void BattleFlowProcessor::onTacticsEnded(const BattleInfo & battle)
 {
 {
 	//initial stacks appearance triggers, e.g. built-in bonus spells
 	//initial stacks appearance triggers, e.g. built-in bonus spells
-	auto initialStacks = gameHandler->gameState()->curB->stacks; //use temporary variable to outclude summoned stacks added to gameHandler->gameState()->curB->stacks from processing
+	auto initialStacks = battle.stacks; //use temporary variable to outclude summoned stacks added to battle.stacks from processing
 
 
 	for (CStack * stack : initialStacks)
 	for (CStack * stack : initialStacks)
 	{
 	{
-		trySummonGuardians(stack);
-		stackEnchantedTrigger(stack);
+		trySummonGuardians(battle, stack);
+		stackEnchantedTrigger(battle, stack);
 	}
 	}
 
 
-	castOpeningSpells();
+	castOpeningSpells(battle);
 
 
 	// it is possible that due to opening spells one side was eliminated -> check for end of battle
 	// it is possible that due to opening spells one side was eliminated -> check for end of battle
-	if (owner->checkBattleStateChanges())
+	if (owner->checkBattleStateChanges(battle))
 		return;
 		return;
 
 
-	startNextRound(true);
-	activateNextStack();
+	startNextRound(battle, true);
+	activateNextStack(battle);
 }
 }
 
 
-void BattleFlowProcessor::startNextRound(bool isFirstRound)
+void BattleFlowProcessor::startNextRound(const BattleInfo & battle, bool isFirstRound)
 {
 {
 	BattleNextRound bnr;
 	BattleNextRound bnr;
-	bnr.round = gameHandler->gameState()->curB->round + 1;
+	bnr.round = battle.round + 1;
 	logGlobal->debug("Round %d", bnr.round);
 	logGlobal->debug("Round %d", bnr.round);
 	gameHandler->sendAndApply(&bnr);
 	gameHandler->sendAndApply(&bnr);
 
 
-	auto obstacles = gameHandler->gameState()->curB->obstacles; //we copy container, because we're going to modify it
+	auto obstacles = battle.obstacles; //we copy container, because we're going to modify it
 	for (auto &obstPtr : obstacles)
 	for (auto &obstPtr : obstacles)
 	{
 	{
 		if (const SpellCreatedObstacle *sco = dynamic_cast<const SpellCreatedObstacle *>(obstPtr.get()))
 		if (const SpellCreatedObstacle *sco = dynamic_cast<const SpellCreatedObstacle *>(obstPtr.get()))
 			if (sco->turnsRemaining == 0)
 			if (sco->turnsRemaining == 0)
-				removeObstacle(*obstPtr);
+				removeObstacle(battle, *obstPtr);
 	}
 	}
 
 
-	const BattleInfo & curB = *gameHandler->gameState()->curB;
+	const BattleInfo & curB = *&battle;
 
 
 	for(auto stack : curB.stacks)
 	for(auto stack : curB.stacks)
 	{
 	{
 		if(stack->alive() && !isFirstRound)
 		if(stack->alive() && !isFirstRound)
-			stackEnchantedTrigger(stack);
+			stackEnchantedTrigger(battle, stack);
 	}
 	}
 }
 }
 
 
-const CStack * BattleFlowProcessor::getNextStack()
+const CStack * BattleFlowProcessor::getNextStack(const BattleInfo & battle)
 {
 {
 	std::vector<battle::Units> q;
 	std::vector<battle::Units> q;
-	gameHandler->gameState()->curB->battleGetTurnOrder(q, 1, 0, -1); //todo: get rid of "turn -1"
+	battle.battleGetTurnOrder(q, 1, 0, -1); //todo: get rid of "turn -1"
 
 
 	if(q.empty())
 	if(q.empty())
 		return nullptr;
 		return nullptr;
@@ -284,31 +283,29 @@ const CStack * BattleFlowProcessor::getNextStack()
 	return stack;
 	return stack;
 }
 }
 
 
-void BattleFlowProcessor::activateNextStack()
+void BattleFlowProcessor::activateNextStack(const BattleInfo & battle)
 {
 {
-	const auto & curB = *gameHandler->gameState()->curB;
-
 	// Find next stack that requires manual control
 	// Find next stack that requires manual control
 	for (;;)
 	for (;;)
 	{
 	{
 		// battle has ended
 		// battle has ended
-		if (owner->checkBattleStateChanges())
+		if (owner->checkBattleStateChanges(battle))
 			return;
 			return;
 
 
-		const CStack * next = getNextStack();
+		const CStack * next = getNextStack(battle);
 
 
 		if (!next)
 		if (!next)
 		{
 		{
 			// No stacks to move - start next round
 			// No stacks to move - start next round
-			startNextRound(false);
-			next = getNextStack();
+			startNextRound(battle, false);
+			next = getNextStack(battle);
 			if (!next)
 			if (!next)
 				throw std::runtime_error("Failed to find valid stack to act!");
 				throw std::runtime_error("Failed to find valid stack to act!");
 		}
 		}
 
 
 		BattleUnitsChanged removeGhosts;
 		BattleUnitsChanged removeGhosts;
 
 
-		for(auto stack : curB.stacks)
+		for(auto stack : battle.stacks)
 		{
 		{
 			if(stack->ghostPending)
 			if(stack->ghostPending)
 				removeGhosts.changedStacks.emplace_back(stack->unitId(), UnitChanges::EOperation::REMOVE);
 				removeGhosts.changedStacks.emplace_back(stack->unitId(), UnitChanges::EOperation::REMOVE);
@@ -317,20 +314,18 @@ void BattleFlowProcessor::activateNextStack()
 		if(!removeGhosts.changedStacks.empty())
 		if(!removeGhosts.changedStacks.empty())
 			gameHandler->sendAndApply(&removeGhosts);
 			gameHandler->sendAndApply(&removeGhosts);
 		
 		
-		gameHandler->turnTimerHandler.onBattleNextStack(*next);
+		gameHandler->turnTimerHandler.onBattleNextStack(battle.battleID, *next);
 
 
-		if (!tryMakeAutomaticAction(next))
+		if (!tryMakeAutomaticAction(battle, next))
 		{
 		{
-			setActiveStack(next);
+			setActiveStack(battle, next);
 			break;
 			break;
 		}
 		}
 	}
 	}
 }
 }
 
 
-bool BattleFlowProcessor::tryMakeAutomaticAction(const CStack * next)
+bool BattleFlowProcessor::tryMakeAutomaticAction(const BattleInfo & battle, const CStack * next)
 {
 {
-	const auto & curB = *gameHandler->gameState()->curB;
-
 	// check for bad morale => freeze
 	// check for bad morale => freeze
 	int nextStackMorale = next->moraleVal();
 	int nextStackMorale = next->moraleVal();
 	if(!next->hadMorale && !next->waited() && nextStackMorale < 0)
 	if(!next->hadMorale && !next->waited() && nextStackMorale < 0)
@@ -346,7 +341,7 @@ bool BattleFlowProcessor::tryMakeAutomaticAction(const CStack * next)
 			ba.side = next->unitSide();
 			ba.side = next->unitSide();
 			ba.stackNumber = next->unitId();
 			ba.stackNumber = next->unitId();
 
 
-			makeAutomaticAction(next, ba);
+			makeAutomaticAction(battle, next, ba);
 			return true;
 			return true;
 		}
 		}
 	}
 	}
@@ -354,7 +349,7 @@ bool BattleFlowProcessor::tryMakeAutomaticAction(const CStack * next)
 	if (next->hasBonusOfType(BonusType::ATTACKS_NEAREST_CREATURE)) //while in berserk
 	if (next->hasBonusOfType(BonusType::ATTACKS_NEAREST_CREATURE)) //while in berserk
 	{
 	{
 		logGlobal->trace("Handle Berserk effect");
 		logGlobal->trace("Handle Berserk effect");
-		std::pair<const battle::Unit *, BattleHex> attackInfo = curB.getNearestStack(next);
+		std::pair<const battle::Unit *, BattleHex> attackInfo = battle.getNearestStack(next);
 		if (attackInfo.first != nullptr)
 		if (attackInfo.first != nullptr)
 		{
 		{
 			BattleAction attack;
 			BattleAction attack;
@@ -364,12 +359,12 @@ bool BattleFlowProcessor::tryMakeAutomaticAction(const CStack * next)
 			attack.aimToHex(attackInfo.second);
 			attack.aimToHex(attackInfo.second);
 			attack.aimToUnit(attackInfo.first);
 			attack.aimToUnit(attackInfo.first);
 
 
-			makeAutomaticAction(next, attack);
+			makeAutomaticAction(battle, next, attack);
 			logGlobal->trace("Attacked nearest target %s", attackInfo.first->getDescription());
 			logGlobal->trace("Attacked nearest target %s", attackInfo.first->getDescription());
 		}
 		}
 		else
 		else
 		{
 		{
-			makeStackDoNothing(next);
+			makeStackDoNothing(battle, next);
 			logGlobal->trace("No target found");
 			logGlobal->trace("No target found");
 		}
 		}
 		return true;
 		return true;
@@ -390,12 +385,12 @@ bool BattleFlowProcessor::tryMakeAutomaticAction(const CStack * next)
 
 
 		const battle::Unit * target = nullptr;
 		const battle::Unit * target = nullptr;
 
 
-		for(auto & elem : gameHandler->gameState()->curB->stacks)
+		for(auto & elem : battle.stacks)
 		{
 		{
 			if(elem->unitType()->getId() != CreatureID::CATAPULT
 			if(elem->unitType()->getId() != CreatureID::CATAPULT
 			   && elem->unitOwner() != next->unitOwner()
 			   && elem->unitOwner() != next->unitOwner()
 			   && elem->isValidTarget()
 			   && elem->isValidTarget()
-			   && gameHandler->gameState()->curB->battleCanShoot(next, elem->getPosition()))
+			   && battle.battleCanShoot(next, elem->getPosition()))
 			{
 			{
 				target = elem;
 				target = elem;
 				break;
 				break;
@@ -404,23 +399,23 @@ bool BattleFlowProcessor::tryMakeAutomaticAction(const CStack * next)
 
 
 		if(target == nullptr)
 		if(target == nullptr)
 		{
 		{
-			makeStackDoNothing(next);
+			makeStackDoNothing(battle, next);
 		}
 		}
 		else
 		else
 		{
 		{
 			attack.aimToUnit(target);
 			attack.aimToUnit(target);
-			makeAutomaticAction(next, attack);
+			makeAutomaticAction(battle, next, attack);
 		}
 		}
 		return true;
 		return true;
 	}
 	}
 
 
 	if (next->unitType()->getId() == CreatureID::CATAPULT)
 	if (next->unitType()->getId() == CreatureID::CATAPULT)
 	{
 	{
-		const auto & attackableBattleHexes = curB.getAttackableBattleHexes();
+		const auto & attackableBattleHexes = battle.getAttackableBattleHexes();
 
 
 		if (attackableBattleHexes.empty())
 		if (attackableBattleHexes.empty())
 		{
 		{
-			makeStackDoNothing(next);
+			makeStackDoNothing(battle, next);
 			return true;
 			return true;
 		}
 		}
 
 
@@ -431,7 +426,7 @@ bool BattleFlowProcessor::tryMakeAutomaticAction(const CStack * next)
 			attack.side = next->unitSide();
 			attack.side = next->unitSide();
 			attack.stackNumber = next->unitId();
 			attack.stackNumber = next->unitId();
 
 
-			makeAutomaticAction(next, attack);
+			makeAutomaticAction(battle, next, attack);
 			return true;
 			return true;
 		}
 		}
 	}
 	}
@@ -445,7 +440,7 @@ bool BattleFlowProcessor::tryMakeAutomaticAction(const CStack * next)
 
 
 		if (possibleStacks.empty())
 		if (possibleStacks.empty())
 		{
 		{
-			makeStackDoNothing(next);
+			makeStackDoNothing(battle, next);
 			return true;
 			return true;
 		}
 		}
 
 
@@ -460,22 +455,22 @@ bool BattleFlowProcessor::tryMakeAutomaticAction(const CStack * next)
 			heal.side = next->unitSide();
 			heal.side = next->unitSide();
 			heal.stackNumber = next->unitId();
 			heal.stackNumber = next->unitId();
 
 
-			makeAutomaticAction(next, heal);
+			makeAutomaticAction(battle, next, heal);
 			return true;
 			return true;
 		}
 		}
 	}
 	}
 
 
-	stackTurnTrigger(next); //various effects
+	stackTurnTrigger(battle, next); //various effects
 
 
 	if(next->fear)
 	if(next->fear)
 	{
 	{
-		makeStackDoNothing(next); //end immediately if stack was affected by fear
+		makeStackDoNothing(battle, next); //end immediately if stack was affected by fear
 		return true;
 		return true;
 	}
 	}
 	return false;
 	return false;
 }
 }
 
 
-bool BattleFlowProcessor::rollGoodMorale(const CStack * next)
+bool BattleFlowProcessor::rollGoodMorale(const BattleInfo & battle, const CStack * next)
 {
 {
 	//check for good morale
 	//check for good morale
 	auto nextStackMorale = next->moraleVal();
 	auto nextStackMorale = next->moraleVal();
@@ -503,27 +498,25 @@ bool BattleFlowProcessor::rollGoodMorale(const CStack * next)
 	return false;
 	return false;
 }
 }
 
 
-void BattleFlowProcessor::onActionMade(const BattleAction &ba)
+void BattleFlowProcessor::onActionMade(const BattleInfo & battle, const BattleAction &ba)
 {
 {
-	const auto & battle = gameHandler->gameState()->curB;
-
+	const CStack * actedStack = battle.battleGetStackByID(ba.stackNumber, false);
+	const CStack * activeStack = battle.battleGetStackByID(battle.getActiveStackID(), false);
 	if (ba.actionType == EActionType::END_TACTIC_PHASE)
 	if (ba.actionType == EActionType::END_TACTIC_PHASE)
 	{
 	{
-		onTacticsEnded();
+		onTacticsEnded(battle);
 		return;
 		return;
 	}
 	}
 
 
-	const CStack * actedStack = battle->battleGetStackByID(ba.stackNumber, false);
-	const CStack * activeStack = battle->battleGetStackByID(battle->getActiveStackID(), false);
 
 
 	//we're after action, all results applied
 	//we're after action, all results applied
 
 
 	// check whether action has ended the battle
 	// check whether action has ended the battle
-	if(owner->checkBattleStateChanges())
+	if(owner->checkBattleStateChanges(battle))
 		return;
 		return;
 
 
 	// tactics - next stack will be selected by player
 	// tactics - next stack will be selected by player
-	if(battle->tacticDistance != 0)
+	if(battle.tacticDistance != 0)
 		return;
 		return;
 
 
 	if (ba.isUnitAction())
 	if (ba.isUnitAction())
@@ -531,10 +524,10 @@ void BattleFlowProcessor::onActionMade(const BattleAction &ba)
 		assert(activeStack != nullptr);
 		assert(activeStack != nullptr);
 		assert(actedStack != nullptr);
 		assert(actedStack != nullptr);
 
 
-		if (rollGoodMorale(actedStack))
+		if (rollGoodMorale(battle, actedStack))
 		{
 		{
 			// Good morale - same stack makes 2nd turn
 			// Good morale - same stack makes 2nd turn
-			setActiveStack(actedStack);
+			setActiveStack(battle, actedStack);
 			return;
 			return;
 		}
 		}
 	}
 	}
@@ -544,36 +537,36 @@ void BattleFlowProcessor::onActionMade(const BattleAction &ba)
 		{
 		{
 			// this is action made by hero AND unit is alive (e.g. not killed by casted spell)
 			// this is action made by hero AND unit is alive (e.g. not killed by casted spell)
 			// keep current active stack for next action
 			// keep current active stack for next action
-			setActiveStack(activeStack);
+			setActiveStack(battle, activeStack);
 			return;
 			return;
 		}
 		}
 	}
 	}
 
 
-	activateNextStack();
+	activateNextStack(battle);
 }
 }
 
 
-void BattleFlowProcessor::makeStackDoNothing(const CStack * next)
+void BattleFlowProcessor::makeStackDoNothing(const BattleInfo & battle, const CStack * next)
 {
 {
 	BattleAction doNothing;
 	BattleAction doNothing;
 	doNothing.actionType = EActionType::NO_ACTION;
 	doNothing.actionType = EActionType::NO_ACTION;
 	doNothing.side = next->unitSide();
 	doNothing.side = next->unitSide();
 	doNothing.stackNumber = next->unitId();
 	doNothing.stackNumber = next->unitId();
 
 
-	makeAutomaticAction(next, doNothing);
+	makeAutomaticAction(battle, next, doNothing);
 }
 }
 
 
-bool BattleFlowProcessor::makeAutomaticAction(const CStack *stack, BattleAction &ba)
+bool BattleFlowProcessor::makeAutomaticAction(const BattleInfo & battle, const CStack *stack, BattleAction &ba)
 {
 {
 	BattleSetActiveStack bsa;
 	BattleSetActiveStack bsa;
 	bsa.stack = stack->unitId();
 	bsa.stack = stack->unitId();
 	bsa.askPlayerInterface = false;
 	bsa.askPlayerInterface = false;
 	gameHandler->sendAndApply(&bsa);
 	gameHandler->sendAndApply(&bsa);
 
 
-	bool ret = owner->makeAutomaticBattleAction(ba);
+	bool ret = owner->makeAutomaticBattleAction(battle, ba);
 	return ret;
 	return ret;
 }
 }
 
 
-void BattleFlowProcessor::stackEnchantedTrigger(const CStack * st)
+void BattleFlowProcessor::stackEnchantedTrigger(const BattleInfo & battle, const CStack * st)
 {
 {
 	auto bl = *(st->getBonuses(Selector::type()(BonusType::ENCHANTED)));
 	auto bl = *(st->getBonuses(Selector::type()(BonusType::ENCHANTED)));
 	for(auto b : bl)
 	for(auto b : bl)
@@ -585,7 +578,7 @@ void BattleFlowProcessor::stackEnchantedTrigger(const CStack * st)
 		const int32_t val = bl.valOfBonuses(Selector::typeSubtype(b->type, b->subtype));
 		const int32_t val = bl.valOfBonuses(Selector::typeSubtype(b->type, b->subtype));
 		const int32_t level = ((val > 3) ? (val - 3) : val);
 		const int32_t level = ((val > 3) ? (val - 3) : val);
 
 
-		spells::BattleCast battleCast(gameHandler->gameState()->curB, st, spells::Mode::PASSIVE, sp);
+		spells::BattleCast battleCast(&battle, st, spells::Mode::PASSIVE, sp);
 		//this makes effect accumulate for at most 50 turns by default, but effect may be permanent and last till the end of battle
 		//this makes effect accumulate for at most 50 turns by default, but effect may be permanent and last till the end of battle
 		battleCast.setEffectDuration(50);
 		battleCast.setEffectDuration(50);
 		battleCast.setSpellLevel(level);
 		battleCast.setSpellLevel(level);
@@ -593,7 +586,7 @@ void BattleFlowProcessor::stackEnchantedTrigger(const CStack * st)
 
 
 		if(val > 3)
 		if(val > 3)
 		{
 		{
-			for(auto s : gameHandler->gameState()->curB->battleGetAllStacks())
+			for(auto s : battle.battleGetAllStacks())
 				if(gameHandler->battleMatchOwner(st, s, true) && s->isValidTarget()) //all allied
 				if(gameHandler->battleMatchOwner(st, s, true) && s->isValidTarget()) //all allied
 					target.emplace_back(s);
 					target.emplace_back(s);
 		}
 		}
@@ -605,14 +598,14 @@ void BattleFlowProcessor::stackEnchantedTrigger(const CStack * st)
 	}
 	}
 }
 }
 
 
-void BattleFlowProcessor::removeObstacle(const CObstacleInstance & obstacle)
+void BattleFlowProcessor::removeObstacle(const BattleInfo & battle, const CObstacleInstance & obstacle)
 {
 {
 	BattleObstaclesChanged obsRem;
 	BattleObstaclesChanged obsRem;
 	obsRem.changes.emplace_back(obstacle.uniqueID, ObstacleChanges::EOperation::REMOVE);
 	obsRem.changes.emplace_back(obstacle.uniqueID, ObstacleChanges::EOperation::REMOVE);
 	gameHandler->sendAndApply(&obsRem);
 	gameHandler->sendAndApply(&obsRem);
 }
 }
 
 
-void BattleFlowProcessor::stackTurnTrigger(const CStack *st)
+void BattleFlowProcessor::stackTurnTrigger(const BattleInfo & battle, const CStack *st)
 {
 {
 	BattleTriggerEffect bte;
 	BattleTriggerEffect bte;
 	bte.stackID = st->unitId();
 	bte.stackID = st->unitId();
@@ -626,13 +619,13 @@ void BattleFlowProcessor::stackTurnTrigger(const CStack *st)
 		{
 		{
 			bool unbind = true;
 			bool unbind = true;
 			BonusList bl = *(st->getBonuses(Selector::type()(BonusType::BIND_EFFECT)));
 			BonusList bl = *(st->getBonuses(Selector::type()(BonusType::BIND_EFFECT)));
-			auto adjacent = gameHandler->gameState()->curB->battleAdjacentUnits(st);
+			auto adjacent = battle.battleAdjacentUnits(st);
 
 
 			for (auto b : bl)
 			for (auto b : bl)
 			{
 			{
 				if(b->additionalInfo != CAddInfo::NONE)
 				if(b->additionalInfo != CAddInfo::NONE)
 				{
 				{
-					const CStack * stack = gameHandler->gameState()->curB->battleGetStackByID(b->additionalInfo[0]); //binding stack must be alive and adjacent
+					const CStack * stack = battle.battleGetStackByID(b->additionalInfo[0]); //binding stack must be alive and adjacent
 					if(stack)
 					if(stack)
 					{
 					{
 						if(vstd::contains(adjacent, stack)) //binding stack is still present
 						if(vstd::contains(adjacent, stack)) //binding stack is still present
@@ -668,8 +661,8 @@ void BattleFlowProcessor::stackTurnTrigger(const CStack *st)
 		}
 		}
 		if(st->hasBonusOfType(BonusType::MANA_DRAIN) && !st->drainedMana)
 		if(st->hasBonusOfType(BonusType::MANA_DRAIN) && !st->drainedMana)
 		{
 		{
-			const PlayerColor opponent = gameHandler->gameState()->curB->otherPlayer(gameHandler->gameState()->curB->battleGetOwner(st));
-			const CGHeroInstance * opponentHero = gameHandler->gameState()->curB->getHero(opponent);
+			const PlayerColor opponent = battle.otherPlayer(battle.battleGetOwner(st));
+			const CGHeroInstance * opponentHero = battle.getHero(opponent);
 			if(opponentHero)
 			if(opponentHero)
 			{
 			{
 				ui32 manaDrained = st->valOfBonuses(BonusType::MANA_DRAIN);
 				ui32 manaDrained = st->valOfBonuses(BonusType::MANA_DRAIN);
@@ -686,7 +679,7 @@ void BattleFlowProcessor::stackTurnTrigger(const CStack *st)
 		if (st->isLiving() && !st->hasBonusOfType(BonusType::FEARLESS))
 		if (st->isLiving() && !st->hasBonusOfType(BonusType::FEARLESS))
 		{
 		{
 			bool fearsomeCreature = false;
 			bool fearsomeCreature = false;
-			for (CStack * stack : gameHandler->gameState()->curB->stacks)
+			for (CStack * stack : battle.stacks)
 			{
 			{
 				if (gameHandler->battleMatchOwner(st, stack) && stack->alive() && stack->hasBonusOfType(BonusType::FEAR))
 				if (gameHandler->battleMatchOwner(st, stack) && stack->alive() && stack->hasBonusOfType(BonusType::FEAR))
 				{
 				{
@@ -704,8 +697,8 @@ void BattleFlowProcessor::stackTurnTrigger(const CStack *st)
 			}
 			}
 		}
 		}
 		BonusList bl = *(st->getBonuses(Selector::type()(BonusType::ENCHANTER)));
 		BonusList bl = *(st->getBonuses(Selector::type()(BonusType::ENCHANTER)));
-		int side = gameHandler->gameState()->curB->whatSide(st->unitOwner());
-		if(st->canCast() && gameHandler->gameState()->curB->battleGetEnchanterCounter(side) == 0)
+		int side = battle.whatSide(st->unitOwner());
+		if(st->canCast() && battle.battleGetEnchanterCounter(side) == 0)
 		{
 		{
 			bool cast = false;
 			bool cast = false;
 			while(!bl.empty() && !cast)
 			while(!bl.empty() && !cast)
@@ -717,7 +710,7 @@ void BattleFlowProcessor::stackTurnTrigger(const CStack *st)
 				{
 				{
 					return b == bonus.get();
 					return b == bonus.get();
 				});
 				});
-				spells::BattleCast parameters(gameHandler->gameState()->curB, st, spells::Mode::ENCHANTER, spell);
+				spells::BattleCast parameters(&battle, st, spells::Mode::ENCHANTER, spell);
 				parameters.setSpellLevel(bonus->val);
 				parameters.setSpellLevel(bonus->val);
 				parameters.massive = true;
 				parameters.massive = true;
 				parameters.smart = true;
 				parameters.smart = true;
@@ -739,7 +732,7 @@ void BattleFlowProcessor::stackTurnTrigger(const CStack *st)
 	}
 	}
 }
 }
 
 
-void BattleFlowProcessor::setActiveStack(const CStack * stack)
+void BattleFlowProcessor::setActiveStack(const BattleInfo & battle, const CStack * stack)
 {
 {
 	assert(stack);
 	assert(stack);
 
 

+ 19 - 18
server/battles/BattleFlowProcessor.h

@@ -13,6 +13,7 @@ VCMI_LIB_NAMESPACE_BEGIN
 class CStack;
 class CStack;
 struct BattleHex;
 struct BattleHex;
 class BattleAction;
 class BattleAction;
+class BattleInfo;
 struct CObstacleInstance;
 struct CObstacleInstance;
 VCMI_LIB_NAMESPACE_END
 VCMI_LIB_NAMESPACE_END
 
 
@@ -25,31 +26,31 @@ class BattleFlowProcessor : boost::noncopyable
 	BattleProcessor * owner;
 	BattleProcessor * owner;
 	CGameHandler * gameHandler;
 	CGameHandler * gameHandler;
 
 
-	const CStack * getNextStack();
+	const CStack * getNextStack(const BattleInfo & battle);
 
 
-	bool rollGoodMorale(const CStack * stack);
-	bool tryMakeAutomaticAction(const CStack * stack);
+	bool rollGoodMorale(const BattleInfo & battle, const CStack * stack);
+	bool tryMakeAutomaticAction(const BattleInfo & battle, const CStack * stack);
 
 
-	void summonGuardiansHelper(std::vector<BattleHex> & output, const BattleHex & targetPosition, ui8 side, bool targetIsTwoHex);
-	void trySummonGuardians(const CStack * stack);
-	void tryPlaceMoats();
-	void castOpeningSpells();
-	void activateNextStack();
-	void startNextRound(bool isFirstRound);
+	void summonGuardiansHelper(const BattleInfo & battle, std::vector<BattleHex> & output, const BattleHex & targetPosition, ui8 side, bool targetIsTwoHex);
+	void trySummonGuardians(const BattleInfo & battle, const CStack * stack);
+	void tryPlaceMoats(const BattleInfo & battle);
+	void castOpeningSpells(const BattleInfo & battle);
+	void activateNextStack(const BattleInfo & battle);
+	void startNextRound(const BattleInfo & battle, bool isFirstRound);
 
 
-	void stackEnchantedTrigger(const CStack * stack);
-	void removeObstacle(const CObstacleInstance & obstacle);
-	void stackTurnTrigger(const CStack * stack);
-	void setActiveStack(const CStack * stack);
+	void stackEnchantedTrigger(const BattleInfo & battle, const CStack * stack);
+	void removeObstacle(const BattleInfo & battle, const CObstacleInstance & obstacle);
+	void stackTurnTrigger(const BattleInfo & battle, const CStack * stack);
+	void setActiveStack(const BattleInfo & battle, const CStack * stack);
 
 
-	void makeStackDoNothing(const CStack * next);
-	bool makeAutomaticAction(const CStack * stack, BattleAction & ba); //used when action is taken by stack without volition of player (eg. unguided catapult attack)
+	void makeStackDoNothing(const BattleInfo & battle, const CStack * next);
+	bool makeAutomaticAction(const BattleInfo & battle, const CStack * stack, BattleAction & ba); //used when action is taken by stack without volition of player (eg. unguided catapult attack)
 
 
 public:
 public:
 	explicit BattleFlowProcessor(BattleProcessor * owner);
 	explicit BattleFlowProcessor(BattleProcessor * owner);
 	void setGameHandler(CGameHandler * newGameHandler);
 	void setGameHandler(CGameHandler * newGameHandler);
 
 
-	void onBattleStarted();
-	void onTacticsEnded();
-	void onActionMade(const BattleAction & ba);
+	void onBattleStarted(const BattleInfo & battle);
+	void onTacticsEnded(const BattleInfo & battle);
+	void onActionMade(const BattleInfo & battle, const BattleAction & ba);
 };
 };

+ 59 - 35
server/battles/BattleProcessor.cpp

@@ -54,8 +54,8 @@ void BattleProcessor::startBattlePrimary(const CArmedInstance *army1, const CArm
 								const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool creatureBank,
 								const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool creatureBank,
 								const CGTownInstance *town) //use hero=nullptr for no hero
 								const CGTownInstance *town) //use hero=nullptr for no hero
 {
 {
-	if(gameHandler->gameState()->curB)
-		gameHandler->gameState()->curB.dellNull();
+	assert(gameHandler->gameState()->getBattle(army1->getOwner()) == nullptr);
+	assert(gameHandler->gameState()->getBattle(army2->getOwner()) == nullptr);
 
 
 	engageIntoBattle(army1->tempOwner);
 	engageIntoBattle(army1->tempOwner);
 	engageIntoBattle(army2->tempOwner);
 	engageIntoBattle(army2->tempOwner);
@@ -67,10 +67,12 @@ void BattleProcessor::startBattlePrimary(const CArmedInstance *army1, const CArm
 	heroes[0] = hero1;
 	heroes[0] = hero1;
 	heroes[1] = hero2;
 	heroes[1] = hero2;
 
 
-	resultProcessor->setupBattle();
-	setupBattle(tile, armies, heroes, creatureBank, town); //initializes stacks, places creatures on battlefield, blocks and informs player interfaces
+	auto battleID = setupBattle(tile, armies, heroes, creatureBank, town); //initializes stacks, places creatures on battlefield, blocks and informs player interfaces
 
 
-	auto lastBattleQuery = std::dynamic_pointer_cast<CBattleQuery>(gameHandler->queries->topQuery(gameHandler->gameState()->curB->sides[0].color));
+	const auto * battle = gameHandler->gameState()->getBattle(battleID);
+	assert(battle);
+
+	auto lastBattleQuery = std::dynamic_pointer_cast<CBattleQuery>(gameHandler->queries->topQuery(battle->sides[0].color));
 
 
 	//existing battle query for retying auto-combat
 	//existing battle query for retying auto-combat
 	if(lastBattleQuery)
 	if(lastBattleQuery)
@@ -86,13 +88,13 @@ void BattleProcessor::startBattlePrimary(const CArmedInstance *army1, const CArm
 			}
 			}
 		}
 		}
 
 
-		lastBattleQuery->bi = gameHandler->gameState()->curB;
+		lastBattleQuery->bi = battle;
 		lastBattleQuery->result = std::nullopt;
 		lastBattleQuery->result = std::nullopt;
-		lastBattleQuery->belligerents[0] = gameHandler->gameState()->curB->sides[0].armyObject;
-		lastBattleQuery->belligerents[1] = gameHandler->gameState()->curB->sides[1].armyObject;
+		lastBattleQuery->belligerents[0] = battle->sides[0].armyObject;
+		lastBattleQuery->belligerents[1] = battle->sides[1].armyObject;
 	}
 	}
 
 
-	auto nextBattleQuery = std::make_shared<CBattleQuery>(gameHandler, gameHandler->gameState()->curB);
+	auto nextBattleQuery = std::make_shared<CBattleQuery>(gameHandler, battle);
 	for(int i : {0, 1})
 	for(int i : {0, 1})
 	{
 	{
 		if(heroes[i])
 		if(heroes[i])
@@ -102,7 +104,7 @@ void BattleProcessor::startBattlePrimary(const CArmedInstance *army1, const CArm
 	}
 	}
 	gameHandler->queries->addQuery(nextBattleQuery);
 	gameHandler->queries->addQuery(nextBattleQuery);
 
 
-	flowProcessor->onBattleStarted();
+	flowProcessor->onBattleStarted(*battle);
 }
 }
 
 
 void BattleProcessor::startBattleI(const CArmedInstance *army1, const CArmedInstance *army2, int3 tile, bool creatureBank)
 void BattleProcessor::startBattleI(const CArmedInstance *army1, const CArmedInstance *army2, int3 tile, bool creatureBank)
@@ -118,7 +120,7 @@ void BattleProcessor::startBattleI(const CArmedInstance *army1, const CArmedInst
 	startBattleI(army1, army2, army2->visitablePos(), creatureBank);
 	startBattleI(army1, army2, army2->visitablePos(), creatureBank);
 }
 }
 
 
-void BattleProcessor::setupBattle(int3 tile, const CArmedInstance *armies[2], const CGHeroInstance *heroes[2], bool creatureBank, const CGTownInstance *town)
+BattleID BattleProcessor::setupBattle(int3 tile, const CArmedInstance *armies[2], const CGHeroInstance *heroes[2], bool creatureBank, const CGTownInstance *town)
 {
 {
 	const auto & t = *gameHandler->getTile(tile);
 	const auto & t = *gameHandler->getTile(tile);
 	TerrainId terrain = t.terType->getId();
 	TerrainId terrain = t.terType->getId();
@@ -132,6 +134,7 @@ void BattleProcessor::setupBattle(int3 tile, const CArmedInstance *armies[2], co
 	//send info about battles
 	//send info about battles
 	BattleStart bs;
 	BattleStart bs;
 	bs.info = BattleInfo::setupBattle(tile, terrain, terType, armies, heroes, creatureBank, town);
 	bs.info = BattleInfo::setupBattle(tile, terrain, terType, armies, heroes, creatureBank, town);
+	bs.battleID = gameHandler->gameState()->nextBattleID;
 
 
 	engageIntoBattle(bs.info->sides[0].color);
 	engageIntoBattle(bs.info->sides[0].color);
 	engageIntoBattle(bs.info->sides[1].color);
 	engageIntoBattle(bs.info->sides[1].color);
@@ -140,28 +143,30 @@ void BattleProcessor::setupBattle(int3 tile, const CArmedInstance *armies[2], co
 	bs.info->replayAllowed = lastBattleQuery == nullptr && !bs.info->sides[1].color.isValidPlayer();
 	bs.info->replayAllowed = lastBattleQuery == nullptr && !bs.info->sides[1].color.isValidPlayer();
 
 
 	gameHandler->sendAndApply(&bs);
 	gameHandler->sendAndApply(&bs);
+
+	return bs.battleID;
 }
 }
 
 
-bool BattleProcessor::checkBattleStateChanges()
+bool BattleProcessor::checkBattleStateChanges(const BattleInfo & battle)
 {
 {
 	//check if drawbridge state need to be changes
 	//check if drawbridge state need to be changes
 	if (gameHandler->battleGetSiegeLevel() > 0)
 	if (gameHandler->battleGetSiegeLevel() > 0)
-		updateGateState();
+		updateGateState(battle);
 
 
-	if (resultProcessor->battleIsEnding())
+	if (resultProcessor->battleIsEnding(battle))
 		return true;
 		return true;
 
 
 	//check if battle ended
 	//check if battle ended
 	if (auto result = gameHandler->battleIsFinished())
 	if (auto result = gameHandler->battleIsFinished())
 	{
 	{
-		setBattleResult(EBattleResult::NORMAL, *result);
+		setBattleResult(battle, EBattleResult::NORMAL, *result);
 		return true;
 		return true;
 	}
 	}
 
 
 	return false;
 	return false;
 }
 }
 
 
-void BattleProcessor::updateGateState()
+void BattleProcessor::updateGateState(const BattleInfo & battle)
 {
 {
 	// GATE_BRIDGE - leftmost tile, located over moat
 	// GATE_BRIDGE - leftmost tile, located over moat
 	// GATE_OUTER - central tile, mostly covered by gate image
 	// GATE_OUTER - central tile, mostly covered by gate image
@@ -178,17 +183,19 @@ void BattleProcessor::updateGateState()
 	// - deals moat damage to attacker if bridge is closed (fortress only)
 	// - deals moat damage to attacker if bridge is closed (fortress only)
 
 
 	bool hasForceFieldOnBridge = !gameHandler->battleGetAllObstaclesOnPos(BattleHex(BattleHex::GATE_BRIDGE), true).empty();
 	bool hasForceFieldOnBridge = !gameHandler->battleGetAllObstaclesOnPos(BattleHex(BattleHex::GATE_BRIDGE), true).empty();
-	bool hasStackAtGateInner   = gameHandler->gameState()->curB->battleGetUnitByPos(BattleHex(BattleHex::GATE_INNER), false) != nullptr;
-	bool hasStackAtGateOuter   = gameHandler->gameState()->curB->battleGetUnitByPos(BattleHex(BattleHex::GATE_OUTER), false) != nullptr;
-	bool hasStackAtGateBridge  = gameHandler->gameState()->curB->battleGetUnitByPos(BattleHex(BattleHex::GATE_BRIDGE), false) != nullptr;
+	bool hasStackAtGateInner   = battle.battleGetUnitByPos(BattleHex(BattleHex::GATE_INNER), false) != nullptr;
+	bool hasStackAtGateOuter   = battle.battleGetUnitByPos(BattleHex(BattleHex::GATE_OUTER), false) != nullptr;
+	bool hasStackAtGateBridge  = battle.battleGetUnitByPos(BattleHex(BattleHex::GATE_BRIDGE), false) != nullptr;
 	bool hasWideMoat           = vstd::contains_if(gameHandler->battleGetAllObstaclesOnPos(BattleHex(BattleHex::GATE_BRIDGE), false), [](const std::shared_ptr<const CObstacleInstance> & obst)
 	bool hasWideMoat           = vstd::contains_if(gameHandler->battleGetAllObstaclesOnPos(BattleHex(BattleHex::GATE_BRIDGE), false), [](const std::shared_ptr<const CObstacleInstance> & obst)
 	{
 	{
 		return obst->obstacleType == CObstacleInstance::MOAT;
 		return obst->obstacleType == CObstacleInstance::MOAT;
 	});
 	});
 
 
 	BattleUpdateGateState db;
 	BattleUpdateGateState db;
-	db.state = gameHandler->gameState()->curB->si.gateState;
-	if (gameHandler->gameState()->curB->si.wallState[EWallPart::GATE] == EWallState::DESTROYED)
+	db.state = battle.si.gateState;
+	db.battleID = battle.battleID;
+
+	if (battle.si.wallState.at(EWallPart::GATE) == EWallState::DESTROYED)
 	{
 	{
 		db.state = EGateState::DESTROYED;
 		db.state = EGateState::DESTROYED;
 	}
 	}
@@ -212,37 +219,54 @@ void BattleProcessor::updateGateState()
 			db.state = EGateState::CLOSED;
 			db.state = EGateState::CLOSED;
 	}
 	}
 
 
-	if (db.state != gameHandler->gameState()->curB->si.gateState)
+	if (db.state != battle.si.gateState)
 		gameHandler->sendAndApply(&db);
 		gameHandler->sendAndApply(&db);
 }
 }
 
 
-bool BattleProcessor::makePlayerBattleAction(PlayerColor player, const BattleAction &ba)
+bool BattleProcessor::makePlayerBattleAction(const BattleID & battleID, PlayerColor player, const BattleAction &ba)
 {
 {
-	bool result = actionsProcessor->makePlayerBattleAction(player, ba);
-	if (!resultProcessor->battleIsEnding() && gameHandler->gameState()->curB != nullptr)
-		flowProcessor->onActionMade(ba);
+	const auto * battle = gameHandler->gameState()->getBattle(battleID);
+
+	if (!battle)
+		return false;
+
+	bool result = actionsProcessor->makePlayerBattleAction(*battle, player, ba);
+	if (gameHandler->gameState()->getBattle(battleID) != nullptr && !resultProcessor->battleIsEnding(*battle))
+		flowProcessor->onActionMade(*battle, ba);
 	return result;
 	return result;
 }
 }
 
 
-void BattleProcessor::setBattleResult(EBattleResult resultType, int victoriusSide)
+void BattleProcessor::setBattleResult(const BattleInfo & battle, EBattleResult resultType, int victoriusSide)
 {
 {
-	resultProcessor->setBattleResult(resultType, victoriusSide);
-	resultProcessor->endBattle(gameHandler->gameState()->curB->tile, gameHandler->gameState()->curB->battleGetFightingHero(0), gameHandler->gameState()->curB->battleGetFightingHero(1));
+	resultProcessor->setBattleResult(battle, resultType, victoriusSide);
+	resultProcessor->endBattle(battle);
 }
 }
 
 
-bool BattleProcessor::makeAutomaticBattleAction(const BattleAction &ba)
+bool BattleProcessor::makeAutomaticBattleAction(const BattleInfo & battle, const BattleAction &ba)
 {
 {
-	return actionsProcessor->makeAutomaticBattleAction(ba);
+	return actionsProcessor->makeAutomaticBattleAction(battle, ba);
 }
 }
 
 
-void BattleProcessor::endBattleConfirm(const BattleInfo * battleInfo)
+void BattleProcessor::endBattleConfirm(const BattleID & battleID)
 {
 {
-	resultProcessor->endBattleConfirm(battleInfo);
+	auto battle = gameHandler->gameState()->getBattle(battleID);
+	assert(battle);
+
+	if (!battle)
+		return;
+
+	resultProcessor->endBattleConfirm(*battle);
 }
 }
 
 
-void BattleProcessor::battleAfterLevelUp(const BattleResult &result)
+void BattleProcessor::battleAfterLevelUp(const BattleID & battleID, const BattleResult &result)
 {
 {
-	resultProcessor->battleAfterLevelUp(result);
+	auto battle = gameHandler->gameState()->getBattle(battleID);
+	assert(battle);
+
+	if (!battle)
+		return;
+
+	resultProcessor->battleAfterLevelUp(*battle, result);
 }
 }
 
 
 void BattleProcessor::setGameHandler(CGameHandler * newGameHandler)
 void BattleProcessor::setGameHandler(CGameHandler * newGameHandler)

+ 9 - 8
server/battles/BattleProcessor.h

@@ -19,6 +19,7 @@ class BattleAction;
 class int3;
 class int3;
 class BattleInfo;
 class BattleInfo;
 struct BattleResult;
 struct BattleResult;
+class BattleID;
 VCMI_LIB_NAMESPACE_END
 VCMI_LIB_NAMESPACE_END
 
 
 class CGameHandler;
 class CGameHandler;
@@ -40,15 +41,15 @@ class BattleProcessor : boost::noncopyable
 	std::unique_ptr<BattleResultProcessor> resultProcessor;
 	std::unique_ptr<BattleResultProcessor> resultProcessor;
 
 
 	void onTacticsEnded();
 	void onTacticsEnded();
-	void updateGateState();
+	void updateGateState(const BattleInfo & battle);
 	void engageIntoBattle(PlayerColor player);
 	void engageIntoBattle(PlayerColor player);
 
 
-	bool checkBattleStateChanges();
-	void setupBattle(int3 tile, const CArmedInstance *armies[2], const CGHeroInstance *heroes[2], bool creatureBank, const CGTownInstance *town);
+	bool checkBattleStateChanges(const BattleInfo & battle);
+	BattleID setupBattle(int3 tile, const CArmedInstance *armies[2], const CGHeroInstance *heroes[2], bool creatureBank, const CGTownInstance *town);
 
 
-	bool makeAutomaticBattleAction(const BattleAction & ba);
+	bool makeAutomaticBattleAction(const BattleInfo & battle, const BattleAction & ba);
 
 
-	void setBattleResult(EBattleResult resultType, int victoriusSide);
+	void setBattleResult(const BattleInfo & battle, EBattleResult resultType, int victoriusSide);
 
 
 public:
 public:
 	explicit BattleProcessor(CGameHandler * gameHandler);
 	explicit BattleProcessor(CGameHandler * gameHandler);
@@ -65,12 +66,12 @@ public:
 	void startBattleI(const CArmedInstance *army1, const CArmedInstance *army2, bool creatureBank = false);
 	void startBattleI(const CArmedInstance *army1, const CArmedInstance *army2, bool creatureBank = false);
 
 
 	/// Processing of incoming battle action netpack
 	/// Processing of incoming battle action netpack
-	bool makePlayerBattleAction(PlayerColor player, const BattleAction & ba);
+	bool makePlayerBattleAction(const BattleID & battleID, PlayerColor player, const BattleAction & ba);
 
 
 	/// Applies results of a battle once player agrees to them
 	/// Applies results of a battle once player agrees to them
-	void endBattleConfirm(const BattleInfo * battleInfo);
+	void endBattleConfirm(const BattleID & battleID);
 	/// Applies results of a battle after potential levelup
 	/// Applies results of a battle after potential levelup
-	void battleAfterLevelUp(const BattleResult & result);
+	void battleAfterLevelUp(const BattleID & battleID, const BattleResult & result);
 
 
 	template <typename Handler> void serialize(Handler &h, const int version)
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
 	{

+ 44 - 35
server/battles/BattleResultProcessor.cpp

@@ -196,7 +196,7 @@ FinishingBattleHelper::FinishingBattleHelper()
 	remainingBattleQueriesCount = 0;
 	remainingBattleQueriesCount = 0;
 }
 }
 
 
-void BattleResultProcessor::endBattle(int3 tile, const CGHeroInstance * heroAttacker, const CGHeroInstance * heroDefender)
+void BattleResultProcessor::endBattle(const BattleInfo & battle)
 {
 {
 	auto const & giveExp = [](BattleResult &r)
 	auto const & giveExp = [](BattleResult &r)
 	{
 	{
@@ -215,6 +215,10 @@ void BattleResultProcessor::endBattle(int3 tile, const CGHeroInstance * heroAtta
 
 
 	LOG_TRACE(logGlobal);
 	LOG_TRACE(logGlobal);
 
 
+	auto * battleResult = battleResults.at(battle.battleID).get();
+	const auto * heroAttacker = battle.getSideHero(BattleSide::ATTACKER);
+	const auto * heroDefender = battle.getSideHero(BattleSide::DEFENDER);
+
 	//Fill BattleResult structure with exp info
 	//Fill BattleResult structure with exp info
 	giveExp(*battleResult);
 	giveExp(*battleResult);
 
 
@@ -231,11 +235,11 @@ void BattleResultProcessor::endBattle(int3 tile, const CGHeroInstance * heroAtta
 	if(heroDefender)
 	if(heroDefender)
 		battleResult->exp[1] = heroDefender->calculateXp(battleResult->exp[1]);
 		battleResult->exp[1] = heroDefender->calculateXp(battleResult->exp[1]);
 
 
-	auto battleQuery = std::dynamic_pointer_cast<CBattleQuery>(gameHandler->queries->topQuery(gameHandler->gameState()->curB->sides[0].color));
+	auto battleQuery = std::dynamic_pointer_cast<CBattleQuery>(gameHandler->queries->topQuery(battle.sides[0].color));
 	if (!battleQuery)
 	if (!battleQuery)
 	{
 	{
 		logGlobal->error("Cannot find battle query!");
 		logGlobal->error("Cannot find battle query!");
-		gameHandler->complain("Player " + boost::lexical_cast<std::string>(gameHandler->gameState()->curB->sides[0].color) + " has no battle query at the top!");
+		gameHandler->complain("Player " + boost::lexical_cast<std::string>(battle.sides[0].color) + " has no battle query at the top!");
 		return;
 		return;
 	}
 	}
 
 
@@ -243,12 +247,14 @@ void BattleResultProcessor::endBattle(int3 tile, const CGHeroInstance * heroAtta
 
 
 	//Check how many battle gameHandler->queries were created (number of players blocked by battle)
 	//Check how many battle gameHandler->queries were created (number of players blocked by battle)
 	const int queriedPlayers = battleQuery ? (int)boost::count(gameHandler->queries->allQueries(), battleQuery) : 0;
 	const int queriedPlayers = battleQuery ? (int)boost::count(gameHandler->queries->allQueries(), battleQuery) : 0;
-	finishingBattle = std::make_unique<FinishingBattleHelper>(battleQuery, queriedPlayers);
+
+	assert(finishingBattles.count(battle.battleID) == 0);
+	finishingBattles[battle.battleID] = std::make_unique<FinishingBattleHelper>(battleQuery, queriedPlayers);
 
 
 	// in battles against neutrals, 1st player can ask to replay battle manually
 	// in battles against neutrals, 1st player can ask to replay battle manually
-	if (!gameHandler->gameState()->curB->sides[1].color.isValidPlayer())
+	if (!battle.sides[1].color.isValidPlayer())
 	{
 	{
-		auto battleDialogQuery = std::make_shared<CBattleDialogQuery>(gameHandler, gameHandler->gameState()->curB);
+		auto battleDialogQuery = std::make_shared<CBattleDialogQuery>(gameHandler, &battle);
 		battleResult->queryID = battleDialogQuery->queryID;
 		battleResult->queryID = battleDialogQuery->queryID;
 		gameHandler->queries->addQuery(battleDialogQuery);
 		gameHandler->queries->addQuery(battleDialogQuery);
 	}
 	}
@@ -263,25 +269,27 @@ void BattleResultProcessor::endBattle(int3 tile, const CGHeroInstance * heroAtta
 			otherBattleQuery->result = battleQuery->result;
 			otherBattleQuery->result = battleQuery->result;
 	}
 	}
 
 
-	gameHandler->turnTimerHandler.onBattleEnd();
-	gameHandler->sendAndApply(battleResult.get()); //after this point casualties objects are destroyed
+	gameHandler->turnTimerHandler.onBattleEnd(battle.battleID);
 
 
 	if (battleResult->queryID == QueryID::NONE)
 	if (battleResult->queryID == QueryID::NONE)
-		endBattleConfirm(gameHandler->gameState()->curB);
+		endBattleConfirm(battle);
 }
 }
 
 
-void BattleResultProcessor::endBattleConfirm(const BattleInfo * battleInfo)
+void BattleResultProcessor::endBattleConfirm(const BattleInfo & battle)
 {
 {
-	auto battleQuery = std::dynamic_pointer_cast<CBattleQuery>(gameHandler->queries->topQuery(battleInfo->sides.at(0).color));
+	auto battleQuery = std::dynamic_pointer_cast<CBattleQuery>(gameHandler->queries->topQuery(battle.sides.at(0).color));
 	if(!battleQuery)
 	if(!battleQuery)
 	{
 	{
 		logGlobal->trace("No battle query, battle end was confirmed by another player");
 		logGlobal->trace("No battle query, battle end was confirmed by another player");
 		return;
 		return;
 	}
 	}
 
 
-	const EBattleResult result = battleResult.get()->result;
+	auto * battleResult = battleResults.at(battle.battleID).get();
+	auto * finishingBattle = finishingBattles.at(battle.battleID).get();
+
+	const EBattleResult result = battleResult->result;
 
 
-	CasualtiesAfterBattle cab1(battleInfo->sides.at(0), battleInfo), cab2(battleInfo->sides.at(1), battleInfo); //calculate casualties before deleting battle
+	CasualtiesAfterBattle cab1(battle.sides.at(0), &battle), cab2(battle.sides.at(1), &battle); //calculate casualties before deleting battle
 	ChangeSpells cs; //for Eagle Eye
 	ChangeSpells cs; //for Eagle Eye
 
 
 	if(!finishingBattle->isDraw() && finishingBattle->winnerHero)
 	if(!finishingBattle->isDraw() && finishingBattle->winnerHero)
@@ -289,7 +297,7 @@ void BattleResultProcessor::endBattleConfirm(const BattleInfo * battleInfo)
 		if (int eagleEyeLevel = finishingBattle->winnerHero->valOfBonuses(BonusType::LEARN_BATTLE_SPELL_LEVEL_LIMIT, -1))
 		if (int eagleEyeLevel = finishingBattle->winnerHero->valOfBonuses(BonusType::LEARN_BATTLE_SPELL_LEVEL_LIMIT, -1))
 		{
 		{
 			double eagleEyeChance = finishingBattle->winnerHero->valOfBonuses(BonusType::LEARN_BATTLE_SPELL_CHANCE, 0);
 			double eagleEyeChance = finishingBattle->winnerHero->valOfBonuses(BonusType::LEARN_BATTLE_SPELL_CHANCE, 0);
-			for(auto & spellId : battleInfo->sides.at(!battleResult->winner).usedSpellsHistory)
+			for(auto & spellId : battle.sides.at(!battleResult->winner).usedSpellsHistory)
 			{
 			{
 				auto spell = spellId.toSpell(VLC->spells());
 				auto spell = spellId.toSpell(VLC->spells());
 				if(spell && spell->getLevel() <= eagleEyeLevel && !finishingBattle->winnerHero->spellbookContainsSpell(spell->getId()) && gameHandler->getRandomGenerator().nextInt(99) < eagleEyeChance)
 				if(spell && spell->getLevel() <= eagleEyeLevel && !finishingBattle->winnerHero->spellbookContainsSpell(spell->getId()) && gameHandler->getRandomGenerator().nextInt(99) < eagleEyeChance)
@@ -357,7 +365,7 @@ void BattleResultProcessor::endBattleConfirm(const BattleInfo * battleInfo)
 				}
 				}
 			}
 			}
 		}
 		}
-		for (auto armySlot : battleInfo->sides.at(!battleResult->winner).armyObject->stacks)
+		for (auto armySlot : battle.sides.at(!battleResult->winner).armyObject->stacks)
 		{
 		{
 			auto artifactsWorn = armySlot.second->artifactsWorn;
 			auto artifactsWorn = armySlot.second->artifactsWorn;
 			for (auto artSlot : artifactsWorn)
 			for (auto artSlot : artifactsWorn)
@@ -458,10 +466,10 @@ void BattleResultProcessor::endBattleConfirm(const BattleInfo * battleInfo)
 		gameHandler->changePrimSkill(finishingBattle->winnerHero, PrimarySkill::EXPERIENCE, battleResult->exp[finishingBattle->winnerSide]);
 		gameHandler->changePrimSkill(finishingBattle->winnerHero, PrimarySkill::EXPERIENCE, battleResult->exp[finishingBattle->winnerSide]);
 
 
 	BattleResultAccepted raccepted;
 	BattleResultAccepted raccepted;
-	raccepted.heroResult[0].army = const_cast<CArmedInstance*>(battleInfo->sides.at(0).armyObject);
-	raccepted.heroResult[1].army = const_cast<CArmedInstance*>(battleInfo->sides.at(1).armyObject);
-	raccepted.heroResult[0].hero = const_cast<CGHeroInstance*>(battleInfo->sides.at(0).hero);
-	raccepted.heroResult[1].hero = const_cast<CGHeroInstance*>(battleInfo->sides.at(1).hero);
+	raccepted.heroResult[0].army = const_cast<CArmedInstance*>(battle.sides.at(0).armyObject);
+	raccepted.heroResult[1].army = const_cast<CArmedInstance*>(battle.sides.at(1).armyObject);
+	raccepted.heroResult[0].hero = const_cast<CGHeroInstance*>(battle.sides.at(0).hero);
+	raccepted.heroResult[1].hero = const_cast<CGHeroInstance*>(battle.sides.at(1).hero);
 	raccepted.heroResult[0].exp = battleResult->exp[0];
 	raccepted.heroResult[0].exp = battleResult->exp[0];
 	raccepted.heroResult[1].exp = battleResult->exp[1];
 	raccepted.heroResult[1].exp = battleResult->exp[1];
 	raccepted.winnerSide = finishingBattle->winnerSide; 
 	raccepted.winnerSide = finishingBattle->winnerSide; 
@@ -471,13 +479,16 @@ void BattleResultProcessor::endBattleConfirm(const BattleInfo * battleInfo)
 	//--> continuation (battleAfterLevelUp) occurs after level-up gameHandler->queries are handled or on removing query
 	//--> continuation (battleAfterLevelUp) occurs after level-up gameHandler->queries are handled or on removing query
 }
 }
 
 
-void BattleResultProcessor::battleAfterLevelUp(const BattleResult &result)
+void BattleResultProcessor::battleAfterLevelUp(const BattleInfo & battle, const BattleResult & result)
 {
 {
 	LOG_TRACE(logGlobal);
 	LOG_TRACE(logGlobal);
 
 
-	if(!finishingBattle)
+	assert(finishingBattles.count(battle.battleID) != 0);
+	if(finishingBattles.count(battle.battleID) == 0)
 		return;
 		return;
 
 
+	auto & finishingBattle = finishingBattles[battle.battleID];
+
 	finishingBattle->remainingBattleQueriesCount--;
 	finishingBattle->remainingBattleQueriesCount--;
 	logGlobal->trace("Decremented gameHandler->queries count to %d", finishingBattle->remainingBattleQueriesCount);
 	logGlobal->trace("Decremented gameHandler->queries count to %d", finishingBattle->remainingBattleQueriesCount);
 
 
@@ -490,7 +501,7 @@ void BattleResultProcessor::battleAfterLevelUp(const BattleResult &result)
 	// Still, it looks like a hole.
 	// Still, it looks like a hole.
 
 
 	// Necromancy if applicable.
 	// Necromancy if applicable.
-	const CStackBasicDescriptor raisedStack = finishingBattle->winnerHero ? finishingBattle->winnerHero->calculateNecromancy(*battleResult) : CStackBasicDescriptor();
+	const CStackBasicDescriptor raisedStack = finishingBattle->winnerHero ? finishingBattle->winnerHero->calculateNecromancy(result) : CStackBasicDescriptor();
 	// Give raised units to winner and show dialog, if any were raised,
 	// Give raised units to winner and show dialog, if any were raised,
 	// units will be given after casualties are taken
 	// units will be given after casualties are taken
 	const SlotID necroSlot = raisedStack.type ? finishingBattle->winnerHero->getSlotFor(raisedStack.type) : SlotID();
 	const SlotID necroSlot = raisedStack.type ? finishingBattle->winnerHero->getSlotFor(raisedStack.type) : SlotID();
@@ -528,25 +539,23 @@ void BattleResultProcessor::battleAfterLevelUp(const BattleResult &result)
 			gameHandler->heroPool->onHeroEscaped(finishingBattle->victor, finishingBattle->winnerHero);
 			gameHandler->heroPool->onHeroEscaped(finishingBattle->victor, finishingBattle->winnerHero);
 	}
 	}
 
 
-	finishingBattle.reset();
-	battleResult.reset();
+	finishingBattles.erase(battle.battleID);
+	battleResults.erase(battle.battleID);
 }
 }
 
 
-void BattleResultProcessor::setBattleResult(EBattleResult resultType, int victoriusSide)
+void BattleResultProcessor::setBattleResult(const BattleInfo & battle, EBattleResult resultType, int victoriusSide)
 {
 {
-	battleResult = std::make_unique<BattleResult>();
+	assert(battleResults.count(battle.battleID) == 0);
+
+	battleResults[battle.battleID] = std::make_unique<BattleResult>();
+
+	auto & battleResult = battleResults[battle.battleID];
 	battleResult->result = resultType;
 	battleResult->result = resultType;
 	battleResult->winner = victoriusSide; //surrendering side loses
 	battleResult->winner = victoriusSide; //surrendering side loses
-	gameHandler->gameState()->curB->calculateCasualties(battleResult->casualties);
-}
-
-void BattleResultProcessor::setupBattle()
-{
-	finishingBattle.reset();
-	battleResult.reset();
+	battle.calculateCasualties(battleResult->casualties);
 }
 }
 
 
-bool BattleResultProcessor::battleIsEnding() const
+bool BattleResultProcessor::battleIsEnding(const BattleInfo & battle) const
 {
 {
-	return battleResult != nullptr;
+	return battleResults.count(battle.battleID) != 0;
 }
 }

+ 7 - 8
server/battles/BattleResultProcessor.h

@@ -64,18 +64,17 @@ class BattleResultProcessor : boost::noncopyable
 	//	BattleProcessor * owner;
 	//	BattleProcessor * owner;
 	CGameHandler * gameHandler;
 	CGameHandler * gameHandler;
 
 
-	std::unique_ptr<BattleResult> battleResult;
-	std::unique_ptr<FinishingBattleHelper> finishingBattle;
+	std::map<BattleID, std::unique_ptr<BattleResult>> battleResults;
+	std::map<BattleID, std::unique_ptr<FinishingBattleHelper>> finishingBattles;
 
 
 public:
 public:
 	explicit BattleResultProcessor(BattleProcessor * owner);
 	explicit BattleResultProcessor(BattleProcessor * owner);
 	void setGameHandler(CGameHandler * newGameHandler);
 	void setGameHandler(CGameHandler * newGameHandler);
 
 
-	bool battleIsEnding() const;
+	bool battleIsEnding(const BattleInfo & battle) const;
 
 
-	void setupBattle();
-	void setBattleResult(EBattleResult resultType, int victoriusSide);
-	void endBattle(int3 tile, const CGHeroInstance * hero1, const CGHeroInstance * hero2); //ends battle
-	void endBattleConfirm(const BattleInfo * battleInfo);
-	void battleAfterLevelUp(const BattleResult & result);
+	void setBattleResult(const BattleInfo & battle, EBattleResult resultType, int victoriusSide);
+	void endBattle(const BattleInfo & battle); //ends battle
+	void endBattleConfirm(const BattleInfo & battle);
+	void battleAfterLevelUp(const BattleInfo & battle, const BattleResult & result);
 };
 };

+ 2 - 2
server/queries/BattleQueries.cpp

@@ -49,7 +49,7 @@ bool CBattleQuery::blocksPack(const CPack * pack) const
 void CBattleQuery::onRemoval(PlayerColor color)
 void CBattleQuery::onRemoval(PlayerColor color)
 {
 {
 	if(result)
 	if(result)
-		gh->battles->battleAfterLevelUp(*result);
+		gh->battles->battleAfterLevelUp(bi->battleID, *result);
 }
 }
 
 
 CBattleDialogQuery::CBattleDialogQuery(CGameHandler * owner, const BattleInfo * Bi):
 CBattleDialogQuery::CBattleDialogQuery(CGameHandler * owner, const BattleInfo * Bi):
@@ -70,6 +70,6 @@ void CBattleDialogQuery::onRemoval(PlayerColor color)
 	}
 	}
 	else
 	else
 	{
 	{
-		gh->battles->endBattleConfirm(bi);
+		gh->battles->endBattleConfirm(bi->battleID);
 	}
 	}
 }
 }

+ 12 - 12
test/game/CGameStateTest.cpp

@@ -204,9 +204,9 @@ public:
 
 
 		BattleStart bs;
 		BattleStart bs;
 		bs.info = battle;
 		bs.info = battle;
-		ASSERT_EQ(gameState->curB, nullptr);
+		ASSERT_EQ(gameState->currentBattles.size(), 0);
 		gameCallback->sendAndApply(&bs);
 		gameCallback->sendAndApply(&bs);
-		ASSERT_EQ(gameState->curB, battle);
+		ASSERT_EQ(gameState->currentBattles.size(), 1);
 	}
 	}
 
 
 	std::shared_ptr<CGameState> gameState;
 	std::shared_ptr<CGameState> gameState;
@@ -247,11 +247,11 @@ TEST_F(CGameStateTest, issue2765)
 
 
 	{
 	{
 		battle::UnitInfo info;
 		battle::UnitInfo info;
-		info.id = gameState->curB->battleNextUnitId();
+		info.id = gameState->currentBattles.front()->battleNextUnitId();
 		info.count = 1;
 		info.count = 1;
 		info.type = CreatureID(69);
 		info.type = CreatureID(69);
 		info.side = BattleSide::ATTACKER;
 		info.side = BattleSide::ATTACKER;
-		info.position = gameState->curB->getAvaliableHex(info.type, info.side);
+		info.position = gameState->currentBattles.front()->getAvaliableHex(info.type, info.side);
 		info.summoned = false;
 		info.summoned = false;
 
 
 		BattleUnitsChanged pack;
 		BattleUnitsChanged pack;
@@ -263,7 +263,7 @@ TEST_F(CGameStateTest, issue2765)
 	const CStack * att = nullptr;
 	const CStack * att = nullptr;
 	const CStack * def = nullptr;
 	const CStack * def = nullptr;
 
 
-	for(const CStack * s : gameState->curB->stacks)
+	for(const CStack * s : gameState->currentBattles.front()->stacks)
 	{
 	{
 		if(s->unitType()->getId() == CreatureID::BALLISTA && s->unitSide() == BattleSide::DEFENDER)
 		if(s->unitType()->getId() == CreatureID::BALLISTA && s->unitSide() == BattleSide::DEFENDER)
 			def = s;
 			def = s;
@@ -292,7 +292,7 @@ TEST_F(CGameStateTest, issue2765)
 		spells::AbilityCaster caster(att, 3);
 		spells::AbilityCaster caster(att, 3);
 
 
 		//here tested ballista, but this applied to all war machines
 		//here tested ballista, but this applied to all war machines
-		spells::BattleCast cast(gameState->curB, &caster, spells::Mode::PASSIVE, age);
+		spells::BattleCast cast(gameState->currentBattles.front().get(), &caster, spells::Mode::PASSIVE, age);
 
 
 		spells::Target target;
 		spells::Target target;
 		target.emplace_back(def);
 		target.emplace_back(def);
@@ -339,7 +339,7 @@ TEST_F(CGameStateTest, battleResurrection)
 
 
 	startTestBattle(attacker, defender);
 	startTestBattle(attacker, defender);
 
 
-	uint32_t unitId = gameState->curB->battleNextUnitId();
+	uint32_t unitId = gameState->currentBattles.front()->battleNextUnitId();
 
 
 	{
 	{
 		battle::UnitInfo info;
 		battle::UnitInfo info;
@@ -347,7 +347,7 @@ TEST_F(CGameStateTest, battleResurrection)
 		info.count = 10;
 		info.count = 10;
 		info.type = CreatureID(13);
 		info.type = CreatureID(13);
 		info.side = BattleSide::ATTACKER;
 		info.side = BattleSide::ATTACKER;
-		info.position = gameState->curB->getAvaliableHex(info.type, info.side);
+		info.position = gameState->currentBattles.front()->getAvaliableHex(info.type, info.side);
 		info.summoned = false;
 		info.summoned = false;
 
 
 		BattleUnitsChanged pack;
 		BattleUnitsChanged pack;
@@ -358,11 +358,11 @@ TEST_F(CGameStateTest, battleResurrection)
 
 
 	{
 	{
 		battle::UnitInfo info;
 		battle::UnitInfo info;
-		info.id = gameState->curB->battleNextUnitId();
+		info.id = gameState->currentBattles.front()->battleNextUnitId();
 		info.count = 10;
 		info.count = 10;
 		info.type = CreatureID(13);
 		info.type = CreatureID(13);
 		info.side = BattleSide::DEFENDER;
 		info.side = BattleSide::DEFENDER;
-		info.position = gameState->curB->getAvaliableHex(info.type, info.side);
+		info.position = gameState->currentBattles.front()->getAvaliableHex(info.type, info.side);
 		info.summoned = false;
 		info.summoned = false;
 
 
 		BattleUnitsChanged pack;
 		BattleUnitsChanged pack;
@@ -371,7 +371,7 @@ TEST_F(CGameStateTest, battleResurrection)
 		gameCallback->sendAndApply(&pack);
 		gameCallback->sendAndApply(&pack);
 	}
 	}
 
 
-	CStack * unit = gameState->curB->getStack(unitId);
+	CStack * unit = gameState->currentBattles.front()->getStack(unitId);
 
 
 	ASSERT_NE(unit, nullptr);
 	ASSERT_NE(unit, nullptr);
 
 
@@ -390,7 +390,7 @@ TEST_F(CGameStateTest, battleResurrection)
 		const CSpell * spell = SpellID(SpellID::RESURRECTION).toSpell();
 		const CSpell * spell = SpellID(SpellID::RESURRECTION).toSpell();
 		ASSERT_NE(spell, nullptr);
 		ASSERT_NE(spell, nullptr);
 
 
-			spells::BattleCast cast(gameState->curB, attacker, spells::Mode::HERO, spell);
+			spells::BattleCast cast(gameState->currentBattles.front().get(), attacker, spells::Mode::HERO, spell);
 
 
 		spells::Target target;
 		spells::Target target;
 		target.emplace_back(unit);
 		target.emplace_back(unit);