UnitEffect.cpp 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  1. /*
  2. * UnitEffect.cpp, part of VCMI engine
  3. *
  4. * Authors: listed in file AUTHORS in main folder
  5. *
  6. * License: GNU General Public License v2.0 or later
  7. * Full text of license available in license.txt file, in main folder
  8. *
  9. */
  10. #include "StdInc.h"
  11. #include "UnitEffect.h"
  12. #include "../ISpellMechanics.h"
  13. #include "../../bonuses/BonusSelector.h"
  14. #include "../../NetPacksBase.h"
  15. #include "../../battle/CBattleInfoCallback.h"
  16. #include "../../battle/Unit.h"
  17. #include "../../serializer/JsonSerializeFormat.h"
  18. VCMI_LIB_NAMESPACE_BEGIN
  19. namespace spells
  20. {
  21. namespace effects
  22. {
  23. void UnitEffect::adjustTargetTypes(std::vector<TargetType> & types) const
  24. {
  25. }
  26. void UnitEffect::adjustAffectedHexes(std::set<BattleHex> & hexes, const Mechanics * m, const Target & spellTarget) const
  27. {
  28. for(const auto & destnation : spellTarget)
  29. hexes.insert(destnation.hexValue);
  30. }
  31. bool UnitEffect::applicable(Problem & problem, const Mechanics * m) const
  32. {
  33. //stack effect is applicable in general if there is at least one smart target
  34. auto mainFilter = std::bind(&UnitEffect::getStackFilter, this, m, false, _1);
  35. auto predicate = std::bind(&UnitEffect::eraseByImmunityFilter, this, m, _1);
  36. auto targets = m->battle()->battleGetUnitsIf(mainFilter);
  37. vstd::erase_if(targets, predicate);
  38. if(targets.empty())
  39. return m->adaptProblem(ESpellCastProblem::NO_APPROPRIATE_TARGET, problem);
  40. return true;
  41. }
  42. bool UnitEffect::applicable(Problem & problem, const Mechanics * m, const EffectTarget & target) const
  43. {
  44. //stack effect is applicable if it affects at least one target (smartness should not be checked)
  45. //assume target correctly transformed, just reapply filter
  46. for(const auto & item : target)
  47. if(item.unitValue)
  48. if(getStackFilter(m, false, item.unitValue))
  49. return true;
  50. return false;
  51. }
  52. bool UnitEffect::getStackFilter(const Mechanics * m, bool alwaysSmart, const battle::Unit * s) const
  53. {
  54. return isValidTarget(m, s) && isSmartTarget(m, s, alwaysSmart);
  55. }
  56. bool UnitEffect::eraseByImmunityFilter(const Mechanics * m, const battle::Unit * s) const
  57. {
  58. return !isReceptive(m, s);
  59. }
  60. EffectTarget UnitEffect::filterTarget(const Mechanics * m, const EffectTarget & target) const
  61. {
  62. EffectTarget res;
  63. vstd::copy_if(target, std::back_inserter(res), [this, m](const Destination & d)
  64. {
  65. return d.unitValue && isValidTarget(m, d.unitValue) && isReceptive(m, d.unitValue);
  66. });
  67. return res;
  68. }
  69. EffectTarget UnitEffect::transformTarget(const Mechanics * m, const Target & aimPoint, const Target & spellTarget) const
  70. {
  71. if(chainLength > 1)
  72. return transformTargetByChain(m, aimPoint, spellTarget);
  73. else
  74. return transformTargetByRange(m, aimPoint, spellTarget);
  75. }
  76. EffectTarget UnitEffect::transformTargetByRange(const Mechanics * m, const Target & aimPoint, const Target & spellTarget) const
  77. {
  78. auto mainFilter = std::bind(&UnitEffect::getStackFilter, this, m, false, _1);
  79. Target spellTargetCopy(spellTarget);
  80. // make sure that we have valid target with valid aim, even if spell have invalid range configured
  81. // TODO: check than spell range is actually not valid
  82. // also hackfix for banned creature massive spells
  83. // FIXME: potentially breaking change: aimPoint may NOT be in Target - example: frost ring
  84. if(!aimPoint.empty() && spellTarget.empty())
  85. spellTargetCopy.insert(spellTargetCopy.begin(), Destination(aimPoint.front()));
  86. std::set<const battle::Unit *> targets;
  87. if(m->isMassive())
  88. {
  89. //ignore spellTarget and add all stacks
  90. auto units = m->battle()->battleGetUnitsIf(mainFilter);
  91. for(const auto *unit : units)
  92. targets.insert(unit);
  93. }
  94. else
  95. {
  96. //process each tile
  97. for(const Destination & dest : spellTargetCopy)
  98. {
  99. if(dest.unitValue)
  100. {
  101. if(mainFilter(dest.unitValue))
  102. targets.insert(dest.unitValue);
  103. }
  104. else if(dest.hexValue.isValid())
  105. {
  106. //select one unit on tile, prefer alive one
  107. const battle::Unit * targetOnTile = nullptr;
  108. auto predicate = [&](const battle::Unit * unit)
  109. {
  110. return unit->coversPos(dest.hexValue) && mainFilter(unit);
  111. };
  112. auto units = m->battle()->battleGetUnitsIf(predicate);
  113. for(const auto *unit : units)
  114. {
  115. if(unit->alive())
  116. {
  117. targetOnTile = unit;
  118. break;
  119. }
  120. }
  121. if(targetOnTile == nullptr && !units.empty())
  122. targetOnTile = units.front();
  123. if(targetOnTile)
  124. targets.insert(targetOnTile);
  125. }
  126. else
  127. {
  128. logGlobal->debug("Invalid destination in spell Target");
  129. }
  130. }
  131. }
  132. auto predicate = std::bind(&UnitEffect::eraseByImmunityFilter, this, m, _1);
  133. vstd::erase_if(targets, predicate);
  134. if(m->alwaysHitFirstTarget())
  135. {
  136. if(!aimPoint.empty() && aimPoint.front().unitValue)
  137. targets.insert(aimPoint.front().unitValue);
  138. else
  139. logGlobal->error("Spell-like attack with no primary target.");
  140. }
  141. EffectTarget effectTarget;
  142. for(const auto *s : targets)
  143. effectTarget.push_back(Destination(s));
  144. return effectTarget;
  145. }
  146. EffectTarget UnitEffect::transformTargetByChain(const Mechanics * m, const Target & aimPoint, const Target & spellTarget) const
  147. {
  148. EffectTarget byRange = transformTargetByRange(m, aimPoint, spellTarget);
  149. if(byRange.empty())
  150. {
  151. return EffectTarget();
  152. }
  153. const Destination & mainDestination = byRange.front();
  154. if(!mainDestination.hexValue.isValid())
  155. {
  156. return EffectTarget();
  157. }
  158. std::set<BattleHex> possibleHexes;
  159. auto possibleTargets = m->battle()->battleGetUnitsIf([&](const battle::Unit * unit) -> bool
  160. {
  161. return isValidTarget(m, unit);
  162. });
  163. for(const auto *unit : possibleTargets)
  164. {
  165. for(auto hex : battle::Unit::getHexes(unit->getPosition(), unit->doubleWide(), unit->unitSide()))
  166. possibleHexes.insert(hex);
  167. }
  168. BattleHex destHex = mainDestination.hexValue;
  169. EffectTarget effectTarget;
  170. for(int32_t targetIndex = 0; targetIndex < chainLength; ++targetIndex)
  171. {
  172. const auto *unit = m->battle()->battleGetUnitByPos(destHex, true);
  173. if(!unit)
  174. break;
  175. if(m->alwaysHitFirstTarget() && targetIndex == 0)
  176. effectTarget.emplace_back(unit);
  177. else if(isReceptive(m, unit) && isValidTarget(m, unit))
  178. effectTarget.emplace_back(unit);
  179. else
  180. effectTarget.emplace_back();
  181. for(auto hex : battle::Unit::getHexes(unit->getPosition(), unit->doubleWide(), unit->unitSide()))
  182. possibleHexes.erase(hex);
  183. if(possibleHexes.empty())
  184. break;
  185. destHex = BattleHex::getClosestTile(unit->unitSide(), destHex, possibleHexes);
  186. }
  187. return effectTarget;
  188. }
  189. bool UnitEffect::isValidTarget(const Mechanics * m, const battle::Unit * unit) const
  190. {
  191. // TODO: override in rising effect
  192. // TODO: check absolute immunity here
  193. return unit->isValidTarget(false);
  194. }
  195. bool UnitEffect::isReceptive(const Mechanics * m, const battle::Unit * unit) const
  196. {
  197. if(ignoreImmunity)
  198. {
  199. //ignore all immunities, except specific absolute immunity(VCMI addition)
  200. //SPELL_IMMUNITY absolute case
  201. std::stringstream cachingStr;
  202. cachingStr << "type_" << vstd::to_underlying(BonusType::SPELL_IMMUNITY) << "subtype_" << m->getSpellIndex() << "addInfo_1";
  203. return !unit->hasBonus(Selector::typeSubtypeInfo(BonusType::SPELL_IMMUNITY, m->getSpellIndex(), 1), cachingStr.str());
  204. }
  205. else
  206. {
  207. return m->isReceptive(unit);
  208. }
  209. }
  210. bool UnitEffect::isSmartTarget(const Mechanics * m, const battle::Unit * unit, bool alwaysSmart) const
  211. {
  212. const bool smart = m->isSmart() || alwaysSmart;
  213. const bool ignoreOwner = !smart;
  214. return ignoreOwner || m->ownerMatches(unit);
  215. }
  216. void UnitEffect::serializeJsonEffect(JsonSerializeFormat & handler)
  217. {
  218. handler.serializeBool("ignoreImmunity", ignoreImmunity);
  219. handler.serializeInt("chainLength", chainLength, 0);
  220. handler.serializeFloat("chainFactor", chainFactor, 0);
  221. serializeJsonUnitEffect(handler);
  222. }
  223. }
  224. }
  225. VCMI_LIB_NAMESPACE_END