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

Merge pull request #62 from vcmi/SpellsRefactoring4

OK
This commit is contained in:
DjWarmonger 2014-12-26 08:46:29 +01:00
commit 84b2510aa4
35 changed files with 3155 additions and 1873 deletions

View File

@ -457,19 +457,15 @@ void CBattleAI::attemptCastingSpell()
case OFFENSIVE_SPELL:
{
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

View File

@ -876,17 +876,22 @@ void CPlayerInterface::battleStacksAttacked(const std::vector<BattleStackAttacke
{
EVENT_HANDLER_CALLED_BY_CLIENT;
BATTLE_EVENT_POSSIBLE_RETURN;
std::vector<StackAttackedInfo> arg;
for(auto & elem : bsa)
{
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)
{

View File

@ -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,83 +866,81 @@ 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())
{
customAnim = graphics->battleACToDef[effect][0];
}
if(customAnim.empty())
{
if(effect == -1 || graphics->battleACToDef[effect].size() != 0)
endAnim();
return false;
}
const bool areaEffect = (!destTile.isValid() && x == -1 && y == -1);
if(areaEffect) //f.e. armageddon
{
CDefHandler * anim = CDefHandler::giveDef(customAnim);
for(int i=0; i * anim->width < owner->pos.w ; ++i)
{
CDefHandler * anim;
if(customAnim.size())
anim = CDefHandler::giveDef(customAnim);
else
anim = CDefHandler::giveDef(graphics->battleACToDef[effect][0]);
if (Vflip)
for(int j=0; j * anim->height < owner->pos.h ; ++j)
{
for (auto & elem : anim->ourImages)
BattleEffect be;
be.effectID = ID;
be.anim = CDefHandler::giveDef(customAnim);
if (Vflip)
{
CSDL_Ext::VflipSurf(elem.bitmap);
}
}
for(int i=0; i * anim->width < owner->pos.w ; ++i)
{
for(int j=0; j * anim->height < owner->pos.h ; ++j)
{
BattleEffect be;
be.effectID = ID;
be.anim = CDefHandler::giveDef(graphics->battleACToDef[effect][0]);
if (Vflip)
for (auto & elem : be.anim->ourImages)
{
for (auto & elem : be.anim->ourImages)
{
CSDL_Ext::VflipSurf(elem.bitmap);
}
CSDL_Ext::VflipSurf(elem.bitmap);
}
be.currentFrame = 0;
be.maxFrame = be.anim->ourImages.size();
be.x = i * anim->width + owner->pos.x;
be.y = j * anim->height + owner->pos.y;
be.position = BattleHex::INVALID;
owner->battleEffects.push_back(be);
}
be.currentFrame = 0;
be.maxFrame = be.anim->ourImages.size();
be.x = i * anim->width + owner->pos.x;
be.y = j * anim->height + owner->pos.y;
be.position = BattleHex::INVALID;
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]);
be.anim = CDefHandler::giveDef(customAnim);
if (Vflip)
{
@ -953,28 +952,31 @@ bool CSpellEffectAnimation::init()
be.currentFrame = 0;
be.maxFrame = be.anim->ourImages.size();
if(effect == 1)
be.maxFrame = 3;
//todo: lightning anim frame count override
// if(effect == 1)
// be.maxFrame = 3;
switch (effect)
if(x == -1)
{
be.x = tilePos.x + tilePos.w/2 - be.anim->width/2;
}
else
{
case ui32(-1):
be.x = x;
}
if(y == -1)
{
if(alignToBottom)
be.y = tilePos.y + tilePos.h - be.anim->height;
else
be.y = tilePos.y - be.anim->height/2;
}
else
{
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;
be.y = tilePos.y - be.anim->height/2;
break;
}
// 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;
be.position = destTile;
owner->battleEffects.push_back(be);
}
else //there is nothing to play
{
endAnim();
return false;
}
}
//battleEffects
return true;

View File

@ -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(){};
};

View File

@ -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:
}
//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 = (angle < 0);
if(Vflip)
angle = -angle;
std::string animToDisplay = spell.animationInfo.selectProjectile(angle);
if(!animToDisplay.empty())
{
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
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;
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];
//displaying animation
CDefEssential * animDef = CDefHandler::giveDefEss(animToDisplay);
double diffX = (destcoord.x - srccoord.x)*(destcoord.x - srccoord.x);
@ -1288,31 +1285,26 @@ void CBattleInterface::spellCast( const BattleSpellCast * sc )
int dy = (destcoord.y - srccoord.y - animDef->ourImages[0].bitmap->h)/steps;
delete animDef;
addNewAnim(new CSpellEffectAnimation(this, animToDisplay, srccoord.x, srccoord.y, dx, dy, Vflip));
addNewAnim(new CSpellEffectAnimation(this, animToDisplay, srccoord.x, srccoord.y, dx, dy, Vflip));
}
}
waitForAnims();
displaySpellHit(spellID, sc->tile);
//queuing affect /resist animation
for (auto & elem : sc->affectedCres)
{
BattleHex position = curInt->cb->battleGetStackByID(elem, false)->position;
if(vstd::contains(sc->resisted,elem))
displayEffect(78, position);
else
displaySpellEffect(spellID, position);
}
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
{
displayEffect(1, curInt->cb->battleGetStackByID(elem, false)->position);
displayEffect(spell.mainEffectAnim, curInt->cb->battleGetStackByID(elem, false)->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)
{
displayEffect(spell.mainEffectAnim, curInt->cb->battleGetStackByID(elem, false)->position);
}
break;
switch(sc->id)
{
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]) %

