1
0
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:
AlexVinS 2015-10-13 08:12:22 +03:00
commit 61af4ee89a
32 changed files with 1023 additions and 652 deletions

View File

@ -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++)

View File

@ -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;
}

View File

@ -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&)

View File

@ -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);

View File

@ -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;

View File

@ -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",

View File

@ -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" :

View File

@ -564,7 +564,7 @@
"subtype" : "primSkill.defence",
"val" : -3,
"valueType" : "ADDITIVE_VALUE",
"duration" : "N_TURNS"
"duration" : "PERMANENT"
}
}
},

View File

@ -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)

View File

@ -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)
{

View File

@ -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

View File

@ -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.

View File

@ -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':

View File

@ -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
}

View File

@ -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,

View File

@ -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 */\

View File

@ -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

View File

@ -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);
}
}
}

View File

@ -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
{

View File

@ -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;

View File

@ -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);
}
}

View File

@ -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;
};

View File

@ -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;
}

View File

@ -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;
};

View File

@ -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);

View File

@ -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);

View File

@ -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;

View File

@ -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;
};

View File

@ -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)

View File

@ -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;

View File

@ -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;
};

View File

@ -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);