Browse Source

Merge pull request #11 from alexvins/SpellsRefactoring

Spells refactoring&tweaks
Alexander Shishkin 11 years ago
parent
commit
708958c1e9

+ 1 - 1
config/spells/ability.json

@@ -578,7 +578,7 @@
 				"range" : "0"
 			}
 		},
-		"immunity" : {
+		"absoluteImmunity" : {
 			"UNDEAD": true,
 			"NON_LIVING": true
 		},

+ 4 - 2
config/spells/offensive.json

@@ -324,9 +324,11 @@
 			"offensive": true,
 			"negative": true
 		},
-		"immunity" : {
-			"SIEGE_WEAPON": true,
+		"absoluteImmunity":{
 			"UNDEAD": true,
+		},		
+		"immunity" : {
+			"SIEGE_WEAPON": true,			
 			"DIRECT_DAMAGE_IMMUNITY": true
 		}
 	},

+ 0 - 39
lib/BattleState.cpp

@@ -723,45 +723,6 @@ const CGHeroInstance * BattleInfo::getHero( PlayerColor player ) const
 	return nullptr;
 }
 
-std::vector<ui32> BattleInfo::calculateResistedStacks(const CSpell * sp, const CGHeroInstance * caster, const CGHeroInstance * hero2,
-	const std::vector<const CStack*> & affectedCreatures, PlayerColor casterSideOwner, ECastingMode::ECastingMode mode,
-	int usedSpellPower, int spellLevel, CRandomGenerator & rand) const
-{
-	std::vector<ui32> ret;
-	for(auto & affectedCreature : affectedCreatures)
-	{
-		if(battleIsImmune(caster, sp, mode, (affectedCreature)->position) != ESpellCastProblem::OK) //FIXME: immune stacks should not display resisted animation
-		{
-			ret.push_back((affectedCreature)->ID);
-			continue;
-		}
-
-		//non-negative spells should always succeed, unless immune
-		if(!sp->isNegative())// && (*it)->owner == casterSideOwner)
-			continue;
-
-		/*
-		const CGHeroInstance * bonusHero; //hero we should take bonuses from
-		if((*it)->owner == casterSideOwner)
-			bonusHero = caster;
-		else
-			bonusHero = hero2;*/
-
-		int prob = (affectedCreature)->magicResistance(); //probability of resistance in %
-
-		if(prob > 100) prob = 100;
-
-		//immunity from resistance
-		if(rand.nextInt(99) < prob)
-		{
-			ret.push_back((affectedCreature)->ID);
-		}
-
-	}
-
-	return ret;
-}
-
 PlayerColor BattleInfo::theOtherPlayer(PlayerColor player) const
 {
 	return sides[!whatSide(player)].color;

+ 0 - 2
lib/BattleState.h

@@ -144,8 +144,6 @@ struct DLL_LINKAGE BattleInfo : public CBonusSystemNode, public CBattleInfoCallb
 
 	const CGHeroInstance * getHero(PlayerColor player) const; //returns fighting hero that belongs to given player
 
-	std::vector<ui32> calculateResistedStacks(const CSpell * sp, const CGHeroInstance * caster, const CGHeroInstance * hero2, const std::vector<const CStack*> & affectedCreatures, PlayerColor casterSideOwner, ECastingMode::ECastingMode mode, int usedSpellPower, int spellLevel, CRandomGenerator & rand) const;
-
 	const CStack * battleGetStack(BattleHex pos, bool onlyAlive); //returns stack at given tile
 	const CGHeroInstance * battleGetOwner(const CStack * stack) const; //returns hero that owns given stack; nullptr if none
 	void localInit();

+ 39 - 57
lib/CBattleCallback.cpp

@@ -177,29 +177,36 @@ bool CBattleInfoEssentials::battleHasNativeStack(ui8 side) const
 	return false;
 }
 
