2014-11-12 10:36:34 +02:00
/*
* SpellMechanics . cpp , part of VCMI engine
*
* Authors : listed in file AUTHORS in main folder
*
* License : GNU General Public License v2 .0 or later
* Full text of license available in license . txt file , in main folder
*
*/
# include "StdInc.h"
# include "SpellMechanics.h"
2014-11-25 15:16:49 +02:00
# include "CObstacleInstance.h"
2014-11-12 10:36:34 +02:00
# include "mapObjects/CGHeroInstance.h"
# include "BattleState.h"
2014-11-25 13:19:13 +02:00
# include "CRandomGenerator.h"
2014-11-12 10:36:34 +02:00
2014-11-13 03:53:25 +02:00
# include "NetPacks.h"
2014-11-13 06:34:20 +02:00
namespace SRSLPraserHelpers
{
static int XYToHex ( int x , int y )
{
2014-11-13 07:13:06 +02:00
return x + GameConstants : : BFIELD_WIDTH * y ;
2014-11-13 06:34:20 +02:00
}
static int XYToHex ( std : : pair < int , int > xy )
{
return XYToHex ( xy . first , xy . second ) ;
}
static int hexToY ( int battleFieldPosition )
{
2014-11-13 07:13:06 +02:00
return battleFieldPosition / GameConstants : : BFIELD_WIDTH ;
2014-11-13 06:34:20 +02:00
}
static int hexToX ( int battleFieldPosition )
{
2014-11-13 07:13:06 +02:00
int pos = battleFieldPosition - hexToY ( battleFieldPosition ) * GameConstants : : BFIELD_WIDTH ;
2014-11-13 06:34:20 +02:00
return pos ;
}
static std : : pair < int , int > hexToPair ( int battleFieldPosition )
{
return std : : make_pair ( hexToX ( battleFieldPosition ) , hexToY ( battleFieldPosition ) ) ;
}
//moves hex by one hex in given direction
//0 - left top, 1 - right top, 2 - right, 3 - right bottom, 4 - left bottom, 5 - left
static std : : pair < int , int > gotoDir ( int x , int y , int direction )
{
switch ( direction )
{
case 0 : //top left
return std : : make_pair ( ( y % 2 ) ? x - 1 : x , y - 1 ) ;
case 1 : //top right
return std : : make_pair ( ( y % 2 ) ? x : x + 1 , y - 1 ) ;
case 2 : //right
return std : : make_pair ( x + 1 , y ) ;
case 3 : //right bottom
return std : : make_pair ( ( y % 2 ) ? x : x + 1 , y + 1 ) ;
case 4 : //left bottom
return std : : make_pair ( ( y % 2 ) ? x - 1 : x , y + 1 ) ;
case 5 : //left
return std : : make_pair ( x - 1 , y ) ;
default :
throw std : : runtime_error ( " Disaster: wrong direction in SRSLPraserHelpers::gotoDir! \n " ) ;
}
}
static std : : pair < int , int > gotoDir ( std : : pair < int , int > xy , int direction )
{
return gotoDir ( xy . first , xy . second , direction ) ;
}
static bool isGoodHex ( std : : pair < int , int > xy )
{
2014-11-13 07:13:06 +02:00
return xy . first > = 0 & & xy . first < GameConstants : : BFIELD_WIDTH & & xy . second > = 0 & & xy . second < GameConstants : : BFIELD_HEIGHT ;
2014-11-13 06:34:20 +02:00
}
2014-11-13 07:13:06 +02:00
//helper function for rangeInHexes
2014-11-13 06:34:20 +02:00
static std : : set < ui16 > getInRange ( unsigned int center , int low , int high )
{
std : : set < ui16 > ret ;
if ( low = = 0 )
{
ret . insert ( center ) ;
}
std : : pair < int , int > mainPointForLayer [ 6 ] ; //A, B, C, D, E, F points
for ( auto & elem : mainPointForLayer )
elem = hexToPair ( center ) ;
for ( int it = 1 ; it < = high ; + + it ) //it - distance to the center
{
for ( int b = 0 ; b < 6 ; + + b )
mainPointForLayer [ b ] = gotoDir ( mainPointForLayer [ b ] , b ) ;
if ( it > = low )
{
std : : pair < int , int > curHex ;
//adding lines (A-b, B-c, C-d, etc)
for ( int v = 0 ; v < 6 ; + + v )
{
curHex = mainPointForLayer [ v ] ;
for ( int h = 0 ; h < it ; + + h )
{
if ( isGoodHex ( curHex ) )
ret . insert ( XYToHex ( curHex ) ) ;
curHex = gotoDir ( curHex , ( v + 2 ) % 6 ) ;
}
}
} //if(it>=low)
}
return ret ;
}
}
2014-11-25 15:16:49 +02:00
struct SpellCastContext
{
SpellCastContext ( std : : vector < const CStack * > & attackedCres , BattleSpellCast & sc , StacksInjured & si ) :
attackedCres ( attackedCres ) , sc ( sc ) , si ( si ) { } ;
std : : vector < const CStack * > & attackedCres ;
BattleSpellCast & sc ;
StacksInjured & si ;
} ;
2014-11-13 06:34:20 +02:00
2014-11-25 14:00:34 +02:00
class DefaultSpellMechanics : public ISpellMechanics
{
public :
DefaultSpellMechanics ( CSpell * s ) : ISpellMechanics ( s ) { } ;
2014-11-25 17:27:18 +02:00
std : : vector < BattleHex > rangeInHexes ( BattleHex centralHex , ui8 schoolLvl , ui8 side , bool * outDroppedHexes = nullptr ) const override ;
2014-11-25 14:00:34 +02:00
std : : set < const CStack * > getAffectedStacks ( SpellTargetingContext & ctx ) const override ;
ESpellCastProblem : : ESpellCastProblem isImmuneByStack ( const CGHeroInstance * caster , const CStack * obj ) const override ;
//bool adventureCast(const SpellCastContext & context) const override;
2014-11-25 15:16:49 +02:00
void battleCast ( const SpellCastEnvironment * env , BattleSpellCastParameters & parameters ) const override ;
protected :
virtual void applyBattleEffects ( const SpellCastEnvironment * env , BattleSpellCastParameters & parameters , SpellCastContext & ctx ) const ;
virtual int calculateDuration ( const CGHeroInstance * caster , int usedSpellPower ) const ;
2014-11-25 16:35:20 +02:00
2014-11-25 14:00:34 +02:00
} ;
2014-11-25 16:35:20 +02:00
class AcidBreathDamageMechnics : public DefaultSpellMechanics
2014-11-25 14:00:34 +02:00
{
public :
2014-11-25 16:35:20 +02:00
AcidBreathDamageMechnics ( CSpell * s ) : DefaultSpellMechanics ( s ) { } ;
2014-11-25 15:16:49 +02:00
protected :
2014-11-25 16:35:20 +02:00
void applyBattleEffects ( const SpellCastEnvironment * env , BattleSpellCastParameters & parameters , SpellCastContext & ctx ) const override ;
2014-11-25 14:00:34 +02:00
} ;
class ChainLightningMechanics : public DefaultSpellMechanics
{
public :
ChainLightningMechanics ( CSpell * s ) : DefaultSpellMechanics ( s ) { } ;
std : : set < const CStack * > getAffectedStacks ( SpellTargetingContext & ctx ) const override ;
} ;
class CloneMechanics : public DefaultSpellMechanics
{
public :
CloneMechanics ( CSpell * s ) : DefaultSpellMechanics ( s ) { } ;
ESpellCastProblem : : ESpellCastProblem isImmuneByStack ( const CGHeroInstance * caster , const CStack * obj ) const override ;
2014-11-25 16:35:20 +02:00
protected :
void applyBattleEffects ( const SpellCastEnvironment * env , BattleSpellCastParameters & parameters , SpellCastContext & ctx ) const override ;
} ;
class DeathStareMechnics : public DefaultSpellMechanics
{
public :
DeathStareMechnics ( CSpell * s ) : DefaultSpellMechanics ( s ) { } ;
protected :
void applyBattleEffects ( const SpellCastEnvironment * env , BattleSpellCastParameters & parameters , SpellCastContext & ctx ) const override ;
2014-11-25 14:00:34 +02:00
} ;
class DispellHelpfulMechanics : public DefaultSpellMechanics
{
public :
DispellHelpfulMechanics ( CSpell * s ) : DefaultSpellMechanics ( s ) { } ;
ESpellCastProblem : : ESpellCastProblem isImmuneByStack ( const CGHeroInstance * caster , const CStack * obj ) const override ;
} ;
class HypnotizeMechanics : public DefaultSpellMechanics
{
public :
HypnotizeMechanics ( CSpell * s ) : DefaultSpellMechanics ( s ) { } ;
ESpellCastProblem : : ESpellCastProblem isImmuneByStack ( const CGHeroInstance * caster , const CStack * obj ) const override ;
} ;
2014-11-25 16:35:20 +02:00
class ObstacleMechanics : public DefaultSpellMechanics
{
public :
ObstacleMechanics ( CSpell * s ) : DefaultSpellMechanics ( s ) { } ;
protected :
void applyBattleEffects ( const SpellCastEnvironment * env , BattleSpellCastParameters & parameters , SpellCastContext & ctx ) const override ;
} ;
class WallMechanics : public ObstacleMechanics
{
public :
WallMechanics ( CSpell * s ) : ObstacleMechanics ( s ) { } ;
std : : vector < BattleHex > rangeInHexes ( BattleHex centralHex , ui8 schoolLvl , ui8 side , bool * outDroppedHexes = nullptr ) const override ;
} ;
class RemoveObstacleMechanics : public DefaultSpellMechanics
{
public :
RemoveObstacleMechanics ( CSpell * s ) : DefaultSpellMechanics ( s ) { } ;
protected :
void applyBattleEffects ( const SpellCastEnvironment * env , BattleSpellCastParameters & parameters , SpellCastContext & ctx ) const override ;
} ;
2014-11-25 14:00:34 +02:00
///all rising spells
class RisingSpellMechanics : public DefaultSpellMechanics
{
public :
RisingSpellMechanics ( CSpell * s ) : DefaultSpellMechanics ( s ) { } ;
} ;
2014-11-25 16:35:20 +02:00
class SacrificeMechanics : public RisingSpellMechanics
{
public :
2014-11-25 21:00:04 +02:00
SacrificeMechanics ( CSpell * s ) : RisingSpellMechanics ( s ) { } ;
protected :
void applyBattleEffects ( const SpellCastEnvironment * env , BattleSpellCastParameters & parameters , SpellCastContext & ctx ) const override ;
2014-11-25 16:35:20 +02:00
} ;
2014-11-25 14:00:34 +02:00
///all rising spells but SACRIFICE
class SpecialRisingSpellMechanics : public RisingSpellMechanics
{
public :
SpecialRisingSpellMechanics ( CSpell * s ) : RisingSpellMechanics ( s ) { } ;
ESpellCastProblem : : ESpellCastProblem isImmuneByStack ( const CGHeroInstance * caster , const CStack * obj ) const override ;
} ;
2014-11-25 16:35:20 +02:00
class SummonMechanics : public DefaultSpellMechanics
2014-11-25 14:00:34 +02:00
{
public :
2014-11-25 16:35:20 +02:00
SummonMechanics ( CSpell * s ) : DefaultSpellMechanics ( s ) { } ;
protected :
void applyBattleEffects ( const SpellCastEnvironment * env , BattleSpellCastParameters & parameters , SpellCastContext & ctx ) const override ;
} ;
class TeleportMechanics : public DefaultSpellMechanics
{
public :
TeleportMechanics ( CSpell * s ) : DefaultSpellMechanics ( s ) { } ;
protected :
void applyBattleEffects ( const SpellCastEnvironment * env , BattleSpellCastParameters & parameters , SpellCastContext & ctx ) const override ;
2014-11-25 14:00:34 +02:00
} ;
2014-11-13 06:34:20 +02:00
///ISpellMechanics
ISpellMechanics : : ISpellMechanics ( CSpell * s ) :
owner ( s )
{
}
2014-11-25 17:27:18 +02:00
ISpellMechanics * ISpellMechanics : : createMechanics ( CSpell * s )
2014-11-25 13:58:42 +02:00
{
switch ( s - > id )
{
case SpellID : : CLONE :
return new CloneMechanics ( s ) ;
case SpellID : : DISPEL_HELPFUL_SPELLS :
return new DispellHelpfulMechanics ( s ) ;
case SpellID : : SACRIFICE :
return new SacrificeMechanics ( s ) ;
case SpellID : : CHAIN_LIGHTNING :
return new ChainLightningMechanics ( s ) ;
case SpellID : : FIRE_WALL :
case SpellID : : FORCE_FIELD :
return new WallMechanics ( s ) ;
case SpellID : : LAND_MINE :
case SpellID : : QUICKSAND :
return new ObstacleMechanics ( s ) ;
2014-11-25 16:35:20 +02:00
case SpellID : : TELEPORT :
return new TeleportMechanics ( s ) ;
case SpellID : : SUMMON_FIRE_ELEMENTAL :
case SpellID : : SUMMON_EARTH_ELEMENTAL :
case SpellID : : SUMMON_WATER_ELEMENTAL :
case SpellID : : SUMMON_AIR_ELEMENTAL :
return new SummonMechanics ( s ) ;
case SpellID : : REMOVE_OBSTACLE :
return new RemoveObstacleMechanics ( s ) ;
case SpellID : : DEATH_STARE :
return new DeathStareMechnics ( s ) ;
case SpellID : : ACID_BREATH_DAMAGE :
return new AcidBreathDamageMechnics ( s ) ;
2014-11-25 13:58:42 +02:00
default :
if ( s - > isRisingSpell ( ) )
return new SpecialRisingSpellMechanics ( s ) ;
else
return new DefaultSpellMechanics ( s ) ;
}
}
2014-11-13 06:34:20 +02:00
2014-11-12 10:36:34 +02:00
///DefaultSpellMechanics
2014-11-13 03:53:25 +02:00
2014-11-25 13:19:13 +02:00
//bool DefaultSpellMechanics::adventureCast(const SpellCastContext& context) const
//{
// return false; //there is no general algorithm for casting adventure spells
//}
2014-11-13 03:53:25 +02:00
2014-11-25 15:16:49 +02:00
void DefaultSpellMechanics : : battleCast ( const SpellCastEnvironment * env , BattleSpellCastParameters & parameters ) const
2014-11-13 03:53:25 +02:00
{
2014-11-25 13:19:13 +02:00
BattleSpellCast sc ;
sc . side = parameters . casterSide ;
sc . id = owner - > id ;
sc . skill = parameters . spellLvl ;
sc . tile = parameters . destination ;
sc . dmgToDisplay = 0 ;
sc . castedByHero = nullptr ! = parameters . caster ;
sc . attackerType = ( parameters . casterStack ? parameters . casterStack - > type - > idNumber : CreatureID ( CreatureID : : NONE ) ) ;
sc . manaGained = 0 ;
sc . spellCost = 0 ;
//calculate spell cost
2014-11-25 17:27:18 +02:00
if ( parameters . caster )
2014-11-25 13:19:13 +02:00
{
sc . spellCost = parameters . cb - > battleGetSpellCost ( owner , parameters . caster ) ;
2014-11-25 17:27:18 +02:00
if ( parameters . secHero & & parameters . mode = = ECastingMode : : HERO_CASTING ) //handle mana channel
2014-11-25 13:19:13 +02:00
{
int manaChannel = 0 ;
for ( const CStack * stack : parameters . cb - > battleGetAllStacks ( true ) ) //TODO: shouldn't bonus system handle it somehow?
{
2014-11-25 17:27:18 +02:00
if ( stack - > owner = = parameters . secHero - > tempOwner )
2014-11-25 13:19:13 +02:00
{
vstd : : amax ( manaChannel , stack - > valOfBonuses ( Bonus : : MANA_CHANNELING ) ) ;
}
}
sc . manaGained = ( manaChannel * sc . spellCost ) / 100 ;
}
}
//calculating affected creatures for all spells
//must be vector, as in Chain Lightning order matters
std : : vector < const CStack * > attackedCres ; //CStack vector is somewhat more suitable than ID vector
auto creatures = owner - > getAffectedStacks ( parameters . cb , parameters . mode , parameters . casterColor , parameters . spellLvl , parameters . destination , parameters . caster ) ;
std : : copy ( creatures . begin ( ) , creatures . end ( ) , std : : back_inserter ( attackedCres ) ) ;
for ( auto cre : attackedCres )
{
2014-11-25 17:27:18 +02:00
sc . affectedCres . insert ( cre - > ID ) ;
2014-11-25 13:19:13 +02:00
}
//checking if creatures resist
//resistance is applied only to negative spells
if ( owner - > isNegative ( ) )
{
for ( auto s : attackedCres )
{
const int prob = std : : min ( ( s ) - > magicResistance ( ) , 100 ) ; //probability of resistance in %
if ( env - > getRandomGenerator ( ) . nextInt ( 99 ) < prob )
{
sc . resisted . push_back ( s - > ID ) ;
}
}
}
2014-11-25 16:35:20 +02:00
StacksInjured si ;
SpellCastContext ctx ( attackedCres , sc , si ) ;
2014-11-25 13:19:13 +02:00
2014-11-25 15:16:49 +02:00
applyBattleEffects ( env , parameters , ctx ) ;
2014-11-25 13:19:13 +02:00
env - > sendAndApply ( & sc ) ;
if ( ! si . stacks . empty ( ) ) //after spellcast info shows
env - > sendAndApply ( & si ) ;
//reduce number of casts remaining
//TODO: this should be part of BattleSpellCast apply
if ( parameters . mode = = ECastingMode : : CREATURE_ACTIVE_CASTING | | parameters . mode = = ECastingMode : : ENCHANTER_CASTING )
{
assert ( parameters . casterStack ) ;
BattleSetStackProperty ssp ;
ssp . stackID = parameters . casterStack - > ID ;
ssp . which = BattleSetStackProperty : : CASTS ;
ssp . val = - 1 ;
ssp . absolute = false ;
env - > sendAndApply ( & ssp ) ;
}
//Magic Mirror effect
2014-11-25 17:27:18 +02:00
if ( owner - > isNegative ( ) & & parameters . mode ! = ECastingMode : : MAGIC_MIRROR & & owner - > level & & owner - > getLevelInfo ( 0 ) . range = = " 0 " ) //it is actual spell and can be reflected to single target, no recurrence
2014-11-25 13:19:13 +02:00
{
for ( auto & attackedCre : attackedCres )
{
int mirrorChance = ( attackedCre ) - > valOfBonuses ( Bonus : : MAGIC_MIRROR ) ;
if ( mirrorChance > env - > getRandomGenerator ( ) . nextInt ( 99 ) )
{
std : : vector < const CStack * > mirrorTargets ;
auto battleStacks = parameters . cb - > battleGetAllStacks ( true ) ;
2014-11-25 17:27:18 +02:00
for ( auto & battleStack : battleStacks )
2014-11-25 13:19:13 +02:00
{
if ( battleStack - > owner = = parameters . casterColor ) //get enemy stacks which can be affected by this spell
{
if ( ESpellCastProblem : : OK = = owner - > isImmuneByStack ( nullptr , battleStack ) )
mirrorTargets . push_back ( battleStack ) ;
}
}
2014-11-25 17:27:18 +02:00
if ( ! mirrorTargets . empty ( ) )
2014-11-25 13:19:13 +02:00
{
int targetHex = ( * RandomGeneratorUtil : : nextItem ( mirrorTargets , env - > getRandomGenerator ( ) ) ) - > position ;
BattleSpellCastParameters mirrorParameters = parameters ;
mirrorParameters . spellLvl = 0 ;
mirrorParameters . casterSide = 1 - parameters . casterSide ;
mirrorParameters . casterColor = ( attackedCre ) - > owner ;
mirrorParameters . caster = nullptr ;
mirrorParameters . destination = targetHex ;
mirrorParameters . secHero = parameters . caster ;
mirrorParameters . mode = ECastingMode : : MAGIC_MIRROR ;
mirrorParameters . casterStack = ( attackedCre ) ;
mirrorParameters . selectedStack = nullptr ;
battleCast ( env , mirrorParameters ) ;
}
}
}
}
2014-11-25 15:16:49 +02:00
}
2014-11-25 17:27:18 +02:00
int DefaultSpellMechanics : : calculateDuration ( const CGHeroInstance * caster , int usedSpellPower ) const
2014-11-25 15:16:49 +02:00
{
if ( ! caster )
{
if ( ! usedSpellPower )
return 3 ; //default duration of all creature spells
else
return usedSpellPower ; //use creature spell power
}
switch ( owner - > id )
{
case SpellID : : FRENZY :
return 1 ;
default : //other spells
return caster - > getPrimSkillLevel ( PrimarySkill : : SPELL_POWER ) + caster - > valOfBonuses ( Bonus : : SPELL_DURATION ) ;
}
}
2014-11-25 18:43:34 +02:00
void DefaultSpellMechanics : : applyBattleEffects ( const SpellCastEnvironment * env , BattleSpellCastParameters & parameters , SpellCastContext & ctx ) const
2014-11-25 15:16:49 +02:00
{
//applying effects
2014-11-25 17:27:18 +02:00
if ( owner - > isOffensiveSpell ( ) )
2014-11-25 15:16:49 +02:00
{
int spellDamage = 0 ;
2014-11-25 17:27:18 +02:00
if ( parameters . casterStack & & parameters . mode ! = ECastingMode : : MAGIC_MIRROR )
2014-11-25 15:16:49 +02:00
{
int unitSpellPower = parameters . casterStack - > valOfBonuses ( Bonus : : SPECIFIC_SPELL_POWER , owner - > id . toEnum ( ) ) ;
2014-11-25 17:27:18 +02:00
if ( unitSpellPower )
2014-11-25 15:16:49 +02:00
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 chainLightningModifier = 0 ;
for ( auto & attackedCre : ctx . attackedCres )
{
if ( vstd : : contains ( ctx . sc . resisted , ( attackedCre ) - > ID ) ) //this creature resisted the spell
continue ;
BattleStackAttacked bsa ;
2014-11-25 17:27:18 +02:00
if ( ( parameters . destination > - 1 & & ( attackedCre ) - > coversPos ( parameters . destination ) ) | | ( owner - > getLevelInfo ( parameters . spellLvl ) . range = = " X " | | parameters . mode = = ECastingMode : : ENCHANTER_CASTING ) )
2014-11-25 15:16:49 +02:00
//display effect only upon primary target of area spell
//FIXME: if no stack is attacked, there is no animation and interface freezes
{
bsa . flags | = BattleStackAttacked : : EFFECT ;
bsa . effect = owner - > mainEffectAnim ;
}
2014-11-25 17:27:18 +02:00
if ( spellDamage )
2014-11-25 15:16:49 +02:00
bsa . damageAmount = spellDamage > > chainLightningModifier ;
else
bsa . damageAmount = owner - > calculateDamage ( parameters . caster , attackedCre , parameters . spellLvl , parameters . usedSpellPower ) > > chainLightningModifier ;
ctx . sc . dmgToDisplay + = bsa . damageAmount ;
bsa . stackAttacked = ( attackedCre ) - > ID ;
2014-11-25 17:27:18 +02:00
if ( parameters . mode = = ECastingMode : : ENCHANTER_CASTING ) //multiple damage spells cast
2014-11-25 15:16:49 +02:00
bsa . attackerID = parameters . casterStack - > ID ;
else
bsa . attackerID = - 1 ;
( attackedCre ) - > prepareAttacked ( bsa , env - > getRandomGenerator ( ) ) ;
ctx . si . stacks . push_back ( bsa ) ;
2014-11-25 17:27:18 +02:00
if ( owner - > id = = SpellID : : CHAIN_LIGHTNING )
2014-11-25 15:16:49 +02:00
+ + chainLightningModifier ;
}
}
2014-11-25 13:19:13 +02:00
2014-11-25 17:27:18 +02:00
if ( owner - > hasEffects ( ) )
2014-11-25 15:16:49 +02:00
{
int stackSpellPower = 0 ;
2014-11-25 17:27:18 +02:00
if ( parameters . casterStack & & parameters . mode ! = ECastingMode : : MAGIC_MIRROR )
2014-11-25 15:16:49 +02:00
{
stackSpellPower = parameters . casterStack - > valOfBonuses ( Bonus : : CREATURE_ENCHANT_POWER ) ;
}
SetStackEffect sse ;
Bonus pseudoBonus ;
pseudoBonus . sid = owner - > id ;
pseudoBonus . val = parameters . spellLvl ;
pseudoBonus . turnsRemain = calculateDuration ( parameters . caster , stackSpellPower ? stackSpellPower : parameters . usedSpellPower ) ;
CStack : : stackEffectToFeature ( sse . effect , pseudoBonus ) ;
2014-11-25 17:27:18 +02:00
if ( owner - > id = = SpellID : : SHIELD | | owner - > id = = SpellID : : AIR_SHIELD )
2014-11-25 15:16:49 +02:00
{
sse . effect . back ( ) . val = ( 100 - sse . effect . back ( ) . val ) ; //fix to original config: shield should display damage reduction
}
2014-11-25 17:27:18 +02:00
if ( owner - > id = = SpellID : : BIND & & parameters . casterStack ) //bind
2014-11-25 15:16:49 +02:00
{
sse . effect . back ( ) . additionalInfo = parameters . casterStack - > ID ; //we need to know who casted Bind
}
const Bonus * bonus = nullptr ;
2014-11-25 17:27:18 +02:00
if ( parameters . caster )
2014-11-25 15:16:49 +02:00
bonus = parameters . caster - > getBonusLocalFirst ( Selector : : typeSubtype ( Bonus : : SPECIAL_PECULIAR_ENCHANT , owner - > id ) ) ;
//TODO does hero specialty should affects his stack casting spells?
si32 power = 0 ;
for ( const CStack * affected : ctx . attackedCres )
{
if ( vstd : : contains ( ctx . sc . resisted , affected - > ID ) ) //this creature resisted the spell
continue ;
sse . stacks . push_back ( affected - > ID ) ;
//Apply hero specials - peculiar enchants
const ui8 tier = std : : max ( ( ui8 ) 1 , affected - > getCreature ( ) - > level ) ; //don't divide by 0 for certain creatures (commanders, war machines)
2014-11-25 17:27:18 +02:00
if ( bonus )
2014-11-25 15:16:49 +02:00
{
switch ( bonus - > additionalInfo )
{
case 0 : //normal
{
switch ( tier )
{
case 1 : case 2 :
power = 3 ;
break ;
case 3 : case 4 :
power = 2 ;
break ;
case 5 : case 6 :
power = 1 ;
break ;
}
Bonus specialBonus ( sse . effect . back ( ) ) ;
specialBonus . val = power ; //it doesn't necessarily make sense for some spells, use it wisely
sse . uniqueBonuses . push_back ( std : : pair < ui32 , Bonus > ( affected - > ID , specialBonus ) ) ; //additional premy to given effect
}
break ;
case 1 : //only Coronius as yet
{
power = std : : max ( 5 - tier , 0 ) ;
Bonus specialBonus = CStack : : featureGenerator ( Bonus : : PRIMARY_SKILL , PrimarySkill : : ATTACK , power , pseudoBonus . turnsRemain ) ;
specialBonus . sid = owner - > id ;
2014-11-25 17:27:18 +02:00
sse . uniqueBonuses . push_back ( std : : pair < ui32 , Bonus > ( affected - > ID , specialBonus ) ) ; //additional attack to Slayer effect
2014-11-25 15:16:49 +02:00
}
break ;
}
}
if ( parameters . caster & & parameters . caster - > hasBonusOfType ( Bonus : : SPECIAL_BLESS_DAMAGE , owner - > id ) ) //TODO: better handling of bonus percentages
{
int damagePercent = parameters . caster - > level * parameters . caster - > valOfBonuses ( Bonus : : SPECIAL_BLESS_DAMAGE , owner - > id . toEnum ( ) ) / tier ;
Bonus specialBonus = CStack : : featureGenerator ( Bonus : : CREATURE_DAMAGE , 0 , damagePercent , pseudoBonus . turnsRemain ) ;
specialBonus . valType = Bonus : : PERCENT_TO_ALL ;
specialBonus . sid = owner - > id ;
sse . uniqueBonuses . push_back ( std : : pair < ui32 , Bonus > ( affected - > ID , specialBonus ) ) ;
}
}
if ( ! sse . stacks . empty ( ) )
env - > sendAndApply ( & sse ) ;
}
if ( owner - > isHealingSpell ( ) )
{
int hpGained = 0 ;
2014-11-25 17:27:18 +02:00
if ( parameters . casterStack )
2014-11-25 15:16:49 +02:00
{
int unitSpellPower = parameters . casterStack - > valOfBonuses ( Bonus : : SPECIFIC_SPELL_POWER , owner - > id . toEnum ( ) ) ;
2014-11-25 17:27:18 +02:00
if ( unitSpellPower )
2014-11-25 15:16:49 +02:00
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
{
2014-11-25 18:43:34 +02:00
const bool resurrect = owner - > isRisingSpell ( ) ;
2014-11-25 15:16:49 +02:00
if ( hpGained )
{
2014-11-25 18:43:34 +02:00
//archangel
hi . healedHP = std : : min < ui32 > ( hpGained , attackedCre - > MaxHealth ( ) - attackedCre - > firstHPleft + ( resurrect ? attackedCre - > baseAmount * attackedCre - > MaxHealth ( ) : 0 ) ) ;
2014-11-25 15:16:49 +02:00
}
else
2014-11-25 18:43:34 +02:00
{
//any typical spell (commander's cure or animate dead)
int healedHealth = parameters . usedSpellPower * owner - > power + owner - > getPower ( parameters . spellLvl ) ;
hi . healedHP = std : : min < ui32 > ( healedHealth , attackedCre - > MaxHealth ( ) - attackedCre - > firstHPleft + ( resurrect ? attackedCre - > baseAmount * attackedCre - > MaxHealth ( ) : 0 ) ) ;
}
2014-11-25 15:16:49 +02:00
}
else
hi . healedHP = owner - > calculateHealedHP ( parameters . caster , attackedCre , parameters . selectedStack ) ; //Casted by hero
hi . lowLevelResurrection = parameters . spellLvl < = 1 ;
shr . healedStacks . push_back ( hi ) ;
}
if ( ! shr . healedStacks . empty ( ) )
env - > sendAndApply ( & shr ) ;
}
2014-11-13 03:53:25 +02:00
}
2014-11-25 15:16:49 +02:00
2014-11-13 06:34:20 +02:00
std : : vector < BattleHex > DefaultSpellMechanics : : rangeInHexes ( BattleHex centralHex , ui8 schoolLvl , ui8 side , bool * outDroppedHexes ) const
{
using namespace SRSLPraserHelpers ;
std : : vector < BattleHex > ret ;
std : : string rng = owner - > getLevelInfo ( schoolLvl ) . range + ' , ' ; //copy + artificial comma for easier handling
2014-11-13 07:29:04 +02:00
if ( rng . size ( ) > = 2 & & rng [ 0 ] ! = ' X ' ) //there is at lest one hex in range (+artificial comma)
2014-11-13 06:34:20 +02:00
{
std : : string number1 , number2 ;
int beg , end ;
bool readingFirst = true ;
for ( auto & elem : rng )
{
if ( std : : isdigit ( elem ) ) //reading number
{
if ( readingFirst )
number1 + = elem ;
else
number2 + = elem ;
}
else if ( elem = = ' , ' ) //comma
{
//calculating variables
if ( readingFirst )
{
beg = atoi ( number1 . c_str ( ) ) ;
number1 = " " ;
}
else
{
end = atoi ( number2 . c_str ( ) ) ;
number2 = " " ;
}
//obtaining new hexes
std : : set < ui16 > curLayer ;
if ( readingFirst )
{
curLayer = getInRange ( centralHex , beg , beg ) ;
}
else
{
curLayer = getInRange ( centralHex , beg , end ) ;
readingFirst = true ;
}
//adding abtained hexes
for ( auto & curLayer_it : curLayer )
{
ret . push_back ( curLayer_it ) ;
}
}
else if ( elem = = ' - ' ) //dash
{
beg = atoi ( number1 . c_str ( ) ) ;
number1 = " " ;
readingFirst = false ;
}
}
}
//remove duplicates (TODO check if actually needed)
range : : unique ( ret ) ;
return ret ;
}
2014-11-13 04:40:52 +02:00
std : : set < const CStack * > DefaultSpellMechanics : : getAffectedStacks ( SpellTargetingContext & ctx ) const
2014-11-13 03:53:25 +02:00
{
2014-11-13 11:22:20 +02:00
std : : set < const CStack * > attackedCres ; //std::set to exclude multiple occurrences of two hex creatures
2014-11-13 03:53:25 +02:00
2014-11-13 11:22:20 +02:00
const ui8 attackerSide = ctx . cb - > playerToSide ( ctx . casterColor ) = = 1 ;
const auto attackedHexes = rangeInHexes ( ctx . destination , ctx . schoolLvl , attackerSide ) ;
const CSpell : : TargetInfo ti ( owner , ctx . schoolLvl , ctx . mode ) ;
//TODO: more generic solution for mass spells
2014-11-25 17:27:18 +02:00
if ( owner - > getLevelInfo ( ctx . schoolLvl ) . range . size ( ) > 1 ) //custom many-hex range
2014-11-13 11:22:20 +02:00
{
for ( BattleHex hex : attackedHexes )
{
if ( const CStack * st = ctx . cb - > battleGetStackByPos ( hex , ti . onlyAlive ) )
{
attackedCres . insert ( st ) ;
}
}
}
else if ( ti . type = = CSpell : : CREATURE )
{
auto predicate = [ = ] ( const CStack * s ) {
const bool positiveToAlly = owner - > isPositive ( ) & & s - > owner = = ctx . casterColor ;
const bool negativeToEnemy = owner - > isNegative ( ) & & s - > owner ! = ctx . casterColor ;
const bool validTarget = s - > isValidTarget ( ! ti . onlyAlive ) ; //todo: this should be handled by spell class
//for single target spells select stacks covering destination tile
const bool rangeCovers = ti . massive | | s - > coversPos ( ctx . destination ) ;
//handle smart targeting
const bool positivenessFlag = ! ti . smart | | owner - > isNeutral ( ) | | positiveToAlly | | negativeToEnemy ;
return rangeCovers & & positivenessFlag & & validTarget ;
} ;
TStacks stacks = ctx . cb - > battleGetStacksIf ( predicate ) ;
2014-11-25 17:27:18 +02:00
if ( ti . massive )
2014-11-13 11:22:20 +02:00
{
//for massive spells add all targets
for ( auto stack : stacks )
attackedCres . insert ( stack ) ;
}
else
{
//for single target spells we must select one target. Alive stack is preferred (issue #1763)
for ( auto stack : stacks )
{
if ( stack - > alive ( ) )
{
attackedCres . insert ( stack ) ;
break ;
}
}
if ( attackedCres . empty ( ) & & ! stacks . empty ( ) )
{
attackedCres . insert ( stacks . front ( ) ) ;
}
}
}
else //custom range from attackedHexes
{
for ( BattleHex hex : attackedHexes )
{
if ( const CStack * st = ctx . cb - > battleGetStackByPos ( hex , ti . onlyAlive ) )
attackedCres . insert ( st ) ;
}
}
return attackedCres ;
2014-11-13 03:53:25 +02:00
}
2014-11-13 04:40:52 +02:00
ESpellCastProblem : : ESpellCastProblem DefaultSpellMechanics : : isImmuneByStack ( const CGHeroInstance * caster , const CStack * obj ) const
{
//by default use general algorithm
return owner - > isImmuneBy ( obj ) ;
}
2014-11-25 16:35:20 +02:00
///AcidBreathDamageMechnics
void AcidBreathDamageMechnics : : applyBattleEffects ( const SpellCastEnvironment * env , BattleSpellCastParameters & parameters , SpellCastContext & ctx ) const
{
//calculating dmg to display
ctx . sc . dmgToDisplay = parameters . usedSpellPower ;
for ( auto & attackedCre : ctx . attackedCres ) //no immunities
{
BattleStackAttacked bsa ;
bsa . flags | = BattleStackAttacked : : EFFECT ;
bsa . effect = owner - > mainEffectAnim ;
bsa . damageAmount = parameters . usedSpellPower ; //damage times the number of attackers
bsa . stackAttacked = ( attackedCre ) - > ID ;
bsa . attackerID = - 1 ;
( attackedCre ) - > prepareAttacked ( bsa , env - > getRandomGenerator ( ) ) ;
ctx . si . stacks . push_back ( bsa ) ;
}
}
///ChainLightningMechanics
std : : set < const CStack * > ChainLightningMechanics : : getAffectedStacks ( SpellTargetingContext & ctx ) const
{
std : : set < const CStack * > attackedCres ;
std : : set < BattleHex > possibleHexes ;
for ( auto stack : ctx . cb - > battleGetAllStacks ( ) )
{
if ( stack - > isValidTarget ( ) )
{
for ( auto hex : stack - > getHexes ( ) )
{
possibleHexes . insert ( hex ) ;
}
}
}
int targetsOnLevel [ 4 ] = { 4 , 4 , 5 , 5 } ;
BattleHex lightningHex = ctx . destination ;
for ( int i = 0 ; i < targetsOnLevel [ ctx . schoolLvl ] ; + + i )
{
auto stack = ctx . cb - > battleGetStackByPos ( lightningHex , true ) ;
if ( ! stack )
break ;
attackedCres . insert ( stack ) ;
for ( auto hex : stack - > getHexes ( ) )
{
possibleHexes . erase ( hex ) ; //can't hit same place twice
}
if ( possibleHexes . empty ( ) ) //not enough targets
break ;
lightningHex = BattleHex : : getClosestTile ( stack - > attackerOwned , ctx . destination , possibleHexes ) ;
}
return attackedCres ;
}
///CloneMechanics
void CloneMechanics : : applyBattleEffects ( const SpellCastEnvironment * env , BattleSpellCastParameters & parameters , SpellCastContext & ctx ) const
{
const CStack * clonedStack = nullptr ;
if ( ctx . attackedCres . size ( ) )
clonedStack = * ctx . attackedCres . begin ( ) ;
if ( ! clonedStack )
{
env - > complain ( " No target stack to clone! " ) ;
return ;
}
const int attacker = ! ( bool ) parameters . casterSide ;
BattleStackAdded bsa ;
bsa . creID = clonedStack - > type - > idNumber ;
bsa . attacker = attacker ;
bsa . summoned = true ;
bsa . pos = parameters . cb - > getAvaliableHex ( bsa . creID , attacker ) ; //TODO: unify it
bsa . amount = clonedStack - > count ;
env - > sendAndApply ( & bsa ) ;
BattleSetStackProperty ssp ;
ssp . stackID = bsa . newStackID ; //we know stack ID after apply
ssp . which = BattleSetStackProperty : : CLONED ;
ssp . val = 0 ;
ssp . absolute = 1 ;
env - > sendAndApply ( & ssp ) ;
}
2014-11-25 17:27:18 +02:00
ESpellCastProblem : : ESpellCastProblem CloneMechanics : : isImmuneByStack ( const CGHeroInstance * caster , const CStack * obj ) const
2014-11-25 16:35:20 +02:00
{
//can't clone already cloned creature
2014-11-25 17:27:18 +02:00
if ( vstd : : contains ( obj - > state , EBattleStackState : : CLONED ) )
2014-11-25 16:35:20 +02:00
return ESpellCastProblem : : STACK_IMMUNE_TO_SPELL ;
//TODO: how about stacks casting Clone?
//currently Clone casted by stack is assumed Expert level
ui8 schoolLevel ;
2014-11-25 17:27:18 +02:00
if ( caster )
2014-11-25 16:35:20 +02:00
{
schoolLevel = caster - > getSpellSchoolLevel ( owner ) ;
}
else
{
schoolLevel = 3 ;
}
2014-11-25 17:27:18 +02:00
if ( schoolLevel < 3 )
2014-11-25 16:35:20 +02:00
{
int maxLevel = ( std : : max ( schoolLevel , ( ui8 ) 1 ) + 4 ) ;
int creLevel = obj - > getCreature ( ) - > level ;
2014-11-25 17:27:18 +02:00
if ( maxLevel < creLevel ) //tier 1-5 for basic, 1-6 for advanced, any level for expert
2014-11-25 16:35:20 +02:00
return ESpellCastProblem : : STACK_IMMUNE_TO_SPELL ;
}
//use default algorithm only if there is no mechanics-related problem
2014-11-25 17:27:18 +02:00
return DefaultSpellMechanics : : isImmuneByStack ( caster , obj ) ;
2014-11-25 16:35:20 +02:00
}
///DeathStareMechnics
void DeathStareMechnics : : applyBattleEffects ( const SpellCastEnvironment * env , BattleSpellCastParameters & parameters , SpellCastContext & ctx ) const
{
//calculating dmg to display
ctx . sc . dmgToDisplay = parameters . usedSpellPower ;
vstd : : amin ( ctx . sc . dmgToDisplay , ( * ctx . attackedCres . begin ( ) ) - > count ) ; //stack is already reduced after attack
for ( auto & attackedCre : ctx . attackedCres )
{
BattleStackAttacked bsa ;
bsa . flags | = BattleStackAttacked : : EFFECT ;
bsa . effect = owner - > mainEffectAnim ; //from config\spell-Info.txt
bsa . damageAmount = parameters . usedSpellPower * ( attackedCre ) - > valOfBonuses ( Bonus : : STACK_HEALTH ) ;
bsa . stackAttacked = ( attackedCre ) - > ID ;
bsa . attackerID = - 1 ;
( attackedCre ) - > prepareAttacked ( bsa , env - > getRandomGenerator ( ) ) ;
ctx . si . stacks . push_back ( bsa ) ;
}
}
///DispellHelpfulMechanics
ESpellCastProblem : : ESpellCastProblem DispellHelpfulMechanics : : isImmuneByStack ( const CGHeroInstance * caster , const CStack * obj ) const
{
TBonusListPtr spellBon = obj - > getSpellBonuses ( ) ;
bool hasPositiveSpell = false ;
for ( const Bonus * b : * spellBon )
{
if ( SpellID ( b - > sid ) . toSpell ( ) - > isPositive ( ) )
{
hasPositiveSpell = true ;
break ;
}
}
if ( ! hasPositiveSpell )
{
return ESpellCastProblem : : NO_SPELLS_TO_DISPEL ;
}
//use default algorithm only if there is no mechanics-related problem
return DefaultSpellMechanics : : isImmuneByStack ( caster , obj ) ;
}
///HypnotizeMechanics
ESpellCastProblem : : ESpellCastProblem HypnotizeMechanics : : isImmuneByStack ( const CGHeroInstance * caster , const CStack * obj ) const
{
if ( nullptr ! = caster ) //do not resist hypnotize casted after attack, for example
{
//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 = owner - > calculateBonus ( caster - > getPrimSkillLevel ( PrimarySkill : : SPELL_POWER )
* owner - > power + owner - > getPower ( caster - > getSpellSchoolLevel ( owner ) ) , caster , obj ) ;
if ( subjectHealth > maxHealth )
return ESpellCastProblem : : STACK_IMMUNE_TO_SPELL ;
}
return DefaultSpellMechanics : : isImmuneByStack ( caster , obj ) ;
}
2014-11-25 15:16:49 +02:00
///ObstacleMechanics
2014-11-25 17:27:18 +02:00
void ObstacleMechanics : : applyBattleEffects ( const SpellCastEnvironment * env , BattleSpellCastParameters & parameters , SpellCastContext & ctx ) const
2014-11-25 15:16:49 +02:00
{
auto placeObstacle = [ & , this ] ( BattleHex pos )
{
static int obstacleIdToGive = parameters . cb - > obstacles . size ( )
? ( parameters . cb - > obstacles . back ( ) - > uniqueID + 1 )
: 0 ;
auto obstacle = make_shared < SpellCreatedObstacle > ( ) ;
switch ( owner - > id ) // :/
{
case SpellID : : QUICKSAND :
obstacle - > obstacleType = CObstacleInstance : : QUICKSAND ;
obstacle - > turnsRemaining = - 1 ;
obstacle - > visibleForAnotherSide = false ;
break ;
case SpellID : : LAND_MINE :
obstacle - > obstacleType = CObstacleInstance : : LAND_MINE ;
obstacle - > turnsRemaining = - 1 ;
obstacle - > visibleForAnotherSide = false ;
break ;
case SpellID : : FIRE_WALL :
obstacle - > obstacleType = CObstacleInstance : : FIRE_WALL ;
obstacle - > turnsRemaining = 2 ;
obstacle - > visibleForAnotherSide = true ;
break ;
case SpellID : : FORCE_FIELD :
obstacle - > obstacleType = CObstacleInstance : : FORCE_FIELD ;
obstacle - > turnsRemaining = 2 ;
obstacle - > visibleForAnotherSide = true ;
break ;
default :
//this function cannot be used with spells that do not create obstacles
assert ( 0 ) ;
}
obstacle - > pos = pos ;
obstacle - > casterSide = parameters . casterSide ;
obstacle - > ID = owner - > id ;
obstacle - > spellLevel = parameters . spellLvl ;
obstacle - > casterSpellPower = parameters . usedSpellPower ;
obstacle - > uniqueID = obstacleIdToGive + + ;
BattleObstaclePlaced bop ;
bop . obstacle = obstacle ;
env - > sendAndApply ( & bop ) ;
} ;
2014-11-25 17:27:18 +02:00
switch ( owner - > id )
2014-11-25 15:16:49 +02:00
{
case SpellID : : QUICKSAND :
case SpellID : : LAND_MINE :
{
std : : vector < BattleHex > availableTiles ;
for ( int i = 0 ; i < GameConstants : : BFIELD_SIZE ; i + = 1 )
{
BattleHex hex = i ;
if ( hex . getX ( ) > 2 & & hex . getX ( ) < 14 & & ! ( parameters . cb - > battleGetStackByPos ( hex , false ) ) & & ! ( parameters . cb - > battleGetObstacleOnPos ( hex , false ) ) )
availableTiles . push_back ( hex ) ;
}
boost : : range : : random_shuffle ( availableTiles ) ;
const int patchesForSkill [ ] = { 4 , 4 , 6 , 8 } ;
const int patchesToPut = std : : min < int > ( patchesForSkill [ parameters . spellLvl ] , availableTiles . size ( ) ) ;
//land mines or quicksand patches are handled as spell created obstacles
for ( int i = 0 ; i < patchesToPut ; i + + )
placeObstacle ( availableTiles . at ( i ) ) ;
}
break ;
case SpellID : : FORCE_FIELD :
placeObstacle ( parameters . destination ) ;
break ;
case SpellID : : FIRE_WALL :
{
//fire wall is build from multiple obstacles - one fire piece for each affected hex
auto affectedHexes = owner - > rangeInHexes ( parameters . destination , parameters . spellLvl , parameters . casterSide ) ;
for ( BattleHex hex : affectedHexes )
placeObstacle ( hex ) ;
}
break ;
default :
assert ( 0 ) ;
}
}
2014-11-13 11:22:20 +02:00
///WallMechanics
2014-11-25 17:27:18 +02:00
std : : vector < BattleHex > WallMechanics : : rangeInHexes ( BattleHex centralHex , ui8 schoolLvl , ui8 side , bool * outDroppedHexes ) const
2014-11-13 06:34:20 +02:00
{
using namespace SRSLPraserHelpers ;
std : : vector < BattleHex > ret ;
//Special case - shape of obstacle depends on caster's side
//TODO make it possible through spell_info config
BattleHex : : EDir firstStep , secondStep ;
if ( side )
{
firstStep = BattleHex : : TOP_LEFT ;
secondStep = BattleHex : : TOP_RIGHT ;
}
else
{
firstStep = BattleHex : : TOP_RIGHT ;
secondStep = BattleHex : : TOP_LEFT ;
}
//Adds hex to the ret if it's valid. Otherwise sets output arg flag if given.
auto addIfValid = [ & ] ( BattleHex hex )
{
if ( hex . isValid ( ) )
ret . push_back ( hex ) ;
else if ( outDroppedHexes )
* outDroppedHexes = true ;
} ;
ret . push_back ( centralHex ) ;
addIfValid ( centralHex . moveInDir ( firstStep , false ) ) ;
if ( schoolLvl > = 2 ) //advanced versions of fire wall / force field cotnains of 3 hexes
addIfValid ( centralHex . moveInDir ( secondStep , false ) ) ; //moveInDir function modifies subject hex
return ret ;
}
2014-11-25 16:35:20 +02:00
///RemoveObstacleMechanics
void RemoveObstacleMechanics : : applyBattleEffects ( const SpellCastEnvironment * env , BattleSpellCastParameters & parameters , SpellCastContext & ctx ) const
2014-11-13 11:22:20 +02:00
{
2014-11-25 16:35:20 +02:00
if ( auto obstacleToRemove = parameters . cb - > battleGetObstacleOnPos ( parameters . destination , false ) )
2014-11-13 11:22:20 +02:00
{
2014-11-25 16:35:20 +02:00
ObstaclesRemoved obr ;
obr . obstacles . insert ( obstacleToRemove - > uniqueID ) ;
env - > sendAndApply ( & obr ) ;
2014-11-12 10:36:34 +02:00
}
else
2014-11-25 16:35:20 +02:00
env - > complain ( " There's no obstacle to remove! " ) ;
2014-11-12 10:36:34 +02:00
}
2014-11-25 21:00:04 +02:00
///SpecialRisingSpellMechanics
void SacrificeMechanics : : applyBattleEffects ( const SpellCastEnvironment * env , 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?
{
//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 ) ;
}
BattleStacksRemoved bsr ;
bsr . stackIDs . insert ( parameters . selectedStack - > ID ) ; //somehow it works for teleport?
env - > sendAndApply ( & bsr ) ;
}
2014-11-12 10:36:34 +02:00
///SpecialRisingSpellMechanics
2014-11-25 16:35:20 +02:00
ESpellCastProblem : : ESpellCastProblem SpecialRisingSpellMechanics : : isImmuneByStack ( const CGHeroInstance * caster , const CStack * obj ) const
2014-11-12 10:36:34 +02:00
{
// following does apply to resurrect and animate dead(?) only
// for sacrifice health calculation and health limit check don't matter
if ( obj - > count > = obj - > baseAmount )
return ESpellCastProblem : : STACK_IMMUNE_TO_SPELL ;
2014-11-25 16:35:20 +02:00
if ( caster ) //FIXME: Archangels can cast immune stack
2014-11-12 10:36:34 +02:00
{
2014-11-25 16:35:20 +02:00
auto maxHealth = owner - > calculateHealedHP ( caster , obj ) ;
2014-11-12 10:36:34 +02:00
if ( maxHealth < obj - > MaxHealth ( ) ) //must be able to rise at least one full creature
return ESpellCastProblem : : STACK_IMMUNE_TO_SPELL ;
}
2014-11-13 03:53:25 +02:00
return DefaultSpellMechanics : : isImmuneByStack ( caster , obj ) ;
2014-11-12 10:36:34 +02:00
}
2014-11-25 16:35:20 +02:00
///SummonMechanics
void SummonMechanics : : applyBattleEffects ( const SpellCastEnvironment * env , BattleSpellCastParameters & parameters , SpellCastContext & ctx ) const
{
//todo: make configurable
CreatureID creID = CreatureID : : NONE ;
switch ( owner - > id )
{
case SpellID : : SUMMON_FIRE_ELEMENTAL :
creID = CreatureID : : FIRE_ELEMENTAL ;
break ;
case SpellID : : SUMMON_EARTH_ELEMENTAL :
creID = CreatureID : : EARTH_ELEMENTAL ;
break ;
case SpellID : : SUMMON_WATER_ELEMENTAL :
creID = CreatureID : : WATER_ELEMENTAL ;
break ;
case SpellID : : SUMMON_AIR_ELEMENTAL :
creID = CreatureID : : AIR_ELEMENTAL ;
break ;
default :
env - > complain ( " Unable to determine summoned creature " ) ;
return ;
}
BattleStackAdded bsa ;
bsa . creID = creID ;
bsa . attacker = ! ( bool ) parameters . casterSide ;
bsa . summoned = true ;
bsa . pos = parameters . cb - > getAvaliableHex ( creID , ! ( bool ) parameters . casterSide ) ; //TODO: unify it
//TODO stack casting -> probably power will be zero; set the proper number of creatures manually
int percentBonus = parameters . caster ? parameters . caster - > valOfBonuses ( Bonus : : SPECIFIC_SPELL_DAMAGE , owner - > id . toEnum ( ) ) : 0 ;
bsa . amount = parameters . usedSpellPower
* owner - > getPower ( parameters . spellLvl )
* ( 100 + percentBonus ) / 100.0 ; //new feature - percentage bonus
if ( bsa . amount )
env - > sendAndApply ( & bsa ) ;
else
env - > complain ( " Summoning didn't summon any! " ) ;
}
///TeleportMechanics
void TeleportMechanics : : applyBattleEffects ( const SpellCastEnvironment * env , 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 ) ;
}
2014-11-12 10:36:34 +02:00