Jelajahi Sumber

some changes towards editing H3 objects via mods. Should be stable, report if not.
- removed duplicated json loading code in handlers
- simpler and mod-friendly handling of combined artifacts
- reorganized CCreature to avoid huge number of fields in one structure

Ivan Savenko 12 tahun lalu
induk
melakukan
f306d7bb70

+ 14 - 11
client/BattleInterface/CBattleAnimations.cpp

@@ -137,7 +137,10 @@ bool CDefenceAnimation::init()
 		if(attacker != NULL)
 		{
 			int attackerAnimType = owner->creAnims[attacker->ID]->getType();
-			if( attackerAnimType == 11 && attackerAnimType == 12 && attackerAnimType == 13 && owner->creAnims[attacker->ID]->getFrame() < attacker->getCreature()->attackClimaxFrame )
+			if( ( attackerAnimType == CCreatureAnim::ATTACK_UP ||
+			    attackerAnimType == CCreatureAnim::ATTACK_FRONT ||
+			    attackerAnimType == CCreatureAnim::ATTACK_DOWN ) &&
+			    owner->creAnims[attacker->ID]->getFrame() < attacker->getCreature()->animation.attackClimaxFrame )
 				return false;
 		}
 
@@ -738,7 +741,7 @@ bool CShootingAnimation::init()
 
 	spi.step = 0;
 	spi.frameNum = 0;
-	spi.spin = shooterInfo->projectileSpin;
+	spi.spin = shooterInfo->animation.projectileSpin;
 
 	Point xycoord = CClickableHex::getXYUnitAnim(shooter->position, true, shooter, owner);
 	Point destcoord;
@@ -756,20 +759,20 @@ bool CShootingAnimation::init()
 		if (projectileAngle > straightAngle)
 		{
 			//upper shot
-			spi.x = xycoord.x + projectileOrigin.x + shooterInfo->upperRightMissleOffsetX;
-			spi.y = xycoord.y + projectileOrigin.y + shooterInfo->upperRightMissleOffsetY;
+			spi.x = xycoord.x + projectileOrigin.x + shooterInfo->animation.upperRightMissleOffsetX;
+			spi.y = xycoord.y + projectileOrigin.y + shooterInfo->animation.upperRightMissleOffsetY;
 		}
 		else if (projectileAngle < -straightAngle) 
 		{
 			//lower shot
-			spi.x = xycoord.x + projectileOrigin.x + shooterInfo->lowerRightMissleOffsetX;
-			spi.y = xycoord.y + projectileOrigin.y + shooterInfo->lowerRightMissleOffsetY;
+			spi.x = xycoord.x + projectileOrigin.x + shooterInfo->animation.lowerRightMissleOffsetX;
+			spi.y = xycoord.y + projectileOrigin.y + shooterInfo->animation.lowerRightMissleOffsetY;
 		}
 		else 
 		{
 			//straight shot
-			spi.x = xycoord.x + projectileOrigin.x + shooterInfo->rightMissleOffsetX;
-			spi.y = xycoord.y + projectileOrigin.y + shooterInfo->rightMissleOffsetY;
+			spi.x = xycoord.x + projectileOrigin.x + shooterInfo->animation.rightMissleOffsetX;
+			spi.y = xycoord.y + projectileOrigin.y + shooterInfo->animation.rightMissleOffsetY;
 		}
 
 		double animSpeed = 23.0 * owner->getAnimSpeed(); // flight speed of projectile
@@ -808,8 +811,8 @@ bool CShootingAnimation::init()
 			spi.lastStep = static_cast<int>((spi.catapultInfo->toX - spi.catapultInfo->fromX) / animSpeed);
 			spi.dx = animSpeed;
 			spi.dy = 0;
-			spi.x = xycoord.x + projectileOrigin.x + shooterInfo->rightMissleOffsetX + 17.;
-			spi.y = xycoord.y + projectileOrigin.y + shooterInfo->rightMissleOffsetY + 10.;
+			spi.x = xycoord.x + projectileOrigin.x + shooterInfo->animation.rightMissleOffsetX + 17.;
+			spi.y = xycoord.y + projectileOrigin.y + shooterInfo->animation.rightMissleOffsetY + 10.;
 
 			// Add explosion anim
 			int xEnd = static_cast<int>(spi.x + spi.lastStep * spi.dx);
@@ -830,7 +833,7 @@ bool CShootingAnimation::init()
 	}
 
 	// Set projectile animation start delay which is specified in frames
-	spi.animStartDelay = shooterInfo->attackClimaxFrame;
+	spi.animStartDelay = shooterInfo->animation.attackClimaxFrame;
 	owner->projectiles.push_back(spi);
 
 	//attack animation

+ 1 - 1
client/BattleInterface/CBattleInterface.cpp

