2015-02-02 10:40:06 +02:00
/*
* AdventureSpellMechanics . 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 "AdventureSpellMechanics.h"
2015-02-02 11:22:19 +02:00
# include "../CRandomGenerator.h"
# include "../mapObjects/CGHeroInstance.h"
# include "../NetPacks.h"
# include "../CGameInfoCallback.h"
2015-12-02 21:05:10 +02:00
# include "../mapping/CMap.h"
2015-12-02 21:39:53 +02:00
# include "../CPlayerState.h"
2015-02-02 11:22:19 +02:00
2016-09-04 07:19:28 +02:00
///AdventureSpellMechanics
bool AdventureSpellMechanics : : adventureCast ( const SpellCastEnvironment * env , AdventureSpellCastParameters & parameters ) const
{
if ( ! owner - > isAdventureSpell ( ) )
{
env - > complain ( " Attempt to cast non adventure spell in adventure mode " ) ;
return false ;
}
const CGHeroInstance * caster = parameters . caster ;
if ( caster - > inTownGarrison )
{
env - > complain ( " Attempt to cast an adventure spell in town garrison " ) ;
return false ;
}
const int cost = caster - > getSpellCost ( owner ) ;
if ( ! caster - > canCastThisSpell ( owner ) )
{
env - > complain ( " Hero cannot cast this spell! " ) ;
return false ;
}
if ( caster - > mana < cost )
{
env - > complain ( " Hero doesn't have enough spell points to cast this spell! " ) ;
return false ;
}
{
AdvmapSpellCast asc ;
asc . caster = caster ;
asc . spellID = owner - > id ;
env - > sendAndApply ( & asc ) ;
}
switch ( applyAdventureEffects ( env , parameters ) )
{
case ESpellCastResult : : OK :
{
SetMana sm ;
sm . hid = caster - > id ;
sm . absolute = false ;
sm . val = - cost ;
env - > sendAndApply ( & sm ) ;
return true ;
}
break ;
case ESpellCastResult : : CANCEL :
return true ;
}
return false ;
}
ESpellCastResult AdventureSpellMechanics : : applyAdventureEffects ( const SpellCastEnvironment * env , AdventureSpellCastParameters & parameters ) const
{
if ( owner - > hasEffects ( ) )
{
const int schoolLevel = parameters . caster - > getSpellSchoolLevel ( owner ) ;
std : : vector < Bonus > bonuses ;
owner - > getEffects ( bonuses , schoolLevel ) ;
for ( Bonus b : bonuses )
{
GiveBonus gb ;
gb . id = parameters . caster - > id . getNum ( ) ;
gb . bonus = b ;
env - > sendAndApply ( & gb ) ;
}
return ESpellCastResult : : OK ;
}
else
{
//There is no generic algorithm of adventure cast
env - > complain ( " Unimplemented adventure spell " ) ;
return ESpellCastResult : : ERROR ;
}
}
2015-02-02 11:22:19 +02:00
///SummonBoatMechanics
2015-04-13 05:12:23 +02:00
ESpellCastResult SummonBoatMechanics : : applyAdventureEffects ( const SpellCastEnvironment * env , AdventureSpellCastParameters & parameters ) const
2015-02-02 11:22:19 +02:00
{
2015-02-26 19:59:18 +02:00
const int schoolLevel = parameters . caster - > getSpellSchoolLevel ( owner ) ;
2015-02-02 11:22:19 +02:00
//check if spell works at all
if ( env - > getRandomGenerator ( ) . nextInt ( 99 ) > = owner - > getPower ( schoolLevel ) ) //power is % chance of success
{
InfoWindow iw ;
iw . player = parameters . caster - > tempOwner ;
iw . text . addTxt ( MetaString : : GENERAL_TXT , 336 ) ; //%s tried to summon a boat, but failed.
iw . text . addReplacement ( parameters . caster - > name ) ;
env - > sendAndApply ( & iw ) ;
2015-04-13 05:12:23 +02:00
return ESpellCastResult : : OK ;
2015-02-02 11:22:19 +02:00
}
//try to find unoccupied boat to summon
const CGBoat * nearest = nullptr ;
double dist = 0 ;
int3 summonPos = parameters . caster - > bestLocation ( ) ;
if ( summonPos . x < 0 )
{
env - > complain ( " There is no water tile available! " ) ;
2015-04-13 05:12:23 +02:00
return ESpellCastResult : : ERROR ;
2015-02-26 19:59:18 +02:00
}
2015-02-02 11:22:19 +02:00
for ( const CGObjectInstance * obj : env - > getMap ( ) - > objects )
{
if ( obj & & obj - > ID = = Obj : : BOAT )
{
const CGBoat * b = static_cast < const CGBoat * > ( obj ) ;
2015-02-26 19:59:18 +02:00
if ( b - > hero )
2015-02-02 11:22:19 +02:00
continue ; //we're looking for unoccupied boat
double nDist = b - > pos . dist2d ( parameters . caster - > getPosition ( ) ) ;
if ( ! nearest | | nDist < dist ) //it's first boat or closer than previous
{
nearest = b ;
dist = nDist ;
}
2015-02-26 19:59:18 +02:00
}
2015-02-02 11:22:19 +02:00
}
if ( nullptr ! = nearest ) //we found boat to summon
{
ChangeObjPos cop ;
cop . objid = nearest - > id ;
cop . nPos = summonPos + int3 ( 1 , 0 , 0 ) ; ;
cop . flags = 1 ;
env - > sendAndApply ( & cop ) ;
}
else if ( schoolLevel < 2 ) //none or basic level -> cannot create boat :(
{
InfoWindow iw ;
iw . player = parameters . caster - > tempOwner ;
iw . text . addTxt ( MetaString : : GENERAL_TXT , 335 ) ; //There are no boats to summon.
env - > sendAndApply ( & iw ) ;
}
else //create boat
{
NewObject no ;
no . ID = Obj : : BOAT ;
no . subID = parameters . caster - > getBoatType ( ) ;
no . pos = summonPos + int3 ( 1 , 0 , 0 ) ; ;
env - > sendAndApply ( & no ) ;
2015-02-26 19:59:18 +02:00
}
2015-04-13 05:12:23 +02:00
return ESpellCastResult : : OK ;
2015-02-02 11:22:19 +02:00
}
///ScuttleBoatMechanics
2015-04-13 05:12:23 +02:00
ESpellCastResult ScuttleBoatMechanics : : applyAdventureEffects ( const SpellCastEnvironment * env , AdventureSpellCastParameters & parameters ) const
2015-02-02 11:22:19 +02:00
{
const int schoolLevel = parameters . caster - > getSpellSchoolLevel ( owner ) ;
//check if spell works at all
if ( env - > getRandomGenerator ( ) . nextInt ( 99 ) > = owner - > getPower ( schoolLevel ) ) //power is % chance of success
{
InfoWindow iw ;
iw . player = parameters . caster - > tempOwner ;
iw . text . addTxt ( MetaString : : GENERAL_TXT , 337 ) ; //%s tried to scuttle the boat, but failed
iw . text . addReplacement ( parameters . caster - > name ) ;
env - > sendAndApply ( & iw ) ;
2015-04-13 05:12:23 +02:00
return ESpellCastResult : : OK ;
2015-02-02 11:22:19 +02:00
}
2015-02-26 19:59:18 +02:00
2015-02-02 11:22:19 +02:00
if ( ! env - > getMap ( ) - > isInTheMap ( parameters . pos ) )
{
env - > complain ( " Invalid dst tile for scuttle! " ) ;
2015-04-13 05:12:23 +02:00
return ESpellCastResult : : ERROR ;
2015-02-02 11:22:19 +02:00
}
//TODO: test range, visibility
const TerrainTile * t = & env - > getMap ( ) - > getTile ( parameters . pos ) ;
if ( ! t - > visitableObjects . size ( ) | | t - > visitableObjects . back ( ) - > ID ! = Obj : : BOAT )
{
env - > complain ( " There is no boat to scuttle! " ) ;
2015-04-13 05:12:23 +02:00
return ESpellCastResult : : ERROR ;
2015-02-02 11:22:19 +02:00
}
RemoveObject ro ;
ro . id = t - > visitableObjects . back ( ) - > id ;
env - > sendAndApply ( & ro ) ;
2015-04-13 05:12:23 +02:00
return ESpellCastResult : : OK ;
2015-02-02 11:22:19 +02:00
}
///DimensionDoorMechanics
2015-04-13 05:12:23 +02:00
ESpellCastResult DimensionDoorMechanics : : applyAdventureEffects ( const SpellCastEnvironment * env , AdventureSpellCastParameters & parameters ) const
2015-02-02 11:22:19 +02:00
{
if ( ! env - > getMap ( ) - > isInTheMap ( parameters . pos ) )
{
env - > complain ( " Destination is out of map! " ) ;
2015-04-13 05:12:23 +02:00
return ESpellCastResult : : ERROR ;
2015-02-26 19:59:18 +02:00
}
2015-02-02 11:22:19 +02:00
const TerrainTile * dest = env - > getCb ( ) - > getTile ( parameters . pos ) ;
const TerrainTile * curr = env - > getCb ( ) - > getTile ( parameters . caster - > getSightCenter ( ) ) ;
if ( nullptr = = dest )
{
env - > complain ( " Destination tile doesn't exist! " ) ;
2015-04-13 05:12:23 +02:00
return ESpellCastResult : : ERROR ;
2015-02-02 11:22:19 +02:00
}
2015-02-26 19:59:18 +02:00
2015-02-02 11:22:19 +02:00
if ( nullptr = = curr )
{
env - > complain ( " Source tile doesn't exist! " ) ;
2015-04-13 05:12:23 +02:00
return ESpellCastResult : : ERROR ;
2015-02-26 19:59:18 +02:00
}
2015-12-21 17:44:21 +02:00
if ( parameters . caster - > movement < = 0 ) //unlike town portal non-zero MP is enough
2015-02-02 11:22:19 +02:00
{
env - > complain ( " Hero needs movement points to cast Dimension Door! " ) ;
2015-04-13 05:12:23 +02:00
return ESpellCastResult : : ERROR ;
2015-02-02 11:22:19 +02:00
}
2015-02-26 19:59:18 +02:00
2015-02-02 11:22:19 +02:00
const int schoolLevel = parameters . caster - > getSpellSchoolLevel ( owner ) ;
2015-12-21 17:44:21 +02:00
const int movementCost = GameConstants : : BASE_MOVEMENT_COST * ( ( schoolLevel > = 3 ) ? 2 : 3 ) ;
2015-02-26 19:59:18 +02:00
2016-10-01 05:51:12 +02:00
std : : stringstream cachingStr ;
cachingStr < < " source_ " < < Bonus : : SPELL_EFFECT < < " id_ " < < owner - > id . num ;
if ( parameters . caster - > getBonuses ( Selector : : source ( Bonus : : SPELL_EFFECT , owner - > id ) , Selector : : all , cachingStr . str ( ) ) - > size ( ) > = owner - > getPower ( schoolLevel ) ) //limit casts per turn
2015-02-02 11:22:19 +02:00
{
InfoWindow iw ;
iw . player = parameters . caster - > tempOwner ;
iw . text . addTxt ( MetaString : : GENERAL_TXT , 338 ) ; //%s is not skilled enough to cast this spell again today.
iw . text . addReplacement ( parameters . caster - > name ) ;
env - > sendAndApply ( & iw ) ;
2015-04-13 05:12:23 +02:00
return ESpellCastResult : : CANCEL ;
2015-02-02 11:22:19 +02:00
}
GiveBonus gb ;
gb . id = parameters . caster - > id . getNum ( ) ;
gb . bonus = Bonus ( Bonus : : ONE_DAY , Bonus : : NONE , Bonus : : SPELL_EFFECT , 0 , owner - > id ) ;
env - > sendAndApply ( & gb ) ;
if ( ! dest - > isClear ( curr ) ) //wrong dest tile
{
InfoWindow iw ;
iw . player = parameters . caster - > tempOwner ;
iw . text . addTxt ( MetaString : : GENERAL_TXT , 70 ) ; //Dimension Door failed!
2015-02-26 19:59:18 +02:00
env - > sendAndApply ( & iw ) ;
2015-02-02 11:22:19 +02:00
}
else if ( env - > moveHero ( parameters . caster - > id , parameters . pos + parameters . caster - > getVisitableOffset ( ) , true ) )
{
SetMovePoints smp ;
smp . hid = parameters . caster - > id ;
2015-12-21 17:44:21 +02:00
smp . val = std : : max < ui32 > ( 0 , parameters . caster - > movement - movementCost ) ;
2015-02-02 11:22:19 +02:00
env - > sendAndApply ( & smp ) ;
}
2015-04-13 05:12:23 +02:00
return ESpellCastResult : : OK ;
2015-02-02 11:22:19 +02:00
}
///TownPortalMechanics
2015-04-13 05:12:23 +02:00
ESpellCastResult TownPortalMechanics : : applyAdventureEffects ( const SpellCastEnvironment * env , AdventureSpellCastParameters & parameters ) const
2015-02-02 11:22:19 +02:00
{
if ( ! env - > getMap ( ) - > isInTheMap ( parameters . pos ) )
{
env - > complain ( " Destination tile not present! " ) ;
2015-04-13 05:12:23 +02:00
return ESpellCastResult : : ERROR ;
2015-02-26 19:59:18 +02:00
}
2015-02-02 11:22:19 +02:00
TerrainTile tile = env - > getMap ( ) - > getTile ( parameters . pos ) ;
if ( tile . visitableObjects . empty ( ) | | tile . visitableObjects . back ( ) - > ID ! = Obj : : TOWN )
{
2015-02-26 19:59:18 +02:00
env - > complain ( " Town not found for Town Portal! " ) ;
2015-04-13 05:12:23 +02:00
return ESpellCastResult : : ERROR ;
2015-02-26 19:59:18 +02:00
}
2015-02-02 11:22:19 +02:00
CGTownInstance * town = static_cast < CGTownInstance * > ( tile . visitableObjects . back ( ) ) ;
2016-02-15 12:34:37 +02:00
2015-04-01 03:04:02 +02:00
const auto relations = env - > getCb ( ) - > getPlayerRelations ( town - > tempOwner , parameters . caster - > tempOwner ) ;
2016-02-15 12:34:37 +02:00
2015-04-01 03:04:02 +02:00
if ( relations = = PlayerRelations : : ENEMIES )
2015-02-02 11:22:19 +02:00
{
2015-04-01 03:04:02 +02:00
env - > complain ( " Can't teleport to enemy! " ) ;
2015-04-13 05:12:23 +02:00
return ESpellCastResult : : ERROR ;
2015-02-26 19:59:18 +02:00
}
2015-02-02 11:22:19 +02:00
if ( town - > visitingHero )
{
env - > complain ( " Can't teleport to occupied town! " ) ;
2015-04-13 05:12:23 +02:00
return ESpellCastResult : : ERROR ;
2015-02-02 11:22:19 +02:00
}
2015-02-26 19:59:18 +02:00
2015-02-02 11:22:19 +02:00
if ( parameters . caster - > getSpellSchoolLevel ( owner ) < 2 )
{
si32 dist = town - > pos . dist2dSQ ( parameters . caster - > pos ) ;
ObjectInstanceID nearest = town - > id ; //nearest town's ID
for ( const CGTownInstance * currTown : env - > getCb ( ) - > getPlayer ( parameters . caster - > tempOwner ) - > towns )
{
si32 currDist = currTown - > pos . dist2dSQ ( parameters . caster - > pos ) ;
if ( currDist < dist )
{
nearest = currTown - > id ;
dist = currDist ;
}
}
if ( town - > id ! = nearest )
{
env - > complain ( " This hero can only teleport to nearest town! " ) ;
2015-04-13 05:12:23 +02:00
return ESpellCastResult : : ERROR ;
2015-02-02 11:22:19 +02:00
}
2015-02-26 19:59:18 +02:00
2015-02-02 11:22:19 +02:00
}
2016-02-15 12:34:37 +02:00
2015-12-21 17:44:21 +02:00
const int movementCost = GameConstants : : BASE_MOVEMENT_COST * ( ( parameters . caster - > getSpellSchoolLevel ( owner ) > = 3 ) ? 2 : 3 ) ;
2016-02-15 12:34:37 +02:00
2015-04-01 03:48:50 +02:00
if ( parameters . caster - > movement < movementCost )
{
env - > complain ( " This hero has not enough movement points! " ) ;
2016-02-15 12:34:37 +02:00
return ESpellCastResult : : ERROR ;
2015-04-01 03:48:50 +02:00
}
2016-02-15 12:34:37 +02:00
2015-04-01 03:48:50 +02:00
if ( env - > moveHero ( parameters . caster - > id , town - > visitablePos ( ) + parameters . caster - > getVisitableOffset ( ) , 1 ) )
{
SetMovePoints smp ;
smp . hid = parameters . caster - > id ;
smp . val = std : : max < ui32 > ( 0 , parameters . caster - > movement - movementCost ) ;
2016-02-15 12:34:37 +02:00
env - > sendAndApply ( & smp ) ;
2015-04-01 03:48:50 +02:00
}
2015-04-13 05:12:23 +02:00
return ESpellCastResult : : OK ;
2015-02-02 11:22:19 +02:00
}
2015-02-02 14:02:27 +02:00
2015-04-13 05:12:23 +02:00
ESpellCastResult ViewMechanics : : applyAdventureEffects ( const SpellCastEnvironment * env , AdventureSpellCastParameters & parameters ) const
2015-02-26 16:15:17 +02:00
{
ShowWorldViewEx pack ;
2015-02-26 19:59:18 +02:00
2016-09-28 06:58:15 +02:00
pack . player = parameters . caster - > getOwner ( ) ;
2015-02-26 19:59:18 +02:00
2015-02-26 16:15:17 +02:00
const int spellLevel = parameters . caster - > getSpellSchoolLevel ( owner ) ;
2015-02-26 19:59:18 +02:00
2016-09-28 06:58:15 +02:00
const auto & fowMap = env - > getCb ( ) - > getPlayerTeam ( parameters . caster - > getOwner ( ) ) - > fogOfWarMap ;
2015-02-26 16:15:17 +02:00
for ( const CGObjectInstance * obj : env - > getMap ( ) - > objects )
{
2016-09-28 06:58:15 +02:00
//deleted object remain as empty pointer
if ( obj & & filterObject ( obj , spellLevel ) )
{
ObjectPosInfo posInfo ( obj ) ;
2016-02-15 12:34:37 +02:00
2016-09-28 06:58:15 +02:00
if ( fowMap [ posInfo . pos . x ] [ posInfo . pos . y ] [ posInfo . pos . z ] = = 0 )
pack . objectPositions . push_back ( posInfo ) ;
}
2015-02-26 19:59:18 +02:00
}
2015-02-26 16:15:17 +02:00
env - > sendAndApply ( & pack ) ;
2015-02-26 19:59:18 +02:00
2015-04-13 05:12:23 +02:00
return ESpellCastResult : : OK ;
2015-02-26 16:15:17 +02:00
}
2015-02-02 14:02:27 +02:00
2015-02-26 16:15:17 +02:00
bool ViewAirMechanics : : filterObject ( const CGObjectInstance * obj , const int spellLevel ) const
2015-02-02 14:02:27 +02:00
{
2015-02-26 16:15:17 +02:00
return ( obj - > ID = = Obj : : ARTIFACT ) | | ( spellLevel > 1 & & obj - > ID = = Obj : : HERO ) | | ( spellLevel > 2 & & obj - > ID = = Obj : : TOWN ) ;
2015-02-02 14:02:27 +02:00
}
2015-02-26 16:15:17 +02:00
bool ViewEarthMechanics : : filterObject ( const CGObjectInstance * obj , const int spellLevel ) const
2015-02-02 14:02:27 +02:00
{
2015-02-26 16:15:17 +02:00
return ( obj - > ID = = Obj : : RESOURCE ) | | ( spellLevel > 1 & & obj - > ID = = Obj : : MINE ) ;
2015-02-02 14:02:27 +02:00
}
2015-02-26 16:15:17 +02:00