Răsfoiți Sursa

Intermediate commit

nordsoft 2 ani în urmă
părinte
comite
ce3028bd73

+ 1 - 1
AI/BattleAI/BattleAI.h

@@ -81,7 +81,7 @@ public:
 	//void actionStarted(const BattleAction &action) override;//occurs BEFORE every action taken by any stack or by the hero
 	//void battleAttack(const BattleAttack *ba) override; //called when stack is performing attack
 	//void battleStacksAttacked(const std::vector<BattleStackAttacked> & bsa, bool ranged) override; //called when stack receives damage (after battleAttack())
-	//void battleEnd(const BattleResult *br) override;
+	//void battleEnd(const BattleResult *br, QueryID queryID) override;
 	//void battleResultsApplied() override; //called when all effects of last battle are applied
 	//void battleNewRoundFirst(int round) override; //called at the beginning of each turn before changes are applied;
 	//void battleNewRound(int round) override; //called at the beginning of each turn, round=-1 is the tactic phase, round=0 is the first "normal" turn

+ 8 - 2
AI/Nullkiller/AIGateway.cpp

@@ -1090,7 +1090,7 @@ void AIGateway::battleStart(const CCreatureSet * army1, const CCreatureSet * arm
 	CAdventureAI::battleStart(army1, army2, tile, hero1, hero2, side);
 }
 
-void AIGateway::battleEnd(const BattleResult * br)
+void AIGateway::battleEnd(const BattleResult * br, QueryID queryID)
 {
 	NET_EVENT_HANDLER;
 	assert(status.getBattle() == ONGOING_BATTLE);
@@ -1098,7 +1098,13 @@ void AIGateway::battleEnd(const BattleResult * br)
 	bool won = br->winner == myCb->battleGetMySide();
 	logAi->debug("Player %d (%s): I %s the %s!", playerID, playerID.getStr(), (won ? "won" : "lost"), battlename);
 	battlename.clear();
-	CAdventureAI::battleEnd(br);
+	status.addQuery(queryID, "Combat result dialog");
+	const int confirmAction = 0;
+	requestActionASAP([=]()
+	{
+		answerQuery(queryID, confirmAction);
+	});
+	CAdventureAI::battleEnd(br, queryID);
 }
 
 void AIGateway::waitTillFree()

+ 1 - 1
AI/Nullkiller/AIGateway.h

@@ -169,7 +169,7 @@ public:
 	boost::optional<BattleAction> makeSurrenderRetreatDecision(const BattleStateInfoForRetreat & battleState) override;
 
 	void battleStart(const CCreatureSet * army1, const CCreatureSet * army2, int3 tile, const CGHeroInstance * hero1, const CGHeroInstance * hero2, bool side) override;
-	void battleEnd(const BattleResult * br) override;
+	void battleEnd(const BattleResult * br, QueryID queryID) override;
 
 	void makeTurn();
 

+ 1 - 1
AI/StupidAI/StupidAI.cpp

@@ -184,7 +184,7 @@ void CStupidAI::battleStacksAttacked(const std::vector<BattleStackAttacked> & bs
 	print("battleStacksAttacked called");
 }
 
-void CStupidAI::battleEnd(const BattleResult *br)
+void CStupidAI::battleEnd(const BattleResult *br, QueryID queryID)
 {
 	print("battleEnd called");
 }

+ 1 - 1
AI/StupidAI/StupidAI.h

@@ -32,7 +32,7 @@ public:
 
 	void battleAttack(const BattleAttack *ba) override; //called when stack is performing attack
 	void battleStacksAttacked(const std::vector<BattleStackAttacked> & bsa, bool ranged) override; //called when stack receives damage (after battleAttack())
-	void battleEnd(const BattleResult *br) override;
+	void battleEnd(const BattleResult *br, QueryID queryID) override;
 	//void battleResultsApplied() override; //called when all effects of last battle are applied
 	void battleNewRoundFirst(int round) override; //called at the beginning of each turn before changes are applied;
 	void battleNewRound(int round) override; //called at the beginning of each turn, round=-1 is the tactic phase, round=0 is the first "normal" turn

+ 8 - 2
AI/VCAI/VCAI.cpp

@@ -1580,7 +1580,7 @@ void VCAI::battleStart(const CCreatureSet * army1, const CCreatureSet * army2, i
 	CAdventureAI::battleStart(army1, army2, tile, hero1, hero2, side);
 }
 
