mirror of
https://github.com/vcmi/vcmi.git
synced 2024-12-24 22:14:36 +02:00
Merge branch 'SpellsRefactoring7' into develop
This commit is contained in:
commit
61af4ee89a
@ -531,7 +531,7 @@ std::vector<BattleHex> CBattleAI::getTargetsToConsider( const CSpell *spell ) co
|
||||
{
|
||||
if(spell->getTargetType() == CSpell::NO_TARGET)
|
||||
{
|
||||
//Spell can be casted anywhere, all hexes are potentially considerable.
|
||||
//Spell can be cast anywhere, all hexes are potentially considerable.
|
||||
std::vector<BattleHex> ret;
|
||||
|
||||
for(int i = 0; i < GameConstants::BFIELD_SIZE; i++)
|
||||
|
@ -772,7 +772,8 @@ BattleAction CPlayerInterface::activeStack(const CStack * stack) //called when i
|
||||
{
|
||||
THREAD_CREATED_BY_CLIENT;
|
||||
logGlobal->traceStream() << "Awaiting command for " << stack->nodeName();
|
||||
|
||||
auto stackId = stack->ID;
|
||||
auto stackName = stack->nodeName();
|
||||
if(autofightingAI)
|
||||
{
|
||||
if(isAutoFightOn)
|
||||
@ -806,17 +807,23 @@ BattleAction CPlayerInterface::activeStack(const CStack * stack) //called when i
|
||||
while(!b->givenCommand->data)
|
||||
{
|
||||
b->givenCommand->cond.wait(lock);
|
||||
if(!battleInt) //batle ended while we were waiting for movement (eg. because of spell)
|
||||
if(!battleInt) //battle ended while we were waiting for movement (eg. because of spell)
|
||||
throw boost::thread_interrupted(); //will shut the thread peacefully
|
||||
}
|
||||
|
||||
//tidy up
|
||||
BattleAction ret = *(b->givenCommand->data);
|
||||
delete b->givenCommand->data;
|
||||
b->givenCommand->data = nullptr;
|
||||
|
||||
//return command
|
||||
logGlobal->traceStream() << "Giving command for " << stack->nodeName();
|
||||
vstd::clear_pointer(b->givenCommand->data);
|
||||
|
||||
if(ret.actionType == Battle::CANCEL)
|
||||
{
|
||||
if(stackId != ret.stackNumber)
|
||||
logGlobal->error("Not current active stack action canceled");
|
||||
logGlobal->traceStream() << "Canceled command for " << stackName;
|
||||
}
|
||||
else
|
||||
logGlobal->traceStream() << "Giving command for " << stackName;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
@ -140,9 +140,12 @@ void CClient::waitForMoveAndSend(PlayerColor color)
|
||||
setThreadName("CClient::waitForMoveAndSend");
|
||||
assert(vstd::contains(battleints, color));
|
||||
BattleAction ba = battleints[color]->activeStack(gs->curB->battleGetStackByID(gs->curB->activeStack, false));
|
||||
logNetwork->traceStream() << "Send battle action to server: " << ba;
|
||||
MakeAction temp_action(ba);
|
||||
sendRequest(&temp_action, color);
|
||||
if(ba.actionType != Battle::CANCEL)
|
||||
{
|
||||
logNetwork->traceStream() << "Send battle action to server: " << ba;
|
||||
MakeAction temp_action(ba);
|
||||
sendRequest(&temp_action, color);
|
||||
}
|
||||
return;
|
||||
}
|
||||
catch(boost::thread_interrupted&)
|
||||
|
@ -750,7 +750,7 @@ void CatapultAttack::applyCl( CClient *cl )
|
||||
BATTLE_INTERFACE_CALL_IF_PRESENT_FOR_BOTH_SIDES(battleCatapultAttacked, *this);
|
||||
}
|
||||
|
||||
void BattleStacksRemoved::applyCl( CClient *cl )
|
||||
void BattleStacksRemoved::applyFirstCl(CClient * cl)
|
||||
{
|
||||
//inform interfaces about removed stacks
|
||||
BATTLE_INTERFACE_CALL_IF_PRESENT_FOR_BOTH_SIDES(battleStacksRemoved, *this);
|
||||
|
@ -1005,11 +1005,22 @@ void CBattleInterface::newStack(const CStack * stack)
|
||||
|
||||
void CBattleInterface::stackRemoved(int stackID)
|
||||
{
|
||||
if(activeStack != nullptr)
|
||||
{
|
||||
if(activeStack->ID == stackID)
|
||||
{
|
||||
BattleAction * action = new BattleAction();
|
||||
action->side = defendingHeroInstance ? (curInt->playerID == defendingHeroInstance->tempOwner) : false;
|
||||
action->actionType = Battle::CANCEL;
|
||||
action->stackNumber = activeStack->ID;
|
||||
givenCommand->setn(action);
|
||||
setActiveStack(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
delete creAnims[stackID];
|
||||
creAnims.erase(stackID);
|
||||
creDir.erase(stackID);
|
||||
//FIXME: what if currently removed stack is active one (Sacrifice)?
|
||||
|
||||
redrawBackgroundWithHexes(activeStack);
|
||||
queue->update();
|
||||
}
|
||||
@ -1091,10 +1102,6 @@ void CBattleInterface::newRoundFirst( int round )
|
||||
void CBattleInterface::newRound(int number)
|
||||
{
|
||||
console->addText(CGI->generaltexth->allTexts[412]);
|
||||
|
||||
//unlock spellbook
|
||||
//bSpell->block(!curInt->cb->battleCanCastSpell());
|
||||
//don't unlock spellbook - this should be done when we have axctive creature
|
||||
}
|
||||
|
||||
void CBattleInterface::giveCommand(Battle::ActionType action, BattleHex tile, ui32 stackID, si32 additional, si32 selected)
|
||||
@ -1379,7 +1386,7 @@ void CBattleInterface::castThisSpell(SpellID spellID)
|
||||
sp = spellID.toSpell();
|
||||
spellSelMode = ANY_LOCATION;
|
||||
|
||||
const CSpell::TargetInfo ti = sp->getTargetInfo(castingHero->getSpellSchoolLevel(sp));
|
||||
const CSpell::TargetInfo ti(sp, castingHero->getSpellSchoolLevel(sp));
|
||||
|
||||
if(ti.massive || ti.type == CSpell::NO_TARGET)
|
||||
spellSelMode = NO_LOCATION;
|
||||
@ -2103,9 +2110,9 @@ void CBattleInterface::handleHex(BattleHex myNumber, int eventType)
|
||||
{
|
||||
ui8 skill = 0;
|
||||
if (creatureCasting)
|
||||
skill = sactive->getSpellSchoolLevel(SpellID(SpellID::TELEPORT).toSpell());
|
||||
skill = sactive->getEffectLevel(SpellID(SpellID::TELEPORT).toSpell());
|
||||
else
|
||||
skill = getActiveHero()->getSpellSchoolLevel (CGI->spellh->objects[spellToCast->additionalInfo]);
|
||||
skill = getActiveHero()->getEffectLevel(SpellID(SpellID::TELEPORT).toSpell());
|
||||
//TODO: explicitely save power, skill
|
||||
if (curInt->cb->battleCanTeleportTo(selectedStack, myNumber, skill))
|
||||
legalAction = true;
|
||||
@ -2470,11 +2477,19 @@ bool CBattleInterface::isCastingPossibleHere (const CStack * sactive, const CSta
|
||||
|
||||
if (sp)
|
||||
{
|
||||
if (creatureCasting)
|
||||
isCastingPossible = (curInt->cb->battleCanCreatureCastThisSpell (sp, myNumber) == ESpellCastProblem::OK);
|
||||
const ISpellCaster * caster = creatureCasting ? dynamic_cast<const ISpellCaster *>(sactive) : dynamic_cast<const ISpellCaster *>(curInt->cb->battleGetMyHero());
|
||||
if(caster == nullptr)
|
||||
{
|
||||
isCastingPossible = false;//just in case
|
||||
}
|
||||
else
|
||||
isCastingPossible = (curInt->cb->battleCanCastThisSpell (sp, myNumber) == ESpellCastProblem::OK);
|
||||
{
|
||||
const ECastingMode::ECastingMode mode = creatureCasting ? ECastingMode::CREATURE_ACTIVE_CASTING : ECastingMode::HERO_CASTING;
|
||||
isCastingPossible = (curInt->cb->battleCanCastThisSpellHere(caster, sp, mode, myNumber) == ESpellCastProblem::OK);
|
||||
}
|
||||
}
|
||||
else
|
||||
isCastingPossible = false;
|
||||
if(!myNumber.isAvailable() && !shere) //empty tile outside battlefield (or in the unavailable border column)
|
||||
isCastingPossible = false;
|
||||
|
||||
|
@ -1313,7 +1313,8 @@
|
||||
"subtype" : 35,
|
||||
"type" : "SPELL_IMMUNITY",
|
||||
"val" : 0,
|
||||
"valueType" : "BASE_NUMBER"
|
||||
"valueType" : "BASE_NUMBER",
|
||||
"addInfo" : 1
|
||||
}
|
||||
],
|
||||
"index" : 92,
|
||||
@ -1324,10 +1325,17 @@
|
||||
"bonuses" : [
|
||||
{
|
||||
"type" : "NEGATE_ALL_NATURAL_IMMUNITIES",
|
||||
"subtype" : 0,
|
||||
"val" : 0,
|
||||
"valueType" : "BASE_NUMBER",
|
||||
"propagator": "BATTLE_WIDE"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type" : "NEGATE_ALL_NATURAL_IMMUNITIES",
|
||||
"subtype" : 1,
|
||||
"val" : 0,
|
||||
"valueType" : "BASE_NUMBER"
|
||||
}
|
||||
],
|
||||
"index" : 93,
|
||||
"type" : ["HERO"]
|
||||
@ -1812,7 +1820,8 @@
|
||||
"subtype" : "spell.armageddon",
|
||||
"type" : "SPELL_IMMUNITY",
|
||||
"val" : 0,
|
||||
"valueType" : "BASE_NUMBER"
|
||||
"valueType" : "BASE_NUMBER",
|
||||
"addInfo" : 1
|
||||
},
|
||||
{
|
||||
"subtype" : "primSkill.attack",
|
||||
|
@ -9,12 +9,14 @@
|
||||
"blindImmunity" :
|
||||
{
|
||||
"type" : "SPELL_IMMUNITY",
|
||||
"subtype" : "spell.blind"
|
||||
"subtype" : "spell.blind",
|
||||
"addInfo" : 1
|
||||
},
|
||||
"petrifyImmunity" :
|
||||
{
|
||||
"type" : "SPELL_IMMUNITY",
|
||||
"subtype" : "spell.stoneGaze"
|
||||
"subtype" : "spell.stoneGaze",
|
||||
"addInfo" : 1
|
||||
}
|
||||
},
|
||||
"upgrades": ["infernalTroglodyte"],
|
||||
@ -42,12 +44,14 @@
|
||||
"blindImmunity" :
|
||||
{
|
||||
"type" : "SPELL_IMMUNITY",
|
||||
"subtype" : "spell.blind"
|
||||
"subtype" : "spell.blind",
|
||||
"addInfo" : 1
|
||||
},
|
||||
"petrifyImmunity" :
|
||||
{
|
||||
"type" : "SPELL_IMMUNITY",
|
||||
"subtype" : "spell.stoneGaze"
|
||||
"subtype" : "spell.stoneGaze",
|
||||
"addInfo" : 1
|
||||
}
|
||||
},
|
||||
"graphics" :
|
||||
|
@ -564,7 +564,7 @@
|
||||
"subtype" : "primSkill.defence",
|
||||
"val" : -3,
|
||||
"valueType" : "ADDITIVE_VALUE",
|
||||
"duration" : "N_TURNS"
|
||||
"duration" : "PERMANENT"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -1170,6 +1170,17 @@ bool CStack::canBeHealed() const
|
||||
&& !hasBonusOfType(Bonus::SIEGE_WEAPON);
|
||||
}
|
||||
|
||||
ui32 CStack::calculateHealedHealthPoints(ui32 toHeal, const bool resurrect) const
|
||||
{
|
||||
if(!resurrect && !alive())
|
||||
{
|
||||
logGlobal->warnStream() <<"Attempt to heal corpse detected.";
|
||||
return 0;
|
||||
}
|
||||
|
||||
return std::min<ui32>(toHeal, MaxHealth() - firstHPleft + (resurrect ? (baseAmount - count) * MaxHealth() : 0));
|
||||
}
|
||||
|
||||
ui8 CStack::getSpellSchoolLevel(const CSpell * spell, int * outSelectedSchool) const
|
||||
{
|
||||
int skill = valOfBonuses(Selector::typeSubtype(Bonus::SPELLCASTER, spell->id));
|
||||
@ -1181,10 +1192,39 @@ ui8 CStack::getSpellSchoolLevel(const CSpell * spell, int * outSelectedSchool) c
|
||||
|
||||
ui32 CStack::getSpellBonus(const CSpell * spell, ui32 base, const CStack * affectedStack) const
|
||||
{
|
||||
//stacks does not have spellpower etc. (yet?)
|
||||
//stacks does not have sorcery-like bonuses (yet?)
|
||||
return base;
|
||||
}
|
||||
|
||||
int CStack::getEffectLevel(const CSpell * spell) const
|
||||
{
|
||||
return getSpellSchoolLevel(spell);
|
||||
}
|
||||
|
||||
int CStack::getEffectPower(const CSpell * spell) const
|
||||
{
|
||||
return valOfBonuses(Bonus::CREATURE_SPELL_POWER) * count / 100;
|
||||
}
|
||||
|
||||
int CStack::getEnchantPower(const CSpell * spell) const
|
||||
{
|
||||
int res = valOfBonuses(Bonus::CREATURE_ENCHANT_POWER);
|
||||
if(res<=0)
|
||||
res = 3;//default for creatures
|
||||
return res;
|
||||
}
|
||||
|
||||
int CStack::getEffectValue(const CSpell * spell) const
|
||||
{
|
||||
return valOfBonuses(Bonus::SPECIFIC_SPELL_POWER, spell->id.toEnum()) * count;
|
||||
}
|
||||
|
||||
const PlayerColor CStack::getOwner() const
|
||||
{
|
||||
return owner;
|
||||
}
|
||||
|
||||
|
||||
bool CMP_stack::operator()( const CStack* a, const CStack* b )
|
||||
{
|
||||
switch(phase)
|
||||
|
@ -203,6 +203,8 @@ public:
|
||||
bool waited(int turn = 0) const;
|
||||
bool canMove(int turn = 0) const; //if stack can move
|
||||
bool canBeHealed() const; //for first aid tent - only harmed stacks that are not war machines
|
||||
///returns actual heal value based on internal state
|
||||
ui32 calculateHealedHealthPoints(ui32 toHeal, const bool resurrect) const;
|
||||
ui32 level() const;
|
||||
si32 magicResistance() const override; //include aura of resistance
|
||||
static void stackEffectToFeature(std::vector<Bonus> & sf, const Bonus & sse);
|
||||
@ -233,6 +235,20 @@ public:
|
||||
///ISpellCaster
|
||||
ui8 getSpellSchoolLevel(const CSpell * spell, int *outSelectedSchool = nullptr) const override;
|
||||
ui32 getSpellBonus(const CSpell * spell, ui32 base, const CStack * affectedStack) const override;
|
||||
|
||||
///default spell school level for effect calculation
|
||||
int getEffectLevel(const CSpell * spell) const override;
|
||||
|
||||
///default spell-power for damage/heal calculation
|
||||
int getEffectPower(const CSpell * spell) const override;
|
||||
|
||||
///default spell-power for timed effects duration
|
||||
int getEnchantPower(const CSpell * spell) const override;
|
||||
|
||||
///damage/heal override(ignores spell configuration, effect level and effect power)
|
||||
int getEffectValue(const CSpell * spell) const override;
|
||||
|
||||
const PlayerColor getOwner() const override;
|
||||
|
||||
template <typename Handler> void serialize(Handler &h, const int version)
|
||||
{
|
||||
|
@ -335,6 +335,11 @@ int CBattleInfoEssentials::battleCastSpells(ui8 side) const
|
||||
return getBattle()->sides[side].castSpellsCount;
|
||||
}
|
||||
|
||||
const IBonusBearer * CBattleInfoEssentials::getBattleNode() const
|
||||
{
|
||||
return getBattle();
|
||||
}
|
||||
|
||||
ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCastSpell(PlayerColor player, ECastingMode::ECastingMode mode) const
|
||||
{
|
||||
RETURN_IF_NOT_BATTLE(ESpellCastProblem::INVALID);
|
||||
@ -1587,9 +1592,15 @@ std::vector<BattleHex> CBattleInfoCallback::getAttackableBattleHexes() const
|
||||
return attackableBattleHexes;
|
||||
}
|
||||
|
||||
ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCastThisSpell( PlayerColor player, const CSpell * spell, ECastingMode::ECastingMode mode ) const
|
||||
ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCastThisSpell(const ISpellCaster * caster, const CSpell * spell, ECastingMode::ECastingMode mode) const
|
||||
{
|
||||
RETURN_IF_NOT_BATTLE(ESpellCastProblem::INVALID);
|
||||
if(caster == nullptr)
|
||||
{
|
||||
logGlobal->errorStream() << "CBattleInfoCallback::battleCanCastThisSpell: no spellcaster.";
|
||||
return ESpellCastProblem::INVALID;
|
||||
}
|
||||
const PlayerColor player = caster->getOwner();
|
||||
const ui8 side = playerToSide(player);
|
||||
if(!battleDoWeKnowAbout(side))
|
||||
return ESpellCastProblem::INVALID;
|
||||
@ -1598,16 +1609,11 @@ ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCastThisSpell
|
||||
if(genProblem != ESpellCastProblem::OK)
|
||||
return genProblem;
|
||||
|
||||
//Casting hero, set only if he is an actual caster.
|
||||
const CGHeroInstance *castingHero = mode == ECastingMode::HERO_CASTING
|
||||
? battleGetFightingHero(side)
|
||||
: nullptr;
|
||||
|
||||
|
||||
switch(mode)
|
||||
{
|
||||
case ECastingMode::HERO_CASTING:
|
||||
{
|
||||
const CGHeroInstance * castingHero = dynamic_cast<const CGHeroInstance *>(caster);//todo: unify hero|creature spell cost
|
||||
assert(castingHero);
|
||||
if(!castingHero->canCastThisSpell(spell))
|
||||
return ESpellCastProblem::HERO_DOESNT_KNOW_SPELL;
|
||||
@ -1617,11 +1623,10 @@ ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCastThisSpell
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
if(!spell->combatSpell)
|
||||
return ESpellCastProblem::ADVMAP_SPELL_INSTEAD_OF_BATTLE_SPELL;
|
||||
|
||||
const ESpellCastProblem::ESpellCastProblem specificProblem = spell->canBeCasted(this, player);
|
||||
const ESpellCastProblem::ESpellCastProblem specificProblem = spell->canBeCast(this, player);
|
||||
|
||||
if(specificProblem != ESpellCastProblem::OK)
|
||||
return specificProblem;
|
||||
@ -1634,7 +1639,7 @@ ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCastThisSpell
|
||||
auto stacks = spell->isNegative() ? battleAliveStacks(!side) : battleAliveStacks();
|
||||
for(auto stack : stacks)
|
||||
{
|
||||
if(ESpellCastProblem::OK == spell->isImmuneByStack(castingHero, stack))
|
||||
if(ESpellCastProblem::OK == spell->isImmuneByStack(caster, stack))
|
||||
{
|
||||
allStacksImmune = false;
|
||||
break;
|
||||
@ -1645,7 +1650,7 @@ ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCastThisSpell
|
||||
return ESpellCastProblem::NO_APPROPRIATE_TARGET;
|
||||
}
|
||||
|
||||
if(battleMaxSpellLevel() < spell->level) //effect like Recanter's Cloak or Orb of Inhibition
|
||||
if(battleMaxSpellLevel(side) < spell->level) //effect like Recanter's Cloak or Orb of Inhibition
|
||||
return ESpellCastProblem::SPELL_LEVEL_LIMIT_EXCEEDED;
|
||||
|
||||
//checking if there exists an appropriate target
|
||||
@ -1654,8 +1659,7 @@ ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCastThisSpell
|
||||
case CSpell::CREATURE:
|
||||
if(mode == ECastingMode::HERO_CASTING)
|
||||
{
|
||||
const CGHeroInstance * caster = battleGetFightingHero(side);
|
||||
const CSpell::TargetInfo ti = spell->getTargetInfo(caster->getSpellSchoolLevel(spell));
|
||||
const CSpell::TargetInfo ti(spell, caster->getSpellSchoolLevel(spell));
|
||||
bool targetExists = false;
|
||||
|
||||
for(const CStack * stack : battleGetAllStacks()) //dead stacks will be immune anyway
|
||||
@ -1710,7 +1714,7 @@ std::vector<BattleHex> CBattleInfoCallback::battleGetPossibleTargets(PlayerColor
|
||||
case CSpell::CREATURE:
|
||||
{
|
||||
const CGHeroInstance * caster = battleGetFightingHero(playerToSide(player)); //TODO
|
||||
const CSpell::TargetInfo ti = spell->getTargetInfo(caster->getSpellSchoolLevel(spell));
|
||||
const CSpell::TargetInfo ti(spell, caster->getSpellSchoolLevel(spell));
|
||||
|
||||
for(const CStack * stack : battleAliveStacks())
|
||||
{
|
||||
@ -1772,10 +1776,16 @@ ui32 CBattleInfoCallback::battleGetSpellCost(const CSpell * sp, const CGHeroInst
|
||||
return ret - manaReduction + manaIncrease;
|
||||
}
|
||||
|
||||
ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCastThisSpellHere( PlayerColor player, const CSpell * spell, ECastingMode::ECastingMode mode, BattleHex dest ) const
|
||||
ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCastThisSpellHere(const ISpellCaster * caster, const CSpell * spell, ECastingMode::ECastingMode mode, BattleHex dest) const
|
||||
{
|
||||
RETURN_IF_NOT_BATTLE(ESpellCastProblem::INVALID);
|
||||
ESpellCastProblem::ESpellCastProblem moreGeneralProblem = battleCanCastThisSpell(player, spell, mode);
|
||||
if(caster == nullptr)
|
||||
{
|
||||
logGlobal->errorStream() << "CBattleInfoCallback::battleCanCastThisSpellHere: no spellcaster.";
|
||||
return ESpellCastProblem::INVALID;
|
||||
}
|
||||
const PlayerColor player = caster->getOwner();
|
||||
ESpellCastProblem::ESpellCastProblem moreGeneralProblem = battleCanCastThisSpell(caster, spell, mode);
|
||||
if(moreGeneralProblem != ESpellCastProblem::OK)
|
||||
return moreGeneralProblem;
|
||||
|
||||
@ -1826,8 +1836,6 @@ ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCastThisSpell
|
||||
{
|
||||
if(!deadStack && !aliveStack)
|
||||
return ESpellCastProblem::NO_APPROPRIATE_TARGET;
|
||||
if(spell->id == SpellID::ANIMATE_DEAD && deadStack && !deadStack->hasBonusOfType(Bonus::UNDEAD))
|
||||
return ESpellCastProblem::NO_APPROPRIATE_TARGET;
|
||||
if(deadStack && deadStack->owner != player) //you can resurrect only your own stacks //FIXME: it includes alive stacks as well
|
||||
return ESpellCastProblem::NO_APPROPRIATE_TARGET;
|
||||
}
|
||||
@ -1840,11 +1848,6 @@ ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCastThisSpell
|
||||
if(spell->isPositive() && aliveStack->owner != player)
|
||||
return ESpellCastProblem::NO_APPROPRIATE_TARGET;
|
||||
}
|
||||
|
||||
const CGHeroInstance * caster = nullptr;
|
||||
if (mode == ECastingMode::HERO_CASTING)
|
||||
caster = battleGetFightingHero(playerToSide(player));
|
||||
|
||||
return spell->isImmuneAt(this, caster, mode, dest);
|
||||
}
|
||||
|
||||
@ -1927,7 +1930,7 @@ SpellID CBattleInfoCallback::getRandomBeneficialSpell(const CStack * subject) co
|
||||
{
|
||||
if (subject->hasBonusFrom(Bonus::SPELL_EFFECT, spellID)
|
||||
//TODO: this ability has special limitations
|
||||
|| battleCanCastThisSpellHere(subject->owner, spellID.toSpell(), ECastingMode::CREATURE_ACTIVE_CASTING, subject->position) != ESpellCastProblem::OK)
|
||||
|| battleCanCastThisSpellHere(subject, spellID.toSpell(), ECastingMode::CREATURE_ACTIVE_CASTING, subject->position) != ESpellCastProblem::OK)
|
||||
continue;
|
||||
|
||||
switch (spellID)
|
||||
@ -2060,12 +2063,14 @@ int CBattleInfoCallback::battleGetSurrenderCost(PlayerColor Player) const
|
||||
return ret;
|
||||
}
|
||||
|
||||
si8 CBattleInfoCallback::battleMaxSpellLevel() const
|
||||
si8 CBattleInfoCallback::battleMaxSpellLevel(ui8 side) const
|
||||
{
|
||||
const CBonusSystemNode *node = nullptr;
|
||||
if(const CGHeroInstance *h = battleGetFightingHero(battleGetMySide()))
|
||||
const IBonusBearer *node = nullptr;
|
||||
if(const CGHeroInstance * h = battleGetFightingHero(side))
|
||||
node = h;
|
||||
//TODO else use battle node
|
||||
else
|
||||
node = getBattleNode();
|
||||
|
||||
if(!node)
|
||||
return GameConstants::SPELL_LEVELS;
|
||||
|
||||
@ -2163,21 +2168,12 @@ ESpellCastProblem::ESpellCastProblem CPlayerBattleCallback::battleCanCastThisSpe
|
||||
{
|
||||
RETURN_IF_NOT_BATTLE(ESpellCastProblem::INVALID);
|
||||
ASSERT_IF_CALLED_WITH_PLAYER
|
||||
return CBattleInfoCallback::battleCanCastThisSpell(*player, spell, ECastingMode::HERO_CASTING);
|
||||
}
|
||||
|
||||
ESpellCastProblem::ESpellCastProblem CPlayerBattleCallback::battleCanCastThisSpell(const CSpell * spell, BattleHex destination) const
|
||||
{
|
||||
RETURN_IF_NOT_BATTLE(ESpellCastProblem::INVALID);
|
||||
ASSERT_IF_CALLED_WITH_PLAYER
|
||||
return battleCanCastThisSpellHere(*player, spell, ECastingMode::HERO_CASTING, destination);
|
||||
}
|
||||
|
||||
ESpellCastProblem::ESpellCastProblem CPlayerBattleCallback::battleCanCreatureCastThisSpell(const CSpell * spell, BattleHex destination) const
|
||||
{
|
||||
RETURN_IF_NOT_BATTLE(ESpellCastProblem::INVALID);
|
||||
ASSERT_IF_CALLED_WITH_PLAYER
|
||||
return battleCanCastThisSpellHere(*player, spell, ECastingMode::CREATURE_ACTIVE_CASTING, destination);
|
||||
const ISpellCaster * hero = battleGetMyHero();
|
||||
if(hero == nullptr)
|
||||
return ESpellCastProblem::INVALID;
|
||||
else
|
||||
return CBattleInfoCallback::battleCanCastThisSpell(hero, spell, ECastingMode::HERO_CASTING);
|
||||
}
|
||||
|
||||
bool CPlayerBattleCallback::battleCanFlee() const
|
||||
|
@ -15,6 +15,7 @@ class CGameState;
|
||||
class CGTownInstance;
|
||||
class CGHeroInstance;
|
||||
class CStack;
|
||||
class ISpellCaster;
|
||||
class CSpell;
|
||||
struct BattleInfo;
|
||||
struct CObstacleInstance;
|
||||
@ -158,6 +159,7 @@ class DLL_LINKAGE CBattleInfoEssentials : public virtual CCallbackBase
|
||||
{
|
||||
protected:
|
||||
bool battleDoWeKnowAbout(ui8 side) const;
|
||||
const IBonusBearer * getBattleNode() const;
|
||||
public:
|
||||
enum EStackOwnership
|
||||
{
|
||||
@ -189,7 +191,7 @@ public:
|
||||
ui8 playerToSide(PlayerColor player) const;
|
||||
ui8 battleGetSiegeLevel() const; //returns 0 when there is no siege, 1 if fort, 2 is citadel, 3 is castle
|
||||
bool battleHasHero(ui8 side) const;
|
||||
int battleCastSpells(ui8 side) const; //how many spells has given side casted
|
||||
int battleCastSpells(ui8 side) const; //how many spells has given side cast
|
||||
const CGHeroInstance * battleGetFightingHero(ui8 side) const; //depracated for players callback, easy to get wrong
|
||||
const CArmedInstance * battleGetArmyObject(ui8 side) const;
|
||||
InfoAboutHero battleGetHeroInfo(ui8 side) const;
|
||||
@ -276,12 +278,11 @@ public:
|
||||
std::vector<BattleHex> getAttackableBattleHexes() const;
|
||||
|
||||
//*** MAGIC
|
||||
si8 battleMaxSpellLevel() const; //calculates minimum spell level possible to be cast on battlefield - takes into account artifacts of both heroes; if no effects are set, 0 is returned
|
||||
si8 battleMaxSpellLevel(ui8 side) const; //calculates minimum spell level possible to be cast on battlefield - takes into account artifacts of both heroes; if no effects are set, 0 is returned
|
||||
ui32 battleGetSpellCost(const CSpell * sp, const CGHeroInstance * caster) const; //returns cost of given spell
|
||||
ESpellCastProblem::ESpellCastProblem battleCanCastSpell(PlayerColor player, ECastingMode::ECastingMode mode) const; //returns true if there are no general issues preventing from casting a spell
|
||||
ESpellCastProblem::ESpellCastProblem battleCanCastThisSpell(PlayerColor player, const CSpell * spell, ECastingMode::ECastingMode mode) const; //checks if given player can cast given spell
|
||||
ESpellCastProblem::ESpellCastProblem battleCanCastThisSpellHere(PlayerColor player, const CSpell * spell, ECastingMode::ECastingMode mode, BattleHex dest) const; //checks if given player can cast given spell at given tile in given mode
|
||||
ESpellCastProblem::ESpellCastProblem battleCanCreatureCastThisSpell(const CSpell * spell, BattleHex destination) const; //determines if creature can cast a spell here
|
||||
ESpellCastProblem::ESpellCastProblem battleCanCastThisSpell(const ISpellCaster * caster, const CSpell * spell, ECastingMode::ECastingMode mode) const; //checks if given player can cast given spell
|
||||
ESpellCastProblem::ESpellCastProblem battleCanCastThisSpellHere(const ISpellCaster * caster, const CSpell * spell, ECastingMode::ECastingMode mode, BattleHex dest) const; //checks if given player can cast given spell at given tile in given mode
|
||||
std::vector<BattleHex> battleGetPossibleTargets(PlayerColor player, const CSpell *spell) const;
|
||||
|
||||
SpellID battleGetRandomStackSpell(const CStack * stack, ERandomSpell mode) const;
|
||||
@ -324,9 +325,8 @@ class DLL_LINKAGE CPlayerBattleCallback : public CBattleInfoCallback
|
||||
public:
|
||||
bool battleCanFlee() const; //returns true if caller can flee from the battle
|
||||
TStacks battleGetStacks(EStackOwnership whose = MINE_AND_ENEMY, bool onlyAlive = true) const; //returns stacks on battlefield
|
||||
ESpellCastProblem::ESpellCastProblem battleCanCastThisSpell(const CSpell * spell) const; //determines if given spell can be casted (and returns problem description)
|
||||
ESpellCastProblem::ESpellCastProblem battleCanCastThisSpell(const CSpell * spell, BattleHex destination) const; //if hero can cast spell here
|
||||
ESpellCastProblem::ESpellCastProblem battleCanCreatureCastThisSpell(const CSpell * spell, BattleHex destination) const; //determines if creature can cast a spell here
|
||||
ESpellCastProblem::ESpellCastProblem battleCanCastThisSpell(const CSpell * spell) const; //determines if given spell can be cast (and returns problem description)
|
||||
|
||||
int battleGetSurrenderCost() const; //returns cost of surrendering battle, -1 if surrendering is not possible
|
||||
|
||||
bool battleCanCastSpell(ESpellCastProblem::ESpellCastProblem *outProblem = nullptr) const; //returns true, if caller can cast a spell. If not, if pointer is given via arg, the reason will be written.
|
||||
|
@ -853,34 +853,42 @@ void CCreatureHandler::loadStackExp(Bonus & b, BonusList & bl, CLegacyConfigPars
|
||||
case 'B': //Blind
|
||||
b.type = Bonus::SPELL_IMMUNITY;
|
||||
b.subtype = SpellID::BLIND;
|
||||
b.additionalInfo = 0;//normal immunity
|
||||
break;
|
||||
case 'H': //Hypnotize
|
||||
b.type = Bonus::SPELL_IMMUNITY;
|
||||
b.subtype = SpellID::HYPNOTIZE;
|
||||
b.additionalInfo = 0;//normal immunity
|
||||
break;
|
||||
case 'I': //Implosion
|
||||
b.type = Bonus::SPELL_IMMUNITY;
|
||||
b.subtype = SpellID::IMPLOSION;
|
||||
b.additionalInfo = 0;//normal immunity
|
||||
break;
|
||||
case 'K': //Berserk
|
||||
b.type = Bonus::SPELL_IMMUNITY;
|
||||
b.subtype = SpellID::BERSERK;
|
||||
b.additionalInfo = 0;//normal immunity
|
||||
break;
|
||||
case 'M': //Meteor Shower
|
||||
b.type = Bonus::SPELL_IMMUNITY;
|
||||
b.subtype = SpellID::METEOR_SHOWER;
|
||||
b.additionalInfo = 0;//normal immunity
|
||||
break;
|
||||
case 'N': //dispell beneficial spells
|
||||
b.type = Bonus::SPELL_IMMUNITY;
|
||||
b.subtype = SpellID::DISPEL_HELPFUL_SPELLS;
|
||||
b.additionalInfo = 0;//normal immunity
|
||||
break;
|
||||
case 'R': //Armageddon
|
||||
b.type = Bonus::SPELL_IMMUNITY;
|
||||
b.subtype = SpellID::ARMAGEDDON;
|
||||
b.additionalInfo = 0;//normal immunity
|
||||
break;
|
||||
case 'S': //Slow
|
||||
b.type = Bonus::SPELL_IMMUNITY;
|
||||
b.subtype = SpellID::SLOW;
|
||||
b.additionalInfo = 0;//normal immunity
|
||||
break;
|
||||
case '6':
|
||||
case '7':
|
||||
|
@ -173,7 +173,7 @@ int CGameInfoCallback::estimateSpellDamage(const CSpell * sp, const CGHeroInstan
|
||||
ERROR_RET_VAL_IF(hero && !canGetFullInfo(hero), "Cannot get info about caster!", -1);
|
||||
|
||||
if (hero) //we see hero's spellbook
|
||||
return sp->calculateDamage(hero, nullptr, hero->getSpellSchoolLevel(sp), hero->getPrimSkillLevel(PrimarySkill::SPELL_POWER));
|
||||
return sp->calculateDamage(hero, nullptr, hero->getEffectLevel(sp), hero->getEffectPower(sp));
|
||||
else
|
||||
return 0; //mage guild
|
||||
}
|
||||
|
@ -414,7 +414,8 @@ namespace ECastingMode
|
||||
{
|
||||
HERO_CASTING, AFTER_ATTACK_CASTING, //also includes cast before attack
|
||||
MAGIC_MIRROR, CREATURE_ACTIVE_CASTING, ENCHANTER_CASTING,
|
||||
SPELL_LIKE_ATTACK
|
||||
SPELL_LIKE_ATTACK,
|
||||
PASSIVE_CASTING//f.e. opening battle spells
|
||||
};
|
||||
}
|
||||
|
||||
@ -699,6 +700,7 @@ namespace Battle
|
||||
{
|
||||
enum ActionType
|
||||
{
|
||||
CANCEL = -3,
|
||||
END_TACTIC_PHASE = -2,
|
||||
INVALID = -1,
|
||||
NO_ACTION = 0,
|
||||
|
@ -207,9 +207,9 @@ public:
|
||||
BONUS_NAME(RECEPTIVE) /*accepts friendly spells even with immunity*/\
|
||||
BONUS_NAME(DIRECT_DAMAGE_IMMUNITY) /*direct damage spells, that is*/\
|
||||
BONUS_NAME(CASTS) /*how many times creature can cast activated spell*/ \
|
||||
BONUS_NAME(SPECIFIC_SPELL_POWER) /* value used for Thunderbolt and Resurrection casted by units, subtype - spell id */\
|
||||
BONUS_NAME(SPECIFIC_SPELL_POWER) /* value used for Thunderbolt and Resurrection cast by units, subtype - spell id */\
|
||||
BONUS_NAME(CREATURE_SPELL_POWER) /* value per unit, divided by 100 (so faerie Dragons have 800)*/ \
|
||||
BONUS_NAME(CREATURE_ENCHANT_POWER) /* total duration of spells casted by creature */ \
|
||||
BONUS_NAME(CREATURE_ENCHANT_POWER) /* total duration of spells cast by creature */ \
|
||||
BONUS_NAME(ENCHANTED) /* permanently enchanted with spell subID of level = val, if val > 3 then spell is mass and has level of val-3*/ \
|
||||
BONUS_NAME(REBIRTH) /* val - percent of life restored, subtype = 0 - regular, 1 - at least one unit (sacred Phoenix) */\
|
||||
BONUS_NAME(ADDITIONAL_UNITS) /*val of units with id = subtype will be added to hero's army at the beginning of battle */\
|
||||
|
@ -1323,7 +1323,7 @@ struct StacksHealedOrResurrected : public CPackForClient //3013
|
||||
{
|
||||
ui32 stackID;
|
||||
ui32 healedHP;
|
||||
ui8 lowLevelResurrection; //in case this stack is resurrected by this heal, it will be marked as SUMMONED //TODO: replace with separate counter
|
||||
bool lowLevelResurrection; //in case this stack is resurrected by this heal, it will be marked as SUMMONED //TODO: replace with separate counter
|
||||
template <typename Handler> void serialize(Handler &h, const int version)
|
||||
{
|
||||
h & stackID & healedHP & lowLevelResurrection;
|
||||
@ -1502,7 +1502,7 @@ struct BattleSpellCast : public CPackForClient//3009
|
||||
std::vector<CustomEffect> customEffects;
|
||||
std::set<ui32> affectedCres; //ids of creatures affected by this spell, generally used if spell does not set any effect (like dispel or cure)
|
||||
si32 casterStack;// -1 if not cated by creature, >=0 caster stack ID
|
||||
bool castByHero; //if true - spell has been casted by hero, otherwise by a creature
|
||||
bool castByHero; //if true - spell has been cast by hero, otherwise by a creature
|
||||
template <typename Handler> void serialize(Handler &h, const int version)
|
||||
{
|
||||
h & dmgToDisplay & side & id & skill & manaGained & tile & customEffects & affectedCres & casterStack & castByHero;
|
||||
@ -1604,7 +1604,7 @@ struct BattleStacksRemoved : public CPackForClient //3016
|
||||
BattleStacksRemoved(){type = 3016;}
|
||||
|
||||
DLL_LINKAGE void applyGs(CGameState *gs);
|
||||
void applyCl(CClient *cl);
|
||||
void applyFirstCl(CClient *cl);//inform client before stack objects are destroyed
|
||||
|
||||
std::set<ui32> stackIDs; //IDs of removed stacks
|
||||
|
||||
|
@ -1136,7 +1136,7 @@ DLL_LINKAGE void BattleTriggerEffect::applyGs( CGameState *gs )
|
||||
}
|
||||
case Bonus::POISON:
|
||||
{
|
||||
Bonus * b = st->getBonusLocalFirst(Selector::source(Bonus::SPELL_EFFECT, 71)
|
||||
Bonus * b = st->getBonusLocalFirst(Selector::source(Bonus::SPELL_EFFECT, SpellID::POISON)
|
||||
.And(Selector::type(Bonus::STACK_HEALTH)));
|
||||
if (b)
|
||||
b->val = val;
|
||||
@ -1459,21 +1459,17 @@ DLL_LINKAGE void StacksHealedOrResurrected::applyGs( CGameState *gs )
|
||||
}
|
||||
}
|
||||
vstd::amin(changedStack->firstHPleft, changedStack->MaxHealth());
|
||||
//removal of negative effects
|
||||
if(resurrected)
|
||||
{
|
||||
//removing all features from negative spells
|
||||
const BonusList tmpFeatures = changedStack->getBonusList();
|
||||
//changedStack->bonuses.clear();
|
||||
|
||||
for(Bonus *b : tmpFeatures)
|
||||
//removing all effects from negative spells
|
||||
auto selector = [](const Bonus * b)
|
||||
{
|
||||
const CSpell *s = b->sourceSpell();
|
||||
if(s && s->isNegative())
|
||||
{
|
||||
changedStack->removeBonus(b);
|
||||
}
|
||||
}
|
||||
//Special case: DISRUPTING_RAY is "immune" to dispell
|
||||
//Other even PERMANENT effects can be removed
|
||||
return (s != nullptr) && s->isNegative() && (s->id != SpellID::DISRUPTING_RAY);
|
||||
};
|
||||
changedStack->popBonuses(selector);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -902,6 +902,33 @@ ui32 CGHeroInstance::getSpellBonus(const CSpell * spell, ui32 base, const CStack
|
||||
return base;
|
||||
}
|
||||
|
||||
int CGHeroInstance::getEffectLevel(const CSpell * spell) const
|
||||
{
|
||||
if(hasBonusOfType(Bonus::MAXED_SPELL, spell->id))
|
||||
return 3;//todo: recheck specialty from where this bonus is. possible bug
|
||||
else
|
||||
return getSpellSchoolLevel(spell);
|
||||
}
|
||||
|
||||
int CGHeroInstance::getEffectPower(const CSpell * spell) const
|
||||
{
|
||||
return getPrimSkillLevel(PrimarySkill::SPELL_POWER);
|
||||
}
|
||||
|
||||
int CGHeroInstance::getEnchantPower(const CSpell * spell) const
|
||||
{
|
||||
return getPrimSkillLevel(PrimarySkill::SPELL_POWER) + valOfBonuses(Bonus::SPELL_DURATION);
|
||||
}
|
||||
|
||||
int CGHeroInstance::getEffectValue(const CSpell * spell) const
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
const PlayerColor CGHeroInstance::getOwner() const
|
||||
{
|
||||
return tempOwner;
|
||||
}
|
||||
|
||||
bool CGHeroInstance::canCastThisSpell(const CSpell * spell) const
|
||||
{
|
||||
|
@ -210,6 +210,20 @@ public:
|
||||
ui8 getSpellSchoolLevel(const CSpell * spell, int *outSelectedSchool = nullptr) const override;
|
||||
ui32 getSpellBonus(const CSpell * spell, ui32 base, const CStack * affectedStack) const override;
|
||||
|
||||
///default spell school level for effect calculation
|
||||
int getEffectLevel(const CSpell * spell) const override;
|
||||
|
||||
///default spell-power for damage/heal calculation
|
||||
int getEffectPower(const CSpell * spell) const override;
|
||||
|
||||
///default spell-power for timed effects duration
|
||||
int getEnchantPower(const CSpell * spell) const override;
|
||||
|
||||
///damage/heal override(ignores spell configuration, effect level and effect power)
|
||||
int getEffectValue(const CSpell * spell) const override;
|
||||
|
||||
const PlayerColor getOwner() const override;
|
||||
|
||||
void deserializationFix();
|
||||
|
||||
void initObj() override;
|
||||
|
@ -13,6 +13,37 @@
|
||||
|
||||
#include "../NetPacks.h"
|
||||
#include "../BattleState.h"
|
||||
#include "../mapObjects/CGHeroInstance.h"
|
||||
|
||||
///HealingSpellMechanics
|
||||
void HealingSpellMechanics::applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
|
||||
{
|
||||
EHealLevel healLevel = getHealLevel(parameters.effectLevel);
|
||||
int hpGained = calculateHealedHP(env, parameters, ctx);
|
||||
StacksHealedOrResurrected shr;
|
||||
shr.lifeDrain = false;
|
||||
shr.tentHealing = false;
|
||||
|
||||
const bool resurrect = (healLevel != EHealLevel::HEAL);
|
||||
for(auto & attackedCre : ctx.attackedCres)
|
||||
{
|
||||
StacksHealedOrResurrected::HealInfo hi;
|
||||
hi.stackID = (attackedCre)->ID;
|
||||
int stackHPgained = parameters.caster->getSpellBonus(owner, hpGained, attackedCre);
|
||||
hi.healedHP = attackedCre->calculateHealedHealthPoints(stackHPgained, resurrect);
|
||||
hi.lowLevelResurrection = (healLevel == EHealLevel::RESURRECT);
|
||||
shr.healedStacks.push_back(hi);
|
||||
}
|
||||
if(!shr.healedStacks.empty())
|
||||
env->sendAndApply(&shr);
|
||||
}
|
||||
|
||||
int HealingSpellMechanics::calculateHealedHP(const SpellCastEnvironment* env, const BattleSpellCastParameters& parameters, SpellCastContext& ctx) const
|
||||
{
|
||||
if(parameters.effectValue != 0)
|
||||
return parameters.effectValue; //Archangel
|
||||
return owner->calculateRawEffectValue(parameters.effectLevel, parameters.effectPower); //???
|
||||
}
|
||||
|
||||
///AntimagicMechanics
|
||||
void AntimagicMechanics::applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const
|
||||
@ -67,7 +98,7 @@ std::set<const CStack *> ChainLightningMechanics::getAffectedStacks(SpellTargeti
|
||||
}
|
||||
|
||||
///CloneMechanics
|
||||
void CloneMechanics::applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
|
||||
void CloneMechanics::applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
|
||||
{
|
||||
const CStack * clonedStack = nullptr;
|
||||
if(ctx.attackedCres.size())
|
||||
@ -101,23 +132,21 @@ void CloneMechanics::applyBattleEffects(const SpellCastEnvironment * env, Battle
|
||||
env->sendAndApply(&ssp);
|
||||
}
|
||||
|
||||
ESpellCastProblem::ESpellCastProblem CloneMechanics::isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const
|
||||
ESpellCastProblem::ESpellCastProblem CloneMechanics::isImmuneByStack(const ISpellCaster * caster, const CStack * obj) const
|
||||
{
|
||||
//can't clone already cloned creature
|
||||
if(vstd::contains(obj->state, EBattleStackState::CLONED))
|
||||
return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
|
||||
if(obj->cloneID != -1)
|
||||
return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
|
||||
//TODO: how about stacks casting Clone?
|
||||
//currently Clone casted by stack is assumed Expert level
|
||||
ui8 schoolLevel;
|
||||
if(caster)
|
||||
{
|
||||
schoolLevel = caster->getSpellSchoolLevel(owner);
|
||||
schoolLevel = caster->getEffectLevel(owner);
|
||||
}
|
||||
else
|
||||
{
|
||||
schoolLevel = 3;
|
||||
schoolLevel = 3;//todo: remove
|
||||
}
|
||||
|
||||
if(schoolLevel < 3)
|
||||
@ -146,6 +175,11 @@ void CureMechanics::applyBattle(BattleInfo * battle, const BattleSpellCast * pac
|
||||
});
|
||||
}
|
||||
|
||||
HealingSpellMechanics::EHealLevel CureMechanics::getHealLevel(int effectLevel) const
|
||||
{
|
||||
return EHealLevel::HEAL;
|
||||
}
|
||||
|
||||
///DispellMechanics
|
||||
void DispellMechanics::applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const
|
||||
{
|
||||
@ -153,21 +187,35 @@ void DispellMechanics::applyBattle(BattleInfo * battle, const BattleSpellCast *
|
||||
doDispell(battle, packet, Selector::sourceType(Bonus::SPELL_EFFECT));
|
||||
}
|
||||
|
||||
ESpellCastProblem::ESpellCastProblem DispellMechanics::isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const
|
||||
ESpellCastProblem::ESpellCastProblem DispellMechanics::isImmuneByStack(const ISpellCaster * caster, const CStack * obj) const
|
||||
{
|
||||
//DISPELL ignores all immunities, so do not call default
|
||||
std::stringstream cachingStr;
|
||||
cachingStr << "source_" << Bonus::SPELL_EFFECT;
|
||||
|
||||
if(obj->hasBonus(Selector::sourceType(Bonus::SPELL_EFFECT), cachingStr.str()))
|
||||
{
|
||||
return ESpellCastProblem::OK;
|
||||
//just in case
|
||||
if(!obj->alive())
|
||||
return ESpellCastProblem::WRONG_SPELL_TARGET;
|
||||
}
|
||||
//DISPELL ignores all immunities, except specific absolute immunity
|
||||
{
|
||||
//SPELL_IMMUNITY absolute case
|
||||
std::stringstream cachingStr;
|
||||
cachingStr << "type_" << Bonus::SPELL_IMMUNITY << "subtype_" << owner->id.toEnum() << "addInfo_1";
|
||||
if(obj->hasBonus(Selector::typeSubtypeInfo(Bonus::SPELL_IMMUNITY, owner->id.toEnum(), 1), cachingStr.str()))
|
||||
return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
|
||||
}
|
||||
{
|
||||
std::stringstream cachingStr;
|
||||
cachingStr << "source_" << Bonus::SPELL_EFFECT;
|
||||
|
||||
if(obj->hasBonus(Selector::sourceType(Bonus::SPELL_EFFECT), cachingStr.str()))
|
||||
{
|
||||
return ESpellCastProblem::OK;
|
||||
}
|
||||
}
|
||||
return ESpellCastProblem::WRONG_SPELL_TARGET;
|
||||
//any other immunities are ignored - do not execute default algorithm
|
||||
}
|
||||
|
||||
void DispellMechanics::applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
|
||||
void DispellMechanics::applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
|
||||
{
|
||||
DefaultSpellMechanics::applyBattleEffects(env, parameters, ctx);
|
||||
|
||||
@ -190,15 +238,15 @@ void DispellMechanics::applyBattleEffects(const SpellCastEnvironment * env, Batt
|
||||
}
|
||||
|
||||
///EarthquakeMechanics
|
||||
void EarthquakeMechanics::applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
|
||||
void EarthquakeMechanics::applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
|
||||
{
|
||||
if(nullptr == parameters.cb->town)
|
||||
if(nullptr == parameters.cb->battleGetDefendedTown())
|
||||
{
|
||||
env->complain("EarthquakeMechanics: not town siege");
|
||||
return;
|
||||
}
|
||||
|
||||
if(CGTownInstance::NONE == parameters.cb->town->fortLevel())
|
||||
if(CGTownInstance::NONE == parameters.cb->battleGetDefendedTown()->fortLevel())
|
||||
{
|
||||
env->complain("EarthquakeMechanics: town has no fort");
|
||||
return;
|
||||
@ -277,7 +325,7 @@ void EarthquakeMechanics::applyBattleEffects(const SpellCastEnvironment * env, B
|
||||
env->sendAndApply(&ca);
|
||||
}
|
||||
|
||||
ESpellCastProblem::ESpellCastProblem EarthquakeMechanics::canBeCasted(const CBattleInfoCallback * cb, PlayerColor player) const
|
||||
ESpellCastProblem::ESpellCastProblem EarthquakeMechanics::canBeCast(const CBattleInfoCallback * cb, PlayerColor player) const
|
||||
{
|
||||
if(nullptr == cb->battleGetDefendedTown())
|
||||
{
|
||||
@ -288,8 +336,9 @@ ESpellCastProblem::ESpellCastProblem EarthquakeMechanics::canBeCasted(const CBat
|
||||
{
|
||||
return ESpellCastProblem::NO_APPROPRIATE_TARGET;
|
||||
}
|
||||
|
||||
if(owner->getTargetInfo(0).smart) //TODO: use real spell level
|
||||
|
||||
CSpell::TargetInfo ti(owner, 0);//TODO: use real spell level
|
||||
if(ti.smart)
|
||||
{
|
||||
//if spell targeting is smart, then only attacker can use it
|
||||
if(cb->playerToSide(player) != 0)
|
||||
@ -300,15 +349,15 @@ ESpellCastProblem::ESpellCastProblem EarthquakeMechanics::canBeCasted(const CBat
|
||||
}
|
||||
|
||||
///HypnotizeMechanics
|
||||
ESpellCastProblem::ESpellCastProblem HypnotizeMechanics::isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const
|
||||
ESpellCastProblem::ESpellCastProblem HypnotizeMechanics::isImmuneByStack(const ISpellCaster * caster, const CStack * obj) const
|
||||
{
|
||||
if(nullptr != caster) //do not resist hypnotize casted after attack, for example
|
||||
//todo: maybe do not resist on passive cast
|
||||
if(nullptr != caster)
|
||||
{
|
||||
//TODO: what with other creatures casting hypnotize, Faerie Dragons style?
|
||||
ui64 subjectHealth = (obj->count - 1) * obj->MaxHealth() + obj->firstHPleft;
|
||||
//apply 'damage' bonus for hypnotize, including hero specialty
|
||||
ui64 maxHealth = caster->getSpellBonus(owner, caster->getPrimSkillLevel(PrimarySkill::SPELL_POWER)
|
||||
* owner->power + owner->getPower(caster->getSpellSchoolLevel(owner)), obj);
|
||||
ui64 maxHealth = caster->getSpellBonus(owner, owner->calculateRawEffectValue(caster->getEffectLevel(owner), caster->getEffectPower(owner)), obj);
|
||||
if (subjectHealth > maxHealth)
|
||||
return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
|
||||
}
|
||||
@ -316,9 +365,9 @@ ESpellCastProblem::ESpellCastProblem HypnotizeMechanics::isImmuneByStack(const C
|
||||
}
|
||||
|
||||
///ObstacleMechanics
|
||||
void ObstacleMechanics::applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
|
||||
void ObstacleMechanics::applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
|
||||
{
|
||||
auto placeObstacle = [&, this](BattleHex pos)
|
||||
auto placeObstacle = [&, this](const BattleHex & pos)
|
||||
{
|
||||
static int obstacleIdToGive = parameters.cb->obstacles.size()
|
||||
? (parameters.cb->obstacles.back()->uniqueID+1)
|
||||
@ -355,8 +404,8 @@ void ObstacleMechanics::applyBattleEffects(const SpellCastEnvironment * env, Bat
|
||||
obstacle->pos = pos;
|
||||
obstacle->casterSide = parameters.casterSide;
|
||||
obstacle->ID = owner->id;
|
||||
obstacle->spellLevel = parameters.spellLvl;
|
||||
obstacle->casterSpellPower = parameters.usedSpellPower;
|
||||
obstacle->spellLevel = parameters.effectLevel;
|
||||
obstacle->casterSpellPower = parameters.effectPower;
|
||||
obstacle->uniqueID = obstacleIdToGive++;
|
||||
|
||||
BattleObstaclePlaced bop;
|
||||
@ -364,6 +413,8 @@ void ObstacleMechanics::applyBattleEffects(const SpellCastEnvironment * env, Bat
|
||||
env->sendAndApply(&bop);
|
||||
};
|
||||
|
||||
const BattleHex destination = parameters.getFirstDestinationHex();
|
||||
|
||||
switch(owner->id)
|
||||
{
|
||||
case SpellID::QUICKSAND:
|
||||
@ -388,12 +439,22 @@ void ObstacleMechanics::applyBattleEffects(const SpellCastEnvironment * env, Bat
|
||||
|
||||
break;
|
||||
case SpellID::FORCE_FIELD:
|
||||
placeObstacle(parameters.destination);
|
||||
if(!destination.isValid())
|
||||
{
|
||||
env->complain("Invalid destination for FORCE_FIELD");
|
||||
return;
|
||||
}
|
||||
placeObstacle(destination);
|
||||
break;
|
||||
case SpellID::FIRE_WALL:
|
||||
{
|
||||
if(!destination.isValid())
|
||||
{
|
||||
env->complain("Invalid destination for FIRE_WALL");
|
||||
return;
|
||||
}
|
||||
//fire wall is build from multiple obstacles - one fire piece for each affected hex
|
||||
auto affectedHexes = owner->rangeInHexes(parameters.destination, parameters.spellLvl, parameters.casterSide);
|
||||
auto affectedHexes = owner->rangeInHexes(destination, parameters.spellLvl, parameters.casterSide);
|
||||
for(BattleHex hex : affectedHexes)
|
||||
placeObstacle(hex);
|
||||
}
|
||||
@ -442,9 +503,9 @@ std::vector<BattleHex> WallMechanics::rangeInHexes(BattleHex centralHex, ui8 sch
|
||||
}
|
||||
|
||||
///RemoveObstacleMechanics
|
||||
void RemoveObstacleMechanics::applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
|
||||
void RemoveObstacleMechanics::applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
|
||||
{
|
||||
if(auto obstacleToRemove = parameters.cb->battleGetObstacleOnPos(parameters.destination, false))
|
||||
if(auto obstacleToRemove = parameters.cb->battleGetObstacleOnPos(parameters.getFirstDestinationHex(), false))
|
||||
{
|
||||
ObstaclesRemoved obr;
|
||||
obr.obstacles.insert(obstacleToRemove->uniqueID);
|
||||
@ -454,20 +515,34 @@ void RemoveObstacleMechanics::applyBattleEffects(const SpellCastEnvironment * en
|
||||
env->complain("There's no obstacle to remove!");
|
||||
}
|
||||
|
||||
///SpecialRisingSpellMechanics
|
||||
ESpellCastProblem::ESpellCastProblem SacrificeMechanics::canBeCasted(const CBattleInfoCallback * cb, PlayerColor player) const
|
||||
HealingSpellMechanics::EHealLevel RisingSpellMechanics::getHealLevel(int effectLevel) const
|
||||
{
|
||||
//this may be even distinct class
|
||||
if((effectLevel <= 1) && (owner->id == SpellID::RESURRECTION))
|
||||
return EHealLevel::RESURRECT;
|
||||
|
||||
return EHealLevel::TRUE_RESURRECT;
|
||||
}
|
||||
|
||||
///SacrificeMechanics
|
||||
ESpellCastProblem::ESpellCastProblem SacrificeMechanics::canBeCast(const CBattleInfoCallback * cb, PlayerColor player) const
|
||||
{
|
||||
// for sacrifice we have to check for 2 targets (one dead to resurrect and one living to destroy)
|
||||
|
||||
bool targetExists = false;
|
||||
bool targetToSacrificeExists = false;
|
||||
|
||||
const CGHeroInstance * caster = nullptr; //todo: use ISpellCaster
|
||||
|
||||
if(cb->battleHasHero(cb->playerToSide(player)))
|
||||
caster = cb->battleGetFightingHero(cb->playerToSide(player));
|
||||
|
||||
for(const CStack * stack : cb->battleGetAllStacks())
|
||||
{
|
||||
//using isImmuneBy directly as this mechanics does not have overridden immunity check
|
||||
//therefore we do not need to check caster and casting mode
|
||||
//TODO: check that we really should check immunity for both stacks
|
||||
ESpellCastProblem::ESpellCastProblem res = owner->isImmuneBy(stack);
|
||||
ESpellCastProblem::ESpellCastProblem res = owner->internalIsImmune(caster, stack);
|
||||
const bool immune = ESpellCastProblem::OK != res && ESpellCastProblem::NOT_DECIDED != res;
|
||||
const bool casterStack = stack->owner == player;
|
||||
|
||||
@ -489,39 +564,57 @@ ESpellCastProblem::ESpellCastProblem SacrificeMechanics::canBeCasted(const CBatt
|
||||
}
|
||||
|
||||
|
||||
void SacrificeMechanics::applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
|
||||
void SacrificeMechanics::applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
|
||||
{
|
||||
RisingSpellMechanics::applyBattleEffects(env, parameters, ctx);
|
||||
|
||||
if(parameters.selectedStack == parameters.cb->battleActiveStack())
|
||||
//set another active stack than the one removed, or bad things will happen
|
||||
//TODO: make that part of BattleStacksRemoved? what about client update?
|
||||
const CStack * victim = nullptr;
|
||||
if(parameters.destinations.size() == 2)
|
||||
{
|
||||
//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 = parameters.cb->getNextStack()->ID; //why the hell next stack has same ID as current?
|
||||
env->sendAndApply(&sas);
|
||||
|
||||
victim = parameters.destinations[1].stackValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
//todo: remove and report error
|
||||
victim = parameters.selectedStack;
|
||||
}
|
||||
if(nullptr == victim)
|
||||
{
|
||||
env->complain("SacrificeMechanics: No stack to sacrifice");
|
||||
return;
|
||||
}
|
||||
//resurrect target after basic checks
|
||||
RisingSpellMechanics::applyBattleEffects(env, parameters, ctx);
|
||||
//it is safe to remove even active stack
|
||||
BattleStacksRemoved bsr;
|
||||
bsr.stackIDs.insert(parameters.selectedStack->ID); //somehow it works for teleport?
|
||||
bsr.stackIDs.insert(victim->ID);
|
||||
env->sendAndApply(&bsr);
|
||||
}
|
||||
|
||||
int SacrificeMechanics::calculateHealedHP(const SpellCastEnvironment* env, const BattleSpellCastParameters& parameters, SpellCastContext& ctx) const
|
||||
{
|
||||
int res = 0;
|
||||
const CStack * victim = nullptr;
|
||||
|
||||
if(parameters.destinations.size() == 2)
|
||||
{
|
||||
victim = parameters.destinations[1].stackValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
//todo: remove and report error
|
||||
victim = parameters.selectedStack;
|
||||
}
|
||||
if(nullptr == victim)
|
||||
{
|
||||
env->complain("SacrificeMechanics: No stack to sacrifice");
|
||||
return 0;
|
||||
}
|
||||
|
||||
res = (parameters.effectPower + victim->MaxHealth() + owner->getPower(parameters.effectLevel)) * victim->count;
|
||||
return res;
|
||||
}
|
||||
|
||||
///SpecialRisingSpellMechanics
|
||||
ESpellCastProblem::ESpellCastProblem SpecialRisingSpellMechanics::isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const
|
||||
ESpellCastProblem::ESpellCastProblem SpecialRisingSpellMechanics::isImmuneByStack(const ISpellCaster * caster, const CStack * obj) const
|
||||
{
|
||||
// following does apply to resurrect and animate dead(?) only
|
||||
// for sacrifice health calculation and health limit check don't matter
|
||||
@ -529,18 +622,19 @@ ESpellCastProblem::ESpellCastProblem SpecialRisingSpellMechanics::isImmuneByStac
|
||||
if(obj->count >= obj->baseAmount)
|
||||
return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
|
||||
|
||||
if(caster) //FIXME: Archangels can cast immune stack
|
||||
{
|
||||
auto maxHealth = calculateHealedHP(caster, obj, nullptr);
|
||||
if (maxHealth < obj->MaxHealth()) //must be able to rise at least one full creature
|
||||
return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
|
||||
}
|
||||
//FIXME: Archangels can cast immune stack and this should be applied for them and not hero
|
||||
// if(caster)
|
||||
// {
|
||||
// auto maxHealth = calculateHealedHP(caster, obj, nullptr);
|
||||
// if (maxHealth < obj->MaxHealth()) //must be able to rise at least one full creature
|
||||
// return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
|
||||
// }
|
||||
|
||||
return DefaultSpellMechanics::isImmuneByStack(caster,obj);
|
||||
}
|
||||
|
||||
///SummonMechanics
|
||||
ESpellCastProblem::ESpellCastProblem SummonMechanics::canBeCasted(const CBattleInfoCallback * cb, PlayerColor player) const
|
||||
ESpellCastProblem::ESpellCastProblem SummonMechanics::canBeCast(const CBattleInfoCallback * cb, PlayerColor player) const
|
||||
{
|
||||
const ui8 side = cb->playerToSide(player);
|
||||
|
||||
@ -559,7 +653,7 @@ ESpellCastProblem::ESpellCastProblem SummonMechanics::canBeCasted(const CBattleI
|
||||
return ESpellCastProblem::OK;
|
||||
}
|
||||
|
||||
void SummonMechanics::applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
|
||||
void SummonMechanics::applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
|
||||
{
|
||||
BattleStackAdded bsa;
|
||||
bsa.creID = creatureToSummon;
|
||||
@ -570,7 +664,7 @@ void SummonMechanics::applyBattleEffects(const SpellCastEnvironment * env, Battl
|
||||
//TODO stack casting -> probably power will be zero; set the proper number of creatures manually
|
||||
int percentBonus = parameters.casterHero ? parameters.casterHero->valOfBonuses(Bonus::SPECIFIC_SPELL_DAMAGE, owner->id.toEnum()) : 0;
|
||||
|
||||
bsa.amount = parameters.usedSpellPower
|
||||
bsa.amount = parameters.effectPower
|
||||
* owner->getPower(parameters.spellLvl)
|
||||
* (100 + percentBonus) / 100.0; //new feature - percentage bonus
|
||||
if(bsa.amount)
|
||||
@ -580,15 +674,46 @@ void SummonMechanics::applyBattleEffects(const SpellCastEnvironment * env, Battl
|
||||
}
|
||||
|
||||
///TeleportMechanics
|
||||
void TeleportMechanics::applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
|
||||
void TeleportMechanics::applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
|
||||
{
|
||||
BattleStackMoved bsm;
|
||||
bsm.distance = -1;
|
||||
bsm.stack = parameters.selectedStack->ID;
|
||||
std::vector<BattleHex> tiles;
|
||||
tiles.push_back(parameters.destination);
|
||||
bsm.tilesToMove = tiles;
|
||||
bsm.teleporting = true;
|
||||
env->sendAndApply(&bsm);
|
||||
//todo: check legal teleport
|
||||
if(parameters.destinations.size() == 2)
|
||||
{
|
||||
//first destination creature to move
|
||||
const CStack * target = parameters.destinations[0].stackValue;
|
||||
if(nullptr == target)
|
||||
{
|
||||
env->complain("TeleportMechanics: no stack to teleport");
|
||||
return;
|
||||
}
|
||||
//second destination hex to move to
|
||||
const BattleHex destination = parameters.destinations[1].hexValue;
|
||||
if(!destination.isValid())
|
||||
{
|
||||
env->complain("TeleportMechanics: invalid teleport destination");
|
||||
return;
|
||||
}
|
||||
BattleStackMoved bsm;
|
||||
bsm.distance = -1;
|
||||
bsm.stack = target->ID;
|
||||
std::vector<BattleHex> tiles;
|
||||
tiles.push_back(destination);
|
||||
bsm.tilesToMove = tiles;
|
||||
bsm.teleporting = true;
|
||||
env->sendAndApply(&bsm);
|
||||
}
|
||||
else
|
||||
{
|
||||
//todo: remove and report error
|
||||
BattleStackMoved bsm;
|
||||
bsm.distance = -1;
|
||||
bsm.stack = parameters.selectedStack->ID;
|
||||
std::vector<BattleHex> tiles;
|
||||
tiles.push_back(parameters.getFirstDestinationHex());
|
||||
bsm.tilesToMove = tiles;
|
||||
bsm.teleporting = true;
|
||||
env->sendAndApply(&bsm);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -12,6 +12,23 @@
|
||||
|
||||
#include "CDefaultSpellMechanics.h"
|
||||
|
||||
class DLL_LINKAGE HealingSpellMechanics : public DefaultSpellMechanics
|
||||
{
|
||||
public:
|
||||
enum class EHealLevel
|
||||
{
|
||||
HEAL,
|
||||
RESURRECT,
|
||||
TRUE_RESURRECT
|
||||
};
|
||||
|
||||
HealingSpellMechanics(CSpell * s): DefaultSpellMechanics(s){};
|
||||
protected:
|
||||
void applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;
|
||||
virtual int calculateHealedHP(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const;
|
||||
virtual EHealLevel getHealLevel(int effectLevel) const = 0;
|
||||
};
|
||||
|
||||
class DLL_LINKAGE AntimagicMechanics : public DefaultSpellMechanics
|
||||
{
|
||||
public:
|
||||
@ -31,44 +48,46 @@ class DLL_LINKAGE CloneMechanics : public DefaultSpellMechanics
|
||||
{
|
||||
public:
|
||||
CloneMechanics(CSpell * s): DefaultSpellMechanics(s){};
|
||||
ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const override;
|
||||
ESpellCastProblem::ESpellCastProblem isImmuneByStack(const ISpellCaster * caster, const CStack * obj) const override;
|
||||
protected:
|
||||
void applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;
|
||||
void applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;
|
||||
};
|
||||
|
||||
class DLL_LINKAGE CureMechanics : public DefaultSpellMechanics
|
||||
class DLL_LINKAGE CureMechanics : public HealingSpellMechanics
|
||||
{
|
||||
public:
|
||||
CureMechanics(CSpell * s): DefaultSpellMechanics(s){};
|
||||
CureMechanics(CSpell * s): HealingSpellMechanics(s){};
|
||||
|
||||
void applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const override final;
|
||||
|
||||
EHealLevel getHealLevel(int effectLevel) const override final;
|
||||
};
|
||||
|
||||
class DLL_LINKAGE DispellMechanics : public DefaultSpellMechanics
|
||||
{
|
||||
public:
|
||||
DispellMechanics(CSpell * s): DefaultSpellMechanics(s){};
|
||||
ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const override;
|
||||
ESpellCastProblem::ESpellCastProblem isImmuneByStack(const ISpellCaster * caster, const CStack * obj) const override;
|
||||
|
||||
void applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const override final;
|
||||
protected:
|
||||
void applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;
|
||||
void applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;
|
||||
};
|
||||
|
||||
class DLL_LINKAGE EarthquakeMechanics : public DefaultSpellMechanics
|
||||
{
|
||||
public:
|
||||
EarthquakeMechanics(CSpell * s): DefaultSpellMechanics(s){};
|
||||
ESpellCastProblem::ESpellCastProblem canBeCasted(const CBattleInfoCallback * cb, PlayerColor player) const override;
|
||||
ESpellCastProblem::ESpellCastProblem canBeCast(const CBattleInfoCallback * cb, PlayerColor player) const override;
|
||||
protected:
|
||||
void applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;
|
||||
void applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;
|
||||
};
|
||||
|
||||
class DLL_LINKAGE HypnotizeMechanics : public DefaultSpellMechanics
|
||||
{
|
||||
public:
|
||||
HypnotizeMechanics(CSpell * s): DefaultSpellMechanics(s){};
|
||||
ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const override;
|
||||
ESpellCastProblem::ESpellCastProblem isImmuneByStack(const ISpellCaster * caster, const CStack * obj) const override;
|
||||
};
|
||||
|
||||
class DLL_LINKAGE ObstacleMechanics : public DefaultSpellMechanics
|
||||
@ -77,7 +96,7 @@ public:
|
||||
ObstacleMechanics(CSpell * s): DefaultSpellMechanics(s){};
|
||||
|
||||
protected:
|
||||
void applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;
|
||||
void applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;
|
||||
};
|
||||
|
||||
class DLL_LINKAGE WallMechanics : public ObstacleMechanics
|
||||
@ -92,15 +111,15 @@ class DLL_LINKAGE RemoveObstacleMechanics : public DefaultSpellMechanics
|
||||
public:
|
||||
RemoveObstacleMechanics(CSpell * s): DefaultSpellMechanics(s){};
|
||||
protected:
|
||||
void applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;
|
||||
void applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;
|
||||
};
|
||||
|
||||
///all rising spells
|
||||
class DLL_LINKAGE RisingSpellMechanics : public DefaultSpellMechanics
|
||||
class DLL_LINKAGE RisingSpellMechanics : public HealingSpellMechanics
|
||||
{
|
||||
public:
|
||||
RisingSpellMechanics(CSpell * s): DefaultSpellMechanics(s){};
|
||||
|
||||
RisingSpellMechanics(CSpell * s): HealingSpellMechanics(s){};
|
||||
EHealLevel getHealLevel(int effectLevel) const override;
|
||||
};
|
||||
|
||||
class DLL_LINKAGE SacrificeMechanics : public RisingSpellMechanics
|
||||
@ -108,9 +127,10 @@ class DLL_LINKAGE SacrificeMechanics : public RisingSpellMechanics
|
||||
public:
|
||||
SacrificeMechanics(CSpell * s): RisingSpellMechanics(s){};
|
||||
|
||||
ESpellCastProblem::ESpellCastProblem canBeCasted(const CBattleInfoCallback * cb, PlayerColor player) const override;
|
||||
ESpellCastProblem::ESpellCastProblem canBeCast(const CBattleInfoCallback * cb, PlayerColor player) const override;
|
||||
protected:
|
||||
void applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;
|
||||
void applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;
|
||||
int calculateHealedHP(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;
|
||||
};
|
||||
|
||||
///all rising spells but SACRIFICE
|
||||
@ -118,7 +138,7 @@ class DLL_LINKAGE SpecialRisingSpellMechanics : public RisingSpellMechanics
|
||||
{
|
||||
public:
|
||||
SpecialRisingSpellMechanics(CSpell * s): RisingSpellMechanics(s){};
|
||||
ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const override;
|
||||
ESpellCastProblem::ESpellCastProblem isImmuneByStack(const ISpellCaster * caster, const CStack * obj) const override;
|
||||
};
|
||||
|
||||
class DLL_LINKAGE SummonMechanics : public DefaultSpellMechanics
|
||||
@ -126,9 +146,9 @@ class DLL_LINKAGE SummonMechanics : public DefaultSpellMechanics
|
||||
public:
|
||||
SummonMechanics(CSpell * s, CreatureID cre): DefaultSpellMechanics(s), creatureToSummon(cre){};
|
||||
|
||||
ESpellCastProblem::ESpellCastProblem canBeCasted(const CBattleInfoCallback * cb, PlayerColor player) const override;
|
||||
ESpellCastProblem::ESpellCastProblem canBeCast(const CBattleInfoCallback * cb, PlayerColor player) const override;
|
||||
protected:
|
||||
void applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;
|
||||
void applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;
|
||||
private:
|
||||
CreatureID creatureToSummon;
|
||||
};
|
||||
@ -138,5 +158,5 @@ class DLL_LINKAGE TeleportMechanics: public DefaultSpellMechanics
|
||||
public:
|
||||
TeleportMechanics(CSpell * s): DefaultSpellMechanics(s){};
|
||||
protected:
|
||||
void applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;
|
||||
void applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;
|
||||
};
|
||||
|
@ -139,7 +139,7 @@ void DefaultSpellMechanics::applyBattle(BattleInfo * battle, const BattleSpellCa
|
||||
//check for each bonus if it should be removed
|
||||
const bool isSpellEffect = Selector::sourceType(Bonus::SPELL_EFFECT)(b);
|
||||
const int spellID = isSpellEffect ? b->sid : -1;
|
||||
|
||||
//No exceptions, ANY spell can be countered, even if it can`t be dispelled.
|
||||
return isSpellEffect && vstd::contains(owner->counteredSpells, spellID);
|
||||
});
|
||||
}
|
||||
@ -223,29 +223,35 @@ ESpellCastResult DefaultSpellMechanics::applyAdventureEffects(const SpellCastEnv
|
||||
|
||||
void DefaultSpellMechanics::battleCast(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters) const
|
||||
{
|
||||
logGlobal->debugStream() << "Started spell cast. Spell: "<<owner->name<<"; mode:"<<parameters.mode;
|
||||
|
||||
if(nullptr == parameters.caster)
|
||||
{
|
||||
env->complain("No spell-caster provided.");
|
||||
return;
|
||||
}
|
||||
|
||||
BattleSpellCast sc;
|
||||
sc.side = parameters.casterSide;
|
||||
sc.id = owner->id;
|
||||
sc.skill = parameters.spellLvl;
|
||||
sc.tile = parameters.destination;
|
||||
sc.dmgToDisplay = 0;
|
||||
sc.castByHero = nullptr != parameters.casterHero;
|
||||
sc.casterStack = (parameters.casterStack ? parameters.casterStack->ID : -1);
|
||||
sc.manaGained = 0;
|
||||
|
||||
prepareBattleCast(parameters, sc);
|
||||
|
||||
//check it there is opponent hero
|
||||
const ui8 otherSide = 1-parameters.casterSide;
|
||||
const CGHeroInstance * otherHero = nullptr;
|
||||
if(parameters.cb->battleHasHero(otherSide))
|
||||
otherHero = parameters.cb->battleGetFightingHero(otherSide);
|
||||
int spellCost = 0;
|
||||
|
||||
//calculate spell cost
|
||||
if(parameters.casterHero)
|
||||
if(parameters.mode == ECastingMode::HERO_CASTING)
|
||||
{
|
||||
spellCost = parameters.cb->battleGetSpellCost(owner, parameters.casterHero);
|
||||
|
||||
if(parameters.secHero && parameters.mode == ECastingMode::HERO_CASTING) //handle mana channel
|
||||
{
|
||||
if(nullptr != otherHero) //handle mana channel
|
||||
{
|
||||
int manaChannel = 0;
|
||||
for(const CStack * stack : parameters.cb->battleGetAllStacks(true)) //TODO: shouldn't bonus system handle it somehow?
|
||||
{
|
||||
if(stack->owner == parameters.secHero->tempOwner)
|
||||
if(stack->owner == otherHero->tempOwner)
|
||||
{
|
||||
vstd::amax(manaChannel, stack->valOfBonuses(Bonus::MANA_CHANNELING));
|
||||
}
|
||||
@ -253,53 +259,36 @@ void DefaultSpellMechanics::battleCast(const SpellCastEnvironment * env, BattleS
|
||||
sc.manaGained = (manaChannel * spellCost) / 100;
|
||||
}
|
||||
}
|
||||
logGlobal->debugStream() << "spellCost: " << spellCost;
|
||||
|
||||
//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 = owner->getAffectedStacks(parameters.cb, parameters.mode, parameters.casterColor, parameters.spellLvl, parameters.destination, parameters.casterHero);
|
||||
auto creatures = owner->getAffectedStacks(parameters.cb, parameters.mode, parameters.casterColor, parameters.spellLvl, parameters.getFirstDestinationHex(), parameters.caster);
|
||||
std::copy(creatures.begin(), creatures.end(), std::back_inserter(attackedCres));
|
||||
|
||||
logGlobal->debugStream() << "will affect: " << attackedCres.size() << " stacks";
|
||||
|
||||
std::vector <const CStack*> reflected;//for magic mirror
|
||||
|
||||
//checking if creatures resist
|
||||
//resistance/reflection is applied only to negative spells
|
||||
if(owner->isNegative())
|
||||
//checking if creatures resist
|
||||
handleResistance(env, attackedCres, sc);
|
||||
//it is actual spell and can be reflected to single target, no recurrence
|
||||
const bool tryMagicMirror = owner->isNegative() && owner->level && owner->getLevelInfo(0).range == "0";
|
||||
if(tryMagicMirror)
|
||||
{
|
||||
//it is actual spell and can be reflected to single target, no recurrence
|
||||
const bool tryMagicMirror = parameters.mode != ECastingMode::MAGIC_MIRROR && owner->level && owner->getLevelInfo(0).range == "0";
|
||||
std::vector <const CStack*> resisted;
|
||||
for(auto s : attackedCres)
|
||||
{
|
||||
//magic resistance
|
||||
const int prob = std::min((s)->magicResistance(), 100); //probability of resistance in %
|
||||
|
||||
if(env->getRandomGenerator().nextInt(99) < prob)
|
||||
{
|
||||
resisted.push_back(s);
|
||||
}
|
||||
//magic mirror
|
||||
if(tryMagicMirror)
|
||||
{
|
||||
const int mirrorChance = (s)->valOfBonuses(Bonus::MAGIC_MIRROR);
|
||||
if(env->getRandomGenerator().nextInt(99) < mirrorChance)
|
||||
reflected.push_back(s);
|
||||
}
|
||||
const int mirrorChance = (s)->valOfBonuses(Bonus::MAGIC_MIRROR);
|
||||
if(env->getRandomGenerator().nextInt(99) < mirrorChance)
|
||||
reflected.push_back(s);
|
||||
}
|
||||
|
||||
vstd::erase_if(attackedCres, [&resisted, reflected](const CStack * s)
|
||||
vstd::erase_if(attackedCres, [&reflected](const CStack * s)
|
||||
{
|
||||
return vstd::contains(resisted, s) || vstd::contains(reflected, s);
|
||||
return vstd::contains(reflected, s);
|
||||
});
|
||||
|
||||
for(auto s : resisted)
|
||||
{
|
||||
BattleSpellCast::CustomEffect effect;
|
||||
effect.effect = 78;
|
||||
effect.stack = s->ID;
|
||||
sc.customEffects.push_back(effect);
|
||||
}
|
||||
for(auto s : reflected)
|
||||
{
|
||||
BattleSpellCast::CustomEffect effect;
|
||||
@ -316,13 +305,12 @@ void DefaultSpellMechanics::battleCast(const SpellCastEnvironment * env, BattleS
|
||||
|
||||
StacksInjured si;
|
||||
SpellCastContext ctx(attackedCres, sc, si);
|
||||
|
||||
applyBattleEffects(env, parameters, ctx);
|
||||
|
||||
env->sendAndApply(&sc);
|
||||
|
||||
//spend mana
|
||||
if(parameters.casterHero)
|
||||
if(parameters.mode == ECastingMode::HERO_CASTING)
|
||||
{
|
||||
SetMana sm;
|
||||
sm.absolute = false;
|
||||
@ -334,9 +322,9 @@ void DefaultSpellMechanics::battleCast(const SpellCastEnvironment * env, BattleS
|
||||
|
||||
if(sc.manaGained > 0)
|
||||
{
|
||||
assert(parameters.secHero);
|
||||
assert(otherHero);
|
||||
|
||||
sm.hid = parameters.secHero->id;
|
||||
sm.hid = otherHero->id;
|
||||
sm.val = sc.manaGained;
|
||||
env->sendAndApply(&sm);
|
||||
}
|
||||
@ -358,7 +346,7 @@ void DefaultSpellMechanics::battleCast(const SpellCastEnvironment * env, BattleS
|
||||
ssp.absolute = false;
|
||||
env->sendAndApply(&ssp);
|
||||
}
|
||||
|
||||
logGlobal->debugStream() << "Finished spell cast. Spell: "<<owner->name<<"; mode:"<<parameters.mode;
|
||||
//Magic Mirror effect
|
||||
for(auto & attackedCre : reflected)
|
||||
{
|
||||
@ -373,18 +361,17 @@ void DefaultSpellMechanics::battleCast(const SpellCastEnvironment * env, BattleS
|
||||
{
|
||||
int targetHex = (*RandomGeneratorUtil::nextItem(mirrorTargets, env->getRandomGenerator()))->position;
|
||||
|
||||
BattleSpellCastParameters mirrorParameters = parameters;
|
||||
BattleSpellCastParameters mirrorParameters(parameters.cb, attackedCre, owner);
|
||||
mirrorParameters.spellLvl = 0;
|
||||
mirrorParameters.casterSide = 1-parameters.casterSide;
|
||||
mirrorParameters.casterColor = (attackedCre)->owner;
|
||||
mirrorParameters.casterHero = nullptr;
|
||||
mirrorParameters.destination = targetHex;
|
||||
mirrorParameters.secHero = parameters.casterHero;
|
||||
mirrorParameters.aimToHex(targetHex);
|
||||
mirrorParameters.mode = ECastingMode::MAGIC_MIRROR;
|
||||
mirrorParameters.casterStack = (attackedCre);
|
||||
mirrorParameters.selectedStack = nullptr;
|
||||
|
||||
battleCast(env, mirrorParameters);
|
||||
mirrorParameters.spellLvl = parameters.spellLvl;
|
||||
mirrorParameters.effectLevel = parameters.effectLevel;
|
||||
mirrorParameters.effectPower = parameters.effectPower;
|
||||
mirrorParameters.effectValue = parameters.effectValue;
|
||||
mirrorParameters.enchantPower = parameters.enchantPower;
|
||||
castMagicMirror(env, mirrorParameters);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -486,74 +473,21 @@ void DefaultSpellMechanics::battleLogSingleTarget(std::vector<std::string> & log
|
||||
}
|
||||
}
|
||||
|
||||
int DefaultSpellMechanics::calculateDuration(const CGHeroInstance * caster, int usedSpellPower) const
|
||||
void DefaultSpellMechanics::applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
|
||||
{
|
||||
if(caster == nullptr)
|
||||
{
|
||||
if (!usedSpellPower)
|
||||
return 3; //default duration of all creature spells
|
||||
else
|
||||
return usedSpellPower; //use creature spell power
|
||||
}
|
||||
else
|
||||
return caster->getPrimSkillLevel(PrimarySkill::SPELL_POWER) + caster->valOfBonuses(Bonus::SPELL_DURATION);
|
||||
}
|
||||
|
||||
ui32 DefaultSpellMechanics::calculateHealedHP(const CGHeroInstance* caster, const CStack* stack, const CStack* sacrificedStack) const
|
||||
{
|
||||
int healedHealth;
|
||||
|
||||
if(!owner->isHealingSpell())
|
||||
{
|
||||
logGlobal->errorStream() << "calculateHealedHP called for nonhealing spell "<< owner->name;
|
||||
return 0;
|
||||
}
|
||||
|
||||
const int spellPowerSkill = caster->getPrimSkillLevel(PrimarySkill::SPELL_POWER);
|
||||
const int levelPower = owner->getPower(caster->getSpellSchoolLevel(owner));
|
||||
|
||||
if (owner->id == SpellID::SACRIFICE && sacrificedStack)
|
||||
healedHealth = (spellPowerSkill + sacrificedStack->MaxHealth() + levelPower) * sacrificedStack->count;
|
||||
else
|
||||
healedHealth = spellPowerSkill * owner->power + levelPower; //???
|
||||
healedHealth = caster->getSpellBonus(owner, healedHealth, stack);
|
||||
return std::min<ui32>(healedHealth, stack->MaxHealth() - stack->firstHPleft + (owner->isRisingSpell() ? stack->baseAmount * stack->MaxHealth() : 0));
|
||||
}
|
||||
|
||||
|
||||
void DefaultSpellMechanics::applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
|
||||
{
|
||||
int effectLevel = parameters.spellLvl;
|
||||
{
|
||||
//MAXED_SPELL bonus.
|
||||
if(parameters.casterHero != nullptr)
|
||||
if(parameters.casterHero->hasBonusOfType(Bonus::MAXED_SPELL, owner->id))
|
||||
effectLevel = 3;
|
||||
}
|
||||
|
||||
//applying effects
|
||||
if(owner->isOffensiveSpell())
|
||||
{
|
||||
int spellDamage = 0;
|
||||
if(parameters.casterStack && parameters.mode != ECastingMode::MAGIC_MIRROR)
|
||||
{
|
||||
int unitSpellPower = parameters.casterStack->valOfBonuses(Bonus::SPECIFIC_SPELL_POWER, owner->id.toEnum());
|
||||
if(unitSpellPower)
|
||||
ctx.sc.dmgToDisplay = spellDamage = parameters.casterStack->count * unitSpellPower; //TODO: handle immunities
|
||||
else //Faerie Dragon
|
||||
{
|
||||
parameters.usedSpellPower = parameters.casterStack->valOfBonuses(Bonus::CREATURE_SPELL_POWER) * parameters.casterStack->count / 100;
|
||||
ctx.sc.dmgToDisplay = 0;
|
||||
}
|
||||
}
|
||||
int spellDamage = parameters.effectValue;
|
||||
|
||||
int chainLightningModifier = 0;
|
||||
for(auto & attackedCre : ctx.attackedCres)
|
||||
{
|
||||
BattleStackAttacked bsa;
|
||||
if(spellDamage)
|
||||
bsa.damageAmount = spellDamage >> chainLightningModifier;
|
||||
if(spellDamage != 0)
|
||||
bsa.damageAmount = owner->adjustRawDamage(parameters.caster, attackedCre, spellDamage) >> chainLightningModifier;
|
||||
else
|
||||
bsa.damageAmount = owner->calculateDamage(parameters.casterHero, attackedCre, effectLevel, parameters.usedSpellPower) >> chainLightningModifier;
|
||||
bsa.damageAmount = owner->calculateDamage(parameters.caster, attackedCre, parameters.effectLevel, parameters.effectPower) >> chainLightningModifier;
|
||||
|
||||
ctx.sc.dmgToDisplay += bsa.damageAmount;
|
||||
|
||||
@ -572,19 +506,14 @@ void DefaultSpellMechanics::applyBattleEffects(const SpellCastEnvironment * env,
|
||||
|
||||
if(owner->hasEffects())
|
||||
{
|
||||
int stackSpellPower = 0;
|
||||
if(parameters.casterStack && parameters.mode != ECastingMode::MAGIC_MIRROR)
|
||||
{
|
||||
stackSpellPower = parameters.casterStack->valOfBonuses(Bonus::CREATURE_ENCHANT_POWER);
|
||||
}
|
||||
SetStackEffect sse;
|
||||
//get default spell duration (spell power with bonuses for heroes)
|
||||
int duration = calculateDuration(parameters.casterHero, stackSpellPower ? stackSpellPower : parameters.usedSpellPower);
|
||||
int duration = parameters.enchantPower;
|
||||
//generate actual stack bonuses
|
||||
{
|
||||
int maxDuration = 0;
|
||||
std::vector<Bonus> tmp;
|
||||
owner->getEffects(tmp, effectLevel);
|
||||
owner->getEffects(tmp, parameters.effectLevel);
|
||||
for(Bonus& b : tmp)
|
||||
{
|
||||
//use configured duration if present
|
||||
@ -664,49 +593,7 @@ void DefaultSpellMechanics::applyBattleEffects(const SpellCastEnvironment * env,
|
||||
if(!sse.stacks.empty())
|
||||
env->sendAndApply(&sse);
|
||||
|
||||
}
|
||||
|
||||
if(owner->isHealingSpell())
|
||||
{
|
||||
int hpGained = 0;
|
||||
if(parameters.casterStack)
|
||||
{
|
||||
int unitSpellPower = parameters.casterStack->valOfBonuses(Bonus::SPECIFIC_SPELL_POWER, owner->id.toEnum());
|
||||
if(unitSpellPower)
|
||||
hpGained = parameters.casterStack->count * unitSpellPower; //Archangel
|
||||
else //Faerie Dragon-like effect - unused so far
|
||||
parameters.usedSpellPower = parameters.casterStack->valOfBonuses(Bonus::CREATURE_SPELL_POWER) * parameters.casterStack->count / 100;
|
||||
}
|
||||
StacksHealedOrResurrected shr;
|
||||
shr.lifeDrain = false;
|
||||
shr.tentHealing = false;
|
||||
for(auto & attackedCre : ctx.attackedCres)
|
||||
{
|
||||
StacksHealedOrResurrected::HealInfo hi;
|
||||
hi.stackID = (attackedCre)->ID;
|
||||
if (parameters.casterStack) //casted by creature
|
||||
{
|
||||
const bool resurrect = owner->isRisingSpell();
|
||||
if (hpGained)
|
||||
{
|
||||
//archangel
|
||||
hi.healedHP = std::min<ui32>(hpGained, attackedCre->MaxHealth() - attackedCre->firstHPleft + (resurrect ? attackedCre->baseAmount * attackedCre->MaxHealth() : 0));
|
||||
}
|
||||
else
|
||||
{
|
||||
//any typical spell (commander's cure or animate dead)
|
||||
int healedHealth = parameters.usedSpellPower * owner->power + owner->getPower(effectLevel);
|
||||
hi.healedHP = std::min<ui32>(healedHealth, attackedCre->MaxHealth() - attackedCre->firstHPleft + (resurrect ? attackedCre->baseAmount * attackedCre->MaxHealth() : 0));
|
||||
}
|
||||
}
|
||||
else
|
||||
hi.healedHP = calculateHealedHP(parameters.casterHero, attackedCre, parameters.selectedStack); //Casted by hero
|
||||
hi.lowLevelResurrection = (effectLevel <= 1) && (owner->id != SpellID::ANIMATE_DEAD);
|
||||
shr.healedStacks.push_back(hi);
|
||||
}
|
||||
if(!shr.healedStacks.empty())
|
||||
env->sendAndApply(&shr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<BattleHex> DefaultSpellMechanics::rangeInHexes(BattleHex centralHex, ui8 schoolLvl, ui8 side, bool *outDroppedHexes) const
|
||||
@ -849,24 +736,125 @@ std::set<const CStack *> DefaultSpellMechanics::getAffectedStacks(SpellTargeting
|
||||
return attackedCres;
|
||||
}
|
||||
|
||||
ESpellCastProblem::ESpellCastProblem DefaultSpellMechanics::canBeCasted(const CBattleInfoCallback * cb, PlayerColor player) const
|
||||
ESpellCastProblem::ESpellCastProblem DefaultSpellMechanics::canBeCast(const CBattleInfoCallback * cb, PlayerColor player) const
|
||||
{
|
||||
//no problems by default, this method is for spell-specific problems
|
||||
return ESpellCastProblem::OK;
|
||||
}
|
||||
|
||||
|
||||
ESpellCastProblem::ESpellCastProblem DefaultSpellMechanics::isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const
|
||||
ESpellCastProblem::ESpellCastProblem DefaultSpellMechanics::isImmuneByStack(const ISpellCaster * caster, const CStack * obj) const
|
||||
{
|
||||
//by default use general algorithm
|
||||
return owner->isImmuneBy(obj);
|
||||
return owner->internalIsImmune(caster, obj);
|
||||
}
|
||||
|
||||
void DefaultSpellMechanics::doDispell(BattleInfo * battle, const BattleSpellCast * packet, const CSelector & selector) const
|
||||
{
|
||||
auto localSelector = [](const Bonus * bonus)
|
||||
{
|
||||
const CSpell * sourceSpell = bonus->sourceSpell();
|
||||
if(sourceSpell != nullptr)
|
||||
{
|
||||
//Special case: DISRUPTING_RAY is "immune" to dispell
|
||||
//Other even PERMANENT effects can be removed (f.e. BIND)
|
||||
if(sourceSpell->id == SpellID::DISRUPTING_RAY)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
for(auto stackID : packet->affectedCres)
|
||||
{
|
||||
CStack *s = battle->getStack(stackID);
|
||||
s->popBonuses(selector);
|
||||
s->popBonuses(CSelector(localSelector).And(selector));
|
||||
}
|
||||
}
|
||||
|
||||
void DefaultSpellMechanics::castMagicMirror(const SpellCastEnvironment* env, BattleSpellCastParameters& parameters) const
|
||||
{
|
||||
logGlobal->debugStream() << "Started spell cast. Spell: "<<owner->name<<"; mode: MAGIC_MIRROR";
|
||||
if(parameters.mode != ECastingMode::MAGIC_MIRROR)
|
||||
{
|
||||
env->complain("MagicMirror: invalid mode");
|
||||
return;
|
||||
}
|
||||
BattleHex destination = parameters.getFirstDestinationHex();
|
||||
if(!destination.isValid())
|
||||
{
|
||||
env->complain("MagicMirror: invalid destination");
|
||||
return;
|
||||
}
|
||||
|
||||
BattleSpellCast sc;
|
||||
prepareBattleCast(parameters, sc);
|
||||
|
||||
//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 = owner->getAffectedStacks(parameters.cb, parameters.mode, parameters.casterColor, parameters.spellLvl, destination, parameters.caster);
|
||||
std::copy(creatures.begin(), creatures.end(), std::back_inserter(attackedCres));
|
||||
|
||||
logGlobal->debugStream() << "will affect: " << attackedCres.size() << " stacks";
|
||||
|
||||
handleResistance(env, attackedCres, sc);
|
||||
|
||||
for(auto cre : attackedCres)
|
||||
{
|
||||
sc.affectedCres.insert(cre->ID);
|
||||
}
|
||||
|
||||
StacksInjured si;
|
||||
SpellCastContext ctx(attackedCres, sc, si);
|
||||
applyBattleEffects(env, parameters, ctx);
|
||||
|
||||
env->sendAndApply(&sc);
|
||||
if(!si.stacks.empty()) //after spellcast info shows
|
||||
env->sendAndApply(&si);
|
||||
logGlobal->debugStream() << "Finished spell cast. Spell: "<<owner->name<<"; mode: MAGIC_MIRROR";
|
||||
}
|
||||
|
||||
void DefaultSpellMechanics::handleResistance(const SpellCastEnvironment * env, std::vector<const CStack* >& attackedCres, BattleSpellCast& sc) const
|
||||
{
|
||||
//checking if creatures resist
|
||||
//resistance/reflection is applied only to negative spells
|
||||
if(owner->isNegative())
|
||||
{
|
||||
std::vector <const CStack*> resisted;
|
||||
for(auto s : attackedCres)
|
||||
{
|
||||
//magic resistance
|
||||
const int prob = std::min((s)->magicResistance(), 100); //probability of resistance in %
|
||||
|
||||
if(env->getRandomGenerator().nextInt(99) < prob)
|
||||
{
|
||||
resisted.push_back(s);
|
||||
}
|
||||
}
|
||||
|
||||
vstd::erase_if(attackedCres, [&resisted](const CStack * s)
|
||||
{
|
||||
return vstd::contains(resisted, s);
|
||||
});
|
||||
|
||||
for(auto s : resisted)
|
||||
{
|
||||
BattleSpellCast::CustomEffect effect;
|
||||
effect.effect = 78;
|
||||
effect.stack = s->ID;
|
||||
sc.customEffects.push_back(effect);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DefaultSpellMechanics::prepareBattleCast(const BattleSpellCastParameters& parameters, BattleSpellCast& sc) const
|
||||
{
|
||||
sc.side = parameters.casterSide;
|
||||
sc.id = owner->id;
|
||||
sc.skill = parameters.spellLvl;
|
||||
sc.tile = parameters.getFirstDestinationHex();
|
||||
sc.dmgToDisplay = 0;
|
||||
sc.castByHero = parameters.mode == ECastingMode::HERO_CASTING;
|
||||
sc.casterStack = (parameters.casterStack ? parameters.casterStack->ID : -1);
|
||||
sc.manaGained = 0;
|
||||
}
|
||||
|
||||
|
@ -19,7 +19,9 @@ class StacksInjured;
|
||||
struct SpellCastContext
|
||||
{
|
||||
SpellCastContext(std::vector<const CStack *> & attackedCres, BattleSpellCast & sc, StacksInjured & si):
|
||||
attackedCres(attackedCres), sc(sc), si(si){};
|
||||
attackedCres(attackedCres), sc(sc), si(si)
|
||||
{
|
||||
};
|
||||
std::vector<const CStack *> & attackedCres;
|
||||
BattleSpellCast & sc;
|
||||
StacksInjured & si;
|
||||
@ -40,26 +42,25 @@ public:
|
||||
std::vector<BattleHex> rangeInHexes(BattleHex centralHex, ui8 schoolLvl, ui8 side, bool * outDroppedHexes = nullptr) const override;
|
||||
std::set<const CStack *> getAffectedStacks(SpellTargetingContext & ctx) const override;
|
||||
|
||||
ESpellCastProblem::ESpellCastProblem canBeCasted(const CBattleInfoCallback * cb, PlayerColor player) const override;
|
||||
ESpellCastProblem::ESpellCastProblem canBeCast(const CBattleInfoCallback * cb, PlayerColor player) const override;
|
||||
|
||||
ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const override;
|
||||
ESpellCastProblem::ESpellCastProblem isImmuneByStack(const ISpellCaster * caster, const CStack * obj) const override;
|
||||
|
||||
virtual void applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const override;
|
||||
bool adventureCast(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const override final;
|
||||
void battleCast(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters) const override;
|
||||
void battleCast(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters) const override final;
|
||||
|
||||
void battleLogSingleTarget(std::vector<std::string> & logLines, const BattleSpellCast * packet,
|
||||
const std::string & casterName, const CStack * attackedStack, bool & displayDamage) const override;
|
||||
protected:
|
||||
virtual void applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const;
|
||||
|
||||
virtual int calculateDuration(const CGHeroInstance * caster, int usedSpellPower) const;
|
||||
|
||||
///calculate healed HP for all spells casted by hero
|
||||
ui32 calculateHealedHP(const CGHeroInstance * caster, const CStack * stack, const CStack * sacrificedStack) const;
|
||||
virtual void applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const;
|
||||
|
||||
///actual adventure cast implementation
|
||||
virtual ESpellCastResult applyAdventureEffects(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const;
|
||||
|
||||
void doDispell(BattleInfo * battle, const BattleSpellCast * packet, const CSelector & selector) const;
|
||||
private:
|
||||
void castMagicMirror(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters) const;
|
||||
void handleResistance(const SpellCastEnvironment * env, std::vector<const CStack*> & attackedCres, BattleSpellCast & sc) const;
|
||||
void prepareBattleCast(const BattleSpellCastParameters & parameters, BattleSpellCast & sc) const;
|
||||
};
|
||||
|
@ -23,10 +23,11 @@
|
||||
#include "../CModHandler.h"
|
||||
#include "../StringConstants.h"
|
||||
|
||||
#include "../mapObjects/CGHeroInstance.h"
|
||||
#include "../BattleState.h"
|
||||
#include "../CBattleCallback.h"
|
||||
#include "../CGameState.h"
|
||||
#include "../CGameState.h" //todo: remove
|
||||
|
||||
#include "../NetPacks.h" //todo: remove
|
||||
|
||||
#include "ISpellMechanics.h"
|
||||
|
||||
@ -71,13 +72,6 @@ namespace SpellConfig
|
||||
};
|
||||
}
|
||||
|
||||
BattleSpellCastParameters::BattleSpellCastParameters(const BattleInfo* cb)
|
||||
: spellLvl(0), destination(BattleHex::INVALID), casterSide(0),casterColor(PlayerColor::CANNOT_DETERMINE),casterHero(nullptr), secHero(nullptr),
|
||||
usedSpellPower(0),mode(ECastingMode::HERO_CASTING), casterStack(nullptr), selectedStack(nullptr), cb(cb)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
///CSpell::LevelInfo
|
||||
CSpell::LevelInfo::LevelInfo()
|
||||
:description(""),cost(0),power(0),AIValue(0),smartTarget(true), clearTarget(false), clearAffected(false), range("0")
|
||||
@ -121,9 +115,13 @@ bool CSpell::adventureCast(const SpellCastEnvironment * env, AdventureSpellCastP
|
||||
}
|
||||
|
||||
void CSpell::battleCast(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters) const
|
||||
{
|
||||
{
|
||||
assert(env);
|
||||
|
||||
if(parameters.destinations.size()<1)
|
||||
{
|
||||
env->complain("Spell must have at least one destination");
|
||||
return;
|
||||
}
|
||||
mechanics->battleCast(env, parameters);
|
||||
}
|
||||
|
||||
@ -140,52 +138,15 @@ const CSpell::LevelInfo & CSpell::getLevelInfo(const int level) const
|
||||
|
||||
ui32 CSpell::calculateDamage(const ISpellCaster * caster, const CStack * affectedCreature, int spellSchoolLevel, int usedSpellPower) const
|
||||
{
|
||||
ui32 ret = 0; //value to return
|
||||
|
||||
//check if spell really does damage - if not, return 0
|
||||
if(!isDamageSpell())
|
||||
return 0;
|
||||
|
||||
ret = usedSpellPower * power;
|
||||
ret += getPower(spellSchoolLevel);
|
||||
|
||||
//affected creature-specific part
|
||||
if(nullptr != affectedCreature)
|
||||
{
|
||||
//applying protections - when spell has more then one elements, only one protection should be applied (I think)
|
||||
forEachSchool([&](const SpellSchoolInfo & cnf, bool & stop)
|
||||
{
|
||||
if(affectedCreature->hasBonusOfType(Bonus::SPELL_DAMAGE_REDUCTION, (ui8)cnf.id))
|
||||
{
|
||||
ret *= affectedCreature->valOfBonuses(Bonus::SPELL_DAMAGE_REDUCTION, (ui8)cnf.id);
|
||||
ret /= 100;
|
||||
stop = true;//only bonus from one school is used
|
||||
}
|
||||
});
|
||||
|
||||
//general spell dmg reduction
|
||||
if(affectedCreature->hasBonusOfType(Bonus::SPELL_DAMAGE_REDUCTION, -1))
|
||||
{
|
||||
ret *= affectedCreature->valOfBonuses(Bonus::SPELL_DAMAGE_REDUCTION, -1);
|
||||
ret /= 100;
|
||||
}
|
||||
|
||||
//dmg increasing
|
||||
if(affectedCreature->hasBonusOfType(Bonus::MORE_DAMAGE_FROM_SPELL, id))
|
||||
{
|
||||
ret *= 100 + affectedCreature->valOfBonuses(Bonus::MORE_DAMAGE_FROM_SPELL, id.toEnum());
|
||||
ret /= 100;
|
||||
}
|
||||
}
|
||||
|
||||
if(nullptr != caster) //todo: make sure that caster always present
|
||||
ret = caster->getSpellBonus(this, ret, affectedCreature);
|
||||
return ret;
|
||||
return adjustRawDamage(caster, affectedCreature, calculateRawEffectValue(spellSchoolLevel, usedSpellPower));
|
||||
}
|
||||
|
||||
ESpellCastProblem::ESpellCastProblem CSpell::canBeCasted(const CBattleInfoCallback * cb, PlayerColor player) const
|
||||
ESpellCastProblem::ESpellCastProblem CSpell::canBeCast(const CBattleInfoCallback * cb, PlayerColor player) const
|
||||
{
|
||||
return mechanics->canBeCasted(cb, player);
|
||||
return mechanics->canBeCast(cb, player);
|
||||
}
|
||||
|
||||
std::vector<BattleHex> CSpell::rangeInHexes(BattleHex centralHex, ui8 schoolLvl, ui8 side, bool *outDroppedHexes) const
|
||||
@ -193,7 +154,7 @@ std::vector<BattleHex> CSpell::rangeInHexes(BattleHex centralHex, ui8 schoolLvl,
|
||||
return mechanics->rangeInHexes(centralHex,schoolLvl,side,outDroppedHexes);
|
||||
}
|
||||
|
||||
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 *> CSpell::getAffectedStacks(const CBattleInfoCallback * cb, ECastingMode::ECastingMode mode, PlayerColor casterColor, int spellLvl, BattleHex destination, const ISpellCaster * caster) const
|
||||
{
|
||||
ISpellMechanics::SpellTargetingContext ctx(this, cb,mode,casterColor,spellLvl,destination);
|
||||
|
||||
@ -217,12 +178,6 @@ CSpell::ETargetType CSpell::getTargetType() const
|
||||
return targetType;
|
||||
}
|
||||
|
||||
CSpell::TargetInfo CSpell::getTargetInfo(const int level) const
|
||||
{
|
||||
TargetInfo info(this, level);
|
||||
return info;
|
||||
}
|
||||
|
||||
void CSpell::forEachSchool(const std::function<void(const SpellSchoolInfo &, bool &)>& cb) const
|
||||
{
|
||||
bool stop = false;
|
||||
@ -351,7 +306,7 @@ void CSpell::getEffects(std::vector<Bonus> & lst, const int level) const
|
||||
}
|
||||
}
|
||||
|
||||
ESpellCastProblem::ESpellCastProblem CSpell::isImmuneAt(const CBattleInfoCallback * cb, const CGHeroInstance * caster, ECastingMode::ECastingMode mode, BattleHex destination) const
|
||||
ESpellCastProblem::ESpellCastProblem CSpell::isImmuneAt(const CBattleInfoCallback * cb, const ISpellCaster * caster, ECastingMode::ECastingMode mode, BattleHex destination) const
|
||||
{
|
||||
// Get all stacks at destination hex. only alive if not rising spell
|
||||
TStacks stacks = cb->battleGetStacksIf([=](const CStack * s){
|
||||
@ -402,7 +357,48 @@ ESpellCastProblem::ESpellCastProblem CSpell::isImmuneAt(const CBattleInfoCallbac
|
||||
return ESpellCastProblem::OK;
|
||||
}
|
||||
|
||||
ESpellCastProblem::ESpellCastProblem CSpell::isImmuneBy(const IBonusBearer* obj) const
|
||||
int CSpell::adjustRawDamage(const ISpellCaster * caster, const CStack * affectedCreature, int rawDamage) const
|
||||
{
|
||||
int ret = rawDamage;
|
||||
//affected creature-specific part
|
||||
if(nullptr != affectedCreature)
|
||||
{
|
||||
//applying protections - when spell has more then one elements, only one protection should be applied (I think)
|
||||
forEachSchool([&](const SpellSchoolInfo & cnf, bool & stop)
|
||||
{
|
||||
if(affectedCreature->hasBonusOfType(Bonus::SPELL_DAMAGE_REDUCTION, (ui8)cnf.id))
|
||||
{
|
||||
ret *= affectedCreature->valOfBonuses(Bonus::SPELL_DAMAGE_REDUCTION, (ui8)cnf.id);
|
||||
ret /= 100;
|
||||
stop = true;//only bonus from one school is used
|
||||
}
|
||||
});
|
||||
|
||||
//general spell dmg reduction
|
||||
if(affectedCreature->hasBonusOfType(Bonus::SPELL_DAMAGE_REDUCTION, -1))
|
||||
{
|
||||
ret *= affectedCreature->valOfBonuses(Bonus::SPELL_DAMAGE_REDUCTION, -1);
|
||||
ret /= 100;
|
||||
}
|
||||
|
||||
//dmg increasing
|
||||
if(affectedCreature->hasBonusOfType(Bonus::MORE_DAMAGE_FROM_SPELL, id))
|
||||
{
|
||||
ret *= 100 + affectedCreature->valOfBonuses(Bonus::MORE_DAMAGE_FROM_SPELL, id.toEnum());
|
||||
ret /= 100;
|
||||
}
|
||||
}
|
||||
if(caster != nullptr)
|
||||
ret = caster->getSpellBonus(this, ret, affectedCreature);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int CSpell::calculateRawEffectValue(int effectLevel, int effectPower) const
|
||||
{
|
||||
return effectPower * power + getPower(effectLevel);
|
||||
}
|
||||
|
||||
ESpellCastProblem::ESpellCastProblem CSpell::internalIsImmune(const ISpellCaster * caster, const CStack *obj) const
|
||||
{
|
||||
//todo: use new bonus API
|
||||
//1. Check absolute limiters
|
||||
@ -419,25 +415,46 @@ ESpellCastProblem::ESpellCastProblem CSpell::isImmuneBy(const IBonusBearer* obj)
|
||||
return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
|
||||
}
|
||||
|
||||
//spell-based spell immunity (only ANTIMAGIC in OH3) is treated as absolute
|
||||
std::stringstream cachingStr;
|
||||
cachingStr << "type_" << Bonus::LEVEL_SPELL_IMMUNITY << "source_" << Bonus::SPELL_EFFECT;
|
||||
|
||||
TBonusListPtr levelImmunitiesFromSpell = obj->getBonuses(Selector::type(Bonus::LEVEL_SPELL_IMMUNITY).And(Selector::sourceType(Bonus::SPELL_EFFECT)), cachingStr.str());
|
||||
|
||||
if(levelImmunitiesFromSpell->size() > 0 && levelImmunitiesFromSpell->totalValue() >= level && level)
|
||||
{
|
||||
return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
|
||||
//spell-based spell immunity (only ANTIMAGIC in OH3) is treated as absolute
|
||||
std::stringstream cachingStr;
|
||||
cachingStr << "type_" << Bonus::LEVEL_SPELL_IMMUNITY << "source_" << Bonus::SPELL_EFFECT;
|
||||
|
||||
TBonusListPtr levelImmunitiesFromSpell = obj->getBonuses(Selector::type(Bonus::LEVEL_SPELL_IMMUNITY).And(Selector::sourceType(Bonus::SPELL_EFFECT)), cachingStr.str());
|
||||
|
||||
if(levelImmunitiesFromSpell->size() > 0 && levelImmunitiesFromSpell->totalValue() >= level && level)
|
||||
{
|
||||
return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
|
||||
}
|
||||
}
|
||||
{
|
||||
//SPELL_IMMUNITY absolute case
|
||||
std::stringstream cachingStr;
|
||||
cachingStr << "type_" << Bonus::SPELL_IMMUNITY << "subtype_" << id.toEnum() << "addInfo_1";
|
||||
if(obj->hasBonus(Selector::typeSubtypeInfo(Bonus::SPELL_IMMUNITY, id.toEnum(), 1), cachingStr.str()))
|
||||
return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
|
||||
}
|
||||
|
||||
//check receptivity
|
||||
if (isPositive() && obj->hasBonusOfType(Bonus::RECEPTIVE)) //accept all positive spells
|
||||
return ESpellCastProblem::OK;
|
||||
|
||||
//3. Check negation
|
||||
//FIXME: Orb of vulnerability mechanics is not such trivial
|
||||
if(obj->hasBonusOfType(Bonus::NEGATE_ALL_NATURAL_IMMUNITIES)) //Orb of vulnerability
|
||||
//Orb of vulnerability
|
||||
//FIXME: Orb of vulnerability mechanics is not such trivial (issue 1791)
|
||||
const bool battleWideNegation = obj->hasBonusOfType(Bonus::NEGATE_ALL_NATURAL_IMMUNITIES, 0);
|
||||
const bool heroNegation = obj->hasBonusOfType(Bonus::NEGATE_ALL_NATURAL_IMMUNITIES, 1);
|
||||
//anyone can cast on artifact holder`s stacks
|
||||
if(heroNegation)
|
||||
return ESpellCastProblem::NOT_DECIDED;
|
||||
//this stack is from other player
|
||||
//todo: check that caster is always present (not trivial is this case)
|
||||
//todo: NEGATE_ALL_NATURAL_IMMUNITIES special cases: dispell, chain lightning
|
||||
else if(battleWideNegation && caster)
|
||||
{
|
||||
if(obj->owner != caster->getOwner())
|
||||
return ESpellCastProblem::NOT_DECIDED;
|
||||
}
|
||||
|
||||
//4. Check negatable limit
|
||||
for(auto b : limiters)
|
||||
@ -491,7 +508,7 @@ ESpellCastProblem::ESpellCastProblem CSpell::isImmuneBy(const IBonusBearer* obj)
|
||||
return ESpellCastProblem::NOT_DECIDED;
|
||||
}
|
||||
|
||||
ESpellCastProblem::ESpellCastProblem CSpell::isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const
|
||||
ESpellCastProblem::ESpellCastProblem CSpell::isImmuneByStack(const ISpellCaster * caster, const CStack * obj) const
|
||||
{
|
||||
const auto immuneResult = mechanics->isImmuneByStack(caster,obj);
|
||||
|
||||
|
@ -30,6 +30,9 @@ struct BattleSpellCast;
|
||||
class CGameInfoCallback;
|
||||
class CRandomGenerator;
|
||||
class CMap;
|
||||
struct AdventureSpellCastParameters;
|
||||
struct BattleSpellCastParameters;
|
||||
class SpellCastEnvironment;
|
||||
|
||||
struct SpellSchoolInfo
|
||||
{
|
||||
@ -41,45 +44,6 @@ struct SpellSchoolInfo
|
||||
Bonus::BonusType knoledgeBonus;
|
||||
};
|
||||
|
||||
///callback to be provided by server
|
||||
class DLL_LINKAGE SpellCastEnvironment
|
||||
{
|
||||
public:
|
||||
virtual ~SpellCastEnvironment(){};
|
||||
virtual void sendAndApply(CPackForClient * info) const = 0;
|
||||
|
||||
virtual CRandomGenerator & getRandomGenerator() const = 0;
|
||||
virtual void complain(const std::string & problem) const = 0;
|
||||
|
||||
virtual const CMap * getMap() const = 0;
|
||||
virtual const CGameInfoCallback * getCb() const = 0;
|
||||
|
||||
virtual bool moveHero(ObjectInstanceID hid, int3 dst, ui8 teleporting, PlayerColor asker = PlayerColor::NEUTRAL) const =0; //TODO: remove
|
||||
};
|
||||
|
||||
///helper struct
|
||||
struct DLL_LINKAGE BattleSpellCastParameters
|
||||
{
|
||||
public:
|
||||
BattleSpellCastParameters(const BattleInfo * cb);
|
||||
int spellLvl;
|
||||
BattleHex destination;
|
||||
ui8 casterSide;
|
||||
PlayerColor casterColor;
|
||||
const CGHeroInstance * casterHero; //deprecated
|
||||
const CGHeroInstance * secHero;
|
||||
int usedSpellPower;
|
||||
ECastingMode::ECastingMode mode;
|
||||
const CStack * casterStack;
|
||||
const CStack * selectedStack;
|
||||
const BattleInfo * cb;
|
||||
};
|
||||
|
||||
struct DLL_LINKAGE AdventureSpellCastParameters
|
||||
{
|
||||
const CGHeroInstance * caster;
|
||||
int3 pos;
|
||||
};
|
||||
|
||||
enum class VerticalPosition : ui8{TOP, CENTER, BOTTOM};
|
||||
|
||||
@ -136,10 +100,10 @@ public:
|
||||
///displayed on caster.
|
||||
TAnimationQueue cast;
|
||||
|
||||
///displayed on target hex. If spell was casted with no target selection displayed on entire battlefield (f.e. ARMAGEDDON)
|
||||
///displayed on target hex. If spell was cast with no target selection displayed on entire battlefield (f.e. ARMAGEDDON)
|
||||
TAnimationQueue hit;
|
||||
|
||||
///displayed "between" caster and (first) target. Ignored if spell was casted with no target selection.
|
||||
///displayed "between" caster and (first) target. Ignored if spell was cast with no target selection.
|
||||
///use selectProjectile to access
|
||||
std::vector<ProjectileInfo> projectile;
|
||||
|
||||
@ -188,7 +152,7 @@ public:
|
||||
enum ETargetType {NO_TARGET, CREATURE, OBSTACLE, LOCATION};
|
||||
enum ESpellPositiveness {NEGATIVE = -1, NEUTRAL = 0, POSITIVE = 1};
|
||||
|
||||
struct TargetInfo
|
||||
struct DLL_LINKAGE TargetInfo
|
||||
{
|
||||
ETargetType type;
|
||||
bool smart;
|
||||
@ -231,8 +195,6 @@ public:
|
||||
std::vector<BattleHex> rangeInHexes(BattleHex centralHex, ui8 schoolLvl, ui8 side, bool * outDroppedHexes = nullptr ) const; //convert range to specific hexes; last optional out parameter is set to true, if spell would cover unavailable hexes (that are not included in ret)
|
||||
ETargetType getTargetType() const; //deprecated
|
||||
|
||||
CSpell::TargetInfo getTargetInfo(const int level) const;
|
||||
|
||||
bool isCombatSpell() const;
|
||||
bool isAdventureSpell() const;
|
||||
bool isCreatureAbility() const;
|
||||
@ -251,17 +213,12 @@ public:
|
||||
bool hasEffects() const;
|
||||
void getEffects(std::vector<Bonus> &lst, const int level) const;
|
||||
|
||||
///checks for creature immunity / anything that prevent casting *at given hex* - doesn't take into account general problems such as not having spellbook or mana points etc.
|
||||
ESpellCastProblem::ESpellCastProblem isImmuneAt(const CBattleInfoCallback * cb, const CGHeroInstance * caster, ECastingMode::ECastingMode mode, BattleHex destination) const;
|
||||
|
||||
//internal, for use only by Mechanics classes
|
||||
ESpellCastProblem::ESpellCastProblem isImmuneBy(const IBonusBearer *obj) const;
|
||||
|
||||
///calculate spell damage on stack taking caster`s secondary skills and affectedCreature`s bonuses into account
|
||||
ui32 calculateDamage(const ISpellCaster * caster, const CStack * affectedCreature, int spellSchoolLevel, int usedSpellPower) 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;
|
||||
std::set<const CStack *> getAffectedStacks(const CBattleInfoCallback * cb, ECastingMode::ECastingMode mode, PlayerColor casterColor, int spellLvl, BattleHex destination, const ISpellCaster * caster) const;
|
||||
|
||||
si32 getCost(const int skillLevel) const;
|
||||
|
||||
@ -310,10 +267,13 @@ public:
|
||||
///internal interface (for callbacks)
|
||||
|
||||
///Checks general but spell-specific problems for all casting modes. Use only during battle.
|
||||
ESpellCastProblem::ESpellCastProblem canBeCasted(const CBattleInfoCallback * cb, PlayerColor player) const;
|
||||
ESpellCastProblem::ESpellCastProblem canBeCast(const CBattleInfoCallback * cb, PlayerColor player) const;
|
||||
|
||||
///checks for creature immunity / anything that prevent casting *at given hex* - doesn't take into account general problems such as not having spellbook or mana points etc.
|
||||
ESpellCastProblem::ESpellCastProblem isImmuneAt(const CBattleInfoCallback * cb, const ISpellCaster * caster, ECastingMode::ECastingMode mode, BattleHex destination) const;
|
||||
|
||||
///checks for creature immunity / anything that prevent casting *at given target* - doesn't take into account general problems such as not having spellbook or mana points etc.
|
||||
ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const;
|
||||
ESpellCastProblem::ESpellCastProblem isImmuneByStack(const ISpellCaster * caster, const CStack * obj) const;
|
||||
public:
|
||||
///Server logic. Has write access to GameState via packets.
|
||||
///May be executed on client side by (future) non-cheat-proof scripts.
|
||||
@ -328,11 +288,17 @@ public:
|
||||
///implementation of BattleSpellCast applying
|
||||
void applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const;
|
||||
|
||||
public:
|
||||
///Client logic.
|
||||
|
||||
public://Client logic.
|
||||
void prepareBattleLog(const CBattleInfoCallback * cb, const BattleSpellCast * packet, std::vector<std::string> & logLines) const;
|
||||
|
||||
public://internal, for use only by Mechanics classes
|
||||
///applies caster`s secondary skills and affectedCreature`s to raw damage
|
||||
int adjustRawDamage(const ISpellCaster * caster, const CStack * affectedCreature, int rawDamage) const;
|
||||
///returns raw damage or healed HP
|
||||
int calculateRawEffectValue(int effectLevel, int effectPower) const;
|
||||
///generic immunity calculation
|
||||
ESpellCastProblem::ESpellCastProblem internalIsImmune(const ISpellCaster * caster, const CStack *obj) const;
|
||||
|
||||
private:
|
||||
void setIsOffensive(const bool val);
|
||||
void setIsRising(const bool val);
|
||||
|
@ -16,17 +16,18 @@
|
||||
#include "../BattleState.h"
|
||||
|
||||
///AcidBreathDamageMechanics
|
||||
void AcidBreathDamageMechanics::applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
|
||||
void AcidBreathDamageMechanics::applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
|
||||
{
|
||||
//todo: this should be effectValue
|
||||
//calculating dmg to display
|
||||
ctx.sc.dmgToDisplay = parameters.usedSpellPower;
|
||||
ctx.sc.dmgToDisplay = parameters.effectPower;
|
||||
|
||||
for(auto & attackedCre : ctx.attackedCres) //no immunities
|
||||
{
|
||||
BattleStackAttacked bsa;
|
||||
bsa.flags |= BattleStackAttacked::SPELL_EFFECT;
|
||||
bsa.spellID = owner->id;
|
||||
bsa.damageAmount = parameters.usedSpellPower; //damage times the number of attackers
|
||||
bsa.damageAmount = parameters.effectPower; //damage times the number of attackers
|
||||
bsa.stackAttacked = (attackedCre)->ID;
|
||||
bsa.attackerID = -1;
|
||||
(attackedCre)->prepareAttacked(bsa, env->getRandomGenerator());
|
||||
@ -35,10 +36,10 @@ void AcidBreathDamageMechanics::applyBattleEffects(const SpellCastEnvironment *
|
||||
}
|
||||
|
||||
///DeathStareMechanics
|
||||
void DeathStareMechanics::applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
|
||||
void DeathStareMechanics::applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
|
||||
{
|
||||
//calculating dmg to display
|
||||
ctx.sc.dmgToDisplay = parameters.usedSpellPower;
|
||||
ctx.sc.dmgToDisplay = parameters.effectPower;
|
||||
if(!ctx.attackedCres.empty())
|
||||
vstd::amin(ctx.sc.dmgToDisplay, (*ctx.attackedCres.begin())->count); //stack is already reduced after attack
|
||||
|
||||
@ -47,7 +48,7 @@ void DeathStareMechanics::applyBattleEffects(const SpellCastEnvironment * env, B
|
||||
BattleStackAttacked bsa;
|
||||
bsa.flags |= BattleStackAttacked::SPELL_EFFECT;
|
||||
bsa.spellID = owner->id;
|
||||
bsa.damageAmount = parameters.usedSpellPower * (attackedCre)->valOfBonuses(Bonus::STACK_HEALTH);
|
||||
bsa.damageAmount = parameters.effectPower * (attackedCre)->valOfBonuses(Bonus::STACK_HEALTH);//todo: move here all DeathStare calculation
|
||||
bsa.stackAttacked = (attackedCre)->ID;
|
||||
bsa.attackerID = -1;
|
||||
(attackedCre)->prepareAttacked(bsa, env->getRandomGenerator());
|
||||
@ -63,7 +64,7 @@ void DispellHelpfulMechanics::applyBattle(BattleInfo * battle, const BattleSpell
|
||||
doDispell(battle, packet, Selector::positiveSpellEffects);
|
||||
}
|
||||
|
||||
ESpellCastProblem::ESpellCastProblem DispellHelpfulMechanics::isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const
|
||||
ESpellCastProblem::ESpellCastProblem DispellHelpfulMechanics::isImmuneByStack(const ISpellCaster * caster, const CStack * obj) const
|
||||
{
|
||||
TBonusListPtr spellBon = obj->getSpellBonuses();
|
||||
bool hasPositiveSpell = false;
|
||||
|
@ -18,7 +18,7 @@ class DLL_LINKAGE AcidBreathDamageMechanics : public DefaultSpellMechanics
|
||||
public:
|
||||
AcidBreathDamageMechanics(CSpell * s): DefaultSpellMechanics(s){};
|
||||
protected:
|
||||
void applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;
|
||||
void applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;
|
||||
};
|
||||
|
||||
class DLL_LINKAGE DeathStareMechanics : public DefaultSpellMechanics
|
||||
@ -26,7 +26,7 @@ class DLL_LINKAGE DeathStareMechanics : public DefaultSpellMechanics
|
||||
public:
|
||||
DeathStareMechanics(CSpell * s): DefaultSpellMechanics(s){};
|
||||
protected:
|
||||
void applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;
|
||||
void applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;
|
||||
};
|
||||
|
||||
class DLL_LINKAGE DispellHelpfulMechanics : public DefaultSpellMechanics
|
||||
@ -36,5 +36,5 @@ public:
|
||||
|
||||
void applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const override final;
|
||||
|
||||
ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const override;
|
||||
ESpellCastProblem::ESpellCastProblem isImmuneByStack(const ISpellCaster * caster, const CStack * obj) const override;
|
||||
};
|
||||
|
@ -11,12 +11,71 @@
|
||||
#include "StdInc.h"
|
||||
#include "ISpellMechanics.h"
|
||||
|
||||
#include "../BattleState.h"
|
||||
#include "../NetPacks.h"
|
||||
|
||||
#include "CDefaultSpellMechanics.h"
|
||||
|
||||
#include "AdventureSpellMechanics.h"
|
||||
#include "BattleSpellMechanics.h"
|
||||
#include "CreatureSpellMechanics.h"
|
||||
|
||||
BattleSpellCastParameters::Destination::Destination(const CStack * destination):
|
||||
stackValue(destination),
|
||||
hexValue(destination->position)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
BattleSpellCastParameters::Destination::Destination(const BattleHex & destination):
|
||||
stackValue(nullptr),
|
||||
hexValue(destination)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
BattleSpellCastParameters::BattleSpellCastParameters(const BattleInfo * cb, const ISpellCaster * caster, const CSpell * spell)
|
||||
: cb(cb), caster(caster), casterColor(caster->getOwner()), casterSide(cb->whatSide(casterColor)),
|
||||
casterHero(nullptr),
|
||||
mode(ECastingMode::HERO_CASTING), casterStack(nullptr), selectedStack(nullptr),
|
||||
spellLvl(-1), effectLevel(-1), effectPower(0), enchantPower(0), effectValue(0)
|
||||
{
|
||||
casterStack = dynamic_cast<const CStack *>(caster);
|
||||
casterHero = dynamic_cast<const CGHeroInstance *>(caster);
|
||||
prepare(spell);
|
||||
}
|
||||
|
||||
void BattleSpellCastParameters::aimToHex(const BattleHex& destination)
|
||||
{
|
||||
destinations.push_back(Destination(destination));
|
||||
}
|
||||
|
||||
void BattleSpellCastParameters::aimToStack(const CStack * destination)
|
||||
{
|
||||
destinations.push_back(Destination(destination));
|
||||
}
|
||||
|
||||
|
||||
BattleHex BattleSpellCastParameters::getFirstDestinationHex() const
|
||||
{
|
||||
return destinations.at(0).hexValue;
|
||||
}
|
||||
|
||||
void BattleSpellCastParameters::prepare(const CSpell * spell)
|
||||
{
|
||||
spellLvl = caster->getSpellSchoolLevel(spell);
|
||||
effectLevel = caster->getEffectLevel(spell);
|
||||
effectPower = caster->getEffectPower(spell);
|
||||
effectValue = caster->getEffectValue(spell);
|
||||
enchantPower = caster->getEnchantPower(spell);
|
||||
|
||||
vstd::amax(spellLvl, 0);
|
||||
vstd::amax(effectLevel, 0);
|
||||
vstd::amax(enchantPower, 0);
|
||||
vstd::amax(enchantPower, 0);
|
||||
vstd::amax(effectValue, 0);
|
||||
}
|
||||
|
||||
///ISpellMechanics
|
||||
ISpellMechanics::ISpellMechanics(CSpell * s):
|
||||
owner(s)
|
||||
|
@ -12,8 +12,75 @@
|
||||
|
||||
#include "CSpellHandler.h"
|
||||
#include "../BattleHex.h"
|
||||
#include "../BattleState.h"
|
||||
#include "../NetPacks.h"
|
||||
|
||||
|
||||
///callback to be provided by server
|
||||
class DLL_LINKAGE SpellCastEnvironment
|
||||
{
|
||||
public:
|
||||
virtual ~SpellCastEnvironment(){};
|
||||
virtual void sendAndApply(CPackForClient * info) const = 0;
|
||||
|
||||
virtual CRandomGenerator & getRandomGenerator() const = 0;
|
||||
virtual void complain(const std::string & problem) const = 0;
|
||||
|
||||
virtual const CMap * getMap() const = 0;
|
||||
virtual const CGameInfoCallback * getCb() const = 0;
|
||||
|
||||
virtual bool moveHero(ObjectInstanceID hid, int3 dst, ui8 teleporting, PlayerColor asker = PlayerColor::NEUTRAL) const =0; //TODO: remove
|
||||
};
|
||||
|
||||
///all parameters of particular cast event
|
||||
struct DLL_LINKAGE BattleSpellCastParameters
|
||||
{
|
||||
public:
|
||||
///Single spell destination.
|
||||
/// (assumes that anything but battle stack can share same hex)
|
||||
struct DLL_LINKAGE Destination
|
||||
{
|
||||
explicit Destination(const CStack * destination);
|
||||
explicit Destination(const BattleHex & destination);
|
||||
|
||||
const CStack * stackValue;
|
||||
const BattleHex hexValue;
|
||||
};
|
||||
|
||||
BattleSpellCastParameters(const BattleInfo * cb, const ISpellCaster * caster, const CSpell * spell);
|
||||
void aimToHex(const BattleHex & destination);
|
||||
void aimToStack(const CStack * destination);
|
||||
BattleHex getFirstDestinationHex() const;
|
||||
|
||||
const BattleInfo * cb;
|
||||
const ISpellCaster * caster;
|
||||
const PlayerColor casterColor;
|
||||
const ui8 casterSide;
|
||||
|
||||
std::vector<Destination> destinations;
|
||||
|
||||
const CGHeroInstance * casterHero; //deprecated
|
||||
ECastingMode::ECastingMode mode;
|
||||
const CStack * casterStack; //deprecated
|
||||
const CStack * selectedStack;//deprecated
|
||||
|
||||
///spell school level
|
||||
int spellLvl;
|
||||
///spell school level to use for effects
|
||||
int effectLevel;
|
||||
///actual spell-power affecting effect values
|
||||
int effectPower;
|
||||
///actual spell-power affecting effect duration
|
||||
int enchantPower;
|
||||
///for Archangel-like casting
|
||||
int effectValue;
|
||||
private:
|
||||
void prepare(const CSpell * spell);
|
||||
};
|
||||
|
||||
struct DLL_LINKAGE AdventureSpellCastParameters
|
||||
{
|
||||
const CGHeroInstance * caster;
|
||||
int3 pos;
|
||||
};
|
||||
|
||||
class DLL_LINKAGE ISpellMechanics
|
||||
{
|
||||
@ -39,9 +106,9 @@ public:
|
||||
virtual std::vector<BattleHex> rangeInHexes(BattleHex centralHex, ui8 schoolLvl, ui8 side, bool * outDroppedHexes = nullptr) const = 0;
|
||||
virtual std::set<const CStack *> getAffectedStacks(SpellTargetingContext & ctx) const = 0;
|
||||
|
||||
virtual ESpellCastProblem::ESpellCastProblem canBeCasted(const CBattleInfoCallback * cb, PlayerColor player) const = 0;
|
||||
virtual ESpellCastProblem::ESpellCastProblem canBeCast(const CBattleInfoCallback * cb, PlayerColor player) const = 0;
|
||||
|
||||
virtual ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const = 0;
|
||||
virtual ESpellCastProblem::ESpellCastProblem isImmuneByStack(const ISpellCaster * caster, const CStack * obj) const = 0;
|
||||
|
||||
virtual void applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const = 0;
|
||||
virtual bool adventureCast(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const = 0;
|
||||
|
@ -17,6 +17,7 @@
|
||||
|
||||
class CSpell;
|
||||
class CStack;
|
||||
class PlayerColor;
|
||||
|
||||
class DLL_LINKAGE ISpellCaster
|
||||
{
|
||||
@ -26,7 +27,22 @@ public:
|
||||
/// returns level on which given spell would be cast by this(0 - none, 1 - basic etc);
|
||||
/// caster may not know this spell at all
|
||||
/// optionally returns number of selected school by arg - 0 - air magic, 1 - fire magic, 2 - water magic, 3 - earth magic
|
||||
virtual ui8 getSpellSchoolLevel(const CSpell * spell, int *outSelectedSchool = nullptr) const = 0;
|
||||
|
||||
virtual ui8 getSpellSchoolLevel(const CSpell * spell, int *outSelectedSchool = nullptr) const = 0;
|
||||
|
||||
///applying sorcery secondary skill etc
|
||||
virtual ui32 getSpellBonus(const CSpell * spell, ui32 base, const CStack * affectedStack) const = 0;
|
||||
|
||||
///default spell school level for effect calculation
|
||||
virtual int getEffectLevel(const CSpell * spell) const = 0;
|
||||
|
||||
///default spell-power for damage/heal calculation
|
||||
virtual int getEffectPower(const CSpell * spell) const = 0;
|
||||
|
||||
///default spell-power for timed effects duration
|
||||
virtual int getEnchantPower(const CSpell * spell) const = 0;
|
||||
|
||||
///damage/heal override(ignores spell configuration, effect level and effect power)
|
||||
virtual int getEffectValue(const CSpell * spell) const = 0;
|
||||
|
||||
virtual const PlayerColor getOwner() const = 0;
|
||||
};
|
||||
|
@ -10,6 +10,7 @@
|
||||
#include "../lib/CBuildingHandler.h"
|
||||
#include "../lib/CHeroHandler.h"
|
||||
#include "../lib/spells/CSpellHandler.h"
|
||||
#include "../lib/spells/ISpellMechanics.h"
|
||||
#include "../lib/CGeneralTextHandler.h"
|
||||
#include "../lib/CTownHandler.h"
|
||||
#include "../lib/CCreatureHandler.h"
|
||||
@ -805,7 +806,7 @@ void CGameHandler::prepareAttack(BattleAttack &bat, const CStack *att, const CSt
|
||||
|
||||
//TODO: should spell override creature`s projectile?
|
||||
|
||||
std::set<const CStack*> attackedCreatures = SpellID(bonus->subtype).toSpell()->getAffectedStacks(gs->curB, ECastingMode::SPELL_LIKE_ATTACK, att->owner, bonus->val, targetHex);
|
||||
std::set<const CStack*> attackedCreatures = SpellID(bonus->subtype).toSpell()->getAffectedStacks(gs->curB, ECastingMode::SPELL_LIKE_ATTACK, att->owner, bonus->val, targetHex, att);
|
||||
|
||||
//TODO: get exact attacked hex for defender
|
||||
|
||||
@ -845,14 +846,13 @@ void CGameHandler::applyBattleEffects(BattleAttack &bat, const CStack *att, cons
|
||||
if (att->hasBonusOfType(Bonus::LIFE_DRAIN) && def->isLiving())
|
||||
{
|
||||
StacksHealedOrResurrected shi;
|
||||
shi.lifeDrain = (ui8)true;
|
||||
shi.tentHealing = (ui8)false;
|
||||
shi.lifeDrain = true;
|
||||
shi.tentHealing = false;
|
||||
shi.drainedFrom = def->ID;
|
||||
|
||||
StacksHealedOrResurrected::HealInfo hi;
|
||||
hi.stackID = att->ID;
|
||||
hi.healedHP = std::min<int> (bsa.damageAmount * att->valOfBonuses (Bonus::LIFE_DRAIN) / 100,
|
||||
att->MaxHealth() - att->firstHPleft + att->MaxHealth() * (att->baseAmount - att->count) );
|
||||
hi.healedHP = att->calculateHealedHealthPoints(bsa.damageAmount * att->valOfBonuses (Bonus::LIFE_DRAIN) / 100, true);
|
||||
hi.lowLevelResurrection = false;
|
||||
shi.healedStacks.push_back(hi);
|
||||
|
||||
@ -3771,14 +3771,17 @@ bool CGameHandler::makeBattleAction( BattleAction &ba )
|
||||
const CStack *healer = gs->curB->battleGetStackByID(ba.stackNumber),
|
||||
*destStack = gs->curB->battleGetStackByPos(ba.destinationTile);
|
||||
|
||||
ui32 healed = 0;
|
||||
|
||||
if(healer == nullptr || destStack == nullptr || !healer->hasBonusOfType(Bonus::HEALER))
|
||||
{
|
||||
complain("There is either no healer, no destination, or healer cannot heal :P");
|
||||
}
|
||||
int maxHealable = destStack->MaxHealth() - destStack->firstHPleft;
|
||||
int maxiumHeal = healer->count * std::max(10, attackingHero->valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, SecondarySkill::FIRST_AID));
|
||||
|
||||
int healed = std::min(maxHealable, maxiumHeal);
|
||||
else
|
||||
{
|
||||
ui32 maxiumHeal = healer->count * std::max(10, attackingHero->valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, SecondarySkill::FIRST_AID));
|
||||
healed = destStack->calculateHealedHealthPoints(maxiumHeal, false);
|
||||
}
|
||||
|
||||
if(healed == 0)
|
||||
{
|
||||
@ -3793,14 +3796,12 @@ bool CGameHandler::makeBattleAction( BattleAction &ba )
|
||||
|
||||
StacksHealedOrResurrected::HealInfo hi;
|
||||
hi.healedHP = healed;
|
||||
hi.lowLevelResurrection = 0;
|
||||
hi.lowLevelResurrection = false;
|
||||
hi.stackID = destStack->ID;
|
||||
|
||||
shr.healedStacks.push_back(hi);
|
||||
sendAndApply(&shr);
|
||||
}
|
||||
|
||||
|
||||
sendAndApply(&end_action);
|
||||
break;
|
||||
}
|
||||
@ -3867,27 +3868,19 @@ bool CGameHandler::makeBattleAction( BattleAction &ba )
|
||||
complain("That stack can't cast spells!");
|
||||
else
|
||||
{
|
||||
BattleSpellCastParameters p(gs->curB);
|
||||
|
||||
p.spellLvl = 0;
|
||||
const CSpell * spell = SpellID(spellID).toSpell();
|
||||
BattleSpellCastParameters parameters(gs->curB, stack, spell);
|
||||
parameters.spellLvl = 0;
|
||||
if (spellcaster)
|
||||
vstd::amax(p.spellLvl, spellcaster->val);
|
||||
vstd::amax(parameters.spellLvl, spellcaster->val);
|
||||
if (randSpellcaster)
|
||||
vstd::amax(p.spellLvl, randSpellcaster->val);
|
||||
vstd::amin (p.spellLvl, 3);
|
||||
|
||||
p.casterSide = gs->curB->whatSide(stack->owner);
|
||||
p.secHero = gs->curB->getHero(gs->curB->theOtherPlayer(stack->owner));
|
||||
p.mode = ECastingMode::CREATURE_ACTIVE_CASTING;
|
||||
p.destination = destination;
|
||||
p.casterColor = stack->owner;
|
||||
p.casterHero = nullptr;
|
||||
p.usedSpellPower = 0;
|
||||
p.casterStack = stack;
|
||||
p.selectedStack = nullptr;
|
||||
|
||||
const CSpell * spell = SpellID(spellID).toSpell();
|
||||
spell->battleCast(spellEnv, p);
|
||||
vstd::amax(parameters.spellLvl, randSpellcaster->val);
|
||||
vstd::amin (parameters.spellLvl, 3);
|
||||
parameters.effectLevel = parameters.spellLvl;
|
||||
parameters.mode = ECastingMode::CREATURE_ACTIVE_CASTING;
|
||||
parameters.aimToHex(destination);//todo: allow multiple destinations
|
||||
parameters.selectedStack = nullptr;
|
||||
spell->battleCast(spellEnv, parameters);
|
||||
}
|
||||
sendAndApply(&end_action);
|
||||
break;
|
||||
@ -4062,7 +4055,6 @@ bool CGameHandler::makeCustomAction( BattleAction &ba )
|
||||
|
||||
|
||||
const CGHeroInstance *h = gs->curB->battleGetFightingHero(ba.side);
|
||||
const CGHeroInstance *secondHero = gs->curB->battleGetFightingHero(!ba.side);
|
||||
if(!h)
|
||||
{
|
||||
logGlobal->warnStream() << "Wrong caster!";
|
||||
@ -4076,20 +4068,12 @@ bool CGameHandler::makeCustomAction( BattleAction &ba )
|
||||
|
||||
const CSpell * s = SpellID(ba.additionalInfo).toSpell();
|
||||
|
||||
BattleSpellCastParameters parameters(gs->curB);
|
||||
parameters.spellLvl = h->getSpellSchoolLevel(s);
|
||||
parameters.destination = ba.destinationTile;
|
||||
parameters.casterSide = ba.side;
|
||||
parameters.casterColor = h->tempOwner;
|
||||
parameters.casterHero = h;
|
||||
parameters.secHero = secondHero;
|
||||
|
||||
parameters.usedSpellPower = h->getPrimSkillLevel(PrimarySkill::SPELL_POWER);
|
||||
BattleSpellCastParameters parameters(gs->curB, h, s);
|
||||
parameters.aimToHex(ba.destinationTile);//todo: allow multiple destinations
|
||||
parameters.mode = ECastingMode::HERO_CASTING;
|
||||
parameters.casterStack = nullptr;
|
||||
parameters.selectedStack = gs->curB->battleGetStackByID(ba.selectedStack, false);
|
||||
|
||||
ESpellCastProblem::ESpellCastProblem escp = gs->curB->battleCanCastThisSpell(h->tempOwner, s, ECastingMode::HERO_CASTING);
|
||||
ESpellCastProblem::ESpellCastProblem escp = gs->curB->battleCanCastThisSpell(h, s, ECastingMode::HERO_CASTING);//todo: should we check aimed cast(battleCanCastThisSpellHere)?
|
||||
if(escp != ESpellCastProblem::OK)
|
||||
{
|
||||
logGlobal->warnStream() << "Spell cannot be cast!";
|
||||
@ -4186,17 +4170,17 @@ void CGameHandler::stackTurnTrigger(const CStack * st)
|
||||
}
|
||||
if (st->hasBonusOfType(Bonus::MANA_DRAIN) && !vstd::contains(st->state, EBattleStackState::DRAINED_MANA))
|
||||
{
|
||||
const CGHeroInstance * enemy = gs->curB->getHero(gs->curB->theOtherPlayer(st->owner));
|
||||
//const CGHeroInstance * owner = gs->curB->getHero(st->owner);
|
||||
if (enemy)
|
||||
const PlayerColor opponent = gs->curB->theOtherPlayer(st->owner);
|
||||
const CGHeroInstance * opponentHero = gs->curB->getHero(opponent);
|
||||
if (opponentHero)
|
||||
{
|
||||
ui32 manaDrained = st->valOfBonuses(Bonus::MANA_DRAIN);
|
||||
vstd::amin(manaDrained, gs->curB->battleGetFightingHero(0)->mana);
|
||||
vstd::amin(manaDrained, opponentHero->mana);
|
||||
if (manaDrained)
|
||||
{
|
||||
bte.effect = Bonus::MANA_DRAIN;
|
||||
bte.val = manaDrained;
|
||||
bte.additionalInfo = enemy->id.getNum(); //for sanity
|
||||
bte.additionalInfo = opponentHero->id.getNum(); //for sanity
|
||||
sendAndApply(&bte);
|
||||
}
|
||||
}
|
||||
@ -4225,30 +4209,26 @@ void CGameHandler::stackTurnTrigger(const CStack * st)
|
||||
int side = gs->curB->whatSide(st->owner);
|
||||
if (st->casts && !gs->curB->sides.at(side).enchanterCounter)
|
||||
{
|
||||
bool casted = false;
|
||||
while (!bl.empty() && !casted)
|
||||
bool cast = false;
|
||||
while (!bl.empty() && !cast)
|
||||
{
|
||||
auto bonus = *RandomGeneratorUtil::nextItem(bl, gs->getRandomGenerator());
|
||||
auto spellID = SpellID(bonus->subtype);
|
||||
const CSpell * spell = SpellID(spellID).toSpell();
|
||||
bl.remove_if([&bonus](Bonus * b){return b==bonus;});
|
||||
|
||||
if (gs->curB->battleCanCastThisSpell(st->owner, spell, ECastingMode::ENCHANTER_CASTING) == ESpellCastProblem::OK)
|
||||
if (gs->curB->battleCanCastThisSpell(st, spell, ECastingMode::ENCHANTER_CASTING) == ESpellCastProblem::OK)
|
||||
{
|
||||
BattleSpellCastParameters parameters(gs->curB);
|
||||
BattleSpellCastParameters parameters(gs->curB, st, spell);
|
||||
parameters.spellLvl = bonus->val;
|
||||
parameters.destination = BattleHex::INVALID;
|
||||
parameters.casterSide = side;
|
||||
parameters.casterColor = st->owner;
|
||||
parameters.casterHero = nullptr;
|
||||
parameters.secHero = gs->curB->getHero(gs->curB->theOtherPlayer(st->owner));
|
||||
parameters.usedSpellPower = 0;
|
||||
parameters.effectLevel = bonus->val;//todo: recheck
|
||||
parameters.aimToHex(BattleHex::INVALID);
|
||||
parameters.mode = ECastingMode::ENCHANTER_CASTING;
|
||||
parameters.casterStack = st;
|
||||
parameters.selectedStack = nullptr;
|
||||
|
||||
spell->battleCast(spellEnv, parameters);
|
||||
|
||||
//todo: move to mechanics
|
||||
BattleSetStackProperty ssp;
|
||||
ssp.which = BattleSetStackProperty::ENCHANTER_COUNTER;
|
||||
ssp.absolute = false;
|
||||
@ -4256,7 +4236,7 @@ void CGameHandler::stackTurnTrigger(const CStack * st)
|
||||
ssp.stackID = st->ID;
|
||||
sendAndApply(&ssp);
|
||||
|
||||
casted = true;
|
||||
cast = true;
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -4297,7 +4277,7 @@ void CGameHandler::handleDamageFromObstacle(const CObstacleInstance &obstacle, c
|
||||
//helper info
|
||||
const SpellCreatedObstacle *spellObstacle = dynamic_cast<const SpellCreatedObstacle*>(&obstacle); //not nice but we may need spell params
|
||||
const ui8 side = !curStack->attackerOwned; //if enemy is defending (false = 0), side of enemy hero is 1 (true)
|
||||
const CGHeroInstance *hero = gs->curB->battleGetFightingHero(side);
|
||||
const CGHeroInstance *hero = gs->curB->battleGetFightingHero(side);//FIXME: there may be no hero - landmines in Tower
|
||||
|
||||
if(obstacle.obstacleType == CObstacleInstance::MOAT)
|
||||
{
|
||||
@ -4319,7 +4299,7 @@ void CGameHandler::handleDamageFromObstacle(const CObstacleInstance &obstacle, c
|
||||
|
||||
damage = sp->calculateDamage(hero, curStack,
|
||||
spellObstacle->spellLevel, spellObstacle->casterSpellPower);
|
||||
//TODO even if obstacle wasn't created by hero (Tower "moat") it should deal dmg as if casted by hero,
|
||||
//TODO even if obstacle wasn't created by hero (Tower "moat") it should deal dmg as if cast by hero,
|
||||
//if it is bigger than default dmg. Or is it just irrelevant H3 implementation quirk
|
||||
}
|
||||
else if(obstacle.obstacleType == CObstacleInstance::FIRE_WALL)
|
||||
@ -4933,32 +4913,23 @@ void CGameHandler::attackCasting(const BattleAttack & bat, Bonus::BonusType atta
|
||||
}
|
||||
int chance = attacker->valOfBonuses((Selector::typeSubtype(attackMode, spellID)));
|
||||
vstd::amin (chance, 100);
|
||||
int destination = oneOfAttacked->position;
|
||||
|
||||
const CSpell * spell = SpellID(spellID).toSpell();
|
||||
if(gs->curB->battleCanCastThisSpellHere(attacker->owner, spell, ECastingMode::AFTER_ATTACK_CASTING, oneOfAttacked->position) != ESpellCastProblem::OK)
|
||||
if(gs->curB->battleCanCastThisSpellHere(attacker, spell, ECastingMode::AFTER_ATTACK_CASTING, oneOfAttacked->position) != ESpellCastProblem::OK)
|
||||
continue;
|
||||
|
||||
//check if spell should be casted (probability handling)
|
||||
//check if spell should be cast (probability handling)
|
||||
if(gs->getRandomGenerator().nextInt(99) >= chance)
|
||||
continue;
|
||||
|
||||
//casting
|
||||
if (castMe) //stacks use 0 spell power. If needed, default = 3 or custom value is used
|
||||
{
|
||||
const CSpell * spell = SpellID(spellID).toSpell();
|
||||
|
||||
BattleSpellCastParameters parameters(gs->curB);
|
||||
BattleSpellCastParameters parameters(gs->curB, attacker, spell);
|
||||
parameters.spellLvl = spellLevel;
|
||||
parameters.destination = destination;
|
||||
parameters.casterSide = !attacker->attackerOwned;
|
||||
parameters.casterColor = attacker->owner;
|
||||
parameters.casterHero = nullptr;
|
||||
parameters.secHero = nullptr;
|
||||
|
||||
parameters.usedSpellPower = 0;
|
||||
parameters.effectLevel = spellLevel;
|
||||
parameters.aimToStack(oneOfAttacked);
|
||||
parameters.mode = ECastingMode::AFTER_ATTACK_CASTING;
|
||||
parameters.casterStack = attacker;
|
||||
parameters.selectedStack = nullptr;
|
||||
|
||||
spell->battleCast(spellEnv, parameters);
|
||||
@ -4983,17 +4954,12 @@ void CGameHandler::handleAfterAttackCasting( const BattleAttack & bat )
|
||||
{
|
||||
const CSpell * spell = SpellID(spellID).toSpell();
|
||||
|
||||
BattleSpellCastParameters parameters(gs->curB);
|
||||
BattleSpellCastParameters parameters(gs->curB, attacker, spell);
|
||||
parameters.spellLvl = 0;
|
||||
parameters.destination = gs->curB->battleGetStackByID(bat.bsa.at(0).stackAttacked)->position;
|
||||
parameters.casterSide = !attacker->attackerOwned;
|
||||
parameters.casterColor = attacker->owner;
|
||||
parameters.casterHero = nullptr;
|
||||
parameters.secHero = nullptr;
|
||||
|
||||
parameters.usedSpellPower = power;
|
||||
parameters.effectLevel = 0;
|
||||
parameters.aimToStack(gs->curB->battleGetStackByID(bat.bsa.at(0).stackAttacked));
|
||||
parameters.effectPower = power;
|
||||
parameters.mode = ECastingMode::AFTER_ATTACK_CASTING;
|
||||
parameters.casterStack = attacker;
|
||||
parameters.selectedStack = nullptr;
|
||||
|
||||
spell->battleCast(this->spellEnv, parameters);
|
||||
@ -5293,25 +5259,16 @@ void CGameHandler::runBattle()
|
||||
{
|
||||
TBonusListPtr bl = h->getBonuses(Selector::type(Bonus::OPENING_BATTLE_SPELL));
|
||||
|
||||
BattleSpellCastParameters parameters(gs->curB);
|
||||
parameters.spellLvl = 3;
|
||||
parameters.destination = BattleHex::INVALID;
|
||||
parameters.casterSide = i;
|
||||
parameters.casterColor = h->tempOwner;
|
||||
parameters.casterHero = nullptr;
|
||||
parameters.secHero = gs->curB->battleGetFightingHero(1-i);
|
||||
|
||||
|
||||
parameters.mode = ECastingMode::HERO_CASTING;
|
||||
parameters.casterStack = nullptr;
|
||||
parameters.selectedStack = nullptr;
|
||||
|
||||
for (Bonus *b : *bl)
|
||||
{
|
||||
parameters.usedSpellPower = b->val;
|
||||
|
||||
const CSpell * spell = SpellID(b->subtype).toSpell();
|
||||
|
||||
BattleSpellCastParameters parameters(gs->curB, h, spell);
|
||||
parameters.spellLvl = 3;
|
||||
parameters.effectLevel = 3;
|
||||
parameters.aimToHex(BattleHex::INVALID);
|
||||
parameters.mode = ECastingMode::PASSIVE_CASTING;
|
||||
parameters.selectedStack = nullptr;
|
||||
parameters.enchantPower = b->val;
|
||||
spell->battleCast(spellEnv, parameters);
|
||||
}
|
||||
}
|
||||
@ -5484,14 +5441,30 @@ void CGameHandler::runBattle()
|
||||
else
|
||||
{
|
||||
logGlobal->traceStream() << "Activating " << next->nodeName();
|
||||
BattleSetActiveStack sas;
|
||||
sas.stack = next->ID;
|
||||
auto nextId = next->ID;
|
||||
BattleSetActiveStack sas;
|
||||
sas.stack = nextId;
|
||||
sendAndApply(&sas);
|
||||
|
||||
auto actionWasMade = [&]() -> bool
|
||||
{
|
||||
if(battleMadeAction.data)//active stack has made its action
|
||||
return true;
|
||||
if(battleResult.get())// battle is finished
|
||||
return true;
|
||||
if(next == nullptr)//active stack was been removed
|
||||
return true;
|
||||
return !next->alive();//active stack is dead
|
||||
};
|
||||
|
||||
boost::unique_lock<boost::mutex> lock(battleMadeAction.mx);
|
||||
battleMadeAction.data = false;
|
||||
while (next->alive() && //next is invalid after sacrificing current stack :?
|
||||
(!battleMadeAction.data && !battleResult.get())) //active stack hasn't made its action and battle is still going
|
||||
while(!actionWasMade())
|
||||
{
|
||||
battleMadeAction.cond.wait(lock);
|
||||
if(battleGetStackByID(nextId, false) != next)
|
||||
next = nullptr; //it may be removed, while we wait
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -5500,36 +5473,37 @@ void CGameHandler::runBattle()
|
||||
breakOuter = true;
|
||||
break;
|
||||
}
|
||||
|
||||
//we're after action, all results applied
|
||||
checkForBattleEnd(); //check if this action ended the battle
|
||||
|
||||
//check for good morale
|
||||
nextStackMorale = next->MoraleVal();
|
||||
if(!vstd::contains(next->state,EBattleStackState::HAD_MORALE) //only one extra move per turn possible
|
||||
&& !vstd::contains(next->state,EBattleStackState::DEFENDING)
|
||||
&& !next->waited()
|
||||
&& !vstd::contains(next->state, EBattleStackState::FEAR)
|
||||
&& next->alive()
|
||||
&& nextStackMorale > 0
|
||||
&& !(NBonus::hasOfType(gs->curB->battleGetFightingHero(0), Bonus::BLOCK_MORALE)
|
||||
|| NBonus::hasOfType(gs->curB->battleGetFightingHero(1), Bonus::BLOCK_MORALE)) //checking if gs->curB->heroes have (or don't have) morale blocking bonuses
|
||||
)
|
||||
if(next != nullptr)
|
||||
{
|
||||
if(gs->getRandomGenerator().nextInt(23) < nextStackMorale) //this stack hasn't got morale this turn
|
||||
//check for good morale
|
||||
nextStackMorale = next->MoraleVal();
|
||||
if(!vstd::contains(next->state,EBattleStackState::HAD_MORALE) //only one extra move per turn possible
|
||||
&& !vstd::contains(next->state,EBattleStackState::DEFENDING)
|
||||
&& !next->waited()
|
||||
&& !vstd::contains(next->state, EBattleStackState::FEAR)
|
||||
&& next->alive()
|
||||
&& nextStackMorale > 0
|
||||
&& !(NBonus::hasOfType(gs->curB->battleGetFightingHero(0), Bonus::BLOCK_MORALE)
|
||||
|| NBonus::hasOfType(gs->curB->battleGetFightingHero(1), Bonus::BLOCK_MORALE)) //checking if gs->curB->heroes have (or don't have) morale blocking bonuses
|
||||
)
|
||||
{
|
||||
if(gs->getRandomGenerator().nextInt(23) < nextStackMorale) //this stack hasn't got morale this turn
|
||||
|
||||
{
|
||||
BattleTriggerEffect bte;
|
||||
bte.stackID = next->ID;
|
||||
bte.effect = Bonus::MORALE;
|
||||
bte.val = 1;
|
||||
bte.additionalInfo = 0;
|
||||
sendAndApply(&bte); //play animation
|
||||
{
|
||||
BattleTriggerEffect bte;
|
||||
bte.stackID = next->ID;
|
||||
bte.effect = Bonus::MORALE;
|
||||
bte.val = 1;
|
||||
bte.additionalInfo = 0;
|
||||
sendAndApply(&bte); //play animation
|
||||
|
||||
++numberOfAsks; //move this stack once more
|
||||
}
|
||||
++numberOfAsks; //move this stack once more
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
--numberOfAsks;
|
||||
} while (numberOfAsks > 0);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user