Explorar o código

[refactor]
* use spells configuration (all timed effects for battle spells)
* a few more cleanups
+register erm resource types (useful in future and less noise in log now)

alexvins %!s(int64=12) %!d(string=hai) anos
pai
achega
53b684180d

+ 4 - 59
AI/BattleAI/BattleAI.cpp

@@ -545,67 +545,12 @@ enum SpellTypes
 
 SpellTypes spellType(const CSpell *spell)
 {
-	switch(spell->id)
-	{
-		//offense spell
-	case Spells::MAGIC_ARROW:
-	case Spells::ICE_BOLT:
-	case Spells::LIGHTNING_BOLT:
-	case Spells::IMPLOSION:
-	case Spells::CHAIN_LIGHTNING:
-	case Spells::FROST_RING:
-	case Spells::FIREBALL:
-	case Spells::INFERNO:
-	case Spells::METEOR_SHOWER:
-	case Spells::DEATH_RIPPLE:
-	case Spells::DESTROY_UNDEAD:
-	case Spells::ARMAGEDDON:
-	case Spells::TITANS_LIGHTNING_BOLT:
-	case Spells::THUNDERBOLT: //(thunderbirds)
+	if (spell->isOffensiveSpell())
 		return OFFENSIVE_SPELL;
+	if (spell->hasEffects())
+		return TIMED_EFFECT;	
+	return OTHER;
 
-	case Spells::SHIELD:
-	case Spells::AIR_SHIELD:
-	case Spells::FIRE_SHIELD:
-	case Spells::PROTECTION_FROM_AIR:
-	case Spells::PROTECTION_FROM_FIRE:
-	case Spells::PROTECTION_FROM_WATER:
-	case Spells::PROTECTION_FROM_EARTH:
-	case Spells::ANTI_MAGIC:
-	case Spells::MAGIC_MIRROR:
-	case Spells::BLESS:
-	case Spells::CURSE:
-	case Spells::BLOODLUST:
-	case Spells::PRECISION:
-	case Spells::WEAKNESS:
-	case Spells::STONE_SKIN:
-	case Spells::DISRUPTING_RAY:
-	case Spells::PRAYER:
-	case Spells::MIRTH:
-	case Spells::SORROW:
-	case Spells::FORTUNE:
-	case Spells::MISFORTUNE:
-	case Spells::HASTE:
-	case Spells::SLOW:
-	case Spells::SLAYER:
-	case Spells::FRENZY:
-	case Spells::COUNTERSTRIKE:
-	case Spells::BERSERK:
-	case Spells::HYPNOTIZE:
-	case Spells::FORGETFULNESS:
-	case Spells::BLIND:
-	case Spells::STONE_GAZE:
-	case Spells::POISON:
-	case Spells::BIND:
-	case Spells::DISEASE:
-	case Spells::PARALYZE:
-	case Spells::AGE:
-	case Spells::ACID_BREATH_DEFENSE:
-		return TIMED_EFFECT;
-
-	default:
-		return OTHER;
-	}
 }
 
 struct PossibleSpellcast

+ 164 - 81
config/spell_info.json

@@ -6,14 +6,25 @@
     //			  1 -> spell is positive for them
 	//   anim: main effect animation (AC format), -1 - none
     //   ranges: spell range description in SRSL ([no magic] [basic] [advanced] [expert])
+    //	 counters: array of ids of countering spells
 
     // flags: string array of
     //		damage
-    //		directDamage //todo
+    //		offensive
     //		rising
     //		mind
+    //		summoning //todo:
 
-    //effects: array of bonus for permanent effects
+    //effects: array of structure for bonuses for permanent effects
+    // {bonus format} - effect //todo
+    // + values: [4 int values] (OPTIONAL default from sptraits) values for levels
+    // + ainfos: [4 int values] (optional) additional infos for levels (atm only CURSE)
+    
+    //
+    //immunity - name of bonus granting immunity to this spell
+    //
+    // 	immunity: array any of these bonus grants immunity
+    //  limit: array required bonus to be affected, all required
 
 	"spells":
 	{
@@ -130,7 +141,7 @@
 			"effect": -1,
 			"anim": 64,
 			"ranges": [ "0", "0", "0", "0" ],
-			"flags" : ["damage"]
+			"flags" : ["damage, offensive"]
 		},
 		"iceBolt"        :
 		{
@@ -138,7 +149,7 @@
 			"effect": -1,
 			"anim": 46,
 			"ranges": [ "0", "0", "0", "0" ],
-			"flags" : ["damage"]
+			"flags" : ["damage, offensive"]
 		},
 		"lightningBolt"  :
 		{
@@ -146,7 +157,7 @@
 			"effect": -1,
 			"anim": 38,
 			"ranges": [ "0", "0", "0", "0" ],
-			"flags" : ["damage"]
+			"flags" : ["damage, offensive"]
 		},
 		"implosion"      :
 		{
@@ -154,7 +165,7 @@
 			"effect": -1,
 			"anim": 10,
 			"ranges": [ "0", "0", "0", "0" ],
-			"flags" : ["damage"]
+			"flags" : ["damage, offensive"]
 		},
 		"chainLightning" :
 		{
@@ -162,7 +173,7 @@
 			"effect": -1,
 			"anim": 38,
 			"ranges": [ "0", "0", "0", "0" ],
-			"flags" : ["damage"]
+			"flags" : ["damage, offensive"]
 		},
 		"frostRing"      :
 		{
@@ -170,7 +181,7 @@
 			"effect": -1,
 			"anim": 45,
 			"ranges": [ "1", "1", "1", "1" ],
-			"flags" : ["damage"]
+			"flags" : ["damage, offensive"]
 		},
 		"fireball"       :
 		{
@@ -178,7 +189,7 @@
 			"effect": -1,
 			"anim": 53,
 			"ranges": [ "0,1", "0,1", "0,1", "0,1" ],
-			"flags" : ["damage"]
+			"flags" : ["damage, offensive"]
 		},
 		"inferno"        :
 		{
@@ -186,7 +197,7 @@
 			"effect": -1,
 			"anim": 9,
 			"ranges": [ "0-2", "0-2", "0-2", "0-2" ],
-			"flags" : ["damage"]
+			"flags" : ["damage, offensive"]
 		},
 		"meteorShower"   :
 		{
@@ -194,7 +205,7 @@
 			"effect": -1,
 			"anim": 16,
 			"ranges": [ "0,1", "0,1", "0,1", "0,1" ],
-			"flags" : ["damage"]
+			"flags" : ["damage, offensive"]
 		},
 		"deathRipple"    :
 		{
@@ -202,7 +213,8 @@
 			"effect": -1,
 			"anim": 8,
 			"ranges": [ "X", "X", "X", "X" ],
-			"flags" : ["damage"]
+			"flags" : ["damage, offensive"],
+			"immunity":  ["SIEGE_WEAPON","UNDEAD"]
 		},
 		"destroyUndead"  :
 		{
@@ -210,7 +222,9 @@
 			"effect": -1,
 			"anim": 29,
 			"ranges": [ "X", "X", "X", "X" ],
-			"flags" : ["damage"]
+			"flags" : ["damage, offensive"],
+			"limit":["UNDEAD"]
+			
 		},
 		"armageddon"     :
 		{
@@ -218,7 +232,7 @@
 			"effect": -1,
 			"anim": 12,
 			"ranges": [ "X", "X", "X", "X" ],
-			"flags" : ["damage"]
+			"flags" : ["damage, offensive"]
 		},
 		"shield"         :
 		{
@@ -228,9 +242,11 @@
 			"ranges": [ "0", "0", "0", "X" ],
 			"effects":
 			[
+
 				{
 					"type": 	"GENERAL_DAMAGE_REDUCTION",
-					"subtype":0
+					"subtype":0,
+					"duration": "N_TURNS"
 				}
 			]
 		},