-void VCAI::battleEnd(const BattleResult * br)
+void VCAI::battleEnd(const BattleResult * br, QueryID queryID)
 {
 	NET_EVENT_HANDLER;
 	assert(status.getBattle() == ONGOING_BATTLE);
@@ -1588,7 +1588,13 @@ void VCAI::battleEnd(const BattleResult * br)
 	bool won = br->winner == myCb->battleGetMySide();
 	logAi->debug("Player %d (%s): I %s the %s!", playerID, playerID.getStr(), (won ? "won" : "lost"), battlename);
 	battlename.clear();
-	CAdventureAI::battleEnd(br);
+	status.addQuery(queryID, "Combat result dialog");
+	const int confirmAction = 0;
+	requestActionASAP([=]()
+	{
+		answerQuery(queryID, confirmAction);
+	});
+	CAdventureAI::battleEnd(br, queryID);
 }
 
 void VCAI::waitTillFree()

+ 1 - 1
AI/VCAI/VCAI.h

@@ -201,7 +201,7 @@ public:
 	void showWorldViewEx(const std::vector<ObjectPosInfo> & objectPositions, bool showTerrain) override;
 
 	void battleStart(const CCreatureSet * army1, const CCreatureSet * army2, int3 tile, const CGHeroInstance * hero1, const CGHeroInstance * hero2, bool side) override;
-	void battleEnd(const BattleResult * br) override;
+	void battleEnd(const BattleResult * br, QueryID queryID) override;
 
 	void makeTurn();
 	void mainLoop();

+ 20 - 4
client/CPlayerInterface.cpp

