1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-07-13 01:20:34 +02:00

Unified spellcasting handling with other actions

This commit is contained in:
Ivan Savenko
2023-08-17 14:56:24 +03:00
parent 6297140bf5
commit a1d3181a98
8 changed files with 143 additions and 106 deletions

View File

@ -283,7 +283,8 @@ void CBattleAI::activeStack( const CStack * stack )
return; return;
} }
attemptCastingSpell(); if (attemptCastingSpell())
return;
logAi->trace("Spellcast attempt completed in %lld", timeElapsed(start)); logAi->trace("Spellcast attempt completed in %lld", timeElapsed(start));
@ -476,14 +477,14 @@ BattleAction CBattleAI::useCatapult(const CStack * stack)
return attack; return attack;
} }
void CBattleAI::attemptCastingSpell() bool CBattleAI::attemptCastingSpell()
{ {
auto hero = cb->battleGetMyHero(); auto hero = cb->battleGetMyHero();
if(!hero) if(!hero)
return; return false;
if(cb->battleCanCastSpell(hero, spells::Mode::HERO) != ESpellCastProblem::OK) if(cb->battleCanCastSpell(hero, spells::Mode::HERO) != ESpellCastProblem::OK)
return; return false;
LOGL("Casting spells sounds like fun. Let's see..."); LOGL("Casting spells sounds like fun. Let's see...");
//Get all spells we can cast //Get all spells we can cast
@ -522,7 +523,7 @@ void CBattleAI::attemptCastingSpell()
} }
LOGFL("Found %d spell-target combinations.", possibleCasts.size()); LOGFL("Found %d spell-target combinations.", possibleCasts.size());
if(possibleCasts.empty()) if(possibleCasts.empty())
return; return false;
using ValueMap = PossibleSpellcast::ValueMap; using ValueMap = PossibleSpellcast::ValueMap;
@ -657,7 +658,7 @@ void CBattleAI::attemptCastingSpell()
if(battleIsFinishedOpt) if(battleIsFinishedOpt)
{ {
print("No need to cast a spell. Battle will finish soon."); print("No need to cast a spell. Battle will finish soon.");
return; return false;
} }
} }
} }
@ -786,10 +787,12 @@ void CBattleAI::attemptCastingSpell()
spellcast.stackNumber = (!side) ? -1 : -2; spellcast.stackNumber = (!side) ? -1 : -2;
cb->battleMakeSpellAction(spellcast); cb->battleMakeSpellAction(spellcast);
movesSkippedByDefense = 0; movesSkippedByDefense = 0;
return true;
} }
else else
{ {
LOGFL("Best spell is %s. But it is actually useless (value %d).", castToPerform.spell->getNameTranslated() % castToPerform.value); LOGFL("Best spell is %s. But it is actually useless (value %d).", castToPerform.spell->getNameTranslated() % castToPerform.value);
return false;
} }
} }

View File

@ -68,7 +68,7 @@ public:
~CBattleAI(); ~CBattleAI();
void initBattleInterface(std::shared_ptr<Environment> ENV, std::shared_ptr<CBattleCallback> CB) override; void initBattleInterface(std::shared_ptr<Environment> ENV, std::shared_ptr<CBattleCallback> CB) override;
void attemptCastingSpell(); bool attemptCastingSpell();
void evaluateCreatureSpellcast(const CStack * stack, PossibleSpellcast & ps); //for offensive damaging spells only void evaluateCreatureSpellcast(const CStack * stack, PossibleSpellcast & ps); //for offensive damaging spells only

View File

@ -136,13 +136,8 @@ bool BattleActionProcessor::doHeroSpellAction(const BattleAction & ba)
return false; return false;
} }
StartAction start_action(ba);
gameHandler->sendAndApply(&start_action); //start spell casting
parameters.cast(gameHandler->spellEnv, ba.getTarget(gameHandler->gameState()->curB)); parameters.cast(gameHandler->spellEnv, ba.getTarget(gameHandler->gameState()->curB));
EndAction end_action;
gameHandler->sendAndApply(&end_action);
return true; return true;
} }

View File