@@ -244,7 +260,8 @@
 			[
 				{
 					"type": 	"GENERAL_DAMAGE_REDUCTION",
-					"subtype":1
+					"subtype":1,
+					"duration": "N_TURNS"	
 				}
 			]
 		},
@@ -257,7 +274,8 @@
 			"effects":
 			[
 				{
-					"type": 	"FIRE_SHIELD"
+					"type": 	"FIRE_SHIELD",
+					"duration": "N_TURNS"	
 				}
 			]
 		},
@@ -270,10 +288,14 @@
 			"effects":
 			[
 				{
+
 					"type": 	"SPELL_DAMAGE_REDUCTION",
-					"subtype":0
+					"subtype":0,
+					"duration": "N_TURNS"
+			
 				}
 			]
+			
 		},
 		"protectFire"    :
 		{
@@ -285,7 +307,8 @@
 			[
 				{
 					"type": 	"SPELL_DAMAGE_REDUCTION",
-					"subtype":1
+					"subtype":1,
+					"duration": "N_TURNS"
 				}
 			]
 		},
@@ -299,7 +322,8 @@
 			[
 				{
 					"type": 	"SPELL_DAMAGE_REDUCTION",
-					"subtype":2
+					"subtype":2,
+					"duration": "N_TURNS"
 				}
 			]
 		},
@@ -313,7 +337,8 @@
 			[
 				{
 					"type": 	"SPELL_DAMAGE_REDUCTION",
-					"subtype":3
+					"subtype":3,
+					"duration": "N_TURNS"
 				}
 			]
 		},
@@ -327,8 +352,10 @@
 			[
 				{
 					"type": 	"LEVEL_SPELL_IMMUNITY",
-					"subtype":5,
-					"valType":"INDEPENDENT_MAX"
+					"subtype":5, //needed?
+					"valType":"INDEPENDENT_MAX",
+					"duration": "N_TURNS",
+					"values":[3,3,4,5]
 				}
 			]
 		},
@@ -349,7 +376,8 @@
 			[
 				{
 					"type": 	"MAGIC_MIRROR",
-					"valType":"INDEPENDENT_MAX"
+					"valType":"INDEPENDENT_MAX",
+					"duration": "N_TURNS"
 				}
 			]
 		},
@@ -358,8 +386,7 @@
 			"id": 37,
 			"effect": 1,
 			"anim": 39,
-			"ranges": [ "0", "0", "0", "0" ],
-			"flags" : ["rising"]
+			"ranges": [ "0", "0", "0", "0" ]
 		},
 		"resurrection"   :
 		{
@@ -382,7 +409,8 @@
 			"id": 40,
 			"effect": 1,
 			"anim": 79,
-			"ranges": [ "0", "0", "0", "0" ]
+			"ranges": [ "0", "0", "0", "0" ],
+			"flags" : ["rising"]
 		},
 		"bless"          :
 		{
@@ -395,9 +423,11 @@
 			[
 				{
 					"type": 	"ALWAYS_MAXIMUM_DAMAGE",
-					"valType":"INDEPENDENT_MAX"
+					"valType":"INDEPENDENT_MAX",
+					"duration": "N_TURNS"
 				}
-			]
+			],
+			"immunity":["UNDEAD"]
 		},
 		"curse"          :
 		{
@@ -409,9 +439,13 @@
 			[
 				{
 					"type": 	"ALWAYS_MINIMUM_DAMAGE",
-					"valType":"INDEPENDENT_MAX"
+					"subtype": -1, //any attack
+					"valType":	"INDEPENDENT_MAX",
+					"duration": "N_TURNS",
+					"ainfos":[0,0,20,20]
 				}
-			]
+			],
+			"immunity":["UNDEAD"]
 		},
 		"bloodlust"      :
 		{
@@ -424,7 +458,8 @@
 				{
 					"type": 	"PRIMARY_SKILL",
 					"subtype": 0, //ATTACK
-					"effectRange" : "ONLY_MELEE_FIGHT"
+					"effectRange" : "ONLY_MELEE_FIGHT",
+					"duration": "N_TURNS"
 				}
 			]
 		},
@@ -439,9 +474,11 @@
 				{
 					"type": 	"PRIMARY_SKILL",
 					"subtype": 0, //ATTACK
-					"effectRange" : "ONLY_DISTANCE_FIGHT"
+					"effectRange" : "ONLY_DISTANCE_FIGHT",
+					"duration": "N_TURNS"
 				}
-			]
+			],
+			"limit":["SHOOTER"]
 		},
 		"weakness"       :
 		{
@@ -453,7 +490,9 @@
 			[
 				{
 					"type": 	"PRIMARY_SKILL",
-					"subtype": 0 //ATTACK
+					"subtype": 0, //ATTACK
+					"duration": "N_TURNS",
+					"values":[-3,-3,-6,-6]
 				}
 			]
 		},
@@ -467,7 +506,8 @@
 			[
 				{
 					"type": 	"PRIMARY_SKILL",
-					"subtype": 1 //DEFENSE
+					"subtype": 1, //DEFENSE
+					"duration": "N_TURNS"
 				}
 			]
 		},
@@ -481,7 +521,10 @@
 			[
 				{
 					"type": 	"PRIMARY_SKILL",
-					"subtype": 1 //DEFENSE
+					"subtype": 1, //DEFENSE
+					"valueType": "ADDITIVE_VALUE",
+					"duration": "N_TURNS",
+					"values":[-3,-3,-4,-5]
 				}
 			]
 		},
@@ -495,14 +538,17 @@
 			[
 				{
 					"type": 	"PRIMARY_SKILL",
-					"subtype": 0 //ATTACK
+					"subtype": 0, //ATTACK
+					"duration": "N_TURNS"
 				},
 				{
 					"type": 	"PRIMARY_SKILL",
-					"subtype": 1 //DEFENSE
+					"subtype": 1, //DEFENSE
+					"duration": "N_TURNS"
 				},
 				{
-					"type": 	"STACKS_SPEED"
+					"type": 	"STACKS_SPEED",
+					"duration": "N_TURNS"
 				}
 			]
 		},
@@ -516,7 +562,8 @@
 			"effects":
 			[
 				{
-					"type": 	"MORALE"
+					"type": 	"MORALE",
+					"duration": "N_TURNS"
 				}
 			]
 		},
@@ -530,7 +577,9 @@
 			"effects":
 			[
 				{
-					"type": 	"MORALE"
+					"type": 	"MORALE",
+					"duration": "N_TURNS",
+					"values":[-1,-1,-2,-2]
 				}
 			]
 		},
@@ -544,7 +593,8 @@
 			"effects":
 			[
 				{
-					"type": 	"LUCK"
+					"type": 	"LUCK",
+					"duration": "N_TURNS"
 				}
 			]
 		},
@@ -558,7 +608,9 @@
 			"effects":
 			[
 				{
-					"type": 	"LUCK"
+					"type": 	"LUCK",
+					"duration": "N_TURNS",
+					"values":[-1,-1,-2,-2]
 				}
 			]
 		},
@@ -572,9 +624,11 @@
 			"effects":
 			[
 				{
-					"type": 	"STACKS_SPEED"
+					"type": 	"STACKS_SPEED",
+					"duration": "N_TURNS"
 				}
-			]
+			],
+			"immunity":["SIEGE_WEAPON"]
 		},
 		"slow"           :
 		{
@@ -586,9 +640,13 @@
 			"effects":
 			[
 				{
-					"type": 	"STACKS_SPEED"
+					"type": 	"STACKS_SPEED",
+					"valueType": "PERCENT_TO_ALL",
+					"duration": "N_TURNS"
+					"values":[-25,-25,-50,-50]
 				}
-			]
+			],
+			"immunity":["SIEGE_WEAPON"]
 		},
 		"slayer"         :
 		{
@@ -599,7 +657,9 @@
 			"effects":
 			[
 				{
-					"type": 	"SLAYER"
+					"type": 	"SLAYER",
+					"duration": "N_TURNS",
+					"values":[0,1,2,3]
 				}
 			]
 		},
