浏览代码

Fix inability of unit to cast spell after receiving morale

Ivan Savenko 5 月之前
父节点
当前提交
5550edeb9a

+ 1 - 1
AI/BattleAI/BattleEvaluator.cpp

@@ -568,7 +568,7 @@ bool BattleEvaluator::attemptCastingSpell(const CStack * activeStack)
 					ourTurnSpan++;
 				}
 
-				state->nextTurn(unit->unitId());
+				state->nextTurn(unit->unitId(), BattleUnitTurnReason::TURN_QUEUE);
 
 				PotentialTargets potentialTargets(unit, damageCache, state);
 

+ 2 - 2
AI/BattleAI/StackWithBonuses.cpp

@@ -342,14 +342,14 @@ void HypotheticBattle::nextRound()
 	}
 }
 
-void HypotheticBattle::nextTurn(uint32_t unitId)
+void HypotheticBattle::nextTurn(uint32_t unitId, BattleUnitTurnReason reason)
 {
 	activeUnitId = unitId;
 	auto unit = getForUpdate(unitId);
 
 	unit->removeUnitBonus(Bonus::UntilGetsTurn);
 
-	unit->afterGetsTurn();
+	unit->afterGetsTurn(reason);
 }
 
 void HypotheticBattle::addUnit(uint32_t id, const JsonNode & data)

+ 1 - 1
AI/BattleAI/StackWithBonuses.h

@@ -137,7 +137,7 @@ public:
 	battle::Units getUnitsIf(const battle::UnitFilter & predicate) const override;
 
 	void nextRound() override;
-	void nextTurn(uint32_t unitId) override;
+	void nextTurn(uint32_t unitId, BattleUnitTurnReason reason) override;
 
 	void addUnit(uint32_t id, const JsonNode & data) override;
 	void setUnitState(uint32_t id, const JsonNode & data, int64_t healthDelta) override;

+ 1 - 1
client/NetPacksClient.cpp