@@ -300,7 +300,7 @@ CBattleInterface::CBattleInterface(const CCreatureSet * army1, const CCreatureSe
 			else
 				creature = s->getCreature();
 
-			projectile = CDefHandler::giveDef(creature->projectile);
+			projectile = CDefHandler::giveDef(creature->animation.projectileImageName);
 
 			if(projectile->ourImages.size() > 2) //add symmetric images
 			{

+ 93 - 93
config/heroes.json

@@ -123,7 +123,7 @@
 			"id": 8,
 			"class" : "cleric",
 			"female": false,
-			"spellbook": [ 46 ],
+			"spellbook": [ "stoneSkin" ],
 			"skills":
 			[
 				{ "skill" : "wisdom", "level": "basic" },
@@ -138,7 +138,7 @@
 			"id": 9,
 			"class" : "cleric",
 			"female": true,
-			"spellbook": [ 41 ],
+			"spellbook": [ "bless" ],
 			"skills":
 			[
 				{ "skill" : "wisdom", "level": "basic" },
@@ -153,7 +153,7 @@
 			"id": 10,
 			"class" : "cleric",
 			"female": false,
-			"spellbook": [ 45 ],
+			"spellbook": [ "weakness" ],
 			"skills":
 			[
 				{ "skill" : "wisdom", "level": "basic" },
@@ -168,7 +168,7 @@
 			"id": 11,
 			"class" : "cleric",
 			"female": true,
-			"spellbook": [ 20 ],
+			"spellbook": [ "frostRing" ],
 			"skills":
 			[
 				{ "skill" : "wisdom", "level": "advanced" }
@@ -182,7 +182,7 @@
 			"id": 12,
 			"class" : "cleric",
 			"female": false,
-			"spellbook": [ 42 ],
+			"spellbook": [ "curse" ],
 			"skills":
 			[
 				{ "skill" : "wisdom", "level": "basic" },
@@ -197,7 +197,7 @@
 			"id": 13,
 			"class" : "cleric",
 			"female": true,
-			"spellbook": [ 35 ],
+			"spellbook": [ "dispel" ],
 			"skills":
 			[
 				{ "skill" : "wisdom", "level": "basic" },
@@ -211,7 +211,7 @@
 		{
 			"id": 14,
 			"class" : "cleric",
-			"spellbook": [ 48 ],
+			"spellbook": [ "prayer" ],
 			"female": false,
 			"skills":
 			[
@@ -227,7 +227,7 @@
 			"id": 15,
 			"class" : "cleric",
 			"female": true,
-			"spellbook": [ 37 ],
+			"spellbook": [ "cure" ],
 			"skills":
 			[
 				{ "skill" : "wisdom", "level": "basic" },
@@ -351,7 +351,7 @@
 		{
 			"id": 24,
 			"class" : "druid",
-			"spellbook": [ 55 ],
+			"spellbook": [ "slayer" ],
 			"female": false,
 			"skills":
 			[
@@ -367,7 +367,7 @@
 			"id": 25,
 			"class" : "druid",
 			"female": false,
-			"spellbook": [ 37 ],
+			"spellbook": [ "cure" ],
 			"skills":
 			[
 				{ "skill" : "wisdom", "level": "advanced" },
@@ -382,7 +382,7 @@
 			"id": 26,
 			"class" : "druid",
 			"female": false,
-			"spellbook": [ 42 ],
+			"spellbook": [ "curse" ],
 			"skills":
 			[
 				{ "skill" : "wisdom", "level": "basic" },
@@ -397,7 +397,7 @@
 			"id": 27,
 			"class" : "druid",
 			"female": true,
-			"spellbook": [ 0 ],
+			"spellbook": [ "summonBoat" ],
 			"skills":
 			[
 				{ "skill" : "wisdom", "level": "basic" },
@@ -412,7 +412,7 @@
 			"id": 28,
 			"class" : "druid",
 			"female": false,
-			"spellbook": [ 15 ],
+			"spellbook": [ "magicArrow" ],
 			"skills":
 			[
 				{ "skill" : "wisdom", "level": "basic" },
@@ -426,7 +426,7 @@
 		{
 			"id": 29,
 			"class" : "druid",
-			"spellbook": [ 51 ],
+			"spellbook": [ "fortune" ],
 			"female": true,
 			"skills":
 			[
@@ -442,7 +442,7 @@
 			"id": 30,
 			"class" : "druid",
 			"female": false,
-			"spellbook": [ 16 ],
+			"spellbook": [ "iceBolt" ],
 			"skills":
 			[
 				{ "skill" : "wisdom", "level": "basic" },
@@ -457,7 +457,7 @@
 			"id": 31,
 			"class" : "druid",
 			"female": false,
-			"spellbook": [ 30 ],
+			"spellbook": [ "protectAir" ],
 			"skills":
 			[
 				{ "skill" : "wisdom", "level": "basic" },
@@ -472,7 +472,7 @@
 			"id": 32,
 			"class" : "alchemist",
 			"female": false,
-			"spellbook": [ 27 ],
+			"spellbook": [ "shield" ],
 			"skills":
 			[
 				{ "skill" : "scouting", "level": "basic" },
@@ -487,7 +487,7 @@
 			"id": 33,
 			"class" : "alchemist",
 			"female": false,
-			"spellbook": [ 15 ],
+			"spellbook": [ "magicArrow" ],
 			"skills":
 			[
 				{ "skill" : "scholar", "level": "advanced" }
@@ -500,7 +500,7 @@
 		{
 			"id": 34,
 			"class" : "alchemist",
-			"spellbook": [ 53 ],
+			"spellbook": [ "haste" ],
 			"female": true,
 			"skills":
 			[
@@ -516,7 +516,7 @@
 			"id": 35,
 			"class" : "alchemist",
 			"female": true,
-			"spellbook": [ 27 ],
+			"spellbook": [ "shield" ],
 			"skills":
 			[
 				{ "skill" : "scholar", "level": "basic" },
@@ -531,7 +531,7 @@
 			"id": 36,
 			"class" : "alchemist",
 			"female": false,
-			"spellbook": [ 15 ],
+			"spellbook": [ "magicArrow" ],
 			"skills":
 			[
 				{ "skill" : "mysticism", "level": "basic" },
@@ -546,7 +546,7 @@
 			"id": 37,
 			"class" : "alchemist",
 			"female": false,
-			"spellbook": [ 53 ],
+			"spellbook": [ "haste" ],
 			"skills":
 			[
 				{ "skill" : "scholar", "level": "basic" },
@@ -561,7 +561,7 @@
 			"id": 38,
 			"class" : "alchemist",
 			"female": true,
-			"spellbook": [ 15 ],
+			"spellbook": [ "magicArrow" ],
 			"skills":
 			[
 				{ "skill" : "mysticism", "level": "basic" },
@@ -575,7 +575,7 @@
 		{
 			"id": 39,
 			"class" : "alchemist",
-			"spellbook": [ 15 ],
+			"spellbook": [ "magicArrow" ],
 			"female": true,
 			"skills":
 			[
@@ -591,7 +591,7 @@
 			"id": 40,
 			"class" : "wizard",
 			"female": false,
-			"spellbook": [ 60 ],
+			"spellbook": [ "hypnotize" ],
 			"skills":
 			[
 				{ "skill" : "wisdom", "level": "advanced" }
@@ -605,7 +605,7 @@
 			"id": 41,
 			"class" : "wizard",
 			"female": false,
-			"spellbook": [ 46 ],
+			"spellbook": [ "stoneSkin" ],
 			"skills":
 			[
 				{ "skill" : "wisdom", "level": "basic" },
@@ -620,7 +620,7 @@
 			"id": 42,
 			"class" : "wizard",
 			"female": true,
-			"spellbook": [ 35 ],
+			"spellbook": [ "dispel" ],
 			"skills":
 			[
 				{ "skill" : "wisdom", "level": "basic" },
@@ -635,7 +635,7 @@
 			"id": 43,
 			"class" : "wizard",
 			"female": true,
-			"spellbook": [ 51 ],
+			"spellbook": [ "fortune" ],
 			"skills":
 			[
 				{ "skill" : "wisdom", "level": "basic" },
@@ -649,7 +649,7 @@
 		{
 			"id": 44,
 			"class" : "wizard",
-			"spellbook": [ 27 ],
+			"spellbook": [ "shield" ],
 			"female": false,
 			"skills":
 			[
@@ -665,7 +665,7 @@
 			"id": 45,
 			"class" : "wizard",
 			"female": false,
-			"spellbook": [ 19 ],
+			"spellbook": [ "chainLightning" ],
 			"skills":
 			[
 				{ "skill" : "wisdom", "level": "basic" },
@@ -680,7 +680,7 @@
 			"id": 46,
 			"class" : "wizard",
 			"female": true,
-			"spellbook": [ 53 ],
+			"spellbook": [ "haste" ],
 			"skills":
 			[
 				{ "skill" : "wisdom", "level": "basic" },
@@ -695,7 +695,7 @@
 			"id": 47,
 			"class" : "wizard",
 			"female": true,
-			"spellbook": [ 42 ],
+			"spellbook": [ "curse" ],
 			"skills":
 			[
 				{ "skill" : "wisdom", "level": "basic" },
@@ -818,7 +818,7 @@
 		{
 			"id": 56,
 			"class" : "heretic",
-			"spellbook": [ 3 ],
+			"spellbook": [ "viewEarth" ],
 			"female": false,
 			"skills":
 			[
@@ -834,7 +834,7 @@
 			"id": 57,
 			"class" : "heretic",
 			"female": false,
-			"spellbook": [ 22 ],
+			"spellbook": [ "inferno" ],
 			"skills":
 			[
 				{ "skill" : "wisdom", "level": "basic" },
@@ -849,7 +849,7 @@
 			"id": 58,
 			"class" : "heretic",
 			"female": false,
-			"spellbook": [ 30 ],
+			"spellbook": [ "protectAir" ],
 			"skills":
 			[
 				{ "skill" : "wisdom", "level": "basic" },
@@ -864,7 +864,7 @@
 			"id": 59,
 			"class" : "heretic",
 			"female": true,
-			"spellbook": [ 45 ],
+			"spellbook": [ "weakness" ],
 			"skills":
 			[
 				{ "skill" : "wisdom", "level": "basic" },
@@ -879,7 +879,7 @@
 			"id": 60,
 			"class" : "heretic",
 			"female": false,
-			"spellbook": [ 53 ],
+			"spellbook": [ "haste" ],
 			"skills":
 			[
 				{ "skill" : "wisdom", "level": "basic" },
@@ -894,7 +894,7 @@
 			"id": 61,
 			"class" : "heretic",
 			"female": true,
-			"spellbook": [ 43 ],
+			"spellbook": [ "bloodlust" ],
 			"skills":
 			[
 				{ "skill" : "wisdom", "level": "basic" },
@@ -909,7 +909,7 @@
 			"id": 62,
 			"class" : "heretic",
 			"female": false,
-			"spellbook": [ 46 ],
+			"spellbook": [ "stoneSkin" ],
 			"skills":
 			[
 				{ "skill" : "wisdom", "level": "basic" },
@@ -924,7 +924,7 @@
 			"id": 63,
 			"class" : "heretic",
 			"female": false,
-			"spellbook": [ 21 ],
+			"spellbook": [ "fireball" ],
 			"skills":
 			[
 				{ "skill" : "wisdom", "level": "basic" },
@@ -939,7 +939,7 @@
 			"id": 64,
 			"class" : "deathknight",
 			"female": false,
-			"spellbook": [ 53 ],
+			"spellbook": [ "haste" ],
 			"skills":
 			[
 				{ "skill" : "necromancy", "level": "basic" },
@@ -954,7 +954,7 @@
 			"id": 65,
 			"class" : "deathknight",
 			"female": false,
-			"spellbook": [ 46 ],
+			"spellbook": [ "stoneSkin" ],
 			"skills":
 			[
 				{ "skill" : "necromancy", "level": "basic" },
@@ -969,7 +969,7 @@
 			"id": 66,
 			"class" : "deathknight",
 			"female": false,
-			"spellbook": [ 54 ],
+			"spellbook": [ "slow" ],
 			"skills":
 			[
 				{ "skill" : "necromancy", "level": "basic" },
@@ -984,7 +984,7 @@
 			"id": 67,
 			"class" : "deathknight",
 			"female": true,
-			"spellbook": [ 15 ],
+			"spellbook": [ "magicArrow" ],
 			"skills":
 			[
 				{ "skill" : "necromancy", "level": "basic" },
@@ -999,7 +999,7 @@
 			"id": 68,
 			"class" : "deathknight",
 			"female": true,
-			"spellbook": [ 15 ],
+			"spellbook": [ "magicArrow" ],
 			"skills":
 			[
 				{ "skill" : "necromancy", "level": "basic" },
@@ -1014,7 +1014,7 @@
 			"id": 69,
 			"class" : "deathknight",
 			"female": true,
-			"spellbook": [ 15 ],
+			"spellbook": [ "magicArrow" ],
 			"skills":
 			[
 				{ "skill" : "necromancy", "level": "advanced" }
@@ -1028,7 +1028,7 @@
 			"id": 70,
 			"class" : "deathknight",
 			"female": false,
-			"spellbook": [ 15 ],
+			"spellbook": [ "magicArrow" ],
 			"skills":
 			[
 				{ "skill" : "necromancy", "level": "basic" },
@@ -1043,7 +1043,7 @@
 			"id": 71,
 			"class" : "deathknight",
 			"female": false,
-			"spellbook": [ 27 ],
+			"spellbook": [ "shield" ],
 			"skills":
 			[
 				{ "skill" : "necromancy", "level": "basic" },
@@ -1058,7 +1058,7 @@
 			"id": 72,
 			"class" : "necromancer",
 			"female": true,
-			"spellbook": [ 24 ],
+			"spellbook": [ "deathRipple" ],
 			"skills":
 			[
 				{ "skill" : "necromancy", "level": "basic" },
@@ -1073,7 +1073,7 @@
 			"id": 73,
 			"class" : "necromancer",
 			"female": true,
-			"spellbook": [ 23 ],
+			"spellbook": [ "meteorShower" ],
 			"skills":
 			[
 				{ "skill" : "necromancy", "level": "basic" },
@@ -1088,7 +1088,7 @@
 			"id": 74,
 			"class" : "necromancer",
 			"female": false,
-			"spellbook": [ 54 ],
+			"spellbook": [ "slow" ],
 			"skills":
 			[
 				{ "skill" : "necromancy", "level": "basic" },
@@ -1103,7 +1103,7 @@
 			"id": 75,
 			"class" : "necromancer",
 			"female": false,
-			"spellbook": [ 27 ],
+			"spellbook": [ "shield" ],
 			"skills":
 			[
 				{ "skill" : "necromancy", "level": "basic" },
@@ -1118,7 +1118,7 @@
 			"id": 76,
 			"class" : "necromancer",
 			"female": false,
-			"spellbook": [ 39 ],
+			"spellbook": [ "animateDead" ],
 			"skills":
 			[
 				{ "skill" : "necromancy", "level": "basic" },
@@ -1133,7 +1133,7 @@
 			"id": 77,
 			"class" : "necromancer",
 			"female": true,
-			"spellbook": [ 46 ],
+			"spellbook": [ "stoneSkin" ],
 			"skills":
 			[
 				{ "skill" : "necromancy", "level": "basic" },
@@ -1148,7 +1148,7 @@
 			"id": 78,
 			"class" : "necromancer",
 			"female": true,
-			"spellbook": [ 42 ],
+			"spellbook": [ "curse" ],
 			"skills":
 			[
 				{ "skill" : "necromancy", "level": "advanced" }
@@ -1162,7 +1162,7 @@
 			"id": 79,
 			"class" : "necromancer",
 			"female": false,
-			"spellbook": [ 30 ],
+			"spellbook": [ "protectAir" ],
 			"skills":
 			[
 				{ "skill" : "necromancy", "level": "basic" },
@@ -1287,7 +1287,7 @@
 		{
 			"id": 88,
 			"class" : "overlord",
-			"spellbook": [ 38 ],
+			"spellbook": [ "resurrection" ],
 			"female": false,
 			"skills":
 			[
@@ -1303,7 +1303,7 @@
 			"id": 89,
 			"class" : "overlord",
 			"female": false,
-			"spellbook": [ 27 ],
+			"spellbook": [ "shield" ],
 			"skills":
 			[
 				{ "skill" : "wisdom", "level": "basic" },
@@ -1318,7 +1318,7 @@
 			"id": 90,
 			"class" : "overlord",
 			"female": false,
-			"spellbook": [ 43 ],
+			"spellbook": [ "bloodlust" ],
 			"skills":
 			[
 				{ "skill" : "wisdom", "level": "basic" },
@@ -1333,7 +1333,7 @@
 			"id": 91,
 			"class" : "overlord",
 			"female": true,
-			"spellbook": [ 38 ],
+			"spellbook": [ "resurrection" ],
 			"skills":
 			[
 				{ "skill" : "wisdom", "level": "advanced" }
@@ -1347,7 +1347,7 @@
 			"id": 92,
 			"class" : "overlord",
 			"female": false,
-			"spellbook": [ 54 ],
+			"spellbook": [ "slow" ],
 			"skills":
 			[
 				{ "skill" : "wisdom", "level": "basic" },
@@ -1362,7 +1362,7 @@
 			"id": 93,
 			"class" : "overlord",
 			"female": false,
-			"spellbook": [ 23 ],
+			"spellbook": [ "meteorShower" ],
 			"skills":
 			[
 				{ "skill" : "wisdom", "level": "basic" },
@@ -1377,7 +1377,7 @@
 			"id": 94,
 			"class" : "overlord",
 			"female": true,
-			"spellbook": [ 30 ],
+			"spellbook": [ "protectAir" ],
 			"skills":
 			[
 				{ "skill" : "wisdom", "level": "basic" },
@@ -1392,7 +1392,7 @@
 			"id": 95,
 			"class" : "overlord",
 			"female": false,
-			"spellbook": [ 46 ],
+			"spellbook": [ "stoneSkin" ],
 			"skills":
 			[
 				{ "skill" : "wisdom", "level": "basic" },
@@ -1513,7 +1513,7 @@
 		{
 			"id": 104,
 			"class" : "battlemage",
-			"spellbook": [ 43 ],
+			"spellbook": [ "bloodlust" ],
 			"female": true,
 			"skills":
 			[
@@ -1529,7 +1529,7 @@
 			"id": 105,
 			"class" : "battlemage",
 			"female": false,
-			"spellbook": [ 15 ],
+			"spellbook": [ "magicArrow" ],
 			"skills":
 			[
 				{ "skill" : "wisdom", "level": "basic" },
@@ -1544,7 +1544,7 @@
 			"id": 106,
 			"class" : "battlemage",
 			"female": true,
-			"spellbook": [ 46 ],
+			"spellbook": [ "stoneSkin" ],
 			"skills":
 			[
 				{ "skill" : "wisdom", "level": "basic" },
@@ -1559,7 +1559,7 @@
 			"id": 107,
 			"class" : "battlemage",
 			"female": false,
-			"spellbook": [ 53 ],
+			"spellbook": [ "haste" ],
 			"skills":
 			[
 				{ "skill" : "wisdom", "level": "basic" },
@@ -1574,7 +1574,7 @@
 			"id": 108,
 			"class" : "battlemage",
 			"female": false,
-			"spellbook": [ 44 ],
+			"spellbook": [ "precision" ],
 			"skills":
 			[
 				{ "skill" : "wisdom", "level": "basic" },
@@ -1588,7 +1588,7 @@
 		{
 			"id": 109,
 			"class" : "battlemage",
-			"spellbook": [ 54 ],
+			"spellbook": [ "slow" ],
 			"female": true,
 			"skills":
 			[
@@ -1604,7 +1604,7 @@
 			"id": 110,
 			"class" : "battlemage",
 			"female": true,
-			"spellbook": [ 30 ],
+			"spellbook": [ "protectAir" ],
 			"skills":
 			[
 				{ "skill" : "wisdom", "level": "basic" },
@@ -1619,7 +1619,7 @@
 			"id": 111,
 			"class" : "battlemage",
 			"female": false,
-			"spellbook": [ 43 ],
+			"spellbook": [ "bloodlust" ],
 			"skills":
 			[
 				{ "skill" : "wisdom", "level": "basic" },
@@ -1744,7 +1744,7 @@
 		{
 			"id": 120,
 			"class" : "witch",
-			"spellbook": [ 45 ],
+			"spellbook": [ "weakness" ],
 			"female": true,
 			"skills":
 			[
@@ -1759,7 +1759,7 @@
 			"id": 121,
 			"class" : "witch",
 			"female": true,
-			"spellbook": [ 15 ],
+			"spellbook": [ "magicArrow" ],
 			"skills":
 			[
 				{ "skill" : "wisdom", "level": "basic" },
@@ -1774,7 +1774,7 @@
 			"id": 122,
 			"class" : "witch",
 			"female": true,
-			"spellbook": [ 54 ],
+			"spellbook": [ "slow" ],
 			"skills":
 			[
 				{ "skill" : "wisdom", "level": "basic" },
@@ -1789,7 +1789,7 @@
 			"id": 123,
 			"class" : "witch",
 			"female": true,
-			"spellbook": [ 31 ],
+			"spellbook": [ "protectFire" ],
 			"skills":
 			[
 				{ "skill" : "wisdom", "level": "basic" },
@@ -1804,7 +1804,7 @@
 			"id": 124,
 			"class" : "witch",
 			"female": true,
-			"spellbook": [ 46 ],
+			"spellbook": [ "stoneSkin" ],
 			"skills":
 			[
 				{ "skill" : "wisdom", "level": "basic" },
@@ -1819,7 +1819,7 @@
 			"id": 125,
 			"class" : "witch",
 			"female": true,
-			"spellbook": [ 27 ],
+			"spellbook": [ "shield" ],
 			"skills":
 			[
 				{ "skill" : "wisdom", "level": "basic" },
@@ -1834,7 +1834,7 @@
 			"id": 126,
 			"class" : "witch",
 			"female": true,
-			"spellbook": [ 35 ],
+			"spellbook": [ "dispel" ],
 			"skills":
 			[
 				{ "skill" : "wisdom", "level": "basic" },
@@ -1849,7 +1849,7 @@
 			"id": 127,
 			"class" : "witch",
 			"female": true,
-			"spellbook": [ 46 ],
+			"spellbook": [ "stoneSkin" ],
 			"skills":
 			[
 				{ "skill" : "wisdom", "level": "basic" },
@@ -1983,7 +1983,7 @@
 		{
 			"id": 136,
 			"class" : "elementalist",
-			"spellbook": [ 13 ],
+			"spellbook": [ "fireWall" ],
 			"female": true,
 			"skills":
 			[
@@ -1999,7 +1999,7 @@
 			"id": 137,
 			"class" : "elementalist",
 			"female": true,
-			"spellbook": [ 53 ],
+			"spellbook": [ "haste" ],
 			"skills":
 			[
 				{ "skill" : "wisdom", "level": "basic" },
@@ -2014,7 +2014,7 @@
 			"id": 138,
 			"class" : "elementalist",
 			"female": true,
-			"spellbook": [ 15 ],
+			"spellbook": [ "magicArrow" ],
 			"skills":
 			[
 				{ "skill" : "wisdom", "level": "basic" },
@@ -2029,7 +2029,7 @@
 			"id": 139,
 			"class" : "elementalist",
 			"female": true,
-			"spellbook": [ 46 ],
+			"spellbook": [ "stoneSkin" ],
 			"skills":
 			[
 				{ "skill" : "wisdom", "level": "basic" },
@@ -2044,7 +2044,7 @@
 			"id": 140,
 			"class" : "elementalist",
 			"female": false,
-			"spellbook": [ 43 ],
+			"spellbook": [ "bloodlust" ],
 			"skills":
 			[
 				{ "skill" : "wisdom", "level": "basic" },
@@ -2059,7 +2059,7 @@
 			"id": 141,
 			"class" : "elementalist",
 			"female": false,
-			"spellbook": [ 47 ],
+			"spellbook": [ "disruptingRay" ],
 			"skills":
 			[
 				{ "skill" : "wisdom", "level": "basic" },
@@ -2074,7 +2074,7 @@
 			"id": 142,
 			"class" : "elementalist",
 			"female": false,
-			"spellbook": [ 35 ],
+			"spellbook": [ "dispel" ],
 			"skills":
 			[
 				{ "skill" : "wisdom", "level": "basic" },
@@ -2089,7 +2089,7 @@
 			"id": 143,
 			"class" : "elementalist",
 			"female": false,
-			"spellbook": [ 54 ],
+			"spellbook": [ "slow" ],
 			"skills":
 			[
 				{ "skill" : "wisdom", "level": "basic" },
@@ -2119,7 +2119,7 @@
 			"class" : "witch",
 			"female": true,
 			"special" : true,
-			"spellbook": [ 22 ],
+			"spellbook": [ "inferno" ],
 			"skills":
 			[
 				{ "skill" : "wisdom", "level": "basic" },
@@ -2149,7 +2149,7 @@
 			"class" : "wizard",
 			"female": false,
 			"special" : true,
-			"spellbook": [ 53 ],
+			"spellbook": [ "haste" ],
 			"skills":
 			[
 				{ "skill" : "wisdom", "level": "advanced" }
@@ -2197,7 +2197,7 @@
 			"class" : "deathknight",
 			"female": false,
 			"special" : true,
-			"spellbook": [ 54 ],
+			"spellbook": [ "slow" ],
 			"skills":
 			[
 				{ "skill" : "necromancy", "level": "advanced" }
@@ -2214,7 +2214,7 @@
 			"class" : "warlock",
 			"female": true,
 			"special" : true,
-			"spellbook": [ 15 ],
+			"spellbook": [ "magicArrow" ],
 			"skills":
 			[
 				{ "skill" : "estates", "level": "basic" },
@@ -2246,7 +2246,7 @@
 			"class" : "warlock",
 			"female": true,
 			"special" : true,
-			"spellbook": [ 15 ],
+			"spellbook": [ "magicArrow" ],
 			"skills":
 			[
 				{ "skill" : "estates", "level": "basic" },

+ 93 - 324
lib/CArtHandler.cpp

@@ -78,102 +78,12 @@ bool CArtifact::isBig () const
 	return VLC->arth->isBigArtifact(id);
 }
 
-// /**
-//  * Checks whether the artifact fits at a given slot.
-//  * @param artifWorn A hero's set of worn artifacts.
-//  */
-// bool CArtifact::fitsAt (const std::map<ui16, const CArtifact*> &artifWorn, ui16 slotID) const
-// {
-// 	if (!vstd::contains(possibleSlots, slotID))
-// 		return false;
-//
-// 	// Can't put an artifact in a locked slot.
-// 	std::map<ui16, const CArtifact*>::const_iterator it = artifWorn.find(slotID);
-// 	if (it != artifWorn.end() && it->second->id == 145)
-// 		return false;
-//
-// 	// Check if a combination artifact fits.
-// 	// TODO: Might want a more general algorithm?
-// 	//       Assumes that misc & rings fits only in their slots, and others in only one slot and no duplicates.
-// 	if (constituents != NULL)
-// 	{
-// 		std::map<ui16, const CArtifact*> tempArtifWorn = artifWorn;
-// 		const ui16 ringSlots[] = {6, 7};
-// 		const ui16 miscSlots[] = {9, 10, 11, 12, 18};
-// 		int rings = 0;
-// 		int misc = 0;
-//
-// 		VLC->arth->unequipArtifact(tempArtifWorn, slotID);
-//
-// 		BOOST_FOREACH(ui32 constituentID, *constituents)
-// 		{
-// 			const CArtifact& constituent = *VLC->arth->artifacts[constituentID];
-// 			const int slot = constituent.possibleSlots[0];
-//
-// 			if (slot == 6 || slot == 7)
-// 				rings++;
-// 			else if ((slot >= 9 && slot <= 12) || slot == 18)
-// 				misc++;
-// 			else if (tempArtifWorn.find(slot) != tempArtifWorn.end())
-// 				return false;
-// 		}
-//
-// 		// Ensure enough ring slots are free
-// 		for (int i = 0; i < sizeof(ringSlots)/sizeof(*ringSlots); i++)
-// 		{
-// 			if (tempArtifWorn.find(ringSlots[i]) == tempArtifWorn.end() || ringSlots[i] == slotID)
-// 				rings--;
-// 		}
-// 		if (rings > 0)
-// 			return false;
-//
-// 		// Ensure enough misc slots are free.
-// 		for (int i = 0; i < sizeof(miscSlots)/sizeof(*miscSlots); i++)
-// 		{
-// 			if (tempArtifWorn.find(miscSlots[i]) == tempArtifWorn.end() || miscSlots[i] == slotID)
-// 				misc--;
-// 		}
-// 		if (misc > 0)
-// 			return false;
-// 	}
-//
-// 	return true;
-// }
-
-// bool CArtifact::canBeAssembledTo (const std::map<ui16, const CArtifact*> &artifWorn, ui32 artifactID) const
-// {
-// 	if (constituentOf == NULL || !vstd::contains(*constituentOf, artifactID))
-// 		return false;
-//
-// 	const CArtifact &artifact = *VLC->arth->artifacts[artifactID];
-// 	assert(artifact.constituents);
-//
-// 	BOOST_FOREACH(ui32 constituentID, *artifact.constituents)
-// 	{
-// 		bool found = false;
-// 		for (std::map<ui16, const CArtifact*>::const_iterator it = artifWorn.begin(); it != artifWorn.end(); ++it)
-// 		{
-// 			if (it->second->id == constituentID)
-// 			{
-// 				found = true;
-// 				break;
-// 			}
-// 		}
-// 		if (!found)
-// 			return false;
-// 	}
-//
-// 	return true;
-// }
-
 CArtifact::CArtifact()
 {
 	setNodeType(ARTIFACT);
 	possibleSlots[ArtBearer::HERO]; //we want to generate map entry even if it will be empty
 	possibleSlots[ArtBearer::CREATURE]; //we want to generate map entry even if it will be empty
 	possibleSlots[ArtBearer::COMMANDER];
-	constituents = NULL; //default pointer to zero
-	constituentOf = NULL;
 }
 
 CArtifact::~CArtifact()
@@ -205,21 +115,6 @@ std::string CArtifact::nodeName() const
 {
 	return "Artifact: " + Name();
 }
-// void CArtifact::getParents(TCNodes &out, const CBonusSystemNode *root /*= NULL*/) const
-// {
-// 	//combined artifact carries bonuses from its parts
-// 	if(constituents)
-// 	{
-// 		BOOST_FOREACH(ui32 id, *constituents)
-// 			out.insert(VLC->arth->artifacts[id]);
-// 	}
-// }
-
-// void CScroll::Init()
-// {
-// // 	addNewBonus (Bonus (Bonus::PERMANENT, Bonus::SPELL, Bonus::ARTIFACT, 1, id, spellid, Bonus::INDEPENDENT_MAX));
-// // 	//boost::algorithm::replace_first(description, "[spell name]", VLC->spellh->spells[spellid].name);
-// }
 
 void CArtifact::addNewBonus(Bonus *b)
 {
@@ -229,19 +124,6 @@ void CArtifact::addNewBonus(Bonus *b)
 	CBonusSystemNode::addNewBonus(b);
 }
 
-void CArtifact::setName (std::string desc)
-{
-	name = desc;
-}
-void CArtifact::setDescription (std::string desc)
-{
-	description = desc;
-}
-void CArtifact::setEventText (std::string desc)
-{
-	eventText = desc;
-}
-
 void CGrowingArtifact::levelUpArtifact (CArtifactInstance * art)
 {
 	Bonus b;
@@ -277,20 +159,20 @@ CArtHandler::CArtHandler()
 
 CArtHandler::~CArtHandler()
 {
-	for (std::vector< ConstTransitivePtr<CArtifact> >::iterator it = artifacts.begin(); it != artifacts.end(); ++it)
-	{
-		delete (*it)->constituents;
-		delete (*it)->constituentOf;
-	}
 }
 
 void CArtHandler::loadArtifacts(bool onlyTxt)
 {
-	std::vector<ui16> slots;
-	slots += 17, 16, 15, 14, 13, 18, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0;
+	if (onlyTxt)
+		return; // looks to be broken anyway...
+
+	std::vector<ui16> artSlots;
+	artSlots += 17, 16, 15, 14, 13, 18, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0;
+
 	growingArtifacts += ArtifactID::AXE_OF_SMASHING, ArtifactID::MITHRIL_MAIL,
 		ArtifactID::SWORD_OF_SHARPNESS, ArtifactID::PENDANT_OF_SORCERY, ArtifactID::BOOTS_OF_HASTE,
 		ArtifactID::BOW_OF_SEEKING, ArtifactID::DRAGON_EYE_RING;
+
 	static std::map<char, CArtifact::EartClass> classes =
 	  map_list_of('S',CArtifact::ART_SPECIAL)('T',CArtifact::ART_TREASURE)('N',CArtifact::ART_MINOR)('J',CArtifact::ART_MAJOR)('R',CArtifact::ART_RELIC);
 
@@ -313,32 +195,32 @@ void CArtHandler::loadArtifacts(bool onlyTxt)
 		{
 			art = new CArtifact();
 		}
-		CArtifact &nart = *art;
-		nart.id=i;
-		nart.iconIndex=i;
-		nart.setName (parser.readString());
-		nart.setEventText (events.readString());
+		art->id=i;
+		art->iconIndex=i;
+		art->name = parser.readString();
+		art->eventText = events.readString();
 		events.endLine();
 
-		nart.price= parser.readNumber();
+		art->price= parser.readNumber();
 
-		for(int j=0;j<slots.size();j++)
+		for(int j=0; j<artSlots.size(); j++)
 		{
 			if(parser.readString() == "x")
-				nart.possibleSlots[ArtBearer::HERO].push_back(ArtifactPosition(slots[j]));
+				art->possibleSlots[ArtBearer::HERO].push_back(ArtifactPosition(artSlots[j]));
 		}
-		nart.aClass = classes[parser.readString()[0]];
+		art->aClass = classes[parser.readString()[0]];
 
 		//load description and remove quotation marks
-		nart.setDescription (parser.readString());
+		art->description = parser.readString();
 
 		parser.endLine();
 
-		if(onlyTxt)
+		if(onlyTxt) // FIXME: pointer to art will be lost. Bug?
 			continue;
 
-		artifacts.push_back(&nart);
+		artifacts.push_back(art);
 	}
+
 	if (VLC->modh->modules.COMMANDERS)
 	{ //TODO: move all artifacts config to separate json file
 		const JsonNode config(ResourceID("config/commanders.json"));
@@ -356,65 +238,51 @@ void CArtHandler::loadArtifacts(bool onlyTxt)
 		}
 	}
 
-	sortArts();
 	if(onlyTxt)
 		return;
 
-	addBonuses();
-}
+	JsonNode config(ResourceID("config/artifacts.json"));
 
-void CArtHandler::reverseMapArtifactConstituents() // Populate reverse mappings of combinational artifacts.
-{
-	BOOST_FOREACH(CArtifact *artifact, artifacts)
+	BOOST_FOREACH(auto & node, config["artifacts"].Struct())
 	{
-		if (artifact->constituents != NULL)
-		{
-			BOOST_FOREACH(ui32 constituentID, *artifact->constituents)
-			{
-				if (artifacts[constituentID]->constituentOf == NULL)
-					artifacts[constituentID]->constituentOf = new std::vector<ArtifactID>();
-				artifacts[constituentID]->constituentOf->push_back(artifact->id);
-			}
-		}
+		int numeric = node.second["id"].Float();
+		CArtifact * art = artifacts[numeric];
+
+		loadArtifactJson(art, node.second);
+
+		VLC->modh->identifiers.registerObject ("artifact." + node.first, numeric);
 	}
 }
 
-void CArtHandler::load(const JsonNode & node)
+void CArtHandler::load(std::string objectID, const JsonNode & node)
 {
-	BOOST_FOREACH(auto & entry, node.Struct())
-	{
-		if (!entry.second.isNull()) // may happens if mod removed creature by setting json entry to null
-		{
-			CArtifact * art = loadArtifact(entry.second);
-			art->id = ArtifactID(artifacts.size());
+	CArtifact * art = loadArtifact(node);
+	art->id = ArtifactID(artifacts.size());
 
-			artifacts.push_back(art);
-			tlog5 << "Added artifact: " << entry.first << "\n";
-			VLC->modh->identifiers.registerObject (std::string("artifact.") + entry.first, art->id);
-		}
-	}
+	artifacts.push_back(art);
+	tlog5 << "Added artifact: " << objectID << "\n";
+	VLC->modh->identifiers.registerObject ("artifact." + objectID, art->id);
 }
 
 CArtifact * CArtHandler::loadArtifact(const JsonNode & node)
 {
 	CArtifact * art = new CArtifact;
-	const JsonNode *value;
 
 	const JsonNode & text = node["text"];
-	art->setName		(text["name"].String());
-	art->setDescription	(text["description"].String());
-	art->setEventText		(text["event"].String());
+	art->name        = text["name"].String();
+	art->description = text["description"].String();
+	art->eventText   = text["event"].String();
 
 	const JsonNode & graphics = node["graphics"];
 	art->iconIndex = graphics["iconIndex"].Float();
 	art->image = graphics["image"].String();
-	value = &graphics["large"];
-	if (!value->isNull())
-		art->large = value->String();
+
+	if (!graphics["large"].loadTo(art->large))
+		art->large = art->image;
+
 	art->advMapDef = graphics["map"].String();
 
 	art->price = node["value"].Float();
-
 	{
 		auto it = artifactClassMap.find (node["class"].String());
 		if (it != artifactClassMap.end())
@@ -423,44 +291,14 @@ CArtifact * CArtHandler::loadArtifact(const JsonNode & node)
 		}
 		else
 		{
-			tlog2 << "Warning! Artifact rarity " << value->String() << " not recognized!";
+			tlog2 << "Warning! Artifact rarity " << node["class"].String() << " not recognized!";
 			art->aClass = CArtifact::ART_SPECIAL;
 		}
 	}
 
-	int bearerType = -1;
-	bool heroArt = false;
-
-	{
-		const JsonNode & bearer = node["type"];
-		BOOST_FOREACH (const JsonNode & b, bearer.Vector())
-		{
-			auto it = artifactBearerMap.find (b.String());
-			if (it != artifactBearerMap.end())
-			{
-				bearerType = it->second;
-				switch (bearerType)
-				{
-					case ArtBearer::HERO: //TODO: allow arts having several possible bearers
-						heroArt = true;
-						break;
-					case ArtBearer::COMMANDER:
-						makeItCommanderArt (art, false); //do not erase already existing slots
-						break;
-					case ArtBearer::CREATURE:
-						makeItCreatureArt (art, false);
-						break;
-				}
-			}
-			else
-				tlog2 << "Warning! Artifact type " << b.String() << " not recognized!";
-		}
-	}
-
-	value = &node["slot"];
-	if (!value->isNull() && heroArt) //we assume non-hero slots are irrelevant?
+	if (!node["slot"].isNull()) //we assume non-hero slots are irrelevant?
 	{
-		std::string slotName = value->String();
+		std::string slotName = node["slot"].String();
 		if (slotName == "MISC")
 		{
 			//unfortunatelly slot ids aare not continuous
@@ -480,25 +318,64 @@ CArtifact * CArtHandler::loadArtifact(const JsonNode & node)
 				art->possibleSlots[ArtBearer::HERO].push_back (slot);
 			}
 			else
-				tlog2 << "Warning! Artifact slot " << value->String() << " not recognized!";
+				tlog2 << "Warning! Artifact slot " << node["slot"].String() << " not recognized!";
 		}
 	}
 
-	readComponents (node, art);
+	loadArtifactJson(art, node);
 
-	BOOST_FOREACH (const JsonNode &bonus, node["bonuses"].Vector())
+	return art;
+}
+
+void CArtHandler::loadArtifactJson(CArtifact * art, const JsonNode & artifact)
+{
+	BOOST_FOREACH (auto b, artifact["bonuses"].Vector())
+	{
+		auto bonus = JsonUtils::parseBonus (b);
+		bonus->sid = art->id;
+		art->addNewBonus (bonus);
+	}
+	BOOST_FOREACH (const JsonNode & b, artifact["type"].Vector())
 	{
-		auto b = JsonUtils::parseBonus(bonus);
-		//TODO: bonus->sid = art->id;
-		art->addNewBonus(b);
+		auto it = artifactBearerMap.find (b.String());
+		if (it != artifactBearerMap.end())
+		{
+			int bearerType = it->second;
+			switch (bearerType)
+			{
+				case ArtBearer::HERO://TODO: allow arts having several possible bearers
+					break;
+				case ArtBearer::COMMANDER:
+					makeItCommanderArt (art); //original artifacts should have only one bearer type
+					break;
+				case ArtBearer::CREATURE:
+					makeItCreatureArt (art);
+					break;
+			}
+		}
+		else
+			tlog2 << "Warning! Artifact type " << b.String() << " not recognized!";
 	}
 
-	return art;
+	if (!artifact["components"].isNull())
+	{
+		art->constituents.reset(new std::vector<ArtifactID>());
+		BOOST_FOREACH (auto component, artifact["components"].Vector())
+		{
+			VLC->modh->identifiers.requestIdentifier("artifact." + component.String(), [art](si32 id)
+			{
+				// when this code is called both combinational art as well as component are loaded
+				// so it is safe to access any of them
+				art->addConstituent(ArtifactID(id));
+				VLC->arth->artifacts[id]->constituentOf.push_back(art->id);
+			});
+		}
+	}
 }
 
 void CArtifact::addConstituent (ArtifactID component)
 {
-	assert (constituents);
+	assert (constituents); // not a combinational art
 	constituents->push_back (component);
 }
 
@@ -534,28 +411,6 @@ CreatureID CArtHandler::machineIDToCreature(ArtifactID id)
 	return CreatureID(id + dif);
 }
 
-void CArtHandler::sortArts()
-{
- 	//for (int i=0; i<allowedArtifacts.size(); ++i) //do 144, bo nie chcemy bzdurek
- 	//{
- 	//	switch (allowedArtifacts[i]->aClass)
- 	//	{
- 	//	case CArtifact::ART_TREASURE:
- 	//		treasures.push_back(allowedArtifacts[i]);
- 	//		break;
- 	//	case CArtifact::ART_MINOR:
- 	//		minors.push_back(allowedArtifacts[i]);
- 	//		break;
- 	//	case CArtifact::ART_MAJOR:
- 	//		majors.push_back(allowedArtifacts[i]);
- 	//		break;
- 	//	case CArtifact::ART_RELIC:
- 	//		relics.push_back(allowedArtifacts[i]);
- 	//		break;
- 	//	}
- 	//}
-}
-
 ArtifactID CArtHandler::getRandomArt(int flags)
 {
 	return getArtSync(ran(), flags, true);
@@ -679,84 +534,6 @@ void CArtHandler::makeItCommanderArt( ArtifactID aid, bool onlyCommander /*= tru
 	makeItCommanderArt (a, onlyCommander);
 }
 
-void CArtHandler::addBonuses()
-{
-	const JsonNode config(ResourceID("config/artifacts.json"));
-	BOOST_FOREACH (auto & artifact, config["artifacts"].Struct()) //pair <string, JsonNode> (id, properties)
-	{
-		auto ga = artifacts[artifact.second["id"].Float()].get();
-
-		BOOST_FOREACH (auto b, artifact.second["bonuses"].Vector())
-		{
-			auto bonus = JsonUtils::parseBonus (b);
-			bonus->sid = ga->id;
-			ga->addNewBonus (bonus);
-		}
-		BOOST_FOREACH (const JsonNode & b, artifact.second["type"].Vector()) //TODO: remove duplicate code
-		{
-			auto it = artifactBearerMap.find (b.String());
-			if (it != artifactBearerMap.end())
-			{
-				int bearerType = it->second;
-				switch (bearerType)
-				{
-					case ArtBearer::HERO:
-						break;
-					case ArtBearer::COMMANDER:
-						makeItCommanderArt (ga); //original artifacts should have only one bearer type
-						break;
-					case ArtBearer::CREATURE:
-						makeItCreatureArt (ga);
-						break;
-				}
-			}
-			else
-				tlog2 << "Warning! Artifact type " << b.String() << " not recognized!";
-		}
-		readComponents (artifact.second, ga);
-
-		VLC->modh->identifiers.registerObject ("artifact." + artifact.first, ga->id);
-	}
-}
-
-void CArtHandler::readComponents (const JsonNode & node, CArtifact * art)
-{
-	const JsonNode *value;
-	value = &node["components"];
-	if (!value->isNull())
-	{
-		art->constituents = new std::vector<ArtifactID>();
-		BOOST_FOREACH (auto component, value->Vector())
-		{
-			VLC->modh->identifiers.requestIdentifier(std::string("artifact.") + component.String(),
-				[art](si32 id)
-				{
-					art->addConstituent(ArtifactID(id));
-				}
-			);
-		}
-	}
-
-}
-
-void CArtHandler::clear()
-{
-	BOOST_FOREACH(CArtifact *art, artifacts)
-		delete art;
-	artifacts.clear();
-
-	clearHlpLists();
-
-}
-
-void CArtHandler::clearHlpLists()
-{
-	treasures.clear();
-	minors.clear();
-	majors.clear();
-	relics.clear();
-}
-
 bool CArtHandler::legalArtifact(ArtifactID id)
 {
 	auto art = artifacts[id];
@@ -770,7 +547,6 @@ bool CArtHandler::legalArtifact(ArtifactID id)
 void CArtHandler::initAllowedArtifactsList(const std::vector<bool> &allowed)
 {
 	allowedArtifacts.clear();
-	clearHlpLists();
 	for (ArtifactID i=ArtifactID::SPELLBOOK; i<ArtifactID::ART_SELECTION; i.advance(1))
 	{
 		if (allowed[i] && legalArtifact(i))
@@ -860,12 +636,6 @@ CArtifactInstance::CArtifactInstance( CArtifact *Art)
 	setType(Art);
 }
 
-// CArtifactInstance::CArtifactInstance(int aid)
-// {
-// 	init();
-// 	setType(VLC->arth->artifacts[aid]);
-// }
-
 void CArtifactInstance::setType( CArtifact *Art )
 {
 	artType = Art;
@@ -966,17 +736,16 @@ void CArtifactInstance::removeFrom(ArtifactLocation al)
 
 bool CArtifactInstance::canBeDisassembled() const
 {
-	return artType->constituents && artType->constituentOf->size();
+	return artType->constituents != nullptr;
 }
 
 std::vector<const CArtifact *> CArtifactInstance::assemblyPossibilities(const CArtifactSet *h) const
 {
 	std::vector<const CArtifact *> ret;
-	if(!artType->constituentOf //not a part of combined artifact
-		|| artType->constituents) //combined artifact already: no combining of combined artifacts... for now.
+	if(artType->constituents) //combined artifact already: no combining of combined artifacts... for now.
 		return ret;
 
-	BOOST_FOREACH(ui32 possibleCombinedArt, *artType->constituentOf)
+	BOOST_FOREACH(ui32 possibleCombinedArt, artType->constituentOf)
 	{
 		const CArtifact * const artifact = VLC->arth->artifacts[possibleCombinedArt];
 		assert(artifact->constituents);

+ 13 - 19
lib/CArtHandler.h

@@ -15,6 +15,8 @@
  * Full text of license available in license.txt file, in main folder
  *
  */
+
+class CArtHandler;
 class CDefHandler;
 class CArtifact;
 class CGHeroInstance;
@@ -53,22 +55,20 @@ public:
 	const std::string &Name() const; //getter
 	const std::string &Description() const; //getter
 	const std::string &EventText() const;
+
 	bool isBig () const;
-	void setName (std::string desc);
-	void setDescription (std::string desc);
-	void setEventText (std::string desc);
 	void addConstituent (ArtifactID component);
 
 	int getArtClassSerial() const; //0 - treasure, 1 - minor, 2 - major, 3 - relic, 4 - spell scroll, 5 - other
-	std::string nodeName() const OVERRIDE;
-	void addNewBonus(Bonus *b) OVERRIDE;
+	std::string nodeName() const override;
+	void addNewBonus(Bonus *b) override;
 
 	virtual void levelUpArtifact (CArtifactInstance * art){};
 
 	ui32 price;
 	bmap<ArtBearer::ArtBearer, std::vector<ArtifactPosition> > possibleSlots; //Bearer Type => ids of slots where artifact can be placed
-	std::vector<ArtifactID> * constituents; // Artifacts IDs a combined artifact consists of, or NULL.
-	std::vector<ArtifactID> * constituentOf; // Reverse map of constituents.
+	std::unique_ptr<std::vector<ArtifactID> > constituents; // Artifacts IDs a combined artifact consists of, or NULL.
+	std::vector<ArtifactID> constituentOf; // Reverse map of constituents - combined arts that include this art
 	EartClass aClass;
 	ArtifactID id;
 
@@ -82,8 +82,7 @@ public:
 	CArtifact();
 	~CArtifact();
 
-	//override
-	//void getParents(TCNodes &out, const CBonusSystemNode *root = NULL) const;
+	friend class CArtHandler;
 };
 
 class DLL_LINKAGE CGrowingArtifact : public CArtifact //for example commander artifacts getting bonuses after battle
@@ -201,20 +200,15 @@ public:
 	std::set<ArtifactID> growingArtifacts;
 
 	void loadArtifacts(bool onlyTxt);
-	/// load all artifacts from json structure
-	void load(const JsonNode & node);
+	/// load artifact from json structure
+	void load(std::string objectID, const JsonNode & node);
 	/// load one artifact from json config
 	CArtifact * loadArtifact(const JsonNode & node);
-	///read (optional) components of combined artifact
-	void readComponents (const JsonNode & node, CArtifact * art);
-	void reverseMapArtifactConstituents ();
 
-	void sortArts();
-	void addBonuses();
-	void clear();
-	void clearHlpLists();
+	void loadArtifactJson(CArtifact * art, const JsonNode & node);
+
+	void addBonuses(CArtifact *art, const JsonNode &bonusList);
 
-	//void restockArtifactList()''
 	void fillList(std::vector<CArtifact*> &listToBeFilled, CArtifact::EartClass artifactClass); //fills given empty list with allowed artifacts of gibven class. No side effects
 
 	boost::optional<std::vector<CArtifact*>&> listFromClass(CArtifact::EartClass artifactClass);

+ 148 - 109
lib/CCreatureHandler.cpp

@@ -208,6 +208,50 @@ static void RemoveAbility(CCreature *cre, const JsonNode &ability)
 	cre->removeBonus(b);
 }
 
+void CCreatureHandler::loadBonuses(CCreature & ncre, std::string bonuses)
+{
+	static const std::map<std::string,Bonus::BonusType> abilityMap =
+	  boost::assign::map_list_of
+	    ("FLYING_ARMY", Bonus::FLYING)
+	    ("SHOOTING_ARMY", Bonus::SHOOTER)
+	    ("SIEGE_WEAPON", Bonus::SIEGE_WEAPON)
+	    ("const_free_attack", Bonus::BLOCKS_RETALIATION)
+	    ("IS_UNDEAD", Bonus::UNDEAD)
+	    ("const_no_melee_penalty",Bonus::NO_MELEE_PENALTY)
+	    ("const_jousting",Bonus::JOUSTING)
+	    ("KING_1",Bonus::KING1)
+	    ("KING_2",Bonus::KING2)
+		("KING_3",Bonus::KING3)
+		("const_no_wall_penalty",Bonus::NO_WALL_PENALTY)
+		("CATAPULT",Bonus::CATAPULT)
+		("MULTI_HEADED",Bonus::ATTACKS_ALL_ADJACENT)
+		("IMMUNE_TO_MIND_SPELLS",Bonus::MIND_IMMUNITY)
+		("IMMUNE_TO_FIRE_SPELLS",Bonus::FIRE_IMMUNITY)
+		("HAS_EXTENDED_ATTACK",Bonus::TWO_HEX_ATTACK_BREATH);
+
+	auto hasAbility = [&](const std::string name) -> bool
+	{
+		return boost::algorithm::find_first(bonuses, name);
+	};
+	BOOST_FOREACH(auto a, abilityMap)
+	{
+		if(hasAbility(a.first))
+			ncre.addBonus(0, a.second);
+	}
+	if(hasAbility("DOUBLE_WIDE"))
+		ncre.doubleWide = true;
+	if(hasAbility("const_raises_morale"))
+	{
+		ncre.addBonus(+1, Bonus::MORALE);;
+		ncre.getBonusList().back()->addPropagator(make_shared<CPropagatorNodeType>(CBonusSystemNode::HERO));
+	}
+	if(hasAbility("const_lowers_morale"))
+	{
+		ncre.addBonus(-1, Bonus::MORALE);;
+		ncre.getBonusList().back()->effectRange = Bonus::ONLY_ENEMY_ARMY;
+	}
+}
+
 void CCreatureHandler::loadCreatures()
 {
 	tlog5 << "\t\tReading ZCRTRAIT.TXT" << std::endl;
@@ -255,8 +299,8 @@ void CCreatureHandler::loadCreatures()
 		ncre.ammMin = parser.readNumber();
 		ncre.ammMax = parser.readNumber();
 
-		ncre.abilityText = parser.readString();
-		ncre.abilityRefs = parser.readString();
+		std::string abilities = parser.readString();
+		loadBonuses(ncre, parser.readString());
 
 		{ //adding abilities from ZCRTRAIT.TXT
 			static const std::map < std::string,Bonus::BonusType> abilityMap = boost::assign::map_list_of
@@ -277,11 +321,11 @@ void CCreatureHandler::loadCreatures()
 				("IMMUNE_TO_FIRE_SPELLS",Bonus::FIRE_IMMUNITY)
 				("IMMUNE_TO_FIRE_SPELLS",Bonus::FIRE_IMMUNITY)
 				("HAS_EXTENDED_ATTACK",Bonus::TWO_HEX_ATTACK_BREATH);
-				
-			auto hasAbility = [&ncre](const std::string name) -> bool
+
+			auto hasAbility = [&](const std::string name) -> bool
 			{
-				return boost::algorithm::find_first(ncre.abilityRefs,name);
-			};			
+				return boost::algorithm::find_first(abilities, name);
+			};
 			BOOST_FOREACH(auto a, abilityMap)
 			{
 				if(hasAbility(a.first))
@@ -315,18 +359,6 @@ void CCreatureHandler::loadCreatures()
 		int creatureID = node.second["id"].Float();
 		CCreature *c = creatures[creatureID];
 
-		BOOST_FOREACH(const JsonNode &ability, node.second["ability_remove"].Vector())
-		{
-			RemoveAbility(c, ability);
-		}
-		BOOST_FOREACH(const JsonNode &ability, node.second["abilities"].Vector())
-		{
-			if (ability.getType() == JsonNode::DATA_VECTOR)
-				AddAbility(c, ability.Vector());
-			else
-				c->addNewBonus(JsonUtils::parseBonus(ability));
-		}
-
 		loadCreatureJson(c, node.second);
 
 		// Main reference name, e.g. royalGriffin
@@ -508,47 +540,41 @@ void CCreatureHandler::loadAnimationInfo()
 
 void CCreatureHandler::loadUnitAnimInfo(CCreature & unit, CLegacyConfigParser & parser)
 {
-	unit.timeBetweenFidgets = parser.readNumber();
-	unit.walkAnimationTime = parser.readNumber();
-	unit.attackAnimationTime = parser.readNumber();
-	unit.flightAnimationDistance = parser.readNumber();
+	unit.animation.timeBetweenFidgets = parser.readNumber();
+	unit.animation.walkAnimationTime = parser.readNumber();
+	unit.animation.attackAnimationTime = parser.readNumber();
+	unit.animation.flightAnimationDistance = parser.readNumber();
 	///////////////////////
 
-	unit.upperRightMissleOffsetX = parser.readNumber();
-	unit.upperRightMissleOffsetY = parser.readNumber();
-	unit.rightMissleOffsetX = parser.readNumber();
-	unit.rightMissleOffsetY = parser.readNumber();
-	unit.lowerRightMissleOffsetX = parser.readNumber();
-	unit.lowerRightMissleOffsetY = parser.readNumber();
+	unit.animation.upperRightMissleOffsetX = parser.readNumber();
+	unit.animation.upperRightMissleOffsetY = parser.readNumber();
+	unit.animation.rightMissleOffsetX = parser.readNumber();
+	unit.animation.rightMissleOffsetY = parser.readNumber();
+	unit.animation.lowerRightMissleOffsetX = parser.readNumber();
+	unit.animation.lowerRightMissleOffsetY = parser.readNumber();
 
 	///////////////////////
 
 	for(int jjj=0; jjj<12; ++jjj)
 	{
-		unit.missleFrameAngles[jjj] = parser.readNumber();
+		unit.animation.missleFrameAngles[jjj] = parser.readNumber();
 	}
 
-	unit.troopCountLocationOffset= parser.readNumber();
-	unit.attackClimaxFrame = parser.readNumber();
+	unit.animation.troopCountLocationOffset= parser.readNumber();
+	unit.animation.attackClimaxFrame = parser.readNumber();
 
 	parser.endLine();
 }
 
-void CCreatureHandler::load(const JsonNode & node)
+void CCreatureHandler::load(std::string creatureID, const JsonNode & node)
 {
-	BOOST_FOREACH(auto & entry, node.Struct())
-	{
-		if (!entry.second.isNull()) // may happens if mod removed creature by setting json entry to null
-		{
-			CCreature * creature = loadCreature(entry.second);
-			creature->nameRef = entry.first;
-			creature->idNumber = CreatureID(creatures.size());
+	CCreature * creature = loadCreature(node);
+	creature->nameRef = creatureID;
+	creature->idNumber = CreatureID(creatures.size());
 
-			creatures.push_back(creature);
-			tlog5 << "Added creature: " << entry.first << "\n";
-			registerCreature(creature->nameRef,creature->idNumber);
-		}
-	}
+	creatures.push_back(creature);
+	tlog5 << "Added creature: " << creatureID << "\n";
+	registerCreature(creature->nameRef, creature->idNumber);
 }
 
 CCreature * CCreatureHandler::loadCreature(const JsonNode & node)
@@ -586,74 +612,31 @@ CCreature * CCreatureHandler::loadCreature(const JsonNode & node)
 
 	cre->doubleWide = node["doubleWide"].Bool();
 
-	BOOST_FOREACH (const JsonNode &bonus, node["abilities"].Vector())
-	{
-		auto b = JsonUtils::parseBonus(bonus);
-		b->source = Bonus::CREATURE_ABILITY;
-		b->duration = Bonus::PERMANENT;
-		cre->addNewBonus(b);
-	}
-
-	BOOST_FOREACH (const JsonNode &exp, node["stackExperience"].Vector())
-	{
-		auto bonus = JsonUtils::parseBonus (exp["bonus"]);
-		bonus->source = Bonus::STACK_EXPERIENCE;
-		bonus->duration = Bonus::PERMANENT;
-		const JsonVector &values = exp["values"].Vector();
-		int lowerLimit = 1;//, upperLimit = 255;
-		if (values[0].getType() == JsonNode::JsonType::DATA_BOOL)
-		{
-			BOOST_FOREACH (const JsonNode &val, values)
-			{
-				if (val.Bool() == true)
-				{
-					bonus->limiter = make_shared<RankRangeLimiter>(RankRangeLimiter(lowerLimit));
-					cre->addNewBonus (new Bonus(*bonus)); //bonuses must be unique objects
-					break; //TODO: allow bonuses to turn off?
-				}
-				++lowerLimit;
-			}
-		}
-		else
-		{
-			int lastVal = 0;
-			BOOST_FOREACH (const JsonNode &val, values)
-			{
-				if (val.Float() != lastVal)
-				{
-					bonus->val = val.Float() - lastVal;
-					bonus->limiter.reset (new RankRangeLimiter(lowerLimit));
-					cre->addNewBonus (new Bonus(*bonus));
-				}
-				lastVal = val.Float();
-				++lowerLimit;
-			}
-		}
-	}
 	//graphics
+	loadStackExperience(cre, node["stackExperience"]);
 
 	const JsonNode & graphics = node["graphics"];
-	cre->timeBetweenFidgets = graphics["timeBetweenFidgets"].Float();
-	cre->troopCountLocationOffset = graphics["troopCountLocationOffset"].Float();
-	cre->attackClimaxFrame = graphics["attackClimaxFrame"].Float();
+	cre->animation.timeBetweenFidgets = graphics["timeBetweenFidgets"].Float();
+	cre->animation.troopCountLocationOffset = graphics["troopCountLocationOffset"].Float();
+	cre->animation.attackClimaxFrame = graphics["attackClimaxFrame"].Float();
 
 	const JsonNode & animationTime = graphics["animationTime"];
-	cre->walkAnimationTime = animationTime["walk"].Float();
-	cre->attackAnimationTime = animationTime["attack"].Float();
-	cre->flightAnimationDistance = animationTime["flight"].Float(); //?
+	cre->animation.walkAnimationTime = animationTime["walk"].Float();
+	cre->animation.attackAnimationTime = animationTime["attack"].Float();
+	cre->animation.flightAnimationDistance = animationTime["flight"].Float(); //?
 
 	const JsonNode & missile = graphics["missile"];
 	const JsonNode & offsets = missile["offset"];
-	cre->upperRightMissleOffsetX = offsets["upperX"].Float();
-	cre->upperRightMissleOffsetY = offsets["upperY"].Float();
-	cre->rightMissleOffsetX = offsets["middleX"].Float();
-	cre->rightMissleOffsetY = offsets["middleY"].Float();
-	cre->lowerRightMissleOffsetX = offsets["lowerX"].Float();
-	cre->lowerRightMissleOffsetY = offsets["lowerY"].Float();
+	cre->animation.upperRightMissleOffsetX = offsets["upperX"].Float();
+	cre->animation.upperRightMissleOffsetY = offsets["upperY"].Float();
+	cre->animation.rightMissleOffsetX = offsets["middleX"].Float();
+	cre->animation.rightMissleOffsetY = offsets["middleY"].Float();
+	cre->animation.lowerRightMissleOffsetX = offsets["lowerX"].Float();
+	cre->animation.lowerRightMissleOffsetY = offsets["lowerY"].Float();
 	int i = 0;
 	BOOST_FOREACH (auto & angle, missile["frameAngles"].Vector())
 	{
-		cre->missleFrameAngles[i++] = angle.Float();
+		cre->animation.missleFrameAngles[i++] = angle.Float();
 	}
 	cre->advMapDef = graphics["map"].String();
 	cre->iconIndex = graphics["iconIndex"].Float();
@@ -667,6 +650,24 @@ void CCreatureHandler::loadCreatureJson(CCreature * creature, const JsonNode & c
 	creature->level = config["level"].Float();
 	creature->animDefName = config["graphics"]["animation"].String();
 
+	BOOST_FOREACH(const JsonNode &ability, config["ability_remove"].Vector())
+	{
+		RemoveAbility(creature, ability);
+	}
+
+	BOOST_FOREACH(const JsonNode &ability, config["abilities"].Vector())
+	{
+		if (ability.getType() == JsonNode::DATA_VECTOR)
+			AddAbility(creature, ability.Vector()); // used only for H3 creatures
+		else
+		{
+			auto b = JsonUtils::parseBonus(ability);
+			b->source = Bonus::CREATURE_ABILITY;
+			b->duration = Bonus::PERMANENT;
+			creature->addNewBonus(b);
+		}
+	}
+
 	VLC->modh->identifiers.requestIdentifier(std::string("faction.") + config["faction"].String(), [=](si32 faction)
 	{
 		creature->faction = faction;
@@ -683,12 +684,10 @@ void CCreatureHandler::loadCreatureJson(CCreature * creature, const JsonNode & c
 	if(config["hasDoubleWeek"].Bool())
 		doubledCreatures.insert(creature->idNumber);
 
-	creature->projectile = config["graphics"]["missile"]["projectile"].String();
-	creature->projectileSpin = config["graphics"]["missile"]["spinning"].Bool();
+	creature->animation.projectileImageName = config["graphics"]["missile"]["projectile"].String();
+	creature->animation.projectileSpin = config["graphics"]["missile"]["spinning"].Bool();
 
 	creature->special = config["special"].Bool();
-	if ( creature->special )
-		notUsedMonsters.insert(creature->idNumber);
 
 	const JsonNode & sounds = config["sound"];
 
@@ -706,6 +705,46 @@ void CCreatureHandler::loadCreatureJson(CCreature * creature, const JsonNode & c
 #undef GET_SOUND_VALUE
 }
 
+void CCreatureHandler::loadStackExperience(CCreature * creature, const JsonNode & input)
+{
+	BOOST_FOREACH (const JsonNode &exp, input.Vector())
+	{
+		auto bonus = JsonUtils::parseBonus (exp["bonus"]);
+		bonus->source = Bonus::STACK_EXPERIENCE;
+		bonus->duration = Bonus::PERMANENT;
+		const JsonVector &values = exp["values"].Vector();
+		int lowerLimit = 1;//, upperLimit = 255;
+		if (values[0].getType() == JsonNode::JsonType::DATA_BOOL)
+		{
+			BOOST_FOREACH (const JsonNode &val, values)
+			{
+				if (val.Bool() == true)
+				{
+					bonus->limiter = make_shared<RankRangeLimiter>(RankRangeLimiter(lowerLimit));
+					creature->addNewBonus (new Bonus(*bonus)); //bonuses must be unique objects
+					break; //TODO: allow bonuses to turn off?
+				}
+				++lowerLimit;
+			}
+		}
+		else
+		{
+			int lastVal = 0;
+			BOOST_FOREACH (const JsonNode &val, values)
+			{
+				if (val.Float() != lastVal)
+				{
+					bonus->val = val.Float() - lastVal;
+					bonus->limiter.reset (new RankRangeLimiter(lowerLimit));
+					creature->addNewBonus (new Bonus(*bonus));
+				}
+				lastVal = val.Float();
+				++lowerLimit;
+			}
+		}
+	}
+}
+
 void CCreatureHandler::loadStackExp(Bonus & b, BonusList & bl, CLegacyConfigParser & parser) //help function for parsing CREXPBON.txt
 {
 	bool enable = false; //some bonuses are activated with values 2 or 1
@@ -1022,7 +1061,7 @@ CreatureID CCreatureHandler::pickRandomMonster(const boost::function<int()> &ran
 		do
 		{
 			r = vstd::pickRandomElementOf(creatures, randGen)->idNumber;
-		} while (vstd::contains(VLC->creh->notUsedMonsters,r));
+		} while (VLC->creh->creatures[r] && VLC->creh->creatures[r]->special); // find first "not special" creature
 	}
 	else
 	{
@@ -1031,9 +1070,9 @@ CreatureID CCreatureHandler::pickRandomMonster(const boost::function<int()> &ran
 		BOOST_FOREACH(const CBonusSystemNode *b, creaturesOfLevel[tier].getChildrenNodes())
 		{
 			assert(b->getNodeType() == CBonusSystemNode::CREATURE);
-			CreatureID creid = static_cast<const CCreature*>(b)->idNumber;
-			if(!vstd::contains(notUsedMonsters, creid))
-				allowed.push_back(creid);
+			const CCreature * crea = dynamic_cast<const CCreature*>(b);
+			if(crea && !crea->special)
+				allowed.push_back(crea->idNumber);
 		}
 
 		if(!allowed.size())

+ 51 - 37
lib/CCreatureHandler.h

@@ -20,40 +20,58 @@
 class CLegacyConfigParser;
 class CCreatureHandler;
 class CCreature;
-struct CreaturesBattleSounds;
 
 class DLL_LINKAGE CCreature : public CBonusSystemNode
 {
 public:
-	std::string namePl, nameSing, nameRef; //name in singular and plural form; and reference name
-	TResources cost; //cost[res_id] - amount of that resource
-	std::set<std::string> upgradeNames; //for reference, they are later transformed info ui32 upgrades
-	std::set<CreatureID> upgrades; // IDs of creatures to which this creature can be upgraded
-	//damage, hp. etc are handled by Bonuses
-	ui32 fightValue, AIValue, growth, hordeGrowth;
-	ui32 ammMin, ammMax;
+	std::string nameRef; // reference name, stringID
+	std::string nameSing;// singular name, e.g. Centaur
+	std::string namePl;  // plural name, e.g. Centaurs
+
 	std::string abilityText; //description of abilities
-	std::string abilityRefs; //references to abilities, in text format
-	std::string animDefName;
-	std::string advMapDef; //for new creatures only
+
 	CreatureID idNumber;
-	si32 iconIndex; // index of icon in files like twcrport
-	TFaction faction; //-1 = neutral
+	TFaction faction;
 	ui8 level; // 0 - unknown
+
+	//stats that are not handled by bonus system
+	ui32 fightValue, AIValue, growth, hordeGrowth;
+	ui32 ammMin, ammMax; // initial size of stack of these creatures on adventure map (if not set in editor)
+
 	bool doubleWide;
-	bool special; // Creature is not available normally (war machines, commanders, etc
+	bool special; // Creature is not available normally (war machines, commanders, several unused creatures, etc
+
+	TResources cost; //cost[res_id] - amount of that resource required to buy creature from dwelling
+	std::set<CreatureID> upgrades; // IDs of creatures to which this creature can be upgraded
 
-	///animation info
-	double timeBetweenFidgets, walkAnimationTime, attackAnimationTime, flightAnimationDistance;
-	int upperRightMissleOffsetX, rightMissleOffsetX, lowerRightMissleOffsetX, upperRightMissleOffsetY, rightMissleOffsetY, lowerRightMissleOffsetY;
-	double missleFrameAngles[12];
-	int troopCountLocationOffset, attackClimaxFrame;
-	std::string projectile;
-	bool projectileSpin; //if true, appropriate projectile is spinning during flight
-	///end of anim info
+	std::string animDefName; // creature animation used during battles
+	std::string advMapDef; //for new creatures only, image for adventure map
+	si32 iconIndex; // index of icon in files like twcrport
+
+	struct CreatureAnimation
+	{
+		double timeBetweenFidgets, walkAnimationTime, attackAnimationTime, flightAnimationDistance;
+		int upperRightMissleOffsetX, rightMissleOffsetX, lowerRightMissleOffsetX,
+		    upperRightMissleOffsetY, rightMissleOffsetY, lowerRightMissleOffsetY;
+
+		double missleFrameAngles[12];
+		int troopCountLocationOffset, attackClimaxFrame;
+
+		std::string projectileImageName;
+		bool projectileSpin; //if true, appropriate projectile is spinning during flight
+
+		template <typename Handler> void serialize(Handler &h, const int version)
+		{
+			h & timeBetweenFidgets & walkAnimationTime & attackAnimationTime & flightAnimationDistance;
+			h & upperRightMissleOffsetX & rightMissleOffsetX & lowerRightMissleOffsetX;
+			h & upperRightMissleOffsetY & rightMissleOffsetY & lowerRightMissleOffsetY;
+			h & missleFrameAngles & troopCountLocationOffset & attackClimaxFrame;
+			h & projectileImageName & projectileSpin;
+		}
+	} animation;
 
 	//sound info
-	struct CreaturesBattleSounds
+	struct CreatureBattleSounds
 	{
 		std::string attack;
 		std::string defend;
@@ -106,21 +124,15 @@ public:
 			& cost & upgrades
 			& fightValue & AIValue & growth & hordeGrowth
 			& ammMin & ammMax & level
-			& abilityText & abilityRefs & animDefName & advMapDef;
+			& abilityText & animDefName & advMapDef;
 		h & iconIndex;
 
-		h & idNumber & faction
-			& timeBetweenFidgets & walkAnimationTime & attackAnimationTime & flightAnimationDistance
-			& upperRightMissleOffsetX & rightMissleOffsetX & lowerRightMissleOffsetX & upperRightMissleOffsetY & rightMissleOffsetY & lowerRightMissleOffsetY
-			& missleFrameAngles & troopCountLocationOffset & attackClimaxFrame;
-		h & sounds & projectile & projectileSpin;
+		h & idNumber & faction & sounds & animation;
 
-		h & doubleWide;
+		h & doubleWide & special;
 	}
 
-
 	CCreature();
-	friend class CCreatureHandler;
 };
 
 class DLL_LINKAGE CCreatureHandler
@@ -129,11 +141,11 @@ private:
 	CBonusSystemNode allCreatures;
 	CBonusSystemNode creaturesOfLevel[GameConstants::CREATURES_PER_TOWN + 1];//index 0 is used for creatures of unknown tier or outside <1-7> range
 
+	void loadStackExperience(CCreature * creature, const JsonNode &input);
 	void loadCreatureJson(CCreature * creature, const JsonNode & config);
 public:
-	std::set<CreatureID> notUsedMonsters;
 	std::set<CreatureID> doubledCreatures; //they get double week
-	std::vector<ConstTransitivePtr<CCreature> > creatures; //creature ID -> creature info
+	std::vector<ConstTransitivePtr<CCreature> > creatures; //creature ID -> creature info.
 
 	//stack exp
 	std::map<Bonus::BonusType, std::pair<std::string, std::string> > stackBonuses; // bonus => name, description
@@ -148,10 +160,12 @@ public:
 
 	/// loading functions
 
+	/// adding abilities from ZCRTRAIT.TXT
+	void loadBonuses(CCreature & creature, std::string bonuses);
 	/// load all creatures from H3 files
 	void loadCreatures();
-	/// load all creatures from json structure
-	void load(const JsonNode & node);
+	/// load creature from json structure
+	void load(std::string creatureID, const JsonNode & node);
 	/// load one creature from json config
 	CCreature * loadCreature(const JsonNode & node);
 	/// generates tier-specific bonus tree entries
@@ -176,7 +190,7 @@ public:
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
 		//TODO: should be optimized, not all these informations needs to be serialized (same for ccreature)
-		h & notUsedMonsters & doubledCreatures & creatures;
+		h & doubledCreatures & creatures;
 		h & stackBonuses & expRanks & maxExpPerBattle & expAfterUpgrade;
 		h & skillLevels & skillRequirements & commanderLevelPremy;
 		h & allCreatures;

+ 1 - 1
lib/CGameState.cpp

@@ -2478,7 +2478,7 @@ void CGameState::obtainPlayersStats(SThievesGuildInfo & tgi, int level)
 	}
 	if(level >= 4) //obelisks found
 	{
-		//TODO: obtainPlayersStats - obelisks found
+		FILL_FIELD(obelisks, CGObelisk::visited[gs->getPlayerTeam(g->second.color)->id])
 	}
 	if(level >= 5) //artifacts
 	{

+ 42 - 49
lib/CHeroHandler.cpp

@@ -131,21 +131,15 @@ void CHeroClassHandler::load()
 	}
 }
 
-void CHeroClassHandler::load(const JsonNode & classes)
+void CHeroClassHandler::load(std::string objectID, const JsonNode & input)
 {
-	BOOST_FOREACH(auto & entry, classes.Struct())
-	{
-		if (!entry.second.isNull()) // may happens if mod removed creature by setting json entry to null
-		{
-			CHeroClass * heroClass = loadClass(entry.second);
-			heroClass->identifier = entry.first;
-			heroClass->id = heroClasses.size();
+	CHeroClass * heroClass = loadClass(input);
+	heroClass->identifier = objectID;
+	heroClass->id = heroClasses.size();
 
-			heroClasses.push_back(heroClass);
-			tlog5 << "Added hero class: " << entry.first << "\n";
-			VLC->modh->identifiers.registerObject("heroClass." + heroClass->identifier, heroClass->id);
-		}
-	}
+	heroClasses.push_back(heroClass);
+	tlog5 << "Added hero class: " << objectID << "\n";
+	VLC->modh->identifiers.registerObject("heroClass." + heroClass->identifier, heroClass->id);
 }
 
 CHeroClass *CHeroClassHandler::loadClass(const JsonNode & node)
@@ -209,20 +203,14 @@ CHeroHandler::CHeroHandler()
 {
 }
 
-void CHeroHandler::load(const JsonNode & input)
+void CHeroHandler::load(std::string objectID, const JsonNode & input)
 {
-	BOOST_FOREACH(auto & entry, input.Struct())
-	{
-		if (!entry.second.isNull()) // may happens if mod removed creature by setting json entry to null
-		{
-			CHero * hero = loadHero(entry.second);
-			hero->ID = heroes.size();
+	CHero * hero = loadHero(input);
+	hero->ID = heroes.size();
 
-			heroes.push_back(hero);
-			tlog5 << "Added hero: " << entry.first << "\n";
-			VLC->modh->identifiers.registerObject("hero." + entry.first, hero->ID);
-		}
-	}
+	heroes.push_back(hero);
+	tlog5 << "Added hero: " << objectID << "\n";
+	VLC->modh->identifiers.registerObject("hero." + objectID, hero->ID);
 }
 
 CHero * CHeroHandler::loadHero(const JsonNode & node)
@@ -278,9 +266,23 @@ void CHeroHandler::loadHeroJson(CHero * hero, const JsonNode & node)
 		hero->secSkillsInit.push_back(std::make_pair(skillID, skillLevel));
 	}
 
+	// spellbook is considered present if hero have "spellbook" entry even when this is an empty set (0 spells)
+	hero->haveSpellBook = node["spellbook"].isNull();
+
 	BOOST_FOREACH(const JsonNode & spell, node["spellbook"].Vector())
 	{
-		hero->spells.insert(SpellID(spell.Float()));
+		if (spell.getType() == JsonNode::DATA_FLOAT) // for compatibility
+		{
+			hero->spells.insert(SpellID(spell.Float()));
+		}
+		else
+		{
+			VLC->modh->identifiers.requestIdentifier("spell." + spell.String(),
+			[=](si32 spellID)
+			{
+				hero->spells.insert(SpellID(spellID));
+			});
+		}
 	}
 
 	//deprecated, used only for original spciealties
@@ -317,13 +319,14 @@ void CHeroHandler::loadHeroJson(CHero * hero, const JsonNode & node)
 
 void CHeroHandler::load()
 {
+	VLC->heroh = this;
+
 	for (int i = 0; i < GameConstants::SKILL_QUANTITY; ++i)
 	{
 		VLC->modh->identifiers.registerObject("skill." + NSecondarySkill::names[i], i);
 	}
 	classes.load();
 	loadHeroes();
-	loadHeroTexts();
 	loadObstacles();
 	loadTerrains();
 	loadBallistics();
@@ -382,17 +385,26 @@ void CHeroHandler::loadObstacles()
 
 void CHeroHandler::loadHeroes()
 {
-	VLC->heroh = this;
+	CLegacyConfigParser specParser("DATA/HEROSPEC.TXT");
+	CLegacyConfigParser bioParser("DATA/HEROBIOS.TXT");
 	CLegacyConfigParser parser("DATA/HOTRAITS.TXT");
 
 	parser.endLine(); //ignore header
 	parser.endLine();
 
+	specParser.endLine(); //ignore header
+	specParser.endLine();
+
 	for (int i=0; i<GameConstants::HEROES_QUANTITY; i++)
 	{
 		CHero * hero = new CHero;
 		hero->name = parser.readString();
 
+		hero->specName    = specParser.readString();
+		hero->specTooltip = specParser.readString();
+		hero->specDescr   = specParser.readString();
+		hero->biography   = bioParser.readString();
+
 		hero->initialArmy.resize(3);
 		for(int x=0;x<3;x++)
 		{
@@ -408,6 +420,8 @@ void CHeroHandler::loadHeroes()
 			});
 		}
 		parser.endLine();
+		specParser.endLine();
+		bioParser.endLine();
 
 		hero->ID = heroes.size();
 		hero->imageIndex = hero->ID;
@@ -422,27 +436,6 @@ void CHeroHandler::loadHeroes()
 	}
 }
 
-void CHeroHandler::loadHeroTexts()
-{
-	CLegacyConfigParser parser("DATA/HEROSPEC.TXT");
-	CLegacyConfigParser bioParser("DATA/HEROBIOS.TXT");
-
-	//skip header
-	parser.endLine();
-	parser.endLine();
-
-	int i=0;
-	do
-	{
-		CHero * hero = heroes[i++];
-		hero->specName    = parser.readString();
-		hero->specTooltip = parser.readString();
-		hero->specDescr   = parser.readString();
-		hero->biography   = bioParser.readString();
-	}
-	while (parser.endLine() && bioParser.endLine() && heroes.size() > i);
-}
-
 void CHeroHandler::loadBallistics()
 {
 	CLegacyConfigParser ballParser("DATA/BALLIST.TXT");

+ 5 - 5
lib/CHeroHandler.h

@@ -68,8 +68,9 @@ public:
 	std::vector<SSpecialtyInfo> spec;
 	std::vector<SSpecialtyBonus> specialty;
 	std::set<SpellID> spells;
+	bool haveSpellBook;
+	bool special; // hero is special and won't be placed in game (unless preset on map), e.g. campaign heroes
 	ui8 sex; // default sex: 0=male, 1=female
-	ui8 special; // hero is special and won't be placed in game (unless preset on map), e.g. campaign heroes
 
 	/// Localized texts
 	std::string name; //name of hero
@@ -86,7 +87,7 @@ public:
 
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
-		h & ID & imageIndex & initialArmy & heroClass & secSkillsInit & spec & specialty & spells & sex;
+		h & ID & imageIndex & initialArmy & heroClass & secSkillsInit & spec & specialty & spells & haveSpellBook & sex & special;
 		h & name & biography & specName & specDescr & specTooltip;
 		h & iconSpecSmall & iconSpecLarge & portraitSmall & portraitLarge;
 	}
@@ -157,7 +158,7 @@ public:
 	void load();
 
 	/// load any number of classes from json
-	void load(const JsonNode & classes);
+	void load(std::string objectID, const JsonNode & classes);
 
 	/// load one class from json
 	CHeroClass * loadClass(const JsonNode & node);
@@ -206,7 +207,7 @@ public:
 	ui64 reqExp(ui32 level) const; //calculates experience required for given level
 
 	/// Load multiple heroes from json
-	void load(const JsonNode & heroes);
+	void load(std::string objectID, const JsonNode & heroes);
 
 	/// Load single hero from json
 	CHero * loadHero(const JsonNode & node);
@@ -215,7 +216,6 @@ public:
 	void load();
 
 	void loadHeroes();
-	void loadHeroTexts();
 	void loadExperience();
 	void loadBallistics();
 	void loadTerrains();

+ 10 - 4
lib/CModHandler.cpp

@@ -281,9 +281,17 @@ std::vector<std::string> CModHandler::getActiveMods()
 }
 
 template<typename Handler>
-void handleData(Handler handler, const JsonNode & config)
+void handleData(Handler handler, const JsonNode & sourceList)
 {
-	handler->load(JsonUtils::assembleFromFiles(config.convertTo<std::vector<std::string> >()));
+	JsonNode config = JsonUtils::assembleFromFiles(sourceList.convertTo<std::vector<std::string> >());
+
+	BOOST_FOREACH(auto & entry, config.Struct())
+	{
+		if (!entry.second.isNull()) // may happens if mod removed object by setting json entry to null
+		{
+			handler->load(entry.first, entry.second);
+		}
+	}
 }
 
 void CModHandler::loadActiveMods()
@@ -346,8 +354,6 @@ void CModHandler::reload()
 				VLC->dobjinfo->gobjs[Obj::ARTIFACT][art->id] = info;
 			}
 		}
-
-		VLC->arth->reverseMapArtifactConstituents();
 	}
 
 	{

+ 1 - 1
lib/CObjectHandler.cpp

@@ -740,7 +740,7 @@ void CGHeroInstance::initHero()
 	else //remove placeholder
 		spells -= SpellID::PRESET;
 
-	if(!getArt(ArtifactPosition::MACH4) && !getArt(ArtifactPosition::SPELLBOOK) && !type->spells.empty()) //no catapult means we haven't read pre-existent set -> use default rules for spellbook
+	if(!getArt(ArtifactPosition::MACH4) && !getArt(ArtifactPosition::SPELLBOOK) && type->haveSpellBook) //no catapult means we haven't read pre-existent set -> use default rules for spellbook
 		putArtifact(ArtifactPosition::SPELLBOOK, CArtifactInstance::createNewArtifactInstance(0));
 
 	if(!getArt(ArtifactPosition::MACH4))

+ 39 - 37
lib/CTownHandler.cpp

@@ -21,6 +21,8 @@
  *
  */
 
+const int NAMES_PER_TOWN=16; // number of town names per faction in H3 files. Json can define any number
+
 const std::string & CBuilding::Name() const
 {
 	return name;
@@ -218,7 +220,7 @@ void CTownHandler::loadLegacyData(JsonNode & dest)
 			town["name"].String() = typeParser.readString();
 
 
-			for (int i=0; i<GameConstants::NAMES_PER_TOWN; i++)
+			for (int i=0; i<NAMES_PER_TOWN; i++)
 			{
 				JsonNode name;
 				name.String() = nameParser.readString();
@@ -502,50 +504,47 @@ void CTownHandler::loadPuzzle(CFaction &faction, const JsonNode &source)
 	assert(faction.puzzleMap.size() == GameConstants::PUZZLE_MAP_PIECES);
 }
 
-void CTownHandler::load(const JsonNode &source)
+void CTownHandler::load(std::string townID, const JsonNode &source)
 {
-	BOOST_FOREACH(auto & node, source.Struct())
-	{
-		int id;
+	int id;
 
-		if (node.second["index"].isNull())
-			id = factions.rbegin()->first + 1;
-		else
-			id = node.second["index"].Float();
+	if (source["index"].isNull())
+		id = factions.rbegin()->first + 1;
+	else
+		id = source["index"].Float();
 
-		CFaction & faction = factions[id];
+	CFaction & faction = factions[id];
 
-		faction.factionID = id;
-		faction.name = node.second["name"].String();
+	faction.factionID = id;
+	faction.name = source["name"].String();
 
-		VLC->modh->identifiers.requestIdentifier ("creature." + node.second["commander"].String(),
-			[=](si32 commanderID)
-			{
-				factions[id].commander = CreatureID(commanderID);
-			});
+	VLC->modh->identifiers.requestIdentifier ("creature." + source["commander"].String(),
+		[=](si32 commanderID)
+		{
+			factions[id].commander = CreatureID(commanderID);
+		});
 
-		faction.creatureBg120 = node.second["creatureBackground"]["120px"].String();
-		faction.creatureBg130 = node.second["creatureBackground"]["130px"].String();
+	faction.creatureBg120 = source["creatureBackground"]["120px"].String();
+	faction.creatureBg130 = source["creatureBackground"]["130px"].String();
 
-		faction.nativeTerrain = ETerrainType(vstd::find_pos(GameConstants::TERRAIN_NAMES,
-			node.second["nativeTerrain"].String()));
-		int alignment = vstd::find_pos(EAlignment::names, node.second["alignment"].String());
-		if (alignment == -1)
-			faction.alignment = EAlignment::NEUTRAL;
-		else
-			faction.alignment = static_cast<EAlignment::EAlignment>(alignment);
-
-		if (!node.second["town"].isNull())
-		{
-			towns[id].typeID = id;
-			loadTown(towns[id], node.second["town"]);
-		}
-		if (!node.second["puzzleMap"].isNull())
-			loadPuzzle(faction, node.second["puzzleMap"]);
+	faction.nativeTerrain = ETerrainType(vstd::find_pos(GameConstants::TERRAIN_NAMES,
+		source["nativeTerrain"].String()));
+	int alignment = vstd::find_pos(EAlignment::names, source["alignment"].String());
+	if (alignment == -1)
+		faction.alignment = EAlignment::NEUTRAL;
+	else
+		faction.alignment = static_cast<EAlignment::EAlignment>(alignment);
 
-		tlog5 << "Added faction: " << node.first << "\n";
-		VLC->modh->identifiers.registerObject(std::string("faction.") + node.first, faction.factionID);
+	if (!source["town"].isNull())
+	{
+		towns[id].typeID = id;
+		loadTown(towns[id], source["town"]);
 	}
+	if (!source["puzzleMap"].isNull())
+		loadPuzzle(faction, source["puzzleMap"]);
+
+	tlog5 << "Added faction: " << townID << "\n";
+	VLC->modh->identifiers.registerObject(std::string("faction.") + townID, faction.factionID);
 }
 
 void CTownHandler::load()
@@ -587,7 +586,10 @@ void CTownHandler::load()
 			}
 		}
 	}
-	load(buildingsConf);
+	BOOST_FOREACH(auto & entry, buildingsConf.Struct())
+	{
+		load(entry.first, entry.second);
+	}
 }
 
 std::set<TFaction> CTownHandler::getDefaultAllowedFactions() const

+ 1 - 1
lib/CTownHandler.h

@@ -221,7 +221,7 @@ public:
 
 	/// main loading function for mods, accepts merged JSON source and add all entries from it into game
 	/// all entries in JSON should be checked for validness before using this function
-	void load(const JsonNode & source);
+	void load(std::string townID, const JsonNode & source);
 
 	/// "entry point" for loading of OH3 town.
 	/// reads legacy txt's from H3 + vcmi json, merges them

+ 26 - 24
lib/GameConstants.h

@@ -60,36 +60,38 @@ namespace GameConstants
 	const int BFIELD_HEIGHT = 11;
 	const int BFIELD_SIZE = BFIELD_WIDTH * BFIELD_HEIGHT;
 
-	const int ARMY_SIZE = 7;
+	const int PUZZLE_MAP_PIECES = 48;
 
-	const int CREATURES_COUNT = 197;
-	const int CRE_LEVELS = 10;
-	const int F_NUMBER = 9; //factions (town types) quantity
 	const int PLAYER_LIMIT = 8; //player limit per map
 	const int MAX_HEROES_PER_PLAYER = 8;
-	const int ALL_PLAYERS = 255; //bitfield
-	const int HEROES_PER_TYPE=8; //amount of heroes of each type
-	const int SKILL_QUANTITY=28;
-	const int SKILL_PER_HERO=8;
-	const int ARTIFACTS_QUANTITY=171;
-	const int HEROES_QUANTITY=156;
-	const int SPELLS_QUANTITY=70;
-	const int PRIMARY_SKILLS=4;
+	const int AVAILABLE_HEROES_PER_PLAYER = 2;
+
 	const int UNFLAGGABLE_PLAYER = 254; //254 - neutral objects (pandora, banks)
 	const int NEUTRAL_PLAYER=255;
-	const int NAMES_PER_TOWN=16;
+	const int ALL_PLAYERS = 255; //bitfield
+
+	const ui16 BACKPACK_START = 19;
 	const int CREATURES_PER_TOWN = 7; //without upgrades
 	const int SPELL_LEVELS = 5;
-	const int AVAILABLE_HEROES_PER_PLAYER = 2;
-	const int SPELLBOOK_GOLD_COST = 500;
-	const int PUZZLE_MAP_PIECES = 48;
 
+	const int SPELLBOOK_GOLD_COST = 500;
 	const int BATTLE_PENALTY_DISTANCE = 10; //if the distance is > than this, then shooting stack has distance penalty
+	const int ARMY_SIZE = 7;
+	const int SKILL_PER_HERO=8;
 
-	const ui16 BACKPACK_START = 19;
-
+	const int CRE_LEVELS = 10;
+	const int SKILL_QUANTITY=28;
+	const int PRIMARY_SKILLS=4;
 	const int TERRAIN_TYPES=10;
 	const int RESOURCE_QUANTITY=8;
+	const int HEROES_PER_TYPE=8; //amount of heroes of each type
+
+	// amounts of OH3 objects. Can be changed by mods, should be used only during H3 loading phase
+	const int F_NUMBER = 9;
+	const int ARTIFACTS_QUANTITY=171;
+	const int HEROES_QUANTITY=156;
+	const int SPELLS_QUANTITY=70;
+	const int CREATURES_COUNT = 197;
 
 }
 
@@ -740,9 +742,9 @@ public:
 		BALLISTA = 4,
 		AMMO_CART = 5,
 		FIRST_AID_TENT = 6,
-		CENTAUR_AXE = 7,
-		BLACKSHARD_OF_THE_DEAD_KNIGHT = 8,
-		CORNUCOPIA = 140,
+		//CENTAUR_AXE = 7,
+		//BLACKSHARD_OF_THE_DEAD_KNIGHT = 8,
+		//CORNUCOPIA = 140,
 		ART_SELECTION = 144,
 		ART_LOCK = 145,
 		AXE_OF_SMASHING = 146,
@@ -753,8 +755,8 @@ public:
 		BOOTS_OF_HASTE = 151,
 		BOW_OF_SEEKING = 152,
 		DRAGON_EYE_RING = 153,
-		HARDENED_SHIELD = 154,
-		SLAVAS_RING_OF_POWER = 155
+		//HARDENED_SHIELD = 154,
+		//SLAVAS_RING_OF_POWER = 155
 	};
 
 	ArtifactID(EArtifactID _num = NONE) : num(_num)
@@ -853,7 +855,7 @@ public:
 ID_LIKE_OPERATORS_DECLS(SpellID, SpellID::ESpellID)
 
 // Typedef declarations
-typedef si8 TFaction;
+typedef ui8 TFaction;
 typedef si64 TExpType;
 typedef std::pair<ui32, ui32> TDmgRange;
 typedef si32 TBonusSubtype;

+ 15 - 1
lib/JsonNode.h

@@ -89,6 +89,12 @@ public:
 	template<typename Type>
 	Type convertTo() const;
 
+	/// Similar to convertTo but will assign data only if node is not null
+	/// Othervice original data will be preserved
+	/// Returns true if data was assigned
+	template<typename Type>
+	bool loadTo(Type & data) const;
+
 	//operator [], for structs only - get child node by name
 	JsonNode & operator[](std::string child);
 	const JsonNode & operator[](std::string child) const;
@@ -367,4 +373,12 @@ template<typename Type>
 Type JsonNode::convertTo() const
 {
 	return JsonDetail::JsonConverter<Type>::convert(*this);
-}
+}
+
+template<typename Type>
+bool JsonNode::loadTo(Type & data) const
+{
+	if (!isNull())
+		data = convertTo<Type>();
+	return !isNull();
+}

+ 1 - 1
lib/Mapping/MapFormatH3M.cpp

@@ -683,7 +683,7 @@ CArtifactInstance * CMapLoaderH3M::createArtifact(int aid, int spellID /*= -1*/)
 	map->addNewArtifactInstance(a);
 
 	//TODO make it nicer
-	if(a->artType && a->artType->constituents)
+	if(a->artType && a->artType->constituents != nullptr)
 	{
 		CCombinedArtifactInstance * comb = dynamic_cast<CCombinedArtifactInstance *>(a);
 		BOOST_FOREACH(CCombinedArtifactInstance::ConstituentInfo & ci, comb->constituentsInfo)