@@ -612,7 +672,8 @@
 			"effects":
 			[
 				{
-					"type": 	"IN_FRENZY"
+					"type": 	"IN_FRENZY",
+					"duration": "N_TURNS"
 				}
 			]
 		},
@@ -622,7 +683,7 @@
 			"effect": -1,
 			"anim": 38,
 			"ranges": [ "0", "0", "0", "0" ],
-			"flags" : ["damage"]
+			"flags" : ["damage, offensive"]
 		},
 		"counterstrike"  :
 		{
@@ -633,7 +694,8 @@
 			"effects":
 			[
 				{
-					"type": 	"ADDITIONAL_RETALIATION"
+					"type": 	"ADDITIONAL_RETALIATION",
+					"duration": "N_TURNS"
 				}
 			]
 		},
@@ -647,7 +709,9 @@
 			"effects":
 			[
 				{
-					"type": 	"ATTACKS_NEAREST_CREATURE"
+					"type": 	"ATTACKS_NEAREST_CREATURE",
+					"duration": "N_TURNS",
+					"values":[0,1,2,3]
 				}
 			]
 		},
@@ -661,7 +725,9 @@
 			"effects":
 			[
 				{
-					"type": 	"HYPNOTIZED"
+					"type": 	"HYPNOTIZED",
+					"duration": "N_TURNS",
+					"values":[0,1,2,3]
 				}
 			]
 		},
@@ -675,9 +741,12 @@
 			"effects":
 			[
 				{
-					"type": 	"FORGETFULL"
+					"type": 	"FORGETFULL",
+					"duration": "N_TURNS",
+					"values":[0,1,2,3]
 				}
-			]
+			],
+			"limit":["SHOOTER"]
 		},
 		"blind"          :
 		{
@@ -690,16 +759,18 @@
 			[
 				{
 					"type": 	"NOT_ACTIVE",
-					"subtype": 	62, //really ase spell id, is it right?
-					//TODO: duration
+					"subtype": 	62,
+					"duration":	"UNITL_BEING_ATTACKED N_TURNS",
+					"values":[0,0,0,0] 
 				},
 				{
-					"type": 	"GENERAL_ATTACK_REDUCTION"
-					//TODO: duration
+					"type": 	"GENERAL_ATTACK_REDUCTION",
+					"duration":	"UNITL_BEING_ATTACKED N_TURNS" 
 				},
 				{
 					"type": 	"NO_RETALIATION",
-					"duration":	"UNITL_BEING_ATTACKED"
+					"duration":	"UNITL_BEING_ATTACKED",
+					"values":[0,0,0,0] 
 				}
 			]
 		},
@@ -708,7 +779,8 @@
 			"id": 63,
 			"effect": 1,
 			"anim": -1,
-			"ranges": [ "0", "0", "0", "0" ]
+			"ranges": [ "0", "0", "0", "0" ],
+			"immunity":["SIEGE_WEAPON"]
 		},
 		"removeObstacle" :
 		{
@@ -722,7 +794,8 @@
 			"id": 65,
 			"effect": 1,
 			"anim": -1,
-			"ranges": [ "0", "0", "0", "0" ]
+			"ranges": [ "0", "0", "0", "0" ],
+			"immunity":["SIEGE_WEAPON"]
 		},
 		"fireElemental"  :
 		{
@@ -762,12 +835,14 @@
 			[
 				{
 					"type": 	"NOT_ACTIVE",
-					"subtype": 	62
-					//TODO: duration
+					"subtype": 	62,
+					"duration":	"UNITL_BEING_ATTACKED N_TURNS",
+					"values":[0,0,0,0] 
 				},
 				{
 					"type": 	"NO_RETALIATION",
-					"duration":	"UNITL_BEING_ATTACKED"
+					"duration":	"UNITL_BEING_ATTACKED",
+					"values":[0,0,0,0] 
 				}
 			]
 		},
@@ -781,13 +856,16 @@
 			[
 				{
 					"type": 	"POISON",
-					"val" : 	30,
-					"valueType": 	"INDEPENDENT_MAX"
+					"valueType": 	"INDEPENDENT_MAX",
+					"duration": "N_TURNS",
+					"values":[30,30,30,30]
 				},
 				{
 					"type": 	"STACK_HEALTH",
 					"val" : 	-10,
-					"valueType": 	"PERCENT_TO_ALL"
+					"valueType": 	"PERCENT_TO_ALL",
+					"duration": "N_TURNS",
+					"values":[-10,-10,-10,-10]
 				}
 			]
 		},
@@ -801,9 +879,9 @@
 			[
 				{
 					"type": 	"BIND_EFFECT",
-					"val" : 	30,
-					"turns" : 	1,
-					"duration" : 	"PERMANENT"
+					"duration" : 	"PERMANENT",
+					"addInfo" : 	-1,
+					"values":[0,0,0,0]
 				}
 			]
 		},
@@ -818,12 +896,14 @@
 				{
 					"type": 	"PRIMARY_SKILL",
 					"subtype": 	0,
-					"val" : 	-2,
+					"duration": "N_TURNS",
+					"values":[-2,-2,-2,-2]
 				},
 				{
 					"type": 	"PRIMARY_SKILL",
 					"subtype": 	1,
-					"val" : 	-2,
+					"duration": "N_TURNS",
+					"values":[-2,-2,-2,-2]
 				}
 			]
 		},
@@ -838,11 +918,13 @@
 				{
 					"type": 	"NOT_ACTIVE",
 					"subtype": 	74,
-					//TODO: duration
+					"duration":	"UNITL_BEING_ATTACKED N_TURNS",
+					"values":[0,0,0,0]  
 				},
 				{
 					"type": 	"NO_RETALIATION",
-					"duration":	"UNITL_BEING_ATTACKED"
+					"duration":	"UNITL_BEING_ATTACKED",
+					"values":[0,0,0,0] 
 				}
 			]
 		},
@@ -856,8 +938,9 @@
 			[
 				{
 					"type": 	"STACK_HEALTH",
-					"val" : 	-50,
-					"valueType": 	"PERCENT_TO_ALL"
+					"valueType": 	"PERCENT_TO_ALL",
+					"duration": "N_TURNS",
+					"values":[-50,-50,-50,-50]
 				}
 			]
 		},
@@ -874,7 +957,7 @@
 			"effect": -1,
 			"anim": 38,
 			"ranges": [ "0", "0", "0", "0" ],
-			"flags" : ["damage"]
+			"flags" : ["damage, offensive"]
 		},
 		"dispelHelpful"  :
 		{
@@ -901,9 +984,9 @@
 				{
 					"type": 	"PRIMARY_SKILL",
 					"subtype": 	1,
-					"val" : 	-3,
 					"duration" : 	"PERMANENT",
-					"valueType": 	"ADDITIVE_VALUE"
+					"valueType": 	"ADDITIVE_VALUE",
+					"values":[-3,-3,-3,-3]
 				}
 			]
 		},

+ 9 - 156
lib/BattleState.cpp

@@ -120,7 +120,6 @@ void BattleInfo::calculateCasualties( std::map<ui32,si32> *casualties ) const
 	}
 }
 