View File

@ -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);

View File

@ -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)

View File

@ -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" ] }
]

View File

@ -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
}
},
@ -114,18 +171,14 @@
"type": "object",
"description": "Chance in % to gain for faction. NOTE: this field is merged with faction config",
"additionalProperties" : {
"type": "number",
"minimum" : 0
}
"type": "number",
"minimum" : 0
}
},
"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,

View File

@ -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"
},

View File

@ -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"
},

View File

@ -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
}
},
}
}

View File

@ -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"
},

View File

@ -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"
},

View File

@ -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;

View File

@ -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

View File

@ -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);
}
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);
caster = battleGetFightingHero(playerToSide(player));
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,93 +1891,126 @@ 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;
for(const CSpell *spell : VLC->spellh->objects)
//This is complete list. No spells from mods.
//todo: this should be Spellbook of caster Stack
static const std::set<SpellID> allPossibleSpells =
{
if (spell->isPositive() && !spell->isRisingSpell()) //only positive and not rising
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;
auto getAliveEnemy = [=](const std::function<bool(const CStack * )> & pred)
{
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)
continue;
return pred(stack) && stack->owner != subject->owner && stack->alive();
});
};
switch (spell->id)
{
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
{
return stack->owner != subject->owner && !stack->shots;
});
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;
if (!walker)
continue;
}
break;
case SpellID::AIR_SHIELD: //only against active shooters
switch (spellID)
{
case SpellID::SHIELD:
case SpellID::FIRE_SHIELD: // not if all enemy units are shooters
{
auto walker = getAliveEnemy([&](const CStack * stack) //look for enemy, non-shooting stack
{
return !stack->shots;
});
auto shooter = getStackIf([&](const CStack *stack) //look for enemy, non-shooting stack
{
return stack->owner != subject->owner && stack->hasBonusOfType(Bonus::SHOOTER) && stack->shots;
});
if (!shooter)
continue;
}
break;
case SpellID::ANTI_MAGIC:
case SpellID::MAGIC_MIRROR:
{
if (!battleHasHero(subject->attackerOwned)) //only if there is enemy hero
continue;
}
break;
case SpellID::CURE: //only damaged units - what about affected by curse?
{
if (subject->firstHPleft >= subject->MaxHealth())
continue;
}
break;
case SpellID::BLOODLUST:
{
if (subject->shots) //if can shoot - only if enemy uits are adjacent
continue;
}
break;
case SpellID::PRECISION:
{
if (!(subject->hasBonusOfType(Bonus::SHOOTER) && subject->shots))
continue;
}
break;
case SpellID::SLAYER://only if monsters are present
{
auto kingMonster = getStackIf([&](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);
});
if (!kingMonster)
continue;
}
break;
case SpellID::TELEPORT: //issue 1928
case SpellID::CLONE: //not allowed
continue;
break;
if (!walker)
continue;
}
possibleSpells.push_back(spell->id);
break;
case SpellID::AIR_SHIELD: //only against active shooters
{
auto shooter = getAliveEnemy([&](const CStack * stack) //look for enemy, non-shooting stack
{
return stack->hasBonusOfType(Bonus::SHOOTER) && stack->shots;
});
if (!shooter)
continue;
}
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:
{
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
{
//do not cast on affected by debuffs
if (subject->firstHPleft >= subject->MaxHealth())
continue;
}
break;
case SpellID::BLOODLUST:
{
if (subject->shots) //if can shoot - only if enemy uits are adjacent
continue;
}
break;
case SpellID::PRECISION:
{
if (!(subject->hasBonusOfType(Bonus::SHOOTER) && subject->shots))
continue;
}
break;
case SpellID::SLAYER://only if monsters are present
{
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->hasBonus(isKing);
});
if (!kingMonster)
continue;
}
break;
}
beneficialSpells.push_back(spellID);
}
if(!possibleSpells.empty())
if(!beneficialSpells.empty())
{
return *RandomGeneratorUtil::nextItem(possibleSpells, gs->getRandomGenerator());
return *RandomGeneratorUtil::nextItem(beneficialSpells, gs->getRandomGenerator());
}
else
{

View File

@ -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 &params) const;
ReachabilityInfo makeBFS(const AccessibilityInfo &accessibility, const ReachabilityInfo::Parameters &params) 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

View File

