|
|
@@ -11,17 +11,72 @@
|
|
|
#include "StdInc.h"
|
|
|
#include "AdventureSpellMechanics.h"
|
|
|
|
|
|
+#include "AdventureSpellEffect.h"
|
|
|
+#include "DimensionDoorEffect.h"
|
|
|
+#include "RemoveObjectEffect.h"
|
|
|
+#include "SummonBoatEffect.h"
|
|
|
+#include "TownPortalEffect.h"
|
|
|
+#include "ViewWorldEffect.h"
|
|
|
+
|
|
|
#include "../CSpellHandler.h"
|
|
|
#include "../Problem.h"
|
|
|
|
|
|
+#include "../../json/JsonBonus.h"
|
|
|
#include "../../mapObjects/CGHeroInstance.h"
|
|
|
#include "../../networkPacks/PacksForClient.h"
|
|
|
+#include "../../callback/IGameInfoCallback.h"
|
|
|
|
|
|
VCMI_LIB_NAMESPACE_BEGIN
|
|
|
|
|
|
+static std::unique_ptr<IAdventureSpellEffect> createAdventureEffect(const CSpell * s, const JsonNode & node)
|
|
|
+{
|
|
|
+ const std::string & typeID = node["type"].String();
|
|
|
+
|
|
|
+ if(typeID == "generic")
|
|
|
+ return std::make_unique<AdventureSpellEffect>();
|
|
|
+ if(typeID == "dimensionDoor")
|
|
|
+ return std::make_unique<DimensionDoorEffect>(s, node);
|
|
|
+ if(typeID == "removeObject")
|
|
|
+ return std::make_unique<RemoveObjectEffect>(s, node);
|
|
|
+ if(typeID == "summonBoat")
|
|
|
+ return std::make_unique<SummonBoatEffect>(s, node);
|
|
|
+ if(typeID == "townPortal")
|
|
|
+ return std::make_unique<TownPortalEffect>(s, node);
|
|
|
+ if(typeID == "viewWorld")
|
|
|
+ return std::make_unique<ViewWorldEffect>(s, node);
|
|
|
+
|
|
|
+ return std::make_unique<AdventureSpellEffect>();
|
|
|
+}
|
|
|
+
|
|
|
AdventureSpellMechanics::AdventureSpellMechanics(const CSpell * s)
|
|
|
: IAdventureSpellMechanics(s)
|
|
|
{
|
|
|
+ for(int level = 0; level < GameConstants::SPELL_SCHOOL_LEVELS; level++)
|
|
|
+ {
|
|
|
+ const JsonNode & config = s->getLevelInfo(level).adventureEffect;
|
|
|
+
|
|
|
+ levelOptions[level].effect = createAdventureEffect(s, config);
|
|
|
+ levelOptions[level].castsPerDay = config["castsPerDay"].Integer();
|
|
|
+ levelOptions[level].castsPerDayXL = config["castsPerDayXL"].Integer();
|
|
|
+
|
|
|
+ levelOptions[level].bonuses = s->getLevelInfo(level).effects;
|
|
|
+
|
|
|
+ for(const auto & elem : config["bonuses"].Struct())
|
|
|
+ {
|
|
|
+ auto b = JsonUtils::parseBonus(elem.second);
|
|
|
+ b->sid = BonusSourceID(s->id);
|
|
|
+ b->source = BonusSource::SPELL_EFFECT;
|
|
|
+ levelOptions[level].bonuses.push_back(b);
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+AdventureSpellMechanics::~AdventureSpellMechanics() = default;
|
|
|
+
|
|
|
+const AdventureSpellMechanics::LevelOptions & AdventureSpellMechanics::getLevel(const spells::Caster * caster) const
|
|
|
+{
|
|
|
+ int schoolLevel = caster->getSpellSchoolLevel(owner);
|
|
|
+ return levelOptions.at(schoolLevel);
|
|
|
}
|
|
|
|
|
|
bool AdventureSpellMechanics::canBeCast(spells::Problem & problem, const IGameInfoCallback * cb, const spells::Caster * caster) const
|
|
|
@@ -44,24 +99,30 @@ bool AdventureSpellMechanics::canBeCast(spells::Problem & problem, const IGameIn
|
|
|
|
|
|
if(heroCaster->mana < cost)
|
|
|
return false;
|
|
|
- }
|
|
|
|
|
|
- return canBeCastImpl(problem, cb, caster);
|
|
|
-}
|
|
|
+ std::stringstream cachingStr;
|
|
|
+ cachingStr << "source_" << vstd::to_underlying(BonusSource::SPELL_EFFECT) << "id_" << owner->id.num;
|
|
|
+ int castsAlreadyPerformedThisTurn = caster->getHeroCaster()->getBonuses(Selector::source(BonusSource::SPELL_EFFECT, BonusSourceID(owner->id)), cachingStr.str())->size();
|
|
|
+ int3 mapSize = cb->getMapSize();
|
|
|
+ bool mapSizeIsAtLeastXL = mapSize.x * mapSize.y * mapSize.z >= GameConstants::TOURNAMENT_RULES_DD_MAP_TILES_THRESHOLD;
|
|
|
+ bool useAlternativeLimit = mapSizeIsAtLeastXL && getLevel(caster).castsPerDayXL != 0;
|
|
|
+ int castsLimit = useAlternativeLimit ? getLevel(caster).castsPerDayXL : getLevel(caster).castsPerDay;
|
|
|
|
|
|
-bool AdventureSpellMechanics::canBeCastAt(spells::Problem & problem, const IGameInfoCallback * cb, const spells::Caster * caster, const int3 & pos) const
|
|
|
-{
|
|
|
- return canBeCast(problem, cb, caster) && canBeCastAtImpl(problem, cb, caster, pos);
|
|
|
-}
|
|
|
+ if(castsLimit > 0 && castsLimit <= castsAlreadyPerformedThisTurn ) //limit casts per turn
|
|
|
+ {
|
|
|
+ MetaString message = MetaString::createFromTextID("core.genrltxt.338");
|
|
|
+ caster->getCasterName(message);
|
|
|
+ problem.add(std::move(message));
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
-bool AdventureSpellMechanics::canBeCastImpl(spells::Problem & problem, const IGameInfoCallback * cb, const spells::Caster * caster) const
|
|
|
-{
|
|
|
- return true;
|
|
|
+ return getLevel(caster).effect->canBeCastImpl(problem, cb, caster);
|
|
|
}
|
|
|
|
|
|
-bool AdventureSpellMechanics::canBeCastAtImpl(spells::Problem & problem, const IGameInfoCallback * cb, const spells::Caster * caster, const int3 & pos) const
|
|
|
+bool AdventureSpellMechanics::canBeCastAt(spells::Problem & problem, const IGameInfoCallback * cb, const spells::Caster * caster, const int3 & pos) const
|
|
|
{
|
|
|
- return true;
|
|
|
+ return canBeCast(problem, cb, caster) && getLevel(caster).effect->canBeCastAtImpl(problem, cb, caster, pos);
|
|
|
}
|
|
|
|
|
|
bool AdventureSpellMechanics::adventureCast(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const
|
|
|
@@ -71,7 +132,7 @@ bool AdventureSpellMechanics::adventureCast(SpellCastEnvironment * env, const Ad
|
|
|
if(!canBeCastAt(problem, env->getCb(), parameters.caster, parameters.pos))
|
|
|
return false;
|
|
|
|
|
|
- ESpellCastResult result = beginCast(env, parameters);
|
|
|
+ ESpellCastResult result = getLevel(parameters.caster).effect->beginCast(env, parameters, *this);
|
|
|
|
|
|
if(result == ESpellCastResult::OK)
|
|
|
performCast(env, parameters);
|
|
|
@@ -79,43 +140,21 @@ bool AdventureSpellMechanics::adventureCast(SpellCastEnvironment * env, const Ad
|
|
|
return result != ESpellCastResult::ERROR;
|
|
|
}
|
|
|
|
|
|
-ESpellCastResult AdventureSpellMechanics::applyAdventureEffects(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const
|
|
|
+void AdventureSpellMechanics::giveBonuses(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const
|
|
|
{
|
|
|
- if(owner->hasEffects())
|
|
|
+ for(const auto & b : getLevel(parameters.caster).bonuses)
|
|
|
{
|
|
|
- //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;
|
|
|
- gb.id = ObjectInstanceID(parameters.caster->getCasterUnitId());
|
|
|
- gb.bonus = b;
|
|
|
- env->apply(gb);
|
|
|
- }
|
|
|
-
|
|
|
- return ESpellCastResult::OK;
|
|
|
+ GiveBonus gb;
|
|
|
+ gb.id = ObjectInstanceID(parameters.caster->getCasterUnitId());
|
|
|
+ gb.bonus = *b;
|
|
|
+ gb.bonus.duration = parameters.caster->getEnchantPower(owner);
|
|
|
+ env->apply(gb);
|
|
|
}
|
|
|
- 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::endCast(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const
|
|
|
-{
|
|
|
- // no-op, only for implementation in derived classes
|
|
|
+ GiveBonus gb;
|
|
|
+ gb.id = ObjectInstanceID(parameters.caster->getCasterUnitId());
|
|
|
+ gb.bonus = Bonus(BonusDuration::ONE_DAY, BonusType::NONE, BonusSource::SPELL_EFFECT, 0, BonusSourceID(owner->id));
|
|
|
+ env->apply(gb);
|
|
|
}
|
|
|
|
|
|
void AdventureSpellMechanics::performCast(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const
|
|
|
@@ -128,16 +167,13 @@ void AdventureSpellMechanics::performCast(SpellCastEnvironment * env, const Adve
|
|
|
asc.spellID = owner->id;
|
|
|
env->apply(asc);
|
|
|
|
|
|
- ESpellCastResult result = applyAdventureEffects(env, parameters);
|
|
|
+ ESpellCastResult result = getLevel(parameters.caster).effect->applyAdventureEffects(env, parameters);
|
|
|
|
|
|
- switch(result)
|
|
|
+ if (result == ESpellCastResult::OK)
|
|
|
{
|
|
|
- case ESpellCastResult::OK:
|
|
|
- parameters.caster->spendMana(env, cost);
|
|
|
- endCast(env, parameters);
|
|
|
- break;
|
|
|
- default:
|
|
|
- break;
|
|
|
+ giveBonuses(env, parameters);
|
|
|
+ parameters.caster->spendMana(env, cost);
|
|
|
+ getLevel(parameters.caster).effect->endCast(env, parameters);
|
|
|
}
|
|
|
}
|
|
|
|