1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-11-28 08:48:48 +02:00

[refactor] spell handling

* more config options for spells
  + mind immunity handled by config
  + direct damage immunity handled by config
  + immunity icon configurable
- removed mind_spell flag 
* more use of new spell identifacation
This commit is contained in:
alexvins 2013-02-13 19:35:43 +00:00
parent 6d06710684
commit ceea466f54
18 changed files with 282 additions and 217 deletions

View File

@ -9,10 +9,9 @@
// counters: array of ids of countering spells
// flags: string array of
// damage
// damage - ATM used only in CBattleInfoCallback::calculateSpellDmg
// offensive
// rising
// mind
// summoning //todo:
//effects: array of structure for bonuses for permanent effects
@ -26,6 +25,9 @@
// immunity: array any of these bonus grants immunity
// limit: array required bonus to be affected, all required
//graphics - OPTIONAL; object;
// iconImmune - OPTIONAL; string; resourse path of icon for SPELL_IMMUNITY bonus (relative to DATA or SPRITES)
"spells":
{
"summonBoat" :
@ -111,7 +113,9 @@
"effect": 0,
"anim": -1,
"ranges": [ "X", "X", "X", "X" ],
"flags" : ["damage"]
"flags" : ["damage"],
"immunities" : ["DIRECT_DAMAGE_IMMUNITY"]
},
"forceField" :
{
@ -126,7 +130,8 @@
"effect": 0,
"anim": -1,
"ranges": [ "0", "0", "0", "0" ],
"flags" : ["damage"]
"flags" : ["damage"],
"immunities" : ["DIRECT_DAMAGE_IMMUNITY"]
},
"earthquake" :
{
@ -141,7 +146,8 @@
"effect": -1,
"anim": 64,
"ranges": [ "0", "0", "0", "0" ],
"flags" : ["damage", "offensive"]
"flags" : ["damage", "offensive"],
"immunities" : ["DIRECT_DAMAGE_IMMUNITY"]
},
"iceBolt" :
{
@ -149,7 +155,8 @@
"effect": -1,
"anim": 46,
"ranges": [ "0", "0", "0", "0" ],
"flags" : ["damage", "offensive"]
"flags" : ["damage", "offensive"],
"immunities" : ["DIRECT_DAMAGE_IMMUNITY"]
},
"lightningBolt" :
{
@ -157,7 +164,8 @@
"effect": -1,
"anim": 38,
"ranges": [ "0", "0", "0", "0" ],
"flags" : ["damage", "offensive"]
"flags" : ["damage", "offensive"],
"immunities" : ["DIRECT_DAMAGE_IMMUNITY"]
},
"implosion" :
{
@ -165,7 +173,11 @@
"effect": -1,
"anim": 10,
"ranges": [ "0", "0", "0", "0" ],
"flags" : ["damage", "offensive"]
"graphics":{
"iconImmune":"ZVS/LIB1.RES/E_SPIMP"
},
"flags" : ["damage", "offensive"],
"immunities" : ["DIRECT_DAMAGE_IMMUNITY"]
},
"chainLightning" :
{
@ -181,7 +193,8 @@
"effect": -1,
"anim": 45,
"ranges": [ "1", "1", "1", "1" ],
"flags" : ["damage", "offensive"]
"flags" : ["damage", "offensive"],
"immunities" : ["DIRECT_DAMAGE_IMMUNITY"]
},
"fireball" :
{
@ -189,7 +202,8 @@
"effect": -1,
"anim": 53,
"ranges": [ "0,1", "0,1", "0,1", "0,1" ],
"flags" : ["damage", "offensive"]
"flags" : ["damage", "offensive"],
"immunities" : ["DIRECT_DAMAGE_IMMUNITY"]
},
"inferno" :
{
@ -197,7 +211,8 @@
"effect": -1,
"anim": 9,
"ranges": [ "0-2", "0-2", "0-2", "0-2" ],
"flags" : ["damage", "offensive"]
"flags" : ["damage", "offensive"],
"immunities" : ["DIRECT_DAMAGE_IMMUNITY"]
},
"meteorShower" :
{
@ -205,7 +220,11 @@
"effect": -1,
"anim": 16,
"ranges": [ "0,1", "0,1", "0,1", "0,1" ],
"flags" : ["damage", "offensive"]
"graphics":{
"iconImmune":"ZVS/LIB1.RES/E_SPMET"
},
"flags" : ["damage", "offensive"],
"immunities" : ["DIRECT_DAMAGE_IMMUNITY"]
},
"deathRipple" :
{
@ -214,7 +233,8 @@
"anim": 8,
"ranges": [ "X", "X", "X", "X" ],
"flags" : ["damage", "offensive"],
"immunity": ["SIEGE_WEAPON","UNDEAD"]
"immunity": ["SIEGE_WEAPON","UNDEAD"],
"immunities" : ["DIRECT_DAMAGE_IMMUNITY"]
},
"destroyUndead" :
{
@ -223,7 +243,8 @@
"anim": 29,
"ranges": [ "X", "X", "X", "X" ],
"flags" : ["damage", "offensive"],
"limit":["UNDEAD"]
"limit":["UNDEAD"],
"immunities" : ["DIRECT_DAMAGE_IMMUNITY"]
},
"armageddon" :
@ -232,7 +253,11 @@
"effect": -1,
"anim": 12,
"ranges": [ "X", "X", "X", "X" ],
"flags" : ["damage", "offensive"]
"graphics":{
"iconImmune":"ZVS/LIB1.RES/E_SPARM"
},
"flags" : ["damage", "offensive"],
"immunities" : ["DIRECT_DAMAGE_IMMUNITY"]
},
"shield" :
{
@ -364,7 +389,10 @@
"id": 35,
"effect": 0,
"anim": 41,
"ranges": [ "0", "0", "0", "X" ]
"ranges": [ "0", "0", "0", "X" ],
"graphics":{
"iconImmune":"ZVS/LIB1.RES/E_SPDISP"
}
},
"magicMirror" :
{
@ -573,7 +601,6 @@
"effect": -1,
"anim": 30,
"ranges": [ "0", "0", "0", "X" ], "counters" : [49],
"flags" : ["mind"],
"effects":
[
{
@ -581,7 +608,8 @@
"duration": "N_TURNS",
"values":[-1,-1,-2,-2]
}
]
],
"immunity":["MIND_IMMUNITY"]
},
"fortune" :
{
@ -636,6 +664,9 @@
"effect": -1,
"anim": 19,
"ranges": [ "0", "0", "0", "X" ],
"graphics":{
"iconImmune":"ZVS/LIB1.RES/E_SPSLOW"
},
"counters" : [53],
"effects":
[
@ -705,7 +736,9 @@
"effect": -1,
"anim": 35,
"ranges": [ "0", "0", "0-1", "0-2" ],
"flags" : ["mind"],
"graphics":{
"iconImmune":"ZVS/LIB1.RES/E_SPBERS"
},
"effects":
[
{
@ -713,7 +746,8 @@
"duration": "N_TURNS",
"values":[0,1,2,3]
}
]
],
"immunity":["MIND_IMMUNITY"]
},
"hypnotize" :
{
@ -721,7 +755,9 @@
"effect": -1,
"anim": 21,
"ranges": [ "0", "0", "0", "0" ],
"flags" : ["mind"],
"graphics":{
"iconImmune":"ZVS/LIB1.RES/E_SPHYPN"
},
"effects":
[
{
@ -729,7 +765,8 @@
"duration": "N_TURNS",
"values":[0,1,2,3]
}
]
],
"immunity":["MIND_IMMUNITY"]
},
"forgetfulness" :
{
@ -737,7 +774,6 @@
"effect": -1,
"anim": 42,
"ranges": [ "0", "0", "0", "X" ],
"flags" : ["mind"],
"effects":
[
{
@ -746,7 +782,8 @@
"values":[0,1,2,3]
}
],
"limit":["SHOOTER"]
"limit":["SHOOTER"],
"immunity":["MIND_IMMUNITY"]
},
"blind" :
{
@ -754,7 +791,9 @@
"effect": -1,
"anim": 6,
"ranges": [ "0", "0", "0", "0" ],
"flags" : ["mind"],
"graphics":{
"iconImmune":"ZVS/LIB1.RES/E_SPBLIND"
},
"effects":
[
{
@ -772,7 +811,8 @@
"duration": "UNITL_BEING_ATTACKED",
"values":[0,0,0,0]
}
]
],
"immunity":["MIND_IMMUNITY"]
},
"teleport" :
{
@ -964,7 +1004,10 @@
"id": 78,
"effect": -1,
"anim": 41,
"ranges": [ "0", "0", "0", "0" ]
"ranges": [ "0", "0", "0", "0" ],
"graphics":{
"iconImmune":"ZVS/LIB1.RES/E_SPDISB"
}
},
"deathStare" :
{
@ -996,7 +1039,8 @@
"effect": 0,
"anim": 81,
"ranges": [ "0", "0", "0", "0" ],
"flags" : ["damage"]
"flags" : ["damage"],
"immunities" : ["DIRECT_DAMAGE_IMMUNITY"]
}
}

