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:
commit
6c63041d1a
@ -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()
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
@ -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))
|
||||
|
@ -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;
|
||||
|
@ -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 )
|
||||
{
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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>();
|
||||
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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))
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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 *>();
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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)
|
||||
|
@ -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 ¢er, const int3 &pos); //for spells like Dimension Door
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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>();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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.");
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user