diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 07a3f7b41..01ad4ae27 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -96,11 +96,14 @@ set(lib_SRCS serializer/JsonSerializeFormat.cpp serializer/JsonSerializer.cpp + spells/AbilityCaster.cpp spells/AdventureSpellMechanics.cpp spells/BattleSpellMechanics.cpp + spells/BonusCaster.cpp spells/CSpellHandler.cpp spells/ISpellMechanics.cpp spells/Problem.cpp + spells/ProxyCaster.cpp spells/TargetCondition.cpp spells/ViewSpellInt.cpp @@ -258,13 +261,16 @@ set(lib_HEADERS serializer/JsonSerializeFormat.h serializer/JsonSerializer.h + spells/AbilityCaster.h spells/AdventureSpellMechanics.h spells/BattleSpellMechanics.h + spells/BonusCaster.h spells/CSpellHandler.h spells/ISpellMechanics.h spells/Magic.h spells/SpellMechanics.h spells/Problem.h + spells/ProxyCaster.h spells/TargetCondition.h spells/ViewSpellInt.h diff --git a/lib/VCMI_lib.cbp b/lib/VCMI_lib.cbp index 1d024e648..7f7d44a61 100644 --- a/lib/VCMI_lib.cbp +++ b/lib/VCMI_lib.cbp @@ -381,10 +381,14 @@ + + + + @@ -392,6 +396,8 @@ + + diff --git a/lib/battle/CUnitState.cpp b/lib/battle/CUnitState.cpp index 14198d108..fabf9482c 100644 --- a/lib/battle/CUnitState.cpp +++ b/lib/battle/CUnitState.cpp @@ -606,7 +606,7 @@ void CUnitState::getCasterName(MetaString & text) const addNameReplacement(text, true); } -void CUnitState::getCastDescription(const spells::Spell * spell, MetaString & text) const +void CUnitState::getCastDescription(const spells::Spell * spell, const std::vector & attacked, MetaString & text) const { text.addTxt(MetaString::GENERAL_TXT, 565);//The %s casts %s //todo: use text 566 for single creature @@ -614,11 +614,6 @@ void CUnitState::getCastDescription(const spells::Spell * spell, MetaString & te text.addReplacement(MetaString::SPELL_NAME, spell->getIndex()); } -void CUnitState::getCastDescription(const spells::Spell * spell, const std::vector & attacked, MetaString & text) const -{ - getCastDescription(spell, text); -} - bool CUnitState::ableToRetaliate() const { return alive() diff --git a/lib/battle/CUnitState.h b/lib/battle/CUnitState.h index 2054c50ce..0ae2bdcf4 100644 --- a/lib/battle/CUnitState.h +++ b/lib/battle/CUnitState.h @@ -226,7 +226,6 @@ public: const PlayerColor getOwner() const override; void getCasterName(MetaString & text) const override; - void getCastDescription(const spells::Spell * spell, MetaString & text) const override; void getCastDescription(const spells::Spell * spell, const std::vector & attacked, MetaString & text) const override; bool ableToRetaliate() const override; diff --git a/lib/mapObjects/CGHeroInstance.cpp b/lib/mapObjects/CGHeroInstance.cpp index 606b7f8ed..f7a57ccf7 100644 --- a/lib/mapObjects/CGHeroInstance.cpp +++ b/lib/mapObjects/CGHeroInstance.cpp @@ -699,13 +699,6 @@ void CGHeroInstance::getCasterName(MetaString & text) const text.addReplacement(name); } -void CGHeroInstance::getCastDescription(const spells::Spell * spell, MetaString & text) const -{ - text.addTxt(MetaString::GENERAL_TXT, 196); - getCasterName(text); - text.addReplacement(MetaString::SPELL_NAME, spell->getIndex()); -} - void CGHeroInstance::getCastDescription(const spells::Spell * spell, const std::vector & attacked, MetaString & text) const { const bool singleTarget = attacked.size() == 1; diff --git a/lib/mapObjects/CGHeroInstance.h b/lib/mapObjects/CGHeroInstance.h index b7037c890..7ccf140d2 100644 --- a/lib/mapObjects/CGHeroInstance.h +++ b/lib/mapObjects/CGHeroInstance.h @@ -247,7 +247,6 @@ public: const PlayerColor getOwner() const override; void getCasterName(MetaString & text) const override; - void getCastDescription(const spells::Spell * spell, MetaString & text) const override; void getCastDescription(const spells::Spell * spell, const std::vector & attacked, MetaString & text) const override; void spendMana(const spells::PacketSender * server, const int spellCost) const override; diff --git a/lib/spells/AbilityCaster.cpp b/lib/spells/AbilityCaster.cpp new file mode 100644 index 000000000..3127cf3c5 --- /dev/null +++ b/lib/spells/AbilityCaster.cpp @@ -0,0 +1,58 @@ +/* + * AbilityCaster.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 "AbilityCaster.h" + +#include "../battle/Unit.h" + +namespace spells +{ + +AbilityCaster::AbilityCaster(const battle::Unit * actualCaster_, int baseSpellLevel_) + : ProxyCaster(actualCaster_), + actualCaster(actualCaster_), + baseSpellLevel(baseSpellLevel_) +{ +} + +AbilityCaster::~AbilityCaster() = default; + +ui8 AbilityCaster::getSpellSchoolLevel(const Spell * spell, int * outSelectedSchool) const +{ + int skill = baseSpellLevel; + + if(spell->getLevel() > 0) + { + vstd::amax(skill, actualCaster->valOfBonuses(Bonus::MAGIC_SCHOOL_SKILL, 0)); + } + + vstd::amax(skill, 0); + vstd::amin(skill, 3); + + return static_cast(skill); //todo: unify spell school level type +} + +int AbilityCaster::getEffectLevel(const Spell * spell) const +{ + return getSpellSchoolLevel(spell); +} + +void AbilityCaster::getCastDescription(const Spell * spell, const std::vector & attacked, MetaString & text) const +{ + //do nothing +} + +void AbilityCaster::spendMana(const PacketSender * server, const int spellCost) const +{ + //do nothing +} + +} // namespace spells diff --git a/lib/spells/AbilityCaster.h b/lib/spells/AbilityCaster.h new file mode 100644 index 000000000..c3dd90b95 --- /dev/null +++ b/lib/spells/AbilityCaster.h @@ -0,0 +1,34 @@ +/* + * AbilityCaster.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 "ProxyCaster.h" + +namespace spells +{ + +class DLL_LINKAGE AbilityCaster : public ProxyCaster +{ +public: + AbilityCaster(const battle::Unit * actualCaster_, int baseSpellLevel_); + virtual ~AbilityCaster(); + + ui8 getSpellSchoolLevel(const Spell * spell, int * outSelectedSchool = nullptr) const override; + int getEffectLevel(const Spell * spell) const override; + void getCastDescription(const Spell * spell, const std::vector & attacked, MetaString & text) const override; + void spendMana(const PacketSender * server, const int spellCost) const override; + +private: + const battle::Unit * actualCaster; + int baseSpellLevel; +}; + +} // namespace spells diff --git a/lib/spells/BattleSpellMechanics.cpp b/lib/spells/BattleSpellMechanics.cpp index bf731e1db..f6e08650f 100644 --- a/lib/spells/BattleSpellMechanics.cpp +++ b/lib/spells/BattleSpellMechanics.cpp @@ -262,7 +262,8 @@ void BattleSpellMechanics::cast(const PacketSender * server, vstd::RNG & rng, co { MetaString line; caster->getCastDescription(owner, affectedUnits, line); - sc.battleLog.push_back(line); + if(!line.message.empty()) + sc.battleLog.push_back(line); } break; diff --git a/lib/spells/BonusCaster.cpp b/lib/spells/BonusCaster.cpp new file mode 100644 index 000000000..05ca2db2a --- /dev/null +++ b/lib/spells/BonusCaster.cpp @@ -0,0 +1,57 @@ +/* + * BonusCaster.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 "BonusCaster.h" + +#include "../NetPacksBase.h" +#include "../HeroBonus.h" +#include "../battle/Unit.h" + +namespace spells +{ + +BonusCaster::BonusCaster(const Caster * actualCaster_, std::shared_ptr bonus_) + : ProxyCaster(actualCaster_), + actualCaster(actualCaster_), + bonus(bonus_) +{ + +} + +BonusCaster::~BonusCaster() = default; + +void BonusCaster::getCasterName(MetaString & text) const +{ + if(!bonus->description.empty()) + text.addReplacement(bonus->description); + else + actualCaster->getCasterName(text); +} + +void BonusCaster::getCastDescription(const Spell * spell, const std::vector & attacked, MetaString & text) const +{ + const bool singleTarget = attacked.size() == 1; + const int textIndex = singleTarget ? 195 : 196; + + text.addTxt(MetaString::GENERAL_TXT, textIndex); + getCasterName(text); + text.addReplacement(MetaString::SPELL_NAME, spell->getIndex()); + if(singleTarget) + attacked.at(0)->addNameReplacement(text, true); +} + +void BonusCaster::spendMana(const PacketSender * server, const int spellCost) const +{ + logGlobal->error("Unexpected call to BonusCaster::spendMana"); +} + + +} // namespace spells diff --git a/lib/spells/BonusCaster.h b/lib/spells/BonusCaster.h new file mode 100644 index 000000000..79997bcc3 --- /dev/null +++ b/lib/spells/BonusCaster.h @@ -0,0 +1,36 @@ +/* + * BonusCaster.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 "ProxyCaster.h" + +struct Bonus; + +namespace spells +{ + +class DLL_LINKAGE BonusCaster : public ProxyCaster +{ +public: + BonusCaster(const Caster * actualCaster_, std::shared_ptr bonus_); + virtual ~BonusCaster(); + + void getCasterName(MetaString & text) const override; + void getCastDescription(const Spell * spell, const std::vector & attacked, MetaString & text) const override; + void spendMana(const PacketSender * server, const int spellCost) const override; + +private: + const Caster * actualCaster; + std::shared_ptr bonus; +}; + +} // namespace spells + diff --git a/lib/spells/CSpellHandler.cpp b/lib/spells/CSpellHandler.cpp index fd2dbc327..a16c58e13 100644 --- a/lib/spells/CSpellHandler.cpp +++ b/lib/spells/CSpellHandler.cpp @@ -250,6 +250,11 @@ int32_t CSpell::getIndex() const return id.toEnum(); } +int32_t CSpell::getLevel() const +{ + return level; +} + bool CSpell::isCombatSpell() const { return combatSpell; diff --git a/lib/spells/CSpellHandler.h b/lib/spells/CSpellHandler.h index e31c61275..44e4fd4a4 100644 --- a/lib/spells/CSpellHandler.h +++ b/lib/spells/CSpellHandler.h @@ -291,6 +291,7 @@ public: void forEachSchool(const std::function & cb) const override; int32_t getIndex() const override; + int32_t getLevel() const override; /** * Returns resource name of icon for SPELL_IMMUNITY bonus diff --git a/lib/spells/ISpellMechanics.cpp b/lib/spells/ISpellMechanics.cpp index aae895a6d..5a24bab3f 100644 --- a/lib/spells/ISpellMechanics.cpp +++ b/lib/spells/ISpellMechanics.cpp @@ -158,8 +158,7 @@ BattleCast::BattleCast(const CBattleInfoCallback * cb, const Caster * caster_, c cb(cb), caster(caster_), mode(mode_), - spellLvl(), - effectLevel(), + magicSkillLevel(), effectPower(), effectDuration(), effectValue(), @@ -174,8 +173,7 @@ BattleCast::BattleCast(const BattleCast & orig, const Caster * caster_) cb(orig.cb), caster(caster_), mode(Mode::MAGIC_MIRROR), - spellLvl(orig.spellLvl), - effectLevel(orig.effectLevel), + magicSkillLevel(orig.magicSkillLevel), effectPower(orig.effectPower), effectDuration(orig.effectDuration), effectValue(orig.effectValue), @@ -206,20 +204,9 @@ const CBattleInfoCallback * BattleCast::getBattle() const return cb; } -BattleCast::OptionalValue BattleCast::getEffectLevel() const +BattleCast::OptionalValue BattleCast::getSpellLevel() const { - if(effectLevel) - return effectLevel; - else - return spellLvl; -} - -BattleCast::OptionalValue BattleCast::getRangeLevel() const -{ - if(rangeLevel) - return rangeLevel; - else - return spellLvl; + return magicSkillLevel; } BattleCast::OptionalValue BattleCast::getEffectPower() const @@ -249,17 +236,7 @@ boost::logic::tribool BattleCast::isMassive() const void BattleCast::setSpellLevel(BattleCast::Value value) { - spellLvl = boost::make_optional(value); -} - -void BattleCast::setEffectLevel(BattleCast::Value value) -{ - effectLevel = boost::make_optional(value); -} - -void BattleCast::setRangeLevel(BattleCast::Value value) -{ - rangeLevel = boost::make_optional(value); + magicSkillLevel = boost::make_optional(value); } void BattleCast::setEffectPower(BattleCast::Value value) @@ -484,7 +461,7 @@ BaseMechanics::BaseMechanics(const IBattleCast * event) casterSide = cb->playerToSide(caster->getOwner()).get(); { - auto value = event->getRangeLevel(); + auto value = event->getSpellLevel(); if(value) rangeLevel = value.get(); else @@ -492,7 +469,7 @@ BaseMechanics::BaseMechanics(const IBattleCast * event) vstd::abetween(rangeLevel, 0, 3); } { - auto value = event->getEffectLevel(); + auto value = event->getSpellLevel(); if(value) effectLevel = value.get(); else diff --git a/lib/spells/ISpellMechanics.h b/lib/spells/ISpellMechanics.h index d9ec25432..a5f6ccb16 100644 --- a/lib/spells/ISpellMechanics.h +++ b/lib/spells/ISpellMechanics.h @@ -88,8 +88,7 @@ public: virtual const Caster * getCaster() const = 0; virtual const CBattleInfoCallback * getBattle() const = 0; - virtual OptionalValue getEffectLevel() const = 0; - virtual OptionalValue getRangeLevel() const = 0; + virtual OptionalValue getSpellLevel() const = 0; virtual OptionalValue getEffectPower() const = 0; virtual OptionalValue getEffectDuration() const = 0; @@ -123,8 +122,7 @@ public: const Caster * getCaster() const override; const CBattleInfoCallback * getBattle() const override; - OptionalValue getEffectLevel() const override; - OptionalValue getRangeLevel() const override; + OptionalValue getSpellLevel() const override; OptionalValue getEffectPower() const override; OptionalValue getEffectDuration() const override; @@ -135,8 +133,6 @@ public: boost::logic::tribool isMassive() const override; void setSpellLevel(Value value); - void setEffectLevel(Value value); - void setRangeLevel(Value value); void setEffectPower(Value value); void setEffectDuration(Value value); @@ -162,10 +158,8 @@ public: private: ///spell school level - OptionalValue spellLvl; + OptionalValue magicSkillLevel; - OptionalValue rangeLevel; - OptionalValue effectLevel; ///actual spell-power affecting effect values OptionalValue effectPower; ///actual spell-power affecting effect duration diff --git a/lib/spells/Magic.h b/lib/spells/Magic.h index 09a62214c..4ece8b1ce 100644 --- a/lib/spells/Magic.h +++ b/lib/spells/Magic.h @@ -41,8 +41,6 @@ enum class Mode { //ACTIVE, //todo: use HERO, //deprecated - AFTER_ATTACK, - BEFORE_ATTACK, MAGIC_MIRROR, CREATURE_ACTIVE, //deprecated ENCHANTER, @@ -92,6 +90,8 @@ public: virtual int32_t getIndex() const = 0; + virtual int32_t getLevel() const = 0; + virtual void forEachSchool(const std::function & cb) const = 0; }; @@ -129,7 +129,6 @@ public: virtual void getCasterName(MetaString & text) const = 0; ///full default text - virtual void getCastDescription(const Spell * spell, MetaString & text) const = 0; virtual void getCastDescription(const Spell * spell, const std::vector & attacked, MetaString & text) const = 0; virtual void spendMana(const PacketSender * server, const int spellCost) const = 0; diff --git a/lib/spells/ProxyCaster.cpp b/lib/spells/ProxyCaster.cpp new file mode 100644 index 000000000..5746dd643 --- /dev/null +++ b/lib/spells/ProxyCaster.cpp @@ -0,0 +1,82 @@ +/* + * ProxyCaster.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 "ProxyCaster.h" + +#include "../GameConstants.h" + +namespace spells +{ + +ProxyCaster::ProxyCaster(const Caster * actualCaster_) + : actualCaster(actualCaster_) +{ + +} + +ProxyCaster::~ProxyCaster() = default; + +ui8 ProxyCaster::getSpellSchoolLevel(const Spell * spell, int * outSelectedSchool) const +{ + return actualCaster->getSpellSchoolLevel(spell, outSelectedSchool); +} + +int ProxyCaster::getEffectLevel(const Spell * spell) const +{ + return actualCaster->getEffectLevel(spell); +} + +int64_t ProxyCaster::getSpellBonus(const Spell * spell, int64_t base, const battle::Unit * affectedStack) const +{ + return actualCaster->getSpellBonus(spell, base, affectedStack); +} + +int64_t ProxyCaster::getSpecificSpellBonus(const Spell * spell, int64_t base) const +{ + return actualCaster->getSpecificSpellBonus(spell, base); +} + +int ProxyCaster::getEffectPower(const Spell * spell) const +{ + return actualCaster->getEffectPower(spell); +} + +int ProxyCaster::getEnchantPower(const Spell * spell) const +{ + return actualCaster->getEnchantPower(spell); +} + +int64_t ProxyCaster::getEffectValue(const Spell * spell) const +{ + return actualCaster->getEffectValue(spell); +} + +const PlayerColor ProxyCaster::getOwner() const +{ + return actualCaster->getOwner(); +} + +void ProxyCaster::getCasterName(MetaString & text) const +{ + return actualCaster->getCasterName(text); +} + +void ProxyCaster::getCastDescription(const Spell * spell, const std::vector & attacked, MetaString & text) const +{ + actualCaster->getCastDescription(spell, attacked, text); +} + +void ProxyCaster::spendMana(const PacketSender * server, const int spellCost) const +{ + actualCaster->spendMana(server, spellCost); +} + +} diff --git a/lib/spells/ProxyCaster.h b/lib/spells/ProxyCaster.h new file mode 100644 index 000000000..febec8191 --- /dev/null +++ b/lib/spells/ProxyCaster.h @@ -0,0 +1,40 @@ +/* + * ProxyCaster.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 "Magic.h" + +namespace spells +{ + +class DLL_LINKAGE ProxyCaster : public Caster +{ +public: + ProxyCaster(const Caster * actualCaster_); + virtual ~ProxyCaster(); + + ui8 getSpellSchoolLevel(const Spell * spell, int * outSelectedSchool = nullptr) const override; + int getEffectLevel(const Spell * spell) const override; + int64_t getSpellBonus(const Spell * spell, int64_t base, const battle::Unit * affectedStack) const override; + int64_t getSpecificSpellBonus(const Spell * spell, int64_t base) const override; + int getEffectPower(const Spell * spell) const override; + int getEnchantPower(const Spell * spell) const override; + int64_t getEffectValue(const Spell * spell) const override; + const PlayerColor getOwner() const override; + void getCasterName(MetaString & text) const override; + void getCastDescription(const Spell * spell, const std::vector & attacked, MetaString & text) const override; + void spendMana(const PacketSender * server, const int spellCost) const override; + +private: + const Caster * actualCaster; +}; + +} // namespace spells diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 9f1c80f91..cd4c8ca1b 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -18,6 +18,8 @@ #include "../lib/CArtHandler.h" #include "../lib/CBuildingHandler.h" #include "../lib/CHeroHandler.h" +#include "../lib/spells/AbilityCaster.h" +#include "../lib/spells/BonusCaster.h" #include "../lib/spells/CSpellHandler.h" #include "../lib/spells/ISpellMechanics.h" #include "../lib/spells/Problem.h" @@ -141,11 +143,6 @@ public: logGlobal->error("Unexpected call to ObstacleCasterProxy::getCasterName"); } - void getCastDescription(const Spell * spell, MetaString & text) const override - { - logGlobal->error("Unexpected call to ObstacleCasterProxy::getCastDescription"); - } - void getCastDescription(const Spell * spell, const std::vector & attacked, MetaString & text) const override { logGlobal->error("Unexpected call to ObstacleCasterProxy::getCastDescription"); @@ -163,95 +160,6 @@ private: const SpellCreatedObstacle * obs; }; -class BonusCasterProxy : public Caster -{ -public: - BonusCasterProxy(const CGHeroInstance * hero_, std::shared_ptr bonus_) - : hero(hero_), - bonus(bonus_) - { - - } - - ~BonusCasterProxy() = default; - - ui8 getSpellSchoolLevel(const Spell * spell, int * outSelectedSchool = nullptr) const override - { - return hero->getSpellSchoolLevel(spell, outSelectedSchool); - } - - int getEffectLevel(const Spell * spell) const override - { - return hero->getEffectLevel(spell); - } - - int64_t getSpellBonus(const Spell * spell, int64_t base, const battle::Unit * affectedStack) const override - { - return hero->getSpellBonus(spell, base, affectedStack); - } - - int64_t getSpecificSpellBonus(const Spell * spell, int64_t base) const override - { - return hero->getSpecificSpellBonus(spell, base); - } - - int getEffectPower(const Spell * spell) const override - { - return hero->getEffectPower(spell); - } - - int getEnchantPower(const Spell * spell) const override - { - return hero->getEnchantPower(spell); - } - - int64_t getEffectValue(const Spell * spell) const override - { - return hero->getEffectValue(spell); - } - - const PlayerColor getOwner() const override - { - return hero->getOwner(); - } - - void getCasterName(MetaString & text) const override - { - if(!bonus->description.empty()) - text.addReplacement(bonus->description); - else - hero->getCasterName(text); - } - - void getCastDescription(const Spell * spell, MetaString & text) const override - { - text.addTxt(MetaString::GENERAL_TXT, 196); - getCasterName(text); - text.addReplacement(MetaString::SPELL_NAME, spell->getIndex()); - } - - void getCastDescription(const Spell * spell, const std::vector & attacked, MetaString & text) const override - { - const bool singleTarget = attacked.size() == 1; - const int textIndex = singleTarget ? 195 : 196; - - text.addTxt(MetaString::GENERAL_TXT, textIndex); - getCasterName(text); - text.addReplacement(MetaString::SPELL_NAME, spell->getIndex()); - if(singleTarget) - attacked.at(0)->addNameReplacement(text, true); - } - - void spendMana(const PacketSender * server, const int spellCost) const override - { - logGlobal->error("Unexpected call to BonusCasterProxy::spendMana"); - } - -private: - const CGHeroInstance * hero; - std::shared_ptr bonus; -}; - }// CondSh battleMadeAction(false); @@ -5530,10 +5438,8 @@ bool CGameHandler::dig(const CGHeroInstance *h) return true; } -void CGameHandler::attackCasting(bool ranged, Bonus::BonusType attackMode, const CStack * attacker, const CStack * defender) +void CGameHandler::attackCasting(bool ranged, Bonus::BonusType attackMode, const battle::Unit * attacker, const battle::Unit * defender) { - spells::Mode mode = (attackMode == Bonus::SPELL_AFTER_ATTACK) ? spells::Mode::AFTER_ATTACK : spells::Mode::BEFORE_ATTACK; - if(attacker->hasBonusOfType(attackMode)) { std::set spellsToCast; @@ -5563,7 +5469,9 @@ void CGameHandler::attackCasting(bool ranged, Bonus::BonusType attackMode, const vstd::amin(chance, 100); const CSpell * spell = SpellID(spellID).toSpell(); - if(!spell->canBeCastAt(gs->curB, mode, attacker, defender->getPosition())) + spells::AbilityCaster caster(attacker, spellLevel); + + if(!spell->canBeCastAt(gs->curB, spells::Mode::PASSIVE, &caster, defender->getPosition())) continue; //check if spell should be cast (probability handling) @@ -5573,8 +5481,7 @@ void CGameHandler::attackCasting(bool ranged, Bonus::BonusType attackMode, const //casting if(castMe) { - spells::BattleCast parameters(gs->curB, attacker, mode, spell); - parameters.setSpellLevel(spellLevel); + spells::BattleCast parameters(gs->curB, &caster, spells::Mode::PASSIVE, spell); parameters.aimToUnit(defender); parameters.cast(spellEnv); } @@ -5623,8 +5530,9 @@ void CGameHandler::handleAfterAttackCasting(bool ranged, const CStack * attacker //TODO: death stare was not originally available for multiple-hex attacks, but... const CSpell * spell = SpellID(SpellID::DEATH_STARE).toSpell(); - spells::BattleCast parameters(gs->curB, attacker, spells::Mode::AFTER_ATTACK, spell); - parameters.setSpellLevel(0); + spells::AbilityCaster caster(attacker, 0); + + spells::BattleCast parameters(gs->curB, &caster, spells::Mode::PASSIVE, spell); parameters.aimToUnit(defender); parameters.setEffectValue(staredCreatures); parameters.cast(spellEnv); @@ -5646,8 +5554,9 @@ void CGameHandler::handleAfterAttackCasting(bool ranged, const CStack * attacker { const CSpell * spell = SpellID(SpellID::ACID_BREATH_DAMAGE).toSpell(); - spells::BattleCast parameters(gs->curB, attacker, spells::Mode::AFTER_ATTACK, spell); - parameters.setSpellLevel(0); + spells::AbilityCaster caster(attacker, 0); + + spells::BattleCast parameters(gs->curB, &caster, spells::Mode::PASSIVE, spell); parameters.aimToUnit(defender); parameters.setEffectValue(acidDamage * attacker->getCount()); parameters.cast(spellEnv); @@ -6066,7 +5975,7 @@ void CGameHandler::runBattle() for (auto b : *bl) { - spells::BonusCasterProxy caster(h, b); + spells::BonusCaster caster(h, b); const CSpell * spell = SpellID(b->subtype).toSpell(); diff --git a/server/CGameHandler.h b/server/CGameHandler.h index 08d24f7d6..085d7d4d3 100644 --- a/server/CGameHandler.h +++ b/server/CGameHandler.h @@ -289,7 +289,7 @@ public: void newTurn(); void handleAttackBeforeCasting(bool ranged, const CStack * attacker, const CStack * defender); void handleAfterAttackCasting(bool ranged, const CStack * attacker, const CStack * defender); - void attackCasting(bool ranged, Bonus::BonusType attackMode, const CStack * attacker, const CStack * defender); + void attackCasting(bool ranged, Bonus::BonusType attackMode, const battle::Unit * attacker, const battle::Unit * defender); bool sacrificeArtifact(const IMarket * m, const CGHeroInstance * hero, const std::vector & slot); void spawnWanderingMonsters(CreatureID creatureID); void handleCheatCode(std::string & cheat, PlayerColor player, const CGHeroInstance * hero, const CGTownInstance * town, bool & cheated); diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 5ea5a69d1..25251f7c6 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -30,6 +30,7 @@ set(test_SRCS map/CMapFormatTest.cpp map/MapComparer.cpp + spells/AbilityCasterTest.cpp spells/TargetConditionTest.cpp spells/effects/EffectFixture.cpp diff --git a/test/Test.cbp b/test/Test.cbp index 6c47d5ad7..c7121f107 100644 --- a/test/Test.cbp +++ b/test/Test.cbp @@ -109,6 +109,7 @@ + diff --git a/test/game/CGameStateTest.cpp b/test/game/CGameStateTest.cpp index 5460acd20..b9a0a0e7b 100644 --- a/test/game/CGameStateTest.cpp +++ b/test/game/CGameStateTest.cpp @@ -26,6 +26,7 @@ #include "../../lib/spells/CSpellHandler.h" #include "../../lib/spells/ISpellMechanics.h" +#include "../../lib/spells/AbilityCaster.h" class CGameStateTest : public ::testing::Test, public SpellCastEnvironment, public MapListener { @@ -224,12 +225,14 @@ TEST_F(CGameStateTest, issue2765) { const CSpell * age = SpellID(SpellID::AGE).toSpell(); ASSERT_NE(age, nullptr); - //here tested ballista, but this applied to all war machines - spells::BattleCast cast(gameState->curB, att, spells::Mode::AFTER_ATTACK, age); - cast.aimToUnit(def); - cast.setSpellLevel(3); - EXPECT_FALSE(age->canBeCastAt(gameState->curB, spells::Mode::AFTER_ATTACK, att, def->getPosition())); + spells::AbilityCaster caster(att, 3); + + //here tested ballista, but this applied to all war machines + spells::BattleCast cast(gameState->curB, &caster, spells::Mode::PASSIVE, age); + cast.aimToUnit(def); + + EXPECT_FALSE(age->canBeCastAt(gameState->curB, spells::Mode::PASSIVE, &caster, def->getPosition())); EXPECT_TRUE(cast.castIfPossible(this));//should be possible, but with no effect (change to aimed cast check?) diff --git a/test/mock/mock_battle_Unit.h b/test/mock/mock_battle_Unit.h index 968379e73..3b26050c9 100644 --- a/test/mock/mock_battle_Unit.h +++ b/test/mock/mock_battle_Unit.h @@ -27,7 +27,6 @@ public: MOCK_CONST_METHOD1(getEffectValue, int64_t(const spells::Spell *)); MOCK_CONST_METHOD0(getOwner, const PlayerColor()); MOCK_CONST_METHOD1(getCasterName, void(MetaString &)); - MOCK_CONST_METHOD2(getCastDescription, void(const spells::Spell *, MetaString &)); MOCK_CONST_METHOD3(getCastDescription, void(const spells::Spell *, const std::vector &, MetaString &)); MOCK_CONST_METHOD2(spendMana, void(const spells::PacketSender *, const int)); diff --git a/test/mock/mock_spells_Spell.h b/test/mock/mock_spells_Spell.h index 006d1de62..52892e6ec 100644 --- a/test/mock/mock_spells_Spell.h +++ b/test/mock/mock_spells_Spell.h @@ -19,6 +19,7 @@ class SpellMock : public Spell { public: MOCK_CONST_METHOD0(getIndex, int32_t()); + MOCK_CONST_METHOD0(getLevel, int32_t()); MOCK_CONST_METHOD1(forEachSchool, void(const std::function &)); }; diff --git a/test/spells/AbilityCasterTest.cpp b/test/spells/AbilityCasterTest.cpp new file mode 100644 index 000000000..f7863f94a --- /dev/null +++ b/test/spells/AbilityCasterTest.cpp @@ -0,0 +1,84 @@ +/* + * AbilityCasterTest.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 "mock/mock_battle_Unit.h" +#include "mock/mock_BonusBearer.h" +#include "mock/mock_spells_Spell.h" + +#include "../../../lib/NetPacksBase.h" +#include "../../../lib/spells/AbilityCaster.h" + +namespace test +{ +using namespace ::spells; +using namespace ::testing; + + +class AbilityCasterTest : public Test +{ +public: + std::shared_ptr subject; + + StrictMock actualCaster; + BonusBearerMock casterBonuses; + StrictMock spellMock; + +protected: + void SetUp() override + { + ON_CALL(actualCaster, getAllBonuses(_, _, _, _)).WillByDefault(Invoke(&casterBonuses, &BonusBearerMock::getAllBonuses)); + ON_CALL(actualCaster, getTreeVersion()).WillByDefault(Invoke(&casterBonuses, &BonusBearerMock::getTreeVersion)); + } + + void setupSubject(int skillLevel) + { + subject = std::make_shared(&actualCaster, skillLevel); + } +}; + +TEST_F(AbilityCasterTest, NonMagicAbilityIgnoresBonuses) +{ + EXPECT_CALL(spellMock, getLevel()).WillRepeatedly(Return(0)); + setupSubject(1); + + EXPECT_EQ(subject->getSpellSchoolLevel(&spellMock), 1); +} + +TEST_F(AbilityCasterTest, MagicAbilityAffectedByGenericBonus) +{ + EXPECT_CALL(spellMock, getLevel()).WillRepeatedly(Return(1)); + + casterBonuses.addNewBonus(std::make_shared(Bonus::ONE_BATTLE, Bonus::MAGIC_SCHOOL_SKILL, Bonus::OTHER, 2, 0, 0)); + + EXPECT_CALL(actualCaster, getAllBonuses(_, _, _, _)).Times(AtLeast(1)); + EXPECT_CALL(actualCaster, getTreeVersion()).Times(AtLeast(0)); + + setupSubject(1); + + EXPECT_EQ(subject->getSpellSchoolLevel(&spellMock), 2); +} + +TEST_F(AbilityCasterTest, MagicAbilityIngoresSchoolBonus) +{ + EXPECT_CALL(spellMock, getLevel()).WillRepeatedly(Return(1)); + + casterBonuses.addNewBonus(std::make_shared(Bonus::ONE_BATTLE, Bonus::MAGIC_SCHOOL_SKILL, Bonus::OTHER, 2, 0, 1)); + + EXPECT_CALL(actualCaster, getAllBonuses(_, _, _, _)).Times(AtLeast(1)); + EXPECT_CALL(actualCaster, getTreeVersion()).Times(AtLeast(0)); + + setupSubject(1); + + EXPECT_EQ(subject->getSpellSchoolLevel(&spellMock), 1); +} + + +}