@ -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));
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));
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
}
void CGameInfoCallback::getThievesGuildInfo(SThievesGuildInfo & thi, const CGObjectInstance * obj)

View File

@ -83,6 +83,7 @@ set(lib_SRCS
VCMI_Lib.cpp
VCMIDirs.cpp
IHandlerBase.cpp
SpellMechanics.cpp
IGameCallback.cpp
CGameInfoCallback.cpp

View File

@ -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

View File

@ -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);

View File

@ -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
*
@ -23,113 +31,55 @@
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;
}
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;
}
{
tmp = ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
stop = 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 &center, const int3 &pos)
@ -523,6 +680,7 @@ bool DLL_LINKAGE isInScreenRange(const int3 &center, 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);
@ -682,11 +820,11 @@ CSpell * CSpellHandler::loadFromJson(const JsonNode& json)
logGlobal->traceStream() << __FUNCTION__ << ": loading spell " << spell->name;
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,14 +994,15 @@ CSpell * CSpellHandler::loadFromJson(const JsonNode& json)
CSpell::LevelInfo & levelObject = spell->levels[levelIndex];
const si32 levelPower = 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.range = levelNode["range"].String();
const si32 levelPower = levelObject.power = levelNode["power"].Float();
levelObject.description = levelNode["description"].String();
levelObject.cost = levelNode["cost"].Float();
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)

View File

@ -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
@ -84,12 +212,15 @@ 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,16 +230,36 @@ 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;
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;
h & levels;
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 &center, 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;
};

View File

@ -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];
}

View File

@ -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

View File

@ -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;
}
};
@ -1578,6 +1592,9 @@ struct BattleStackAdded : public CPackForClient //3017
int amount;
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)
{

View File

@ -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
hero->mana = val;
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++;
}
}
//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);
});
}
const CSpell * spell = SpellID(id).toSpell();
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

File diff suppressed because it is too large Load Diff

54
lib/SpellMechanics.h Normal file
View 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;
};

View File

@ -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" />

View File

@ -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" />

View File

@ -863,28 +863,18 @@ TExpType CGHeroInstance::calculateXp(TExpType exp) const
ui8 CGHeroInstance::getSpellSchoolLevel(const CSpell * spell, int *outSelectedSchool) const
{
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; \
} \
}
TRY_SCHOOL(fire, 0, 1)
TRY_SCHOOL(air, 1, 0)
TRY_SCHOOL(water, 2, 2)
TRY_SCHOOL(earth, 3, 3)
#undef TRY_SCHOOL
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;
}
});
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
if (hasBonusOfType(Bonus::MAXED_SPELL, spell->id))//hero specialty (Daremyth, Melodia)
@ -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);
}
/**

View File

@ -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?
{
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?
std::set<const CStack*> attackedCreatures = gs->curB->getAffectedCreatures(SpellID(bonus->subtype).toSpell(), bonus->val, att->owner, targetHex);
if (bonus && (bat.shot())) //TODO: make it work in melee?
{
//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);
//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,52 +3996,49 @@ 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
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)
{
ui8 skill = h->getSpellSchoolLevel(s); //skill level
ESpellCastProblem::ESpellCastProblem escp = gs->curB->battleCanCastThisSpell(h->tempOwner, s, ECastingMode::HERO_CASTING);
if(escp != ESpellCastProblem::OK)
{
logGlobal->warnStream() << "Spell cannot be cast!";
logGlobal->warnStream() << "Problem : " << escp;
return false;
}
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);
sendAndApply(&end_action);
if( !gs->curB->battleGetStackByID(gs->curB->activeStack, true))
{
battleMadeAction.setn(true);
}
checkForBattleEnd();
if(battleResult.get())
{
battleMadeAction.setn(true);
//battle will be ended by startBattle function
//endBattle(gs->curB->tile, gs->curB->heroes[0], gs->curB->heroes[1]);
}
return true;
}
else
{
logGlobal->warnStream() << "Spell " << s->name << " is not yet supported!";
logGlobal->warnStream() << "Spell cannot be cast!";
logGlobal->warnStream() << "Problem : " << escp;
return false;
}
StartAction start_action(ba);
sendAndApply(&start_action); //start spell casting
s->battleCast(spellEnv, parameters);
sendAndApply(&end_action);
if( !gs->curB->battleGetStackByID(gs->curB->activeStack, true))
{
battleMadeAction.setn(true);
}
checkForBattleEnd();
if(battleResult.get())
{
battleMadeAction.setn(true);
//battle will be ended by startBattle function
//endBattle(gs->curB->tile, gs->curB->heroes[0], gs->curB->heroes[1]);
}
return true;
}
}
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;
@ -5853,10 +5404,28 @@ void CGameHandler::runBattle()
auto h = gs->curB->battleGetFightingHero(i);
if(h && h->hasBonusOfType(Bonus::OPENING_BATTLE_SPELL))
{
TBonusListPtr bl = h->getBonuses(Selector::type(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);
}

View File

@ -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;