1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-05-23 22:40:07 +02:00

Fix inability of unit to cast spell after receiving morale

This commit is contained in:
Ivan Savenko 2025-04-28 19:34:36 +03:00
parent 5433b07e5f
commit 5550edeb9a
16 changed files with 62 additions and 26 deletions

View File

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

View File

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

View File

@ -137,7 +137,7 @@ public:
battle::Units getUnitsIf(const battle::UnitFilter & predicate) const override; battle::Units getUnitsIf(const battle::UnitFilter & predicate) const override;
void nextRound() 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 addUnit(uint32_t id, const JsonNode & data) override;
void setUnitState(uint32_t id, const JsonNode & data, int64_t healthDelta) override; void setUnitState(uint32_t id, const JsonNode & data, int64_t healthDelta) override;

View File

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

View File

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

View File

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

View File

@ -128,7 +128,7 @@ public:
// IBattleState // IBattleState
void nextRound() 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 addUnit(uint32_t id, const JsonNode & data) override;
void moveUnit(uint32_t id, const BattleHex & destination) override; void moveUnit(uint32_t id, const BattleHex & destination) override;

View File

@ -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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -334,7 +334,7 @@ void BattleFlowProcessor::activateNextStack(const CBattleInfoCallback & battle)
if (!tryMakeAutomaticAction(battle, next)) if (!tryMakeAutomaticAction(battle, next))
{ {
if(next->alive()) { if(next->alive()) {
setActiveStack(battle, next); setActiveStack(battle, next, BattleUnitTurnReason::TURN_QUEUE);
break; 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 // 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()) if(actedStack->castSpellThisTurn && ba.spell.hasValue() && ba.spell.toSpell()->canCastWithoutSkip())
{ {
setActiveStack(battle, actedStack); setActiveStack(battle, actedStack, BattleUnitTurnReason::UNIT_SPELLCAST);
return; return;
} }
} }
@ -589,7 +589,7 @@ void BattleFlowProcessor::onActionMade(const CBattleInfoCallback & battle, const
if (rollGoodMorale(battle, actedStack)) if (rollGoodMorale(battle, actedStack))
{ {
// Good morale - same stack makes 2nd turn // Good morale - same stack makes 2nd turn
setActiveStack(battle, actedStack); setActiveStack(battle, actedStack, BattleUnitTurnReason::MORALE);
return; 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) // 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(battle, activeStack); setActiveStack(battle, activeStack, BattleUnitTurnReason::HERO_SPELLCAST);
return; return;
} }
} }
@ -622,7 +622,7 @@ bool BattleFlowProcessor::makeAutomaticAction(const CBattleInfoCallback & battle
BattleSetActiveStack bsa; BattleSetActiveStack bsa;
bsa.battleID = battle.getBattle()->getBattleID(); bsa.battleID = battle.getBattle()->getBattleID();
bsa.stack = stack->unitId(); bsa.stack = stack->unitId();
bsa.askPlayerInterface = false; bsa.reason = BattleUnitTurnReason::AUTOMATIC_ACTION;
gameHandler->sendAndApply(bsa); gameHandler->sendAndApply(bsa);
bool ret = owner->makeAutomaticBattleAction(battle, ba); 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); assert(stack);
BattleSetActiveStack sas; BattleSetActiveStack sas;
sas.battleID = battle.getBattle()->getBattleID(); sas.battleID = battle.getBattle()->getBattleID();
sas.stack = stack->unitId(); sas.stack = stack->unitId();
sas.reason = reason;
gameHandler->sendAndApply(sas); gameHandler->sendAndApply(sas);
} }

View File

@ -10,6 +10,7 @@
#pragma once #pragma once
#include "../lib/battle/BattleSide.h" #include "../lib/battle/BattleSide.h"
#include "../lib/battle/BattleUnitTurnReason.h"
VCMI_LIB_NAMESPACE_BEGIN VCMI_LIB_NAMESPACE_BEGIN
class CStack; class CStack;
@ -48,7 +49,7 @@ class BattleFlowProcessor : boost::noncopyable
void stackEnchantedTrigger(const CBattleInfoCallback & battle, const CStack * stack); void stackEnchantedTrigger(const CBattleInfoCallback & battle, const CStack * stack);
void removeObstacle(const CBattleInfoCallback & battle, const CObstacleInstance & obstacle); void removeObstacle(const CBattleInfoCallback & battle, const CObstacleInstance & obstacle);
void stackTurnTrigger(const CBattleInfoCallback & battle, const CStack * stack); 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); 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) 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)

View File

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