-
 int BattleInfo::calculateSpellDuration( const CSpell * spell, const CGHeroInstance * caster, int usedSpellPower)
 {
 	if(!caster)
@@ -167,9 +166,9 @@ ui32 BattleInfo::calculateHealedHP(const CGHeroInstance * caster, const CSpell *
 	bool resurrect = resurrects(spell->id);
 	int healedHealth;
 	if (spell->id == Spells::SACRIFICE && sacrificedStack)
-		healedHealth = (caster->getPrimSkillLevel(2) + sacrificedStack->MaxHealth() + spell->powers[caster->getSpellSchoolLevel(spell)]) * sacrificedStack->count;
+		healedHealth = (caster->getPrimSkillLevel(PrimarySkill::SPELL_POWER) + sacrificedStack->MaxHealth() + spell->powers[caster->getSpellSchoolLevel(spell)]) * sacrificedStack->count;
 	else
-		healedHealth = caster->getPrimSkillLevel(2) * spell->power + spell->powers[caster->getSpellSchoolLevel(spell)];
+		healedHealth = caster->getPrimSkillLevel(PrimarySkill::SPELL_POWER) * spell->power + spell->powers[caster->getSpellSchoolLevel(spell)];
 	healedHealth = calculateSpellBonus(healedHealth, spell, caster, stack);
 	return std::min<ui32>(healedHealth, stack->MaxHealth() - stack->firstHPleft + (resurrect ? stack->baseAmount * stack->MaxHealth() : 0));
 }
@@ -746,7 +745,7 @@ std::vector<ui32> BattleInfo::calculateResistedStacks(const CSpell * sp, const C
 
 	}
 
-	if(sp->id == 60) //hypnotize
+	if(sp->id == Spells::HYPNOTIZE) //hypnotize
 	{
 		for(auto it = affectedCreatures.begin(); it != affectedCreatures.end(); ++it)
 		{
@@ -931,163 +930,17 @@ si32 CStack::magicResistance() const
 
 void CStack::stackEffectToFeature(std::vector<Bonus> & sf, const Bonus & sse)
 {
-	//TODO: get rid of this spaghetti code
-
 	const CSpell * sp = VLC->spellh->spells[sse.sid];
-	si32 power = sp->powers[sse.val];
-
-	auto addFull = [&](Bonus::BonusType type, si16 subtype, si32 value, si32 additionalInfo, si32 limit)
-	{
-	 	sf.push_back(featureGenerator(type, subtype, value, sse.turnsRemain,additionalInfo, limit));
-	 	sf.back().sid = sse.sid;		
-	};
-	
-	auto add = [&](Bonus::BonusType type, si16 subtype, si32 value)
-	{
-		addFull(type, subtype, value, 0, Bonus::NO_LIMIT);
-	};
-
-	auto addVT = [&](Bonus::BonusType type, si16 subtype, si32 value, ui8 valType)
-	{
-		add(type, subtype, value);
-		sf.back().valType = valType;
-	};
-	
-	auto addVTFull = [&](Bonus::BonusType type, si16 subtype, si32 value, ui8 valType,si32 additionalInfo)
-	{
-		addFull(type, subtype, value, additionalInfo, Bonus::NO_LIMIT);
-		sf.back().valType = valType;
-	};
 
-	auto addDur = [&](Bonus::BonusType type, si16 subtype, si32 value, ui8 duration)
-	{
-		add(type, subtype, value);
-		sf.back().duration = duration;
-	};
+	std::vector<Bonus> tmp;
+	sp->getEffects(tmp, sse.val);
 
-	switch(sse.sid)
+	BOOST_FOREACH(Bonus& b, tmp)
 	{
-	case Spells::SHIELD:
-		add(Bonus::GENERAL_DAMAGE_REDUCTION, 0, power);
-		break;
-	case Spells::AIR_SHIELD:
-	 	add(Bonus::GENERAL_DAMAGE_REDUCTION, 1, power);
-	 	break;
-	case Spells::FIRE_SHIELD:
-	 	add(Bonus::FIRE_SHIELD, 0, power);
-	 	break;
-	case Spells::PROTECTION_FROM_AIR:
-	 	add(Bonus::SPELL_DAMAGE_REDUCTION, 0, power);
-	 	break;
-	case Spells::PROTECTION_FROM_FIRE:
-	 	add(Bonus::SPELL_DAMAGE_REDUCTION, 1, power);
-	 	break;
-	case Spells::PROTECTION_FROM_WATER:
-	 	add(Bonus::SPELL_DAMAGE_REDUCTION, 2, power);
-	 	break;
-	case Spells::PROTECTION_FROM_EARTH:
-	 	add(Bonus::SPELL_DAMAGE_REDUCTION, 3, power);
-	 	break;
-	case Spells::ANTI_MAGIC:
-	 	addVT(Bonus::LEVEL_SPELL_IMMUNITY, GameConstants::SPELL_LEVELS, power - 1, Bonus::INDEPENDENT_MAX);break;
-	case Spells::MAGIC_MIRROR:
-		addVT(Bonus::MAGIC_MIRROR, -1, power, Bonus::INDEPENDENT_MAX);
-		break;
-	case Spells::BLESS:
-		addVT(Bonus::ALWAYS_MAXIMUM_DAMAGE, -1, power,Bonus::INDEPENDENT_MAX);
-	 	break;
-	case Spells::CURSE:
-		addVTFull(Bonus::ALWAYS_MINIMUM_DAMAGE, -1, power, Bonus::INDEPENDENT_MAX, sse.val >= 2 ? 20 : 0);
-	 	break;
-	case Spells::BLOODLUST:
-		addFull(Bonus::PRIMARY_SKILL, PrimarySkill::ATTACK, power, 0, Bonus::ONLY_MELEE_FIGHT);
-	 	break;
-	case Spells::PRECISION:
-		addFull(Bonus::PRIMARY_SKILL, PrimarySkill::ATTACK, power, 0, Bonus::ONLY_DISTANCE_FIGHT);
-	 	break;
-	case Spells::WEAKNESS:
-		add(Bonus::PRIMARY_SKILL, PrimarySkill::ATTACK, -1 * power);
-		break;
-	case Spells::STONE_SKIN:
-		add(Bonus::PRIMARY_SKILL, PrimarySkill::DEFENSE, power);
-	 	break;
-	case Spells::DISRUPTING_RAY:
-		addVT(Bonus::PRIMARY_SKILL, PrimarySkill::DEFENSE, -1 * power,Bonus::ADDITIVE_VALUE);
-	 	break;
-	case Spells::PRAYER:
-		add(Bonus::PRIMARY_SKILL, PrimarySkill::ATTACK, power);
-		add(Bonus::PRIMARY_SKILL, PrimarySkill::DEFENSE, power);
-		add(Bonus::STACKS_SPEED, 0, power);
-	 	break;
-	case Spells::MIRTH:
-		add(Bonus::MORALE, 0, power);
-	 	break;
-	case Spells::SORROW:
-		add(Bonus::MORALE, 0, -1 * power);
-	 	break;
-	case Spells::FORTUNE:
-		add(Bonus::LUCK, 0, power);
-	 	break;
-	case Spells::MISFORTUNE:
-		add(Bonus::LUCK, 0, -1 * power);
-	 	break;
-	case Spells::HASTE: //haste
-		add(Bonus::STACKS_SPEED, 0, power);
-	 	break;
-	case Spells::SLOW:
-		addVT(Bonus::STACKS_SPEED, 0, -1 * ( 100 - power ),Bonus::PERCENT_TO_ALL);
-	 	break;
-	case Spells::SLAYER:
-		add(Bonus::SLAYER, 0, sse.val);
-	 	break;
-	case Spells::FRENZY:
-		add(Bonus::IN_FRENZY, 0, power/100.0);
-	 	break;
-	case Spells::COUNTERSTRIKE:
-		add(Bonus::ADDITIONAL_RETALIATION, 0, power);
-	 	break;
-	case Spells::BERSERK:
-		add(Bonus::ATTACKS_NEAREST_CREATURE, 0, sse.val);
-	 	break;
-	case Spells::HYPNOTIZE:
-	 	add(Bonus::HYPNOTIZED, 0, sse.val);
-	 	break;
-	case Spells::FORGETFULNESS:
-		add(Bonus::FORGETFULL, 0, sse.val);
-	 	break;
-	case Spells::BLIND:
-		addDur(Bonus::NOT_ACTIVE, sse.sid, 0, Bonus::UNITL_BEING_ATTACKED | Bonus::N_TURNS);
-		addDur(Bonus::GENERAL_ATTACK_REDUCTION, 0, power, Bonus::UNITL_BEING_ATTACKED | Bonus::N_TURNS);
-		addDur(Bonus::NO_RETALIATION,0,0, Bonus::UNITL_BEING_ATTACKED);
-	 	break;
-	case Spells::STONE_GAZE:
-	case Spells::PARALYZE:
-		addDur(Bonus::NOT_ACTIVE, sse.sid, 0, Bonus::UNITL_BEING_ATTACKED | Bonus::N_TURNS);
-		addDur(Bonus::NO_RETALIATION,0,0, Bonus::UNITL_BEING_ATTACKED);
-		break;
-	case Spells::POISON: //Poison
-		addVT(Bonus::POISON, 0, 30,Bonus::INDEPENDENT_MAX); //max hp penalty from this source
-		addVT(Bonus::STACK_HEALTH, 0, -10, Bonus::PERCENT_TO_ALL);
-		break;
-	case Spells::BIND:
-		sf.push_back(featureGenerator(Bonus::BIND_EFFECT, 0, 0, 1)); //marker
-		sf.back().duration = Bonus::PERMANENT;
-	 	sf.back().sid = sse.sid;
-		break;
-	case Spells::DISEASE:
-		add(Bonus::PRIMARY_SKILL, PrimarySkill::ATTACK, -2);
-		add(Bonus::PRIMARY_SKILL, PrimarySkill::DEFENSE, -2);
-		break;
-	case Spells::AGE:
-		addVT(Bonus::STACK_HEALTH, 0, -50, Bonus::PERCENT_TO_ALL);
-		break;
-	case Spells::ACID_BREATH_DEFENSE:
-		sf.push_back(featureGenerator(Bonus::PRIMARY_SKILL, PrimarySkill::DEFENSE, -sse.turnsRemain, 1));
-	 	sf.back().sid = sse.sid;
-		sf.back().duration = Bonus::PERMANENT;
-		sf.back().valType = Bonus::ADDITIVE_VALUE;
-		break;
+		b.turnsRemain =  sse.turnsRemain;
+		sf.push_back(b);
 	}
+
 }
 
 bool CStack::willMove(int turn /*= 0*/) const

+ 0 - 7
lib/BattleState.h

@@ -182,13 +182,6 @@ public:
 		return hb;
 	}
 
-	static inline Bonus featureGeneratorVT(Bonus::BonusType type, si16 subtype, si32 value, ui16 turnsRemain, ui8 valType)
-	{
-		Bonus ret = makeFeatureVal(type, Bonus::N_TURNS, subtype, value, Bonus::SPELL_EFFECT, turnsRemain);
-		ret.valType = valType;
-		return ret;
-	}
-
 	static bool isMeleeAttackPossible(const CStack * attacker, const CStack * defender, BattleHex attackerPos = BattleHex::INVALID, BattleHex defenderPos = BattleHex::INVALID);
 
 	bool doubleWide() const;

+ 6 - 83
lib/CBattleCallback.cpp

@@ -835,7 +835,7 @@ TDmgRange CBattleInfoCallback::calculateDmgRange(const BattleAttackInfo &info) c
 	if(const Bonus *slayerEffect = info.attackerBonuses->getEffect(Spells::SLAYER)) //slayer handling
 	{
 		std::vector<int> affectedIds;
-		int spLevel = slayerEffect->val;
+		int spLevel = slayerEffect->val; 
 
 		for(int g = 0; g < VLC->creh->creatures.size(); ++g)
 		{
@@ -1487,7 +1487,6 @@ ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleIsImmune(const C
 {
 	RETURN_IF_NOT_BATTLE(ESpellCastProblem::INVALID);
 
-
 	// Get stack at destination hex -> subject of our spell.
 	const CStack * subject = battleGetStackByPos(dest, !spell->isRisingSpell()); //only alive if not rising spell
 
@@ -1495,28 +1494,14 @@ 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;
 
 		switch (spell->id) //TODO: more general logic for new spells?
 		{
-		case Spells::DESTROY_UNDEAD:
-			if (!subject->hasBonusOfType(Bonus::UNDEAD))
-				return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
-			break;
-		case Spells::DEATH_RIPPLE:
-			if (subject->hasBonusOfType(Bonus::SIEGE_WEAPON))
-				return ESpellCastProblem::STACK_IMMUNE_TO_SPELL; //don't break here - undeads and war machines are immune, non-living are not
-		case Spells::BLESS:
-		case Spells::CURSE: //undeads are immune to bless & curse
-			if (subject->hasBonusOfType(Bonus::UNDEAD))
-				return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
-			break;
-		case Spells::HASTE:
-		case Spells::SLOW:
-		case Spells::TELEPORT:
 		case Spells::CLONE:
-			if (subject->hasBonusOfType(Bonus::SIEGE_WEAPON))
-				return ESpellCastProblem::STACK_IMMUNE_TO_SPELL; //war machines are immune to some spells than involve movement
-			if (spell->id == Spells::CLONE && caster) //TODO: how about stacks casting Clone?
+			if (caster) //TODO: how about stacks casting Clone?
 			{
 				if (vstd::contains(subject->state, EBattleStackState::CLONED))
 					return ESpellCastProblem::STACK_IMMUNE_TO_SPELL; //can't clone already cloned creature
@@ -1526,11 +1511,6 @@ ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleIsImmune(const C
 					return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
 			}
 			break;
-		case Spells::FORGETFULNESS:
-		case Spells::PRECISION:
-			if (!subject->hasBonusOfType(Bonus::SHOOTER))
-				return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
-			break;
 		case Spells::DISPEL_HELPFUL_SPELLS:
 			{
 				TBonusListPtr spellBon = subject->getSpellBonuses();
@@ -1551,69 +1531,12 @@ ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleIsImmune(const C
 			break;
 		}
 
-		const bool damageSpell = spell->isDamageSpell();
-		auto battleTestElementalImmunity = [&](Bonus::BonusType element) -> bool //helper for battleisImmune
-		{
-			if (!spell->isPositive()) //negative or indifferent
-			{
-				if ((damageSpell && subject->hasBonusOfType(element, 2)) || subject->hasBonusOfType(element, 1))
-					return true;
-			}
-			else if (spell->isPositive()) //positive
-			{
-				if (subject->hasBonusOfType(element, 0)) //must be immune to all spells
-					return true;
-			}
-			return false;
-		};
-
-
-		if (damageSpell && subject->hasBonusOfType(Bonus::DIRECT_DAMAGE_IMMUNITY))
-			return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
-
-		if (spell->fire)
-		{
-			if (battleTestElementalImmunity(Bonus::FIRE_IMMUNITY))
-				return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
-		}
-		if (spell->water)
-		{
-			if (battleTestElementalImmunity(Bonus::WATER_IMMUNITY))
-				return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
-		}
-		if (spell->earth)
-		{
-			if (battleTestElementalImmunity(Bonus::EARTH_IMMUNITY))
-				return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
-		}
-		if (spell->air)
-		{
-			if (battleTestElementalImmunity(Bonus::AIR_IMMUNITY))
-				return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
-		}
-		if (spell->isMindSpell())
-		{
-			if (subject->hasBonusOfType(Bonus::MIND_IMMUNITY))
-				return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
-		}
-
 		if (spell->isRisingSpell())
 		{
 			if (subject->count >= subject->baseAmount) //TODO: calculate potential hp raised
 				return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
 		}
 
-		TBonusListPtr immunities = subject->getBonuses(Selector::type(Bonus::LEVEL_SPELL_IMMUNITY));
-		if(subject->hasBonusOfType(Bonus::NEGATE_ALL_NATURAL_IMMUNITIES))
-		{
-			immunities->remove_if([](const Bonus* b){  return b->source == Bonus::CREATURE_ABILITY;  });
-		}
-
-		if(subject->hasBonusOfType(Bonus::SPELL_IMMUNITY, spell->id)
-			|| ( immunities->size() > 0  &&  immunities->totalValue() >= spell->level  &&  spell->level))
-		{
-			return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
-		}
 	}
 	else //no target stack on this tile
 	{
@@ -1661,7 +1584,7 @@ ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCastThisSpell
 	}
 
 
-	if(spell->id < 10) //it's adventure spell (not combat))
+	if(!spell->combatSpell)
 		return ESpellCastProblem::ADVMAP_SPELL_INSTEAD_OF_BATTLE_SPELL;
 
 	//TODO?

+ 175 - 54
lib/CSpellHandler.cpp

@@ -130,9 +130,10 @@ CSpellHandler::CSpellHandler()
 
 CSpell::CSpell()
 {
-	_isDamage = false;
-	_isMind = false;
-	_isRising = false;
+	isDamage = false;
+	isMind = false;
+	isRising = false;
+	isOffensive = false;
 }
 
 std::vector<BattleHex> CSpell::rangeInHexes(BattleHex centralHex, ui8 schoolLvl, ui8 side, bool *outDroppedHexes) const
@@ -262,29 +263,118 @@ bool CSpell::isNegative() const
 
 bool CSpell::isRisingSpell() const
 {
-	return _isRising;
+	return isRising;
 }
 
 bool CSpell::isDamageSpell() const
 {
-	return _isDamage;
+	return isDamage;
 }
 
 bool CSpell::isMindSpell() const
 {
-	return _isMind;
+	return isMind;
 }
 
-void CSpell::getEffects(std::vector<Bonus>& lst) const
+bool CSpell::isOffensiveSpell() const
 {
-	lst.reserve(lst.size() + _effects.size());
+	return isOffensive;
+}
+
+bool CSpell::hasEffects() const
+{
+	return !effects[0].empty();
+}
+
 
-	BOOST_FOREACH (Bonus b, _effects)
+void CSpell::getEffects(std::vector<Bonus>& lst, const int level) const
+{
+	if (level < 0 || level>3)
 	{
+		tlog1 << __FUNCTION__ << " invalid school level " << level;
+		return;
+	}
+	lst.reserve(lst.size() + effects[level].size());
+
+	BOOST_FOREACH (Bonus b, effects[level])
+	{
+		//TODO: value, add value
 		lst.push_back(b);
 	}
 }
 
+bool CSpell::isImmuneBy(const IBonusBearer* obj) const
+{
+	BOOST_FOREACH(auto b, limiters)
+	{
+		if (!obj->hasBonusOfType(b))
+			return true;
+	}
+
+	BOOST_FOREACH(auto b, immunities)
+	{
+		if (obj->hasBonusOfType(b))
+			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 (obj->hasBonusOfType(element, 0)) //must be immune to all spells
+				return true;
+		}
+		return false;
+	};
+
+	if (fire)
+	{
+		if (battleTestElementalImmunity(Bonus::FIRE_IMMUNITY))
+			return true;
+	}
+	if (water)
+	{
+		if (battleTestElementalImmunity(Bonus::WATER_IMMUNITY))
+			return true;
+	}
+
+	if (earth)
+	{
+		if (battleTestElementalImmunity(Bonus::EARTH_IMMUNITY))
+			return true;
+	}
+	if (air)
+	{
+		if (battleTestElementalImmunity(Bonus::AIR_IMMUNITY))
+			return true;
+	}
+
+	TBonusListPtr immunities = 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;  });
+	}
+
+	if(obj->hasBonusOfType(Bonus::SPELL_IMMUNITY, id)
+		|| ( immunities->size() > 0  &&  immunities->totalValue() >= level  &&  level))
+	{
+		return true;
+	}
+
+	return false;
+}
+
 
 bool DLL_LINKAGE isInScreenRange(const int3 &center, const int3 &pos)
 {
@@ -329,44 +419,31 @@ void CSpellHandler::loadSpells()
 {
 	CLegacyConfigParser parser("DATA/SPTRAITS.TXT");
 
-	for(int i=0; i<5; i++) // header
-		parser.endLine();
-
-	do //read adventure map spells
+	auto read = [&,this](bool combat, bool alility)
 	{
-		CSpell * spell = loadSpell(parser);
-		spell->id = spells.size();
-		spell->combatSpell = false;
-		spell->creatureAbility = false;
-		spells.push_back(spell);
-	}
-	while (parser.endLine() && !parser.isNextEntryEmpty());
-
-	for(int i=0; i<3; i++)
-		parser.endLine();
+		do
+		{
+			CSpell * spell = loadSpell(parser);
+			spell->id = spells.size();
+			spell->combatSpell = combat;
+			spell->creatureAbility = alility;
+			spells.push_back(spell);
+		}
+		while (parser.endLine() && !parser.isNextEntryEmpty());
+	};
 
-	do //read battle spells
+	auto skip = [&](int cnt)
 	{
-		CSpell * spell = loadSpell(parser);
-		spell->id = spells.size();
-		spell->combatSpell = true;
-		spell->creatureAbility = false;
-		spells.push_back(spell);
-	}
-	while (parser.endLine() && !parser.isNextEntryEmpty());
+		for(int i=0; i<cnt; i++)
+			parser.endLine();
+	};
 
-	for(int i=0; i<3; i++)
-		parser.endLine();
-
-	do //read creature abilities
-	{
-		CSpell * spell = loadSpell(parser);
-		spell->id = spells.size();
-		spell->combatSpell = true;
-		spell->creatureAbility = true;
-		spells.push_back(spell);
-	}
-	while (parser.endLine() && !parser.isNextEntryEmpty());
+	skip(5);// header
+	read(false,false); //read adventure map spells
+	skip(3);
+	read(true,false); //read battle spells
+	skip(3);
+	read(true,true);//read creature abilities
 
 	boost::replace_first (spells[Spells::DISRUPTING_RAY]->attributes, "2", ""); // disrupting ray will now affect single creature
 
@@ -404,35 +481,79 @@ void CSpellHandler::loadSpells()
 			{
 				if (flag == "damage")
 				{
-					s->_isDamage = true;
+					s->isDamage = true;
 				}
 				else if (flag == "rising")
 				{
-					s->_isRising = true;
+					s->isRising = true;
 				}
 				else if (flag == "mind")
 				{
-					s->_isMind = true;
+					s->isMind = true;
+				}
+				else if (flag == "offensive")
+				{
+					s->isOffensive = true;
 				}
-
 			}
 		}
 
 		const JsonNode & effects_node = spell.second["effects"];
 
-		if (!effects_node.isNull())
+		BOOST_FOREACH (const JsonNode & bonus_node, effects_node.Vector())
 		{
-			BOOST_FOREACH (const JsonNode & bonus_node, effects_node.Vector())
+			auto &v_node = bonus_node["values"];
+			auto &a_node = bonus_node["ainfos"];
+
+			auto v = v_node.convertTo<std::vector<int> >();
+			auto a = a_node.convertTo<std::vector<int> >();
+
+			for (int i=0; i<4 ; i++)
 			{
 				Bonus * b = JsonUtils::parseBonus(bonus_node);
-				b->sid = s->id;
-				b->source = Bonus::SPELL_EFFECT;
-				b->duration = Bonus::N_TURNS; //default
-				//TODO: make duration configurable
-				s->_effects.push_back(*b);
+				b->sid = s->id; //for all
+				b->source = Bonus::SPELL_EFFECT;//for all
+				b->val = s->powers[i];
+
+				if (!v.empty())
+					b->val = v[i];
+				if (!a.empty())
+					b->additionalInfo = a[i];
+
+				s->effects[i].push_back(*b);
 			}
+
 		}
 
+
+		auto find_in_map = [](std::string name, std::vector<Bonus::BonusType> &vec)
+		{
+			auto it = bonusNameMap.find(name);
+			if (it == bonusNameMap.end())
+			{
+				tlog1 << "Error: invalid bonus name" << name << std::endl;
+			}
+			else
+			{
+				vec.push_back((Bonus::BonusType)it->second);
+			}
+		};
+
+		auto read_node = [&](std::string name, std::vector<Bonus::BonusType> &vec)
+		{
+			const JsonNode & node = spell.second[name];
+
+			if (!node.isNull())
+			{
+				auto names = node.convertTo<std::vector<std::string> >();
+				BOOST_FOREACH(auto name, names)
+				   find_in_map(name, vec);
+			}
+		};
+
+		read_node("immunity",s->immunities);
+		read_node("limit",s->limiters);
+
 	}
 
 	//spell fixes

+ 17 - 10
lib/CSpellHandler.h

@@ -56,26 +56,33 @@ public:
 
 	bool isRisingSpell() const;
 	bool isDamageSpell() const;
-	bool isMindSpell() const;
+	bool isMindSpell() const;	
+	bool isOffensiveSpell() const;
+	
+	bool hasEffects() const;
+	void getEffects(std::vector<Bonus> &lst, const int level) const;
 
-
-	void getEffects(std::vector<Bonus> & lst) const;
+	bool isImmuneBy(const IBonusBearer *obj) 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 & _effects;
+		h & isRising & isDamage & isMind;
+		h & effects & immunities & limiters;
 	}
 	friend class CSpellHandler;
 
 private:
-	bool _isRising;
-	bool _isDamage;
-	bool _isMind;
-
-	std::vector<Bonus> _effects;
+	bool isRising;
+	bool isDamage;
+	bool isMind;
+	bool isOffensive;
+
+	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
+	
 
 };
 

