123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281 |
- /*
- * UnitEffect.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 "UnitEffect.h"
- #include "../ISpellMechanics.h"
- #include "../../bonuses/BonusSelector.h"
- #include "../../battle/CBattleInfoCallback.h"
- #include "../../battle/Unit.h"
- #include "../../serializer/JsonSerializeFormat.h"
- VCMI_LIB_NAMESPACE_BEGIN
- namespace spells
- {
- namespace effects
- {
- void UnitEffect::adjustTargetTypes(std::vector<TargetType> & types) const
- {
- }
- void UnitEffect::adjustAffectedHexes(BattleHexArray & hexes, const Mechanics * m, const Target & spellTarget) const
- {
- for(const auto & destnation : spellTarget)
- hexes.insert(destnation.hexValue);
- }
- bool UnitEffect::applicable(Problem & problem, const Mechanics * m) const
- {
- //stack effect is applicable in general if there is at least one smart target
- auto mainFilter = std::bind(&UnitEffect::getStackFilter, this, m, false, _1);
- auto predicate = std::bind(&UnitEffect::eraseByImmunityFilter, this, m, _1);
- auto targets = m->battle()->battleGetUnitsIf(mainFilter);
- vstd::erase_if(targets, predicate);
- if(targets.empty())
- return m->adaptProblem(ESpellCastProblem::NO_APPROPRIATE_TARGET, problem);
- return true;
- }
- bool UnitEffect::applicable(Problem & problem, const Mechanics * m, const EffectTarget & target) const
- {
- //stack effect is applicable if it affects at least one target (smartness should not be checked)
- //assume target correctly transformed, just reapply filter
- for(const auto & item : target)
- if(item.unitValue)
- if(getStackFilter(m, false, item.unitValue))
- return true;
- return false;
- }
- bool UnitEffect::getStackFilter(const Mechanics * m, bool alwaysSmart, const battle::Unit * s) const
- {
- return isValidTarget(m, s) && isSmartTarget(m, s, alwaysSmart);
- }
- bool UnitEffect::eraseByImmunityFilter(const Mechanics * m, const battle::Unit * s) const
- {
- return !isReceptive(m, s);
- }
- EffectTarget UnitEffect::filterTarget(const Mechanics * m, const EffectTarget & target) const
- {
- EffectTarget res;
- vstd::copy_if(target, std::back_inserter(res), [this, m](const Destination & d)
- {
- return d.unitValue && isValidTarget(m, d.unitValue) && isReceptive(m, d.unitValue);
- });
- return res;
- }
- EffectTarget UnitEffect::transformTarget(const Mechanics * m, const Target & aimPoint, const Target & spellTarget) const
- {
- if(chainLength > 1)
- return transformTargetByChain(m, aimPoint, spellTarget);
- else
- return transformTargetByRange(m, aimPoint, spellTarget);
- }
- EffectTarget UnitEffect::transformTargetByRange(const Mechanics * m, const Target & aimPoint, const Target & spellTarget) const
- {
- auto mainFilter = std::bind(&UnitEffect::getStackFilter, this, m, false, _1);
- Target spellTargetCopy(spellTarget);
- // make sure that we have valid target with valid aim, even if spell have invalid range configured
- // TODO: check than spell range is actually not valid
- // also hackfix for banned creature massive spells
- // FIXME: potentially breaking change: aimPoint may NOT be in Target - example: frost ring
- if(!aimPoint.empty() && spellTarget.empty())
- spellTargetCopy.insert(spellTargetCopy.begin(), Destination(aimPoint.front()));
- std::set<const battle::Unit *> targets;
- if(m->isMassive())
- {
- //ignore spellTarget and add all stacks
- auto units = m->battle()->battleGetUnitsIf(mainFilter);
- for(const auto *unit : units)
- targets.insert(unit);
- }
- else
- {
- //process each tile
- for(const Destination & dest : spellTargetCopy)
- {
- if(dest.unitValue)
- {
- if(mainFilter(dest.unitValue))
- targets.insert(dest.unitValue);
- }
- else if(dest.hexValue.isValid())
- {
- //select one unit on tile, prefer alive one
- const battle::Unit * targetOnTile = nullptr;
- auto predicate = [&](const battle::Unit * unit)
- {
- return unit->coversPos(dest.hexValue) && mainFilter(unit);
- };
- auto units = m->battle()->battleGetUnitsIf(predicate);
- for(const auto *unit : units)
- {
- if(unit->alive())
- {
- targetOnTile = unit;
- break;
- }
- }
- if(targetOnTile == nullptr && !units.empty())
- targetOnTile = units.front();
- if(targetOnTile)
- targets.insert(targetOnTile);
- }
- else
- {
- logGlobal->debug("Invalid destination in spell Target");
- }
- }
- }
- auto predicate = std::bind(&UnitEffect::eraseByImmunityFilter, this, m, _1);
- vstd::erase_if(targets, predicate);
- if(m->alwaysHitFirstTarget())
- {
- //TODO: examine if adjustments needed related to INVINCIBLE bonus
- if(!aimPoint.empty() && aimPoint.front().unitValue)
- targets.insert(aimPoint.front().unitValue);
- }
- EffectTarget effectTarget;
- for(const auto *s : targets)
- effectTarget.push_back(Destination(s));
- return effectTarget;
- }
- EffectTarget UnitEffect::transformTargetByChain(const Mechanics * m, const Target & aimPoint, const Target & spellTarget) const
- {
- EffectTarget byRange = transformTargetByRange(m, aimPoint, spellTarget);
- if(byRange.empty())
- {
- return EffectTarget();
- }
- const Destination & mainDestination = byRange.front();
- if(!mainDestination.hexValue.isValid())
- {
- return EffectTarget();
- }
- BattleHexArray possibleHexes;
- auto possibleTargets = m->battle()->battleGetUnitsIf([&](const battle::Unit * unit) -> bool
- {
- return isReceptive(m, unit) && isValidTarget(m, unit);
- });
- for(const auto *unit : possibleTargets)
- {
- for(auto hex : battle::Unit::getHexes(unit->getPosition(), unit->doubleWide(), unit->unitSide()))
- possibleHexes.insert(hex);
- }
- BattleHex destHex = mainDestination.hexValue;
- EffectTarget effectTarget;
- for(int32_t targetIndex = 0; targetIndex < chainLength; ++targetIndex)
- {
- const auto *unit = m->battle()->battleGetUnitByPos(destHex, true);
- if(!unit)
- break;
- if(m->alwaysHitFirstTarget() && targetIndex == 0)
- effectTarget.emplace_back(unit);
- else if(isReceptive(m, unit) && isValidTarget(m, unit))
- effectTarget.emplace_back(unit);
- else
- effectTarget.emplace_back();
- for(auto hex : battle::Unit::getHexes(unit->getPosition(), unit->doubleWide(), unit->unitSide()))
- possibleHexes.erase(hex.toInt());
- if(possibleHexes.empty())
- break;
- destHex = BattleHex::getClosestTile(unit->unitSide(), destHex, possibleHexes);
- }
- return effectTarget;
- }
- bool UnitEffect::isValidTarget(const Mechanics * m, const battle::Unit * unit) const
- {
- // TODO: override in rising effect
- // TODO: check absolute immunity here
- return unit->isValidTarget(false);
- }
- bool UnitEffect::isReceptive(const Mechanics * m, const battle::Unit * unit) const
- {
- if(ignoreImmunity)
- {
- //ignore all immunities, except specific absolute immunity(VCMI addition)
- //SPELL_IMMUNITY absolute case
- std::stringstream cachingStr;
- cachingStr << "type_" << vstd::to_underlying(BonusType::SPELL_IMMUNITY) << "subtype_" << m->getSpellIndex() << "addInfo_1";
- return !unit->hasBonus(Selector::typeSubtypeInfo(BonusType::SPELL_IMMUNITY, BonusSubtypeID(m->getSpellId()), 1), cachingStr.str());
- }
- else
- {
- return m->isReceptive(unit);
- }
- }
- bool UnitEffect::isSmartTarget(const Mechanics * m, const battle::Unit * unit, bool alwaysSmart) const
- {
- const bool smart = m->isSmart() || alwaysSmart;
- const bool ignoreOwner = !smart;
- return ignoreOwner || m->ownerMatches(unit);
- }
- void UnitEffect::serializeJsonEffect(JsonSerializeFormat & handler)
- {
- handler.serializeBool("ignoreImmunity", ignoreImmunity);
- handler.serializeInt("chainLength", chainLength, 0);
- handler.serializeFloat("chainFactor", chainFactor, 0);
- serializeJsonUnitEffect(handler);
- }
- }
- }
- VCMI_LIB_NAMESPACE_END
|