mirror of
https://github.com/vcmi/vcmi.git
synced 2025-01-30 04:30:42 +02:00
Merge branch 'SpellsRefactoring6' into develop
This commit is contained in:
commit
6374eaef7a
@ -283,7 +283,9 @@ void CDefenceAnimation::endAnim()
|
||||
|
||||
CDummyAnimation::CDummyAnimation(CBattleInterface * _owner, int howManyFrames)
|
||||
: CBattleAnimation(_owner), counter(0), howMany(howManyFrames)
|
||||
{}
|
||||
{
|
||||
logAnim->debugStream() << "Created dummy animation for " << howManyFrames <<" frames";
|
||||
}
|
||||
|
||||
bool CDummyAnimation::init()
|
||||
{
|
||||
|
@ -360,7 +360,7 @@ CBattleInterface::CBattleInterface(const CCreatureSet * army1, const CCreatureSe
|
||||
{
|
||||
idToObstacle[ID] = CDefHandler::giveDef(elem->getInfo().defName);
|
||||
for(auto & _n : idToObstacle[ID]->ourImages)
|
||||
{
|
||||
{
|
||||
CSDL_Ext::setDefaultColorKey(_n.bitmap);
|
||||
}
|
||||
}
|
||||
@ -1240,15 +1240,9 @@ void CBattleInterface::displayBattleFinished()
|
||||
void CBattleInterface::spellCast( const BattleSpellCast * sc )
|
||||
{
|
||||
const SpellID spellID(sc->id);
|
||||
const CSpell &spell = * spellID.toSpell();
|
||||
const std::string & spellName = spell.name;
|
||||
const CSpell & spell = * spellID.toSpell();
|
||||
|
||||
const std::string& castSoundPath = spell.getCastSound();
|
||||
|
||||
std::string casterName("Something");
|
||||
|
||||
if(sc->castedByHero)
|
||||
casterName = curInt->cb->battleGetHeroInfo(sc->side).name;
|
||||
const std::string & castSoundPath = spell.getCastSound();
|
||||
|
||||
if(!castSoundPath.empty())
|
||||
CCS->soundh->playSound(castSoundPath);
|
||||
@ -1262,19 +1256,16 @@ void CBattleInterface::spellCast( const BattleSpellCast * sc )
|
||||
const CStack * casterStack = curInt->cb->battleGetStackByID(casterStackID);
|
||||
if(casterStack != nullptr)
|
||||
{
|
||||
casterName = casterStack->type->namePl;
|
||||
srccoord = CClickableHex::getXYUnitAnim(casterStack->position, casterStack, this);
|
||||
srccoord = CClickableHex::getXYUnitAnim(casterStack->position, casterStack, this);
|
||||
srccoord.x += 250;
|
||||
srccoord.y += 240;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: play custom cast animation
|
||||
{
|
||||
//todo: play custom cast animation
|
||||
displaySpellCast(spellID, BattleHex::INVALID, false);
|
||||
|
||||
}
|
||||
|
||||
//playing projectile animation
|
||||
if(sc->tile.isValid())
|
||||
{
|
||||
@ -1304,13 +1295,13 @@ void CBattleInterface::spellCast( const BattleSpellCast * sc )
|
||||
delete animDef;
|
||||
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)
|
||||
|
||||
//queuing affect /resist animation
|
||||
for (auto & elem : sc->affectedCres)
|
||||
{
|
||||
BattleHex position = curInt->cb->battleGetStackByID(elem, false)->position;
|
||||
|
||||
@ -1320,147 +1311,14 @@ void CBattleInterface::spellCast( const BattleSpellCast * sc )
|
||||
displaySpellEffect(spellID, position);
|
||||
}
|
||||
|
||||
switch(sc->id)
|
||||
{
|
||||
case SpellID::SUMMON_FIRE_ELEMENTAL:
|
||||
case SpellID::SUMMON_EARTH_ELEMENTAL:
|
||||
case SpellID::SUMMON_WATER_ELEMENTAL:
|
||||
case SpellID::SUMMON_AIR_ELEMENTAL:
|
||||
case SpellID::CLONE:
|
||||
case SpellID::REMOVE_OBSTACLE:
|
||||
addNewAnim(new CDummyAnimation(this, 2)); //interface won't return until animation is played. TODO: make it smarter?
|
||||
break;
|
||||
} //switch(sc->id)
|
||||
|
||||
//displaying message in console
|
||||
bool customSpell = false;
|
||||
if(sc->affectedCres.size() == 1)
|
||||
{
|
||||
const CStack * attackedStack = curInt->cb->battleGetStackByID(*sc->affectedCres.begin(), false);
|
||||
std::vector<std::string> logLines;
|
||||
|
||||
spell.prepareBattleLog(curInt->cb.get(), sc, logLines);
|
||||
|
||||
for(auto line : logLines)
|
||||
console->addText(line);
|
||||
|
||||
const std::string attackedName = attackedStack->getName();
|
||||
const std::string attackedNameSing = attackedStack->getCreature()->nameSing;
|
||||
const std::string attackedNamePl = attackedStack->getCreature()->namePl;
|
||||
|
||||
std::string text = CGI->generaltexth->allTexts[195];
|
||||
if(sc->castedByHero)
|
||||
{
|
||||
boost::algorithm::replace_first(text, "%s", casterName);
|
||||
boost::algorithm::replace_first(text, "%s", spellName);
|
||||
boost::algorithm::replace_first(text, "%s", attackedNamePl); //target
|
||||
}
|
||||
else
|
||||
{
|
||||
auto getPluralText = [attackedStack](const int baseTextID) -> std::string
|
||||
{
|
||||
return CGI->generaltexth->allTexts[(attackedStack->count > 1 ? baseTextID+1 : baseTextID)];
|
||||
};
|
||||
|
||||
bool plural = false; //add singular / plural form of creature text if this is true
|
||||
int textID = 0;
|
||||
switch(sc->id)
|
||||
{
|
||||
case SpellID::STONE_GAZE:
|
||||
customSpell = true;
|
||||
plural = true;
|
||||
textID = 558;
|
||||
break;
|
||||
case SpellID::POISON:
|
||||
customSpell = true;
|
||||
plural = true;
|
||||
textID = 561;
|
||||
break;
|
||||
case SpellID::BIND:
|
||||
customSpell = true;
|
||||
text = CGI->generaltexth->allTexts[560];
|
||||
boost::algorithm::replace_first(text, "%s", attackedNamePl);
|
||||
break;//Roots and vines bind the %s to the ground!
|
||||
case SpellID::DISEASE:
|
||||
customSpell = true;
|
||||
plural = true;
|
||||
textID = 553;
|
||||
break;
|
||||
case SpellID::PARALYZE:
|
||||
customSpell = true;
|
||||
plural = true;
|
||||
textID = 563;
|
||||
break;
|
||||
case SpellID::AGE:
|
||||
{
|
||||
customSpell = true;
|
||||
text = getPluralText(551);
|
||||
boost::algorithm::replace_first(text, "%s", attackedName);
|
||||
//The %s shrivel with age, and lose %d hit points."
|
||||
TBonusListPtr bl = attackedStack->getBonuses(Selector::type(Bonus::STACK_HEALTH));
|
||||
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;
|
||||
case SpellID::THUNDERBOLT:
|
||||
text = CGI->generaltexth->allTexts[367];
|
||||
boost::algorithm::replace_first(text, "%s", attackedNamePl);
|
||||
console->addText(text);
|
||||
text = CGI->generaltexth->allTexts[343].substr(1, CGI->generaltexth->allTexts[343].size() - 1); //Does %d points of damage.
|
||||
boost::algorithm::replace_first(text, "%d", boost::lexical_cast<std::string>(sc->dmgToDisplay)); //no more text afterwards
|
||||
console->addText(text);
|
||||
customSpell = true;
|
||||
text = ""; //yeah, it's a terrible mess
|
||||
break;
|
||||
case SpellID::DISPEL_HELPFUL_SPELLS:
|
||||
text = CGI->generaltexth->allTexts[555];
|
||||
boost::algorithm::replace_first(text, "%s", attackedNamePl);
|
||||
customSpell = true;
|
||||
break;
|
||||
case SpellID::DEATH_STARE:
|
||||
customSpell = true;
|
||||
if (sc->dmgToDisplay)
|
||||
{
|
||||
if (sc->dmgToDisplay > 1)
|
||||
{
|
||||
text = CGI->generaltexth->allTexts[119]; //%d %s die under the terrible gaze of the %s.
|
||||
boost::algorithm::replace_first(text, "%d", boost::lexical_cast<std::string>(sc->dmgToDisplay));
|
||||
boost::algorithm::replace_first(text, "%s", attackedNamePl);
|
||||
}
|
||||
else
|
||||
{
|
||||
text = CGI->generaltexth->allTexts[118]; //One %s dies under the terrible gaze of the %s.
|
||||
boost::algorithm::replace_first(text, "%s", attackedNameSing);
|
||||
}
|
||||
boost::algorithm::replace_first(text, "%s", casterName); //casting stack
|
||||
}
|
||||
else
|
||||
text = "";
|
||||
break;
|
||||
default:
|
||||
text = CGI->generaltexth->allTexts[565]; //The %s casts %s
|
||||
boost::algorithm::replace_first(text, "%s", casterName); //casting stack
|
||||
|
||||
}
|
||||
if (plural)
|
||||
{
|
||||
text = getPluralText(textID);
|
||||
boost::algorithm::replace_first(text, "%s", attackedName);
|
||||
}
|
||||
}
|
||||
if (!customSpell && !sc->dmgToDisplay)
|
||||
boost::algorithm::replace_first(text, "%s", spellName); //simple spell name
|
||||
if (text.size())
|
||||
console->addText(text);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::string text = CGI->generaltexth->allTexts[196];
|
||||
boost::algorithm::replace_first(text, "%s", casterName);
|
||||
boost::algorithm::replace_first(text, "%s", spellName);
|
||||
console->addText(text);
|
||||
}
|
||||
if(sc->dmgToDisplay && !customSpell)
|
||||
{
|
||||
std::string dmgInfo = CGI->generaltexth->allTexts[376];
|
||||
boost::algorithm::replace_first(dmgInfo, "%s", spellName); //simple spell name
|
||||
boost::algorithm::replace_first(dmgInfo, "%d", boost::lexical_cast<std::string>(sc->dmgToDisplay));
|
||||
console->addText(dmgInfo); //todo: casualties (?)
|
||||
}
|
||||
waitForAnims();
|
||||
//mana absorption
|
||||
if(sc->manaGained > 0)
|
||||
@ -1517,14 +1375,14 @@ void CBattleInterface::castThisSpell(SpellID spellID)
|
||||
assert(castingHero); // code below assumes non-null hero
|
||||
sp = spellID.toSpell();
|
||||
spellSelMode = ANY_LOCATION;
|
||||
|
||||
|
||||
const CSpell::TargetInfo ti = sp->getTargetInfo(castingHero->getSpellSchoolLevel(sp));
|
||||
|
||||
|
||||
if(ti.massive || ti.type == CSpell::NO_TARGET)
|
||||
spellSelMode = NO_LOCATION;
|
||||
spellSelMode = NO_LOCATION;
|
||||
else if(ti.type == CSpell::LOCATION && ti.clearAffected)
|
||||
{
|
||||
spellSelMode = FREE_LOCATION;
|
||||
spellSelMode = FREE_LOCATION;
|
||||
}
|
||||
else if(ti.type == CSpell::CREATURE)
|
||||
{
|
||||
@ -1532,11 +1390,11 @@ void CBattleInterface::castThisSpell(SpellID spellID)
|
||||
spellSelMode = selectionTypeByPositiveness(*sp);
|
||||
else
|
||||
spellSelMode = ANY_CREATURE;
|
||||
}
|
||||
}
|
||||
else if(ti.type == CSpell::OBSTACLE)
|
||||
{
|
||||
spellSelMode = OBSTACLE;
|
||||
}
|
||||
}
|
||||
|
||||
if (spellSelMode == NO_LOCATION) //user does not have to select location
|
||||
{
|
||||
@ -1558,33 +1416,57 @@ void CBattleInterface::displayEffect(ui32 effect, int destTile, bool areaEffect)
|
||||
addNewAnim(new CSpellEffectAnimation(this, effect, destTile, 0, 0, false));
|
||||
}
|
||||
|
||||
void CBattleInterface::displaySpellAnimation(const CSpell::TAnimation & animation, BattleHex destinationTile, bool areaEffect)
|
||||
{
|
||||
if(animation.pause > 0)
|
||||
{
|
||||
addNewAnim(new CDummyAnimation(this, animation.pause));
|
||||
}
|
||||
else
|
||||
{
|
||||
addNewAnim(new CSpellEffectAnimation(this, animation.resourceName, destinationTile, false, animation.verticalPosition == VerticalPosition::BOTTOM));
|
||||
}
|
||||
}
|
||||
|
||||
void CBattleInterface::displaySpellCast(SpellID spellID, BattleHex destinationTile, bool areaEffect)
|
||||
{
|
||||
const CSpell * spell = spellID.toSpell();
|
||||
|
||||
if(spell == nullptr)
|
||||
return;
|
||||
|
||||
for(const CSpell::TAnimation & animation : spell->animationInfo.cast)
|
||||
{
|
||||
displaySpellAnimation(animation, destinationTile, areaEffect);
|
||||
}
|
||||
}
|
||||
|
||||
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));
|
||||
{
|
||||
displaySpellAnimation(animation, destinationTile, areaEffect);
|
||||
}
|
||||
}
|
||||
|
||||
void CBattleInterface::displaySpellHit(SpellID spellID, BattleHex destinationTile, bool areaEffect)
|
||||
{
|
||||
const CSpell * spell = spellID.toSpell();
|
||||
|
||||
|
||||
if(spell == nullptr)
|
||||
return;
|
||||
|
||||
return;
|
||||
|
||||
for(const CSpell::TAnimation & animation : spell->animationInfo.hit)
|
||||
{
|
||||
addNewAnim(new CSpellEffectAnimation(this, animation.resourceName, destinationTile, false, animation.verticalPosition == VerticalPosition::BOTTOM));
|
||||
{
|
||||
displaySpellAnimation(animation, destinationTile, areaEffect);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void CBattleInterface::battleTriggerEffect(const BattleTriggerEffect & bte)
|
||||
{
|
||||
const CStack * stack = curInt->cb->battleGetStackByID(bte.stackID);
|
||||
@ -1712,8 +1594,8 @@ void CBattleInterface::endCastingSpell()
|
||||
{
|
||||
assert(spellDestSelectMode);
|
||||
|
||||
delete spellToCast;
|
||||
spellToCast = nullptr;
|
||||
vstd::clear_pointer(spellToCast);
|
||||
|
||||
sp = nullptr;
|
||||
spellDestSelectMode = false;
|
||||
CCS->curh->changeGraphic(ECursor::COMBAT, ECursor::COMBAT_POINTER);
|
||||
@ -1797,7 +1679,7 @@ void CBattleInterface::printConsoleAttacked( const CStack * defender, int dmg, i
|
||||
{
|
||||
if (attacker)
|
||||
formattedText.append(" ");
|
||||
|
||||
|
||||
boost::format txt;
|
||||
if(killed > 1)
|
||||
{
|
||||
@ -2218,7 +2100,7 @@ void CBattleInterface::handleHex(BattleHex myNumber, int eventType)
|
||||
{
|
||||
ui8 skill = 0;
|
||||
if (creatureCasting)
|
||||
skill = sactive->valOfBonuses(Selector::typeSubtype(Bonus::SPELLCASTER, SpellID::TELEPORT));
|
||||
skill = sactive->getSpellSchoolLevel(SpellID(SpellID::TELEPORT).toSpell());
|
||||
else
|
||||
skill = getActiveHero()->getSpellSchoolLevel (CGI->spellh->objects[spellToCast->additionalInfo]);
|
||||
//TODO: explicitely save power, skill
|
||||
@ -2283,7 +2165,7 @@ void CBattleInterface::handleHex(BattleHex myNumber, int eventType)
|
||||
|
||||
if (vstd::contains(localActions, selectedAction)) //try to use last selected action by default
|
||||
currentAction = selectedAction;
|
||||
else if (localActions.size()) //if not possible, select first available action 9they are sorted by suggested priority)
|
||||
else if (localActions.size()) //if not possible, select first available action (they are sorted by suggested priority)
|
||||
currentAction = localActions.front();
|
||||
else //no legal action possible
|
||||
{
|
||||
|
@ -1,12 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
|
||||
//#include "../../lib/CCreatureSet.h"
|
||||
#include "../../lib/ConstTransitivePtr.h" //may be reundant
|
||||
#include "../../lib/GameConstants.h"
|
||||
|
||||
#include "CBattleAnimations.h"
|
||||
|
||||
#include "../../lib/spells/CSpellHandler.h" //CSpell::TAnimation
|
||||
|
||||
/*
|
||||
* CBattleInterface.h, part of VCMI engine
|
||||
*
|
||||
@ -155,7 +155,7 @@ private:
|
||||
|
||||
shared_ptr<CPlayerInterface> tacticianInterface; //used during tactics mode, points to the interface of player with higher tactics (can be either attacker or defender in hot-seat), valid onloy for human players
|
||||
bool tacticsMode;
|
||||
bool stackCanCastSpell; //if true, active stack could possibly cats some target spell
|
||||
bool stackCanCastSpell; //if true, active stack could possibly cast some target spell
|
||||
bool creatureCasting; //if true, stack currently aims to cats a spell
|
||||
bool spellDestSelectMode; //if true, player is choosing destination for his spell - only for GUI / console
|
||||
PossibleActions spellSelMode;
|
||||
@ -319,8 +319,12 @@ public:
|
||||
void battleStacksEffectsSet(const SetStackEffect & sse); //called when a specific effect is set to stacks
|
||||
void castThisSpell(SpellID spellID); //called when player has chosen a spell from spellbook
|
||||
void displayEffect(ui32 effect, int destTile, bool areaEffect = true); //displays custom effect on the battlefield
|
||||
|
||||
void displaySpellCast(SpellID spellID, BattleHex destinationTile, bool areaEffect = true); //displays spell`s cast animation
|
||||
void displaySpellEffect(SpellID spellID, BattleHex destinationTile, bool areaEffect = true); //displays spell`s affected animation
|
||||
void displaySpellHit(SpellID spellID, BattleHex destinationTile, bool areaEffect = true); //displays spell`s affected animation
|
||||
|
||||
void displaySpellAnimation(const CSpell::TAnimation & animation, BattleHex destinationTile, bool areaEffect = true);
|
||||
|
||||
void battleTriggerEffect(const BattleTriggerEffect & bte);
|
||||
void setBattleCursor(const int myNumber); //really complex and messy, sets attackingHex
|
||||
|
@ -69,6 +69,7 @@ void CBattleConsole::showAll(SDL_Surface * to)
|
||||
|
||||
bool CBattleConsole::addText(const std::string & text)
|
||||
{
|
||||
logGlobal->traceStream() <<"CBattleConsole message: "<<text;
|
||||
if(text.size()>70)
|
||||
return false; //text too long!
|
||||
int firstInToken = 0;
|
||||
|
@ -12,6 +12,10 @@
|
||||
"type": "array",
|
||||
"items":{
|
||||
"anyOf":[
|
||||
{
|
||||
//dummy animation, pause, Value - frame count
|
||||
"type": "number"
|
||||
},
|
||||
{
|
||||
//assumed verticalPosition: top
|
||||
"type": "string",
|
||||
|
@ -242,7 +242,9 @@
|
||||
"removeObstacle" : {
|
||||
"index" : 64,
|
||||
"targetType" : "OBSTACLE",
|
||||
|
||||
"animation":{
|
||||
"cast":[2]
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "REMOVEOB"
|
||||
},
|
||||
@ -258,7 +260,9 @@
|
||||
"clone" : {
|
||||
"index" : 65,
|
||||
"targetType" : "CREATURE",
|
||||
|
||||
"animation":{
|
||||
"cast":[2]
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "CLONE"
|
||||
},
|
||||
@ -278,7 +282,9 @@
|
||||
"fireElemental" : {
|
||||
"index" : 66,
|
||||
"targetType" : "NO_TARGET",
|
||||
|
||||
"animation":{
|
||||
"cast":[2]
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "SUMNELM"
|
||||
},
|
||||
@ -294,7 +300,9 @@
|
||||
"earthElemental" : {
|
||||
"index" : 67,
|
||||
"targetType" : "NO_TARGET",
|
||||
|
||||
"animation":{
|
||||
"cast":[2]
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "SUMNELM"
|
||||
},
|
||||
@ -310,7 +318,9 @@
|
||||
"waterElemental" : {
|
||||
"index" : 68,
|
||||
"targetType" : "NO_TARGET",
|
||||
|
||||
"animation":{
|
||||
"cast":[2]
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "SUMNELM"
|
||||
},
|
||||
@ -326,7 +336,9 @@
|
||||
"airElemental" : {
|
||||
"index" : 69,
|
||||
"targetType" : "NO_TARGET",
|
||||
|
||||
"animation":{
|
||||
"cast":[2]
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "SUMNELM"
|
||||
},
|
||||
|
@ -1152,6 +1152,21 @@ bool CStack::canBeHealed() const
|
||||
&& !hasBonusOfType(Bonus::SIEGE_WEAPON);
|
||||
}
|
||||
|
||||
ui8 CStack::getSpellSchoolLevel(const CSpell * spell, int * outSelectedSchool) const
|
||||
{
|
||||
int skill = valOfBonuses(Selector::typeSubtype(Bonus::SPELLCASTER, spell->id));
|
||||
|
||||
vstd::abetween(skill, 0, 3);
|
||||
|
||||
return skill;
|
||||
}
|
||||
|
||||
ui32 CStack::getSpellBonus(const CSpell * spell, ui32 base, const CStack * affectedStack) const
|
||||
{
|
||||
//stacks does not have spellpower etc. (yet?)
|
||||
return base;
|
||||
}
|
||||
|
||||
bool CMP_stack::operator()( const CStack* a, const CStack* b )
|
||||
{
|
||||
switch(phase)
|
||||
|
@ -1,5 +1,14 @@
|
||||
#pragma once
|
||||
/*
|
||||
* BattleState.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 "BattleHex.h"
|
||||
#include "HeroBonus.h"
|
||||
@ -12,16 +21,7 @@
|
||||
#include "GameConstants.h"
|
||||
#include "CBattleCallback.h"
|
||||
#include "int3.h"
|
||||
|
||||
/*
|
||||
* BattleState.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
|
||||
*
|
||||
*/
|
||||
#include "spells/Magic.h"
|
||||
|
||||
class CGHeroInstance;
|
||||
class CStack;
|
||||
@ -159,7 +159,7 @@ struct DLL_LINKAGE BattleInfo : public CBonusSystemNode, public CBattleInfoCallb
|
||||
static int battlefieldTypeToTerrain(int bfieldType); //converts above to ERM BI format
|
||||
};
|
||||
|
||||
class DLL_LINKAGE CStack : public CBonusSystemNode, public CStackBasicDescriptor
|
||||
class DLL_LINKAGE CStack : public CBonusSystemNode, public CStackBasicDescriptor, public ISpellCaster
|
||||
{
|
||||
public:
|
||||
const CStackInstance *base; //garrison slot from which stack originates (nullptr for war machines, summoned cres, etc)
|
||||
@ -222,6 +222,10 @@ public:
|
||||
std::pair<int,int> countKilledByAttack(int damageReceived) const; //returns pair<killed count, new left HP>
|
||||
void prepareAttacked(BattleStackAttacked &bsa, CRandomGenerator & rand, boost::optional<int> customCount = boost::none) const; //requires bsa.damageAmout filled
|
||||
|
||||
///ISpellCaster
|
||||
ui8 getSpellSchoolLevel(const CSpell * spell, int *outSelectedSchool = nullptr) const override;
|
||||
ui32 getSpellBonus(const CSpell * spell, ui32 base, const CStack * affectedStack) const override;
|
||||
|
||||
template <typename Handler> void serialize(Handler &h, const int version)
|
||||
{
|
||||
assert(isIndependentNode());
|
||||
|
@ -206,12 +206,24 @@ std::string CLegacyConfigParser::extractQuotedString()
|
||||
{
|
||||
ret += extractQuotedPart();
|
||||
|
||||
// double quote - add it to string and continue unless
|
||||
// line terminated using tabulation
|
||||
if (curr < end && *curr == '\"' && *curr != '\t')
|
||||
// double quote - add it to string and continue quoted part
|
||||
if (curr < end && *curr == '\"')
|
||||
{
|
||||
ret += '\"';
|
||||
}
|
||||
//extract normal part
|
||||
else if(curr < end && *curr != '\t' && *curr != '\r')
|
||||
{
|
||||
char * begin = curr;
|
||||
|
||||
while (curr < end && *curr != '\t' && *curr != '\r' && *curr != '\"')//find end of string or next quoted part start
|
||||
curr++;
|
||||
|
||||
ret += std::string(begin, curr);
|
||||
|
||||
if(curr>=end || *curr != '\"')
|
||||
return ret;
|
||||
}
|
||||
else // end of string
|
||||
return ret;
|
||||
}
|
||||
|
@ -27,8 +27,8 @@
|
||||
#include "mapping/CCampaignHandler.h" //for CCampaignState
|
||||
#include "rmg/CMapGenerator.h" // for CMapGenOptions
|
||||
|
||||
const ui32 version = 753;
|
||||
const ui32 minSupportedVersion = version;
|
||||
const ui32 version = 754;
|
||||
const ui32 minSupportedVersion = 753;
|
||||
|
||||
class CISer;
|
||||
class COSer;
|
||||
|
@ -1491,10 +1491,10 @@ struct BattleSpellCast : public CPackForClient//3009
|
||||
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)
|
||||
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
|
||||
bool castByHero; //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 & manaGained & tile & resisted & affectedCres & casterStack & castedByHero;
|
||||
h & dmgToDisplay & side & id & skill & manaGained & tile & resisted & affectedCres & casterStack & castByHero;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -311,6 +311,7 @@
|
||||
<Unit filename="spells/CreatureSpellMechanics.h" />
|
||||
<Unit filename="spells/ISpellMechanics.cpp" />
|
||||
<Unit filename="spells/ISpellMechanics.h" />
|
||||
<Unit filename="spells/Magic.h" />
|
||||
<Unit filename="spells/ViewSpellInt.cpp" />
|
||||
<Unit filename="spells/ViewSpellInt.h" />
|
||||
<Unit filename="vcmi_endian.h" />
|
||||
|
@ -21,6 +21,7 @@
|
||||
#include "../IGameCallback.h"
|
||||
#include "../CGameState.h"
|
||||
#include "../CCreatureHandler.h"
|
||||
#include "../BattleState.h"
|
||||
|
||||
///helpers
|
||||
static void showInfoDialog(const PlayerColor playerID, const ui32 txtID, const ui16 soundID)
|
||||
@ -883,6 +884,26 @@ ui8 CGHeroInstance::getSpellSchoolLevel(const CSpell * spell, int *outSelectedSc
|
||||
return skill;
|
||||
}
|
||||
|
||||
ui32 CGHeroInstance::getSpellBonus(const CSpell * spell, ui32 base, const CStack * affectedStack) const
|
||||
{
|
||||
//applying sorcery secondary skill
|
||||
|
||||
base *= (100.0 + valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, SecondarySkill::SORCERY)) / 100.0;
|
||||
base *= (100.0 + valOfBonuses(Bonus::SPELL_DAMAGE) + valOfBonuses(Bonus::SPECIFIC_SPELL_DAMAGE, spell->id.toEnum())) / 100.0;
|
||||
|
||||
spell->forEachSchool([&base, this](const SpellSchoolInfo & cnf, bool & stop)
|
||||
{
|
||||
base *= (100.0 + valOfBonuses(cnf.damagePremyBonus)) / 100.0;
|
||||
stop = true; //only bonus from one school is used
|
||||
});
|
||||
|
||||
if (affectedStack && affectedStack->getCreature()->level) //Hero specials like Solmyr, Deemer
|
||||
base *= (100. + ((valOfBonuses(Bonus::SPECIAL_SPELL_LEV, spell->id.toEnum()) * level) / affectedStack->getCreature()->level)) / 100.0;
|
||||
|
||||
return base;
|
||||
}
|
||||
|
||||
|
||||
bool CGHeroInstance::canCastThisSpell(const CSpell * spell) const
|
||||
{
|
||||
return spell->isCastableBy(this, nullptr !=getArt(ArtifactPosition::SPELLBOOK), spells);
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
#include "CObjectHandler.h"
|
||||
#include "CArmedInstance.h"
|
||||
#include "../spells/Magic.h"
|
||||
|
||||
#include "../CArtHandler.h" // For CArtifactSet
|
||||
#include "../CRandomGenerator.h"
|
||||
@ -35,7 +36,7 @@ public:
|
||||
};
|
||||
|
||||
|
||||
class DLL_LINKAGE CGHeroInstance : public CArmedInstance, public IBoatGenerator, public CArtifactSet
|
||||
class DLL_LINKAGE CGHeroInstance : public CArmedInstance, public IBoatGenerator, public CArtifactSet, public ISpellCaster
|
||||
{
|
||||
public:
|
||||
enum ECanDig
|
||||
@ -162,14 +163,13 @@ public:
|
||||
int maxMovePoints(bool onLand) const;
|
||||
int movementPointsAfterEmbark(int MPsBefore, int basicCost, bool disembark = false) const;
|
||||
|
||||
//int getSpellSecLevel(int spell) const; //returns level of secondary ability (fire, water, earth, air magic) known to this hero and applicable to given spell; -1 if error
|
||||
static int3 convertPosition(int3 src, bool toh3m); //toh3m=true: manifest->h3m; toh3m=false: h3m->manifest
|
||||
double getFightingStrength() const; // takes attack / defense skill into account
|
||||
double getMagicStrength() const; // takes knowledge / spell power skill into account
|
||||
double getHeroStrength() const; // includes fighting and magic strength
|
||||
ui64 getTotalStrength() const; // includes fighting strength and army strength
|
||||
TExpType calculateXp(TExpType exp) const; //apply learning skill
|
||||
ui8 getSpellSchoolLevel(const CSpell * spell, int *outSelectedSchool = nullptr) const; //returns level on which given spell would be cast by this hero (0 - none, 1 - basic etc); optionally returns number of selected school by arg - 0 - air magic, 1 - fire magic, 2 - water magic, 3 - earth magic,
|
||||
|
||||
bool canCastThisSpell(const CSpell * spell) const; //determines if this hero can cast given spell; takes into account existing spell in spellbook, existing spellbook and artifact bonuses
|
||||
CStackBasicDescriptor calculateNecromancy (const BattleResult &battleResult) const;
|
||||
void showNecromancyDialog(const CStackBasicDescriptor &raisedStack) const;
|
||||
@ -198,13 +198,18 @@ public:
|
||||
|
||||
CGHeroInstance();
|
||||
virtual ~CGHeroInstance();
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
|
||||
///ArtBearer
|
||||
ArtBearer::ArtBearer bearerType() const override;
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
///IBonusBearer
|
||||
CBonusSystemNode *whereShouldBeAttached(CGameState *gs) override;
|
||||
std::string nodeName() const override;
|
||||
|
||||
///ISpellCaster
|
||||
ui8 getSpellSchoolLevel(const CSpell * spell, int *outSelectedSchool = nullptr) const override;
|
||||
ui32 getSpellBonus(const CSpell * spell, ui32 base, const CStack * affectedStack) const override;
|
||||
|
||||
void deserializationFix();
|
||||
|
||||
void initObj() override;
|
||||
|
@ -127,26 +127,15 @@ ESpellCastProblem::ESpellCastProblem CloneMechanics::isImmuneByStack(const CGHer
|
||||
void CureMechanics::applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const
|
||||
{
|
||||
DefaultSpellMechanics::applyBattle(battle, packet);
|
||||
|
||||
for(auto stackID : packet->affectedCres)
|
||||
doDispell(battle, packet, [](const Bonus * b) -> bool
|
||||
{
|
||||
if(vstd::contains(packet->resisted, stackID))
|
||||
if(b->source == Bonus::SPELL_EFFECT)
|
||||
{
|
||||
logGlobal->errorStream() << "Resistance to positive spell CURE";
|
||||
continue;
|
||||
CSpell * sp = SpellID(b->sid).toSpell();
|
||||
return sp->isNegative();
|
||||
}
|
||||
|
||||
CStack *s = battle->getStack(stackID);
|
||||
s->popBonuses([&](const Bonus *b) -> bool
|
||||
{
|
||||
if(b->source == Bonus::SPELL_EFFECT)
|
||||
{
|
||||
CSpell * sp = SpellID(b->sid).toSpell();
|
||||
return sp->isNegative();
|
||||
}
|
||||
return false; //not a spell effect
|
||||
});
|
||||
}
|
||||
return false; //not a spell effect
|
||||
});
|
||||
}
|
||||
|
||||
///DispellMechanics
|
||||
@ -310,8 +299,8 @@ ESpellCastProblem::ESpellCastProblem HypnotizeMechanics::isImmuneByStack(const C
|
||||
//TODO: what with other creatures casting hypnotize, Faerie Dragons style?
|
||||
ui64 subjectHealth = (obj->count - 1) * obj->MaxHealth() + obj->firstHPleft;
|
||||
//apply 'damage' bonus for hypnotize, including hero specialty
|
||||
ui64 maxHealth = owner->calculateBonus(caster->getPrimSkillLevel(PrimarySkill::SPELL_POWER)
|
||||
* owner->power + owner->getPower(caster->getSpellSchoolLevel(owner)), caster, obj);
|
||||
ui64 maxHealth = caster->getSpellBonus(owner, caster->getPrimSkillLevel(PrimarySkill::SPELL_POWER)
|
||||
* owner->power + owner->getPower(caster->getSpellSchoolLevel(owner)), obj);
|
||||
if (subjectHealth > maxHealth)
|
||||
return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
|
||||
}
|
||||
@ -571,7 +560,7 @@ void SummonMechanics::applyBattleEffects(const SpellCastEnvironment * env, Battl
|
||||
bsa.pos = parameters.cb->getAvaliableHex(creatureToSummon, !(bool)parameters.casterSide); //TODO: unify it
|
||||
|
||||
//TODO stack casting -> probably power will be zero; set the proper number of creatures manually
|
||||
int percentBonus = parameters.caster ? parameters.caster->valOfBonuses(Bonus::SPECIFIC_SPELL_DAMAGE, owner->id.toEnum()) : 0;
|
||||
int percentBonus = parameters.casterHero ? parameters.casterHero->valOfBonuses(Bonus::SPECIFIC_SPELL_DAMAGE, owner->id.toEnum()) : 0;
|
||||
|
||||
bsa.amount = parameters.usedSpellPower
|
||||
* owner->getPower(parameters.spellLvl)
|
||||
|
@ -15,6 +15,8 @@
|
||||
#include "../NetPacks.h"
|
||||
#include "../BattleState.h"
|
||||
|
||||
#include "../CGeneralTextHandler.h"
|
||||
|
||||
namespace SRSLPraserHelpers
|
||||
{
|
||||
static int XYToHex(int x, int y)
|
||||
@ -120,7 +122,7 @@ namespace SRSLPraserHelpers
|
||||
///DefaultSpellMechanics
|
||||
void DefaultSpellMechanics::applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const
|
||||
{
|
||||
if (packet->castedByHero)
|
||||
if (packet->castByHero)
|
||||
{
|
||||
if (packet->side < 2)
|
||||
{
|
||||
@ -230,16 +232,16 @@ void DefaultSpellMechanics::battleCast(const SpellCastEnvironment * env, BattleS
|
||||
sc.skill = parameters.spellLvl;
|
||||
sc.tile = parameters.destination;
|
||||
sc.dmgToDisplay = 0;
|
||||
sc.castedByHero = nullptr != parameters.caster;
|
||||
sc.castByHero = nullptr != parameters.casterHero;
|
||||
sc.casterStack = (parameters.casterStack ? parameters.casterStack->ID : -1);
|
||||
sc.manaGained = 0;
|
||||
|
||||
int spellCost = 0;
|
||||
|
||||
//calculate spell cost
|
||||
if(parameters.caster)
|
||||
if(parameters.casterHero)
|
||||
{
|
||||
spellCost = parameters.cb->battleGetSpellCost(owner, parameters.caster);
|
||||
spellCost = parameters.cb->battleGetSpellCost(owner, parameters.casterHero);
|
||||
|
||||
if(parameters.secHero && parameters.mode == ECastingMode::HERO_CASTING) //handle mana channel
|
||||
{
|
||||
@ -259,7 +261,7 @@ void DefaultSpellMechanics::battleCast(const SpellCastEnvironment * env, BattleS
|
||||
//must be vector, as in Chain Lightning order matters
|
||||
std::vector<const CStack*> attackedCres; //CStack vector is somewhat more suitable than ID vector
|
||||
|
||||
auto creatures = owner->getAffectedStacks(parameters.cb, parameters.mode, parameters.casterColor, parameters.spellLvl, parameters.destination, parameters.caster);
|
||||
auto creatures = owner->getAffectedStacks(parameters.cb, parameters.mode, parameters.casterColor, parameters.spellLvl, parameters.destination, parameters.casterHero);
|
||||
std::copy(creatures.begin(), creatures.end(), std::back_inserter(attackedCres));
|
||||
|
||||
for (auto cre : attackedCres)
|
||||
@ -290,12 +292,12 @@ void DefaultSpellMechanics::battleCast(const SpellCastEnvironment * env, BattleS
|
||||
env->sendAndApply(&sc);
|
||||
|
||||
//spend mana
|
||||
if(parameters.caster)
|
||||
if(parameters.casterHero)
|
||||
{
|
||||
SetMana sm;
|
||||
sm.absolute = false;
|
||||
|
||||
sm.hid = parameters.caster->id;
|
||||
sm.hid = parameters.casterHero->id;
|
||||
sm.val = -spellCost;
|
||||
|
||||
env->sendAndApply(&sm);
|
||||
@ -353,9 +355,9 @@ void DefaultSpellMechanics::battleCast(const SpellCastEnvironment * env, BattleS
|
||||
mirrorParameters.spellLvl = 0;
|
||||
mirrorParameters.casterSide = 1-parameters.casterSide;
|
||||
mirrorParameters.casterColor = (attackedCre)->owner;
|
||||
mirrorParameters.caster = nullptr;
|
||||
mirrorParameters.casterHero = nullptr;
|
||||
mirrorParameters.destination = targetHex;
|
||||
mirrorParameters.secHero = parameters.caster;
|
||||
mirrorParameters.secHero = parameters.casterHero;
|
||||
mirrorParameters.mode = ECastingMode::MAGIC_MIRROR;
|
||||
mirrorParameters.casterStack = (attackedCre);
|
||||
mirrorParameters.selectedStack = nullptr;
|
||||
@ -367,6 +369,103 @@ void DefaultSpellMechanics::battleCast(const SpellCastEnvironment * env, BattleS
|
||||
}
|
||||
}
|
||||
|
||||
void DefaultSpellMechanics::battleLogSingleTarget(std::vector<std::string> & logLines, const BattleSpellCast * packet,
|
||||
const std::string & casterName, const CStack * attackedStack, bool & displayDamage) const
|
||||
{
|
||||
const std::string attackedName = attackedStack->getName();
|
||||
const std::string attackedNameSing = attackedStack->getCreature()->nameSing;
|
||||
const std::string attackedNamePl = attackedStack->getCreature()->namePl;
|
||||
|
||||
auto getPluralFormat = [attackedStack](const int baseTextID) -> boost::format
|
||||
{
|
||||
return boost::format(VLC->generaltexth->allTexts[(attackedStack->count > 1 ? baseTextID + 1 : baseTextID)]);
|
||||
};
|
||||
|
||||
auto logSimple = [&logLines, getPluralFormat, attackedName](const int baseTextID)
|
||||
{
|
||||
boost::format fmt = getPluralFormat(baseTextID);
|
||||
fmt % attackedName;
|
||||
logLines.push_back(fmt.str());
|
||||
};
|
||||
|
||||
auto logPlural = [&logLines, attackedNamePl](const int baseTextID)
|
||||
{
|
||||
boost::format fmt(VLC->generaltexth->allTexts[baseTextID]);
|
||||
fmt % attackedNamePl;
|
||||
logLines.push_back(fmt.str());
|
||||
};
|
||||
|
||||
displayDamage = false; //in most following cases damage info text is custom
|
||||
switch(owner->id)
|
||||
{
|
||||
case SpellID::STONE_GAZE:
|
||||
logSimple(558);
|
||||
break;
|
||||
case SpellID::POISON:
|
||||
logSimple(561);
|
||||
break;
|
||||
case SpellID::BIND:
|
||||
logPlural(560);//Roots and vines bind the %s to the ground!
|
||||
break;
|
||||
case SpellID::DISEASE:
|
||||
logSimple(553);
|
||||
break;
|
||||
case SpellID::PARALYZE:
|
||||
logSimple(563);
|
||||
break;
|
||||
case SpellID::AGE:
|
||||
{
|
||||
boost::format text = getPluralFormat(551);
|
||||
text % attackedName;
|
||||
//The %s shrivel with age, and lose %d hit points."
|
||||
TBonusListPtr bl = attackedStack->getBonuses(Selector::type(Bonus::STACK_HEALTH));
|
||||
const int fullHP = bl->totalValue();
|
||||
bl->remove_if(Selector::source(Bonus::SPELL_EFFECT, SpellID::AGE));
|
||||
text % (fullHP - bl->totalValue());
|
||||
logLines.push_back(text.str());
|
||||
}
|
||||
break;
|
||||
case SpellID::THUNDERBOLT:
|
||||
{
|
||||
logPlural(367);
|
||||
std::string text = VLC->generaltexth->allTexts[343].substr(1, VLC->generaltexth->allTexts[343].size() - 1); //Does %d points of damage.
|
||||
boost::algorithm::replace_first(text, "%d", boost::lexical_cast<std::string>(packet->dmgToDisplay)); //no more text afterwards
|
||||
logLines.push_back(text);
|
||||
}
|
||||
break;
|
||||
case SpellID::DISPEL_HELPFUL_SPELLS:
|
||||
logPlural(555);
|
||||
break;
|
||||
case SpellID::DEATH_STARE:
|
||||
if (packet->dmgToDisplay > 0)
|
||||
{
|
||||
std::string text;
|
||||
if (packet->dmgToDisplay > 1)
|
||||
{
|
||||
text = VLC->generaltexth->allTexts[119]; //%d %s die under the terrible gaze of the %s.
|
||||
boost::algorithm::replace_first(text, "%d", boost::lexical_cast<std::string>(packet->dmgToDisplay));
|
||||
boost::algorithm::replace_first(text, "%s", attackedNamePl);
|
||||
}
|
||||
else
|
||||
{
|
||||
text = VLC->generaltexth->allTexts[118]; //One %s dies under the terrible gaze of the %s.
|
||||
boost::algorithm::replace_first(text, "%s", attackedNameSing);
|
||||
}
|
||||
boost::algorithm::replace_first(text, "%s", casterName); //casting stack
|
||||
logLines.push_back(text);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
{
|
||||
boost::format text(VLC->generaltexth->allTexts[565]); //The %s casts %s
|
||||
text % casterName % owner->name;
|
||||
displayDamage = true;
|
||||
logLines.push_back(text.str());
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int DefaultSpellMechanics::calculateDuration(const CGHeroInstance * caster, int usedSpellPower) const
|
||||
{
|
||||
if(!caster)
|
||||
@ -402,7 +501,7 @@ ui32 DefaultSpellMechanics::calculateHealedHP(const CGHeroInstance* caster, cons
|
||||
healedHealth = (spellPowerSkill + sacrificedStack->MaxHealth() + levelPower) * sacrificedStack->count;
|
||||
else
|
||||
healedHealth = spellPowerSkill * owner->power + levelPower; //???
|
||||
healedHealth = owner->calculateBonus(healedHealth, caster, stack);
|
||||
healedHealth = caster->getSpellBonus(owner, healedHealth, stack);
|
||||
return std::min<ui32>(healedHealth, stack->MaxHealth() - stack->firstHPleft + (owner->isRisingSpell() ? stack->baseAmount * stack->MaxHealth() : 0));
|
||||
}
|
||||
|
||||
@ -434,7 +533,7 @@ void DefaultSpellMechanics::applyBattleEffects(const SpellCastEnvironment * env,
|
||||
if(spellDamage)
|
||||
bsa.damageAmount = spellDamage >> chainLightningModifier;
|
||||
else
|
||||
bsa.damageAmount = owner->calculateDamage(parameters.caster, attackedCre, parameters.spellLvl, parameters.usedSpellPower) >> chainLightningModifier;
|
||||
bsa.damageAmount = owner->calculateDamage(parameters.casterHero, attackedCre, parameters.spellLvl, parameters.usedSpellPower) >> chainLightningModifier;
|
||||
|
||||
ctx.sc.dmgToDisplay += bsa.damageAmount;
|
||||
|
||||
@ -462,7 +561,7 @@ void DefaultSpellMechanics::applyBattleEffects(const SpellCastEnvironment * env,
|
||||
Bonus pseudoBonus;
|
||||
pseudoBonus.sid = owner->id;
|
||||
pseudoBonus.val = parameters.spellLvl;
|
||||
pseudoBonus.turnsRemain = calculateDuration(parameters.caster, stackSpellPower ? stackSpellPower : parameters.usedSpellPower);
|
||||
pseudoBonus.turnsRemain = calculateDuration(parameters.casterHero, stackSpellPower ? stackSpellPower : parameters.usedSpellPower);
|
||||
CStack::stackEffectToFeature(sse.effect, pseudoBonus);
|
||||
if(owner->id == SpellID::SHIELD || owner->id == SpellID::AIR_SHIELD)
|
||||
{
|
||||
@ -473,8 +572,8 @@ void DefaultSpellMechanics::applyBattleEffects(const SpellCastEnvironment * env,
|
||||
sse.effect.back().additionalInfo = parameters.casterStack->ID; //we need to know who casted Bind
|
||||
}
|
||||
const Bonus * bonus = nullptr;
|
||||
if(parameters.caster)
|
||||
bonus = parameters.caster->getBonusLocalFirst(Selector::typeSubtype(Bonus::SPECIAL_PECULIAR_ENCHANT, owner->id));
|
||||
if(parameters.casterHero)
|
||||
bonus = parameters.casterHero->getBonusLocalFirst(Selector::typeSubtype(Bonus::SPECIAL_PECULIAR_ENCHANT, owner->id));
|
||||
//TODO does hero specialty should affects his stack casting spells?
|
||||
|
||||
si32 power = 0;
|
||||
@ -519,9 +618,9 @@ void DefaultSpellMechanics::applyBattleEffects(const SpellCastEnvironment * env,
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (parameters.caster && parameters.caster->hasBonusOfType(Bonus::SPECIAL_BLESS_DAMAGE, owner->id)) //TODO: better handling of bonus percentages
|
||||
if (parameters.casterHero && parameters.casterHero->hasBonusOfType(Bonus::SPECIAL_BLESS_DAMAGE, owner->id)) //TODO: better handling of bonus percentages
|
||||
{
|
||||
int damagePercent = parameters.caster->level * parameters.caster->valOfBonuses(Bonus::SPECIAL_BLESS_DAMAGE, owner->id.toEnum()) / tier;
|
||||
int damagePercent = parameters.casterHero->level * parameters.casterHero->valOfBonuses(Bonus::SPECIAL_BLESS_DAMAGE, owner->id.toEnum()) / tier;
|
||||
Bonus specialBonus = CStack::featureGenerator(Bonus::CREATURE_DAMAGE, 0, damagePercent, pseudoBonus.turnsRemain);
|
||||
specialBonus.valType = Bonus::PERCENT_TO_ALL;
|
||||
specialBonus.sid = owner->id;
|
||||
@ -568,7 +667,7 @@ void DefaultSpellMechanics::applyBattleEffects(const SpellCastEnvironment * env,
|
||||
}
|
||||
}
|
||||
else
|
||||
hi.healedHP = calculateHealedHP(parameters.caster, attackedCre, parameters.selectedStack); //Casted by hero
|
||||
hi.healedHP = calculateHealedHP(parameters.casterHero, attackedCre, parameters.selectedStack); //Casted by hero
|
||||
hi.lowLevelResurrection = (parameters.spellLvl <= 1) && (owner->id != SpellID::ANIMATE_DEAD);
|
||||
shr.healedStacks.push_back(hi);
|
||||
}
|
||||
@ -735,8 +834,11 @@ void DefaultSpellMechanics::doDispell(BattleInfo * battle, const BattleSpellCast
|
||||
for(auto stackID : packet->affectedCres)
|
||||
{
|
||||
if(vstd::contains(packet->resisted, stackID))
|
||||
{
|
||||
if(owner->isPositive())
|
||||
logGlobal->errorStream() <<"Resistance to positive spell " << owner->name;
|
||||
continue;
|
||||
|
||||
}
|
||||
CStack *s = battle->getStack(stackID);
|
||||
s->popBonuses(selector);
|
||||
}
|
||||
|
@ -48,6 +48,8 @@ public:
|
||||
bool adventureCast(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const override final;
|
||||
void battleCast(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters) const override;
|
||||
|
||||
void battleLogSingleTarget(std::vector<std::string> & logLines, const BattleSpellCast * packet,
|
||||
const std::string & casterName, const CStack * attackedStack, bool & displayDamage) const override;
|
||||
protected:
|
||||
virtual void applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const;
|
||||
|
||||
|
@ -26,6 +26,7 @@
|
||||
#include "../mapObjects/CGHeroInstance.h"
|
||||
#include "../BattleState.h"
|
||||
#include "../CBattleCallback.h"
|
||||
#include "../CGameState.h"
|
||||
|
||||
#include "ISpellMechanics.h"
|
||||
|
||||
@ -71,7 +72,7 @@ namespace SpellConfig
|
||||
}
|
||||
|
||||
BattleSpellCastParameters::BattleSpellCastParameters(const BattleInfo* cb)
|
||||
: spellLvl(0), destination(BattleHex::INVALID), casterSide(0),casterColor(PlayerColor::CANNOT_DETERMINE),caster(nullptr), secHero(nullptr),
|
||||
: spellLvl(0), destination(BattleHex::INVALID), casterSide(0),casterColor(PlayerColor::CANNOT_DETERMINE),casterHero(nullptr), secHero(nullptr),
|
||||
usedSpellPower(0),mode(ECastingMode::HERO_CASTING), casterStack(nullptr), selectedStack(nullptr), cb(cb)
|
||||
{
|
||||
|
||||
@ -169,29 +170,7 @@ 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 CSpell::calculateDamage(const ISpellCaster * caster, const CStack * affectedCreature, int spellSchoolLevel, int usedSpellPower) const
|
||||
{
|
||||
ui32 ret = 0; //value to return
|
||||
|
||||
@ -230,7 +209,9 @@ ui32 CSpell::calculateDamage(const CGHeroInstance * caster, const CStack * affec
|
||||
ret /= 100;
|
||||
}
|
||||
}
|
||||
ret = calculateBonus(ret, caster, affectedCreature);
|
||||
|
||||
if(nullptr != caster) //todo: make sure that caster always present
|
||||
ret = caster->getSpellBonus(this, ret, affectedCreature);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -551,6 +532,59 @@ ESpellCastProblem::ESpellCastProblem CSpell::isImmuneByStack(const CGHeroInstanc
|
||||
return ESpellCastProblem::OK;
|
||||
}
|
||||
|
||||
void CSpell::prepareBattleLog(const CBattleInfoCallback * cb, const BattleSpellCast * packet, std::vector<std::string> & logLines) const
|
||||
{
|
||||
bool displayDamage = true;
|
||||
|
||||
std::string casterName("Something"); //todo: localize
|
||||
|
||||
if(packet->castByHero)
|
||||
casterName = cb->battleGetHeroInfo(packet->side).name;
|
||||
|
||||
{
|
||||
const auto casterStackID = packet->casterStack;
|
||||
|
||||
if(casterStackID > 0)
|
||||
{
|
||||
const CStack * casterStack = cb->battleGetStackByID(casterStackID);
|
||||
if(casterStack != nullptr)
|
||||
casterName = casterStack->type->namePl;
|
||||
}
|
||||
}
|
||||
|
||||
if(packet->affectedCres.size() == 1)
|
||||
{
|
||||
const CStack * attackedStack = cb->battleGetStackByID(*packet->affectedCres.begin(), false);
|
||||
|
||||
const std::string attackedNamePl = attackedStack->getCreature()->namePl;
|
||||
|
||||
if(packet->castByHero)
|
||||
{
|
||||
const std::string fmt = VLC->generaltexth->allTexts[195];
|
||||
logLines.push_back(boost::to_string(boost::format(fmt) % casterName % this->name % attackedNamePl));
|
||||
}
|
||||
else
|
||||
{
|
||||
mechanics->battleLogSingleTarget(logLines, packet, casterName, attackedStack, displayDamage);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
boost::format text(VLC->generaltexth->allTexts[196]);
|
||||
text % casterName % this->name;
|
||||
logLines.push_back(text.str());
|
||||
}
|
||||
|
||||
|
||||
if(packet->dmgToDisplay > 0 && displayDamage)
|
||||
{
|
||||
boost::format dmgInfo(VLC->generaltexth->allTexts[376]);
|
||||
dmgInfo % this->name % packet->dmgToDisplay;
|
||||
logLines.push_back(dmgInfo.str());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void CSpell::setIsOffensive(const bool val)
|
||||
{
|
||||
isOffensive = val;
|
||||
@ -588,6 +622,14 @@ void CSpell::setupMechanics()
|
||||
mechanics = ISpellMechanics::createMechanics(this);
|
||||
}
|
||||
|
||||
///CSpell::AnimationInfo
|
||||
CSpell::AnimationItem::AnimationItem()
|
||||
:resourceName(""),verticalPosition(VerticalPosition::TOP),pause(0)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
///CSpell::AnimationInfo
|
||||
CSpell::AnimationInfo::AnimationInfo()
|
||||
{
|
||||
@ -924,7 +966,6 @@ CSpell * CSpellHandler::loadFromJson(const JsonNode & json)
|
||||
for(const JsonNode & item : queueNode)
|
||||
{
|
||||
CSpell::TAnimation newItem;
|
||||
newItem.verticalPosition = VerticalPosition::TOP;
|
||||
|
||||
if(item.getType() == JsonNode::DATA_STRING)
|
||||
newItem.resourceName = item.String();
|
||||
@ -936,6 +977,11 @@ CSpell * CSpellHandler::loadFromJson(const JsonNode & json)
|
||||
if("bottom" == vPosStr)
|
||||
newItem.verticalPosition = VerticalPosition::BOTTOM;
|
||||
}
|
||||
else if(item.getType() == JsonNode::DATA_FLOAT)
|
||||
{
|
||||
newItem.pause = item.Float();
|
||||
}
|
||||
|
||||
q.push_back(newItem);
|
||||
}
|
||||
};
|
||||
|
@ -9,7 +9,7 @@
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Magic.h"
|
||||
#include "../IHandlerBase.h"
|
||||
#include "../ConstTransitivePtr.h"
|
||||
#include "../int3.h"
|
||||
@ -66,7 +66,7 @@ public:
|
||||
BattleHex destination;
|
||||
ui8 casterSide;
|
||||
PlayerColor casterColor;
|
||||
const CGHeroInstance * caster;
|
||||
const CGHeroInstance * casterHero; //deprecated
|
||||
const CGHeroInstance * secHero;
|
||||
int usedSpellPower;
|
||||
ECastingMode::ECastingMode mode;
|
||||
@ -104,10 +104,21 @@ public:
|
||||
{
|
||||
std::string resourceName;
|
||||
VerticalPosition verticalPosition;
|
||||
int pause;
|
||||
|
||||
AnimationItem();
|
||||
|
||||
template <typename Handler> void serialize(Handler & h, const int version)
|
||||
{
|
||||
h & resourceName & verticalPosition;
|
||||
if(version >= 754)
|
||||
{
|
||||
h & pause;
|
||||
}
|
||||
else if(!h.saving)
|
||||
{
|
||||
pause = 0;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -248,11 +259,8 @@ public:
|
||||
//internal, for use only by Mechanics classes
|
||||
ESpellCastProblem::ESpellCastProblem isImmuneBy(const IBonusBearer *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;
|
||||
ui32 calculateDamage(const ISpellCaster * caster, const CStack * affectedCreature, int spellSchoolLevel, int usedSpellPower) const;
|
||||
|
||||
///selects from allStacks actually affected stacks
|
||||
std::set<const CStack *> getAffectedStacks(const CBattleInfoCallback * cb, ECastingMode::ECastingMode mode, PlayerColor casterColor, int spellLvl, BattleHex destination, const CGHeroInstance * caster = nullptr) const;
|
||||
@ -322,6 +330,11 @@ public:
|
||||
///implementation of BattleSpellCast applying
|
||||
void applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const;
|
||||
|
||||
public:
|
||||
///Client logic.
|
||||
|
||||
void prepareBattleLog(const CBattleInfoCallback * cb, const BattleSpellCast * packet, std::vector<std::string> & logLines) const;
|
||||
|
||||
private:
|
||||
void setIsOffensive(const bool val);
|
||||
void setIsRising(const bool val);
|
||||
|
@ -47,6 +47,9 @@ public:
|
||||
virtual bool adventureCast(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const = 0;
|
||||
virtual void battleCast(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters) const = 0;
|
||||
|
||||
virtual void battleLogSingleTarget(std::vector<std::string> & logLines, const BattleSpellCast * packet,
|
||||
const std::string & casterName, const CStack * attackedStack, bool & displayDamage) const = 0;
|
||||
|
||||
static ISpellMechanics * createMechanics(CSpell * s);
|
||||
protected:
|
||||
CSpell * owner;
|
||||
|
32
lib/spells/Magic.h
Normal file
32
lib/spells/Magic.h
Normal file
@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Magic.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
|
||||
|
||||
/**
|
||||
* High-level interface for spells subsystem
|
||||
*/
|
||||
|
||||
|
||||
class CSpell;
|
||||
class CStack;
|
||||
|
||||
class DLL_LINKAGE ISpellCaster
|
||||
{
|
||||
public:
|
||||
virtual ~ISpellCaster(){};
|
||||
|
||||
/// returns level on which given spell would be cast by this(0 - none, 1 - basic etc);
|
||||
/// caster may not know this spell at all
|
||||
/// optionally returns number of selected school by arg - 0 - air magic, 1 - fire magic, 2 - water magic, 3 - earth magic
|
||||
virtual ui8 getSpellSchoolLevel(const CSpell * spell, int *outSelectedSchool = nullptr) const = 0;
|
||||
|
||||
virtual ui32 getSpellBonus(const CSpell * spell, ui32 base, const CStack * affectedStack) const = 0;
|
||||
};
|
@ -3881,7 +3881,7 @@ bool CGameHandler::makeBattleAction( BattleAction &ba )
|
||||
p.mode = ECastingMode::CREATURE_ACTIVE_CASTING;
|
||||
p.destination = destination;
|
||||
p.casterColor = stack->owner;
|
||||
p.caster = nullptr;
|
||||
p.casterHero = nullptr;
|
||||
p.usedSpellPower = 0;
|
||||
p.casterStack = stack;
|
||||
p.selectedStack = nullptr;
|
||||
@ -4081,7 +4081,7 @@ bool CGameHandler::makeCustomAction( BattleAction &ba )
|
||||
parameters.destination = ba.destinationTile;
|
||||
parameters.casterSide = ba.side;
|
||||
parameters.casterColor = h->tempOwner;
|
||||
parameters.caster = h;
|
||||
parameters.casterHero = h;
|
||||
parameters.secHero = secondHero;
|
||||
|
||||
parameters.usedSpellPower = h->getPrimSkillLevel(PrimarySkill::SPELL_POWER);
|
||||
@ -4240,7 +4240,7 @@ void CGameHandler::stackTurnTrigger(const CStack * st)
|
||||
parameters.destination = BattleHex::INVALID;
|
||||
parameters.casterSide = side;
|
||||
parameters.casterColor = st->owner;
|
||||
parameters.caster = nullptr;
|
||||
parameters.casterHero = nullptr;
|
||||
parameters.secHero = gs->curB->getHero(gs->curB->theOtherPlayer(st->owner));
|
||||
parameters.usedSpellPower = 0;
|
||||
parameters.mode = ECastingMode::ENCHANTER_CASTING;
|
||||
@ -4953,7 +4953,7 @@ void CGameHandler::attackCasting(const BattleAttack & bat, Bonus::BonusType atta
|
||||
parameters.destination = destination;
|
||||
parameters.casterSide = !attacker->attackerOwned;
|
||||
parameters.casterColor = attacker->owner;
|
||||
parameters.caster = nullptr;
|
||||
parameters.casterHero = nullptr;
|
||||
parameters.secHero = nullptr;
|
||||
|
||||
parameters.usedSpellPower = 0;
|
||||
@ -4988,7 +4988,7 @@ void CGameHandler::handleAfterAttackCasting( const BattleAttack & bat )
|
||||
parameters.destination = gs->curB->battleGetStackByID(bat.bsa.at(0).stackAttacked)->position;
|
||||
parameters.casterSide = !attacker->attackerOwned;
|
||||
parameters.casterColor = attacker->owner;
|
||||
parameters.caster = nullptr;
|
||||
parameters.casterHero = nullptr;
|
||||
parameters.secHero = nullptr;
|
||||
|
||||
parameters.usedSpellPower = power;
|
||||
@ -5298,7 +5298,7 @@ void CGameHandler::runBattle()
|
||||
parameters.destination = BattleHex::INVALID;
|
||||
parameters.casterSide = i;
|
||||
parameters.casterColor = h->tempOwner;
|
||||
parameters.caster = nullptr;
|
||||
parameters.casterHero = nullptr;
|
||||
parameters.secHero = gs->curB->battleGetFightingHero(1-i);
|
||||
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user