@@ -693,7 +693,15 @@ void CPlayerInterface::battleStartBefore(const CCreatureSet *army1, const CCreat
 void CPlayerInterface::battleStart(const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool side)
 {
 	EVENT_HANDLER_CALLED_BY_CLIENT;
-	if (settings["adventure"]["quickCombat"].Bool())
+	bool replay = (lastBattleArmies.first == army1 && lastBattleArmies.second == army2); //will be true if player already refused auto-battle result
+	lastBattleArmies.first = army1;
+	lastBattleArmies.second = army2;
+	//quick combat with neutral creatures only
+	auto * army2_object = dynamic_cast<const CGObjectInstance *>(army2);
+	if((!replay && !allowBattleReplay && army2_object
+		&& army2_object->getOwner() == PlayerColor::UNFLAGGABLE
+		&& settings["adventure"]["quickCombat"].Bool())
+		|| settings["adventure"]["alwaysSkipCombat"].Bool())
 	{
 		autofightingAI = CDynLibHandler::getNewBattleAI(settings["server"]["friendlyAI"].String());
 		autofightingAI->initBattleInterface(env, cb);
@@ -702,6 +710,7 @@ void CPlayerInterface::battleStart(const CCreatureSet *army1, const CCreatureSet
 		cb->registerBattleInterface(autofightingAI);
 		// Player shouldn't be able to move on adventure map if quick combat is going
 		adventureInt->quickCombatLock();
+		allowBattleReplay = true;
 	}
 
 	//Don't wait for dialogs when we are non-active hot-seat player
@@ -889,7 +898,7 @@ BattleAction CPlayerInterface::activeStack(const CStack * stack) //called when i
 	return ret;
 }
 
-void CPlayerInterface::battleEnd(const BattleResult *br)
+void CPlayerInterface::battleEnd(const BattleResult *br, QueryID queryID)
 {
 	EVENT_HANDLER_CALLED_BY_CLIENT;
 	if(isAutoFightOn || autofightingAI)
@@ -900,7 +909,14 @@ void CPlayerInterface::battleEnd(const BattleResult *br)
 
 		if(!battleInt)
 		{
-			GH.pushIntT<BattleResultWindow>(*br, *this);
+			bool replay = allowBattleReplay && !settings["adventure"]["alwaysSkipCombat"].Bool(); //do not allow manual replay
+			allowBattleReplay = false;
+			auto wnd = std::make_shared<CBattleResultWindow>(*br, *this, replay);
+			wnd->resultCallback = [=](ui32 selection)
+			{
+				cb->selectionMade(selection, queryID);
+			};
+			GH.pushInt(wnd);
 			// #1490 - during AI turn when quick combat is on, we need to display the message and wait for user to close it.
 			// Otherwise NewTurn causes freeze.
 			waitWhileDialog();
@@ -911,7 +927,7 @@ void CPlayerInterface::battleEnd(const BattleResult *br)
 
 	BATTLE_EVENT_POSSIBLE_RETURN;
 
-	battleInt->battleFinished(*br);
+	battleInt->battleFinished(*br, queryID);
 	adventureInt->quickCombatUnlock();
 }
 

+ 3 - 1
client/CPlayerInterface.h

@@ -123,6 +123,8 @@ public:
 	//During battle is quick combat mode is used
 	std::shared_ptr<CBattleGameInterface> autofightingAI; //AI that makes decisions
 	bool isAutoFightOn; //Flag, switch it to stop quick combat. Don't touch if there is no battle interface.
+	bool allowBattleReplay = false;
+	std::pair<const CCreatureSet *, const CCreatureSet *> lastBattleArmies;
 
 	struct SpellbookLastSetting
 	{
@@ -210,7 +212,7 @@ public:
 	void actionStarted(const BattleAction& action) override;//occurs BEFORE action taken by active stack or by the hero
 	BattleAction activeStack(const CStack * stack) override; //called when it's turn of that stack
 	void battleAttack(const BattleAttack *ba) override; //stack performs attack
-	void battleEnd(const BattleResult *br) override; //end of battle
+	void battleEnd(const BattleResult *br, QueryID queryID) override; //end of battle
 	void battleNewRoundFirst(int round) override; //called at the beginning of each turn before changes are applied; used for HP regen handling
 	void battleNewRound(int round) override; //called at the beginning of each turn, round=-1 is the tactic phase, round=0 is the first "normal" turn
 	void battleLogMessage(const std::vector<MetaString> & lines) override;

+ 21 - 17
client/Client.cpp

@@ -553,13 +553,29 @@ void CClient::battleStarted(const BattleInfo * info)
 	auto & leftSide = info->sides[0], & rightSide = info->sides[1];
 
 	//If quick combat is not, do not prepare interfaces for battleint
-	if(!settings["adventure"]["quickCombat"].Bool())
+	auto callBattleStart = [&](PlayerColor color, ui8 side)
 	{
-		if(vstd::contains(playerint, leftSide.color) && playerint[leftSide.color]->human)
-			att = std::dynamic_pointer_cast<CPlayerInterface>(playerint[leftSide.color]);
+		if(vstd::contains(battleints, color))
+			battleints[color]->battleStart(leftSide.armyObject, rightSide.armyObject, info->tile, leftSide.hero, rightSide.hero, side);
+	};
+	
+	callBattleStart(leftSide.color, 0);
+	callBattleStart(rightSide.color, 1);
+	callBattleStart(PlayerColor::UNFLAGGABLE, 1);
+	if(settings["session"]["spectate"].Bool() && !settings["session"]["spectate-skip-battle"].Bool())
+		callBattleStart(PlayerColor::SPECTATOR, 1);
+	
+	if(vstd::contains(playerint, leftSide.color) && playerint[leftSide.color]->human)
+		att = std::dynamic_pointer_cast<CPlayerInterface>(playerint[leftSide.color]);
 
-		if(vstd::contains(playerint, rightSide.color) && playerint[rightSide.color]->human)
-			def = std::dynamic_pointer_cast<CPlayerInterface>(playerint[rightSide.color]);
+	if(vstd::contains(playerint, rightSide.color) && playerint[rightSide.color]->human)
+		def = std::dynamic_pointer_cast<CPlayerInterface>(playerint[rightSide.color]);
+	
+	//Remove player interfaces for auto battle (quickCombat option)
+	if(att && att->isAutoFightOn)
+	{
+		att.reset();
+		def.reset();
 	}
 
 	if(!settings["session"]["headless"].Bool())
@@ -579,18 +595,6 @@ void CClient::battleStarted(const BattleInfo * info)
 		}
 	}
 
-	auto callBattleStart = [&](PlayerColor color, ui8 side)
-	{
-		if(vstd::contains(battleints, color))
-			battleints[color]->battleStart(leftSide.armyObject, rightSide.armyObject, info->tile, leftSide.hero, rightSide.hero, side);
-	};
-
-	callBattleStart(leftSide.color, 0);
-	callBattleStart(rightSide.color, 1);
-	callBattleStart(PlayerColor::UNFLAGGABLE, 1);
-	if(settings["session"]["spectate"].Bool() && !settings["session"]["spectate-skip-battle"].Bool())
-		callBattleStart(PlayerColor::SPECTATOR, 1);
-
 	if(info->tacticDistance && vstd::contains(battleints, info->sides[info->tacticsSide].color))
 	{
 		boost::thread(&CClient::commenceTacticPhaseForInt, this, battleints[info->sides[info->tacticsSide].color]);

+ 1 - 1
client/NetPacksClient.cpp

@@ -735,7 +735,7 @@ void ApplyFirstClientNetPackVisitor::visitBattleUpdateGateState(BattleUpdateGate
 
 void ApplyFirstClientNetPackVisitor::visitBattleResult(BattleResult & pack)
 {
-	callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::battleEnd, &pack);
+	callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::battleEnd, &pack, queryID);
 	cl.battleFinished();
 }
 

+ 9 - 2
client/battle/BattleInterface.cpp

@@ -308,7 +308,7 @@ void BattleInterface::gateStateChanged(const EGateState state)
 		siegeController->gateStateChanged(state);
 }
 
-void BattleInterface::battleFinished(const BattleResult& br)
+void BattleInterface::battleFinished(const BattleResult& br, QueryID queryID)
 {
 	checkForAnimations();
 	stacksController->setActiveStack(nullptr);
@@ -318,11 +318,18 @@ void BattleInterface::battleFinished(const BattleResult& br)
 
 	if(settings["session"]["spectate"].Bool() && settings["session"]["spectate-skip-battle-result"].Bool())
 	{
+		curInt->cb->selectionMade(0, queryID);
 		windowObject->close();
 		return;
 	}
 
-	GH.pushInt(std::make_shared<BattleResultWindow>(br, *(this->curInt)));
+	auto wnd = std::make_shared<BattleResultWindow>(br, *(this->curInt));
+	wnd->resultCallback = [=](ui32 selection)
+	{
+		curInt->cb->selectionMade(selection, queryID);
+	};
+	GH.pushInt(wnd);
+	
 	curInt->waitWhileDialog(); // Avoid freeze when AI end turn after battle. Check bug #1897
 	CPlayerInterface::battleInt = nullptr;
 }

+ 1 - 1
client/battle/BattleInterface.h

@@ -200,7 +200,7 @@ public:
 	void newRoundFirst( int round );
 	void newRound(int number); //caled when round is ended; number is the number of round
 	void stackIsCatapulting(const CatapultAttack & ca); //called when a stack is attacking walls
-	void battleFinished(const BattleResult& br); //called when battle is finished - battleresult window should be printed
+	void battleFinished(const BattleResult& br, QueryID queryID); //called when battle is finished - battleresult window should be printed
 	void spellCast(const BattleSpellCast *sc); //called when a hero casts a spell
 	void battleStacksEffectsSet(const SetStackEffect & sse); //called when a specific effect is set to stacks
 	void castThisSpell(SpellID spellID); //called when player has chosen a spell from spellbook

+ 19 - 2
client/battle/BattleInterfaceClasses.cpp

@@ -397,7 +397,7 @@ HeroInfoWindow::HeroInfoWindow(const InfoAboutHero & hero, Point * position)
 	labels.push_back(std::make_shared<CLabel>(39, 186, EFonts::FONT_TINY, ETextAlignment::CENTER, Colors::WHITE, std::to_string(currentSpellPoints) + "/" + std::to_string(maxSpellPoints)));
 }
 
