diff --git a/lib/spells/effects/Summon.cpp b/lib/spells/effects/Summon.cpp index c624148c0..b9a361f12 100644 --- a/lib/spells/effects/Summon.cpp +++ b/lib/spells/effects/Summon.cpp @@ -18,9 +18,11 @@ #include "../../NetPacks.h" #include "../../serializer/JsonSerializeFormat.h" +#include "../../CCreatureHandler.h" #include "../../CHeroHandler.h" #include "../../mapObjects/CGHeroInstance.h" + static const std::string EFFECT_NAME = "core:summon"; namespace spells @@ -34,7 +36,8 @@ Summon::Summon() : Effect(), creature(), permanent(false), - exclusive(true) + exclusive(true), + summonByHealth(false) { } @@ -52,41 +55,41 @@ void Summon::adjustTargetTypes(std::vector & types) const bool Summon::applicable(Problem & problem, const Mechanics * m) const { - if(!exclusive) - return true; - - //check if there are summoned elementals of other type - - auto otherSummoned = m->cb->battleGetUnitsIf([m, this](const battle::Unit * unit) + if(exclusive) { - return (unit->unitOwner() == m->getCasterColor()) - && (unit->unitSlot() == SlotID::SUMMONED_SLOT_PLACEHOLDER) - && (!unit->isClone()) - && (unit->creatureId() != creature); - }); + //check if there are summoned creatures of other type - if(!otherSummoned.empty()) - { - auto elemental = otherSummoned.front(); - - MetaString text; - text.addTxt(MetaString::GENERAL_TXT, 538); - - auto caster = dynamic_cast(m->caster); - if(caster) + auto otherSummoned = m->cb->battleGetUnitsIf([m, this](const battle::Unit * unit) { - text.addReplacement(caster->name); + return (unit->unitOwner() == m->getCasterColor()) + && (unit->unitSlot() == SlotID::SUMMONED_SLOT_PLACEHOLDER) + && (!unit->isClone()) + && (unit->creatureId() != creature); + }); - text.addReplacement(MetaString::CRE_PL_NAMES, elemental->creatureIndex()); + if(!otherSummoned.empty()) + { + auto elemental = otherSummoned.front(); - if(caster->type->sex) - text.addReplacement(MetaString::GENERAL_TXT, 540); - else - text.addReplacement(MetaString::GENERAL_TXT, 539); + MetaString text; + text.addTxt(MetaString::GENERAL_TXT, 538); + auto caster = dynamic_cast(m->caster); + if(caster) + { + text.addReplacement(caster->name); + + text.addReplacement(MetaString::CRE_PL_NAMES, elemental->creatureIndex()); + + if(caster->type->sex) + text.addReplacement(MetaString::GENERAL_TXT, 540); + else + text.addReplacement(MetaString::GENERAL_TXT, 539); + + } + problem.add(std::move(text), Problem::NORMAL); + return false; } - problem.add(std::move(text), Problem::NORMAL); - return false; } return true; @@ -95,12 +98,7 @@ bool Summon::applicable(Problem & problem, const Mechanics * m) const void Summon::apply(BattleStateProxy * battleState, RNG & rng, const Mechanics * m, const EffectTarget & target) const { //new feature - percentage bonus - auto amount = m->applySpecificSpellBonus(m->calculateRawEffectValue(0, m->getEffectPower())); - if(amount < 1) - { - battleState->complain("Summoning didn't summon any!"); - return; - } + auto valueWithBonus = m->applySpecificSpellBonus(m->calculateRawEffectValue(0, m->getEffectPower()));//TODO: consider use base power too BattleUnitsChanged pack; @@ -110,13 +108,32 @@ void Summon::apply(BattleStateProxy * battleState, RNG & rng, const Mechanics * { const battle::Unit * summoned = dest.unitValue; std::shared_ptr state = summoned->acquire(); - int64_t healthValue = amount * summoned->MaxHealth(); + int64_t healthValue = (summonByHealth ? valueWithBonus : (valueWithBonus * summoned->MaxHealth())); state->heal(healthValue, EHealLevel::OVERHEAL, (permanent ? EHealPower::PERMANENT : EHealPower::ONE_BATTLE)); pack.changedStacks.emplace_back(summoned->unitId(), UnitChanges::EOperation::RESET_STATE); state->save(pack.changedStacks.back().data); } else { + int32_t amount = 0; + + if(summonByHealth) + { + auto creatureType = creature.toCreature(); + auto creatureMaxHealth = creatureType->MaxHealth(); + amount = valueWithBonus / creatureMaxHealth; + } + else + { + amount = static_cast(valueWithBonus); + } + + if(amount < 1) + { + battleState->complain("Summoning didn't summon any!"); + continue; + } + battle::UnitInfo info; info.id = m->cb->battleNextUnitId(); info.count = amount; @@ -144,6 +161,7 @@ void Summon::serializeJsonEffect(JsonSerializeFormat & handler) handler.serializeId("id", creature, CreatureID()); handler.serializeBool("permanent", permanent, false); handler.serializeBool("exclusive", exclusive, true); + handler.serializeBool("summonByHealth", summonByHealth, false); } EffectTarget Summon::transformTarget(const Mechanics * m, const Target & aimPoint, const Target & spellTarget) const diff --git a/lib/spells/effects/Summon.h b/lib/spells/effects/Summon.h index cb3e9e6a4..b236b31eb 100644 --- a/lib/spells/effects/Summon.h +++ b/lib/spells/effects/Summon.h @@ -21,8 +21,6 @@ namespace effects class Summon : public Effect { public: - CreatureID creature; - Summon(); virtual ~Summon(); @@ -41,8 +39,11 @@ protected: void serializeJsonEffect(JsonSerializeFormat & handler) override final; private: + CreatureID creature; + bool permanent; bool exclusive; + bool summonByHealth; }; } diff --git a/test/spells/effects/SummonTest.cpp b/test/spells/effects/SummonTest.cpp index d9830c151..fac4bb118 100644 --- a/test/spells/effects/SummonTest.cpp +++ b/test/spells/effects/SummonTest.cpp @@ -13,6 +13,8 @@ #include +#include "../../../lib/CCreatureHandler.h" + namespace test { using namespace ::spells; @@ -133,20 +135,28 @@ INSTANTIATE_TEST_CASE_P ) ); -class SummonApplyTest : public TestWithParam, public EffectFixture +class SummonApplyTest : public TestWithParam<::testing::tuple>, public EffectFixture { public: CreatureID toSummon; bool permanent = false; + bool summonByHealth = false; + const uint32_t unitId = 42; const int32_t unitAmount = 123; + const int32_t unitHealth; + const int64_t unitTotalHealth; + + const BattleHex unitPosition = BattleHex(5,5); std::shared_ptr<::battle::UnitInfo> unitAddInfo; std::shared_ptr acquired; SummonApplyTest() : EffectFixture("core:summon"), - toSummon(creature1) + toSummon(creature1), + unitHealth(toSummon.toCreature()->MaxHealth()), + unitTotalHealth(unitAmount * unitHealth) { } @@ -155,7 +165,15 @@ public: InSequence local; EXPECT_CALL(mechanicsMock, getEffectPower()).WillOnce(Return(38)); EXPECT_CALL(mechanicsMock, calculateRawEffectValue(Eq(0), Eq(38))).WillOnce(Return(567)); - EXPECT_CALL(mechanicsMock, applySpecificSpellBonus(Eq(567))).WillOnce(Return(unitAmount)); + + if(summonByHealth) + { + EXPECT_CALL(mechanicsMock, applySpecificSpellBonus(Eq(567))).WillOnce(Return(unitTotalHealth)); + } + else + { + EXPECT_CALL(mechanicsMock, applySpecificSpellBonus(Eq(567))).WillOnce(Return(unitAmount)); + } } void onUnitAdded(uint32_t id, const JsonNode & data) @@ -168,11 +186,13 @@ protected: { EffectFixture::setUp(); - permanent = GetParam(); + permanent = ::testing::get<0>(GetParam()); + summonByHealth = ::testing::get<1>(GetParam()); JsonNode options(JsonNode::JsonType::DATA_STRUCT); options["id"].String() = "airElemental"; options["permanent"].Bool() = permanent; + options["summonByHealth"].Bool() = summonByHealth; EffectFixture::setupEffect(options); @@ -182,6 +202,7 @@ protected: void TearDown() override { acquired.reset(); + unitAddInfo.reset(); } }; @@ -189,20 +210,17 @@ TEST_P(SummonApplyTest, SpawnsNewUnit) { expectAmountCalculation(); - BattleHex position(5,5); - const uint32_t unitId = 42; - EXPECT_CALL(*battleFake, nextUnitId()).WillOnce(Return(unitId)); EXPECT_CALL(*battleFake, addUnit(Eq(unitId), _)).WillOnce(Invoke(this, &SummonApplyTest::onUnitAdded)); EffectTarget target; - target.emplace_back(position); + target.emplace_back(unitPosition); subject->apply(battleProxy.get(), rngMock, &mechanicsMock, target); EXPECT_EQ(unitAddInfo->count, unitAmount); EXPECT_EQ(unitAddInfo->id, unitId); - EXPECT_EQ(unitAddInfo->position, position); + EXPECT_EQ(unitAddInfo->position, unitPosition); EXPECT_EQ(unitAddInfo->side, mechanicsMock.casterSide); EXPECT_EQ(unitAddInfo->summoned, !permanent); EXPECT_EQ(unitAddInfo->type, toSummon); @@ -212,10 +230,6 @@ TEST_P(SummonApplyTest, UpdatesOldUnit) { expectAmountCalculation(); - BattleHex position(5,5); - const uint32_t unitId = 42; - const int32_t unitHealth = 234; - acquired = std::make_shared(); acquired->addNewBonus(std::make_shared(Bonus::PERMANENT, Bonus::STACK_HEALTH, Bonus::CREATURE_ABILITY, unitHealth, 0)); acquired->redirectBonusesToFake(); @@ -226,14 +240,13 @@ TEST_P(SummonApplyTest, UpdatesOldUnit) { EXPECT_CALL(unit, acquire()).WillOnce(Return(acquired)); - EXPECT_CALL(*acquired, heal(Eq(unitHealth * unitAmount), Eq(EHealLevel::OVERHEAL), Eq(permanent ? EHealPower::PERMANENT : EHealPower::ONE_BATTLE))); + EXPECT_CALL(*acquired, heal(Eq(unitTotalHealth), Eq(EHealLevel::OVERHEAL), Eq(permanent ? EHealPower::PERMANENT : EHealPower::ONE_BATTLE))); EXPECT_CALL(*acquired, save(_)); EXPECT_CALL(*battleFake, setUnitState(Eq(unitId), _, _)); } EXPECT_CALL(unit, unitId()).WillOnce(Return(unitId)); - unitsFake.setDefaultBonusExpectations(); EffectTarget target; @@ -242,12 +255,15 @@ TEST_P(SummonApplyTest, UpdatesOldUnit) subject->apply(battleProxy.get(), rngMock, &mechanicsMock, target); } - INSTANTIATE_TEST_CASE_P ( ByConfig, SummonApplyTest, - Values(false, true) + Combine + ( + Values(false, true), + Values(false, true) + ) ); }