diff --git a/CCallback.cpp b/CCallback.cpp index 7c8581644..184726f35 100644 --- a/CCallback.cpp +++ b/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) diff --git a/client/CBattleInterface.cpp b/client/CBattleInterface.cpp index f4f1bd0ec..bae0a4992 100644 --- a/client/CBattleInterface.cpp +++ b/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; diff --git a/client/CPreGame.cpp b/client/CPreGame.cpp index 66a7cc5ec..947dd13cf 100644 --- a/client/CPreGame.cpp +++ b/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) { diff --git a/client/CPreGame.h b/client/CPreGame.h index ef71377f0..9c5389941 100644 --- a/client/CPreGame.h +++ b/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); diff --git a/config/cr_abils.txt b/config/cr_abils.txt index fba7cd38c..fb8b6107f 100644 --- a/config/cr_abils.txt +++ b/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 diff --git a/config/spell_info.txt b/config/spell_info.txt index 69e24dbe6..a9d2234d1 100644 --- a/config/spell_info.txt +++ b/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 \ No newline at end of file diff --git a/lib/CGameState.cpp b/lib/CGameState.cpp index 5b1f43d24..2563ab428 100644 --- a/lib/CGameState.cpp +++ b/lib/CGameState.cpp @@ -2668,9 +2668,9 @@ si8 CGameState::battleMaxSpellLevel() return levelLimit; } -std::set BattleInfo::getAttackedCreatures(const CSpell * s, const CGHeroInstance * caster, int destinationTile) +std::set BattleInfo::getAttackedCreatures( const CSpell * s, int skillLevel, ui8 attackerOwner, int destinationTile ) { - std::set attackedHexes = s->rangeInHexes(destinationTile, caster->getSpellSchoolLevel(s)); + std::set attackedHexes = s->rangeInHexes(destinationTile, skillLevel); std::set 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 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 BattleInfo::getAttackedCreatures(const CSpell * s, const CGHer for(int it=0; itspellh->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; gowner == 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 BattleInfo::getNearestStack(const CStack * closes return std::make_pair(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 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 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; } diff --git a/lib/CGameState.h b/lib/CGameState.h index 0cd3a2715..0046ac762 100644 --- a/lib/CGameState.h +++ b/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 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 void calculateCasualties(std::map *casualties) const; //casualties are array of maps size 2 (attacker, defeneder), maps are (crid => amount) - std::set getAttackedCreatures(const CSpell * s, const CGHeroInstance * caster, int destinationTile); //calculates stack affected by given spell + std::set 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 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__ + \ No newline at end of file diff --git a/lib/NetPacks.h b/lib/NetPacks.h index c7438699d..2eab5aeac 100644 --- a/lib/NetPacks.h +++ b/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 resisted; //ids of creatures that resisted this spell std::set 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 void serialize(Handler &h, const int version) { - h & dmgToDisplay & side & id & skill & tile & resisted & affectedCres; + h & dmgToDisplay & side & id & skill & tile & resisted & affectedCres & castedByHero; } }; diff --git a/lib/StackFeature.h b/lib/StackFeature.h index 495deea0f..cbbc9aee6 100644 --- a/lib/StackFeature.h +++ b/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*/ \ diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 9a310ff33..aa9c4cee1 100644 --- a/server/CGameHandler.cpp +++ b/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 calculateResistedStacks(const CSpell * sp, const CGHero for(std::set::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 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 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 attackedCres = gs->curB->getAttackedCreatures(spell, spellLvl, casterColor, destination); + for(std::set::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::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::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::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::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; gcurB->obstacles.size(); ++g) + { + std::vector 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 attackedCres = gs->curB->getAttackedCreatures(s, h, ba.destinationTile); - for(std::set::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::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::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::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::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; gcurB->obstacles.size(); ++g) - { - std::vector 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; -} \ No newline at end of file +} + +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; itfeatures.size(); ++it) + { + const StackFeature & sf = attacker->features[it]; + if (sf.type == StackFeature::SPELL_AFTER_ATTACK) + { + const CStack * oneOfAttacked = NULL; + for(int g=0; g 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); + } + } + } +} diff --git a/server/CGameHandler.h b/server/CGameHandler.h index c5de9e07e..8db3f6786 100644 --- a/server/CGameHandler.h +++ b/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; };