From a49da360d27ee2959dfb0bfd69eca797540bb9f9 Mon Sep 17 00:00:00 2001 From: AlexVinS Date: Wed, 12 Nov 2014 05:50:51 +0300 Subject: [PATCH 01/59] Use ESpellCastProblem inside SpellHandler --- lib/CBattleCallback.cpp | 10 +++++----- lib/CModHandler.cpp | 8 ++++++++ lib/CModHandler.h | 2 ++ lib/CSpellHandler.cpp | 36 ++++++++++++++++++++---------------- lib/CSpellHandler.h | 2 +- lib/GameConstants.h | 1 + 6 files changed, 37 insertions(+), 22 deletions(-) diff --git a/lib/CBattleCallback.cpp b/lib/CBattleCallback.cpp index 4ccd287aa..0318e7d2b 100644 --- a/lib/CBattleCallback.cpp +++ b/lib/CBattleCallback.cpp @@ -1621,12 +1621,12 @@ ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleIsImmune(const C ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleStackIsImmune(const CGHeroInstance * caster, const CSpell * spell, ECastingMode::ECastingMode mode, const CStack * subject) const { - if (spell->isPositive() && subject->hasBonusOfType(Bonus::RECEPTIVE)) //accept all positive spells - return ESpellCastProblem::OK; - - if (spell->isImmuneBy(subject)) //TODO: move all logic to spellhandler - return ESpellCastProblem::STACK_IMMUNE_TO_SPELL; + const auto immuneResult = spell->isImmuneBy(subject); + + if (ESpellCastProblem::NOT_DECIDED != immuneResult) + return immuneResult; + //TODO: move all logic to spellhandler switch (spell->id) //TODO: more general logic for new spells? { case SpellID::CLONE: diff --git a/lib/CModHandler.cpp b/lib/CModHandler.cpp index 9f5422566..3aaad7af5 100644 --- a/lib/CModHandler.cpp +++ b/lib/CModHandler.cpp @@ -99,6 +99,14 @@ void CIdentifierStorage::requestIdentifier(std::string scope, std::string type, requestIdentifier(ObjectCallback(scope, pair.first, type, pair.second, callback, false)); } +void CIdentifierStorage::requestIdentifier(std::string scope, std::string fullName, const std::function& callback) +{ + auto scopeAndFullName = splitString(fullName, ':'); + auto typeAndName = splitString(scopeAndFullName.second, '.'); + + requestIdentifier(ObjectCallback(scope, scopeAndFullName.first, typeAndName.first, typeAndName.second, callback, false)); +} + void CIdentifierStorage::requestIdentifier(std::string type, const JsonNode & name, const std::function & callback) { auto pair = splitString(name.String(), ':'); // remoteScope:name diff --git a/lib/CModHandler.h b/lib/CModHandler.h index 279e634e7..7d4f00fe5 100644 --- a/lib/CModHandler.h +++ b/lib/CModHandler.h @@ -72,6 +72,8 @@ public: /// request identifier for specific object name. /// Function callback will be called during ID resolution phase of loading void requestIdentifier(std::string scope, std::string type, std::string name, const std::function & callback); + ///fullName = [remoteScope:]type.name + void requestIdentifier(std::string scope, std::string fullName, const std::function & callback); void requestIdentifier(std::string type, const JsonNode & name, const std::function & callback); void requestIdentifier(const JsonNode & name, const std::function & callback); diff --git a/lib/CSpellHandler.cpp b/lib/CSpellHandler.cpp index 8428bc46d..f06869866 100644 --- a/lib/CSpellHandler.cpp +++ b/lib/CSpellHandler.cpp @@ -408,33 +408,37 @@ void CSpell::getEffects(std::vector& lst, const int level) const } } -bool CSpell::isImmuneBy(const IBonusBearer* obj) const +ESpellCastProblem::ESpellCastProblem CSpell::isImmuneBy(const IBonusBearer* obj) const { + //0. check receptivity + if (isPositive() && obj->hasBonusOfType(Bonus::RECEPTIVE)) //accept all positive spells + return ESpellCastProblem::OK; + //todo: use new bonus API //1. Check absolute limiters for(auto b : absoluteLimiters) { if (!obj->hasBonusOfType(b)) - return true; + return ESpellCastProblem::STACK_IMMUNE_TO_SPELL; } //2. Check absolute immunities for(auto b : absoluteImmunities) { if (obj->hasBonusOfType(b)) - return true; + return ESpellCastProblem::STACK_IMMUNE_TO_SPELL; } //3. Check negation //FIXME: Orb of vulnerability mechanics is not such trivial if(obj->hasBonusOfType(Bonus::NEGATE_ALL_NATURAL_IMMUNITIES)) //Orb of vulnerability - return false; + return ESpellCastProblem::NOT_DECIDED; //4. Check negatable limit for(auto b : limiters) { if (!obj->hasBonusOfType(b)) - return true; + return ESpellCastProblem::STACK_IMMUNE_TO_SPELL; } @@ -442,42 +446,42 @@ bool CSpell::isImmuneBy(const IBonusBearer* obj) const for(auto b : immunities) { if (obj->hasBonusOfType(b)) - return true; + return ESpellCastProblem::STACK_IMMUNE_TO_SPELL; } auto battleTestElementalImmunity = [&,this](Bonus::BonusType element) -> bool { if(obj->hasBonusOfType(element, 0)) //always resist if immune to all spells altogether - return true; + return ESpellCastProblem::STACK_IMMUNE_TO_SPELL; else if(!isPositive()) //negative or indifferent { if((isDamageSpell() && obj->hasBonusOfType(element, 2)) || obj->hasBonusOfType(element, 1)) - return true; + return ESpellCastProblem::STACK_IMMUNE_TO_SPELL; } - return false; + return ESpellCastProblem::NOT_DECIDED; }; //6. Check elemental immunities if(fire) { if(battleTestElementalImmunity(Bonus::FIRE_IMMUNITY)) - return true; + return ESpellCastProblem::STACK_IMMUNE_TO_SPELL; } if(water) { if(battleTestElementalImmunity(Bonus::WATER_IMMUNITY)) - return true; + return ESpellCastProblem::STACK_IMMUNE_TO_SPELL; } if(earth) { if(battleTestElementalImmunity(Bonus::EARTH_IMMUNITY)) - return true; + return ESpellCastProblem::STACK_IMMUNE_TO_SPELL; } if(air) { if(battleTestElementalImmunity(Bonus::AIR_IMMUNITY)) - return true; + return ESpellCastProblem::STACK_IMMUNE_TO_SPELL; } TBonusListPtr levelImmunities = obj->getBonuses(Selector::type(Bonus::LEVEL_SPELL_IMMUNITY)); @@ -485,10 +489,10 @@ bool CSpell::isImmuneBy(const IBonusBearer* obj) const if(obj->hasBonusOfType(Bonus::SPELL_IMMUNITY, id) || ( levelImmunities->size() > 0 && levelImmunities->totalValue() >= level && level)) { - return true; + return ESpellCastProblem::STACK_IMMUNE_TO_SPELL; } - return false; + return ESpellCastProblem::NOT_DECIDED; } void CSpell::setIsOffensive(const bool val) @@ -719,7 +723,7 @@ CSpell * CSpellHandler::loadFromJson(const JsonNode& json) for(const auto & counteredSpell: json["counters"].Struct()) if (counteredSpell.second.Bool()) { - VLC->modh->identifiers.requestIdentifier(json.meta, "spell", counteredSpell.first, [=](si32 id) + VLC->modh->identifiers.requestIdentifier(json.meta, counteredSpell.first, [=](si32 id) { spell->counteredSpells.push_back(SpellID(id)); }); diff --git a/lib/CSpellHandler.h b/lib/CSpellHandler.h index 48e61b2ae..34eb4a9f2 100644 --- a/lib/CSpellHandler.h +++ b/lib/CSpellHandler.h @@ -108,7 +108,7 @@ public: bool hasEffects() const; void getEffects(std::vector &lst, const int level) const; - bool isImmuneBy(const IBonusBearer *obj) const; + ESpellCastProblem::ESpellCastProblem isImmuneBy(const IBonusBearer *obj) const; si32 getCost(const int skillLevel) const; diff --git a/lib/GameConstants.h b/lib/GameConstants.h index db28084cc..3cc78841e 100644 --- a/lib/GameConstants.h +++ b/lib/GameConstants.h @@ -395,6 +395,7 @@ namespace ESpellCastProblem SECOND_HEROS_SPELL_IMMUNITY, SPELL_LEVEL_LIMIT_EXCEEDED, NO_SPELLS_TO_DISPEL, NO_APPROPRIATE_TARGET, STACK_IMMUNE_TO_SPELL, WRONG_SPELL_TARGET, ONGOING_TACTIC_PHASE, MAGIC_IS_BLOCKED, //For Orb of Inhibition and similar - no casting at all + NOT_DECIDED, INVALID }; } From 61d6bca3ff994de7c90fb31541593c62ed1cec94 Mon Sep 17 00:00:00 2001 From: AlexVinS Date: Wed, 12 Nov 2014 06:59:53 +0300 Subject: [PATCH 02/59] Extract some spell imuunity mechanics + draft of overall design (UNTESTED) --- lib/CSpellHandler.cpp | 139 +++++++++++++++++++++++++++++++++++++++++- lib/CSpellHandler.h | 27 +++++++- 2 files changed, 162 insertions(+), 4 deletions(-) diff --git a/lib/CSpellHandler.cpp b/lib/CSpellHandler.cpp index f06869866..6b7cc1998 100644 --- a/lib/CSpellHandler.cpp +++ b/lib/CSpellHandler.cpp @@ -10,6 +10,9 @@ #include "CModHandler.h" #include "StringConstants.h" +#include "mapObjects/CGHeroInstance.h" +#include "BattleState.h" + /* * CSpellHandler.cpp, part of VCMI engine * @@ -128,6 +131,96 @@ namespace SRSLPraserHelpers } } + +///CSpellMechanics +CSpellMechanics::CSpellMechanics(CSpell * s): + owner(s) +{ + +} + +CSpellMechanics::~CSpellMechanics() +{ + +} + +ESpellCastProblem::ESpellCastProblem CSpellMechanics::isImmuneByStack(const CGHeroInstance * caster, ECastingMode::ECastingMode mode, const CStack * obj) +{ + //by default no immunity + return ESpellCastProblem::OK; +} + +namespace +{ + class CloneMechnics: public CSpellMechanics + { + public: + CloneMechnics(CSpell * s): CSpellMechanics(s){}; + ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, ECastingMode::ECastingMode mode, const CStack * obj) override; + }; + + class DispellHelpfulMechanics: public CSpellMechanics + { + public: + DispellHelpfulMechanics(CSpell * s): CSpellMechanics(s){}; + ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, ECastingMode::ECastingMode mode, const CStack * obj) override; + }; + + + ///CloneMechanics + ESpellCastProblem::ESpellCastProblem CloneMechnics::isImmuneByStack(const CGHeroInstance* caster, ECastingMode::ECastingMode mode, const CStack * obj) + { + //can't clone already cloned creature + if (vstd::contains(obj->state, EBattleStackState::CLONED)) + return ESpellCastProblem::STACK_IMMUNE_TO_SPELL; + //TODO: how about stacks casting Clone? + //currently Clone casted by stack is assumed Expert level + ui8 schoolLevel; + if (caster) + { + schoolLevel = caster->getSpellSchoolLevel(owner); + } + else + { + schoolLevel = 3; + } + + if (schoolLevel < 3) + { + int maxLevel = (std::max(schoolLevel, (ui8)1) + 4); + int creLevel = obj->getCreature()->level; + if (maxLevel < creLevel) //tier 1-5 for basic, 1-6 for advanced, any level for expert + return ESpellCastProblem::STACK_IMMUNE_TO_SPELL; + } + + return CSpellMechanics::isImmuneByStack(caster,mode,obj); + } + + ///DispellHelpfulMechanics + ESpellCastProblem::ESpellCastProblem DispellHelpfulMechanics::isImmuneByStack(const CGHeroInstance* caster, ECastingMode::ECastingMode mode, const CStack* obj) + { + TBonusListPtr spellBon = obj->getSpellBonuses(); + bool hasPositiveSpell = false; + for(const Bonus * b : *spellBon) + { + if(SpellID(b->sid).toSpell()->isPositive()) + { + hasPositiveSpell = true; + break; + } + } + if(!hasPositiveSpell) + { + return ESpellCastProblem::NO_SPELLS_TO_DISPEL; + } + return CSpellMechanics::isImmuneByStack(caster,mode,obj); + } + + +} + + +///CSpell::LevelInfo CSpell::LevelInfo::LevelInfo() :description(""),cost(0),power(0),AIValue(0),smartTarget(true),range("0") { @@ -139,7 +232,7 @@ CSpell::LevelInfo::~LevelInfo() } - +///CSpell CSpell::CSpell(): id(SpellID::NONE), level(0), earth(false), water(false), fire(false), air(false), @@ -148,13 +241,15 @@ CSpell::CSpell(): mainEffectAnim(-1), defaultProbability(0), isRising(false), isDamage(false), isOffensive(false), - targetType(ETargetType::NO_TARGET) + targetType(ETargetType::NO_TARGET), + mechanics(nullptr) { levels.resize(GameConstants::SPELL_SCHOOL_LEVELS); } CSpell::~CSpell() { + delete mechanics; } const CSpell::LevelInfo & CSpell::getLevelInfo(const int level) const @@ -495,6 +590,18 @@ ESpellCastProblem::ESpellCastProblem CSpell::isImmuneBy(const IBonusBearer* obj) return ESpellCastProblem::NOT_DECIDED; } +ESpellCastProblem::ESpellCastProblem CSpell::isImmuneByStack(const CGHeroInstance* caster, ECastingMode::ECastingMode mode, const CStack* obj) const +{ + const auto immuneResult = isImmuneBy(obj); + + if (ESpellCastProblem::NOT_DECIDED != immuneResult) + return immuneResult; + + return mechanics->isImmuneByStack(caster,mode,obj); + +} + + void CSpell::setIsOffensive(const bool val) { isOffensive = val; @@ -516,6 +623,30 @@ void CSpell::setIsRising(const bool val) } } +void CSpell::setupMechanics() +{ + if(nullptr != mechanics) + { + logGlobal->errorStream() << "Spell " << this->name << " mechanics already set"; + delete mechanics; + mechanics = nullptr; + } + + switch (id) + { + case SpellID::CLONE: + mechanics = new CloneMechnics(this); + break; + case SpellID::DISPEL_HELPFUL_SPELLS: + mechanics = new DispellHelpfulMechanics(this); + break; + default: + mechanics = new CSpellMechanics(this); + break; + } + +} + bool DLL_LINKAGE isInScreenRange(const int3 ¢er, const int3 &pos) @@ -527,6 +658,7 @@ bool DLL_LINKAGE isInScreenRange(const int3 ¢er, const int3 &pos) return false; } +///CSpellHandler CSpellHandler::CSpellHandler() { @@ -854,9 +986,12 @@ void CSpellHandler::afterLoadFinalization() { //FIXME: it is a bad place for this code, should refactor loadFromJson to know object id during loading for(auto spell: objects) + { for(auto & level: spell->levels) for(auto & bonus: level.effects) bonus.sid = spell->id; + spell->setupMechanics(); + } } void CSpellHandler::beforeValidate(JsonNode & object) diff --git a/lib/CSpellHandler.h b/lib/CSpellHandler.h index 34eb4a9f2..199b895a1 100644 --- a/lib/CSpellHandler.h +++ b/lib/CSpellHandler.h @@ -19,6 +19,20 @@ class CLegacyConfigParser; struct BattleHex; +class CSpell; +class CGHeroInstance; +class CStack; + +class DLL_LINKAGE CSpellMechanics +{ +public: + CSpellMechanics(CSpell * s); + virtual ~CSpellMechanics(); + + virtual ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, ECastingMode::ECastingMode mode, const CStack * obj); +protected: + CSpell * owner; +}; class DLL_LINKAGE CSpell { @@ -109,6 +123,9 @@ public: void getEffects(std::vector &lst, const int level) const; ESpellCastProblem::ESpellCastProblem isImmuneBy(const IBonusBearer *obj) const; + + //checks for creature immunity / anything that prevent casting *at given hex* - doesn't take into acount general problems such as not having spellbook or mana points etc. + ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, ECastingMode::ECastingMode mode, const CStack * obj) const; si32 getCost(const int skillLevel) const; @@ -147,7 +164,9 @@ public: h & castSound & iconBook & iconEffect & iconScenarioBonus & iconScroll; h & levels; - + + if(!h.saving) + setupMechanics(); } friend class CSpellHandler; friend class Graphics; @@ -155,7 +174,9 @@ public: private: void setIsOffensive(const bool val); void setIsRising(const bool val); - + + //call this after load or deserialization. cant be done in constructor. + void setupMechanics(); private: si32 defaultProbability; @@ -186,6 +207,8 @@ private: std::string castSound; std::vector levels; + + CSpellMechanics * mechanics;//(!) do not serialize }; From 059698217ca9a09672d00139bbbe27d6da6d89f6 Mon Sep 17 00:00:00 2001 From: AlexVinS Date: Wed, 12 Nov 2014 07:41:12 +0300 Subject: [PATCH 03/59] Use CLONE and DISPEL_HELPFUL_SPELLS Mechanics classes in immunity caculation --- lib/CBattleCallback.cpp | 52 ++--------------------------------------- lib/CSpellHandler.cpp | 48 +++++++++++++++++++++++++++++-------- lib/CSpellHandler.h | 3 ++- 3 files changed, 42 insertions(+), 61 deletions(-) diff --git a/lib/CBattleCallback.cpp b/lib/CBattleCallback.cpp index 0318e7d2b..c0a40825d 100644 --- a/lib/CBattleCallback.cpp +++ b/lib/CBattleCallback.cpp @@ -1621,60 +1621,12 @@ ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleIsImmune(const C ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleStackIsImmune(const CGHeroInstance * caster, const CSpell * spell, ECastingMode::ECastingMode mode, const CStack * subject) const { - const auto immuneResult = spell->isImmuneBy(subject); + const auto immuneResult = spell->isImmuneByStack(caster, mode, subject); if (ESpellCastProblem::NOT_DECIDED != immuneResult) return immuneResult; - //TODO: move all logic to spellhandler - switch (spell->id) //TODO: more general logic for new spells? - { - case SpellID::CLONE: - { - //can't clone already cloned creature - if (vstd::contains(subject->state, EBattleStackState::CLONED)) - return ESpellCastProblem::STACK_IMMUNE_TO_SPELL; - //TODO: how about stacks casting Clone? - //currently Clone casted by stack is assumed Expert level - ui8 schoolLevel; - if (caster) - { - schoolLevel = caster->getSpellSchoolLevel(spell); - } - else - { - schoolLevel = 3; - } - - if (schoolLevel < 3) - { - int maxLevel = (std::max(schoolLevel, (ui8)1) + 4); - int creLevel = subject->getCreature()->level; - if (maxLevel < creLevel) //tier 1-5 for basic, 1-6 for advanced, any level for expert - return ESpellCastProblem::STACK_IMMUNE_TO_SPELL; - } - } - break; - case SpellID::DISPEL_HELPFUL_SPELLS: - { - TBonusListPtr spellBon = subject->getSpellBonuses(); - bool hasPositiveSpell = false; - for(const Bonus * b : *spellBon) - { - if(SpellID(b->sid).toSpell()->isPositive()) - { - hasPositiveSpell = true; - break; - } - } - if(!hasPositiveSpell) - { - return ESpellCastProblem::NO_SPELLS_TO_DISPEL; - } - } - break; - } - + //TODO: move to spellhandler if (spell->isRisingSpell() && spell->id != SpellID::SACRIFICE) { // following does apply to resurrect and animate dead(?) only diff --git a/lib/CSpellHandler.cpp b/lib/CSpellHandler.cpp index 6b7cc1998..5245e2836 100644 --- a/lib/CSpellHandler.cpp +++ b/lib/CSpellHandler.cpp @@ -146,8 +146,8 @@ CSpellMechanics::~CSpellMechanics() ESpellCastProblem::ESpellCastProblem CSpellMechanics::isImmuneByStack(const CGHeroInstance * caster, ECastingMode::ECastingMode mode, const CStack * obj) { - //by default no immunity - return ESpellCastProblem::OK; + //by default use general algorithm + return owner->isImmuneBy(obj); } namespace @@ -166,6 +166,21 @@ namespace ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, ECastingMode::ECastingMode mode, const CStack * obj) override; }; + ///all rising spells + class RisingSpellMechanics: public CSpellMechanics + { + public: + RisingSpellMechanics(CSpell * s): CSpellMechanics(s){}; + + }; + + ///all rising spells but SACRIFICE + class SpecialRisingSpellMechanics: public RisingSpellMechanics + { + public: + SpecialRisingSpellMechanics(CSpell * s): RisingSpellMechanics(s){}; + ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, ECastingMode::ECastingMode mode, const CStack * obj) override; + }; ///CloneMechanics ESpellCastProblem::ESpellCastProblem CloneMechnics::isImmuneByStack(const CGHeroInstance* caster, ECastingMode::ECastingMode mode, const CStack * obj) @@ -192,7 +207,7 @@ namespace if (maxLevel < creLevel) //tier 1-5 for basic, 1-6 for advanced, any level for expert return ESpellCastProblem::STACK_IMMUNE_TO_SPELL; } - + //use default algorithm only if there is no mechanics-related problem return CSpellMechanics::isImmuneByStack(caster,mode,obj); } @@ -212,10 +227,29 @@ namespace if(!hasPositiveSpell) { return ESpellCastProblem::NO_SPELLS_TO_DISPEL; - } + } + + //use default algorithm only if there is no mechanics-related problem return CSpellMechanics::isImmuneByStack(caster,mode,obj); } + ///SpecialRisingSpellMechanics + ESpellCastProblem::ESpellCastProblem SpecialRisingSpellMechanics::isImmuneByStack(const CGHeroInstance* caster, ECastingMode::ECastingMode mode, const CStack* obj) + { +// // following does apply to resurrect and animate dead(?) only +// // for sacrifice health calculation and health limit check don't matter +// +// if(obj->count >= obj->baseAmount) +// return ESpellCastProblem::STACK_IMMUNE_TO_SPELL; +// +// if (caster) //FIXME: Archangels can cast immune stack +// { +// auto maxHealth = calculateHealedHP (caster, spell, obj); +// if (maxHealth < obj->MaxHealth()) //must be able to rise at least one full creature +// return ESpellCastProblem::STACK_IMMUNE_TO_SPELL; +// } + } + } @@ -592,13 +626,7 @@ ESpellCastProblem::ESpellCastProblem CSpell::isImmuneBy(const IBonusBearer* obj) ESpellCastProblem::ESpellCastProblem CSpell::isImmuneByStack(const CGHeroInstance* caster, ECastingMode::ECastingMode mode, const CStack* obj) const { - const auto immuneResult = isImmuneBy(obj); - - if (ESpellCastProblem::NOT_DECIDED != immuneResult) - return immuneResult; - return mechanics->isImmuneByStack(caster,mode,obj); - } diff --git a/lib/CSpellHandler.h b/lib/CSpellHandler.h index 199b895a1..b72626659 100644 --- a/lib/CSpellHandler.h +++ b/lib/CSpellHandler.h @@ -121,7 +121,8 @@ public: bool hasEffects() const; void getEffects(std::vector &lst, const int level) const; - + + //internal, for use only by Mechanics classes ESpellCastProblem::ESpellCastProblem isImmuneBy(const IBonusBearer *obj) const; //checks for creature immunity / anything that prevent casting *at given hex* - doesn't take into acount general problems such as not having spellbook or mana points etc. From 639b915391960a0c9599e058b7cfe63ea73c2090 Mon Sep 17 00:00:00 2001 From: AlexVinS Date: Wed, 12 Nov 2014 08:02:27 +0300 Subject: [PATCH 04/59] Move calculateSpellBonus to CSpell --- lib/BattleState.cpp | 2 +- lib/CBattleCallback.cpp | 30 +++--------------------------- lib/CBattleCallback.h | 1 - lib/CSpellHandler.cpp | 24 ++++++++++++++++++++++++ lib/CSpellHandler.h | 3 +++ 5 files changed, 31 insertions(+), 29 deletions(-) diff --git a/lib/BattleState.cpp b/lib/BattleState.cpp index 642fdeb94..69017b115 100644 --- a/lib/BattleState.cpp +++ b/lib/BattleState.cpp @@ -168,7 +168,7 @@ ui32 CBattleInfoCallback::calculateHealedHP(const CGHeroInstance * caster, const healedHealth = (caster->getPrimSkillLevel(PrimarySkill::SPELL_POWER) + sacrificedStack->MaxHealth() + spell->getPower(caster->getSpellSchoolLevel(spell))) * sacrificedStack->count; else healedHealth = caster->getPrimSkillLevel(PrimarySkill::SPELL_POWER) * spell->power + spell->getPower(caster->getSpellSchoolLevel(spell)); //??? - healedHealth = calculateSpellBonus(healedHealth, spell, caster, stack); + healedHealth = spell->calculateBonus(healedHealth, caster, stack); return std::min(healedHealth, stack->MaxHealth() - stack->firstHPleft + (resurrect ? stack->baseAmount * stack->MaxHealth() : 0)); } //Archangel diff --git a/lib/CBattleCallback.cpp b/lib/CBattleCallback.cpp index c0a40825d..185337efa 100644 --- a/lib/CBattleCallback.cpp +++ b/lib/CBattleCallback.cpp @@ -1647,8 +1647,8 @@ ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleStackIsImmune(co //TODO: what with other creatures casting hypnotize, Faerie Dragons style? ui64 subjectHealth = (subject->count - 1) * subject->MaxHealth() + subject->firstHPleft; //apply 'damage' bonus for hypnotize, including hero specialty - ui64 maxHealth = calculateSpellBonus (caster->getPrimSkillLevel(PrimarySkill::SPELL_POWER) - * spell->power + spell->getPower(caster->getSpellSchoolLevel(spell)), spell, caster, subject); + ui64 maxHealth = spell->calculateBonus (caster->getPrimSkillLevel(PrimarySkill::SPELL_POWER) + * spell->power + spell->getPower(caster->getSpellSchoolLevel(spell)), caster, subject); if (subjectHealth > maxHealth) return ESpellCastProblem::STACK_IMMUNE_TO_SPELL; } @@ -1945,30 +1945,6 @@ ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCastThisSpell return battleIsImmune(nullptr, spell, mode, dest); } -ui32 CBattleInfoCallback::calculateSpellBonus(ui32 baseDamage, const CSpell * sp, const CGHeroInstance * caster, const CStack * affectedCreature) const -{ - ui32 ret = baseDamage; - //applying sorcery secondary skill - if(caster) - { - ret *= (100.0 + caster->valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, SecondarySkill::SORCERY)) / 100.0; - ret *= (100.0 + caster->valOfBonuses(Bonus::SPELL_DAMAGE) + caster->valOfBonuses(Bonus::SPECIFIC_SPELL_DAMAGE, sp->id.toEnum())) / 100.0; - - if(sp->air) - ret *= (100.0 + caster->valOfBonuses(Bonus::AIR_SPELL_DMG_PREMY)) / 100.0; - else if(sp->fire) //only one type of bonus for Magic Arrow - ret *= (100.0 + caster->valOfBonuses(Bonus::FIRE_SPELL_DMG_PREMY)) / 100.0; - else if(sp->water) - ret *= (100.0 + caster->valOfBonuses(Bonus::WATER_SPELL_DMG_PREMY)) / 100.0; - else if(sp->earth) - ret *= (100.0 + caster->valOfBonuses(Bonus::EARTH_SPELL_DMG_PREMY)) / 100.0; - - if (affectedCreature && affectedCreature->getCreature()->level) //Hero specials like Solmyr, Deemer - ret *= (100. + ((caster->valOfBonuses(Bonus::SPECIAL_SPELL_LEV, sp->id.toEnum()) * caster->level) / affectedCreature->getCreature()->level)) / 100.0; - } - return ret; -} - ui32 CBattleInfoCallback::calculateSpellDmg( const CSpell * sp, const CGHeroInstance * caster, const CStack * affectedCreature, int spellSchoolLevel, int usedSpellPower ) const { ui32 ret = 0; //value to return @@ -2018,7 +1994,7 @@ ui32 CBattleInfoCallback::calculateSpellDmg( const CSpell * sp, const CGHeroInst ret /= 100; } } - ret = calculateSpellBonus(ret, sp, caster, affectedCreature); + ret = sp->calculateBonus(ret, caster, affectedCreature); return ret; } diff --git a/lib/CBattleCallback.h b/lib/CBattleCallback.h index 7ab326620..5d8a653e5 100644 --- a/lib/CBattleCallback.h +++ b/lib/CBattleCallback.h @@ -281,7 +281,6 @@ public: ESpellCastProblem::ESpellCastProblem battleCanCastThisSpellHere(PlayerColor player, const CSpell * spell, ECastingMode::ECastingMode mode, BattleHex dest) const; //checks if given player can cast given spell at given tile in given mode ESpellCastProblem::ESpellCastProblem battleCanCreatureCastThisSpell(const CSpell * spell, BattleHex destination) const; //determines if creature can cast a spell here std::vector battleGetPossibleTargets(PlayerColor player, const CSpell *spell) const; - ui32 calculateSpellBonus(ui32 baseDamage, const CSpell * sp, const CGHeroInstance * caster, const CStack * affectedCreature) const; ui32 calculateSpellDmg(const CSpell * sp, const CGHeroInstance * caster, const CStack * affectedCreature, int spellSchoolLevel, int usedSpellPower) const; //calculates damage inflicted by spell ui32 calculateHealedHP(const CGHeroInstance * caster, const CSpell * spell, const CStack * stack, const CStack * sacrificedStack = nullptr) const; //Sacrifice ui32 calculateHealedHP(int healedHealth, const CSpell * spell, const CStack * stack) const; //for Archangel diff --git a/lib/CSpellHandler.cpp b/lib/CSpellHandler.cpp index 5245e2836..f2e4c7171 100644 --- a/lib/CSpellHandler.cpp +++ b/lib/CSpellHandler.cpp @@ -297,6 +297,30 @@ const CSpell::LevelInfo & CSpell::getLevelInfo(const int level) const return levels.at(level); } +ui32 CSpell::calculateBonus(ui32 baseDamage, const CGHeroInstance* caster, const CStack* affectedCreature) const +{ + ui32 ret = baseDamage; + //applying sorcery secondary skill + if(caster) + { + ret *= (100.0 + caster->valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, SecondarySkill::SORCERY)) / 100.0; + ret *= (100.0 + caster->valOfBonuses(Bonus::SPELL_DAMAGE) + caster->valOfBonuses(Bonus::SPECIFIC_SPELL_DAMAGE, id.toEnum())) / 100.0; + + if(air) + ret *= (100.0 + caster->valOfBonuses(Bonus::AIR_SPELL_DMG_PREMY)) / 100.0; + else if(fire) //only one type of bonus for Magic Arrow + ret *= (100.0 + caster->valOfBonuses(Bonus::FIRE_SPELL_DMG_PREMY)) / 100.0; + else if(water) + ret *= (100.0 + caster->valOfBonuses(Bonus::WATER_SPELL_DMG_PREMY)) / 100.0; + else if(earth) + ret *= (100.0 + caster->valOfBonuses(Bonus::EARTH_SPELL_DMG_PREMY)) / 100.0; + + if (affectedCreature && affectedCreature->getCreature()->level) //Hero specials like Solmyr, Deemer + ret *= (100. + ((caster->valOfBonuses(Bonus::SPECIAL_SPELL_LEV, id.toEnum()) * caster->level) / affectedCreature->getCreature()->level)) / 100.0; + } + return ret; +} + std::vector CSpell::rangeInHexes(BattleHex centralHex, ui8 schoolLvl, ui8 side, bool *outDroppedHexes) const { diff --git a/lib/CSpellHandler.h b/lib/CSpellHandler.h index b72626659..35472f7ff 100644 --- a/lib/CSpellHandler.h +++ b/lib/CSpellHandler.h @@ -127,6 +127,9 @@ public: //checks for creature immunity / anything that prevent casting *at given hex* - doesn't take into acount general problems such as not having spellbook or mana points etc. ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, ECastingMode::ECastingMode mode, const CStack * obj) const; + + //applying secondary skills + ui32 calculateBonus(ui32 baseDamage, const CGHeroInstance * caster, const CStack * affectedCreature) const; si32 getCost(const int skillLevel) const; From ddf98a59207b1bb56de3bfcf8374a6692cf99b3e Mon Sep 17 00:00:00 2001 From: AlexVinS Date: Wed, 12 Nov 2014 08:34:43 +0300 Subject: [PATCH 05/59] +SpecialRisingSpellMechanics::isImmuneByStack --- lib/BattleState.cpp | 12 ------- lib/CBattleCallback.cpp | 17 +--------- lib/CBattleCallback.h | 1 - lib/CSpellHandler.cpp | 70 ++++++++++++++++++++++++++++++++--------- lib/CSpellHandler.h | 6 +++- server/CGameHandler.cpp | 4 +-- 6 files changed, 63 insertions(+), 47 deletions(-) diff --git a/lib/BattleState.cpp b/lib/BattleState.cpp index 69017b115..a2fe2113c 100644 --- a/lib/BattleState.cpp +++ b/lib/BattleState.cpp @@ -159,18 +159,6 @@ CStack * BattleInfo::generateNewStack(const CStackBasicDescriptor &base, bool at return ret; } -//All spells casted by hero 9resurrection, cure, sacrifice) -ui32 CBattleInfoCallback::calculateHealedHP(const CGHeroInstance * caster, const CSpell * spell, const CStack * stack, const CStack * sacrificedStack) const -{ - bool resurrect = spell->isRisingSpell(); - int healedHealth; - if (spell->id == SpellID::SACRIFICE && sacrificedStack) - healedHealth = (caster->getPrimSkillLevel(PrimarySkill::SPELL_POWER) + sacrificedStack->MaxHealth() + spell->getPower(caster->getSpellSchoolLevel(spell))) * sacrificedStack->count; - else - healedHealth = caster->getPrimSkillLevel(PrimarySkill::SPELL_POWER) * spell->power + spell->getPower(caster->getSpellSchoolLevel(spell)); //??? - healedHealth = spell->calculateBonus(healedHealth, caster, stack); - return std::min(healedHealth, stack->MaxHealth() - stack->firstHPleft + (resurrect ? stack->baseAmount * stack->MaxHealth() : 0)); -} //Archangel ui32 CBattleInfoCallback::calculateHealedHP(int healedHealth, const CSpell * spell, const CStack * stack) const { diff --git a/lib/CBattleCallback.cpp b/lib/CBattleCallback.cpp index 185337efa..ddbb8bab4 100644 --- a/lib/CBattleCallback.cpp +++ b/lib/CBattleCallback.cpp @@ -1627,22 +1627,7 @@ ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleStackIsImmune(co return immuneResult; //TODO: move to spellhandler - if (spell->isRisingSpell() && spell->id != SpellID::SACRIFICE) - { - // following does apply to resurrect and animate dead(?) only - // for sacrifice health calculation and health limit check don't matter - - if(subject->count >= subject->baseAmount) - return ESpellCastProblem::STACK_IMMUNE_TO_SPELL; - - if (caster) //FIXME: Archangels can cast immune stack - { - auto maxHealth = calculateHealedHP (caster, spell, subject); - if (maxHealth < subject->MaxHealth()) //must be able to rise at least one full creature - return ESpellCastProblem::STACK_IMMUNE_TO_SPELL; - } - } - else if(spell->id == SpellID::HYPNOTIZE && caster) //do not resist hypnotize casted after attack, for example + if(spell->id == SpellID::HYPNOTIZE && caster) //do not resist hypnotize casted after attack, for example { //TODO: what with other creatures casting hypnotize, Faerie Dragons style? ui64 subjectHealth = (subject->count - 1) * subject->MaxHealth() + subject->firstHPleft; diff --git a/lib/CBattleCallback.h b/lib/CBattleCallback.h index 5d8a653e5..c40d2c1c9 100644 --- a/lib/CBattleCallback.h +++ b/lib/CBattleCallback.h @@ -282,7 +282,6 @@ public: ESpellCastProblem::ESpellCastProblem battleCanCreatureCastThisSpell(const CSpell * spell, BattleHex destination) const; //determines if creature can cast a spell here std::vector battleGetPossibleTargets(PlayerColor player, const CSpell *spell) const; ui32 calculateSpellDmg(const CSpell * sp, const CGHeroInstance * caster, const CStack * affectedCreature, int spellSchoolLevel, int usedSpellPower) const; //calculates damage inflicted by spell - ui32 calculateHealedHP(const CGHeroInstance * caster, const CSpell * spell, const CStack * stack, const CStack * sacrificedStack = nullptr) const; //Sacrifice ui32 calculateHealedHP(int healedHealth, const CSpell * spell, const CStack * stack) const; //for Archangel ui32 calculateHealedHP(const CSpell * spell, int usedSpellPower, int spellSchoolLevel, const CStack * stack) const; //healing spells casted by stacks std::set getAffectedCreatures(const CSpell * s, int skillLevel, PlayerColor attackerOwner, BattleHex destinationTile); //calculates stack affected by given spell diff --git a/lib/CSpellHandler.cpp b/lib/CSpellHandler.cpp index f2e4c7171..97f315f5e 100644 --- a/lib/CSpellHandler.cpp +++ b/lib/CSpellHandler.cpp @@ -182,6 +182,12 @@ namespace ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, ECastingMode::ECastingMode mode, const CStack * obj) override; }; + class SacrificeMechanics: public RisingSpellMechanics + { + public: + + }; + ///CloneMechanics ESpellCastProblem::ESpellCastProblem CloneMechnics::isImmuneByStack(const CGHeroInstance* caster, ECastingMode::ECastingMode mode, const CStack * obj) { @@ -236,18 +242,20 @@ namespace ///SpecialRisingSpellMechanics ESpellCastProblem::ESpellCastProblem SpecialRisingSpellMechanics::isImmuneByStack(const CGHeroInstance* caster, ECastingMode::ECastingMode mode, const CStack* obj) { -// // following does apply to resurrect and animate dead(?) only -// // for sacrifice health calculation and health limit check don't matter -// -// if(obj->count >= obj->baseAmount) -// return ESpellCastProblem::STACK_IMMUNE_TO_SPELL; -// -// if (caster) //FIXME: Archangels can cast immune stack -// { -// auto maxHealth = calculateHealedHP (caster, spell, obj); -// if (maxHealth < obj->MaxHealth()) //must be able to rise at least one full creature -// return ESpellCastProblem::STACK_IMMUNE_TO_SPELL; -// } + // following does apply to resurrect and animate dead(?) only + // for sacrifice health calculation and health limit check don't matter + + if(obj->count >= obj->baseAmount) + return ESpellCastProblem::STACK_IMMUNE_TO_SPELL; + + if (caster) //FIXME: Archangels can cast immune stack + { + auto maxHealth = calculateHealedHP (caster, obj); + if (maxHealth < obj->MaxHealth()) //must be able to rise at least one full creature + return ESpellCastProblem::STACK_IMMUNE_TO_SPELL; + } + + return CSpellMechanics::isImmuneByStack(caster,mode,obj); } @@ -321,6 +329,28 @@ ui32 CSpell::calculateBonus(ui32 baseDamage, const CGHeroInstance* caster, const return ret; } +ui32 CSpell::calculateHealedHP(const CGHeroInstance* caster, const CStack* stack, const CStack* sacrificedStack) const +{ +//todo: use Mechanics class + int healedHealth; + + if(!isHealingSpell()) + { + logGlobal->errorStream() << "calculateHealedHP called for nonhealing spell "<< name; + return 0; + } + + const int spellPowerSkill = caster->getPrimSkillLevel(PrimarySkill::SPELL_POWER); + const int levelPower = getPower(caster->getSpellSchoolLevel(this)); + + if (id == SpellID::SACRIFICE && sacrificedStack) + healedHealth = (spellPowerSkill + sacrificedStack->MaxHealth() + levelPower) * sacrificedStack->count; + else + healedHealth = spellPowerSkill * power + levelPower); //??? + healedHealth = calculateBonus(healedHealth, caster, stack); + return std::min(healedHealth, stack->MaxHealth() - stack->firstHPleft + (isRisingSpell() ? stack->baseAmount * stack->MaxHealth() : 0)); +} + std::vector CSpell::rangeInHexes(BattleHex centralHex, ui8 schoolLvl, ui8 side, bool *outDroppedHexes) const { @@ -474,6 +504,10 @@ bool CSpell::isNeutral() const return positiveness == NEUTRAL; } +bool CSpell::isHealingSpell() const +{ + return isRisingSpell() || (id == SpellID::CURE); +} bool CSpell::isRisingSpell() const { @@ -691,9 +725,15 @@ void CSpell::setupMechanics() break; case SpellID::DISPEL_HELPFUL_SPELLS: mechanics = new DispellHelpfulMechanics(this); - break; - default: - mechanics = new CSpellMechanics(this); + break; + case SpellID::SACRIFICE: + mechanics = new SacrificeMechanics(this); + break + default: + if(isRisingSpell()) + mechanics = new SpecialRisingSpellMechanics(this); + else + mechanics = new CSpellMechanics(this); break; } diff --git a/lib/CSpellHandler.h b/lib/CSpellHandler.h index 35472f7ff..e69c5c681 100644 --- a/lib/CSpellHandler.h +++ b/lib/CSpellHandler.h @@ -113,8 +113,9 @@ public: bool isNegative() const; bool isNeutral() const; - bool isRisingSpell() const; bool isDamageSpell() const; + bool isHealingSpell() const; + bool isRisingSpell() const; bool isOffensiveSpell() const; bool isSpecialSpell() const; @@ -130,6 +131,9 @@ public: //applying secondary skills ui32 calculateBonus(ui32 baseDamage, const CGHeroInstance * caster, const CStack * affectedCreature) const; + + ///calculate healed HP for all spells casted by hero + ui32 calculateHealedHP(const CGHeroInstance * caster, const CStack * stack, const CStack * sacrificedStack = nullptr) const; si32 getCost(const int skillLevel) const; diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index a3136a72f..7e8d0c245 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -4205,7 +4205,7 @@ void CGameHandler::handleSpellCasting( SpellID spellID, int spellLvl, BattleHex } - if (spell->isRisingSpell() || spell->id == SpellID::CURE) + if(spell->isHealingSpell()) { int hpGained = 0; if (stack) @@ -4233,7 +4233,7 @@ void CGameHandler::handleSpellCasting( SpellID spellID, int spellLvl, BattleHex hi.healedHP = gs->curB->calculateHealedHP(spell, usedSpellPower, spellLvl, attackedCre); //any typical spell (commander's cure or animate dead) } else - hi.healedHP = gs->curB->calculateHealedHP(caster, spell, attackedCre, gs->curB->battleGetStackByID(selectedStack)); //Casted by hero + hi.healedHP = spell->calculateHealedHP(caster, attackedCre, gs->curB->battleGetStackByID(selectedStack)); //Casted by hero hi.lowLevelResurrection = spellLvl <= 1; shr.healedStacks.push_back(hi); } From e4b726151d2eb6a84f1ca1cdee22066748dc65e7 Mon Sep 17 00:00:00 2001 From: AlexVinS Date: Wed, 12 Nov 2014 09:20:20 +0300 Subject: [PATCH 06/59] get rid of CBattleInfoCallback::battleStackIsImmune --- AI/BattleAI/BattleAI.cpp | 2 +- lib/CBattleCallback.cpp | 30 ++++-------------------------- lib/CBattleCallback.h | 4 ---- lib/CSpellHandler.cpp | 38 +++++++++++++++++++++++++++++++++----- server/CGameHandler.cpp | 4 ++-- 5 files changed, 40 insertions(+), 38 deletions(-) diff --git a/AI/BattleAI/BattleAI.cpp b/AI/BattleAI/BattleAI.cpp index 9fc2ead37..f5b2a608f 100644 --- a/AI/BattleAI/BattleAI.cpp +++ b/AI/BattleAI/BattleAI.cpp @@ -461,7 +461,7 @@ void CBattleAI::attemptCastingSpell() auto stacksSuffering = cb->getAffectedCreatures(ps.spell, skillLevel, playerID, ps.dest); vstd::erase_if(stacksSuffering, [&](const CStack * s) -> bool { - return ESpellCastProblem::OK != cb->battleStackIsImmune(hero, ps.spell, ECastingMode::HERO_CASTING, s); + return ESpellCastProblem::OK != ps.spell->isImmuneByStack(hero, ECastingMode::HERO_CASTING, s); }); if(stacksSuffering.empty()) diff --git a/lib/CBattleCallback.cpp b/lib/CBattleCallback.cpp index ddbb8bab4..ff02bf097 100644 --- a/lib/CBattleCallback.cpp +++ b/lib/CBattleCallback.cpp @@ -1582,7 +1582,7 @@ ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleIsImmune(const C for(auto s : stacks) { - ESpellCastProblem::ESpellCastProblem res = battleStackIsImmune(caster,spell,mode,s); + ESpellCastProblem::ESpellCastProblem res = spell->isImmuneByStack(caster,mode,s); if(res == ESpellCastProblem::OK) { @@ -1619,28 +1619,6 @@ ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleIsImmune(const C return ESpellCastProblem::OK; } -ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleStackIsImmune(const CGHeroInstance * caster, const CSpell * spell, ECastingMode::ECastingMode mode, const CStack * subject) const -{ - const auto immuneResult = spell->isImmuneByStack(caster, mode, subject); - - if (ESpellCastProblem::NOT_DECIDED != immuneResult) - return immuneResult; - - //TODO: move to spellhandler - if(spell->id == SpellID::HYPNOTIZE && caster) //do not resist hypnotize casted after attack, for example - { - //TODO: what with other creatures casting hypnotize, Faerie Dragons style? - ui64 subjectHealth = (subject->count - 1) * subject->MaxHealth() + subject->firstHPleft; - //apply 'damage' bonus for hypnotize, including hero specialty - ui64 maxHealth = spell->calculateBonus (caster->getPrimSkillLevel(PrimarySkill::SPELL_POWER) - * spell->power + spell->getPower(caster->getSpellSchoolLevel(spell)), caster, subject); - if (subjectHealth > maxHealth) - return ESpellCastProblem::STACK_IMMUNE_TO_SPELL; - } - - return ESpellCastProblem::OK; -} - ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCastThisSpell( PlayerColor player, const CSpell * spell, ECastingMode::ECastingMode mode ) const { RETURN_IF_NOT_BATTLE(ESpellCastProblem::INVALID); @@ -1683,7 +1661,7 @@ ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCastThisSpell auto stacks = spell->isNegative() ? battleAliveStacks(!side) : battleAliveStacks(); for(auto stack : stacks) { - if( ESpellCastProblem::OK == battleStackIsImmune(castingHero, spell, mode, stack)) + if(ESpellCastProblem::OK == spell->isImmuneByStack(castingHero, mode, stack)) { allStacksImmune = false; break; @@ -1727,7 +1705,7 @@ ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCastThisSpell for(const CStack * stack : battleGetAllStacks()) //dead stacks will be immune anyway { - bool immune = ESpellCastProblem::OK != battleStackIsImmune(caster, spell, mode, stack); + bool immune = ESpellCastProblem::OK != spell->isImmuneByStack(caster, mode, stack); bool casterStack = stack->owner == caster->getOwner(); if(spell->id == SpellID::SACRIFICE) @@ -1796,7 +1774,7 @@ std::vector CBattleInfoCallback::battleGetPossibleTargets(PlayerColor for(const CStack * stack : battleAliveStacks()) { - bool immune = ESpellCastProblem::OK != battleStackIsImmune(caster, spell, mode, stack); + bool immune = ESpellCastProblem::OK != spell->isImmuneByStack(caster, mode, stack); bool casterStack = stack->owner == caster->getOwner(); if(!immune) diff --git a/lib/CBattleCallback.h b/lib/CBattleCallback.h index c40d2c1c9..246a80a8b 100644 --- a/lib/CBattleCallback.h +++ b/lib/CBattleCallback.h @@ -290,10 +290,6 @@ public: SpellID getRandomBeneficialSpell(const CStack * subject) const; SpellID getRandomCastedSpell(const CStack * caster) const; //called at the beginning of turn for Faerie Dragon - //checks for creature immunity / anything that prevent casting *at given hex* - doesn't take into acount general problems such as not having spellbook or mana points etc. - ESpellCastProblem::ESpellCastProblem battleStackIsImmune(const CGHeroInstance * caster, const CSpell * spell, ECastingMode::ECastingMode mode, const CStack * subject) const; - - const CStack * getStackIf(std::function pred) const; si8 battleHasShootingPenalty(const CStack * stack, BattleHex destHex) diff --git a/lib/CSpellHandler.cpp b/lib/CSpellHandler.cpp index 97f315f5e..165f61d08 100644 --- a/lib/CSpellHandler.cpp +++ b/lib/CSpellHandler.cpp @@ -166,6 +166,13 @@ namespace ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, ECastingMode::ECastingMode mode, const CStack * obj) override; }; + class HypnotizeMechanics: public CSpellMechanics + { + public: + HypnotizeMechanics(CSpell * s): CSpellMechanics(s){}; + ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, ECastingMode::ECastingMode mode, const CStack * obj) override; + }; + ///all rising spells class RisingSpellMechanics: public CSpellMechanics { @@ -185,7 +192,7 @@ namespace class SacrificeMechanics: public RisingSpellMechanics { public: - + SacrificeMechanics(CSpell * s): RisingSpellMechanics(s){}; }; ///CloneMechanics @@ -239,6 +246,23 @@ namespace return CSpellMechanics::isImmuneByStack(caster,mode,obj); } + ///HypnotizeMechanics + ESpellCastProblem::ESpellCastProblem HypnotizeMechanics::isImmuneByStack(const CGHeroInstance* caster, ECastingMode::ECastingMode mode, const CStack* obj) + { + if(nullptr != caster) //do not resist hypnotize casted after attack, for example + { + //TODO: what with other creatures casting hypnotize, Faerie Dragons style? + ui64 subjectHealth = (obj->count - 1) * obj->MaxHealth() + obj->firstHPleft; + //apply 'damage' bonus for hypnotize, including hero specialty + ui64 maxHealth = owner->calculateBonus(caster->getPrimSkillLevel(PrimarySkill::SPELL_POWER) + * owner->power + owner->getPower(caster->getSpellSchoolLevel(owner)), caster, obj); + if (subjectHealth > maxHealth) + return ESpellCastProblem::STACK_IMMUNE_TO_SPELL; + } + return CSpellMechanics::isImmuneByStack(caster,mode,obj); + } + + ///SpecialRisingSpellMechanics ESpellCastProblem::ESpellCastProblem SpecialRisingSpellMechanics::isImmuneByStack(const CGHeroInstance* caster, ECastingMode::ECastingMode mode, const CStack* obj) { @@ -250,7 +274,7 @@ namespace if (caster) //FIXME: Archangels can cast immune stack { - auto maxHealth = calculateHealedHP (caster, obj); + auto maxHealth = owner->calculateHealedHP (caster, obj); if (maxHealth < obj->MaxHealth()) //must be able to rise at least one full creature return ESpellCastProblem::STACK_IMMUNE_TO_SPELL; } @@ -346,7 +370,7 @@ ui32 CSpell::calculateHealedHP(const CGHeroInstance* caster, const CStack* stack if (id == SpellID::SACRIFICE && sacrificedStack) healedHealth = (spellPowerSkill + sacrificedStack->MaxHealth() + levelPower) * sacrificedStack->count; else - healedHealth = spellPowerSkill * power + levelPower); //??? + healedHealth = spellPowerSkill * power + levelPower; //??? healedHealth = calculateBonus(healedHealth, caster, stack); return std::min(healedHealth, stack->MaxHealth() - stack->firstHPleft + (isRisingSpell() ? stack->baseAmount * stack->MaxHealth() : 0)); } @@ -684,7 +708,11 @@ ESpellCastProblem::ESpellCastProblem CSpell::isImmuneBy(const IBonusBearer* obj) ESpellCastProblem::ESpellCastProblem CSpell::isImmuneByStack(const CGHeroInstance* caster, ECastingMode::ECastingMode mode, const CStack* obj) const { - return mechanics->isImmuneByStack(caster,mode,obj); + const auto immuneResult = mechanics->isImmuneByStack(caster,mode,obj); + + if (ESpellCastProblem::NOT_DECIDED != immuneResult) + return immuneResult; + return ESpellCastProblem::OK; } @@ -728,7 +756,7 @@ void CSpell::setupMechanics() break; case SpellID::SACRIFICE: mechanics = new SacrificeMechanics(this); - break + break; default: if(isRisingSpell()) mechanics = new SpecialRisingSpellMechanics(this); diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 7e8d0c245..919ec4274 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -4040,7 +4040,7 @@ void CGameHandler::handleSpellCasting( SpellID spellID, int spellLvl, BattleHex } vstd::erase_if(attackedCres,[=](const CStack * s){ - return ESpellCastProblem::OK != gs->curB->battleStackIsImmune(caster,spell,mode,s); + return ESpellCastProblem::OK != spell->isImmuneByStack(caster,mode,s); }); for (auto cre : attackedCres) @@ -4453,7 +4453,7 @@ void CGameHandler::handleSpellCasting( SpellID spellID, int spellLvl, BattleHex { if(battleStack->owner == gs->curB->sides.at(casterSide).color) //get enemy stacks which can be affected by this spell { - if (ESpellCastProblem::OK == gs->curB->battleStackIsImmune(nullptr, spell, ECastingMode::MAGIC_MIRROR, battleStack)) + if (ESpellCastProblem::OK == spell->isImmuneByStack(nullptr, ECastingMode::MAGIC_MIRROR, battleStack)) mirrorTargets.push_back(battleStack); } } From d7800b834e1bd7ce6efe755b5a35c78dcab3741a Mon Sep 17 00:00:00 2001 From: AlexVinS Date: Wed, 12 Nov 2014 09:52:11 +0300 Subject: [PATCH 07/59] get rid of CBattleInfoCallback::calculateSpellDmg --- AI/BattleAI/BattleAI.cpp | 2 +- lib/CBattleCallback.cpp | 53 -------------------------------------- lib/CBattleCallback.h | 1 - lib/CGameInfoCallback.cpp | 7 +++-- lib/CSpellHandler.cpp | 54 +++++++++++++++++++++++++++++++++++++++ lib/CSpellHandler.h | 6 ++++- server/CGameHandler.cpp | 6 ++--- 7 files changed, 66 insertions(+), 63 deletions(-) diff --git a/AI/BattleAI/BattleAI.cpp b/AI/BattleAI/BattleAI.cpp index f5b2a608f..6ead457d5 100644 --- a/AI/BattleAI/BattleAI.cpp +++ b/AI/BattleAI/BattleAI.cpp @@ -469,7 +469,7 @@ void CBattleAI::attemptCastingSpell() for(auto stack : stacksSuffering) { - const int dmg = cb->calculateSpellDmg(ps.spell, hero, stack, skillLevel, spellPower); + const int dmg = ps.spell->calculateDamage(hero, stack, skillLevel, spellPower); if(stack->owner == playerID) damageReceived += dmg; else diff --git a/lib/CBattleCallback.cpp b/lib/CBattleCallback.cpp index ff02bf097..d2ea2aafa 100644 --- a/lib/CBattleCallback.cpp +++ b/lib/CBattleCallback.cpp @@ -1908,59 +1908,6 @@ ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCastThisSpell return battleIsImmune(nullptr, spell, mode, dest); } -ui32 CBattleInfoCallback::calculateSpellDmg( const CSpell * sp, const CGHeroInstance * caster, const CStack * affectedCreature, int spellSchoolLevel, int usedSpellPower ) const -{ - ui32 ret = 0; //value to return - - //check if spell really does damage - if not, return 0 - if(!sp->isDamageSpell()) - return 0; - - ret = usedSpellPower * sp->power; - ret += sp->getPower(spellSchoolLevel); - - //affected creature-specific part - if(affectedCreature) - { - //applying protections - when spell has more then one elements, only one protection should be applied (I think) - if(sp->air && affectedCreature->hasBonusOfType(Bonus::SPELL_DAMAGE_REDUCTION, 0)) //air spell & protection from air - { - ret *= affectedCreature->valOfBonuses(Bonus::SPELL_DAMAGE_REDUCTION, 0); - ret /= 100; - } - else if(sp->fire && affectedCreature->hasBonusOfType(Bonus::SPELL_DAMAGE_REDUCTION, 1)) //fire spell & protection from fire - { - ret *= affectedCreature->valOfBonuses(Bonus::SPELL_DAMAGE_REDUCTION, 1); - ret /= 100; - } - else if(sp->water && affectedCreature->hasBonusOfType(Bonus::SPELL_DAMAGE_REDUCTION, 2)) //water spell & protection from water - { - ret *= affectedCreature->valOfBonuses(Bonus::SPELL_DAMAGE_REDUCTION, 2); - ret /= 100; - } - else if (sp->earth && affectedCreature->hasBonusOfType(Bonus::SPELL_DAMAGE_REDUCTION, 3)) //earth spell & protection from earth - { - ret *= affectedCreature->valOfBonuses(Bonus::SPELL_DAMAGE_REDUCTION, 3); - ret /= 100; - } - //general spell dmg reduction - //FIXME? - if(sp->air && affectedCreature->hasBonusOfType(Bonus::SPELL_DAMAGE_REDUCTION, -1)) - { - ret *= affectedCreature->valOfBonuses(Bonus::SPELL_DAMAGE_REDUCTION, -1); - ret /= 100; - } - //dmg increasing - if( affectedCreature->hasBonusOfType(Bonus::MORE_DAMAGE_FROM_SPELL, sp->id) ) - { - ret *= 100 + affectedCreature->valOfBonuses(Bonus::MORE_DAMAGE_FROM_SPELL, sp->id.toEnum()); - ret /= 100; - } - } - ret = sp->calculateBonus(ret, caster, affectedCreature); - return ret; -} - std::set CBattleInfoCallback::getAffectedCreatures(const CSpell * spell, int skillLevel, PlayerColor attackerOwner, BattleHex destinationTile) { std::set attackedCres; //std::set to exclude multiple occurrences of two hex creatures diff --git a/lib/CBattleCallback.h b/lib/CBattleCallback.h index 246a80a8b..a1fae5373 100644 --- a/lib/CBattleCallback.h +++ b/lib/CBattleCallback.h @@ -281,7 +281,6 @@ public: ESpellCastProblem::ESpellCastProblem battleCanCastThisSpellHere(PlayerColor player, const CSpell * spell, ECastingMode::ECastingMode mode, BattleHex dest) const; //checks if given player can cast given spell at given tile in given mode ESpellCastProblem::ESpellCastProblem battleCanCreatureCastThisSpell(const CSpell * spell, BattleHex destination) const; //determines if creature can cast a spell here std::vector battleGetPossibleTargets(PlayerColor player, const CSpell *spell) const; - ui32 calculateSpellDmg(const CSpell * sp, const CGHeroInstance * caster, const CStack * affectedCreature, int spellSchoolLevel, int usedSpellPower) const; //calculates damage inflicted by spell ui32 calculateHealedHP(int healedHealth, const CSpell * spell, const CStack * stack) const; //for Archangel ui32 calculateHealedHP(const CSpell * spell, int usedSpellPower, int spellSchoolLevel, const CStack * stack) const; //healing spells casted by stacks std::set getAffectedCreatures(const CSpell * s, int skillLevel, PlayerColor attackerOwner, BattleHex destinationTile); //calculates stack affected by given spell diff --git a/lib/CGameInfoCallback.cpp b/lib/CGameInfoCallback.cpp index 7bd3e5137..9448e837c 100644 --- a/lib/CGameInfoCallback.cpp +++ b/lib/CGameInfoCallback.cpp @@ -17,6 +17,7 @@ #include "BattleState.h" // for BattleInfo #include "NetPacks.h" // for InfoWindow #include "CModHandler.h" +#include "CSpellHandler.h" //TODO make clean #define ERROR_VERBOSE_OR_NOT_RET_VAL_IF(cond, verbose, txt, retVal) do {if(cond){if(verbose)logGlobal->errorStream() << BOOST_CURRENT_FUNCTION << ": " << txt; return retVal;}} while(0) @@ -173,16 +174,14 @@ int CGameInfoCallback::estimateSpellDamage(const CSpell * sp, const CGHeroInstan if(!gs->curB) //no battle { if (hero) //but we see hero's spellbook - return gs->curB->calculateSpellDmg( - sp, hero, nullptr, hero->getSpellSchoolLevel(sp), hero->getPrimSkillLevel(PrimarySkill::SPELL_POWER)); + return sp->calculateDamage(hero, nullptr, hero->getSpellSchoolLevel(sp), hero->getPrimSkillLevel(PrimarySkill::SPELL_POWER)); else return 0; //mage guild } //gs->getHero(gs->currentPlayer) //const CGHeroInstance * ourHero = gs->curB->heroes[0]->tempOwner == player ? gs->curB->heroes[0] : gs->curB->heroes[1]; const CGHeroInstance * ourHero = hero; - return gs->curB->calculateSpellDmg( - sp, ourHero, nullptr, ourHero->getSpellSchoolLevel(sp), ourHero->getPrimSkillLevel(PrimarySkill::SPELL_POWER)); + return sp->calculateDamage(ourHero, nullptr, ourHero->getSpellSchoolLevel(sp), ourHero->getPrimSkillLevel(PrimarySkill::SPELL_POWER)); } void CGameInfoCallback::getThievesGuildInfo(SThievesGuildInfo & thi, const CGObjectInstance * obj) diff --git a/lib/CSpellHandler.cpp b/lib/CSpellHandler.cpp index 165f61d08..749eeaa3f 100644 --- a/lib/CSpellHandler.cpp +++ b/lib/CSpellHandler.cpp @@ -353,6 +353,60 @@ ui32 CSpell::calculateBonus(ui32 baseDamage, const CGHeroInstance* caster, const return ret; } +ui32 CSpell::calculateDamage(const CGHeroInstance * caster, const CStack * affectedCreature, int spellSchoolLevel, int usedSpellPower) const +{ + ui32 ret = 0; //value to return + + //check if spell really does damage - if not, return 0 + if(!isDamageSpell()) + return 0; + + ret = usedSpellPower * power; + ret += getPower(spellSchoolLevel); + + //affected creature-specific part + if(nullptr != affectedCreature) + { + //applying protections - when spell has more then one elements, only one protection should be applied (I think) + if(air && affectedCreature->hasBonusOfType(Bonus::SPELL_DAMAGE_REDUCTION, 0)) //air spell & protection from air + { + ret *= affectedCreature->valOfBonuses(Bonus::SPELL_DAMAGE_REDUCTION, 0); + ret /= 100; + } + else if(fire && affectedCreature->hasBonusOfType(Bonus::SPELL_DAMAGE_REDUCTION, 1)) //fire spell & protection from fire + { + ret *= affectedCreature->valOfBonuses(Bonus::SPELL_DAMAGE_REDUCTION, 1); + ret /= 100; + } + else if(water && affectedCreature->hasBonusOfType(Bonus::SPELL_DAMAGE_REDUCTION, 2)) //water spell & protection from water + { + ret *= affectedCreature->valOfBonuses(Bonus::SPELL_DAMAGE_REDUCTION, 2); + ret /= 100; + } + else if (earth && affectedCreature->hasBonusOfType(Bonus::SPELL_DAMAGE_REDUCTION, 3)) //earth spell & protection from earth + { + ret *= affectedCreature->valOfBonuses(Bonus::SPELL_DAMAGE_REDUCTION, 3); + ret /= 100; + } + //general spell dmg reduction + //FIXME? + if(air && affectedCreature->hasBonusOfType(Bonus::SPELL_DAMAGE_REDUCTION, -1)) + { + ret *= affectedCreature->valOfBonuses(Bonus::SPELL_DAMAGE_REDUCTION, -1); + ret /= 100; + } + //dmg increasing + if(affectedCreature->hasBonusOfType(Bonus::MORE_DAMAGE_FROM_SPELL, id)) + { + ret *= 100 + affectedCreature->valOfBonuses(Bonus::MORE_DAMAGE_FROM_SPELL, id.toEnum()); + ret /= 100; + } + } + ret = calculateBonus(ret, caster, affectedCreature); + return ret; +} + + ui32 CSpell::calculateHealedHP(const CGHeroInstance* caster, const CStack* stack, const CStack* sacrificedStack) const { //todo: use Mechanics class diff --git a/lib/CSpellHandler.h b/lib/CSpellHandler.h index e69c5c681..035ff97bd 100644 --- a/lib/CSpellHandler.h +++ b/lib/CSpellHandler.h @@ -129,11 +129,15 @@ public: //checks for creature immunity / anything that prevent casting *at given hex* - doesn't take into acount general problems such as not having spellbook or mana points etc. ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, ECastingMode::ECastingMode mode, const CStack * obj) const; - //applying secondary skills + //internal, for use only by Mechanics classes. applying secondary skills ui32 calculateBonus(ui32 baseDamage, const CGHeroInstance * caster, const CStack * affectedCreature) const; + ui32 calculateDamage(const CGHeroInstance * caster, const CStack * affectedCreature, int spellSchoolLevel, int usedSpellPower) const; + ///calculate healed HP for all spells casted by hero ui32 calculateHealedHP(const CGHeroInstance * caster, const CStack * stack, const CStack * sacrificedStack = nullptr) const; + + si32 getCost(const int skillLevel) const; diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 919ec4274..9b30afa43 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -4105,7 +4105,7 @@ void CGameHandler::handleSpellCasting( SpellID spellID, int spellLvl, BattleHex if (spellDamage) bsa.damageAmount = spellDamage >> chainLightningModifier; else - bsa.damageAmount = gs->curB->calculateSpellDmg(spell, caster, attackedCre, spellLvl, usedSpellPower) >> chainLightningModifier; + bsa.damageAmount = spell->calculateDamage(caster, attackedCre, spellLvl, usedSpellPower) >> chainLightningModifier; sc.dmgToDisplay += bsa.damageAmount; @@ -4710,14 +4710,14 @@ void CGameHandler::handleDamageFromObstacle(const CObstacleInstance &obstacle, c oneTimeObstacle = true; effect = 82; //makes - damage = gs->curB->calculateSpellDmg(SpellID(SpellID::LAND_MINE).toSpell(), hero, curStack, + damage = SpellID(SpellID::LAND_MINE).toSpell()->calculateDamage(hero, curStack, spellObstacle->spellLevel, spellObstacle->casterSpellPower); //TODO even if obstacle wasn't created by hero (Tower "moat") it should deal dmg as if casted by hero, //if it is bigger than default dmg. Or is it just irrelevant H3 implementation quirk } else if(obstacle.obstacleType == CObstacleInstance::FIRE_WALL) { - damage = gs->curB->calculateSpellDmg(SpellID(SpellID::FIRE_WALL).toSpell(), hero, curStack, + damage = SpellID(SpellID::FIRE_WALL).toSpell()->calculateDamage(hero, curStack, spellObstacle->spellLevel, spellObstacle->casterSpellPower); } else From 22178151aad9ec8966784f2a48271061789abe6b Mon Sep 17 00:00:00 2001 From: AlexVinS Date: Wed, 12 Nov 2014 11:03:55 +0300 Subject: [PATCH 08/59] More general spell school handling --- lib/CSpellHandler.cpp | 83 +++++++++++++++++++------------------------ lib/CSpellHandler.h | 52 ++++++++++++++++++++++++--- lib/GameConstants.h | 8 +++++ 3 files changed, 91 insertions(+), 52 deletions(-) diff --git a/lib/CSpellHandler.cpp b/lib/CSpellHandler.cpp index 749eeaa3f..84bf0ba4a 100644 --- a/lib/CSpellHandler.cpp +++ b/lib/CSpellHandler.cpp @@ -337,15 +337,15 @@ ui32 CSpell::calculateBonus(ui32 baseDamage, const CGHeroInstance* caster, const { ret *= (100.0 + caster->valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, SecondarySkill::SORCERY)) / 100.0; ret *= (100.0 + caster->valOfBonuses(Bonus::SPELL_DAMAGE) + caster->valOfBonuses(Bonus::SPECIFIC_SPELL_DAMAGE, id.toEnum())) / 100.0; - - if(air) - ret *= (100.0 + caster->valOfBonuses(Bonus::AIR_SPELL_DMG_PREMY)) / 100.0; - else if(fire) //only one type of bonus for Magic Arrow - ret *= (100.0 + caster->valOfBonuses(Bonus::FIRE_SPELL_DMG_PREMY)) / 100.0; - else if(water) - ret *= (100.0 + caster->valOfBonuses(Bonus::WATER_SPELL_DMG_PREMY)) / 100.0; - else if(earth) - ret *= (100.0 + caster->valOfBonuses(Bonus::EARTH_SPELL_DMG_PREMY)) / 100.0; + + for(const SpellSchoolInfo & cnf : spellSchoolConfig) + { + if(school.at(cnf.id)) + { + ret *= (100.0 + caster->valOfBonuses(cnf.damagePremyBonus)) / 100.0; + break; //only bonus from one school is used + } + } if (affectedCreature && affectedCreature->getCreature()->level) //Hero specials like Solmyr, Deemer ret *= (100. + ((caster->valOfBonuses(Bonus::SPECIAL_SPELL_LEV, id.toEnum()) * caster->level) / affectedCreature->getCreature()->level)) / 100.0; @@ -368,26 +368,17 @@ ui32 CSpell::calculateDamage(const CGHeroInstance * caster, const CStack * affec if(nullptr != affectedCreature) { //applying protections - when spell has more then one elements, only one protection should be applied (I think) - if(air && affectedCreature->hasBonusOfType(Bonus::SPELL_DAMAGE_REDUCTION, 0)) //air spell & protection from air + + for(const SpellSchoolInfo & cnf : spellSchoolConfig) { - ret *= affectedCreature->valOfBonuses(Bonus::SPELL_DAMAGE_REDUCTION, 0); - ret /= 100; - } - else if(fire && affectedCreature->hasBonusOfType(Bonus::SPELL_DAMAGE_REDUCTION, 1)) //fire spell & protection from fire - { - ret *= affectedCreature->valOfBonuses(Bonus::SPELL_DAMAGE_REDUCTION, 1); - ret /= 100; - } - else if(water && affectedCreature->hasBonusOfType(Bonus::SPELL_DAMAGE_REDUCTION, 2)) //water spell & protection from water - { - ret *= affectedCreature->valOfBonuses(Bonus::SPELL_DAMAGE_REDUCTION, 2); - ret /= 100; - } - else if (earth && affectedCreature->hasBonusOfType(Bonus::SPELL_DAMAGE_REDUCTION, 3)) //earth spell & protection from earth - { - ret *= affectedCreature->valOfBonuses(Bonus::SPELL_DAMAGE_REDUCTION, 3); - ret /= 100; - } + if(school.at(cnf.id) && affectedCreature->hasBonusOfType(Bonus::SPELL_DAMAGE_REDUCTION, (ui8)cnf.id)) + { + ret *= affectedCreature->valOfBonuses(Bonus::SPELL_DAMAGE_REDUCTION, (ui8)cnf.id); + ret /= 100; + break; //only bonus from one school is used + } + } + //general spell dmg reduction //FIXME? if(air && affectedCreature->hasBonusOfType(Bonus::SPELL_DAMAGE_REDUCTION, -1)) @@ -727,27 +718,14 @@ ESpellCastProblem::ESpellCastProblem CSpell::isImmuneBy(const IBonusBearer* obj) }; //6. Check elemental immunities - if(fire) + + for(const SpellSchoolInfo & cnf : spellSchoolConfig) { - if(battleTestElementalImmunity(Bonus::FIRE_IMMUNITY)) - return ESpellCastProblem::STACK_IMMUNE_TO_SPELL; - } - if(water) - { - if(battleTestElementalImmunity(Bonus::WATER_IMMUNITY)) - return ESpellCastProblem::STACK_IMMUNE_TO_SPELL; + if(school.at(cnf.id)) + if(battleTestElementalImmunity(cnf.immunityBonus)) + return ESpellCastProblem::STACK_IMMUNE_TO_SPELL; } - if(earth) - { - if(battleTestElementalImmunity(Bonus::EARTH_IMMUNITY)) - return ESpellCastProblem::STACK_IMMUNE_TO_SPELL; - } - if(air) - { - if(battleTestElementalImmunity(Bonus::AIR_IMMUNITY)) - return ESpellCastProblem::STACK_IMMUNE_TO_SPELL; - } TBonusListPtr levelImmunities = obj->getBonuses(Selector::type(Bonus::LEVEL_SPELL_IMMUNITY)); @@ -791,6 +769,17 @@ void CSpell::setIsRising(const bool val) } } +void CSpell::setup() +{ + setupMechanics(); + + school[ESpellSchool::AIR] = air; + school[ESpellSchool::FIRE] = fire; + school[ESpellSchool::WATER] = water; + school[ESpellSchool::EARTH] = earth; +} + + void CSpell::setupMechanics() { if(nullptr != mechanics) @@ -1164,7 +1153,7 @@ void CSpellHandler::afterLoadFinalization() for(auto & level: spell->levels) for(auto & bonus: level.effects) bonus.sid = spell->id; - spell->setupMechanics(); + spell->setup(); } } diff --git a/lib/CSpellHandler.h b/lib/CSpellHandler.h index 035ff97bd..c33adeede 100644 --- a/lib/CSpellHandler.h +++ b/lib/CSpellHandler.h @@ -23,6 +23,44 @@ class CSpell; class CGHeroInstance; class CStack; +struct SpellSchoolInfo +{ + ESpellSchool id; //backlink + Bonus::BonusType damagePremyBonus; + Bonus::BonusType immunityBonus; + std::string jsonName; +}; + +static SpellSchoolInfo spellSchoolConfig[4] = +{ + { + ESpellSchool::AIR, + Bonus::AIR_SPELL_DMG_PREMY, + Bonus::AIR_IMMUNITY, + "air" + }, + { + ESpellSchool::FIRE, + Bonus::FIRE_SPELL_DMG_PREMY, + Bonus::FIRE_IMMUNITY, + "fire" + }, + { + ESpellSchool::WATER, + Bonus::WATER_SPELL_DMG_PREMY, + Bonus::WATER_IMMUNITY, + "water" + }, + { + ESpellSchool::EARTH, + Bonus::EARTH_SPELL_DMG_PREMY, + Bonus::EARTH_IMMUNITY, + "earth" + } +}; + +class CPackForClient; + class DLL_LINKAGE CSpellMechanics { public: @@ -82,10 +120,13 @@ public: std::string name; si32 level; - bool earth; - bool water; - bool fire; - bool air; + bool earth; //deprecated + bool water; //deprecated + bool fire; //deprecated + bool air; //deprecated + + std::map school; //todo: use this instead of separate boolean fields + si32 power; //spell's power std::map probabilities; //% chance to gain for castles @@ -178,7 +219,7 @@ public: h & levels; if(!h.saving) - setupMechanics(); + setup(); } friend class CSpellHandler; friend class Graphics; @@ -188,6 +229,7 @@ private: void setIsRising(const bool val); //call this after load or deserialization. cant be done in constructor. + void setup(); void setupMechanics(); private: si32 defaultProbability; diff --git a/lib/GameConstants.h b/lib/GameConstants.h index 3cc78841e..3cc798a47 100644 --- a/lib/GameConstants.h +++ b/lib/GameConstants.h @@ -890,6 +890,14 @@ public: ESpellID num; }; +enum class ESpellSchool: ui8 +{ + AIR = 0, + FIRE = 1, + WATER = 2, + EARTH = 3 +}; + ID_LIKE_OPERATORS_DECLS(SpellID, SpellID::ESpellID) // Typedef declarations From 5ba53da9bf01812efdc88ddeaf845757cfaf9941 Mon Sep 17 00:00:00 2001 From: AlexVinS Date: Wed, 12 Nov 2014 11:36:34 +0300 Subject: [PATCH 09/59] Extract SpellMechanics to separate file --- lib/CMakeLists.txt | 1 + lib/CSpellHandler.cpp | 157 ++--------------------------------------- lib/CSpellHandler.h | 38 ++++++++-- lib/SpellMechanics.cpp | 112 +++++++++++++++++++++++++++++ lib/SpellMechanics.h | 66 +++++++++++++++++ lib/VCMI_lib.cbp | 2 + 6 files changed, 219 insertions(+), 157 deletions(-) create mode 100644 lib/SpellMechanics.cpp create mode 100644 lib/SpellMechanics.h diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index c079e09c7..125fd6a99 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -83,6 +83,7 @@ set(lib_SRCS VCMI_Lib.cpp VCMIDirs.cpp IHandlerBase.cpp + SpellMechanics.cpp IGameCallback.cpp CGameInfoCallback.cpp diff --git a/lib/CSpellHandler.cpp b/lib/CSpellHandler.cpp index 84bf0ba4a..7bf617114 100644 --- a/lib/CSpellHandler.cpp +++ b/lib/CSpellHandler.cpp @@ -13,6 +13,10 @@ #include "mapObjects/CGHeroInstance.h" #include "BattleState.h" +#include "SpellMechanics.h" + + + /* * CSpellHandler.cpp, part of VCMI engine * @@ -132,160 +136,13 @@ namespace SRSLPraserHelpers } -///CSpellMechanics -CSpellMechanics::CSpellMechanics(CSpell * s): +///ISpellMechanics +ISpellMechanics::ISpellMechanics(CSpell * s): owner(s) { } -CSpellMechanics::~CSpellMechanics() -{ - -} - -ESpellCastProblem::ESpellCastProblem CSpellMechanics::isImmuneByStack(const CGHeroInstance * caster, ECastingMode::ECastingMode mode, const CStack * obj) -{ - //by default use general algorithm - return owner->isImmuneBy(obj); -} - -namespace -{ - class CloneMechnics: public CSpellMechanics - { - public: - CloneMechnics(CSpell * s): CSpellMechanics(s){}; - ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, ECastingMode::ECastingMode mode, const CStack * obj) override; - }; - - class DispellHelpfulMechanics: public CSpellMechanics - { - public: - DispellHelpfulMechanics(CSpell * s): CSpellMechanics(s){}; - ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, ECastingMode::ECastingMode mode, const CStack * obj) override; - }; - - class HypnotizeMechanics: public CSpellMechanics - { - public: - HypnotizeMechanics(CSpell * s): CSpellMechanics(s){}; - ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, ECastingMode::ECastingMode mode, const CStack * obj) override; - }; - - ///all rising spells - class RisingSpellMechanics: public CSpellMechanics - { - public: - RisingSpellMechanics(CSpell * s): CSpellMechanics(s){}; - - }; - - ///all rising spells but SACRIFICE - class SpecialRisingSpellMechanics: public RisingSpellMechanics - { - public: - SpecialRisingSpellMechanics(CSpell * s): RisingSpellMechanics(s){}; - ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, ECastingMode::ECastingMode mode, const CStack * obj) override; - }; - - class SacrificeMechanics: public RisingSpellMechanics - { - public: - SacrificeMechanics(CSpell * s): RisingSpellMechanics(s){}; - }; - - ///CloneMechanics - ESpellCastProblem::ESpellCastProblem CloneMechnics::isImmuneByStack(const CGHeroInstance* caster, ECastingMode::ECastingMode mode, const CStack * obj) - { - //can't clone already cloned creature - if (vstd::contains(obj->state, EBattleStackState::CLONED)) - return ESpellCastProblem::STACK_IMMUNE_TO_SPELL; - //TODO: how about stacks casting Clone? - //currently Clone casted by stack is assumed Expert level - ui8 schoolLevel; - if (caster) - { - schoolLevel = caster->getSpellSchoolLevel(owner); - } - else - { - schoolLevel = 3; - } - - if (schoolLevel < 3) - { - int maxLevel = (std::max(schoolLevel, (ui8)1) + 4); - int creLevel = obj->getCreature()->level; - if (maxLevel < creLevel) //tier 1-5 for basic, 1-6 for advanced, any level for expert - return ESpellCastProblem::STACK_IMMUNE_TO_SPELL; - } - //use default algorithm only if there is no mechanics-related problem - return CSpellMechanics::isImmuneByStack(caster,mode,obj); - } - - ///DispellHelpfulMechanics - ESpellCastProblem::ESpellCastProblem DispellHelpfulMechanics::isImmuneByStack(const CGHeroInstance* caster, ECastingMode::ECastingMode mode, const CStack* obj) - { - TBonusListPtr spellBon = obj->getSpellBonuses(); - bool hasPositiveSpell = false; - for(const Bonus * b : *spellBon) - { - if(SpellID(b->sid).toSpell()->isPositive()) - { - hasPositiveSpell = true; - break; - } - } - if(!hasPositiveSpell) - { - return ESpellCastProblem::NO_SPELLS_TO_DISPEL; - } - - //use default algorithm only if there is no mechanics-related problem - return CSpellMechanics::isImmuneByStack(caster,mode,obj); - } - - ///HypnotizeMechanics - ESpellCastProblem::ESpellCastProblem HypnotizeMechanics::isImmuneByStack(const CGHeroInstance* caster, ECastingMode::ECastingMode mode, const CStack* obj) - { - if(nullptr != caster) //do not resist hypnotize casted after attack, for example - { - //TODO: what with other creatures casting hypnotize, Faerie Dragons style? - ui64 subjectHealth = (obj->count - 1) * obj->MaxHealth() + obj->firstHPleft; - //apply 'damage' bonus for hypnotize, including hero specialty - ui64 maxHealth = owner->calculateBonus(caster->getPrimSkillLevel(PrimarySkill::SPELL_POWER) - * owner->power + owner->getPower(caster->getSpellSchoolLevel(owner)), caster, obj); - if (subjectHealth > maxHealth) - return ESpellCastProblem::STACK_IMMUNE_TO_SPELL; - } - return CSpellMechanics::isImmuneByStack(caster,mode,obj); - } - - - ///SpecialRisingSpellMechanics - ESpellCastProblem::ESpellCastProblem SpecialRisingSpellMechanics::isImmuneByStack(const CGHeroInstance* caster, ECastingMode::ECastingMode mode, const CStack* obj) - { - // following does apply to resurrect and animate dead(?) only - // for sacrifice health calculation and health limit check don't matter - - if(obj->count >= obj->baseAmount) - return ESpellCastProblem::STACK_IMMUNE_TO_SPELL; - - if (caster) //FIXME: Archangels can cast immune stack - { - auto maxHealth = owner->calculateHealedHP (caster, obj); - if (maxHealth < obj->MaxHealth()) //must be able to rise at least one full creature - return ESpellCastProblem::STACK_IMMUNE_TO_SPELL; - } - - return CSpellMechanics::isImmuneByStack(caster,mode,obj); - } - - -} - - ///CSpell::LevelInfo CSpell::LevelInfo::LevelInfo() :description(""),cost(0),power(0),AIValue(0),smartTarget(true),range("0") @@ -804,7 +661,7 @@ void CSpell::setupMechanics() if(isRisingSpell()) mechanics = new SpecialRisingSpellMechanics(this); else - mechanics = new CSpellMechanics(this); + mechanics = new DefaultSpellMechanics(this); break; } diff --git a/lib/CSpellHandler.h b/lib/CSpellHandler.h index c33adeede..eb5d3e930 100644 --- a/lib/CSpellHandler.h +++ b/lib/CSpellHandler.h @@ -23,6 +23,8 @@ class CSpell; class CGHeroInstance; class CStack; +struct CPackForClient; + struct SpellSchoolInfo { ESpellSchool id; //backlink @@ -59,15 +61,37 @@ static SpellSchoolInfo spellSchoolConfig[4] = } }; -class CPackForClient; - -class DLL_LINKAGE CSpellMechanics +///callback to be provided by server +class DLL_LINKAGE SpellCastEnvironment { public: - CSpellMechanics(CSpell * s); - virtual ~CSpellMechanics(); + virtual void sendAndApply(CPackForClient * info) = 0; +}; + +///helper struct +struct DLL_LINKAGE SpellCastContext +{ +public: + SpellCastEnvironment * env; +}; + +class DLL_LINKAGE ISpellMechanics +{ +public: + ISpellMechanics(CSpell * s); + virtual ~ISpellMechanics(){}; + + virtual ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, ECastingMode::ECastingMode mode, const CStack * obj) = 0; + + /** \brief + * + * \param + * \return true if no error + * + */ + virtual bool adventureCast(SpellCastContext & context) = 0; + virtual bool battleCast(SpellCastContext & context) = 0; - virtual ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, ECastingMode::ECastingMode mode, const CStack * obj); protected: CSpell * owner; }; @@ -262,7 +286,7 @@ private: std::vector levels; - CSpellMechanics * mechanics;//(!) do not serialize + ISpellMechanics * mechanics;//(!) do not serialize }; diff --git a/lib/SpellMechanics.cpp b/lib/SpellMechanics.cpp new file mode 100644 index 000000000..8b84734a0 --- /dev/null +++ b/lib/SpellMechanics.cpp @@ -0,0 +1,112 @@ +/* + * SpellMechanics.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#include "StdInc.h" +#include "SpellMechanics.h" + +#include "mapObjects/CGHeroInstance.h" +#include "BattleState.h" + +///DefaultSpellMechanics +ESpellCastProblem::ESpellCastProblem DefaultSpellMechanics::isImmuneByStack(const CGHeroInstance * caster, ECastingMode::ECastingMode mode, const CStack * obj) +{ + //by default use general algorithm + return owner->isImmuneBy(obj); +} + +///CloneMechanics +ESpellCastProblem::ESpellCastProblem CloneMechnics::isImmuneByStack(const CGHeroInstance* caster, ECastingMode::ECastingMode mode, const CStack * obj) +{ + //can't clone already cloned creature + if (vstd::contains(obj->state, EBattleStackState::CLONED)) + return ESpellCastProblem::STACK_IMMUNE_TO_SPELL; + //TODO: how about stacks casting Clone? + //currently Clone casted by stack is assumed Expert level + ui8 schoolLevel; + if (caster) + { + schoolLevel = caster->getSpellSchoolLevel(owner); + } + else + { + schoolLevel = 3; + } + + if (schoolLevel < 3) + { + int maxLevel = (std::max(schoolLevel, (ui8)1) + 4); + int creLevel = obj->getCreature()->level; + if (maxLevel < creLevel) //tier 1-5 for basic, 1-6 for advanced, any level for expert + return ESpellCastProblem::STACK_IMMUNE_TO_SPELL; + } + //use default algorithm only if there is no mechanics-related problem + return DefaultSpellMechanics::isImmuneByStack(caster,mode,obj); +} + +///DispellHelpfulMechanics +ESpellCastProblem::ESpellCastProblem DispellHelpfulMechanics::isImmuneByStack(const CGHeroInstance* caster, ECastingMode::ECastingMode mode, const CStack* obj) +{ + TBonusListPtr spellBon = obj->getSpellBonuses(); + bool hasPositiveSpell = false; + for(const Bonus * b : *spellBon) + { + if(SpellID(b->sid).toSpell()->isPositive()) + { + hasPositiveSpell = true; + break; + } + } + if(!hasPositiveSpell) + { + return ESpellCastProblem::NO_SPELLS_TO_DISPEL; + } + + //use default algorithm only if there is no mechanics-related problem + return DefaultSpellMechanics::isImmuneByStack(caster,mode,obj); +} + +///HypnotizeMechanics +ESpellCastProblem::ESpellCastProblem HypnotizeMechanics::isImmuneByStack(const CGHeroInstance* caster, ECastingMode::ECastingMode mode, const CStack* obj) +{ + if(nullptr != caster) //do not resist hypnotize casted after attack, for example + { + //TODO: what with other creatures casting hypnotize, Faerie Dragons style? + ui64 subjectHealth = (obj->count - 1) * obj->MaxHealth() + obj->firstHPleft; + //apply 'damage' bonus for hypnotize, including hero specialty + ui64 maxHealth = owner->calculateBonus(caster->getPrimSkillLevel(PrimarySkill::SPELL_POWER) + * owner->power + owner->getPower(caster->getSpellSchoolLevel(owner)), caster, obj); + if (subjectHealth > maxHealth) + return ESpellCastProblem::STACK_IMMUNE_TO_SPELL; + } + return DefaultSpellMechanics::isImmuneByStack(caster,mode,obj); +} + + +///SpecialRisingSpellMechanics +ESpellCastProblem::ESpellCastProblem SpecialRisingSpellMechanics::isImmuneByStack(const CGHeroInstance* caster, ECastingMode::ECastingMode mode, const CStack* obj) +{ + // following does apply to resurrect and animate dead(?) only + // for sacrifice health calculation and health limit check don't matter + + if(obj->count >= obj->baseAmount) + return ESpellCastProblem::STACK_IMMUNE_TO_SPELL; + + if (caster) //FIXME: Archangels can cast immune stack + { + auto maxHealth = owner->calculateHealedHP (caster, obj); + if (maxHealth < obj->MaxHealth()) //must be able to rise at least one full creature + return ESpellCastProblem::STACK_IMMUNE_TO_SPELL; + } + + return DefaultSpellMechanics::isImmuneByStack(caster,mode,obj); +} + + + diff --git a/lib/SpellMechanics.h b/lib/SpellMechanics.h new file mode 100644 index 000000000..a5a136911 --- /dev/null +++ b/lib/SpellMechanics.h @@ -0,0 +1,66 @@ +/* + * SpellMechanics.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#pragma once + +#include "CSpellHandler.h" + +class DefaultSpellMechanics: public ISpellMechanics +{ +public: + DefaultSpellMechanics(CSpell * s): ISpellMechanics(s){}; + ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, ECastingMode::ECastingMode mode, const CStack * obj) override; + + bool adventureCast(SpellCastContext & context) override; + bool battleCast(SpellCastContext & context) override; +}; + +class CloneMechnics: public DefaultSpellMechanics +{ +public: + CloneMechnics(CSpell * s): DefaultSpellMechanics(s){}; + ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, ECastingMode::ECastingMode mode, const CStack * obj) override; +}; + +class DispellHelpfulMechanics: public DefaultSpellMechanics +{ +public: + DispellHelpfulMechanics(CSpell * s): DefaultSpellMechanics(s){}; + ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, ECastingMode::ECastingMode mode, const CStack * obj) override; +}; + +class HypnotizeMechanics: public DefaultSpellMechanics +{ +public: + HypnotizeMechanics(CSpell * s): DefaultSpellMechanics(s){}; + ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, ECastingMode::ECastingMode mode, const CStack * obj) override; +}; + +///all rising spells +class RisingSpellMechanics: public DefaultSpellMechanics +{ +public: + RisingSpellMechanics(CSpell * s): DefaultSpellMechanics(s){}; + +}; + +///all rising spells but SACRIFICE +class SpecialRisingSpellMechanics: public RisingSpellMechanics +{ +public: + SpecialRisingSpellMechanics(CSpell * s): RisingSpellMechanics(s){}; + ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, ECastingMode::ECastingMode mode, const CStack * obj) override; +}; + +class SacrificeMechanics: public RisingSpellMechanics +{ +public: + SacrificeMechanics(CSpell * s): RisingSpellMechanics(s){}; +}; diff --git a/lib/VCMI_lib.cbp b/lib/VCMI_lib.cbp index 5090a46b3..772904f6c 100644 --- a/lib/VCMI_lib.cbp +++ b/lib/VCMI_lib.cbp @@ -185,6 +185,8 @@ + +