mirror of
https://github.com/vcmi/vcmi.git
synced 2024-12-24 22:14:36 +02:00
commit
84b2510aa4
@ -458,18 +458,14 @@ void CBattleAI::attemptCastingSpell()
|
||||
{
|
||||
int damageDealt = 0, damageReceived = 0;
|
||||
|
||||
auto stacksSuffering = cb->getAffectedCreatures(ps.spell, skillLevel, playerID, ps.dest);
|
||||
vstd::erase_if(stacksSuffering, [&](const CStack * s) -> bool
|
||||
{
|
||||
return ESpellCastProblem::OK != cb->battleStackIsImmune(hero, ps.spell, ECastingMode::HERO_CASTING, s);
|
||||
});
|
||||
auto stacksSuffering = ps.spell->getAffectedStacks(cb.get(), ECastingMode::HERO_CASTING, playerID, skillLevel, ps.dest, hero);
|
||||
|
||||
if(stacksSuffering.empty())
|
||||
return -1;
|
||||
|
||||
for(auto stack : stacksSuffering)
|
||||
{
|
||||
const int dmg = cb->calculateSpellDmg(ps.spell, hero, stack, skillLevel, spellPower);
|
||||
const int dmg = ps.spell->calculateDamage(hero, stack, skillLevel, spellPower);
|
||||
if(stack->owner == playerID)
|
||||
damageReceived += dmg;
|
||||
else
|
||||
|
@ -882,11 +882,16 @@ void CPlayerInterface::battleStacksAttacked(const std::vector<BattleStackAttacke
|
||||
{
|
||||
const CStack *defender = cb->battleGetStackByID(elem.stackAttacked, false);
|
||||
const CStack *attacker = cb->battleGetStackByID(elem.attackerID, false);
|
||||
if(elem.isEffect() && elem.effect != 12) //and not armageddon
|
||||
if(elem.isEffect())
|
||||
{
|
||||
if (defender && !elem.isSecondary())
|
||||
battleInt->displayEffect(elem.effect, defender->position);
|
||||
}
|
||||
if(elem.isSpell())
|
||||
{
|
||||
if (defender)
|
||||
battleInt->displaySpellEffect(elem.spellID, defender->position);
|
||||
}
|
||||
//FIXME: why action is deleted during enchanter cast?
|
||||
bool remoteAttack = false;
|
||||
|
||||
@ -897,11 +902,6 @@ void CPlayerInterface::battleStacksAttacked(const std::vector<BattleStackAttacke
|
||||
arg.push_back(to_put);
|
||||
}
|
||||
|
||||
if(bsa.begin()->isEffect() && bsa.begin()->effect == 12) //for armageddon - I hope this condition is enough
|
||||
{
|
||||
battleInt->displayEffect(bsa.begin()->effect, -1);
|
||||
}
|
||||
|
||||
battleInt->stacksAreAttacked(arg);
|
||||
}
|
||||
void CPlayerInterface::battleAttack(const BattleAttack *ba)
|
||||
@ -973,6 +973,15 @@ void CPlayerInterface::battleAttack(const BattleAttack *ba)
|
||||
const CStack * attacked = cb->battleGetStackByID(ba->bsa.begin()->stackAttacked);
|
||||
battleInt->stackAttacking( attacker, ba->counter() ? curAction->destinationTile + shift : curAction->additionalInfo, attacked, false);
|
||||
}
|
||||
|
||||
//battleInt->waitForAnims(); //FIXME: freeze
|
||||
|
||||
if(ba->spellLike())
|
||||
{
|
||||
//display hit animation
|
||||
SpellID spellID = ba->spellID;
|
||||
battleInt->displaySpellHit(spellID,curAction->destinationTile);
|
||||
}
|
||||
}
|
||||
void CPlayerInterface::battleObstaclePlaced(const CObstacleInstance &obstacle)
|
||||
{
|
||||
|
@ -20,6 +20,7 @@
|
||||
#include "../../lib/BattleState.h"
|
||||
#include "../../lib/CTownHandler.h"
|
||||
#include "../../lib/mapObjects/CGTownInstance.h"
|
||||
#include "../../lib/CSpellHandler.h"
|
||||
|
||||
/*
|
||||
* CBattleAnimations.cpp, part of VCMI engine
|
||||
@ -865,40 +866,46 @@ void CShootingAnimation::endAnim()
|
||||
delete this;
|
||||
}
|
||||
|
||||
CSpellEffectAnimation::CSpellEffectAnimation(CBattleInterface * _owner, ui32 _effect, BattleHex _destTile, int _dx, int _dy, bool _Vflip, bool _areaEffect)
|
||||
:CBattleAnimation(_owner), effect(_effect), destTile(_destTile), customAnim(""), x(0), y(0), dx(_dx), dy(_dy), Vflip(_Vflip) , areaEffect(_areaEffect)
|
||||
CSpellEffectAnimation::CSpellEffectAnimation(CBattleInterface * _owner, ui32 _effect, BattleHex _destTile, int _dx, int _dy, bool _Vflip, bool _alignToBottom)
|
||||
:CBattleAnimation(_owner), effect(_effect), destTile(_destTile), customAnim(""), x(-1), y(-1), dx(_dx), dy(_dy), Vflip(_Vflip), alignToBottom(_alignToBottom)
|
||||
{
|
||||
logAnim->debugStream() << "Created spell anim for effect #" << effect;
|
||||
}
|
||||
|
||||
CSpellEffectAnimation::CSpellEffectAnimation(CBattleInterface * _owner, std::string _customAnim, int _x, int _y, int _dx, int _dy, bool _Vflip, bool _areaEffect)
|
||||
:CBattleAnimation(_owner), effect(-1), destTile(0), customAnim(_customAnim), x(_x), y(_y), dx(_dx), dy(_dy), Vflip(_Vflip), areaEffect(_areaEffect)
|
||||
CSpellEffectAnimation::CSpellEffectAnimation(CBattleInterface * _owner, std::string _customAnim, int _x, int _y, int _dx, int _dy, bool _Vflip, bool _alignToBottom)
|
||||
:CBattleAnimation(_owner), effect(-1), destTile(BattleHex::INVALID), customAnim(_customAnim), x(_x), y(_y), dx(_dx), dy(_dy), Vflip(_Vflip), alignToBottom(_alignToBottom)
|
||||
{
|
||||
logAnim->debugStream() << "Created spell anim for " << customAnim;
|
||||
}
|
||||
|
||||
CSpellEffectAnimation::CSpellEffectAnimation(CBattleInterface * _owner, std::string _customAnim, BattleHex _destTile, bool _Vflip, bool _alignToBottom)
|
||||
:CBattleAnimation(_owner), effect(-1), destTile(_destTile), customAnim(_customAnim), x(-1), y(-1), dx(0), dy(0), Vflip(_Vflip), alignToBottom(_alignToBottom)
|
||||
{
|
||||
logAnim->debugStream() << "Created spell anim for " << customAnim;
|
||||
}
|
||||
|
||||
|
||||
bool CSpellEffectAnimation::init()
|
||||
{
|
||||
if(!isEarliest(true))
|
||||
return false;
|
||||
|
||||
if(effect == 12) //armageddon
|
||||
if(customAnim.empty() && effect != ui32(-1) && !graphics->battleACToDef[effect].empty())
|
||||
{
|
||||
if(effect == -1 || graphics->battleACToDef[effect].size() != 0)
|
||||
{
|
||||
CDefHandler * anim;
|
||||
if(customAnim.size())
|
||||
anim = CDefHandler::giveDef(customAnim);
|
||||
else
|
||||
anim = CDefHandler::giveDef(graphics->battleACToDef[effect][0]);
|
||||
customAnim = graphics->battleACToDef[effect][0];
|
||||
}
|
||||
|
||||
if (Vflip)
|
||||
if(customAnim.empty())
|
||||
{
|
||||
for (auto & elem : anim->ourImages)
|
||||
endAnim();
|
||||
return false;
|
||||
}
|
||||
|
||||
const bool areaEffect = (!destTile.isValid() && x == -1 && y == -1);
|
||||
|
||||
if(areaEffect) //f.e. armageddon
|
||||
{
|
||||
CSDL_Ext::VflipSurf(elem.bitmap);
|
||||
}
|
||||
}
|
||||
CDefHandler * anim = CDefHandler::giveDef(customAnim);
|
||||
|
||||
for(int i=0; i * anim->width < owner->pos.w ; ++i)
|
||||
{
|
||||
@ -906,7 +913,7 @@ bool CSpellEffectAnimation::init()
|
||||
{
|
||||
BattleEffect be;
|
||||
be.effectID = ID;
|
||||
be.anim = CDefHandler::giveDef(graphics->battleACToDef[effect][0]);
|
||||
be.anim = CDefHandler::giveDef(customAnim);
|
||||
if (Vflip)
|
||||
{
|
||||
for (auto & elem : be.anim->ourImages)
|
||||
@ -923,25 +930,17 @@ bool CSpellEffectAnimation::init()
|
||||
owner->battleEffects.push_back(be);
|
||||
}
|
||||
}
|
||||
}
|
||||
else //there is nothing to play
|
||||
{
|
||||
endAnim();
|
||||
return false;
|
||||
}
|
||||
|
||||
delete anim;
|
||||
}
|
||||
else // Effects targeted at a specific creature/hex.
|
||||
{
|
||||
if(effect == -1 || graphics->battleACToDef[effect].size() != 0)
|
||||
{
|
||||
|
||||
const CStack* destStack = owner->getCurrentPlayerInterface()->cb->battleGetStackByPos(destTile, false);
|
||||
Rect &tilePos = owner->bfield[destTile]->pos;
|
||||
BattleEffect be;
|
||||
be.effectID = ID;
|
||||
if(customAnim.size())
|
||||
be.anim = CDefHandler::giveDef(customAnim);
|
||||
else
|
||||
be.anim = CDefHandler::giveDef(graphics->battleACToDef[effect][0]);
|
||||
|
||||
if (Vflip)
|
||||
{
|
||||
@ -953,28 +952,31 @@ bool CSpellEffectAnimation::init()
|
||||
|
||||
be.currentFrame = 0;
|
||||
be.maxFrame = be.anim->ourImages.size();
|
||||
if(effect == 1)
|
||||
be.maxFrame = 3;
|
||||
|
||||
switch (effect)
|
||||
//todo: lightning anim frame count override
|
||||
|
||||
// if(effect == 1)
|
||||
// be.maxFrame = 3;
|
||||
|
||||
if(x == -1)
|
||||
{
|
||||
be.x = tilePos.x + tilePos.w/2 - be.anim->width/2;
|
||||
}
|
||||
else
|
||||
{
|
||||
case ui32(-1):
|
||||
be.x = x;
|
||||
be.y = y;
|
||||
break;
|
||||
case 0: // Prayer and Lightning Bolt.
|
||||
case 1:
|
||||
case 19: // Slow
|
||||
// Position effect with it's bottom center touching the bottom center of affected tile(s).
|
||||
be.x = tilePos.x + tilePos.w/2 - be.anim->width/2;
|
||||
be.y = tilePos.y + tilePos.h - be.anim->height;
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
// Position effect with it's center touching the top center of affected tile(s).
|
||||
be.x = tilePos.x + tilePos.w/2 - be.anim->width/2;
|
||||
if(y == -1)
|
||||
{
|
||||
if(alignToBottom)
|
||||
be.y = tilePos.y + tilePos.h - be.anim->height;
|
||||
else
|
||||
be.y = tilePos.y - be.anim->height/2;
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
be.y = y;
|
||||
}
|
||||
|
||||
// Correction for 2-hex creatures.
|
||||
@ -982,18 +984,10 @@ bool CSpellEffectAnimation::init()
|
||||
be.x += (destStack->attackerOwned ? -1 : 1)*tilePos.w/2;
|
||||
|
||||
//Indicate if effect should be drawn on top of everything or just on top of the hex
|
||||
if(areaEffect)
|
||||
be.position = BattleHex::INVALID;
|
||||
else
|
||||
be.position = destTile;
|
||||
|
||||
owner->battleEffects.push_back(be);
|
||||
}
|
||||
else //there is nothing to play
|
||||
{
|
||||
endAnim();
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
//battleEffects
|
||||
return true;
|
||||
|
@ -223,13 +223,14 @@ private:
|
||||
std::string customAnim;
|
||||
int x, y, dx, dy;
|
||||
bool Vflip;
|
||||
bool areaEffect;
|
||||
bool alignToBottom;
|
||||
public:
|
||||
bool init();
|
||||
void nextFrame();
|
||||
void endAnim();
|
||||
|
||||
CSpellEffectAnimation(CBattleInterface * _owner, ui32 _effect, BattleHex _destTile, int _dx = 0, int _dy = 0, bool _Vflip = false, bool _areaEffect = true);
|
||||
CSpellEffectAnimation(CBattleInterface * _owner, std::string _customAnim, int _x, int _y, int _dx = 0, int _dy = 0, bool _Vflip = false, bool _areaEffect = true);
|
||||
CSpellEffectAnimation(CBattleInterface * _owner, ui32 _effect, BattleHex _destTile, int _dx = 0, int _dy = 0, bool _Vflip = false, bool _alignToBottom = false);
|
||||
CSpellEffectAnimation(CBattleInterface * _owner, std::string _customAnim, int _x, int _y, int _dx = 0, int _dy = 0, bool _Vflip = false, bool _alignToBottom = false);
|
||||
CSpellEffectAnimation(CBattleInterface * _owner, std::string _customAnim, BattleHex _destTile, bool _Vflip = false, bool _alignToBottom = false);
|
||||
virtual ~CSpellEffectAnimation(){};
|
||||
};
|
||||
|
@ -1226,57 +1226,54 @@ void CBattleInterface::displayBattleFinished()
|
||||
|
||||
void CBattleInterface::spellCast( const BattleSpellCast * sc )
|
||||
{
|
||||
const CSpell &spell = *CGI->spellh->objects[sc->id];
|
||||
|
||||
std::vector< std::string > anims; //for magic arrow and ice bolt
|
||||
const SpellID spellID(sc->id);
|
||||
const CSpell &spell = * spellID.toSpell();
|
||||
|
||||
const std::string& castSoundPath = spell.getCastSound();
|
||||
|
||||
if(!castSoundPath.empty())
|
||||
CCS->soundh->playSound(castSoundPath);
|
||||
|
||||
switch(sc->id)
|
||||
std::string casterCreatureName = "";
|
||||
Point srccoord = (sc->side ? Point(770, 60) : Point(30, 60)) + pos; //hero position by default
|
||||
{
|
||||
case SpellID::MAGIC_ARROW:
|
||||
const auto casterStackID = sc->casterStack;
|
||||
|
||||
if(casterStackID > 0)
|
||||
{
|
||||
//initialization of anims
|
||||
anims.push_back("C20SPX0.DEF"); anims.push_back("C20SPX1.DEF"); anims.push_back("C20SPX2.DEF"); anims.push_back("C20SPX3.DEF"); anims.push_back("C20SPX4.DEF");
|
||||
const CStack * casterStack = curInt->cb->battleGetStackByID(casterStackID);
|
||||
if(casterStack != nullptr)
|
||||
{
|
||||
casterCreatureName = casterStack->type->namePl;
|
||||
|
||||
srccoord = CClickableHex::getXYUnitAnim(casterStack->position, casterStack, this);
|
||||
srccoord.x += 250;
|
||||
srccoord.y += 240;
|
||||
}
|
||||
case SpellID::ICE_BOLT:
|
||||
{
|
||||
if(anims.size() == 0) //initialization of anims
|
||||
{
|
||||
anims.push_back("C08SPW0.DEF"); anims.push_back("C08SPW1.DEF"); anims.push_back("C08SPW2.DEF"); anims.push_back("C08SPW3.DEF"); anims.push_back("C08SPW4.DEF");
|
||||
}
|
||||
} //end of ice bolt only part
|
||||
{ //common ice bolt and magic arrow part
|
||||
//initial variables
|
||||
std::string animToDisplay;
|
||||
Point srccoord = (sc->side ? Point(770, 60) : Point(30, 60)) + pos;
|
||||
Point destcoord = CClickableHex::getXYUnitAnim(sc->tile, curInt->cb->battleGetStackByPos(sc->tile), this); //position attacked by arrow
|
||||
}
|
||||
|
||||
//TODO: play custom cast animation
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
//playing projectile animation
|
||||
if(sc->tile.isValid())
|
||||
{
|
||||
Point destcoord = CClickableHex::getXYUnitAnim(sc->tile, curInt->cb->battleGetStackByPos(sc->tile), this); //position attacked by projectile
|
||||
destcoord.x += 250; destcoord.y += 240;
|
||||
|
||||
//animation angle
|
||||
double angle = atan2(static_cast<double>(destcoord.x - srccoord.x), static_cast<double>(destcoord.y - srccoord.y));
|
||||
bool Vflip = false;
|
||||
if (angle < 0)
|
||||
{
|
||||
Vflip = true;
|
||||
bool Vflip = (angle < 0);
|
||||
if(Vflip)
|
||||
angle = -angle;
|
||||
}
|
||||
|
||||
//choosing animation by angle
|
||||
if(angle > 1.50)
|
||||
animToDisplay = anims[0];
|
||||
else if(angle > 1.20)
|
||||
animToDisplay = anims[1];
|
||||
else if(angle > 0.90)
|
||||
animToDisplay = anims[2];
|
||||
else if(angle > 0.60)
|
||||
animToDisplay = anims[3];
|
||||
else
|
||||
animToDisplay = anims[4];
|
||||
std::string animToDisplay = spell.animationInfo.selectProjectile(angle);
|
||||
|
||||
if(!animToDisplay.empty())
|
||||
{
|
||||
//displaying animation
|
||||
CDefEssential * animDef = CDefHandler::giveDefEss(animToDisplay);
|
||||
double diffX = (destcoord.x - srccoord.x)*(destcoord.x - srccoord.x);
|
||||
@ -1289,30 +1286,25 @@ void CBattleInterface::spellCast( const BattleSpellCast * sc )
|
||||
|
||||
delete animDef;
|
||||
addNewAnim(new CSpellEffectAnimation(this, animToDisplay, srccoord.x, srccoord.y, dx, dy, Vflip));
|
||||
}
|
||||
}
|
||||
waitForAnims();
|
||||
|
||||
break; //for 15 and 16 cases
|
||||
}
|
||||
case SpellID::LIGHTNING_BOLT:
|
||||
case SpellID::TITANS_LIGHTNING_BOLT:
|
||||
case SpellID::THUNDERBOLT:
|
||||
case SpellID::CHAIN_LIGHTNING: //TODO: zigzag effect
|
||||
for (auto & elem : sc->affectedCres) //in case we have multiple targets
|
||||
displaySpellHit(spellID, sc->tile);
|
||||
|
||||
//queuing affect /resist animation
|
||||
for (auto & elem : sc->affectedCres)
|
||||
{
|
||||
displayEffect(1, curInt->cb->battleGetStackByID(elem, false)->position);
|
||||
displayEffect(spell.mainEffectAnim, curInt->cb->battleGetStackByID(elem, false)->position);
|
||||
BattleHex position = curInt->cb->battleGetStackByID(elem, false)->position;
|
||||
|
||||
if(vstd::contains(sc->resisted,elem))
|
||||
displayEffect(78, position);
|
||||
else
|
||||
displaySpellEffect(spellID, position);
|
||||
}
|
||||
break;
|
||||
case SpellID::DISPEL:
|
||||
case SpellID::CURE:
|
||||
case SpellID::RESURRECTION:
|
||||
case SpellID::ANIMATE_DEAD:
|
||||
case SpellID::DISPEL_HELPFUL_SPELLS:
|
||||
case SpellID::SACRIFICE: //TODO: animation upon killed stack
|
||||
for(auto & elem : sc->affectedCres)
|
||||
|
||||
switch(sc->id)
|
||||
{
|
||||
displayEffect(spell.mainEffectAnim, curInt->cb->battleGetStackByID(elem, false)->position);
|
||||
}
|
||||
break;
|
||||
case SpellID::SUMMON_FIRE_ELEMENTAL:
|
||||
case SpellID::SUMMON_EARTH_ELEMENTAL:
|
||||
case SpellID::SUMMON_WATER_ELEMENTAL:
|
||||
@ -1323,19 +1315,6 @@ void CBattleInterface::spellCast( const BattleSpellCast * sc )
|
||||
break;
|
||||
} //switch(sc->id)
|
||||
|
||||
if (spell.isDamageSpell() && sc->affectedCres.empty()) //for example Inferno that causes no BattleStackAttacked
|
||||
{
|
||||
if(sc->tile.isValid() && graphics->battleACToDef.count(spell.mainEffectAnim)) //eg. when casting Lind Mine or Fire Wall
|
||||
displayEffect (spell.mainEffectAnim, sc->tile);
|
||||
}
|
||||
|
||||
//support for resistance
|
||||
for(auto & elem : sc->resisted)
|
||||
{
|
||||
int tile = curInt->cb->battleGetStackByID(elem)->position;
|
||||
displayEffect(78, tile);
|
||||
}
|
||||
|
||||
//displaying message in console
|
||||
bool customSpell = false;
|
||||
if(sc->affectedCres.size() == 1)
|
||||
@ -1393,7 +1372,7 @@ void CBattleInterface::spellCast( const BattleSpellCast * sc )
|
||||
}
|
||||
//The %s shrivel with age, and lose %d hit points."
|
||||
TBonusListPtr bl = curInt->cb->battleGetStackByID(*sc->affectedCres.begin(), false)->getBonuses(Selector::type(Bonus::STACK_HEALTH));
|
||||
bl->remove_if(Selector::source(Bonus::SPELL_EFFECT, 75));
|
||||
bl->remove_if(Selector::source(Bonus::SPELL_EFFECT, SpellID::AGE));
|
||||
boost::algorithm::replace_first(text, "%d", boost::lexical_cast<std::string>(bl->totalValue()/2));
|
||||
}
|
||||
break;
|
||||
@ -1427,15 +1406,15 @@ void CBattleInterface::spellCast( const BattleSpellCast * sc )
|
||||
text = CGI->generaltexth->allTexts[118]; //One %s dies under the terrible gaze of the %s.
|
||||
boost::algorithm::replace_first(text, "%s", curInt->cb->battleGetStackByID(*sc->affectedCres.begin())->type->nameSing);
|
||||
}
|
||||
boost::algorithm::replace_first(text, "%s", CGI->creh->creatures[sc->attackerType]->namePl); //casting stack
|
||||
boost::algorithm::replace_first(text, "%s", casterCreatureName); //casting stack
|
||||
}
|
||||
else
|
||||
text = "";
|
||||
break;
|
||||
default:
|
||||
text = CGI->generaltexth->allTexts[565]; //The %s casts %s
|
||||
if(auto castingCreature = vstd::atOrDefault(CGI->creh->creatures, sc->attackerType, nullptr))
|
||||
boost::algorithm::replace_first(text, "%s", castingCreature->namePl); //casting stack
|
||||
if(casterCreatureName != "")
|
||||
boost::algorithm::replace_first(text, "%s", casterCreatureName); //casting stack
|
||||
else
|
||||
boost::algorithm::replace_first(text, "%s", "@Unknown caster@"); //should not happen
|
||||
}
|
||||
@ -1465,9 +1444,9 @@ void CBattleInterface::spellCast( const BattleSpellCast * sc )
|
||||
{
|
||||
boost::algorithm::replace_first(text, "%s", curInt->cb->battleGetHeroInfo(sc->side).name);
|
||||
}
|
||||
if(auto castingCreature = vstd::atOrDefault(CGI->creh->creatures, sc->attackerType, nullptr))
|
||||
if(casterCreatureName != "")
|
||||
{
|
||||
boost::algorithm::replace_first(text, "%s", castingCreature->namePl); //creature caster
|
||||
boost::algorithm::replace_first(text, "%s", casterCreatureName); //creature caster
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -1486,7 +1465,7 @@ void CBattleInterface::spellCast( const BattleSpellCast * sc )
|
||||
}
|
||||
waitForAnims();
|
||||
//mana absorption
|
||||
if (sc->manaGained)
|
||||
if(sc->manaGained > 0)
|
||||
{
|
||||
Point leftHero = Point(15, 30) + pos;
|
||||
Point rightHero = Point(755, 30) + pos;
|
||||
@ -1497,16 +1476,7 @@ void CBattleInterface::spellCast( const BattleSpellCast * sc )
|
||||
|
||||
void CBattleInterface::battleStacksEffectsSet(const SetStackEffect & sse)
|
||||
{
|
||||
int effID = sse.effect.back().sid;
|
||||
if(effID != -1) //can be -1 for defensive stance effect
|
||||
{
|
||||
for(auto & elem : sse.stacks)
|
||||
{
|
||||
bool areaEffect(CGI->spellh->objects[effID]->getTargetType() == CSpell::ETargetType::NO_TARGET);
|
||||
displayEffect(CGI->spellh->objects[effID]->mainEffectAnim, curInt->cb->battleGetStackByID(elem)->position, areaEffect);
|
||||
}
|
||||
}
|
||||
else if (sse.stacks.size() == 1 && sse.effect.size() == 2)
|
||||
if (sse.effect.back().sid == -1 && sse.stacks.size() == 1 && sse.effect.size() == 2)
|
||||
{
|
||||
const Bonus & bns = sse.effect.front();
|
||||
if (bns.source == Bonus::OTHER && bns.type == Bonus::PRIMARY_SKILL)
|
||||
@ -1552,8 +1522,12 @@ void CBattleInterface::castThisSpell(int spellID)
|
||||
|
||||
const CSpell::TargetInfo ti = sp->getTargetInfo(castingHero->getSpellSchoolLevel(sp));
|
||||
|
||||
if(ti.massive)
|
||||
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)
|
||||
@ -1565,11 +1539,6 @@ void CBattleInterface::castThisSpell(int spellID)
|
||||
{
|
||||
spellSelMode = OBSTACLE;
|
||||
}
|
||||
//todo: move to JSON config
|
||||
if(spellID == SpellID::FIRE_WALL || spellID == SpellID::FORCE_FIELD)
|
||||
{
|
||||
spellSelMode = FREE_LOCATION;
|
||||
}
|
||||
|
||||
if (spellSelMode == NO_LOCATION) //user does not have to select location
|
||||
{
|
||||
@ -1587,9 +1556,37 @@ void CBattleInterface::castThisSpell(int spellID)
|
||||
|
||||
void CBattleInterface::displayEffect(ui32 effect, int destTile, bool areaEffect)
|
||||
{
|
||||
addNewAnim(new CSpellEffectAnimation(this, effect, destTile, 0, 0, false, areaEffect));
|
||||
//todo: recheck areaEffect usage
|
||||
addNewAnim(new CSpellEffectAnimation(this, effect, destTile, 0, 0, false));
|
||||
}
|
||||
|
||||
void CBattleInterface::displaySpellEffect(SpellID spellID, BattleHex destinationTile, bool areaEffect)
|
||||
{
|
||||
const CSpell * spell = spellID.toSpell();
|
||||
|
||||
if(spell == nullptr)
|
||||
return;
|
||||
|
||||
for(const CSpell::TAnimation & animation : spell->animationInfo.affect)
|
||||
{
|
||||
addNewAnim(new CSpellEffectAnimation(this, animation.resourceName, destinationTile, false, animation.verticalPosition == VerticalPosition::BOTTOM));
|
||||
}
|
||||
}
|
||||
|
||||
void CBattleInterface::displaySpellHit(SpellID spellID, BattleHex destinationTile, bool areaEffect)
|
||||
{
|
||||
const CSpell * spell = spellID.toSpell();
|
||||
|
||||
if(spell == nullptr)
|
||||
return;
|
||||
|
||||
for(const CSpell::TAnimation & animation : spell->animationInfo.hit)
|
||||
{
|
||||
addNewAnim(new CSpellEffectAnimation(this, animation.resourceName, destinationTile, false, animation.verticalPosition == VerticalPosition::BOTTOM));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void CBattleInterface::battleTriggerEffect(const BattleTriggerEffect & bte)
|
||||
{
|
||||
const CStack * stack = curInt->cb->battleGetStackByID(bte.stackID);
|
||||
@ -1792,7 +1789,6 @@ void CBattleInterface::getPossibleActionsForStack(const CStack * stack)
|
||||
void CBattleInterface::printConsoleAttacked( const CStack * defender, int dmg, int killed, const CStack * attacker, bool multiple )
|
||||
{
|
||||
boost::format txt;
|
||||
int end = 0;
|
||||
if (attacker) //ignore if stacks were killed by spell
|
||||
{
|
||||
txt = boost::format (CGI->generaltexth->allTexts[attacker->count > 1 ? 377 : 376]) %
|
||||
|
@ -318,7 +318,10 @@ 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(int spellID); //called when player has chosen a spell from spellbook
|
||||
void displayEffect(ui32 effect, int destTile, bool areaEffect = true); //displays effect of a spell on the battlefield; affected: true - attacker. false - defender
|
||||
void displayEffect(ui32 effect, int destTile, bool areaEffect = true); //displays custom effect on the battlefield
|
||||
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 battleTriggerEffect(const BattleTriggerEffect & bte);
|
||||
void setBattleCursor(const int myNumber); //really complex and messy, sets attackingHex
|
||||
void endAction(const BattleAction* action);
|
||||
|
@ -537,7 +537,7 @@ Point CClickableHex::getXYUnitAnim(BattleHex hexNum, const CStack * stack, CBatt
|
||||
}
|
||||
}
|
||||
//returning
|
||||
return ret +CPlayerInterface::battleInt->pos;
|
||||
return ret + CPlayerInterface::battleInt->pos;
|
||||
}
|
||||
|
||||
void CClickableHex::hover(bool on)
|
||||
|
@ -30,80 +30,80 @@
|
||||
|
||||
// WoG_Ac_format_to_def_names_mapping
|
||||
"ac_mapping": [
|
||||
{ "id": 0, "defnames": [ "C10SPW.DEF" ] },
|
||||
{ "id": 1, "defnames": [ "C03SPA0.DEF" ] },
|
||||
{ "id": 2, "defnames": [ "C01SPA0.DEF" ] },
|
||||
{ "id": 3, "defnames": [ "C02SPA0.DEF" ] },
|
||||
{ "id": 4, "defnames": [ "SP12_.DEF" ] },
|
||||
{ "id": 5, "defnames": [ "C02SPE0.DEF" ] },
|
||||
{ "id": 6, "defnames": [ "C02SPF0.DEF" ] },
|
||||
{ "id": 7, "defnames": [ "C04SPA0.DEF" ] },
|
||||
{ "id": 8, "defnames": [ "C04SPE0.DEF" ] },
|
||||
{ "id": 9, "defnames": [ "C04SPF0.DEF" ] },
|
||||
{ "id": 10, "defnames": [ "C05SPE0.DEF" ] },
|
||||
{ "id": 11, "defnames": [ "C05SPF0.DEF" ] },
|
||||
{ "id": 12, "defnames": [ "C06SPF0.DEF" ] },
|
||||
{ "id": 13, "defnames": [ "C07SPA0.DEF" ] },
|
||||
{ "id": 14, "defnames": [ "C07SPA1.DEF" ] },
|
||||
{ "id": 0, "defnames": [ "C10SPW.DEF" ] },//merged
|
||||
{ "id": 1, "defnames": [ "C03SPA0.DEF" ] },//merged
|
||||
{ "id": 2, "defnames": [ "C01SPA0.DEF" ] },//merged
|
||||
{ "id": 3, "defnames": [ "C02SPA0.DEF" ] },//merged
|
||||
{ "id": 4, "defnames": [ "SP12_.DEF" ] },//merged (?)
|
||||
{ "id": 5, "defnames": [ "C02SPE0.DEF" ] },//merged
|
||||
{ "id": 6, "defnames": [ "C02SPF0.DEF" ] },//merged
|
||||
{ "id": 7, "defnames": [ "C04SPA0.DEF" ] },//merged
|
||||
{ "id": 8, "defnames": [ "C04SPE0.DEF" ] },//merged
|
||||
{ "id": 9, "defnames": [ "C04SPF0.DEF" ] },//merged
|
||||
{ "id": 10, "defnames": [ "C05SPE0.DEF" ] },//merged
|
||||
{ "id": 11, "defnames": [ "C05SPF0.DEF" ] },//merged
|
||||
{ "id": 12, "defnames": [ "C06SPF0.DEF" ] },//merged (?)
|
||||
{ "id": 13, "defnames": [ "C07SPA0.DEF" ] },//merged (?)
|
||||
{ "id": 14, "defnames": [ "C07SPA1.DEF" ] },//merged
|
||||
{ "id": 15, "defnames": [ "C0FEAR.DEF" ] },
|
||||
{ "id": 16, "defnames": [ "C08SPE0.DEF" ] },
|
||||
{ "id": 17, "defnames": [ "C08SPF0.DEF" ] },
|
||||
{ "id": 18, "defnames": [ "C09SPA0.DEF" ] },
|
||||
{ "id": 19, "defnames": [ "C09SPE0.DEF" ] },
|
||||
{ "id": 20, "defnames": [ "C09SPW0.DEF" ] },
|
||||
{ "id": 21, "defnames": [ "C10SPA0.DEF" ] },
|
||||
{ "id": 22, "defnames": [ "C11SPE0.DEF" ] },
|
||||
{ "id": 23, "defnames": [ "C11SPF0.DEF" ] },
|
||||
{ "id": 24, "defnames": [ "C11SPW0.DEF" ] },
|
||||
{ "id": 25, "defnames": [ "C12SPA0.DEF" ] },
|
||||
{ "id": 26, "defnames": [ "C13SPA0.DEF" ] },
|
||||
{ "id": 27, "defnames": [ "C13SPE0.DEF" ] },
|
||||
{ "id": 28, "defnames": [ "C13SPW0.DEF" ] },
|
||||
{ "id": 29, "defnames": [ "C14SPA0.DEF" ] },
|
||||
{ "id": 30, "defnames": [ "C14SPE0.DEF" ] },
|
||||
{ "id": 31, "defnames": [ "C15SPA0.DEF" ] },
|
||||
{ "id": 17, "defnames": [ "C08SPF0.DEF" ] },//merged
|
||||
{ "id": 18, "defnames": [ "C09SPA0.DEF" ] },//merged
|
||||
{ "id": 19, "defnames": [ "C09SPE0.DEF" ] },//merged
|
||||
{ "id": 20, "defnames": [ "C09SPW0.DEF" ] },//merged
|
||||
{ "id": 21, "defnames": [ "C10SPA0.DEF" ] },//merged
|
||||
{ "id": 22, "defnames": [ "C11SPE0.DEF" ] },//merged
|
||||
{ "id": 23, "defnames": [ "C11SPF0.DEF" ] },//merged
|
||||
{ "id": 24, "defnames": [ "C11SPW0.DEF" ] },//merged
|
||||
{ "id": 25, "defnames": [ "C12SPA0.DEF" ] },//merged
|
||||
{ "id": 26, "defnames": [ "C13SPA0.DEF" ] },//merged
|
||||
{ "id": 27, "defnames": [ "C13SPE0.DEF" ] },//merged
|
||||
{ "id": 28, "defnames": [ "C13SPW0.DEF" ] },//merged
|
||||
{ "id": 29, "defnames": [ "C14SPA0.DEF" ] },//merged
|
||||
{ "id": 30, "defnames": [ "C14SPE0.DEF" ] },//merged
|
||||
{ "id": 31, "defnames": [ "C15SPA0.DEF" ] },//merged
|
||||
{ "id": 32, "defnames": [ "C15SPE0.DEF", "C15SPE1.DEF", "C15SPE2.DEF" ] },
|
||||
{ "id": 33, "defnames": [ "C15SPE3.DEF", "C15SPE6.DEF", "C15SPE7.DEF", "C15SPE8.DEF", "C15SPE9.DEF", "C15SPE10.DEF", "C15SPE11.DEF" ] },
|
||||
{ "id": 35, "defnames": [ "C01SPF.DEF", "C01SPF0.DEF" ] },
|
||||
{ "id": 36, "defnames": [ "C01SPW.DEF", "C01SPW0.DEF" ] },
|
||||
{ "id": 38, "defnames": [ "C11SPA1.DEF" ] },
|
||||
{ "id": 39, "defnames": [ "C03SPW.DEF", "C03SPW0.DEF" ] },
|
||||
{ "id": 40, "defnames": [ "C04SPW.DEF", "C04SPW0.DEF" ] },
|
||||
{ "id": 41, "defnames": [ "C05SPW.DEF", "C05SPW0.DEF" ] },
|
||||
{ "id": 42, "defnames": [ "C06SPW.DEF", "C06SPW0.DEF" ] },
|
||||
{ "id": 35, "defnames": [ "C01SPF.DEF", "C01SPF0.DEF" ] },//merged
|
||||
{ "id": 36, "defnames": [ "C01SPW.DEF", "C01SPW0.DEF" ] },//merged
|
||||
{ "id": 38, "defnames": [ "C11SPA1.DEF" ] },//merged
|
||||
{ "id": 39, "defnames": [ "C03SPW.DEF", "C03SPW0.DEF" ] },//merged
|
||||
{ "id": 40, "defnames": [ "C04SPW.DEF", "C04SPW0.DEF" ] },//merged
|
||||
{ "id": 41, "defnames": [ "C05SPW.DEF", "C05SPW0.DEF" ] },//merged
|
||||
{ "id": 42, "defnames": [ "C06SPW.DEF", "C06SPW0.DEF" ] },//merged
|
||||
{ "id": 43, "defnames": [ "C07SPF0.DEF", "C07SPF1.DEF", "C07SPF2.DEF", "C07SPF6.DEF", "C07SPF7.DEF", "C07SPF8.DEF" ] },
|
||||
{ "id": 44, "defnames": [ "C07SPF0.DEF", "C07SPF4.DEF", "C07SPF5.DEF", "C07SPF9.DEF", "C07SPF10.DEF", "C07SPF11.DEF" ] },
|
||||
{ "id": 45, "defnames": [ "C07SPW.DEF", "C07SPW0.DEF" ] },
|
||||
{ "id": 46, "defnames": [ "C08SPW5.DEF" ] },
|
||||
{ "id": 45, "defnames": [ "C07SPW.DEF", "C07SPW0.DEF" ] },//merged
|
||||
{ "id": 46, "defnames": [ "C08SPW5.DEF" ] },//merged (?)
|
||||
{ "id": 47, "defnames": [ "C09SPF0.DEF" ] },
|
||||
{ "id": 48, "defnames": [ "C10SPF0.DEF" ] },
|
||||
{ "id": 48, "defnames": [ "C10SPF0.DEF" ] },//merged
|
||||
{ "id": 49, "defnames": [ "C11SPA1.DEF" ] },
|
||||
{ "id": 50, "defnames": [ "C12SPE0.DEF" ] },
|
||||
{ "id": 51, "defnames": [ "C12SPF0.DEF" ] },
|
||||
{ "id": 52, "defnames": [ "SP06_.DEF" ] },
|
||||
{ "id": 53, "defnames": [ "C13SPF.DEF", "C13SPF0.DEF" ] },
|
||||
{ "id": 54, "defnames": [ "C16SPE.DEF", "C16SPE0.DEF" ] },
|
||||
{ "id": 53, "defnames": [ "C13SPF.DEF", "C13SPF0.DEF" ] }, //merged
|
||||
{ "id": 54, "defnames": [ "C16SPE.DEF", "C16SPE0.DEF" ] }, //merged
|
||||
{ "id": 55, "defnames": [ "C17SPE0.DEF" ] },
|
||||
{ "id": 56, "defnames": [ "C0ACID.DEF" ] },
|
||||
{ "id": 56, "defnames": [ "C0ACID.DEF" ] },//merged
|
||||
{ "id": 57, "defnames": [ "C09SPF1.DEF", "C09SPF2.DEF" ] },
|
||||
{ "id": 58, "defnames": [ "C17SPE2.DEF" ] },
|
||||
{ "id": 59, "defnames": [ "C09SPF0.DEF" ] },
|
||||
{ "id": 62, "defnames": [ "C07SPF60.DEF", "C07SPF61.DEF", "C07SPF62.DEF" ] },
|
||||
{ "id": 64, "defnames": [ "C20SPX.DEF" ] },
|
||||
{ "id": 67, "defnames": [ "SP11_.DEF" ] },
|
||||
{ "id": 68, "defnames": [ "SP02_.DEF" ] },
|
||||
{ "id": 69, "defnames": [ "SP05_.DEF" ] },
|
||||
{ "id": 71, "defnames": [ "SP01_.DEF" ] },
|
||||
{ "id": 72, "defnames": [ "SP04_.DEF" ] },
|
||||
{ "id": 64, "defnames": [ "C20SPX.DEF" ] }, //merged
|
||||
{ "id": 67, "defnames": [ "SP11_.DEF" ] }, //merged
|
||||
{ "id": 68, "defnames": [ "SP02_.DEF" ] }, //merged
|
||||
{ "id": 69, "defnames": [ "SP05_.DEF" ] }, //merged
|
||||
{ "id": 71, "defnames": [ "SP01_.DEF" ] }, //merged
|
||||
{ "id": 72, "defnames": [ "SP04_.DEF" ] }, //merged
|
||||
{ "id": 73, "defnames": [ "SP03_.DEF" ] },
|
||||
{ "id": 74, "defnames": [ "SP12_.DEF" ] },
|
||||
{ "id": 75, "defnames": [ "SP07_A.DEF" ] },
|
||||
{ "id": 76, "defnames": [ "SP07_B.DEF" ] },
|
||||
{ "id": 77, "defnames": [ "SP08_.DEF" ] },
|
||||
{ "id": 78, "defnames": [ "SP09_.DEF" ] },
|
||||
{ "id": 79, "defnames": [ "C01SPE0.DEF" ] },
|
||||
{ "id": 80, "defnames": [ "C07SPE0.DEF" ] },
|
||||
{ "id": 81, "defnames": [ "C17SPW0.DEF" ] },
|
||||
{ "id": 79, "defnames": [ "C01SPE0.DEF" ] },//merged
|
||||
{ "id": 80, "defnames": [ "C07SPE0.DEF" ] },//merged
|
||||
{ "id": 81, "defnames": [ "C17SPW0.DEF" ] },//merged
|
||||
{ "id": 82, "defnames": [ "C09SPF3.DEF" ] },
|
||||
{ "id": 84, "defnames": [ "ZMGC02.DEF" ] }
|
||||
]
|
||||
|
@ -8,6 +8,46 @@
|
||||
|
||||
|
||||
"definitions" : {
|
||||
"animationQueue":{
|
||||
"type": "array",
|
||||
"items":{
|
||||
"anyOf":[
|
||||
{
|
||||
//assumed verticalPosition: top
|
||||
"type": "string",
|
||||
"format": "defFile"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties":{
|
||||
"verticalPosition": {"type":"string", "enum":["top","bottom"]},
|
||||
"defName": {"type":"string", "format": "defFile"}
|
||||
},
|
||||
"additionalProperties" : false
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"animation":{
|
||||
"type": "object",
|
||||
"additionalProperties" : false,
|
||||
"properties":{
|
||||
"affect":{"$ref" : "#/definitions/animationQueue"},
|
||||
"hit":{"$ref" : "#/definitions/animationQueue"},
|
||||
"cast":{"$ref" : "#/definitions/animationQueue"},
|
||||
"projectile":{
|
||||
"type":"array",
|
||||
"items":{
|
||||
"type": "object",
|
||||
"properties":{
|
||||
"minimumAngle": {"type":"number", "minimum" : 0},
|
||||
"defName": {"type":"string", "format": "defFile"}
|
||||
},
|
||||
"additionalProperties" : false
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"flags" :{
|
||||
"type" : "object",
|
||||
"additionalProperties" : {
|
||||
@ -53,10 +93,27 @@
|
||||
"smart":{
|
||||
"type": "boolean",
|
||||
"description": "true: friendly/hostile based on positiveness; false: all targets"
|
||||
},
|
||||
"clearTarget":
|
||||
{
|
||||
"type": "boolean",
|
||||
"description": "LOCATION target only. Target hex/tile must be clear"
|
||||
},
|
||||
"clearAffected":
|
||||
{
|
||||
"type": "boolean",
|
||||
"description": "LOCATION target only. All affected hexes/tile must be clear"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"texts":{
|
||||
"type": "object",
|
||||
|
||||
|
||||
"additionalProperties" : false
|
||||
}
|
||||
},
|
||||
|
||||
@ -120,12 +177,8 @@
|
||||
},
|
||||
"targetType":{
|
||||
"type": "string",
|
||||
"enum": ["NO_TARGET","CREATURE","OBSTACLE"]
|
||||
},
|
||||
"anim":{
|
||||
"type": "number",
|
||||
"description": "Main effect animation (AC format), -1 - none, deprecated",
|
||||
"minimum": -1
|
||||
"description": "NO_TARGET - instant cast no aiming, default; CREATURE - target is unit; OBSTACLE - target is OBSTACLE; LOCATION - target is location",
|
||||
"enum": ["NO_TARGET","CREATURE","OBSTACLE","LOCATION"]
|
||||
},
|
||||
"counters":{
|
||||
"$ref" : "#/definitions/flags",
|
||||
@ -182,6 +235,9 @@
|
||||
"$ref" : "#/definitions/flags",
|
||||
"description": "flags structure of bonus names, presence of all bonuses required to be affected by, can't be negated."
|
||||
},
|
||||
|
||||
"animation":{"$ref": "#/definitions/animation"},
|
||||
|
||||
"graphics":{
|
||||
"type": "object",
|
||||
"additionalProperties" : false,
|
||||
|
@ -1,7 +1,10 @@
|
||||
{
|
||||
"stoneGaze" : {
|
||||
"index" : 70,
|
||||
"anim" : 70,
|
||||
"targetType": "NO_TARGET",
|
||||
"animation":{
|
||||
//need special animation
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "PARALYZE"
|
||||
},
|
||||
@ -33,7 +36,11 @@
|
||||
},
|
||||
"poison" : {
|
||||
"index" : 71,
|
||||
"anim" : 67,
|
||||
"targetType": "NO_TARGET",
|
||||
|
||||
"animation":{
|
||||
"affect":["SP11_"]
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "POISON"
|
||||
},
|
||||
@ -67,7 +74,11 @@
|
||||
},
|
||||
"bind" : {
|
||||
"index" : 72,
|
||||
"anim" : 68,
|
||||
"targetType": "NO_TARGET",
|
||||
|
||||
"animation":{
|
||||
"affect":["SP02_"]
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "BIND"
|
||||
},
|
||||
@ -90,7 +101,11 @@
|
||||
},
|
||||
"disease" : {
|
||||
"index" : 73,
|
||||
"anim" : 69,
|
||||
"targetType": "NO_TARGET",
|
||||
|
||||
"animation":{
|
||||
"affect":["SP05_"]
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "DISEASE"
|
||||
},
|
||||
@ -124,7 +139,11 @@
|
||||
},
|
||||
"paralyze" : {
|
||||
"index" : 74,
|
||||
"anim" : 70,
|
||||
"targetType": "NO_TARGET",
|
||||
|
||||
"animation":{
|
||||
//missing
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "PARALYZE"
|
||||
},
|
||||
@ -156,7 +175,11 @@
|
||||
},
|
||||
"age" : {
|
||||
"index" : 75,
|
||||
"anim" : 71,
|
||||
"targetType": "NO_TARGET",
|
||||
|
||||
"animation":{
|
||||
"affect":["SP01_"]
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "AGE"
|
||||
},
|
||||
@ -184,7 +207,11 @@
|
||||
},
|
||||
"deathCloud" : {
|
||||
"index" : 76,
|
||||
"anim" : 72,
|
||||
"targetType": "NO_TARGET",
|
||||
|
||||
"animation":{
|
||||
"hit":["SP04_"]
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "DEATHCLD"
|
||||
},
|
||||
@ -203,7 +230,11 @@
|
||||
},
|
||||
"thunderbolt" : {
|
||||
"index" : 77,
|
||||
"anim" : 38,
|
||||
"targetType": "NO_TARGET",
|
||||
|
||||
"animation":{
|
||||
"affect":[{"defName":"C03SPA0", "verticalPosition":"bottom"}, "C11SPA1"]
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "LIGHTBLT"
|
||||
},
|
||||
@ -220,7 +251,11 @@
|
||||
},
|
||||
"dispelHelpful" : {
|
||||
"index" : 78,
|
||||
"anim" : 41,
|
||||
"targetType": "NO_TARGET",
|
||||
|
||||
"animation":{
|
||||
"affect":["C05SPW"]
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "DISPELL"
|
||||
},
|
||||
@ -236,7 +271,11 @@
|
||||
},
|
||||
"deathStare" : {
|
||||
"index" : 79,
|
||||
"anim" : 80,
|
||||
"targetType": "NO_TARGET",
|
||||
|
||||
"animation":{
|
||||
"affect":["C07SPE0"]
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "DEATHSTR"
|
||||
},
|
||||
@ -255,7 +294,11 @@
|
||||
},
|
||||
"acidBreath" : {
|
||||
"index" : 80,
|
||||
"anim" : 81,
|
||||
"targetType": "NO_TARGET",
|
||||
|
||||
"animation":{
|
||||
//???
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "ACID"
|
||||
},
|
||||
@ -280,7 +323,11 @@
|
||||
},
|
||||
"acidBreathDamage" : {
|
||||
"index" : 81,
|
||||
"anim" : 81,
|
||||
"targetType": "NO_TARGET",
|
||||
|
||||
"animation":{
|
||||
"affect":["C17SPW0"]
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "ACID"
|
||||
},
|
||||
|
@ -1,7 +1,8 @@
|
||||
{
|
||||
"summonBoat" : {
|
||||
"index" : 0,
|
||||
"anim" : -1,
|
||||
|
||||
"targetType": "NO_TARGET",
|
||||
"sounds": {
|
||||
"cast": "SUMMBOAT"
|
||||
},
|
||||
@ -16,7 +17,8 @@
|
||||
},
|
||||
"scuttleBoat" : {
|
||||
"index" : 1,
|
||||
"anim" : -1,
|
||||
"targetType": "NO_TARGET",
|
||||
|
||||
"sounds": {
|
||||
"cast": "SCUTBOAT"
|
||||
},
|
||||
@ -31,7 +33,8 @@
|
||||
},
|
||||
"visions" : {
|
||||
"index" : 2,
|
||||
"anim" : -1,
|
||||
"targetType": "NO_TARGET",
|
||||
|
||||
"sounds": {
|
||||
"cast": "VISIONS"
|
||||
},
|
||||
@ -46,7 +49,8 @@
|
||||
},
|
||||
"viewEarth" : {
|
||||
"index" : 3,
|
||||
"anim" : -1,
|
||||
"targetType": "NO_TARGET",
|
||||
|
||||
"sounds": {
|
||||
"cast": "VIEW"
|
||||
},
|
||||
@ -61,7 +65,8 @@
|
||||
},
|
||||
"disguise" : {
|
||||
"index" : 4,
|
||||
"anim" : -1,
|
||||
"targetType": "NO_TARGET",
|
||||
|
||||
"sounds": {
|
||||
"cast": "DISGUISE"
|
||||
},
|
||||
@ -76,7 +81,8 @@
|
||||
},
|
||||
"viewAir" : {
|
||||
"index" : 5,
|
||||
"anim" : -1,
|
||||
"targetType": "NO_TARGET",
|
||||
|
||||
"sounds": {
|
||||
"cast": "VIEW"
|
||||
},
|
||||
@ -91,7 +97,8 @@
|
||||
},
|
||||
"fly" : {
|
||||
"index" : 6,
|
||||
"anim" : -1,
|
||||
"targetType": "NO_TARGET",
|
||||
|
||||
"sounds": {
|
||||
"cast": "FLYSPELL"
|
||||
},
|
||||
@ -106,7 +113,8 @@
|
||||
},
|
||||
"waterWalk" : {
|
||||
"index" : 7,
|
||||
"anim" : -1,
|
||||
"targetType": "NO_TARGET",
|
||||
|
||||
"sounds": {
|
||||
"cast": "WATRWALK"
|
||||
},
|
||||
@ -121,7 +129,8 @@
|
||||
},
|
||||
"dimensionDoor" : {
|
||||
"index" : 8,
|
||||
"anim" : -1,
|
||||
"targetType": "LOCATION",
|
||||
|
||||
"sounds": {
|
||||
"cast": "TELPTOUT"
|
||||
},
|
||||
@ -136,7 +145,8 @@
|
||||
},
|
||||
"townPortal" : {
|
||||
"index" : 9,
|
||||
"anim" : -1,
|
||||
"targetType": "NO_TARGET",
|
||||
|
||||
"sounds": {
|
||||
"cast": "TELPTOUT"
|
||||
},
|
||||
|
@ -1,7 +1,18 @@
|
||||
{
|
||||
"magicArrow" : {
|
||||
"index" : 15,
|
||||
"anim" : 64,
|
||||
"targetType": "CREATURE",
|
||||
|
||||
"animation":{
|
||||
"projectile": [
|
||||
{"minimumAngle": 0 ,"defName":"C20SPX4"},
|
||||
{"minimumAngle": 0.60 ,"defName":"C20SPX3"},
|
||||
{"minimumAngle": 0.90 ,"defName":"C20SPX2"},
|
||||
{"minimumAngle": 1.20 ,"defName":"C20SPX1"},
|
||||
{"minimumAngle": 1.50 ,"defName":"C20SPX0"}
|
||||
],
|
||||
"hit":["C20SPX"]
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "MAGICBLT"
|
||||
},
|
||||
@ -22,7 +33,18 @@
|
||||
},
|
||||
"iceBolt" : {
|
||||
"index" : 16,
|
||||
"anim" : 46,
|
||||
"targetType": "CREATURE",
|
||||
|
||||
"animation":{
|
||||
"projectile": [
|
||||
{"minimumAngle": 0 ,"defName":"C08SPW4"},
|
||||
{"minimumAngle": 0.60 ,"defName":"C08SPW3"},
|
||||
{"minimumAngle": 0.90 ,"defName":"C08SPW2"},
|
||||
{"minimumAngle": 1.20 ,"defName":"C08SPW1"},
|
||||
{"minimumAngle": 1.50 ,"defName":"C08SPW0"}
|
||||
],
|
||||
"hit":["C08SPW5"]
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "ICERAY"
|
||||
},
|
||||
@ -43,7 +65,11 @@
|
||||
},
|
||||
"lightningBolt" : {
|
||||
"index" : 17,
|
||||
"anim" : 38,
|
||||
"targetType": "CREATURE",
|
||||
|
||||
"animation":{
|
||||
"affect":[{"defName":"C03SPA0", "verticalPosition":"bottom"}, "C11SPA1"]
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "LIGHTBLT"
|
||||
},
|
||||
@ -64,7 +90,11 @@
|
||||
},
|
||||
"implosion" : {
|
||||
"index" : 18,
|
||||
"anim" : 10,
|
||||
"targetType": "CREATURE",
|
||||
|
||||
"animation":{
|
||||
"affect":["C05SPE0"]
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "DECAY"
|
||||
},
|
||||
@ -86,7 +116,11 @@
|
||||
},
|
||||
"chainLightning" : {
|
||||
"index" : 19,
|
||||
"anim" : 38,
|
||||
"targetType": "CREATURE",
|
||||
|
||||
"animation":{
|
||||
"affect":[{"defName":"C03SPA0", "verticalPosition":"bottom"}, "C11SPA1"]
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "CHAINLTE"
|
||||
},
|
||||
@ -104,7 +138,11 @@
|
||||
},
|
||||
"frostRing" : {
|
||||
"index" : 20,
|
||||
"anim" : 45,
|
||||
"targetType": "LOCATION",
|
||||
|
||||
"animation":{
|
||||
"hit":["C07SPW"] //C07SPW0 ???
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "FROSTING"
|
||||
},
|
||||
@ -125,7 +163,11 @@
|
||||
},
|
||||
"fireball" : {
|
||||
"index" : 21,
|
||||
"anim" : 53,
|
||||
"targetType": "LOCATION",
|
||||
|
||||
"animation":{
|
||||
"hit":["C13SPF"] //C13SPF0 ???
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "FIREBALL"
|
||||
},
|
||||
@ -146,7 +188,11 @@
|
||||
},
|
||||
"inferno" : {
|
||||
"index" : 22,
|
||||
"anim" : 9,
|
||||
"targetType": "LOCATION",
|
||||
|
||||
"animation":{
|
||||
"hit":["C04SPF0"]
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "FIREBLST"
|
||||
},
|
||||
@ -167,7 +213,11 @@
|
||||
},
|
||||
"meteorShower" : {
|
||||
"index" : 23,
|
||||
"anim" : 16,
|
||||
"targetType": "LOCATION",
|
||||
|
||||
"animation":{
|
||||
"hit":["C08SPE0"]
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "METEOR"
|
||||
},
|
||||
@ -189,7 +239,10 @@
|
||||
"deathRipple" : {
|
||||
"index" : 24,
|
||||
"targetType" : "CREATURE",
|
||||
"anim" : 8,
|
||||
|
||||
"animation":{
|
||||
"affect":["C04SPE0"]
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "DEATHRIP"
|
||||
},
|
||||
@ -215,7 +268,10 @@
|
||||
"destroyUndead" : {
|
||||
"index" : 25,
|
||||
"targetType" : "CREATURE",
|
||||
"anim" : 29,
|
||||
|
||||
"animation":{
|
||||
"affect":["C14SPA0"]
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "COLDRING"
|
||||
},
|
||||
@ -240,7 +296,10 @@
|
||||
"armageddon" : {
|
||||
"index" : 26,
|
||||
"targetType" : "CREATURE",
|
||||
"anim" : 12,
|
||||
|
||||
"animation":{
|
||||
"hit":["C06SPF0"]
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "ARMGEDN"
|
||||
},
|
||||
@ -261,7 +320,11 @@
|
||||
},
|
||||
"titanBolt" : {
|
||||
"index" : 57,
|
||||
"anim" : 38,
|
||||
"targetType" : "CREATURE",
|
||||
|
||||
"animation":{
|
||||
"hit":[{"defName":"C03SPA0", "verticalPosition":"bottom"}, "C11SPA1"]
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "LIGHTBLT"
|
||||
},
|
||||
@ -277,5 +340,5 @@
|
||||
"negative": true,
|
||||
"special": true
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,8 @@
|
||||
{
|
||||
"quicksand" : {
|
||||
"index" : 10,
|
||||
"anim" : -1,
|
||||
"targetType" : "NO_TARGET",
|
||||
|
||||
"sounds": {
|
||||
"cast": "QUIKSAND"
|
||||
},
|
||||
@ -16,7 +17,8 @@
|
||||
},
|
||||
"landMine" : {
|
||||
"index" : 11,
|
||||
"anim" : -1,
|
||||
"targetType" : "NO_TARGET",
|
||||
|
||||
"sounds": {
|
||||
"cast": ""
|
||||
},
|
||||
@ -35,13 +37,17 @@
|
||||
},
|
||||
"forceField" : {
|
||||
"index" : 12,
|
||||
"anim" : -1,
|
||||
"targetType" : "LOCATION",
|
||||
|
||||
"sounds": {
|
||||
"cast": "FORCEFLD"
|
||||
},
|
||||
"levels" : {
|
||||
"base":{
|
||||
"range" : "0"
|
||||
"range" : "0",
|
||||
"targetModifier":{
|
||||
"clearAffected": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"flags" : {
|
||||
@ -50,13 +56,17 @@
|
||||
},
|
||||
"fireWall" : {
|
||||
"index" : 13,
|
||||
"anim" : -1,
|
||||
"targetType" : "LOCATION",
|
||||
|
||||
"sounds": {
|
||||
"cast": "FIREWALL"
|
||||
},
|
||||
"levels" : {
|
||||
"base":{
|
||||
"range" : "0"
|
||||
"range" : "0",
|
||||
"targetModifier":{
|
||||
"clearAffected": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"flags" : {
|
||||
@ -69,7 +79,8 @@
|
||||
},
|
||||
"earthquake" : {
|
||||
"index" : 14,
|
||||
"anim" : -1,
|
||||
"targetType" : "NO_TARGET",
|
||||
|
||||
"sounds": {
|
||||
"cast": "ERTHQUAK"
|
||||
},
|
||||
@ -85,7 +96,11 @@
|
||||
|
||||
"dispel" : {
|
||||
"index" : 35,
|
||||
"anim" : 41,
|
||||
"targetType" : "CREATURE",
|
||||
|
||||
"animation":{
|
||||
"affect":["C05SPW"] //C05SPW0
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "DISPELL"
|
||||
},
|
||||
@ -103,7 +118,11 @@
|
||||
},
|
||||
"cure" : {
|
||||
"index" : 37,
|
||||
"anim" : 39,
|
||||
"targetType" : "CREATURE",
|
||||
"animation":{
|
||||
"affect":["C03SPW"]//C03SPW0
|
||||
},
|
||||
|
||||
"sounds": {
|
||||
"cast": "CURE"
|
||||
},
|
||||
@ -122,7 +141,11 @@
|
||||
},
|
||||
"resurrection" : {
|
||||
"index" : 38,
|
||||
"anim" : 79,
|
||||
"targetType" : "CREATURE",
|
||||
|
||||
"animation":{
|
||||
"affect":["C01SPE0"]
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "RESURECT"
|
||||
},
|
||||
@ -143,7 +166,11 @@
|
||||
},
|
||||
"animateDead" : {
|
||||
"index" : 39,
|
||||
"anim" : 79,
|
||||
"targetType" : "CREATURE",
|
||||
|
||||
"animation":{
|
||||
"affect":["C01SPE0"]
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "ANIMDEAD"
|
||||
},
|
||||
@ -163,7 +190,11 @@
|
||||
},
|
||||
"sacrifice" : {
|
||||
"index" : 40,
|
||||
"anim" : 79,
|
||||
"targetType" : "CREATURE",
|
||||
|
||||
"animation":{
|
||||
"affect":["C01SPE0"]
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "SACRIF1"
|
||||
},
|
||||
@ -184,7 +215,8 @@
|
||||
},
|
||||
"teleport" : {
|
||||
"index" : 63,
|
||||
"anim" : -1,
|
||||
"targetType" : "CREATURE",
|
||||
|
||||
"sounds": {
|
||||
"cast": "TELPTOUT"
|
||||
},
|
||||
@ -203,7 +235,8 @@
|
||||
},
|
||||
"removeObstacle" : {
|
||||
"index" : 64,
|
||||
"anim" : -1,
|
||||
"targetType" : "OBSTACLE",
|
||||
|
||||
"sounds": {
|
||||
"cast": "REMOVEOB"
|
||||
},
|
||||
@ -218,7 +251,8 @@
|
||||
},
|
||||
"clone" : {
|
||||
"index" : 65,
|
||||
"anim" : -1,
|
||||
"targetType" : "CREATURE",
|
||||
|
||||
"sounds": {
|
||||
"cast": "CLONE"
|
||||
},
|
||||
@ -237,7 +271,8 @@
|
||||
},
|
||||
"fireElemental" : {
|
||||
"index" : 66,
|
||||
"anim" : -1,
|
||||
"targetType" : "NO_TARGET",
|
||||
|
||||
"sounds": {
|
||||
"cast": "SUMNELM"
|
||||
},
|
||||
@ -252,7 +287,8 @@
|
||||
},
|
||||
"earthElemental" : {
|
||||
"index" : 67,
|
||||
"anim" : -1,
|
||||
"targetType" : "NO_TARGET",
|
||||
|
||||
"sounds": {
|
||||
"cast": "SUMNELM"
|
||||
},
|
||||
@ -267,7 +303,8 @@
|
||||
},
|
||||
"waterElemental" : {
|
||||
"index" : 68,
|
||||
"anim" : -1,
|
||||
"targetType" : "NO_TARGET",
|
||||
|
||||
"sounds": {
|
||||
"cast": "SUMNELM"
|
||||
},
|
||||
@ -282,7 +319,8 @@
|
||||
},
|
||||
"airElemental" : {
|
||||
"index" : 69,
|
||||
"anim" : -1,
|
||||
"targetType" : "NO_TARGET",
|
||||
|
||||
"sounds": {
|
||||
"cast": "SUMNELM"
|
||||
},
|
||||
|
@ -1,7 +1,11 @@
|
||||
{
|
||||
"shield" : {
|
||||
"index" : 27,
|
||||
"anim" : 27,
|
||||
"targetType" : "CREATURE",
|
||||
|
||||
"animation":{
|
||||
"affect":["C13SPE0"]
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "SHIELD"
|
||||
},
|
||||
@ -27,7 +31,11 @@
|
||||
},
|
||||
"airShield" : {
|
||||
"index" : 28,
|
||||
"anim" : 2,
|
||||
"targetType" : "CREATURE",
|
||||
|
||||
"animation":{
|
||||
"affect":["C01SPA0"]
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "AIRSHELD"
|
||||
},
|
||||
@ -53,7 +61,11 @@
|
||||
},
|
||||
"fireShield" : {
|
||||
"index" : 29,
|
||||
"anim" : 11,
|
||||
"targetType" : "CREATURE",
|
||||
|
||||
"animation":{
|
||||
"affect":["C05SPF0"]
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "FIRESHIE"
|
||||
},
|
||||
@ -81,7 +93,11 @@
|
||||
},
|
||||
"protectAir" : {
|
||||
"index" : 30,
|
||||
"anim" : 22,
|
||||
"targetType" : "CREATURE",
|
||||
|
||||
"animation":{
|
||||
"affect":["C11SPE0"]
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "PROTECTA"
|
||||
},
|
||||
@ -107,7 +123,11 @@
|
||||
},
|
||||
"protectFire" : {
|
||||
"index" : 31,
|
||||
"anim" : 24,
|
||||
"targetType" : "CREATURE",
|
||||
|
||||
"animation":{
|
||||
"affect":["C11SPW0"]
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "PROTECTF"
|
||||
},
|
||||
@ -133,7 +153,11 @@
|
||||
},
|
||||
"protectWater" : {
|
||||
"index" : 32,
|
||||
"anim" : 23,
|
||||
"targetType" : "CREATURE",
|
||||
|
||||
"animation":{
|
||||
"affect":["C11SPF0"]
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "PROTECTW"
|
||||
},
|
||||
@ -159,7 +183,11 @@
|
||||
},
|
||||
"protectEarth" : {
|
||||
"index" : 33,
|
||||
"anim" : 26,
|
||||
"targetType" : "CREATURE",
|
||||
|
||||
"animation":{
|
||||
"affect":["C13SPA0"]
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "PROTECTE"
|
||||
},
|
||||
@ -185,7 +213,11 @@
|
||||
},
|
||||
"antiMagic" : {
|
||||
"index" : 34,
|
||||
"anim" : 5,
|
||||
"targetType" : "CREATURE",
|
||||
|
||||
"animation":{
|
||||
"affect":["C02SPE0"]
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "ANTIMAGK"
|
||||
},
|
||||
@ -224,7 +256,11 @@
|
||||
|
||||
"magicMirror" : {
|
||||
"index" : 36,
|
||||
"anim" : 3,
|
||||
"targetType" : "CREATURE",
|
||||
|
||||
"animation":{
|
||||
"affect":["C02SPA0"]
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "BACKLASH"
|
||||
},
|
||||
@ -245,10 +281,13 @@
|
||||
"positive": true
|
||||
}
|
||||
},
|
||||
|
||||
"bless" : {
|
||||
"index" : 41,
|
||||
"anim" : 36,
|
||||
"targetType" : "CREATURE",
|
||||
|
||||
"animation":{
|
||||
"affect":["C01SPW"] //C01SPW0
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "BLESS"
|
||||
},
|
||||
@ -282,7 +321,11 @@
|
||||
},
|
||||
"curse" : {
|
||||
"index" : 42,
|
||||
"anim" : 40,
|
||||
"targetType" : "CREATURE",
|
||||
|
||||
"animation":{
|
||||
"affect":["C04SPW"]//C04SPW0
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "CURSE"
|
||||
},
|
||||
@ -317,7 +360,11 @@
|
||||
},
|
||||
"bloodlust" : {
|
||||
"index" : 43,
|
||||
"anim" : 4,
|
||||
"targetType" : "CREATURE",
|
||||
|
||||
"animation":{
|
||||
"affect":["SP12_"] //???
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "BLOODLUS"
|
||||
},
|
||||
@ -360,7 +407,11 @@
|
||||
},
|
||||
"precision" : {
|
||||
"index" : 44,
|
||||
"anim" : 25,
|
||||
"targetType" : "CREATURE",
|
||||
|
||||
"animation":{
|
||||
"affect":["C12SPA0"]
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "PRECISON"
|
||||
},
|
||||
@ -403,7 +454,11 @@
|
||||
},
|
||||
"weakness" : {
|
||||
"index" : 45,
|
||||
"anim" : 56,
|
||||
"targetType" : "CREATURE",
|
||||
|
||||
"animation":{
|
||||
"affect":["C0ACID"]
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "WEAKNESS"
|
||||
},
|
||||
@ -446,7 +501,11 @@
|
||||
},
|
||||
"stoneSkin" : {
|
||||
"index" : 46,
|
||||
"anim" : 54,
|
||||
"targetType" : "CREATURE",
|
||||
|
||||
"animation":{
|
||||
"affect":["C16SPE"] //C16SPE0
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "TUFFSKIN"
|
||||
},
|
||||
@ -485,8 +544,12 @@
|
||||
},
|
||||
"disruptingRay" : {
|
||||
"index" : 47,
|
||||
"targetType" : "CREATURE", //fix, dont remove
|
||||
"anim" : 14,
|
||||
"targetType" : "CREATURE",
|
||||
|
||||
"animation":{
|
||||
"affect":["C07SPA1"],
|
||||
"projectile":[{"defName":"C07SPA0"}]//???
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "DISRUPTR"
|
||||
},
|
||||
@ -525,7 +588,11 @@
|
||||
},
|
||||
"prayer" : {
|
||||
"index" : 48,
|
||||
"anim" : 0,
|
||||
"targetType" : "CREATURE",
|
||||
|
||||
"animation":{
|
||||
"affect":[{"defName":"C10SPW", "verticalPosition":"bottom"}]
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "PRAYER"
|
||||
},
|
||||
@ -588,7 +655,11 @@
|
||||
},
|
||||
"mirth" : {
|
||||
"index" : 49,
|
||||
"anim" : 20,
|
||||
"targetType" : "CREATURE",
|
||||
|
||||
"animation":{
|
||||
"affect":["C09SPW0"]
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "MIRTH"
|
||||
},
|
||||
@ -636,7 +707,11 @@
|
||||
},
|
||||
"sorrow" : {
|
||||
"index" : 50,
|
||||
"anim" : 30,
|
||||
"targetType" : "CREATURE",
|
||||
|
||||
"animation":{
|
||||
"affect":["C14SPE0"]
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "SORROW"
|
||||
},
|
||||
@ -684,7 +759,11 @@
|
||||
},
|
||||
"fortune" : {
|
||||
"index" : 51,
|
||||
"anim" : 18,
|
||||
"targetType" : "CREATURE",
|
||||
|
||||
"animation":{
|
||||
"affect":["C09SPA0"]
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "FORTUNE"
|
||||
},
|
||||
@ -725,7 +804,11 @@
|
||||
},
|
||||
"misfortune" : {
|
||||
"index" : 52,
|
||||
"anim" : 48,
|
||||
"targetType" : "CREATURE",
|
||||
|
||||
"animation":{
|
||||
"affect":["C10SPF0"]
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "MISFORT"
|
||||
},
|
||||
@ -766,7 +849,11 @@
|
||||
},
|
||||
"haste" : {
|
||||
"index" : 53,
|
||||
"anim" : 31,
|
||||
"targetType" : "CREATURE",
|
||||
|
||||
"animation":{
|
||||
"affect":["C15SPA0"]
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "HASTE"
|
||||
},
|
||||
@ -811,7 +898,11 @@
|
||||
},
|
||||
"slow" : {
|
||||
"index" : 54,
|
||||
"anim" : 19,
|
||||
"targetType" : "CREATURE",
|
||||
|
||||
"animation":{
|
||||
"affect":[{"defName":"C09SPE0", "verticalPosition":"bottom"}]
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "MUCKMIRE"
|
||||
},
|
||||
@ -858,7 +949,11 @@
|
||||
},
|
||||
"slayer" : {
|
||||
"index" : 55,
|
||||
"anim" : 28,
|
||||
"targetType" : "CREATURE",
|
||||
|
||||
"animation":{
|
||||
"affect":["C13SPW0"]
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "SLAYER"
|
||||
},
|
||||
@ -908,7 +1003,11 @@
|
||||
},
|
||||
"frenzy" : {
|
||||
"index" : 56,
|
||||
"anim" : 17,
|
||||
"targetType" : "CREATURE",
|
||||
|
||||
"animation":{
|
||||
"affect":["C08SPF0"]
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "FRENZY"
|
||||
},
|
||||
@ -946,7 +1045,11 @@
|
||||
|
||||
"counterstrike" : {
|
||||
"index" : 58,
|
||||
"anim" : 7,
|
||||
"targetType" : "CREATURE",
|
||||
|
||||
"animation":{
|
||||
"affect":["C04SPA0"]
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "CNTRSTRK"
|
||||
},
|
||||
@ -984,7 +1087,11 @@
|
||||
},
|
||||
"berserk" : {
|
||||
"index" : 59,
|
||||
"anim" : 35,
|
||||
"targetType" : "CREATURE",
|
||||
|
||||
"animation":{
|
||||
"affect":["C01SPF"] //C01SPF0
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "BERSERK"
|
||||
},
|
||||
@ -1042,7 +1149,11 @@
|
||||
},
|
||||
"hypnotize" : {
|
||||
"index" : 60,
|
||||
"anim" : 21,
|
||||
"targetType" : "CREATURE",
|
||||
|
||||
"animation":{
|
||||
"affect":["C10SPA0"]
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "HYPNOTIZ"
|
||||
},
|
||||
@ -1099,7 +1210,10 @@
|
||||
"forgetfulness" : {
|
||||
"index" : 61,
|
||||
"targetType" : "CREATURE",
|
||||
"anim" : 42,
|
||||
|
||||
"animation":{
|
||||
"affect":["C06SPW"]//C06SPW0
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "FORGET"
|
||||
},
|
||||
@ -1158,7 +1272,11 @@
|
||||
},
|
||||
"blind" : {
|
||||
"index" : 62,
|
||||
"anim" : 6,
|
||||
"targetType" : "CREATURE",
|
||||
|
||||
"animation":{
|
||||
"affect":["C02SPF0"]
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "BLIND"
|
||||
},
|
||||
|
@ -118,24 +118,6 @@ void BattleInfo::calculateCasualties( std::map<ui32,si32> *casualties ) const
|
||||
}
|
||||
}
|
||||
|
||||
int BattleInfo::calculateSpellDuration( const CSpell * spell, const CGHeroInstance * caster, int usedSpellPower)
|
||||
{
|
||||
if(!caster)
|
||||
{
|
||||
if (!usedSpellPower)
|
||||
return 3; //default duration of all creature spells
|
||||
else
|
||||
return usedSpellPower; //use creature spell power
|
||||
}
|
||||
switch(spell->id)
|
||||
{
|
||||
case SpellID::FRENZY:
|
||||
return 1;
|
||||
default: //other spells
|
||||
return caster->getPrimSkillLevel(PrimarySkill::SPELL_POWER) + caster->valOfBonuses(Bonus::SPELL_DURATION);
|
||||
}
|
||||
}
|
||||
|
||||
CStack * BattleInfo::generateNewStack(const CStackInstance &base, bool attackerOwned, SlotID slot, BattleHex position) const
|
||||
{
|
||||
int stackID = getIdForNewStack();
|
||||
@ -159,36 +141,6 @@ CStack * BattleInfo::generateNewStack(const CStackBasicDescriptor &base, bool at
|
||||
return ret;
|
||||
}
|
||||
|
||||
//All spells casted by hero 9resurrection, cure, sacrifice)
|
||||
ui32 CBattleInfoCallback::calculateHealedHP(const CGHeroInstance * caster, const CSpell * spell, const CStack * stack, const CStack * sacrificedStack) const
|
||||
{
|
||||
bool resurrect = spell->isRisingSpell();
|
||||
int healedHealth;
|
||||
if (spell->id == SpellID::SACRIFICE && sacrificedStack)
|
||||
healedHealth = (caster->getPrimSkillLevel(PrimarySkill::SPELL_POWER) + sacrificedStack->MaxHealth() + spell->getPower(caster->getSpellSchoolLevel(spell))) * sacrificedStack->count;
|
||||
else
|
||||
healedHealth = caster->getPrimSkillLevel(PrimarySkill::SPELL_POWER) * spell->power + spell->getPower(caster->getSpellSchoolLevel(spell)); //???
|
||||
healedHealth = calculateSpellBonus(healedHealth, spell, caster, stack);
|
||||
return std::min<ui32>(healedHealth, stack->MaxHealth() - stack->firstHPleft + (resurrect ? stack->baseAmount * stack->MaxHealth() : 0));
|
||||
}
|
||||
//Archangel
|
||||
ui32 CBattleInfoCallback::calculateHealedHP(int healedHealth, const CSpell * spell, const CStack * stack) const
|
||||
{
|
||||
bool resurrect = spell->isRisingSpell();
|
||||
return std::min<ui32>(healedHealth, stack->MaxHealth() - stack->firstHPleft + (resurrect ? stack->baseAmount * stack->MaxHealth() : 0));
|
||||
}
|
||||
//Casted by stack, no hero bonus applied
|
||||
ui32 CBattleInfoCallback::calculateHealedHP(const CSpell * spell, int usedSpellPower, int spellSchoolLevel, const CStack * stack) const
|
||||
{
|
||||
bool resurrect = spell->isRisingSpell();
|
||||
int healedHealth = usedSpellPower * spell->power + spell->getPower(spellSchoolLevel);
|
||||
return std::min<ui32>(healedHealth, stack->MaxHealth() - stack->firstHPleft + (resurrect ? stack->baseAmount * stack->MaxHealth() : 0));
|
||||
}
|
||||
bool BattleInfo::resurrects(SpellID spellid) const
|
||||
{
|
||||
return spellid.toSpell()->isRisingSpell();
|
||||
}
|
||||
|
||||
const CStack * BattleInfo::battleGetStack(BattleHex pos, bool onlyAlive)
|
||||
{
|
||||
CStack * stack = nullptr;
|
||||
|
@ -136,12 +136,11 @@ struct DLL_LINKAGE BattleInfo : public CBonusSystemNode, public CBattleInfoCallb
|
||||
//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
|
||||
static int calculateSpellDuration(const CSpell * spell, const CGHeroInstance * caster, int usedSpellPower);
|
||||
|
||||
CStack * generateNewStack(const CStackInstance &base, bool attackerOwned, SlotID slot, BattleHex position) const; //helper for CGameHandler::setupBattle and spells addign new stacks to the battlefield
|
||||
CStack * generateNewStack(const CStackBasicDescriptor &base, bool attackerOwned, SlotID 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)
|
||||
bool resurrects(SpellID spellid) const; //TODO: move it to spellHandler?
|
||||
|
||||
const CGHeroInstance * getHero(PlayerColor player) const; //returns fighting hero that belongs to given player
|
||||
|
||||
|
@ -1565,145 +1565,6 @@ std::vector<BattleHex> CBattleInfoCallback::getAttackableBattleHexes() const
|
||||
return attackableBattleHexes;
|
||||
}
|
||||
|
||||
ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleIsImmune(const CGHeroInstance * caster, const CSpell * spell, ECastingMode::ECastingMode mode, BattleHex dest) const
|
||||
{
|
||||
RETURN_IF_NOT_BATTLE(ESpellCastProblem::INVALID);
|
||||
|
||||
// Get all stacks at destination hex -> subject of our spell. only alive if not rising spell
|
||||
TStacks stacks = battleGetStacksIf([=](const CStack * s){
|
||||
return s->coversPos(dest) && (spell->isRisingSpell() || s->alive());
|
||||
});
|
||||
|
||||
if(!stacks.empty())
|
||||
{
|
||||
bool allImmune = true;
|
||||
|
||||
ESpellCastProblem::ESpellCastProblem problem;
|
||||
|
||||
for(auto s : stacks)
|
||||
{
|
||||
ESpellCastProblem::ESpellCastProblem res = battleStackIsImmune(caster,spell,mode,s);
|
||||
|
||||
if(res == ESpellCastProblem::OK)
|
||||
{
|
||||
allImmune = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
problem = res;
|
||||
}
|
||||
}
|
||||
|
||||
if(allImmune)
|
||||
return problem;
|
||||
}
|
||||
else //no target stack on this tile
|
||||
{
|
||||
if(spell->getTargetType() == CSpell::CREATURE)
|
||||
{
|
||||
if(caster && mode == ECastingMode::HERO_CASTING) //TODO why???
|
||||
{
|
||||
const CSpell::TargetInfo ti = spell->getTargetInfo(caster->getSpellSchoolLevel(spell));
|
||||
|
||||
if(!ti.massive)
|
||||
return ESpellCastProblem::WRONG_SPELL_TARGET;
|
||||
}
|
||||
else
|
||||
{
|
||||
return ESpellCastProblem::WRONG_SPELL_TARGET;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return ESpellCastProblem::OK;
|
||||
}
|
||||
|
||||
ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleStackIsImmune(const CGHeroInstance * caster, const CSpell * spell, ECastingMode::ECastingMode mode, const CStack * subject) const
|
||||
{
|
||||
if (spell->isPositive() && subject->hasBonusOfType(Bonus::RECEPTIVE)) //accept all positive spells
|
||||
return ESpellCastProblem::OK;
|
||||
|
||||
if (spell->isImmuneBy(subject)) //TODO: move all logic to spellhandler
|
||||
return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
|
||||
|
||||
switch (spell->id) //TODO: more general logic for new spells?
|
||||
{
|
||||
case SpellID::CLONE:
|
||||
{
|
||||
//can't clone already cloned creature
|
||||
if (vstd::contains(subject->state, EBattleStackState::CLONED))
|
||||
return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
|
||||
//TODO: how about stacks casting Clone?
|
||||
//currently Clone casted by stack is assumed Expert level
|
||||
ui8 schoolLevel;
|
||||
if (caster)
|
||||
{
|
||||
schoolLevel = caster->getSpellSchoolLevel(spell);
|
||||
}
|
||||
else
|
||||
{
|
||||
schoolLevel = 3;
|
||||
}
|
||||
|
||||
if (schoolLevel < 3)
|
||||
{
|
||||
int maxLevel = (std::max(schoolLevel, (ui8)1) + 4);
|
||||
int creLevel = subject->getCreature()->level;
|
||||
if (maxLevel < creLevel) //tier 1-5 for basic, 1-6 for advanced, any level for expert
|
||||
return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case SpellID::DISPEL_HELPFUL_SPELLS:
|
||||
{
|
||||
TBonusListPtr spellBon = subject->getSpellBonuses();
|
||||
bool hasPositiveSpell = false;
|
||||
for(const Bonus * b : *spellBon)
|
||||
{
|
||||
if(SpellID(b->sid).toSpell()->isPositive())
|
||||
{
|
||||
hasPositiveSpell = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(!hasPositiveSpell)
|
||||
{
|
||||
return ESpellCastProblem::NO_SPELLS_TO_DISPEL;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (spell->isRisingSpell() && spell->id != SpellID::SACRIFICE)
|
||||
{
|
||||
// following does apply to resurrect and animate dead(?) only
|
||||
// for sacrifice health calculation and health limit check don't matter
|
||||
|
||||
if(subject->count >= subject->baseAmount)
|
||||
return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
|
||||
|
||||
if (caster) //FIXME: Archangels can cast immune stack
|
||||
{
|
||||
auto maxHealth = calculateHealedHP (caster, spell, subject);
|
||||
if (maxHealth < subject->MaxHealth()) //must be able to rise at least one full creature
|
||||
return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
|
||||
}
|
||||
}
|
||||
else if(spell->id == SpellID::HYPNOTIZE && caster) //do not resist hypnotize casted after attack, for example
|
||||
{
|
||||
//TODO: what with other creatures casting hypnotize, Faerie Dragons style?
|
||||
ui64 subjectHealth = (subject->count - 1) * subject->MaxHealth() + subject->firstHPleft;
|
||||
//apply 'damage' bonus for hypnotize, including hero specialty
|
||||
ui64 maxHealth = calculateSpellBonus (caster->getPrimSkillLevel(PrimarySkill::SPELL_POWER)
|
||||
* spell->power + spell->getPower(caster->getSpellSchoolLevel(spell)), spell, caster, subject);
|
||||
if (subjectHealth > maxHealth)
|
||||
return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
|
||||
}
|
||||
|
||||
return ESpellCastProblem::OK;
|
||||
}
|
||||
|
||||
ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCastThisSpell( PlayerColor player, const CSpell * spell, ECastingMode::ECastingMode mode ) const
|
||||
{
|
||||
RETURN_IF_NOT_BATTLE(ESpellCastProblem::INVALID);
|
||||
@ -1746,7 +1607,7 @@ ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCastThisSpell
|
||||
auto stacks = spell->isNegative() ? battleAliveStacks(!side) : battleAliveStacks();
|
||||
for(auto stack : stacks)
|
||||
{
|
||||
if( ESpellCastProblem::OK == battleStackIsImmune(castingHero, spell, mode, stack))
|
||||
if(ESpellCastProblem::OK == spell->isImmuneByStack(castingHero, stack))
|
||||
{
|
||||
allStacksImmune = false;
|
||||
break;
|
||||
@ -1790,7 +1651,7 @@ ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCastThisSpell
|
||||
|
||||
for(const CStack * stack : battleGetAllStacks()) //dead stacks will be immune anyway
|
||||
{
|
||||
bool immune = ESpellCastProblem::OK != battleStackIsImmune(caster, spell, mode, stack);
|
||||
bool immune = ESpellCastProblem::OK != spell->isImmuneByStack(caster, stack);
|
||||
bool casterStack = stack->owner == caster->getOwner();
|
||||
|
||||
if(spell->id == SpellID::SACRIFICE)
|
||||
@ -1847,8 +1708,6 @@ std::vector<BattleHex> CBattleInfoCallback::battleGetPossibleTargets(PlayerColor
|
||||
std::vector<BattleHex> ret;
|
||||
RETURN_IF_NOT_BATTLE(ret);
|
||||
|
||||
auto mode = ECastingMode::HERO_CASTING; //TODO get rid of this!
|
||||
|
||||
switch(spell->getTargetType())
|
||||
{
|
||||
case CSpell::CREATURE:
|
||||
@ -1858,7 +1717,7 @@ std::vector<BattleHex> CBattleInfoCallback::battleGetPossibleTargets(PlayerColor
|
||||
|
||||
for(const CStack * stack : battleAliveStacks())
|
||||
{
|
||||
bool immune = ESpellCastProblem::OK != battleStackIsImmune(caster, spell, mode, stack);
|
||||
bool immune = ESpellCastProblem::OK != spell->isImmuneByStack(caster, stack);
|
||||
bool casterStack = stack->owner == caster->getOwner();
|
||||
|
||||
if(!immune)
|
||||
@ -1985,199 +1844,11 @@ ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCastThisSpell
|
||||
return ESpellCastProblem::NO_APPROPRIATE_TARGET;
|
||||
}
|
||||
|
||||
|
||||
const CGHeroInstance * caster = nullptr;
|
||||
if (mode == ECastingMode::HERO_CASTING)
|
||||
return battleIsImmune(battleGetFightingHero(playerToSide(player)), spell, mode, dest);
|
||||
else
|
||||
return battleIsImmune(nullptr, spell, mode, dest);
|
||||
}
|
||||
caster = battleGetFightingHero(playerToSide(player));
|
||||
|
||||
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, SecondarySkill::SORCERY)) / 100.0;
|
||||
ret *= (100.0 + caster->valOfBonuses(Bonus::SPELL_DAMAGE) + caster->valOfBonuses(Bonus::SPECIFIC_SPELL_DAMAGE, sp->id.toEnum())) / 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.toEnum()) * 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(!sp->isDamageSpell())
|
||||
return 0;
|
||||
|
||||
ret = usedSpellPower * sp->power;
|
||||
ret += sp->getPower(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.toEnum());
|
||||
ret /= 100;
|
||||
}
|
||||
}
|
||||
ret = calculateSpellBonus(ret, sp, caster, affectedCreature);
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::set<const CStack*> CBattleInfoCallback::getAffectedCreatures(const CSpell * spell, int skillLevel, PlayerColor 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 CSpell::TargetInfo ti = spell->getTargetInfo(skillLevel);
|
||||
//TODO: more generic solution for mass spells
|
||||
if (spell->id == SpellID::CHAIN_LIGHTNING)
|
||||
{
|
||||
std::set<BattleHex> possibleHexes;
|
||||
for (auto stack : battleGetAllStacks())
|
||||
{
|
||||
if (stack->isValidTarget())
|
||||
{
|
||||
for (auto hex : stack->getHexes())
|
||||
{
|
||||
possibleHexes.insert (hex);
|
||||
}
|
||||
}
|
||||
}
|
||||
int targetsOnLevel[4] = {4, 4, 5, 5};
|
||||
|
||||
BattleHex lightningHex = destinationTile;
|
||||
for (int i = 0; i < targetsOnLevel[skillLevel]; ++i)
|
||||
{
|
||||
auto stack = battleGetStackByPos (lightningHex, true);
|
||||
if (!stack)
|
||||
break;
|
||||
attackedCres.insert (stack);
|
||||
for (auto hex : stack->getHexes())
|
||||
{
|
||||
possibleHexes.erase (hex); //can't hit same place twice
|
||||
}
|
||||
if (possibleHexes.empty()) //not enough targets
|
||||
break;
|
||||
lightningHex = BattleHex::getClosestTile (stack->attackerOwned, destinationTile, possibleHexes);
|
||||
}
|
||||
}
|
||||
else if (spell->getLevelInfo(skillLevel).range.size() > 1) //custom many-hex range
|
||||
{
|
||||
for(BattleHex hex : attackedHexes)
|
||||
{
|
||||
if(const CStack * st = battleGetStackByPos(hex, ti.onlyAlive))
|
||||
{
|
||||
if (spell->id == SpellID::DEATH_CLOUD) //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)
|
||||
{
|
||||
auto predicate = [=](const CStack * s){
|
||||
const bool positiveToAlly = spell->isPositive() && s->owner == attackerOwner;
|
||||
const bool negativeToEnemy = spell->isNegative() && s->owner != attackerOwner;
|
||||
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(destinationTile);
|
||||
//handle smart targeting
|
||||
const bool positivenessFlag = !ti.smart || spell->isNeutral() || positiveToAlly || negativeToEnemy;
|
||||
|
||||
return rangeCovers && positivenessFlag && validTarget;
|
||||
};
|
||||
|
||||
TStacks stacks = battleGetStacksIf(predicate);
|
||||
|
||||
if (ti.massive)
|
||||
{
|
||||
//for massive spells add all targets
|
||||
for (auto stack : stacks)
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
if(attackedCres.empty() && !stacks.empty())
|
||||
{
|
||||
attackedCres.insert(stacks.front());
|
||||
}
|
||||
}
|
||||
}
|
||||
else //custom range from attackedHexes
|
||||
{
|
||||
for(BattleHex hex : attackedHexes)
|
||||
{
|
||||
if(const CStack * st = battleGetStackByPos(hex, ti.onlyAlive))
|
||||
attackedCres.insert(st);
|
||||
}
|
||||
}
|
||||
return attackedCres;
|
||||
return spell->isImmuneAt(this, caster, mode, dest);
|
||||
}
|
||||
|
||||
const CStack * CBattleInfoCallback::getStackIf(std::function<bool(const CStack*)> pred) const
|
||||
@ -2220,24 +1891,56 @@ std::set<const CStack*> CBattleInfoCallback:: batteAdjacentCreatures(const CStac
|
||||
SpellID CBattleInfoCallback::getRandomBeneficialSpell(const CStack * subject) const
|
||||
{
|
||||
RETURN_IF_NOT_BATTLE(SpellID::NONE);
|
||||
std::vector<SpellID> possibleSpells;
|
||||
//This is complete list. No spells from mods.
|
||||
//todo: this should be Spellbook of caster Stack
|
||||
static const std::set<SpellID> allPossibleSpells =
|
||||
{
|
||||
SpellID::AIR_SHIELD,
|
||||
SpellID::ANTI_MAGIC,
|
||||
SpellID::BLESS,
|
||||
SpellID::BLOODLUST,
|
||||
SpellID::COUNTERSTRIKE,
|
||||
SpellID::CURE,
|
||||
SpellID::FIRE_SHIELD,
|
||||
SpellID::FORTUNE,
|
||||
SpellID::HASTE,
|
||||
SpellID::MAGIC_MIRROR,
|
||||
SpellID::MIRTH,
|
||||
SpellID::PRAYER,
|
||||
SpellID::PRECISION,
|
||||
SpellID::PROTECTION_FROM_AIR,
|
||||
SpellID::PROTECTION_FROM_EARTH,
|
||||
SpellID::PROTECTION_FROM_FIRE,
|
||||
SpellID::PROTECTION_FROM_WATER,
|
||||
SpellID::SHIELD,
|
||||
SpellID::SLAYER,
|
||||
SpellID::STONE_SKIN
|
||||
};
|
||||
std::vector<SpellID> beneficialSpells;
|
||||
|
||||
for(const CSpell *spell : VLC->spellh->objects)
|
||||
auto getAliveEnemy = [=](const std::function<bool(const CStack * )> & pred)
|
||||
{
|
||||
if (spell->isPositive() && !spell->isRisingSpell()) //only positive and not rising
|
||||
return getStackIf([=](const CStack * stack)
|
||||
{
|
||||
if (subject->hasBonusFrom(Bonus::SPELL_EFFECT, spell->id)
|
||||
|| battleCanCastThisSpellHere(subject->owner, spell, ECastingMode::CREATURE_ACTIVE_CASTING, subject->position) != ESpellCastProblem::OK)
|
||||
return pred(stack) && stack->owner != subject->owner && stack->alive();
|
||||
});
|
||||
};
|
||||
|
||||
for(const SpellID spellID : allPossibleSpells)
|
||||
{
|
||||
if (subject->hasBonusFrom(Bonus::SPELL_EFFECT, spellID)
|
||||
//TODO: this ability has special limitations
|
||||
|| battleCanCastThisSpellHere(subject->owner, spellID.toSpell(), ECastingMode::CREATURE_ACTIVE_CASTING, subject->position) != ESpellCastProblem::OK)
|
||||
continue;
|
||||
|
||||
switch (spell->id)
|
||||
switch (spellID)
|
||||
{
|
||||
case SpellID::SHIELD:
|
||||
case SpellID::FIRE_SHIELD: // not if all enemy units are shooters
|
||||
{
|
||||
auto walker = getStackIf([&](const CStack *stack) //look for enemy, non-shooting stack
|
||||
auto walker = getAliveEnemy([&](const CStack * stack) //look for enemy, non-shooting stack
|
||||
{
|
||||
return stack->owner != subject->owner && !stack->shots;
|
||||
return !stack->shots;
|
||||
});
|
||||
|
||||
if (!walker)
|
||||
@ -2246,10 +1949,9 @@ SpellID CBattleInfoCallback::getRandomBeneficialSpell(const CStack * subject) co
|
||||
break;
|
||||
case SpellID::AIR_SHIELD: //only against active shooters
|
||||
{
|
||||
|
||||
auto shooter = getStackIf([&](const CStack *stack) //look for enemy, non-shooting stack
|
||||
auto shooter = getAliveEnemy([&](const CStack * stack) //look for enemy, non-shooting stack
|
||||
{
|
||||
return stack->owner != subject->owner && stack->hasBonusOfType(Bonus::SHOOTER) && stack->shots;
|
||||
return stack->hasBonusOfType(Bonus::SHOOTER) && stack->shots;
|
||||
});
|
||||
if (!shooter)
|
||||
continue;
|
||||
@ -2257,13 +1959,20 @@ SpellID CBattleInfoCallback::getRandomBeneficialSpell(const CStack * subject) co
|
||||
break;
|
||||
case SpellID::ANTI_MAGIC:
|
||||
case SpellID::MAGIC_MIRROR:
|
||||
case SpellID::PROTECTION_FROM_AIR:
|
||||
case SpellID::PROTECTION_FROM_EARTH:
|
||||
case SpellID::PROTECTION_FROM_FIRE:
|
||||
case SpellID::PROTECTION_FROM_WATER:
|
||||
{
|
||||
if (!battleHasHero(subject->attackerOwned)) //only if there is enemy hero
|
||||
const ui8 enemySide = (ui8)subject->attackerOwned;
|
||||
//todo: only if enemy has spellbook
|
||||
if (!battleHasHero(enemySide)) //only if there is enemy hero
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
case SpellID::CURE: //only damaged units - what about affected by curse?
|
||||
case SpellID::CURE: //only damaged units
|
||||
{
|
||||
//do not cast on affected by debuffs
|
||||
if (subject->firstHPleft >= subject->MaxHealth())
|
||||
continue;
|
||||
}
|
||||
@ -2282,31 +1991,26 @@ SpellID CBattleInfoCallback::getRandomBeneficialSpell(const CStack * subject) co
|
||||
break;
|
||||
case SpellID::SLAYER://only if monsters are present
|
||||
{
|
||||
auto kingMonster = getStackIf([&](const CStack *stack) -> bool //look for enemy, non-shooting stack
|
||||
auto kingMonster = getAliveEnemy([&](const CStack *stack) -> bool //look for enemy, non-shooting stack
|
||||
{
|
||||
const auto isKing = Selector::type(Bonus::KING1)
|
||||
.Or(Selector::type(Bonus::KING2))
|
||||
.Or(Selector::type(Bonus::KING3));
|
||||
|
||||
return stack->owner != subject->owner && stack->hasBonus(isKing);
|
||||
return stack->hasBonus(isKing);
|
||||
});
|
||||
|
||||
if (!kingMonster)
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
case SpellID::TELEPORT: //issue 1928
|
||||
case SpellID::CLONE: //not allowed
|
||||
continue;
|
||||
break;
|
||||
}
|
||||
possibleSpells.push_back(spell->id);
|
||||
}
|
||||
beneficialSpells.push_back(spellID);
|
||||
}
|
||||
|
||||
if(!possibleSpells.empty())
|
||||
if(!beneficialSpells.empty())
|
||||
{
|
||||
return *RandomGeneratorUtil::nextItem(possibleSpells, gs->getRandomGenerator());
|
||||
return *RandomGeneratorUtil::nextItem(beneficialSpells, gs->getRandomGenerator());
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -281,21 +281,11 @@ public:
|
||||
ESpellCastProblem::ESpellCastProblem battleCanCastThisSpellHere(PlayerColor 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(PlayerColor 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
|
||||
ui32 calculateHealedHP(const CGHeroInstance * caster, const CSpell * spell, const CStack * stack, const CStack * sacrificedStack = nullptr) const; //Sacrifice
|
||||
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; //healing spells casted by stacks
|
||||
std::set<const CStack*> getAffectedCreatures(const CSpell * s, int skillLevel, PlayerColor attackerOwner, BattleHex destinationTile); //calculates stack affected by given spell
|
||||
|
||||
SpellID battleGetRandomStackSpell(const CStack * stack, ERandomSpell mode) const;
|
||||
SpellID getRandomBeneficialSpell(const CStack * subject) const;
|
||||
SpellID getRandomCastedSpell(const CStack * caster) const; //called at the beginning of turn for Faerie Dragon
|
||||
|
||||
//checks for creature immunity / anything that prevent casting *at given hex* - doesn't take into acount general problems such as not having spellbook or mana points etc.
|
||||
ESpellCastProblem::ESpellCastProblem battleStackIsImmune(const CGHeroInstance * caster, const CSpell * spell, ECastingMode::ECastingMode mode, const CStack * subject) const;
|
||||
|
||||
|
||||
const CStack * getStackIf(std::function<bool(const CStack*)> pred) const;
|
||||
|
||||
si8 battleHasShootingPenalty(const CStack * stack, BattleHex destHex)
|
||||
@ -321,17 +311,10 @@ public:
|
||||
AccessibilityInfo getAccesibility(const std::vector<BattleHex> &accessibleHexes) const; //given hexes will be marked as accessible
|
||||
std::pair<const CStack *, BattleHex> getNearestStack(const CStack * closest, boost::logic::tribool attackerOwned) const;
|
||||
protected:
|
||||
|
||||
//checks for creature immunity / anything that prevent casting *at given hex* - doesn't take into acount general problems such as not having spellbook or mana points etc.
|
||||
ESpellCastProblem::ESpellCastProblem battleIsImmune(const CGHeroInstance * caster, const CSpell * spell, ECastingMode::ECastingMode mode, BattleHex dest) const;
|
||||
|
||||
|
||||
ReachabilityInfo getFlyingReachability(const ReachabilityInfo::Parameters ¶ms) const;
|
||||
ReachabilityInfo makeBFS(const AccessibilityInfo &accessibility, const ReachabilityInfo::Parameters ¶ms) const;
|
||||
ReachabilityInfo makeBFS(const CStack *stack) const; //uses default parameters -> stack position and owner's perspective
|
||||
std::set<BattleHex> getStoppers(BattlePerspective::BattlePerspective whichSidePerspective) const; //get hexes with stopping obstacles (quicksands)
|
||||
|
||||
|
||||
};
|
||||
|
||||
class DLL_LINKAGE CPlayerBattleCallback : public CBattleInfoCallback
|
||||
|
@ -17,6 +17,7 @@
|
||||
#include "BattleState.h" // for BattleInfo
|
||||
#include "NetPacks.h" // for InfoWindow
|
||||
#include "CModHandler.h"
|
||||
#include "CSpellHandler.h"
|
||||
|
||||
//TODO make clean
|
||||
#define ERROR_VERBOSE_OR_NOT_RET_VAL_IF(cond, verbose, txt, retVal) do {if(cond){if(verbose)logGlobal->errorStream() << BOOST_CURRENT_FUNCTION << ": " << txt; return retVal;}} while(0)
|
||||
@ -170,19 +171,11 @@ int CGameInfoCallback::estimateSpellDamage(const CSpell * sp, const CGHeroInstan
|
||||
//boost::shared_lock<boost::shared_mutex> lock(*gs->mx);
|
||||
|
||||
ERROR_RET_VAL_IF(hero && !canGetFullInfo(hero), "Cannot get info about caster!", -1);
|
||||
if(!gs->curB) //no battle
|
||||
{
|
||||
if (hero) //but we see hero's spellbook
|
||||
return gs->curB->calculateSpellDmg(
|
||||
sp, hero, nullptr, hero->getSpellSchoolLevel(sp), hero->getPrimSkillLevel(PrimarySkill::SPELL_POWER));
|
||||
|
||||
if (hero) //we see hero's spellbook
|
||||
return sp->calculateDamage(hero, nullptr, hero->getSpellSchoolLevel(sp), hero->getPrimSkillLevel(PrimarySkill::SPELL_POWER));
|
||||
else
|
||||
return 0; //mage guild
|
||||
}
|
||||
//gs->getHero(gs->currentPlayer)
|
||||
//const CGHeroInstance * ourHero = gs->curB->heroes[0]->tempOwner == player ? gs->curB->heroes[0] : gs->curB->heroes[1];
|
||||
const CGHeroInstance * ourHero = hero;
|
||||
return gs->curB->calculateSpellDmg(
|
||||
sp, ourHero, nullptr, ourHero->getSpellSchoolLevel(sp), ourHero->getPrimSkillLevel(PrimarySkill::SPELL_POWER));
|
||||
}
|
||||
|
||||
void CGameInfoCallback::getThievesGuildInfo(SThievesGuildInfo & thi, const CGObjectInstance * obj)
|
||||
|
@ -83,6 +83,7 @@ set(lib_SRCS
|
||||
VCMI_Lib.cpp
|
||||
VCMIDirs.cpp
|
||||
IHandlerBase.cpp
|
||||
SpellMechanics.cpp
|
||||
|
||||
IGameCallback.cpp
|
||||
CGameInfoCallback.cpp
|
||||
|
@ -99,6 +99,14 @@ void CIdentifierStorage::requestIdentifier(std::string scope, std::string type,
|
||||
requestIdentifier(ObjectCallback(scope, pair.first, type, pair.second, callback, false));
|
||||
}
|
||||
|
||||
void CIdentifierStorage::requestIdentifier(std::string scope, std::string fullName, const std::function<void(si32)>& callback)
|
||||
{
|
||||
auto scopeAndFullName = splitString(fullName, ':');
|
||||
auto typeAndName = splitString(scopeAndFullName.second, '.');
|
||||
|
||||
requestIdentifier(ObjectCallback(scope, scopeAndFullName.first, typeAndName.first, typeAndName.second, callback, false));
|
||||
}
|
||||
|
||||
void CIdentifierStorage::requestIdentifier(std::string type, const JsonNode & name, const std::function<void(si32)> & callback)
|
||||
{
|
||||
auto pair = splitString(name.String(), ':'); // remoteScope:name
|
||||
|
@ -72,6 +72,8 @@ public:
|
||||
/// request identifier for specific object name.
|
||||
/// Function callback will be called during ID resolution phase of loading
|
||||
void requestIdentifier(std::string scope, std::string type, std::string name, const std::function<void(si32)> & callback);
|
||||
///fullName = [remoteScope:]type.name
|
||||
void requestIdentifier(std::string scope, std::string fullName, const std::function<void(si32)> & callback);
|
||||
void requestIdentifier(std::string type, const JsonNode & name, const std::function<void(si32)> & callback);
|
||||
void requestIdentifier(const JsonNode & name, const std::function<void(si32)> & callback);
|
||||
|
||||
|
@ -10,6 +10,14 @@
|
||||
#include "CModHandler.h"
|
||||
#include "StringConstants.h"
|
||||
|
||||
#include "mapObjects/CGHeroInstance.h"
|
||||
#include "BattleState.h"
|
||||
#include "CBattleCallback.h"
|
||||
|
||||
#include "SpellMechanics.h"
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* CSpellHandler.cpp, part of VCMI engine
|
||||
*
|
||||
@ -24,112 +32,54 @@ namespace SpellConfig
|
||||
{
|
||||
static const std::string LEVEL_NAMES[] = {"none", "basic", "advanced", "expert"};
|
||||
|
||||
static const SpellSchoolInfo SCHOOL[4] =
|
||||
{
|
||||
{
|
||||
ESpellSchool::AIR,
|
||||
Bonus::AIR_SPELL_DMG_PREMY,
|
||||
Bonus::AIR_IMMUNITY,
|
||||
"air",
|
||||
SecondarySkill::AIR_MAGIC,
|
||||
Bonus::AIR_SPELLS
|
||||
},
|
||||
{
|
||||
ESpellSchool::FIRE,
|
||||
Bonus::FIRE_SPELL_DMG_PREMY,
|
||||
Bonus::FIRE_IMMUNITY,
|
||||
"fire",
|
||||
SecondarySkill::FIRE_MAGIC,
|
||||
Bonus::FIRE_SPELLS
|
||||
},
|
||||
{
|
||||
ESpellSchool::WATER,
|
||||
Bonus::WATER_SPELL_DMG_PREMY,
|
||||
Bonus::WATER_IMMUNITY,
|
||||
"water",
|
||||
SecondarySkill::WATER_MAGIC,
|
||||
Bonus::WATER_SPELLS
|
||||
},
|
||||
{
|
||||
ESpellSchool::EARTH,
|
||||
Bonus::EARTH_SPELL_DMG_PREMY,
|
||||
Bonus::EARTH_IMMUNITY,
|
||||
"earth",
|
||||
SecondarySkill::EARTH_MAGIC,
|
||||
Bonus::EARTH_SPELLS
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
namespace SRSLPraserHelpers
|
||||
BattleSpellCastParameters::BattleSpellCastParameters(const BattleInfo* cb)
|
||||
: spellLvl(0), destination(BattleHex::INVALID), casterSide(0),casterColor(PlayerColor::CANNOT_DETERMINE),caster(nullptr), secHero(nullptr),
|
||||
usedSpellPower(0),mode(ECastingMode::HERO_CASTING), casterStack(nullptr), selectedStack(nullptr), cb(cb)
|
||||
{
|
||||
static int XYToHex(int x, int y)
|
||||
{
|
||||
return x + 17 * y;
|
||||
}
|
||||
|
||||
static int XYToHex(std::pair<int, int> xy)
|
||||
{
|
||||
return XYToHex(xy.first, xy.second);
|
||||
}
|
||||
|
||||
static int hexToY(int battleFieldPosition)
|
||||
{
|
||||
return battleFieldPosition/17;
|
||||
}
|
||||
|
||||
static int hexToX(int battleFieldPosition)
|
||||
{
|
||||
int pos = battleFieldPosition - hexToY(battleFieldPosition) * 17;
|
||||
return pos;
|
||||
}
|
||||
|
||||
static std::pair<int, int> hexToPair(int battleFieldPosition)
|
||||
{
|
||||
return std::make_pair(hexToX(battleFieldPosition), hexToY(battleFieldPosition));
|
||||
}
|
||||
|
||||
//moves hex by one hex in given direction
|
||||
//0 - left top, 1 - right top, 2 - right, 3 - right bottom, 4 - left bottom, 5 - left
|
||||
static std::pair<int, int> gotoDir(int x, int y, int direction)
|
||||
{
|
||||
switch(direction)
|
||||
{
|
||||
case 0: //top left
|
||||
return std::make_pair((y%2) ? x-1 : x, y-1);
|
||||
case 1: //top right
|
||||
return std::make_pair((y%2) ? x : x+1, y-1);
|
||||
case 2: //right
|
||||
return std::make_pair(x+1, y);
|
||||
case 3: //right bottom
|
||||
return std::make_pair((y%2) ? x : x+1, y+1);
|
||||
case 4: //left bottom
|
||||
return std::make_pair((y%2) ? x-1 : x, y+1);
|
||||
case 5: //left
|
||||
return std::make_pair(x-1, y);
|
||||
default:
|
||||
throw std::runtime_error("Disaster: wrong direction in SRSLPraserHelpers::gotoDir!\n");
|
||||
}
|
||||
}
|
||||
|
||||
static std::pair<int, int> gotoDir(std::pair<int, int> xy, int direction)
|
||||
{
|
||||
return gotoDir(xy.first, xy.second, direction);
|
||||
}
|
||||
|
||||
static bool isGoodHex(std::pair<int, int> xy)
|
||||
{
|
||||
return xy.first >=0 && xy.first < 17 && xy.second >= 0 && xy.second < 11;
|
||||
}
|
||||
|
||||
//helper function for std::set<ui16> CSpell::rangeInHexes(unsigned int centralHex, ui8 schoolLvl ) const
|
||||
static std::set<ui16> getInRange(unsigned int center, int low, int high)
|
||||
{
|
||||
std::set<ui16> ret;
|
||||
if(low == 0)
|
||||
{
|
||||
ret.insert(center);
|
||||
}
|
||||
|
||||
std::pair<int, int> mainPointForLayer[6]; //A, B, C, D, E, F points
|
||||
for(auto & elem : mainPointForLayer)
|
||||
elem = hexToPair(center);
|
||||
|
||||
for(int it=1; it<=high; ++it) //it - distance to the center
|
||||
{
|
||||
for(int b=0; b<6; ++b)
|
||||
mainPointForLayer[b] = gotoDir(mainPointForLayer[b], b);
|
||||
|
||||
if(it>=low)
|
||||
{
|
||||
std::pair<int, int> curHex;
|
||||
|
||||
//adding lines (A-b, B-c, C-d, etc)
|
||||
for(int v=0; v<6; ++v)
|
||||
{
|
||||
curHex = mainPointForLayer[v];
|
||||
for(int h=0; h<it; ++h)
|
||||
{
|
||||
if(isGoodHex(curHex))
|
||||
ret.insert(XYToHex(curHex));
|
||||
curHex = gotoDir(curHex, (v+2)%6);
|
||||
}
|
||||
}
|
||||
|
||||
} //if(it>=low)
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
///CSpell::LevelInfo
|
||||
CSpell::LevelInfo::LevelInfo()
|
||||
:description(""),cost(0),power(0),AIValue(0),smartTarget(true),range("0")
|
||||
:description(""),cost(0),power(0),AIValue(0),smartTarget(true), clearTarget(false), clearAffected(false), range("0")
|
||||
{
|
||||
|
||||
}
|
||||
@ -139,22 +89,68 @@ CSpell::LevelInfo::~LevelInfo()
|
||||
|
||||
}
|
||||
|
||||
|
||||
///CSpell
|
||||
CSpell::CSpell():
|
||||
id(SpellID::NONE), level(0),
|
||||
earth(false), water(false), fire(false), air(false),
|
||||
combatSpell(false), creatureAbility(false),
|
||||
positiveness(ESpellPositiveness::NEUTRAL),
|
||||
mainEffectAnim(-1),
|
||||
defaultProbability(0),
|
||||
isRising(false), isDamage(false), isOffensive(false),
|
||||
targetType(ETargetType::NO_TARGET)
|
||||
targetType(ETargetType::NO_TARGET),
|
||||
mechanics(nullptr)
|
||||
{
|
||||
levels.resize(GameConstants::SPELL_SCHOOL_LEVELS);
|
||||
}
|
||||
|
||||
CSpell::~CSpell()
|
||||
{
|
||||
delete mechanics;
|
||||
}
|
||||
|
||||
void CSpell::afterCast(BattleInfo * battle, const BattleSpellCast * packet) const
|
||||
{
|
||||
mechanics->afterCast(battle, packet);
|
||||
}
|
||||
|
||||
|
||||
void CSpell::battleCast(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters) const
|
||||
{
|
||||
assert(env);
|
||||
|
||||
mechanics->battleCast(env, parameters);
|
||||
}
|
||||
|
||||
bool CSpell::isCastableBy(const IBonusBearer * caster, bool hasSpellBook, const std::set<SpellID> & spellBook) const
|
||||
{
|
||||
if(!hasSpellBook)
|
||||
return false;
|
||||
|
||||
const bool inSpellBook = vstd::contains(spellBook, id);
|
||||
const bool isBonus = caster->hasBonusOfType(Bonus::SPELL, id);
|
||||
|
||||
bool inTome = false;
|
||||
|
||||
forEachSchool([&](const SpellSchoolInfo & cnf, bool & stop)
|
||||
{
|
||||
if(caster->hasBonusOfType(cnf.knoledgeBonus))
|
||||
{
|
||||
inTome = stop = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (isSpecialSpell())
|
||||
{
|
||||
if (inSpellBook)
|
||||
{//hero has this spell in spellbook
|
||||
logGlobal->errorStream() << "Special spell in spellbook "<<name;
|
||||
}
|
||||
return isBonus;
|
||||
}
|
||||
else
|
||||
{
|
||||
return inSpellBook || inTome || isBonus || caster->hasBonusOfType(Bonus::SPELLS_OF_LEVEL, level);
|
||||
}
|
||||
}
|
||||
|
||||
const CSpell::LevelInfo & CSpell::getLevelInfo(const int level) const
|
||||
@ -168,128 +164,145 @@ const CSpell::LevelInfo & CSpell::getLevelInfo(const int level) const
|
||||
return levels.at(level);
|
||||
}
|
||||
|
||||
ui32 CSpell::calculateBonus(ui32 baseDamage, 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, SecondarySkill::SORCERY)) / 100.0;
|
||||
ret *= (100.0 + caster->valOfBonuses(Bonus::SPELL_DAMAGE) + caster->valOfBonuses(Bonus::SPECIFIC_SPELL_DAMAGE, id.toEnum())) / 100.0;
|
||||
|
||||
forEachSchool([&](const SpellSchoolInfo & cnf, bool & stop)
|
||||
{
|
||||
ret *= (100.0 + caster->valOfBonuses(cnf.damagePremyBonus)) / 100.0;
|
||||
stop = true; //only bonus from one school is used
|
||||
});
|
||||
|
||||
if (affectedCreature && affectedCreature->getCreature()->level) //Hero specials like Solmyr, Deemer
|
||||
ret *= (100. + ((caster->valOfBonuses(Bonus::SPECIAL_SPELL_LEV, id.toEnum()) * caster->level) / affectedCreature->getCreature()->level)) / 100.0;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
ui32 CSpell::calculateDamage(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(!isDamageSpell())
|
||||
return 0;
|
||||
|
||||
ret = usedSpellPower * power;
|
||||
ret += getPower(spellSchoolLevel);
|
||||
|
||||
//affected creature-specific part
|
||||
if(nullptr != affectedCreature)
|
||||
{
|
||||
//applying protections - when spell has more then one elements, only one protection should be applied (I think)
|
||||
|
||||
forEachSchool([&](const SpellSchoolInfo & cnf, bool & stop)
|
||||
{
|
||||
if(affectedCreature->hasBonusOfType(Bonus::SPELL_DAMAGE_REDUCTION, (ui8)cnf.id))
|
||||
{
|
||||
ret *= affectedCreature->valOfBonuses(Bonus::SPELL_DAMAGE_REDUCTION, (ui8)cnf.id);
|
||||
ret /= 100;
|
||||
stop = true;//only bonus from one school is used
|
||||
}
|
||||
});
|
||||
|
||||
//general spell dmg reduction
|
||||
if(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, id))
|
||||
{
|
||||
ret *= 100 + affectedCreature->valOfBonuses(Bonus::MORE_DAMAGE_FROM_SPELL, id.toEnum());
|
||||
ret /= 100;
|
||||
}
|
||||
}
|
||||
ret = calculateBonus(ret, caster, affectedCreature);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
ui32 CSpell::calculateHealedHP(const CGHeroInstance* caster, const CStack* stack, const CStack* sacrificedStack) const
|
||||
{
|
||||
//todo: use Mechanics class
|
||||
int healedHealth;
|
||||
|
||||
if(!isHealingSpell())
|
||||
{
|
||||
logGlobal->errorStream() << "calculateHealedHP called for nonhealing spell "<< name;
|
||||
return 0;
|
||||
}
|
||||
|
||||
const int spellPowerSkill = caster->getPrimSkillLevel(PrimarySkill::SPELL_POWER);
|
||||
const int levelPower = getPower(caster->getSpellSchoolLevel(this));
|
||||
|
||||
if (id == SpellID::SACRIFICE && sacrificedStack)
|
||||
healedHealth = (spellPowerSkill + sacrificedStack->MaxHealth() + levelPower) * sacrificedStack->count;
|
||||
else
|
||||
healedHealth = spellPowerSkill * power + levelPower; //???
|
||||
healedHealth = calculateBonus(healedHealth, caster, stack);
|
||||
return std::min<ui32>(healedHealth, stack->MaxHealth() - stack->firstHPleft + (isRisingSpell() ? stack->baseAmount * stack->MaxHealth() : 0));
|
||||
}
|
||||
|
||||
|
||||
std::vector<BattleHex> CSpell::rangeInHexes(BattleHex centralHex, ui8 schoolLvl, ui8 side, bool *outDroppedHexes) const
|
||||
{
|
||||
using namespace SRSLPraserHelpers;
|
||||
|
||||
std::vector<BattleHex> ret;
|
||||
|
||||
if(id == SpellID::FIRE_WALL || id == SpellID::FORCE_FIELD)
|
||||
{
|
||||
//Special case - shape of obstacle depends on caster's side
|
||||
//TODO make it possible through spell_info config
|
||||
|
||||
BattleHex::EDir firstStep, secondStep;
|
||||
if(side)
|
||||
{
|
||||
firstStep = BattleHex::TOP_LEFT;
|
||||
secondStep = BattleHex::TOP_RIGHT;
|
||||
}
|
||||
else
|
||||
{
|
||||
firstStep = BattleHex::TOP_RIGHT;
|
||||
secondStep = BattleHex::TOP_LEFT;
|
||||
}
|
||||
|
||||
//Adds hex to the ret if it's valid. Otherwise sets output arg flag if given.
|
||||
auto addIfValid = [&](BattleHex hex)
|
||||
{
|
||||
if(hex.isValid())
|
||||
ret.push_back(hex);
|
||||
else if(outDroppedHexes)
|
||||
*outDroppedHexes = true;
|
||||
};
|
||||
|
||||
ret.push_back(centralHex);
|
||||
addIfValid(centralHex.moveInDir(firstStep, false));
|
||||
if(schoolLvl >= 2) //advanced versions of fire wall / force field cotnains of 3 hexes
|
||||
addIfValid(centralHex.moveInDir(secondStep, false)); //moveInDir function modifies subject hex
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
std::string rng = getLevelInfo(schoolLvl).range + ','; //copy + artificial comma for easier handling
|
||||
|
||||
if(rng.size() >= 1 && rng[0] != 'X') //there is at lest one hex in range
|
||||
{
|
||||
std::string number1, number2;
|
||||
int beg, end;
|
||||
bool readingFirst = true;
|
||||
for(auto & elem : rng)
|
||||
{
|
||||
if(std::isdigit(elem) ) //reading number
|
||||
{
|
||||
if(readingFirst)
|
||||
number1 += elem;
|
||||
else
|
||||
number2 += elem;
|
||||
}
|
||||
else if(elem == ',') //comma
|
||||
{
|
||||
//calculating variables
|
||||
if(readingFirst)
|
||||
{
|
||||
beg = atoi(number1.c_str());
|
||||
number1 = "";
|
||||
}
|
||||
else
|
||||
{
|
||||
end = atoi(number2.c_str());
|
||||
number2 = "";
|
||||
}
|
||||
//obtaining new hexes
|
||||
std::set<ui16> curLayer;
|
||||
if(readingFirst)
|
||||
{
|
||||
curLayer = getInRange(centralHex, beg, beg);
|
||||
}
|
||||
else
|
||||
{
|
||||
curLayer = getInRange(centralHex, beg, end);
|
||||
readingFirst = true;
|
||||
}
|
||||
//adding abtained hexes
|
||||
for(auto & curLayer_it : curLayer)
|
||||
{
|
||||
ret.push_back(curLayer_it);
|
||||
}
|
||||
|
||||
}
|
||||
else if(elem == '-') //dash
|
||||
{
|
||||
beg = atoi(number1.c_str());
|
||||
number1 = "";
|
||||
readingFirst = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//remove duplicates (TODO check if actually needed)
|
||||
range::unique(ret);
|
||||
return ret;
|
||||
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 CGHeroInstance * caster) 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;
|
||||
}
|
||||
|
||||
|
||||
CSpell::ETargetType CSpell::getTargetType() const
|
||||
{
|
||||
return targetType;
|
||||
}
|
||||
|
||||
const CSpell::TargetInfo CSpell::getTargetInfo(const int level) const
|
||||
CSpell::TargetInfo CSpell::getTargetInfo(const int level) const
|
||||
{
|
||||
TargetInfo info;
|
||||
|
||||
auto & levelInfo = getLevelInfo(level);
|
||||
|
||||
info.type = getTargetType();
|
||||
info.smart = levelInfo.smartTarget;
|
||||
info.massive = levelInfo.range == "X";
|
||||
info.onlyAlive = !isRisingSpell();
|
||||
|
||||
TargetInfo info(this, level);
|
||||
return info;
|
||||
}
|
||||
|
||||
void CSpell::forEachSchool(const std::function<void(const SpellSchoolInfo &, bool &)>& cb) const
|
||||
{
|
||||
bool stop = false;
|
||||
for(const SpellSchoolInfo & cnf : SpellConfig::SCHOOL)
|
||||
{
|
||||
if(school.at(cnf.id))
|
||||
{
|
||||
cb(cnf, stop);
|
||||
|
||||
if(stop)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool CSpell::isCombatSpell() const
|
||||
{
|
||||
@ -321,6 +334,10 @@ bool CSpell::isNeutral() const
|
||||
return positiveness == NEUTRAL;
|
||||
}
|
||||
|
||||
bool CSpell::isHealingSpell() const
|
||||
{
|
||||
return isRisingSpell() || (id == SpellID::CURE);
|
||||
}
|
||||
|
||||
bool CSpell::isRisingSpell() const
|
||||
{
|
||||
@ -408,33 +425,89 @@ void CSpell::getEffects(std::vector<Bonus>& lst, const int level) const
|
||||
}
|
||||
}
|
||||
|
||||
bool CSpell::isImmuneBy(const IBonusBearer* obj) const
|
||||
ESpellCastProblem::ESpellCastProblem CSpell::isImmuneAt(const CBattleInfoCallback * cb, const CGHeroInstance * caster, ECastingMode::ECastingMode mode, BattleHex destination) const
|
||||
{
|
||||
// Get all stacks at destination hex. only alive if not rising spell
|
||||
TStacks stacks = cb->battleGetStacksIf([=](const CStack * s){
|
||||
return s->coversPos(destination) && (isRisingSpell() || s->alive());
|
||||
});
|
||||
|
||||
if(!stacks.empty())
|
||||
{
|
||||
bool allImmune = true;
|
||||
|
||||
ESpellCastProblem::ESpellCastProblem problem;
|
||||
|
||||
for(auto s : stacks)
|
||||
{
|
||||
ESpellCastProblem::ESpellCastProblem res = isImmuneByStack(caster,s);
|
||||
|
||||
if(res == ESpellCastProblem::OK)
|
||||
{
|
||||
allImmune = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
problem = res;
|
||||
}
|
||||
}
|
||||
|
||||
if(allImmune)
|
||||
return problem;
|
||||
}
|
||||
else //no target stack on this tile
|
||||
{
|
||||
if(getTargetType() == CSpell::CREATURE)
|
||||
{
|
||||
if(caster && mode == ECastingMode::HERO_CASTING) //TODO why???
|
||||
{
|
||||
const CSpell::TargetInfo ti(this, caster->getSpellSchoolLevel(this), mode);
|
||||
|
||||
if(!ti.massive)
|
||||
return ESpellCastProblem::WRONG_SPELL_TARGET;
|
||||
}
|
||||
else
|
||||
{
|
||||
return ESpellCastProblem::WRONG_SPELL_TARGET;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ESpellCastProblem::OK;
|
||||
}
|
||||
|
||||
|
||||
ESpellCastProblem::ESpellCastProblem CSpell::isImmuneBy(const IBonusBearer* obj) const
|
||||
{
|
||||
//todo: use new bonus API
|
||||
//1. Check absolute limiters
|
||||
for(auto b : absoluteLimiters)
|
||||
{
|
||||
if (!obj->hasBonusOfType(b))
|
||||
return true;
|
||||
return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
|
||||
}
|
||||
|
||||
//2. Check absolute immunities
|
||||
for(auto b : absoluteImmunities)
|
||||
{
|
||||
if (obj->hasBonusOfType(b))
|
||||
return true;
|
||||
return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
|
||||
}
|
||||
|
||||
//check receptivity
|
||||
if (isPositive() && obj->hasBonusOfType(Bonus::RECEPTIVE)) //accept all positive spells
|
||||
return ESpellCastProblem::OK;
|
||||
|
||||
//3. Check negation
|
||||
//FIXME: Orb of vulnerability mechanics is not such trivial
|
||||
if(obj->hasBonusOfType(Bonus::NEGATE_ALL_NATURAL_IMMUNITIES)) //Orb of vulnerability
|
||||
return false;
|
||||
return ESpellCastProblem::NOT_DECIDED;
|
||||
|
||||
//4. Check negatable limit
|
||||
for(auto b : limiters)
|
||||
{
|
||||
if (!obj->hasBonusOfType(b))
|
||||
return true;
|
||||
return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
|
||||
}
|
||||
|
||||
|
||||
@ -442,55 +515,56 @@ bool CSpell::isImmuneBy(const IBonusBearer* obj) const
|
||||
for(auto b : immunities)
|
||||
{
|
||||
if (obj->hasBonusOfType(b))
|
||||
return true;
|
||||
return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
|
||||
}
|
||||
|
||||
auto battleTestElementalImmunity = [&,this](Bonus::BonusType element) -> bool
|
||||
//6. Check elemental immunities
|
||||
|
||||
ESpellCastProblem::ESpellCastProblem tmp = ESpellCastProblem::NOT_DECIDED;
|
||||
|
||||
forEachSchool([&](const SpellSchoolInfo & cnf, bool & stop)
|
||||
{
|
||||
auto element = cnf.immunityBonus;
|
||||
|
||||
if(obj->hasBonusOfType(element, 0)) //always resist if immune to all spells altogether
|
||||
return true;
|
||||
{
|
||||
tmp = ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
|
||||
stop = true;
|
||||
}
|
||||
else if(!isPositive()) //negative or indifferent
|
||||
{
|
||||
if((isDamageSpell() && obj->hasBonusOfType(element, 2)) || obj->hasBonusOfType(element, 1))
|
||||
return true;
|
||||
{
|
||||
tmp = ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
|
||||
stop = true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
//6. Check elemental immunities
|
||||
if(fire)
|
||||
{
|
||||
if(battleTestElementalImmunity(Bonus::FIRE_IMMUNITY))
|
||||
return true;
|
||||
}
|
||||
if(water)
|
||||
{
|
||||
if(battleTestElementalImmunity(Bonus::WATER_IMMUNITY))
|
||||
return true;
|
||||
}
|
||||
|
||||
if(earth)
|
||||
{
|
||||
if(battleTestElementalImmunity(Bonus::EARTH_IMMUNITY))
|
||||
return true;
|
||||
}
|
||||
if(air)
|
||||
{
|
||||
if(battleTestElementalImmunity(Bonus::AIR_IMMUNITY))
|
||||
return true;
|
||||
}
|
||||
if(tmp != ESpellCastProblem::NOT_DECIDED)
|
||||
return tmp;
|
||||
|
||||
TBonusListPtr levelImmunities = obj->getBonuses(Selector::type(Bonus::LEVEL_SPELL_IMMUNITY));
|
||||
|
||||
if(obj->hasBonusOfType(Bonus::SPELL_IMMUNITY, id)
|
||||
|| ( levelImmunities->size() > 0 && levelImmunities->totalValue() >= level && level))
|
||||
{
|
||||
return true;
|
||||
return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
|
||||
}
|
||||
|
||||
return false;
|
||||
return ESpellCastProblem::NOT_DECIDED;
|
||||
}
|
||||
|
||||
ESpellCastProblem::ESpellCastProblem CSpell::isImmuneByStack(const CGHeroInstance* caster, const CStack* obj) const
|
||||
{
|
||||
const auto immuneResult = mechanics->isImmuneByStack(caster,obj);
|
||||
|
||||
if (ESpellCastProblem::NOT_DECIDED != immuneResult)
|
||||
return immuneResult;
|
||||
return ESpellCastProblem::OK;
|
||||
}
|
||||
|
||||
|
||||
void CSpell::setIsOffensive(const bool val)
|
||||
{
|
||||
isOffensive = val;
|
||||
@ -512,6 +586,89 @@ void CSpell::setIsRising(const bool val)
|
||||
}
|
||||
}
|
||||
|
||||
void CSpell::setup()
|
||||
{
|
||||
setupMechanics();
|
||||
|
||||
air = school[ESpellSchool::AIR];
|
||||
fire = school[ESpellSchool::FIRE];
|
||||
water = school[ESpellSchool::WATER];
|
||||
earth = school[ESpellSchool::EARTH];
|
||||
}
|
||||
|
||||
|
||||
void CSpell::setupMechanics()
|
||||
{
|
||||
if(nullptr != mechanics)
|
||||
{
|
||||
logGlobal->errorStream() << "Spell " << this->name << " mechanics already set";
|
||||
delete mechanics;
|
||||
}
|
||||
|
||||
mechanics = ISpellMechanics::createMechanics(this);
|
||||
}
|
||||
|
||||
///CSpell::AnimationInfo
|
||||
CSpell::AnimationInfo::AnimationInfo()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
CSpell::AnimationInfo::~AnimationInfo()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
std::string CSpell::AnimationInfo::selectProjectile(const double angle) const
|
||||
{
|
||||
std::string res;
|
||||
double maximum = 0.0;
|
||||
|
||||
for(const auto & info : projectile)
|
||||
{
|
||||
if(info.minimumAngle < angle && info.minimumAngle > maximum)
|
||||
{
|
||||
maximum = info.minimumAngle;
|
||||
res = info.resourceName;
|
||||
}
|
||||
}
|
||||
|
||||
return std::move(res);
|
||||
}
|
||||
|
||||
|
||||
///CSpell::TargetInfo
|
||||
CSpell::TargetInfo::TargetInfo(const CSpell * spell, const int level)
|
||||
{
|
||||
init(spell, level);
|
||||
}
|
||||
|
||||
CSpell::TargetInfo::TargetInfo(const CSpell * spell, const int level, ECastingMode::ECastingMode mode)
|
||||
{
|
||||
init(spell, level);
|
||||
if(mode == ECastingMode::ENCHANTER_CASTING)
|
||||
{
|
||||
smart = true; //FIXME: not sure about that, this makes all spells smart in this mode
|
||||
massive = true;
|
||||
}
|
||||
else if(mode == ECastingMode::SPELL_LIKE_ATTACK)
|
||||
{
|
||||
alwaysHitDirectly = true;
|
||||
}
|
||||
}
|
||||
|
||||
void CSpell::TargetInfo::init(const CSpell * spell, const int level)
|
||||
{
|
||||
auto & levelInfo = spell->getLevelInfo(level);
|
||||
|
||||
type = spell->getTargetType();
|
||||
smart = levelInfo.smartTarget;
|
||||
massive = levelInfo.range == "X";
|
||||
onlyAlive = !spell->isRisingSpell();
|
||||
alwaysHitDirectly = false;
|
||||
clearAffected = levelInfo.clearAffected;
|
||||
clearTarget = levelInfo.clearTarget;
|
||||
}
|
||||
|
||||
|
||||
bool DLL_LINKAGE isInScreenRange(const int3 ¢er, const int3 &pos)
|
||||
@ -523,6 +680,7 @@ bool DLL_LINKAGE isInScreenRange(const int3 ¢er, const int3 &pos)
|
||||
return false;
|
||||
}
|
||||
|
||||
///CSpellHandler
|
||||
CSpellHandler::CSpellHandler()
|
||||
{
|
||||
|
||||
@ -590,17 +748,7 @@ std::vector<JsonNode> CSpellHandler::loadLegacyData(size_t dataSize)
|
||||
for(size_t i = 0; i < GameConstants::SPELL_SCHOOL_LEVELS ; i++)
|
||||
descriptions.push_back(parser.readString());
|
||||
|
||||
std::string attributes = parser.readString();
|
||||
|
||||
std::string targetType = "NO_TARGET";
|
||||
|
||||
if(attributes.find("CREATURE_TARGET_1") != std::string::npos
|
||||
|| attributes.find("CREATURE_TARGET_2") != std::string::npos)
|
||||
targetType = "CREATURE_EXPERT_MASSIVE";
|
||||
else if(attributes.find("CREATURE_TARGET") != std::string::npos)
|
||||
targetType = "CREATURE";
|
||||
else if(attributes.find("OBSTACLE_TARGET") != std::string::npos)
|
||||
targetType = "OBSTACLE";
|
||||
parser.readString(); //ignore attributes. All data present in JSON
|
||||
|
||||
//save parsed level specific data
|
||||
for(size_t i = 0; i < GameConstants::SPELL_SCHOOL_LEVELS; i++)
|
||||
@ -612,16 +760,6 @@ std::vector<JsonNode> CSpellHandler::loadLegacyData(size_t dataSize)
|
||||
level["aiValue"].Float() = AIVals[i];
|
||||
}
|
||||
|
||||
if(targetType == "CREATURE_EXPERT_MASSIVE")
|
||||
{
|
||||
lineNode["targetType"].String() = "CREATURE";
|
||||
getLevel(3)["range"].String() = "X";
|
||||
}
|
||||
else
|
||||
{
|
||||
lineNode["targetType"].String() = targetType;
|
||||
}
|
||||
|
||||
legacyData.push_back(lineNode);
|
||||
|
||||
|
||||
@ -683,10 +821,10 @@ CSpell * CSpellHandler::loadFromJson(const JsonNode& json)
|
||||
|
||||
const auto schoolNames = json["school"];
|
||||
|
||||
spell->air = schoolNames["air"].Bool();
|
||||
spell->earth = schoolNames["earth"].Bool();
|
||||
spell->fire = schoolNames["fire"].Bool();
|
||||
spell->water = schoolNames["water"].Bool();
|
||||
for(const SpellSchoolInfo & info : SpellConfig::SCHOOL)
|
||||
{
|
||||
spell->school[info.id] = schoolNames[info.jsonName].Bool();
|
||||
}
|
||||
|
||||
spell->level = json["level"].Float();
|
||||
spell->power = json["power"].Float();
|
||||
@ -711,18 +849,16 @@ CSpell * CSpellHandler::loadFromJson(const JsonNode& json)
|
||||
spell->targetType = CSpell::CREATURE;
|
||||
else if(targetType == "OBSTACLE")
|
||||
spell->targetType = CSpell::OBSTACLE;
|
||||
|
||||
|
||||
spell->mainEffectAnim = json["anim"].Float();
|
||||
else if(targetType == "LOCATION")
|
||||
spell->targetType = CSpell::LOCATION;
|
||||
else
|
||||
logGlobal->warnStream() << "Spell " << spell->name << ". Target type " << (targetType.empty() ? "empty" : "unknown ("+targetType+")") << ". Assumed NO_TARGET.";
|
||||
|
||||
for(const auto & counteredSpell: json["counters"].Struct())
|
||||
if (counteredSpell.second.Bool())
|
||||
{
|
||||
JsonNode tmp(JsonNode::DATA_STRING);
|
||||
tmp.meta = json.meta;
|
||||
tmp.String() = counteredSpell.first;
|
||||
|
||||
VLC->modh->identifiers.requestIdentifier(tmp,[=](si32 id){
|
||||
VLC->modh->identifiers.requestIdentifier(json.meta, counteredSpell.first, [=](si32 id)
|
||||
{
|
||||
spell->counteredSpells.push_back(SpellID(id));
|
||||
});
|
||||
}
|
||||
@ -805,10 +941,46 @@ CSpell * CSpellHandler::loadFromJson(const JsonNode& json)
|
||||
spell->iconScenarioBonus = graphicsNode["iconScenarioBonus"].String();
|
||||
spell->iconScroll = graphicsNode["iconScroll"].String();
|
||||
|
||||
const JsonNode & animationNode = json["animation"];
|
||||
|
||||
auto loadAnimationQueue = [&](const std::string & jsonName, CSpell::TAnimationQueue & q)
|
||||
{
|
||||
auto queueNode = animationNode[jsonName].Vector();
|
||||
for(const JsonNode & item : queueNode)
|
||||
{
|
||||
CSpell::TAnimation newItem;
|
||||
newItem.verticalPosition = VerticalPosition::TOP;
|
||||
|
||||
if(item.getType() == JsonNode::DATA_STRING)
|
||||
newItem.resourceName = item.String();
|
||||
else if(item.getType() == JsonNode::DATA_STRUCT)
|
||||
{
|
||||
newItem.resourceName = item["defName"].String();
|
||||
|
||||
auto vPosStr = item["verticalPosition"].String();
|
||||
if("bottom" == vPosStr)
|
||||
newItem.verticalPosition = VerticalPosition::BOTTOM;
|
||||
}
|
||||
q.push_back(newItem);
|
||||
}
|
||||
};
|
||||
|
||||
loadAnimationQueue("affect", spell->animationInfo.affect);
|
||||
loadAnimationQueue("cast", spell->animationInfo.cast);
|
||||
loadAnimationQueue("hit", spell->animationInfo.hit);
|
||||
|
||||
const JsonVector & projectile = animationNode["projectile"].Vector();
|
||||
|
||||
for(const JsonNode & item : projectile)
|
||||
{
|
||||
CSpell::ProjectileInfo info;
|
||||
info.resourceName = item["defName"].String();
|
||||
info.minimumAngle = item["minimumAngle"].Float();
|
||||
|
||||
spell->animationInfo.projectile.push_back(info);
|
||||
}
|
||||
|
||||
const JsonNode & soundsNode = json["sounds"];
|
||||
|
||||
spell->castSound = soundsNode["cast"].String();
|
||||
|
||||
|
||||
@ -822,13 +994,14 @@ CSpell * CSpellHandler::loadFromJson(const JsonNode& json)
|
||||
|
||||
CSpell::LevelInfo & levelObject = spell->levels[levelIndex];
|
||||
|
||||
const si32 levelPower = levelNode["power"].Float();
|
||||
const si32 levelPower = levelObject.power = levelNode["power"].Float();
|
||||
|
||||
levelObject.description = levelNode["description"].String();
|
||||
levelObject.cost = levelNode["cost"].Float();
|
||||
levelObject.power = levelPower;
|
||||
levelObject.AIValue = levelNode["aiValue"].Float();
|
||||
levelObject.smartTarget = levelNode["targetModifier"]["smart"].Bool();
|
||||
levelObject.clearTarget = levelNode["targetModifier"]["clearTarget"].Bool();
|
||||
levelObject.clearAffected = levelNode["targetModifier"]["clearAffected"].Bool();
|
||||
levelObject.range = levelNode["range"].String();
|
||||
|
||||
for(const auto & elem : levelNode["effects"].Struct())
|
||||
@ -857,9 +1030,12 @@ void CSpellHandler::afterLoadFinalization()
|
||||
{
|
||||
//FIXME: it is a bad place for this code, should refactor loadFromJson to know object id during loading
|
||||
for(auto spell: objects)
|
||||
{
|
||||
for(auto & level: spell->levels)
|
||||
for(auto & bonus: level.effects)
|
||||
bonus.sid = spell->id;
|
||||
spell->setup();
|
||||
}
|
||||
}
|
||||
|
||||
void CSpellHandler::beforeValidate(JsonNode & object)
|
||||
|
@ -4,6 +4,7 @@
|
||||
#include "../lib/ConstTransitivePtr.h"
|
||||
#include "int3.h"
|
||||
#include "GameConstants.h"
|
||||
#include "BattleHex.h"
|
||||
#include "HeroBonus.h"
|
||||
|
||||
|
||||
@ -17,20 +18,132 @@
|
||||
*
|
||||
*/
|
||||
|
||||
class CSpell;
|
||||
class ISpellMechanics;
|
||||
|
||||
class CLegacyConfigParser;
|
||||
struct BattleHex;
|
||||
|
||||
class CGHeroInstance;
|
||||
class CStack;
|
||||
|
||||
class CBattleInfoCallback;
|
||||
class BattleInfo;
|
||||
|
||||
struct CPackForClient;
|
||||
struct BattleSpellCast;
|
||||
|
||||
class CRandomGenerator;
|
||||
|
||||
struct SpellSchoolInfo
|
||||
{
|
||||
ESpellSchool id; //backlink
|
||||
Bonus::BonusType damagePremyBonus;
|
||||
Bonus::BonusType immunityBonus;
|
||||
std::string jsonName;
|
||||
SecondarySkill::ESecondarySkill skill;
|
||||
Bonus::BonusType knoledgeBonus;
|
||||
};
|
||||
|
||||
///callback to be provided by server
|
||||
class DLL_LINKAGE SpellCastEnvironment
|
||||
{
|
||||
public:
|
||||
virtual ~SpellCastEnvironment(){};
|
||||
virtual void sendAndApply(CPackForClient * info) const = 0;
|
||||
|
||||
virtual CRandomGenerator & getRandomGenerator() const = 0;
|
||||
virtual void complain(const std::string & problem) const = 0;
|
||||
};
|
||||
|
||||
///helper struct
|
||||
struct DLL_LINKAGE BattleSpellCastParameters
|
||||
{
|
||||
public:
|
||||
BattleSpellCastParameters(const BattleInfo * cb);
|
||||
int spellLvl;
|
||||
BattleHex destination;
|
||||
ui8 casterSide;
|
||||
PlayerColor casterColor;
|
||||
const CGHeroInstance * caster;
|
||||
const CGHeroInstance * secHero;
|
||||
int usedSpellPower;
|
||||
ECastingMode::ECastingMode mode;
|
||||
const CStack * casterStack;
|
||||
const CStack * selectedStack;
|
||||
const BattleInfo * cb;
|
||||
};
|
||||
|
||||
enum class VerticalPosition : ui8{TOP, CENTER, BOTTOM};
|
||||
|
||||
class DLL_LINKAGE CSpell
|
||||
{
|
||||
public:
|
||||
|
||||
struct ProjectileInfo
|
||||
{
|
||||
///in radians. Only positive value. Negative angle is handled by vertical flip
|
||||
double minimumAngle;
|
||||
|
||||
///resource name
|
||||
std::string resourceName;
|
||||
|
||||
template <typename Handler> void serialize(Handler &h, const int version)
|
||||
{
|
||||
h & minimumAngle & resourceName;
|
||||
}
|
||||
};
|
||||
|
||||
struct AnimationItem
|
||||
{
|
||||
std::string resourceName;
|
||||
VerticalPosition verticalPosition;
|
||||
|
||||
template <typename Handler> void serialize(Handler &h, const int version)
|
||||
{
|
||||
h & resourceName & verticalPosition;
|
||||
}
|
||||
};
|
||||
|
||||
typedef AnimationItem TAnimation;
|
||||
typedef std::vector<TAnimation> TAnimationQueue;
|
||||
|
||||
struct DLL_LINKAGE AnimationInfo
|
||||
{
|
||||
AnimationInfo();
|
||||
~AnimationInfo();
|
||||
|
||||
///displayed on all affected targets.
|
||||
TAnimationQueue affect;
|
||||
|
||||
///displayed on caster.
|
||||
TAnimationQueue cast;
|
||||
|
||||
///displayed on target hex. If spell was casted with no target selection displayed on entire battlefield (f.e. ARMAGEDDON)
|
||||
TAnimationQueue hit;
|
||||
|
||||
///displayed "between" caster and (first) target. Ignored if spell was casted with no target selection.
|
||||
///use selectProjectile to access
|
||||
std::vector<ProjectileInfo> projectile;
|
||||
|
||||
template <typename Handler> void serialize(Handler &h, const int version)
|
||||
{
|
||||
h & projectile & hit & cast;
|
||||
}
|
||||
|
||||
std::string selectProjectile(const double angle) const;
|
||||
} animationInfo;
|
||||
|
||||
public:
|
||||
struct LevelInfo
|
||||
{
|
||||
std::string description; //descriptions of spell for skill level
|
||||
si32 cost; //per skill level: 0 - none, 1 - basic, etc
|
||||
si32 power; //per skill level: 0 - none, 1 - basic, etc
|
||||
si32 AIValue; //AI values: per skill level: 0 - none, 1 - basic, etc
|
||||
si32 cost;
|
||||
si32 power;
|
||||
si32 AIValue;
|
||||
|
||||
bool smartTarget;
|
||||
bool clearTarget;
|
||||
bool clearAffected;
|
||||
std::string range;
|
||||
|
||||
std::vector<Bonus> effects;
|
||||
@ -41,6 +154,7 @@ public:
|
||||
template <typename Handler> void serialize(Handler &h, const int version)
|
||||
{
|
||||
h & description & cost & power & AIValue & smartTarget & range & effects;
|
||||
h & clearTarget & clearAffected;
|
||||
}
|
||||
};
|
||||
|
||||
@ -52,7 +166,7 @@ public:
|
||||
*/
|
||||
const CSpell::LevelInfo& getLevelInfo(const int level) const;
|
||||
public:
|
||||
enum ETargetType {NO_TARGET, CREATURE, OBSTACLE};
|
||||
enum ETargetType {NO_TARGET, CREATURE, OBSTACLE, LOCATION};
|
||||
enum ESpellPositiveness {NEGATIVE = -1, NEUTRAL = 0, POSITIVE = 1};
|
||||
|
||||
struct TargetInfo
|
||||
@ -61,6 +175,17 @@ public:
|
||||
bool smart;
|
||||
bool massive;
|
||||
bool onlyAlive;
|
||||
///no immunity on primary target (mostly spell-like attack)
|
||||
bool alwaysHitDirectly;
|
||||
|
||||
bool clearTarget;
|
||||
bool clearAffected;
|
||||
|
||||
TargetInfo(const CSpell * spell, const int level);
|
||||
TargetInfo(const CSpell * spell, const int level, ECastingMode::ECastingMode mode);
|
||||
|
||||
private:
|
||||
void init(const CSpell * spell, const int level);
|
||||
};
|
||||
|
||||
SpellID id;
|
||||
@ -68,10 +193,13 @@ public:
|
||||
std::string name;
|
||||
|
||||
si32 level;
|
||||
bool earth;
|
||||
bool water;
|
||||
bool fire;
|
||||
bool air;
|
||||
bool earth; //deprecated
|
||||
bool water; //deprecated
|
||||
bool fire; //deprecated
|
||||
bool air; //deprecated
|
||||
|
||||
std::map<ESpellSchool, bool> school; //todo: use this instead of separate boolean fields
|
||||
|
||||
si32 power; //spell's power
|
||||
|
||||
std::map<TFaction, si32> probabilities; //% chance to gain for castles
|
||||
@ -85,11 +213,14 @@ public:
|
||||
CSpell();
|
||||
~CSpell();
|
||||
|
||||
bool isCastableBy(const IBonusBearer * caster, bool hasSpellBook, const std::set<SpellID> & spellBook) const;
|
||||
|
||||
|
||||
std::vector<BattleHex> rangeInHexes(BattleHex centralHex, ui8 schoolLvl, ui8 side, bool *outDroppedHexes = nullptr ) const; //convert range to specific hexes; last optional out parameter is set to true, if spell would cover unavailable hexes (that are not included in ret)
|
||||
si16 mainEffectAnim; //main spell effect animation, in AC format (or -1 when none)
|
||||
ETargetType getTargetType() const; //deprecated
|
||||
|
||||
const CSpell::TargetInfo getTargetInfo(const int level) const;
|
||||
CSpell::TargetInfo getTargetInfo(const int level) const;
|
||||
|
||||
|
||||
bool isCombatSpell() const;
|
||||
bool isAdventureSpell() const;
|
||||
@ -99,8 +230,9 @@ public:
|
||||
bool isNegative() const;
|
||||
bool isNeutral() const;
|
||||
|
||||
bool isRisingSpell() const;
|
||||
bool isDamageSpell() const;
|
||||
bool isHealingSpell() const;
|
||||
bool isRisingSpell() const;
|
||||
bool isOffensiveSpell() const;
|
||||
|
||||
bool isSpecialSpell() const;
|
||||
@ -108,7 +240,26 @@ public:
|
||||
bool hasEffects() const;
|
||||
void getEffects(std::vector<Bonus> &lst, const int level) const;
|
||||
|
||||
bool isImmuneBy(const IBonusBearer *obj) 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 CGHeroInstance * caster, ECastingMode::ECastingMode mode, BattleHex destination) const;
|
||||
|
||||
//internal, for use only by Mechanics classes
|
||||
ESpellCastProblem::ESpellCastProblem isImmuneBy(const IBonusBearer *obj) 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 CGHeroInstance * caster, const CStack * obj) const;
|
||||
|
||||
//internal, for use only by Mechanics classes. applying secondary skills
|
||||
ui32 calculateBonus(ui32 baseDamage, const CGHeroInstance * caster, const CStack * affectedCreature) const;
|
||||
|
||||
///calculate spell damage on stack taking caster`s secondary skills and affectedCreature`s bonuses into account
|
||||
ui32 calculateDamage(const CGHeroInstance * caster, const CStack * affectedCreature, int spellSchoolLevel, int usedSpellPower) const;
|
||||
|
||||
///calculate healed HP for all spells casted by hero
|
||||
ui32 calculateHealedHP(const CGHeroInstance * caster, const CStack * stack, const CStack * sacrificedStack = nullptr) 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 CGHeroInstance * caster = nullptr) const;
|
||||
|
||||
si32 getCost(const int skillLevel) const;
|
||||
|
||||
@ -125,6 +276,13 @@ public:
|
||||
|
||||
si32 getProbability(const TFaction factionId) const;
|
||||
|
||||
/**
|
||||
* Calls cb for each school this spell belongs to
|
||||
*
|
||||
* Set stop to true to abort looping
|
||||
*/
|
||||
void forEachSchool(const std::function<void (const SpellSchoolInfo &, bool &)> & cb) const;
|
||||
|
||||
/**
|
||||
* Returns resource name of icon for SPELL_IMMUNITY bonus
|
||||
*/
|
||||
@ -134,28 +292,45 @@ public:
|
||||
|
||||
template <typename Handler> void serialize(Handler &h, const int version)
|
||||
{
|
||||
h & identifier & id & name & level & earth & water & fire & air & power
|
||||
& probabilities & attributes & combatSpell & creatureAbility & positiveness & counteredSpells & mainEffectAnim;
|
||||
h & identifier & id & name & level & power
|
||||
& probabilities & attributes & combatSpell & creatureAbility & positiveness & counteredSpells;
|
||||
h & isRising & isDamage & isOffensive;
|
||||
h & targetType;
|
||||
h & immunities & limiters & absoluteImmunities & absoluteLimiters;
|
||||
h & iconImmune;
|
||||
h & defaultProbability;
|
||||
|
||||
h & isSpecial;
|
||||
|
||||
h & castSound & iconBook & iconEffect & iconScenarioBonus & iconScroll;
|
||||
|
||||
h & levels;
|
||||
h & school;
|
||||
h & animationInfo;
|
||||
|
||||
if(!h.saving)
|
||||
setup();
|
||||
}
|
||||
friend class CSpellHandler;
|
||||
friend class Graphics;
|
||||
public:
|
||||
///Server logic. Has write access to GameState via packets.
|
||||
///May be executed on client side by (future) non-cheat-proof scripts.
|
||||
|
||||
//void adventureCast() const;
|
||||
void battleCast(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters) const;
|
||||
|
||||
public:
|
||||
///Client-server logic. Has direct write access to GameState.
|
||||
///Shall be called (only) when applying packets on BOTH SIDES
|
||||
|
||||
///implementation of BattleSpellCast applying
|
||||
void afterCast(BattleInfo * battle, const BattleSpellCast * packet) const;
|
||||
|
||||
private:
|
||||
void setIsOffensive(const bool val);
|
||||
void setIsRising(const bool val);
|
||||
|
||||
//call this after load or deserialization. cant be done in constructor.
|
||||
void setup();
|
||||
void setupMechanics();
|
||||
private:
|
||||
si32 defaultProbability;
|
||||
|
||||
@ -186,10 +361,10 @@ private:
|
||||
std::string castSound;
|
||||
|
||||
std::vector<LevelInfo> levels;
|
||||
|
||||
ISpellMechanics * mechanics;//(!) do not serialize
|
||||
};
|
||||
|
||||
|
||||
|
||||
bool DLL_LINKAGE isInScreenRange(const int3 ¢er, const int3 &pos); //for spells like Dimension Door
|
||||
|
||||
class DLL_LINKAGE CSpellHandler: public CHandlerBase<SpellID, CSpell>
|
||||
@ -216,6 +391,7 @@ public:
|
||||
{
|
||||
h & objects ;
|
||||
}
|
||||
|
||||
protected:
|
||||
CSpell * loadFromJson(const JsonNode & json) override;
|
||||
};
|
||||
|
@ -87,6 +87,11 @@ CCreature * CreatureID::toCreature() const
|
||||
|
||||
CSpell * SpellID::toSpell() const
|
||||
{
|
||||
if(num < 0 || num >= VLC->spellh->objects.size())
|
||||
{
|
||||
logGlobal->errorStream() << "Unable to get spell of invalid ID " << int(num);
|
||||
return nullptr;
|
||||
}
|
||||
return VLC->spellh->objects[*this];
|
||||
}
|
||||
|
||||
|
@ -395,14 +395,19 @@ namespace ESpellCastProblem
|
||||
SECOND_HEROS_SPELL_IMMUNITY, SPELL_LEVEL_LIMIT_EXCEEDED, NO_SPELLS_TO_DISPEL,
|
||||
NO_APPROPRIATE_TARGET, STACK_IMMUNE_TO_SPELL, WRONG_SPELL_TARGET, ONGOING_TACTIC_PHASE,
|
||||
MAGIC_IS_BLOCKED, //For Orb of Inhibition and similar - no casting at all
|
||||
NOT_DECIDED,
|
||||
INVALID
|
||||
};
|
||||
}
|
||||
|
||||
namespace ECastingMode
|
||||
{
|
||||
enum ECastingMode {HERO_CASTING, AFTER_ATTACK_CASTING, //also includes cast before attack
|
||||
MAGIC_MIRROR, CREATURE_ACTIVE_CASTING, ENCHANTER_CASTING};
|
||||
enum ECastingMode
|
||||
{
|
||||
HERO_CASTING, AFTER_ATTACK_CASTING, //also includes cast before attack
|
||||
MAGIC_MIRROR, CREATURE_ACTIVE_CASTING, ENCHANTER_CASTING,
|
||||
SPELL_LIKE_ATTACK
|
||||
};
|
||||
}
|
||||
|
||||
namespace EMarketMode
|
||||
@ -890,6 +895,14 @@ public:
|
||||
ESpellID num;
|
||||
};
|
||||
|
||||
enum class ESpellSchool: ui8
|
||||
{
|
||||
AIR = 0,
|
||||
FIRE = 1,
|
||||
WATER = 2,
|
||||
EARTH = 3
|
||||
};
|
||||
|
||||
ID_LIKE_OPERATORS_DECLS(SpellID, SpellID::ESpellID)
|
||||
|
||||
// Typedef declarations
|
||||
|
@ -274,17 +274,18 @@ struct ChangeSpells : public CPackForClient //109
|
||||
|
||||
struct SetMana : public CPackForClient //110
|
||||
{
|
||||
SetMana(){type = 110;};
|
||||
SetMana(){type = 110;absolute=true;};
|
||||
void applyCl(CClient *cl);
|
||||
DLL_LINKAGE void applyGs(CGameState *gs);
|
||||
|
||||
|
||||
ObjectInstanceID hid;
|
||||
si32 val;
|
||||
bool absolute;
|
||||
|
||||
template <typename Handler> void serialize(Handler &h, const int version)
|
||||
{
|
||||
h & val & hid;
|
||||
h & val & hid & absolute;
|
||||
}
|
||||
};
|
||||
|
||||
@ -1319,16 +1320,18 @@ struct StacksHealedOrResurrected : public CPackForClient //3013
|
||||
|
||||
struct BattleStackAttacked : public CPackForClient//3005
|
||||
{
|
||||
BattleStackAttacked(){flags = 0; type = 3005;};
|
||||
BattleStackAttacked():
|
||||
flags(0), spellID(SpellID::NONE){type=3005;};
|
||||
void applyFirstCl(CClient * cl);
|
||||
//void applyCl(CClient *cl);
|
||||
DLL_LINKAGE void applyGs(CGameState *gs);
|
||||
|
||||
ui32 stackAttacked, attackerID;
|
||||
ui32 newAmount, newHP, killedAmount, damageAmount;
|
||||
enum EFlags {KILLED = 1, EFFECT = 2, SECONDARY = 4, REBIRTH = 8, CLONE_KILLED = 16};
|
||||
ui8 flags; //uses EFlags (above)
|
||||
enum EFlags {KILLED = 1, EFFECT = 2/*deprecated */, SECONDARY = 4, REBIRTH = 8, CLONE_KILLED = 16, SPELL_EFFECT = 32 /*, BONUS_EFFECT = 64 */};
|
||||
ui32 flags; //uses EFlags (above)
|
||||
ui32 effect; //set only if flag EFFECT is set
|
||||
SpellID spellID; //only if flag SPELL_EFFECT is set
|
||||
std::vector<StacksHealedOrResurrected> healedStacks; //used when life drain
|
||||
|
||||
|
||||
@ -1348,6 +1351,11 @@ struct BattleStackAttacked : public CPackForClient//3005
|
||||
{
|
||||
return flags & SECONDARY;
|
||||
}
|
||||
///Attacked with spell (SPELL_LIKE_ATTACK)
|
||||
bool isSpell() const
|
||||
{
|
||||
return flags & SPELL_EFFECT;
|
||||
}
|
||||
bool willRebirth() const//resurrection, e.g. Phoenix
|
||||
{
|
||||
return flags & REBIRTH;
|
||||
@ -1360,6 +1368,7 @@ struct BattleStackAttacked : public CPackForClient//3005
|
||||
{
|
||||
h & stackAttacked & attackerID & newAmount & newHP & flags & killedAmount & damageAmount & effect
|
||||
& healedStacks;
|
||||
h & spellID;
|
||||
}
|
||||
bool operator<(const BattleStackAttacked &b) const
|
||||
{
|
||||
@ -1369,15 +1378,17 @@ struct BattleStackAttacked : public CPackForClient//3005
|
||||
|
||||
struct BattleAttack : public CPackForClient//3006
|
||||
{
|
||||
BattleAttack(){flags = 0; type = 3006;};
|
||||
BattleAttack(): flags(0), spellID(SpellID::NONE){type = 3006;};
|
||||
void applyFirstCl(CClient *cl);
|
||||
DLL_LINKAGE void applyGs(CGameState *gs);
|
||||
void applyCl(CClient *cl);
|
||||
|
||||
std::vector<BattleStackAttacked> bsa;
|
||||
ui32 stackAttacking;
|
||||
ui8 flags; //uses Eflags (below)
|
||||
enum EFlags{SHOT = 1, COUNTER = 2, LUCKY = 4, UNLUCKY = 8, BALLISTA_DOUBLE_DMG = 16, DEATH_BLOW = 32};
|
||||
ui32 flags; //uses Eflags (below)
|
||||
enum EFlags{SHOT = 1, COUNTER = 2, LUCKY = 4, UNLUCKY = 8, BALLISTA_DOUBLE_DMG = 16, DEATH_BLOW = 32, SPELL_LIKE = 64};
|
||||
|
||||
SpellID spellID; //for SPELL_LIKE
|
||||
|
||||
bool shot() const//distance attack - decrease number of shots
|
||||
{
|
||||
@ -1403,13 +1414,17 @@ struct BattleAttack : public CPackForClient//3006
|
||||
{
|
||||
return flags & DEATH_BLOW;
|
||||
}
|
||||
bool spellLike() const
|
||||
{
|
||||
return flags & SPELL_LIKE;
|
||||
}
|
||||
//bool killed() //if target stack was killed
|
||||
//{
|
||||
// return bsa.killed();
|
||||
//}
|
||||
template <typename Handler> void serialize(Handler &h, const int version)
|
||||
{
|
||||
h & bsa & stackAttacking & flags;
|
||||
h & bsa & stackAttacking & flags & spellID;
|
||||
}
|
||||
};
|
||||
|
||||
@ -1439,7 +1454,7 @@ struct EndAction : public CPackForClient//3008
|
||||
|
||||
struct BattleSpellCast : public CPackForClient//3009
|
||||
{
|
||||
BattleSpellCast(){type = 3009;};
|
||||
BattleSpellCast(){type = 3009; casterStack = -1;};
|
||||
DLL_LINKAGE void applyGs(CGameState *gs);
|
||||
void applyCl(CClient *cl);
|
||||
|
||||
@ -1447,16 +1462,15 @@ struct BattleSpellCast : public CPackForClient//3009
|
||||
ui8 side; //which hero did cast spell: 0 - attacker, 1 - defender
|
||||
ui32 id; //id of spell
|
||||
ui8 skill; //caster's skill level
|
||||
ui8 spellCost;
|
||||
ui8 manaGained; //mana channeling ability
|
||||
BattleHex tile; //destination tile (may not be set in some global/mass spells
|
||||
std::vector<ui32> resisted; //ids of creatures that resisted this spell
|
||||
std::set<ui32> affectedCres; //ids of creatures affected by this spell, generally used if spell does not set any effect (like dispel or cure)
|
||||
CreatureID attackerType;//id of caster to generate console message; -1 if not set (eg. spell casted by artifact)
|
||||
si32 casterStack;// -1 if not cated by creature, >=0 caster stack ID
|
||||
bool castedByHero; //if true - spell has been casted by hero, otherwise by a creature
|
||||
template <typename Handler> void serialize(Handler &h, const int version)
|
||||
{
|
||||
h & dmgToDisplay & side & id & skill & spellCost & manaGained & tile & resisted & affectedCres & attackerType & castedByHero;
|
||||
h & dmgToDisplay & side & id & skill & manaGained & tile & resisted & affectedCres & casterStack & castedByHero;
|
||||
}
|
||||
};
|
||||
|
||||
@ -1579,6 +1593,9 @@ struct BattleStackAdded : public CPackForClient //3017
|
||||
int pos;
|
||||
int summoned; //if true, remove it afterwards
|
||||
|
||||
///Actual stack ID, set on apply, do not serialize
|
||||
int newStackID;
|
||||
|
||||
template <typename Handler> void serialize(Handler &h, const int version)
|
||||
{
|
||||
h & attacker & creID & amount & pos & summoned;
|
||||
|
@ -184,9 +184,16 @@ DLL_LINKAGE void ChangeSpells::applyGs( CGameState *gs )
|
||||
|
||||
DLL_LINKAGE void SetMana::applyGs( CGameState *gs )
|
||||
{
|
||||
CGHeroInstance *hero = gs->getHero(hid);
|
||||
vstd::amax(val, 0); //not less than 0
|
||||
CGHeroInstance * hero = gs->getHero(hid);
|
||||
|
||||
assert(hero);
|
||||
|
||||
if(absolute)
|
||||
hero->mana = val;
|
||||
else
|
||||
hero->mana += val;
|
||||
|
||||
vstd::amax(hero->mana, 0); //not less than 0
|
||||
}
|
||||
|
||||
DLL_LINKAGE void SetMovePoints::applyGs( CGameState *gs )
|
||||
@ -1329,44 +1336,10 @@ DLL_LINKAGE void StartAction::applyGs( CGameState *gs )
|
||||
DLL_LINKAGE void BattleSpellCast::applyGs( CGameState *gs )
|
||||
{
|
||||
assert(gs->curB);
|
||||
if (castedByHero)
|
||||
{
|
||||
CGHeroInstance * h = gs->curB->battleGetFightingHero(side);
|
||||
CGHeroInstance * enemy = gs->curB->battleGetFightingHero(!side);
|
||||
|
||||
h->mana -= spellCost;
|
||||
vstd::amax(h->mana, 0);
|
||||
if (enemy && manaGained)
|
||||
enemy->mana += manaGained;
|
||||
if (side < 2)
|
||||
{
|
||||
gs->curB->sides[side].castSpellsCount++;
|
||||
}
|
||||
}
|
||||
const CSpell * spell = SpellID(id).toSpell();
|
||||
|
||||
//Handle spells removing effects from stacks
|
||||
const CSpell *spell = SpellID(id).toSpell();
|
||||
const bool removeAllSpells = id == SpellID::DISPEL;
|
||||
const bool removeHelpful = id == SpellID::DISPEL_HELPFUL_SPELLS;
|
||||
|
||||
for(auto stackID : affectedCres)
|
||||
{
|
||||
if(vstd::contains(resisted, stackID))
|
||||
continue;
|
||||
|
||||
CStack *s = gs->curB->getStack(stackID);
|
||||
s->popBonuses([&](const Bonus *b) -> bool
|
||||
{
|
||||
//check for each bonus if it should be removed
|
||||
const bool isSpellEffect = Selector::sourceType(Bonus::SPELL_EFFECT)(b);
|
||||
const bool isPositiveSpell = Selector::positiveSpellEffects(b);
|
||||
const int spellID = isSpellEffect ? b->sid : -1;
|
||||
|
||||
return (removeHelpful && isPositiveSpell)
|
||||
|| (removeAllSpells && isSpellEffect)
|
||||
|| vstd::contains(spell->counteredSpells, spellID);
|
||||
});
|
||||
}
|
||||
spell->afterCast(gs->curB, this);
|
||||
}
|
||||
|
||||
void actualizeEffect(CStack * s, const std::vector<Bonus> & ef)
|
||||
@ -1591,6 +1564,7 @@ DLL_LINKAGE void BattleStacksRemoved::applyGs( CGameState *gs )
|
||||
|
||||
DLL_LINKAGE void BattleStackAdded::applyGs(CGameState *gs)
|
||||
{
|
||||
newStackID = 0;
|
||||
if (!BattleHex(pos).isValid())
|
||||
{
|
||||
logNetwork->warnStream() << "No place found for new stack!";
|
||||
@ -1604,6 +1578,8 @@ DLL_LINKAGE void BattleStackAdded::applyGs(CGameState *gs)
|
||||
|
||||
gs->curB->localInitStack(addedStack);
|
||||
gs->curB->stacks.push_back(addedStack); //the stack is not "SUMMONED", it is permanent
|
||||
|
||||
newStackID = addedStack->ID;
|
||||
}
|
||||
|
||||
DLL_LINKAGE void BattleSetStackProperty::applyGs(CGameState *gs)
|
||||
|
1335
lib/SpellMechanics.cpp
Normal file
1335
lib/SpellMechanics.cpp
Normal file
File diff suppressed because it is too large
Load Diff
54
lib/SpellMechanics.h
Normal file
54
lib/SpellMechanics.h
Normal file
@ -0,0 +1,54 @@
|
||||
/*
|
||||
* SpellMechanics.h, part of VCMI engine
|
||||
*
|
||||
* Authors: listed in file AUTHORS in main folder
|
||||
*
|
||||
* License: GNU General Public License v2.0 or later
|
||||
* Full text of license available in license.txt file, in main folder
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CSpellHandler.h"
|
||||
#include "BattleHex.h"
|
||||
|
||||
class DLL_LINKAGE ISpellMechanics
|
||||
{
|
||||
public:
|
||||
|
||||
struct DLL_LINKAGE SpellTargetingContext
|
||||
{
|
||||
const CBattleInfoCallback * cb;
|
||||
CSpell::TargetInfo ti;
|
||||
ECastingMode::ECastingMode mode;
|
||||
BattleHex destination;
|
||||
PlayerColor casterColor;
|
||||
int schoolLvl;
|
||||
|
||||
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 isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const = 0;
|
||||
|
||||
//virtual bool adventureCast(const SpellCastContext & context) const = 0;
|
||||
virtual void battleCast(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters) const = 0;
|
||||
|
||||
static ISpellMechanics * createMechanics(CSpell * s);
|
||||
|
||||
virtual void afterCast(BattleInfo * battle, const BattleSpellCast * packet) const = 0;
|
||||
|
||||
protected:
|
||||
CSpell * owner;
|
||||
};
|
||||
|
@ -185,6 +185,8 @@
|
||||
<Unit filename="ResourceSet.cpp" />
|
||||
<Unit filename="ResourceSet.h" />
|
||||
<Unit filename="ScopeGuard.h" />
|
||||
<Unit filename="SpellMechanics.cpp" />
|
||||
<Unit filename="SpellMechanics.h" />
|
||||
<Unit filename="StartInfo.h" />
|
||||
<Unit filename="StdInc.h">
|
||||
<Option weight="0" />
|
||||
|
@ -185,6 +185,7 @@
|
||||
<ClCompile Include="CObstacleInstance.cpp" />
|
||||
<ClCompile Include="Connection.cpp" />
|
||||
<ClCompile Include="CSpellHandler.cpp" />
|
||||
<ClCompile Include="SpellMechanics.cpp" />
|
||||
<ClCompile Include="CThreadHelper.cpp" />
|
||||
<ClCompile Include="CTownHandler.cpp" />
|
||||
<ClCompile Include="CRandomGenerator.cpp" />
|
||||
|
@ -864,26 +864,16 @@ ui8 CGHeroInstance::getSpellSchoolLevel(const CSpell * spell, int *outSelectedSc
|
||||
{
|
||||
si16 skill = -1; //skill level
|
||||
|
||||
#define TRY_SCHOOL(schoolName, schoolMechanicsId, schoolOutId) \
|
||||
if(spell-> schoolName) \
|
||||
{ \
|
||||
int thisSchool = std::max<int>(getSecSkillLevel( \
|
||||
SecondarySkill(14 + (schoolMechanicsId))), \
|
||||
valOfBonuses(Bonus::MAGIC_SCHOOL_SKILL, 1 << (schoolMechanicsId))); \
|
||||
if(thisSchool > skill) \
|
||||
{ \
|
||||
skill = thisSchool; \
|
||||
if(outSelectedSchool) \
|
||||
*outSelectedSchool = schoolOutId; \
|
||||
} \
|
||||
spell->forEachSchool([&, this](const SpellSchoolInfo & cnf, bool & stop)
|
||||
{
|
||||
int thisSchool = std::max<int>(getSecSkillLevel(cnf.skill), valOfBonuses(Bonus::MAGIC_SCHOOL_SKILL, 1 << ((ui8)cnf.id)));
|
||||
if(thisSchool > skill)
|
||||
{
|
||||
skill = thisSchool;
|
||||
if(outSelectedSchool)
|
||||
*outSelectedSchool = (ui8)cnf.id;
|
||||
}
|
||||
TRY_SCHOOL(fire, 0, 1)
|
||||
TRY_SCHOOL(air, 1, 0)
|
||||
TRY_SCHOOL(water, 2, 2)
|
||||
TRY_SCHOOL(earth, 3, 3)
|
||||
#undef TRY_SCHOOL
|
||||
|
||||
|
||||
});
|
||||
|
||||
vstd::amax(skill, valOfBonuses(Bonus::MAGIC_SCHOOL_SKILL, 0)); //any school bonus
|
||||
vstd::amax(skill, valOfBonuses(Bonus::SPELL, spell->id.toEnum())); //given by artifact or other effect
|
||||
@ -895,35 +885,7 @@ ui8 CGHeroInstance::getSpellSchoolLevel(const CSpell * spell, int *outSelectedSc
|
||||
|
||||
bool CGHeroInstance::canCastThisSpell(const CSpell * spell) const
|
||||
{
|
||||
if(!getArt(ArtifactPosition::SPELLBOOK)) //if hero has no spellbook
|
||||
return false;
|
||||
|
||||
if (spell->isSpecialSpell())
|
||||
{
|
||||
if (vstd::contains(spells, spell->id))
|
||||
{//hero has this spell in spellbook
|
||||
logGlobal->errorStream() << "Special spell in spellbook "<<spell->name;
|
||||
}
|
||||
|
||||
if (hasBonusOfType(Bonus::SPELL, spell->id))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(vstd::contains(spells, spell->id) //hero has this spell in spellbook
|
||||
|| (spell->air && hasBonusOfType(Bonus::AIR_SPELLS)) // this is air spell and hero can cast all air spells
|
||||
|| (spell->fire && hasBonusOfType(Bonus::FIRE_SPELLS)) // this is fire spell and hero can cast all fire spells
|
||||
|| (spell->water && hasBonusOfType(Bonus::WATER_SPELLS)) // this is water spell and hero can cast all water spells
|
||||
|| (spell->earth && hasBonusOfType(Bonus::EARTH_SPELLS)) // this is earth spell and hero can cast all earth spells
|
||||
|| hasBonusOfType(Bonus::SPELL, spell->id)
|
||||
|| hasBonusOfType(Bonus::SPELLS_OF_LEVEL, spell->level)
|
||||
)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
return spell->isCastableBy(this, nullptr !=getArt(ArtifactPosition::SPELLBOOK), spells);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -59,6 +59,18 @@ extern bool end2;
|
||||
bnr.round = gs->curB->round + 1;\
|
||||
sendAndApply(&bnr);
|
||||
|
||||
class ServerSpellCastEnvironment: public SpellCastEnvironment
|
||||
{
|
||||
public:
|
||||
ServerSpellCastEnvironment(CGameHandler * gh);
|
||||
~ServerSpellCastEnvironment(){};
|
||||
void sendAndApply(CPackForClient * info) const override;
|
||||
CRandomGenerator & getRandomGenerator() const override;
|
||||
void complain(const std::string & problem) const override;
|
||||
private:
|
||||
CGameHandler * gh;
|
||||
};
|
||||
|
||||
CondSh<bool> battleMadeAction;
|
||||
CondSh<BattleResult *> battleResult(nullptr);
|
||||
template <typename T> class CApplyOnGH;
|
||||
@ -782,12 +794,16 @@ void CGameHandler::prepareAttack(BattleAttack &bat, const CStack *att, const CSt
|
||||
}
|
||||
|
||||
const Bonus * bonus = att->getBonusLocalFirst(Selector::type(Bonus::SPELL_LIKE_ATTACK));
|
||||
if (bonus && (bat.shot())) //TODO: make it work in meele?
|
||||
if (bonus && (bat.shot())) //TODO: make it work in melee?
|
||||
{
|
||||
bat.bsa.front().flags |= BattleStackAttacked::EFFECT;
|
||||
bat.bsa.front().effect = VLC->spellh->objects.at(bonus->subtype)->mainEffectAnim; //hopefully it does not interfere with any other effect?
|
||||
//this is need for displaying hit animation
|
||||
bat.flags |= BattleAttack::SPELL_LIKE;
|
||||
bat.spellID = SpellID(bonus->subtype);
|
||||
|
||||
//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);
|
||||
|
||||
std::set<const CStack*> attackedCreatures = gs->curB->getAffectedCreatures(SpellID(bonus->subtype).toSpell(), bonus->val, att->owner, targetHex);
|
||||
//TODO: get exact attacked hex for defender
|
||||
|
||||
for(const CStack * stack : attackedCreatures)
|
||||
@ -797,6 +813,18 @@ void CGameHandler::prepareAttack(BattleAttack &bat, const CStack *att, const CSt
|
||||
applyBattleEffects(bat, att, stack, distance, true);
|
||||
}
|
||||
}
|
||||
|
||||
//now add effect info for all attacked stacks
|
||||
for(BattleStackAttacked & bsa : bat.bsa)
|
||||
{
|
||||
if(bsa.attackerID == att->ID) //this is our attack and not f.e. fire shield
|
||||
{
|
||||
//this is need for displaying affect animation
|
||||
bsa.flags |= BattleStackAttacked::SPELL_EFFECT;
|
||||
bsa.spellID = SpellID(bonus->subtype);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
void CGameHandler::applyBattleEffects(BattleAttack &bat, const CStack *att, const CStack *def, int distance, bool secondary) //helper function for prepareAttack
|
||||
@ -1051,10 +1079,13 @@ CGameHandler::CGameHandler(void)
|
||||
registerTypesServerPacks(*applier);
|
||||
visitObjectAfterVictory = false;
|
||||
queries.gh = this;
|
||||
|
||||
spellEnv = new ServerSpellCastEnvironment(this);
|
||||
}
|
||||
|
||||
CGameHandler::~CGameHandler(void)
|
||||
{
|
||||
delete spellEnv;
|
||||
delete applier;
|
||||
applier = nullptr;
|
||||
delete gs;
|
||||
@ -2029,6 +2060,7 @@ void CGameHandler::setManaPoints( ObjectInstanceID hid, int val )
|
||||
SetMana sm;
|
||||
sm.hid = hid;
|
||||
sm.val = val;
|
||||
sm.absolute = true;
|
||||
sendAndApply(&sm);
|
||||
}
|
||||
|
||||
@ -3757,17 +3789,27 @@ bool CGameHandler::makeBattleAction( BattleAction &ba )
|
||||
complain("That stack can't cast spells!");
|
||||
else
|
||||
{
|
||||
int spellLvl = 0;
|
||||
BattleSpellCastParameters p(gs->curB);
|
||||
|
||||
p.spellLvl = 0;
|
||||
if (spellcaster)
|
||||
vstd::amax(spellLvl, spellcaster->val);
|
||||
vstd::amax(p.spellLvl, spellcaster->val);
|
||||
if (randSpellcaster)
|
||||
vstd::amax(spellLvl, randSpellcaster->val);
|
||||
vstd::amin (spellLvl, 3);
|
||||
vstd::amax(p.spellLvl, randSpellcaster->val);
|
||||
vstd::amin (p.spellLvl, 3);
|
||||
|
||||
int casterSide = gs->curB->whatSide(stack->owner);
|
||||
const CGHeroInstance * secHero = gs->curB->getHero(gs->curB->theOtherPlayer(stack->owner));
|
||||
p.casterSide = gs->curB->whatSide(stack->owner);
|
||||
p.secHero = gs->curB->getHero(gs->curB->theOtherPlayer(stack->owner));
|
||||
p.mode = ECastingMode::CREATURE_ACTIVE_CASTING;
|
||||
p.destination = destination;
|
||||
p.casterColor = stack->owner;
|
||||
p.caster = nullptr;
|
||||
p.usedSpellPower = 0;
|
||||
p.casterStack = stack;
|
||||
p.selectedStack = nullptr;
|
||||
|
||||
handleSpellCasting(spellID, spellLvl, destination, casterSide, stack->owner, nullptr, secHero, 0, ECastingMode::CREATURE_ACTIVE_CASTING, stack);
|
||||
const CSpell * spell = SpellID(spellID).toSpell();
|
||||
spell->battleCast(spellEnv, p);
|
||||
}
|
||||
sendAndApply(&end_action);
|
||||
break;
|
||||
@ -3804,6 +3846,7 @@ void CGameHandler::playerMessage( PlayerColor player, const std::string &message
|
||||
|
||||
//give mana
|
||||
sm.val = 999;
|
||||
sm.absolute = true;
|
||||
|
||||
if(!h->hasSpellbook()) //hero doesn't have spellbook
|
||||
giveHeroNewArtifact(h, VLC->arth->artifacts.at(0), ArtifactPosition::SPELLBOOK); //give spellbook
|
||||
@ -3931,542 +3974,6 @@ void CGameHandler::playerMessage( PlayerColor player, const std::string &message
|
||||
}
|
||||
}
|
||||
|
||||
void CGameHandler::handleSpellCasting( SpellID spellID, int spellLvl, BattleHex destination, ui8 casterSide, PlayerColor casterColor, const CGHeroInstance * caster, const CGHeroInstance * secHero,
|
||||
int usedSpellPower, ECastingMode::ECastingMode mode, const CStack * stack, si32 selectedStack)
|
||||
{
|
||||
const CSpell * spell = SpellID(spellID).toSpell();
|
||||
|
||||
|
||||
//Helper local function that creates obstacle on given position. Obstacle type is inferred from spell type.
|
||||
//It creates, sends and applies needed package.
|
||||
auto placeObstacle = [&](BattleHex pos)
|
||||
{
|
||||
static int obstacleIdToGive = gs->curB->obstacles.size()
|
||||
? (gs->curB->obstacles.back()->uniqueID+1)
|
||||
: 0;
|
||||
|
||||
auto obstacle = make_shared<SpellCreatedObstacle>();
|
||||
switch(spellID.toEnum()) // :/
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
obstacle->pos = pos;
|
||||
obstacle->casterSide = casterSide;
|
||||
obstacle->ID = spellID;
|
||||
obstacle->spellLevel = spellLvl;
|
||||
obstacle->casterSpellPower = usedSpellPower;
|
||||
obstacle->uniqueID = obstacleIdToGive++;
|
||||
|
||||
BattleObstaclePlaced bop;
|
||||
bop.obstacle = obstacle;
|
||||
sendAndApply(&bop);
|
||||
};
|
||||
|
||||
BattleSpellCast sc;
|
||||
sc.side = casterSide;
|
||||
sc.id = spellID;
|
||||
sc.skill = spellLvl;
|
||||
sc.tile = destination;
|
||||
sc.dmgToDisplay = 0;
|
||||
sc.castedByHero = (bool)caster;
|
||||
sc.attackerType = (stack ? stack->type->idNumber : CreatureID(CreatureID::NONE));
|
||||
sc.manaGained = 0;
|
||||
sc.spellCost = 0;
|
||||
|
||||
if (caster) //calculate spell cost
|
||||
{
|
||||
sc.spellCost = gs->curB->battleGetSpellCost(spell, caster);
|
||||
|
||||
if (secHero && mode == ECastingMode::HERO_CASTING) //handle mana channel
|
||||
{
|
||||
int manaChannel = 0;
|
||||
for(CStack * stack : gs->curB->stacks) //TODO: shouldn't bonus system handle it somehow?
|
||||
{
|
||||
if (stack->owner == secHero->tempOwner)
|
||||
{
|
||||
vstd::amax(manaChannel, stack->valOfBonuses(Bonus::MANA_CHANNELING));
|
||||
}
|
||||
}
|
||||
sc.manaGained = (manaChannel * sc.spellCost) / 100;
|
||||
}
|
||||
}
|
||||
|
||||
//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
|
||||
|
||||
if (mode != ECastingMode::ENCHANTER_CASTING)
|
||||
{
|
||||
auto creatures = gs->curB->getAffectedCreatures(spell, spellLvl, casterColor, destination);
|
||||
std::copy(creatures.begin(), creatures.end(), std::back_inserter(attackedCres));
|
||||
}
|
||||
else //enchanter - hit all possible stacks
|
||||
{
|
||||
for (const CStack * stack : gs->curB->stacks)
|
||||
{
|
||||
/*if it's non negative spell and our unit or non positive spell and hostile unit */
|
||||
if((!spell->isNegative() && stack->owner == casterColor)
|
||||
|| (!spell->isPositive() && stack->owner != casterColor))
|
||||
{
|
||||
if(stack->isValidTarget()) //TODO: allow dead targets somewhere in the future
|
||||
{
|
||||
attackedCres.push_back(stack);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vstd::erase_if(attackedCres,[=](const CStack * s){
|
||||
return ESpellCastProblem::OK != gs->curB->battleStackIsImmune(caster,spell,mode,s);
|
||||
});
|
||||
|
||||
for (auto cre : attackedCres)
|
||||
{
|
||||
sc.affectedCres.insert (cre->ID);
|
||||
}
|
||||
|
||||
//checking if creatures resist
|
||||
//resistance is applied only to negative spells
|
||||
if(spell->isNegative())
|
||||
{
|
||||
for(auto s : attackedCres)
|
||||
{
|
||||
const int prob = std::min((s)->magicResistance(), 100); //probability of resistance in %
|
||||
|
||||
if(gs->getRandomGenerator().nextInt(99) < prob)
|
||||
{
|
||||
sc.resisted.push_back(s->ID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//calculating dmg to display
|
||||
if (spellID == SpellID::DEATH_STARE || spellID == SpellID::ACID_BREATH_DAMAGE)
|
||||
{
|
||||
sc.dmgToDisplay = usedSpellPower;
|
||||
if (spellID == SpellID::DEATH_STARE)
|
||||
vstd::amin(sc.dmgToDisplay, (*attackedCres.begin())->count); //stack is already reduced after attack
|
||||
}
|
||||
StacksInjured si;
|
||||
|
||||
//applying effects
|
||||
|
||||
if (spell->isOffensiveSpell())
|
||||
{
|
||||
int spellDamage = 0;
|
||||
if (stack && mode != ECastingMode::MAGIC_MIRROR)
|
||||
{
|
||||
int unitSpellPower = stack->valOfBonuses(Bonus::SPECIFIC_SPELL_POWER, spellID.toEnum());
|
||||
if (unitSpellPower)
|
||||
sc.dmgToDisplay = spellDamage = stack->count * unitSpellPower; //TODO: handle immunities
|
||||
else //Faerie Dragon
|
||||
{
|
||||
usedSpellPower = stack->valOfBonuses(Bonus::CREATURE_SPELL_POWER) * stack->count / 100;
|
||||
sc.dmgToDisplay = 0;
|
||||
}
|
||||
}
|
||||
int chainLightningModifier = 0;
|
||||
for(auto & attackedCre : attackedCres)
|
||||
{
|
||||
if(vstd::contains(sc.resisted, (attackedCre)->ID)) //this creature resisted the spell
|
||||
continue;
|
||||
|
||||
BattleStackAttacked bsa;
|
||||
if ((destination > -1 && (attackedCre)->coversPos(destination)) || (spell->getLevelInfo(spellLvl).range == "X" || mode == ECastingMode::ENCHANTER_CASTING))
|
||||
//display effect only upon primary target of area spell
|
||||
//FIXME: if no stack is attacked, there is no animation and interface freezes
|
||||
{
|
||||
bsa.flags |= BattleStackAttacked::EFFECT;
|
||||
bsa.effect = spell->mainEffectAnim;
|
||||
}
|
||||
if (spellDamage)
|
||||
bsa.damageAmount = spellDamage >> chainLightningModifier;
|
||||
else
|
||||
bsa.damageAmount = gs->curB->calculateSpellDmg(spell, caster, attackedCre, spellLvl, usedSpellPower) >> chainLightningModifier;
|
||||
|
||||
sc.dmgToDisplay += bsa.damageAmount;
|
||||
|
||||
bsa.stackAttacked = (attackedCre)->ID;
|
||||
if (mode == ECastingMode::ENCHANTER_CASTING) //multiple damage spells cast
|
||||
bsa.attackerID = stack->ID;
|
||||
else
|
||||
bsa.attackerID = -1;
|
||||
(attackedCre)->prepareAttacked(bsa, gs->getRandomGenerator());
|
||||
si.stacks.push_back(bsa);
|
||||
|
||||
if (spellID == SpellID::CHAIN_LIGHTNING)
|
||||
++chainLightningModifier;
|
||||
}
|
||||
}
|
||||
|
||||
if (spell->hasEffects())
|
||||
{
|
||||
int stackSpellPower = 0;
|
||||
if (stack && mode != ECastingMode::MAGIC_MIRROR)
|
||||
{
|
||||
stackSpellPower = stack->valOfBonuses(Bonus::CREATURE_ENCHANT_POWER);
|
||||
}
|
||||
SetStackEffect sse;
|
||||
Bonus pseudoBonus;
|
||||
pseudoBonus.sid = spellID;
|
||||
pseudoBonus.val = spellLvl;
|
||||
pseudoBonus.turnsRemain = gs->curB->calculateSpellDuration(spell, caster, stackSpellPower ? stackSpellPower : usedSpellPower);
|
||||
CStack::stackEffectToFeature(sse.effect, pseudoBonus);
|
||||
if (spellID == SpellID::SHIELD || spellID == SpellID::AIR_SHIELD)
|
||||
{
|
||||
sse.effect.back().val = (100 - sse.effect.back().val); //fix to original config: shield should display damage reduction
|
||||
}
|
||||
if (spellID == SpellID::BIND && stack)//bind
|
||||
{
|
||||
sse.effect.back().additionalInfo = stack->ID; //we need to know who casted Bind
|
||||
}
|
||||
const Bonus * bonus = nullptr;
|
||||
if (caster)
|
||||
bonus = caster->getBonusLocalFirst(Selector::typeSubtype(Bonus::SPECIAL_PECULIAR_ENCHANT, spellID));
|
||||
//TODO does hero specialty should affects his stack casting spells?
|
||||
|
||||
si32 power = 0;
|
||||
for(const CStack *affected : attackedCres)
|
||||
{
|
||||
if(vstd::contains(sc.resisted, affected->ID)) //this creature resisted the spell
|
||||
continue;
|
||||
sse.stacks.push_back(affected->ID);
|
||||
|
||||
//Apply hero specials - peculiar enchants
|
||||
const ui8 tier = std::max((ui8)1, affected->getCreature()->level); //don't divide by 0 for certain creatures (commanders, war machines)
|
||||
if (bonus)
|
||||
{
|
||||
switch(bonus->additionalInfo)
|
||||
{
|
||||
case 0: //normal
|
||||
{
|
||||
switch(tier)
|
||||
{
|
||||
case 1: case 2:
|
||||
power = 3;
|
||||
break;
|
||||
case 3: case 4:
|
||||
power = 2;
|
||||
break;
|
||||
case 5: case 6:
|
||||
power = 1;
|
||||
break;
|
||||
}
|
||||
Bonus specialBonus(sse.effect.back());
|
||||
specialBonus.val = power; //it doesn't necessarily make sense for some spells, use it wisely
|
||||
sse.uniqueBonuses.push_back (std::pair<ui32,Bonus> (affected->ID, specialBonus)); //additional premy to given effect
|
||||
}
|
||||
break;
|
||||
case 1: //only Coronius as yet
|
||||
{
|
||||
power = std::max(5 - tier, 0);
|
||||
Bonus specialBonus = CStack::featureGenerator(Bonus::PRIMARY_SKILL, PrimarySkill::ATTACK, power, pseudoBonus.turnsRemain);
|
||||
specialBonus.sid = spellID;
|
||||
sse.uniqueBonuses.push_back (std::pair<ui32,Bonus> (affected->ID, specialBonus)); //additional attack to Slayer effect
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (caster && caster->hasBonusOfType(Bonus::SPECIAL_BLESS_DAMAGE, spellID)) //TODO: better handling of bonus percentages
|
||||
{
|
||||
int damagePercent = caster->level * caster->valOfBonuses(Bonus::SPECIAL_BLESS_DAMAGE, spellID.toEnum()) / tier;
|
||||
Bonus specialBonus = CStack::featureGenerator(Bonus::CREATURE_DAMAGE, 0, damagePercent, pseudoBonus.turnsRemain);
|
||||
specialBonus.valType = Bonus::PERCENT_TO_ALL;
|
||||
specialBonus.sid = spellID;
|
||||
sse.uniqueBonuses.push_back (std::pair<ui32,Bonus> (affected->ID, specialBonus));
|
||||
}
|
||||
}
|
||||
|
||||
if(!sse.stacks.empty())
|
||||
sendAndApply(&sse);
|
||||
|
||||
}
|
||||
|
||||
if (spell->isRisingSpell() || spell->id == SpellID::CURE)
|
||||
{
|
||||
int hpGained = 0;
|
||||
if (stack)
|
||||
{
|
||||
int unitSpellPower = stack->valOfBonuses(Bonus::SPECIFIC_SPELL_POWER, spellID.toEnum());
|
||||
if (unitSpellPower)
|
||||
hpGained = stack->count * unitSpellPower; //Archangel
|
||||
else //Faerie Dragon-like effect - unused so far
|
||||
usedSpellPower = stack->valOfBonuses(Bonus::CREATURE_SPELL_POWER) * stack->count / 100;
|
||||
}
|
||||
StacksHealedOrResurrected shr;
|
||||
shr.lifeDrain = false;
|
||||
shr.tentHealing = false;
|
||||
for(auto & attackedCre : attackedCres)
|
||||
{
|
||||
StacksHealedOrResurrected::HealInfo hi;
|
||||
hi.stackID = (attackedCre)->ID;
|
||||
if (stack) //casted by creature
|
||||
{
|
||||
if (hpGained)
|
||||
{
|
||||
hi.healedHP = gs->curB->calculateHealedHP(hpGained, spell, attackedCre); //archangel
|
||||
}
|
||||
else
|
||||
hi.healedHP = gs->curB->calculateHealedHP(spell, usedSpellPower, spellLvl, attackedCre); //any typical spell (commander's cure or animate dead)
|
||||
}
|
||||
else
|
||||
hi.healedHP = gs->curB->calculateHealedHP(caster, spell, attackedCre, gs->curB->battleGetStackByID(selectedStack)); //Casted by hero
|
||||
hi.lowLevelResurrection = spellLvl <= 1;
|
||||
shr.healedStacks.push_back(hi);
|
||||
}
|
||||
if(!shr.healedStacks.empty())
|
||||
sendAndApply(&shr);
|
||||
if (spellID == SpellID::SACRIFICE) //remove victim
|
||||
{
|
||||
if (selectedStack == gs->curB->activeStack)
|
||||
//set another active stack than the one removed, or bad things will happen
|
||||
//TODO: make that part of BattleStacksRemoved? what about client update?
|
||||
{
|
||||
//makeStackDoNothing(gs->curB->getStack (selectedStack));
|
||||
|
||||
BattleSetActiveStack sas;
|
||||
|
||||
//std::vector<const CStack *> hlp;
|
||||
//battleGetStackQueue(hlp, 1, selectedStack); //next after this one
|
||||
|
||||
//if(hlp.size())
|
||||
//{
|
||||
// sas.stack = hlp[0]->ID;
|
||||
//}
|
||||
//else
|
||||
// complain ("No new stack to activate!");
|
||||
sas.stack = gs->curB->getNextStack()->ID; //why the hell next stack has same ID as current?
|
||||
sendAndApply(&sas);
|
||||
|
||||
}
|
||||
BattleStacksRemoved bsr;
|
||||
bsr.stackIDs.insert (selectedStack); //somehow it works for teleport?
|
||||
sendAndApply(&bsr);
|
||||
}
|
||||
}
|
||||
|
||||
switch (spellID)
|
||||
{
|
||||
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 && !battleGetStackByPos(hex, false) && !battleGetObstacleOnPos(hex, false))
|
||||
availableTiles.push_back(hex);
|
||||
}
|
||||
boost::range::random_shuffle(availableTiles);
|
||||
|
||||
const int patchesForSkill[] = {4, 4, 6, 8};
|
||||
const int patchesToPut = std::min<int>(patchesForSkill[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:
|
||||
placeObstacle(destination);
|
||||
break;
|
||||
case SpellID::FIRE_WALL:
|
||||
{
|
||||
//fire wall is build from multiple obstacles - one fire piece for each affected hex
|
||||
auto affectedHexes = spell->rangeInHexes(destination, spellLvl, casterSide);
|
||||
for(BattleHex hex : affectedHexes)
|
||||
placeObstacle(hex);
|
||||
}
|
||||
break;
|
||||
case SpellID::TELEPORT:
|
||||
{
|
||||
BattleStackMoved bsm;
|
||||
bsm.distance = -1;
|
||||
bsm.stack = selectedStack;
|
||||
std::vector<BattleHex> tiles;
|
||||
tiles.push_back(destination);
|
||||
bsm.tilesToMove = tiles;
|
||||
bsm.teleporting = true;
|
||||
sendAndApply(&bsm);
|
||||
break;
|
||||
}
|
||||
case SpellID::SUMMON_FIRE_ELEMENTAL:
|
||||
case SpellID::SUMMON_EARTH_ELEMENTAL:
|
||||
case SpellID::SUMMON_WATER_ELEMENTAL:
|
||||
case SpellID::SUMMON_AIR_ELEMENTAL:
|
||||
{ //elemental summoning
|
||||
CreatureID creID;
|
||||
switch(spellID)
|
||||
{
|
||||
case SpellID::SUMMON_FIRE_ELEMENTAL:
|
||||
creID = CreatureID::FIRE_ELEMENTAL;
|
||||
break;
|
||||
case SpellID::SUMMON_EARTH_ELEMENTAL:
|
||||
creID = CreatureID::EARTH_ELEMENTAL;
|
||||
break;
|
||||
case SpellID::SUMMON_WATER_ELEMENTAL:
|
||||
creID = CreatureID::WATER_ELEMENTAL;
|
||||
break;
|
||||
case SpellID::SUMMON_AIR_ELEMENTAL:
|
||||
creID = CreatureID::AIR_ELEMENTAL;
|
||||
break;
|
||||
}
|
||||
|
||||
BattleStackAdded bsa;
|
||||
bsa.creID = creID;
|
||||
bsa.attacker = !(bool)casterSide;
|
||||
bsa.summoned = true;
|
||||
bsa.pos = gs->curB->getAvaliableHex(creID, !(bool)casterSide); //TODO: unify it
|
||||
|
||||
//TODO stack casting -> probably power will be zero; set the proper number of creatures manually
|
||||
int percentBonus = caster ? caster->valOfBonuses(Bonus::SPECIFIC_SPELL_DAMAGE, spellID.toEnum()) : 0;
|
||||
|
||||
bsa.amount = usedSpellPower
|
||||
* SpellID(spellID).toSpell()->getPower(spellLvl)
|
||||
* (100 + percentBonus) / 100.0; //new feature - percentage bonus
|
||||
if(bsa.amount)
|
||||
sendAndApply(&bsa);
|
||||
else
|
||||
complain("Summoning elementals didn't summon any!");
|
||||
}
|
||||
break;
|
||||
case SpellID::CLONE:
|
||||
{
|
||||
const CStack * clonedStack = nullptr;
|
||||
if (attackedCres.size())
|
||||
clonedStack = *attackedCres.begin();
|
||||
if (!clonedStack)
|
||||
{
|
||||
complain ("No target stack to clone!");
|
||||
break;
|
||||
}
|
||||
|
||||
BattleStackAdded bsa;
|
||||
bsa.creID = clonedStack->type->idNumber;
|
||||
bsa.attacker = !(bool)casterSide;
|
||||
bsa.summoned = true;
|
||||
bsa.pos = gs->curB->getAvaliableHex(bsa.creID, !(bool)casterSide); //TODO: unify it
|
||||
bsa.amount = clonedStack->count;
|
||||
sendAndApply (&bsa);
|
||||
|
||||
BattleSetStackProperty ssp;
|
||||
ssp.stackID = gs->curB->stacks.back()->ID; //how to get recent stack?
|
||||
ssp.which = BattleSetStackProperty::CLONED; //using enum values
|
||||
ssp.val = 0;
|
||||
ssp.absolute = 1;
|
||||
sendAndApply(&ssp);
|
||||
}
|
||||
break;
|
||||
case SpellID::REMOVE_OBSTACLE:
|
||||
{
|
||||
if(auto obstacleToRemove = battleGetObstacleOnPos(destination, false))
|
||||
{
|
||||
ObstaclesRemoved obr;
|
||||
obr.obstacles.insert(obstacleToRemove->uniqueID);
|
||||
sendAndApply(&obr);
|
||||
}
|
||||
else
|
||||
complain("There's no obstacle to remove!");
|
||||
}
|
||||
break;
|
||||
case SpellID::DEATH_STARE: //handled in a bit different way
|
||||
{
|
||||
for(auto & attackedCre : attackedCres)
|
||||
{
|
||||
BattleStackAttacked bsa;
|
||||
bsa.flags |= BattleStackAttacked::EFFECT;
|
||||
bsa.effect = spell->mainEffectAnim; //from config\spell-Info.txt
|
||||
bsa.damageAmount = usedSpellPower * (attackedCre)->valOfBonuses(Bonus::STACK_HEALTH);
|
||||
bsa.stackAttacked = (attackedCre)->ID;
|
||||
bsa.attackerID = -1;
|
||||
(attackedCre)->prepareAttacked(bsa, gameState()->getRandomGenerator());
|
||||
si.stacks.push_back(bsa);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case SpellID::ACID_BREATH_DAMAGE: //new effect, separate from acid breath defense reduction
|
||||
{
|
||||
for(auto & attackedCre : attackedCres) //no immunities
|
||||
{
|
||||
BattleStackAttacked bsa;
|
||||
bsa.flags |= BattleStackAttacked::EFFECT;
|
||||
bsa.effect = spell->mainEffectAnim;
|
||||
bsa.damageAmount = usedSpellPower; //damage times the number of attackers
|
||||
bsa.stackAttacked = (attackedCre)->ID;
|
||||
bsa.attackerID = -1;
|
||||
(attackedCre)->prepareAttacked(bsa, gameState()->getRandomGenerator());
|
||||
si.stacks.push_back(bsa);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
sendAndApply(&sc);
|
||||
if(!si.stacks.empty()) //after spellcast info shows
|
||||
sendAndApply(&si);
|
||||
|
||||
if (mode == ECastingMode::CREATURE_ACTIVE_CASTING || mode == ECastingMode::ENCHANTER_CASTING) //reduce number of casts remaining
|
||||
{
|
||||
BattleSetStackProperty ssp;
|
||||
ssp.stackID = stack->ID;
|
||||
ssp.which = BattleSetStackProperty::CASTS;
|
||||
ssp.val = -1;
|
||||
ssp.absolute = false;
|
||||
sendAndApply(&ssp);
|
||||
}
|
||||
|
||||
//Magic Mirror effect
|
||||
if (spell->isNegative() && mode != ECastingMode::MAGIC_MIRROR && spell->level && spell->getLevelInfo(0).range == "0") //it is actual spell and can be reflected to single target, no recurrence
|
||||
{
|
||||
for(auto & attackedCre : attackedCres)
|
||||
{
|
||||
int mirrorChance = (attackedCre)->valOfBonuses(Bonus::MAGIC_MIRROR);
|
||||
if(mirrorChance > gs->getRandomGenerator().nextInt(99))
|
||||
{
|
||||
std::vector<CStack *> mirrorTargets;
|
||||
std::vector<CStack *> & battleStacks = gs->curB->stacks;
|
||||
for (auto & battleStack : battleStacks)
|
||||
{
|
||||
if(battleStack->owner == gs->curB->sides.at(casterSide).color) //get enemy stacks which can be affected by this spell
|
||||
{
|
||||
if (ESpellCastProblem::OK == gs->curB->battleStackIsImmune(nullptr, spell, ECastingMode::MAGIC_MIRROR, battleStack))
|
||||
mirrorTargets.push_back(battleStack);
|
||||
}
|
||||
}
|
||||
if (!mirrorTargets.empty())
|
||||
{
|
||||
int targetHex = (*RandomGeneratorUtil::nextItem(mirrorTargets, gs->getRandomGenerator()))->position;
|
||||
handleSpellCasting(spellID, 0, targetHex, 1 - casterSide, (attackedCre)->owner, nullptr, (caster ? caster : nullptr), usedSpellPower, ECastingMode::MAGIC_MIRROR, (attackedCre));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool CGameHandler::makeCustomAction( BattleAction &ba )
|
||||
{
|
||||
switch(ba.actionType)
|
||||
@ -4489,16 +3996,20 @@ bool CGameHandler::makeCustomAction( BattleAction &ba )
|
||||
return false;
|
||||
}
|
||||
|
||||
const CSpell *s = SpellID(ba.additionalInfo).toSpell();
|
||||
if (s->mainEffectAnim > -1
|
||||
|| s->id == SpellID::CLONE
|
||||
|| s->id >= SpellID::SUMMON_FIRE_ELEMENTAL
|
||||
|| s->id <= SpellID::SUMMON_AIR_ELEMENTAL
|
||||
|| s->id <= SpellID::SUMMON_EARTH_ELEMENTAL
|
||||
|| s->id <= SpellID::SUMMON_WATER_ELEMENTAL)
|
||||
//TODO: special effects, like Clone
|
||||
{
|
||||
ui8 skill = h->getSpellSchoolLevel(s); //skill level
|
||||
const CSpell * s = SpellID(ba.additionalInfo).toSpell();
|
||||
|
||||
BattleSpellCastParameters parameters(gs->curB);
|
||||
parameters.spellLvl = h->getSpellSchoolLevel(s);
|
||||
parameters.destination = ba.destinationTile;
|
||||
parameters.casterSide = ba.side;
|
||||
parameters.casterColor = h->tempOwner;
|
||||
parameters.caster = h;
|
||||
parameters.secHero = secondHero;
|
||||
|
||||
parameters.usedSpellPower = h->getPrimSkillLevel(PrimarySkill::SPELL_POWER);
|
||||
parameters.mode = ECastingMode::HERO_CASTING;
|
||||
parameters.casterStack = nullptr;
|
||||
parameters.selectedStack = gs->curB->battleGetStackByID(ba.selectedStack, false);
|
||||
|
||||
ESpellCastProblem::ESpellCastProblem escp = gs->curB->battleCanCastThisSpell(h->tempOwner, s, ECastingMode::HERO_CASTING);
|
||||
if(escp != ESpellCastProblem::OK)
|
||||
@ -4511,9 +4022,7 @@ bool CGameHandler::makeCustomAction( BattleAction &ba )
|
||||
StartAction start_action(ba);
|
||||
sendAndApply(&start_action); //start spell casting
|
||||
|
||||
handleSpellCasting (SpellID(ba.additionalInfo), skill, ba.destinationTile, ba.side, h->tempOwner,
|
||||
h, secondHero, h->getPrimSkillLevel(PrimarySkill::SPELL_POWER),
|
||||
ECastingMode::HERO_CASTING, nullptr, ba.selectedStack);
|
||||
s->battleCast(spellEnv, parameters);
|
||||
|
||||
sendAndApply(&end_action);
|
||||
if( !gs->curB->battleGetStackByID(gs->curB->activeStack, true))
|
||||
@ -4529,12 +4038,7 @@ bool CGameHandler::makeCustomAction( BattleAction &ba )
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
logGlobal->warnStream() << "Spell " << s->name << " is not yet supported!";
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return false;
|
||||
@ -4645,11 +4149,22 @@ void CGameHandler::stackTurnTrigger(const CStack * st)
|
||||
{
|
||||
auto bonus = *RandomGeneratorUtil::nextItem(bl, gs->getRandomGenerator());
|
||||
auto spellID = SpellID(bonus->subtype);
|
||||
if (gs->curB->battleCanCastThisSpell(st->owner, SpellID(spellID).toSpell(), ECastingMode::ENCHANTER_CASTING) == ESpellCastProblem::OK) //TODO: select another available?
|
||||
const CSpell * spell = SpellID(spellID).toSpell();
|
||||
if (gs->curB->battleCanCastThisSpell(st->owner, spell, ECastingMode::ENCHANTER_CASTING) == ESpellCastProblem::OK) //TODO: select another available?
|
||||
{
|
||||
int spellLeveL = bonus->val; //spell level
|
||||
const CGHeroInstance * enemyHero = gs->curB->getHero(gs->curB->theOtherPlayer(st->owner));
|
||||
handleSpellCasting(spellID, spellLeveL, -1, side, st->owner, nullptr, enemyHero, 0, ECastingMode::ENCHANTER_CASTING, st);
|
||||
BattleSpellCastParameters parameters(gs->curB);
|
||||
parameters.spellLvl = bonus->val;
|
||||
parameters.destination = BattleHex::INVALID;
|
||||
parameters.casterSide = side;
|
||||
parameters.casterColor = st->owner;
|
||||
parameters.caster = nullptr;
|
||||
parameters.secHero = gs->curB->getHero(gs->curB->theOtherPlayer(st->owner));
|
||||
parameters.usedSpellPower = 0;
|
||||
parameters.mode = ECastingMode::ENCHANTER_CASTING;
|
||||
parameters.casterStack = st;
|
||||
parameters.selectedStack = nullptr;
|
||||
|
||||
spell->battleCast(spellEnv, parameters);
|
||||
|
||||
BattleSetStackProperty ssp;
|
||||
ssp.which = BattleSetStackProperty::ENCHANTER_COUNTER;
|
||||
@ -4710,14 +4225,14 @@ void CGameHandler::handleDamageFromObstacle(const CObstacleInstance &obstacle, c
|
||||
|
||||
oneTimeObstacle = true;
|
||||
effect = 82; //makes
|
||||
damage = gs->curB->calculateSpellDmg(SpellID(SpellID::LAND_MINE).toSpell(), hero, curStack,
|
||||
damage = SpellID(SpellID::LAND_MINE).toSpell()->calculateDamage(hero, curStack,
|
||||
spellObstacle->spellLevel, spellObstacle->casterSpellPower);
|
||||
//TODO even if obstacle wasn't created by hero (Tower "moat") it should deal dmg as if casted by hero,
|
||||
//if it is bigger than default dmg. Or is it just irrelevant H3 implementation quirk
|
||||
}
|
||||
else if(obstacle.obstacleType == CObstacleInstance::FIRE_WALL)
|
||||
{
|
||||
damage = gs->curB->calculateSpellDmg(SpellID(SpellID::FIRE_WALL).toSpell(), hero, curStack,
|
||||
damage = SpellID(SpellID::FIRE_WALL).toSpell()->calculateDamage(hero, curStack,
|
||||
spellObstacle->spellLevel, spellObstacle->casterSpellPower);
|
||||
}
|
||||
else
|
||||
@ -5331,9 +4846,26 @@ void CGameHandler::attackCasting(const BattleAttack & bat, Bonus::BonusType atta
|
||||
if(gs->getRandomGenerator().nextInt(99) >= chance)
|
||||
continue;
|
||||
|
||||
//casting //TODO: check if spell can be blocked or target is immune
|
||||
//casting
|
||||
if (castMe) //stacks use 0 spell power. If needed, default = 3 or custom value is used
|
||||
handleSpellCasting(spellID, spellLevel, destination, !attacker->attackerOwned, attacker->owner, nullptr, nullptr, 0, ECastingMode::AFTER_ATTACK_CASTING, attacker);
|
||||
{
|
||||
const CSpell * spell = SpellID(spellID).toSpell();
|
||||
|
||||
BattleSpellCastParameters parameters(gs->curB);
|
||||
parameters.spellLvl = spellLevel;
|
||||
parameters.destination = destination;
|
||||
parameters.casterSide = !attacker->attackerOwned;
|
||||
parameters.casterColor = attacker->owner;
|
||||
parameters.caster = nullptr;
|
||||
parameters.secHero = nullptr;
|
||||
|
||||
parameters.usedSpellPower = 0;
|
||||
parameters.mode = ECastingMode::AFTER_ATTACK_CASTING;
|
||||
parameters.casterStack = attacker;
|
||||
parameters.selectedStack = nullptr;
|
||||
|
||||
spell->battleCast(spellEnv, parameters);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -5349,6 +4881,27 @@ void CGameHandler::handleAfterAttackCasting( const BattleAttack & bat )
|
||||
const CStack * attacker = gs->curB->battleGetStackByID(bat.stackAttacking);
|
||||
if (!attacker) //could be already dead
|
||||
return;
|
||||
|
||||
auto cast = [=](SpellID spellID, int power)
|
||||
{
|
||||
const CSpell * spell = SpellID(spellID).toSpell();
|
||||
|
||||
BattleSpellCastParameters parameters(gs->curB);
|
||||
parameters.spellLvl = 0;
|
||||
parameters.destination = gs->curB->battleGetStackByID(bat.bsa.at(0).stackAttacked)->position;
|
||||
parameters.casterSide = !attacker->attackerOwned;
|
||||
parameters.casterColor = attacker->owner;
|
||||
parameters.caster = nullptr;
|
||||
parameters.secHero = nullptr;
|
||||
|
||||
parameters.usedSpellPower = power;
|
||||
parameters.mode = ECastingMode::AFTER_ATTACK_CASTING;
|
||||
parameters.casterStack = attacker;
|
||||
parameters.selectedStack = nullptr;
|
||||
|
||||
spell->battleCast(this->spellEnv, parameters);
|
||||
};
|
||||
|
||||
attackCasting(bat, Bonus::SPELL_AFTER_ATTACK, attacker);
|
||||
|
||||
if(bat.bsa.at(0).newAmount <= 0)
|
||||
@ -5379,8 +4932,7 @@ void CGameHandler::handleAfterAttackCasting( const BattleAttack & bat )
|
||||
if (staredCreatures)
|
||||
{
|
||||
if (bat.bsa.at(0).newAmount > 0) //TODO: death stare was not originally available for multiple-hex attacks, but...
|
||||
handleSpellCasting(SpellID::DEATH_STARE, 0, gs->curB->battleGetStackByID(bat.bsa.at(0).stackAttacked)->position,
|
||||
!attacker->attackerOwned, attacker->owner, nullptr, nullptr, staredCreatures, ECastingMode::AFTER_ATTACK_CASTING, attacker);
|
||||
cast(SpellID::DEATH_STARE, staredCreatures);
|
||||
}
|
||||
}
|
||||
|
||||
@ -5393,9 +4945,7 @@ void CGameHandler::handleAfterAttackCasting( const BattleAttack & bat )
|
||||
}
|
||||
if (acidDamage)
|
||||
{
|
||||
handleSpellCasting(SpellID::ACID_BREATH_DAMAGE, 0, gs->curB->battleGetStackByID(bat.bsa.at(0).stackAttacked)->position,
|
||||
!attacker->attackerOwned, attacker->owner, nullptr, nullptr,
|
||||
acidDamage * attacker->count, ECastingMode::AFTER_ATTACK_CASTING, attacker);
|
||||
cast(SpellID::ACID_BREATH_DAMAGE, acidDamage * attacker->count);
|
||||
}
|
||||
}
|
||||
|
||||
@ -5613,7 +5163,8 @@ bool CGameHandler::castSpell(const CGHeroInstance *h, SpellID spellID, const int
|
||||
|
||||
SetMana sm;
|
||||
sm.hid = h->id;
|
||||
sm.val = h->mana - cost;
|
||||
sm.absolute = false;
|
||||
sm.val = -cost;
|
||||
sendAndApply(&sm);
|
||||
|
||||
return true;
|
||||
@ -5854,9 +5405,27 @@ void CGameHandler::runBattle()
|
||||
if(h && h->hasBonusOfType(Bonus::OPENING_BATTLE_SPELL))
|
||||
{
|
||||
TBonusListPtr bl = h->getBonuses(Selector::type(Bonus::OPENING_BATTLE_SPELL));
|
||||
|
||||
BattleSpellCastParameters parameters(gs->curB);
|
||||
parameters.spellLvl = 3;
|
||||
parameters.destination = BattleHex::INVALID;
|
||||
parameters.casterSide = 0;
|
||||
parameters.casterColor = h->tempOwner;
|
||||
parameters.caster = nullptr;
|
||||
parameters.secHero = gs->curB->battleGetFightingHero(1-i);
|
||||
|
||||
|
||||
parameters.mode = ECastingMode::HERO_CASTING;
|
||||
parameters.casterStack = nullptr;
|
||||
parameters.selectedStack = nullptr;
|
||||
|
||||
for (Bonus *b : *bl)
|
||||
{
|
||||
handleSpellCasting(SpellID(b->subtype), 3, -1, 0, h->tempOwner, nullptr, gs->curB->battleGetFightingHero(1-i), b->val, ECastingMode::HERO_CASTING, nullptr);
|
||||
parameters.usedSpellPower = b->val;
|
||||
|
||||
const CSpell * spell = SpellID(b->subtype).toSpell();
|
||||
|
||||
spell->battleCast(spellEnv, parameters);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -6422,3 +5991,24 @@ CGameHandler::FinishingBattleHelper::FinishingBattleHelper()
|
||||
{
|
||||
winnerHero = loserHero = nullptr;
|
||||
}
|
||||
|
||||
|
||||
ServerSpellCastEnvironment::ServerSpellCastEnvironment(CGameHandler * gh): gh(gh)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void ServerSpellCastEnvironment::sendAndApply(CPackForClient * info) const
|
||||
{
|
||||
gh->sendAndApply(info);
|
||||
}
|
||||
|
||||
CRandomGenerator & ServerSpellCastEnvironment::getRandomGenerator() const
|
||||
{
|
||||
return gh->gameState()->getRandomGenerator();
|
||||
}
|
||||
|
||||
void ServerSpellCastEnvironment::complain(const std::string& problem) const
|
||||
{
|
||||
gh->complain(problem);
|
||||
}
|
||||
|
@ -36,6 +36,8 @@ struct NewStructures;
|
||||
class CGHeroInstance;
|
||||
class IMarket;
|
||||
|
||||
class ServerSpellCastEnvironment;
|
||||
|
||||
extern std::map<ui32, CFunctionList<void(ui32)> > callbacks; //question id => callback functions - for selection dialogs
|
||||
extern boost::mutex gsm;
|
||||
|
||||
@ -201,8 +203,6 @@ public:
|
||||
void playerMessage( PlayerColor player, const std::string &message, ObjectInstanceID currObj);
|
||||
bool makeBattleAction(BattleAction &ba);
|
||||
bool makeAutomaticAction(const CStack *stack, BattleAction &ba); //used when action is taken by stack without volition of player (eg. unguided catapult attack)
|
||||
void handleSpellCasting(SpellID spellID, int spellLvl, BattleHex destination, ui8 casterSide, PlayerColor casterColor, const CGHeroInstance * caster, const CGHeroInstance * secHero,
|
||||
int usedSpellPower, ECastingMode::ECastingMode mode, const CStack * stack, si32 selectedStack = -1);
|
||||
bool makeCustomAction(BattleAction &ba);
|
||||
void stackTurnTrigger(const CStack * stack);
|
||||
void handleDamageFromObstacle(const CObstacleInstance &obstacle, const CStack * curStack); //checks if obstacle is land mine and handles possible consequences
|
||||
@ -288,6 +288,8 @@ public:
|
||||
friend class CVCMIServer;
|
||||
|
||||
private:
|
||||
ServerSpellCastEnvironment * spellEnv;
|
||||
|
||||
std::list<PlayerColor> generatePlayerTurnOrder() const;
|
||||
void makeStackDoNothing(const CStack * next);
|
||||
void getVictoryLossMessage(PlayerColor player, const EVictoryLossCheckResult & victoryLossCheckResult, InfoWindow & out) const;
|
||||
|
Loading…
Reference in New Issue
Block a user