1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-02-03 13:01:33 +02:00

* Further work on Battle AI. Now it is able to cast a number of offensive spells. Battle callback exposes more spell-casting info.

* Took down the one Boost.Assign usage offending VC11. I'm getting impatient I guess...
This commit is contained in:
Michał W. Urbańczyk 2012-09-23 23:10:56 +00:00
parent 6a81c8b1af
commit 95b866c131
13 changed files with 458 additions and 266 deletions

View File

@ -15,6 +15,8 @@ CBattleCallback * cbc;
#define LOGL(text) print(text)
#define LOGFL(text, formattingEl) print(boost::str(boost::format(text) % formattingEl))
class StackWithBonuses : public IBonusBearer
{
public:
@ -238,6 +240,21 @@ struct AttackPossibility
}
};
template<typename Key, typename Val>
const Val &getValOr(const std::map<Key, Val> &Map, const Key &key, const Key &defaultValue)
{
auto i = Map.find(key);
if(i != Map.end())
return i->second;
else
return defaultValue;
}
struct HypotheticChangesToBattleState
{
std::map<const CStack *, const IBonusBearer *> bonusesOfStacks;
};
struct PotentialTargets
{
std::vector<AttackPossibility> possibleAttacks;
@ -245,7 +262,10 @@ struct PotentialTargets
std::function<AttackPossibility(bool,BattleHex)> GenerateAttackInfo; //args: shooting, destHex
PotentialTargets(const CStack *attacker, optional<IBonusBearer*> attackerBonuses = boost::none)
PotentialTargets()
{}
PotentialTargets(const CStack *attacker, const HypotheticChangesToBattleState &state = HypotheticChangesToBattleState())
{
auto dists = cbc->battleGetDistances(attacker);
std::vector<BattleHex> avHexes = cbc->battleGetAvailableHexes(attacker, false);
@ -259,8 +279,8 @@ struct PotentialTargets
GenerateAttackInfo = [&](bool shooting, BattleHex hex) -> AttackPossibility
{
auto bai = BattleAttackInfo(attacker, enemy, shooting);
if(attackerBonuses)
bai.attackerBonuses = *attackerBonuses;
bai.attackerBonuses = getValOr(state.bonusesOfStacks, bai.attacker, bai.attacker);
bai.defenderBonuses = getValOr(state.bonusesOfStacks, bai.defender, bai.defender);
AttackPossibility ap = {enemy, hex, bai, 0, 0};
if(hex.isValid())
@ -495,11 +515,32 @@ BattleAction CBattleAI::useCatapult(const CStack * stack)
throw std::runtime_error("The method or operation is not implemented.");
}
bool isSupportedSpell(const CSpell *spell)
enum SpellTypes
{
OFFENSIVE_SPELL, TIMED_EFFECT, OTHER
};
SpellTypes spellType(const CSpell *spell)
{
switch(spell->id)
{
// permanent effects
//offense spell
case Spells::MAGIC_ARROW:
case Spells::ICE_BOLT:
case Spells::LIGHTNING_BOLT:
case Spells::IMPLOSION:
case Spells::CHAIN_LIGHTNING:
case Spells::FROST_RING:
case Spells::FIREBALL:
case Spells::INFERNO:
case Spells::METEOR_SHOWER:
case Spells::DEATH_RIPPLE:
case Spells::DESTROY_UNDEAD:
case Spells::ARMAGEDDON:
case Spells::TITANS_LIGHTNING_BOLT:
case Spells::THUNDERBOLT: //(thunderbirds)
return OFFENSIVE_SPELL;
case Spells::SHIELD:
case Spells::AIR_SHIELD:
case Spells::FIRE_SHIELD:
@ -537,10 +578,10 @@ bool isSupportedSpell(const CSpell *spell)
case Spells::PARALYZE:
case Spells::AGE:
case Spells::ACID_BREATH_DEFENSE:
return true;
return TIMED_EFFECT;
default:
return false;
return OTHER;
}
};
@ -550,10 +591,55 @@ struct PossibleSpellcast
BattleHex dest;
};
struct CurrentOffensivePotential
{
std::map<const CStack *, PotentialTargets> ourAttacks;
std::map<const CStack *, PotentialTargets> enemyAttacks;
CurrentOffensivePotential(ui8 side)
{
BOOST_FOREACH(auto stack, cbc->battleGetStacks())
{
if(stack->attackerOwned == !side)
ourAttacks[stack] = PotentialTargets(stack);
else
enemyAttacks[stack] = PotentialTargets(stack);
}
}
int potentialValue()
{
int ourPotential = 0, enemyPotential = 0;
BOOST_FOREACH(auto &p, ourAttacks)
ourPotential += p.second.bestAction().attackValue();
BOOST_FOREACH(auto &p, enemyAttacks)
enemyPotential += p.second.bestAction().attackValue();
return ourPotential - enemyPotential;
}
};
//
// //set has its own order, so remove_if won't work. TODO - reuse for map
// template<typename Elem, typename Predicate>
// void erase_if(std::set<Elem> &setContainer, Predicate pred)
// {
// auto itr = setContainer.begin();
// auto endItr = setContainer.end();
// while(itr != endItr)
// {
// auto tmpItr = itr++;
// if(pred(*tmpItr))
// setContainer.erase(tmpItr);
// }
// }
void CBattleAI::attemptCastingSpell()
{
LOGL("Casting spells sounds like fun. Let's see...");
auto hero = cb->battleGetMyHero();
//auto known = cb->battleGetFightingHero(side);
//Get all spells we can cast
@ -566,14 +652,14 @@ void CBattleAI::attemptCastingSpell()
LOGFL("I can cast %d spells.", possibleSpells.size());
vstd::erase_if(possibleSpells, [](const CSpell *s)
{return !isSupportedSpell(s); });
{return spellType(s) == OTHER; });
LOGFL("I know about workings of %d of them.", possibleSpells.size());
//Get possible spell-target pairs
std::vector<PossibleSpellcast> possibleCasts;
BOOST_FOREACH(auto spell, possibleSpells)
{
BOOST_FOREACH(auto hex, cbc->battleGetPossibleTargets(playerID, spell))
BOOST_FOREACH(auto hex, getTargetsToConsider(spell))
{
PossibleSpellcast ps = {spell, hex};
possibleCasts.push_back(ps);
@ -592,33 +678,77 @@ void CBattleAI::attemptCastingSpell()
auto evaluateSpellcast = [&] (const PossibleSpellcast &ps) -> int
{
int skillLevel = 0;
const int skillLevel = hero->getSpellSchoolLevel(ps.spell);
const int spellPower = hero->getPrimSkillLevel(PrimarySkill::SPELL_POWER);
StackWithBonuses swb;
swb.stack = cb->battleGetStackByPos(ps.dest);
if(!swb.stack)
return -1;
switch(spellType(ps.spell))
{
case OFFENSIVE_SPELL:
{
int damageDealt = 0, damageReceived = 0;
Bonus pseudoBonus;
pseudoBonus.sid = ps.spell->id;
pseudoBonus.val = skillLevel;
pseudoBonus.turnsRemain = 1; //TODO
CStack::stackEffectToFeature(swb.bonusesToAdd, pseudoBonus);
auto stacksSuffering = cb->getAffectedCreatures(ps.spell, skillLevel, playerID, ps.dest);
vstd::erase_if(stacksSuffering, [&](const CStack *stack) -> bool
{
return cb->battleIsImmune(hero, ps.spell, ECastingMode::HERO_CASTING, ps.dest);
});
PotentialTargets pt(swb.stack, &swb);
auto newValue = pt.bestAction().attackValue();
auto oldValue = valueOfStack[swb.stack];
auto gain = newValue - oldValue;
if(swb.stack->owner != playerID) //enemy
gain = -gain;
if(stacksSuffering.empty())
return -1;
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));
BOOST_FOREACH(auto stack, stacksSuffering)
{
const int dmg = cb->calculateSpellDmg(ps.spell, hero, stack, skillLevel, spellPower);
if(stack->owner == playerID)
damageReceived += dmg;
else
damageDealt += dmg;
}
return gain;
const int damageDiff = damageDealt - damageReceived;
LOGFL("Casting %s on hex %d would deal %d damage points among %d stacks.",
ps.spell->name % ps.dest % damageDiff % stacksSuffering.size());
//TODO tactic effect too
return damageDiff;
}
case TIMED_EFFECT:
{
StackWithBonuses swb;
swb.stack = cb->battleGetStackByPos(ps.dest);
if(!swb.stack)
return -1;
Bonus pseudoBonus;
pseudoBonus.sid = ps.spell->id;
pseudoBonus.val = skillLevel;
pseudoBonus.turnsRemain = 1; //TODO
CStack::stackEffectToFeature(swb.bonusesToAdd, pseudoBonus);
HypotheticChangesToBattleState state;
state.bonusesOfStacks[swb.stack] = &swb;
PotentialTargets pt(swb.stack, state);
auto newValue = pt.bestAction().attackValue();
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 % swb.stack->nodeName() % gain % (oldValue) % (newValue));
return gain;
}
default:
assert(0);
}
};
auto castToPerform = *vstd::maxElementByFun(possibleCasts, evaluateSpellcast);
LOGFL("Best spell is %s. Will cast.", castToPerform.spell->name);
BattleAction spellcast;
spellcast.actionType = BattleAction::HERO_SPELL;
spellcast.additionalInfo = castToPerform.spell->id;
@ -627,4 +757,24 @@ void CBattleAI::attemptCastingSpell()
spellcast.stackNumber = (!side) ? -1 : -2;
cb->battleMakeAction(&spellcast);
}
}
std::vector<BattleHex> CBattleAI::getTargetsToConsider( const CSpell *spell ) const
{
if(spell->getTargetType() == CSpell::NO_TARGET)
{
//Spell can be casted 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;
}
else
{
//TODO when massive effect -> doesnt matter where cast
return cbc->battleGetPossibleTargets(playerID, spell);
}
}

