diff --git a/client/CBattleInterface.cpp b/client/CBattleInterface.cpp index e1477a593..cf3144081 100644 --- a/client/CBattleInterface.cpp +++ b/client/CBattleInterface.cpp @@ -2909,7 +2909,7 @@ void CBattleInterface::castThisSpell(int spellID) const CGHeroInstance * castingHero = (attackingHeroInstance->tempOwner == curInt->playerID) ? attackingHeroInstance : defendingHeroInstance; const CSpell & spell = *CGI->spellh->spells[spellID]; spellSelMode = 0; - if(spell.attributes.find("CREATURE_TARGET") != std::string::npos) //spell to be cast on one specific creature + if(spell.getTargetType() == CSpell::CREATURE) { switch(spell.positiveness) { @@ -2924,8 +2924,7 @@ void CBattleInterface::castThisSpell(int spellID) break; } } - if(spell.attributes.find("CREATURE_TARGET_1") != std::string::npos || - spell.attributes.find("CREATURE_TARGET_2") != std::string::npos) //spell to be cast on a specific creature but massive on expert + if(spell.getTargetType() == CSpell::CREATURE_EXPERT_MASSIVE) { if(castingHero && castingHero->getSpellSchoolLevel(&spell) < 3) { @@ -2947,7 +2946,7 @@ void CBattleInterface::castThisSpell(int spellID) spellSelMode = -1; } } - if(spell.attributes.find("OBSTACLE_TARGET") != std::string::npos) //spell to be cast on an obstacle + if(spell.getTargetType() == CSpell::OBSTACLE) { spellSelMode = 4; } diff --git a/client/CSpellWindow.cpp b/client/CSpellWindow.cpp index 4be85067c..e929fde6a 100644 --- a/client/CSpellWindow.cpp +++ b/client/CSpellWindow.cpp @@ -665,6 +665,11 @@ void CSpellWindow::SpellArea::clickLeft(tribool down, bool previousState) owner->myInt->showInfoDialog(text); } break; + case SpellCasting::NO_APPROPRIATE_TARGET: + { + owner->myInt->showInfoDialog(CGI->generaltexth->allTexts[185]); + } + break; } } else if(!sp->combatSpell && !owner->myInt->battleInt) //adventure spell diff --git a/global.h b/global.h index a79534cd3..d0f2a70dc 100644 --- a/global.h +++ b/global.h @@ -313,7 +313,8 @@ namespace SpellCasting { OK, NO_HERO_TO_CAST_SPELL, ALREADY_CASTED_THIS_TURN, NO_SPELLBOOK, ANOTHER_ELEMENTAL_SUMMONED, HERO_DOESNT_KNOW_SPELL, NOT_ENOUGH_MANA, ADVMAP_SPELL_INSTEAD_OF_BATTLE_SPELL, - SECOND_HEROS_SPELL_IMMUNITY, SPELL_LEVEL_LIMIT_EXCEEDED, NO_SPELLS_TO_DISPEL + SECOND_HEROS_SPELL_IMMUNITY, SPELL_LEVEL_LIMIT_EXCEEDED, NO_SPELLS_TO_DISPEL, + NO_APPROPRIATE_TARGET, STACK_IMMUNE_TO_SPELL, WRONG_SPELL_TARGET }; } diff --git a/lib/BattleState.cpp b/lib/BattleState.cpp index 040bc3d85..a5ea77661 100644 --- a/lib/BattleState.cpp +++ b/lib/BattleState.cpp @@ -665,8 +665,7 @@ std::set BattleInfo::getAttackedCreatures( const CSpell * s, int skillL attackedCres.insert(st); } } - 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 + else if(s->getTargetType() == CSpell::CREATURE_EXPERT_MASSIVE) { if(skillLevel < 3) /*not expert */ { @@ -679,8 +678,8 @@ std::set BattleInfo::getAttackedCreatures( const CSpell * s, int skillL for(int it=0; itspellh->spells[s->id]->positiveness >= 0 && stacks[it]->owner == attackerOwner) - ||(VLC->spellh->spells[s->id]->positiveness <= 0 && stacks[it]->owner != attackerOwner ) + if((s->positiveness >= 0 && stacks[it]->owner == attackerOwner) + ||(s->positiveness <= 0 && stacks[it]->owner != attackerOwner ) ) { if(!onlyAlive || stacks[it]->alive()) @@ -689,7 +688,7 @@ std::set BattleInfo::getAttackedCreatures( const CSpell * s, int skillL } } //if(caster->getSpellSchoolLevel(s) < 3) } - else if(VLC->spellh->spells[s->id]->attributes.find("CREATURE_TARGET") != std::string::npos) //spell to be cast on one specific creature + else if(s->getTargetType() == CSpell::CREATURE) { CStack * st = getStackT(destinationTile, onlyAlive); if(st) @@ -1169,18 +1168,18 @@ const CGHeroInstance * BattleInfo::battleGetOwner(const CStack * stack) const return heroes[!stack->attackerOwned]; } -si8 BattleInfo::battleMaxSpellLevel() const +si8 BattleInfo::battleMinSpellLevel() const { - si8 levelLimit = SPELL_LEVELS; + si8 levelLimit = 0; if(const CGHeroInstance *h1 = heroes[0]) { - amin(levelLimit, h1->valOfBonuses(Bonus::BLOCK_SPELLS_ABOVE_LEVEL)); + amax(levelLimit, h1->valOfBonuses(Bonus::LEVEL_SPELL_IMMUNITY)); } if(const CGHeroInstance *h2 = heroes[1]) { - amin(levelLimit, h2->valOfBonuses(Bonus::BLOCK_SPELLS_ABOVE_LEVEL)); + amax(levelLimit, h2->valOfBonuses(Bonus::LEVEL_SPELL_IMMUNITY)); } return levelLimit; @@ -1461,8 +1460,8 @@ BattleInfo * BattleInfo::setupBattle( int3 tile, int terrain, int terType, const } //spell level limiting bonus - curB->addNewBonus(new Bonus(Bonus::ONE_BATTLE, Bonus::BLOCK_SPELLS_ABOVE_LEVEL, Bonus::OTHER, - SPELL_LEVELS, -1, -1, Bonus::INDEPENDENT_MIN));; + curB->addNewBonus(new Bonus(Bonus::ONE_BATTLE, Bonus::LEVEL_SPELL_IMMUNITY, Bonus::OTHER, + 0, -1, -1, Bonus::INDEPENDENT_MAX)); //giving terrain overalay premies int bonusSubtype = -1; @@ -1515,8 +1514,8 @@ BattleInfo * BattleInfo::setupBattle( int3 tile, int terrain, int terType, const { curB->addNewBonus(makeFeature(Bonus::NO_MORALE, Bonus::ONE_BATTLE, 0, 0, Bonus::TERRAIN_OVERLAY)); curB->addNewBonus(makeFeature(Bonus::NO_LUCK, Bonus::ONE_BATTLE, 0, 0, Bonus::TERRAIN_OVERLAY)); - Bonus * b = makeFeature(Bonus::BLOCK_SPELLS_ABOVE_LEVEL, Bonus::ONE_BATTLE, 0, 1, Bonus::TERRAIN_OVERLAY); - b->valType = Bonus::INDEPENDENT_MIN; + Bonus * b = makeFeature(Bonus::LEVEL_SPELL_IMMUNITY, Bonus::ONE_BATTLE, SPELL_LEVELS, 1, Bonus::TERRAIN_OVERLAY); + b->valType = Bonus::INDEPENDENT_MAX; curB->addNewBonus(b); break; } @@ -1630,7 +1629,7 @@ SpellCasting::ESpellCastProblem BattleInfo::battleCanCastThisSpell( int player, if(NBonus::hasOfType(heroes[1-cside], Bonus::SPELL_IMMUNITY, spell->id)) //non - casting hero provides immunity for this spell return SpellCasting::SECOND_HEROS_SPELL_IMMUNITY; - if(battleMaxSpellLevel() < spell->level) //non - casting hero stops caster from casting this spell + if(battleMinSpellLevel() > spell->level) //non - casting hero stops caster from casting this spell return SpellCasting::SPELL_LEVEL_LIMIT_EXCEEDED; int spellIDs[] = {66, 67, 68, 69}; //IDs of summon elemental spells (fire, earth, water, air) @@ -1650,6 +1649,58 @@ SpellCasting::ESpellCastProblem BattleInfo::battleCanCastThisSpell( int player, } } + //checking if there exists an appropriate target + switch(spell->getTargetType()) + { + case CSpell::CREATURE: + case CSpell::CREATURE_EXPERT_MASSIVE: + if(mode == HERO_CASTING) + { + const CGHeroInstance * caster = getHero(player); + bool targetExists = false; + BOOST_FOREACH(const CStack * stack, stacks) + { + switch (spell->positiveness) + { + case 1: + if(stack->owner == caster->getOwner()) + { + if(canCastHereLower(player, spell, mode, stack->position) == SpellCasting::OK) + { + targetExists = true; + break; + } + } + break; + case 0: + if(canCastHereLower(player, spell, mode, stack->position) == SpellCasting::OK) + { + targetExists = true; + break; + } + break; + case -1: + if(stack->owner != caster->getOwner()) + { + if(canCastHereLower(player, spell, mode, stack->position) == SpellCasting::OK) + { + targetExists = true; + break; + } + } + break; + } + } + if(!targetExists) + { + return SpellCasting::NO_APPROPRIATE_TARGET; + } + } + break; + case CSpell::OBSTACLE: + break; + } + return SpellCasting::OK; } @@ -1659,25 +1710,56 @@ SpellCasting::ESpellCastProblem BattleInfo::battleCanCastThisSpellHere( int play if(moreGeneralProblem != SpellCasting::OK) return moreGeneralProblem; + return canCastHereLower(player, spell, mode, dest); +} + +const CGHeroInstance * BattleInfo::getHero( int player ) const +{ + if(heroes[0] && heroes[0]->getOwner() == player) + return heroes[0]; + + return heroes[1]; +} + +SpellCasting::ESpellCastProblem BattleInfo::canCastHereLower( int player, const CSpell * spell, ECastingMode mode, THex dest ) const +{ const CStack * subject = getStackT(dest, false); - //dispel helpful spells - if(spell->id == 78) + const CGHeroInstance * caster = mode == HERO_CASTING ? getHero(player) : NULL; + if(subject) { - BonusList spellBon = subject->getSpellBonuses(); - bool hasPositiveSpell = false; - BOOST_FOREACH(const Bonus * b, spellBon) + if(subject->hasBonusOfType(Bonus::SPELL_IMMUNITY, spell->id) + || ( subject->hasBonusOfType(Bonus::LEVEL_SPELL_IMMUNITY) && subject->valOfBonuses(Bonus::LEVEL_SPELL_IMMUNITY) >= spell->level)) { - if(VLC->spellh->spells[b->sid]->positiveness > 0) + return SpellCasting::STACK_IMMUNE_TO_SPELL; + } + //dispel helpful spells + if(spell->id == 78) + { + BonusList spellBon = subject->getSpellBonuses(); + bool hasPositiveSpell = false; + BOOST_FOREACH(const Bonus * b, spellBon) { - hasPositiveSpell = true; - break; + if(VLC->spellh->spells[b->sid]->positiveness > 0) + { + hasPositiveSpell = true; + break; + } + } + if(!hasPositiveSpell) + { + return SpellCasting::NO_SPELLS_TO_DISPEL; } } - if(!hasPositiveSpell) + } + else + { + if(spell->getTargetType() == CSpell::CREATURE || + (spell->getTargetType() == CSpell::CREATURE_EXPERT_MASSIVE && caster && caster->getSpellSchoolLevel(spell) < 3)) { - return SpellCasting::NO_SPELLS_TO_DISPEL; + return SpellCasting::WRONG_SPELL_TARGET; } } + return SpellCasting::OK; } @@ -1833,7 +1915,8 @@ void CStack::stackEffectToFeature(std::vector & sf, const Bonus & sse) sf.back().sid = sse.sid; break; case 34: //anti-magic - sf.push_back(featureGenerator(Bonus::LEVEL_SPELL_IMMUNITY, 0, power - 1, sse.turnsRemain)); + sf.push_back(featureGenerator(Bonus::LEVEL_SPELL_IMMUNITY, SPELL_LEVELS, power - 1, sse.turnsRemain)); + sf.back().valType = Bonus::INDEPENDENT_MAX; sf.back().sid = sse.sid; break; case 41: //bless diff --git a/lib/BattleState.h b/lib/BattleState.h index 005a9e55a..dc9431293 100644 --- a/lib/BattleState.h +++ b/lib/BattleState.h @@ -114,17 +114,21 @@ struct DLL_EXPORT BattleInfo : public CBonusSystemNode si8 hasWallPenalty(const CStack * stack, THex destHex) const; //determines if given stack has wall penalty shooting given pos si8 canTeleportTo(const CStack * stack, THex destHex, int telportLevel) const; //determines if given stack can teleport to given place bool battleCanShoot(const CStack * stack, THex dest) const; //determines if stack with given ID shoot at the selected destination + const CGHeroInstance * getHero(int player) const; //returns fighting hero that belongs to given player enum ECastingMode {HERO_CASTING, AFTER_ATTACK_CASTING}; SpellCasting::ESpellCastProblem battleCanCastSpell(int player, ECastingMode mode) const; //returns true if there are no general issues preventing from casting a spell SpellCasting::ESpellCastProblem battleCanCastThisSpell(int player, const CSpell * spell, ECastingMode mode) const; //checks if given player can cast given spell +private: + SpellCasting::ESpellCastProblem canCastHereLower(int player, const CSpell * spell, ECastingMode mode, THex dest) const; //same as battleCanCastThisSpellHere, but doesn't refer to battleCanCastThisSpell +public: SpellCasting::ESpellCastProblem battleCanCastThisSpellHere(int player, const CSpell * spell, ECastingMode mode, THex dest); //checks if given player can cast given spell at given tile in given mode bool battleCanFlee(int player) const; //returns true if player can flee from the battle const CStack * battleGetStack(THex pos, bool onlyAlive); //returns stack at given tile const CGHeroInstance * battleGetOwner(const CStack * stack) const; //returns hero that owns given stack; NULL if none - si8 battleMaxSpellLevel() const; //calculates maximum spell level possible to be cast on battlefield - takes into account artifacts of both heroes; if no effects are set, SPELL_LEVELS is returned + si8 battleMinSpellLevel() const; //calculates minimum spell level possible to be cast on battlefield - takes into account artifacts of both heroes; if no effects are set, 0 is returned void localInit(); static BattleInfo * setupBattle( int3 tile, int terrain, int terType, const CArmedInstance *armies[2], const CGHeroInstance * heroes[2], bool creatureBank, const CGTownInstance *town ); bool isInTacticRange( THex dest ) const; diff --git a/lib/CArtHandler.cpp b/lib/CArtHandler.cpp index 19c16ca94..0a9bdd635 100644 --- a/lib/CArtHandler.cpp +++ b/lib/CArtHandler.cpp @@ -627,7 +627,7 @@ void CArtHandler::addBonuses() giveArtBonus(81,Bonus::FIRE_SPELL_DMG_PREMY,+50);//Orb of Tempestuous Fire giveArtBonus(82,Bonus::WATER_SPELL_DMG_PREMY,+50);//Orb of Driving Rain - giveArtBonus(83,Bonus::BLOCK_SPELLS_ABOVE_LEVEL,3,-1,Bonus::INDEPENDENT_MIN);//Recanter's Cloak + giveArtBonus(83,Bonus::LEVEL_SPELL_IMMUNITY,3,-1,Bonus::INDEPENDENT_MAX);//Recanter's Cloak giveArtBonus(84,Bonus::BLOCK_MORALE,0);//Spirit of Oppression giveArtBonus(85,Bonus::BLOCK_LUCK,0);//Hourglass of the Evil Hour @@ -684,7 +684,7 @@ void CArtHandler::addBonuses() giveArtBonus(124,Bonus::SPELLS_OF_LEVEL,3,1); //Spellbinder's Hat giveArtBonus(125,Bonus::ENEMY_CANT_ESCAPE,0); //Shackles of War - giveArtBonus(126,Bonus::BLOCK_SPELLS_ABOVE_LEVEL,0,-1,Bonus::INDEPENDENT_MIN);//Orb of Inhibition + giveArtBonus(126,Bonus::LEVEL_SPELL_IMMUNITY,SPELL_LEVELS,-1,Bonus::INDEPENDENT_MAX);//Orb of Inhibition //vial of dragon blood giveArtBonus(127, Bonus::PRIMARY_SKILL, +5, PrimarySkill::ATTACK, Bonus::BASE_NUMBER, new HasAnotherBonusLimiter(Bonus::DRAGON_NATURE)); @@ -718,7 +718,7 @@ void CArtHandler::addBonuses() giveArtBonus(133, Bonus::CREATURE_GROWTH_PERCENT, 50, -1); //Power of the Dragon Father - giveArtBonus(134, Bonus::LEVEL_SPELL_IMMUNITY, 4); + giveArtBonus(134, Bonus::LEVEL_SPELL_IMMUNITY, 4, -1, Bonus::INDEPENDENT_MAX); //Titan's Thunder // FIXME: should also add a permanent spell book, somehow. diff --git a/lib/CSpellHandler.cpp b/lib/CSpellHandler.cpp index f9efb61b4..9fd83cad4 100644 --- a/lib/CSpellHandler.cpp +++ b/lib/CSpellHandler.cpp @@ -188,6 +188,21 @@ std::set CSpell::rangeInHexes(unsigned int centralHex, ui8 schoolLvl ) con return ret; } +CSpell::ETargetType CSpell::getTargetType() const +{ + if(attributes.find("CREATURE_TARGET_1") != std::string::npos + || attributes.find("CREATURE_TARGET_2") != std::string::npos) + return CREATURE_EXPERT_MASSIVE; + + if(attributes.find("CREATURE_TARGET") != std::string::npos) + return CREATURE; + + if(attributes.find("OBSTACLE_TARGET") != std::string::npos) + return OBSTACLE; + + return NO_TARGET; +} + static bool startsWithX(const std::string &s) { return s.size() && s[0] == 'x'; diff --git a/lib/CSpellHandler.h b/lib/CSpellHandler.h index b983bce4d..3c110df50 100644 --- a/lib/CSpellHandler.h +++ b/lib/CSpellHandler.h @@ -20,6 +20,7 @@ class DLL_EXPORT CSpell { public: + enum ETargetType {NO_TARGET, CREATURE, CREATURE_EXPERT_MASSIVE, OBSTACLE}; ui32 id; std::string name; std::string abbName; //abbreviated name @@ -41,6 +42,7 @@ public: std::vector range; //description of spell's range in SRSL by magic school level std::set rangeInHexes(unsigned int centralHex, ui8 schoolLvl ) const; //convert range to specific hexes si16 mainEffectAnim; //main spell effect animation, in AC format (or -1 when none) + ETargetType getTargetType() const; template void serialize(Handler &h, const int version) { diff --git a/lib/HeroBonus.cpp b/lib/HeroBonus.cpp index e837fc9b6..e3a811921 100644 --- a/lib/HeroBonus.cpp +++ b/lib/HeroBonus.cpp @@ -52,7 +52,7 @@ int DLL_EXPORT BonusList::totalValue() const additive += i->val; break; case Bonus::INDEPENDENT_MAX: - if (!indepMax) + if (!hasIndepMax) { indepMax = i->val; hasIndepMax = true; @@ -64,14 +64,14 @@ int DLL_EXPORT BonusList::totalValue() const break; case Bonus::INDEPENDENT_MIN: - if (!indepMin) + if (!hasIndepMin) { indepMin = i->val; hasIndepMin = true; } else { - amax(indepMin, i->val); + amin(indepMin, i->val); } break; @@ -85,7 +85,7 @@ int DLL_EXPORT BonusList::totalValue() const assert(indepMin < indepMax); if (hasIndepMax) amax(valFirst, indepMax); - if (hasIndepMax) + if (hasIndepMin) amin(valFirst, indepMin); return valFirst; diff --git a/lib/HeroBonus.h b/lib/HeroBonus.h index e3d54e428..83f040bd1 100644 --- a/lib/HeroBonus.h +++ b/lib/HeroBonus.h @@ -55,7 +55,6 @@ namespace PrimarySkill BONUS_NAME(EARTH_SPELL_DMG_PREMY) \ BONUS_NAME(FIRE_SPELL_DMG_PREMY) \ BONUS_NAME(WATER_SPELL_DMG_PREMY) \ - BONUS_NAME(BLOCK_SPELLS_ABOVE_LEVEL) \ BONUS_NAME(WATER_WALKING) /*subtype 1 - without penalty, 2 - with penalty*/ \ BONUS_NAME(NO_SHOTING_PENALTY) \ BONUS_NAME(NEGATE_ALL_NATURAL_IMMUNITIES) \