1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-12-16 10:19:47 +02:00
vcmi/lib/spells/effects/Catapult.cpp
2018-10-29 16:56:14 +01:00

158 lines
3.7 KiB
C++

/*
* Catapult.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 "Catapult.h"
#include "Registry.h"
#include "../ISpellMechanics.h"
#include "../../NetPacks.h"
#include "../../battle/IBattleState.h"
#include "../../battle/CBattleInfoCallback.h"
#include "../../battle/Unit.h"
#include "../../mapObjects/CGTownInstance.h"
#include "../../serializer/JsonSerializeFormat.h"
static const std::string EFFECT_NAME = "core:catapult";
namespace spells
{
namespace effects
{
VCMI_REGISTER_SPELL_EFFECT(Catapult, EFFECT_NAME);
Catapult::Catapult()
: LocationEffect(),
targetsToAttack(0)
{
}
Catapult::~Catapult() = default;
bool Catapult::applicable(Problem & problem, const Mechanics * m) const
{
auto town = m->cb->battleGetDefendedTown();
if(nullptr == town)
{
return m->adaptProblem(ESpellCastProblem::NO_APPROPRIATE_TARGET, problem);
}
if(CGTownInstance::NONE == town->fortLevel())
{
return m->adaptProblem(ESpellCastProblem::NO_APPROPRIATE_TARGET, problem);
}
if(m->isSmart() && m->casterSide != BattleSide::ATTACKER)
{
//if spell targeting is smart, then only attacker can use it
return m->adaptProblem(ESpellCastProblem::NO_APPROPRIATE_TARGET, problem);
}
const auto attackableBattleHexes = m->cb->getAttackableBattleHexes();
if(attackableBattleHexes.empty())
return m->adaptProblem(ESpellCastProblem::NO_APPROPRIATE_TARGET, problem);
return true;
}
void Catapult::apply(BattleStateProxy * battleState, RNG & rng, const Mechanics * m, const EffectTarget & /* eTarget */) const
{
//start with all destructible parts
static const std::set<EWallPart::EWallPart> possibleTargets =
{
EWallPart::KEEP,
EWallPart::BOTTOM_TOWER,
EWallPart::BOTTOM_WALL,
EWallPart::BELOW_GATE,
EWallPart::OVER_GATE,
EWallPart::UPPER_WALL,
EWallPart::UPPER_TOWER,
EWallPart::GATE
};
assert(possibleTargets.size() == EWallPart::PARTS_COUNT);
CatapultAttack ca;
ca.attacker = -1;
BattleUnitsChanged removeUnits;
for(int i = 0; i < targetsToAttack; i++)
{
//Any destructible part can be hit regardless of its HP. Multiple hit on same target is allowed.
EWallPart::EWallPart target = *RandomGeneratorUtil::nextItem(possibleTargets, rng);
auto state = m->cb->battleGetWallState(target);
if(state == EWallState::DESTROYED || state == EWallState::NONE)
continue;
CatapultAttack::AttackInfo attackInfo;
attackInfo.damageDealt = 1;
attackInfo.attackedPart = target;
attackInfo.destinationTile = m->cb->wallPartToBattleHex(target);
ca.attackedParts.push_back(attackInfo);
//removing creatures in turrets / keep if one is destroyed
BattleHex posRemove;
switch(target)
{
case EWallPart::KEEP:
posRemove = -2;
break;
case EWallPart::BOTTOM_TOWER:
posRemove = -3;
break;
case EWallPart::UPPER_TOWER:
posRemove = -4;
break;
}
if(posRemove != BattleHex::INVALID && state - attackInfo.damageDealt <= 0) //HP enum subtraction not intuitive, consider using SiegeInfo::applyDamage
{
auto all = m->cb->battleGetUnitsIf([=](const battle::Unit * unit)
{
return !unit->isGhost();
});
for(auto & elem : all)
{
if(elem->getPosition() == posRemove)
{
removeUnits.changedStacks.emplace_back(elem->unitId(), UnitChanges::EOperation::REMOVE);
break;
}
}
}
}
battleState->apply(&ca);
if(!removeUnits.changedStacks.empty())
battleState->apply(&removeUnits);
}
void Catapult::serializeJsonEffect(JsonSerializeFormat & handler)
{
//TODO: add configuration unifying with Catapult ability
handler.serializeInt("targetsToAttack", targetsToAttack);
}
}
}