mirror of
https://github.com/vcmi/vcmi.git
synced 2025-01-26 03:52:01 +02:00
Move getAffectedCreatures to CSpell. + more drafts
This commit is contained in:
parent
5ba53da9bf
commit
a06dae1f96
@ -457,12 +457,8 @@ void CBattleAI::attemptCastingSpell()
|
|||||||
case OFFENSIVE_SPELL:
|
case OFFENSIVE_SPELL:
|
||||||
{
|
{
|
||||||
int damageDealt = 0, damageReceived = 0;
|
int damageDealt = 0, damageReceived = 0;
|
||||||
|
|
||||||
auto stacksSuffering = cb->getAffectedCreatures(ps.spell, skillLevel, playerID, ps.dest);
|
auto stacksSuffering = ps.spell->getAffectedStacks(cb.get(), ECastingMode::HERO_CASTING, playerID, skillLevel, ps.dest, hero);
|
||||||
vstd::erase_if(stacksSuffering, [&](const CStack * s) -> bool
|
|
||||||
{
|
|
||||||
return ESpellCastProblem::OK != ps.spell->isImmuneByStack(hero, ECastingMode::HERO_CASTING, s);
|
|
||||||
});
|
|
||||||
|
|
||||||
if(stacksSuffering.empty())
|
if(stacksSuffering.empty())
|
||||||
return -1;
|
return -1;
|
||||||
|
@ -1582,7 +1582,7 @@ ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleIsImmune(const C
|
|||||||
|
|
||||||
for(auto s : stacks)
|
for(auto s : stacks)
|
||||||
{
|
{
|
||||||
ESpellCastProblem::ESpellCastProblem res = spell->isImmuneByStack(caster,mode,s);
|
ESpellCastProblem::ESpellCastProblem res = spell->isImmuneByStack(caster,s);
|
||||||
|
|
||||||
if(res == ESpellCastProblem::OK)
|
if(res == ESpellCastProblem::OK)
|
||||||
{
|
{
|
||||||
@ -1661,7 +1661,7 @@ ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCastThisSpell
|
|||||||
auto stacks = spell->isNegative() ? battleAliveStacks(!side) : battleAliveStacks();
|
auto stacks = spell->isNegative() ? battleAliveStacks(!side) : battleAliveStacks();
|
||||||
for(auto stack : stacks)
|
for(auto stack : stacks)
|
||||||
{
|
{
|
||||||
if(ESpellCastProblem::OK == spell->isImmuneByStack(castingHero, mode, stack))
|
if(ESpellCastProblem::OK == spell->isImmuneByStack(castingHero, stack))
|
||||||
{
|
{
|
||||||
allStacksImmune = false;
|
allStacksImmune = false;
|
||||||
break;
|
break;
|
||||||
@ -1705,7 +1705,7 @@ ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCastThisSpell
|
|||||||
|
|
||||||
for(const CStack * stack : battleGetAllStacks()) //dead stacks will be immune anyway
|
for(const CStack * stack : battleGetAllStacks()) //dead stacks will be immune anyway
|
||||||
{
|
{
|
||||||
bool immune = ESpellCastProblem::OK != spell->isImmuneByStack(caster, mode, stack);
|
bool immune = ESpellCastProblem::OK != spell->isImmuneByStack(caster, stack);
|
||||||
bool casterStack = stack->owner == caster->getOwner();
|
bool casterStack = stack->owner == caster->getOwner();
|
||||||
|
|
||||||
if(spell->id == SpellID::SACRIFICE)
|
if(spell->id == SpellID::SACRIFICE)
|
||||||
@ -1763,8 +1763,6 @@ std::vector<BattleHex> CBattleInfoCallback::battleGetPossibleTargets(PlayerColor
|
|||||||
std::vector<BattleHex> ret;
|
std::vector<BattleHex> ret;
|
||||||
RETURN_IF_NOT_BATTLE(ret);
|
RETURN_IF_NOT_BATTLE(ret);
|
||||||
|
|
||||||
auto mode = ECastingMode::HERO_CASTING; //TODO get rid of this!
|
|
||||||
|
|
||||||
switch(spell->getTargetType())
|
switch(spell->getTargetType())
|
||||||
{
|
{
|
||||||
case CSpell::CREATURE:
|
case CSpell::CREATURE:
|
||||||
@ -1774,7 +1772,7 @@ std::vector<BattleHex> CBattleInfoCallback::battleGetPossibleTargets(PlayerColor
|
|||||||
|
|
||||||
for(const CStack * stack : battleAliveStacks())
|
for(const CStack * stack : battleAliveStacks())
|
||||||
{
|
{
|
||||||
bool immune = ESpellCastProblem::OK != spell->isImmuneByStack(caster, mode, stack);
|
bool immune = ESpellCastProblem::OK != spell->isImmuneByStack(caster, stack);
|
||||||
bool casterStack = stack->owner == caster->getOwner();
|
bool casterStack = stack->owner == caster->getOwner();
|
||||||
|
|
||||||
if(!immune)
|
if(!immune)
|
||||||
@ -1908,117 +1906,6 @@ ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCastThisSpell
|
|||||||
return battleIsImmune(nullptr, spell, mode, dest);
|
return battleIsImmune(nullptr, spell, mode, dest);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::set<const CStack*> CBattleInfoCallback::getAffectedCreatures(const CSpell * spell, int skillLevel, PlayerColor attackerOwner, BattleHex destinationTile)
|
|
||||||
{
|
|
||||||
std::set<const CStack*> attackedCres; //std::set to exclude multiple occurrences of two hex creatures
|
|
||||||
|
|
||||||
const ui8 attackerSide = playerToSide(attackerOwner) == 1;
|
|
||||||
const auto attackedHexes = spell->rangeInHexes(destinationTile, skillLevel, attackerSide);
|
|
||||||
|
|
||||||
const CSpell::TargetInfo ti = spell->getTargetInfo(skillLevel);
|
|
||||||
//TODO: more generic solution for mass spells
|
|
||||||
if (spell->id == SpellID::CHAIN_LIGHTNING)
|
|
||||||
{
|
|
||||||
std::set<BattleHex> possibleHexes;
|
|
||||||
for (auto stack : battleGetAllStacks())
|
|
||||||
{
|
|
||||||
if (stack->isValidTarget())
|
|
||||||
{
|
|
||||||
for (auto hex : stack->getHexes())
|
|
||||||
{
|
|
||||||
possibleHexes.insert (hex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
int targetsOnLevel[4] = {4, 4, 5, 5};
|
|
||||||
|
|
||||||
BattleHex lightningHex = destinationTile;
|
|
||||||
for (int i = 0; i < targetsOnLevel[skillLevel]; ++i)
|
|
||||||
{
|
|
||||||
auto stack = battleGetStackByPos (lightningHex, true);
|
|
||||||
if (!stack)
|
|
||||||
break;
|
|
||||||
attackedCres.insert (stack);
|
|
||||||
for (auto hex : stack->getHexes())
|
|
||||||
{
|
|
||||||
possibleHexes.erase (hex); //can't hit same place twice
|
|
||||||
}
|
|
||||||
if (possibleHexes.empty()) //not enough targets
|
|
||||||
break;
|
|
||||||
lightningHex = BattleHex::getClosestTile (stack->attackerOwned, destinationTile, possibleHexes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (spell->getLevelInfo(skillLevel).range.size() > 1) //custom many-hex range
|
|
||||||
{
|
|
||||||
for(BattleHex hex : attackedHexes)
|
|
||||||
{
|
|
||||||
if(const CStack * st = battleGetStackByPos(hex, ti.onlyAlive))
|
|
||||||
{
|
|
||||||
if (spell->id == SpellID::DEATH_CLOUD) //Death Cloud //TODO: fireball and fire immunity
|
|
||||||
{
|
|
||||||
if (st->isLiving() || st->coversPos(destinationTile)) //directly hit or alive
|
|
||||||
{
|
|
||||||
attackedCres.insert(st);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
attackedCres.insert(st);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if(spell->getTargetType() == CSpell::CREATURE)
|
|
||||||
{
|
|
||||||
auto predicate = [=](const CStack * s){
|
|
||||||
const bool positiveToAlly = spell->isPositive() && s->owner == attackerOwner;
|
|
||||||
const bool negativeToEnemy = spell->isNegative() && s->owner != attackerOwner;
|
|
||||||
const bool validTarget = s->isValidTarget(!ti.onlyAlive); //todo: this should be handled by spell class
|
|
||||||
|
|
||||||
//for single target spells select stacks covering destination tile
|
|
||||||
const bool rangeCovers = ti.massive || s->coversPos(destinationTile);
|
|
||||||
//handle smart targeting
|
|
||||||
const bool positivenessFlag = !ti.smart || spell->isNeutral() || positiveToAlly || negativeToEnemy;
|
|
||||||
|
|
||||||
return rangeCovers && positivenessFlag && validTarget;
|
|
||||||
};
|
|
||||||
|
|
||||||
TStacks stacks = battleGetStacksIf(predicate);
|
|
||||||
|
|
||||||
if (ti.massive)
|
|
||||||
{
|
|
||||||
//for massive spells add all targets
|
|
||||||
for (auto stack : stacks)
|
|
||||||
attackedCres.insert(stack);
|
|
||||||
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
//for single target spells we must select one target. Alive stack is preferred (issue #1763)
|
|
||||||
for(auto stack : stacks)
|
|
||||||
{
|
|
||||||
if(stack->alive())
|
|
||||||
{
|
|
||||||
attackedCres.insert(stack);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(attackedCres.empty() && !stacks.empty())
|
|
||||||
{
|
|
||||||
attackedCres.insert(stacks.front());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else //custom range from attackedHexes
|
|
||||||
{
|
|
||||||
for(BattleHex hex : attackedHexes)
|
|
||||||
{
|
|
||||||
if(const CStack * st = battleGetStackByPos(hex, ti.onlyAlive))
|
|
||||||
attackedCres.insert(st);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return attackedCres;
|
|
||||||
}
|
|
||||||
|
|
||||||
const CStack * CBattleInfoCallback::getStackIf(std::function<bool(const CStack*)> pred) const
|
const CStack * CBattleInfoCallback::getStackIf(std::function<bool(const CStack*)> pred) const
|
||||||
{
|
{
|
||||||
RETURN_IF_NOT_BATTLE(nullptr);
|
RETURN_IF_NOT_BATTLE(nullptr);
|
||||||
|
@ -283,7 +283,6 @@ public:
|
|||||||
std::vector<BattleHex> battleGetPossibleTargets(PlayerColor player, const CSpell *spell) const;
|
std::vector<BattleHex> battleGetPossibleTargets(PlayerColor player, const CSpell *spell) const;
|
||||||
ui32 calculateHealedHP(int healedHealth, const CSpell * spell, const CStack * stack) const; //for Archangel
|
ui32 calculateHealedHP(int healedHealth, const CSpell * spell, const CStack * stack) const; //for Archangel
|
||||||
ui32 calculateHealedHP(const CSpell * spell, int usedSpellPower, int spellSchoolLevel, const CStack * stack) const; //healing spells casted by stacks
|
ui32 calculateHealedHP(const CSpell * spell, int usedSpellPower, int spellSchoolLevel, const CStack * stack) const; //healing spells casted by stacks
|
||||||
std::set<const CStack*> getAffectedCreatures(const CSpell * s, int skillLevel, PlayerColor attackerOwner, BattleHex destinationTile); //calculates stack affected by given spell
|
|
||||||
|
|
||||||
SpellID battleGetRandomStackSpell(const CStack * stack, ERandomSpell mode) const;
|
SpellID battleGetRandomStackSpell(const CStack * stack, ERandomSpell mode) const;
|
||||||
SpellID getRandomBeneficialSpell(const CStack * subject) const;
|
SpellID getRandomBeneficialSpell(const CStack * subject) const;
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
|
|
||||||
#include "mapObjects/CGHeroInstance.h"
|
#include "mapObjects/CGHeroInstance.h"
|
||||||
#include "BattleState.h"
|
#include "BattleState.h"
|
||||||
|
#include "CBattleCallback.h"
|
||||||
|
|
||||||
#include "SpellMechanics.h"
|
#include "SpellMechanics.h"
|
||||||
|
|
||||||
@ -380,12 +381,130 @@ std::vector<BattleHex> CSpell::rangeInHexes(BattleHex centralHex, ui8 schoolLvl,
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::set<const CStack* > CSpell::getAffectedStacks(const CBattleInfoCallback * cb, ECastingMode::ECastingMode mode, PlayerColor casterColor, int spellLvl, BattleHex destination, const CGHeroInstance * caster) const
|
||||||
|
{
|
||||||
|
std::set<const CStack* > attackedCres;//std::set to exclude multiple occurrences of two hex creatures
|
||||||
|
|
||||||
|
const ui8 attackerSide = cb->playerToSide(casterColor) == 1;
|
||||||
|
const auto attackedHexes = rangeInHexes(destination, spellLvl, attackerSide);
|
||||||
|
|
||||||
|
const CSpell::TargetInfo ti = getTargetInfoEx(spellLvl, mode);
|
||||||
|
|
||||||
|
|
||||||
|
//TODO: more generic solution for mass spells
|
||||||
|
if (id == SpellID::CHAIN_LIGHTNING)
|
||||||
|
{
|
||||||
|
std::set<BattleHex> possibleHexes;
|
||||||
|
for (auto stack : cb->battleGetAllStacks())
|
||||||
|
{
|
||||||
|
if (stack->isValidTarget())
|
||||||
|
{
|
||||||
|
for (auto hex : stack->getHexes())
|
||||||
|
{
|
||||||
|
possibleHexes.insert (hex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int targetsOnLevel[4] = {4, 4, 5, 5};
|
||||||
|
|
||||||
|
BattleHex lightningHex = destination;
|
||||||
|
for (int i = 0; i < targetsOnLevel[spellLvl]; ++i)
|
||||||
|
{
|
||||||
|
auto stack = cb->battleGetStackByPos (lightningHex, true);
|
||||||
|
if (!stack)
|
||||||
|
break;
|
||||||
|
attackedCres.insert (stack);
|
||||||
|
for (auto hex : stack->getHexes())
|
||||||
|
{
|
||||||
|
possibleHexes.erase (hex); //can't hit same place twice
|
||||||
|
}
|
||||||
|
if (possibleHexes.empty()) //not enough targets
|
||||||
|
break;
|
||||||
|
lightningHex = BattleHex::getClosestTile (stack->attackerOwned, destination, possibleHexes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (getLevelInfo(spellLvl).range.size() > 1) //custom many-hex range
|
||||||
|
{
|
||||||
|
for(BattleHex hex : attackedHexes)
|
||||||
|
{
|
||||||
|
if(const CStack * st = cb->battleGetStackByPos(hex, ti.onlyAlive))
|
||||||
|
{
|
||||||
|
attackedCres.insert(st);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(getTargetType() == CSpell::CREATURE)
|
||||||
|
{
|
||||||
|
auto predicate = [=](const CStack * s){
|
||||||
|
const bool positiveToAlly = isPositive() && s->owner == casterColor;
|
||||||
|
const bool negativeToEnemy = isNegative() && s->owner != casterColor;
|
||||||
|
const bool validTarget = s->isValidTarget(!ti.onlyAlive); //todo: this should be handled by spell class
|
||||||
|
|
||||||
|
//for single target spells select stacks covering destination tile
|
||||||
|
const bool rangeCovers = ti.massive || s->coversPos(destination);
|
||||||
|
//handle smart targeting
|
||||||
|
const bool positivenessFlag = !ti.smart || isNeutral() || positiveToAlly || negativeToEnemy;
|
||||||
|
|
||||||
|
return rangeCovers && positivenessFlag && validTarget;
|
||||||
|
};
|
||||||
|
|
||||||
|
TStacks stacks = cb->battleGetStacksIf(predicate);
|
||||||
|
|
||||||
|
if (ti.massive)
|
||||||
|
{
|
||||||
|
//for massive spells add all targets
|
||||||
|
for (auto stack : stacks)
|
||||||
|
attackedCres.insert(stack);
|
||||||
|
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//for single target spells we must select one target. Alive stack is preferred (issue #1763)
|
||||||
|
for(auto stack : stacks)
|
||||||
|
{
|
||||||
|
if(stack->alive())
|
||||||
|
{
|
||||||
|
attackedCres.insert(stack);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(attackedCres.empty() && !stacks.empty())
|
||||||
|
{
|
||||||
|
attackedCres.insert(stacks.front());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else //custom range from attackedHexes
|
||||||
|
{
|
||||||
|
for(BattleHex hex : attackedHexes)
|
||||||
|
{
|
||||||
|
if(const CStack * st = cb->battleGetStackByPos(hex, ti.onlyAlive))
|
||||||
|
attackedCres.insert(st);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//now handle immunities
|
||||||
|
auto predicate = [&, this](const CStack * s)->bool
|
||||||
|
{
|
||||||
|
bool hitDirectly = ti.alwaysHitDirectly && s->coversPos(destination);
|
||||||
|
bool notImmune = (ESpellCastProblem::OK == isImmuneByStack(caster, s));
|
||||||
|
|
||||||
|
return !(hitDirectly || notImmune);
|
||||||
|
};
|
||||||
|
|
||||||
|
vstd::erase_if(attackedCres, predicate);
|
||||||
|
|
||||||
|
return attackedCres;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
CSpell::ETargetType CSpell::getTargetType() const
|
CSpell::ETargetType CSpell::getTargetType() const
|
||||||
{
|
{
|
||||||
return targetType;
|
return targetType;
|
||||||
}
|
}
|
||||||
|
|
||||||
const CSpell::TargetInfo CSpell::getTargetInfo(const int level) const
|
CSpell::TargetInfo CSpell::getTargetInfo(const int level) const
|
||||||
{
|
{
|
||||||
TargetInfo info;
|
TargetInfo info;
|
||||||
|
|
||||||
@ -395,10 +514,28 @@ const CSpell::TargetInfo CSpell::getTargetInfo(const int level) const
|
|||||||
info.smart = levelInfo.smartTarget;
|
info.smart = levelInfo.smartTarget;
|
||||||
info.massive = levelInfo.range == "X";
|
info.massive = levelInfo.range == "X";
|
||||||
info.onlyAlive = !isRisingSpell();
|
info.onlyAlive = !isRisingSpell();
|
||||||
|
info.alwaysHitDirectly = false;
|
||||||
|
|
||||||
return info;
|
return info;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CSpell::TargetInfo CSpell::getTargetInfoEx(const int level, ECastingMode::ECastingMode mode) const
|
||||||
|
{
|
||||||
|
TargetInfo info = getTargetInfo(level);
|
||||||
|
|
||||||
|
if(mode == ECastingMode::ENCHANTER_CASTING)
|
||||||
|
{
|
||||||
|
info.smart = true; //FIXME: not sure about that, this makes all spells smart in this mode
|
||||||
|
info.massive = true;
|
||||||
|
}
|
||||||
|
else if(mode == ECastingMode::SPELL_LIKE_ATTACK)
|
||||||
|
{
|
||||||
|
info.alwaysHitDirectly = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
bool CSpell::isCombatSpell() const
|
bool CSpell::isCombatSpell() const
|
||||||
{
|
{
|
||||||
@ -595,9 +732,9 @@ ESpellCastProblem::ESpellCastProblem CSpell::isImmuneBy(const IBonusBearer* obj)
|
|||||||
return ESpellCastProblem::NOT_DECIDED;
|
return ESpellCastProblem::NOT_DECIDED;
|
||||||
}
|
}
|
||||||
|
|
||||||
ESpellCastProblem::ESpellCastProblem CSpell::isImmuneByStack(const CGHeroInstance* caster, ECastingMode::ECastingMode mode, const CStack* obj) const
|
ESpellCastProblem::ESpellCastProblem CSpell::isImmuneByStack(const CGHeroInstance* caster, const CStack* obj) const
|
||||||
{
|
{
|
||||||
const auto immuneResult = mechanics->isImmuneByStack(caster,mode,obj);
|
const auto immuneResult = mechanics->isImmuneByStack(caster,obj);
|
||||||
|
|
||||||
if (ESpellCastProblem::NOT_DECIDED != immuneResult)
|
if (ESpellCastProblem::NOT_DECIDED != immuneResult)
|
||||||
return immuneResult;
|
return immuneResult;
|
||||||
@ -649,7 +786,7 @@ void CSpell::setupMechanics()
|
|||||||
switch (id)
|
switch (id)
|
||||||
{
|
{
|
||||||
case SpellID::CLONE:
|
case SpellID::CLONE:
|
||||||
mechanics = new CloneMechnics(this);
|
mechanics = new CloneMechanics(this);
|
||||||
break;
|
break;
|
||||||
case SpellID::DISPEL_HELPFUL_SPELLS:
|
case SpellID::DISPEL_HELPFUL_SPELLS:
|
||||||
mechanics = new DispellHelpfulMechanics(this);
|
mechanics = new DispellHelpfulMechanics(this);
|
||||||
@ -660,6 +797,8 @@ void CSpell::setupMechanics()
|
|||||||
default:
|
default:
|
||||||
if(isRisingSpell())
|
if(isRisingSpell())
|
||||||
mechanics = new SpecialRisingSpellMechanics(this);
|
mechanics = new SpecialRisingSpellMechanics(this);
|
||||||
|
else if(isOffensiveSpell())
|
||||||
|
mechanics = new OffenciveSpellMechnics(this);
|
||||||
else
|
else
|
||||||
mechanics = new DefaultSpellMechanics(this);
|
mechanics = new DefaultSpellMechanics(this);
|
||||||
break;
|
break;
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
#include "../lib/ConstTransitivePtr.h"
|
#include "../lib/ConstTransitivePtr.h"
|
||||||
#include "int3.h"
|
#include "int3.h"
|
||||||
#include "GameConstants.h"
|
#include "GameConstants.h"
|
||||||
|
#include "BattleHex.h"
|
||||||
#include "HeroBonus.h"
|
#include "HeroBonus.h"
|
||||||
|
|
||||||
|
|
||||||
@ -17,12 +18,16 @@
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
class CLegacyConfigParser;
|
|
||||||
struct BattleHex;
|
|
||||||
class CSpell;
|
class CSpell;
|
||||||
|
class ISpellMechanics;
|
||||||
|
|
||||||
|
class CLegacyConfigParser;
|
||||||
|
|
||||||
class CGHeroInstance;
|
class CGHeroInstance;
|
||||||
class CStack;
|
class CStack;
|
||||||
|
|
||||||
|
class CBattleInfoCallback;
|
||||||
|
|
||||||
struct CPackForClient;
|
struct CPackForClient;
|
||||||
|
|
||||||
struct SpellSchoolInfo
|
struct SpellSchoolInfo
|
||||||
@ -73,28 +78,19 @@ struct DLL_LINKAGE SpellCastContext
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
SpellCastEnvironment * env;
|
SpellCastEnvironment * env;
|
||||||
|
|
||||||
|
int spellLvl;
|
||||||
|
// BattleHex destination;
|
||||||
|
ui8 casterSide;
|
||||||
|
PlayerColor casterColor;
|
||||||
|
CGHeroInstance * caster;
|
||||||
|
CGHeroInstance * secHero;
|
||||||
|
int usedSpellPower;
|
||||||
|
ECastingMode::ECastingMode mode;
|
||||||
|
CStack * targetStack;
|
||||||
|
CStack * selectedStack;
|
||||||
};
|
};
|
||||||
|
|
||||||
class DLL_LINKAGE ISpellMechanics
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
ISpellMechanics(CSpell * s);
|
|
||||||
virtual ~ISpellMechanics(){};
|
|
||||||
|
|
||||||
virtual ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, ECastingMode::ECastingMode mode, const CStack * obj) = 0;
|
|
||||||
|
|
||||||
/** \brief
|
|
||||||
*
|
|
||||||
* \param
|
|
||||||
* \return true if no error
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
virtual bool adventureCast(SpellCastContext & context) = 0;
|
|
||||||
virtual bool battleCast(SpellCastContext & context) = 0;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
CSpell * owner;
|
|
||||||
};
|
|
||||||
|
|
||||||
class DLL_LINKAGE CSpell
|
class DLL_LINKAGE CSpell
|
||||||
{
|
{
|
||||||
@ -137,6 +133,8 @@ public:
|
|||||||
bool smart;
|
bool smart;
|
||||||
bool massive;
|
bool massive;
|
||||||
bool onlyAlive;
|
bool onlyAlive;
|
||||||
|
///no immunity on primary target (mostly spell-like attack)
|
||||||
|
bool alwaysHitDirectly;
|
||||||
};
|
};
|
||||||
|
|
||||||
SpellID id;
|
SpellID id;
|
||||||
@ -168,7 +166,8 @@ public:
|
|||||||
si16 mainEffectAnim; //main spell effect animation, in AC format (or -1 when none)
|
si16 mainEffectAnim; //main spell effect animation, in AC format (or -1 when none)
|
||||||
ETargetType getTargetType() const; //deprecated
|
ETargetType getTargetType() const; //deprecated
|
||||||
|
|
||||||
const CSpell::TargetInfo getTargetInfo(const int level) const;
|
CSpell::TargetInfo getTargetInfo(const int level) const;
|
||||||
|
CSpell::TargetInfo getTargetInfoEx(const int level, ECastingMode::ECastingMode mode) const;
|
||||||
|
|
||||||
bool isCombatSpell() const;
|
bool isCombatSpell() const;
|
||||||
bool isAdventureSpell() const;
|
bool isAdventureSpell() const;
|
||||||
@ -192,7 +191,7 @@ public:
|
|||||||
ESpellCastProblem::ESpellCastProblem isImmuneBy(const IBonusBearer *obj) const;
|
ESpellCastProblem::ESpellCastProblem isImmuneBy(const IBonusBearer *obj) const;
|
||||||
|
|
||||||
//checks for creature immunity / anything that prevent casting *at given hex* - doesn't take into acount general problems such as not having spellbook or mana points etc.
|
//checks for creature immunity / anything that prevent casting *at given hex* - doesn't take into acount general problems such as not having spellbook or mana points etc.
|
||||||
ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, ECastingMode::ECastingMode mode, const CStack * obj) const;
|
ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const;
|
||||||
|
|
||||||
//internal, for use only by Mechanics classes. applying secondary skills
|
//internal, for use only by Mechanics classes. applying secondary skills
|
||||||
ui32 calculateBonus(ui32 baseDamage, const CGHeroInstance * caster, const CStack * affectedCreature) const;
|
ui32 calculateBonus(ui32 baseDamage, const CGHeroInstance * caster, const CStack * affectedCreature) const;
|
||||||
@ -202,7 +201,8 @@ public:
|
|||||||
///calculate healed HP for all spells casted by hero
|
///calculate healed HP for all spells casted by hero
|
||||||
ui32 calculateHealedHP(const CGHeroInstance * caster, const CStack * stack, const CStack * sacrificedStack = nullptr) const;
|
ui32 calculateHealedHP(const CGHeroInstance * caster, const CStack * stack, const CStack * sacrificedStack = nullptr) const;
|
||||||
|
|
||||||
|
///selects from allStacks actually affected stacks
|
||||||
|
std::set<const CStack *> getAffectedStacks(const CBattleInfoCallback * cb, ECastingMode::ECastingMode mode, PlayerColor casterColor, int spellLvl, BattleHex destination, const CGHeroInstance * caster = nullptr) const;
|
||||||
|
|
||||||
si32 getCost(const int skillLevel) const;
|
si32 getCost(const int skillLevel) const;
|
||||||
|
|
||||||
@ -289,6 +289,38 @@ private:
|
|||||||
ISpellMechanics * mechanics;//(!) do not serialize
|
ISpellMechanics * mechanics;//(!) do not serialize
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class DLL_LINKAGE ISpellMechanics
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
struct SpellTargetingContext
|
||||||
|
{
|
||||||
|
CBattleInfoCallback * cb;
|
||||||
|
|
||||||
|
CSpell::TargetInfo ti;
|
||||||
|
};
|
||||||
|
|
||||||
|
public:
|
||||||
|
ISpellMechanics(CSpell * s);
|
||||||
|
virtual ~ISpellMechanics(){};
|
||||||
|
|
||||||
|
virtual std::set<const CStack *> getAffectedStacks(SpellTargetingContext & ctx) const = 0;
|
||||||
|
|
||||||
|
virtual ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const = 0;
|
||||||
|
|
||||||
|
|
||||||
|
/** \brief
|
||||||
|
*
|
||||||
|
* \param
|
||||||
|
* \return true if no error
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
virtual bool adventureCast(SpellCastContext & context) const = 0;
|
||||||
|
virtual bool battleCast(SpellCastContext & context) const = 0;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
CSpell * owner;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
bool DLL_LINKAGE isInScreenRange(const int3 ¢er, const int3 &pos); //for spells like Dimension Door
|
bool DLL_LINKAGE isInScreenRange(const int3 ¢er, const int3 &pos); //for spells like Dimension Door
|
||||||
|
@ -402,8 +402,12 @@ namespace ESpellCastProblem
|
|||||||
|
|
||||||
namespace ECastingMode
|
namespace ECastingMode
|
||||||
{
|
{
|
||||||
enum ECastingMode {HERO_CASTING, AFTER_ATTACK_CASTING, //also includes cast before attack
|
enum ECastingMode
|
||||||
MAGIC_MIRROR, CREATURE_ACTIVE_CASTING, ENCHANTER_CASTING};
|
{
|
||||||
|
HERO_CASTING, AFTER_ATTACK_CASTING, //also includes cast before attack
|
||||||
|
MAGIC_MIRROR, CREATURE_ACTIVE_CASTING, ENCHANTER_CASTING,
|
||||||
|
SPELL_LIKE_ATTACK
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace EMarketMode
|
namespace EMarketMode
|
||||||
|
@ -14,15 +14,43 @@
|
|||||||
#include "mapObjects/CGHeroInstance.h"
|
#include "mapObjects/CGHeroInstance.h"
|
||||||
#include "BattleState.h"
|
#include "BattleState.h"
|
||||||
|
|
||||||
|
#include "NetPacks.h"
|
||||||
|
|
||||||
///DefaultSpellMechanics
|
///DefaultSpellMechanics
|
||||||
ESpellCastProblem::ESpellCastProblem DefaultSpellMechanics::isImmuneByStack(const CGHeroInstance * caster, ECastingMode::ECastingMode mode, const CStack * obj)
|
|
||||||
|
std::set<const CStack *> DefaultSpellMechanics::getAffectedStacks(SpellTargetingContext & ctx) const
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ESpellCastProblem::ESpellCastProblem DefaultSpellMechanics::isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const
|
||||||
{
|
{
|
||||||
//by default use general algorithm
|
//by default use general algorithm
|
||||||
return owner->isImmuneBy(obj);
|
return owner->isImmuneBy(obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool DefaultSpellMechanics::adventureCast(SpellCastContext& context) const
|
||||||
|
{
|
||||||
|
return false; //there is no general algorithm for castind adventure spells
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DefaultSpellMechanics::battleCast(SpellCastContext& context) const
|
||||||
|
{
|
||||||
|
return false; //todo; DefaultSpellMechanics::battleCast
|
||||||
|
}
|
||||||
|
|
||||||
|
///OffenciveSpellMechnics
|
||||||
|
bool OffenciveSpellMechnics::battleCast(SpellCastContext& context) const
|
||||||
|
{
|
||||||
|
assert(owner->isOffensiveSpell());
|
||||||
|
|
||||||
|
//todo:OffenciveSpellMechnics::battleCast
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
///CloneMechanics
|
///CloneMechanics
|
||||||
ESpellCastProblem::ESpellCastProblem CloneMechnics::isImmuneByStack(const CGHeroInstance* caster, ECastingMode::ECastingMode mode, const CStack * obj)
|
ESpellCastProblem::ESpellCastProblem CloneMechanics::isImmuneByStack(const CGHeroInstance* caster, const CStack * obj) const
|
||||||
{
|
{
|
||||||
//can't clone already cloned creature
|
//can't clone already cloned creature
|
||||||
if (vstd::contains(obj->state, EBattleStackState::CLONED))
|
if (vstd::contains(obj->state, EBattleStackState::CLONED))
|
||||||
@ -47,11 +75,11 @@ ESpellCastProblem::ESpellCastProblem CloneMechnics::isImmuneByStack(const CGHero
|
|||||||
return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
|
return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
|
||||||
}
|
}
|
||||||
//use default algorithm only if there is no mechanics-related problem
|
//use default algorithm only if there is no mechanics-related problem
|
||||||
return DefaultSpellMechanics::isImmuneByStack(caster,mode,obj);
|
return DefaultSpellMechanics::isImmuneByStack(caster,obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
///DispellHelpfulMechanics
|
///DispellHelpfulMechanics
|
||||||
ESpellCastProblem::ESpellCastProblem DispellHelpfulMechanics::isImmuneByStack(const CGHeroInstance* caster, ECastingMode::ECastingMode mode, const CStack* obj)
|
ESpellCastProblem::ESpellCastProblem DispellHelpfulMechanics::isImmuneByStack(const CGHeroInstance* caster, const CStack* obj) const
|
||||||
{
|
{
|
||||||
TBonusListPtr spellBon = obj->getSpellBonuses();
|
TBonusListPtr spellBon = obj->getSpellBonuses();
|
||||||
bool hasPositiveSpell = false;
|
bool hasPositiveSpell = false;
|
||||||
@ -69,11 +97,11 @@ ESpellCastProblem::ESpellCastProblem DispellHelpfulMechanics::isImmuneByStack(co
|
|||||||
}
|
}
|
||||||
|
|
||||||
//use default algorithm only if there is no mechanics-related problem
|
//use default algorithm only if there is no mechanics-related problem
|
||||||
return DefaultSpellMechanics::isImmuneByStack(caster,mode,obj);
|
return DefaultSpellMechanics::isImmuneByStack(caster,obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
///HypnotizeMechanics
|
///HypnotizeMechanics
|
||||||
ESpellCastProblem::ESpellCastProblem HypnotizeMechanics::isImmuneByStack(const CGHeroInstance* caster, ECastingMode::ECastingMode mode, const CStack* obj)
|
ESpellCastProblem::ESpellCastProblem HypnotizeMechanics::isImmuneByStack(const CGHeroInstance* caster, const CStack* obj) const
|
||||||
{
|
{
|
||||||
if(nullptr != caster) //do not resist hypnotize casted after attack, for example
|
if(nullptr != caster) //do not resist hypnotize casted after attack, for example
|
||||||
{
|
{
|
||||||
@ -85,12 +113,12 @@ ESpellCastProblem::ESpellCastProblem HypnotizeMechanics::isImmuneByStack(const C
|
|||||||
if (subjectHealth > maxHealth)
|
if (subjectHealth > maxHealth)
|
||||||
return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
|
return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
|
||||||
}
|
}
|
||||||
return DefaultSpellMechanics::isImmuneByStack(caster,mode,obj);
|
return DefaultSpellMechanics::isImmuneByStack(caster,obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
///SpecialRisingSpellMechanics
|
///SpecialRisingSpellMechanics
|
||||||
ESpellCastProblem::ESpellCastProblem SpecialRisingSpellMechanics::isImmuneByStack(const CGHeroInstance* caster, ECastingMode::ECastingMode mode, const CStack* obj)
|
ESpellCastProblem::ESpellCastProblem SpecialRisingSpellMechanics::isImmuneByStack(const CGHeroInstance* caster, const CStack* obj) const
|
||||||
{
|
{
|
||||||
// following does apply to resurrect and animate dead(?) only
|
// following does apply to resurrect and animate dead(?) only
|
||||||
// for sacrifice health calculation and health limit check don't matter
|
// for sacrifice health calculation and health limit check don't matter
|
||||||
@ -105,7 +133,7 @@ ESpellCastProblem::ESpellCastProblem SpecialRisingSpellMechanics::isImmuneByStac
|
|||||||
return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
|
return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
|
||||||
}
|
}
|
||||||
|
|
||||||
return DefaultSpellMechanics::isImmuneByStack(caster,mode,obj);
|
return DefaultSpellMechanics::isImmuneByStack(caster,obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -16,32 +16,42 @@ class DefaultSpellMechanics: public ISpellMechanics
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
DefaultSpellMechanics(CSpell * s): ISpellMechanics(s){};
|
DefaultSpellMechanics(CSpell * s): ISpellMechanics(s){};
|
||||||
ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, ECastingMode::ECastingMode mode, const CStack * obj) override;
|
|
||||||
|
|
||||||
bool adventureCast(SpellCastContext & context) override;
|
std::set<const CStack *> getAffectedStacks(SpellTargetingContext & ctx) const override;
|
||||||
bool battleCast(SpellCastContext & context) override;
|
|
||||||
|
ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const override;
|
||||||
|
|
||||||
|
bool adventureCast(SpellCastContext & context) const override;
|
||||||
|
bool battleCast(SpellCastContext & context) const override;
|
||||||
};
|
};
|
||||||
|
|
||||||
class CloneMechnics: public DefaultSpellMechanics
|
class OffenciveSpellMechnics: public DefaultSpellMechanics
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
CloneMechnics(CSpell * s): DefaultSpellMechanics(s){};
|
OffenciveSpellMechnics(CSpell * s): DefaultSpellMechanics(s){};
|
||||||
ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, ECastingMode::ECastingMode mode, const CStack * obj) override;
|
bool battleCast(SpellCastContext & context) const override;
|
||||||
|
};
|
||||||
|
|
||||||
|
class CloneMechanics: public DefaultSpellMechanics
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
CloneMechanics(CSpell * s): DefaultSpellMechanics(s){};
|
||||||
|
ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const override;
|
||||||
};
|
};
|
||||||
|
|
||||||
class DispellHelpfulMechanics: public DefaultSpellMechanics
|
class DispellHelpfulMechanics: public DefaultSpellMechanics
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
DispellHelpfulMechanics(CSpell * s): DefaultSpellMechanics(s){};
|
DispellHelpfulMechanics(CSpell * s): DefaultSpellMechanics(s){};
|
||||||
ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, ECastingMode::ECastingMode mode, const CStack * obj) override;
|
ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const override;
|
||||||
};
|
};
|
||||||
|
|
||||||
class HypnotizeMechanics: public DefaultSpellMechanics
|
class HypnotizeMechanics: public DefaultSpellMechanics
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
HypnotizeMechanics(CSpell * s): DefaultSpellMechanics(s){};
|
HypnotizeMechanics(CSpell * s): DefaultSpellMechanics(s){};
|
||||||
ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, ECastingMode::ECastingMode mode, const CStack * obj) override;
|
ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const override;
|
||||||
};
|
};
|
||||||
|
|
||||||
///all rising spells
|
///all rising spells
|
||||||
class RisingSpellMechanics: public DefaultSpellMechanics
|
class RisingSpellMechanics: public DefaultSpellMechanics
|
||||||
@ -56,7 +66,7 @@ class SpecialRisingSpellMechanics: public RisingSpellMechanics
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
SpecialRisingSpellMechanics(CSpell * s): RisingSpellMechanics(s){};
|
SpecialRisingSpellMechanics(CSpell * s): RisingSpellMechanics(s){};
|
||||||
ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, ECastingMode::ECastingMode mode, const CStack * obj) override;
|
ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const override;
|
||||||
};
|
};
|
||||||
|
|
||||||
class SacrificeMechanics: public RisingSpellMechanics
|
class SacrificeMechanics: public RisingSpellMechanics
|
||||||
|
@ -787,7 +787,8 @@ void CGameHandler::prepareAttack(BattleAttack &bat, const CStack *att, const CSt
|
|||||||
bat.bsa.front().flags |= BattleStackAttacked::EFFECT;
|
bat.bsa.front().flags |= BattleStackAttacked::EFFECT;
|
||||||
bat.bsa.front().effect = VLC->spellh->objects.at(bonus->subtype)->mainEffectAnim; //hopefully it does not interfere with any other effect?
|
bat.bsa.front().effect = VLC->spellh->objects.at(bonus->subtype)->mainEffectAnim; //hopefully it does not interfere with any other effect?
|
||||||
|
|
||||||
std::set<const CStack*> attackedCreatures = gs->curB->getAffectedCreatures(SpellID(bonus->subtype).toSpell(), bonus->val, att->owner, targetHex);
|
std::set<const CStack*> attackedCreatures = SpellID(bonus->subtype).toSpell()->getAffectedStacks(gs->curB, ECastingMode::SPELL_LIKE_ATTACK, att->owner, bonus->val, targetHex);
|
||||||
|
|
||||||
//TODO: get exact attacked hex for defender
|
//TODO: get exact attacked hex for defender
|
||||||
|
|
||||||
for(const CStack * stack : attackedCreatures)
|
for(const CStack * stack : attackedCreatures)
|
||||||
@ -4018,31 +4019,9 @@ void CGameHandler::handleSpellCasting( SpellID spellID, int spellLvl, BattleHex
|
|||||||
//must be vector, as in Chain Lightning order matters
|
//must be vector, as in Chain Lightning order matters
|
||||||
std::vector<const CStack*> attackedCres; //CStack vector is somewhat more suitable than ID vector
|
std::vector<const CStack*> attackedCres; //CStack vector is somewhat more suitable than ID vector
|
||||||
|
|
||||||
if (mode != ECastingMode::ENCHANTER_CASTING)
|
auto creatures = spell->getAffectedStacks(gs->curB, mode, casterColor, spellLvl, destination, caster);
|
||||||
{
|
std::copy(creatures.begin(), creatures.end(), std::back_inserter(attackedCres));
|
||||||
auto creatures = gs->curB->getAffectedCreatures(spell, spellLvl, casterColor, destination);
|
|
||||||
std::copy(creatures.begin(), creatures.end(), std::back_inserter(attackedCres));
|
|
||||||
}
|
|
||||||
else //enchanter - hit all possible stacks
|
|
||||||
{
|
|
||||||
for (const CStack * stack : gs->curB->stacks)
|
|
||||||
{
|
|
||||||
/*if it's non negative spell and our unit or non positive spell and hostile unit */
|
|
||||||
if((!spell->isNegative() && stack->owner == casterColor)
|
|
||||||
|| (!spell->isPositive() && stack->owner != casterColor))
|
|
||||||
{
|
|
||||||
if(stack->isValidTarget()) //TODO: allow dead targets somewhere in the future
|
|
||||||
{
|
|
||||||
attackedCres.push_back(stack);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
vstd::erase_if(attackedCres,[=](const CStack * s){
|
|
||||||
return ESpellCastProblem::OK != spell->isImmuneByStack(caster,mode,s);
|
|
||||||
});
|
|
||||||
|
|
||||||
for (auto cre : attackedCres)
|
for (auto cre : attackedCres)
|
||||||
{
|
{
|
||||||
sc.affectedCres.insert (cre->ID);
|
sc.affectedCres.insert (cre->ID);
|
||||||
@ -4453,7 +4432,7 @@ void CGameHandler::handleSpellCasting( SpellID spellID, int spellLvl, BattleHex
|
|||||||
{
|
{
|
||||||
if(battleStack->owner == gs->curB->sides.at(casterSide).color) //get enemy stacks which can be affected by this spell
|
if(battleStack->owner == gs->curB->sides.at(casterSide).color) //get enemy stacks which can be affected by this spell
|
||||||
{
|
{
|
||||||
if (ESpellCastProblem::OK == spell->isImmuneByStack(nullptr, ECastingMode::MAGIC_MIRROR, battleStack))
|
if (ESpellCastProblem::OK == spell->isImmuneByStack(nullptr, battleStack))
|
||||||
mirrorTargets.push_back(battleStack);
|
mirrorTargets.push_back(battleStack);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user