-TStacks CBattleInfoEssentials::battleGetAllStacks(bool includeTurrets /*= false*/) const /*returns all stacks, alive or dead or undead or mechanical :) */
+TStacks CBattleInfoEssentials::battleGetAllStacks(bool includeTurrets /*= false*/) const
+{
+	return battleGetStacksIf([](const CStack * s){return true;},includeTurrets);
+}
+
+TStacks CBattleInfoEssentials::battleGetStacksIf(TStackFilter predicate, bool includeTurrets /*= false*/) const
 {
 	TStacks ret;
 	RETURN_IF_NOT_BATTLE(ret);
-	boost::copy(getBattle()->stacks, std::back_inserter(ret));
-	if(!includeTurrets)
-		vstd::erase_if(ret, [](const CStack *stack) { return stack->type->idNumber == CreatureID::ARROW_TOWERS; });
-
+	
+	vstd::copy_if(getBattle()->stacks, std::back_inserter(ret), [=](const CStack * s){
+		return predicate(s) && (includeTurrets || !(s->type->idNumber == CreatureID::ARROW_TOWERS));
+	});
+	
 	return ret;
 }
 
+
 TStacks CBattleInfoEssentials::battleAliveStacks() const
 {
-	TStacks ret;
-	vstd::copy_if(battleGetAllStacks(), std::back_inserter(ret), [](const CStack *s){ return s->alive(); });
-	return ret;
+	return battleGetStacksIf([](const CStack * s){
+		return s->alive();
+	});
 }
 
 TStacks CBattleInfoEssentials::battleAliveStacks(ui8 side) const
 {
-	TStacks ret;
-	vstd::copy_if(battleGetAllStacks(), std::back_inserter(ret), [=](const CStack *s){ return s->alive()  &&  s->attackerOwned == !side; });
-	return ret;
+	return battleGetStacksIf([=](const CStack * s){
+		return s->alive() && s->attackerOwned == !side;
+	});
 }
 
 int CBattleInfoEssentials::battleGetMoatDmg() const
