1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-12-20 20:23:03 +02:00
vcmi/lib/spells/AdventureSpellMechanics.cpp

654 lines
18 KiB
C++
Raw Normal View History

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
Spells configuration version 2 (effect-based) * Indirect spell effects loading * Json serializer improvements * spell->canBeCastAt do not allow useless cast for any spell * Added proxy caster class for spell-created obstacles * Handle damage from spell-created obstacles inside mechanics * Experimental GameState integration/regression tests * Ignore mod settings and load only "vcmi" mod when running tests * fixed https://bugs.vcmi.eu/view.php?id=2765 (with tests) * Huge improvements of BattleAI regarding spell casts * AI can cast almost any combat spell except TELEPORT, SACRIFICE and obstacle placement spells. * Possible fix for https://bugs.vcmi.eu/view.php?id=1811 * CStack factored out to several classes * [Battle] Allowed RETURN_AFTER_STRIKE effect on server side to be optional * [Battle] Allowed BattleAction have multiple destinations * [Spells] Converted limit|immunity to target condition * [Spells] Use partial configuration reload for backward compatibility handling * [Tests] Started tests for CUnitState * Partial fixes of fire shield effect * [Battle] Do HP calculations in 64 bits * [BattleAI] Use threading for spell cast evaluation * [BattleAI] Made AI be able to evaluate modified turn order (on hypothetical battle state) * Implemented https://bugs.vcmi.eu/view.php?id=2811 * plug rare freeze when hypnotized unit shots vertically * Correctly apply ONLY_MELEE_FIGHT / ONLY_DISTANCE_FIGHT for unit damage, attack & defense * [BattleAI] Try to not waste a cast if battle is actually won already * Extended JsonSerializeFormat API * fixed https://bugs.vcmi.eu/view.php?id=2847 * Any unit effect can be now chained (not only damage like Chain Lightning) ** only damage effect for now actually uses "chainFactor" * Possible quick fix for https://bugs.vcmi.eu/view.php?id=2860
2017-07-20 06:08:49 +02:00
#include "CSpellHandler.h"
2015-02-02 11:22:19 +02:00
#include "../CRandomGenerator.h"
#include "../mapObjects/CGHeroInstance.h"
#include "../NetPacks.h"
#include "../CGameInfoCallback.h"
#include "../mapping/CMap.h"
#include "../CPlayerState.h"
2015-02-02 11:22:19 +02:00
VCMI_LIB_NAMESPACE_BEGIN
///AdventureSpellMechanics
AdventureSpellMechanics::AdventureSpellMechanics(const CSpell * s):
IAdventureSpellMechanics(s)
{
}
bool AdventureSpellMechanics::adventureCast(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const
{
if(!owner->isAdventure())
{
env->complain("Attempt to cast non adventure spell in adventure mode");
return false;
}
2023-04-09 23:06:02 +02:00
if(const CGHeroInstance * heroCaster = dynamic_cast<const CGHeroInstance *>(parameters.caster))
{
2023-04-09 23:06:02 +02:00
if(heroCaster->inTownGarrison)
{
env->complain("Attempt to cast an adventure spell in town garrison");
return false;
}
2023-04-09 23:06:02 +02:00
const auto level = heroCaster->getSpellSchoolLevel(owner);
const auto cost = owner->getCost(level);
2023-04-09 23:06:02 +02:00
if(!heroCaster->canCastThisSpell(owner))
{
env->complain("Hero cannot cast this spell!");
return false;
}
2023-04-09 23:06:02 +02:00
if(heroCaster->mana < cost)
{
env->complain("Hero doesn't have enough spell points to cast this spell!");
return false;
}
}
ESpellCastResult result = beginCast(env, parameters);
if(result == ESpellCastResult::OK)
performCast(env, parameters);
return result != ESpellCastResult::ERROR;
}
ESpellCastResult AdventureSpellMechanics::applyAdventureEffects(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const
{
if(owner->hasEffects())
{
//todo: cumulative effects support
const auto schoolLevel = parameters.caster->getSpellSchoolLevel(owner);
std::vector<Bonus> bonuses;
owner->getEffects(bonuses, schoolLevel, false, parameters.caster->getEnchantPower(owner));
for(const Bonus & b : bonuses)
{
GiveBonus gb;
2023-04-09 23:06:02 +02:00
gb.id = parameters.caster->getCasterUnitId();
gb.bonus = b;
env->apply(&gb);
}
return ESpellCastResult::OK;
}
else
{
//There is no generic algorithm of adventure cast
env->complain("Unimplemented adventure spell");
return ESpellCastResult::ERROR;
}
}
ESpellCastResult AdventureSpellMechanics::beginCast(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const
{
return ESpellCastResult::OK;
}
void AdventureSpellMechanics::performCast(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const
{
AdvmapSpellCast asc;
2023-04-09 23:06:02 +02:00
asc.casterID = ObjectInstanceID(parameters.caster->getCasterUnitId());
asc.spellID = owner->id;
env->apply(&asc);
ESpellCastResult result = applyAdventureEffects(env, parameters);
endCast(env, parameters, result);
}
void AdventureSpellMechanics::endCast(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters, const ESpellCastResult result) const
{
const auto level = parameters.caster->getSpellSchoolLevel(owner);
const auto cost = owner->getCost(level);
switch(result)
{
case ESpellCastResult::OK:
parameters.caster->spendMana(env, cost);
break;
default:
break;
}
}
2015-02-02 11:22:19 +02:00
///SummonBoatMechanics
SummonBoatMechanics::SummonBoatMechanics(const CSpell * s):
AdventureSpellMechanics(s)
2015-02-02 11:22:19 +02:00
{
}
ESpellCastResult SummonBoatMechanics::applyAdventureEffects(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const
{
if(!parameters.caster->getHeroCaster())
{
2023-04-09 23:37:46 +02:00
env->complain("Not a hero caster!");
return ESpellCastResult::ERROR;
}
2023-04-09 23:06:02 +02:00
if(parameters.caster->getHeroCaster()->boat)
{
InfoWindow iw;
2023-04-09 23:06:02 +02:00
iw.player = parameters.caster->getCasterOwner();
iw.text.appendLocalString(EMetaText::GENERAL_TXT, 333);//%s is already in boat
2023-04-09 23:06:02 +02:00
parameters.caster->getCasterName(iw.text);
env->apply(&iw);
return ESpellCastResult::CANCEL;
}
int3 summonPos = parameters.caster->getHeroCaster()->bestLocation();
2023-04-09 23:06:02 +02:00
if(summonPos.x < 0)
{
InfoWindow iw;
2023-04-09 23:06:02 +02:00
iw.player = parameters.caster->getCasterOwner();
iw.text.appendLocalString(EMetaText::GENERAL_TXT, 334);//There is no place to put the boat.
env->apply(&iw);
return ESpellCastResult::CANCEL;
}
const auto schoolLevel = parameters.caster->getSpellSchoolLevel(owner);
2015-02-02 11:22:19 +02:00
//check if spell works at all
if(env->getRNG()->getInt64Range(0, 99)() >= owner->getLevelPower(schoolLevel)) //power is % chance of success
2015-02-02 11:22:19 +02:00
{
InfoWindow iw;
2023-04-09 23:06:02 +02:00
iw.player = parameters.caster->getCasterOwner();
iw.text.appendLocalString(EMetaText::GENERAL_TXT, 336); //%s tried to summon a boat, but failed.
2023-04-09 23:06:02 +02:00
parameters.caster->getCasterName(iw.text);
env->apply(&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;
for(const CGObjectInstance * obj : env->getMap()->objects)
2015-02-02 11:22:19 +02:00
{
2023-04-20 19:20:51 +02:00
if(obj && obj->ID == Obj::BOAT)
2015-02-02 11:22:19 +02:00
{
const auto * b = dynamic_cast<const CGBoat *>(obj);
2023-04-18 20:21:26 +02:00
if(b->hero || b->layer != EPathfindingLayer::SAIL)
continue; //we're looking for unoccupied boat
double nDist = b->pos.dist2d(parameters.caster->getHeroCaster()->visitablePos());
if(!nearest || nDist < dist) //it's first boat or closer than previous
2015-02-02 11:22:19 +02:00
{
nearest = b;
dist = nDist;
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 != nearest) //we found boat to summon
{
ChangeObjPos cop;
cop.objid = nearest->id;
cop.nPos = summonPos;
env->apply(&cop);
2015-02-02 11:22:19 +02:00
}
else if(schoolLevel < 2) //none or basic level -> cannot create boat :(
{
InfoWindow iw;
2023-04-09 23:06:02 +02:00
iw.player = parameters.caster->getCasterOwner();
iw.text.appendLocalString(EMetaText::GENERAL_TXT, 335); //There are no boats to summon.
env->apply(&iw);
2015-02-02 11:22:19 +02:00
}
else //create boat
2015-02-02 11:22:19 +02:00
{
NewObject no;
2023-04-20 19:20:51 +02:00
no.ID = Obj::BOAT;
2023-06-16 17:11:45 +02:00
no.subID = BoatId(EBoatId::NECROPOLIS);
no.targetPos = summonPos;
env->apply(&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
ScuttleBoatMechanics::ScuttleBoatMechanics(const CSpell * s):
AdventureSpellMechanics(s)
{
}
ESpellCastResult ScuttleBoatMechanics::applyAdventureEffects(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const
2015-02-02 11:22:19 +02:00
{
const auto schoolLevel = parameters.caster->getSpellSchoolLevel(owner);
2015-02-02 11:22:19 +02:00
//check if spell works at all
if(env->getRNG()->getInt64Range(0, 99)() >= owner->getLevelPower(schoolLevel)) //power is % chance of success
2015-02-02 11:22:19 +02:00
{
InfoWindow iw;
2023-04-09 23:06:02 +02:00
iw.player = parameters.caster->getCasterOwner();
iw.text.appendLocalString(EMetaText::GENERAL_TXT, 337); //%s tried to scuttle the boat, but failed
2023-04-09 23:06:02 +02:00
parameters.caster->getCasterName(iw.text);
env->apply(&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);
2023-04-20 19:20:51 +02:00
if(t->visitableObjects.empty() || t->visitableObjects.back()->ID != Obj::BOAT)
2015-02-02 11:22:19 +02:00
{
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->apply(&ro);
2015-04-13 05:12:23 +02:00
return ESpellCastResult::OK;
2015-02-02 11:22:19 +02:00
}
///DimensionDoorMechanics
DimensionDoorMechanics::DimensionDoorMechanics(const CSpell * s):
AdventureSpellMechanics(s)
{
}
ESpellCastResult DimensionDoorMechanics::applyAdventureEffects(SpellCastEnvironment * env, const 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
}
2023-04-09 23:37:46 +02:00
if(!parameters.caster->getHeroCaster())
{
env->complain("Not a hero caster!");
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);
2023-04-09 23:37:46 +02:00
const TerrainTile * curr = env->getCb()->getTile(parameters.caster->getHeroCaster()->getSightCenter());
2015-02-02 11:22:19 +02:00
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
}
if(parameters.caster->getHeroCaster()->movementPointsRemaining() <= 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
const auto schoolLevel = parameters.caster->getSpellSchoolLevel(owner);
const int movementCost = GameConstants::BASE_MOVEMENT_COST * ((schoolLevel >= 3) ? 2 : 3);
2015-02-26 19:59:18 +02:00
std::stringstream cachingStr;
cachingStr << "source_" << vstd::to_underlying(BonusSource::SPELL_EFFECT) << "id_" << owner->id.num;
if(parameters.caster->getHeroCaster()->getBonuses(Selector::source(BonusSource::SPELL_EFFECT, owner->id), Selector::all, cachingStr.str())->size() >= owner->getLevelPower(schoolLevel)) //limit casts per turn
2015-02-02 11:22:19 +02:00
{
InfoWindow iw;
2023-04-09 23:06:02 +02:00
iw.player = parameters.caster->getCasterOwner();
iw.text.appendLocalString(EMetaText::GENERAL_TXT, 338); //%s is not skilled enough to cast this spell again today.
2023-04-09 23:06:02 +02:00
parameters.caster->getCasterName(iw.text);
env->apply(&iw);
2015-04-13 05:12:23 +02:00
return ESpellCastResult::CANCEL;
2015-02-02 11:22:19 +02:00
}
GiveBonus gb;
2023-04-09 23:37:46 +02:00
gb.id = parameters.caster->getCasterUnitId();
gb.bonus = Bonus(BonusDuration::ONE_DAY, BonusType::NONE, BonusSource::SPELL_EFFECT, 0, owner->id);
env->apply(&gb);
2015-02-02 11:22:19 +02:00
if(!dest->isClear(curr)) //wrong dest tile
{
InfoWindow iw;
2023-04-09 23:06:02 +02:00
iw.player = parameters.caster->getCasterOwner();
iw.text.appendLocalString(EMetaText::GENERAL_TXT, 70); //Dimension Door failed!
env->apply(&iw);
2015-02-02 11:22:19 +02:00
}
2023-04-09 23:37:46 +02:00
else if(env->moveHero(ObjectInstanceID(parameters.caster->getCasterUnitId()), parameters.caster->getHeroCaster()->convertFromVisitablePos(parameters.pos), true))
2015-02-02 11:22:19 +02:00
{
SetMovePoints smp;
2023-04-09 23:37:46 +02:00
smp.hid = ObjectInstanceID(parameters.caster->getCasterUnitId());
if(movementCost < static_cast<int>(parameters.caster->getHeroCaster()->movementPointsRemaining()))
smp.val = parameters.caster->getHeroCaster()->movementPointsRemaining() - movementCost;
else
smp.val = 0;
env->apply(&smp);
2015-02-02 11:22:19 +02:00
}
2015-04-13 05:12:23 +02:00
return ESpellCastResult::OK;
2015-02-02 11:22:19 +02:00
}
///TownPortalMechanics
TownPortalMechanics::TownPortalMechanics(const CSpell * s):
AdventureSpellMechanics(s)
2015-02-02 11:22:19 +02:00
{
}
2015-02-02 11:22:19 +02:00
ESpellCastResult TownPortalMechanics::applyAdventureEffects(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const
{
const CGTownInstance * destination = nullptr;
const int moveCost = movementCost(parameters);
2023-04-09 23:46:37 +02:00
if(!parameters.caster->getHeroCaster())
{
env->complain("Not a hero caster!");
return ESpellCastResult::ERROR;
}
2016-02-15 12:34:37 +02:00
if(parameters.caster->getSpellSchoolLevel(owner) < 2)
{
std::vector <const CGTownInstance*> pool = getPossibleTowns(env, parameters);
destination = findNearestTown(env, parameters, pool);
2016-02-15 12:34:37 +02:00
if(nullptr == destination)
return ESpellCastResult::ERROR;
2015-02-26 19:59:18 +02:00
if(static_cast<int>(parameters.caster->getHeroCaster()->movementPointsRemaining()) < moveCost)
return ESpellCastResult::ERROR;
2015-02-26 19:59:18 +02:00
if(destination->visitingHero)
{
InfoWindow iw;
2023-04-09 23:46:37 +02:00
iw.player = parameters.caster->getCasterOwner();
iw.text.appendLocalString(EMetaText::GENERAL_TXT, 123);
env->apply(&iw);
return ESpellCastResult::CANCEL;
}
}
else if(env->getMap()->isInTheMap(parameters.pos))
2015-02-02 11:22:19 +02:00
{
const TerrainTile & tile = env->getMap()->getTile(parameters.pos);
auto * const topObj = tile.topVisitableObj(false);
if(!topObj)
{
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)
2015-02-02 11:22:19 +02:00
{
env->complain("No town at destination tile " + parameters.pos.toString());
return ESpellCastResult::ERROR;
2015-02-02 11:22:19 +02:00
}
destination = dynamic_cast<const CGTownInstance*>(topObj);
if(nullptr == destination)
2015-02-02 11:22:19 +02:00
{
env->complain("[Internal error] invalid town object at " + parameters.pos.toString());
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
2023-04-09 23:46:37 +02:00
const auto relations = env->getCb()->getPlayerRelations(destination->tempOwner, parameters.caster->getCasterOwner());
2016-02-15 12:34:37 +02:00
if(relations == PlayerRelations::ENEMIES)
{
env->complain("Can't teleport to enemy!");
return ESpellCastResult::ERROR;
}
if(static_cast<int>(parameters.caster->getHeroCaster()->movementPointsRemaining()) < moveCost)
{
env->complain("This hero has not enough movement points!");
return ESpellCastResult::ERROR;
}
2016-02-15 12:34:37 +02:00
if(destination->visitingHero)
{
env->complain("[Internal error] Can't teleport to occupied town");
return ESpellCastResult::ERROR;
}
}
else
{
env->complain("Invalid destination tile");
2016-02-15 12:34:37 +02:00
return ESpellCastResult::ERROR;
}
2016-02-15 12:34:37 +02:00
2023-04-09 23:46:37 +02:00
if(env->moveHero(ObjectInstanceID(parameters.caster->getCasterUnitId()), parameters.caster->getHeroCaster()->convertFromVisitablePos(destination->visitablePos()), true))
{
SetMovePoints smp;
2023-04-09 23:46:37 +02:00
smp.hid = ObjectInstanceID(parameters.caster->getCasterUnitId());
smp.val = std::max<ui32>(0, parameters.caster->getHeroCaster()->movementPointsRemaining() - moveCost);
env->apply(&smp);
}
2015-04-13 05:12:23 +02:00
return ESpellCastResult::OK;
2015-02-02 11:22:19 +02:00
}
ESpellCastResult TownPortalMechanics::beginCast(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const
{
std::vector<const CGTownInstance *> towns = getPossibleTowns(env, parameters);
2023-04-09 23:46:37 +02:00
if(!parameters.caster->getHeroCaster())
{
env->complain("Not a hero caster!");
return ESpellCastResult::ERROR;
}
if(towns.empty())
{
InfoWindow iw;
2023-04-09 23:46:37 +02:00
iw.player = parameters.caster->getCasterOwner();
iw.text.appendLocalString(EMetaText::GENERAL_TXT, 124);
env->apply(&iw);
return ESpellCastResult::CANCEL;
}
const int moveCost = movementCost(parameters);
if(static_cast<int>(parameters.caster->getHeroCaster()->movementPointsRemaining()) < moveCost)
{
InfoWindow iw;
2023-04-09 23:46:37 +02:00
iw.player = parameters.caster->getCasterOwner();
iw.text.appendLocalString(EMetaText::GENERAL_TXT, 125);
env->apply(&iw);
return ESpellCastResult::CANCEL;
}
2017-07-04 00:32:40 +02:00
if(!parameters.pos.valid() && parameters.caster->getSpellSchoolLevel(owner) >= 2)
{
auto queryCallback = [=](const JsonNode & reply) -> void
{
if(reply.getType() == JsonNode::JsonType::DATA_INTEGER)
{
ObjectInstanceID townId(static_cast<si32>(reply.Integer()));
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(const auto * t : towns)
{
2017-07-04 00:32:40 +02:00
if(t->visitingHero == nullptr) //empty town
request.objects.push_back(t->id);
}
if(request.objects.empty())
{
InfoWindow iw;
2023-04-09 23:46:37 +02:00
iw.player = parameters.caster->getCasterOwner();
iw.text.appendLocalString(EMetaText::GENERAL_TXT, 124);
env->apply(&iw);
return ESpellCastResult::CANCEL;
}
2023-04-09 23:46:37 +02:00
request.player = parameters.caster->getCasterOwner();
request.title.appendLocalString(EMetaText::JK_TXT, 40);
request.description.appendLocalString(EMetaText::JK_TXT, 41);
request.icon.id = Component::EComponentType::SPELL;
2017-07-04 00:32:40 +02:00
request.icon.subtype = owner->id.toEnum();
env->genericQuery(&request, request.player, queryCallback);
return ESpellCastResult::PENDING;
}
return ESpellCastResult::OK;
}
const CGTownInstance * TownPortalMechanics::findNearestTown(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters, const std::vector <const CGTownInstance *> & pool) const
{
if(pool.empty())
return nullptr;
2023-04-09 23:46:37 +02:00
if(!parameters.caster->getHeroCaster())
return nullptr;
auto nearest = pool.cbegin(); //nearest town's iterator
2023-04-09 23:46:37 +02:00
si32 dist = (*nearest)->pos.dist2dSQ(parameters.caster->getHeroCaster()->pos);
2017-07-04 00:32:40 +02:00
for(auto i = nearest + 1; i != pool.cend(); ++i)
{
2023-04-09 23:46:37 +02:00
si32 curDist = (*i)->pos.dist2dSQ(parameters.caster->getHeroCaster()->pos);
2017-07-04 00:32:40 +02:00
if(curDist < dist)
{
nearest = i;
dist = curDist;
}
}
return *nearest;
}
std::vector <const CGTownInstance*> TownPortalMechanics::getPossibleTowns(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const
{
std::vector <const CGTownInstance*> ret;
2023-04-09 23:46:37 +02:00
const TeamState * team = env->getCb()->getPlayerTeam(parameters.caster->getCasterOwner());
for(const auto & color : team->players)
{
for(auto currTown : env->getCb()->getPlayerState(color)->towns)
{
ret.push_back(currTown.get());
}
}
return ret;
}
int32_t TownPortalMechanics::movementCost(const AdventureSpellCastParameters & parameters) const
{
if(parameters.caster != parameters.caster->getHeroCaster()) //if caster is not hero
return 0;
return GameConstants::BASE_MOVEMENT_COST * ((parameters.caster->getSpellSchoolLevel(owner) >= 3) ? 2 : 3);
}
///ViewMechanics
ViewMechanics::ViewMechanics(const CSpell * s):
AdventureSpellMechanics(s)
{
}
ESpellCastResult ViewMechanics::applyAdventureEffects(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const
{
ShowWorldViewEx pack;
2015-02-26 19:59:18 +02:00
2023-04-09 23:46:37 +02:00
pack.player = parameters.caster->getCasterOwner();
2015-02-26 19:59:18 +02:00
const auto spellLevel = parameters.caster->getSpellSchoolLevel(owner);
2015-02-26 19:59:18 +02:00
2023-04-09 23:46:37 +02:00
const auto fowMap = env->getCb()->getPlayerTeam(parameters.caster->getCasterOwner())->fogOfWarMap;
for(const CGObjectInstance * obj : env->getMap()->objects)
{
//deleted object remain as empty pointer
if(obj && filterObject(obj, spellLevel))
{
ObjectPosInfo posInfo(obj);
2016-02-15 12:34:37 +02:00
2023-01-19 20:19:29 +02:00
if((*fowMap)[posInfo.pos.z][posInfo.pos.x][posInfo.pos.y] == 0)
pack.objectPositions.push_back(posInfo);
}
2015-02-26 19:59:18 +02:00
}
pack.showTerrain = showTerrain(spellLevel);
2015-02-26 19:59:18 +02:00
env->apply(&pack);
2015-02-26 19:59:18 +02:00
2015-04-13 05:12:23 +02:00
return ESpellCastResult::OK;
}
///ViewAirMechanics
ViewAirMechanics::ViewAirMechanics(const CSpell * s):
ViewMechanics(s)
{
}
bool ViewAirMechanics::filterObject(const CGObjectInstance * obj, const int32_t spellLevel) const
{
return (obj->ID == Obj::ARTIFACT) || (spellLevel > 1 && obj->ID == Obj::HERO) || (spellLevel > 2 && obj->ID == Obj::TOWN);
}
bool ViewAirMechanics::showTerrain(const int32_t spellLevel) const
{
return false;
}
///ViewEarthMechanics
ViewEarthMechanics::ViewEarthMechanics(const CSpell * s):
ViewMechanics(s)
{
}
bool ViewEarthMechanics::filterObject(const CGObjectInstance * obj, const int32_t spellLevel) const
{
return (obj->ID == Obj::RESOURCE) || (spellLevel > 1 && obj->ID == Obj::MINE);
}
bool ViewEarthMechanics::showTerrain(const int32_t spellLevel) const
{
return spellLevel > 2;
}
VCMI_LIB_NAMESPACE_END