/* * CustomSpellMechanics.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 "CustomSpellMechanics.h" #include "CDefaultSpellMechanics.h" #include "../battle/IBattleState.h" #include "../battle/CBattleInfoCallback.h" #include "Problem.h" #include "CSpellHandler.h" #include "../CStack.h" namespace spells { CustomSpellMechanics::CustomSpellMechanics(const IBattleCast * event, std::shared_ptr e) : DefaultSpellMechanics(event), effects(e) {} CustomSpellMechanics::~CustomSpellMechanics() = default; void CustomSpellMechanics::applyEffects(const SpellCastEnvironment * env, const Target & targets) const { auto callback = [&](const effects::Effect * e, bool & stop) { EffectTarget target = e->filterTarget(this, targets); e->apply(env, env->getRandomGenerator(), this, target); }; effects->forEachEffect(getEffectLevel(), callback); } void CustomSpellMechanics::applyIndirectEffects(const SpellCastEnvironment * env, const Target & targets) const { auto callback = [&](const effects::Effect * e, bool & stop) { if(!e->automatic) { EffectTarget target = e->filterTarget(this, targets); e->apply(env, env->getRandomGenerator(), this, target); } }; effects->forEachEffect(getEffectLevel(), callback); } void CustomSpellMechanics::applyEffectsForced(const SpellCastEnvironment * env, const Target & targets) const { auto callback = [&](const effects::Effect * e, bool & stop) { e->apply(env, env->getRandomGenerator(), this, targets); }; effects->forEachEffect(getEffectLevel(), callback); } bool CustomSpellMechanics::canBeCast(Problem & problem) const { return effects->applicable(problem, this); } bool CustomSpellMechanics::canBeCastAt(BattleHex destination) const { detail::ProblemImpl problem; //TODO: add support for secondary targets //TODO: send problem to caller (for battle log message in BattleInterface) Target tmp; tmp.push_back(Destination(destination)); Target spellTarget = transformSpellTarget(tmp); return effects->applicable(problem, this, tmp, spellTarget); } std::vector CustomSpellMechanics::getAffectedStacks(BattleHex destination) const { Target tmp; tmp.push_back(Destination(destination)); Target spellTarget = transformSpellTarget(tmp); EffectTarget all; effects->forEachEffect(getEffectLevel(), [&all, &tmp, &spellTarget, this](const effects::Effect * e, bool & stop) { EffectTarget one = e->transformTarget(this, tmp, spellTarget); vstd::concatenate(all, one); }); std::set stacks; for(const Destination & dest : all) { if(dest.unitValue) { //FIXME: remove and return battle::Unit stacks.insert(cb->battleGetStackByID(dest.unitValue->unitId(), false)); } } std::vector res; std::copy(stacks.begin(), stacks.end(), std::back_inserter(res)); return res; } void CustomSpellMechanics::beforeCast(vstd::RNG & rng, const Target & target, std::vector & reflected) { reflected.clear(); affectedUnits.clear(); Target spellTarget = transformSpellTarget(target); std::vector resisted; auto rangeGen = rng.getInt64Range(0, 99); auto filterReflected = [&, this](const CStack * s) -> bool { const bool tryMagicMirror = mode != Mode::MAGIC_MIRROR && isNegativeSpell() && owner->level && owner->getLevelInfo(0).range == "0"; if(tryMagicMirror) { const int mirrorChance = s->valOfBonuses(Bonus::MAGIC_MIRROR); if(rangeGen() < mirrorChance) return true; } return false; }; auto filterResisted = [&, this](const CStack * s) -> bool { if(isNegativeSpell()) { //magic resistance const int prob = std::min((s)->magicResistance(), 100); //probability of resistance in % if(rangeGen() < prob) return true; } return false; }; auto filterStack = [&](const battle::Unit * st) { const CStack * s = dynamic_cast(st); if(!s) s = cb->battleGetStackByID(st->unitId(), false); if(filterResisted(s)) resisted.push_back(s); else if(filterReflected(s)) reflected.push_back(s); else affectedUnits.push_back(s); }; //prepare targets effectsToApply = effects->prepare(this, target, spellTarget); std::set stacks = collectTargets(); //process them for(auto s : stacks) filterStack(s); //and update targets for(auto & p : effectsToApply) { vstd::erase_if(p.second, [&](const Destination & d) { if(!d.unitValue) return false; return vstd::contains(resisted, d.unitValue) || vstd::contains(reflected, d.unitValue); }); } for(auto s : reflected) addCustomEffect(s, 3); for(auto s : resisted) addCustomEffect(s, 78); } void CustomSpellMechanics::applyCastEffects(const SpellCastEnvironment * env, const Target & target) const { for(auto & p : effectsToApply) p.first->apply(env, env->getRandomGenerator(), this, p.second); } void CustomSpellMechanics::cast(IBattleState * battleState, vstd::RNG & rng, const Target & target) { //TODO: evaluate caster updates (mana usage etc.) //TODO: evaluate random values Target spellTarget = transformSpellTarget(target); effectsToApply = effects->prepare(this, target, spellTarget); std::set stacks = collectTargets(); for(const battle::Unit * one : stacks) { auto selector = std::bind(&DefaultSpellMechanics::counteringSelector, this, _1); std::vector buffer; auto bl = one->getBonuses(selector); for(auto item : *bl) buffer.emplace_back(*item); if(!buffer.empty()) battleState->removeUnitBonus(one->unitId(), buffer); } for(auto & p : effectsToApply) p.first->apply(battleState, rng, this, p.second); } std::set CustomSpellMechanics::collectTargets() const { std::set result; for(const auto & p : effectsToApply) { for(const Destination & d : p.second) if(d.unitValue) result.insert(d.unitValue); } return result; } Target CustomSpellMechanics::transformSpellTarget(const Target & aimPoint) const { Target spellTarget; if(aimPoint.size() < 1) { logGlobal->error("Aimed spell cast with no destination."); } else { const Destination & primary = aimPoint.at(0); BattleHex aimPoint = primary.hexValue; //transform primary spell target with spell range (if it`s valid), leave anything else to effects if(aimPoint.isValid()) { auto spellRange = spellRangeInHexes(aimPoint); for(auto & hex : spellRange) spellTarget.push_back(Destination(hex)); } } if(spellTarget.empty()) spellTarget.push_back(Destination(BattleHex::INVALID)); return std::move(spellTarget); } std::vector CustomSpellMechanics::getTargetTypes() const { auto ret = DefaultSpellMechanics::getTargetTypes(); if(!ret.empty()) { effects->forEachEffect(getEffectLevel(), [&](const effects::Effect * e, bool & stop) { e->adjustTargetTypes(ret); stop = ret.empty(); }); } return ret; } std::vector CustomSpellMechanics::getPossibleDestinations(size_t index, AimType aimType, const Target & current) const { //TODO: CustomSpellMechanics::getPossibleDestinations if(index != 0) return std::vector(); std::vector ret; switch(aimType) { case AimType::CREATURE: case AimType::LOCATION: for(int i = 0; i < GameConstants::BFIELD_SIZE; i++) { BattleHex dest(i); if(dest.isAvailable()) if(canBeCastAt(dest)) ret.emplace_back(dest); } break; case AimType::NO_TARGET: ret.emplace_back(); break; default: break; } return ret; } std::vector CustomSpellMechanics::rangeInHexes(BattleHex centralHex, bool * outDroppedHexes) const { if(isMassive() || !centralHex.isValid()) return std::vector(1, BattleHex::INVALID); Target aimPoint; aimPoint.push_back(Destination(centralHex)); Target spellTarget = transformSpellTarget(aimPoint); std::set effectRange; effects->forEachEffect(getEffectLevel(), [&](const effects::Effect * effect, bool & stop) { if(effect->automatic) { effect->adjustAffectedHexes(effectRange, this, spellTarget); } }); std::vector ret; ret.reserve(effectRange.size()); std::copy(effectRange.begin(), effectRange.end(), std::back_inserter(ret)); return ret; } } //namespace spells