2015-02-02 11:40:06 +03: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 12:22:19 +03:00
2017-07-20 07:08:49 +03:00
#include "CSpellHandler.h"
2015-02-02 12:22:19 +03: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 12:22:19 +03:00
2016-09-04 08:19:28 +03:00
///AdventureSpellMechanics
2017-06-06 07:53:51 +03:00
AdventureSpellMechanics :: AdventureSpellMechanics ( const CSpell * s ) :
IAdventureSpellMechanics ( s )
{
}
2018-03-17 17:58:30 +03:00
bool AdventureSpellMechanics :: adventureCast ( SpellCastEnvironment * env , const AdventureSpellCastParameters & parameters ) const
2016-09-04 08:19:28 +03:00
{
2018-03-17 17:58:30 +03:00
if ( ! owner -> isAdventure ())
2016-09-04 08:19:28 +03:00
{
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 ;
}
2018-03-17 17:58:30 +03:00
const auto level = caster -> getSpellSchoolLevel ( owner );
const auto cost = owner -> getCost ( level );
2016-09-04 08:19:28 +03:00
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 ;
}
2017-07-03 21:09:27 +03:00
ESpellCastResult result = beginCast ( env , parameters );
2016-09-04 08:19:28 +03:00
2017-07-03 21:09:27 +03:00
if ( result == ESpellCastResult :: OK )
performCast ( env , parameters );
return result != ESpellCastResult :: ERROR ;
2016-09-04 08:19:28 +03:00
}
2018-03-17 17:58:30 +03:00
ESpellCastResult AdventureSpellMechanics :: applyAdventureEffects ( SpellCastEnvironment * env , const AdventureSpellCastParameters & parameters ) const
2016-09-04 08:19:28 +03:00
{
if ( owner -> hasEffects ())
{
2016-11-02 20:11:01 +03:00
//todo: cumulative effects support
2018-03-17 17:58:30 +03:00
const auto schoolLevel = parameters . caster -> getSpellSchoolLevel ( owner );
2016-09-04 08:19:28 +03:00
std :: vector < Bonus > bonuses ;
2016-11-02 20:11:01 +03:00
owner -> getEffects ( bonuses , schoolLevel , false , parameters . caster -> getEnchantPower ( owner ));
2016-09-04 08:19:28 +03:00
for ( Bonus b : bonuses )
{
GiveBonus gb ;
gb . id = parameters . caster -> id . getNum ();
gb . bonus = b ;
2018-03-17 17:58:30 +03:00
env -> apply ( & gb );
2016-09-04 08:19:28 +03:00
}
return ESpellCastResult :: OK ;
}
else
{
//There is no generic algorithm of adventure cast
env -> complain ( "Unimplemented adventure spell" );
return ESpellCastResult :: ERROR ;
}
}
2018-03-17 17:58:30 +03:00
ESpellCastResult AdventureSpellMechanics :: beginCast ( SpellCastEnvironment * env , const AdventureSpellCastParameters & parameters ) const
2017-07-03 21:09:27 +03:00
{
return ESpellCastResult :: OK ;
}
2018-03-17 17:58:30 +03:00
void AdventureSpellMechanics :: performCast ( SpellCastEnvironment * env , const AdventureSpellCastParameters & parameters ) const
2017-07-03 21:09:27 +03:00
{
AdvmapSpellCast asc ;
2018-03-10 22:19:55 +03:00
asc . casterID = parameters . caster -> id ;
2017-07-03 21:09:27 +03:00
asc . spellID = owner -> id ;
2018-03-17 17:58:30 +03:00
env -> apply ( & asc );
2017-07-03 21:09:27 +03:00
ESpellCastResult result = applyAdventureEffects ( env , parameters );
endCast ( env , parameters , result );
}
2018-03-17 17:58:30 +03:00
void AdventureSpellMechanics :: endCast ( SpellCastEnvironment * env , const AdventureSpellCastParameters & parameters , const ESpellCastResult result ) const
2017-07-03 21:09:27 +03:00
{
2018-03-17 17:58:30 +03:00
const auto level = parameters . caster -> getSpellSchoolLevel ( owner );
const auto cost = owner -> getCost ( level );
2017-07-03 21:09:27 +03:00
switch ( result )
{
case ESpellCastResult :: OK :
{
SetMana sm ;
sm . hid = parameters . caster -> id ;
sm . absolute = false ;
sm . val = - cost ;
2018-03-17 17:58:30 +03:00
env -> apply ( & sm );
2017-07-03 21:09:27 +03:00
}
break ;
default :
break ;
}
}
2015-02-02 12:22:19 +03:00
///SummonBoatMechanics
2017-06-06 07:53:51 +03:00
SummonBoatMechanics :: SummonBoatMechanics ( const CSpell * s ) :
AdventureSpellMechanics ( s )
2015-02-02 12:22:19 +03:00
{
2017-06-06 07:53:51 +03:00
}
2018-03-17 17:58:30 +03:00
ESpellCastResult SummonBoatMechanics :: applyAdventureEffects ( SpellCastEnvironment * env , const AdventureSpellCastParameters & parameters ) const
2017-06-06 07:53:51 +03:00
{
2017-07-22 22:47:29 +02:00
if ( parameters . caster -> boat )
{
InfoWindow iw ;
iw . player = parameters . caster -> tempOwner ;
iw . text . addTxt ( MetaString :: GENERAL_TXT , 333 ); //%s is already in boat
iw . text . addReplacement ( parameters . caster -> name );
2018-03-17 17:58:30 +03:00
env -> apply ( & iw );
2017-07-22 22:47:29 +02:00
return ESpellCastResult :: CANCEL ;
}
2017-06-06 07:53:51 +03:00
int3 summonPos = parameters . caster -> bestLocation ();
if ( summonPos . x < 0 )
{
InfoWindow iw ;
iw . player = parameters . caster -> tempOwner ;
iw . text . addTxt ( MetaString :: GENERAL_TXT , 334 ); //There is no place to put the boat.
2018-03-17 17:58:30 +03:00
env -> apply ( & iw );
2017-06-06 07:53:51 +03:00
return ESpellCastResult :: CANCEL ;
}
2018-03-17 17:58:30 +03:00
const auto schoolLevel = parameters . caster -> getSpellSchoolLevel ( owner );
2017-06-06 07:53:51 +03:00
2015-02-02 12:22:19 +03:00
//check if spell works at all
2018-03-17 17:58:30 +03:00
if ( env -> getRNG () -> getInt64Range ( 0 , 99 )() >= owner -> getLevelPower ( schoolLevel )) //power is % chance of success
2015-02-02 12:22:19 +03:00
{
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 );
2018-03-17 17:58:30 +03:00
env -> apply ( & iw );
2015-04-13 06:12:23 +03:00
return ESpellCastResult :: OK ;
2015-02-02 12:22:19 +03:00
}
//try to find unoccupied boat to summon
const CGBoat * nearest = nullptr ;
double dist = 0 ;
for ( const CGObjectInstance * obj : env -> getMap () -> objects )
{
if ( obj && obj -> ID == Obj :: BOAT )
{
const CGBoat * b = static_cast < const CGBoat *> ( obj );
2015-02-26 20:59:18 +03:00
if ( b -> hero )
2015-02-02 12:22:19 +03: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 20:59:18 +03:00
}
2015-02-02 12:22:19 +03:00
}
if ( nullptr != nearest ) //we found boat to summon
{
ChangeObjPos cop ;
cop . objid = nearest -> id ;
2017-06-06 07:53:51 +03:00
cop . nPos = summonPos + int3 ( 1 , 0 , 0 );
2015-02-02 12:22:19 +03:00
cop . flags = 1 ;
2018-03-17 17:58:30 +03:00
env -> apply ( & cop );
2015-02-02 12:22:19 +03:00
}
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.
2018-03-17 17:58:30 +03:00
env -> apply ( & iw );
2015-02-02 12:22:19 +03:00
}
else //create boat
{
NewObject no ;
no . ID = Obj :: BOAT ;
no . subID = parameters . caster -> getBoatType ();
2017-06-06 07:53:51 +03:00
no . pos = summonPos + int3 ( 1 , 0 , 0 );
2018-03-17 17:58:30 +03:00
env -> apply ( & no );
2015-02-26 20:59:18 +03:00
}
2015-04-13 06:12:23 +03:00
return ESpellCastResult :: OK ;
2015-02-02 12:22:19 +03:00
}
///ScuttleBoatMechanics
2017-06-06 07:53:51 +03:00
ScuttleBoatMechanics :: ScuttleBoatMechanics ( const CSpell * s ) :
AdventureSpellMechanics ( s )
{
}
2018-03-17 17:58:30 +03:00
ESpellCastResult ScuttleBoatMechanics :: applyAdventureEffects ( SpellCastEnvironment * env , const AdventureSpellCastParameters & parameters ) const
2015-02-02 12:22:19 +03:00
{
2018-03-17 17:58:30 +03:00
const auto schoolLevel = parameters . caster -> getSpellSchoolLevel ( owner );
2015-02-02 12:22:19 +03:00
//check if spell works at all
2018-03-17 17:58:30 +03:00
if ( env -> getRNG () -> getInt64Range ( 0 , 99 )() >= owner -> getLevelPower ( schoolLevel )) //power is % chance of success
2015-02-02 12:22:19 +03:00
{
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 );
2018-03-17 17:58:30 +03:00
env -> apply ( & iw );
2015-04-13 06:12:23 +03:00
return ESpellCastResult :: OK ;
2015-02-02 12:22:19 +03:00
}
2015-02-26 20:59:18 +03:00
2015-02-02 12:22:19 +03:00
if ( ! env -> getMap () -> isInTheMap ( parameters . pos ))
{
env -> complain ( "Invalid dst tile for scuttle!" );
2015-04-13 06:12:23 +03:00
return ESpellCastResult :: ERROR ;
2015-02-02 12:22:19 +03: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 06:12:23 +03:00
return ESpellCastResult :: ERROR ;
2015-02-02 12:22:19 +03:00
}
RemoveObject ro ;
ro . id = t -> visitableObjects . back () -> id ;
2018-03-17 17:58:30 +03:00
env -> apply ( & ro );
2015-04-13 06:12:23 +03:00
return ESpellCastResult :: OK ;
2015-02-02 12:22:19 +03:00
}
///DimensionDoorMechanics
2017-06-06 07:53:51 +03:00
DimensionDoorMechanics :: DimensionDoorMechanics ( const CSpell * s ) :
AdventureSpellMechanics ( s )
{
}
2018-03-17 17:58:30 +03:00
ESpellCastResult DimensionDoorMechanics :: applyAdventureEffects ( SpellCastEnvironment * env , const AdventureSpellCastParameters & parameters ) const
2015-02-02 12:22:19 +03:00
{
if ( ! env -> getMap () -> isInTheMap ( parameters . pos ))
{
env -> complain ( "Destination is out of map!" );
2015-04-13 06:12:23 +03:00
return ESpellCastResult :: ERROR ;
2015-02-26 20:59:18 +03:00
}
2015-02-02 12:22:19 +03: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 06:12:23 +03:00
return ESpellCastResult :: ERROR ;
2015-02-02 12:22:19 +03:00
}
2015-02-26 20:59:18 +03:00
2015-02-02 12:22:19 +03:00
if ( nullptr == curr )
{
env -> complain ( "Source tile doesn't exist!" );
2015-04-13 06:12:23 +03:00
return ESpellCastResult :: ERROR ;
2015-02-26 20:59:18 +03:00
}
2015-12-21 18:44:21 +03:00
if ( parameters . caster -> movement <= 0 ) //unlike town portal non-zero MP is enough
2015-02-02 12:22:19 +03:00
{
env -> complain ( "Hero needs movement points to cast Dimension Door!" );
2015-04-13 06:12:23 +03:00
return ESpellCastResult :: ERROR ;
2015-02-02 12:22:19 +03:00
}
2015-02-26 20:59:18 +03:00
2018-03-17 17:58:30 +03:00
const auto schoolLevel = parameters . caster -> getSpellSchoolLevel ( owner );
2015-12-21 18:44:21 +03:00
const int movementCost = GameConstants :: BASE_MOVEMENT_COST * (( schoolLevel >= 3 ) ? 2 : 3 );
2015-02-26 20:59:18 +03:00
2016-10-01 06:51:12 +03:00
std :: stringstream cachingStr ;
cachingStr << "source_" << Bonus :: SPELL_EFFECT << "id_" << owner -> id . num ;
2018-03-17 17:58:30 +03:00
if ( parameters . caster -> getBonuses ( Selector :: source ( Bonus :: SPELL_EFFECT , owner -> id ), Selector :: all , cachingStr . str ()) -> size () >= owner -> getLevelPower ( schoolLevel )) //limit casts per turn
2015-02-02 12:22:19 +03: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 );
2018-03-17 17:58:30 +03:00
env -> apply ( & iw );
2015-04-13 06:12:23 +03:00
return ESpellCastResult :: CANCEL ;
2015-02-02 12:22:19 +03:00
}
GiveBonus gb ;
gb . id = parameters . caster -> id . getNum ();
gb . bonus = Bonus ( Bonus :: ONE_DAY , Bonus :: NONE , Bonus :: SPELL_EFFECT , 0 , owner -> id );
2018-03-17 17:58:30 +03:00
env -> apply ( & gb );
2015-02-02 12:22:19 +03:00
if ( ! dest -> isClear ( curr )) //wrong dest tile
{
InfoWindow iw ;
iw . player = parameters . caster -> tempOwner ;
iw . text . addTxt ( MetaString :: GENERAL_TXT , 70 ); //Dimension Door failed!
2018-03-17 17:58:30 +03:00
env -> apply ( & iw );
2015-02-02 12:22:19 +03:00
}
else if ( env -> moveHero ( parameters . caster -> id , parameters . pos + parameters . caster -> getVisitableOffset (), true ))
{
SetMovePoints smp ;
smp . hid = parameters . caster -> id ;
2020-10-01 01:38:06 -07:00
if ( movementCost < ( int ) parameters . caster -> movement )
2017-08-18 21:33:15 +02:00
smp . val = parameters . caster -> movement - movementCost ;
else
smp . val = 0 ;
2018-03-17 17:58:30 +03:00
env -> apply ( & smp );
2015-02-02 12:22:19 +03:00
}
2015-04-13 06:12:23 +03:00
return ESpellCastResult :: OK ;
2015-02-02 12:22:19 +03:00
}
///TownPortalMechanics
2017-06-06 07:53:51 +03:00
TownPortalMechanics :: TownPortalMechanics ( const CSpell * s ) :
AdventureSpellMechanics ( s )
2015-02-02 12:22:19 +03:00
{
2017-06-06 07:53:51 +03:00
}
2015-02-02 12:22:19 +03:00
2018-03-17 17:58:30 +03:00
ESpellCastResult TownPortalMechanics :: applyAdventureEffects ( SpellCastEnvironment * env , const AdventureSpellCastParameters & parameters ) const
2017-06-06 07:53:51 +03:00
{
const CGTownInstance * destination = nullptr ;
2017-07-03 21:09:27 +03:00
const int moveCost = movementCost ( parameters );
2015-02-02 12:22:19 +03:00
2018-03-17 17:58:30 +03:00
if ( parameters . caster -> getSpellSchoolLevel ( owner ) < 2 )
{
2017-06-06 07:53:51 +03:00
std :: vector < const CGTownInstance *> pool = getPossibleTowns ( env , parameters );
destination = findNearestTown ( env , parameters , pool );
2016-02-15 13:34:37 +03:00
2017-06-06 07:53:51 +03:00
if ( nullptr == destination )
2017-07-03 21:09:27 +03:00
return ESpellCastResult :: ERROR ;
2017-06-06 07:53:51 +03:00
2020-10-01 01:38:06 -07:00
if (( int ) parameters . caster -> movement < moveCost )
2017-07-03 21:09:27 +03:00
return ESpellCastResult :: ERROR ;
2017-06-06 07:53:51 +03:00
2017-07-03 21:09:27 +03:00
if ( destination -> visitingHero )
2017-06-06 07:53:51 +03:00
{
InfoWindow iw ;
iw . player = parameters . caster -> tempOwner ;
iw . text . addTxt ( MetaString :: GENERAL_TXT , 123 );
2018-03-17 17:58:30 +03:00
env -> apply ( & iw );
2017-06-06 07:53:51 +03:00
return ESpellCastResult :: CANCEL ;
}
}
else if ( env -> getMap () -> isInTheMap ( parameters . pos ))
{
const TerrainTile & tile = env -> getMap () -> getTile ( parameters . pos );
2019-02-23 20:51:03 +03:00
const auto topObj = tile . topVisitableObj ( false );
if ( ! topObj )
2017-06-06 07:53:51 +03:00
{
2019-02-23 20:51:03 +03:00
env -> complain ( "Destination tile is not visitable" + parameters . pos . toString ());
return ESpellCastResult :: ERROR ;
}
else if ( topObj -> ID == Obj :: HERO )
{
env -> complain ( "Can't teleport to occupied town at " + parameters . pos . toString ());
return ESpellCastResult :: ERROR ;
}
else if ( topObj -> ID != Obj :: TOWN )
{
env -> complain ( "No town at destination tile " + parameters . pos . toString ());
2015-04-13 06:12:23 +03:00
return ESpellCastResult :: ERROR ;
2015-02-02 12:22:19 +03:00
}
2015-02-26 20:59:18 +03:00
2019-02-23 20:51:03 +03:00
destination = dynamic_cast < const CGTownInstance *> ( topObj );
2017-06-06 07:53:51 +03:00
if ( nullptr == destination )
{
2019-02-23 20:51:03 +03:00
env -> complain ( "[Internal error] invalid town object at " + parameters . pos . toString ());
2017-06-06 07:53:51 +03:00
return ESpellCastResult :: ERROR ;
}
const auto relations = env -> getCb () -> getPlayerRelations ( destination -> tempOwner , parameters . caster -> tempOwner );
if ( relations == PlayerRelations :: ENEMIES )
{
env -> complain ( "Can't teleport to enemy!" );
return ESpellCastResult :: ERROR ;
}
2020-10-01 01:38:06 -07:00
if (( int ) parameters . caster -> movement < moveCost )
2017-06-06 07:53:51 +03:00
{
env -> complain ( "This hero has not enough movement points!" );
return ESpellCastResult :: ERROR ;
}
if ( destination -> visitingHero )
{
2019-02-23 20:51:03 +03:00
env -> complain ( "[Internal error] Can't teleport to occupied town" );
2017-06-06 07:53:51 +03:00
return ESpellCastResult :: ERROR ;
}
2015-02-02 12:22:19 +03:00
}
2017-06-06 07:53:51 +03:00
else
2015-04-01 04:48:50 +03:00
{
2017-06-06 07:53:51 +03:00
env -> complain ( "Invalid destination tile" );
2016-02-15 13:34:37 +03:00
return ESpellCastResult :: ERROR ;
2015-04-01 04:48:50 +03:00
}
2016-02-15 13:34:37 +03:00
2017-07-03 21:09:27 +03:00
if ( env -> moveHero ( parameters . caster -> id , destination -> visitablePos () + parameters . caster -> getVisitableOffset (), true ))
2015-04-01 04:48:50 +03:00
{
SetMovePoints smp ;
smp . hid = parameters . caster -> id ;
2017-07-03 21:09:27 +03:00
smp . val = std :: max < ui32 > ( 0 , parameters . caster -> movement - moveCost );
2018-03-17 17:58:30 +03:00
env -> apply ( & smp );
2015-04-01 04:48:50 +03:00
}
2015-04-13 06:12:23 +03:00
return ESpellCastResult :: OK ;
2015-02-02 12:22:19 +03:00
}
2015-02-02 15:02:27 +03:00
2018-03-17 17:58:30 +03:00
ESpellCastResult TownPortalMechanics :: beginCast ( SpellCastEnvironment * env , const AdventureSpellCastParameters & parameters ) const
2017-07-03 21:09:27 +03:00
{
std :: vector < const CGTownInstance *> towns = getPossibleTowns ( env , parameters );
if ( towns . empty ())
{
InfoWindow iw ;
iw . player = parameters . caster -> tempOwner ;
iw . text . addTxt ( MetaString :: GENERAL_TXT , 124 );
2018-03-17 17:58:30 +03:00
env -> apply ( & iw );
2017-07-03 21:09:27 +03:00
return ESpellCastResult :: CANCEL ;
}
const int moveCost = movementCost ( parameters );
2020-10-01 01:38:06 -07:00
if (( int ) parameters . caster -> movement < moveCost )
2017-07-03 21:09:27 +03:00
{
InfoWindow iw ;
iw . player = parameters . caster -> tempOwner ;
iw . text . addTxt ( MetaString :: GENERAL_TXT , 125 );
2018-03-17 17:58:30 +03:00
env -> apply ( & iw );
2017-07-03 21:09:27 +03:00
return ESpellCastResult :: CANCEL ;
}
2017-07-04 01:32:40 +03:00
if ( ! parameters . pos . valid () && parameters . caster -> getSpellSchoolLevel ( owner ) >= 2 )
2017-07-03 21:09:27 +03:00
{
auto queryCallback = [ = ]( const JsonNode & reply ) -> void
{
2017-11-26 22:18:18 +01:00
if ( reply . getType () == JsonNode :: JsonType :: DATA_INTEGER )
2017-07-03 21:09:27 +03:00
{
2020-10-01 01:38:06 -07:00
ObjectInstanceID townId (( si32 ) reply . Integer ());
2017-07-03 21:09:27 +03:00
const CGObjectInstance * o = env -> getCb () -> getObj ( townId , true );
if ( o == nullptr )
{
env -> complain ( "Invalid object instance selected" );
return ;
}
if ( ! dynamic_cast < const CGTownInstance *> ( o ))
{
env -> complain ( "Object instance is not town" );
return ;
}
AdventureSpellCastParameters p ;
p . caster = parameters . caster ;
p . pos = o -> visitablePos ();
performCast ( env , p );
}
};
MapObjectSelectDialog request ;
for ( auto t : towns )
{
2017-07-04 01:32:40 +03:00
if ( t -> visitingHero == nullptr ) //empty town
2017-07-03 21:09:27 +03:00
request . objects . push_back ( t -> id );
}
if ( request . objects . empty ())
{
InfoWindow iw ;
iw . player = parameters . caster -> tempOwner ;
iw . text . addTxt ( MetaString :: GENERAL_TXT , 124 );
2018-03-17 17:58:30 +03:00
env -> apply ( & iw );
2017-07-03 21:09:27 +03:00
return ESpellCastResult :: CANCEL ;
}
request . player = parameters . caster -> getOwner ();
2017-07-04 01:32:40 +03:00
request . title . addTxt ( MetaString :: JK_TXT , 40 );
request . description . addTxt ( MetaString :: JK_TXT , 41 );
request . icon . id = Component :: SPELL ;
request . icon . subtype = owner -> id . toEnum ();
2017-07-03 21:09:27 +03:00
env -> genericQuery ( & request , request . player , queryCallback );
return ESpellCastResult :: PENDING ;
}
return ESpellCastResult :: OK ;
}
2018-03-17 17:58:30 +03:00
const CGTownInstance * TownPortalMechanics :: findNearestTown ( SpellCastEnvironment * env , const AdventureSpellCastParameters & parameters , const std :: vector < const CGTownInstance *> & pool ) const
2017-06-06 07:53:51 +03:00
{
if ( pool . empty ())
return nullptr ;
auto nearest = pool . cbegin (); //nearest town's iterator
si32 dist = ( * nearest ) -> pos . dist2dSQ ( parameters . caster -> pos );
2017-07-04 01:32:40 +03:00
for ( auto i = nearest + 1 ; i != pool . cend (); ++ i )
2017-06-06 07:53:51 +03:00
{
si32 curDist = ( * i ) -> pos . dist2dSQ ( parameters . caster -> pos );
2017-07-04 01:32:40 +03:00
if ( curDist < dist )
2017-06-06 07:53:51 +03:00
{
nearest = i ;
dist = curDist ;
}
}
return * nearest ;
}
2018-03-17 17:58:30 +03:00
std :: vector < const CGTownInstance *> TownPortalMechanics :: getPossibleTowns ( SpellCastEnvironment * env , const AdventureSpellCastParameters & parameters ) const
2017-06-06 07:53:51 +03:00
{
std :: vector < const CGTownInstance *> ret ;
const TeamState * team = env -> getCb () -> getPlayerTeam ( parameters . caster -> getOwner ());
for ( const auto & color : team -> players )
{
2018-03-17 17:58:30 +03:00
for ( auto currTown : env -> getCb () -> getPlayerState ( color ) -> towns )
2017-06-06 07:53:51 +03:00
{
ret . push_back ( currTown . get ());
}
}
return ret ;
}
2018-03-17 17:58:30 +03:00
int32_t TownPortalMechanics :: movementCost ( const AdventureSpellCastParameters & parameters ) const
2017-07-03 21:09:27 +03:00
{
return GameConstants :: BASE_MOVEMENT_COST * (( parameters . caster -> getSpellSchoolLevel ( owner ) >= 3 ) ? 2 : 3 );
}
2017-06-06 07:53:51 +03:00
///ViewMechanics
ViewMechanics :: ViewMechanics ( const CSpell * s ) :
AdventureSpellMechanics ( s )
{
}
2018-03-17 17:58:30 +03:00
ESpellCastResult ViewMechanics :: applyAdventureEffects ( SpellCastEnvironment * env , const AdventureSpellCastParameters & parameters ) const
2015-02-02 15:02:27 +03:00
{
2015-02-26 17:15:17 +03:00
ShowWorldViewEx pack ;
2015-02-26 20:59:18 +03:00
2016-09-28 07:58:15 +03:00
pack . player = parameters . caster -> getOwner ();
2015-02-26 20:59:18 +03:00
2018-03-17 17:58:30 +03:00
const auto spellLevel = parameters . caster -> getSpellSchoolLevel ( owner );
2015-02-26 20:59:18 +03:00
2016-09-28 07:58:15 +03:00
const auto & fowMap = env -> getCb () -> getPlayerTeam ( parameters . caster -> getOwner ()) -> fogOfWarMap ;
2015-02-26 17:15:17 +03:00
for ( const CGObjectInstance * obj : env -> getMap () -> objects )
{
2016-09-28 07:58:15 +03:00
//deleted object remain as empty pointer
if ( obj && filterObject ( obj , spellLevel ))
{
ObjectPosInfo posInfo ( obj );
2016-02-15 13:34:37 +03:00
2016-09-28 07:58:15 +03:00
if ( fowMap [ posInfo . pos . x ][ posInfo . pos . y ][ posInfo . pos . z ] == 0 )
pack . objectPositions . push_back ( posInfo );
}
2015-02-26 20:59:18 +03:00
}
2018-03-17 17:58:30 +03:00
env -> apply ( & pack );
2015-02-26 20:59:18 +03:00
2015-04-13 06:12:23 +03:00
return ESpellCastResult :: OK ;
2015-02-02 15:02:27 +03:00
}
2017-06-06 07:53:51 +03:00
///ViewAirMechanics
ViewAirMechanics :: ViewAirMechanics ( const CSpell * s ) :
ViewMechanics ( s )
{
}
2018-03-17 17:58:30 +03:00
bool ViewAirMechanics :: filterObject ( const CGObjectInstance * obj , const int32_t spellLevel ) const
2015-02-02 15:02:27 +03:00
{
2017-06-06 07:53:51 +03:00
return ( obj -> ID == Obj :: ARTIFACT ) || ( spellLevel > 1 && obj -> ID == Obj :: HERO ) || ( spellLevel > 2 && obj -> ID == Obj :: TOWN );
}
///ViewEarthMechanics
ViewEarthMechanics :: ViewEarthMechanics ( const CSpell * s ) :
ViewMechanics ( s )
{
2015-02-02 15:02:27 +03:00
}
2015-02-26 17:15:17 +03:00
2018-03-17 17:58:30 +03:00
bool ViewEarthMechanics :: filterObject ( const CGObjectInstance * obj , const int32_t spellLevel ) const
2015-02-26 17:15:17 +03:00
{
2017-06-06 07:53:51 +03:00
return ( obj -> ID == Obj :: RESOURCE ) || ( spellLevel > 1 && obj -> ID == Obj :: MINE );
2015-02-26 17:15:17 +03:00
}