diff --git a/client/CBattleInterface.cpp b/client/CBattleInterface.cpp index 27ce9637d..9a748e1f0 100644 --- a/client/CBattleInterface.cpp +++ b/client/CBattleInterface.cpp @@ -2843,19 +2843,39 @@ void CBattleInterface::spellCast( const BattleSpellCast * sc ) } //displaying message in console + bool customSpell = false; if(sc->affectedCres.size() == 1) { std::string text = CGI->generaltexth->allTexts[195]; if(sc->castedByHero) { boost::algorithm::replace_first(text, "%s", curInt->cb->battleGetFightingHero(sc->side)->name); + boost::algorithm::replace_first(text, "%s", curInt->cb->battleGetStackByID(*sc->affectedCres.begin(), false)->getCreature()->namePl ); //target } else { - boost::algorithm::replace_first(text, "%s", "Creature"); //TODO: better fix + if (sc->id == 79) // Death Stare + { + customSpell = true; + if (sc->dmgToDisplay > 1) + { + text = CGI->generaltexth->allTexts[119]; //%d %s die under the terrible gaze of the %s. + boost::algorithm::replace_first(text, "%d", boost::lexical_cast(sc->dmgToDisplay)); + boost::algorithm::replace_first(text, "%s", curInt->cb->battleGetStackByID(*sc->affectedCres.begin(), false)->getCreature()->namePl ); + } + else + { + text = CGI->generaltexth->allTexts[118]; //One %s dies under the terrible gaze of the %s. + boost::algorithm::replace_first(text, "%s", curInt->cb->battleGetStackByID(*sc->affectedCres.begin())->type->nameSing); + } + boost::algorithm::replace_first(text, "%s", "Creatures"); //casting stack + } + else + boost::algorithm::replace_first(text, "%s", "Creature"); //TODO: better fix } - boost::algorithm::replace_first(text, "%s", CGI->spellh->spells[sc->id]->name); - boost::algorithm::replace_first(text, "%s", curInt->cb->battleGetStackByID(*sc->affectedCres.begin(), false)->getCreature()->namePl ); + if (!customSpell) + boost::algorithm::replace_first(text, "%s", CGI->spellh->spells[sc->id]->name); + console->addText(text); } else @@ -2872,7 +2892,7 @@ void CBattleInterface::spellCast( const BattleSpellCast * sc ) boost::algorithm::replace_first(text, "%s", CGI->spellh->spells[sc->id]->name); console->addText(text); } - if(sc->dmgToDisplay != 0) + if(sc->dmgToDisplay && !customSpell) { std::string dmgInfo = CGI->generaltexth->allTexts[343].substr(1, CGI->generaltexth->allTexts[343].size() - 1); boost::algorithm::replace_first(dmgInfo, "%d", boost::lexical_cast(sc->dmgToDisplay)); diff --git a/config/cr_abils.txt b/config/cr_abils.txt index 7da950674..46801a5d2 100644 --- a/config/cr_abils.txt +++ b/config/cr_abils.txt @@ -115,7 +115,7 @@ + 93 SPELL_AFTER_ATTACK 0 77 20 //thunderbirds + 96 ENEMY_DEFENCE_REDUCTION 40 0 0 //behemots + 97 ENEMY_DEFENCE_REDUCTION 80 0 0 //ancient behemots -+ 103 SPELL_AFTER_ATTACK 0 79 10 //mighty gorgons ++ 103 DEATH_STARE 10 0 0 //mighty gorgons + 104 SPELL_AFTER_ATTACK 0 78 100 //serpent fly + 105 SPELL_AFTER_ATTACK 0 45 100 //mighty gorgons + 105 SPELL_AFTER_ATTACK 0 78 100 //dragon fly diff --git a/config/sp_sounds.txt b/config/sp_sounds.txt index b039010a3..337f389d3 100644 --- a/config/sp_sounds.txt +++ b/config/sp_sounds.txt @@ -18,6 +18,7 @@ 31 PROTECTF.wav # protection from fire 32 PROTECTW.wav # protection from water 33 PROTECTE.wav # protection from earth +35 DISPELL.wav # dispell 41 BLESS.wav # bless 42 CURSE.wav # curse 43 BLOODLUS.wav # bloodlust @@ -35,7 +36,7 @@ 55 SLAYER.wav # slayer 56 FRENZY.wav # frenzy 61 FORGET.wav # forgetfulness - +79 DEATHSTR.wav # Death Stare @@ -47,7 +48,6 @@ #DEATHBLO.wav #DRAINLIF.wav #DRGNSLAY.wav -#DISPELL.wav #DISGUISE.wav #DISEASE.wav #QUIKSAND.wav diff --git a/config/spell_info.txt b/config/spell_info.txt index 03a001860..c85dc26e3 100644 --- a/config/spell_info.txt +++ b/config/spell_info.txt @@ -79,6 +79,6 @@ 76 -1 -1 0 0 0 0 77 -1 38 0 0 0 0 78 -1 41 0 0 0 0 -79 -1 -1 0 0 0 0 +79 0 80 0 0 0 0 80 -1 -1 0 0 0 0 -1 \ No newline at end of file diff --git a/lib/BattleState.cpp b/lib/BattleState.cpp index 443273205..e763003ad 100644 --- a/lib/BattleState.cpp +++ b/lib/BattleState.cpp @@ -1791,9 +1791,10 @@ SpellCasting::ESpellCastProblem BattleInfo::battleIsImmune(const CGHeroInstance std::remove_if(immunities.begin(), immunities.end(), NegateRemover); } - if(subject->hasBonusOfType(Bonus::SPELL_IMMUNITY, spell->id) - || ( immunities.size() > 0 && immunities.totalValue() >= spell->level)) - { + if(subject->hasBonusOfType(Bonus::SPELL_IMMUNITY, spell->id) || + ( immunities.size() > 0 && immunities.totalValue() >= spell->level) + && spell->level) //many creature abilities have level equal to 0, may cause bugs + { return SpellCasting::STACK_IMMUNE_TO_SPELL; } //dispel helpful spells diff --git a/lib/CCreatureHandler.cpp b/lib/CCreatureHandler.cpp index c292213b4..59630c0d4 100644 --- a/lib/CCreatureHandler.cpp +++ b/lib/CCreatureHandler.cpp @@ -841,6 +841,10 @@ void CCreatureHandler::loadStackExp(Bonus & b, BonusList & bl, std::string & src b.type = Bonus::CHANGES_SPELL_COST_FOR_ALLY; break; case 'e': b.type = Bonus::DOUBLE_DAMAGE_CHANCE; break; + case 'E': + b.type = Bonus::DEATH_STARE; + b.subtype = 0; //Gorgon + break; case 'g': b.type = Bonus::SPELL_DAMAGE_REDUCTION; b.subtype = -1; //all magic schools diff --git a/lib/CCreatureSet.cpp b/lib/CCreatureSet.cpp index 70a3c896f..3798e7c90 100644 --- a/lib/CCreatureSet.cpp +++ b/lib/CCreatureSet.cpp @@ -600,6 +600,7 @@ std::string CStackInstance::bonusToString(Bonus *bonus, bool description) const case Bonus::CHANGES_SPELL_COST_FOR_ALLY: case Bonus::CHANGES_SPELL_COST_FOR_ENEMY: case Bonus::ENEMY_DEFENCE_REDUCTION: + case Bonus::DEATH_STARE: boost::algorithm::replace_first(text, "%d", boost::lexical_cast(valOfBonuses(Selector::typeSybtype(bonus->type, bonus->subtype)))); break; case Bonus::HATE: @@ -644,7 +645,8 @@ std::string CStackInstance::bonusToGraphics(Bonus *bonus) const fileName = "E_CHAMP.bmp"; break; case Bonus::DOUBLE_DAMAGE_CHANCE: fileName = "E_DBLOW.bmp"; break; - //"E_DEATH.bmp" + case Bonus::DEATH_STARE: + fileName = "E_DEATH.bmp"; break; //"E_DEFBON.bmp" case Bonus::NO_DISTANCE_PENALTY: fileName = "E_DIST.bmp"; break; diff --git a/lib/HeroBonus.h b/lib/HeroBonus.h index 076966bbf..60ebce899 100644 --- a/lib/HeroBonus.h +++ b/lib/HeroBonus.h @@ -156,7 +156,8 @@ namespace PrimarySkill BONUS_NAME(DRAGON_NATURE) \ BONUS_NAME(CREATURE_DAMAGE)/*subtype 0 = both, 1 = min, 2 = max*/\ BONUS_NAME(EXP_MULTIPLIER)/* val - percent of additional exp gained by stack/commander (base value 100)*/\ - BONUS_NAME(SHOTS) + BONUS_NAME(SHOTS)\ + BONUS_NAME(DEATH_STARE) /*subtype 0 - gorgon, 1 - commander*/ /// Struct for handling bonuses of several types. Can be transfered to any hero struct DLL_EXPORT Bonus diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 83f037183..8722c71fc 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -20,6 +20,9 @@ #include "../lib/VCMIDirs.h" #include "../client/CSoundBase.h" #include "CGameHandler.h" +#include +#include +#include /* * CGameHandler.cpp, part of VCMI engine @@ -3401,6 +3404,11 @@ void CGameHandler::handleSpellCasting( int spellID, int spellLvl, int destinatio continue; sc.dmgToDisplay += gs->curB->calculateSpellDmg(spell, caster, *it, spellLvl, usedSpellPower); } + if (spellID = 79) // Death stare + { + sc.dmgToDisplay = usedSpellPower; + amin(sc.dmgToDisplay, (*attackedCres.begin())->count); // hopefully stack is already reduced after attack + } sendAndApply(&sc); @@ -3585,6 +3593,27 @@ void CGameHandler::handleSpellCasting( int spellID, int spellLvl, int destinatio break; } + case 79: //Death stare - handled in a bit different way + { + StacksInjured si; + for(std::set::iterator it = attackedCres.begin(); it != attackedCres.end(); ++it) + { + if((*it)->hasBonusOfType (Bonus::UNDEAD) || (*it)->hasBonusOfType (Bonus::NON_LIVING)) //this creature is immune + continue; + + BattleStackAttacked bsa; + bsa.flags |= BattleStackAttacked::EFFECT; + bsa.effect = spell->mainEffectAnim; //TODO: find which it is + bsa.damageAmount = usedSpellPower * (*it)->valOfBonuses(Bonus::STACK_HEALTH); + bsa.stackAttacked = (*it)->ID; + bsa.attackerID = -1; + (*it)->prepareAttacked(bsa); + si.stacks.push_back(bsa); + } + if(!si.stacks.empty()) + sendAndApply(&si); + } + break; } } @@ -4210,6 +4239,28 @@ void CGameHandler::handleAfterAttackCasting( const BattleAttack & bat ) } } } + if (attacker->hasBonusOfType(Bonus::DEATH_STARE)) // spell id 79 + { + int staredCreatures = 0; + double mean = attacker->count * attacker->valOfBonuses(Bonus::DEATH_STARE, 0) / 100; + if (mean >= 1) + { + boost::poisson_distribution p((int)mean); + boost::mt19937 rng; + boost::variate_generator> dice (rng, p); + staredCreatures += dice(); + } + if (((int)(mean * 100)) < rand() % 100) //fractional chance for one last kill + ++staredCreatures; + + staredCreatures += attacker->type->level * attacker->valOfBonuses(Bonus::DEATH_STARE, 1); + if (staredCreatures) + { + if (bat.bsa.size() && bat.bsa[0].newAmount > 0) //TODO: death stare was not originally avaliable for multiple-hex attacks, but... + handleSpellCasting(79, 0, gs->curB->getStack(bat.bsa[0].stackAttacked)->position, + !attacker->attackerOwned, attacker->owner, NULL, NULL, staredCreatures, SpellCasting::AFTER_ATTACK_CASTING); + } + } } bool CGameHandler::castSpell(const CGHeroInstance *h, int spellID, const int3 &pos)