View File

@ -188,7 +188,7 @@ ui32 BattleInfo::calculateHealedHP(const CSpell * spell, int usedSpellPower, int
}
bool BattleInfo::resurrects(SpellID spellid) const
{
return VLC->spellh->spells[spellid]->isRisingSpell();
return spellid.toSpell()->isRisingSpell();
}
const CStack * BattleInfo::battleGetStack(BattleHex pos, bool onlyAlive)
@ -942,7 +942,7 @@ si32 CStack::magicResistance() const
void CStack::stackEffectToFeature(std::vector<Bonus> & sf, const Bonus & sse)
{
const CSpell * sp = VLC->spellh->spells[sse.sid];
const CSpell * sp = SpellID(sse.sid).toSpell();
std::vector<Bonus> tmp;
sp->getEffects(tmp, sse.val);

View File

@ -837,7 +837,7 @@ TDmgRange CBattleInfoCallback::calculateDmgRange(const BattleAttackInfo &info) c
{
if(defenderType->idNumber == affectedIds[g])
{
attackDefenceDifference += VLC->spellh->spells[SpellID::SLAYER]->powers[spLevel];
attackDefenceDifference += SpellID(SpellID::SLAYER).toSpell()->powers[spLevel];
break;
}
}
@ -1499,7 +1499,7 @@ ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleIsImmune(const C
bool hasPositiveSpell = false;
BOOST_FOREACH(const Bonus * b, *spellBon)
{
if(VLC->spellh->spells[b->sid]->isPositive())
if(SpellID(b->sid).toSpell()->isPositive())
{
hasPositiveSpell = true;
break;

View File

@ -295,7 +295,7 @@ void CCreatureHandler::loadCreatures()
ncre.addBonus(0, Bonus::ATTACKS_ALL_ADJACENT);
if(boost::algorithm::find_first(ncre.abilityRefs, "IMMUNE_TO_MIND_SPELLS"))
ncre.addBonus(0, Bonus::MIND_IMMUNITY); //giants are immune to mind spells
ncre.addBonus(0, Bonus::MIND_IMMUNITY);
if(boost::algorithm::find_first(ncre.abilityRefs, "IMMUNE_TO_FIRE_SPELLS"))
ncre.addBonus(0, Bonus::FIRE_IMMUNITY);
if(boost::algorithm::find_first(ncre.abilityRefs, "HAS_EXTENDED_ATTACK"))

View File

@ -703,6 +703,7 @@ std::string CStackInstance::bonusToString(Bonus *bonus, bool description) const
std::string CStackInstance::bonusToGraphics(Bonus *bonus) const
{
std::string fileName;
bool fullPath = false;
switch (bonus->type)
{
//"E_ALIVE.bmp"
@ -800,28 +801,9 @@ std::string CStackInstance::bonusToGraphics(Bonus *bonus) const
//"E_SHOOTN.bmp"
case Bonus::SPELL_IMMUNITY:
{
switch (bonus->subtype)
{
case 62: //Blind
fileName = "E_SPBLIND.bmp"; break;
case 35: // Dispell
fileName = "E_SPDISP.bmp"; break;
case 78: // Dispell beneficial spells
fileName = "E_SPDISB.bmp"; break;
case 60: //Hypnotize
fileName = "E_SPHYPN.bmp"; break;
case 18: //Implosion
fileName = "E_SPIMP.bmp"; break;
case 59: //Berserk
fileName = "E_SPBERS.bmp"; break;
case 23: //Meteor Shower
fileName = "E_SPMET.bmp"; break;
case 26: //Armageddon
fileName = "E_SPARM.bmp"; break;
case 54: //Slow
fileName = "E_SPSLOW.bmp"; break;
//TODO: some generic spell handling?
}
fullPath = true;
const CSpell * sp = SpellID(bonus->subtype).toSpell();
fileName = sp->getIconImmune();
break;
}
//"E_SPAWILL.bmp"
@ -909,7 +891,7 @@ std::string CStackInstance::bonusToGraphics(Bonus *bonus) const
case Bonus::LIFE_DRAIN:
fileName = "DrainLife.bmp"; break;
}
if(!fileName.empty())
if(!fileName.empty() && !fullPath)
fileName = "zvs/Lib1.res/" + fileName;
return fileName;
}

View File

@ -164,7 +164,7 @@ void MetaString::getLocalString(const std::pair<ui8,ui32> &txt, std::string &dst
}
else if(type == SPELL_NAME)
{
dst = VLC->spellh->spells[ser]->name;
dst = SpellID(ser).toSpell()->name;
}
else if(type == CRE_SING_NAMES)
{
@ -819,7 +819,7 @@ void CGameState::init(StartInfo * si)
break;
case CScenarioTravel::STravelBonus::SPELL_SCROLL:
{
CArtifactInstance * scroll = CArtifactInstance::createScroll(VLC->spellh->spells[curBonus->info2]);
CArtifactInstance * scroll = CArtifactInstance::createScroll(SpellID(curBonus->info2).toSpell());
scroll->putAt(ArtifactLocation(hero, scroll->firstAvailableSlot(hero)));
}
break;
@ -1531,10 +1531,10 @@ void CGameState::init(StartInfo * si)
}
//init spells
vti->spells.resize(GameConstants::SPELL_LEVELS);
CSpell *s;
for(ui32 z=0; z<vti->obligatorySpells.size();z++)
{
s = VLC->spellh->spells[vti->obligatorySpells[z]];
CSpell *s = vti->obligatorySpells[z].toSpell();
vti->spells[s->level-1].push_back(s->id);
vti->possibleSpells -= s->id;
}
@ -1544,7 +1544,7 @@ void CGameState::init(StartInfo * si)
int sel = -1;
for(ui32 ps=0;ps<vti->possibleSpells.size();ps++)
total += VLC->spellh->spells[vti->possibleSpells[ps]]->probabilities[vti->subID];
total += vti->possibleSpells[ps].toSpell()->probabilities[vti->subID];
if (total == 0) // remaining spells have 0 probability
break;
@ -1552,7 +1552,7 @@ void CGameState::init(StartInfo * si)
int r = ran()%total;
for(ui32 ps=0; ps<vti->possibleSpells.size();ps++)
{
r -= VLC->spellh->spells[vti->possibleSpells[ps]]->probabilities[vti->subID];
r -= vti->possibleSpells[ps].toSpell()->probabilities[vti->subID];
if(r<0)
{
sel = ps;
@ -1562,7 +1562,7 @@ void CGameState::init(StartInfo * si)
if(sel<0)
sel=0;
CSpell *s = VLC->spellh->spells[vti->possibleSpells[sel]];
CSpell *s = vti->possibleSpells[sel].toSpell();
vti->spells[s->level-1].push_back(s->id);
vti->possibleSpells -= s->id;
}

View File

@ -5445,7 +5445,7 @@ void CGObservatory::onHeroVisit( const CGHeroInstance * h ) const
void CGShrine::onHeroVisit( const CGHeroInstance * h ) const
{
if(spell == 255)
if(spell == SpellID::NONE)
{
tlog1 << "Not initialized shrine visited!\n";
return;
@ -5476,7 +5476,7 @@ void CGShrine::onHeroVisit( const CGHeroInstance * h ) const
else //give spell
{
std::set<SpellID> spells;
spells.insert(SpellID(spell));
spells.insert(spell);
cb->changeSpells(h, true, spells);
iw.components.push_back(Component(Component::SPELL,spell,0,0));
@ -5487,7 +5487,7 @@ void CGShrine::onHeroVisit( const CGHeroInstance * h ) const
void CGShrine::initObj()
{
if(spell == 255) //spell not set
if(spell == SpellID::NONE) //spell not set
{
int level = ID-87;
std::vector<SpellID> possibilities;
@ -5509,7 +5509,7 @@ const std::string & CGShrine::getHoverText() const
if(wasVisited(cb->getCurrentPlayer())) //TODO: use local player, not current
{
hoverName += "\n" + VLC->generaltexth->allTexts[355]; // + (learn %s)
boost::algorithm::replace_first(hoverName,"%s",VLC->spellh->spells[spell]->name);
boost::algorithm::replace_first(hoverName,"%s", spell.toSpell()->name);
const CGHeroInstance *h = cb->getSelectedHero(cb->getCurrentPlayer());
if(h && vstd::contains(h->spells,spell)) //hero knows that ability
hoverName += "\n\n" + VLC->generaltexth->allTexts[354]; // (Already learned)
@ -5557,7 +5557,7 @@ void CGScholar::onHeroVisit( const CGHeroInstance * h ) const
if((type == SECONDARY_SKILL
&& ((ssl == 3) || (!ssl && !h->canLearnSkill()))) ////hero already has expert level in the skill or (don't know skill and doesn't have free slot)
|| (type == SPELL && (!h->getArt(ArtifactPosition::SPELLBOOK) || vstd::contains(h->spells, (ui32) bid)
|| (VLC->spellh->spells[bid]->level > h->getSecSkillLevel(SecondarySkill::WISDOM) + 2)
|| ( SpellID(bid).toSpell()->level > h->getSecSkillLevel(SecondarySkill::WISDOM) + 2)
))) //hero doesn't have a spellbook or already knows the spell or doesn't have Wisdom
{
type = PRIM_SKILL;

View File

@ -957,7 +957,7 @@ public:
class DLL_LINKAGE CGShrine : public CPlayersVisited
{
public:
ui8 spell; //number of spell or 255 if random
SpellID spell; //id of spell or NONE if random
void onHeroVisit(const CGHeroInstance * h) const override;
void initObj() override;
const std::string & getHoverText() const override;

View File

@ -129,7 +129,7 @@ std::vector<BattleHex> SpellCreatedObstacle::getAffectedTiles() const
case FIRE_WALL:
return std::vector<BattleHex>(1, pos);
case FORCE_FIELD:
return VLC->spellh->spells[SpellID::FORCE_FIELD]->rangeInHexes(pos, spellLevel, casterSide);
return SpellID(SpellID::FORCE_FIELD).toSpell()->rangeInHexes(pos, spellLevel, casterSide);
//TODO Fire Wall
default:
assert(0);

View File

@ -81,7 +81,7 @@ namespace SRSLPraserHelpers
return xy.first >=0 && xy.first < 17 && xy.second >= 0 && xy.second < 11;
}
//helper fonction for std::set<ui16> CSpell::rangeInHexes(unsigned int centralHex, ui8 schoolLvl ) const
//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;
@ -123,14 +123,10 @@ namespace SRSLPraserHelpers
}
using namespace SRSLPraserHelpers;
CSpellHandler::CSpellHandler()
{
}
CSpell::CSpell()
{
isDamage = false;
isMind = false;
isRising = false;
isOffensive = false;
}
@ -235,23 +231,12 @@ std::vector<BattleHex> CSpell::rangeInHexes(BattleHex centralHex, ui8 schoolLvl,
return ret;
}
CSpell::ETargetType CSpell::getTargetType() const //TODO: parse these at game launch
CSpell::ETargetType CSpell::getTargetType() const
{
if(attributes.find("CREATURE_TARGET_1") != std::string::npos
|| attributes.find("CREATURE_TARGET_2") != std::string::npos)
return CREATURE_EXPERT_MASSIVE;
if(attributes.find("CREATURE_TARGET") != std::string::npos)
return CREATURE;
if(attributes.find("OBSTACLE_TARGET") != std::string::npos)
return OBSTACLE;
return NO_TARGET;
return targetType;
}
void CSpell::getEffects(std::vector<Bonus>& lst, const int level) const
{
if (level < 0 || level>3)
@ -270,6 +255,7 @@ void CSpell::getEffects(std::vector<Bonus>& lst, const int level) const
bool CSpell::isImmuneBy(const IBonusBearer* obj) const
{
//todo: use new bonus API
BOOST_FOREACH(auto b, limiters)
{
if (!obj->hasBonusOfType(b))
@ -282,24 +268,18 @@ bool CSpell::isImmuneBy(const IBonusBearer* obj) const
return true;
}
if (isMindSpell() && obj->hasBonusOfType(Bonus::MIND_IMMUNITY))
return true;
if (isDamageSpell() && obj->hasBonusOfType(Bonus::DIRECT_DAMAGE_IMMUNITY))
return true;
auto battleTestElementalImmunity = [&,this](Bonus::BonusType element) -> bool
{
if (!isPositive()) //negative or indifferent
{
if ((isDamageSpell() && obj->hasBonusOfType(element, 2)) || obj->hasBonusOfType(element, 1))
return true;
}
else if (isPositive()) //positive
if (isPositive())
{
if (obj->hasBonusOfType(element, 0)) //must be immune to all spells
return true;
}
else //negative or indifferent
{
if ((isDamageSpell() && obj->hasBonusOfType(element, 2)) || obj->hasBonusOfType(element, 1))
return true;
}
return false;
};
@ -325,14 +305,14 @@ bool CSpell::isImmuneBy(const IBonusBearer* obj) const
return true;
}
TBonusListPtr immunities = obj->getBonuses(Selector::type(Bonus::LEVEL_SPELL_IMMUNITY));
TBonusListPtr levelImmunities = obj->getBonuses(Selector::type(Bonus::LEVEL_SPELL_IMMUNITY));
if(obj->hasBonusOfType(Bonus::NEGATE_ALL_NATURAL_IMMUNITIES))
{
immunities->remove_if([](const Bonus* b){ return b->source == Bonus::CREATURE_ABILITY; });
levelImmunities->remove_if([](const Bonus* b){ return b->source == Bonus::CREATURE_ABILITY; });
}
if(obj->hasBonusOfType(Bonus::SPELL_IMMUNITY, id)
|| ( immunities->size() > 0 && immunities->totalValue() >= level && level))
|| ( levelImmunities->size() > 0 && levelImmunities->totalValue() >= level && level))
{
return true;
}
@ -340,6 +320,20 @@ bool CSpell::isImmuneBy(const IBonusBearer* obj) const
return false;
}
void CSpell::setAttributes(const std::string& newValue)
{
attributes = newValue;
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;
else
targetType = NO_TARGET;
}
bool DLL_LINKAGE isInScreenRange(const int3 &center, const int3 &pos)
{
@ -350,10 +344,16 @@ bool DLL_LINKAGE isInScreenRange(const int3 &center, const int3 &pos)
return false;
}
CSpell * CSpellHandler::loadSpell(CLegacyConfigParser & parser)
CSpellHandler::CSpellHandler()
{
}
CSpell * CSpellHandler::loadSpell(CLegacyConfigParser & parser, const SpellID id)
{
CSpell * spell = new CSpell; //new currently being read spell
spell->id = id;
spell->name = parser.readString();
spell->abbName = parser.readString();
spell->level = parser.readNumber();
@ -375,7 +375,24 @@ CSpell * CSpellHandler::loadSpell(CLegacyConfigParser & parser)
for (int i = 0; i < 4 ; i++)
spell->descriptions.push_back(parser.readString());
spell->attributes = parser.readString();
std::string attributes = parser.readString();
//spell fixes
if (id == SpellID::FORGETFULNESS)
{
//forgetfulness needs to get targets automatically on expert level
boost::replace_first(attributes, "CREATURE_TARGET", "CREATURE_TARGET_2");
}
if (id == SpellID::DISRUPTING_RAY)
{
// disrupting ray will now affect single creature
boost::replace_first(attributes,"2", "");
}
spell->setAttributes(attributes);
spell->mainEffectAnim = -1;
return spell;
}
@ -388,8 +405,8 @@ void CSpellHandler::loadSpells()
{
do
{
CSpell * spell = loadSpell(parser);
spell->id = SpellID(spells.size());
const SpellID id = SpellID(spells.size());
CSpell * spell = loadSpell(parser,id);
spell->combatSpell = combat;
spell->creatureAbility = alility;
spells.push_back(spell);
@ -410,9 +427,6 @@ void CSpellHandler::loadSpells()
skip(3);
read(true,true);//read creature abilities
boost::replace_first (spells[SpellID::DISRUPTING_RAY]->attributes, "2", ""); // disrupting ray will now affect single creature
spells.push_back(spells[SpellID::ACID_BREATH_DEFENSE]); //clone Acid Breath attributes for Acid Breath damage effect
//loading of additional spell traits
@ -452,10 +466,6 @@ void CSpellHandler::loadSpells()
{
s->isRising = true;
}
else if (flag == "mind")
{
s->isMind = true;
}
else if (flag == "offensive")
{
s->isOffensive = true;
@ -519,12 +529,14 @@ void CSpellHandler::loadSpells()
read_node("immunity",s->immunities);
read_node("limit",s->limiters);
const JsonNode & graphicsNode = spell.second["graphics"];
if (!graphicsNode.isNull())
{
const JsonNode& iconImmune = graphicsNode["iconImmune"];
if (!iconImmune.isNull())
s->iconImmune = iconImmune.String();
}
}
//spell fixes
//forgetfulness needs to get targets automatically on expert level
boost::replace_first(spells[SpellID::FORGETFULNESS]->attributes, "CREATURE_TARGET", "CREATURE_TARGET_2"); //TODO: use flags instead?
}
std::vector<bool> CSpellHandler::getDefaultAllowedSpells() const

View File

@ -38,7 +38,7 @@ public:
std::vector<si32> powers; //[er skill level: 0 - none, 1 - basic, etc
std::map<TFaction, si32> probabilities; //% chance to gain for castles
std::vector<si32> AIVals; //AI values: per skill level: 0 - none, 1 - basic, etc
std::string attributes; //reference only attributes
bool combatSpell; //is this spell combat (true) or adventure (false)
bool creatureAbility; //if true, only creatures can use this spell
si8 positiveness; //1 if spell is positive for influenced stacks, 0 if it is indifferent, -1 if it's negative
@ -60,7 +60,6 @@ public:
inline bool isRisingSpell() const;
inline bool isDamageSpell() const;
inline bool isMindSpell() const; //TODO: deprecated - remove, refactor
inline bool isOffensiveSpell() const;
inline bool hasEffects() const;
@ -68,26 +67,42 @@ public:
bool isImmuneBy(const IBonusBearer *obj) const;
/**
* Returns resource name of icon for SPELL_IMMUNITY bonus
*/
inline const std::string& getIconImmune() const;
template <typename Handler> void serialize(Handler &h, const int version)
{
h & identifier & id & name & abbName & descriptions & level & earth & water & fire & air & power & costs
& powers & probabilities & AIVals & attributes & combatSpell & creatureAbility & positiveness & range & counteredSpells & mainEffectAnim;
h & isRising & isDamage & isMind;
h & isRising & isDamage & isOffensive;
h & targetType;
h & effects & immunities & limiters;
h & iconImmune;
}
friend class CSpellHandler;
private:
bool isRising;
bool isDamage;
bool isMind;
bool isOffensive;
std::string attributes; //reference only attributes
void setAttributes(const std::string& newValue);
ETargetType targetType;
std::vector<Bonus> effects [4];
std::vector<Bonus::BonusType> immunities; //any of these hrants immunity
std::vector<Bonus::BonusType> limiters; //all of them are required
std::vector<Bonus::BonusType> immunities; //any of these grants immunity
std::vector<Bonus::BonusType> limiters; //all of them are required to be affected
///graphics related stuff
std::string iconImmune;
};
///CSpell inlines
@ -127,11 +142,6 @@ bool CSpell::isDamageSpell() const
return isDamage;
}
bool CSpell::isMindSpell() const
{
return isMind;
}
bool CSpell::isOffensiveSpell() const
{
return isOffensive;
@ -142,11 +152,17 @@ bool CSpell::hasEffects() const
return !effects[0].empty();
}
const std::string& CSpell::getIconImmune() const
{
return iconImmune;
}
bool DLL_LINKAGE isInScreenRange(const int3 &center, const int3 &pos); //for spells like Dimension Door
class DLL_LINKAGE CSpellHandler
{
CSpell * loadSpell(CLegacyConfigParser & parser);
CSpell * loadSpell(CLegacyConfigParser & parser, const SpellID id);
public:
CSpellHandler();

View File

@ -468,7 +468,7 @@ void CTownHandler::loadTown(CTown &town, const JsonNode & source)
VLC->modh->identifiers.requestIdentifier("spell." + node.first, [=, &town](si32 spellID)
{
VLC->spellh->spells[spellID]->probabilities[town.typeID] = chance;
SpellID(spellID).toSpell()->probabilities[town.typeID] = chance;
});
}

View File

@ -744,6 +744,7 @@ public:
SpellID(ESpellID _num = NONE) : num(_num)
{}
//TODO: should this be const?
DLL_LINKAGE CSpell * toSpell() const;
ID_LIKE_CLASS_COMMON(SpellID, ESpellID)

View File

@ -1097,7 +1097,7 @@ int NBonus::getCount(const CBonusSystemNode *obj, Bonus::BonusSource from, int i
const CSpell * Bonus::sourceSpell() const
{
if(source == SPELL_EFFECT)
return VLC->spellh->spells[sid];
return SpellID(sid).toSpell();
return NULL;
}
@ -1115,7 +1115,7 @@ std::string Bonus::Description() const
str << VLC->arth->artifacts[sid]->Name();
break;;
case SPELL_EFFECT:
str << VLC->spellh->spells[sid]->name;
str << SpellID(sid).toSpell()->name;
break;
case CREATURE_ABILITY:
str << VLC->creh->creatures[sid]->namePl;
@ -1228,7 +1228,7 @@ namespace Selector
{
if(b->source == Bonus::SPELL_EFFECT)
{
CSpell *sp = VLC->spellh->spells[b->sid];
CSpell *sp = SpellID(b->sid).toSpell();
return sp->isPositive();
}
return false; //not a spell effect

View File

@ -199,11 +199,10 @@ ArtifactID CPrivilagedInfoCallback::getArtSync (ui32 rand, int flags, bool erase
void CPrivilagedInfoCallback::getAllowedSpells(std::vector<SpellID> &out, ui16 level)
{
CSpell *spell;
for (ui32 i = 0; i < gs->map->allowedSpell.size(); i++) //spellh size appears to be greater (?)
{
spell = VLC->spellh->spells[i];
const CSpell *spell = SpellID(i).toSpell();
if (isAllowed (0, spell->id) && spell->level == level)
{
out.push_back(spell->id);

View File

@ -672,7 +672,7 @@ CArtifactInstance * CMapLoaderH3M::createArtifact(int aid, int spellID /*= -1*/)
}
else
{
a = CArtifactInstance::createScroll(VLC->spellh->spells[spellID]);
a = CArtifactInstance::createScroll(SpellID(spellID).toSpell());
}
}
else
@ -1166,7 +1166,17 @@ void CMapLoaderH3M::readObjects()
{
CGShrine * shr = new CGShrine();
nobj = shr;
shr->spell = reader.readUInt8();
ui8 raw_id = reader.readUInt8();
if (255 == raw_id)
{
shr->spell = SpellID(SpellID::NONE);
}
else
{
shr->spell = SpellID(raw_id);
}
reader.skip(3);
break;
}

View File

@ -1199,7 +1199,7 @@ DLL_LINKAGE void StartAction::applyGs( CGameState *gs )
}
else
{
gs->curB->usedSpellsHistory[ba.side].push_back(VLC->spellh->spells[ba.additionalInfo]);
gs->curB->usedSpellsHistory[ba.side].push_back(SpellID(ba.additionalInfo).toSpell());
}
switch(ba.actionType)
@ -1240,7 +1240,7 @@ DLL_LINKAGE void BattleSpellCast::applyGs( CGameState *gs )
}
//Handle spells removing effects from stacks
const CSpell *spell = VLC->spellh->spells[id];
const CSpell *spell = SpellID(id).toSpell();
const bool removeAllSpells = id == SpellID::DISPEL;
const bool removeHelpful = id == SpellID::DISPEL_HELPFUL_SPELLS;

View File

@ -827,7 +827,7 @@ void CGameHandler::prepareAttack(BattleAttack &bat, const CStack *att, const CSt
bat.bsa.front().flags |= BattleStackAttacked::EFFECT;
bat.bsa.front().effect = VLC->spellh->spells[bonus->subtype]->mainEffectAnim; //hopefully it does not interfere with any other effect?
std::set<const CStack*> attackedCreatures = gs->curB->getAffectedCreatures(VLC->spellh->spells[bonus->subtype], bonus->val, att->owner, targetHex);
std::set<const CStack*> attackedCreatures = gs->curB->getAffectedCreatures(SpellID(bonus->subtype).toSpell(), bonus->val, att->owner, targetHex);
//TODO: get exact attacked hex for defender
BOOST_FOREACH(const CStack * stack, attackedCreatures)
@ -3913,7 +3913,7 @@ void CGameHandler::playerMessage( TPlayerColor player, const std::string &messag
void CGameHandler::handleSpellCasting( int spellID, int spellLvl, BattleHex destination, ui8 casterSide, TPlayerColor casterColor, const CGHeroInstance * caster, const CGHeroInstance * secHero,
int usedSpellPower, ECastingMode::ECastingMode mode, const CStack * stack, si32 selectedStack)
{
const CSpell *spell = VLC->spellh->spells[spellID];
const CSpell *spell = SpellID(spellID).toSpell();
//Helper local function that creates obstacle on given position. Obstacle type is inferred from spell type.
@ -3977,7 +3977,7 @@ void CGameHandler::handleSpellCasting( int spellID, int spellLvl, BattleHex dest
if (caster) //calculate spell cost
{
sc.spellCost = gs->curB->battleGetSpellCost(VLC->spellh->spells[spellID], caster);
sc.spellCost = gs->curB->battleGetSpellCost(SpellID(spellID).toSpell(), caster);
if (secHero && mode == ECastingMode::HERO_CASTING) //handle mana channel
{
@ -4290,8 +4290,9 @@ void CGameHandler::handleSpellCasting( int spellID, int spellLvl, BattleHex dest
//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) : 0;
bsa.amount = usedSpellPower * VLC->spellh->spells[spellID]->powers[spellLvl] *
(100 + percentBonus) / 100.0; //new feature - percentage bonus
bsa.amount = usedSpellPower
* SpellID(spellID).toSpell()->powers[spellLvl]
* (100 + percentBonus) / 100.0; //new feature - percentage bonus
if(bsa.amount)
sendAndApply(&bsa);
else
@ -4440,7 +4441,7 @@ bool CGameHandler::makeCustomAction( BattleAction &ba )
return false;
}
const CSpell *s = VLC->spellh->spells[ba.additionalInfo];
const CSpell *s = SpellID(ba.additionalInfo).toSpell();
if (s->mainEffectAnim > -1 || (s->id >= 66 || s->id <= 69) || s->id == SpellID::CLONE) //allow summon elementals
//TODO: special effects, like Clone
{
@ -4590,7 +4591,7 @@ void CGameHandler::stackTurnTrigger(const CStack * st)
{
int index = rand() % bl.size();
int spellID = bl[index]->subtype; //spell ID
if (gs->curB->battleCanCastThisSpell(st->owner, VLC->spellh->spells[spellID], ECastingMode::ENCHANTER_CASTING)) //TODO: select another?
if (gs->curB->battleCanCastThisSpell(st->owner, SpellID(spellID).toSpell(), ECastingMode::ENCHANTER_CASTING)) //TODO: select another?
{
int spellLeveL = bl[index]->val; //spell level
const CGHeroInstance * enemyHero = gs->curB->getHero(gs->curB->theOtherPlayer(st->owner));
@ -4654,14 +4655,14 @@ void CGameHandler::handleDamageFromObstacle(const CObstacleInstance &obstacle, c
oneTimeObstacle = true;
effect = 82; //makes
damage = gs->curB->calculateSpellDmg(VLC->spellh->spells[SpellID::LAND_MINE], hero, curStack,
damage = gs->curB->calculateSpellDmg(SpellID(SpellID::LAND_MINE).toSpell(), 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(VLC->spellh->spells[SpellID::FIRE_WALL], hero, curStack,
damage = gs->curB->calculateSpellDmg(SpellID(SpellID::FIRE_WALL).toSpell(), hero, curStack,
spellObstacle->spellLevel, spellObstacle->casterSpellPower);
}
else
@ -5302,7 +5303,7 @@ void CGameHandler::attackCasting(const BattleAttack & bat, Bonus::BonusType atta
vstd::amin (chance, 100);
int destination = oneOfAttacked->position;
const CSpell * spell = VLC->spellh->spells[spellID];
const CSpell * spell = SpellID(spellID).toSpell();
if(gs->curB->battleCanCastThisSpellHere(attacker->owner, spell, ECastingMode::AFTER_ATTACK_CASTING, oneOfAttacked->position) != ESpellCastProblem::OK)
continue;