+ 7 - 1
lib/Filesystem/CResourceLoader.cpp

@@ -253,7 +253,10 @@ EResType::Type EResTypeHelper::getTypeFromExtension(std::string extension)
 	        (".PAL",   EResType::PALETTE)
 	        (".VCGM1", EResType::CLIENT_SAVEGAME)
 	        (".VLGM1", EResType::LIB_SAVEGAME)
-	        (".VSGM1", EResType::SERVER_SAVEGAME);
+	        (".VSGM1", EResType::SERVER_SAVEGAME)
+	        (".ERM",   EResType::ERM)
+	        (".ERT",   EResType::ERT)
+	        (".ERS",   EResType::ERS);
 
 	auto iter = stringToRes.find(extension);
 	if (iter == stringToRes.end())
@@ -285,6 +288,9 @@ std::string EResTypeHelper::getEResTypeAsString(EResType::Type type)
 		MAP_ENUM(LIB_SAVEGAME)
 		MAP_ENUM(SERVER_SAVEGAME)
 		MAP_ENUM(DIRECTORY)
+		MAP_ENUM(ERM)
+		MAP_ENUM(ERT)
+		MAP_ENUM(ERS)
 		MAP_ENUM(OTHER);
 
 #undef MAP_ENUM

+ 3 - 0
lib/Filesystem/CResourceLoader.h

