Browse Source

Extract battleStackIsImmune from battleIsImmune
This fix possible problems with rising spells as now immunty is handled on stack level not on hex level
* battleIsImmune in now protected - only used in canCastThisSpellHere

AlexVinS 11 năm trước cách đây
mục cha
commit
6f65d2484b
4 tập tin đã thay đổi với 115 bổ sung82 xóa
  1. 2 2
      AI/BattleAI/BattleAI.cpp
  2. 105 77
      lib/CBattleCallback.cpp
  3. 6 1
      lib/CBattleCallback.h
  4. 2 2
      server/CGameHandler.cpp

+ 2 - 2
AI/BattleAI/BattleAI.cpp

@@ -459,9 +459,9 @@ void CBattleAI::attemptCastingSpell()
 				int damageDealt = 0, damageReceived = 0;
 
 				auto stacksSuffering = cb->getAffectedCreatures(ps.spell, skillLevel, playerID, ps.dest);
-				vstd::erase_if(stacksSuffering, [&](const CStack *stack) -> bool
+				vstd::erase_if(stacksSuffering, [&](const CStack * s) -> bool
 				{
-					return cb->battleIsImmune(hero, ps.spell, ECastingMode::HERO_CASTING, ps.dest);
+					return  ESpellCastProblem::OK != cb->battleStackIsImmune(hero, ps.spell, ECastingMode::HERO_CASTING, s);
 				});
 
 				if(stacksSuffering.empty())

+ 105 - 77
lib/CBattleCallback.cpp

@@ -1569,87 +1569,33 @@ ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleIsImmune(const C
 {
 	RETURN_IF_NOT_BATTLE(ESpellCastProblem::INVALID);
 
-	// Get stack at destination hex -> subject of our spell.
-	const CStack * subject = battleGetStackByPos(dest, !spell->isRisingSpell()); //only alive if not rising spell
-
-	if(subject)
+	// Get all stacks at destination hex -> subject of our spell. only alive if not rising spell
+	TStacks stacks = battleGetStacksIf([=](const CStack * s){
+		return s->coversPos(dest) && (spell->isRisingSpell() || s->alive());
+	});
+	
+	if(!stacks.empty())
 	{
-		if (spell->isPositive() && subject->hasBonusOfType(Bonus::RECEPTIVE)) //accept all positive spells
-			return ESpellCastProblem::OK;
-
-		if (spell->isImmuneBy(subject)) //TODO: move all logic to spellhandler
-			return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
-
-		switch (spell->id) //TODO: more general logic for new spells?
+		bool allImmune = true;
+		
+		ESpellCastProblem::ESpellCastProblem problem;		
+		
+		for(auto s : stacks)
 		{
-		case SpellID::CLONE:
+			ESpellCastProblem::ESpellCastProblem res = battleStackIsImmune(caster,spell,mode,s);
+			
+			if(res == ESpellCastProblem::OK)
 			{
-				//can't clone already cloned creature
-				if (vstd::contains(subject->state, EBattleStackState::CLONED))
-					return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
-				//TODO: how about stacks casting Clone?
-				//currently Clone casted by stack is assumed Expert level
-				ui8 schoolLevel;
-				if (caster)
-				{
-					schoolLevel = caster->getSpellSchoolLevel(spell);
-				}
-				else
-				{
-					schoolLevel = 3;
-				}
-
-				if (schoolLevel < 3)
-				{
-					int maxLevel = (std::max(schoolLevel, (ui8)1) + 4);
-					int creLevel = subject->getCreature()->level;
-					if (maxLevel < creLevel) //tier 1-5 for basic, 1-6 for advanced, any level for expert
-						return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
-				}
+				allImmune = false;
 			}
-			break;
-		case SpellID::DISPEL_HELPFUL_SPELLS:
-			{
-				TBonusListPtr spellBon = subject->getSpellBonuses();
-				bool hasPositiveSpell = false;
-				for(const Bonus * b : *spellBon)
-				{
-					if(SpellID(b->sid).toSpell()->isPositive())
-					{
-						hasPositiveSpell = true;
-						break;
-					}
-				}
-				if(!hasPositiveSpell)
-				{
-					return ESpellCastProblem::NO_SPELLS_TO_DISPEL;
-				}
-			}
-			break;
-		}
-
-		if (spell->isRisingSpell())
-		{
-			if(subject->count >= subject->baseAmount)
-				return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
-			
-			if (caster) //FIXME: Archangels can cast immune stack
+			else
 			{
-				auto maxHealth = calculateHealedHP (caster, spell, subject);
-				if (maxHealth < subject->MaxHealth()) //must be able to rise at least one full creature
-					return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
+				problem = res;
 			}
 		}
-		else if(spell->id == SpellID::HYPNOTIZE && caster) //do not resist hypnotize casted after attack, for example
-		{
-			//TODO: what with other creatures casting hypnotize, Faerie Dragons style?
-			ui64 subjectHealth = (subject->count - 1) * subject->MaxHealth() + subject->firstHPleft;
-			//apply 'damage' bonus for hypnotize, including hero specialty
-			ui64 maxHealth = calculateSpellBonus (caster->getPrimSkillLevel(PrimarySkill::SPELL_POWER)
-				* spell->power + spell->getPower(caster->getSpellSchoolLevel(spell)), spell, caster, subject);
-			if (subjectHealth > maxHealth)
-				return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
-		}
+		
+		if(allImmune)
+			return problem;
 	}
 	else //no target stack on this tile
 	{
@@ -1673,6 +1619,88 @@ ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleIsImmune(const C
 	return ESpellCastProblem::OK;
 }
 
+ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleStackIsImmune(const CGHeroInstance * caster, const CSpell * spell, ECastingMode::ECastingMode mode, const CStack * subject) const
+{
+	if (spell->isPositive() && subject->hasBonusOfType(Bonus::RECEPTIVE)) //accept all positive spells
+		return ESpellCastProblem::OK;
+
+	if (spell->isImmuneBy(subject)) //TODO: move all logic to spellhandler
+		return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
+
+	switch (spell->id) //TODO: more general logic for new spells?
+	{
+	case SpellID::CLONE:
+		{
+			//can't clone already cloned creature
+			if (vstd::contains(subject->state, EBattleStackState::CLONED))
+				return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
+			//TODO: how about stacks casting Clone?
+			//currently Clone casted by stack is assumed Expert level
+			ui8 schoolLevel;
+			if (caster)
+			{
+				schoolLevel = caster->getSpellSchoolLevel(spell);
+			}
+			else
+			{
+				schoolLevel = 3;
+			}
+
+			if (schoolLevel < 3)
+			{
+				int maxLevel = (std::max(schoolLevel, (ui8)1) + 4);
+				int creLevel = subject->getCreature()->level;
+				if (maxLevel < creLevel) //tier 1-5 for basic, 1-6 for advanced, any level for expert
+					return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
+			}
+		}
+		break;
+	case SpellID::DISPEL_HELPFUL_SPELLS:
+		{
+			TBonusListPtr spellBon = subject->getSpellBonuses();
+			bool hasPositiveSpell = false;
+			for(const Bonus * b : *spellBon)
+			{
+				if(SpellID(b->sid).toSpell()->isPositive())
+				{
+					hasPositiveSpell = true;
+					break;
+				}
+			}
+			if(!hasPositiveSpell)
+			{
+				return ESpellCastProblem::NO_SPELLS_TO_DISPEL;
+			}
+		}
+		break;
+	}
+
+	if (spell->isRisingSpell())
+	{
+		if(subject->count >= subject->baseAmount)
+			return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
+		
+		if (caster) //FIXME: Archangels can cast immune stack
+		{
+			auto maxHealth = calculateHealedHP (caster, spell, subject);
+			if (maxHealth < subject->MaxHealth()) //must be able to rise at least one full creature
+				return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
+		}
+	}
+	else if(spell->id == SpellID::HYPNOTIZE && caster) //do not resist hypnotize casted after attack, for example
+	{
+		//TODO: what with other creatures casting hypnotize, Faerie Dragons style?
+		ui64 subjectHealth = (subject->count - 1) * subject->MaxHealth() + subject->firstHPleft;
+		//apply 'damage' bonus for hypnotize, including hero specialty
+		ui64 maxHealth = calculateSpellBonus (caster->getPrimSkillLevel(PrimarySkill::SPELL_POWER)
+			* spell->power + spell->getPower(caster->getSpellSchoolLevel(spell)), spell, caster, subject);
+		if (subjectHealth > maxHealth)
+			return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
+	}	
+		
+	return ESpellCastProblem::OK;
+}
+
 ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCastThisSpell( PlayerColor player, const CSpell * spell, ECastingMode::ECastingMode mode ) const
 {
 	RETURN_IF_NOT_BATTLE(ESpellCastProblem::INVALID);
@@ -1715,7 +1743,7 @@ ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCastThisSpell
 		auto stacks = spell->isNegative() ? battleAliveStacks(!side) : battleAliveStacks();
 		for(auto stack : stacks)
 		{
-			if(!battleIsImmune(castingHero, spell, mode, stack->position))
+			if( ESpellCastProblem::OK == battleStackIsImmune(castingHero, spell, mode, stack))
 			{
 				allStacksImmune = false;
 				break;
@@ -1757,7 +1785,7 @@ ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCastThisSpell
 			bool targetExists = false;
 			for(const CStack * stack : battleGetAllStacks()) //dead stacks will be immune anyway
 			{
-				bool immune = battleIsImmune(caster, spell, mode, stack->position) != ESpellCastProblem::OK;
+				bool immune =  ESpellCastProblem::OK != battleStackIsImmune(caster, spell, mode, stack);
 				bool casterStack = stack->owner == caster->getOwner();
 				
 				if(!immune)
@@ -1812,7 +1840,7 @@ std::vector<BattleHex> CBattleInfoCallback::battleGetPossibleTargets(PlayerColor
 			
 			for(const CStack * stack : battleAliveStacks())
 			{
-				bool immune = battleIsImmune(caster, spell, mode, stack->position) != ESpellCastProblem::OK;
+				bool immune = ESpellCastProblem::OK != battleStackIsImmune(caster, spell, mode, stack);
 				bool casterStack = stack->owner == caster->getOwner();
 				
 				if(!immune)

+ 6 - 1
lib/CBattleCallback.h

@@ -293,7 +293,7 @@ public:
 	SpellID getRandomCastedSpell(const CStack * caster) const; //called at the beginning of turn for Faerie Dragon
 
 	//checks for creature immunity / anything that prevent casting *at given hex* - doesn't take into acount general problems such as not having spellbook or mana points etc.
-	ESpellCastProblem::ESpellCastProblem battleIsImmune(const CGHeroInstance * caster, const CSpell * spell, ECastingMode::ECastingMode mode, BattleHex dest) const; 
+	ESpellCastProblem::ESpellCastProblem battleStackIsImmune(const CGHeroInstance * caster, const CSpell * spell, ECastingMode::ECastingMode mode, const CStack * subject) const; 
 
 
 	const CStack * getStackIf(std::function<bool(const CStack*)> pred) const;
@@ -321,6 +321,11 @@ public:
 	AccessibilityInfo getAccesibility(const std::vector<BattleHex> &accessibleHexes) const; //given hexes will be marked as accessible
 	std::pair<const CStack *, BattleHex> getNearestStack(const CStack * closest, boost::logic::tribool attackerOwned) const;
 protected:
+	
+	//checks for creature immunity / anything that prevent casting *at given hex* - doesn't take into acount general problems such as not having spellbook or mana points etc.
+	ESpellCastProblem::ESpellCastProblem battleIsImmune(const CGHeroInstance * caster, const CSpell * spell, ECastingMode::ECastingMode mode, BattleHex dest) const;
+	
+	
 	ReachabilityInfo getFlyingReachability(const ReachabilityInfo::Parameters &params) const;
 	ReachabilityInfo makeBFS(const AccessibilityInfo &accessibility, const ReachabilityInfo::Parameters &params) const;
 	ReachabilityInfo makeBFS(const CStack *stack) const; //uses default parameters -> stack position and owner's perspective

+ 2 - 2
server/CGameHandler.cpp

@@ -4050,7 +4050,7 @@ void CGameHandler::handleSpellCasting( SpellID spellID, int spellLvl, BattleHex
 	}
 
 	vstd::erase_if(attackedCres,[=](const CStack * s){
-		return gs->curB->battleIsImmune(caster,spell,mode,s->position);		
+		return ESpellCastProblem::OK != gs->curB->battleStackIsImmune(caster,spell,mode,s);		
 	});
 
 	for (auto cre : attackedCres)
@@ -4463,7 +4463,7 @@ void CGameHandler::handleSpellCasting( SpellID spellID, int spellLvl, BattleHex
 				{
 					if(battleStack->owner == gs->curB->sides.at(casterSide).color) //get enemy stacks which can be affected by this spell
 					{
-						if (!gs->curB->battleIsImmune(nullptr, spell, ECastingMode::MAGIC_MIRROR, battleStack->position))
+						if (ESpellCastProblem::OK == gs->curB->battleStackIsImmune(nullptr, spell, ECastingMode::MAGIC_MIRROR, battleStack))
 							mirrorTargets.push_back(battleStack);
 					}
 				}