-BattleResultWindow::BattleResultWindow(const BattleResult & br, CPlayerInterface & _owner)
+BattleResultWindow::BattleResultWindow(const BattleResult & br, CPlayerInterface & _owner, bool allowReplay)
 	: owner(_owner)
 {
 	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
@@ -408,6 +408,12 @@ BattleResultWindow::BattleResultWindow(const BattleResult & br, CPlayerInterface
 
 	exit = std::make_shared<CButton>(Point(384, 505), "iok6432.def", std::make_pair("", ""), [&](){ bExitf();}, SDLK_RETURN);
 	exit->setBorderColor(Colors::METALLIC_GOLD);
+	
+	if(allowReplay)
+	{
+		repeat = std::make_shared<CButton>(Point(24, 505), "icn6432.def", std::make_pair("", ""), [&](){ bRepeatf();}, SDLK_ESCAPE);
+		repeat->setBorderColor(Colors::METALLIC_GOLD);
+	}
 
 	if(br.winner == 0) //attacker won
 	{
@@ -569,8 +575,9 @@ void BattleResultWindow::show(SDL_Surface * to)
 	CCS->videoh->update(pos.x + 107, pos.y + 70, to, true, false);
 }
 
-void BattleResultWindow::bExitf()
+void BattleResultWindow::buttonPressed(int button)
 {
+	resultCallback(button);
 	CPlayerInterface &intTmp = owner; //copy reference because "this" will be destructed soon
 
 	close();
@@ -584,6 +591,16 @@ void BattleResultWindow::bExitf()
 	CCS->videoh->close();
 }
 
+void BattleResultWindow::bExitf()
+{
+	buttonPressed(0);
+}
+
+void BattleResultWindow::bRepeatf()
+{
+	buttonPressed(1);
+}
+
 StackQueue::StackQueue(bool Embedded, BattleInterface & owner)
 	: embedded(Embedded),
 	owner(owner)

+ 6 - 1
client/battle/BattleInterfaceClasses.h

@@ -143,13 +143,18 @@ private:
 	std::shared_ptr<CPicture> background;
 	std::vector<std::shared_ptr<CLabel>> labels;
 	std::shared_ptr<CButton> exit;
+	std::shared_ptr<CButton> repeat;
 	std::vector<std::shared_ptr<CAnimImage>> icons;
 	std::shared_ptr<CTextBox> description;
 	CPlayerInterface & owner;
+	
+	void buttonPressed(int button); //internal function for button callbacks
 public:
-	BattleResultWindow(const BattleResult & br, CPlayerInterface & _owner);
+	BattleResultWindow(const BattleResult & br, CPlayerInterface & _owner, bool allowReplay = false);
 
 	void bExitf(); //exit button callback
+	void bRepeatf(); //repeat button callback
+	std::function<void(int result)> resultCallback; //callback receiving which button was pressed
 
 	void activate() override;
 	void show(SDL_Surface * to = 0) override;

+ 2 - 2
lib/CGameInterface.cpp

@@ -230,9 +230,9 @@ void CAdventureAI::battleSpellCast(const BattleSpellCast * sc)
 	battleAI->battleSpellCast(sc);
 }
 
-void CAdventureAI::battleEnd(const BattleResult * br)
+void CAdventureAI::battleEnd(const BattleResult * br, QueryID queryID)
 {
-	battleAI->battleEnd(br);
+	battleAI->battleEnd(br, queryID);
 	battleAI.reset();
 }
 

+ 1 - 1
lib/CGameInterface.h

@@ -162,7 +162,7 @@ public:
 	virtual void battleStackMoved(const CStack * stack, std::vector<BattleHex> dest, int distance, bool teleport) override;
 	virtual void battleAttack(const BattleAttack *ba) override;
 	virtual void battleSpellCast(const BattleSpellCast *sc) override;
-	virtual void battleEnd(const BattleResult *br) override;
+	virtual void battleEnd(const BattleResult *br, QueryID queryID) override;
 	virtual void battleUnitsChanged(const std::vector<UnitChanges> & units) override;
 
 	virtual void saveGame(BinarySerializer & h, const int version) override;

+ 1 - 1
lib/IGameEventsReceiver.h

@@ -60,7 +60,7 @@ public:
 	virtual void actionStarted(const BattleAction &action){};//occurs BEFORE every action taken by any stack or by the hero
 	virtual void battleAttack(const BattleAttack *ba){}; //called when stack is performing attack
 	virtual void battleStacksAttacked(const std::vector<BattleStackAttacked> & bsa, bool ranged){}; //called when stack receives damage (after battleAttack())
-	virtual void battleEnd(const BattleResult *br){};
+	virtual void battleEnd(const BattleResult *br, QueryID queryID){};
 	virtual void battleNewRoundFirst(int round){}; //called at the beginning of each turn before changes are applied;
 	virtual void battleNewRound(int round){}; //called at the beginning of each turn, round=-1 is the tactic phase, round=0 is the first "normal" turn
 	virtual void battleLogMessage(const std::vector<MetaString> & lines){};

+ 22 - 2
lib/NetPacks.h

@@ -1466,12 +1466,31 @@ struct DLL_LINKAGE BattleSetActiveStack : public CPackForClient
 	}
 };
 