@ -18,7 +18,8 @@ struct BattleHex;
class CStack; class CStack;
enum class BonusType; enum class BonusType;
namespace battle { namespace battle
{
class Unit; class Unit;
class CUnitState; class CUnitState;
} }
@ -28,6 +29,7 @@ VCMI_LIB_NAMESPACE_END
class CGameHandler; class CGameHandler;
class BattleProcessor; class BattleProcessor;
/// Processes incoming battle action queries and applies requested action(s)
class BattleActionProcessor : boost::noncopyable class BattleActionProcessor : boost::noncopyable
{ {
using FireShieldInfo = std::vector<std::pair<const CStack *, int64_t>>; using FireShieldInfo = std::vector<std::pair<const CStack *, int64_t>>;
@ -71,7 +73,5 @@ public:
explicit BattleActionProcessor(BattleProcessor * owner); explicit BattleActionProcessor(BattleProcessor * owner);
void setGameHandler(CGameHandler * newGameHandler); void setGameHandler(CGameHandler * newGameHandler);
bool makeBattleAction(const BattleAction &ba); bool makeBattleAction(const BattleAction & ba);
}; };

View File

@ -147,15 +147,11 @@ void BattleFlowProcessor::onBattleStarted()
onTacticsEnded(); onTacticsEnded();
} }
void BattleFlowProcessor::onTacticsEnded() void BattleFlowProcessor::trySummonGuardians(const CStack * stack)
{ {
//initial stacks appearance triggers, e.g. built-in bonus spells if (!stack->hasBonusOfType(BonusType::SUMMON_GUARDIANS))
auto initialStacks = gameHandler->gameState()->curB->stacks; //use temporary variable to outclude summoned stacks added to gameHandler->gameState()->curB->stacks from processing return;
for (CStack * stack : initialStacks)
{
if (stack->hasBonusOfType(BonusType::SUMMON_GUARDIANS))
{
std::shared_ptr<const Bonus> summonInfo = stack->getBonus(Selector::type()(BonusType::SUMMON_GUARDIANS)); std::shared_ptr<const Bonus> summonInfo = stack->getBonus(Selector::type()(BonusType::SUMMON_GUARDIANS));
auto accessibility = gameHandler->getAccesibility(); auto accessibility = gameHandler->getAccesibility();
CreatureID creatureData = CreatureID(summonInfo->subtype); CreatureID creatureData = CreatureID(summonInfo->subtype);
@ -190,17 +186,16 @@ void BattleFlowProcessor::onTacticsEnded()
gameHandler->sendAndApply(&pack); gameHandler->sendAndApply(&pack);
} }
} }
} }
stackEnchantedTrigger(stack); void BattleFlowProcessor::castOpeningSpells()
} {
//spells opening battle
for (int i = 0; i < 2; ++i) for (int i = 0; i < 2; ++i)
{ {
auto h = gameHandler->gameState()->curB->battleGetFightingHero(i); auto h = gameHandler->gameState()->curB->battleGetFightingHero(i);
if (h) if (!h)
{ continue;
TConstBonusListPtr bl = h->getBonuses(Selector::type()(BonusType::OPENING_BATTLE_SPELL)); TConstBonusListPtr bl = h->getBonuses(Selector::type()(BonusType::OPENING_BATTLE_SPELL));
for (auto b : *bl) for (auto b : *bl)
@ -216,11 +211,26 @@ void BattleFlowProcessor::onTacticsEnded()
parameters.castIfPossible(gameHandler->spellEnv, spells::Target()); parameters.castIfPossible(gameHandler->spellEnv, spells::Target());
} }
} }
}
void BattleFlowProcessor::onTacticsEnded()
{
//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
for (CStack * stack : initialStacks)
{
trySummonGuardians(stack);
stackEnchantedTrigger(stack);
} }
castOpeningSpells();
// 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
owner->checkBattleStateChanges(); owner->checkBattleStateChanges();
startNextRound(true); startNextRound(true);
activateNextStack();
} }
void BattleFlowProcessor::startNextRound(bool isFirstRound) void BattleFlowProcessor::startNextRound(bool isFirstRound)
@ -245,8 +255,6 @@ void BattleFlowProcessor::startNextRound(bool isFirstRound)
if(stack->alive() && !isFirstRound) if(stack->alive() && !isFirstRound)
stackEnchantedTrigger(stack); stackEnchantedTrigger(stack);
} }
activateNextStack();
} }
const CStack * BattleFlowProcessor::getNextStack() const CStack * BattleFlowProcessor::getNextStack()
@ -296,7 +304,13 @@ void BattleFlowProcessor::activateNextStack()
const CStack * next = getNextStack(); const CStack * next = getNextStack();
if (!next) if (!next)
return; {
// No stacks to move - start next round
startNextRound(false);
next = getNextStack();
if (!next)
throw std::runtime_error("Failed to find valid stack to act!");
}
BattleUnitsChanged removeGhosts; BattleUnitsChanged removeGhosts;
@ -499,28 +513,42 @@ bool BattleFlowProcessor::rollGoodMorale(const CStack * next)
void BattleFlowProcessor::onActionMade(const BattleAction &ba) void BattleFlowProcessor::onActionMade(const BattleAction &ba)
{ {
const CStack * next = gameHandler->gameState()->curB->battleGetStackByID(ba.stackNumber); const auto & battle = gameHandler->gameState()->curB;
const CStack * actedStack = battle->battleGetStackByID(ba.stackNumber);
const CStack * activeStack = battle->battleGetStackByID(battle->getActiveStackID());
assert(activeStack != nullptr);
//we're after action, all results applied //we're after action, all results applied
owner->checkBattleStateChanges(); //check if this action ended the battle owner->checkBattleStateChanges(); //check if this action ended the battle
if(next == nullptr) bool heroAction = ba.actionType == EActionType::HERO_SPELL || ba.actionType ==EActionType::SURRENDER || ba.actionType ==EActionType::RETREAT || ba.actionType ==EActionType::END_TACTIC_PHASE;
return;
bool heroAction = ba.actionType == EActionType::HERO_SPELL || ba.actionType ==EActionType::SURRENDER|| ba.actionType ==EActionType::RETREAT; if (heroAction)
{
if (heroAction && next->alive()) if (activeStack->alive())
{ {
// 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
BattleSetActiveStack sas;
sas.stack = activeStack->unitId();
gameHandler->sendAndApply(&sas);
return; return;
} }
}
else
{
assert(actedStack != nullptr);
if (rollGoodMorale(next)) if (rollGoodMorale(actedStack))
{ {
// Good morale - same stack makes 2nd turn // Good morale - same stack makes 2nd turn
BattleSetActiveStack sas;
sas.stack = actedStack->unitId();
gameHandler->sendAndApply(&sas);
return; return;
} }
}
activateNextStack(); activateNextStack();
} }

