mirror of
https://github.com/vcmi/vcmi.git
synced 2024-12-24 22:14:36 +02:00
Use spell mechanics clases in GameHandler
This commit is contained in:
parent
6d4cd1c91b
commit
c67887ae58
@ -59,6 +59,18 @@ extern bool end2;
|
||||
bnr.round = gs->curB->round + 1;\
|
||||
sendAndApply(&bnr);
|
||||
|
||||
class ServerSpellCastEnvironment: public SpellCastEnvironment
|
||||
{
|
||||
public:
|
||||
ServerSpellCastEnvironment(CGameHandler * gh);
|
||||
~ServerSpellCastEnvironment(){};
|
||||
void sendAndApply(CPackForClient * info) const override;
|
||||
CRandomGenerator & getRandomGenerator() const override;
|
||||
void complain(const std::string & problem) const override;
|
||||
private:
|
||||
CGameHandler * gh;
|
||||
};
|
||||
|
||||
CondSh<bool> battleMadeAction;
|
||||
CondSh<BattleResult *> battleResult(nullptr);
|
||||
template <typename T> class CApplyOnGH;
|
||||
@ -3932,518 +3944,28 @@ void CGameHandler::playerMessage( PlayerColor player, const std::string &message
|
||||
}
|
||||
}
|
||||
|
||||
void CGameHandler::handleSpellCasting( SpellID spellID, int spellLvl, BattleHex destination, ui8 casterSide, PlayerColor casterColor, const CGHeroInstance * caster, const CGHeroInstance * secHero,
|
||||
void CGameHandler::handleSpellCasting(SpellID spellID, int spellLvl, BattleHex destination, ui8 casterSide, PlayerColor casterColor, const CGHeroInstance * caster, const CGHeroInstance * secHero,
|
||||
int usedSpellPower, ECastingMode::ECastingMode mode, const CStack * stack, si32 selectedStack)
|
||||
{
|
||||
const CSpell * spell = SpellID(spellID).toSpell();
|
||||
|
||||
ServerSpellCastEnvironment spellEnvironment(this);
|
||||
|
||||
//Helper local function that creates obstacle on given position. Obstacle type is inferred from spell type.
|
||||
//It creates, sends and applies needed package.
|
||||
auto placeObstacle = [&](BattleHex pos)
|
||||
{
|
||||
static int obstacleIdToGive = gs->curB->obstacles.size()
|
||||
? (gs->curB->obstacles.back()->uniqueID+1)
|
||||
: 0;
|
||||
BattleSpellCastParameters parameters;
|
||||
parameters.spellLvl = spellLvl;
|
||||
parameters.destination = destination;
|
||||
parameters.casterSide = casterSide;
|
||||
parameters.casterColor = casterColor;
|
||||
parameters.caster = caster;
|
||||
parameters.secHero = secHero;
|
||||
|
||||
auto obstacle = make_shared<SpellCreatedObstacle>();
|
||||
switch(spellID.toEnum()) // :/
|
||||
{
|
||||
case SpellID::QUICKSAND:
|
||||
obstacle->obstacleType = CObstacleInstance::QUICKSAND;
|
||||
obstacle->turnsRemaining = -1;
|
||||
obstacle->visibleForAnotherSide = false;
|
||||
break;
|
||||
case SpellID::LAND_MINE:
|
||||
obstacle->obstacleType = CObstacleInstance::LAND_MINE;
|
||||
obstacle->turnsRemaining = -1;
|
||||
obstacle->visibleForAnotherSide = false;
|
||||
break;
|
||||
case SpellID::FIRE_WALL:
|
||||
obstacle->obstacleType = CObstacleInstance::FIRE_WALL;
|
||||
obstacle->turnsRemaining = 2;
|
||||
obstacle->visibleForAnotherSide = true;
|
||||
break;
|
||||
case SpellID::FORCE_FIELD:
|
||||
obstacle->obstacleType = CObstacleInstance::FORCE_FIELD;
|
||||
obstacle->turnsRemaining = 2;
|
||||
obstacle->visibleForAnotherSide = true;
|
||||
break;
|
||||
default:
|
||||
//this function cannot be used with spells that do not create obstacles
|
||||
assert(0);
|
||||
}
|
||||
parameters.usedSpellPower = usedSpellPower;
|
||||
parameters.mode = mode;
|
||||
parameters.casterStack = stack;
|
||||
parameters.selectedStack = gs->curB->battleGetStackByID(selectedStack, false);
|
||||
|
||||
obstacle->pos = pos;
|
||||
obstacle->casterSide = casterSide;
|
||||
obstacle->ID = spellID;
|
||||
obstacle->spellLevel = spellLvl;
|
||||
obstacle->casterSpellPower = usedSpellPower;
|
||||
obstacle->uniqueID = obstacleIdToGive++;
|
||||
|
||||
BattleObstaclePlaced bop;
|
||||
bop.obstacle = obstacle;
|
||||
sendAndApply(&bop);
|
||||
};
|
||||
|
||||
BattleSpellCast sc;
|
||||
sc.side = casterSide;
|
||||
sc.id = spellID;
|
||||
sc.skill = spellLvl;
|
||||
sc.tile = destination;
|
||||
sc.dmgToDisplay = 0;
|
||||
sc.castedByHero = (bool)caster;
|
||||
sc.attackerType = (stack ? stack->type->idNumber : CreatureID(CreatureID::NONE));
|
||||
sc.manaGained = 0;
|
||||
sc.spellCost = 0;
|
||||
|
||||
if (caster) //calculate spell cost
|
||||
{
|
||||
sc.spellCost = gs->curB->battleGetSpellCost(spell, caster);
|
||||
|
||||
if (secHero && mode == ECastingMode::HERO_CASTING) //handle mana channel
|
||||
{
|
||||
int manaChannel = 0;
|
||||
for(CStack * stack : gs->curB->stacks) //TODO: shouldn't bonus system handle it somehow?
|
||||
{
|
||||
if (stack->owner == secHero->tempOwner)
|
||||
{
|
||||
vstd::amax(manaChannel, stack->valOfBonuses(Bonus::MANA_CHANNELING));
|
||||
}
|
||||
}
|
||||
sc.manaGained = (manaChannel * sc.spellCost) / 100;
|
||||
}
|
||||
}
|
||||
|
||||
//calculating affected creatures for all spells
|
||||
//must be vector, as in Chain Lightning order matters
|
||||
std::vector<const CStack*> attackedCres; //CStack vector is somewhat more suitable than ID vector
|
||||
|
||||
auto creatures = spell->getAffectedStacks(gs->curB, mode, casterColor, spellLvl, destination, caster);
|
||||
std::copy(creatures.begin(), creatures.end(), std::back_inserter(attackedCres));
|
||||
|
||||
for (auto cre : attackedCres)
|
||||
{
|
||||
sc.affectedCres.insert (cre->ID);
|
||||
}
|
||||
|
||||
//checking if creatures resist
|
||||
//resistance is applied only to negative spells
|
||||
if(spell->isNegative())
|
||||
{
|
||||
for(auto s : attackedCres)
|
||||
{
|
||||
const int prob = std::min((s)->magicResistance(), 100); //probability of resistance in %
|
||||
|
||||
if(gs->getRandomGenerator().nextInt(99) < prob)
|
||||
{
|
||||
sc.resisted.push_back(s->ID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//calculating dmg to display
|
||||
if (spellID == SpellID::DEATH_STARE || spellID == SpellID::ACID_BREATH_DAMAGE)
|
||||
{
|
||||
sc.dmgToDisplay = usedSpellPower;
|
||||
if (spellID == SpellID::DEATH_STARE)
|
||||
vstd::amin(sc.dmgToDisplay, (*attackedCres.begin())->count); //stack is already reduced after attack
|
||||
}
|
||||
StacksInjured si;
|
||||
|
||||
//applying effects
|
||||
|
||||
if (spell->isOffensiveSpell())
|
||||
{
|
||||
int spellDamage = 0;
|
||||
if (stack && mode != ECastingMode::MAGIC_MIRROR)
|
||||
{
|
||||
int unitSpellPower = stack->valOfBonuses(Bonus::SPECIFIC_SPELL_POWER, spellID.toEnum());
|
||||
if (unitSpellPower)
|
||||
sc.dmgToDisplay = spellDamage = stack->count * unitSpellPower; //TODO: handle immunities
|
||||
else //Faerie Dragon
|
||||
{
|
||||
usedSpellPower = stack->valOfBonuses(Bonus::CREATURE_SPELL_POWER) * stack->count / 100;
|
||||
sc.dmgToDisplay = 0;
|
||||
}
|
||||
}
|
||||
int chainLightningModifier = 0;
|
||||
for(auto & attackedCre : attackedCres)
|
||||
{
|
||||
if(vstd::contains(sc.resisted, (attackedCre)->ID)) //this creature resisted the spell
|
||||
continue;
|
||||
|
||||
BattleStackAttacked bsa;
|
||||
if ((destination > -1 && (attackedCre)->coversPos(destination)) || (spell->getLevelInfo(spellLvl).range == "X" || mode == ECastingMode::ENCHANTER_CASTING))
|
||||
//display effect only upon primary target of area spell
|
||||
//FIXME: if no stack is attacked, there is no animation and interface freezes
|
||||
{
|
||||
bsa.flags |= BattleStackAttacked::EFFECT;
|
||||
bsa.effect = spell->mainEffectAnim;
|
||||
}
|
||||
if (spellDamage)
|
||||
bsa.damageAmount = spellDamage >> chainLightningModifier;
|
||||
else
|
||||
bsa.damageAmount = spell->calculateDamage(caster, attackedCre, spellLvl, usedSpellPower) >> chainLightningModifier;
|
||||
|
||||
sc.dmgToDisplay += bsa.damageAmount;
|
||||
|
||||
bsa.stackAttacked = (attackedCre)->ID;
|
||||
if (mode == ECastingMode::ENCHANTER_CASTING) //multiple damage spells cast
|
||||
bsa.attackerID = stack->ID;
|
||||
else
|
||||
bsa.attackerID = -1;
|
||||
(attackedCre)->prepareAttacked(bsa, gs->getRandomGenerator());
|
||||
si.stacks.push_back(bsa);
|
||||
|
||||
if (spellID == SpellID::CHAIN_LIGHTNING)
|
||||
++chainLightningModifier;
|
||||
}
|
||||
}
|
||||
|
||||
if (spell->hasEffects())
|
||||
{
|
||||
int stackSpellPower = 0;
|
||||
if (stack && mode != ECastingMode::MAGIC_MIRROR)
|
||||
{
|
||||
stackSpellPower = stack->valOfBonuses(Bonus::CREATURE_ENCHANT_POWER);
|
||||
}
|
||||
SetStackEffect sse;
|
||||
Bonus pseudoBonus;
|
||||
pseudoBonus.sid = spellID;
|
||||
pseudoBonus.val = spellLvl;
|
||||
pseudoBonus.turnsRemain = gs->curB->calculateSpellDuration(spell, caster, stackSpellPower ? stackSpellPower : usedSpellPower);
|
||||
CStack::stackEffectToFeature(sse.effect, pseudoBonus);
|
||||
if (spellID == SpellID::SHIELD || spellID == SpellID::AIR_SHIELD)
|
||||
{
|
||||
sse.effect.back().val = (100 - sse.effect.back().val); //fix to original config: shield should display damage reduction
|
||||
}
|
||||
if (spellID == SpellID::BIND && stack)//bind
|
||||
{
|
||||
sse.effect.back().additionalInfo = stack->ID; //we need to know who casted Bind
|
||||
}
|
||||
const Bonus * bonus = nullptr;
|
||||
if (caster)
|
||||
bonus = caster->getBonusLocalFirst(Selector::typeSubtype(Bonus::SPECIAL_PECULIAR_ENCHANT, spellID));
|
||||
//TODO does hero specialty should affects his stack casting spells?
|
||||
|
||||
si32 power = 0;
|
||||
for(const CStack *affected : attackedCres)
|
||||
{
|
||||
if(vstd::contains(sc.resisted, affected->ID)) //this creature resisted the spell
|
||||
continue;
|
||||
sse.stacks.push_back(affected->ID);
|
||||
|
||||
//Apply hero specials - peculiar enchants
|
||||
const ui8 tier = std::max((ui8)1, affected->getCreature()->level); //don't divide by 0 for certain creatures (commanders, war machines)
|
||||
if (bonus)
|
||||
{
|
||||
switch(bonus->additionalInfo)
|
||||
{
|
||||
case 0: //normal
|
||||
{
|
||||
switch(tier)
|
||||
{
|
||||
case 1: case 2:
|
||||
power = 3;
|
||||
break;
|
||||
case 3: case 4:
|
||||
power = 2;
|
||||
break;
|
||||
case 5: case 6:
|
||||
power = 1;
|
||||
break;
|
||||
}
|
||||
Bonus specialBonus(sse.effect.back());
|
||||
specialBonus.val = power; //it doesn't necessarily make sense for some spells, use it wisely
|
||||
sse.uniqueBonuses.push_back (std::pair<ui32,Bonus> (affected->ID, specialBonus)); //additional premy to given effect
|
||||
}
|
||||
break;
|
||||
case 1: //only Coronius as yet
|
||||
{
|
||||
power = std::max(5 - tier, 0);
|
||||
Bonus specialBonus = CStack::featureGenerator(Bonus::PRIMARY_SKILL, PrimarySkill::ATTACK, power, pseudoBonus.turnsRemain);
|
||||
specialBonus.sid = spellID;
|
||||
sse.uniqueBonuses.push_back (std::pair<ui32,Bonus> (affected->ID, specialBonus)); //additional attack to Slayer effect
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (caster && caster->hasBonusOfType(Bonus::SPECIAL_BLESS_DAMAGE, spellID)) //TODO: better handling of bonus percentages
|
||||
{
|
||||
int damagePercent = caster->level * caster->valOfBonuses(Bonus::SPECIAL_BLESS_DAMAGE, spellID.toEnum()) / tier;
|
||||
Bonus specialBonus = CStack::featureGenerator(Bonus::CREATURE_DAMAGE, 0, damagePercent, pseudoBonus.turnsRemain);
|
||||
specialBonus.valType = Bonus::PERCENT_TO_ALL;
|
||||
specialBonus.sid = spellID;
|
||||
sse.uniqueBonuses.push_back (std::pair<ui32,Bonus> (affected->ID, specialBonus));
|
||||
}
|
||||
}
|
||||
|
||||
if(!sse.stacks.empty())
|
||||
sendAndApply(&sse);
|
||||
|
||||
}
|
||||
|
||||
if(spell->isHealingSpell())
|
||||
{
|
||||
int hpGained = 0;
|
||||
if (stack)
|
||||
{
|
||||
int unitSpellPower = stack->valOfBonuses(Bonus::SPECIFIC_SPELL_POWER, spellID.toEnum());
|
||||
if (unitSpellPower)
|
||||
hpGained = stack->count * unitSpellPower; //Archangel
|
||||
else //Faerie Dragon-like effect - unused so far
|
||||
usedSpellPower = stack->valOfBonuses(Bonus::CREATURE_SPELL_POWER) * stack->count / 100;
|
||||
}
|
||||
StacksHealedOrResurrected shr;
|
||||
shr.lifeDrain = false;
|
||||
shr.tentHealing = false;
|
||||
for(auto & attackedCre : attackedCres)
|
||||
{
|
||||
StacksHealedOrResurrected::HealInfo hi;
|
||||
hi.stackID = (attackedCre)->ID;
|
||||
if (stack) //casted by creature
|
||||
{
|
||||
if (hpGained)
|
||||
{
|
||||
hi.healedHP = gs->curB->calculateHealedHP(hpGained, spell, attackedCre); //archangel
|
||||
}
|
||||
else
|
||||
hi.healedHP = gs->curB->calculateHealedHP(spell, usedSpellPower, spellLvl, attackedCre); //any typical spell (commander's cure or animate dead)
|
||||
}
|
||||
else
|
||||
hi.healedHP = spell->calculateHealedHP(caster, attackedCre, gs->curB->battleGetStackByID(selectedStack)); //Casted by hero
|
||||
hi.lowLevelResurrection = spellLvl <= 1;
|
||||
shr.healedStacks.push_back(hi);
|
||||
}
|
||||
if(!shr.healedStacks.empty())
|
||||
sendAndApply(&shr);
|
||||
if (spellID == SpellID::SACRIFICE) //remove victim
|
||||
{
|
||||
if (selectedStack == gs->curB->activeStack)
|
||||
//set another active stack than the one removed, or bad things will happen
|
||||
//TODO: make that part of BattleStacksRemoved? what about client update?
|
||||
{
|
||||
//makeStackDoNothing(gs->curB->getStack (selectedStack));
|
||||
|
||||
BattleSetActiveStack sas;
|
||||
|
||||
//std::vector<const CStack *> hlp;
|
||||
//battleGetStackQueue(hlp, 1, selectedStack); //next after this one
|
||||
|
||||
//if(hlp.size())
|
||||
//{
|
||||
// sas.stack = hlp[0]->ID;
|
||||
//}
|
||||
//else
|
||||
// complain ("No new stack to activate!");
|
||||
sas.stack = gs->curB->getNextStack()->ID; //why the hell next stack has same ID as current?
|
||||
sendAndApply(&sas);
|
||||
|
||||
}
|
||||
BattleStacksRemoved bsr;
|
||||
bsr.stackIDs.insert (selectedStack); //somehow it works for teleport?
|
||||
sendAndApply(&bsr);
|
||||
}
|
||||
}
|
||||
|
||||
switch (spellID)
|
||||
{
|
||||
case SpellID::QUICKSAND:
|
||||
case SpellID::LAND_MINE:
|
||||
{
|
||||
std::vector<BattleHex> availableTiles;
|
||||
for(int i = 0; i < GameConstants::BFIELD_SIZE; i += 1)
|
||||
{
|
||||
BattleHex hex = i;
|
||||
if(hex.getX() > 2 && hex.getX() < 14 && !battleGetStackByPos(hex, false) && !battleGetObstacleOnPos(hex, false))
|
||||
availableTiles.push_back(hex);
|
||||
}
|
||||
boost::range::random_shuffle(availableTiles);
|
||||
|
||||
const int patchesForSkill[] = {4, 4, 6, 8};
|
||||
const int patchesToPut = std::min<int>(patchesForSkill[spellLvl], availableTiles.size());
|
||||
|
||||
//land mines or quicksand patches are handled as spell created obstacles
|
||||
for (int i = 0; i < patchesToPut; i++)
|
||||
placeObstacle(availableTiles.at(i));
|
||||
}
|
||||
|
||||
break;
|
||||
case SpellID::FORCE_FIELD:
|
||||
placeObstacle(destination);
|
||||
break;
|
||||
case SpellID::FIRE_WALL:
|
||||
{
|
||||
//fire wall is build from multiple obstacles - one fire piece for each affected hex
|
||||
auto affectedHexes = spell->rangeInHexes(destination, spellLvl, casterSide);
|
||||
for(BattleHex hex : affectedHexes)
|
||||
placeObstacle(hex);
|
||||
}
|
||||
break;
|
||||
case SpellID::TELEPORT:
|
||||
{
|
||||
BattleStackMoved bsm;
|
||||
bsm.distance = -1;
|
||||
bsm.stack = selectedStack;
|
||||
std::vector<BattleHex> tiles;
|
||||
tiles.push_back(destination);
|
||||
bsm.tilesToMove = tiles;
|
||||
bsm.teleporting = true;
|
||||
sendAndApply(&bsm);
|
||||
break;
|
||||
}
|
||||
case SpellID::SUMMON_FIRE_ELEMENTAL:
|
||||
case SpellID::SUMMON_EARTH_ELEMENTAL:
|
||||
case SpellID::SUMMON_WATER_ELEMENTAL:
|
||||
case SpellID::SUMMON_AIR_ELEMENTAL:
|
||||
{ //elemental summoning
|
||||
CreatureID creID;
|
||||
switch(spellID)
|
||||
{
|
||||
case SpellID::SUMMON_FIRE_ELEMENTAL:
|
||||
creID = CreatureID::FIRE_ELEMENTAL;
|
||||
break;
|
||||
case SpellID::SUMMON_EARTH_ELEMENTAL:
|
||||
creID = CreatureID::EARTH_ELEMENTAL;
|
||||
break;
|
||||
case SpellID::SUMMON_WATER_ELEMENTAL:
|
||||
creID = CreatureID::WATER_ELEMENTAL;
|
||||
break;
|
||||
case SpellID::SUMMON_AIR_ELEMENTAL:
|
||||
creID = CreatureID::AIR_ELEMENTAL;
|
||||
break;
|
||||
}
|
||||
|
||||
BattleStackAdded bsa;
|
||||
bsa.creID = creID;
|
||||
bsa.attacker = !(bool)casterSide;
|
||||
bsa.summoned = true;
|
||||
bsa.pos = gs->curB->getAvaliableHex(creID, !(bool)casterSide); //TODO: unify it
|
||||
|
||||
//TODO stack casting -> probably power will be zero; set the proper number of creatures manually
|
||||
int percentBonus = caster ? caster->valOfBonuses(Bonus::SPECIFIC_SPELL_DAMAGE, spellID.toEnum()) : 0;
|
||||
|
||||
bsa.amount = usedSpellPower
|
||||
* SpellID(spellID).toSpell()->getPower(spellLvl)
|
||||
* (100 + percentBonus) / 100.0; //new feature - percentage bonus
|
||||
if(bsa.amount)
|
||||
sendAndApply(&bsa);
|
||||
else
|
||||
complain("Summoning elementals didn't summon any!");
|
||||
}
|
||||
break;
|
||||
case SpellID::CLONE:
|
||||
{
|
||||
const CStack * clonedStack = nullptr;
|
||||
if (attackedCres.size())
|
||||
clonedStack = *attackedCres.begin();
|
||||
if (!clonedStack)
|
||||
{
|
||||
complain ("No target stack to clone!");
|
||||
break;
|
||||
}
|
||||
|
||||
BattleStackAdded bsa;
|
||||
bsa.creID = clonedStack->type->idNumber;
|
||||
bsa.attacker = !(bool)casterSide;
|
||||
bsa.summoned = true;
|
||||
bsa.pos = gs->curB->getAvaliableHex(bsa.creID, !(bool)casterSide); //TODO: unify it
|
||||
bsa.amount = clonedStack->count;
|
||||
sendAndApply (&bsa);
|
||||
|
||||
BattleSetStackProperty ssp;
|
||||
ssp.stackID = bsa.newStackID;
|
||||
ssp.which = BattleSetStackProperty::CLONED; //using enum values
|
||||
ssp.val = 0;
|
||||
ssp.absolute = 1;
|
||||
sendAndApply(&ssp);
|
||||
}
|
||||
break;
|
||||
case SpellID::REMOVE_OBSTACLE:
|
||||
{
|
||||
if(auto obstacleToRemove = battleGetObstacleOnPos(destination, false))
|
||||
{
|
||||
ObstaclesRemoved obr;
|
||||
obr.obstacles.insert(obstacleToRemove->uniqueID);
|
||||
sendAndApply(&obr);
|
||||
}
|
||||
else
|
||||
complain("There's no obstacle to remove!");
|
||||
}
|
||||
break;
|
||||
case SpellID::DEATH_STARE: //handled in a bit different way
|
||||
{
|
||||
for(auto & attackedCre : attackedCres)
|
||||
{
|
||||
BattleStackAttacked bsa;
|
||||
bsa.flags |= BattleStackAttacked::EFFECT;
|
||||
bsa.effect = spell->mainEffectAnim; //from config\spell-Info.txt
|
||||
bsa.damageAmount = usedSpellPower * (attackedCre)->valOfBonuses(Bonus::STACK_HEALTH);
|
||||
bsa.stackAttacked = (attackedCre)->ID;
|
||||
bsa.attackerID = -1;
|
||||
(attackedCre)->prepareAttacked(bsa, gameState()->getRandomGenerator());
|
||||
si.stacks.push_back(bsa);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case SpellID::ACID_BREATH_DAMAGE: //new effect, separate from acid breath defense reduction
|
||||
{
|
||||
for(auto & attackedCre : attackedCres) //no immunities
|
||||
{
|
||||
BattleStackAttacked bsa;
|
||||
bsa.flags |= BattleStackAttacked::EFFECT;
|
||||
bsa.effect = spell->mainEffectAnim;
|
||||
bsa.damageAmount = usedSpellPower; //damage times the number of attackers
|
||||
bsa.stackAttacked = (attackedCre)->ID;
|
||||
bsa.attackerID = -1;
|
||||
(attackedCre)->prepareAttacked(bsa, gameState()->getRandomGenerator());
|
||||
si.stacks.push_back(bsa);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
sendAndApply(&sc);
|
||||
if(!si.stacks.empty()) //after spellcast info shows
|
||||
sendAndApply(&si);
|
||||
|
||||
if (mode == ECastingMode::CREATURE_ACTIVE_CASTING || mode == ECastingMode::ENCHANTER_CASTING) //reduce number of casts remaining
|
||||
{
|
||||
BattleSetStackProperty ssp;
|
||||
ssp.stackID = stack->ID;
|
||||
ssp.which = BattleSetStackProperty::CASTS;
|
||||
ssp.val = -1;
|
||||
ssp.absolute = false;
|
||||
sendAndApply(&ssp);
|
||||
}
|
||||
|
||||
//Magic Mirror effect
|
||||
if (spell->isNegative() && mode != ECastingMode::MAGIC_MIRROR && spell->level && spell->getLevelInfo(0).range == "0") //it is actual spell and can be reflected to single target, no recurrence
|
||||
{
|
||||
for(auto & attackedCre : attackedCres)
|
||||
{
|
||||
int mirrorChance = (attackedCre)->valOfBonuses(Bonus::MAGIC_MIRROR);
|
||||
if(mirrorChance > gs->getRandomGenerator().nextInt(99))
|
||||
{
|
||||
std::vector<CStack *> mirrorTargets;
|
||||
std::vector<CStack *> & battleStacks = gs->curB->stacks;
|
||||
for (auto & battleStack : battleStacks)
|
||||
{
|
||||
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, battleStack))
|
||||
mirrorTargets.push_back(battleStack);
|
||||
}
|
||||
}
|
||||
if (!mirrorTargets.empty())
|
||||
{
|
||||
int targetHex = (*RandomGeneratorUtil::nextItem(mirrorTargets, gs->getRandomGenerator()))->position;
|
||||
handleSpellCasting(spellID, 0, targetHex, 1 - casterSide, (attackedCre)->owner, nullptr, (caster ? caster : nullptr), usedSpellPower, ECastingMode::MAGIC_MIRROR, (attackedCre));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
spell->battleCast(&spellEnvironment, parameters);
|
||||
}
|
||||
|
||||
bool CGameHandler::makeCustomAction( BattleAction &ba )
|
||||
@ -6401,3 +5923,24 @@ CGameHandler::FinishingBattleHelper::FinishingBattleHelper()
|
||||
{
|
||||
winnerHero = loserHero = nullptr;
|
||||
}
|
||||
|
||||
|
||||
ServerSpellCastEnvironment::ServerSpellCastEnvironment(CGameHandler * gh): gh(gh)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void ServerSpellCastEnvironment::sendAndApply(CPackForClient * info) const
|
||||
{
|
||||
gh->sendAndApply(info);
|
||||
}
|
||||
|
||||
CRandomGenerator & ServerSpellCastEnvironment::getRandomGenerator() const
|
||||
{
|
||||
return gh->gameState()->getRandomGenerator();
|
||||
}
|
||||
|
||||
void ServerSpellCastEnvironment::complain(const std::string& problem) const
|
||||
{
|
||||
gh->complain(problem);
|
||||
}
|
||||
|
@ -288,6 +288,7 @@ public:
|
||||
friend class CVCMIServer;
|
||||
|
||||
private:
|
||||
|
||||
std::list<PlayerColor> generatePlayerTurnOrder() const;
|
||||
void makeStackDoNothing(const CStack * next);
|
||||
void getVictoryLossMessage(PlayerColor player, const EVictoryLossCheckResult & victoryLossCheckResult, InfoWindow & out) const;
|
||||
|
Loading…
Reference in New Issue
Block a user