@@ -60,6 +60,9 @@ namespace EResType
 		LIB_SAVEGAME,
 		SERVER_SAVEGAME,
 		DIRECTORY,
+		ERM,
+		ERT,
+		ERS,
 		OTHER
 	};
 }

+ 2 - 2
lib/HeroBonus.cpp

@@ -418,9 +418,9 @@ si32 IBonusBearer::Attack() const
 {
 	si32 ret = valOfBonuses(Bonus::PRIMARY_SKILL, PrimarySkill::ATTACK);
 
-	if (int frenzyPower = valOfBonuses(Bonus::IN_FRENZY)) //frenzy for attacker
+	if (double frenzyPower = valOfBonuses(Bonus::IN_FRENZY)) //frenzy for attacker
 	{
-		ret += frenzyPower * Defense(false);
+		ret += (frenzyPower/100) * (double)Defense(false);
 	}
 	vstd::amax(ret, 0);
 

+ 36 - 15
lib/JsonNode.cpp

@@ -903,6 +903,25 @@ Bonus * JsonUtils::parseBonus (const JsonVector &ability_vec) //TODO: merge with
 	b->turnsRemain = 0;
 	return b;
 }
+
+template <typename T>
+const T & parseByMapStr(const std::map<std::string, T> & map, const std::string & val, std::string err)
+{
+	static T defaultValue;
+	
+	auto it = map.find(val);
+	if (it == map.end())
+	{
+		tlog1 << "Error: invalid " << err << val << std::endl;
+		return defaultValue;
+	}
+	else
+	{
+		return it->second;
+	}
+
+}
+
 template <typename T>
 const T & parseByMap(const std::map<std::string, T> & map, const JsonNode * val, std::string err)
 {
@@ -910,20 +929,11 @@ const T & parseByMap(const std::map<std::string, T> & map, const JsonNode * val,
 	if (!val->isNull())
 	{
 		std::string type = val->String();
-		auto it = map.find(type);
-		if (it == map.end())
-		{
-			tlog1 << "Error: invalid " << err << type << std::endl;
-			return defaultValue;
-		}
-		else
-		{
-			return it->second;
-		}
+		return parseByMapStr(map, type, err);
 	}
 	else
 		return defaultValue;
-};
+}
 
 void JsonUtils::resolveIdentifier (si32 &var, const JsonNode &node, std::string name)
 {
@@ -937,9 +947,9 @@ void JsonUtils::resolveIdentifier (si32 &var, const JsonNode &node, std::string
 				var = value->Float();
 				break;
 			case JsonNode::DATA_STRING:
-				VLC->modh->identifiers.requestIdentifier (value->String(), [&](si32 identifier)
-				{
-					var = identifier;
+				VLC->modh->identifiers.requestIdentifier (value->String(), [&](si32 identifier)
+				{
+					var = identifier;
 				});
 				break;
 			default:
@@ -994,7 +1004,18 @@ Bonus * JsonUtils::parseBonus (const JsonNode &ability)
 		b->valType = parseByMap(bonusLimitEffect, value, "effect range ");
 	value = &ability["duration"];
 	if (!value->isNull())
-		b->valType = parseByMap(bonusDurationMap, value, "duration type ");
+	{
+		int dur = 0;
+		std::vector<std::string> strs;
+		boost::split(strs, value->String(), boost::is_any_of("\t "));
+		BOOST_FOREACH(auto s, strs)
+		{
+		  dur |=parseByMapStr(bonusDurationMap, s, "duration type ");
+		}
+ 
+		b->duration = dur;
+	}
+		
 	value = &ability["source"];
 	if (!value->isNull())
 		b->valType = parseByMap(bonusSourceMap, value, "source type ");

+ 58 - 112
server/CGameHandler.cpp

@@ -3994,58 +3994,11 @@ void CGameHandler::handleSpellCasting( int spellID, int spellLvl, BattleHex dest
 			vstd::amin(sc.dmgToDisplay, (*attackedCres.begin())->count); //stack is already reduced after attack
 	}
 	StacksInjured si;
-
+	
 	//applying effects
-	switch (spellID)
+	
+	if (spell->isOffensiveSpell())
 	{
-	case Spells::QUICKSAND:
-	case Spells::LAND_MINE:
-		{
-			std::vector<BattleHex> availableTiles;
-			for(int i = 0; i < GameConstants::BFIELD_SIZE; i += 1)
-			{
-				BattleHex hex = i;
-				if(hex.getX() > 2 && hex.getX() < 14 && !battleGetStackByPos(hex, false) & !battleGetObstacleOnPos(hex, false))
-					availableTiles.push_back(hex);
-			}
-			boost::range::random_shuffle(availableTiles);
-
-			const int patchesForSkill[] = {4, 4, 6, 8};
-			const int patchesToPut = std::min<int>(patchesForSkill[spellLvl], availableTiles.size());
-
-			//land mines or quicksand patches are handled as spell created obstacles
-			for (int i = 0; i < patchesToPut; i++)
-				placeObstacle(availableTiles[i]);
-		}
-
-		break;
-	case Spells::FORCE_FIELD:
-		placeObstacle(destination);
-		break;
-	case Spells::FIRE_WALL:
-		{
-			//fire wall is build from multiple obstacles - one fire piece for each affected hex
-			auto affectedHexes = spell->rangeInHexes(destination, spellLvl, casterSide);
-			BOOST_FOREACH(BattleHex hex, affectedHexes)
-				placeObstacle(hex);
-		}
-		break;
-	//damage spells
-	case Spells::MAGIC_ARROW:
-	case Spells::ICE_BOLT:
-	case Spells::LIGHTNING_BOLT:
-	case Spells::IMPLOSION:
-	case Spells::CHAIN_LIGHTNING:
-	case Spells::FROST_RING:
-	case Spells::FIREBALL:
-	case Spells::INFERNO:
-	case Spells::METEOR_SHOWER:
-	case Spells::DEATH_RIPPLE:
-	case Spells::DESTROY_UNDEAD:
-	case Spells::ARMAGEDDON:
-	case Spells::TITANS_LIGHTNING_BOLT:
-	case Spells::THUNDERBOLT: //(thunderbirds)
-		{
 			int spellDamage = 0;
 			if (stack && mode != ECastingMode::MAGIC_MIRROR)
 			{
@@ -4088,48 +4041,10 @@ void CGameHandler::handleSpellCasting( int spellID, int spellLvl, BattleHex dest
 
 				if (spellID == Spells::CHAIN_LIGHTNING)
 					++chainLightningModifier;
-			}
-			break;
-		}
-		// permanent effects
-	case Spells::SHIELD:
-	case Spells::AIR_SHIELD:
-	case Spells::FIRE_SHIELD:
-	case Spells::PROTECTION_FROM_AIR:
-	case Spells::PROTECTION_FROM_FIRE:
-	case Spells::PROTECTION_FROM_WATER:
-	case Spells::PROTECTION_FROM_EARTH:
-	case Spells::ANTI_MAGIC:
-	case Spells::MAGIC_MIRROR:
-	case Spells::BLESS:
-	case Spells::CURSE:
-	case Spells::BLOODLUST:
-	case Spells::PRECISION:
-	case Spells::WEAKNESS:
-	case Spells::STONE_SKIN:
-	case Spells::DISRUPTING_RAY:
-	case Spells::PRAYER:
-	case Spells::MIRTH:
-	case Spells::SORROW:
-	case Spells::FORTUNE:
-	case Spells::MISFORTUNE:
-	case Spells::HASTE:
-	case Spells::SLOW:
-	case Spells::SLAYER:
-	case Spells::FRENZY:
-	case Spells::COUNTERSTRIKE:
-	case Spells::BERSERK:
-	case Spells::HYPNOTIZE:
-	case Spells::FORGETFULNESS:
-	case Spells::BLIND:
-	case Spells::STONE_GAZE:
-	case Spells::POISON:
-	case Spells::BIND:
-	case Spells::DISEASE:
-	case Spells::PARALYZE:
-	case Spells::AGE:
-	case Spells::ACID_BREATH_DEFENSE:
-		{
+			}		
+	}
+	else if (spell->hasEffects())
+	{
 			int stackSpellPower = 0;
 			if (stack && mode != ECastingMode::MAGIC_MIRROR)
 			{
@@ -4204,25 +4119,10 @@ void CGameHandler::handleSpellCasting( int spellID, int spellLvl, BattleHex dest
 
 			if(!sse.stacks.empty())
 				sendAndApply(&sse);
-			break;
-		}
-	case Spells::TELEPORT:
-		{
-			BattleStackMoved bsm;
-			bsm.distance = -1;
-			bsm.stack = selectedStack;
-			std::vector<BattleHex> tiles;
-			tiles.push_back(destination);
-			bsm.tilesToMove = tiles;
-			bsm.teleporting = true;
-			sendAndApply(&bsm);
-			break;
-		}
-	case Spells::CURE:
-	case Spells::RESURRECTION:
-	case Spells::ANIMATE_DEAD:
-	case Spells::SACRIFICE:
-		{
+					
+	}
+	else if (spell->isRisingSpell() || spell->id == Spells::CURE)
+	{
 			int hpGained = 0;
 			if (stack)
 			{
@@ -4264,7 +4164,53 @@ void CGameHandler::handleSpellCasting( int spellID, int spellLvl, BattleHex dest
 				BattleStacksRemoved bsr;
 				bsr.stackIDs.insert (selectedStack); //somehow it works for teleport?
 				sendAndApply(&bsr);
+			}		
+	}
+	else
+	switch (spellID)
+	{
+	case Spells::QUICKSAND:
+	case Spells::LAND_MINE:
+		{
+			std::vector<BattleHex> availableTiles;
+			for(int i = 0; i < GameConstants::BFIELD_SIZE; i += 1)
+			{
+				BattleHex hex = i;
+				if(hex.getX() > 2 && hex.getX() < 14 && !battleGetStackByPos(hex, false) & !battleGetObstacleOnPos(hex, false))
+					availableTiles.push_back(hex);
 			}
+			boost::range::random_shuffle(availableTiles);
+
+			const int patchesForSkill[] = {4, 4, 6, 8};
+			const int patchesToPut = std::min<int>(patchesForSkill[spellLvl], availableTiles.size());
+
+			//land mines or quicksand patches are handled as spell created obstacles
+			for (int i = 0; i < patchesToPut; i++)
+				placeObstacle(availableTiles[i]);
+		}
+
+		break;
+	case Spells::FORCE_FIELD:
+		placeObstacle(destination);
+		break;
+	case Spells::FIRE_WALL:
+		{
+			//fire wall is build from multiple obstacles - one fire piece for each affected hex
+			auto affectedHexes = spell->rangeInHexes(destination, spellLvl, casterSide);
+			BOOST_FOREACH(BattleHex hex, affectedHexes)
+				placeObstacle(hex);
+		}
+		break;
+	case Spells::TELEPORT:
+		{
+			BattleStackMoved bsm;
+			bsm.distance = -1;
+			bsm.stack = selectedStack;
+			std::vector<BattleHex> tiles;
+			tiles.push_back(destination);
+			bsm.tilesToMove = tiles;
+			bsm.teleporting = true;
+			sendAndApply(&bsm);
 			break;
 		}
 	case Spells::SUMMON_FIRE_ELEMENTAL:
@@ -5536,7 +5482,7 @@ bool CGameHandler::castSpell(const CGHeroInstance *h, int spellID, const int3 &p
 
 			GiveBonus gb;
 			gb.id = h->id;
-			gb.bonus = Bonus(Bonus::ONE_DAY, Bonus::WATER_WALKING, Bonus::SPELL_EFFECT, 0, Spells::FLY, subtype);
+			gb.bonus = Bonus(Bonus::ONE_DAY, Bonus::WATER_WALKING, Bonus::SPELL_EFFECT, 0, Spells::WATER_WALK, subtype);
 			sendAndApply(&gb);
 		}
 		break;