View File

@ -19,6 +19,7 @@ VCMI_LIB_NAMESPACE_END
class CGameHandler; class CGameHandler;
class BattleProcessor; class BattleProcessor;
/// Controls flow of battles - battle startup actions and switching to next stack or next round after actions
class BattleFlowProcessor : boost::noncopyable class BattleFlowProcessor : boost::noncopyable
{ {
BattleProcessor * owner; BattleProcessor * owner;
@ -30,15 +31,17 @@ class BattleFlowProcessor : boost::noncopyable
bool tryMakeAutomaticAction(const CStack * stack); bool tryMakeAutomaticAction(const CStack * stack);
void summonGuardiansHelper(std::vector<BattleHex> & output, const BattleHex & targetPosition, ui8 side, bool targetIsTwoHex); void summonGuardiansHelper(std::vector<BattleHex> & output, const BattleHex & targetPosition, ui8 side, bool targetIsTwoHex);
void trySummonGuardians(const CStack * stack);
void castOpeningSpells();
void activateNextStack(); void activateNextStack();
void startNextRound(bool isFirstRound); void startNextRound(bool isFirstRound);
void stackEnchantedTrigger(const CStack * stack); void stackEnchantedTrigger(const CStack * stack);
void removeObstacle(const CObstacleInstance &obstacle); void removeObstacle(const CObstacleInstance & obstacle);
void stackTurnTrigger(const CStack *stack); void stackTurnTrigger(const CStack * stack);
void makeStackDoNothing(const CStack * next); 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) bool makeAutomaticAction(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);
@ -46,5 +49,5 @@ public:
void onBattleStarted(); void onBattleStarted();
void onTacticsEnded(); void onTacticsEnded();
void onActionMade(const BattleAction &ba); void onActionMade(const BattleAction & ba);
}; };

View File

