Selaa lähdekoodia

[refactor]
* spells are now more configurable (unused yet, WiP)
* a few more cleanups

alexvins 12 vuotta sitten
vanhempi
sitoutus
68e91ada1c

+ 920 - 94
config/spell_info.json

@@ -1,94 +1,920 @@
-{
-	// Additional spell info, not included in original heroes III files
-	//   id: spell ID
-	//   effect: -1 -> spell is negative for influenced creatures, 
-    //			  0 -> spell is indifferent for them
-    //			  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])
-
-	"spells":
-	{
-		"summonBoat"     : { "id": 0, "effect": 0, "anim": -1, "ranges": [ "X", "X", "X", "X" ] },
-		"scuttleBoat"    : { "id": 1, "effect": 0, "anim": -1, "ranges": [ "X", "X", "X", "X" ] },
-		"visions"        : { "id": 2, "effect": 0, "anim": -1, "ranges": [ "X", "X", "X", "X" ] },
-		"viewEarth"      : { "id": 3, "effect": 0, "anim": -1, "ranges": [ "X", "X", "X", "X" ] },
-		"disguise"       : { "id": 4, "effect": 0, "anim": -1, "ranges": [ "X", "X", "X", "X" ] },
-		"viewAir"        : { "id": 5, "effect": 0, "anim": -1, "ranges": [ "X", "X", "X", "X" ] },
-		"fly"            : { "id": 6, "effect": 0, "anim": -1, "ranges": [ "X", "X", "X", "X" ] },
-		"waterWalk"      : { "id": 7, "effect": 0, "anim": -1, "ranges": [ "X", "X", "X", "X" ] },
-		"dimensionDoor"  : { "id": 8, "effect": 0, "anim": -1, "ranges": [ "X", "X", "X", "X" ] },
-		"townPortal"     : { "id": 9, "effect": 0, "anim": -1, "ranges": [ "X", "X", "X", "X" ] },
-		"quicksand"      : { "id": 10, "effect": 0, "anim": -1, "ranges": [ "X", "X", "X", "X" ] },
-		"landMine"       : { "id": 11, "effect": 0, "anim": -1, "ranges": [ "X", "X", "X", "X" ] },
-		"forceField"     : { "id": 12, "effect": 0, "anim": -1, "ranges": [ "0", "0", "0", "0" ] },
-		"fireWall"       : { "id": 13, "effect": 0, "anim": -1, "ranges": [ "0", "0", "0", "0" ] },
-		"earthquake"     : { "id": 14, "effect": 0, "anim": -1, "ranges": [ "X", "X", "X", "X" ] },
-		"magicArrow"     : { "id": 15, "effect": -1, "anim": 64, "ranges": [ "0", "0", "0", "0" ] },
-		"iceBolt"        : { "id": 16, "effect": -1, "anim": 46, "ranges": [ "0", "0", "0", "0" ] },
-		"lightningBolt"  : { "id": 17, "effect": -1, "anim": 38, "ranges": [ "0", "0", "0", "0" ] },
-		"implosion"      : { "id": 18, "effect": -1, "anim": 10, "ranges": [ "0", "0", "0", "0" ] },
-		"chainLightning" : { "id": 19, "effect": -1, "anim": 38, "ranges": [ "0", "0", "0", "0" ] },
-		"frostRing"      : { "id": 20, "effect": -1, "anim": 45, "ranges": [ "1", "1", "1", "1" ] },
-		"fireball"       : { "id": 21, "effect": -1, "anim": 53, "ranges": [ "0,1", "0,1", "0,1", "0,1" ] },
-		"inferno"        : { "id": 22, "effect": -1, "anim": 9, "ranges": [ "0-2", "0-2", "0-2", "0-2" ] },
-		"meteorShower"   : { "id": 23, "effect": -1, "anim": 16, "ranges": [ "0,1", "0,1", "0,1", "0,1" ] },
-		"deathRipple"    : { "id": 24, "effect": -1, "anim": 8, "ranges": [ "X", "X", "X", "X" ] },
-		"destroyUndead"  : { "id": 25, "effect": -1, "anim": 29, "ranges": [ "X", "X", "X", "X" ] },
-		"armageddon"     : { "id": 26, "effect": -1, "anim": 12, "ranges": [ "X", "X", "X", "X" ] },
-		"shield"         : { "id": 27, "effect": 1, "anim": 27, "ranges": [ "0", "0", "0", "X" ] },
-		"airShield"      : { "id": 28, "effect": 1, "anim": 2, "ranges": [ "0", "0", "0", "X" ] },
-		"fireShield"     : { "id": 29, "effect": 1, "anim": 11, "ranges": [ "0", "0", "0", "X" ] },
-		"protectAir"     : { "id": 30, "effect": 1, "anim": 22, "ranges": [ "0", "0", "0", "X" ] },
-		"protectFire"    : { "id": 31, "effect": 1, "anim": 24, "ranges": [ "0", "0", "0", "X" ] },
-		"protectWater"   : { "id": 32, "effect": 1, "anim": 23, "ranges": [ "0", "0", "0", "X" ] },
-		"protectEarth"   : { "id": 33, "effect": 1, "anim": 26, "ranges": [ "0", "0", "0", "X" ] },
-		"antiMagic"      : { "id": 34, "effect": 1, "anim": 5, "ranges": [ "0", "0", "0", "X" ] },
-		"dispel"         : { "id": 35, "effect": 0, "anim": 41, "ranges": [ "0", "0", "0", "X" ] },
-		"magicMirror"    : { "id": 36, "effect": 1, "anim": 3, "ranges": [ "0", "0", "0", "0" ] },
-		"cure"           : { "id": 37, "effect": 1, "anim": 39, "ranges": [ "0", "0", "0", "0" ] },
-		"resurrection"   : { "id": 38, "effect": 1, "anim": 79, "ranges": [ "0", "0", "0", "0" ] },
-		"animateDead"    : { "id": 39, "effect": 1, "anim": 79, "ranges": [ "0", "0", "0", "0" ] },
-		"sacrifice"      : { "id": 40, "effect": 1, "anim": 79, "ranges": [ "0", "0", "0", "0" ] },
-		"bless"          : { "id": 41, "effect": 1, "anim": 36, "ranges": [ "0", "0", "0", "X" ], "counters" : [42] },
-		"curse"          : { "id": 42, "effect": -1, "anim": 40, "ranges": [ "0", "0", "0", "X" ], "counters" : [41] },
-		"bloodlust"      : { "id": 43, "effect": 1, "anim": 4, "ranges": [ "0", "0", "0", "X" ], "counters" : [45] },
-		"precision"      : { "id": 44, "effect": 1, "anim": 25, "ranges": [ "0", "0", "0", "X" ] },
-		"weakness"       : { "id": 45, "effect": -1, "anim": 56, "ranges": [ "0", "0", "0", "X" ], "counters" : [43] },
-		"stoneSkin"      : { "id": 46, "effect": 1, "anim": 54, "ranges": [ "0", "0", "0", "X" ] },
-		"disruptingRay"  : { "id": 47, "effect": -1, "anim": 14, "ranges": [ "0", "0", "0", "0" ] },
-		"prayer"         : { "id": 48, "effect": 1, "anim": 0, "ranges": [ "0", "0", "0", "X" ] },
-		"mirth"          : { "id": 49, "effect": 1, "anim": 20, "ranges": [ "0", "0", "0", "X" ], "counters" : [50] },
-		"sorrow"         : { "id": 50, "effect": -1, "anim": 30, "ranges": [ "0", "0", "0", "X" ], "counters" : [49] },
-		"fortune"        : { "id": 51, "effect": 1, "anim": 18, "ranges": [ "0", "0", "0", "X" ], "counters" : [52] },
-		"misfortune"     : { "id": 52, "effect": -1, "anim": 48, "ranges": [ "0", "0", "0", "X" ], "counters" : [51] },
-		"haste"          : { "id": 53, "effect": 1, "anim": 31, "ranges": [ "0", "0", "0", "X" ], "counters" : [54] },
-		"slow"           : { "id": 54, "effect": -1, "anim": 19, "ranges": [ "0", "0", "0", "X" ], "counters" : [53] },
-		"slayer"         : { "id": 55, "effect": 1, "anim": 28, "ranges": [ "0", "0", "0", "0" ] },
-		"frenzy"         : { "id": 56, "effect": 1, "anim": 17, "ranges": [ "0", "0", "0", "0" ] },
-		"titanBolt"      : { "id": 57, "effect": -1, "anim": 38, "ranges": [ "0", "0", "0", "0" ] },
-		"counterstrike"  : { "id": 58, "effect": 1, "anim": 7, "ranges": [ "0", "0", "0", "X" ] },
-		"berserk"        : { "id": 59, "effect": -1, "anim": 35, "ranges": [ "0", "0", "0-1", "0-2" ] },
-		"hypnotize"      : { "id": 60, "effect": -1, "anim": 21, "ranges": [ "0", "0", "0", "0" ] },
-		"forgetfulness"  : { "id": 61, "effect": -1, "anim": 42, "ranges": [ "0", "0", "0", "X" ] },
-		"blind"          : { "id": 62, "effect": -1, "anim": 6, "ranges": [ "0", "0", "0", "0" ] },
-		"teleport"       : { "id": 63, "effect": 1, "anim": -1, "ranges": [ "0", "0", "0", "0" ] },
-		"removeObstacle" : { "id": 64, "effect": 0, "anim": -1, "ranges": [ "X", "X", "X", "X" ] },
-		"clone"          : { "id": 65, "effect": 1, "anim": -1, "ranges": [ "0", "0", "0", "0" ] },
-		"fireElemental"  : { "id": 66, "effect": 0, "anim": -1, "ranges": [ "X", "X", "X", "X" ] },
-		"earthElemental" : { "id": 67, "effect": 0, "anim": -1, "ranges": [ "X", "X", "X", "X" ] },
-		"waterElemental" : { "id": 68, "effect": 0, "anim": -1, "ranges": [ "X", "X", "X", "X" ] },
-		"airElemental"   : { "id": 69, "effect": 0, "anim": -1, "ranges": [ "X", "X", "X", "X" ] },
-		"stoneGaze"      : { "id": 70, "effect": 0, "anim": 70, "ranges": [ "0", "0", "0", "0" ] },
-		"poison"         : { "id": 71, "effect": -1, "anim": 67, "ranges": [ "0", "0", "0", "0" ] },
-		"bind"           : { "id": 72, "effect": 0, "anim": 68, "ranges": [ "0", "0", "0", "0" ] },
-		"disease"        : { "id": 73, "effect": -1, "anim": 69, "ranges": [ "0", "0", "0", "0" ] },
-		"paralyze"       : { "id": 74, "effect": -1, "anim": 70, "ranges": [ "0", "0", "0", "0" ] },
-		"age"            : { "id": 75, "effect": -1, "anim": 71, "ranges": [ "0", "0", "0", "0" ] },
-		"deathCloud"     : { "id": 76, "effect": 0, "anim": 72, "ranges": [ "0-1", "0-1", "0-1", "0-1" ] },
-		"thunderbolt"    : { "id": 77, "effect": -1, "anim": 38, "ranges": [ "0", "0", "0", "0" ] },
-		"dispelHelpful"  : { "id": 78, "effect": -1, "anim": 41, "ranges": [ "0", "0", "0", "0" ] },
-		"deathStare"     : { "id": 79, "effect": 0, "anim": 80, "ranges": [ "0", "0", "0", "0" ] },
-		"acidBreath"     : { "id": 80, "effect": 0, "anim": 81, "ranges": [ "0", "0", "0", "0" ] }
-	}
-}
+{
+	// Additional spell info, not included in original heroes III files
+	//   id: spell ID
+	//   effect: -1 -> spell is negative for influenced creatures,
+    //			  0 -> spell is indifferent for them
+    //			  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])
+
+    // flags: string array of
+    //		damage
+    //		directDamage //todo
+    //		rising
+    //		mind
+
+    //effects: array of bonus for permanent effects
+
+	"spells":
+	{
+		"summonBoat"     :
+		{
+			"id": 0,
+			"effect": 0,
+			"anim": -1,
+			"ranges": [ "X", "X", "X", "X" ]
+		},
+		"scuttleBoat"    :
+		{
+			"id": 1,
+			"effect": 0,
+			"anim": -1,
+			"ranges": [ "X", "X", "X", "X" ]
+		},
+		"visions"        :
+		{
+			"id": 2,
+			"effect": 0,
+			"anim": -1,
+			"ranges": [ "X", "X", "X", "X" ]
+		},
+		"viewEarth"      :
+		{
+			"id": 3,
+			"effect": 0,
+			"anim": -1,
+			"ranges": [ "X", "X", "X", "X" ]
+		},
+		"disguise"       :
+		{
+			"id": 4,
+			"effect": 0,
+			"anim": -1,
+			"ranges": [ "X", "X", "X", "X" ]
+		},
+		"viewAir"        :
+		{
+			"id": 5,
+			"effect": 0,
+			"anim": -1,
+			"ranges": [ "X", "X", "X", "X" ]
+		},
+		"fly"            :
+		{
+			"id": 6,
+			"effect": 0,
+			"anim": -1,
+			"ranges": [ "X", "X", "X", "X" ]
+		},
+		"waterWalk"      :
+		{
+			"id": 7,
+			"effect": 0,
+			"anim": -1,
+			"ranges": [ "X", "X", "X", "X" ]
+		},
+		"dimensionDoor"  :
+		{
+			"id": 8,
+			"effect": 0,
+			"anim": -1,
+			"ranges": [ "X", "X", "X", "X" ]
+		},
+		"townPortal"     :
+		{
+			"id": 9,
+			"effect": 0,
+			"anim": -1,
+			"ranges": [ "X", "X", "X", "X" ]
+		},
+		"quicksand"      :
+		{
+			"id": 10,
+			"effect": 0,
+			"anim": -1,
+			"ranges": [ "X", "X", "X", "X" ]
+		},
+		"landMine"       :
+		{
+			"id": 11,
+			"effect": 0,
+			"anim": -1,
+			"ranges": [ "X", "X", "X", "X" ],
+			"flags" : ["damage"]
+		},
+		"forceField"     :
+		{
+			"id": 12,
+			"effect": 0,
+			"anim": -1,
+			"ranges": [ "0", "0", "0", "0" ]
+		},
+		"fireWall"       :
+		{
+			"id": 13,
+			"effect": 0,
+			"anim": -1,
+			"ranges": [ "0", "0", "0", "0" ],
+			"flags" : ["damage"]
+		},
+		"earthquake"     :
+		{
+			"id": 14,
+			"effect": 0,
+			"anim": -1,
+			"ranges": [ "X", "X", "X", "X" ]
+		},
+		"magicArrow"     :
+		{
+			"id": 15,
+			"effect": -1,
+			"anim": 64,
+			"ranges": [ "0", "0", "0", "0" ],
+			"flags" : ["damage"]
+		},
+		"iceBolt"        :
+		{
+			"id": 16,
+			"effect": -1,
+			"anim": 46,
+			"ranges": [ "0", "0", "0", "0" ],
+			"flags" : ["damage"]
+		},
+		"lightningBolt"  :
+		{
+			"id": 17,
+			"effect": -1,
+			"anim": 38,
+			"ranges": [ "0", "0", "0", "0" ],
+			"flags" : ["damage"]
+		},
+		"implosion"      :
+		{
+			"id": 18,
+			"effect": -1,
+			"anim": 10,
+			"ranges": [ "0", "0", "0", "0" ],
+			"flags" : ["damage"]
+		},
+		"chainLightning" :
+		{
+			"id": 19,
+			"effect": -1,
+			"anim": 38,
+			"ranges": [ "0", "0", "0", "0" ],
+			"flags" : ["damage"]
+		},
+		"frostRing"      :
+		{
+			"id": 20,
+			"effect": -1,
+			"anim": 45,
+			"ranges": [ "1", "1", "1", "1" ],
+			"flags" : ["damage"]
+		},
+		"fireball"       :
+		{
+			"id": 21,
+			"effect": -1,
+			"anim": 53,
+			"ranges": [ "0,1", "0,1", "0,1", "0,1" ],
+			"flags" : ["damage"]
+		},
+		"inferno"        :
+		{
+			"id": 22,
+			"effect": -1,
+			"anim": 9,
+			"ranges": [ "0-2", "0-2", "0-2", "0-2" ],
+			"flags" : ["damage"]
+		},
+		"meteorShower"   :
+		{
+			"id": 23,
+			"effect": -1,
+			"anim": 16,
+			"ranges": [ "0,1", "0,1", "0,1", "0,1" ],
+			"flags" : ["damage"]
+		},
+		"deathRipple"    :
+		{
+			"id": 24,
+			"effect": -1,
+			"anim": 8,
+			"ranges": [ "X", "X", "X", "X" ],
+			"flags" : ["damage"]
+		},
+		"destroyUndead"  :
+		{
+			"id": 25,
+			"effect": -1,
+			"anim": 29,
+			"ranges": [ "X", "X", "X", "X" ],
+			"flags" : ["damage"]
+		},
+		"armageddon"     :
+		{
+			"id": 26,
+			"effect": -1,
+			"anim": 12,
+			"ranges": [ "X", "X", "X", "X" ],
+			"flags" : ["damage"]
+		},
+		"shield"         :
+		{
+			"id": 27,
+			"effect": 1,
+			"anim": 27,
+			"ranges": [ "0", "0", "0", "X" ],
+			"effects":
+			[
+				{
+					"type": 	"GENERAL_DAMAGE_REDUCTION",
+					"subtype":0
+				}
+			]
+		},
+		"airShield"      :
+		{
+			"id": 28,
+			"effect": 1,
+			"anim": 2,
+			"ranges": [ "0", "0", "0", "X" ],
+			"effects":
+			[
+				{
+					"type": 	"GENERAL_DAMAGE_REDUCTION",
+					"subtype":1
+				}
+			]
+		},
+		"fireShield"     :
+		{
+			"id": 29,
+			"effect": 1,
+			"anim": 11,
+			"ranges": [ "0", "0", "0", "X" ],
+			"effects":
+			[
+				{
+					"type": 	"FIRE_SHIELD"
+				}
+			]
+		},
+		"protectAir"     :
+		{
+			"id": 30,
+			"effect": 1,
+			"anim": 22,
+			"ranges": [ "0", "0", "0", "X" ],
+			"effects":
+			[
+				{
+					"type": 	"SPELL_DAMAGE_REDUCTION",
+					"subtype":0
+				}
+			]
+		},
+		"protectFire"    :
+		{
+			"id": 31,
+			"effect": 1,
+			"anim": 24,
+			"ranges": [ "0", "0", "0", "X" ],
+			"effects":
+			[
+				{
+					"type": 	"SPELL_DAMAGE_REDUCTION",
+					"subtype":1
+				}
+			]
+		},
+		"protectWater"   :
+		{
+			"id": 32,
+			"effect": 1,
+			"anim": 23,
+			"ranges": [ "0", "0", "0", "X" ],
+			"effects":
+			[
+				{
+					"type": 	"SPELL_DAMAGE_REDUCTION",
+					"subtype":2
+				}
+			]
+		},
+		"protectEarth"   :
+		{
+			"id": 33,
+			"effect": 1,
+			"anim": 26,
+			"ranges": [ "0", "0", "0", "X" ],
+			"effects":
+			[
+				{
+					"type": 	"SPELL_DAMAGE_REDUCTION",
+					"subtype":3
+				}
+			]
+		},
+		"antiMagic"      :
+		{
+			"id": 34,
+			"effect": 1,
+			"anim": 5,
+			"ranges": [ "0", "0", "0", "X" ],
+			"effects":
+			[
+				{
+					"type": 	"LEVEL_SPELL_IMMUNITY",
+					"subtype":5,
+					"valType":"INDEPENDENT_MAX"
+				}
+			]
+		},
+		"dispel"         :
+		{
+			"id": 35,
+			"effect": 0,
+			"anim": 41,
+			"ranges": [ "0", "0", "0", "X" ]
+		},
+		"magicMirror"    :
+		{
+			"id": 36,
+			"effect": 1,
+			"anim": 3,
+			"ranges": [ "0", "0", "0", "0" ],
+			"effects":
+			[
+				{
+					"type": 	"MAGIC_MIRROR",
+					"valType":"INDEPENDENT_MAX"
+				}
+			]
+		},
+		"cure"           :
+		{
+			"id": 37,
+			"effect": 1,
+			"anim": 39,
+			"ranges": [ "0", "0", "0", "0" ],
+			"flags" : ["rising"]
+		},
+		"resurrection"   :
+		{
+			"id": 38,
+			"effect": 1,
+			"anim": 79,
+			"ranges": [ "0", "0", "0", "0" ],
+			"flags" : ["rising"]
+		},
+		"animateDead"    :
+		{
+			"id": 39,
+			"effect": 1,
+			"anim": 79,
+			"ranges": [ "0", "0", "0", "0" ],
+			"flags" : ["rising"]
+		},
+		"sacrifice"      :
+		{
+			"id": 40,
+			"effect": 1,
+			"anim": 79,
+			"ranges": [ "0", "0", "0", "0" ]
+		},
+		"bless"          :
+		{
+			"id": 41,
+			"effect": 1,
+			"anim": 36,
+			"ranges": [ "0", "0", "0", "X" ],
+			"counters" : [42],
+			"effects":
+			[
+				{
+					"type": 	"ALWAYS_MAXIMUM_DAMAGE",
+					"valType":"INDEPENDENT_MAX"
+				}
+			]
+		},
+		"curse"          :
+		{
+			"id": 42,
+			"effect": -1,
+			"anim": 40,
+			"ranges": [ "0", "0", "0", "X" ], "counters" : [41],
+			"effects":
+			[
+				{
+					"type": 	"ALWAYS_MINIMUM_DAMAGE",
+					"valType":"INDEPENDENT_MAX"
+				}
+			]
+		},
+		"bloodlust"      :
+		{
+			"id": 43,
+			"effect": 1,
+			"anim": 4,
+			"ranges": [ "0", "0", "0", "X" ], "counters" : [45],
+			"effects":
+			[
+				{
+					"type": 	"PRIMARY_SKILL",
+					"subtype": 0, //ATTACK
+					"effectRange" : "ONLY_MELEE_FIGHT"
+				}
+			]
+		},
+		"precision"      :
+		{
+			"id": 44,
+			"effect": 1,
+			"anim": 25,
+			"ranges": [ "0", "0", "0", "X" ],
+			"effects":
+			[
+				{
+					"type": 	"PRIMARY_SKILL",
+					"subtype": 0, //ATTACK
+					"effectRange" : "ONLY_DISTANCE_FIGHT"
+				}
+			]
+		},
+		"weakness"       :
+		{
+			"id": 45,
+			"effect": -1,
+			"anim": 56,
+			"ranges": [ "0", "0", "0", "X" ], "counters" : [43],
+			"effects":
+			[
+				{
+					"type": 	"PRIMARY_SKILL",
+					"subtype": 0 //ATTACK
+				}
+			]
+		},
+		"stoneSkin"      :
+		{
+			"id": 46,
+			"effect": 1,
+			"anim": 54,
+			"ranges": [ "0", "0", "0", "X" ],
+			"effects":
+			[
+				{
+					"type": 	"PRIMARY_SKILL",
+					"subtype": 1 //DEFENSE
+				}
+			]
+		},
+		"disruptingRay"  :
+		{
+			"id": 47,
+			"effect": -1,
+			"anim": 14,
+			"ranges": [ "0", "0", "0", "0" ],
+			"effects":
+			[
+				{
+					"type": 	"PRIMARY_SKILL",
+					"subtype": 1 //DEFENSE
+				}
+			]
+		},
+		"prayer"         :
+		{
+			"id": 48,
+			"effect": 1,
+			"anim": 0,
+			"ranges": [ "0", "0", "0", "X" ],
+			"effects":
+			[
+				{
+					"type": 	"PRIMARY_SKILL",
+					"subtype": 0 //ATTACK
+				},
+				{
+					"type": 	"PRIMARY_SKILL",
+					"subtype": 1 //DEFENSE
+				},
+				{
+					"type": 	"STACKS_SPEED"
+				}
+			]
+		},
+		"mirth"          :
+		{
+			"id": 49,
+			"effect": 1,
+			"anim": 20,
+			"ranges": [ "0", "0", "0", "X" ],
+			"counters" : [50],
+			"effects":
+			[
+				{
+					"type": 	"MORALE"
+				}
+			]
+		},
+		"sorrow"         :
+		{
+			"id": 50,
+			"effect": -1,
+			"anim": 30,
+			"ranges": [ "0", "0", "0", "X" ], "counters" : [49],
+			"flags" : ["mind"],
+			"effects":
+			[
+				{
+					"type": 	"MORALE"
+				}
+			]
+		},
+		"fortune"        :
+		{
+			"id": 51,
+			"effect": 1,
+			"anim": 18,
+			"ranges": [ "0", "0", "0", "X" ],
+			"counters" : [52],
+			"effects":
+			[
+				{
+					"type": 	"LUCK"
+				}
+			]
+		},
+		"misfortune"     :
+		{
+			"id": 52,
+			"effect": -1,
+			"anim": 48,
+			"ranges": [ "0", "0", "0", "X" ],
+			"counters" : [51],
+			"effects":
+			[
+				{
+					"type": 	"LUCK"
+				}
+			]
+		},
+		"haste"          :
+		{
+			"id": 53,
+			"effect": 1,
+			"anim": 31,
+			"ranges": [ "0", "0", "0", "X" ],
+			"counters" : [54],
+			"effects":
+			[
+				{
+					"type": 	"STACKS_SPEED"
+				}
+			]
+		},
+		"slow"           :
+		{
+			"id": 54,
+			"effect": -1,
+			"anim": 19,
+			"ranges": [ "0", "0", "0", "X" ],
+			"counters" : [53],
+			"effects":
+			[
+				{
+					"type": 	"STACKS_SPEED"
+				}
+			]
+		},
+		"slayer"         :
+		{
+			"id": 55,
+			"effect": 1,
+			"anim": 28,
+			"ranges": [ "0", "0", "0", "0" ],
+			"effects":
+			[
+				{
+					"type": 	"SLAYER"
+				}
+			]
+		},
+		"frenzy"         :
+		{
+			"id": 56,
+			"effect": 1,
+			"anim": 17,
+			"ranges": [ "0", "0", "0", "0" ],
+			"effects":
+			[
+				{
+					"type": 	"IN_FRENZY"
+				}
+			]
+		},
+		"titanBolt"      :
+		{
+			"id": 57,
+			"effect": -1,
+			"anim": 38,
+			"ranges": [ "0", "0", "0", "0" ],
+			"flags" : ["damage"]
+		},
+		"counterstrike"  :
+		{
+			"id": 58,
+			"effect": 1,
+			"anim": 7,
+			"ranges": [ "0", "0", "0", "X" ],
+			"effects":
+			[
+				{
+					"type": 	"ADDITIONAL_RETALIATION"
+				}
+			]
+		},
+		"berserk"        :
+		{
+			"id": 59,
+			"effect": -1,
+			"anim": 35,
+			"ranges": [ "0", "0", "0-1", "0-2" ],
+			"flags" : ["mind"],
+			"effects":
+			[
+				{
+					"type": 	"ATTACKS_NEAREST_CREATURE"
+				}
+			]
+		},
+		"hypnotize"      :
+		{
+			"id": 60,
+			"effect": -1,
+			"anim": 21,
+			"ranges": [ "0", "0", "0", "0" ],
+			"flags" : ["mind"],
+			"effects":
+			[
+				{
+					"type": 	"HYPNOTIZED"
+				}
+			]
+		},
+		"forgetfulness"  :
+		{
+			"id": 61,
+			"effect": -1,
+			"anim": 42,
+			"ranges": [ "0", "0", "0", "X" ],
+			"flags" : ["mind"],
+			"effects":
+			[
+				{
+					"type": 	"FORGETFULL"
+				}
+			]
+		},
+		"blind"          :
+		{
+			"id": 62,
+			"effect": -1,
+			"anim": 6,
+			"ranges": [ "0", "0", "0", "0" ],
+			"flags" : ["mind"],
+			"effects":
+			[
+				{
+					"type": 	"NOT_ACTIVE",
+					"subtype": 	62, //really ase spell id, is it right?
+					//TODO: duration
+				},
+				{
+					"type": 	"GENERAL_ATTACK_REDUCTION"
+					//TODO: duration
+				},
+				{
+					"type": 	"NO_RETALIATION",
+					"duration":	"UNITL_BEING_ATTACKED"
+				}
+			]
+		},
+		"teleport"       :
+		{
+			"id": 63,
+			"effect": 1,
+			"anim": -1,
+			"ranges": [ "0", "0", "0", "0" ]
+		},
+		"removeObstacle" :
+		{
+			"id": 64,
+			"effect": 0,
+			"anim": -1,
+			"ranges": [ "X", "X", "X", "X" ]
+		},
+		"clone"          :
+		{
+			"id": 65,
+			"effect": 1,
+			"anim": -1,
+			"ranges": [ "0", "0", "0", "0" ]
+		},
+		"fireElemental"  :
+		{
+			"id": 66,
+			"effect": 0,
+			"anim": -1,
+			"ranges": [ "X", "X", "X", "X" ]
+		},
+		"earthElemental" :
+		{
+			"id": 67,
+			"effect": 0,
+			"anim": -1,
+			"ranges": [ "X", "X", "X", "X" ]
+		},
+		"waterElemental" :
+		{
+			"id": 68,
+			"effect": 0,
+			"anim": -1,
+			"ranges": [ "X", "X", "X", "X" ]
+		},
+		"airElemental"   :
+		{
+			"id": 69,
+			"effect": 0,
+			"anim": -1,
+			"ranges": [ "X", "X", "X", "X" ]
+		},
+		"stoneGaze"      :
+		{
+			"id": 70,
+			"effect": 0,
+			"anim": 70,
+			"ranges": [ "0", "0", "0", "0" ],
+			"effects":
+			[
+				{
+					"type": 	"NOT_ACTIVE",
+					"subtype": 	62
+					//TODO: duration
+				},
+				{
+					"type": 	"NO_RETALIATION",
+					"duration":	"UNITL_BEING_ATTACKED"
+				}
+			]
+		},
+		"poison"         :
+		{
+			"id": 71,
+			"effect": -1,
+			"anim": 67,
+			"ranges": [ "0", "0", "0", "0" ],
+			"effects":
+			[
+				{
+					"type": 	"POISON",
+					"val" : 	30,
+					"valueType": 	"INDEPENDENT_MAX"
+				},
+				{
+					"type": 	"STACK_HEALTH",
+					"val" : 	-10,
+					"valueType": 	"PERCENT_TO_ALL"
+				}
+			]
+		},
+		"bind"           :
+		{
+			"id": 72,
+			"effect": 0,
+			"anim": 68,
+			"ranges": [ "0", "0", "0", "0" ],
+			"effects":
+			[
+				{
+					"type": 	"BIND_EFFECT",
+					"val" : 	30,
+					"turns" : 	1,
+					"duration" : 	"PERMANENT"
+				}
+			]
+		},
+		"disease"        :
+		{
+			"id": 73,
+			"effect": -1,
+			"anim": 69,
+			"ranges": [ "0", "0", "0", "0" ],
+			"effects":
+			[
+				{
+					"type": 	"PRIMARY_SKILL",
+					"subtype": 	0,
+					"val" : 	-2,
+				},
+				{
+					"type": 	"PRIMARY_SKILL",
+					"subtype": 	1,
+					"val" : 	-2,
+				}
+			]
+		},
+		"paralyze"       :
+		{
+			"id": 74,
+			"effect": -1,
+			"anim": 70,
+			"ranges": [ "0", "0", "0", "0" ],
+			"effects":
+			[
+				{
+					"type": 	"NOT_ACTIVE",
+					"subtype": 	74,
+					//TODO: duration
+				},
+				{
+					"type": 	"NO_RETALIATION",
+					"duration":	"UNITL_BEING_ATTACKED"
+				}
+			]
+		},
+		"age"            :
+		{
+			"id": 75,
+			"effect": -1,
+			"anim": 71,
+			"ranges": [ "0", "0", "0", "0" ],
+			"effects":
+			[
+				{
+					"type": 	"STACK_HEALTH",
+					"val" : 	-50,
+					"valueType": 	"PERCENT_TO_ALL"
+				}
+			]
+		},
+		"deathCloud"     :
+		{
+			"id": 76,
+			"effect": 0,
+			"anim": 72,
+			"ranges": [ "0-1", "0-1", "0-1", "0-1" ]
+		},
+		"thunderbolt"    :
+		{
+			"id": 77,
+			"effect": -1,
+			"anim": 38,
+			"ranges": [ "0", "0", "0", "0" ],
+			"flags" : ["damage"]
+		},
+		"dispelHelpful"  :
+		{
+			"id": 78,
+			"effect": -1,
+			"anim": 41,
+			"ranges": [ "0", "0", "0", "0" ]
+		},
+		"deathStare"     :
+		{
+			"id": 79,
+			"effect": 0,
+			"anim": 80,
+			"ranges": [ "0", "0", "0", "0" ]
+		},
+		"acidBreath"     :
+		{
+			"id": 80,
+			"effect": 0,
+			"anim": 81,
+			"ranges": [ "0", "0", "0", "0" ],
+			"effects":
+			[
+				{
+					"type": 	"PRIMARY_SKILL",
+					"subtype": 	1,
+					"val" : 	-3,
+					"duration" : 	"PERMANENT",
+					"valueType": 	"ADDITIVE_VALUE"
+				}
+			]
+		},
+		"acidBreathDamage"     :
+		{
+			"id": 81,
+			"effect": 0,
+			"anim": 81,
+			"ranges": [ "0", "0", "0", "0" ],
+			"flags" : ["damage"]
+
+		}
+	}
+}