View File

@ -2,6 +2,8 @@
#include "../../lib/BattleHex.h"
class CSpell;
class CBattleAI : public CBattleGameInterface
{
int side;
@ -38,5 +40,6 @@ public:
BattleAction useCatapult(const CStack * stack);
void attemptCastingSpell();
std::vector<BattleHex> getTargetsToConsider(const CSpell *spell) const;
};

View File

@ -371,6 +371,20 @@ namespace vstd
vec.erase(boost::remove_if(vec, pred),vec.end());
}
//set has its own order, so remove_if won't work. TODO - reuse for map
template<typename Elem, typename Predicate>
void erase_if(std::set<Elem> &setContainer, Predicate pred)
{
auto itr = setContainer.begin();
auto endItr = setContainer.end();
while(itr != endItr)
{
auto tmpItr = itr++;
if(pred(*tmpItr))
setContainer.erase(tmpItr);
}
}
template<typename InputRange, typename OutputIterator, typename Predicate>
OutputIterator copy_if(const InputRange &input, OutputIterator result, Predicate pred)
{

View File

@ -2274,9 +2274,15 @@ void CBattleInterface::showPieceOfWall(SDL_Surface * to, int hex, const std::vec
{165, std::list<int>{11}}, {186, std::list<int>{3}}};
#else
using namespace boost::assign;
static const std::map<int, std::list<int> > hexToPart = map_list_of<int, std::list<int> >(12, list_of<int>(8)(1)(7))(45, list_of<int>(12)(6))
(101, list_of<int>(10))(118, list_of<int>(2))(165, list_of<int>(11))(186, list_of<int>(3));
std::map<int, std::list<int> > hexToPart;
hexToPart[12] = list_of<int>(8)(1)(7);
hexToPart[45] = list_of<int>(12)(6);
hexToPart[101] = list_of<int>(10);
hexToPart[118] = list_of<int>(2);
hexToPart[165] = list_of<int>(11);
hexToPart[186] = list_of<int>(3);
#endif
std::map<int, std::list<int> >::const_iterator it = hexToPart.find(hex);
if(it != hexToPart.end())
{

View File

@ -116,6 +116,7 @@ void CClient::waitForMoveAndSend(int color)
{
try
{
setThreadName("CClient::waitForMoveAndSend");
assert(vstd::contains(battleints, color));
BattleAction ba = battleints[color]->activeStack(gs->curB->battleGetStackByID(gs->curB->activeStack, false));
MakeAction temp_action(ba);

View File

@ -104,3 +104,45 @@ bool BattleHex::isAvailable() const
{
return isValid() && getX() > 0 && getX() < GameConstants::BFIELD_WIDTH-1;
}
BattleHex BattleHex::getClosestTile(bool attackerOwned, BattleHex initialPos, std::set<BattleHex> & possibilities)
{
std::vector<BattleHex> sortedTiles (possibilities.begin(), possibilities.end()); //set can't be sorted properly :(
BattleHex initialHex = BattleHex(initialPos);
auto compareDistance = [initialHex](const BattleHex left, const BattleHex right) -> bool
{
return initialHex.getDistance (initialHex, left) < initialHex.getDistance (initialHex, right);
};
boost::sort (sortedTiles, compareDistance); //closest tiles at front
int closestDistance = initialHex.getDistance(initialPos, sortedTiles.front()); //sometimes closest tiles can be many hexes away
auto notClosest = [closestDistance, initialPos](const BattleHex here) -> bool
{
return closestDistance < here.getDistance (initialPos, here);
};
vstd::erase_if(sortedTiles, notClosest); //only closest tiles are interesting
auto compareHorizontal = [attackerOwned, initialPos](const BattleHex left, const BattleHex right) -> bool
{
if(left.getX() != right.getX())
{
if (attackerOwned)
return left.getX() > right.getX(); //find furthest right
else
return left.getX() < right.getX(); //find furthest left
}
else
{
//Prefer tiles in the same row.
return std::abs(left.getY() - initialPos.getY()) < std::abs(right.getY() - initialPos.getY());
}
};
boost::sort (sortedTiles, compareHorizontal);
return sortedTiles.front();
}

View File

@ -111,4 +111,5 @@ struct DLL_LINKAGE BattleHex
static void checkAndPush(BattleHex tile, std::vector<BattleHex> & ret);
bool isAvailable() const; //valid position not in first or last column
static BattleHex getClosestTile(bool attackerOwned, BattleHex initialPos, std::set<BattleHex> & possibilities); //TODO: vector or set? copying one to another is bad
};

View File

@ -36,48 +36,6 @@ const CStack * BattleInfo::getNextStack() const
return NULL;
}
BattleHex BattleInfo::getClosestTile(bool attackerOwned, BattleHex initialPos, std::set<BattleHex> & possibilities) const
{
std::vector<BattleHex> sortedTiles (possibilities.begin(), possibilities.end()); //set can't be sorted properly :(
BattleHex initialHex = BattleHex(initialPos);
auto compareDistance = [initialHex](const BattleHex left, const BattleHex right) -> bool
{
return initialHex.getDistance (initialHex, left) < initialHex.getDistance (initialHex, right);
};
boost::sort (sortedTiles, compareDistance); //closest tiles at front
int closestDistance = initialHex.getDistance(initialPos, sortedTiles.front()); //sometimes closest tiles can be many hexes away
auto notClosest = [closestDistance, initialPos](const BattleHex here) -> bool
{
return closestDistance < here.getDistance (initialPos, here);
};
vstd::erase_if(sortedTiles, notClosest); //only closest tiles are interesting
auto compareHorizontal = [attackerOwned, initialPos](const BattleHex left, const BattleHex right) -> bool
{
if(left.getX() != right.getX())
{
if (attackerOwned)
return left.getX() > right.getX(); //find furthest right
else
return left.getX() < right.getX(); //find furthest left
}
else
{
//Prefer tiles in the same row.
return std::abs(left.getY() - initialPos.getY()) < std::abs(right.getY() - initialPos.getY());
}
};
boost::sort (sortedTiles, compareHorizontal);
return sortedTiles.front();
}
int BattleInfo::getAvaliableHex(TCreature creID, bool attackerOwned, int initialPos) const
{
bool twoHex = VLC->creh->creatures[creID]->isDoubleWide();
@ -106,7 +64,7 @@ int BattleInfo::getAvaliableHex(TCreature creID, bool attackerOwned, int initial
return BattleHex::INVALID; //all tiles are covered
}
return getClosestTile(attackerOwned, pos, occupyable);
return BattleHex::getClosestTile(attackerOwned, pos, occupyable);
}
std::pair< std::vector<BattleHex>, int > BattleInfo::getPath(BattleHex start, BattleHex dest, const CStack *stack)
@ -162,111 +120,6 @@ void BattleInfo::calculateCasualties( std::map<ui32,si32> *casualties ) const
}
}
std::set<const CStack*> BattleInfo::getAttackedCreatures(const CSpell * s, int skillLevel, ui8 attackerOwner, BattleHex destinationTile)
{
std::set<const CStack*> attackedCres; /*std::set to exclude multiple occurrences of two hex creatures*/
const ui8 attackerSide = sides[1] == attackerOwner;
const auto attackedHexes = s->rangeInHexes(destinationTile, skillLevel, attackerSide);
const bool onlyAlive = s->id != Spells::RESURRECTION && s->id != Spells::ANIMATE_DEAD; //when casting resurrection or animate dead we should be allow to select dead stack
//fixme: what about other rising spells (Sacrifice) ?
if(s->id == Spells::DEATH_RIPPLE || s->id == Spells::DESTROY_UNDEAD || s->id == Spells::ARMAGEDDON)
{
for(int it=0; it<stacks.size(); ++it)
{
if((s->id == Spells::DEATH_RIPPLE && !stacks[it]->getCreature()->isUndead()) //death ripple
|| (s->id == Spells::DESTROY_UNDEAD && stacks[it]->getCreature()->isUndead()) //destroy undead
|| (s->id == Spells::ARMAGEDDON) //Armageddon
)
{
if(stacks[it]->isValidTarget())
attackedCres.insert(stacks[it]);
}
}
}
else if (s->id == Spells::CHAIN_LIGHTNING)
{
std::set<BattleHex> possibleHexes;
BOOST_FOREACH (auto stack, stacks)
{
if (stack->isValidTarget())
{
BOOST_FOREACH (auto hex, stack->getHexes())
{
possibleHexes.insert (hex);
}
}
}
BattleHex lightningHex = destinationTile;
for (int i = 0; i < 5; ++i) //TODO: depends on spell school level
{
auto stack = battleGetStackByPos (lightningHex, true);
if (!stack)
break;
attackedCres.insert (stack);
BOOST_FOREACH (auto hex, stack->getHexes())
{
possibleHexes.erase (hex); //can't hit same place twice
}
lightningHex = getClosestTile (attackerOwner, destinationTile, possibleHexes);
}
}
else if (s->range[skillLevel].size() > 1) //custom many-hex range
{
BOOST_FOREACH(BattleHex hex, attackedHexes)
{
if(const CStack * st = battleGetStackByPos(hex, onlyAlive))
{
if (s->id == 76) //Death Cloud //TODO: fireball and fire immunity
{
if (st->isLiving() || st->coversPos(destinationTile)) //directly hit or alive
{
attackedCres.insert(st);
}
}
else
attackedCres.insert(st);
}
}
}
else if(s->getTargetType() == CSpell::CREATURE_EXPERT_MASSIVE)
{
if(skillLevel < 3) /*not expert */
{
const CStack * st = battleGetStackByPos(destinationTile, onlyAlive);
if(st)
attackedCres.insert(st);
}
else
{
for(int it=0; it<stacks.size(); ++it)
{
/*if it's non negative spell and our unit or non positive spell and hostile unit */
if((!s->isNegative() && stacks[it]->owner == attackerOwner)
||(!s->isPositive() && stacks[it]->owner != attackerOwner )
)
{
if(stacks[it]->isValidTarget(!onlyAlive))
attackedCres.insert(stacks[it]);
}
}
} //if(caster->getSpellSchoolLevel(s) < 3)
}
else if(s->getTargetType() == CSpell::CREATURE)
{
if(const CStack * st = battleGetStackByPos(destinationTile, onlyAlive))
attackedCres.insert(st);
}
else //custom range from attackedHexes
{
BOOST_FOREACH(BattleHex hex, attackedHexes)
{
if(const CStack * st = battleGetStackByPos(hex, onlyAlive))
attackedCres.insert(st);
}
}
return attackedCres;
}
int BattleInfo::calculateSpellDuration( const CSpell * spell, const CGHeroInstance * caster, int usedSpellPower)
{
@ -309,85 +162,6 @@ CStack * BattleInfo::generateNewStack(const CStackBasicDescriptor &base, bool at
return ret;
}
ui32 BattleInfo::calculateSpellBonus(ui32 baseDamage, const CSpell * sp, const CGHeroInstance * caster, const CStack * affectedCreature) const
{
ui32 ret = baseDamage;
//applying sorcery secondary skill
if(caster)
{
ret *= (100.0 + caster->valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, CGHeroInstance::SORCERY)) / 100.0;
ret *= (100.0 + caster->valOfBonuses(Bonus::SPELL_DAMAGE) + caster->valOfBonuses(Bonus::SPECIFIC_SPELL_DAMAGE, sp->id)) / 100.0;
if(sp->air)
ret *= (100.0 + caster->valOfBonuses(Bonus::AIR_SPELL_DMG_PREMY)) / 100.0;
else if(sp->fire) //only one type of bonus for Magic Arrow
ret *= (100.0 + caster->valOfBonuses(Bonus::FIRE_SPELL_DMG_PREMY)) / 100.0;
else if(sp->water)
ret *= (100.0 + caster->valOfBonuses(Bonus::WATER_SPELL_DMG_PREMY)) / 100.0;
else if(sp->earth)
ret *= (100.0 + caster->valOfBonuses(Bonus::EARTH_SPELL_DMG_PREMY)) / 100.0;
if (affectedCreature && affectedCreature->getCreature()->level) //Hero specials like Solmyr, Deemer
ret *= (100. + ((caster->valOfBonuses(Bonus::SPECIAL_SPELL_LEV, sp->id) * caster->level) / affectedCreature->getCreature()->level)) / 100.0;
}
return ret;
}
ui32 BattleInfo::calculateSpellDmg( const CSpell * sp, const CGHeroInstance * caster, const CStack * affectedCreature, int spellSchoolLevel, int usedSpellPower ) const
{
ui32 ret = 0; //value to return
//15 - magic arrows, 16 - ice bolt, 17 - lightning bolt, 18 - implosion, 20 - frost ring, 21 - fireball, 22 - inferno, 23 - meteor shower,
//24 - death ripple, 25 - destroy undead, 26 - armageddon, 77 - thunderbolt
//check if spell really does damage - if not, return 0
if(VLC->spellh->damageSpells.find(sp->id) == VLC->spellh->damageSpells.end())
return 0;
ret = usedSpellPower * sp->power;
ret += sp->powers[spellSchoolLevel];
//affected creature-specific part
if(affectedCreature)
{
//applying protections - when spell has more then one elements, only one protection should be applied (I think)
if(sp->air && affectedCreature->hasBonusOfType(Bonus::SPELL_DAMAGE_REDUCTION, 0)) //air spell & protection from air
{
ret *= affectedCreature->valOfBonuses(Bonus::SPELL_DAMAGE_REDUCTION, 0);
ret /= 100;
}
else if(sp->fire && affectedCreature->hasBonusOfType(Bonus::SPELL_DAMAGE_REDUCTION, 1)) //fire spell & protection from fire
{
ret *= affectedCreature->valOfBonuses(Bonus::SPELL_DAMAGE_REDUCTION, 1);
ret /= 100;
}
else if(sp->water && affectedCreature->hasBonusOfType(Bonus::SPELL_DAMAGE_REDUCTION, 2)) //water spell & protection from water
{
ret *= affectedCreature->valOfBonuses(Bonus::SPELL_DAMAGE_REDUCTION, 2);
ret /= 100;
}
else if (sp->earth && affectedCreature->hasBonusOfType(Bonus::SPELL_DAMAGE_REDUCTION, 3)) //earth spell & protection from earth
{
ret *= affectedCreature->valOfBonuses(Bonus::SPELL_DAMAGE_REDUCTION, 3);
ret /= 100;
}
//general spell dmg reduction
if(sp->air && affectedCreature->hasBonusOfType(Bonus::SPELL_DAMAGE_REDUCTION, -1)) //air spell & protection from air
{
ret *= affectedCreature->valOfBonuses(Bonus::SPELL_DAMAGE_REDUCTION, -1);
ret /= 100;
}
//dmg increasing
if( affectedCreature->hasBonusOfType(Bonus::MORE_DAMAGE_FROM_SPELL, sp->id) )
{
ret *= 100 + affectedCreature->valOfBonuses(Bonus::MORE_DAMAGE_FROM_SPELL, sp->id);
ret /= 100;
}
}
ret = calculateSpellBonus(ret, sp, caster, affectedCreature);
return ret;
}
ui32 BattleInfo::calculateHealedHP(const CGHeroInstance * caster, const CSpell * spell, const CStack * stack, const CStack * sacrificedStack) const
{
bool resurrect = resurrects(spell->id);

View File

@ -90,7 +90,6 @@ struct DLL_LINKAGE BattleInfo : public CBonusSystemNode, public CBattleInfoCallb
//void getAccessibilityMap(bool *accessibility, bool twoHex, bool attackerOwned, bool addOccupiable, std::set<BattleHex> & occupyable, bool flying, const CStack* stackToOmmit = NULL) const; //send pointer to at least 187 allocated bytes
//static bool isAccessible(BattleHex hex, bool * accessibility, bool twoHex, bool attackerOwned, bool flying, bool lastPos); //helper for makeBFS
BattleHex getClosestTile (bool attackerOwned, BattleHex initialPos, std::set<BattleHex> & possibilities) const; //TODO: vector or set? copying one to another is bad
int getAvaliableHex(TCreature creID, bool attackerOwned, int initialPos = -1) const; //find place for summon / clone effects
//void makeBFS(BattleHex start, bool*accessibility, BattleHex *predecessor, int *dists, bool twoHex, bool attackerOwned, bool flying, bool fillPredecessors) const; //*accessibility must be prepared bool[187] array; last two pointers must point to the at least 187-elements int arrays - there is written result
std::pair< std::vector<BattleHex>, int > getPath(BattleHex start, BattleHex dest, const CStack *stack); //returned value: pair<path, length>; length may be different than number of elements in path since flying vreatures jump between distant hexes
@ -102,8 +101,7 @@ struct DLL_LINKAGE BattleInfo : public CBonusSystemNode, public CBattleInfoCallb
ui32 calculateDmg(const CStack* attacker, const CStack* defender, const CGHeroInstance * attackerHero, const CGHeroInstance * defendingHero, bool shooting, ui8 charge, bool lucky, bool deathBlow, bool ballistaDoubleDmg); //charge - number of hexes travelled before attack (for champion's jousting)
void calculateCasualties(std::map<ui32,si32> *casualties) const; //casualties are array of maps size 2 (attacker, defeneder), maps are (crid => amount)
using CBattleInfoCallback::getAttackedCreatures;
std::set<const CStack*> getAttackedCreatures(const CSpell * s, int skillLevel, ui8 attackerOwner, BattleHex destinationTile); //calculates stack affected by given spell
//void getPotentiallyAttackableHexes(AttackableTiles &at, const CStack* attacker, BattleHex destinationTile, BattleHex attackerPos); //hexes around target that could be attacked in melee
//std::set<CStack*> getAttackedCreatures(const CStack* attacker, BattleHex destinationTile, BattleHex attackerPos = BattleHex::INVALID); //calculates range of multi-hex attacks
//std::set<BattleHex> getAttackedHexes(const CStack* attacker, BattleHex destinationTile, BattleHex attackerPos = BattleHex::INVALID); //calculates range of multi-hex attacks
@ -112,8 +110,6 @@ struct DLL_LINKAGE BattleInfo : public CBonusSystemNode, public CBattleInfoCallb
CStack * generateNewStack(const CStackBasicDescriptor &base, bool attackerOwned, int slot, BattleHex position) const; //helper for CGameHandler::setupBattle and spells addign new stacks to the battlefield
int getIdForNewStack() const; //suggest a currently unused ID that'd suitable for generating a new stack
//std::pair<const CStack *, BattleHex> getNearestStack(const CStack * closest, boost::logic::tribool attackerOwned) const; //if attackerOwned is indetermnate, returened stack is of any owner; hex is the number of hex we should be looking from; returns (nerarest creature, predecessorHex)
ui32 calculateSpellBonus(ui32 baseDamage, const CSpell * sp, const CGHeroInstance * caster, const CStack * affectedCreature) const;
ui32 calculateSpellDmg(const CSpell * sp, const CGHeroInstance * caster, const CStack * affectedCreature, int spellSchoolLevel, int usedSpellPower) const; //calculates damage inflicted by spell
ui32 calculateHealedHP(const CGHeroInstance * caster, const CSpell * spell, const CStack * stack, const CStack * sacrificedStack = NULL) const;
ui32 calculateHealedHP(int healedHealth, const CSpell * spell, const CStack * stack) const; //for Archangel
ui32 calculateHealedHP(const CSpell * spell, int usedSpellPower, int spellSchoolLevel, const CStack * stack) const; //unused

View File

@ -1816,6 +1816,189 @@ ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCastThisSpell
return battleIsImmune(NULL, spell, mode, dest);
}
ui32 CBattleInfoCallback::calculateSpellBonus(ui32 baseDamage, const CSpell * sp, const CGHeroInstance * caster, const CStack * affectedCreature) const
{
ui32 ret = baseDamage;
//applying sorcery secondary skill
if(caster)
{
ret *= (100.0 + caster->valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, CGHeroInstance::SORCERY)) / 100.0;
ret *= (100.0 + caster->valOfBonuses(Bonus::SPELL_DAMAGE) + caster->valOfBonuses(Bonus::SPECIFIC_SPELL_DAMAGE, sp->id)) / 100.0;
if(sp->air)
ret *= (100.0 + caster->valOfBonuses(Bonus::AIR_SPELL_DMG_PREMY)) / 100.0;
else if(sp->fire) //only one type of bonus for Magic Arrow
ret *= (100.0 + caster->valOfBonuses(Bonus::FIRE_SPELL_DMG_PREMY)) / 100.0;
else if(sp->water)
ret *= (100.0 + caster->valOfBonuses(Bonus::WATER_SPELL_DMG_PREMY)) / 100.0;
else if(sp->earth)
ret *= (100.0 + caster->valOfBonuses(Bonus::EARTH_SPELL_DMG_PREMY)) / 100.0;
if (affectedCreature && affectedCreature->getCreature()->level) //Hero specials like Solmyr, Deemer
ret *= (100. + ((caster->valOfBonuses(Bonus::SPECIAL_SPELL_LEV, sp->id) * caster->level) / affectedCreature->getCreature()->level)) / 100.0;
}
return ret;
}
ui32 CBattleInfoCallback::calculateSpellDmg( const CSpell * sp, const CGHeroInstance * caster, const CStack * affectedCreature, int spellSchoolLevel, int usedSpellPower ) const
{
ui32 ret = 0; //value to return
//check if spell really does damage - if not, return 0
if(VLC->spellh->damageSpells.find(sp->id) == VLC->spellh->damageSpells.end())
return 0;
ret = usedSpellPower * sp->power;
ret += sp->powers[spellSchoolLevel];
//affected creature-specific part
if(affectedCreature)
{
//applying protections - when spell has more then one elements, only one protection should be applied (I think)
if(sp->air && affectedCreature->hasBonusOfType(Bonus::SPELL_DAMAGE_REDUCTION, 0)) //air spell & protection from air
{
ret *= affectedCreature->valOfBonuses(Bonus::SPELL_DAMAGE_REDUCTION, 0);
ret /= 100;
}
else if(sp->fire && affectedCreature->hasBonusOfType(Bonus::SPELL_DAMAGE_REDUCTION, 1)) //fire spell & protection from fire
{
ret *= affectedCreature->valOfBonuses(Bonus::SPELL_DAMAGE_REDUCTION, 1);
ret /= 100;
}
else if(sp->water && affectedCreature->hasBonusOfType(Bonus::SPELL_DAMAGE_REDUCTION, 2)) //water spell & protection from water
{
ret *= affectedCreature->valOfBonuses(Bonus::SPELL_DAMAGE_REDUCTION, 2);
ret /= 100;
}
else if (sp->earth && affectedCreature->hasBonusOfType(Bonus::SPELL_DAMAGE_REDUCTION, 3)) //earth spell & protection from earth
{
ret *= affectedCreature->valOfBonuses(Bonus::SPELL_DAMAGE_REDUCTION, 3);
ret /= 100;
}
//general spell dmg reduction
//FIXME?
if(sp->air && affectedCreature->hasBonusOfType(Bonus::SPELL_DAMAGE_REDUCTION, -1))
{
ret *= affectedCreature->valOfBonuses(Bonus::SPELL_DAMAGE_REDUCTION, -1);
ret /= 100;
}
//dmg increasing
if( affectedCreature->hasBonusOfType(Bonus::MORE_DAMAGE_FROM_SPELL, sp->id) )
{
ret *= 100 + affectedCreature->valOfBonuses(Bonus::MORE_DAMAGE_FROM_SPELL, sp->id);
ret /= 100;
}
}
ret = calculateSpellBonus(ret, sp, caster, affectedCreature);
return ret;
}
std::set<const CStack*> CBattleInfoCallback::getAffectedCreatures(const CSpell * spell, int skillLevel, ui8 attackerOwner, BattleHex destinationTile)
{
std::set<const CStack*> attackedCres; /*std::set to exclude multiple occurrences of two hex creatures*/
const ui8 attackerSide = playerToSide(attackerOwner) == 1;
const auto attackedHexes = spell->rangeInHexes(destinationTile, skillLevel, attackerSide);
const bool onlyAlive = spell->id != Spells::RESURRECTION && spell->id != Spells::ANIMATE_DEAD; //when casting resurrection or animate dead we should be allow to select dead stack
//fixme: what about other rising spells (Sacrifice) ?
if(spell->id == Spells::DEATH_RIPPLE || spell->id == Spells::DESTROY_UNDEAD || spell->id == Spells::ARMAGEDDON)
{
BOOST_FOREACH(const CStack *stack, battleGetAllStacks())
{
if((spell->id == Spells::DEATH_RIPPLE && !stack->getCreature()->isUndead()) //death ripple
|| (spell->id == Spells::DESTROY_UNDEAD && stack->getCreature()->isUndead()) //destroy undead
|| (spell->id == Spells::ARMAGEDDON) //Armageddon
)
{
if(stack->isValidTarget())
attackedCres.insert(stack);
}
}
}
else if (spell->id == Spells::CHAIN_LIGHTNING)
{
std::set<BattleHex> possibleHexes;
BOOST_FOREACH (auto stack, battleGetAllStacks())
{
if (stack->isValidTarget())
{
BOOST_FOREACH (auto hex, stack->getHexes())
{
possibleHexes.insert (hex);
}
}
}
BattleHex lightningHex = destinationTile;
for (int i = 0; i < 5; ++i) //TODO: depends on spell school level
{
auto stack = battleGetStackByPos (lightningHex, true);
if (!stack)
break;
attackedCres.insert (stack);
BOOST_FOREACH (auto hex, stack->getHexes())
{
possibleHexes.erase (hex); //can't hit same place twice
}
lightningHex = BattleHex::getClosestTile (attackerOwner, destinationTile, possibleHexes);
}
}
else if (spell->range[skillLevel].size() > 1) //custom many-hex range
{
BOOST_FOREACH(BattleHex hex, attackedHexes)
{
if(const CStack * st = battleGetStackByPos(hex, onlyAlive))
{
if (spell->id == 76) //Death Cloud //TODO: fireball and fire immunity
{
if (st->isLiving() || st->coversPos(destinationTile)) //directly hit or alive
{
attackedCres.insert(st);
}
}
else
attackedCres.insert(st);
}
}
}
else if(spell->getTargetType() == CSpell::CREATURE_EXPERT_MASSIVE)
{
if(skillLevel < 3) /*not expert */
{
const CStack * st = battleGetStackByPos(destinationTile, onlyAlive);
if(st)
attackedCres.insert(st);
}
else
{
BOOST_FOREACH (auto stack, battleGetAllStacks())
{
/*if it's non negative spell and our unit or non positive spell and hostile unit */
if((!spell->isNegative() && stack->owner == attackerOwner)
||(!spell->isPositive() && stack->owner != attackerOwner )
)
{
if(stack->isValidTarget(!onlyAlive))
attackedCres.insert(stack);
}
}
} //if(caster->getSpellSchoolLevel(s) < 3)
}
else if(spell->getTargetType() == CSpell::CREATURE)
{
if(const CStack * st = battleGetStackByPos(destinationTile, onlyAlive))
attackedCres.insert(st);
}
else //custom range from attackedHexes
{
BOOST_FOREACH(BattleHex hex, attackedHexes)
{
if(const CStack * st = battleGetStackByPos(hex, onlyAlive))
attackedCres.insert(st);
}
}
return attackedCres;
}
const CStack * CBattleInfoCallback::getStackIf(boost::function<bool(const CStack*)> pred) const
{
RETURN_IF_NOT_BATTLE(nullptr);
@ -2110,6 +2293,19 @@ bool CPlayerBattleCallback::battleCanCastSpell(ESpellCastProblem::ESpellCastProb
return problem == ESpellCastProblem::OK;
}
const CGHeroInstance * CPlayerBattleCallback::battleGetMyHero() const
{
return CBattleInfoEssentials::battleGetFightingHero(battleGetMySide());
}
InfoAboutHero CPlayerBattleCallback::battleGetEnemyHero() const
{
InfoAboutHero ret;
assert(0);
///TODO implement and replace usages of battleGetFightingHero obtaining enemy hero
return ret;
}
BattleAttackInfo::BattleAttackInfo(const CStack *Attacker, const CStack *Defender, bool Shooting)
{
attacker = Attacker;

View File

@ -9,6 +9,7 @@ class CSpell;
struct BattleInfo;
struct CObstacleInstance;
class IBonusBearer;
struct InfoAboutHero;
namespace boost
{class shared_mutex;}
@ -243,6 +244,9 @@ public:
ESpellCastProblem::ESpellCastProblem battleCanCastThisSpellHere(int player, const CSpell * spell, ECastingMode::ECastingMode mode, BattleHex dest) const; //checks if given player can cast given spell at given tile in given mode
ESpellCastProblem::ESpellCastProblem battleCanCreatureCastThisSpell(const CSpell * spell, BattleHex destination) const; //determines if creature can cast a spell here
std::vector<BattleHex> battleGetPossibleTargets(int player, const CSpell *spell) const;
ui32 calculateSpellBonus(ui32 baseDamage, const CSpell * sp, const CGHeroInstance * caster, const CStack * affectedCreature) const;
ui32 calculateSpellDmg(const CSpell * sp, const CGHeroInstance * caster, const CStack * affectedCreature, int spellSchoolLevel, int usedSpellPower) const; //calculates damage inflicted by spell
std::set<const CStack*> getAffectedCreatures(const CSpell * s, int skillLevel, ui8 attackerOwner, BattleHex destinationTile); //calculates stack affected by given spell
si32 battleGetRandomStackSpell(const CStack * stack, ERandomSpell mode) const;
TSpell getRandomBeneficialSpell(const CStack * subject) const;
@ -294,4 +298,6 @@ public:
int battleGetSurrenderCost() const; //returns cost of surrendering battle, -1 if surrendering is not possible
bool battleCanCastSpell(ESpellCastProblem::ESpellCastProblem *outProblem = nullptr) const; //returns true, if caller can cast a spell. If not, if pointer is given via arg, the reason will be written.
const CGHeroInstance * battleGetMyHero() const;
InfoAboutHero battleGetEnemyHero() const;
};

View File

@ -2812,8 +2812,11 @@ DuelParameters DuelParameters::fromJSON(const std::string &fname)
{
const JsonNode & spells = n["spells"];
if(spells.getType() == JsonNode::DATA_STRING && spells.String() == "all")
{
BOOST_FOREACH(auto spell, VLC->spellh->spells)
ss.spells.insert(spell->id);
if(spell->id <= Spells::SUMMON_AIR_ELEMENTAL)
ss.spells.insert(spell->id);
}
else
BOOST_FOREACH(const JsonNode &spell, n["spells"].Vector())
ss.spells.insert(spell.Float());

View File

@ -793,7 +793,7 @@ void CGameHandler::prepareAttack(BattleAttack &bat, const CStack *att, const CSt
bat.bsa.front().flags |= BattleStackAttacked::EFFECT;
bat.bsa.front().effect = VLC->spellh->spells[bonus->subtype]->mainEffectAnim; //hopefully it does not interfere with any other effect?
std::set<const CStack*> attackedCreatures = gs->curB->getAttackedCreatures(VLC->spellh->spells[bonus->subtype], bonus->val, att->owner, targetHex);
std::set<const CStack*> attackedCreatures = gs->curB->getAffectedCreatures(VLC->spellh->spells[bonus->subtype], bonus->val, att->owner, targetHex);
//TODO: get exact attacked hex for defender
BOOST_FOREACH(const CStack * stack, attackedCreatures)
@ -3941,7 +3941,7 @@ void CGameHandler::handleSpellCasting( int spellID, int spellLvl, BattleHex dest
std::set<const CStack*> attackedCres;
if (mode != ECastingMode::ENCHANTER_CASTING)
{
attackedCres = gs->curB->getAttackedCreatures(spell, spellLvl, casterColor, destination);
attackedCres = gs->curB->getAffectedCreatures(spell, spellLvl, casterColor, destination);
for(std::set<const CStack*>::const_iterator it = attackedCres.begin(); it != attackedCres.end(); ++it)
{
sc.affectedCres.insert((*it)->ID);