1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-12-24 22:14:36 +02:00

Merge pull request #198 from vcmi/SpellsRefactoring8

No reason to not merge this now.
This commit is contained in:
ArseniyShestakov 2016-09-24 05:29:55 +03:00 committed by GitHub
commit 6c63041d1a
28 changed files with 1526 additions and 1184 deletions

View File

@ -103,11 +103,6 @@ void CBattleAI::init(std::shared_ptr<CBattleCallback> CB)
CB->unlockGsWhenWaiting = false;
}
static bool thereRemainsEnemy()
{
return !cbc->battleIsFinished();
}
BattleAction CBattleAI::activeStack( const CStack * stack )
{
LOG_TRACE_PARAMS(logAi, "stack: %s", stack->nodeName()) ;
@ -136,21 +131,20 @@ BattleAction CBattleAI::activeStack( const CStack * stack )
if(cb->battleCanCastSpell())
attemptCastingSpell();
if(!thereRemainsEnemy())
return BattleAction();
if(auto ret = cbc->battleIsFinished())
{
//spellcast may finish battle
//send special preudo-action
BattleAction cancel;
cancel.actionType = Battle::CANCEL;
return cancel;
}
if(auto action = considerFleeingOrSurrendering())
return *action;
if(cb->battleGetStacks(CBattleInfoEssentials::ONLY_ENEMY).empty())
{
//We apparently won battle by casting spell, return defend... (accessing cb may cause trouble)
return BattleAction::makeDefend(stack);
}
PotentialTargets targets(stack);
if(targets.possibleAttacks.size())
{
auto hlp = targets.bestAction();
@ -358,6 +352,7 @@ struct PossibleSpellcast
{
const CSpell *spell;
BattleHex dest;
si32 value;
};
struct CurrentOffensivePotential
@ -430,9 +425,9 @@ void CBattleAI::attemptCastingSpell()
std::vector<PossibleSpellcast> possibleCasts;
for(auto spell : possibleSpells)
{
for(auto hex : getTargetsToConsider(spell))
for(auto hex : getTargetsToConsider(spell, hero))
{
PossibleSpellcast ps = {spell, hex};
PossibleSpellcast ps = {spell, hex, 0};
possibleCasts.push_back(ps);
}
}
@ -458,7 +453,7 @@ void CBattleAI::attemptCastingSpell()
{
int damageDealt = 0, damageReceived = 0;
auto stacksSuffering = ps.spell->getAffectedStacks(cb.get(), ECastingMode::HERO_CASTING, playerID, skillLevel, ps.dest, hero);
auto stacksSuffering = ps.spell->getAffectedStacks(cb.get(), ECastingMode::HERO_CASTING, hero, skillLevel, ps.dest);
if(stacksSuffering.empty())
return -1;
@ -472,41 +467,52 @@ void CBattleAI::attemptCastingSpell()
damageDealt += dmg;
}
const int damageDiff = damageDealt - damageReceived;
const int damageDiff = damageDealt - damageReceived * 10;
LOGFL("Casting %s on hex %d would deal %d damage points among %d stacks.",
ps.spell->name % ps.dest % damageDiff % stacksSuffering.size());
LOGFL("Casting %s on hex %d would deal { %d %d } damage points among %d stacks.",
ps.spell->name % ps.dest % damageDealt % damageReceived % stacksSuffering.size());
//TODO tactic effect too
return damageDiff;
}
case TIMED_EFFECT:
{
StackWithBonuses swb;
swb.stack = cb->battleGetStackByPos(ps.dest);
if(!swb.stack)
auto stacksAffected = ps.spell->getAffectedStacks(cb.get(), ECastingMode::HERO_CASTING, hero, skillLevel, ps.dest);
if(stacksAffected.empty())
return -1;
Bonus pseudoBonus;
pseudoBonus.sid = ps.spell->id;
pseudoBonus.val = skillLevel;
pseudoBonus.turnsRemain = 1; //TODO
CStack::stackEffectToFeature(swb.bonusesToAdd, pseudoBonus);
int totalGain = 0;
HypotheticChangesToBattleState state;
state.bonusesOfStacks[swb.stack] = &swb;
for(const CStack * sta : stacksAffected)
{
StackWithBonuses swb;
swb.stack = sta;
PotentialTargets pt(swb.stack, state);
auto newValue = pt.bestActionValue();
auto oldValue = valueOfStack[swb.stack];
auto gain = newValue - oldValue;
if(swb.stack->owner != playerID) //enemy
gain = -gain;
Bonus pseudoBonus;
pseudoBonus.sid = ps.spell->id;
pseudoBonus.val = skillLevel;
pseudoBonus.turnsRemain = 1; //TODO
CStack::stackEffectToFeature(swb.bonusesToAdd, pseudoBonus);
LOGFL("Casting %s on %s would improve the stack by %d points (from %d to %d)",
ps.spell->name % swb.stack->nodeName() % gain % (oldValue) % (newValue));
HypotheticChangesToBattleState state;
state.bonusesOfStacks[swb.stack] = &swb;
return gain;
PotentialTargets pt(swb.stack, state);
auto newValue = pt.bestActionValue();
auto oldValue = valueOfStack[swb.stack];
auto gain = newValue - oldValue;
if(swb.stack->owner != playerID) //enemy
gain = -gain;
LOGFL("Casting %s on %s would improve the stack by %d points (from %d to %d)",
ps.spell->name % sta->nodeName() % (gain) % (oldValue) % (newValue));
totalGain += gain;
}
LOGFL("Total gain of cast %s at hex %d is %d", ps.spell->name % (ps.dest.hex) % (totalGain));
return totalGain;
}
default:
assert(0);
@ -514,7 +520,15 @@ void CBattleAI::attemptCastingSpell()
}
};
auto castToPerform = *vstd::maxElementByFun(possibleCasts, evaluateSpellcast);
for(PossibleSpellcast & psc : possibleCasts)
psc.value = evaluateSpellcast(psc);
auto pscValue = [] (const PossibleSpellcast &ps) -> int
{
return ps.value;
};
auto castToPerform = *vstd::maxElementByFun(possibleCasts, pscValue);
LOGFL("Best spell is %s. Will cast.", castToPerform.spell->name);
BattleAction spellcast;
@ -527,24 +541,60 @@ void CBattleAI::attemptCastingSpell()
cb->battleMakeAction(&spellcast);
}
std::vector<BattleHex> CBattleAI::getTargetsToConsider( const CSpell *spell ) const
std::vector<BattleHex> CBattleAI::getTargetsToConsider(const CSpell * spell, const ISpellCaster * caster) const
{
if(spell->getTargetType() == CSpell::NO_TARGET)
const CSpell::TargetInfo targetInfo(spell, caster->getSpellSchoolLevel(spell));
std::vector<BattleHex> ret;
if(targetInfo.massive || targetInfo.type == CSpell::NO_TARGET)
{
//Spell can be cast anywhere, all hexes are potentially considerable.
std::vector<BattleHex> ret;
for(int i = 0; i < GameConstants::BFIELD_SIZE; i++)
if(BattleHex(i).isAvailable())
ret.push_back(i);
return ret;
ret.push_back(BattleHex());
}
else
{
//TODO when massive effect -> doesn't matter where cast
return cbc->battleGetPossibleTargets(playerID, spell);
switch(targetInfo.type)
{
case CSpell::CREATURE:
{
for(const CStack * stack : cbc->battleAliveStacks())
{
bool immune = ESpellCastProblem::OK != spell->isImmuneByStack(caster, stack);
bool casterStack = stack->owner == caster->getOwner();
if(!immune)
switch (spell->positiveness)
{
case CSpell::POSITIVE:
if(casterStack || targetInfo.smart)
ret.push_back(stack->position);
break;
case CSpell::NEUTRAL:
ret.push_back(stack->position);
break;
case CSpell::NEGATIVE:
if(!casterStack || targetInfo.smart)
ret.push_back(stack->position);
break;
}
}
}
break;
case CSpell::LOCATION:
{
for(int i = 0; i < GameConstants::BFIELD_SIZE; i++)
if(BattleHex(i).isAvailable())
ret.push_back(i);
}
break;
default:
break;
}
}
return ret;
}
boost::optional<BattleAction> CBattleAI::considerFleeingOrSurrendering()

View File