+ 105 - 129
lib/BattleState.cpp

@@ -186,7 +186,7 @@ ui32 BattleInfo::calculateHealedHP(const CSpell * spell, int usedSpellPower, int
 }
 bool BattleInfo::resurrects(TSpell spellid) const
 {
-	return vstd::contains(VLC->spellh->risingSpells, spellid);
+	return VLC->spellh->spells[spellid]->isRisingSpell();
 }
 
 const CStack * BattleInfo::battleGetStack(BattleHex pos, bool onlyAlive)
@@ -931,170 +931,146 @@ si32 CStack::magicResistance() const
 
 void CStack::stackEffectToFeature(std::vector<Bonus> & sf, const Bonus & sse)
 {
-	si32 power = VLC->spellh->spells[sse.sid]->powers[sse.val];
-	switch(sse.sid)
+	//TODO: get rid of this spaghetti code
+
+	const CSpell * sp = VLC->spellh->spells[sse.sid];
+	si32 power = sp->powers[sse.val];
+
+	auto add = [&](Bonus::BonusType type, si16 subtype, si32 value,si32 additionalInfo = 0, si32 limit = Bonus::NO_LIMIT)
 	{
-	case 27: //shield
-	 	sf.push_back(featureGenerator(Bonus::GENERAL_DAMAGE_REDUCTION, 0, power, sse.turnsRemain));
-	 	sf.back().sid = sse.sid;
-	 	break;
-	case 28: //air shield
-	 	sf.push_back(featureGenerator(Bonus::GENERAL_DAMAGE_REDUCTION, 1, power, sse.turnsRemain));
-	 	sf.back().sid = sse.sid;
-	 	break;
-	case 29: //fire shield
-	 	sf.push_back(featureGenerator(Bonus::FIRE_SHIELD, 0, power, sse.turnsRemain));
-	 	sf.back().sid = sse.sid;
-	 	break;
-	case 30: //protection from air
-	 	sf.push_back(featureGenerator(Bonus::SPELL_DAMAGE_REDUCTION, 0, power, sse.turnsRemain));
+	 	sf.push_back(featureGenerator(type, subtype, value, sse.turnsRemain,additionalInfo, limit));
 	 	sf.back().sid = sse.sid;
+	};
+
+	auto addVT = [&](Bonus::BonusType type, si16 subtype, si32 value, ui8 valType,si32 additionalInfo = 0, si32 limit = Bonus::NO_LIMIT)
+	{
+		add(type, subtype, value, additionalInfo, limit);
+		sf.back().valType = valType;
+	};
+
+	auto addDur = [&](Bonus::BonusType type, si16 subtype, si32 value, ui8 duration ,si32 additionalInfo = 0, si32 limit = Bonus::NO_LIMIT)
+	{
+		add(type, subtype, value, additionalInfo, limit);
+		sf.back().duration = duration;
+	};
+
+	switch(sse.sid)
+	{
+	case Spells::SHIELD:
+		add(Bonus::GENERAL_DAMAGE_REDUCTION, 0, power);
+		break;
+	case Spells::AIR_SHIELD:
+	 	add(Bonus::GENERAL_DAMAGE_REDUCTION, 1, power);
 	 	break;
-	case 31: //protection from fire
-	 	sf.push_back(featureGenerator(Bonus::SPELL_DAMAGE_REDUCTION, 1, power, sse.turnsRemain));
-	 	sf.back().sid = sse.sid;
+	case Spells::FIRE_SHIELD:
+	 	add(Bonus::FIRE_SHIELD, 0, power);
 	 	break;
-	case 32: //protection from water
-	 	sf.push_back(featureGenerator(Bonus::SPELL_DAMAGE_REDUCTION, 2, power, sse.turnsRemain));
-	 	sf.back().sid = sse.sid;
+	case Spells::PROTECTION_FROM_AIR:
+	 	add(Bonus::SPELL_DAMAGE_REDUCTION, 0, power);
 	 	break;
-	case 33: //protection from earth
-	 	sf.push_back(featureGenerator(Bonus::SPELL_DAMAGE_REDUCTION, 3, power, sse.turnsRemain));
-	 	sf.back().sid = sse.sid;
+	case Spells::PROTECTION_FROM_FIRE:
+	 	add(Bonus::SPELL_DAMAGE_REDUCTION, 1, power);
 	 	break;
-	case 34: //anti-magic
-	 	sf.push_back(featureGenerator(Bonus::LEVEL_SPELL_IMMUNITY, GameConstants::SPELL_LEVELS, power - 1, sse.turnsRemain));
-		sf.back().valType = Bonus::INDEPENDENT_MAX;
-	 	sf.back().sid = sse.sid;
+	case Spells::PROTECTION_FROM_WATER:
+	 	add(Bonus::SPELL_DAMAGE_REDUCTION, 2, power);
 	 	break;
-	case 36: //magic mirror
-		sf.push_back(featureGenerator(Bonus::MAGIC_MIRROR, -1, power, sse.turnsRemain));
-		sf.back().valType = Bonus::INDEPENDENT_MAX;
-	 	sf.back().sid = sse.sid;
-	case 41: //bless
-		sf.push_back(featureGenerator(Bonus::ALWAYS_MAXIMUM_DAMAGE, -1, power, sse.turnsRemain));
-		sf.back().valType = Bonus::INDEPENDENT_MAX;
-	 	sf.back().sid = sse.sid;
+	case Spells::PROTECTION_FROM_EARTH:
+	 	add(Bonus::SPELL_DAMAGE_REDUCTION, 3, power);
 	 	break;
-	case 42: //curse
-	 	sf.push_back(featureGenerator(Bonus::ALWAYS_MINIMUM_DAMAGE, -1, power, sse.turnsRemain, sse.val >= 2 ? 20 : 0));
-		sf.back().valType = Bonus::INDEPENDENT_MAX;
-	 	sf.back().sid = sse.sid;
+	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 43: //bloodlust
-	 	sf.push_back(featureGenerator(Bonus::PRIMARY_SKILL, PrimarySkill::ATTACK, power, sse.turnsRemain, 0, Bonus::ONLY_MELEE_FIGHT));
-	 	sf.back().sid = sse.sid;
+	case Spells::CURSE:
+		addVT(Bonus::ALWAYS_MINIMUM_DAMAGE, -1, power, Bonus::INDEPENDENT_MAX, sse.val >= 2 ? 20 : 0);
 	 	break;
-	case 44: //precision
-	 	sf.push_back(featureGenerator(Bonus::PRIMARY_SKILL, PrimarySkill::ATTACK, power, sse.turnsRemain, 0, Bonus::ONLY_DISTANCE_FIGHT));
-	 	sf.back().sid = sse.sid;
+	case Spells::BLOODLUST:
+		add(Bonus::PRIMARY_SKILL, PrimarySkill::ATTACK, power, 0, Bonus::ONLY_MELEE_FIGHT);
 	 	break;
-	case 45: //weakness
-	 	sf.push_back(featureGenerator(Bonus::PRIMARY_SKILL, PrimarySkill::ATTACK, -1 * power, sse.turnsRemain));
-	 	sf.back().sid = sse.sid;
+	case Spells::PRECISION:
+		add(Bonus::PRIMARY_SKILL, PrimarySkill::ATTACK, power, 0, Bonus::ONLY_DISTANCE_FIGHT);
 	 	break;
-	case 46: //stone skin
-	 	sf.push_back(featureGenerator(Bonus::PRIMARY_SKILL, PrimarySkill::DEFENSE, power, sse.turnsRemain));
-	 	sf.back().sid = sse.sid;
+	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 47: //disrupting ray
-	 	sf.push_back(featureGenerator(Bonus::PRIMARY_SKILL, PrimarySkill::DEFENSE, -1 * power, sse.turnsRemain));
-	 	sf.back().sid = sse.sid;
-		sf.back().valType = Bonus::ADDITIVE_VALUE;
+	case Spells::DISRUPTING_RAY:
+		addVT(Bonus::PRIMARY_SKILL, PrimarySkill::DEFENSE, -1 * power,Bonus::ADDITIVE_VALUE);
 	 	break;
-	case 48: //prayer
-	 	sf.push_back(featureGenerator(Bonus::PRIMARY_SKILL, PrimarySkill::ATTACK, power, sse.turnsRemain));
-	 	sf.back().sid = sse.sid;
-	 	sf.push_back(featureGenerator(Bonus::PRIMARY_SKILL, PrimarySkill::DEFENSE, power, sse.turnsRemain));
-	 	sf.back().sid = sse.sid;
-	 	sf.push_back(featureGenerator(Bonus::STACKS_SPEED, 0, power, sse.turnsRemain));
-	 	sf.back().sid = sse.sid;
+	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 49: //mirth
-	 	sf.push_back(featureGenerator(Bonus::MORALE, 0, power, sse.turnsRemain));
-	 	sf.back().sid = sse.sid;
+	case Spells::MIRTH:
+		add(Bonus::MORALE, 0, power);
 	 	break;
-	case 50: //sorrow
-	 	sf.push_back(featureGenerator(Bonus::MORALE, 0, -1 * power, sse.turnsRemain));
-	 	sf.back().sid = sse.sid;
+	case Spells::SORROW:
+		add(Bonus::MORALE, 0, -1 * power);
 	 	break;
-	case 51: //fortune
-	 	sf.push_back(featureGenerator(Bonus::LUCK, 0, power, sse.turnsRemain));
-	 	sf.back().sid = sse.sid;
+	case Spells::FORTUNE:
+		add(Bonus::LUCK, 0, power);
 	 	break;
-	case 52: //misfortune
-	 	sf.push_back(featureGenerator(Bonus::LUCK, 0, -1 * power, sse.turnsRemain));
-	 	sf.back().sid = sse.sid;
+	case Spells::MISFORTUNE:
+		add(Bonus::LUCK, 0, -1 * power);
 	 	break;
-	case 53: //haste
-	 	sf.push_back(featureGenerator(Bonus::STACKS_SPEED, 0, power, sse.turnsRemain));
-	 	sf.back().sid = sse.sid;
+	case Spells::HASTE: //haste
+		add(Bonus::STACKS_SPEED, 0, power);
 	 	break;
-	case 54: //slow
-	 	sf.push_back(featureGeneratorVT(Bonus::STACKS_SPEED, 0, -1 * ( 100 - power ), sse.turnsRemain, Bonus::PERCENT_TO_ALL));
-	 	sf.back().sid = sse.sid;
+	case Spells::SLOW:
+		addVT(Bonus::STACKS_SPEED, 0, -1 * ( 100 - power ),Bonus::PERCENT_TO_ALL);
 	 	break;
-	case 55: //slayer
-	 	sf.push_back(featureGenerator(Bonus::SLAYER, 0, sse.val, sse.turnsRemain));
-	 	sf.back().sid = sse.sid;
+	case Spells::SLAYER:
+		add(Bonus::SLAYER, 0, sse.val);
 	 	break;
-	case 56: //frenzy
-	 	sf.push_back(featureGenerator(Bonus::IN_FRENZY, 0, VLC->spellh->spells[56]->powers[sse.val]/100.0, sse.turnsRemain));
-	 	sf.back().sid = sse.sid;
+	case Spells::FRENZY:
+		add(Bonus::IN_FRENZY, 0, power/100.0);
 	 	break;
-	case 58: //counterstrike
-	 	sf.push_back(featureGenerator(Bonus::ADDITIONAL_RETALIATION, 0, power, sse.turnsRemain));
-	 	sf.back().sid = sse.sid;
+	case Spells::COUNTERSTRIKE:
+		add(Bonus::ADDITIONAL_RETALIATION, 0, power);
 	 	break;
-	case 59: //bersek
-	 	sf.push_back(featureGenerator(Bonus::ATTACKS_NEAREST_CREATURE, 0, sse.val, sse.turnsRemain));
-	 	sf.back().sid = sse.sid;
+	case Spells::BERSERK:
+		add(Bonus::ATTACKS_NEAREST_CREATURE, 0, sse.val);
 	 	break;
-	case 60: //hypnotize
-	 	sf.push_back(featureGenerator(Bonus::HYPNOTIZED, 0, sse.val, sse.turnsRemain));
-	 	sf.back().sid = sse.sid;
+	case Spells::HYPNOTIZE:
+	 	add(Bonus::HYPNOTIZED, 0, sse.val);
 	 	break;
-	case 61: //forgetfulness
-	 	sf.push_back(featureGenerator(Bonus::FORGETFULL, 0, sse.val, sse.turnsRemain));
-	 	sf.back().sid = sse.sid;
+	case Spells::FORGETFULNESS:
+		add(Bonus::FORGETFULL, 0, sse.val);
 	 	break;
-	case Spells::BLIND: //blind
-		sf.push_back(makeFeatureVal(Bonus::NOT_ACTIVE, Bonus::UNITL_BEING_ATTACKED | Bonus::N_TURNS, sse.sid, 0, Bonus::SPELL_EFFECT, sse.turnsRemain));
-		sf.back().sid = sse.sid;
-		sf.push_back(makeFeatureVal(Bonus::GENERAL_ATTACK_REDUCTION, Bonus::UNTIL_ATTACK | Bonus::N_TURNS, 0, power, Bonus::SPELL_EFFECT, sse.turnsRemain));
-		sf.back().sid = sse.sid;
-		sf.push_back(makeFeatureVal(Bonus::NO_RETALIATION, Bonus::UNITL_BEING_ATTACKED, 0, 0, Bonus::SPELL_EFFECT, 0)); // don't retaliate after basilisk / unicorn attack
-		sf.back().sid = sse.sid;
+	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: //Stone Gaze
-	case Spells::PARALYZE: //Paralyze
-		sf.push_back(makeFeatureVal(Bonus::NOT_ACTIVE, Bonus::UNITL_BEING_ATTACKED | Bonus::N_TURNS, sse.sid, 0, Bonus::SPELL_EFFECT, sse.turnsRemain));
-		sf.back().sid = sse.sid;
-		sf.push_back(makeFeatureVal(Bonus::NO_RETALIATION, Bonus::UNITL_BEING_ATTACKED, 0, 0, Bonus::SPELL_EFFECT, 0)); // don't retaliate after basilisk / unicorn attack
-		sf.back().sid = sse.sid;
+	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 71: //Poison
-		sf.push_back(featureGeneratorVT(Bonus::POISON, 0, 30, sse.turnsRemain, Bonus::INDEPENDENT_MAX)); //max hp penalty from this source
-		sf.back().sid = sse.sid;
-		sf.push_back(featureGeneratorVT(Bonus::STACK_HEALTH, 0, -10, sse.turnsRemain, Bonus::PERCENT_TO_ALL));
-		sf.back().sid = sse.sid;
+	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 72: //Bind
+	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 73: //Disease
-		sf.push_back(featureGenerator(Bonus::PRIMARY_SKILL, PrimarySkill::ATTACK, -2 , sse.turnsRemain));
-	 	sf.back().sid = sse.sid;
-		sf.push_back(featureGenerator(Bonus::PRIMARY_SKILL, PrimarySkill::DEFENSE, -2 , sse.turnsRemain));
-	 	sf.back().sid = sse.sid;
+	case Spells::DISEASE:
+		add(Bonus::PRIMARY_SKILL, PrimarySkill::ATTACK, -2);
+		add(Bonus::PRIMARY_SKILL, PrimarySkill::DEFENSE, -2);
 		break;
-	case 75: //Age
-		sf.push_back(featureGeneratorVT(Bonus::STACK_HEALTH, 0, -50, sse.turnsRemain, Bonus::PERCENT_TO_ALL));
-		sf.back().sid = sse.sid;
+	case Spells::AGE:
+		addVT(Bonus::STACK_HEALTH, 0, -50, Bonus::PERCENT_TO_ALL);
 		break;
-	case 80: //Acid Breath
+	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;

+ 5 - 7
lib/BattleState.h

@@ -33,8 +33,8 @@ struct BattleStackAttacked;
 //only for use in BattleInfo
 struct DLL_LINKAGE SiegeInfo
 {
-	ui8 wallState[EWallParts::PARTS_COUNT]; 
-	
+	ui8 wallState[EWallParts::PARTS_COUNT];
+
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
 		h & wallState;
@@ -101,7 +101,7 @@ struct DLL_LINKAGE BattleInfo : public CBonusSystemNode, public CBattleInfoCallb
 
 	ui32 calculateDmg(const CStack* attacker, const CStack* defender, const CGHeroInstance * attackerHero, const CGHeroInstance * defendingHero, bool shooting, ui8 charge, bool lucky, bool deathBlow, bool ballistaDoubleDmg); //charge - number of hexes travelled before attack (for champion's jousting)
 	void calculateCasualties(std::map<ui32,si32> *casualties) const; //casualties are array of maps size 2 (attacker, defeneder), maps are (crid => amount)
-	
+
 	//void getPotentiallyAttackableHexes(AttackableTiles &at, const CStack* attacker, BattleHex destinationTile, BattleHex attackerPos); //hexes around target that could be attacked in melee
 	//std::set<CStack*> getAttackedCreatures(const CStack* attacker, BattleHex destinationTile, BattleHex attackerPos = BattleHex::INVALID); //calculates range of multi-hex attacks
 	//std::set<BattleHex> getAttackedHexes(const CStack* attacker, BattleHex destinationTile, BattleHex attackerPos = BattleHex::INVALID); //calculates range of multi-hex attacks
@@ -114,7 +114,7 @@ struct DLL_LINKAGE BattleInfo : public CBonusSystemNode, public CBattleInfoCallb
 	ui32 calculateHealedHP(int healedHealth, const CSpell * spell, const CStack * stack) const; //for Archangel
 	ui32 calculateHealedHP(const CSpell * spell, int usedSpellPower, int spellSchoolLevel, const CStack * stack) const; //unused
 	bool resurrects(TSpell spellid) const; //TODO: move it to spellHandler?
-	
+
 	const CGHeroInstance * getHero(int player) const; //returns fighting hero that belongs to given player
 
 
@@ -136,7 +136,7 @@ struct DLL_LINKAGE BattleInfo : public CBonusSystemNode, public CBattleInfoCallb
 };
 
 class DLL_LINKAGE CStack : public CBonusSystemNode, public CStackBasicDescriptor
-{ 
+{
 public:
 	const CStackInstance *base; //garrison slot from which stack originates (NULL for war machines, summoned cres, etc)
 
@@ -179,7 +179,6 @@ public:
 	{
 		Bonus hb = makeFeatureVal(type, Bonus::N_TURNS, subtype, value, Bonus::SPELL_EFFECT, turnsRemain, additionalInfo);
 		hb.effectRange = limit;
-		hb.source = Bonus::SPELL_EFFECT;
 		return hb;
 	}
 
@@ -187,7 +186,6 @@ public:
 	{
 		Bonus ret = makeFeatureVal(type, Bonus::N_TURNS, subtype, value, Bonus::SPELL_EFFECT, turnsRemain);
 		ret.valType = valType;
-		ret.source = Bonus::SPELL_EFFECT;
 		return ret;
 	}
 

+ 5 - 5
lib/CBattleCallback.cpp

@@ -782,7 +782,7 @@ TDmgRange CBattleInfoCallback::calculateDmgRange(const BattleAttackInfo &info) c
 		minDmg = info.attackerBonuses->getMinDamage() * info.attackerCount,
 		maxDmg = info.attackerBonuses->getMaxDamage() * info.attackerCount;
 
-	const CCreature *attackerType = info.attacker->getCreature(), 
+	const CCreature *attackerType = info.attacker->getCreature(),
 		*defenderType = info.defender->getCreature();
 
 	if(attackerType->idNumber == 149) //arrow turret
@@ -1591,7 +1591,7 @@ ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleIsImmune(const C
 			if (battleTestElementalImmunity(Bonus::AIR_IMMUNITY))
 				return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
 		}
-		if (vstd::contains(VLC->spellh->mindSpells, spell->id))
+		if (spell->isMindSpell())
 		{
 			if (subject->hasBonusOfType(Bonus::MIND_IMMUNITY))
 				return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
@@ -1706,7 +1706,7 @@ ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCastThisSpell
 	switch(spell->getTargetType())
 	{
 	case CSpell::CREATURE:
-	case CSpell::CREATURE_EXPERT_MASSIVE:  
+	case CSpell::CREATURE_EXPERT_MASSIVE:
 		if(mode == ECastingMode::HERO_CASTING)
 		{
 			const CGHeroInstance * caster = battleGetFightingHero(side);
@@ -1909,7 +1909,7 @@ ui32 CBattleInfoCallback::calculateSpellDmg( const CSpell * sp, const CGHeroInst
 	ui32 ret = 0; //value to return
 
 	//check if spell really does damage - if not, return 0
-	if(VLC->spellh->damageSpells.find(sp->id) == VLC->spellh->damageSpells.end())
+	if(!sp->isDamageSpell())
 		return 0;
 
 	ret = usedSpellPower * sp->power;
@@ -2373,7 +2373,7 @@ const CGHeroInstance * CPlayerBattleCallback::battleGetMyHero() const
 
 InfoAboutHero CPlayerBattleCallback::battleGetEnemyHero() const
 {
-	InfoAboutHero ret; 
+	InfoAboutHero ret;
 	assert(0);
 	///TODO implement and replace usages of battleGetFightingHero obtaining enemy hero
 	return ret;

+ 15 - 29
lib/CGameState.cpp

@@ -176,7 +176,7 @@ void MetaString::getLocalString(const std::pair<ui8,ui32> &txt, std::string &dst
 	}
 	else if (type == ART_EVNTS)
 	{
-		dst = VLC->arth->artifacts[ser]->EventText(); 
+		dst = VLC->arth->artifacts[ser]->EventText();
 	}
 	else
 	{
@@ -522,7 +522,7 @@ std::pair<int,int> CGameState::pickObject (CGObjectInstance *obj)
 	{
 	case Obj::RANDOM_ART:
 		return std::pair<int,int>(Obj::ARTIFACT, VLC->arth->getRandomArt (CArtifact::ART_TREASURE | CArtifact::ART_MINOR | CArtifact::ART_MAJOR | CArtifact::ART_RELIC));
-	case Obj::RANDOM_TREASURE_ART: 
+	case Obj::RANDOM_TREASURE_ART:
 		return std::pair<int,int>(Obj::ARTIFACT, VLC->arth->getRandomArt (CArtifact::ART_TREASURE));
 	case Obj::RANDOM_MINOR_ART:
 		return std::pair<int,int>(Obj::ARTIFACT, VLC->arth->getRandomArt (CArtifact::ART_MINOR));
@@ -573,7 +573,7 @@ std::pair<int,int> CGameState::pickObject (CGObjectInstance *obj)
 		return std::pair<int,int>(Obj::MONSTER, VLC->creh->pickRandomMonster(boost::ref(ran), 6));
 	case Obj::RANDOM_MONSTER_L7:
 		return std::pair<int,int>(Obj::MONSTER, VLC->creh->pickRandomMonster(boost::ref(ran), 7));
-	case Obj::RANDOM_DWELLING: 
+	case Obj::RANDOM_DWELLING:
 	case Obj::RANDOM_DWELLING_LVL:
 	case Obj::RANDOM_DWELLING_FACTION:
 		{
@@ -1963,7 +1963,7 @@ std::vector<CGObjectInstance*> CGameState::guardingCreatures (int3 pos) const
 		{
 			if(obj->blockVisit)
 			{
-				if (obj->ID == 54) // Monster
+				if (obj->ID == Obj::MONSTER) // Monster
 					guards.push_back(obj);
 			}
 		}
@@ -1976,11 +1976,11 @@ std::vector<CGObjectInstance*> CGameState::guardingCreatures (int3 pos) const
 			if (map->isInTheMap(pos))
 			{
 				TerrainTile &tile = map->terrain[pos.x][pos.y][pos.z];
-                if (tile.visitable && (tile.terType == ETerrainType::WATER) == (posTile.terType == ETerrainType::WATER))
+                if (tile.visitable && (tile.isWater() == posTile.isWater()))
 				{
 					BOOST_FOREACH (CGObjectInstance* obj, tile.visitableObjects)
 					{
-						if (obj->ID == 54  &&  checkForVisitableDir(pos, &map->getTile(originalPos), originalPos)) // Monster being able to attack investigated tile
+						if (obj->ID == Obj::MONSTER  &&  checkForVisitableDir(pos, &map->getTile(originalPos), originalPos)) // Monster being able to attack investigated tile
 						{
 							guards.push_back(obj);
 						}
@@ -2010,7 +2010,7 @@ int3 CGameState::guardingCreaturePosition (int3 pos) const
 		{
 			if(obj->blockVisit)
 			{
-				if (obj->ID == 54) // Monster
+				if (obj->ID == Obj::MONSTER) // Monster
 					return pos;
 				else
 					return int3(-1, -1, -1); //blockvis objects are not guarded by neighbouring creatures
@@ -2027,11 +2027,11 @@ int3 CGameState::guardingCreaturePosition (int3 pos) const
 			if (map->isInTheMap(pos))
 			{
 				TerrainTile &tile = map->terrain[pos.x][pos.y][pos.z];
-                if (tile.visitable && (tile.terType == ETerrainType::WATER) == (posTile.terType == ETerrainType::WATER))
+                if (tile.visitable && (tile.isWater() == posTile.isWater()))
 				{
 					BOOST_FOREACH (CGObjectInstance* obj, tile.visitableObjects)
 					{
-						if (obj->ID == 54  &&  checkForVisitableDir(pos, &map->getTile(originalPos), originalPos)) // Monster being able to attack investigated tile
+						if (obj->ID == Obj::MONSTER  &&  checkForVisitableDir(pos, &map->getTile(originalPos), originalPos)) // Monster being able to attack investigated tile
 						{
 							return pos;
 						}
@@ -2429,7 +2429,7 @@ void CGameState::obtainPlayersStats(SThievesGuildInfo & tgi, int level)
 	}
 	if(level >= 4) //obelisks found
 	{
-		//TODO
+		//TODO: obtainPlayersStats - obelisks found
 	}
 	if(level >= 5) //artifacts
 	{
@@ -2441,7 +2441,7 @@ void CGameState::obtainPlayersStats(SThievesGuildInfo & tgi, int level)
 	}
 	if(level >= 7) //income
 	{
-		//TODO
+		//TODO:obtainPlayersStats - income
 	}
 	if(level >= 8) //best hero's stats
 	{
@@ -2507,18 +2507,11 @@ int CGameState::lossCheck( ui8 player ) const
 		switch(map->lossCondition.typeOfLossCon)
 		{
 		case ELossConditionType::LOSSCASTLE:
-			{
-				const CGTownInstance *t = dynamic_cast<const CGTownInstance *>(map->lossCondition.obj);
-				assert(t);
-				if(t->tempOwner != player)
-					return 1;
-			}
-			break;
 		case ELossConditionType::LOSSHERO:
 			{
-				const CGHeroInstance *h = dynamic_cast<const CGHeroInstance *>(map->lossCondition.obj);
-				assert(h);
-				if(h->tempOwner != player)
+				const CGObjectInstance *obj = map->lossCondition.obj;
+				assert(obj);
+				if(obj->tempOwner != player)
 					return 1;
 			}
 			break;
@@ -3177,13 +3170,6 @@ bool CPathfinder::canMoveBetween(const int3 &a, const int3 &b) const
 	return gs->checkForVisitableDir(a, b) && gs->checkForVisitableDir(b, a);
 }
 
-bool CPathfinder::canStepOntoDst() const
-{
-	//TODO remove
-	assert(0);
-	return false;
-}
-
 CGPathNode::EAccessibility CPathfinder::evaluateAccessibility(const TerrainTile *tinfo) const
 {
 	CGPathNode::EAccessibility ret = (tinfo->blocked ? CGPathNode::BLOCKED : CGPathNode::ACCESSIBLE);
@@ -3194,7 +3180,7 @@ CGPathNode::EAccessibility CPathfinder::evaluateAccessibility(const TerrainTile
 
 	if(tinfo->visitable)
 	{
-		if(tinfo->visitableObjects.front()->ID == 80 && tinfo->visitableObjects.back()->ID == Obj::HERO && tinfo->visitableObjects.back()->tempOwner != hero->tempOwner) //non-owned hero stands on Sanctuary
+		if(tinfo->visitableObjects.front()->ID == Obj::SANCTUARY && tinfo->visitableObjects.back()->ID == Obj::HERO && tinfo->visitableObjects.back()->tempOwner != hero->tempOwner) //non-owned hero stands on Sanctuary
 		{
 			return CGPathNode::BLOCKED;
 		}

+ 0 - 1
lib/CGameState.h

@@ -368,7 +368,6 @@ private:
 
 	CGPathNode::EAccessibility evaluateAccessibility(const TerrainTile *tinfo) const;
 	bool canMoveBetween(const int3 &a, const int3 &b) const; //checks only for visitable objects that may make moving between tiles impossible, not other conditions (like tiles itself accessibility)
-	bool canStepOntoDst() const;
 
 public:
 	CPathfinder(CPathsInfo &_out, CGameState *_gs, const CGHeroInstance *_hero);

+ 1 - 0
lib/CModHandler.cpp

@@ -292,6 +292,7 @@ void CModHandler::loadActiveMods()
 		handleData(VLC->townh, config["factions"]);
 		handleData(VLC->creh, config["creatures"]);
 		handleData(VLC->arth, config["artifacts"]);
+		//todo: spells
 
 		handleData(&VLC->heroh->classes, config["heroClasses"]);
 		handleData(VLC->heroh, config["heroes"]);

+ 3 - 6
lib/CObjectHandler.cpp

@@ -1384,13 +1384,12 @@ void CGHeroInstance::showNecromancyDialog(const CStackBasicDescriptor &raisedSta
 	{
 		iw.text.addTxt(MetaString::GENERAL_TXT, 145);
 		iw.text.addReplacement(raisedStack.count);
-		iw.text.addReplacement(MetaString::CRE_PL_NAMES, raisedStack.type->idNumber);
 	}
 	else // Practicing the dark arts of necromancy, ... (singular)
 	{
 		iw.text.addTxt(MetaString::GENERAL_TXT, 146);
-		iw.text.addReplacement(MetaString::CRE_SING_NAMES, raisedStack.type->idNumber);
 	}
+	iw.text.addReplacement(raisedStack);
 
 	cb->showInfoDialog(&iw);
 }
@@ -4772,7 +4771,7 @@ void CGWitchHut::onHeroVisit( const CGHeroInstance * h ) const
 const std::string & CGWitchHut::getHoverText() const
 {
 	hoverName = VLC->generaltexth->names[ID];
-	if(wasVisited(cb->getCurrentPlayer())) //TODO: use local player, not current
+	if(wasVisited(cb->getLocalPlayer()))
 	{
 		hoverName += "\n" + VLC->generaltexth->allTexts[356]; // + (learn %s)
 		boost::algorithm::replace_first(hoverName,"%s",VLC->generaltexth->skillName[ability]);
@@ -6113,14 +6112,12 @@ void CBank::endBattle (const CGHeroInstance *h, const BattleResult *result) cons
 		//display loot
 		if (!iw.components.empty())
 		{
+			iw.text.addTxt (MetaString::ADVOB_TXT, textID);
 			if (textID == 34)
 			{
-				iw.text.addTxt(MetaString::ADVOB_TXT, 34);//Heaving defeated %s, you discover %s
 				iw.text.addReplacement(MetaString::CRE_PL_NAMES, result->casualties[1].begin()->first);
 				iw.text.addReplacement(loot.buildList());
 			}
-			else
-				iw.text.addTxt (MetaString::ADVOB_TXT, textID);
 			cb->showInfoDialog(&iw);
 		}
 		loot.clear();

+ 1 - 1
lib/CObjectHandler.h

@@ -167,7 +167,7 @@ public:
 	mutable std::string hoverName;
 	int3 pos; //h3m pos
 	si32 ID, subID; //normal ID (this one from OH3 maps ;]) - eg. town=98; hero=34
-	si32 id;//number of object in CObjectHandler's vector
+	si32 id;//number of object in map's vector
 	CGDefInfo * defInfo;
 	ui8 animPhaseShift;
 

+ 74 - 12
lib/CSpellHandler.cpp

@@ -96,7 +96,7 @@ namespace SRSLPraserHelpers
 			mainPointForLayer[b] = hexToPair(center);
 
 		for(int it=1; it<=high; ++it) //it - distance to the center
-		{		
+		{
 			for(int b=0; b<6; ++b)
 				mainPointForLayer[b] = gotoDir(mainPointForLayer[b], b);
 
@@ -122,11 +122,19 @@ namespace SRSLPraserHelpers
 		return ret;
 	}
 }
+
 using namespace SRSLPraserHelpers;
 CSpellHandler::CSpellHandler()
 {
-	VLC->spellh = this;
 }
+
+CSpell::CSpell()
+{
+	_isDamage = false;
+	_isMind = false;
+	_isRising = false;
+}
+
 std::vector<BattleHex> CSpell::rangeInHexes(BattleHex centralHex, ui8 schoolLvl, ui8 side, bool *outDroppedHexes) const
 {
 	std::vector<BattleHex> ret;
@@ -238,7 +246,7 @@ CSpell::ETargetType CSpell::getTargetType() const //TODO: parse these at game la
 
 	if(attributes.find("OBSTACLE_TARGET") != std::string::npos)
 		return OBSTACLE;
-	
+
 	return NO_TARGET;
 }
 
@@ -254,13 +262,30 @@ bool CSpell::isNegative() const
 
 bool CSpell::isRisingSpell() const
 {
-	return vstd::contains(VLC->spellh->risingSpells, id);
+	return _isRising;
 }
 
 bool CSpell::isDamageSpell() const
 {
-	return vstd::contains(VLC->spellh->damageSpells, id);
+	return _isDamage;
 }
+
+bool CSpell::isMindSpell() const
+{
+	return _isMind;
+}
+
+void CSpell::getEffects(std::vector<Bonus>& lst) const
+{
+	lst.reserve(lst.size() + _effects.size());
+
+	BOOST_FOREACH (Bonus b, _effects)
+	{
+		lst.push_back(b);
+	}
+}
+
+
 bool DLL_LINKAGE isInScreenRange(const int3 &center, const int3 &pos)
 {
 	int3 diff = pos - center;
@@ -343,7 +368,10 @@ void CSpellHandler::loadSpells()
 	}
 	while (parser.endLine() && !parser.isNextEntryEmpty());
 
-	boost::replace_first (spells[47]->attributes, "2", ""); // disrupting ray will now affect single creature
+	boost::replace_first (spells[Spells::DISRUPTING_RAY]->attributes, "2", ""); // disrupting ray will now affect single creature
+
+
+	spells.push_back(spells[Spells::ACID_BREATH_DEFENSE]); //clone Acid Breath attributes for Acid Breath damage effect
 
 	//loading of additional spell traits
 	const JsonNode config(ResourceID("config/spell_info.json"));
@@ -367,16 +395,50 @@ void CSpellHandler::loadSpells()
 		s->identifier = spell.first;
 		VLC->modh->identifiers.registerObject("spell." + spell.first, spellID);
 
+		const JsonNode & flags_node = spell.second["flags"];
+		if (!flags_node.isNull())
+		{
+			auto flags = flags_node.convertTo<std::vector<std::string> >();
+
+			BOOST_FOREACH (const auto & flag, flags)
+			{
+				if (flag == "damage")
+				{
+					s->_isDamage = true;
+				}
+				else if (flag == "rising")
+				{
+					s->_isRising = true;
+				}
+				else if (flag == "mind")
+				{
+					s->_isMind = true;
+				}
+
+			}
+		}
+
+		const JsonNode & effects_node = spell.second["effects"];
+
+		if (!effects_node.isNull())
+		{
+			BOOST_FOREACH (const JsonNode & bonus_node, effects_node.Vector())
+			{
+				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);
+			}
+		}
+
 	}
+
 	//spell fixes
 
-	spells.push_back(spells[80]); //clone Acid Breath attributes for Acid Breath damage effect
 	//forgetfulness needs to get targets automatically on expert level
-	boost::replace_first(spells[61]->attributes, "CREATURE_TARGET", "CREATURE_TARGET_2"); //TODO: use flags instead?
-
-	damageSpells += 11, 13, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 57, 77;
-	risingSpells += 38, 39, 40;
-	mindSpells += 50, 59, 60, 61, 62;
+	boost::replace_first(spells[Spells::FORGETFULNESS]->attributes, "CREATURE_TARGET", "CREATURE_TARGET_2"); //TODO: use flags instead?
 }
 
 std::vector<ui8> CSpellHandler::getDefaultAllowedSpells() const

+ 33 - 16
lib/CSpellHandler.h

@@ -3,6 +3,7 @@
 #include "../lib/ConstTransitivePtr.h"
 #include "int3.h"
 #include "GameConstants.h"
+#include "HeroBonus.h"
 
 /*
  * CSpellHandler.h, part of VCMI engine
@@ -44,20 +45,38 @@ public:
 	std::vector<std::string> range; //description of spell's range in SRSL by magic school level
 	std::vector<TSpell> counteredSpells; //spells that are removed when effect of this spell is placed on creature (for bless-curse, haste-slow, and similar pairs)
 
+	CSpell();
+
 	std::vector<BattleHex> rangeInHexes(BattleHex centralHex, ui8 schoolLvl, ui8 side, bool *outDroppedHexes = NULL ) const; //convert range to specific hexes; last optional out parameter is set to true, if spell would cover unavailable hexes (that are not included in ret)
 	si16 mainEffectAnim; //main spell effect animation, in AC format (or -1 when none)
 	ETargetType getTargetType() const;
 
 	bool isPositive() const;
 	bool isNegative() const;
+
 	bool isRisingSpell() const;
 	bool isDamageSpell() const;
+	bool isMindSpell() const;
+
+
+	void getEffects(std::vector<Bonus> & lst) 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;
 	}
+	friend class CSpellHandler;
+
+private:
+	bool _isRising;
+	bool _isDamage;
+	bool _isMind;
+
+	std::vector<Bonus> _effects;
+
 };
 
 namespace Spells
@@ -68,18 +87,18 @@ namespace Spells
 		FLY=6, WATER_WALK=7, DIMENSION_DOOR=8, TOWN_PORTAL=9,
 
 		QUICKSAND=10, LAND_MINE=11, FORCE_FIELD=12, FIRE_WALL=13, EARTHQUAKE=14,
-		MAGIC_ARROW=15, ICE_BOLT=16, LIGHTNING_BOLT=17, IMPLOSION=18, 
-		CHAIN_LIGHTNING=19, FROST_RING=20, FIREBALL=21, INFERNO=22, 
-		METEOR_SHOWER=23, DEATH_RIPPLE=24, DESTROY_UNDEAD=25, ARMAGEDDON=26, 
-		SHIELD=27, AIR_SHIELD=28, FIRE_SHIELD=29, PROTECTION_FROM_AIR=30, 
-		PROTECTION_FROM_FIRE=31, PROTECTION_FROM_WATER=32, 
-		PROTECTION_FROM_EARTH=33, ANTI_MAGIC=34, DISPEL=35, MAGIC_MIRROR=36, 
-		CURE=37, RESURRECTION=38, ANIMATE_DEAD=39, SACRIFICE=40, BLESS=41, 
-		CURSE=42, BLOODLUST=43, PRECISION=44, WEAKNESS=45, STONE_SKIN=46, 
-		DISRUPTING_RAY=47, PRAYER=48, MIRTH=49, SORROW=50, FORTUNE=51, 
-		MISFORTUNE=52, HASTE=53, SLOW=54, SLAYER=55, FRENZY=56, 
-		TITANS_LIGHTNING_BOLT=57, COUNTERSTRIKE=58, BERSERK=59, HYPNOTIZE=60, 
-		FORGETFULNESS=61, BLIND=62, TELEPORT=63, REMOVE_OBSTACLE=64, CLONE=65, 
+		MAGIC_ARROW=15, ICE_BOLT=16, LIGHTNING_BOLT=17, IMPLOSION=18,
+		CHAIN_LIGHTNING=19, FROST_RING=20, FIREBALL=21, INFERNO=22,
+		METEOR_SHOWER=23, DEATH_RIPPLE=24, DESTROY_UNDEAD=25, ARMAGEDDON=26,
+		SHIELD=27, AIR_SHIELD=28, FIRE_SHIELD=29, PROTECTION_FROM_AIR=30,
+		PROTECTION_FROM_FIRE=31, PROTECTION_FROM_WATER=32,
+		PROTECTION_FROM_EARTH=33, ANTI_MAGIC=34, DISPEL=35, MAGIC_MIRROR=36,
+		CURE=37, RESURRECTION=38, ANIMATE_DEAD=39, SACRIFICE=40, BLESS=41,
+		CURSE=42, BLOODLUST=43, PRECISION=44, WEAKNESS=45, STONE_SKIN=46,
+		DISRUPTING_RAY=47, PRAYER=48, MIRTH=49, SORROW=50, FORTUNE=51,
+		MISFORTUNE=52, HASTE=53, SLOW=54, SLAYER=55, FRENZY=56,
+		TITANS_LIGHTNING_BOLT=57, COUNTERSTRIKE=58, BERSERK=59, HYPNOTIZE=60,
+		FORGETFULNESS=61, BLIND=62, TELEPORT=63, REMOVE_OBSTACLE=64, CLONE=65,
 		SUMMON_FIRE_ELEMENTAL=66, SUMMON_EARTH_ELEMENTAL=67, SUMMON_WATER_ELEMENTAL=68, SUMMON_AIR_ELEMENTAL=69,
 
 		STONE_GAZE=70, POISON=71, BIND=72, DISEASE=73, PARALYZE=74, AGE=75, DEATH_CLOUD=76, THUNDERBOLT=77,
@@ -96,9 +115,7 @@ class DLL_LINKAGE CSpellHandler
 public:
 	CSpellHandler();
 	std::vector< ConstTransitivePtr<CSpell> > spells;
-	std::set<TSpell> damageSpells; //they inflict damage and require particular threatment
-	std::set<TSpell> risingSpells; //they affect dead stacks and need special target selection
-	std::set<TSpell> mindSpells;
+
 	void loadSpells();
 
 	/**
@@ -110,6 +127,6 @@ public:
 
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
-		h & spells & damageSpells & risingSpells & mindSpells;
+		h & spells ;
 	}
 };

+ 13 - 2
lib/Mapping/CMapService.cpp

@@ -955,7 +955,7 @@ void CMapLoaderH3M::readDefInfo()
 			defInfo->visitMap[zi] = reverse(bytes[6 + zi]);
 		}
 		pos += 16;
-		if(defInfo->id != Obj::HERO && defInfo->id != 70)
+		if(defInfo->id != Obj::HERO && defInfo->id != Obj::RANDOM_HERO)
 		{
 			CGDefInfo * h = VLC->dobjinfo->gobjs[defInfo->id][defInfo->subid];
 			if(!h)
@@ -1160,11 +1160,22 @@ void CMapLoaderH3M::readObjects()
 		case Obj::FLOTSAM:
 		case Obj::SEA_CHEST:
 		case Obj::SHIPWRECK_SURVIVOR:
-		case Obj::TREASURE_CHEST:
 			{
 				nobj = new CGPickable();
 				break;
 			}
+		case Obj::TREASURE_CHEST:
+				if(defInfo->subid == 0)
+				{
+					nobj = new CGPickable();
+				}
+				else
+				{
+					//WoG pickable object
+					//TODO: possible special handling
+					nobj = new CGObjectInstance();
+				}
+				break;
 		case Obj::MONSTER:  //Monster
 		case Obj::RANDOM_MONSTER:
 		case Obj::RANDOM_MONSTER_L1:

+ 1 - 1
server/CGameHandler.cpp

@@ -4376,7 +4376,7 @@ void CGameHandler::handleSpellCasting( int spellID, int spellLvl, BattleHex dest
 			{
 				BattleStackAttacked bsa;
 				bsa.flags |= BattleStackAttacked::EFFECT;
-				bsa.effect = VLC->spellh->spells[80]->mainEffectAnim; //use acid breath
+				bsa.effect = spell->mainEffectAnim;
 				bsa.damageAmount = usedSpellPower; //damage times the number of attackers
 				bsa.stackAttacked = (*it)->ID;
 				bsa.attackerID = -1;