/* * Sacrifice.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 "Sacrifice.h" #include "Registry.h" #include "../ISpellMechanics.h" #include "../../NetPacks.h" #include "../../battle/IBattleState.h" #include "../../battle/CBattleInfoCallback.h" #include "../../battle/Unit.h" #include "../../serializer/JsonSerializeFormat.h" static const std::string EFFECT_NAME = "core:sacrifice"; namespace spells { namespace effects { VCMI_REGISTER_SPELL_EFFECT(Sacrifice, EFFECT_NAME); Sacrifice::Sacrifice() : Heal() { } Sacrifice::~Sacrifice() = default; void Sacrifice::adjustTargetTypes(std::vector & types) const { if(!types.empty()) { if(types[0] != AimType::CREATURE) { types.clear(); return; } if(types.size() == 1) { types.push_back(AimType::CREATURE); } else if(types.size() > 1) { if(types[1] != AimType::CREATURE) types.clear(); } } } bool Sacrifice::applicable(Problem & problem, const Mechanics * m) const { auto mainFilter = std::bind(&UnitEffect::getStackFilter, this, m, true, _1); auto predicate = std::bind(&UnitEffect::eraseByImmunityFilter, this, m, _1); auto targets = m->cb->battleGetUnitsIf(mainFilter); vstd::erase_if(targets, predicate); bool targetExists = false; bool targetToSacrificeExists = false; for(auto & target : targets) { if(target->alive()) targetToSacrificeExists = true; else if(target->isDead()) targetExists = true; if(targetExists && targetToSacrificeExists) break; } if(!(targetExists && targetToSacrificeExists)) { MetaString text; text.addTxt(MetaString::GENERAL_TXT, 185); problem.add(std::move(text), Problem::NORMAL); return false; } return true; } bool Sacrifice::applicable(Problem & problem, const Mechanics * m, const EffectTarget & target) const { //TODO: support for multiple targets? if(target.empty()) return false; EffectTarget healTarget; healTarget.emplace_back(target.front()); if(!Heal::applicable(problem, m, healTarget)) return false; if(target.size() == 2) { auto victim = target.at(1).unitValue; if(!victim) return false; if(!victim->alive() || !getStackFilter(m, false, victim) || !isReceptive(m, victim)) return false; return true; } return true; } void Sacrifice::apply(BattleStateProxy * battleState, RNG & rng, const Mechanics * m, const EffectTarget & target) const { if(target.size() != 2) { logGlobal->error("Sacrifice effect requires 2 targets"); return; } const battle::Unit * victim = target.back().unitValue; if(!victim) { logGlobal->error("No unit to Sacrifice"); return; } EffectTarget healTarget; healTarget.emplace_back(target.front()); Heal::apply(calculateHealEffectValue(m, victim), battleState, rng, m, healTarget); BattleUnitsChanged removeUnits; removeUnits.changedStacks.emplace_back(victim->unitId(), UnitChanges::EOperation::REMOVE); battleState->apply(&removeUnits); } bool Sacrifice::isValidTarget(const Mechanics * m, const battle::Unit * unit) const { return unit->isValidTarget(true); } EffectTarget Sacrifice::transformTarget(const Mechanics * m, const Target & aimPoint, const Target & spellTarget) const { EffectTarget res = Heal::transformTarget(m, aimPoint, spellTarget); //ignore spell range for now, arbitrary range support requires redesign res.resize(1); //add victim if(aimPoint.size() >= 2) { auto victim = aimPoint.at(1).unitValue; if(victim && getStackFilter(m, false, victim) && isReceptive(m, victim)) res.emplace_back(victim); } return res; } int64_t Sacrifice::calculateHealEffectValue(const Mechanics * m, const battle::Unit * victim) const { return (m->getEffectPower() + victim->MaxHealth() + m->calculateRawEffectValue(0, 1)) * victim->getCount(); } } }