@ -51,11 +51,11 @@ static bool willSecondHexBlockMoreEnemyShooters(const BattleHex &h1, const Battl
struct ThreatMap
{
{
std::array<std::vector<BattleAttackInfo>, GameConstants::BFIELD_SIZE> threatMap; // [hexNr] -> enemies able to strike
const CStack *endangered;
std::array<int, GameConstants::BFIELD_SIZE> sufferedDamage;
const CStack *endangered;
std::array<int, GameConstants::BFIELD_SIZE> sufferedDamage;
ThreatMap(const CStack *Endangered);
};
@ -89,7 +89,7 @@ const Val getValOr(const std::map<Key, Val> &Map, const Key &key, const Val2 def
auto i = Map.find(key);
if(i != Map.end())
return i->second;
else
else
return defaultValue;
}
@ -111,8 +111,8 @@ class CBattleAI : public CBattleGameInterface
{
int side;
std::shared_ptr<CBattleCallback> cb;
//Previous setting of cb
//Previous setting of cb
bool wasWaitingForRealize, wasUnlockingGs;
void print(const std::string &text) const;
@ -148,6 +148,6 @@ public:
boost::optional<BattleAction> considerFleeingOrSurrendering();
void attemptCastingSpell();
std::vector<BattleHex> getTargetsToConsider(const CSpell *spell) const;
std::vector<BattleHex> getTargetsToConsider(const CSpell *spell, const ISpellCaster * caster) const;
};

View File

@ -98,7 +98,7 @@ CBattleInterface::CBattleInterface(const CCreatureSet * army1, const CCreatureSe
std::shared_ptr<CPlayerInterface> att, std::shared_ptr<CPlayerInterface> defen)
: background(nullptr), queue(nullptr), attackingHeroInstance(hero1), defendingHeroInstance(hero2), animCount(0),
activeStack(nullptr), mouseHoveredStack(nullptr), stackToActivate(nullptr), selectedStack(nullptr), previouslyHoveredHex(-1),
currentlyHoveredHex(-1), attackingHex(-1), stackCanCastSpell(false), creatureCasting(false), spellDestSelectMode(false), spellSelMode(NO_LOCATION), spellToCast(nullptr), sp(nullptr),
currentlyHoveredHex(-1), attackingHex(-1), stackCanCastSpell(false), creatureCasting(false), spellDestSelectMode(false), spellToCast(nullptr), sp(nullptr),
siegeH(nullptr), attackerInt(att), defenderInt(defen), curInt(att), animIDhelper(0),
givenCommand(nullptr), myTurn(false), resWindow(nullptr), moveStarted(false), moveSoundHander(-1), bresult(nullptr)
{
@ -1272,7 +1272,7 @@ void CBattleInterface::spellCast( const BattleSpellCast * sc )
}
//todo: play custom cast animation
displaySpellCast(spellID, BattleHex::INVALID, false);
displaySpellCast(spellID, BattleHex::INVALID);
//playing projectile animation
if(sc->tile.isValid())
@ -1323,12 +1323,9 @@ void CBattleInterface::spellCast( const BattleSpellCast * sc )
}
//displaying message in console
std::vector<std::string> logLines;
spell.prepareBattleLog(curInt->cb.get(), sc, logLines);
for(auto line : logLines)
console->addText(line);
for(const auto & line : sc->battleLog)
if(!console->addText(line.toString()))
logGlobal->warn("Too long battle log line");
waitForAnims();
//mana absorption
@ -1369,6 +1366,30 @@ void CBattleInterface::battleStacksEffectsSet(const SetStackEffect & sse)
}
}
CBattleInterface::PossibleActions CBattleInterface::getCasterAction(const CSpell * spell, const ISpellCaster * caster, ECastingMode::ECastingMode mode) const
{
PossibleActions spellSelMode = ANY_LOCATION;
const CSpell::TargetInfo ti(spell, caster->getSpellSchoolLevel(spell), mode);
if(ti.massive || ti.type == CSpell::NO_TARGET)
spellSelMode = NO_LOCATION;
else if(ti.type == CSpell::LOCATION && ti.clearAffected)
{
spellSelMode = FREE_LOCATION;
}
else if(ti.type == CSpell::CREATURE)
{
spellSelMode = AIMED_SPELL_CREATURE;
}
else if(ti.type == CSpell::OBSTACLE)
{
spellSelMode = OBSTACLE;
}
return spellSelMode;
}
void CBattleInterface::castThisSpell(SpellID spellID)
{
auto ba = new BattleAction;
@ -1381,31 +1402,11 @@ void CBattleInterface::castThisSpell(SpellID spellID)
spellDestSelectMode = true;
creatureCasting = false;
//choosing possible tragets
//choosing possible targets
const CGHeroInstance * castingHero = (attackingHeroInstance->tempOwner == curInt->playerID) ? attackingHeroInstance : defendingHeroInstance;
assert(castingHero); // code below assumes non-null hero
sp = spellID.toSpell();
spellSelMode = ANY_LOCATION;
const CSpell::TargetInfo ti(sp, castingHero->getSpellSchoolLevel(sp));
if(ti.massive || ti.type == CSpell::NO_TARGET)
spellSelMode = NO_LOCATION;
else if(ti.type == CSpell::LOCATION && ti.clearAffected)
{
spellSelMode = FREE_LOCATION;
}
else if(ti.type == CSpell::CREATURE)
{
if(ti.smart)
spellSelMode = selectionTypeByPositiveness(*sp);
else
spellSelMode = ANY_CREATURE;
}
else if(ti.type == CSpell::OBSTACLE)
{
spellSelMode = OBSTACLE;
}
PossibleActions spellSelMode = getCasterAction(sp, castingHero, ECastingMode::HERO_CASTING);
if (spellSelMode == NO_LOCATION) //user does not have to select location
{
@ -1421,13 +1422,12 @@ void CBattleInterface::castThisSpell(SpellID spellID)
}
}
void CBattleInterface::displayEffect(ui32 effect, int destTile, bool areaEffect)
void CBattleInterface::displayEffect(ui32 effect, int destTile)
{
//todo: recheck areaEffect usage
addNewAnim(new CSpellEffectAnimation(this, effect, destTile, 0, 0, false));
}
void CBattleInterface::displaySpellAnimation(const CSpell::TAnimation & animation, BattleHex destinationTile, bool areaEffect)
void CBattleInterface::displaySpellAnimation(const CSpell::TAnimation & animation, BattleHex destinationTile)
{
if(animation.pause > 0)
{
@ -1439,7 +1439,7 @@ void CBattleInterface::displaySpellAnimation(const CSpell::TAnimation & animatio
}
}
void CBattleInterface::displaySpellCast(SpellID spellID, BattleHex destinationTile, bool areaEffect)
void CBattleInterface::displaySpellCast(SpellID spellID, BattleHex destinationTile)
{
const CSpell * spell = spellID.toSpell();
@ -1448,11 +1448,11 @@ void CBattleInterface::displaySpellCast(SpellID spellID, BattleHex destinationTi
for(const CSpell::TAnimation & animation : spell->animationInfo.cast)
{
displaySpellAnimation(animation, destinationTile, areaEffect);
displaySpellAnimation(animation, destinationTile);
}
}
void CBattleInterface::displaySpellEffect(SpellID spellID, BattleHex destinationTile, bool areaEffect)
void CBattleInterface::displaySpellEffect(SpellID spellID, BattleHex destinationTile)
{
const CSpell * spell = spellID.toSpell();
@ -1461,11 +1461,11 @@ void CBattleInterface::displaySpellEffect(SpellID spellID, BattleHex destination
for(const CSpell::TAnimation & animation : spell->animationInfo.affect)
{
displaySpellAnimation(animation, destinationTile, areaEffect);
displaySpellAnimation(animation, destinationTile);
}
}
void CBattleInterface::displaySpellHit(SpellID spellID, BattleHex destinationTile, bool areaEffect)
void CBattleInterface::displaySpellHit(SpellID spellID, BattleHex destinationTile)
{
const CSpell * spell = spellID.toSpell();
@ -1474,7 +1474,7 @@ void CBattleInterface::displaySpellHit(SpellID spellID, BattleHex destinationTil
for(const CSpell::TAnimation & animation : spell->animationInfo.hit)
{
displaySpellAnimation(animation, destinationTile, areaEffect);
displaySpellAnimation(animation, destinationTile);
}
}
@ -1586,9 +1586,10 @@ void CBattleInterface::activateStack()
stackCanCastSpell = true;
if(randomSpellcaster)
creatureSpellToCast = -1; //spell will be set later on cast
creatureSpellToCast = curInt->cb->battleGetRandomStackSpell(CRandomGenerator::getDefault(), s, CBattleInfoCallback::RANDOM_AIMED); //faerie dragon can cast only one spell until their next move
else
creatureSpellToCast = curInt->cb->battleGetRandomStackSpell(CRandomGenerator::getDefault(), s, CBattleInfoCallback::RANDOM_AIMED); //faerie dragon can cast only one spell until their next move
//TODO: what if creature can cast BOTH random genie spell and aimed spell?
//TODO: faerie dragon type spell should be selected by server
}
else
{
@ -1633,26 +1634,15 @@ void CBattleInterface::getPossibleActionsForStack(const CStack * stack)
{
if (stack->hasBonusOfType (Bonus::SPELLCASTER))
{
//TODO: poll possible spells
const CSpell * spell;
BonusList spellBonuses = *stack->getBonuses (Selector::type(Bonus::SPELLCASTER));
for (Bonus * spellBonus : spellBonuses)
if(creatureSpellToCast != -1)
{
spell = CGI->spellh->objects[spellBonus->subtype];
switch (spellBonus->subtype)
{
case SpellID::REMOVE_OBSTACLE:
possibleActions.push_back (OBSTACLE);
break;
default:
possibleActions.push_back (selectionTypeByPositiveness (*spell));
break;
}
const CSpell * spell = SpellID(creatureSpellToCast).toSpell();
PossibleActions act = getCasterAction(spell, stack, ECastingMode::CREATURE_ACTIVE_CASTING);
if(act == NO_LOCATION)
logGlobal->error("NO_LOCATION action target is not yet supported for creatures");
else
possibleActions.push_back(act);
}
std::sort(possibleActions.begin(), possibleActions.end());
auto it = std::unique (possibleActions.begin(), possibleActions.end());
possibleActions.erase (it, possibleActions.end());
}
if (stack->hasBonusOfType (Bonus::RANDOM_SPELLCASTER))
possibleActions.push_back (RANDOM_GENIE_SPELL);
@ -1949,21 +1939,6 @@ void CBattleInterface::bTacticNextStack(const CStack *current /*= nullptr*/)
}
CBattleInterface::PossibleActions CBattleInterface::selectionTypeByPositiveness(const CSpell & spell)
{
switch(spell.positiveness)
{
case CSpell::NEGATIVE :
return HOSTILE_CREATURE_SPELL;
case CSpell::NEUTRAL:
return ANY_CREATURE;
case CSpell::POSITIVE:
return FRIENDLY_CREATURE_SPELL;
}
assert(0);
return NO_LOCATION; //should never happen
}
std::string formatDmgRange(std::pair<ui32, ui32> dmgRange)
{
if(dmgRange.first != dmgRange.second)
@ -2076,27 +2051,10 @@ void CBattleInterface::handleHex(BattleHex myNumber, int eventType)
legalAction = true;
}
break;
case ANY_CREATURE:
if (shere && shere->alive() && isCastingPossibleHere (sactive, shere, myNumber))
case AIMED_SPELL_CREATURE:
if (shere && isCastingPossibleHere (sactive, shere, myNumber))
legalAction = true;
break;
case HOSTILE_CREATURE_SPELL:
if (shere && shere->alive() && !ourStack && isCastingPossibleHere (sactive, shere, myNumber))
legalAction = true;
break;
case FRIENDLY_CREATURE_SPELL:
{
if (isCastingPossibleHere (sactive, shere, myNumber)) //need to be called before sp is determined
{
bool rise = false; //TODO: can you imagine rising hostile creatures?
sp = CGI->spellh->objects[creatureCasting ? creatureSpellToCast : spellToCast->additionalInfo];
if (sp && sp->isRisingSpell())
rise = true;
if (shere && (shere->alive() || rise) && ourStack)
legalAction = true;
}
break;
}
case RANDOM_GENIE_SPELL:
{
if (shere && ourStack && shere != sactive) //only positive spells for other allied creatures
@ -2134,30 +2092,11 @@ void CBattleInterface::handleHex(BattleHex myNumber, int eventType)
notLegal = true;
break;
case FREE_LOCATION:
legalAction = true;
if(!isCastingPossibleHere(sactive, shere, myNumber))
{
ui8 side = curInt->cb->battleGetMySide();
auto hero = curInt->cb->battleGetMyHero();
assert(!creatureCasting); //we assume hero casts this spell
assert(hero);
legalAction = true;
bool hexesOutsideBattlefield = false;
auto tilesThatMustBeClear = sp->rangeInHexes(myNumber, hero->getSpellSchoolLevel(sp), side, &hexesOutsideBattlefield);
for(BattleHex hex : tilesThatMustBeClear)
{
if(curInt->cb->battleGetStackByPos(hex, true) || !!curInt->cb->battleGetObstacleOnPos(hex, false)
|| !hex.isAvailable())
{
legalAction = false;
notLegal = true;
}
}
if(hexesOutsideBattlefield)
{
legalAction = false;
notLegal = true;
}
legalAction = false;
notLegal = true;
}
break;
case CATAPULT:
@ -2281,9 +2220,7 @@ void CBattleInterface::handleHex(BattleHex myNumber, int eventType)
consoleMsg = (boost::format(CGI->generaltexth->allTexts[296]) % shere->getName() % sactive->shots % estDmgText).str();
}
break;
case HOSTILE_CREATURE_SPELL:
case FRIENDLY_CREATURE_SPELL:
case ANY_CREATURE:
case AIMED_SPELL_CREATURE:
sp = CGI->spellh->objects[creatureCasting ? creatureSpellToCast : spellToCast->additionalInfo]; //necessary if creature has random Genie spell at same time
consoleMsg = boost::str(boost::format(CGI->generaltexth->allTexts[27]) % sp->name % shere->getName()); //Cast %s on %s
switch (sp->id)
@ -2324,7 +2261,6 @@ void CBattleInterface::handleHex(BattleHex myNumber, int eventType)
isCastingPossible = true;
break;
case FREE_LOCATION:
//cursorFrame = ECursor::SPELLBOOK;
consoleMsg = boost::str(boost::format(CGI->generaltexth->allTexts[26]) % sp->name); //Cast %s
isCastingPossible = true;
break;
@ -2357,9 +2293,7 @@ void CBattleInterface::handleHex(BattleHex myNumber, int eventType)
{
switch (illegalAction)
{
case ANY_CREATURE:
case HOSTILE_CREATURE_SPELL:
case FRIENDLY_CREATURE_SPELL:
case AIMED_SPELL_CREATURE:
case RANDOM_GENIE_SPELL:
cursorFrame = ECursor::COMBAT_BLOCKED;
consoleMsg = CGI->generaltexth->allTexts[23];
@ -2495,7 +2429,7 @@ bool CBattleInterface::isCastingPossibleHere (const CStack * sactive, const CSta
if (sp)
{
const ISpellCaster * caster = creatureCasting ? dynamic_cast<const ISpellCaster *>(sactive) : dynamic_cast<const ISpellCaster *>(curInt->cb->battleGetMyHero());
const ISpellCaster * caster = creatureCasting ? static_cast<const ISpellCaster *>(sactive) : static_cast<const ISpellCaster *>(curInt->cb->battleGetMyHero());
if(caster == nullptr)
{
isCastingPossible = false;//just in case
@ -3076,18 +3010,27 @@ void CBattleInterface::showHighlightedHexes(SDL_Surface * to)
}
if (settings["battle"]["mouseShadow"].Bool())
{
if(spellToCast) //when casting spell
const ISpellCaster * caster = nullptr;
const CSpell * spell = nullptr;
if(spellToCast)//hero casts spell
{
spell = SpellID(spellToCast->additionalInfo).toSpell();
caster = activeStack->attackerOwned ? attackingHeroInstance : defendingHeroInstance;
}
else if(creatureSpellToCast >= 0 && stackCanCastSpell && creatureCasting)//stack casts spell
{
spell = SpellID(creatureSpellToCast).toSpell();
caster = activeStack;
}
if(caster && spell) //when casting spell
{
//calculating spell school level
const CSpell & spToCast = *CGI->spellh->objects[spellToCast->additionalInfo];
ui8 schoolLevel = 0;
auto caster = activeStack->attackerOwned ? attackingHeroInstance : defendingHeroInstance;
if (caster)
schoolLevel = caster->getSpellSchoolLevel(&spToCast);
ui8 schoolLevel = caster->getSpellSchoolLevel(spell);
// printing shaded hex(es)
auto shaded = spToCast.rangeInHexes(currentlyHoveredHex, schoolLevel, curInt->cb->battleGetMySide());
auto shaded = spell->rangeInHexes(currentlyHoveredHex, schoolLevel, curInt->cb->battleGetMySide());
for(BattleHex shadedHex : shaded)
{
if ((shadedHex.getX() != 0) && (shadedHex.getX() != GameConstants::BFIELD_WIDTH -1))

View File

@ -114,9 +114,10 @@ class CBattleInterface : public CIntObject
INVALID = -1, CREATURE_INFO,
MOVE_TACTICS, CHOOSE_TACTICS_STACK,
MOVE_STACK, ATTACK, WALK_AND_ATTACK, ATTACK_AND_RETURN, SHOOT, //OPEN_GATE, //we can open castle gate during siege
NO_LOCATION, ANY_LOCATION, FRIENDLY_CREATURE_SPELL, HOSTILE_CREATURE_SPELL, RISING_SPELL, ANY_CREATURE, OBSTACLE, TELEPORT, SACRIFICE, RANDOM_GENIE_SPELL,
NO_LOCATION, ANY_LOCATION, OBSTACLE, TELEPORT, SACRIFICE, RANDOM_GENIE_SPELL,
FREE_LOCATION, //used with Force Field and Fire Wall - all tiles affected by spell must be free
CATAPULT, HEAL, RISE_DEMONS
CATAPULT, HEAL, RISE_DEMONS,
AIMED_SPELL_CREATURE
};
private:
SDL_Surface * background, * menu, * amountNormal, * amountNegative, * amountPositive, * amountEffNeutral, * cellBorders, * backgroundWithHexes;
@ -158,7 +159,6 @@ private:
bool stackCanCastSpell; //if true, active stack could possibly cast some target spell
bool creatureCasting; //if true, stack currently aims to cats a spell
bool spellDestSelectMode; //if true, player is choosing destination for his spell - only for GUI / console
PossibleActions spellSelMode;
BattleAction * spellToCast; //spell for which player is choosing destination
const CSpell * sp; //spell pointer for convenience
si32 creatureSpellToCast;
@ -194,7 +194,7 @@ private:
const CBattleInterface * owner;
public:
const CGTownInstance * town; //besieged town
SiegeHelper(const CGTownInstance * siegeTown, const CBattleInterface * _owner); //c-tor
~SiegeHelper(); //d-tor
@ -259,6 +259,7 @@ private:
void redrawBackgroundWithHexes(const CStack * activeStack);
/** End of battle screen blitting methods */
PossibleActions getCasterAction(const CSpell * spell, const ISpellCaster * caster, ECastingMode::ECastingMode mode) const;
public:
std::list<std::pair<CBattleAnimation *, bool> > pendingAnims; //currently displayed animations <anim, initialized>
void addNewAnim(CBattleAnimation * anim); //adds new anim to pendingAnims
@ -334,20 +335,20 @@ public:
void spellCast(const BattleSpellCast * sc); //called when a hero casts a spell
void battleStacksEffectsSet(const SetStackEffect & sse); //called when a specific effect is set to stacks
void castThisSpell(SpellID spellID); //called when player has chosen a spell from spellbook
void displayEffect(ui32 effect, int destTile, bool areaEffect = true); //displays custom effect on the battlefield
void displaySpellCast(SpellID spellID, BattleHex destinationTile, bool areaEffect = true); //displays spell`s cast animation
void displaySpellEffect(SpellID spellID, BattleHex destinationTile, bool areaEffect = true); //displays spell`s affected animation
void displaySpellHit(SpellID spellID, BattleHex destinationTile, bool areaEffect = true); //displays spell`s affected animation
void displaySpellAnimation(const CSpell::TAnimation & animation, BattleHex destinationTile, bool areaEffect = true);
void displayEffect(ui32 effect, int destTile); //displays custom effect on the battlefield
void displaySpellCast(SpellID spellID, BattleHex destinationTile); //displays spell`s cast animation
void displaySpellEffect(SpellID spellID, BattleHex destinationTile); //displays spell`s affected animation
void displaySpellHit(SpellID spellID, BattleHex destinationTile); //displays spell`s affected animation
void displaySpellAnimation(const CSpell::TAnimation & animation, BattleHex destinationTile);
void battleTriggerEffect(const BattleTriggerEffect & bte);
void setBattleCursor(const int myNumber); //really complex and messy, sets attackingHex
void endAction(const BattleAction* action);
void hideQueue();
void showQueue();
PossibleActions selectionTypeByPositiveness(const CSpell & spell);
Rect hexPosition(BattleHex hex) const;
void handleHex(BattleHex myNumber, int eventType);
@ -365,7 +366,7 @@ public:
friend class CPlayerInterface;
friend class CButton;
friend class CInGameConsole;
friend class CBattleResultWindow;
friend class CBattleHero;
friend class CSpellEffectAnimation;

View File

@ -1243,6 +1243,19 @@ const PlayerColor CStack::getOwner() const
return owner;
}
void CStack::getCasterName(MetaString & text) const
{
//always plural name in case of spell cast.
text.addReplacement(MetaString::CRE_PL_NAMES, type->idNumber.num);
}
void CStack::getCastDescription(const CSpell * spell, const std::vector<const CStack*> & attacked, MetaString & text) const
{
text.addTxt(MetaString::GENERAL_TXT, 565);//The %s casts %s
//todo: use text 566 for single creature
getCasterName(text);
text.addReplacement(MetaString::SPELL_NAME, spell->id.toEnum());
}
bool CMP_stack::operator()( const CStack* a, const CStack* b )
{

View File

@ -249,6 +249,10 @@ public:
const PlayerColor getOwner() const override;
void getCasterName(MetaString & text) const override;
void getCastDescription(const CSpell * spell, const std::vector<const CStack *> & attacked, MetaString & text) const override;
///stack will be ghost in next battle state update
void makeGhost();

View File

@ -70,7 +70,8 @@ namespace SiegeStuffThatShouldBeMovedToHandlers // <=== TODO
std::make_pair(45, EWallPart::INDESTRUCTIBLE_PART),
std::make_pair(62, EWallPart::INDESTRUCTIBLE_PART),
std::make_pair(112, EWallPart::INDESTRUCTIBLE_PART),
std::make_pair(147, EWallPart::INDESTRUCTIBLE_PART)
std::make_pair(147, EWallPart::INDESTRUCTIBLE_PART),
std::make_pair(165, EWallPart::INDESTRUCTIBLE_PART)
};
static EWallPart::EWallPart hexToWallPart(BattleHex hex)
@ -345,9 +346,15 @@ const IBonusBearer * CBattleInfoEssentials::getBattleNode() const
return getBattle();
}
ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCastSpell(PlayerColor player, ECastingMode::ECastingMode mode) const
ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCastSpell(const ISpellCaster * caster, ECastingMode::ECastingMode mode) const
{
RETURN_IF_NOT_BATTLE(ESpellCastProblem::INVALID);
if(caster == nullptr)
{
logGlobal->errorStream() << "CBattleInfoCallback::battleCanCastSpell: no spellcaster.";
return ESpellCastProblem::INVALID;
}
const PlayerColor player = caster->getOwner();
const ui8 side = playerToSide(player);
if(!battleDoWeKnowAbout(side))
{
@ -355,16 +362,17 @@ ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCastSpell(Pla
return ESpellCastProblem::INVALID;
}
if(battleTacticDist())
return ESpellCastProblem::ONGOING_TACTIC_PHASE;
switch (mode)
{
case ECastingMode::HERO_CASTING:
{
if(battleTacticDist())
return ESpellCastProblem::ONGOING_TACTIC_PHASE;
if(battleCastSpells(side) > 0)
return ESpellCastProblem::ALREADY_CASTED_THIS_TURN;
auto hero = battleGetFightingHero(side);
auto hero = dynamic_cast<const CGHeroInstance *>(caster);
if(!hero)
return ESpellCastProblem::NO_HERO_TO_CAST_SPELL;
@ -1642,7 +1650,7 @@ ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCastThisSpell
if(!battleDoWeKnowAbout(side))
return ESpellCastProblem::INVALID;
ESpellCastProblem::ESpellCastProblem genProblem = battleCanCastSpell(player, mode);
ESpellCastProblem::ESpellCastProblem genProblem = battleCanCastSpell(caster, mode);
if(genProblem != ESpellCastProblem::OK)
return genProblem;
@ -1663,128 +1671,19 @@ ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCastThisSpell
if(!spell->combatSpell)
return ESpellCastProblem::ADVMAP_SPELL_INSTEAD_OF_BATTLE_SPELL;
const ESpellCastProblem::ESpellCastProblem specificProblem = spell->canBeCast(this, player);
const ESpellCastProblem::ESpellCastProblem specificProblem = spell->canBeCast(this, mode, caster);
if(specificProblem != ESpellCastProblem::OK)
return specificProblem;
if(spell->isNegative() || spell->hasEffects())
{
bool allStacksImmune = true;
//we are interested only in enemy stacks when casting offensive spells
//TODO: should hero be able to cast non-smart negative spell if all enemy stacks are immune?
auto stacks = spell->isNegative() ? battleAliveStacks(!side) : battleAliveStacks();
for(auto stack : stacks)
{
if(ESpellCastProblem::OK == spell->isImmuneByStack(caster, stack))
{
allStacksImmune = false;
break;
}
}
if(allStacksImmune)
return ESpellCastProblem::NO_APPROPRIATE_TARGET;
}
if(battleMaxSpellLevel(side) < spell->level) //effect like Recanter's Cloak or Orb of Inhibition
//effect like Recanter's Cloak. Blocks also passive casting.
//TODO: check creature abilities to block
if(battleMaxSpellLevel(side) < spell->level)
return ESpellCastProblem::SPELL_LEVEL_LIMIT_EXCEEDED;
//checking if there exists an appropriate target
switch(spell->getTargetType())
{
case CSpell::CREATURE:
if(mode == ECastingMode::HERO_CASTING)
{
const CSpell::TargetInfo ti(spell, caster->getSpellSchoolLevel(spell));
bool targetExists = false;
for(const CStack * stack : battleGetAllStacks()) //dead stacks will be immune anyway
{
bool immune = ESpellCastProblem::OK != spell->isImmuneByStack(caster, stack);
bool casterStack = stack->owner == caster->getOwner();
if(!immune)
{
switch (spell->positiveness)
{
case CSpell::POSITIVE:
if(casterStack || !ti.smart)
{
targetExists = true;
break;
}
break;
case CSpell::NEUTRAL:
targetExists = true;
break;
case CSpell::NEGATIVE:
if(!casterStack || !ti.smart)
{
targetExists = true;
break;
}
break;
}
}
}
if(!targetExists)
{
return ESpellCastProblem::NO_APPROPRIATE_TARGET;
}
}
break;
case CSpell::OBSTACLE:
break;
}
return ESpellCastProblem::OK;
}
std::vector<BattleHex> CBattleInfoCallback::battleGetPossibleTargets(PlayerColor player, const CSpell *spell) const
{
std::vector<BattleHex> ret;
RETURN_IF_NOT_BATTLE(ret);
switch(spell->getTargetType())
{
case CSpell::CREATURE:
{
const CGHeroInstance * caster = battleGetFightingHero(playerToSide(player)); //TODO
const CSpell::TargetInfo ti(spell, caster->getSpellSchoolLevel(spell));
for(const CStack * stack : battleAliveStacks())
{
bool immune = ESpellCastProblem::OK != spell->isImmuneByStack(caster, stack);
bool casterStack = stack->owner == caster->getOwner();
if(!immune)
switch (spell->positiveness)
{
case CSpell::POSITIVE:
if(casterStack || ti.smart)
ret.push_back(stack->position);
break;
case CSpell::NEUTRAL:
ret.push_back(stack->position);
break;
case CSpell::NEGATIVE:
if(!casterStack || ti.smart)
ret.push_back(stack->position);
break;
}
}
}
break;
default:
logGlobal->errorStream() << "FIXME " << __FUNCTION__ << " doesn't work with target type " << spell->getTargetType();
}
return ret;
}
ui32 CBattleInfoCallback::battleGetSpellCost(const CSpell * sp, const CGHeroInstance * caster) const
{
RETURN_IF_NOT_BATTLE(-1);
@ -1821,71 +1720,12 @@ ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCastThisSpell
logGlobal->errorStream() << "CBattleInfoCallback::battleCanCastThisSpellHere: no spellcaster.";
return ESpellCastProblem::INVALID;
}
const PlayerColor player = caster->getOwner();
ESpellCastProblem::ESpellCastProblem moreGeneralProblem = battleCanCastThisSpell(caster, spell, mode);
if(moreGeneralProblem != ESpellCastProblem::OK)
return moreGeneralProblem;
if(spell->getTargetType() == CSpell::OBSTACLE)
{
if(spell->id == SpellID::REMOVE_OBSTACLE)
{
if(auto obstacle = battleGetObstacleOnPos(dest, false))
{
switch (obstacle->obstacleType)
{
case CObstacleInstance::ABSOLUTE_OBSTACLE: //cliff-like obstacles can't be removed
case CObstacleInstance::MOAT:
return ESpellCastProblem::NO_APPROPRIATE_TARGET;
case CObstacleInstance::USUAL:
return ESpellCastProblem::OK;
ESpellCastProblem::ESpellCastProblem problem = battleCanCastThisSpell(caster, spell, mode);
if(problem != ESpellCastProblem::OK)
return problem;
// //TODO FIRE_WALL only for ADVANCED level casters
// case CObstacleInstance::FIRE_WALL:
// return
// //TODO other magic obstacles for EXPERT
// case CObstacleInstance::QUICKSAND:
// case CObstacleInstance::LAND_MINE:
// case CObstacleInstance::FORCE_FIELD:
// return
default:
// assert(0);
return ESpellCastProblem::OK;
}
}
}
//isObstacleOnTile(dest)
//
//
//TODO
//assert that it's remove obstacle
//rules whether we can remove spell-created obstacle
return ESpellCastProblem::NO_APPROPRIATE_TARGET;
}
//get dead stack if we cast resurrection or animate dead
const CStack *deadStack = getStackIf([dest](const CStack *s) { return !s->alive() && s->coversPos(dest); });
const CStack *aliveStack = getStackIf([dest](const CStack *s) { return s->alive() && s->coversPos(dest);});
if(spell->isRisingSpell())
{
if(!deadStack && !aliveStack)
return ESpellCastProblem::NO_APPROPRIATE_TARGET;
if(deadStack && deadStack->owner != player) //you can resurrect only your own stacks //FIXME: it includes alive stacks as well
return ESpellCastProblem::NO_APPROPRIATE_TARGET;
}
else if(spell->getTargetType() == CSpell::CREATURE)
{
if(!aliveStack)
return ESpellCastProblem::NO_APPROPRIATE_TARGET;
if(spell->isNegative() && aliveStack->owner == player)
return ESpellCastProblem::NO_APPROPRIATE_TARGET;
if(spell->isPositive() && aliveStack->owner != player)
return ESpellCastProblem::NO_APPROPRIATE_TARGET;
}
return spell->isImmuneAt(this, caster, mode, dest);
return spell->canBeCastAt(this, caster, mode, dest);
}
const CStack * CBattleInfoCallback::getStackIf(std::function<bool(const CStack*)> pred) const
@ -2249,7 +2089,16 @@ bool CPlayerBattleCallback::battleCanCastSpell(ESpellCastProblem::ESpellCastProb
{
RETURN_IF_NOT_BATTLE(false);
ASSERT_IF_CALLED_WITH_PLAYER
auto problem = CBattleInfoCallback::battleCanCastSpell(*player, ECastingMode::HERO_CASTING);
const CGHeroInstance * hero = battleGetMyHero();
if(!hero)
{
if(outProblem)
*outProblem = ESpellCastProblem::NO_HERO_TO_CAST_SPELL;
return false;
}
auto problem = CBattleInfoCallback::battleCanCastSpell(hero, ECastingMode::HERO_CASTING);
if(outProblem)
*outProblem = problem;

View File

@ -212,7 +212,6 @@ public:
TStacks battleAliveStacks(ui8 side) const;
const CStack * battleGetStackByID(int ID, bool onlyAlive = true) const; //returns stack info by given ID
bool battleIsObstacleVisibleForSide(const CObstacleInstance & coi, BattlePerspective::BattlePerspective side) const;
//ESpellCastProblem::ESpellCastProblem battleCanCastSpell(int player, ECastingMode::ECastingMode mode) const; //Checks if player is able to cast spells (at all) at the moment
};
struct DLL_LINKAGE BattleAttackInfo
@ -282,10 +281,9 @@ public:
//*** MAGIC
si8 battleMaxSpellLevel(ui8 side) const; //calculates minimum spell level possible to be cast on battlefield - takes into account artifacts of both heroes; if no effects are set, 0 is returned
ui32 battleGetSpellCost(const CSpell * sp, const CGHeroInstance * caster) const; //returns cost of given spell
ESpellCastProblem::ESpellCastProblem battleCanCastSpell(PlayerColor player, ECastingMode::ECastingMode mode) const; //returns true if there are no general issues preventing from casting a spell
ESpellCastProblem::ESpellCastProblem battleCanCastSpell(const ISpellCaster * caster, ECastingMode::ECastingMode mode) const; //returns true if there are no general issues preventing from casting a spell
ESpellCastProblem::ESpellCastProblem battleCanCastThisSpell(const ISpellCaster * caster, const CSpell * spell, ECastingMode::ECastingMode mode) const; //checks if given player can cast given spell
ESpellCastProblem::ESpellCastProblem battleCanCastThisSpellHere(const ISpellCaster * caster, const CSpell * spell, ECastingMode::ECastingMode mode, BattleHex dest) const; //checks if given player can cast given spell at given tile in given mode
std::vector<BattleHex> battleGetPossibleTargets(PlayerColor player, const CSpell *spell) const;
SpellID battleGetRandomStackSpell(CRandomGenerator & rand, const CStack * stack, ERandomSpell mode) const;
SpellID getRandomBeneficialSpell(CRandomGenerator & rand, const CStack * subject) const;

View File

@ -131,7 +131,6 @@ std::vector<BattleHex> SpellCreatedObstacle::getAffectedTiles() const
return std::vector<BattleHex>(1, pos);
case FORCE_FIELD:
return SpellID(SpellID::FORCE_FIELD).toSpell()->rangeInHexes(pos, spellLevel, casterSide);
//TODO Fire Wall
default:
assert(0);
return std::vector<BattleHex>();

View File

@ -1524,7 +1524,6 @@ struct BattleSpellCast : public CPackForClient//3009
DLL_LINKAGE void applyGs(CGameState *gs);
void applyCl(CClient *cl);
si32 dmgToDisplay; //this amount will be displayed as amount of damage dealt by spell
ui8 side; //which hero did cast spell: 0 - attacker, 1 - defender
ui32 id; //id of spell
ui8 skill; //caster's skill level
@ -1534,9 +1533,12 @@ struct BattleSpellCast : public CPackForClient//3009
std::set<ui32> affectedCres; //ids of creatures affected by this spell, generally used if spell does not set any effect (like dispel or cure)
si32 casterStack;// -1 if not cated by creature, >=0 caster stack ID
bool castByHero; //if true - spell has been cast by hero, otherwise by a creature
std::vector<MetaString> battleLog;
template <typename Handler> void serialize(Handler &h, const int version)
{
h & dmgToDisplay & side & id & skill & manaGained & tile & customEffects & affectedCres & casterStack & castByHero;
h & side & id & skill & manaGained & tile & customEffects & affectedCres & casterStack & castByHero;
h & battleLog;
}
};

View File

@ -1227,6 +1227,15 @@ DLL_LINKAGE void BattleNextRound::applyGs( CGameState *gs )
s->counterAttacksTotalCache = 0;
// new turn effects
s->battleTurnPassed();
if(s->alive() && vstd::contains(s->state, EBattleStackState::CLONED))
{
//cloned stack has special lifetime marker
//check it after bonuses updated in battleTurnPassed()
if(!s->hasBonus(Selector::type(Bonus::NONE).And(Selector::source(Bonus::SPELL_EFFECT, SpellID::CLONE))))
s->makeGhost();
}
}
for(auto &obst : gs->curB->obstacles)
@ -1712,6 +1721,13 @@ DLL_LINKAGE void BattleStacksRemoved::applyGs( CGameState *gs )
toRemove->cloneID = -1;
}
//cleanup remaining clone links if any
for(CStack * s : gs->curB->stacks)
{
if(s->cloneID == toRemove->ID)
s->cloneID = -1;
}
break;
}
}

View File

@ -954,6 +954,25 @@ const PlayerColor CGHeroInstance::getOwner() const
return tempOwner;
}
void CGHeroInstance::getCasterName(MetaString & text) const
{
//FIXME: use local name, MetaString need access to gamestate as hero name is part of map object
text.addReplacement(name);
}
void CGHeroInstance::getCastDescription(const CSpell * spell, const std::vector<const CStack*> & attacked, MetaString & text) const
{
const bool singleTarget = attacked.size() == 1;
const int textIndex = singleTarget ? 195 : 196;
text.addTxt(MetaString::GENERAL_TXT, textIndex);
getCasterName(text);
text.addReplacement(MetaString::SPELL_NAME, spell->id.toEnum());
if(singleTarget)
text.addReplacement(MetaString::CRE_PL_NAMES, attacked.at(0)->getCreature()->idNumber.num);
}
bool CGHeroInstance::canCastThisSpell(const CSpell * spell) const
{
if(nullptr == getArt(ArtifactPosition::SPELLBOOK))

View File

@ -246,6 +246,9 @@ public:
const PlayerColor getOwner() const override;
void getCasterName(MetaString & text) const override;
void getCastDescription(const CSpell * spell, const std::vector<const CStack *> & attacked, MetaString & text) const override;
void deserializationFix();
void initObj(CRandomGenerator & rand) override;

View File

@ -15,12 +15,94 @@
#include "../CRandomGenerator.h"
#include "../mapObjects/CGHeroInstance.h"
#include "../NetPacks.h"
#include "../BattleState.h"
#include "../CGameState.h"
#include "../CGameInfoCallback.h"
#include "../mapping/CMap.h"
#include "../CPlayerState.h"
///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;
}
}
///SummonBoatMechanics
ESpellCastResult SummonBoatMechanics::applyAdventureEffects(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const
{

View File

@ -10,47 +10,62 @@
#pragma once
#include "CDefaultSpellMechanics.h"
#include "ISpellMechanics.h"
class ISpellMechanics;
class DefaultSpellMechanics;
enum class ESpellCastResult
{
OK,
CANCEL,//cast failed but it is not an error
ERROR//internal error occurred
};
class DLL_LINKAGE SummonBoatMechanics : public DefaultSpellMechanics
class DLL_LINKAGE AdventureSpellMechanics: public IAdventureSpellMechanics
{
public:
SummonBoatMechanics(CSpell * s): DefaultSpellMechanics(s){};
AdventureSpellMechanics(CSpell * s): IAdventureSpellMechanics(s){};
bool adventureCast(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const override final;
protected:
///actual adventure cast implementation
virtual ESpellCastResult applyAdventureEffects(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const;
};
class DLL_LINKAGE SummonBoatMechanics : public AdventureSpellMechanics
{
public:
SummonBoatMechanics(CSpell * s): AdventureSpellMechanics(s){};
protected:
ESpellCastResult applyAdventureEffects(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const override;
};
class DLL_LINKAGE ScuttleBoatMechanics : public DefaultSpellMechanics
class DLL_LINKAGE ScuttleBoatMechanics : public AdventureSpellMechanics
{
public:
ScuttleBoatMechanics(CSpell * s): DefaultSpellMechanics(s){};
ScuttleBoatMechanics(CSpell * s): AdventureSpellMechanics(s){};
protected:
ESpellCastResult applyAdventureEffects(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const override;
};
class DLL_LINKAGE DimensionDoorMechanics : public DefaultSpellMechanics
class DLL_LINKAGE DimensionDoorMechanics : public AdventureSpellMechanics
{
public:
DimensionDoorMechanics(CSpell * s): DefaultSpellMechanics(s){};
DimensionDoorMechanics(CSpell * s): AdventureSpellMechanics(s){};
protected:
ESpellCastResult applyAdventureEffects(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const override;
};
class DLL_LINKAGE TownPortalMechanics : public DefaultSpellMechanics
class DLL_LINKAGE TownPortalMechanics : public AdventureSpellMechanics
{
public:
TownPortalMechanics(CSpell * s): DefaultSpellMechanics(s){};
TownPortalMechanics(CSpell * s): AdventureSpellMechanics(s){};
protected:
ESpellCastResult applyAdventureEffects(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const override;
};
class DLL_LINKAGE ViewMechanics : public DefaultSpellMechanics
class DLL_LINKAGE ViewMechanics : public AdventureSpellMechanics
{
public:
ViewMechanics(CSpell * s): DefaultSpellMechanics(s){};
ViewMechanics(CSpell * s): AdventureSpellMechanics(s){};
protected:
ESpellCastResult applyAdventureEffects(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const override;
virtual bool filterObject(const CGObjectInstance * obj, const int spellLevel) const = 0;

View File

@ -41,9 +41,7 @@ void HealingSpellMechanics::applyBattleEffects(const SpellCastEnvironment * env,
int HealingSpellMechanics::calculateHealedHP(const SpellCastEnvironment* env, const BattleSpellCastParameters& parameters, SpellCastContext& ctx) const
{
if(parameters.effectValue != 0)
return parameters.effectValue; //Archangel
return owner->calculateRawEffectValue(parameters.effectLevel, parameters.effectPower); //???
return parameters.getEffectValue();
}
///AntimagicMechanics
@ -62,12 +60,12 @@ void AntimagicMechanics::applyBattle(BattleInfo * battle, const BattleSpellCast
}
///ChainLightningMechanics
std::set<const CStack *> ChainLightningMechanics::getAffectedStacks(SpellTargetingContext & ctx) const
std::vector<const CStack *> ChainLightningMechanics::calculateAffectedStacks(const CBattleInfoCallback * cb, const SpellTargetingContext & ctx) const
{
std::set<const CStack* > attackedCres;
std::vector<const CStack *> res;
std::set<BattleHex> possibleHexes;
for(auto stack : ctx.cb->battleGetAllStacks())
for(auto stack : cb->battleGetAllStacks())
{
if(stack->isValidTarget())
{
@ -82,20 +80,20 @@ std::set<const CStack *> ChainLightningMechanics::getAffectedStacks(SpellTargeti
BattleHex lightningHex = ctx.destination;
for(int i = 0; i < targetsOnLevel[ctx.schoolLvl]; ++i)
{
auto stack = ctx.cb->battleGetStackByPos(lightningHex, true);
auto stack = cb->battleGetStackByPos(lightningHex, true);
if(!stack)
break;
attackedCres.insert (stack);
res.push_back(stack);
for(auto hex : stack->getHexes())
{
possibleHexes.erase(hex); //can't hit same place twice
possibleHexes.erase(hex); //can't hit same stack twice
}
if(possibleHexes.empty()) //not enough targets
break;
lightningHex = BattleHex::getClosestTile(stack->attackerOwned, ctx.destination, possibleHexes);
lightningHex = BattleHex::getClosestTile(stack->attackerOwned, lightningHex, possibleHexes);
}
return attackedCres;
return res;
}
///CloneMechanics
@ -131,6 +129,13 @@ void CloneMechanics::applyBattleEffects(const SpellCastEnvironment * env, const
ssp.val = bsa.newStackID;
ssp.absolute = 1;
env->sendAndApply(&ssp);
SetStackEffect sse;
sse.stacks.push_back(bsa.newStackID);
Bonus lifeTimeMarker(Bonus::N_TURNS, Bonus::NONE, Bonus::SPELL_EFFECT, 0, owner->id.num);
lifeTimeMarker.turnsRemain = parameters.enchantPower;
sse.effect.push_back(lifeTimeMarker);
env->sendAndApply(&sse);
}
ESpellCastProblem::ESpellCastProblem CloneMechanics::isImmuneByStack(const ISpellCaster * caster, const CStack * obj) const
@ -165,15 +170,8 @@ ESpellCastProblem::ESpellCastProblem CloneMechanics::isImmuneByStack(const ISpel
void CureMechanics::applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const
{
DefaultSpellMechanics::applyBattle(battle, packet);
doDispell(battle, packet, [](const Bonus * b) -> bool
{
if(b->source == Bonus::SPELL_EFFECT)
{
CSpell * sp = SpellID(b->sid).toSpell();
return sp->isNegative();
}
return false; //not a spell effect
});
doDispell(battle, packet, dispellSelector);
}
HealingSpellMechanics::EHealLevel CureMechanics::getHealLevel(int effectLevel) const
@ -181,6 +179,25 @@ HealingSpellMechanics::EHealLevel CureMechanics::getHealLevel(int effectLevel) c
return EHealLevel::HEAL;
}
bool CureMechanics::dispellSelector(const Bonus * b)
{
if(b->source == Bonus::SPELL_EFFECT)
{
CSpell * sp = SpellID(b->sid).toSpell();
return sp->isNegative();
}
return false; //not a spell effect
}
ESpellCastProblem::ESpellCastProblem CureMechanics::isImmuneByStack(const ISpellCaster * caster, const CStack * obj) const
{
//Selector method name is ok as cashing string. --AVS
if(!obj->canBeHealed() && !obj->hasBonus(dispellSelector, "CureMechanics::dispellSelector"))
return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
return DefaultSpellMechanics::isImmuneByStack(caster, obj);
}
///DispellMechanics
void DispellMechanics::applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const
{
@ -218,8 +235,6 @@ ESpellCastProblem::ESpellCastProblem DispellMechanics::isImmuneByStack(const ISp
void DispellMechanics::applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
{
DefaultSpellMechanics::applyBattleEffects(env, parameters, ctx);
if(parameters.spellLvl > 2)
{
//expert DISPELL also removes spell-created obstacles
@ -326,8 +341,14 @@ void EarthquakeMechanics::applyBattleEffects(const SpellCastEnvironment * env, c
env->sendAndApply(&ca);
}
ESpellCastProblem::ESpellCastProblem EarthquakeMechanics::canBeCast(const CBattleInfoCallback * cb, PlayerColor player) const
ESpellCastProblem::ESpellCastProblem EarthquakeMechanics::canBeCast(const CBattleInfoCallback * cb, const ECastingMode::ECastingMode mode, const ISpellCaster * caster) const
{
if(mode == ECastingMode::AFTER_ATTACK_CASTING || mode == ECastingMode::SPELL_LIKE_ATTACK || mode == ECastingMode::MAGIC_MIRROR)
{
logGlobal->warn("Invalid spell cast attempt: spell %s, mode %d", owner->name, mode); //should not even try to do it
return ESpellCastProblem::INVALID;
}
if(nullptr == cb->battleGetDefendedTown())
{
return ESpellCastProblem::NO_APPROPRIATE_TARGET;
@ -338,17 +359,27 @@ ESpellCastProblem::ESpellCastProblem EarthquakeMechanics::canBeCast(const CBattl
return ESpellCastProblem::NO_APPROPRIATE_TARGET;
}
CSpell::TargetInfo ti(owner, 0);//TODO: use real spell level
CSpell::TargetInfo ti(owner, caster->getSpellSchoolLevel(owner));
if(ti.smart)
{
//if spell targeting is smart, then only attacker can use it
if(cb->playerToSide(player) != 0)
if(cb->playerToSide(caster->getOwner()) != 0)
return ESpellCastProblem::NO_APPROPRIATE_TARGET;
}
const auto attackableBattleHexes = cb->getAttackableBattleHexes();
if(attackableBattleHexes.empty())
return ESpellCastProblem::NO_APPROPRIATE_TARGET;
return ESpellCastProblem::OK;
}
bool EarthquakeMechanics::requiresCreatureTarget() const
{
return false;
}
///HypnotizeMechanics
ESpellCastProblem::ESpellCastProblem HypnotizeMechanics::isImmuneByStack(const ISpellCaster * caster, const CStack * obj) const
{
@ -366,105 +397,115 @@ ESpellCastProblem::ESpellCastProblem HypnotizeMechanics::isImmuneByStack(const I
}
///ObstacleMechanics
void ObstacleMechanics::applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
ESpellCastProblem::ESpellCastProblem ObstacleMechanics::canBeCast(const CBattleInfoCallback * cb, const SpellTargetingContext & ctx) const
{
auto placeObstacle = [&, this](const BattleHex & pos)
{
static int obstacleIdToGive = parameters.cb->obstacles.size()
? (parameters.cb->obstacles.back()->uniqueID+1)
: 0;
ui8 side = cb->playerToSide(ctx.caster->getOwner());
auto obstacle = std::make_shared<SpellCreatedObstacle>();
switch(owner->id) // :/
{
case SpellID::QUICKSAND:
obstacle->obstacleType = CObstacleInstance::QUICKSAND;
obstacle->turnsRemaining = -1;
obstacle->visibleForAnotherSide = false;
break;
case SpellID::LAND_MINE:
obstacle->obstacleType = CObstacleInstance::LAND_MINE;
obstacle->turnsRemaining = -1;
obstacle->visibleForAnotherSide = false;
break;
case SpellID::FIRE_WALL:
obstacle->obstacleType = CObstacleInstance::FIRE_WALL;
obstacle->turnsRemaining = 2;
obstacle->visibleForAnotherSide = true;
break;
case SpellID::FORCE_FIELD:
obstacle->obstacleType = CObstacleInstance::FORCE_FIELD;
obstacle->turnsRemaining = 2;
obstacle->visibleForAnotherSide = true;
break;
default:
//this function cannot be used with spells that do not create obstacles
assert(0);
}
bool hexesOutsideBattlefield = false;
obstacle->pos = pos;
obstacle->casterSide = parameters.casterSide;
obstacle->ID = owner->id;
obstacle->spellLevel = parameters.effectLevel;
obstacle->casterSpellPower = parameters.effectPower;
obstacle->uniqueID = obstacleIdToGive++;
auto tilesThatMustBeClear = owner->rangeInHexes(ctx.destination, ctx.schoolLvl, side, &hexesOutsideBattlefield);
BattleObstaclePlaced bop;
bop.obstacle = obstacle;
env->sendAndApply(&bop);
};
for(const BattleHex & hex : tilesThatMustBeClear)
if(!isHexAviable(cb, hex, ctx.ti.clearAffected))
return ESpellCastProblem::NO_APPROPRIATE_TARGET;
const BattleHex destination = parameters.getFirstDestinationHex();
if(hexesOutsideBattlefield)
return ESpellCastProblem::NO_APPROPRIATE_TARGET;
switch(owner->id)
{
case SpellID::QUICKSAND:
case SpellID::LAND_MINE:
{
std::vector<BattleHex> availableTiles;
for(int i = 0; i < GameConstants::BFIELD_SIZE; i += 1)
{
BattleHex hex = i;
if(hex.getX() > 2 && hex.getX() < 14 && !(parameters.cb->battleGetStackByPos(hex, false)) && !(parameters.cb->battleGetObstacleOnPos(hex, false)))
availableTiles.push_back(hex);
}
RandomGeneratorUtil::randomShuffle(availableTiles, env->getRandomGenerator());
const int patchesForSkill[] = {4, 4, 6, 8};
const int patchesToPut = std::min<int>(patchesForSkill[parameters.spellLvl], availableTiles.size());
//land mines or quicksand patches are handled as spell created obstacles
for (int i = 0; i < patchesToPut; i++)
placeObstacle(availableTiles.at(i));
}
break;
case SpellID::FORCE_FIELD:
if(!destination.isValid())
{
env->complain("Invalid destination for FORCE_FIELD");
return;
}
placeObstacle(destination);
break;
case SpellID::FIRE_WALL:
{
if(!destination.isValid())
{
env->complain("Invalid destination for FIRE_WALL");
return;
}
//fire wall is build from multiple obstacles - one fire piece for each affected hex
auto affectedHexes = owner->rangeInHexes(destination, parameters.spellLvl, parameters.casterSide);
for(BattleHex hex : affectedHexes)
placeObstacle(hex);
}
break;
default:
assert(0);
}
return ESpellCastProblem::OK;
}
bool ObstacleMechanics::isHexAviable(const CBattleInfoCallback * cb, const BattleHex & hex, const bool mustBeClear)
{
if(!hex.isAvailable())
return false;
if(!mustBeClear)
return true;
if(cb->battleGetStackByPos(hex, true) || !!cb->battleGetObstacleOnPos(hex, false))
return false;
if(nullptr != cb->battleGetDefendedTown() && CGTownInstance::NONE != cb->battleGetDefendedTown()->fortLevel())
{
EWallPart::EWallPart part = cb->battleHexToWallPart(hex);
if(part == EWallPart::INVALID)
return true;//no fortification here
else if(static_cast<int>(part) < 0)
return false;//indestuctible part (cant be checked by battleGetWallState)
else if(part == EWallPart::BOTTOM_TOWER || part == EWallPart::UPPER_TOWER)
return false;//destructible, but should not be available
else if(cb->battleGetWallState(part) != EWallState::DESTROYED && cb->battleGetWallState(part) != EWallState::NONE)
return false;
}
return true;
}
void ObstacleMechanics::placeObstacle(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, const BattleHex & pos) const
{
const int obstacleIdToGive = parameters.cb->obstacles.size()+1;
auto obstacle = std::make_shared<SpellCreatedObstacle>();
setupObstacle(obstacle.get());
obstacle->pos = pos;
obstacle->casterSide = parameters.casterSide;
obstacle->ID = owner->id;
obstacle->spellLevel = parameters.effectLevel;
obstacle->casterSpellPower = parameters.effectPower;
obstacle->uniqueID = obstacleIdToGive;
BattleObstaclePlaced bop;
bop.obstacle = obstacle;
env->sendAndApply(&bop);
}
///PatchObstacleMechanics
void PatchObstacleMechanics::applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
{
std::vector<BattleHex> availableTiles;
for(int i = 0; i < GameConstants::BFIELD_SIZE; i += 1)
{
BattleHex hex = i;
if(isHexAviable(parameters.cb, hex, true))
availableTiles.push_back(hex);
}
RandomGeneratorUtil::randomShuffle(availableTiles, env->getRandomGenerator());
const int patchesForSkill[] = {4, 4, 6, 8};
const int patchesToPut = std::min<int>(patchesForSkill[parameters.spellLvl], availableTiles.size());
//land mines or quicksand patches are handled as spell created obstacles
for (int i = 0; i < patchesToPut; i++)
placeObstacle(env, parameters, availableTiles.at(i));
}
///LandMineMechanics
bool LandMineMechanics::requiresCreatureTarget() const
{
return true;
}
void LandMineMechanics::setupObstacle(SpellCreatedObstacle * obstacle) const
{
obstacle->obstacleType = CObstacleInstance::LAND_MINE;
obstacle->turnsRemaining = -1;
obstacle->visibleForAnotherSide = false;
}
///QuicksandMechanics
bool QuicksandMechanics::requiresCreatureTarget() const
{
return false;
}
void QuicksandMechanics::setupObstacle(SpellCreatedObstacle * obstacle) const
{
obstacle->obstacleType = CObstacleInstance::QUICKSAND;
obstacle->turnsRemaining = -1;
obstacle->visibleForAnotherSide = false;
}
///WallMechanics
std::vector<BattleHex> WallMechanics::rangeInHexes(BattleHex centralHex, ui8 schoolLvl, ui8 side, bool * outDroppedHexes) const
@ -503,19 +544,136 @@ std::vector<BattleHex> WallMechanics::rangeInHexes(BattleHex centralHex, ui8 sch
return ret;
}
///FireWallMechanics
bool FireWallMechanics::requiresCreatureTarget() const
{
return true;
}
void FireWallMechanics::applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
{
const BattleHex destination = parameters.getFirstDestinationHex();
if(!destination.isValid())
{
env->complain("Invalid destination for FIRE_WALL");
return;
}
//firewall is build from multiple obstacles - one fire piece for each affected hex
auto affectedHexes = owner->rangeInHexes(destination, parameters.spellLvl, parameters.casterSide);
for(BattleHex hex : affectedHexes)
placeObstacle(env, parameters, hex);
}
void FireWallMechanics::setupObstacle(SpellCreatedObstacle * obstacle) const
{
obstacle->obstacleType = CObstacleInstance::FIRE_WALL;
obstacle->turnsRemaining = 2;
obstacle->visibleForAnotherSide = true;
}
///ForceFieldMechanics
bool ForceFieldMechanics::requiresCreatureTarget() const
{
return false;
}
void ForceFieldMechanics::applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
{
const BattleHex destination = parameters.getFirstDestinationHex();
if(!destination.isValid())
{
env->complain("Invalid destination for FORCE_FIELD");
return;
}
placeObstacle(env, parameters, destination);
}
void ForceFieldMechanics::setupObstacle(SpellCreatedObstacle * obstacle) const
{
obstacle->obstacleType = CObstacleInstance::FORCE_FIELD;
obstacle->turnsRemaining = 2;
obstacle->visibleForAnotherSide = true;
}
///RemoveObstacleMechanics
void RemoveObstacleMechanics::applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
{
if(auto obstacleToRemove = parameters.cb->battleGetObstacleOnPos(parameters.getFirstDestinationHex(), false))
{
ObstaclesRemoved obr;
obr.obstacles.insert(obstacleToRemove->uniqueID);
env->sendAndApply(&obr);
if(canRemove(obstacleToRemove.get(), parameters.spellLvl))
{
ObstaclesRemoved obr;
obr.obstacles.insert(obstacleToRemove->uniqueID);
env->sendAndApply(&obr);
}
else
{
env->complain("Cant remove this obstacle!");
}
}
else
env->complain("There's no obstacle to remove!");
}
ESpellCastProblem::ESpellCastProblem RemoveObstacleMechanics::canBeCast(const CBattleInfoCallback * cb, const ECastingMode::ECastingMode mode, const ISpellCaster * caster) const
{
if(mode == ECastingMode::AFTER_ATTACK_CASTING || mode == ECastingMode::SPELL_LIKE_ATTACK || mode == ECastingMode::MAGIC_MIRROR)
{
logGlobal->warn("Invalid spell cast attempt: spell %s, mode %d", owner->name, mode); //should not even try to do it
return ESpellCastProblem::INVALID;
}
const int spellLevel = caster->getSpellSchoolLevel(owner);
for(auto obstacle : cb->battleGetAllObstacles())
if(canRemove(obstacle.get(), spellLevel))
return ESpellCastProblem::OK;
return ESpellCastProblem::NO_APPROPRIATE_TARGET;
}
ESpellCastProblem::ESpellCastProblem RemoveObstacleMechanics::canBeCast(const CBattleInfoCallback * cb, const SpellTargetingContext & ctx) const
{
if(auto obstacle = cb->battleGetObstacleOnPos(ctx.destination, false))
if(canRemove(obstacle.get(), ctx.schoolLvl))
return ESpellCastProblem::OK;
return ESpellCastProblem::NO_APPROPRIATE_TARGET;
}
bool RemoveObstacleMechanics::canRemove(const CObstacleInstance * obstacle, const int spellLevel) const
{
switch (obstacle->obstacleType)
{
case CObstacleInstance::ABSOLUTE_OBSTACLE: //cliff-like obstacles can't be removed
case CObstacleInstance::MOAT:
return false;
case CObstacleInstance::USUAL:
return true;
case CObstacleInstance::FIRE_WALL:
if(spellLevel >= 2)
return true;
break;
case CObstacleInstance::QUICKSAND:
case CObstacleInstance::LAND_MINE:
case CObstacleInstance::FORCE_FIELD:
if(spellLevel >= 3)
return true;
break;
default:
break;
}
return false;
}
bool RemoveObstacleMechanics::requiresCreatureTarget() const
{
return false;
}
///RisingSpellMechanics
HealingSpellMechanics::EHealLevel RisingSpellMechanics::getHealLevel(int effectLevel) const
{
//this may be even distinct class
@ -526,18 +684,19 @@ HealingSpellMechanics::EHealLevel RisingSpellMechanics::getHealLevel(int effectL
}
///SacrificeMechanics
ESpellCastProblem::ESpellCastProblem SacrificeMechanics::canBeCast(const CBattleInfoCallback * cb, PlayerColor player) const
ESpellCastProblem::ESpellCastProblem SacrificeMechanics::canBeCast(const CBattleInfoCallback * cb, const ECastingMode::ECastingMode mode, const ISpellCaster * caster) const
{
if(mode == ECastingMode::AFTER_ATTACK_CASTING || mode == ECastingMode::SPELL_LIKE_ATTACK || mode == ECastingMode::MAGIC_MIRROR)
{
logGlobal->warn("Invalid spell cast attempt: spell %s, mode %d", owner->name, mode); //should not even try to do it
return ESpellCastProblem::INVALID;
}
// for sacrifice we have to check for 2 targets (one dead to resurrect and one living to destroy)
bool targetExists = false;
bool targetToSacrificeExists = false;
const CGHeroInstance * caster = nullptr; //todo: use ISpellCaster
if(cb->battleHasHero(cb->playerToSide(player)))
caster = cb->battleGetFightingHero(cb->playerToSide(player));
for(const CStack * stack : cb->battleGetAllStacks())
{
//using isImmuneBy directly as this mechanics does not have overridden immunity check
@ -545,7 +704,7 @@ ESpellCastProblem::ESpellCastProblem SacrificeMechanics::canBeCast(const CBattle
//TODO: check that we really should check immunity for both stacks
ESpellCastProblem::ESpellCastProblem res = owner->internalIsImmune(caster, stack);
const bool immune = ESpellCastProblem::OK != res && ESpellCastProblem::NOT_DECIDED != res;
const bool casterStack = stack->owner == player;
const bool casterStack = stack->owner == caster->getOwner();
if(!immune && casterStack)
{
@ -572,11 +731,7 @@ void SacrificeMechanics::applyBattleEffects(const SpellCastEnvironment * env, co
{
victim = parameters.destinations[1].stackValue;
}
else
{
//todo: remove and report error
victim = parameters.selectedStack;
}
if(nullptr == victim)
{
env->complain("SacrificeMechanics: No stack to sacrifice");
@ -592,29 +747,43 @@ void SacrificeMechanics::applyBattleEffects(const SpellCastEnvironment * env, co
int SacrificeMechanics::calculateHealedHP(const SpellCastEnvironment* env, const BattleSpellCastParameters& parameters, SpellCastContext& ctx) const
{
int res = 0;
const CStack * victim = nullptr;
if(parameters.destinations.size() == 2)
{
victim = parameters.destinations[1].stackValue;
}
else
{
//todo: remove and report error
victim = parameters.selectedStack;
}
if(nullptr == victim)
{
env->complain("SacrificeMechanics: No stack to sacrifice");
return 0;
}
res = (parameters.effectPower + victim->MaxHealth() + owner->getPower(parameters.effectLevel)) * victim->count;
return res;
return (parameters.effectPower + victim->MaxHealth() + owner->getPower(parameters.effectLevel)) * victim->count;
}
bool SacrificeMechanics::requiresCreatureTarget() const
{
return false;//canBeCast do all target existence checks
}
///SpecialRisingSpellMechanics
ESpellCastProblem::ESpellCastProblem SpecialRisingSpellMechanics::canBeCast(const CBattleInfoCallback * cb, const SpellTargetingContext & ctx) const
{
const CStack * stack = cb->getStackIf([ctx](const CStack * s)
{
const bool ownerMatches = !ctx.ti.smart || s->getOwner() == ctx.caster->getOwner();
return ownerMatches && s->isValidTarget(!ctx.ti.onlyAlive) && s->coversPos(ctx.destination);
});
if(nullptr == stack)
return ESpellCastProblem::NO_APPROPRIATE_TARGET;
return ESpellCastProblem::OK;
}
ESpellCastProblem::ESpellCastProblem SpecialRisingSpellMechanics::isImmuneByStack(const ISpellCaster * caster, const CStack * obj) const
{
// following does apply to resurrect and animate dead(?) only
@ -635,13 +804,19 @@ ESpellCastProblem::ESpellCastProblem SpecialRisingSpellMechanics::isImmuneByStac
}
///SummonMechanics
ESpellCastProblem::ESpellCastProblem SummonMechanics::canBeCast(const CBattleInfoCallback * cb, PlayerColor player) const
ESpellCastProblem::ESpellCastProblem SummonMechanics::canBeCast(const CBattleInfoCallback * cb, const ECastingMode::ECastingMode mode, const ISpellCaster * caster) const
{
if(mode == ECastingMode::AFTER_ATTACK_CASTING || mode == ECastingMode::SPELL_LIKE_ATTACK || mode == ECastingMode::MAGIC_MIRROR)
{
logGlobal->warn("Invalid spell cast attempt: spell %s, mode %d", owner->name, mode); //should not even try to do it
return ESpellCastProblem::INVALID;
}
//check if there are summoned elementals of other type
auto otherSummoned = cb->battleGetStacksIf([player, this](const CStack * st)
auto otherSummoned = cb->battleGetStacksIf([caster, this](const CStack * st)
{
return (st->owner == player)
return (st->owner == caster->getOwner())
&& (vstd::contains(st->state, EBattleStackState::SUMMONED))
&& (!vstd::contains(st->state, EBattleStackState::CLONED))
&& (st->getCreature()->idNumber != creatureToSummon);
@ -673,26 +848,33 @@ void SummonMechanics::applyBattleEffects(const SpellCastEnvironment * env, const
env->complain("Summoning didn't summon any!");
}
bool SummonMechanics::requiresCreatureTarget() const
{
return false;
}
///TeleportMechanics
void TeleportMechanics::applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
{
//todo: check legal teleport
if(parameters.destinations.size() == 2)
{
//first destination creature to move
const CStack * target = parameters.destinations[0].stackValue;
if(nullptr == target)
{
env->complain("TeleportMechanics: no stack to teleport");
return;
}
//second destination hex to move to
const BattleHex destination = parameters.destinations[1].hexValue;
//first destination hex to move to
const BattleHex destination = parameters.destinations[0].hexValue;
if(!destination.isValid())
{
env->complain("TeleportMechanics: invalid teleport destination");
return;
}
//second destination creature to move
const CStack * target = parameters.destinations[1].stackValue;
if(nullptr == target)
{
env->complain("TeleportMechanics: no stack to teleport");
return;
}
BattleStackMoved bsm;
bsm.distance = -1;
bsm.stack = target->ID;
@ -704,16 +886,18 @@ void TeleportMechanics::applyBattleEffects(const SpellCastEnvironment * env, con
}
else
{
//todo: remove and report error
BattleStackMoved bsm;
bsm.distance = -1;
bsm.stack = parameters.selectedStack->ID;
std::vector<BattleHex> tiles;
tiles.push_back(parameters.getFirstDestinationHex());
bsm.tilesToMove = tiles;
bsm.teleporting = true;
env->sendAndApply(&bsm);
env->complain("TeleportMechanics: 2 destinations required.");
return;
}
}
ESpellCastProblem::ESpellCastProblem TeleportMechanics::canBeCast(const CBattleInfoCallback* cb, const ECastingMode::ECastingMode mode, const ISpellCaster * caster) const
{
if(mode == ECastingMode::AFTER_ATTACK_CASTING || mode == ECastingMode::SPELL_LIKE_ATTACK || mode == ECastingMode::MAGIC_MIRROR)
{
logGlobal->warn("Invalid spell cast attempt: spell %s, mode %d", owner->name, mode); //should not even try to do it
return ESpellCastProblem::INVALID;
}
return DefaultSpellMechanics::canBeCast(cb, mode, caster);
}

View File

@ -12,6 +12,9 @@
#include "CDefaultSpellMechanics.h"
class CObstacleInstance;
class SpellCreatedObstacle;
class DLL_LINKAGE HealingSpellMechanics : public DefaultSpellMechanics
{
public:
@ -41,7 +44,8 @@ class DLL_LINKAGE ChainLightningMechanics : public DefaultSpellMechanics
{
public:
ChainLightningMechanics(CSpell * s): DefaultSpellMechanics(s){};
std::set<const CStack *> getAffectedStacks(SpellTargetingContext & ctx) const override;
protected:
std::vector<const CStack *> calculateAffectedStacks(const CBattleInfoCallback * cb, const SpellTargetingContext & ctx) const override;
};
class DLL_LINKAGE CloneMechanics : public DefaultSpellMechanics
@ -59,8 +63,11 @@ public:
CureMechanics(CSpell * s): HealingSpellMechanics(s){};
void applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const override final;
ESpellCastProblem::ESpellCastProblem isImmuneByStack(const ISpellCaster * caster, const CStack * obj) const override;
EHealLevel getHealLevel(int effectLevel) const override final;
private:
static bool dispellSelector(const Bonus * b);
};
class DLL_LINKAGE DispellMechanics : public DefaultSpellMechanics
@ -74,11 +81,12 @@ protected:
void applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;
};
class DLL_LINKAGE EarthquakeMechanics : public DefaultSpellMechanics
class DLL_LINKAGE EarthquakeMechanics : public SpecialSpellMechanics
{
public:
EarthquakeMechanics(CSpell * s): DefaultSpellMechanics(s){};
ESpellCastProblem::ESpellCastProblem canBeCast(const CBattleInfoCallback * cb, PlayerColor player) const override;
EarthquakeMechanics(CSpell * s): SpecialSpellMechanics(s){};
ESpellCastProblem::ESpellCastProblem canBeCast(const CBattleInfoCallback * cb, const ECastingMode::ECastingMode mode, const ISpellCaster * caster) const override;
bool requiresCreatureTarget() const override;
protected:
void applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;
};
@ -90,15 +98,43 @@ public:
ESpellCastProblem::ESpellCastProblem isImmuneByStack(const ISpellCaster * caster, const CStack * obj) const override;
};
class DLL_LINKAGE ObstacleMechanics : public DefaultSpellMechanics
class DLL_LINKAGE ObstacleMechanics : public SpecialSpellMechanics
{
public:
ObstacleMechanics(CSpell * s): DefaultSpellMechanics(s){};
ObstacleMechanics(CSpell * s): SpecialSpellMechanics(s){};
ESpellCastProblem::ESpellCastProblem canBeCast(const CBattleInfoCallback * cb, const SpellTargetingContext & ctx) const override;
protected:
static bool isHexAviable(const CBattleInfoCallback * cb, const BattleHex & hex, const bool mustBeClear);
void placeObstacle(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, const BattleHex & pos) const;
virtual void setupObstacle(SpellCreatedObstacle * obstacle) const = 0;
};
class PatchObstacleMechanics : public ObstacleMechanics
{
public:
PatchObstacleMechanics(CSpell * s): ObstacleMechanics(s){};
protected:
void applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;
};
class DLL_LINKAGE LandMineMechanics : public PatchObstacleMechanics
{
public:
LandMineMechanics(CSpell * s): PatchObstacleMechanics(s){};
bool requiresCreatureTarget() const override;
protected:
void setupObstacle(SpellCreatedObstacle * obstacle) const override;
};
class DLL_LINKAGE QuicksandMechanics : public PatchObstacleMechanics
{
public:
QuicksandMechanics(CSpell * s): PatchObstacleMechanics(s){};
bool requiresCreatureTarget() const override;
protected:
void setupObstacle(SpellCreatedObstacle * obstacle) const override;
};
class DLL_LINKAGE WallMechanics : public ObstacleMechanics
{
public:
@ -106,12 +142,37 @@ public:
std::vector<BattleHex> rangeInHexes(BattleHex centralHex, ui8 schoolLvl, ui8 side, bool *outDroppedHexes = nullptr) const override;
};
class DLL_LINKAGE RemoveObstacleMechanics : public DefaultSpellMechanics
class DLL_LINKAGE FireWallMechanics : public WallMechanics
{
public:
RemoveObstacleMechanics(CSpell * s): DefaultSpellMechanics(s){};
FireWallMechanics(CSpell * s): WallMechanics(s){};
bool requiresCreatureTarget() const override;
protected:
void applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;
void setupObstacle(SpellCreatedObstacle * obstacle) const override;
};
class DLL_LINKAGE ForceFieldMechanics : public WallMechanics
{
public:
ForceFieldMechanics(CSpell * s): WallMechanics(s){};
bool requiresCreatureTarget() const override;
protected:
void applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;
void setupObstacle(SpellCreatedObstacle * obstacle) const override;
};
class DLL_LINKAGE RemoveObstacleMechanics : public SpecialSpellMechanics
{
public:
RemoveObstacleMechanics(CSpell * s): SpecialSpellMechanics(s){};
ESpellCastProblem::ESpellCastProblem canBeCast(const CBattleInfoCallback * cb, const ECastingMode::ECastingMode mode, const ISpellCaster * caster) const override;
ESpellCastProblem::ESpellCastProblem canBeCast(const CBattleInfoCallback * cb, const SpellTargetingContext & ctx) const override;
bool requiresCreatureTarget() const override;
protected:
void applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;
private:
bool canRemove(const CObstacleInstance * obstacle, const int spellLevel) const;
};
///all rising spells
@ -119,6 +180,7 @@ class DLL_LINKAGE RisingSpellMechanics : public HealingSpellMechanics
{
public:
RisingSpellMechanics(CSpell * s): HealingSpellMechanics(s){};
EHealLevel getHealLevel(int effectLevel) const override;
};
@ -127,26 +189,29 @@ class DLL_LINKAGE SacrificeMechanics : public RisingSpellMechanics
public:
SacrificeMechanics(CSpell * s): RisingSpellMechanics(s){};
ESpellCastProblem::ESpellCastProblem canBeCast(const CBattleInfoCallback * cb, PlayerColor player) const override;
ESpellCastProblem::ESpellCastProblem canBeCast(const CBattleInfoCallback * cb, const ECastingMode::ECastingMode mode, const ISpellCaster * caster) const override;
bool requiresCreatureTarget() const override;
protected:
void applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;
int calculateHealedHP(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;
};
///all rising spells but SACRIFICE
///ANIMATE_DEAD and RESURRECTION
class DLL_LINKAGE SpecialRisingSpellMechanics : public RisingSpellMechanics
{
public:
SpecialRisingSpellMechanics(CSpell * s): RisingSpellMechanics(s){};
ESpellCastProblem::ESpellCastProblem canBeCast(const CBattleInfoCallback * cb, const SpellTargetingContext & ctx) const override;
ESpellCastProblem::ESpellCastProblem isImmuneByStack(const ISpellCaster * caster, const CStack * obj) const override;
};
class DLL_LINKAGE SummonMechanics : public DefaultSpellMechanics
class DLL_LINKAGE SummonMechanics : public SpecialSpellMechanics
{
public:
SummonMechanics(CSpell * s, CreatureID cre): DefaultSpellMechanics(s), creatureToSummon(cre){};
SummonMechanics(CSpell * s, CreatureID cre): SpecialSpellMechanics(s), creatureToSummon(cre){};
ESpellCastProblem::ESpellCastProblem canBeCast(const CBattleInfoCallback * cb, PlayerColor player) const override;
ESpellCastProblem::ESpellCastProblem canBeCast(const CBattleInfoCallback * cb, const ECastingMode::ECastingMode mode, const ISpellCaster * caster) const override;
bool requiresCreatureTarget() const override;
protected:
void applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;
private:
@ -157,6 +222,8 @@ class DLL_LINKAGE TeleportMechanics: public DefaultSpellMechanics
{
public:
TeleportMechanics(CSpell * s): DefaultSpellMechanics(s){};
ESpellCastProblem::ESpellCastProblem canBeCast(const CBattleInfoCallback * cb, const ECastingMode::ECastingMode mode, const ISpellCaster * caster) const override;
protected:
void applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;
};

View File

@ -12,7 +12,6 @@
#include "CDefaultSpellMechanics.h"
#include "../NetPacks.h"
#include "../BattleState.h"
#include "../CGeneralTextHandler.h"
@ -119,6 +118,132 @@ namespace SRSLPraserHelpers
}
}
SpellCastContext::SpellCastContext(const DefaultSpellMechanics * mechanics_, const SpellCastEnvironment * env_, const BattleSpellCastParameters & parameters_):
mechanics(mechanics_), env(env_), attackedCres(), sc(), si(), parameters(parameters_), otherHero(nullptr), spellCost(0), damageToDisplay(0)
{
sc.side = parameters.casterSide;
sc.id = mechanics->owner->id;
sc.skill = parameters.spellLvl;
sc.tile = parameters.getFirstDestinationHex();
sc.castByHero = parameters.mode == ECastingMode::HERO_CASTING;
sc.casterStack = (parameters.casterStack ? parameters.casterStack->ID : -1);
sc.manaGained = 0;
//check it there is opponent hero
const ui8 otherSide = 1-parameters.casterSide;
if(parameters.cb->battleHasHero(otherSide))
otherHero = parameters.cb->battleGetFightingHero(otherSide);
logGlobal->debugStream() << "Started spell cast. Spell: " << mechanics->owner->name << "; mode:" << parameters.mode;
}
SpellCastContext::~SpellCastContext()
{
logGlobal->debugStream() << "Finished spell cast. Spell: " << mechanics->owner->name << "; mode:" << parameters.mode;
}
void SpellCastContext::addDamageToDisplay(const si32 value)
{
damageToDisplay += value;
}
void SpellCastContext::setDamageToDisplay(const si32 value)
{
damageToDisplay = value;
}
void SpellCastContext::prepareBattleLog()
{
bool displayDamage = true;
mechanics->battleLog(sc.battleLog, parameters, attackedCres, damageToDisplay, displayDamage);
displayDamage = displayDamage && damageToDisplay > 0;
if(displayDamage)
{
MetaString line;
line.addTxt(MetaString::GENERAL_TXT, 376);
line.addReplacement(MetaString::SPELL_NAME, mechanics->owner->id.toEnum());
line.addReplacement(damageToDisplay);
sc.battleLog.push_back(line);
}
}
void SpellCastContext::beforeCast()
{
//calculate spell cost
if(parameters.mode == ECastingMode::HERO_CASTING)
{
spellCost = parameters.cb->battleGetSpellCost(mechanics->owner, parameters.casterHero);
if(nullptr != otherHero) //handle mana channel
{
int manaChannel = 0;
for(const CStack * stack : parameters.cb->battleGetAllStacks(true)) //TODO: shouldn't bonus system handle it somehow?
{
if(stack->owner == otherHero->tempOwner)
{
vstd::amax(manaChannel, stack->valOfBonuses(Bonus::MANA_CHANNELING));
}
}
sc.manaGained = (manaChannel * spellCost) / 100;
}
logGlobal->debugStream() << "spellCost: " << spellCost;
}
}
void SpellCastContext::afterCast()
{
for(auto sta : attackedCres)
{
sc.affectedCres.insert(sta->ID);
}
prepareBattleLog();
env->sendAndApply(&sc);
if(parameters.mode == ECastingMode::HERO_CASTING)
{
//spend mana
SetMana sm;
sm.absolute = false;
sm.hid = parameters.casterHero->id;
sm.val = -spellCost;
env->sendAndApply(&sm);
if(sc.manaGained > 0)
{
assert(otherHero);
sm.hid = otherHero->id;
sm.val = sc.manaGained;
env->sendAndApply(&sm);
}
}
else if (parameters.mode == ECastingMode::CREATURE_ACTIVE_CASTING || parameters.mode == ECastingMode::ENCHANTER_CASTING)
{
//reduce number of casts remaining
assert(parameters.casterStack);
BattleSetStackProperty ssp;
ssp.stackID = parameters.casterStack->ID;
ssp.which = BattleSetStackProperty::CASTS;
ssp.val = -1;
ssp.absolute = false;
env->sendAndApply(&ssp);
}
if(!si.stacks.empty()) //after spellcast info shows
env->sendAndApply(&si);
}
///DefaultSpellMechanics
void DefaultSpellMechanics::applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const
{
@ -145,218 +270,27 @@ void DefaultSpellMechanics::applyBattle(BattleInfo * battle, const BattleSpellCa
}
}
bool DefaultSpellMechanics::adventureCast(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const
void DefaultSpellMechanics::battleCast(const SpellCastEnvironment * env, const BattleSpellCastParameters & 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 DefaultSpellMechanics::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;
}
}
void DefaultSpellMechanics::battleCast(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters) const
{
logGlobal->debugStream() << "Started spell cast. Spell: "<<owner->name<<"; mode:"<<parameters.mode;
if(nullptr == parameters.caster)
{
env->complain("No spell-caster provided.");
return;
}
BattleSpellCast sc;
prepareBattleCast(parameters, sc);
//check it there is opponent hero
const ui8 otherSide = 1-parameters.casterSide;
const CGHeroInstance * otherHero = nullptr;
if(parameters.cb->battleHasHero(otherSide))
otherHero = parameters.cb->battleGetFightingHero(otherSide);
int spellCost = 0;
//calculate spell cost
if(parameters.mode == ECastingMode::HERO_CASTING)
{
spellCost = parameters.cb->battleGetSpellCost(owner, parameters.casterHero);
if(nullptr != otherHero) //handle mana channel
{
int manaChannel = 0;
for(const CStack * stack : parameters.cb->battleGetAllStacks(true)) //TODO: shouldn't bonus system handle it somehow?
{
if(stack->owner == otherHero->tempOwner)
{
vstd::amax(manaChannel, stack->valOfBonuses(Bonus::MANA_CHANNELING));
}
}
sc.manaGained = (manaChannel * spellCost) / 100;
}
}
logGlobal->debugStream() << "spellCost: " << spellCost;
//calculating affected creatures for all spells
//must be vector, as in Chain Lightning order matters
std::vector<const CStack*> attackedCres; //CStack vector is somewhat more suitable than ID vector
auto creatures = owner->getAffectedStacks(parameters.cb, parameters.mode, parameters.casterColor, parameters.spellLvl, parameters.getFirstDestinationHex(), parameters.caster);
std::copy(creatures.begin(), creatures.end(), std::back_inserter(attackedCres));
logGlobal->debugStream() << "will affect: " << attackedCres.size() << " stacks";
std::vector <const CStack*> reflected;//for magic mirror
//checking if creatures resist
handleResistance(env, attackedCres, sc);
//it is actual spell and can be reflected to single target, no recurrence
const bool tryMagicMirror = owner->isNegative() && owner->level && owner->getLevelInfo(0).range == "0";
if(tryMagicMirror)
{
for(auto s : attackedCres)
{
const int mirrorChance = (s)->valOfBonuses(Bonus::MAGIC_MIRROR);
if(env->getRandomGenerator().nextInt(99) < mirrorChance)
reflected.push_back(s);
}
vstd::erase_if(attackedCres, [&reflected](const CStack * s)
{
return vstd::contains(reflected, s);
});
cast(env, parameters, reflected);
for(auto s : reflected)
{
BattleSpellCast::CustomEffect effect;
effect.effect = 3;
effect.stack = s->ID;
sc.customEffects.push_back(effect);
}
}
for(auto cre : attackedCres)
{
sc.affectedCres.insert(cre->ID);
}
StacksInjured si;
SpellCastContext ctx(attackedCres, sc, si);
applyBattleEffects(env, parameters, ctx);
env->sendAndApply(&sc);
//spend mana
if(parameters.mode == ECastingMode::HERO_CASTING)
{
SetMana sm;
sm.absolute = false;
sm.hid = parameters.casterHero->id;
sm.val = -spellCost;
env->sendAndApply(&sm);
if(sc.manaGained > 0)
{
assert(otherHero);
sm.hid = otherHero->id;
sm.val = sc.manaGained;
env->sendAndApply(&sm);
}
}
if(!si.stacks.empty()) //after spellcast info shows
env->sendAndApply(&si);
//reduce number of casts remaining
//TODO: this should be part of BattleSpellCast apply
if (parameters.mode == ECastingMode::CREATURE_ACTIVE_CASTING || parameters.mode == ECastingMode::ENCHANTER_CASTING)
{
assert(parameters.casterStack);
BattleSetStackProperty ssp;
ssp.stackID = parameters.casterStack->ID;
ssp.which = BattleSetStackProperty::CASTS;
ssp.val = -1;
ssp.absolute = false;
env->sendAndApply(&ssp);
}
logGlobal->debugStream() << "Finished spell cast. Spell: "<<owner->name<<"; mode:"<<parameters.mode;
//Magic Mirror effect
for(auto & attackedCre : reflected)
{
if(parameters.mode == ECastingMode::MAGIC_MIRROR)
{
logGlobal->error("Magic mirror recurrence!");
return;
}
TStacks mirrorTargets = parameters.cb->battleGetStacksIf([this, parameters](const CStack * battleStack)
{
//Get all enemy stacks. Magic mirror can reflect to immune creature (with no effect)
@ -367,48 +301,68 @@ void DefaultSpellMechanics::battleCast(const SpellCastEnvironment * env, BattleS
{
int targetHex = (*RandomGeneratorUtil::nextItem(mirrorTargets, env->getRandomGenerator()))->position;
BattleSpellCastParameters mirrorParameters(parameters.cb, attackedCre, owner);
mirrorParameters.spellLvl = 0;
BattleSpellCastParameters mirrorParameters(parameters, attackedCre);
mirrorParameters.aimToHex(targetHex);
mirrorParameters.mode = ECastingMode::MAGIC_MIRROR;
mirrorParameters.selectedStack = nullptr;
mirrorParameters.spellLvl = parameters.spellLvl;
mirrorParameters.effectLevel = parameters.effectLevel;
mirrorParameters.effectPower = parameters.effectPower;
mirrorParameters.effectValue = parameters.effectValue;
mirrorParameters.enchantPower = parameters.enchantPower;
castMagicMirror(env, mirrorParameters);
mirrorParameters.cast(env);
}
}
}
void DefaultSpellMechanics::battleLogSingleTarget(std::vector<std::string> & logLines, const BattleSpellCast * packet,
const std::string & casterName, const CStack * attackedStack, bool & displayDamage) const
void DefaultSpellMechanics::cast(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, std::vector <const CStack*> & reflected) const
{
const std::string attackedName = attackedStack->getName();
const std::string attackedNameSing = attackedStack->getCreature()->nameSing;
const std::string attackedNamePl = attackedStack->getCreature()->namePl;
SpellCastContext ctx(this, env, parameters);
auto getPluralFormat = [attackedStack](const int baseTextID) -> boost::format
ctx.beforeCast();
ctx.attackedCres = owner->getAffectedStacks(parameters.cb, parameters.mode, parameters.caster, parameters.spellLvl, parameters.getFirstDestinationHex());
logGlobal->debugStream() << "will affect " << ctx.attackedCres.size() << " stacks";
handleResistance(env, ctx);
if(parameters.mode != ECastingMode::MAGIC_MIRROR)
handleMagicMirror(env, ctx, reflected);
applyBattleEffects(env, parameters, ctx);
ctx.afterCast();
}
void DefaultSpellMechanics::battleLog(std::vector<MetaString> & logLines, const BattleSpellCastParameters & parameters,
const std::vector<const CStack *> & attacked, const si32 damageToDisplay, bool & displayDamage) const
{
if(attacked.size() != 1)
{
return boost::format(VLC->generaltexth->allTexts[(attackedStack->count > 1 ? baseTextID + 1 : baseTextID)]);
displayDamage = true;
battleLogDefault(logLines, parameters, attacked);
return;
}
auto attackedStack = attacked.at(0);
auto getPluralFormat = [attackedStack](const int baseTextID) -> si32
{
return attackedStack->count > 1 ? baseTextID + 1 : baseTextID;
};
auto logSimple = [&logLines, getPluralFormat, attackedName](const int baseTextID)
auto logSimple = [attackedStack, &logLines, getPluralFormat](const int baseTextID)
{
boost::format fmt = getPluralFormat(baseTextID);
fmt % attackedName;
logLines.push_back(fmt.str());
MetaString line;
line.addTxt(MetaString::GENERAL_TXT, getPluralFormat(baseTextID));
line.addReplacement(*attackedStack);
logLines.push_back(line);
};
auto logPlural = [&logLines, attackedNamePl](const int baseTextID)
auto logPlural = [attackedStack, &logLines, getPluralFormat](const int baseTextID)
{
boost::format fmt(VLC->generaltexth->allTexts[baseTextID]);
fmt % attackedNamePl;
logLines.push_back(fmt.str());
MetaString line;
line.addTxt(MetaString::GENERAL_TXT, baseTextID);
line.addReplacement(MetaString::CRE_PL_NAMES, attackedStack->getCreature()->idNumber.num);
logLines.push_back(line);
};
displayDamage = false; //in most following cases damage info text is custom
switch(owner->id)
{
case SpellID::STONE_GAZE:
@ -428,74 +382,78 @@ void DefaultSpellMechanics::battleLogSingleTarget(std::vector<std::string> & log
break;
case SpellID::AGE:
{
boost::format text = getPluralFormat(551);
text % attackedName;
//The %s shrivel with age, and lose %d hit points."
MetaString line;
line.addTxt(MetaString::GENERAL_TXT, getPluralFormat(551));
line.addReplacement(MetaString::CRE_PL_NAMES, attackedStack->getCreature()->idNumber.num);
//todo: display effect from only this cast
TBonusListPtr bl = attackedStack->getBonuses(Selector::type(Bonus::STACK_HEALTH));
const int fullHP = bl->totalValue();
bl->remove_if(Selector::source(Bonus::SPELL_EFFECT, SpellID::AGE));
text % (fullHP - bl->totalValue());
logLines.push_back(text.str());
line.addReplacement(fullHP - bl->totalValue());
logLines.push_back(line);
}
break;
case SpellID::THUNDERBOLT:
{
logPlural(367);
MetaString line;
//todo: handle newlines in metastring
std::string text = VLC->generaltexth->allTexts[343].substr(1, VLC->generaltexth->allTexts[343].size() - 1); //Does %d points of damage.
boost::algorithm::replace_first(text, "%d", boost::lexical_cast<std::string>(packet->dmgToDisplay)); //no more text afterwards
logLines.push_back(text);
line << text;
line.addReplacement(damageToDisplay); //no more text afterwards
logLines.push_back(line);
}
break;
case SpellID::DISPEL_HELPFUL_SPELLS:
logPlural(555);
break;
case SpellID::DEATH_STARE:
if (packet->dmgToDisplay > 0)
if (damageToDisplay > 0)
{
std::string text;
if (packet->dmgToDisplay > 1)
MetaString line;
if (damageToDisplay > 1)
{
text = VLC->generaltexth->allTexts[119]; //%d %s die under the terrible gaze of the %s.
boost::algorithm::replace_first(text, "%d", boost::lexical_cast<std::string>(packet->dmgToDisplay));
boost::algorithm::replace_first(text, "%s", attackedNamePl);
line.addTxt(MetaString::GENERAL_TXT, 119); //%d %s die under the terrible gaze of the %s.
line.addReplacement(damageToDisplay);
line.addReplacement(MetaString::CRE_PL_NAMES, attackedStack->getCreature()->idNumber.num);
}
else
{
text = VLC->generaltexth->allTexts[118]; //One %s dies under the terrible gaze of the %s.
boost::algorithm::replace_first(text, "%s", attackedNameSing);
line.addTxt(MetaString::GENERAL_TXT, 118); //One %s dies under the terrible gaze of the %s.
line.addReplacement(MetaString::CRE_SING_NAMES, attackedStack->getCreature()->idNumber.num);
}
boost::algorithm::replace_first(text, "%s", casterName); //casting stack
logLines.push_back(text);
parameters.caster->getCasterName(line);
logLines.push_back(line);
}
break;
default:
{
boost::format text(VLC->generaltexth->allTexts[565]); //The %s casts %s
text % casterName % owner->name;
displayDamage = true;
logLines.push_back(text.str());
}
displayDamage = true;
battleLogDefault(logLines, parameters, attacked);
break;
}
}
void DefaultSpellMechanics::battleLogDefault(std::vector<MetaString> & logLines, const BattleSpellCastParameters & parameters, const std::vector<const CStack*> & attacked) const
{
MetaString line;
parameters.caster->getCastDescription(owner, attacked, line);
logLines.push_back(line);
}
void DefaultSpellMechanics::applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
{
//applying effects
if(owner->isOffensiveSpell())
{
int spellDamage = parameters.effectValue;
const int rawDamage = parameters.getEffectValue();
int chainLightningModifier = 0;
for(auto & attackedCre : ctx.attackedCres)
{
BattleStackAttacked bsa;
if(spellDamage != 0)
bsa.damageAmount = owner->adjustRawDamage(parameters.caster, attackedCre, spellDamage) >> chainLightningModifier;
else
bsa.damageAmount = owner->calculateDamage(parameters.caster, attackedCre, parameters.effectLevel, parameters.effectPower) >> chainLightningModifier;
ctx.sc.dmgToDisplay += bsa.damageAmount;
bsa.damageAmount = owner->adjustRawDamage(parameters.caster, attackedCre, rawDamage) >> chainLightningModifier;
ctx.addDamageToDisplay(bsa.damageAmount);
bsa.stackAttacked = (attackedCre)->ID;
if(parameters.mode == ECastingMode::ENCHANTER_CASTING) //multiple damage spells cast
@ -598,7 +556,6 @@ void DefaultSpellMechanics::applyBattleEffects(const SpellCastEnvironment * env,
if(!sse.stacks.empty())
env->sendAndApply(&sse);
}
}
@ -609,7 +566,7 @@ std::vector<BattleHex> DefaultSpellMechanics::rangeInHexes(BattleHex centralHex,
std::vector<BattleHex> ret;
std::string rng = owner->getLevelInfo(schoolLvl).range + ','; //copy + artificial comma for easier handling
if(rng.size() >= 2 && rng[0] != 'X') //there is at lest one hex in range (+artificial comma)
if(rng.size() >= 2 && rng[0] != 'X') //there is at least one hex in range (+artificial comma)
{
std::string number1, number2;
int beg, end;
@ -668,85 +625,94 @@ std::vector<BattleHex> DefaultSpellMechanics::rangeInHexes(BattleHex centralHex,
return ret;
}
std::set<const CStack *> DefaultSpellMechanics::getAffectedStacks(SpellTargetingContext & ctx) const
std::vector<const CStack *> DefaultSpellMechanics::getAffectedStacks(const CBattleInfoCallback * cb, SpellTargetingContext & ctx) const
{
std::vector<const CStack *> attackedCres = calculateAffectedStacks(cb, ctx);
handleImmunities(cb, ctx, attackedCres);
return attackedCres;
}
std::vector<const CStack *> DefaultSpellMechanics::calculateAffectedStacks(const CBattleInfoCallback* cb, const SpellTargetingContext& ctx) const
{
std::set<const CStack* > attackedCres;//std::set to exclude multiple occurrences of two hex creatures
const ui8 attackerSide = ctx.cb->playerToSide(ctx.casterColor) == 1;
const auto attackedHexes = rangeInHexes(ctx.destination, ctx.schoolLvl, attackerSide);
const ui8 attackerSide = cb->playerToSide(ctx.caster->getOwner()) == 1;
auto attackedHexes = rangeInHexes(ctx.destination, ctx.schoolLvl, attackerSide);
const CSpell::TargetInfo ti(owner, ctx.schoolLvl, ctx.mode);
//hackfix for banned creature massive spells
if(!ctx.ti.massive && owner->getLevelInfo(ctx.schoolLvl).range == "X")
attackedHexes.push_back(ctx.destination);
//TODO: more generic solution for mass spells
if(owner->getLevelInfo(ctx.schoolLvl).range.size() > 1) //custom many-hex range
auto mainFilter = [=](const CStack * s)
{
for(BattleHex hex : attackedHexes)
const bool positiveToAlly = owner->isPositive() && s->owner == ctx.caster->getOwner();
const bool negativeToEnemy = owner->isNegative() && s->owner != ctx.caster->getOwner();
const bool validTarget = s->isValidTarget(!ctx.ti.onlyAlive); //todo: this should be handled by spell class
const bool positivenessFlag = !ctx.ti.smart || owner->isNeutral() || positiveToAlly || negativeToEnemy;
return positivenessFlag && validTarget;
};
if(ctx.ti.type == CSpell::CREATURE && attackedHexes.size() == 1)
{
//for single target spells we must select one target. Alive stack is preferred (issue #1763)
auto predicate = [&](const CStack * s)
{
if(const CStack * st = ctx.cb->battleGetStackByPos(hex, ti.onlyAlive))
{
attackedCres.insert(st);
}
}
}
else if(ti.type == CSpell::CREATURE)
{
auto predicate = [=](const CStack * s){
const bool positiveToAlly = owner->isPositive() && s->owner == ctx.casterColor;
const bool negativeToEnemy = owner->isNegative() && s->owner != ctx.casterColor;
const bool validTarget = s->isValidTarget(!ti.onlyAlive); //todo: this should be handled by spell class
//for single target spells select stacks covering destination tile
const bool rangeCovers = ti.massive || s->coversPos(ctx.destination);
//handle smart targeting
const bool positivenessFlag = !ti.smart || owner->isNeutral() || positiveToAlly || negativeToEnemy;
return rangeCovers && positivenessFlag && validTarget;
return s->coversPos(attackedHexes.at(0)) && mainFilter(s);
};
TStacks stacks = ctx.cb->battleGetStacksIf(predicate);
TStacks stacks = cb->battleGetStacksIf(predicate);
if(ti.massive)
for(auto stack : stacks)
{
//for massive spells add all targets
for (auto stack : stacks)
if(stack->alive())
{
attackedCres.insert(stack);
}
else
{
//for single target spells we must select one target. Alive stack is preferred (issue #1763)
for(auto stack : stacks)
{
if(stack->alive())
{
attackedCres.insert(stack);
break;
}
break;
}
}
if(attackedCres.empty() && !stacks.empty())
{
attackedCres.insert(stacks.front());
}
if(attackedCres.empty() && !stacks.empty())
{
attackedCres.insert(stacks.front());
}
}
else if(ctx.ti.massive)
{
TStacks stacks = cb->battleGetStacksIf(mainFilter);
for (auto stack : stacks)
attackedCres.insert(stack);
}
else //custom range from attackedHexes
{
for(BattleHex hex : attackedHexes)
{
if(const CStack * st = ctx.cb->battleGetStackByPos(hex, ti.onlyAlive))
attackedCres.insert(st);
if(const CStack * st = cb->battleGetStackByPos(hex, ctx.ti.onlyAlive))
if(mainFilter(st))
attackedCres.insert(st);;
}
}
return attackedCres;
std::vector<const CStack *> res;
std::copy(attackedCres.begin(), attackedCres.end(), std::back_inserter(res));
return res;
}
ESpellCastProblem::ESpellCastProblem DefaultSpellMechanics::canBeCast(const CBattleInfoCallback * cb, PlayerColor player) const
ESpellCastProblem::ESpellCastProblem DefaultSpellMechanics::canBeCast(const CBattleInfoCallback * cb, const ECastingMode::ECastingMode mode, const ISpellCaster * caster) const
{
//no problems by default, this method is for spell-specific problems
return ESpellCastProblem::OK;
}
ESpellCastProblem::ESpellCastProblem DefaultSpellMechanics::canBeCast(const CBattleInfoCallback * cb, const SpellTargetingContext & ctx) const
{
//no problems by default, this method is for spell-specific problems
//common problems handled by CSpell
return ESpellCastProblem::OK;
}
ESpellCastProblem::ESpellCastProblem DefaultSpellMechanics::isImmuneByStack(const ISpellCaster * caster, const CStack * obj) const
{
//by default use general algorithm
@ -774,58 +740,56 @@ void DefaultSpellMechanics::doDispell(BattleInfo * battle, const BattleSpellCast
}
}
void DefaultSpellMechanics::castMagicMirror(const SpellCastEnvironment* env, BattleSpellCastParameters& parameters) const
void DefaultSpellMechanics::handleImmunities(const CBattleInfoCallback * cb, const SpellTargetingContext & ctx, std::vector<const CStack*> & stacks) const
{
logGlobal->debugStream() << "Started spell cast. Spell: "<<owner->name<<"; mode: MAGIC_MIRROR";
if(parameters.mode != ECastingMode::MAGIC_MIRROR)
//now handle immunities
auto predicate = [&, this](const CStack * s)->bool
{
env->complain("MagicMirror: invalid mode");
return;
}
BattleHex destination = parameters.getFirstDestinationHex();
if(!destination.isValid())
{
env->complain("MagicMirror: invalid destination");
return;
}
bool hitDirectly = ctx.ti.alwaysHitDirectly && s->coversPos(ctx.destination);
bool notImmune = (ESpellCastProblem::OK == owner->isImmuneByStack(ctx.caster, s));
BattleSpellCast sc;
prepareBattleCast(parameters, sc);
//calculating affected creatures for all spells
//must be vector, as in Chain Lightning order matters
std::vector<const CStack*> attackedCres; //CStack vector is somewhat more suitable than ID vector
auto creatures = owner->getAffectedStacks(parameters.cb, parameters.mode, parameters.casterColor, parameters.spellLvl, destination, parameters.caster);
std::copy(creatures.begin(), creatures.end(), std::back_inserter(attackedCres));
logGlobal->debugStream() << "will affect: " << attackedCres.size() << " stacks";
handleResistance(env, attackedCres, sc);
for(auto cre : attackedCres)
{
sc.affectedCres.insert(cre->ID);
}
StacksInjured si;
SpellCastContext ctx(attackedCres, sc, si);
applyBattleEffects(env, parameters, ctx);
env->sendAndApply(&sc);
if(!si.stacks.empty()) //after spellcast info shows
env->sendAndApply(&si);
logGlobal->debugStream() << "Finished spell cast. Spell: "<<owner->name<<"; mode: MAGIC_MIRROR";
return !(hitDirectly || notImmune);
};
vstd::erase_if(stacks, predicate);
}
void DefaultSpellMechanics::handleResistance(const SpellCastEnvironment * env, std::vector<const CStack* >& attackedCres, BattleSpellCast& sc) const
void DefaultSpellMechanics::handleMagicMirror(const SpellCastEnvironment * env, SpellCastContext & ctx, std::vector <const CStack*> & reflected) const
{
//reflection is applied only to negative spells
//if it is actual spell and can be reflected to single target, no recurrence
const bool tryMagicMirror = owner->isNegative() && owner->level && owner->getLevelInfo(0).range == "0";
if(tryMagicMirror)
{
for(auto s : ctx.attackedCres)
{
const int mirrorChance = (s)->valOfBonuses(Bonus::MAGIC_MIRROR);
if(env->getRandomGenerator().nextInt(99) < mirrorChance)
reflected.push_back(s);
}
vstd::erase_if(ctx.attackedCres, [&reflected](const CStack * s)
{
return vstd::contains(reflected, s);
});
for(auto s : reflected)
{
BattleSpellCast::CustomEffect effect;
effect.effect = 3;
effect.stack = s->ID;
ctx.sc.customEffects.push_back(effect);
}
}
}
void DefaultSpellMechanics::handleResistance(const SpellCastEnvironment * env, SpellCastContext & ctx) const
{
//checking if creatures resist
//resistance/reflection is applied only to negative spells
//resistance is applied only to negative spells
if(owner->isNegative())
{
std::vector <const CStack*> resisted;
for(auto s : attackedCres)
for(auto s : ctx.attackedCres)
{
//magic resistance
const int prob = std::min((s)->magicResistance(), 100); //probability of resistance in %
@ -836,7 +800,7 @@ void DefaultSpellMechanics::handleResistance(const SpellCastEnvironment * env, s
}
}
vstd::erase_if(attackedCres, [&resisted](const CStack * s)
vstd::erase_if(ctx.attackedCres, [&resisted](const CStack * s)
{
return vstd::contains(resisted, s);
});
@ -846,19 +810,19 @@ void DefaultSpellMechanics::handleResistance(const SpellCastEnvironment * env, s
BattleSpellCast::CustomEffect effect;
effect.effect = 78;
effect.stack = s->ID;
sc.customEffects.push_back(effect);
ctx.sc.customEffects.push_back(effect);
}
}
}
void DefaultSpellMechanics::prepareBattleCast(const BattleSpellCastParameters& parameters, BattleSpellCast& sc) const
bool DefaultSpellMechanics::requiresCreatureTarget() const
{
sc.side = parameters.casterSide;
sc.id = owner->id;
sc.skill = parameters.spellLvl;
sc.tile = parameters.getFirstDestinationHex();
sc.dmgToDisplay = 0;
sc.castByHero = parameters.mode == ECastingMode::HERO_CASTING;
sc.casterStack = (parameters.casterStack ? parameters.casterStack->ID : -1);
sc.manaGained = 0;
//most spells affects creatures somehow regardless of Target Type
//for few exceptions see overrides
return true;
}
std::vector<const CStack *> SpecialSpellMechanics::calculateAffectedStacks(const CBattleInfoCallback * cb, const SpellTargetingContext & ctx) const
{
return std::vector<const CStack *>();
}

View File

@ -11,54 +11,83 @@
#pragma once
#include "ISpellMechanics.h"
#include "../NetPacks.h"
struct StacksInjured;
class DefaultSpellMechanics;
struct SpellCastContext
class DLL_LINKAGE SpellCastContext
{
SpellCastContext(std::vector<const CStack *> & attackedCres, BattleSpellCast & sc, StacksInjured & si):
attackedCres(attackedCres), sc(sc), si(si)
{
};
std::vector<const CStack *> & attackedCres;
BattleSpellCast & sc;
StacksInjured & si;
};
enum class ESpellCastResult
{
OK,
CANCEL,//cast failed but it is not an error
ERROR//internal error occurred
public:
const DefaultSpellMechanics * mechanics;
const SpellCastEnvironment * env;
std::vector<const CStack *> attackedCres;//must be vector, as in Chain Lightning order matters
BattleSpellCast sc;//todo: make private
StacksInjured si;
const BattleSpellCastParameters & parameters;
SpellCastContext(const DefaultSpellMechanics * mechanics_, const SpellCastEnvironment * env_, const BattleSpellCastParameters & parameters_);
virtual ~SpellCastContext();
void addDamageToDisplay(const si32 value);
void setDamageToDisplay(const si32 value);
void beforeCast();
void afterCast();
private:
const CGHeroInstance * otherHero;
int spellCost;
si32 damageToDisplay;
void prepareBattleLog();
};
///all combat spells
class DLL_LINKAGE DefaultSpellMechanics : public ISpellMechanics
{
public:
DefaultSpellMechanics(CSpell * s): ISpellMechanics(s){};
std::vector<BattleHex> rangeInHexes(BattleHex centralHex, ui8 schoolLvl, ui8 side, bool * outDroppedHexes = nullptr) const override;
std::set<const CStack *> getAffectedStacks(SpellTargetingContext & ctx) const override;
std::vector<const CStack *> getAffectedStacks(const CBattleInfoCallback * cb, SpellTargetingContext & ctx) const override final;
ESpellCastProblem::ESpellCastProblem canBeCast(const CBattleInfoCallback * cb, PlayerColor player) const override;
ESpellCastProblem::ESpellCastProblem canBeCast(const CBattleInfoCallback * cb, const ECastingMode::ECastingMode mode, const ISpellCaster * caster) const override;
ESpellCastProblem::ESpellCastProblem canBeCast(const CBattleInfoCallback * cb, const SpellTargetingContext & ctx) const override;
ESpellCastProblem::ESpellCastProblem isImmuneByStack(const ISpellCaster * caster, const CStack * obj) const override;
virtual void applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const override;
bool adventureCast(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const override final;
void battleCast(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters) const override final;
void battleLogSingleTarget(std::vector<std::string> & logLines, const BattleSpellCast * packet,
const std::string & casterName, const CStack * attackedStack, bool & displayDamage) const override;
void battleCast(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters) const override final;
void battleLog(std::vector<MetaString> & logLines, const BattleSpellCastParameters & parameters,
const std::vector<const CStack *> & attacked, const si32 damageToDisplay, bool & displayDamage) const;
void battleLogDefault(std::vector<MetaString> & logLines, const BattleSpellCastParameters & parameters,
const std::vector<const CStack *> & attacked) const;
bool requiresCreatureTarget() const override;
protected:
virtual void applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const;
///actual adventure cast implementation
virtual ESpellCastResult applyAdventureEffects(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const;
virtual std::vector<const CStack *> calculateAffectedStacks(const CBattleInfoCallback * cb, const SpellTargetingContext & ctx) const;
protected:
void doDispell(BattleInfo * battle, const BattleSpellCast * packet, const CSelector & selector) const;
private:
void castMagicMirror(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters) const;
void handleResistance(const SpellCastEnvironment * env, std::vector<const CStack*> & attackedCres, BattleSpellCast & sc) const;
void prepareBattleCast(const BattleSpellCastParameters & parameters, BattleSpellCast & sc) const;
void cast(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, std::vector <const CStack*> & reflected) const;
void handleImmunities(const CBattleInfoCallback * cb, const SpellTargetingContext & ctx, std::vector<const CStack *> & stacks) const;
void handleMagicMirror(const SpellCastEnvironment * env, SpellCastContext & ctx, std::vector <const CStack*> & reflected) const;
void handleResistance(const SpellCastEnvironment * env, SpellCastContext & ctx) const;
friend class SpellCastContext;
};
///not affecting creatures directly
class DLL_LINKAGE SpecialSpellMechanics : public DefaultSpellMechanics
{
public:
SpecialSpellMechanics(CSpell * s): DefaultSpellMechanics(s){};
protected:
std::vector<const CStack *> calculateAffectedStacks(const CBattleInfoCallback * cb, const SpellTargetingContext & ctx) const override;
};

View File

@ -91,14 +91,15 @@ CSpell::CSpell():
defaultProbability(0),
isRising(false), isDamage(false), isOffensive(false),
targetType(ETargetType::NO_TARGET),
mechanics(nullptr)
mechanics(),
adventureMechanics()
{
levels.resize(GameConstants::SPELL_SCHOOL_LEVELS);
}
CSpell::~CSpell()
{
delete mechanics;
}
void CSpell::applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const
@ -110,10 +111,15 @@ bool CSpell::adventureCast(const SpellCastEnvironment * env, AdventureSpellCastP
{
assert(env);
return mechanics->adventureCast(env, parameters);
if(!adventureMechanics.get())
{
env->complain("Invalid adventure spell cast attempt!");
return false;
}
return adventureMechanics->adventureCast(env, parameters);
}
void CSpell::battleCast(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters) const
void CSpell::battleCast(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters) const
{
assert(env);
if(parameters.destinations.size()<1)
@ -143,9 +149,61 @@ ui32 CSpell::calculateDamage(const ISpellCaster * caster, const CStack * affecte
return adjustRawDamage(caster, affectedCreature, calculateRawEffectValue(spellSchoolLevel, usedSpellPower));
}
ESpellCastProblem::ESpellCastProblem CSpell::canBeCast(const CBattleInfoCallback * cb, PlayerColor player) const
ESpellCastProblem::ESpellCastProblem CSpell::canBeCast(const CBattleInfoCallback * cb, ECastingMode::ECastingMode mode, const ISpellCaster * caster) const
{
return mechanics->canBeCast(cb, player);
const ESpellCastProblem::ESpellCastProblem generalProblem = mechanics->canBeCast(cb, mode, caster);
if(generalProblem != ESpellCastProblem::OK)
return generalProblem;
//check for creature target existence
if(mechanics->requiresCreatureTarget())
{
switch(mode)
{
case ECastingMode::HERO_CASTING:
case ECastingMode::CREATURE_ACTIVE_CASTING:
case ECastingMode::ENCHANTER_CASTING:
case ECastingMode::PASSIVE_CASTING:
{
TargetInfo tinfo(this, caster->getSpellSchoolLevel(this), mode);
bool targetExists = false;
for(const CStack * stack : cb->battleGetAllStacks())
{
bool immune = !(stack->isValidTarget(!tinfo.onlyAlive) && ESpellCastProblem::OK == isImmuneByStack(caster, stack));
bool casterStack = stack->owner == caster->getOwner();
if(!immune)
{
switch (positiveness)
{
case CSpell::POSITIVE:
if(casterStack || !tinfo.smart)
targetExists = true;
break;
case CSpell::NEUTRAL:
targetExists = true;
break;
case CSpell::NEGATIVE:
if(!casterStack || !tinfo.smart)
targetExists = true;
break;
}
}
if(targetExists)
break;
}
if(!targetExists)
{
return ESpellCastProblem::NO_APPROPRIATE_TARGET;
}
}
break;
}
}
return ESpellCastProblem::OK;
}
std::vector<BattleHex> CSpell::rangeInHexes(BattleHex centralHex, ui8 schoolLvl, ui8 side, bool *outDroppedHexes) const
@ -153,23 +211,10 @@ std::vector<BattleHex> CSpell::rangeInHexes(BattleHex centralHex, ui8 schoolLvl,
return mechanics->rangeInHexes(centralHex,schoolLvl,side,outDroppedHexes);
}
std::set<const CStack *> CSpell::getAffectedStacks(const CBattleInfoCallback * cb, ECastingMode::ECastingMode mode, PlayerColor casterColor, int spellLvl, BattleHex destination, const ISpellCaster * caster) const
std::vector<const CStack *> CSpell::getAffectedStacks(const CBattleInfoCallback * cb, ECastingMode::ECastingMode mode, const ISpellCaster * caster, int spellLvl, BattleHex destination) const
{
ISpellMechanics::SpellTargetingContext ctx(this, cb,mode,casterColor,spellLvl,destination);
std::set<const CStack* > attackedCres = mechanics->getAffectedStacks(ctx);
//now handle immunities
auto predicate = [&, this](const CStack * s)->bool
{
bool hitDirectly = ctx.ti.alwaysHitDirectly && s->coversPos(destination);
bool notImmune = (ESpellCastProblem::OK == isImmuneByStack(caster, s));
return !(hitDirectly || notImmune);
};
vstd::erase_if(attackedCres, predicate);
return attackedCres;
SpellTargetingContext ctx(this, mode, caster, spellLvl, destination);
return mechanics->getAffectedStacks(cb, ctx);;
}
CSpell::ETargetType CSpell::getTargetType() const
@ -222,11 +267,6 @@ bool CSpell::isNeutral() const
return positiveness == NEUTRAL;
}
bool CSpell::isHealingSpell() const
{
return isRisingSpell() || (id == SpellID::CURE);
}
bool CSpell::isRisingSpell() const
{
return isRising;
@ -305,6 +345,35 @@ void CSpell::getEffects(std::vector<Bonus> & lst, const int level) const
}
}
ESpellCastProblem::ESpellCastProblem CSpell::canBeCastAt(const CBattleInfoCallback * cb, const ISpellCaster * caster, ECastingMode::ECastingMode mode, BattleHex destination) const
{
SpellTargetingContext ctx(this, mode, caster, caster->getSpellSchoolLevel(this), destination);
ESpellCastProblem::ESpellCastProblem specific = mechanics->canBeCast(cb, ctx);
if(specific != ESpellCastProblem::OK)
return specific;
//todo: this should be moved to mechanics
//rising spells handled by mechanics
if(ctx.ti.onlyAlive && getTargetType() == CSpell::CREATURE)
{
const CStack * aliveStack = cb->getStackIf([destination](const CStack * s)
{
return s->isValidTarget(false) && s->coversPos(destination);
});
if(!aliveStack)
return ESpellCastProblem::NO_APPROPRIATE_TARGET;
if(ctx.ti.smart && isNegative() && aliveStack->owner == caster->getOwner())
return ESpellCastProblem::NO_APPROPRIATE_TARGET;
if(ctx.ti.smart && isPositive() && aliveStack->owner != caster->getOwner())
return ESpellCastProblem::NO_APPROPRIATE_TARGET;
}
return isImmuneAt(cb, caster, mode, destination);
}
ESpellCastProblem::ESpellCastProblem CSpell::isImmuneAt(const CBattleInfoCallback * cb, const ISpellCaster * caster, ECastingMode::ECastingMode mode, BattleHex destination) const
{
// Get all stacks at destination hex. only alive if not rising spell
@ -517,59 +586,6 @@ ESpellCastProblem::ESpellCastProblem CSpell::isImmuneByStack(const ISpellCaster
return ESpellCastProblem::OK;
}
void CSpell::prepareBattleLog(const CBattleInfoCallback * cb, const BattleSpellCast * packet, std::vector<std::string> & logLines) const
{
bool displayDamage = true;
std::string casterName("Something"); //todo: localize
if(packet->castByHero)
casterName = cb->battleGetHeroInfo(packet->side).name;
{
const auto casterStackID = packet->casterStack;
if(casterStackID > 0)
{
const CStack * casterStack = cb->battleGetStackByID(casterStackID);
if(casterStack != nullptr)
casterName = casterStack->type->namePl;
}
}
if(packet->affectedCres.size() == 1)
{
const CStack * attackedStack = cb->battleGetStackByID(*packet->affectedCres.begin(), false);
const std::string attackedNamePl = attackedStack->getCreature()->namePl;
if(packet->castByHero)
{
const std::string fmt = VLC->generaltexth->allTexts[195];
logLines.push_back(boost::to_string(boost::format(fmt) % casterName % this->name % attackedNamePl));
}
else
{
mechanics->battleLogSingleTarget(logLines, packet, casterName, attackedStack, displayDamage);
}
}
else
{
boost::format text(VLC->generaltexth->allTexts[196]);
text % casterName % this->name;
logLines.push_back(text.str());
}
if(packet->dmgToDisplay > 0 && displayDamage)
{
boost::format dmgInfo(VLC->generaltexth->allTexts[376]);
dmgInfo % this->name % packet->dmgToDisplay;
logLines.push_back(dmgInfo.str());
}
}
void CSpell::setIsOffensive(const bool val)
{
isOffensive = val;
@ -598,13 +614,8 @@ void CSpell::setup()
void CSpell::setupMechanics()
{
if(nullptr != mechanics)
{
logGlobal->errorStream() << "Spell " << this->name << ": mechanics already set";
delete mechanics;
}
mechanics = ISpellMechanics::createMechanics(this);
adventureMechanics = IAdventureSpellMechanics::createMechanics(this);
}
///CSpell::AnimationInfo
@ -661,6 +672,10 @@ CSpell::TargetInfo::TargetInfo(const CSpell * spell, const int level, ECastingMo
{
alwaysHitDirectly = true;
}
else if(mode == ECastingMode::CREATURE_ACTIVE_CASTING)
{
massive = false;//FIXME: find better solution for Commander spells
}
}
void CSpell::TargetInfo::init(const CSpell * spell, const int level)

View File

@ -20,6 +20,7 @@
class CGObjectInstance;
class CSpell;
class ISpellMechanics;
class IAdventureSpellMechanics;
class CLegacyConfigParser;
class CGHeroInstance;
class CStack;
@ -204,7 +205,6 @@ public:
bool isNeutral() const;
bool isDamageSpell() const;
bool isHealingSpell() const;
bool isRisingSpell() const;
bool isOffensiveSpell() const;
@ -218,7 +218,7 @@ public:
ui32 calculateDamage(const ISpellCaster * caster, const CStack * affectedCreature, int spellSchoolLevel, int usedSpellPower) const;
///selects from allStacks actually affected stacks
std::set<const CStack *> getAffectedStacks(const CBattleInfoCallback * cb, ECastingMode::ECastingMode mode, PlayerColor casterColor, int spellLvl, BattleHex destination, const ISpellCaster * caster) const;
std::vector<const CStack *> getAffectedStacks(const CBattleInfoCallback * cb, ECastingMode::ECastingMode mode, const ISpellCaster * caster, int spellLvl, BattleHex destination) const;
si32 getCost(const int skillLevel) const;
@ -266,11 +266,11 @@ public:
public:
///internal interface (for callbacks)
///Checks general but spell-specific problems for all casting modes. Use only during battle.
ESpellCastProblem::ESpellCastProblem canBeCast(const CBattleInfoCallback * cb, PlayerColor player) const;
///Checks general but spell-specific problems. Use only during battle.
ESpellCastProblem::ESpellCastProblem canBeCast(const CBattleInfoCallback * cb, ECastingMode::ECastingMode mode, const ISpellCaster * caster) const;
///checks for creature immunity / anything that prevent casting *at given hex* - doesn't take into account general problems such as not having spellbook or mana points etc.
ESpellCastProblem::ESpellCastProblem isImmuneAt(const CBattleInfoCallback * cb, const ISpellCaster * caster, ECastingMode::ECastingMode mode, BattleHex destination) const;
ESpellCastProblem::ESpellCastProblem canBeCastAt(const CBattleInfoCallback * cb, const ISpellCaster * caster, ECastingMode::ECastingMode mode, BattleHex destination) const;
///checks for creature immunity / anything that prevent casting *at given target* - doesn't take into account general problems such as not having spellbook or mana points etc.
ESpellCastProblem::ESpellCastProblem isImmuneByStack(const ISpellCaster * caster, const CStack * obj) const;
@ -279,7 +279,7 @@ public:
///May be executed on client side by (future) non-cheat-proof scripts.
bool adventureCast(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const;
void battleCast(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters) const;
void battleCast(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters) const;
public:
///Client-server logic. Has direct write access to GameState.
@ -287,10 +287,6 @@ public:
///implementation of BattleSpellCast applying
void applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const;
public://Client logic.
void prepareBattleLog(const CBattleInfoCallback * cb, const BattleSpellCast * packet, std::vector<std::string> & logLines) const;
public://internal, for use only by Mechanics classes
///applies caster`s secondary skills and affectedCreature`s to raw damage
int adjustRawDamage(const ISpellCaster * caster, const CStack * affectedCreature, int rawDamage) const;
@ -300,6 +296,10 @@ public://internal, for use only by Mechanics classes
ESpellCastProblem::ESpellCastProblem internalIsImmune(const ISpellCaster * caster, const CStack *obj) const;
private:
///checks for creature immunity *at given hex*.
ESpellCastProblem::ESpellCastProblem isImmuneAt(const CBattleInfoCallback * cb, const ISpellCaster * caster, ECastingMode::ECastingMode mode, BattleHex destination) const;
void setIsOffensive(const bool val);
void setIsRising(const bool val);
@ -335,7 +335,8 @@ private:
std::vector<LevelInfo> levels;
ISpellMechanics * mechanics;//(!) do not serialize
std::unique_ptr<ISpellMechanics> mechanics;//(!) do not serialize
std::unique_ptr<IAdventureSpellMechanics> adventureMechanics;//(!) do not serialize
};
bool DLL_LINKAGE isInScreenRange(const int3 &center, const int3 &pos); //for spells like Dimension Door

View File

@ -20,9 +20,9 @@ void AcidBreathDamageMechanics::applyBattleEffects(const SpellCastEnvironment *
{
//todo: this should be effectValue
//calculating dmg to display
ctx.sc.dmgToDisplay = parameters.effectPower;
ctx.setDamageToDisplay(parameters.effectPower);
for(auto & attackedCre : ctx.attackedCres) //no immunities
for(auto & attackedCre : ctx.attackedCres)
{
BattleStackAttacked bsa;
bsa.flags |= BattleStackAttacked::SPELL_EFFECT;
@ -35,13 +35,35 @@ void AcidBreathDamageMechanics::applyBattleEffects(const SpellCastEnvironment *
}
}
ESpellCastProblem::ESpellCastProblem AcidBreathDamageMechanics::isImmuneByStack(const ISpellCaster * caster, const CStack * obj) const
{
//just in case
if(!obj->alive())
return ESpellCastProblem::WRONG_SPELL_TARGET;
//there should be no immunities by design
//but make it a bit configurable
//ignore all immunities, except specific absolute immunity
{
//SPELL_IMMUNITY absolute case
std::stringstream cachingStr;
cachingStr << "type_" << Bonus::SPELL_IMMUNITY << "subtype_" << owner->id.toEnum() << "addInfo_1";
if(obj->hasBonus(Selector::typeSubtypeInfo(Bonus::SPELL_IMMUNITY, owner->id.toEnum(), 1), cachingStr.str()))
return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
}
return ESpellCastProblem::OK;
}
///DeathStareMechanics
void DeathStareMechanics::applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
{
//calculating dmg to display
ctx.sc.dmgToDisplay = parameters.effectPower;
si32 damageToDisplay = parameters.effectPower;
if(!ctx.attackedCres.empty())
vstd::amin(ctx.sc.dmgToDisplay, (*ctx.attackedCres.begin())->count); //stack is already reduced after attack
vstd::amin(damageToDisplay, (*ctx.attackedCres.begin())->count); //stack is already reduced after attack
ctx.setDamageToDisplay(damageToDisplay);
for(auto & attackedCre : ctx.attackedCres)
{

View File

@ -17,6 +17,9 @@ class DLL_LINKAGE AcidBreathDamageMechanics : public DefaultSpellMechanics
{
public:
AcidBreathDamageMechanics(CSpell * s): DefaultSpellMechanics(s){};
ESpellCastProblem::ESpellCastProblem isImmuneByStack(const ISpellCaster * caster, const CStack * obj) const override;
protected:
void applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;
};

View File

@ -34,35 +34,15 @@ BattleSpellCastParameters::Destination::Destination(const BattleHex & destinatio
}
BattleSpellCastParameters::BattleSpellCastParameters(const BattleInfo * cb, const ISpellCaster * caster, const CSpell * spell)
: cb(cb), caster(caster), casterColor(caster->getOwner()), casterSide(cb->whatSide(casterColor)),
BattleSpellCastParameters::BattleSpellCastParameters(const BattleInfo * cb, const ISpellCaster * caster, const CSpell * spell_)
: spell(spell_), cb(cb), caster(caster), casterColor(caster->getOwner()), casterSide(cb->whatSide(casterColor)),
casterHero(nullptr),
mode(ECastingMode::HERO_CASTING), casterStack(nullptr), selectedStack(nullptr),
spellLvl(-1), effectLevel(-1), effectPower(0), enchantPower(0), effectValue(0)
mode(ECastingMode::HERO_CASTING), casterStack(nullptr),
spellLvl(0), effectLevel(0), effectPower(0), enchantPower(0), effectValue(0)
{
casterStack = dynamic_cast<const CStack *>(caster);
casterHero = dynamic_cast<const CGHeroInstance *>(caster);
prepare(spell);
}
void BattleSpellCastParameters::aimToHex(const BattleHex& destination)
{
destinations.push_back(Destination(destination));
}
void BattleSpellCastParameters::aimToStack(const CStack * destination)
{
destinations.push_back(Destination(destination));
}
BattleHex BattleSpellCastParameters::getFirstDestinationHex() const
{
return destinations.at(0).hexValue;
}
void BattleSpellCastParameters::prepare(const CSpell * spell)
{
spellLvl = caster->getSpellSchoolLevel(spell);
effectLevel = caster->getEffectLevel(spell);
effectPower = caster->getEffectPower(spell);
@ -76,6 +56,43 @@ void BattleSpellCastParameters::prepare(const CSpell * spell)
vstd::amax(effectValue, 0);
}
BattleSpellCastParameters::BattleSpellCastParameters(const BattleSpellCastParameters & orig, const ISpellCaster * caster)
:spell(orig.spell), cb(orig.cb), caster(caster), casterColor(caster->getOwner()), casterSide(cb->whatSide(casterColor)),
casterHero(nullptr), mode(ECastingMode::MAGIC_MIRROR), casterStack(nullptr),
spellLvl(orig.spellLvl), effectLevel(orig.effectLevel), effectPower(orig.effectPower), enchantPower(orig.enchantPower), effectValue(orig.effectValue)
{
casterStack = dynamic_cast<const CStack *>(caster);
casterHero = dynamic_cast<const CGHeroInstance *>(caster);
}
void BattleSpellCastParameters::aimToHex(const BattleHex& destination)
{
destinations.push_back(Destination(destination));
}
void BattleSpellCastParameters::aimToStack(const CStack * destination)
{
if(nullptr == destination)
logGlobal->error("BattleSpellCastParameters::aimToStack invalid stack.");
else
destinations.push_back(Destination(destination));
}
void BattleSpellCastParameters::cast(const SpellCastEnvironment * env)
{
spell->battleCast(env, *this);
}
BattleHex BattleSpellCastParameters::getFirstDestinationHex() const
{
return destinations.at(0).hexValue;
}
int BattleSpellCastParameters::getEffectValue() const
{
return (effectValue == 0) ? spell->calculateRawEffectValue(effectLevel, effectPower) : effectValue;
}
///ISpellMechanics
ISpellMechanics::ISpellMechanics(CSpell * s):
owner(s)
@ -83,72 +100,89 @@ ISpellMechanics::ISpellMechanics(CSpell * s):
}
ISpellMechanics * ISpellMechanics::createMechanics(CSpell * s)
std::unique_ptr<ISpellMechanics> ISpellMechanics::createMechanics(CSpell * s)
{
switch (s->id)
{
case SpellID::ANTI_MAGIC:
return new AntimagicMechanics(s);
return make_unique<AntimagicMechanics>(s);
case SpellID::ACID_BREATH_DAMAGE:
return new AcidBreathDamageMechanics(s);
return make_unique<AcidBreathDamageMechanics>(s);
case SpellID::CHAIN_LIGHTNING:
return new ChainLightningMechanics(s);
return make_unique<ChainLightningMechanics>(s);
case SpellID::CLONE:
return new CloneMechanics(s);
return make_unique<CloneMechanics>(s);
case SpellID::CURE:
return new CureMechanics(s);
return make_unique<CureMechanics>(s);
case SpellID::DEATH_STARE:
return new DeathStareMechanics(s);
return make_unique<DeathStareMechanics>(s);
case SpellID::DISPEL:
return new DispellMechanics(s);
return make_unique<DispellMechanics>(s);
case SpellID::DISPEL_HELPFUL_SPELLS:
return new DispellHelpfulMechanics(s);
return make_unique<DispellHelpfulMechanics>(s);
case SpellID::EARTHQUAKE:
return new EarthquakeMechanics(s);
return make_unique<EarthquakeMechanics>(s);
case SpellID::FIRE_WALL:
return make_unique<FireWallMechanics>(s);
case SpellID::FORCE_FIELD:
return new WallMechanics(s);
return make_unique<ForceFieldMechanics>(s);
case SpellID::HYPNOTIZE:
return new HypnotizeMechanics(s);
return make_unique<HypnotizeMechanics>(s);
case SpellID::LAND_MINE:
return make_unique<LandMineMechanics>(s);
case SpellID::QUICKSAND:
return new ObstacleMechanics(s);
return make_unique<QuicksandMechanics>(s);
case SpellID::REMOVE_OBSTACLE:
return new RemoveObstacleMechanics(s);
return make_unique<RemoveObstacleMechanics>(s);
case SpellID::SACRIFICE:
return new SacrificeMechanics(s);
return make_unique<SacrificeMechanics>(s);
case SpellID::SUMMON_FIRE_ELEMENTAL:
return new SummonMechanics(s, CreatureID::FIRE_ELEMENTAL);
return make_unique<SummonMechanics>(s, CreatureID::FIRE_ELEMENTAL);
case SpellID::SUMMON_EARTH_ELEMENTAL:
return new SummonMechanics(s, CreatureID::EARTH_ELEMENTAL);
return make_unique<SummonMechanics>(s, CreatureID::EARTH_ELEMENTAL);
case SpellID::SUMMON_WATER_ELEMENTAL:
return new SummonMechanics(s, CreatureID::WATER_ELEMENTAL);
return make_unique<SummonMechanics>(s, CreatureID::WATER_ELEMENTAL);
case SpellID::SUMMON_AIR_ELEMENTAL:
return new SummonMechanics(s, CreatureID::AIR_ELEMENTAL);
return make_unique<SummonMechanics>(s, CreatureID::AIR_ELEMENTAL);
case SpellID::TELEPORT:
return new TeleportMechanics(s);
return make_unique<TeleportMechanics>(s);
default:
if(s->isRisingSpell())
return make_unique<SpecialRisingSpellMechanics>(s);
else
return make_unique<DefaultSpellMechanics>(s);
}
}
//IAdventureSpellMechanics
IAdventureSpellMechanics::IAdventureSpellMechanics(CSpell * s):
owner(s)
{
}
std::unique_ptr<IAdventureSpellMechanics> IAdventureSpellMechanics::createMechanics(CSpell * s)
{
switch (s->id)
{
case SpellID::SUMMON_BOAT:
return new SummonBoatMechanics(s);
return make_unique<SummonBoatMechanics>(s);
case SpellID::SCUTTLE_BOAT:
return new ScuttleBoatMechanics(s);
return make_unique<ScuttleBoatMechanics>(s);
case SpellID::DIMENSION_DOOR:
return new DimensionDoorMechanics(s);
return make_unique<DimensionDoorMechanics>(s);
case SpellID::FLY:
case SpellID::WATER_WALK:
case SpellID::VISIONS:
case SpellID::DISGUISE:
return new DefaultSpellMechanics(s); //implemented using bonus system
return make_unique<AdventureSpellMechanics>(s); //implemented using bonus system
case SpellID::TOWN_PORTAL:
return new TownPortalMechanics(s);
return make_unique<TownPortalMechanics>(s);
case SpellID::VIEW_EARTH:
return new ViewEarthMechanics(s);
return make_unique<ViewEarthMechanics>(s);
case SpellID::VIEW_AIR:
return new ViewAirMechanics(s);
return make_unique<ViewAirMechanics>(s);
default:
if(s->isRisingSpell())
return new SpecialRisingSpellMechanics(s);
else
return new DefaultSpellMechanics(s);
return std::unique_ptr<IAdventureSpellMechanics>();
}
}

View File

@ -45,11 +45,22 @@ public:
const BattleHex hexValue;
};
BattleSpellCastParameters(const BattleInfo * cb, const ISpellCaster * caster, const CSpell * spell);
//normal constructor
BattleSpellCastParameters(const BattleInfo * cb, const ISpellCaster * caster, const CSpell * spell_);
//magic mirror constructor
BattleSpellCastParameters(const BattleSpellCastParameters & orig, const ISpellCaster * caster);
void aimToHex(const BattleHex & destination);
void aimToStack(const CStack * destination);
void cast(const SpellCastEnvironment * env);
BattleHex getFirstDestinationHex() const;
int getEffectValue() const;
const CSpell * spell;
const BattleInfo * cb;
const ISpellCaster * caster;
const PlayerColor casterColor;
@ -60,7 +71,6 @@ public:
const CGHeroInstance * casterHero; //deprecated
ECastingMode::ECastingMode mode;
const CStack * casterStack; //deprecated
const CStack * selectedStack;//deprecated
///spell school level
int spellLvl;
@ -70,10 +80,50 @@ public:
int effectPower;
///actual spell-power affecting effect duration
int enchantPower;
private:
///for Archangel-like casting
int effectValue;
private:
void prepare(const CSpell * spell);
};
struct DLL_LINKAGE SpellTargetingContext
{
CSpell::TargetInfo ti;
ECastingMode::ECastingMode mode;
BattleHex destination;
const ISpellCaster * caster;
int schoolLvl;
SpellTargetingContext(const CSpell * s, ECastingMode::ECastingMode mode_, const ISpellCaster * caster_, int schoolLvl_, BattleHex destination_)
: ti(s,schoolLvl_, mode_), mode(mode_), destination(destination_), caster(caster_), schoolLvl(schoolLvl_)
{};
};
class DLL_LINKAGE ISpellMechanics
{
public:
ISpellMechanics(CSpell * s);
virtual ~ISpellMechanics(){};
virtual std::vector<BattleHex> rangeInHexes(BattleHex centralHex, ui8 schoolLvl, ui8 side, bool * outDroppedHexes = nullptr) const = 0;
virtual std::vector<const CStack *> getAffectedStacks(const CBattleInfoCallback * cb, SpellTargetingContext & ctx) const = 0;
virtual ESpellCastProblem::ESpellCastProblem canBeCast(const CBattleInfoCallback * cb, const ECastingMode::ECastingMode mode, const ISpellCaster * caster) const = 0;
virtual ESpellCastProblem::ESpellCastProblem canBeCast(const CBattleInfoCallback * cb, const SpellTargetingContext & ctx) const = 0;
virtual ESpellCastProblem::ESpellCastProblem isImmuneByStack(const ISpellCaster * caster, const CStack * obj) const = 0;
virtual void applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const = 0;
virtual void battleCast(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters) const = 0;
//if true use generic algorithm for target existence check, see CSpell::canBeCast
virtual bool requiresCreatureTarget() const = 0;
static std::unique_ptr<ISpellMechanics> createMechanics(CSpell * s);
protected:
CSpell * owner;
};
struct DLL_LINKAGE AdventureSpellCastParameters
@ -82,42 +132,15 @@ struct DLL_LINKAGE AdventureSpellCastParameters
int3 pos;
};
class DLL_LINKAGE ISpellMechanics
class DLL_LINKAGE IAdventureSpellMechanics
{
public:
struct DLL_LINKAGE SpellTargetingContext
{
const CBattleInfoCallback * cb;
CSpell::TargetInfo ti;
ECastingMode::ECastingMode mode;
BattleHex destination;
PlayerColor casterColor;
int schoolLvl;
IAdventureSpellMechanics(CSpell * s);
virtual ~IAdventureSpellMechanics() = default;
SpellTargetingContext(const CSpell * s, const CBattleInfoCallback * c, ECastingMode::ECastingMode m, PlayerColor cc, int lvl, BattleHex dest)
: cb(c), ti(s,lvl, m), mode(m), destination(dest), casterColor(cc), schoolLvl(lvl)
{};
};
public:
ISpellMechanics(CSpell * s);
virtual ~ISpellMechanics(){};
virtual std::vector<BattleHex> rangeInHexes(BattleHex centralHex, ui8 schoolLvl, ui8 side, bool * outDroppedHexes = nullptr) const = 0;
virtual std::set<const CStack *> getAffectedStacks(SpellTargetingContext & ctx) const = 0;
virtual ESpellCastProblem::ESpellCastProblem canBeCast(const CBattleInfoCallback * cb, PlayerColor player) const = 0;
virtual ESpellCastProblem::ESpellCastProblem isImmuneByStack(const ISpellCaster * caster, const CStack * obj) const = 0;
virtual void applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const = 0;
virtual bool adventureCast(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const = 0;
virtual void battleCast(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters) const = 0;
virtual void battleLogSingleTarget(std::vector<std::string> & logLines, const BattleSpellCast * packet,
const std::string & casterName, const CStack * attackedStack, bool & displayDamage) const = 0;
static ISpellMechanics * createMechanics(CSpell * s);
static std::unique_ptr<IAdventureSpellMechanics> createMechanics(CSpell * s);
protected:
CSpell * owner;
};

View File

@ -18,6 +18,7 @@
class CSpell;
class CStack;
class PlayerColor;
struct MetaString;
class DLL_LINKAGE ISpellCaster
{
@ -45,4 +46,10 @@ public:
virtual int getEffectValue(const CSpell * spell) const = 0;
virtual const PlayerColor getOwner() const = 0;
///only name substitution
virtual void getCasterName(MetaString & text) const = 0;
///full default text
virtual void getCastDescription(const CSpell * spell, const std::vector<const CStack *> & attacked, MetaString & text) const = 0;
};

View File

@ -846,7 +846,7 @@ void CGameHandler::prepareAttack(BattleAttack &bat, const CStack *att, const CSt
//TODO: should spell override creature`s projectile?
std::set<const CStack*> attackedCreatures = SpellID(bonus->subtype).toSpell()->getAffectedStacks(gs->curB, ECastingMode::SPELL_LIKE_ATTACK, att->owner, bonus->val, targetHex, att);
auto attackedCreatures = SpellID(bonus->subtype).toSpell()->getAffectedStacks(gs->curB, ECastingMode::SPELL_LIKE_ATTACK, att, bonus->val, targetHex);
//TODO: get exact attacked hex for defender
@ -2520,7 +2520,7 @@ void CGameHandler::heroExchange(ObjectInstanceID hero1, ObjectInstanceID hero2)
void CGameHandler::sendToAllClients( CPackForClient * info )
{
logGlobal->trace("Sending to all clients a package of type %s", typeid(*info).name());
logNetwork->trace("Sending to all clients a package of type %s", typeid(*info).name());
for(auto & elem : conns)
{
boost::unique_lock<boost::mutex> lock(*(elem)->wmx);
@ -4179,7 +4179,7 @@ bool CGameHandler::makeBattleAction( BattleAction &ba )
bsa.creID = summonedType;
ui64 risedHp = summoner->count * summoner->valOfBonuses(Bonus::DAEMON_SUMMONING, bsa.creID.toEnum());
ui64 targetHealth = destStack->getCreature()->MaxHealth() * destStack->baseAmount;//todo: ignore AGE effect
ui64 targetHealth = destStack->getCreature()->MaxHealth() * destStack->baseAmount;
ui64 canRiseHp = std::min(targetHealth, risedHp);
ui32 canRiseAmount = canRiseHp / VLC->creh->creatures.at(bsa.creID)->MaxHealth();
@ -4241,8 +4241,7 @@ bool CGameHandler::makeBattleAction( BattleAction &ba )
parameters.effectLevel = parameters.spellLvl;
parameters.mode = ECastingMode::CREATURE_ACTIVE_CASTING;
parameters.aimToHex(destination);//todo: allow multiple destinations
parameters.selectedStack = nullptr;
spell->battleCast(spellEnv, parameters);
parameters.cast(spellEnv);
}
sendAndApply(&end_action);
break;
@ -4440,7 +4439,8 @@ bool CGameHandler::makeCustomAction( BattleAction &ba )
BattleSpellCastParameters parameters(gs->curB, h, s);
parameters.aimToHex(ba.destinationTile);//todo: allow multiple destinations
parameters.mode = ECastingMode::HERO_CASTING;
parameters.selectedStack = gs->curB->battleGetStackByID(ba.selectedStack, false);
if(ba.selectedStack >= 0)
parameters.aimToStack(gs->curB->battleGetStackByID(ba.selectedStack, false));
ESpellCastProblem::ESpellCastProblem escp = gs->curB->battleCanCastThisSpell(h, s, ECastingMode::HERO_CASTING);//todo: should we check aimed cast(battleCanCastThisSpellHere)?
if(escp != ESpellCastProblem::OK)
@ -4452,7 +4452,7 @@ bool CGameHandler::makeCustomAction( BattleAction &ba )
StartAction start_action(ba);
sendAndApply(&start_action); //start spell casting
s->battleCast(spellEnv, parameters);
parameters.cast(spellEnv);
sendAndApply(&end_action);
if( !gs->curB->battleGetStackByID(gs->curB->activeStack))
@ -4592,9 +4592,8 @@ void CGameHandler::stackTurnTrigger(const CStack * st)
parameters.effectLevel = bonus->val;//todo: recheck
parameters.aimToHex(BattleHex::INVALID);
parameters.mode = ECastingMode::ENCHANTER_CASTING;
parameters.selectedStack = nullptr;
spell->battleCast(spellEnv, parameters);
parameters.cast(spellEnv);
//todo: move to mechanics
BattleSetStackProperty ssp;
@ -5303,9 +5302,8 @@ void CGameHandler::attackCasting(const BattleAttack & bat, Bonus::BonusType atta
parameters.effectLevel = spellLevel;
parameters.aimToStack(oneOfAttacked);
parameters.mode = ECastingMode::AFTER_ATTACK_CASTING;
parameters.selectedStack = nullptr;
spell->battleCast(spellEnv, parameters);
parameters.cast(spellEnv);
}
}
}
@ -5333,9 +5331,8 @@ void CGameHandler::handleAfterAttackCasting( const BattleAttack & bat )
parameters.aimToStack(gs->curB->battleGetStackByID(bat.bsa.at(0).stackAttacked));
parameters.effectPower = power;
parameters.mode = ECastingMode::AFTER_ATTACK_CASTING;
parameters.selectedStack = nullptr;
spell->battleCast(this->spellEnv, parameters);
parameters.cast(spellEnv);
};
attackCasting(bat, Bonus::SPELL_AFTER_ATTACK, attacker);
@ -5356,9 +5353,8 @@ void CGameHandler::handleAfterAttackCasting( const BattleAttack & bat )
vstd::amin(chanceToKill, 1); //cap at 100%
std::binomial_distribution<> distribution(attacker->count, chanceToKill);
std::mt19937 rng(std::time(nullptr));
int staredCreatures = distribution(rng);
int staredCreatures = distribution(getRandomGenerator().getStdGenerator());
double cap = 1 / std::max(chanceToKill, (double)(0.01));//don't divide by 0
int maxToKill = (attacker->count + cap - 1) / cap; //not much more than chance * count
@ -5628,21 +5624,24 @@ void CGameHandler::runBattle()
for(int i = 0; i < 2; ++i)
{
auto h = gs->curB->battleGetFightingHero(i);
if(h && h->hasBonusOfType(Bonus::OPENING_BATTLE_SPELL))
if(h)
{
TBonusListPtr bl = h->getBonuses(Selector::type(Bonus::OPENING_BATTLE_SPELL));
for (Bonus *b : *bl)
{
const CSpell * spell = SpellID(b->subtype).toSpell();
if(ESpellCastProblem::OK != gs->curB->battleCanCastThisSpell(h, spell, ECastingMode::PASSIVE_CASTING))
continue;
BattleSpellCastParameters parameters(gs->curB, h, spell);
parameters.spellLvl = 3;
parameters.effectLevel = 3;
parameters.aimToHex(BattleHex::INVALID);
parameters.mode = ECastingMode::PASSIVE_CASTING;
parameters.selectedStack = nullptr;
parameters.enchantPower = b->val;
spell->battleCast(spellEnv, parameters);
parameters.cast(spellEnv);
}
}
}

View File

@ -126,7 +126,7 @@ void CObjectVisitQuery::onExposure(CGameHandler *gh, QueryPtr topQuery)
void Queries::popQuery(PlayerColor player, QueryPtr query)
{
LOG_TRACE_PARAMS(logGlobal, "player='%s', query='%s'", player % query);
//LOG_TRACE_PARAMS(logGlobal, "player='%s', query='%s'", player % query);
if(topQuery(player) != query)
{
logGlobal->trace("Cannot remove, not a top!");
@ -147,7 +147,7 @@ void Queries::popQuery(PlayerColor player, QueryPtr query)
void Queries::popQuery(const CQuery &query)
{
LOG_TRACE_PARAMS(logGlobal, "query='%s'", query);
//LOG_TRACE_PARAMS(logGlobal, "query='%s'", query);
assert(query.players.size());
for(auto player : query.players)
@ -174,7 +174,7 @@ void Queries::addQuery(QueryPtr query)
void Queries::addQuery(PlayerColor player, QueryPtr query)
{
LOG_TRACE_PARAMS(logGlobal, "player='%d', query='%s'", player.getNum() % query);
//LOG_TRACE_PARAMS(logGlobal, "player='%d', query='%s'", player.getNum() % query);
query->onAdding(gh, player);
queries[player].push_back(query);
}
@ -186,7 +186,7 @@ QueryPtr Queries::topQuery(PlayerColor player)
void Queries::popIfTop(QueryPtr query)
{
LOG_TRACE_PARAMS(logGlobal, "query='%d'", query);
//LOG_TRACE_PARAMS(logGlobal, "query='%d'", query);
if(!query)
logGlobal->error("The query is nullptr! Ignoring.");