-struct DLL_LINKAGE BattleResult : public CPackForClient
+struct DLL_LINKAGE BattleResultAccepted : public CPackForClient
+{
+	void applyGs(CGameState * gs) const;
+	
+	CGHeroInstance * hero1 = nullptr;
+	CGHeroInstance * hero2 = nullptr;
+	CArmedInstance * army1 = nullptr;
+	CArmedInstance * army2 = nullptr;
+	TExpType exp[2];
+
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & hero1;
+		h & hero2;
+		h & army1;
+		h & army2;
+		h & exp;
+	}
+};
+
+struct DLL_LINKAGE BattleResult : public Query
 {
 	enum EResult { NORMAL = 0, ESCAPE = 1, SURRENDER = 2 };
 
 	void applyFirstCl(CClient * cl);
-	void applyGs(CGameState * gs);
 
 	EResult result = NORMAL;
 	ui8 winner = 2; //0 - attacker, 1 - defender, [2 - draw (should be possible?)]
@@ -1483,6 +1502,7 @@ struct DLL_LINKAGE BattleResult : public CPackForClient
 
 	template <typename Handler> void serialize(Handler & h, const int version)
 	{
+		h & queryID;
 		h & result;
 		h & winner;
 		h & casualties[0];

+ 12 - 11
lib/NetPacksLib.cpp

@@ -2190,15 +2190,11 @@ void BattleUpdateGateState::applyGs(CGameState * gs) const
 		gs->curB->si.gateState = state;
 }
 
-void BattleResult::applyGs(CGameState *gs)
+void BattleResultAccepted::applyGs(CGameState * gs) const
 {
-	for (auto & elem : gs->curB->stacks)
-		delete elem;
-
-
-	for(int i = 0; i < 2; ++i)
+	for(auto * h : {hero1, hero2})
 	{
-		if(auto * h = gs->curB->battleGetFightingHero(i))
+		if(h)
 		{
 			h->removeBonusesRecursive(Bonus::OneBattle); 	//remove any "until next battle" bonuses
 			if (h->commander && h->commander->alive)
@@ -2214,15 +2210,20 @@ void BattleResult::applyGs(CGameState *gs)
 	if(VLC->settings()->getBoolean(EGameSettings::MODULE_STACK_EXPERIENCE))
 	{
 		for(int i = 0; i < 2; i++)
+		{
 			if(exp[i])
-				gs->curB->battleGetArmyObject(i)->giveStackExp(exp[i]);
+			{
+				if(auto * army = (i == 0 ? army1 : army2))
+					army->giveStackExp(exp[i]);
+			}
+		}
 
 		CBonusSystemNode::treeHasChanged();
 	}
 
-	for(int i = 0; i < 2; i++)
-		gs->curB->battleGetArmyObject(i)->battle = nullptr;
-
+	army1->battle = nullptr;
+	army2->battle = nullptr;
+	
 	gs->curB.dellNull();
 }
 

+ 9 - 1
lib/battle/BattleInfo.cpp

@@ -561,7 +561,15 @@ BattleInfo::BattleInfo():
 	setNodeType(BATTLE);
 }
 
-BattleInfo::~BattleInfo() = default;
+BattleInfo::~BattleInfo()
+{
+	for (auto & elem : stacks)
+		delete elem;
+
+	for(int i = 0; i < 2; i++)
+		if(auto * _armyObj = battleGetArmyObject(i))
+			_armyObj->battle = nullptr;
+}
 
 int32_t BattleInfo::getActiveStackID() const
 {

+ 1 - 0
lib/battle/BattleInfo.h

@@ -34,6 +34,7 @@ public:
 	si32 round, activeStack;
 	const CGTownInstance * town; //used during town siege, nullptr if this is not a siege (note that fortless town IS also a siege)
 	int3 tile; //for background and bonuses
+	bool creatureBank; //auxilary field, do not serialize
 	std::vector<CStack*> stacks;
 	std::vector<std::shared_ptr<CObstacleInstance> > obstacles;
 	SiegeInfo si;

+ 1 - 0
lib/registerTypes/RegisterTypes.h

@@ -266,6 +266,7 @@ void registerTypesClientPacks2(Serializer &s)
 	s.template registerType<CPackForClient, BattleNextRound>();
 	s.template registerType<CPackForClient, BattleSetActiveStack>();
 	s.template registerType<CPackForClient, BattleResult>();
+	s.template registerType<CPackForClient, BattleResultAccepted>();
 	s.template registerType<CPackForClient, BattleLogMessage>();
 	s.template registerType<CPackForClient, BattleStackMoved>();
 	s.template registerType<CPackForClient, BattleAttack>();

+ 21 - 3
server/CGameHandler.cpp

@@ -832,6 +832,8 @@ void CGameHandler::battleAfterLevelUp(const BattleResult &result)
 {
 	LOG_TRACE(logGlobal);
 
+	if(!finishingBattle)
+		return;
 
 	finishingBattle->remainingBattleQueriesCount--;
 	logGlobal->trace("Decremented queries count to %d", finishingBattle->remainingBattleQueriesCount);
@@ -914,6 +916,8 @@ void CGameHandler::battleAfterLevelUp(const BattleResult &result)
 			sendAndApply(&sah);
 		}
 	}
+	
+	finishingBattle.reset();
 }
 
 void CGameHandler::makeAttack(const CStack * attacker, const CStack * defender, int distance, BattleHex targetHex, bool first, bool ranged, bool counter)
@@ -2627,6 +2631,9 @@ void CGameHandler::startBattlePrimary(const CArmedInstance *army1, const CArmedI
 								const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool creatureBank,
 								const CGTownInstance *town) //use hero=nullptr for no hero
 {
+	if(gs->curB)
+		gs->curB.dellNull();
+	
 	engageIntoBattle(army1->tempOwner);
 	engageIntoBattle(army2->tempOwner);
 
@@ -2640,8 +2647,17 @@ void CGameHandler::startBattlePrimary(const CArmedInstance *army1, const CArmedI
 
 	setupBattle(tile, armies, heroes, creatureBank, town); //initializes stacks, places creatures on battlefield, blocks and informs player interfaces
 
-	auto battleQuery = std::make_shared<CBattleQuery>(this, gs->curB);
-	queries.addQuery(battleQuery);
+	//existing battle query for retying auto-combat
+	auto battleQuery = std::dynamic_pointer_cast<CBattleQuery>(queries.topQuery(gs->curB->sides[0].color));
+	if(battleQuery)
+	{
+		battleQuery->bi = gs->curB;
+		battleQuery->result = boost::none;
+		battleQuery->belligerents[0] = gs->curB->sides[0].armyObject;
+		battleQuery->belligerents[1] = gs->curB->sides[1].armyObject;
+	}
+
+	queries.addQuery(std::make_shared<CBattleQuery>(this, gs->curB));
 
 	this->battleThread = std::make_unique<boost::thread>(boost::thread(&CGameHandler::runBattle, this));
 }
@@ -7189,7 +7205,7 @@ void CGameHandler::showInfoDialog(const std::string & msg, PlayerColor player)
 	showInfoDialog(&iw);
 }
 
-CasualtiesAfterBattle::CasualtiesAfterBattle(const CArmedInstance * _army, BattleInfo *bat):
+CasualtiesAfterBattle::CasualtiesAfterBattle(const CArmedInstance * _army, const BattleInfo * bat):
 	army(_army)
 {
 	heroWithDeadCommander = ObjectInstanceID();
@@ -7341,12 +7357,14 @@ CGameHandler::FinishingBattleHelper::FinishingBattleHelper(std::shared_ptr<const
 	loserHero = result.winner != 0 ? info.sides[0].hero : info.sides[1].hero;
 	victor = info.sides[result.winner].color;
 	loser = info.sides[!result.winner].color;
+	winnerSide = result.winner;
 	remainingBattleQueriesCount = RemainingBattleQueriesCount;
 }
 
 CGameHandler::FinishingBattleHelper::FinishingBattleHelper()
 {
 	winnerHero = loserHero = nullptr;
+	winnerSide = 0;
 	remainingBattleQueriesCount = 0;
 }
 

+ 7 - 2
server/CGameHandler.h

@@ -88,7 +88,7 @@ struct CasualtiesAfterBattle
 	TSummoned summoned;
 	ObjectInstanceID heroWithDeadCommander; //TODO: unify stack locations
 
-	CasualtiesAfterBattle(const CArmedInstance * _army, BattleInfo *bat);
+	CasualtiesAfterBattle(const CArmedInstance * _army, const BattleInfo * bat);
 	void updateArmy(CGameHandler *gh);
 };
 
@@ -130,7 +130,8 @@ public:
 	////used only in endBattle - don't touch elsewhere
 	bool visitObjectAfterVictory;
 	//
-	void endBattle(int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2); //ends battle
+	void endBattle(int3 tile, const CGHeroInstance * hero1, const CGHeroInstance * hero2); //ends battle
+	void endBattleConfirm(const BattleInfo * battleInfo);
 
 	void makeAttack(const CStack * attacker, const CStack * defender, int distance, BattleHex targetHex, bool first, bool ranged, bool counter);
 
@@ -313,9 +314,12 @@ public:
 	{
 		FinishingBattleHelper();
 		FinishingBattleHelper(std::shared_ptr<const CBattleQuery> Query, int RemainingBattleQueriesCount);
+		
+		inline bool isDraw() const {return winnerSide == 2;}
 
 		const CGHeroInstance *winnerHero, *loserHero;
 		PlayerColor victor, loser;
+		ui8 winnerSide;
 
 		int remainingBattleQueriesCount;
 
@@ -325,6 +329,7 @@ public:
 			h & loserHero;
 			h & victor;
 			h & loser;
+			h & winnerSide;
 			h & remainingBattleQueriesCount;
 		}
 	};

