Browse Source

* support for SPELL_AFTER_ATTACK, including most of spell 77 support

mateuszb 15 years ago
parent
commit
5b0d646ddb

+ 1 - 1
CCallback.cpp

@@ -132,7 +132,7 @@ int CCallback::estimateSpellDamage(const CSpell * sp) const
 		return 0;
 
 	const CGHeroInstance * ourHero = gs->curB->heroes[0]->tempOwner == player ? gs->curB->heroes[0] : gs->curB->heroes[1];
-	return gs->curB->calculateSpellDmg(sp, ourHero, NULL);
+	return gs->curB->calculateSpellDmg(sp, ourHero, NULL, ourHero->getSpellSchoolLevel(sp));
 }
 
 void CCallback::getThievesGuildInfo(SThievesGuildInfo & thi, const CGObjectInstance * obj)

+ 3 - 1
client/CBattleInterface.cpp

@@ -2563,7 +2563,8 @@ void CBattleInterface::spellCast(SpellCast * sc)
 {
 	CSpell &spell = CGI->spellh->spells[sc->id];
 
-	if(sc->side == !curInt->cb->battleGetStackByID(activeStack)->attackerOwned)
+
+	if(sc->castedByHero && sc->side == !curInt->cb->battleGetStackByID(activeStack)->attackerOwned)
 		bSpell->block(true);
 
 	std::vector< std::string > anims; //for magic arrow and ice bolt
@@ -2621,6 +2622,7 @@ void CBattleInterface::spellCast(SpellCast * sc)
 			break; //for 15 and 16 cases
 		}
 	case 17: //lightning bolt
+	case 77: //thunderbolt
 		displayEffect(1, sc->tile);
 		displayEffect(spell.mainEffectAnim, sc->tile);
 		break;

+ 28 - 0
client/CPreGame.cpp

@@ -2272,6 +2272,34 @@ void CBonusSelection::show( SDL_Surface * to )
 	CIntObject::show(to);
 }
 
+void CBonusSelection::updateBonusSelection()
+{
+	//graphics:
+	//spell - SPELLBON.DEF
+	//monster - TWCRPORT.DEF
+	//building - ?
+	//artifact - ARTIFBON.DEF
+	//spell scroll - SPELLBON.DEF
+	//prim skill - PSKILBON.DEF
+	//sec skill - SSKILBON.DEF
+	//resource - BORES.DEF
+	//player - ?
+	//hero -?
+
+	bonuses = new CHighlightableButtonsGroup(0);
+	{
+		static const char *bonDefs[] = {"SPELLBON.DEF", "TWCRPORT.DEF", "GSPBUT5.DEF", "ARTIFBON.DEF", "SPELLBON.DEF",
+			"PSKILBON.DEF", "SSKILBON.DEF", "BORES.DEF", "GSPBUT5.DEF", "GSPBUT5.DEF"};
+
+		for(int i = 0; i < 5; i++)
+		{
+			bonuses->addButton(new CHighlightableButton("", "", 0, 110 + i*32, 450, bonDefs[i], i));
+			bonuses->buttons.back()->pos += Point(68, 0);
+		}
+	}
+
+}
+
 CBonusSelection::CRegion::CRegion( CBonusSelection * _owner, bool _accessible, bool _selectable, int _myNumber )
 : owner(_owner), accessible(_accessible), selectable(_selectable), myNumber(_myNumber)
 {

+ 6 - 0
client/CPreGame.h

@@ -301,6 +301,12 @@ class CBonusSelection : public CIntObject
 	CMapHeader *ourHeader;
 	CDefHandler *sizes; //icons of map sizes
 	int whichMap;
+
+	//bonus selection
+	void updateBonusSelection();
+	void selectBonus(int id);
+	CHighlightableButtonsGroup * bonuses;
+
 public:
 	StartInfo sInfo;
 	void selectMap(int whichOne);

+ 2 - 2
config/cr_abils.txt

@@ -95,8 +95,8 @@
 +  72 RETURN_AFTER_STRIKE 0 0 0	   	//Harpies return after attack
 +  73 BLOCKS_RETALIATION 0 0 0	   	//Harpy Hags
 +  73 RETURN_AFTER_STRIKE 0 0 0	   	//Harpy Hags return after attack
-+  76 SPELL_AFTER_ATTACK 0 70 220  	//medusas
-+  77 SPELL_AFTER_ATTACK 0 70 220  	//medusa queens
++  76 SPELL_AFTER_ATTACK 0 70 2020  	//medusas
++  77 SPELL_AFTER_ATTACK 0 70 2020  	//medusa queens
 +  78 SELF_MORALE 0 0 0	   	  	   	//minotaurs
 +  79 SELF_MORALE 0 0 0			   	//minotaur kings
 +  81 SPELL_AFTER_ATTACK 0 74 20   	//scorpicore

+ 1 - 0
config/spell_info.txt

@@ -70,4 +70,5 @@
 67 0 -1 X X X X
 68 0 -1 X X X X
 69 0 -1 X X X X
+77 -1 38 0 0 0 0
 -1

+ 38 - 25
lib/CGameState.cpp

@@ -2668,9 +2668,9 @@ si8 CGameState::battleMaxSpellLevel()
 	return levelLimit;
 }
 
