Pārlūkot izejas kodu

[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

alexvins 12 gadi atpakaļ
vecāks
revīzija
ceea466f54

+ 72 - 28
config/spell_info.json

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

+ 2 - 2
lib/BattleState.cpp

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

+ 4 - 4
lib/CBattleCallback.cpp

@@ -817,7 +817,7 @@ TDmgRange CBattleInfoCallback::calculateDmgRange(const BattleAttackInfo &info) c
 	if(const Bonus *slayerEffect = info.attackerBonuses->getEffect(SpellID::SLAYER)) //slayer handling //TODO: apply only ONLY_MELEE_FIGHT / DISTANCE_FIGHT?
 	{
 		std::vector<int> affectedIds;
-		int spLevel = slayerEffect->val; 
+		int spLevel = slayerEffect->val;
 
 		for(int g = 0; g < VLC->creh->creatures.size(); ++g)
 		{
@@ -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;
 			}
 		}
@@ -1476,7 +1476,7 @@ ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleIsImmune(const C
 	{
 		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;
 
@@ -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;

+ 3 - 3
lib/CCreatureHandler.cpp

@@ -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"))
@@ -925,7 +925,7 @@ void CCreatureHandler::loadStackExp(Bonus & b, BonusList & bl, CLegacyConfigPars
 	case 'K':
 	case 'k':
 		b.type = Bonus::SPELL_AFTER_ATTACK;
-		b.subtype = stringToNumber(mod); 
+		b.subtype = stringToNumber(mod);
 		break;
 	case 'h':
 		b.type= Bonus::HATE;
@@ -1018,7 +1018,7 @@ CreatureID CCreatureHandler::pickRandomMonster(const boost::function<int()> &ran
 	int r = 0;
 	if(tier == -1) //pick any allowed creature
 	{
-		do 
+		do
 		{
 			r = vstd::pickRandomElementOf(creatures, randGen)->idNumber;
 		} while (vstd::contains(VLC->creh->notUsedMonsters,r));

+ 13 - 31
lib/CCreatureSet.cpp

@@ -299,7 +299,7 @@ void CCreatureSet::eraseStack(TSlot slot)
 
 bool CCreatureSet::contains(const CStackInstance *stack) const
 {
-	if(!stack) 
+	if(!stack)
 		return false;
 
 	for(TSlots::const_iterator i = stacks.begin(); i != stacks.end(); ++i)
@@ -315,7 +315,7 @@ TSlot CCreatureSet::findStack(const CStackInstance *stack) const
 	if (h && h->commander == stack)
 		return -2;
 
-	if(!stack) 
+	if(!stack)
 		return -1;
 
 	for(TSlots::const_iterator i = stacks.begin(); i != stacks.end(); ++i)
@@ -345,7 +345,7 @@ void CCreatureSet::joinStack(TSlot slot, CStackInstance * stack)
 	assert(c == stack->type);
 	assert(c);
 
-	//TODO move stuff 
+	//TODO move stuff
 	changeStackCount(slot, stack->count);
 	vstd::clear_pointer(stack);
 }
@@ -486,10 +486,10 @@ void CStackInstance::init()
 	type = NULL;
 	idRand = -1;
 	_armyObj = NULL;
-	setNodeType(STACK_INSTANCE);	
+	setNodeType(STACK_INSTANCE);
 }
 
-int CStackInstance::getQuantityID() const 
+int CStackInstance::getQuantityID() const
 {
 	return CCreature::getQuantityID(count);
 }
@@ -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"
@@ -853,7 +835,7 @@ std::string CStackInstance::bonusToGraphics(Bonus *bonus) const
 					fileName =  "E_SPCOLD.bmp"; break; //direct damage
 			}
 			break;
-		case Bonus::AIR_IMMUNITY: 
+		case Bonus::AIR_IMMUNITY:
 			switch (bonus->subtype)
 			{
 				case 0:
@@ -864,7 +846,7 @@ std::string CStackInstance::bonusToGraphics(Bonus *bonus) const
 					fileName = "E_LIGHT.bmp"; break;//direct damage
 			}
 			break;
-		case Bonus::EARTH_IMMUNITY: 
+		case Bonus::EARTH_IMMUNITY:
 			switch (bonus->subtype)
 			{
 				case 0:
@@ -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;
 }
@@ -981,7 +963,7 @@ CreatureID CStackInstance::getCreatureID() const
 {
 	if(type)
 		return type->idNumber;
-	else 
+	else
 		return CreatureID::NONE;
 }
 

+ 8 - 8
lib/CGameState.cpp

@@ -161,10 +161,10 @@ void MetaString::getLocalString(const std::pair<ui8,ui32> &txt, std::string &dst
 	else if(type == MINE_EVNTS)
 	{
 		dst = VLC->generaltexth->mines[ser].second;
-	} 
+	}
 	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;
 		}

+ 5 - 5
lib/CObjectHandler.cpp

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

+ 1 - 1
lib/CObjectHandler.h

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

+ 1 - 1
lib/CObstacleInstance.cpp

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

+ 59 - 47
lib/CSpellHandler.cpp

@@ -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,22 +268,16 @@ 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 (isPositive())
 		{
-			if ((isDamageSpell() && obj->hasBonusOfType(element, 2)) || obj->hasBonusOfType(element, 1))
+			if (obj->hasBonusOfType(element, 0)) //must be immune to all spells
 				return true;
 		}
-		else if (isPositive()) //positive
+		else //negative or indifferent
 		{
-			if (obj->hasBonusOfType(element, 0)) //must be immune to all spells
+			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

+ 28 - 12
lib/CSpellHandler.h

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

+ 1 - 1
lib/CTownHandler.cpp

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

+ 6 - 5
lib/GameConstants.h

@@ -16,7 +16,7 @@ namespace GameConstants
 {
 	const std::string VCMI_VERSION = "VCMI 0.91b";
 
-	/* 
+	/*
 	 * DATA_DIR contains the game data (Data/, MP3/, ...).
 	 * BIN_DIR is where the vcmiclient/vcmiserver binaries reside
 	 * LIB_DIR is where the AI libraries reside (linux only)
@@ -306,7 +306,7 @@ namespace EMarketMode
 {
 	enum EMarketMode
 	{
-		RESOURCE_RESOURCE, RESOURCE_PLAYER, CREATURE_RESOURCE, RESOURCE_ARTIFACT, 
+		RESOURCE_RESOURCE, RESOURCE_PLAYER, CREATURE_RESOURCE, RESOURCE_ARTIFACT,
 		ARTIFACT_RESOURCE, ARTIFACT_EXP, CREATURE_EXP, CREATURE_UNDEAD, RESOURCE_SKILL,
 		MARTKET_AFTER_LAST_PLACEHOLDER
 	};
@@ -419,7 +419,7 @@ public:
 		RANDOM_MAJOR_ART = 68,
 		RANDOM_RELIC_ART = 69,
 		RANDOM_HERO = 70,
-		RANDOM_MONSTER = 71,		
+		RANDOM_MONSTER = 71,
 		RANDOM_MONSTER_L1 = 72,
 		RANDOM_MONSTER_L2 = 73,
 		RANDOM_MONSTER_L3 = 74,
@@ -461,7 +461,7 @@ public:
 		WATERING_HOLE = 110,
 		WHIRLPOOL = 111,
 		WINDMILL = 112,
-		WITCH_HUT = 113, 
+		WITCH_HUT = 113,
 		HOLE = 124,
 		RANDOM_MONSTER_L5 = 162,
 		RANDOM_MONSTER_L6 = 163,
@@ -574,7 +574,7 @@ ID_LIKE_OPERATORS_DECLS(ETerrainType, ETerrainType::EETerrainType)
 class BFieldType
 {
 public:
-	//   1. sand/shore   2. sand/mesas   3. dirt/birches   4. dirt/hills   5. dirt/pines   6. grass/hills   7. grass/pines 
+	//   1. sand/shore   2. sand/mesas   3. dirt/birches   4. dirt/hills   5. dirt/pines   6. grass/hills   7. grass/pines
 	//8. lava   9. magic plains   10. snow/mountains   11. snow/trees   12. subterranean   13. swamp/trees   14. fiery fields
 	//15. rock lands   16. magic clouds   17. lucid pools   18. holy ground   19. clover field   20. evil fog
 	//21. "favourable winds" text on magic plains background   22. cursed ground   23. rough   24. ship to ship   25. ship
@@ -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)

+ 43 - 43
lib/HeroBonus.cpp

@@ -40,7 +40,7 @@
 
 #define BONUS_ITEM(x) ( #x, Bonus::x )
 
-const std::map<std::string, int> bonusDurationMap = boost::assign::map_list_of 
+const std::map<std::string, int> bonusDurationMap = boost::assign::map_list_of
 	BONUS_ITEM(PERMANENT)
 	BONUS_ITEM(ONE_BATTLE)
 	BONUS_ITEM(ONE_DAY)
@@ -156,9 +156,9 @@ int BonusList::totalValue() const
 	if(hasIndepMin && hasIndepMax)
 		assert(indepMin < indepMax);
 
-	const int notIndepBonuses = boost::count_if(bonuses, [](const Bonus *b) 
-	{ 
-		return b->valType != Bonus::INDEPENDENT_MAX && b->valType != Bonus::INDEPENDENT_MIN; 
+	const int notIndepBonuses = boost::count_if(bonuses, [](const Bonus *b)
+	{
+		return b->valType != Bonus::INDEPENDENT_MAX && b->valType != Bonus::INDEPENDENT_MIN;
 	});
 
 	if (hasIndepMax)
@@ -260,7 +260,7 @@ void BonusList::eliminateDuplicates()
 void BonusList::push_back(Bonus* const &x)
 {
 	bonuses.push_back(x);
-	
+
 	if (belongsToTree)
 		CBonusSystemNode::incrementTreeChangedNum();
 }
@@ -317,7 +317,7 @@ int IBonusBearer::valOfBonuses(Bonus::BonusType type, int subtype /*= -1*/) cons
 {
 	std::stringstream cachingStr;
 	cachingStr << "type_" << type << "s_" << subtype;
-	
+
 	CSelector s = Selector::type(type);
 	if(subtype != -1)
 		s = s && Selector::subtype(subtype);
@@ -393,7 +393,7 @@ int IBonusBearer::MoraleVal() const
 	if(hasBonusOfType(Bonus::NON_LIVING) || hasBonusOfType(Bonus::UNDEAD) ||
 		hasBonusOfType(Bonus::NO_MORALE) || hasBonusOfType(Bonus::SIEGE_WEAPON))
 		return 0;
-	
+
 	int ret = valOfBonuses(Bonus::MORALE);
 
 	if(hasBonusOfType(Bonus::SELF_MORALE)) //eg. minotaur
@@ -406,9 +406,9 @@ int IBonusBearer::LuckVal() const
 {
 	if(hasBonusOfType(Bonus::NO_LUCK))
 		return 0;
-	
+
 	int ret = valOfBonuses(Bonus::LUCK);
-	
+
 	if(hasBonusOfType(Bonus::SELF_LUCK)) //eg. halfling
 		vstd::amax(ret, +1);
 
@@ -461,8 +461,8 @@ ui32 IBonusBearer::getMaxDamage() const
 
 si32 IBonusBearer::manaLimit() const
 {
-	return si32(getPrimSkillLevel(PrimarySkill::KNOWLEDGE) 
-		* (100.0 + valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, SecondarySkill::INTELLIGENCE)) 
+	return si32(getPrimSkillLevel(PrimarySkill::KNOWLEDGE)
+		* (100.0 + valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, SecondarySkill::INTELLIGENCE))
 		/ 10.0);
 }
 
@@ -580,7 +580,7 @@ void CBonusSystemNode::getParents(TNodes &out)
 	{
 		const CBonusSystemNode *parent = parents[i];
 		out.insert(const_cast<CBonusSystemNode*>(parent));
-	}	
+	}
 }
 
 void CBonusSystemNode::getBonusesRec(BonusList &out, const CSelector &selector, const CSelector &limit) const
@@ -606,13 +606,13 @@ void CBonusSystemNode::getAllBonusesRec(BonusList &out) const
 const TBonusListPtr CBonusSystemNode::getAllBonuses(const CSelector &selector, const CSelector &limit, const CBonusSystemNode *root /*= NULL*/, const std::string &cachingStr /*= ""*/) const
 {
 	bool limitOnUs = (!root || root == this); //caching won't work when we want to limit bonuses against an external node
-	if (CBonusSystemNode::cachingEnabled && limitOnUs) 
+	if (CBonusSystemNode::cachingEnabled && limitOnUs)
 	{
 		// Exclusive access for one thread
 		static boost::mutex m;
 		boost::mutex::scoped_lock lock(m);
 
-		// If the bonus system tree changes(state of a single node or the relations to each other) then 
+		// If the bonus system tree changes(state of a single node or the relations to each other) then
 		// cache all bonus objects. Selector objects doesn't matter.
 		if (cachedLast != treeChanged)
 		{
@@ -626,7 +626,7 @@ const TBonusListPtr CBonusSystemNode::getAllBonuses(const CSelector &selector, c
 
 			cachedLast = treeChanged;
 		}
-		
+
 		// If a bonus system request comes with a caching string then look up in the map if there are any
 		// pre-calculated bonus results. Limiters can't be cached so they have to be calculated.
 		if (cachingStr != "")
@@ -642,7 +642,7 @@ const TBonusListPtr CBonusSystemNode::getAllBonuses(const CSelector &selector, c
 		//We still don't have the bonuses (didn't returned them from cache)
 		//Perform bonus selection
 		auto ret = make_shared<BonusList>();
-		cachedBonuses.getBonuses(*ret, selector, limit); 
+		cachedBonuses.getBonuses(*ret, selector, limit);
 
 		// Save the results in the cache
 		if(cachingStr != "")
@@ -672,12 +672,12 @@ const TBonusListPtr CBonusSystemNode::getAllBonusesWithoutCaching(const CSelecto
 	}
 	else if(root)
 	{
-		//We want to limit our query against an external node. We get all its bonuses, 
+		//We want to limit our query against an external node. We get all its bonuses,
 		// add the ones we're considering and see if they're cut out by limiters
 		BonusList rootBonuses, limitedRootBonuses;
 		getAllBonusesRec(rootBonuses);
 
-		BOOST_FOREACH(Bonus *b, beforeLimiting) 
+		BOOST_FOREACH(Bonus *b, beforeLimiting)
 			rootBonuses.push_back(b);
 
 		rootBonuses.eliminateDuplicates();
@@ -709,7 +709,7 @@ CBonusSystemNode::~CBonusSystemNode()
 		while(children.size())
 			children.front()->detachFrom(this);
 	}
-	
+
 	BOOST_FOREACH(Bonus *b, exportedBonuses)
 		delete b;
 }
@@ -855,7 +855,7 @@ bool CBonusSystemNode::isIndependentNode() const
 
 std::string CBonusSystemNode::nodeName() const
 {
-	return description.size() 
+	return description.size()
 		? description
 		: std::string("Bonus system node of type ") + typeid(*this).name();
 }
@@ -1024,7 +1024,7 @@ void CBonusSystemNode::limitBonuses(const BonusList &allBonuses, BonusList &out)
 {
 	assert(&allBonuses != &out); //todo should it work in-place?
 
-	BonusList undecided = allBonuses, 
+	BonusList undecided = allBonuses,
 		&accepted = out;
 
 	while(true)
@@ -1038,13 +1038,13 @@ void CBonusSystemNode::limitBonuses(const BonusList &allBonuses, BonusList &out)
 			if(decision == ILimiter::DISCARD)
 			{
 				undecided.erase(i);
-				i--; continue; 
+				i--; continue;
 			}
 			else if(decision == ILimiter::ACCEPT)
 			{
 				accepted.push_back(b);
 				undecided.erase(i);
-				i--; continue; 
+				i--; continue;
 			}
 			else
 				assert(decision == ILimiter::NOT_SURE);
@@ -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;
@@ -1124,11 +1124,11 @@ std::string Bonus::Description() const
 		str << VLC->generaltexth->skillName[sid]/* << " secondary skill"*/;
 		break;
 	}
-	
+
 	return str.str();
 }
 
-Bonus::Bonus(ui16 Dur, ui8 Type, BonusSource Src, si32 Val, ui32 ID, std::string Desc, si32 Subtype/*=-1*/) 
+Bonus::Bonus(ui16 Dur, ui8 Type, BonusSource Src, si32 Val, ui32 ID, std::string Desc, si32 Subtype/*=-1*/)
 	: duration(Dur), type(Type), subtype(Subtype), source(Src), val(Val), sid(ID), description(Desc)
 {
 	additionalInfo = -1;
@@ -1138,7 +1138,7 @@ Bonus::Bonus(ui16 Dur, ui8 Type, BonusSource Src, si32 Val, ui32 ID, std::string
 	boost::algorithm::trim(description);
 }
 
-Bonus::Bonus(ui16 Dur, ui8 Type, BonusSource Src, si32 Val, ui32 ID, si32 Subtype/*=-1*/, ValueType ValType /*= ADDITIVE_VALUE*/) 
+Bonus::Bonus(ui16 Dur, ui8 Type, BonusSource Src, si32 Val, ui32 ID, si32 Subtype/*=-1*/, ValueType ValType /*= ADDITIVE_VALUE*/)
 	: duration(Dur), type(Type), subtype(Subtype), source(Src), val(Val), sid(ID), valType(ValType)
 {
 	additionalInfo = -1;
@@ -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
@@ -1373,8 +1373,8 @@ HasAnotherBonusLimiter::HasAnotherBonusLimiter( TBonusType bonus, TBonusSubtype
 
 int HasAnotherBonusLimiter::limit(const BonusLimitationContext &context) const
 {
-	CSelector mySelector = isSubtypeRelevant 
-							? Selector::typeSubtype(type, subtype) 
+	CSelector mySelector = isSubtypeRelevant
+							? Selector::typeSubtype(type, subtype)
 							: Selector::type(type);
 
 	//if we have a bonus of required type accepted, limiter should accept also this bonus
@@ -1421,7 +1421,7 @@ bool CPropagatorNodeType::shouldBeAttached(CBonusSystemNode *dest)
 	return nodeType == dest->getNodeType();
 }
 
-CreatureNativeTerrainLimiter::CreatureNativeTerrainLimiter(int TerrainType) 
+CreatureNativeTerrainLimiter::CreatureNativeTerrainLimiter(int TerrainType)
 	: terrainType(TerrainType)
 {
 }
@@ -1465,7 +1465,7 @@ CreatureAlignmentLimiter::CreatureAlignmentLimiter(si8 Alignment)
 int CreatureAlignmentLimiter::limit(const BonusLimitationContext &context) const
 {
 	const CCreature *c = retrieveCreature(&context.node);
-	if(!c) 
+	if(!c)
 		return true;
 	switch(alignment)
 	{
@@ -1503,7 +1503,7 @@ int RankRangeLimiter::limit(const BonusLimitationContext &context) const
 	return true;
 }
 
-int StackOwnerLimiter::limit(const BonusLimitationContext &context) const 
+int StackOwnerLimiter::limit(const BonusLimitationContext &context) const
 {
 	const CStack *s = retreiveStackBattle(&context.node);
 	if(s)
@@ -1524,16 +1524,16 @@ StackOwnerLimiter::StackOwnerLimiter(ui8 Owner)
 	: owner(Owner)
 {
 }
-// int Bonus::limit(const BonusLimitationContext &context) const 
-//  	1162	{ 
-//  	1163	        if (limiter) 
-//  	1164	                return limiter->callNext(context); 
-//  	1165	        else 
-//  	1166	                return ILimiter::ACCEPT; //accept if there's no limiter 
-//  	1167	} 
- 	//1168	 
+// int Bonus::limit(const BonusLimitationContext &context) const
+//  	1162	{
+//  	1163	        if (limiter)
+//  	1164	                return limiter->callNext(context);
+//  	1165	        else
+//  	1166	                return ILimiter::ACCEPT; //accept if there's no limiter
+//  	1167	}
+ 	//1168
 
-int LimiterList::limit( const BonusLimitationContext &context ) const 
+int LimiterList::limit( const BonusLimitationContext &context ) const
 {
 	bool wasntSure = false;
 

+ 9 - 10
lib/IGameCallback.cpp

@@ -37,7 +37,7 @@
 extern boost::rand48 ran;
 
 CGameState * CPrivilagedInfoCallback::gameState ()
-{ 
+{
 	return gs;
 }
 
@@ -92,7 +92,7 @@ void CPrivilagedInfoCallback::getTilesInRange( boost::unordered_set<int3, ShashI
 				double distance = pos.dist2d(int3(xd,yd,pos.z)) - 0.5;
 				if(distance <= radious)
 				{
-					if(player < 0 
+					if(player < 0
 						|| (mode == 1  && team->fogOfWarMap[xd][yd][pos.z]==0)
 						|| (mode == -1 && team->fogOfWarMap[xd][yd][pos.z]==1)
 					)
@@ -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);
@@ -394,7 +393,7 @@ std::vector<const CGObjectInstance*> CGameInfoCallback::getGuardingCreatures (in
 bool CGameInfoCallback::getHeroInfo( const CGObjectInstance *hero, InfoAboutHero &dest ) const
 {
 	const CGHeroInstance *h = dynamic_cast<const CGHeroInstance *>(hero);
-	
+
 	ERROR_RET_VAL_IF(!h, "That's not a hero!", false);
 	ERROR_RET_VAL_IF(!isVisible(h->getPosition(false)), "That hero is not visible!", false);
 
@@ -448,7 +447,7 @@ bool CGameInfoCallback::isVisible(const CGObjectInstance *obj) const
 // 	const CArmedInstance *armi = dynamic_cast<const CArmedInstance*>(obj);
 // 	if(!armi)
 // 		return NULL;
-// 	else 
+// 	else
 // 		return armi;
 // }
 
@@ -509,7 +508,7 @@ std::vector<const CGHeroInstance *> CGameInfoCallback::getAvailableHeroes(const
 	ret.resize(gs->players[*player].availableHeroes.size());
 	std::copy(gs->players[*player].availableHeroes.begin(),gs->players[*player].availableHeroes.end(),ret.begin());
 	return ret;
-}	
+}
 
 const TerrainTile * CGameInfoCallback::getTile( int3 tile, bool verbose) const
 {
@@ -573,7 +572,7 @@ EBuildingState::EBuildingState CGameInfoCallback::canBuildStructure( const CGTow
 	else if(ID == BuildingID::SHIPYARD)
 	{
 		const TerrainTile *tile = getTile(t->bestLocation(), false);
-		
+
         if(!tile || tile->terType != ETerrainType::WATER)
 			return EBuildingState::NO_WATER; //lack of water
 	}
@@ -653,7 +652,7 @@ int CGameInfoCallback::getHeroCount( TPlayerColor player, bool includeGarrisoned
 	int ret = 0;
 	const PlayerState *p = gs->getPlayer(player);
 	ERROR_RET_VAL_IF(!p, "No such player!", -1);
-	
+
 	if(includeGarrisoned)
 		return p->heroes.size();
 	else

+ 12 - 2
lib/Mapping/MapFormatH3M.cpp

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

+ 2 - 2
lib/NetPacksLib.cpp

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

+ 11 - 10
server/CGameHandler.cpp

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