+ 26 - 3
server/CQuery.cpp

@@ -288,8 +288,8 @@ QueryPtr Queries::getQuery(QueryID queryID)
 
 void CBattleQuery::notifyObjectAboutRemoval(const CObjectVisitQuery & objectVisit) const
 {
-	assert(result);
-	objectVisit.visitedObject->battleFinished(objectVisit.visitingHero, *result);
+	if(result)
+		objectVisit.visitedObject->battleFinished(objectVisit.visitingHero, *result);
 }
 
 CBattleQuery::CBattleQuery(CGameHandler * owner, const BattleInfo * Bi):
@@ -318,7 +318,8 @@ bool CBattleQuery::blocksPack(const CPack * pack) const
 
 void CBattleQuery::onRemoval(PlayerColor color)
 {
-	gh->battleAfterLevelUp(*result);
+	if(result)
+		gh->battleAfterLevelUp(*result);
 }
 
 void CGarrisonDialogQuery::notifyObjectAboutRemoval(const CObjectVisitQuery & objectVisit) const
@@ -386,6 +387,28 @@ bool CGarrisonDialogQuery::blocksPack(const CPack * pack) const
 	return CDialogQuery::blocksPack(pack);
 }
 
+CBattleDialogQuery::CBattleDialogQuery(CGameHandler * owner, const BattleInfo * Bi):
+	CDialogQuery(owner)
+{
+	bi = Bi;
+
+	for(auto & side : bi->sides)
+		addPlayer(side.color);
+}
+
+void CBattleDialogQuery::onRemoval(PlayerColor color)
+{
+	assert(answer);
+	if(*answer == 1)
+	{
+		gh->startBattlePrimary(bi->sides[0].armyObject, bi->sides[1].armyObject, bi->tile, bi->sides[0].hero, bi->sides[1].hero, bi->creatureBank, bi->town);
+	}
+	else
+	{
+		gh->endBattleConfirm(bi);
+	}
+}
+
 void CBlockingDialogQuery::notifyObjectAboutRemoval(const CObjectVisitQuery & objectVisit) const
 {
 	assert(answer);

+ 10 - 0
server/CQuery.h

@@ -145,6 +145,16 @@ public:
 	virtual bool blocksPack(const CPack *pack) const override;
 };
 
+class CBattleDialogQuery : public CDialogQuery
+{
+public:
+	CBattleDialogQuery(CGameHandler * owner, const BattleInfo * Bi);
+
+	const BattleInfo * bi;
+
+	virtual void onRemoval(PlayerColor color) override;
+};
+
 //yes/no and component selection dialogs
 class CBlockingDialogQuery : public CDialogQuery
 {