@@ -2022,24 +2029,10 @@ std::set<const CStack*> CBattleInfoCallback::getAffectedCreatures(const CSpell *
 
 	const ui8 attackerSide = playerToSide(attackerOwner) == 1;
 	const auto attackedHexes = spell->rangeInHexes(destinationTile, skillLevel, attackerSide);
-	const bool onlyAlive = !spell->isRisingSpell(); //when casting resurrection or animate dead we should be allow to select dead stack
-
+	
 	const CSpell::TargetInfo ti = spell->getTargetInfo(skillLevel);
 	//TODO: more generic solution for mass spells
-	if(spell->id == SpellID::DEATH_RIPPLE || spell->id == SpellID::DESTROY_UNDEAD )
-	{
-		for(const CStack *stack : battleGetAllStacks())
-		{
-			if((spell->id == SpellID::DEATH_RIPPLE && !stack->getCreature()->isUndead()) //death ripple
-				|| (spell->id == SpellID::DESTROY_UNDEAD && stack->getCreature()->isUndead()) //destroy undead
-				)
-			{
-				if(stack->isValidTarget())
-					attackedCres.insert(stack);
-			}
-		}
-	}
-	else if (spell->id == SpellID::CHAIN_LIGHTNING)
+	if (spell->id == SpellID::CHAIN_LIGHTNING)
 	{
 		std::set<BattleHex> possibleHexes;
 		for (auto stack : battleGetAllStacks())
@@ -2074,7 +2067,7 @@ std::set<const CStack*> CBattleInfoCallback::getAffectedCreatures(const CSpell *
 	{
 		for(BattleHex hex : attackedHexes)
 		{
-			if(const CStack * st = battleGetStackByPos(hex, onlyAlive))
+			if(const CStack * st = battleGetStackByPos(hex, ti.onlyAlive))
 			{
 				if (spell->id == SpellID::DEATH_CLOUD) //Death Cloud //TODO: fireball and fire immunity
 				{
@@ -2090,31 +2083,24 @@ std::set<const CStack*> CBattleInfoCallback::getAffectedCreatures(const CSpell *
 	}
 	else if(spell->getTargetType() == CSpell::CREATURE)
 	{
-		//start with all stacks. 
-		TStacks stacks = battleGetAllStacks();
-		
-		//for single target spells remove stacks from other hexes
-		if(!ti.massive)
-		{
-			vstd::erase_if(stacks,[&](const CStack * stack){
-				return !vstd::contains(stack->getHexes(), destinationTile);
-			});
-		}
+		auto predicate = [=](const CStack * s){
+			const bool positiveToAlly = spell->isPositive() && s->owner == attackerOwner;
+			const bool negativeToEnemy = spell->isNegative() && s->owner != attackerOwner;
+			const bool validTarget = s->isValidTarget(!ti.onlyAlive); //todo: this should be handled by spell class
+	
+			//for single target spells select stacks covering destination tile
+			const bool rangeCovers = ti.massive || s->coversPos(destinationTile);
+			//handle smart targeting
+			const bool positivenessFlag = !ti.smart || spell->isNeutral() || positiveToAlly || negativeToEnemy;
+			
+			return rangeCovers  && positivenessFlag && validTarget;		
+		};
 		
-		//now handle smart targeting and remove invalid targets
-		const bool smartNegative = ti.smart && spell->isNegative();
-		const bool smartPositive = ti.smart && spell->isPositive();
+		TStacks stacks = battleGetStacksIf(predicate);
 		
-		vstd::erase_if(stacks,[&](const CStack * stack){
-			const bool negativeToAlly = smartNegative && stack->owner == attackerOwner;
-			const bool positiveToEnemy = smartPositive && stack->owner != attackerOwner;
-			const bool invalidTarget = !stack->isValidTarget(!onlyAlive); //todo: this should be handled by spell class
-			return negativeToAlly || positiveToEnemy || invalidTarget;
-		});
-
 		if (ti.massive)
 		{
-			//for massive spells add all remaining targets
+			//for massive spells add all targets
 			for (auto stack : stacks)
 				attackedCres.insert(stack);
 
@@ -2141,7 +2127,7 @@ std::set<const CStack*> CBattleInfoCallback::getAffectedCreatures(const CSpell *
 	{
 		for(BattleHex hex : attackedHexes)
 		{
-			if(const CStack * st = battleGetStackByPos(hex, onlyAlive))
+			if(const CStack * st = battleGetStackByPos(hex, ti.onlyAlive))
 				attackedCres.insert(st);
 		}
 	}
@@ -2455,22 +2441,18 @@ bool CPlayerBattleCallback::battleCanFlee() const
 
 TStacks CPlayerBattleCallback::battleGetStacks(EStackOwnership whose /*= MINE_AND_ENEMY*/, bool onlyAlive /*= true*/) const
 {
-	TStacks ret;
-	RETURN_IF_NOT_BATTLE(ret);
 	if(whose != MINE_AND_ENEMY)
 	{
 		ASSERT_IF_CALLED_WITH_PLAYER
 	}
-	vstd::copy_if(battleGetAllStacks(), std::back_inserter(ret), [=](const CStack *s) -> bool
-	{
+	
+	return battleGetStacksIf([=](const CStack * s){
 		const bool ownerMatches = (whose == MINE_AND_ENEMY)
 			|| (whose == ONLY_MINE && s->owner == player)
 			|| (whose == ONLY_ENEMY && s->owner != player);
 		const bool alivenessMatches = s->alive()  ||  !onlyAlive;
-		return ownerMatches && alivenessMatches;
+		return ownerMatches && alivenessMatches;		
 	});
-
-	return ret;
 }
 
 int CPlayerBattleCallback::battleGetSurrenderCost() const

+ 15 - 1
lib/CBattleCallback.h

@@ -31,6 +31,7 @@ namespace BattleSide
 }
 
 typedef std::vector<const CStack*> TStacks;
+typedef std::function<bool(const CStack *)> TStackFilter;
 
 class CBattleInfoEssentials;
 
@@ -168,7 +169,15 @@ public:
 	ETerrainType battleTerrainType() const;
 	BFieldType battleGetBattlefieldType() const;
 	std::vector<shared_ptr<const CObstacleInstance> > battleGetAllObstacles(boost::optional<BattlePerspective::BattlePerspective> perspective = boost::none) const; //returns all obstacles on the battlefield
-	TStacks battleGetAllStacks(bool includeTurrets = false) const; //returns all stacks, alive or dead or undead or mechanical :)
+    
+    /** @brief Main method for getting battle stacks
+     *
+     * @param predicate Functor that shall return true for desired stack
+     * @return filtered stacks
+     *
+     */                             	
+	TStacks battleGetStacksIf(TStackFilter predicate, bool includeTurrets = false) const;
+	
 	bool battleHasNativeStack(ui8 side) const;
 	int battleGetMoatDmg() const; //what dmg unit will suffer if ending turn in the moat
 	const CGTownInstance * battleGetDefendedTown() const; //returns defended town if current battle is a siege, nullptr instead
@@ -190,7 +199,12 @@ public:
 	si8 battleGetWallState(int partOfWall) const;
 
 	//helpers
+	///returns all stacks, alive or dead or undead or mechanical :)
+	TStacks battleGetAllStacks(bool includeTurrets = false) const;
+	
+	///returns all alive stacks excluding turrets
 	TStacks battleAliveStacks() const;
+	///returns all alive stacks from particular side excluding turrets
 	TStacks battleAliveStacks(ui8 side) const;
 	const CStack * battleGetStackByID(int ID, bool onlyAlive = true) const; //returns stack info by given ID
 	bool battleIsObstacleVisibleForSide(const CObstacleInstance & coi, BattlePerspective::BattlePerspective side) const;

+ 7 - 0
lib/CSpellHandler.cpp

@@ -287,6 +287,7 @@ const CSpell::TargetInfo CSpell::getTargetInfo(const int level) const
 	info.type = getTargetType();
 	info.smart = levelInfo.smartTarget;
 	info.massive = levelInfo.range == "X";
+	info.onlyAlive = !isRisingSpell();
 
 	return info;
 }
@@ -317,6 +318,12 @@ bool CSpell::isNegative() const
 	return positiveness == NEGATIVE;
 }
 
+bool CSpell::isNeutral() const
+{
+	return positiveness == NEUTRAL;
+}
+
+
 bool CSpell::isRisingSpell() const
 {
 	return isRising;

+ 2 - 0
lib/CSpellHandler.h

@@ -60,6 +60,7 @@ public:
 		ETargetType type;
 		bool smart;
 		bool massive;
+		bool onlyAlive;
 	};
 
 	SpellID id;
@@ -96,6 +97,7 @@ public:
 
 	bool isPositive() const;
 	bool isNegative() const;
+	bool isNeutral() const;
 
 	bool isRisingSpell() const;
 	bool isDamageSpell() const;

+ 28 - 24
server/CGameHandler.cpp

@@ -3944,7 +3944,7 @@ void CGameHandler::playerMessage( PlayerColor player, const std::string &message
 void CGameHandler::handleSpellCasting( SpellID spellID, int spellLvl, BattleHex destination, ui8 casterSide, PlayerColor casterColor, const CGHeroInstance * caster, const CGHeroInstance * secHero,
 	int usedSpellPower, ECastingMode::ECastingMode mode, const CStack * stack, si32 selectedStack)
 {
-	const CSpell *spell = SpellID(spellID).toSpell();
+	const CSpell * spell = SpellID(spellID).toSpell();
 
 
 	//Helper local function that creates obstacle on given position. Obstacle type is inferred from spell type.
@@ -4008,7 +4008,7 @@ void CGameHandler::handleSpellCasting( SpellID spellID, int spellLvl, BattleHex
 
 	if (caster) //calculate spell cost
 	{
-		sc.spellCost = gs->curB->battleGetSpellCost(SpellID(spellID).toSpell(), caster);
+		sc.spellCost = gs->curB->battleGetSpellCost(spell, caster);
 
 		if (secHero && mode == ECastingMode::HERO_CASTING) //handle mana channel
 		{
@@ -4026,7 +4026,8 @@ void CGameHandler::handleSpellCasting( SpellID spellID, int spellLvl, BattleHex
 
 	//calculating affected creatures for all spells
 	//must be vector, as in Chain Lightning order matters
-	std::vector<const CStack*> attackedCres; //what is that and what is sc.afectedCres?
+	std::vector<const CStack*> attackedCres; //CStack vector is somewhat more suitable than ID vector
+
 	if (mode != ECastingMode::ENCHANTER_CASTING)
 	{
 		auto creatures = gs->curB->getAffectedCreatures(spell, spellLvl, casterColor, destination);
@@ -4048,13 +4049,29 @@ void CGameHandler::handleSpellCasting( SpellID spellID, int spellLvl, BattleHex
 		}
 	}
 
+	vstd::erase_if(attackedCres,[=](const CStack * s){
+		return gs->curB->battleIsImmune(caster,spell,mode,s->position);		
+	});
+
 	for (auto cre : attackedCres)
 	{
 		sc.affectedCres.insert (cre->ID);
 	}
-
+	
 	//checking if creatures resist
-	sc.resisted = gs->curB->calculateResistedStacks(spell, caster, secHero, attackedCres, casterColor, mode, usedSpellPower, spellLvl, gs->getRandomGenerator());
+	//resistance is applied only to negative spells
+	if(spell->isNegative())
+	{
+		for(auto s : attackedCres)
+		{
+			const int prob = std::min((s)->magicResistance(), 100); //probability of resistance in %
+			
+			if(gs->getRandomGenerator().nextInt(99) < prob)
+			{
+				sc.resisted.push_back(s->ID);
+			}
+		}
+	}
 
 	//calculating dmg to display
 	if (spellID == SpellID::DEATH_STARE || spellID == SpellID::ACID_BREATH_DAMAGE)
@@ -4206,18 +4223,14 @@ void CGameHandler::handleSpellCasting( SpellID spellID, int spellLvl, BattleHex
 				int unitSpellPower = stack->valOfBonuses(Bonus::SPECIFIC_SPELL_POWER, spellID.toEnum());
 				if (unitSpellPower)
 					hpGained = stack->count * unitSpellPower; //Archangel
-				else //Faerie Dragon-like effect - unused fo far
+				else //Faerie Dragon-like effect - unused so far
 					usedSpellPower = stack->valOfBonuses(Bonus::CREATURE_SPELL_POWER) * stack->count / 100;
 			}
 			StacksHealedOrResurrected shr;
-			shr.lifeDrain = (ui8)false;
-			shr.tentHealing = (ui8)false;
+			shr.lifeDrain = false;
+			shr.tentHealing = false;
 			for(auto & attackedCre : attackedCres)
 			{
-				if(vstd::contains(sc.resisted, (attackedCre)->ID) //this creature resisted the spell
-					|| (spellID == SpellID::ANIMATE_DEAD && !(attackedCre)->hasBonusOfType(Bonus::UNDEAD)) //we try to cast animate dead on living stack, TODO: showuld be not affected earlier
-					)
-					continue;
 				StacksHealedOrResurrected::HealInfo hi;
 				hi.stackID = (attackedCre)->ID;
 				if (stack) //casted by creature
@@ -4394,12 +4407,6 @@ void CGameHandler::handleSpellCasting( SpellID spellID, int spellLvl, BattleHex
 		{
 			for(auto & attackedCre : attackedCres)
 			{
-				if((attackedCre)->hasBonusOfType(Bonus::UNDEAD) || (attackedCre)->hasBonusOfType(Bonus::NON_LIVING)) //this creature is immune
-				{
-					sc.dmgToDisplay = 0; //TODO: handle Death Stare for multiple targets (?)
-					continue;
-				}
-
 				BattleStackAttacked bsa;
 				bsa.flags |= BattleStackAttacked::EFFECT;
 				bsa.effect = spell->mainEffectAnim; //from config\spell-Info.txt
@@ -5983,12 +5990,9 @@ void CGameHandler::runBattle()
 
 			if(next->getCreature()->idNumber == CreatureID::FIRST_AID_TENT)
 			{
-				std::vector< const CStack * > possibleStacks;
-
-				//is there any clean algorithm for that? (boost.range seems to lack copy_if) -> remove_copy_if?
-				for(const CStack *s : battleGetAllStacks())
-					if(s->owner == next->owner  &&  s->canBeHealed())
-						possibleStacks.push_back(s);
+				TStacks possibleStacks = battleGetStacksIf([&](const CStack * s){
+					return s->owner == next->owner  &&  s->canBeHealed();
+				});
 
 				if(!possibleStacks.size())
 				{