@@ -769,7 +769,7 @@ void ApplyClientNetPackVisitor::visitBattleNextRound(BattleNextRound & pack)
 
 void ApplyClientNetPackVisitor::visitBattleSetActiveStack(BattleSetActiveStack & pack)
 {
-	if(!pack.askPlayerInterface)
+	if(pack.reason == BattleUnitTurnReason::AUTOMATIC_ACTION)
 		return;
 
 	const CStack *activated = gs.getBattle(pack.battleID)->battleGetStackByID(pack.stack);

+ 1 - 0
lib/CMakeLists.txt

@@ -418,6 +418,7 @@ set(lib_MAIN_HEADERS
 	battle/BattleSide.h
 	battle/BattleStateInfoForRetreat.h
 	battle/BattleProxy.h
+	battle/BattleUnitTurnReason.h
 	battle/CBattleInfoCallback.h
 	battle/CBattleInfoEssentials.h
 	battle/CObstacleInstance.h

+ 2 - 2
lib/battle/BattleInfo.cpp

@@ -666,7 +666,7 @@ void BattleInfo::nextRound()
 		obst->battleTurnPassed();
 }
 
-void BattleInfo::nextTurn(uint32_t unitId)
+void BattleInfo::nextTurn(uint32_t unitId, BattleUnitTurnReason reason)
 {
 	activeStack = unitId;
 
@@ -675,7 +675,7 @@ void BattleInfo::nextTurn(uint32_t unitId)
 	//remove bonuses that last until when stack gets new turn
 	st->removeBonusesRecursive(Bonus::UntilGetsTurn);
 
-	st->afterGetsTurn();
+	st->afterGetsTurn(reason);
 }
 
 void BattleInfo::addUnit(uint32_t id, const JsonNode & data)

+ 1 - 1
lib/battle/BattleInfo.h

@@ -128,7 +128,7 @@ public:
 	// IBattleState
 
 	void nextRound() override;
-	void nextTurn(uint32_t unitId) override;
+	void nextTurn(uint32_t unitId, BattleUnitTurnReason reason) override;
 
 	void addUnit(uint32_t id, const JsonNode & data) override;
 	void moveUnit(uint32_t id, const BattleHex & destination) override;

+ 28 - 0
lib/battle/BattleUnitTurnReason.h

@@ -0,0 +1,28 @@
+/*
+ * BattleUnitTurnReason.h, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+#pragma once
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+enum class BattleUnitTurnReason : int8_t
+{
+	/// Unit gained turn due to becoming first unit in turn queue
+	TURN_QUEUE,
+	/// Unit gained turn due to morale triggering
+	MORALE,
+	/// Unit (re)gained	turn due to hero casting a spell while this unit is active
+	HERO_SPELLCAST,
+	/// Unit gained turn due to casting a spell while having ability to cast spells without spending turn
+	UNIT_SPELLCAST,
+	/// Unit gained turn for automatic action, player can not select action for this unit
+	AUTOMATIC_ACTION
+};
+
+VCMI_LIB_NAMESPACE_END

+ 5 - 3
lib/battle/CUnitState.cpp

@@ -920,11 +920,13 @@ void CUnitState::afterNewRound()
 		makeGhost();
 }
 
-void CUnitState::afterGetsTurn()
+void CUnitState::afterGetsTurn(BattleUnitTurnReason reason)
 {
-	//if moving second time this round it must be high morale bonus
-	if(movedThisRound)
+	if(reason == BattleUnitTurnReason::MORALE)
+	{
 		hadMorale = true;
+		castSpellThisTurn = false;
+	}
 }
 
 void CUnitState::makeGhost()

+ 2 - 1
lib/battle/CUnitState.h

@@ -10,6 +10,7 @@
 
 #pragma once
 
+#include "BattleUnitTurnReason.h"
 #include "Unit.h"
 #include "../bonuses/BonusCache.h"
 
@@ -254,7 +255,7 @@ public:
 
 	void afterNewRound();
 
-	void afterGetsTurn();
+	void afterGetsTurn(BattleUnitTurnReason reason);
 
 	void makeGhost();
 

+ 2 - 1
lib/battle/IBattleState.h

@@ -10,6 +10,7 @@
 
 #pragma once
 #include "CBattleInfoEssentials.h"
+#include "BattleUnitTurnReason.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 
@@ -80,7 +81,7 @@ class DLL_LINKAGE IBattleState : public IBattleInfo
 {
 public:
 	virtual void nextRound() = 0;
-	virtual void nextTurn(uint32_t unitId) = 0;
+	virtual void nextTurn(uint32_t unitId, BattleUnitTurnReason reason) = 0;
 
 	virtual void addUnit(uint32_t id, const JsonNode & data) = 0;
 	virtual void setUnitState(uint32_t id, const JsonNode & data, int64_t healthDelta) = 0;

+ 1 - 1
lib/networkPacks/NetPacksLib.cpp

@@ -2007,7 +2007,7 @@ void BattleNextRound::applyGs(CGameState *gs)
 
 void BattleSetActiveStack::applyGs(CGameState *gs)
 {
-	gs->getBattle(battleID)->nextTurn(stack);
+	gs->getBattle(battleID)->nextTurn(stack, reason);
 }
 
 void BattleTriggerEffect::applyGs(CGameState *gs)

+ 5 - 4
lib/networkPacks/PacksForClientBattle.h

@@ -12,9 +12,10 @@
 #include "NetPacksBase.h"
 #include "BattleChanges.h"
 #include "PacksForClient.h"
-#include "../battle/BattleHexArray.h"
 #include "../battle/BattleAction.h"
 #include "../battle/BattleInfo.h"
+#include "../battle/BattleHexArray.h"
+#include "../battle/BattleUnitTurnReason.h"
 #include "../texts/MetaString.h"
 
 class CClient;
@@ -63,8 +64,8 @@ struct DLL_LINKAGE BattleSetActiveStack : public CPackForClient
 	void applyGs(CGameState * gs) override;
 
 	BattleID battleID = BattleID::NONE;
-	ui32 stack = 0;
-	ui8 askPlayerInterface = true;
+	uint32_t stack = 0;
+	BattleUnitTurnReason reason;
 
 	void visitTyped(ICPackVisitor & visitor) override;
 
@@ -72,7 +73,7 @@ struct DLL_LINKAGE BattleSetActiveStack : public CPackForClient
 	{
 		h & battleID;
 		h & stack;
-		h & askPlayerInterface;
+		h & reason;
 		assert(battleID != BattleID::NONE);
 	}
 };

+ 7 - 6
server/battles/BattleFlowProcessor.cpp

@@ -334,7 +334,7 @@ void BattleFlowProcessor::activateNextStack(const CBattleInfoCallback & battle)
 		if (!tryMakeAutomaticAction(battle, next))
 		{
 			if(next->alive()) {
-				setActiveStack(battle, next);
+				setActiveStack(battle, next, BattleUnitTurnReason::TURN_QUEUE);
 				break;
 			}
 		}
@@ -576,7 +576,7 @@ void BattleFlowProcessor::onActionMade(const CBattleInfoCallback & battle, const
 		// NOTE: in case of random spellcaster, (e.g. Master Genie) spell has been selected by server and was not present in action received from player
 		if(actedStack->castSpellThisTurn && ba.spell.hasValue() && ba.spell.toSpell()->canCastWithoutSkip())
 		{
-			setActiveStack(battle, actedStack);
+			setActiveStack(battle, actedStack, BattleUnitTurnReason::UNIT_SPELLCAST);
 			return;
 		}
 	}
@@ -589,7 +589,7 @@ void BattleFlowProcessor::onActionMade(const CBattleInfoCallback & battle, const
 		if (rollGoodMorale(battle, actedStack))
 		{
 			// Good morale - same stack makes 2nd turn
-			setActiveStack(battle, actedStack);
+			setActiveStack(battle, actedStack, BattleUnitTurnReason::MORALE);
 			return;
 		}
 	}
@@ -599,7 +599,7 @@ void BattleFlowProcessor::onActionMade(const CBattleInfoCallback & battle, const
 		{
 			// this is action made by hero AND unit is alive (e.g. not killed by casted spell)
 			// keep current active stack for next action
-			setActiveStack(battle, activeStack);
+			setActiveStack(battle, activeStack, BattleUnitTurnReason::HERO_SPELLCAST);
 			return;
 		}
 	}
@@ -622,7 +622,7 @@ bool BattleFlowProcessor::makeAutomaticAction(const CBattleInfoCallback & battle
 	BattleSetActiveStack bsa;
 	bsa.battleID = battle.getBattle()->getBattleID();
 	bsa.stack = stack->unitId();
-	bsa.askPlayerInterface = false;
+	bsa.reason = BattleUnitTurnReason::AUTOMATIC_ACTION;
 	gameHandler->sendAndApply(bsa);
 
 	bool ret = owner->makeAutomaticBattleAction(battle, ba);
@@ -809,12 +809,13 @@ void BattleFlowProcessor::stackTurnTrigger(const CBattleInfoCallback & battle, c
 	}
 }
 
-void BattleFlowProcessor::setActiveStack(const CBattleInfoCallback & battle, const battle::Unit * stack)
+void BattleFlowProcessor::setActiveStack(const CBattleInfoCallback & battle, const battle::Unit * stack, BattleUnitTurnReason reason)
 {
 	assert(stack);
 
 	BattleSetActiveStack sas;
 	sas.battleID = battle.getBattle()->getBattleID();
 	sas.stack = stack->unitId();
+	sas.reason = reason;
 	gameHandler->sendAndApply(sas);
 }

+ 2 - 1
server/battles/BattleFlowProcessor.h

@@ -10,6 +10,7 @@
 #pragma once
 
 #include "../lib/battle/BattleSide.h"
+#include "../lib/battle/BattleUnitTurnReason.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 class CStack;
@@ -48,7 +49,7 @@ class BattleFlowProcessor : boost::noncopyable
 	void stackEnchantedTrigger(const CBattleInfoCallback & battle, const CStack * stack);
 	void removeObstacle(const CBattleInfoCallback & battle, const CObstacleInstance & obstacle);
 	void stackTurnTrigger(const CBattleInfoCallback & battle, const CStack * stack);
-	void setActiveStack(const CBattleInfoCallback & battle, const battle::Unit * stack);
+	void setActiveStack(const CBattleInfoCallback & battle, const battle::Unit * stack, BattleUnitTurnReason reason);
 
 	void makeStackDoNothing(const CBattleInfoCallback & battle, const CStack * next);
 	bool makeAutomaticAction(const CBattleInfoCallback & battle, const CStack * stack, BattleAction & ba); //used when action is taken by stack without volition of player (eg. unguided catapult attack)

+ 1 - 1
test/mock/mock_battle_IBattleState.h

@@ -42,7 +42,7 @@ public:
 	MOCK_CONST_METHOD1(getUsedSpells, std::vector<SpellID>(BattleSide));
 
 	MOCK_METHOD0(nextRound, void());
-	MOCK_METHOD1(nextTurn, void(uint32_t));
+	MOCK_METHOD2(nextTurn, void(uint32_t, BattleUnitTurnReason));
 	MOCK_METHOD2(addUnit, void(uint32_t, const JsonNode &));
 	MOCK_METHOD3(setUnitState, void(uint32_t, const JsonNode &, int64_t));
 	MOCK_METHOD2(moveUnit, void(uint32_t, const BattleHex &));