-std::set<CStack*> BattleInfo::getAttackedCreatures(const CSpell * s, const CGHeroInstance * caster, int destinationTile)
+std::set<CStack*> BattleInfo::getAttackedCreatures( const CSpell * s, int skillLevel, ui8 attackerOwner, int destinationTile )
 {
-	std::set<ui16> attackedHexes = s->rangeInHexes(destinationTile, caster->getSpellSchoolLevel(s));
+	std::set<ui16> attackedHexes = s->rangeInHexes(destinationTile, skillLevel);
 	std::set<CStack*> attackedCres; /*std::set to exclude multiple occurrences of two hex creatures*/
 
 	bool onlyAlive = s->id != 38 && s->id != 39; //when casting resurrection or animate dead we should be allow to select dead stack
@@ -2692,7 +2692,7 @@ std::set<CStack*> BattleInfo::getAttackedCreatures(const CSpell * s, const CGHer
 	else if(VLC->spellh->spells[s->id].attributes.find("CREATURE_TARGET_1") != std::string::npos
 		|| VLC->spellh->spells[s->id].attributes.find("CREATURE_TARGET_2") != std::string::npos) //spell to be cast on a specific creature but massive on expert
 	{
-		if(caster->getSpellSchoolLevel(s) < 3)  /*not expert */
+		if(skillLevel < 3)  /*not expert */
 		{
 			CStack * st = getStackT(destinationTile, onlyAlive);
 			if(st)
@@ -2703,8 +2703,8 @@ std::set<CStack*> BattleInfo::getAttackedCreatures(const CSpell * s, const CGHer
 			for(int it=0; it<stacks.size(); ++it)
 			{
 				/*if it's non negative spell and our unit or non positive spell and hostile unit */
-				if((VLC->spellh->spells[s->id].positiveness >= 0 && stacks[it]->owner == caster->tempOwner)
-					||(VLC->spellh->spells[s->id].positiveness <= 0 && stacks[it]->owner != caster->tempOwner )
+				if((VLC->spellh->spells[s->id].positiveness >= 0 && stacks[it]->owner == attackerOwner)
+					||(VLC->spellh->spells[s->id].positiveness <= 0 && stacks[it]->owner != attackerOwner )
 					)
 				{
 					if(!onlyAlive || stacks[it]->alive())
@@ -2794,17 +2794,23 @@ ui32 BattleInfo::getSpellCost(const CSpell * sp, const CGHeroInstance * caster)
 {
 	ui32 ret = VLC->spellh->spells[sp->id].costs[caster->getSpellSchoolLevel(sp)];
 
-	//checking for friendly stacks reducing cost of the spell
+	//checking for friendly stacks reducing cost of the spell and
+	//enemy stacks increasing it
 	si32 manaReduction = 0;
+	si32 manaIncrease = 0;
 	for(int g=0; g<stacks.size(); ++g)
 	{
 		if( stacks[g]->owner == caster->tempOwner && stacks[g]->hasFeatureOfType(StackFeature::CHANGES_SPELL_COST_FOR_ALLY) )
 		{
 			amin(manaReduction, stacks[g]->valOfFeatures(StackFeature::CHANGES_SPELL_COST_FOR_ALLY));
 		}
+		if( stacks[g]->owner != caster->tempOwner && stacks[g]->hasFeatureOfType(StackFeature::CHANGES_SPELL_COST_FOR_ENEMY) )
+		{
+			amax(manaIncrease, stacks[g]->valOfFeatures(StackFeature::CHANGES_SPELL_COST_FOR_ENEMY));
+		}
 	}
 
-	return ret + manaReduction;
+	return ret + manaReduction + manaIncrease;
 }
 
 int BattleInfo::hexToWallPart(int hex) const
@@ -2875,47 +2881,54 @@ std::pair<const CStack *, int> BattleInfo::getNearestStack(const CStack * closes
 	return std::make_pair<const CStack * , int>(NULL, -1);
 }
 
-ui32 BattleInfo::calculateSpellDmg(const CSpell * sp, const CGHeroInstance * caster, const CStack * affectedCreature) const
+ui32 BattleInfo::calculateSpellDmg( const CSpell * sp, const CGHeroInstance * caster, const CStack * affectedCreature, int spellSchoolLevel ) const
 {
 	ui32 ret = 0; //value to return
 
 	//15 - magic arrows, 16 - ice bolt, 17 - lightning bolt, 18 - implosion, 20 - frost ring, 21 - fireball, 22 - inferno, 23 - meteor shower,
-	//24 - death ripple, 25 - destroy undead, 26 - armageddon
-	static std::map <int, int> dmgMultipliers = boost::assign::map_list_of(15, 10)(16, 20)(17, 25)(18, 75)(20, 10)(21, 10)(22, 10)(23, 10)(24, 5)(25, 10)(26, 50);
+	//24 - death ripple, 25 - destroy undead, 26 - armageddon, 77 - thunderbolt
+	static std::map <int, int> dmgMultipliers = boost::assign::map_list_of(15, 10)(16, 20)(17, 25)(18, 75)(20, 10)(21, 10)(22, 10)(23, 10)(24, 5)(25, 10)(26, 50)(77, 10);
 
 	//check if spell really does damage - if not, return 0
 	if(dmgMultipliers.find(sp->id) == dmgMultipliers.end())
 		return 0;
 
-	ret = caster->getPrimSkillLevel(2) * dmgMultipliers[sp->id]  +  sp->powers[caster->getSpellSchoolLevel(sp)];
+	if (caster)
+	{
+		ret = caster->getPrimSkillLevel(2) * dmgMultipliers[sp->id];
+	} 
+	ret += sp->powers[spellSchoolLevel];
 	
 	//applying sorcerery secondary skill
-	switch(caster->getSecSkillLevel(25))
+	if(caster)
 	{
-	case 1: //basic
-		ret *= 1.05f;
-		break;
-	case 2: //advanced
-		ret *= 1.1f;
-		break;
-	case 3: //expert
-		ret *= 1.15f;
-		break;
+		switch(caster->getSecSkillLevel(25))
+		{
+		case 1: //basic
+			ret *= 1.05f;
+			break;
+		case 2: //advanced
+			ret *= 1.1f;
+			break;
+		case 3: //expert
+			ret *= 1.15f;
+			break;
+		}
 	}
 	//applying hero bonuses
-	if(sp->air && caster->valOfBonuses(HeroBonus::AIR_SPELL_DMG_PREMY) != 0)
+	if(sp->air && caster && caster->valOfBonuses(HeroBonus::AIR_SPELL_DMG_PREMY) != 0)
 	{
 		ret *= (100.0f + caster->valOfBonuses(HeroBonus::AIR_SPELL_DMG_PREMY)) / 100.0f;
 	}
-	else if(sp->fire && caster->valOfBonuses(HeroBonus::FIRE_SPELL_DMG_PREMY) != 0)
+	else if(sp->fire && caster && caster->valOfBonuses(HeroBonus::FIRE_SPELL_DMG_PREMY) != 0)
 	{
 		ret *= (100.0f + caster->valOfBonuses(HeroBonus::FIRE_SPELL_DMG_PREMY)) / 100.0f;
 	}
-	else if(sp->water && caster->valOfBonuses(HeroBonus::WATER_SPELL_DMG_PREMY) != 0)
+	else if(sp->water && caster && caster->valOfBonuses(HeroBonus::WATER_SPELL_DMG_PREMY) != 0)
 	{
 		ret *= (100.0f + caster->valOfBonuses(HeroBonus::WATER_SPELL_DMG_PREMY)) / 100.0f;
 	}
-	else if(sp->earth && caster->valOfBonuses(HeroBonus::EARTH_SPELL_DMG_PREMY) != 0)
+	else if(sp->earth && caster && caster->valOfBonuses(HeroBonus::EARTH_SPELL_DMG_PREMY) != 0)
 	{
 		ret *= (100.0f + caster->valOfBonuses(HeroBonus::EARTH_SPELL_DMG_PREMY)) / 100.0f;
 	}

+ 3 - 2
lib/CGameState.h

@@ -223,13 +223,13 @@ struct DLL_EXPORT BattleInfo
 	static ui32 calculateDmg(const CStack* attacker, const CStack* defender, const CGHeroInstance * attackerHero, const CGHeroInstance * defendingHero, bool shooting, ui8 charge); //charge - number of hexes travelled before attack (for champion's jousting)
 	static std::pair<ui32, ui32> calculateDmgRange(const CStack* attacker, const CStack* defender, const CGHeroInstance * attackerHero, const CGHeroInstance * defendingHero, bool shooting, ui8 charge); //charge - number of hexes travelled before attack (for champion's jousting); returns pair <min dmg, max dmg>
 	void calculateCasualties(std::map<ui32,si32> *casualties) const; //casualties are array of maps size 2 (attacker, defeneder), maps are (crid => amount)
-	std::set<CStack*> getAttackedCreatures(const CSpell * s, const CGHeroInstance * caster, int destinationTile); //calculates stack affected by given spell
+	std::set<CStack*> getAttackedCreatures(const CSpell * s, int skillLevel, ui8 attackerOwner, int destinationTile); //calculates stack affected by given spell
 	static int calculateSpellDuration(const CSpell * spell, const CGHeroInstance * caster);
 	CStack * generateNewStack(const CGHeroInstance * owner, int creatureID, int amount, int stackID, bool attackerOwned, int slot, int /*TerrainTile::EterrainType*/ terrain, int position) const; //helper for CGameHandler::setupBattle and spells addign new stacks to the battlefield
 	ui32 getSpellCost(const CSpell * sp, const CGHeroInstance * caster) const; //returns cost of given spell
 	int hexToWallPart(int hex) const; //returns part of destructible wall / gate / keep under given hex or -1 if not found
 	std::pair<const CStack *, int> getNearestStack(const CStack * closest, boost::logic::tribool attackerOwned) const; //if attackerOwned is indetermnate, returened stack is of any owner; hex is the number of hex we should be looking from; returns (nerarest creature, predecessorHex)
-	ui32 calculateSpellDmg(const CSpell * sp, const CGHeroInstance * caster, const CStack * affectedCreature) const; //calculates damage inflicted by spell
+	ui32 calculateSpellDmg(const CSpell * sp, const CGHeroInstance * caster, const CStack * affectedCreature, int spellSchoolLevel) const; //calculates damage inflicted by spell
 };
 
 class DLL_EXPORT CStack
@@ -496,3 +496,4 @@ public:
 
 
 #endif // __CGAMESTATE_H__
+ 

+ 2 - 1
lib/NetPacks.h

@@ -998,9 +998,10 @@ struct SpellCast : public CPackForClient//3009
 	ui16 tile; //destination tile (may not be set in some global/mass spells
 	std::vector<ui32> resisted; //ids of creatures that resisted this spell
 	std::set<ui32> affectedCres; //ids of creatures affected by this spell, generally used if spell does not set any effect (like dispel or cure)
+	ui8 castedByHero; //if true - spell has been casted by hero, otherwise by a creature
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
-		h & dmgToDisplay & side & id & skill & tile & resisted & affectedCres;
+		h & dmgToDisplay & side & id & skill & tile & resisted & affectedCres & castedByHero;
 	}
 };
 

+ 1 - 1
lib/StackFeature.h

@@ -21,7 +21,7 @@ struct StackFeature
 	VCMI_CREATURE_ABILITY_NAME(MAGIC_RESISTANCE) /*in % (value)*/		\
 	VCMI_CREATURE_ABILITY_NAME(CHANGES_SPELL_COST_FOR_ALLY) /*in mana points (value) , eg. mage*/ \
 	VCMI_CREATURE_ABILITY_NAME(CHANGES_SPELL_COST_FOR_ENEMY) /*in mana points (value) , eg. pegasus */ \
-	VCMI_CREATURE_ABILITY_NAME(SPELL_AFTER_ATTACK) /* subtype - spell id, value - spell level, (aditional info)%100 - chance in %; eg. dendroids, (additional info)/100 -> [0 - all attacks, 1 - shot only, 2 - melee only*/ \
+	VCMI_CREATURE_ABILITY_NAME(SPELL_AFTER_ATTACK) /* subtype - spell id, value - spell level, (additional info)%1000 - chance in %; eg. dendroids, (additional info)/1000 -> [0 - all attacks, 1 - shot only, 2 - melee only*/ \
 	VCMI_CREATURE_ABILITY_NAME(SPELL_RESISTANCE_AURA) /*eg. unicorns, value - resistance bonus in % for adjacent creatures*/ \
 	VCMI_CREATURE_ABILITY_NAME(LEVEL_SPELL_IMMUNITY) /*creature is immune to all spell with level below or equal to value of this bonus*/ \
 	VCMI_CREATURE_ABILITY_NAME(TWO_HEX_ATTACK_BREATH) /*eg. dragons*/	\

+ 201 - 145
server/CGameHandler.cpp

@@ -2926,6 +2926,7 @@ bool CGameHandler::makeBattleAction( BattleAction &ba )
 			BattleAttack bat;
 			prepareAttack(bat, curStack, stackAtEnd, distance);
 			sendAndApply(&bat);
+			handleAfterAttackCasting(bat);
 
 			//counterattack
 			if(!curStack->hasFeatureOfType(StackFeature::BLOCKS_RETALIATION)
@@ -2937,6 +2938,7 @@ bool CGameHandler::makeBattleAction( BattleAction &ba )
 				prepareAttack(bat, stackAtEnd, curStack, 0);
 				bat.flags |= 2;
 				sendAndApply(&bat);
+				handleAfterAttackCasting(bat);
 			}
 
 			//second attack
@@ -2948,6 +2950,7 @@ bool CGameHandler::makeBattleAction( BattleAction &ba )
 				bat.flags = 0;
 				prepareAttack(bat, curStack, stackAtEnd, 0);
 				sendAndApply(&bat);
+				handleAfterAttackCasting(bat);
 			}
 
 			//return
@@ -2981,6 +2984,7 @@ bool CGameHandler::makeBattleAction( BattleAction &ba )
 			{
 				prepareAttack(bat, curStack, destStack, 0);
 				sendAndApply(&bat);
+				handleAfterAttackCasting(bat);
 			}
 
 			sendAndApply(&EndAction());
@@ -3246,7 +3250,8 @@ static std::vector<ui32> calculateResistedStacks(const CSpell * sp, const CGHero
 	for(std::set<CStack*>::const_iterator it = affectedCreatures.begin(); it != affectedCreatures.end(); ++it)
 	{
 		if ((*it)->hasFeatureOfType(StackFeature::SPELL_IMMUNITY, sp->id) //100% sure spell immunity
-			|| ((*it)->valOfFeatures(StackFeature::LEVEL_SPELL_IMMUNITY) >= sp->level))
+			|| ( (*it)->hasFeatureOfType(StackFeature::LEVEL_SPELL_IMMUNITY) &&
+				(*it)->valOfFeatures(StackFeature::LEVEL_SPELL_IMMUNITY) >= sp->level) ) //some creature abilities have level 0
 		{
 			ret.push_back((*it)->ID);
 			continue;
@@ -3257,7 +3262,7 @@ static std::vector<ui32> calculateResistedStacks(const CSpell * sp, const CGHero
 			continue;
 
 		const CGHeroInstance * bonusHero; //hero we should take bonuses from
-		if((*it)->owner == caster->tempOwner)
+		if(caster && (*it)->owner == caster->tempOwner)
 			bonusHero = caster;
 		else
 			bonusHero = hero2;
@@ -3307,6 +3312,159 @@ static std::vector<ui32> calculateResistedStacks(const CSpell * sp, const CGHero
 	return ret;
 }
 
+void CGameHandler::handleSpellCasting( int spellID, int spellLvl, int destination, ui8 casterSide, ui8 casterColor,
+	const CGHeroInstance * caster, const CGHeroInstance * secHero )
+{
+	CSpell *spell = &VLC->spellh->spells[spellID];
+
+	SpellCast sc;
+	sc.side = casterSide;
+	sc.id = spellID;
+	sc.skill = spellLvl;
+	sc.tile = destination;
+	sc.dmgToDisplay = 0;
+	sc.castedByHero = (bool)caster;
+
+	//calculating affected creatures for all spells
+	std::set<CStack*> attackedCres = gs->curB->getAttackedCreatures(spell, spellLvl, casterColor, destination);
+	for(std::set<CStack*>::const_iterator it = attackedCres.begin(); it != attackedCres.end(); ++it)
+	{
+		sc.affectedCres.insert((*it)->ID);
+	}
+
+	//checking if creatures resist
+	sc.resisted = calculateResistedStacks(spell, caster, secHero, attackedCres);
+
+	//calculating dmg to display
+	for(std::set<CStack*>::iterator it = attackedCres.begin(); it != attackedCres.end(); ++it)
+	{
+		if(vstd::contains(sc.resisted, (*it)->ID)) //this creature resisted the spell
+			continue;
+		sc.dmgToDisplay += gs->curB->calculateSpellDmg(spell, caster, *it, spellLvl);
+	}
+
+	sendAndApply(&sc);
+
+	//applying effects
+	switch(spellID)
+	{
+	case 15: //magic arrow
+	case 16: //ice bolt
+	case 17: //lightning bolt
+	case 18: //implosion
+	case 20: //frost ring
+	case 21: //fireball
+	case 22: //inferno
+	case 23: //meteor shower
+	case 24: //death ripple
+	case 25: //destroy undead
+	case 26: //armageddon
+	case 77: //Thunderbolt (thunderbirds)
+		{
+			StacksInjured si;
+			for(std::set<CStack*>::iterator it = attackedCres.begin(); it != attackedCres.end(); ++it)
+			{
+				if(vstd::contains(sc.resisted, (*it)->ID)) //this creature resisted the spell
+					continue;
+
+				BattleStackAttacked bsa;
+				bsa.flags |= 2;
+				bsa.effect = spell->mainEffectAnim;
+				bsa.damageAmount = gs->curB->calculateSpellDmg(spell, caster, *it, spellLvl);
+				bsa.stackAttacked = (*it)->ID;
+				bsa.attackerID = -1;
+				prepareAttacked(bsa,*it);
+				si.stacks.push_back(bsa);
+			}
+			if(!si.stacks.empty())
+				sendAndApply(&si);
+			break;
+		}
+	case 27: //shield 
+	case 28: //air shield
+	case 30: //protection from air
+	case 31: //protection from fire
+	case 32: //protection from water
+	case 33: //protection from earth
+	case 34: //anti-magic
+	case 41: //bless
+	case 42: //curse
+	case 43: //bloodlust
+	case 44: //precision
+	case 45: //weakness
+	case 46: //stone skin
+	case 47: //disrupting ray
+	case 48: //prayer
+	case 49: //mirth
+	case 50: //sorrow
+	case 51: //fortune
+	case 52: //misfortune
+	case 53: //haste
+	case 54: //slow
+	case 55: //slayer
+	case 56: //frenzy
+	case 58: //counterstrike
+	case 59: //berserk
+	case 60: //hypnotize
+	case 61: //forgetfulness
+	case 62: //blind
+		{
+			SetStackEffect sse;
+			for(std::set<CStack*>::iterator it = attackedCres.begin(); it != attackedCres.end(); ++it)
+			{
+				if(vstd::contains(sc.resisted, (*it)->ID)) //this creature resisted the spell
+					continue;
+				sse.stacks.push_back((*it)->ID);
+			}
+			sse.effect.id = spellID;
+			sse.effect.level = spellLvl;
+			sse.effect.turnsRemain = BattleInfo::calculateSpellDuration(spell, caster);
+			if(!sse.stacks.empty())
+				sendAndApply(&sse);
+			break;
+		}
+	case 37: //cure
+	case 38: //resurrection
+	case 39: //animate dead
+		{
+			StacksHealedOrResurrected shr;
+			for(std::set<CStack*>::iterator it = attackedCres.begin(); it != attackedCres.end(); ++it)
+			{
+				if(vstd::contains(sc.resisted, (*it)->ID) //this creature resisted the spell
+					|| (spellID == 39 && !(*it)->hasFeatureOfType(StackFeature::UNDEAD)) //we try to cast animate dead on living stack
+					) 
+					continue;
+				StacksHealedOrResurrected::HealInfo hi;
+				hi.stackID = (*it)->ID;
+				hi.healedHP = calculateHealedHP(caster, spell, *it);
+				hi.lowLevelResurrection = spellLvl <= 1;
+				shr.healedStacks.push_back(hi);
+			}
+			if(!shr.healedStacks.empty())
+				sendAndApply(&shr);
+			break;
+		}
+	case 64: //remove obstacle
+		{
+			ObstaclesRemoved obr;
+			for(int g=0; g<gs->curB->obstacles.size(); ++g)
+			{
+				std::vector<int> blockedHexes = VLC->heroh->obstacles[gs->curB->obstacles[g].ID].getBlocked(gs->curB->obstacles[g].pos);
+
+				if(vstd::contains(blockedHexes, destination)) //this obstacle covers given hex
+				{
+					obr.obstacles.insert(gs->curB->obstacles[g].uniqueID);
+				}
+			}
+			if(!obr.obstacles.empty())
+				sendAndApply(&obr);
+
+			break;
+		}
+	}
+
+}
+
 bool CGameHandler::makeCustomAction( BattleAction &ba )
 {
 	switch(ba.actionType)
@@ -3343,149 +3501,8 @@ bool CGameHandler::makeCustomAction( BattleAction &ba )
 
 			sendAndApply(&StartAction(ba)); //start spell casting
 
-			SpellCast sc;
-			sc.side = ba.side;
-			sc.id = ba.additionalInfo;
-			sc.skill = skill;
-			sc.tile = ba.destinationTile;
-			sc.dmgToDisplay = 0;
+			handleSpellCasting(ba.additionalInfo, skill, ba.destinationTile, ba.side, h->tempOwner, h, secondHero);
 
-			//calculating affected creatures for all spells
-			std::set<CStack*> attackedCres = gs->curB->getAttackedCreatures(s, h, ba.destinationTile);
-			for(std::set<CStack*>::const_iterator it = attackedCres.begin(); it != attackedCres.end(); ++it)
-			{
-				sc.affectedCres.insert((*it)->ID);
-			}
-
-			//checking if creatures resist
-			sc.resisted = calculateResistedStacks(s, h, secondHero, attackedCres);
-
-			//calculating dmg to display
-			for(std::set<CStack*>::iterator it = attackedCres.begin(); it != attackedCres.end(); ++it)
-			{
-				if(vstd::contains(sc.resisted, (*it)->ID)) //this creature resisted the spell
-					continue;
-				sc.dmgToDisplay += gs->curB->calculateSpellDmg(s, h, *it);
-			}
-			
-			sendAndApply(&sc);
-
-			//applying effects
-			switch(ba.additionalInfo) //spell id
-			{
-			case 15: //magic arrow
-			case 16: //ice bolt
-			case 17: //lightning bolt
-			case 18: //implosion
-			case 20: //frost ring
-			case 21: //fireball
-			case 22: //inferno
-			case 23: //meteor shower
-			case 24: //death ripple
-			case 25: //destroy undead
-			case 26: //armageddon
-				{
-					StacksInjured si;
-					for(std::set<CStack*>::iterator it = attackedCres.begin(); it != attackedCres.end(); ++it)
-					{
-						if(vstd::contains(sc.resisted, (*it)->ID)) //this creature resisted the spell
-							continue;
-
-						BattleStackAttacked bsa;
-						bsa.flags |= 2;
-						bsa.effect = VLC->spellh->spells[ba.additionalInfo].mainEffectAnim;
-						bsa.damageAmount = gs->curB->calculateSpellDmg(s, h, *it);
-						bsa.stackAttacked = (*it)->ID;
-						bsa.attackerID = -1;
-						prepareAttacked(bsa,*it);
-						si.stacks.push_back(bsa);
-					}
-					if(!si.stacks.empty())
-						sendAndApply(&si);
-					break;
-				}
-			case 27: //shield 
-			case 28: //air shield
-			case 30: //protection from air
-			case 31: //protection from fire
-			case 32: //protection from water
-			case 33: //protection from earth
-			case 34: //anti-magic
-			case 41: //bless
-			case 42: //curse
-			case 43: //bloodlust
-			case 44: //precision
-			case 45: //weakness
-			case 46: //stone skin
-			case 47: //disrupting ray
-			case 48: //prayer
-			case 49: //mirth
-			case 50: //sorrow
-			case 51: //fortune
-			case 52: //misfortune
-			case 53: //haste
-			case 54: //slow
-			case 55: //slayer
-			case 56: //frenzy
-			case 58: //counterstrike
-			case 59: //berserk
-			case 60: //hypnotize
-			case 61: //forgetfulness
-			case 62: //blind
-				{
-					SetStackEffect sse;
-					for(std::set<CStack*>::iterator it = attackedCres.begin(); it != attackedCres.end(); ++it)
-					{
-						if(vstd::contains(sc.resisted, (*it)->ID)) //this creature resisted the spell
-							continue;
-						sse.stacks.push_back((*it)->ID);
-					}
-					sse.effect.id = ba.additionalInfo;
-					sse.effect.level = h->getSpellSchoolLevel(s);
-					sse.effect.turnsRemain = BattleInfo::calculateSpellDuration(s, h);
-					if(!sse.stacks.empty())
-						sendAndApply(&sse);
-					break;
-				}
-			case 37: //cure
-			case 38: //resurrection
-			case 39: //animate dead
-				{
-					StacksHealedOrResurrected shr;
-					for(std::set<CStack*>::iterator it = attackedCres.begin(); it != attackedCres.end(); ++it)
-					{
-						if(vstd::contains(sc.resisted, (*it)->ID) //this creature resisted the spell
-							|| (s->id == 39 && !(*it)->hasFeatureOfType(StackFeature::UNDEAD)) //we try to cast animate dead on living stack
-							) 
-							continue;
-						StacksHealedOrResurrected::HealInfo hi;
-						hi.stackID = (*it)->ID;
-						hi.healedHP = calculateHealedHP(h, s, *it);
-						hi.lowLevelResurrection = h->getSpellSchoolLevel(s) <= 1;
-						shr.healedStacks.push_back(hi);
-					}
-					if(!shr.healedStacks.empty())
-						sendAndApply(&shr);
-					break;
-				}
-			case 64: //remove obstacle
-				{
-					ObstaclesRemoved obr;
-					for(int g=0; g<gs->curB->obstacles.size(); ++g)
-					{
-						std::vector<int> blockedHexes = VLC->heroh->obstacles[gs->curB->obstacles[g].ID].getBlocked(gs->curB->obstacles[g].pos);
-
-						if(vstd::contains(blockedHexes, ba.destinationTile)) //this obstacle covers given hex
-						{
-							obr.obstacles.insert(gs->curB->obstacles[g].uniqueID);
-						}
-					}
-					if(!obr.obstacles.empty())
-						sendAndApply(&obr);
-
-					break;
-				}
-			}
 			sendAndApply(&EndAction());
 			if( !gs->curB->getStack(gs->curB->activeStack, false)->alive() )
 			{
@@ -3496,6 +3513,7 @@ bool CGameHandler::makeCustomAction( BattleAction &ba )
 			{
 				endBattle(gs->curB->tile, gs->curB->heroes[0], gs->curB->heroes[1]);
 			}
+
 			return true;
 		}
 	}
@@ -3904,4 +3922,42 @@ bool CGameHandler::dig( const CGHeroInstance *h )
 	no.subID = 0;
 	sendAndApply(&no);
 	return true;
-}
+}
+
+void CGameHandler::handleAfterAttackCasting( const BattleAttack & bat )
+{
+	const CStack * attacker = gs->curB->getStack(bat.stackAttacking);
+	if( attacker->hasFeatureOfType(StackFeature::SPELL_AFTER_ATTACK) )
+	{
+		for (int it=0; it<attacker->features.size(); ++it)
+		{
+			const StackFeature & sf = attacker->features[it];
+			if (sf.type == StackFeature::SPELL_AFTER_ATTACK)
+			{
+				const CStack * oneOfAttacked = NULL;
+				for(int g=0; g<bat.bsa.size(); ++g)
+				{
+					if (bat.bsa[g].newAmount > 0)
+					{
+						oneOfAttacked = gs->curB->getStack(bat.bsa[g].stackAttacked);
+						break;
+					}
+				}
+				if(oneOfAttacked == NULL) //all attacked creatures have been killed
+					return;
+
+				int spellID = sf.subtype;
+				int spellLevel = sf.value;
+				int chance = sf.additionalInfo % 1000;
+				int meleeRanged = sf.additionalInfo / 1000;
+				int destination = oneOfAttacked->position;
+				//check if spell should be casted (probability handling)
+				if( rand()%100 >= chance )
+					continue;
+
+				//casting
+				handleSpellCasting(spellID, spellLevel, destination, !attacker->attackerOwned, attacker->owner, NULL, NULL);
+			}
+		}
+	}
+}

+ 2 - 0
server/CGameHandler.h

@@ -157,6 +157,7 @@ public:
 
 	void playerMessage( ui8 player, const std::string &message);
 	bool makeBattleAction(BattleAction &ba);
+	void handleSpellCasting(int spellID, int spellLvl, int destination, ui8 casterSide, ui8 casterColor, const CGHeroInstance * caster, const CGHeroInstance * secHero);
 	bool makeCustomAction(BattleAction &ba);
 	bool queryReply( ui32 qid, ui32 answer );
 	bool hireHero( ui32 tid, ui8 hid );
@@ -200,6 +201,7 @@ public:
 
 	void run(bool resume);
 	void newTurn();
+	void handleAfterAttackCasting( const BattleAttack & bat );
 	friend class CVCMIServer;
 	friend class CScriptCallback;
 };