Browse Source

Merge pull request #309 from vcmi/magicTweaks

Magic tweaks
Alexander Shishkin 8 years ago
parent
commit
a3b04da5f0

+ 3 - 4
AI/BattleAI/BattleAI.cpp

@@ -186,16 +186,15 @@ void CBattleAI::attemptCastingSpell()
 	if(!hero)
 		return;
 
-	if(!cb->battleCanCastSpell())
+	if(cb->battleCanCastSpell(hero, ECastingMode::HERO_CASTING) != ESpellCastProblem::OK)
 		return;
 
 	LOGL("Casting spells sounds like fun. Let's see...");
 	//Get all spells we can cast
 	std::vector<const CSpell*> possibleSpells;
-	vstd::copy_if(VLC->spellh->objects, std::back_inserter(possibleSpells), [this] (const CSpell *s) -> bool
+	vstd::copy_if(VLC->spellh->objects, std::back_inserter(possibleSpells), [this, hero] (const CSpell *s) -> bool
 	{
-		auto problem = getCbc()->battleCanCastThisSpell(s);
-		return problem == ESpellCastProblem::OK;
+		return s->canBeCast(getCbc().get(), ECastingMode::HERO_CASTING, hero) == ESpellCastProblem::OK;
 	});
 	LOGFL("I can cast %d spells.", possibleSpells.size());
 

+ 22 - 12
client/battle/CBattleInterface.cpp

@@ -871,19 +871,24 @@ void CBattleInterface::bSpellf()
 	if (spellDestSelectMode) //we are casting a spell
 		return;
 
-	CCS->curh->changeGraphic(ECursor::ADVENTURE,0);
-
 	if (!myTurn)
 		return;
 
 	auto myHero = currentHero();
-	ESpellCastProblem::ESpellCastProblem spellCastProblem;
-	if (curInt->cb->battleCanCastSpell(&spellCastProblem))
+	if(!myHero)
+		return;
+
+	CCS->curh->changeGraphic(ECursor::ADVENTURE,0);
+
+	ESpellCastProblem::ESpellCastProblem spellCastProblem = curInt->cb->battleCanCastSpell(myHero, ECastingMode::HERO_CASTING);
+
+	if(spellCastProblem == ESpellCastProblem::OK)
 	{
 		GH.pushInt(new CSpellWindow(myHero, curInt.get()));
 	}
 	else if (spellCastProblem == ESpellCastProblem::MAGIC_IS_BLOCKED)
 	{
+		//TODO: move to spell mechanics, add more information to spell cast problem
 		//Handle Orb of Inhibition-like effects -> we want to display dialog with info, why casting is impossible
 		auto blockingBonus = currentHero()->getBonusLocalFirst(Selector::type(Bonus::BLOCK_ALL_MAGIC));
 		if (!blockingBonus)
@@ -1658,8 +1663,7 @@ void CBattleInterface::enterCreatureCastingMode()
 	{
 		const ISpellCaster *caster = activeStack;
 		const CSpell *spell = SpellID(creatureSpellToCast).toSpell();
-
-		const bool isCastingPossible = (curInt->cb->battleCanCastThisSpellHere(caster, spell, ECastingMode::CREATURE_ACTIVE_CASTING, BattleHex::INVALID) == ESpellCastProblem::OK);
+		const bool isCastingPossible = (spell->canBeCastAt(curInt->cb.get(), caster, ECastingMode::CREATURE_ACTIVE_CASTING, BattleHex::INVALID) == ESpellCastProblem::OK);
 
 		if (isCastingPossible)
 		{
@@ -1849,11 +1853,17 @@ void CBattleInterface::showQueue()
 
 void CBattleInterface::blockUI(bool on)
 {
-	ESpellCastProblem::ESpellCastProblem spellcastingProblem;
-	bool canCastSpells = curInt->cb->battleCanCastSpell(&spellcastingProblem);
-	//if magic is blocked, we leave button active, so the message can be displayed (cf bug #97)
-	if (!canCastSpells)
-		canCastSpells = spellcastingProblem == ESpellCastProblem::MAGIC_IS_BLOCKED;
+	bool canCastSpells = false;
+	auto hero = curInt->cb->battleGetMyHero();
+
+	if(hero)
+	{
+		ESpellCastProblem::ESpellCastProblem spellcastingProblem = curInt->cb->battleCanCastSpell(hero, ECastingMode::HERO_CASTING);
+
+		//if magic is blocked, we leave button active, so the message can be displayed after button click
+		canCastSpells = spellcastingProblem == ESpellCastProblem::OK || spellcastingProblem == ESpellCastProblem::MAGIC_IS_BLOCKED;
+	}
+
 	bool canWait = activeStack ? !activeStack->waited() : false;
 
 	bOptions->block(on);
@@ -2511,7 +2521,7 @@ bool CBattleInterface::isCastingPossibleHere(const CStack *sactive, const CStack
 		else
 		{
 			const ECastingMode::ECastingMode mode = creatureCasting ? ECastingMode::CREATURE_ACTIVE_CASTING : ECastingMode::HERO_CASTING;
-			isCastingPossible = (curInt->cb->battleCanCastThisSpellHere(caster, sp, mode, myNumber) == ESpellCastProblem::OK);
+			isCastingPossible = (sp->canBeCastAt(curInt->cb.get(), caster, mode, myNumber) == ESpellCastProblem::OK);
 		}
 	}
 	else

+ 1 - 1
client/battle/CBattleInterfaceClasses.cpp

@@ -191,7 +191,7 @@ void CBattleHero::clickLeft(tribool down, bool previousState)
 	if(myOwner->spellDestSelectMode) //we are casting a spell
 		return;
 
-	if(myHero != nullptr && !down &&  myOwner->myTurn && myOwner->getCurrentPlayerInterface()->cb->battleCanCastSpell()) //check conditions
+	if(myHero != nullptr && !down &&  myOwner->myTurn && myOwner->getCurrentPlayerInterface()->cb->battleCanCastSpell(myHero, ECastingMode::HERO_CASTING) == ESpellCastProblem::OK) //check conditions
 	{
 		for(int it=0; it<GameConstants::BFIELD_SIZE; ++it) //do nothing when any hex is hovered - hero's animation overlaps battlefield
 		{

+ 3 - 3
client/windows/CSpellWindow.cpp

@@ -337,7 +337,7 @@ void CSpellWindow::computeSpellsPerArea()
 	spellsCurSite.reserve(mySpells.size());
 	for(const CSpell * spell : mySpells)
 	{
-		if(spell->combatSpell ^ !battleSpellsOnly
+		if(spell->isCombatSpell() ^ !battleSpellsOnly
 			&& ((selectedTab == 4) || spell->school.at((ESpellSchool)selectedTab))
 			)
 		{
@@ -547,9 +547,9 @@ void CSpellWindow::SpellArea::clickLeft(tribool down, bool previousState)
 		}
 
 		//we will cast a spell
-		if(mySpell->combatSpell && owner->myInt->battleInt && owner->myInt->cb->battleCanCastSpell()) //if battle window is open
+		if(mySpell->isCombatSpell() && owner->myInt->battleInt) //if battle window is open
 		{
-			ESpellCastProblem::ESpellCastProblem problem = owner->myInt->cb->battleCanCastThisSpell(mySpell);
+			ESpellCastProblem::ESpellCastProblem problem = mySpell->canBeCast(owner->myInt->cb.get(), ECastingMode::HERO_CASTING, owner->myHero);
 			switch (problem)
 			{
 			case ESpellCastProblem::OK:

+ 1 - 102
lib/CBattleCallback.cpp

@@ -1708,59 +1708,6 @@ std::vector<BattleHex> CBattleInfoCallback::getAttackableBattleHexes() const
 	return attackableBattleHexes;
 }
 
-ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCastThisSpell(const ISpellCaster * caster, const CSpell * spell, ECastingMode::ECastingMode mode) const
-{
-	RETURN_IF_NOT_BATTLE(ESpellCastProblem::INVALID);
-	if(caster == nullptr)
-	{
-		logGlobal->errorStream() << "CBattleInfoCallback::battleCanCastThisSpell: no spellcaster.";
-		return ESpellCastProblem::INVALID;
-	}
-	const PlayerColor player = caster->getOwner();
-	const si8 side = playerToSide(player);
-
-	if(side < 0)
-		return ESpellCastProblem::INVALID;
-
-	if(!battleDoWeKnowAbout(side))
-		return ESpellCastProblem::INVALID;
-
-	ESpellCastProblem::ESpellCastProblem genProblem = battleCanCastSpell(caster, mode);
-	if(genProblem != ESpellCastProblem::OK)
-		return genProblem;
-
-	switch(mode)
-	{
-	case ECastingMode::HERO_CASTING:
-		{
-			const CGHeroInstance * castingHero = dynamic_cast<const CGHeroInstance *>(caster);//todo: unify hero|creature spell cost
-			if(!castingHero)
-			{
-				logGlobal->error("battleCanCastThisSpell: invalid caster");
-				return ESpellCastProblem::INVALID;
-			}
-
-			if(!castingHero->getArt(ArtifactPosition::SPELLBOOK))
-				return ESpellCastProblem::NO_SPELLBOOK;
-			if(!castingHero->canCastThisSpell(spell))
-				return ESpellCastProblem::HERO_DOESNT_KNOW_SPELL;
-			if(castingHero->mana < battleGetSpellCost(spell, castingHero)) //not enough mana
-				return ESpellCastProblem::NOT_ENOUGH_MANA;
-		}
-		break;
-	}
-
-	if(!spell->combatSpell)
-		return ESpellCastProblem::ADVMAP_SPELL_INSTEAD_OF_BATTLE_SPELL;
-
-	//effect like Recanter's Cloak. Blocks also passive casting.
-	//TODO: check creature abilities to block
-	if(battleMaxSpellLevel(side) < spell->level)
-		return ESpellCastProblem::SPELL_LEVEL_LIMIT_EXCEEDED;
-
-	return spell->canBeCast(this, mode, caster);
-}
-
 ui32 CBattleInfoCallback::battleGetSpellCost(const CSpell * sp, const CGHeroInstance * caster) const
 {
 	RETURN_IF_NOT_BATTLE(-1);
@@ -1789,22 +1736,6 @@ ui32 CBattleInfoCallback::battleGetSpellCost(const CSpell * sp, const CGHeroInst
 	return ret - manaReduction + manaIncrease;
 }
 
-ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCastThisSpellHere(const ISpellCaster * caster, const CSpell * spell, ECastingMode::ECastingMode mode, BattleHex dest) const
-{
-	RETURN_IF_NOT_BATTLE(ESpellCastProblem::INVALID);
-	if(caster == nullptr)
-	{
-		logGlobal->errorStream() << "CBattleInfoCallback::battleCanCastThisSpellHere: no spellcaster.";
-		return ESpellCastProblem::INVALID;
-	}
-
-	ESpellCastProblem::ESpellCastProblem problem = battleCanCastThisSpell(caster, spell, mode);
-	if(problem != ESpellCastProblem::OK)
-		return problem;
-
-	return spell->canBeCastAt(this, caster, mode, dest);
-}
-
 const CStack * CBattleInfoCallback::getStackIf(std::function<bool(const CStack*)> pred) const
 {
 	RETURN_IF_NOT_BATTLE(nullptr);
@@ -1887,7 +1818,7 @@ SpellID CBattleInfoCallback::getRandomBeneficialSpell(CRandomGenerator & rand, c
 
 		if(subject->hasBonus(Selector::source(Bonus::SPELL_EFFECT, spellID), Selector::all, cachingStr.str())
 			//TODO: this ability has special limitations
-			|| battleCanCastThisSpellHere(subject, spellID.toSpell(), ECastingMode::CREATURE_ACTIVE_CASTING, subject->position) != ESpellCastProblem::OK)
+			|| spellID.toSpell()->canBeCastAt(this, subject, ECastingMode::CREATURE_ACTIVE_CASTING, subject->position) != ESpellCastProblem::OK)
 			continue;
 
 		switch (spellID)
@@ -2127,18 +2058,6 @@ ReachabilityInfo::Parameters::Parameters(const CStack *Stack)
 	knownAccessible = stack->getHexes();
 }
 
-ESpellCastProblem::ESpellCastProblem CPlayerBattleCallback::battleCanCastThisSpell(const CSpell * spell) const
-{
-	RETURN_IF_NOT_BATTLE(ESpellCastProblem::INVALID);
-	ASSERT_IF_CALLED_WITH_PLAYER
-
-	const ISpellCaster * hero = battleGetMyHero();
-	if(hero == nullptr)
-		return ESpellCastProblem::INVALID;
-	else
-		return CBattleInfoCallback::battleCanCastThisSpell(hero, spell, ECastingMode::HERO_CASTING);
-}
-
 bool CPlayerBattleCallback::battleCanFlee() const
 {
 	RETURN_IF_NOT_BATTLE(false);
@@ -2169,26 +2088,6 @@ int CPlayerBattleCallback::battleGetSurrenderCost() const
 	return CBattleInfoCallback::battleGetSurrenderCost(*player);
 }
 
-bool CPlayerBattleCallback::battleCanCastSpell(ESpellCastProblem::ESpellCastProblem *outProblem /*= nullptr*/) const
-{
-	RETURN_IF_NOT_BATTLE(false);
-	ASSERT_IF_CALLED_WITH_PLAYER
-
-	const CGHeroInstance * hero = battleGetMyHero();
-	if(!hero)
-	{
-		if(outProblem)
-			*outProblem = ESpellCastProblem::NO_HERO_TO_CAST_SPELL;
-		return false;
-	}
-
-	auto problem = CBattleInfoCallback::battleCanCastSpell(hero, ECastingMode::HERO_CASTING);
-	if(outProblem)
-		*outProblem = problem;
-
-	return problem == ESpellCastProblem::OK;
-}
-
 const CGHeroInstance * CPlayerBattleCallback::battleGetMyHero() const
 {
 	return CBattleInfoEssentials::battleGetFightingHero(battleGetMySide());

+ 0 - 4
lib/CBattleCallback.h

@@ -293,8 +293,6 @@ public:
 	si8 battleMaxSpellLevel(ui8 side) const; //calculates minimum spell level possible to be cast on battlefield - takes into account artifacts of both heroes; if no effects are set, 0 is returned
 	ui32 battleGetSpellCost(const CSpell * sp, const CGHeroInstance * caster) const; //returns cost of given spell
 	ESpellCastProblem::ESpellCastProblem battleCanCastSpell(const ISpellCaster * caster, ECastingMode::ECastingMode mode) const; //returns true if there are no general issues preventing from casting a spell
-	ESpellCastProblem::ESpellCastProblem battleCanCastThisSpell(const ISpellCaster * caster, const CSpell * spell, ECastingMode::ECastingMode mode) const; //checks if given player can cast given spell
-	ESpellCastProblem::ESpellCastProblem battleCanCastThisSpellHere(const ISpellCaster * caster, const CSpell * spell, ECastingMode::ECastingMode mode, BattleHex dest) const; //checks if given player can cast given spell at given tile in given mode
 
 	SpellID battleGetRandomStackSpell(CRandomGenerator & rand, const CStack * stack, ERandomSpell mode) const;
 	SpellID getRandomBeneficialSpell(CRandomGenerator & rand, const CStack * subject) const;
@@ -336,11 +334,9 @@ class DLL_LINKAGE CPlayerBattleCallback : public CBattleInfoCallback
 public:
 	bool battleCanFlee() const; //returns true if caller can flee from the battle
 	TStacks battleGetStacks(EStackOwnership whose = MINE_AND_ENEMY, bool onlyAlive = true) const; //returns stacks on battlefield
-	ESpellCastProblem::ESpellCastProblem battleCanCastThisSpell(const CSpell * spell) const; //determines if given spell can be cast (and returns problem description)
 
 	int battleGetSurrenderCost() const; //returns cost of surrendering battle, -1 if surrendering is not possible
 
-	bool battleCanCastSpell(ESpellCastProblem::ESpellCastProblem *outProblem = nullptr) const; //returns true, if caller can cast a spell. If not, if pointer is given via arg, the reason will be written.
 	const CGHeroInstance * battleGetMyHero() const;
 	InfoAboutHero battleGetEnemyHero() const;
 };

+ 47 - 3
lib/spells/CSpellHandler.cpp

@@ -157,10 +157,50 @@ ui32 CSpell::calculateDamage(const ISpellCaster * caster, const CStack * affecte
 
 ESpellCastProblem::ESpellCastProblem CSpell::canBeCast(const CBattleInfoCallback * cb, ECastingMode::ECastingMode mode, const ISpellCaster * caster) const
 {
-	const ESpellCastProblem::ESpellCastProblem generalProblem = mechanics->canBeCast(cb, mode, caster);
+	ESpellCastProblem::ESpellCastProblem genProblem = cb->battleCanCastSpell(caster, mode);
+	if(genProblem != ESpellCastProblem::OK)
+		return genProblem;
 
-	if(generalProblem != ESpellCastProblem::OK)
-		return generalProblem;
+	switch(mode)
+	{
+	case ECastingMode::HERO_CASTING:
+		{
+			const CGHeroInstance * castingHero = dynamic_cast<const CGHeroInstance *>(caster);//todo: unify hero|creature spell cost
+			if(!castingHero)
+			{
+				logGlobal->debug("CSpell::canBeCast: invalid caster");
+				return ESpellCastProblem::NO_HERO_TO_CAST_SPELL;
+			}
+
+			if(!castingHero->getArt(ArtifactPosition::SPELLBOOK))
+				return ESpellCastProblem::NO_SPELLBOOK;
+			if(!castingHero->canCastThisSpell(this))
+				return ESpellCastProblem::HERO_DOESNT_KNOW_SPELL;
+			if(castingHero->mana < cb->battleGetSpellCost(this, castingHero)) //not enough mana
+				return ESpellCastProblem::NOT_ENOUGH_MANA;
+		}
+		break;
+	}
+
+	if(!isCombatSpell())
+		return ESpellCastProblem::ADVMAP_SPELL_INSTEAD_OF_BATTLE_SPELL;
+
+	const PlayerColor player = caster->getOwner();
+	const si8 side = cb->playerToSide(player);
+
+	if(side < 0)
+		return ESpellCastProblem::INVALID;
+
+	//effect like Recanter's Cloak. Blocks also passive casting.
+	//TODO: check creature abilities to block
+	//TODO: check any possible caster
+	if(cb->battleMaxSpellLevel(side) < level)
+		return ESpellCastProblem::SPELL_LEVEL_LIMIT_EXCEEDED;
+
+	const ESpellCastProblem::ESpellCastProblem specificProblem = mechanics->canBeCast(cb, mode, caster);
+
+	if(specificProblem != ESpellCastProblem::OK)
+		return specificProblem;
 
 	//check for creature target existence
 	//allow to cast spell if there is at least one smart target
@@ -368,6 +408,10 @@ void CSpell::getEffects(std::vector<Bonus> & lst, const int level) const
 
 ESpellCastProblem::ESpellCastProblem CSpell::canBeCastAt(const CBattleInfoCallback * cb, const ISpellCaster * caster, ECastingMode::ECastingMode mode, BattleHex destination) const
 {
+	ESpellCastProblem::ESpellCastProblem problem = canBeCast(cb, mode, caster);
+	if(problem != ESpellCastProblem::OK)
+		return problem;
+
 	SpellTargetingContext ctx(this, mode, caster, caster->getSpellSchoolLevel(this), destination);
 
 	return mechanics->canBeCast(cb, ctx);

+ 6 - 2
lib/spells/ISpellMechanics.cpp

@@ -87,10 +87,14 @@ void BattleSpellCastParameters::cast(const SpellCastEnvironment * env)
 	spell->battleCast(env, *this);
 }
 
-void BattleSpellCastParameters::castIfPossible(const SpellCastEnvironment * env)
+bool BattleSpellCastParameters::castIfPossible(const SpellCastEnvironment * env)
 {
-	if(ESpellCastProblem::OK == cb->battleCanCastThisSpell(caster, spell, mode))
+	if(ESpellCastProblem::OK == spell->canBeCast(cb, mode, caster))
+	{
 		cast(env);
+		return true;
+	}
+	return false;
 }
 
 BattleHex BattleSpellCastParameters::getFirstDestinationHex() const

+ 2 - 1
lib/spells/ISpellMechanics.h

@@ -57,7 +57,8 @@ public:
 	void cast(const SpellCastEnvironment * env);
 
 	///cast with silent check for permitted cast
-	void castIfPossible(const SpellCastEnvironment * env);
+	///returns true if cast was permitted
+	bool castIfPossible(const SpellCastEnvironment * env);
 
 	BattleHex getFirstDestinationHex() const;
 

+ 9 - 11
server/CGameHandler.cpp

@@ -4458,7 +4458,7 @@ bool CGameHandler::makeCustomAction(BattleAction &ba)
 			if (ba.selectedStack >= 0)
 				parameters.aimToStack(gs->curB->battleGetStackByID(ba.selectedStack, false));
 
-			ESpellCastProblem::ESpellCastProblem escp = gs->curB->battleCanCastThisSpell(h, s, ECastingMode::HERO_CASTING);//todo: should we check aimed cast(battleCanCastThisSpellHere)?
+			ESpellCastProblem::ESpellCastProblem escp = s->canBeCast(gs->curB, ECastingMode::HERO_CASTING, h);//todo: should we check aimed cast?
 			if (escp != ESpellCastProblem::OK)
 			{
 				logGlobal->warn("Spell cannot be cast! Problem: %d", escp);
@@ -4631,14 +4631,14 @@ void CGameHandler::stackTurnTrigger(const CStack *st)
 				const CSpell * spell = SpellID(spellID).toSpell();
 				bl.remove_if([&bonus](const Bonus* b){return b==bonus.get();});
 
-				if (gs->curB->battleCanCastThisSpell(st, spell, ECastingMode::ENCHANTER_CASTING) == ESpellCastProblem::OK)
-				{
-					BattleSpellCastParameters parameters(gs->curB, st, spell);
-					parameters.spellLvl = bonus->val;
-					parameters.effectLevel = bonus->val;//todo: recheck
-					parameters.mode = ECastingMode::ENCHANTER_CASTING;
-					parameters.cast(spellEnv);
+				BattleSpellCastParameters parameters(gs->curB, st, spell);
+				parameters.spellLvl = bonus->val;
+				parameters.effectLevel = bonus->val;//todo: recheck
+				parameters.mode = ECastingMode::ENCHANTER_CASTING;
 
+				cast = parameters.castIfPossible(spellEnv);
+				if(cast)
+				{
 					//todo: move to mechanics
 					BattleSetStackProperty ssp;
 					ssp.which = BattleSetStackProperty::ENCHANTER_COUNTER;
@@ -4646,8 +4646,6 @@ void CGameHandler::stackTurnTrigger(const CStack *st)
 					ssp.val = bonus->additionalInfo; //increase cooldown counter
 					ssp.stackID = st->ID;
 					sendAndApply(&ssp);
-
-					cast = true;
 				}
 			}
 		}
@@ -5302,7 +5300,7 @@ void CGameHandler::attackCasting(const BattleAttack & bat, Bonus::BonusType atta
 			vstd::amin(chance, 100);
 
 			const CSpell * spell = SpellID(spellID).toSpell();
-			if (gs->curB->battleCanCastThisSpellHere(attacker, spell, ECastingMode::AFTER_ATTACK_CASTING, oneOfAttacked->position) != ESpellCastProblem::OK)
+			if(spell->canBeCastAt(gs->curB, attacker, ECastingMode::AFTER_ATTACK_CASTING, oneOfAttacked->position) != ESpellCastProblem::OK)
 				continue;
 
 			//check if spell should be cast (probability handling)