@ -27,6 +27,7 @@ class BattleActionProcessor;
class BattleFlowProcessor; class BattleFlowProcessor;
class BattleResultProcessor; class BattleResultProcessor;
/// Main class for battle handling. Contains all public interface for battles that is accessible from outside, e.g. for CGameHandler
class BattleProcessor : boost::noncopyable class BattleProcessor : boost::noncopyable
{ {
friend class BattleActionProcessor; friend class BattleActionProcessor;
@ -39,14 +40,15 @@ class BattleProcessor : boost::noncopyable
std::unique_ptr<BattleResultProcessor> resultProcessor; std::unique_ptr<BattleResultProcessor> resultProcessor;
void updateGateState(); void updateGateState();
void engageIntoBattle( PlayerColor player ); void engageIntoBattle(PlayerColor player);
void checkBattleStateChanges(); void checkBattleStateChanges();
void setupBattle(int3 tile, const CArmedInstance *armies[2], const CGHeroInstance *heroes[2], bool creatureBank, const CGTownInstance *town); void setupBattle(int3 tile, const CArmedInstance *armies[2], const CGHeroInstance *heroes[2], bool creatureBank, const CGTownInstance *town);
bool makeBattleAction(const BattleAction &ba); bool makeBattleAction(const BattleAction & ba);
void setBattleResult(EBattleResult resultType, int victoriusSide); void setBattleResult(EBattleResult resultType, int victoriusSide);
public: public:
explicit BattleProcessor(CGameHandler * gameHandler); explicit BattleProcessor(CGameHandler * gameHandler);
BattleProcessor(); BattleProcessor();
@ -54,14 +56,20 @@ public:
void setGameHandler(CGameHandler * gameHandler); void setGameHandler(CGameHandler * gameHandler);
void startBattlePrimary(const CArmedInstance *army1, const CArmedInstance *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool creatureBank = false, const CGTownInstance *town = nullptr); //use hero=nullptr for no hero /// Starts battle with specified parameters
void startBattleI(const CArmedInstance *army1, const CArmedInstance *army2, int3 tile, bool creatureBank = false); //if any of armies is hero, hero will be used void startBattlePrimary(const CArmedInstance *army1, const CArmedInstance *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool creatureBank = false, const CGTownInstance *town = nullptr);
void startBattleI(const CArmedInstance *army1, const CArmedInstance *army2, bool creatureBank = false); //if any of armies is hero, hero will be used, visitable tile of second obj is place of battle /// Starts battle between two armies (which can also be heroes) at specified tile
void startBattleI(const CArmedInstance *army1, const CArmedInstance *army2, int3 tile, bool creatureBank = false);
/// Starts battle between two armies (which can also be heroes) at position of 2nd object
void startBattleI(const CArmedInstance *army1, const CArmedInstance *army2, bool creatureBank = false);
bool makeBattleAction(PlayerColor player, BattleAction &ba); /// Processing of incoming battle action netpack
bool makeBattleAction(PlayerColor player, BattleAction & ba);
/// Applies results of a battle once player agrees to them
void endBattleConfirm(const BattleInfo * battleInfo); void endBattleConfirm(const BattleInfo * battleInfo);
void battleAfterLevelUp(const BattleResult &result); /// Applies results of a battle after potential levelup
void battleAfterLevelUp(const BattleResult & result);
template <typename Handler> void serialize(Handler &h, const int version) template <typename Handler> void serialize(Handler &h, const int version)
{ {

View File

@ -24,7 +24,7 @@ struct CasualtiesAfterBattle
{ {
using TStackAndItsNewCount = std::pair<StackLocation, int>; using TStackAndItsNewCount = std::pair<StackLocation, int>;
using TSummoned = std::map<CreatureID, TQuantity>; using TSummoned = std::map<CreatureID, TQuantity>;
enum {ERASE = -1}; // enum {ERASE = -1};
const CArmedInstance * army; const CArmedInstance * army;
std::vector<TStackAndItsNewCount> newStackCounts; std::vector<TStackAndItsNewCount> newStackCounts;
std::vector<ArtifactLocation> removedWarMachines; std::vector<ArtifactLocation> removedWarMachines;
@ -32,7 +32,7 @@ struct CasualtiesAfterBattle
ObjectInstanceID heroWithDeadCommander; //TODO: unify stack locations ObjectInstanceID heroWithDeadCommander; //TODO: unify stack locations
CasualtiesAfterBattle(const SideInBattle & battleSide, const BattleInfo * bat); CasualtiesAfterBattle(const SideInBattle & battleSide, const BattleInfo * bat);
void updateArmy(CGameHandler *gh); void updateArmy(CGameHandler * gh);
}; };
struct FinishingBattleHelper struct FinishingBattleHelper
@ -61,7 +61,7 @@ struct FinishingBattleHelper
class BattleResultProcessor : boost::noncopyable class BattleResultProcessor : boost::noncopyable
{ {
// BattleProcessor * owner; // BattleProcessor * owner;
CGameHandler * gameHandler; CGameHandler * gameHandler;
std::unique_ptr<BattleResult> battleResult; std::unique_ptr<BattleResult> battleResult;
@ -74,5 +74,5 @@ public:
void setBattleResult(EBattleResult resultType, int victoriusSide); void setBattleResult(EBattleResult resultType, int victoriusSide);
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 endBattleConfirm(const BattleInfo * battleInfo);
void battleAfterLevelUp(const BattleResult &result); void battleAfterLevelUp(const BattleResult & result);
}; };