Răsfoiți Sursa

Support for creature 2-hex attack, alternate death and cast animation types
* margin option .json animation
* Use ranged attack animation for spell cast if there is no cast animation, display cast animation only on active casting.

AlexVinS 8 ani în urmă
părinte
comite
270e1b75ce

+ 1 - 1
client/CBitmapHandler.cpp

@@ -178,7 +178,7 @@ SDL_Surface * BitmapHandler::loadBitmapFromDir(std::string path, std::string fna
 
 SDL_Surface * BitmapHandler::loadBitmap(std::string fname, bool setKey)
 {
-	SDL_Surface *bitmap;
+	SDL_Surface * bitmap = nullptr;
 
 	if (!(bitmap = loadBitmapFromDir("DATA/", fname, setKey)) &&
 		!(bitmap = loadBitmapFromDir("SPRITES/", fname, setKey)))

+ 194 - 24
client/battle/CBattleAnimations.cpp

@@ -241,7 +241,12 @@ std::string CDefenceAnimation::getMySound()
 CCreatureAnim::EAnimType CDefenceAnimation::getMyAnimType()
 {
 	if(killed)
-		return CCreatureAnim::DEATH;
+	{
+		if(rangedAttack && myAnim->framesInGroup(CCreatureAnim::DEATH_RANGED) > 0)
+			return CCreatureAnim::DEATH_RANGED;
+		else
+			return CCreatureAnim::DEATH;
+	}
 
 	if(vstd::contains(stack->state, EBattleStackState::DEFENDING_ANIM))
 		return CCreatureAnim::DEFENCE;
@@ -272,7 +277,10 @@ void CDefenceAnimation::endAnim()
 {
 	if(killed)
 	{
-		myAnim->setType(CCreatureAnim::DEAD);
+		if(rangedAttack && myAnim->framesInGroup(CCreatureAnim::DEAD_RANGED) > 0)
+			myAnim->setType(CCreatureAnim::DEAD_RANGED);
+		else
+			myAnim->setType(CCreatureAnim::DEAD);
 	}
 	else
 	{
@@ -312,26 +320,25 @@ void CDummyAnimation::endAnim()
 
 bool CMeleeAttackAnimation::init()
 {
-	if( !CAttackAnimation::checkInitialConditions() )
+	if(!CAttackAnimation::checkInitialConditions())
 		return false;
 
 	if(!attackingStack || myAnim->isDead())
 	{
 		endAnim();
-
 		return false;
 	}
 
 	bool toReverse = owner->getCurrentPlayerInterface()->cb->isToReverse(attackingStackPosBeforeReturn, attackedStack->position, owner->creDir[stack->ID], attackedStack->doubleWide(), owner->creDir[attackedStack->ID]);
 
-	if (toReverse)
+	if(toReverse)
 	{
 		owner->addNewAnim(new CReverseAnimation(owner, stack, attackingStackPosBeforeReturn, true));
 		return false;
 	}
 
 	// opponent must face attacker ( = different directions) before he can be attacked
-	if (attackingStack && attackedStack &&
+	if(attackingStack && attackedStack &&
 	    owner->creDir[attackingStack->ID] == owner->creDir[attackedStack->ID])
 		return false;
 
@@ -339,8 +346,25 @@ bool CMeleeAttackAnimation::init()
 
 	shooting = false;
 
-	static const CCreatureAnim::EAnimType mutPosToGroup[] = {CCreatureAnim::ATTACK_UP, CCreatureAnim::ATTACK_UP,
-		CCreatureAnim::ATTACK_FRONT, CCreatureAnim::ATTACK_DOWN, CCreatureAnim::ATTACK_DOWN, CCreatureAnim::ATTACK_FRONT};
+	static const CCreatureAnim::EAnimType mutPosToGroup[] =
+	{
+		CCreatureAnim::ATTACK_UP,
+		CCreatureAnim::ATTACK_UP,
+		CCreatureAnim::ATTACK_FRONT,
+		CCreatureAnim::ATTACK_DOWN,
+		CCreatureAnim::ATTACK_DOWN,
+		CCreatureAnim::ATTACK_FRONT
+	};
+
+	static const CCreatureAnim::EAnimType mutPosToGroup2H[] =
+	{
+		CCreatureAnim::VCMI_2HEX_UP,
+		CCreatureAnim::VCMI_2HEX_UP,
+		CCreatureAnim::VCMI_2HEX_FRONT,
+		CCreatureAnim::VCMI_2HEX_DOWN,
+		CCreatureAnim::VCMI_2HEX_DOWN,
+		CCreatureAnim::VCMI_2HEX_FRONT
+	};
 
 	int revShiftattacker = (attackingStack->side == BattleSide::ATTACKER ? -1 : 1);
 
@@ -361,8 +385,20 @@ bool CMeleeAttackAnimation::init()
 
 	switch(mutPos) //attack direction
 	{
-	case 0: case 1: case 2: case 3: case 4: case 5:
+	case 0:
+	case 1:
+	case 2:
+	case 3:
+	case 4:
+	case 5:
 		group = mutPosToGroup[mutPos];
+        if(attackingStack->hasBonusOfType(Bonus::TWO_HEX_ATTACK_BREATH))
+		{
+			CCreatureAnim::EAnimType group2H = mutPosToGroup2H[mutPos];
+			if(myAnim->framesInGroup(group2H)>0)
+				group = group2H;
+		}
+
 		break;
 	default:
 		logGlobal->error("Critical Error! Wrong dest in stackAttacking! dest: %d; attacking stack pos: %d; mutual pos: %d", dest.hex, attackingStackPosBeforeReturn, mutPos);
@@ -672,8 +708,16 @@ void CReverseAnimation::setupSecondPart()
 		endAnim();
 }
 
+CRangedAttackAnimation::CRangedAttackAnimation(CBattleInterface * owner_, const CStack * attacker, BattleHex dest_, const CStack * defender)
+	: CAttackAnimation(owner_, attacker, dest_, defender)
+{
+
+}
+
+
 CShootingAnimation::CShootingAnimation(CBattleInterface * _owner, const CStack * attacker, BattleHex _dest, const CStack * _attacked, bool _catapult, int _catapultDmg)
-: CAttackAnimation(_owner, attacker, _dest, _attacked), catapultDamage(_catapultDmg)
+	: CRangedAttackAnimation(_owner, attacker, _dest, _attacked),
+	catapultDamage(_catapultDmg)
 {
 	logAnim->debug("Created shooting anim for %s", stack->getName());
 }
@@ -835,9 +879,9 @@ bool CShootingAnimation::init()
 
 	shooting = true;
 
-	if(projectileAngle > straightAngle) //upper shot
+	if(projectileAngle > straightAngle)
 		group = CCreatureAnim::SHOOT_UP;
-	else if(projectileAngle < -straightAngle) //lower shot
+	else if(projectileAngle < -straightAngle)
 		group = CCreatureAnim::SHOOT_DOWN;
 	else //straight shot
 		group = CCreatureAnim::SHOOT_FRONT;
@@ -860,23 +904,149 @@ void CShootingAnimation::nextFrame()
 
 void CShootingAnimation::endAnim()
 {
-    // play wall hit/miss sound for catapult attack
-    if(!attackedStack)
-    {
-        if(catapultDamage > 0)
-        {
-            CCS->soundh->playSound("WALLHIT");
-        }
-        else
-        {
-            CCS->soundh->playSound("WALLMISS");
-        }
-    }
+	// play wall hit/miss sound for catapult attack
+	if(!attackedStack)
+	{
+		if(catapultDamage > 0)
+		{
+			CCS->soundh->playSound("WALLHIT");
+		}
+		else
+		{
+			CCS->soundh->playSound("WALLMISS");
+		}
+	}
 
 	CAttackAnimation::endAnim();
 	delete this;
 }
 
+CCastAnimation::CCastAnimation(CBattleInterface * owner_, const CStack * attacker, BattleHex dest_, const CStack * defender)
+	: CRangedAttackAnimation(owner_, attacker, dest_, defender)
+{
+	if(!dest_.isValid() && defender)
+		dest = defender->position;
+}
+
+bool CCastAnimation::init()
+{
+	if(!CAttackAnimation::checkInitialConditions())
+		return false;
+
+	if(!attackingStack || myAnim->isDead())
+	{
+		endAnim();
+		return false;
+	}
+
+	//reverse unit if necessary
+	if(attackedStack)
+	{
+        if(owner->getCurrentPlayerInterface()->cb->isToReverse(attackingStack->position, attackedStack->position, owner->creDir[attackingStack->ID], attackingStack->doubleWide(), owner->creDir[attackedStack->ID]))
+		{
+			owner->addNewAnim(new CReverseAnimation(owner, attackingStack, attackingStack->position, true));
+			return false;
+		}
+	}
+	else
+	{
+        if(dest.isValid() && owner->getCurrentPlayerInterface()->cb->isToReverse(attackingStack->position, dest, owner->creDir[attackingStack->ID], false, false))
+		{
+			owner->addNewAnim(new CReverseAnimation(owner, attackingStack, attackingStack->position, true));
+			return false;
+		}
+	}
+
+	//TODO: display spell projectile here
+
+	static const double straightAngle = 0.2;
+
+
+	Point fromPos;
+	Point destPos;
+
+	// NOTE: two lines below return different positions (very notable with 2-hex creatures). Obtaining via creanims seems to be more precise
+	fromPos = owner->creAnims[attackingStack->ID]->pos.topLeft();
+	//xycoord = CClickableHex::getXYUnitAnim(shooter->position, true, shooter, owner);
+
+	destPos = CClickableHex::getXYUnitAnim(dest, attackedStack, owner);
+
+
+	double projectileAngle = atan2(fabs((double)destPos.y - fromPos.y), fabs((double)destPos.x - fromPos.x));
+	if(attackingStack->position < dest)
+		projectileAngle = -projectileAngle;
+
+
+	if(projectileAngle > straightAngle)
+		group = CCreatureAnim::VCMI_CAST_UP;
+	else if(projectileAngle < -straightAngle)
+		group = CCreatureAnim::VCMI_CAST_DOWN;
+	else
+		group = CCreatureAnim::VCMI_CAST_FRONT;
+
+	//fall back to H3 cast/2hex
+	//even if creature have 2hex attack instead of cast it is ok since we fall back to attack anyway
+	if(myAnim->framesInGroup(group) == 0)
+	{
+		if(projectileAngle > straightAngle)
+			group = CCreatureAnim::CAST_UP;
+		else if(projectileAngle < -straightAngle)
+			group = CCreatureAnim::CAST_DOWN;
+		else
+			group = CCreatureAnim::CAST_FRONT;
+	}
+
+	//fall back to ranged attack
+	if(myAnim->framesInGroup(group) == 0)
+	{
+		if(projectileAngle > straightAngle)
+			group = CCreatureAnim::SHOOT_UP;
+		else if(projectileAngle < -straightAngle)
+			group = CCreatureAnim::SHOOT_DOWN;
+		else
+			group = CCreatureAnim::SHOOT_FRONT;
+	}
+
+	//fall back to normal attack
+	if(myAnim->framesInGroup(group) == 0)
+	{
+		if(projectileAngle > straightAngle)
+			group = CCreatureAnim::ATTACK_UP;
+		else if(projectileAngle < -straightAngle)
+			group = CCreatureAnim::ATTACK_DOWN;
+		else
+			group = CCreatureAnim::ATTACK_FRONT;
+	}
+
+	return true;
+}
+
+void CCastAnimation::nextFrame()
+{
+	for(auto & it : owner->pendingAnims)
+	{
+		CReverseAnimation * anim = dynamic_cast<CReverseAnimation *>(it.first);
+		if(anim && anim->stack->ID == stack->ID && anim->priority)
+			return;
+	}
+
+	if(myAnim->getType() != group)
+	{
+		myAnim->setType(group);
+		myAnim->onAnimationReset += std::bind(&CAttackAnimation::endAnim, this);
+	}
+
+	CBattleAnimation::nextFrame();
+}
+
+
+void CCastAnimation::endAnim()
+{
+	CAttackAnimation::endAnim();
+	delete this;
+}
+
+
 CEffectAnimation::CEffectAnimation(CBattleInterface * _owner, std::string _customAnim, int _x, int _y, int _dx, int _dy, bool _Vflip, bool _alignToBottom)
 	: CBattleAnimation(_owner),
 	destTile(BattleHex::INVALID),

+ 21 - 1
client/battle/CBattleAnimations.h

@@ -197,8 +197,16 @@ struct ProjectileInfo
 	std::shared_ptr<CatapultProjectileInfo> catapultInfo; // holds info about the parabolic trajectory of the cannon
 };
 
+class CRangedAttackAnimation : public CAttackAnimation
+{
+public:
+	CRangedAttackAnimation(CBattleInterface * owner_, const CStack * attacker, BattleHex dest_, const CStack * defender);
+protected:
+
+};
+
 /// Shooting attack
-class CShootingAnimation : public CAttackAnimation
+class CShootingAnimation : public CRangedAttackAnimation
 {
 private:
 	int catapultDamage;
@@ -213,6 +221,18 @@ public:
 	virtual ~CShootingAnimation(){};
 };
 
+class CCastAnimation : public CRangedAttackAnimation
+{
+public:
+	CCastAnimation(CBattleInterface * owner_, const CStack * attacker, BattleHex dest_, const CStack * defender);
+
+	bool init() override;
+	void nextFrame() override;
+	void endAnim() override;
+
+};
+
+
 /// This class manages effect animation
 class CEffectAnimation : public CBattleAnimation
 {

+ 23 - 14
client/battle/CBattleInterface.cpp

@@ -1266,7 +1266,7 @@ void CBattleInterface::displayBattleFinished()
 	curInt->waitWhileDialog(); // Avoid freeze when AI end turn after battle. Check bug #1897
 }
 
-void CBattleInterface::spellCast(const BattleSpellCast *sc)
+void CBattleInterface::spellCast(const BattleSpellCast * sc)
 {
 	const SpellID spellID(sc->id);
 	const CSpell & spell = *spellID.toSpell();
@@ -1276,24 +1276,32 @@ void CBattleInterface::spellCast(const BattleSpellCast *sc)
 	if (!castSoundPath.empty())
 		CCS->soundh->playSound(castSoundPath);
 
-	Point srccoord = (sc->side ? Point(770, 60) : Point(30, 60)) + pos;	//hero position by default
+	const auto casterStackID = sc->casterStack;
+	const CStack * casterStack = nullptr;
+	if(casterStackID >= 0)
 	{
-		const auto casterStackID = sc->casterStack;
+		casterStack = curInt->cb->battleGetStackByID(casterStackID);
+	}
 
-		if (casterStackID > 0)
+	Point srccoord = (sc->side ? Point(770, 60) : Point(30, 60)) + pos;	//hero position by default
+	{
+		if(casterStack != nullptr)
 		{
-			const CStack *casterStack = curInt->cb->battleGetStackByID(casterStackID);
-			if (casterStack != nullptr)
-			{
-				srccoord = CClickableHex::getXYUnitAnim(casterStack->position, casterStack, this);
-				srccoord.x += 250;
-				srccoord.y += 240;
-			}
+			srccoord = CClickableHex::getXYUnitAnim(casterStack->position, casterStack, this);
+			srccoord.x += 250;
+			srccoord.y += 240;
 		}
 	}
 
-	//todo: play custom cast animation
-	displaySpellCast(spellID, BattleHex::INVALID);
+	if(casterStack != nullptr && sc->activeCast)
+	{
+		//todo: custom cast animation for hero
+		displaySpellCast(spellID, casterStack->position);
+
+		addNewAnim(new CCastAnimation(this, casterStack, sc->tile, curInt->cb->battleGetStackByPos(sc->tile)));
+	}
+
+	waitForAnims(); //wait for cast animation
 
 	//playing projectile animation
 	if (sc->tile.isValid())
@@ -1328,7 +1336,8 @@ void CBattleInterface::spellCast(const BattleSpellCast *sc)
 			addNewAnim(new CEffectAnimation(this, animToDisplay, srccoord.x, srccoord.y, dx, dy, Vflip));
 		}
 	}
-	waitForAnims();
+
+	waitForAnims(); //wait for projectile animation
 
 	displaySpellHit(spellID, sc->tile);
 

+ 1 - 0
client/battle/CBattleInterface.h

@@ -388,5 +388,6 @@ public:
 	friend class CAttackAnimation;
 	friend class CMeleeAttackAnimation;
 	friend class CShootingAnimation;
+	friend class CCastAnimation;
 	friend class CClickableHex;
 };

+ 27 - 11
client/battle/CCreatureAnimation.cpp

@@ -72,6 +72,9 @@ float AnimationControls::getCreatureAnimationSpeed(const CCreature * creature, c
 	case CCreatureAnim::CAST_UP:
 	case CCreatureAnim::CAST_FRONT:
 	case CCreatureAnim::CAST_DOWN:
+	case CCreatureAnim::VCMI_CAST_DOWN:
+	case CCreatureAnim::VCMI_CAST_FRONT:
+	case CCreatureAnim::VCMI_CAST_UP:
 		return speed * 4 * creature->animation.attackAnimationTime / anim->framesInGroup(type);
 
 	// as strange as it looks like "attackAnimationTime" does not affects melee attacks
@@ -82,6 +85,10 @@ float AnimationControls::getCreatureAnimationSpeed(const CCreature * creature, c
 	case CCreatureAnim::HITTED:
 	case CCreatureAnim::DEFENCE:
 	case CCreatureAnim::DEATH:
+	case CCreatureAnim::DEATH_RANGED:
+	case CCreatureAnim::VCMI_2HEX_DOWN:
+	case CCreatureAnim::VCMI_2HEX_FRONT:
+	case CCreatureAnim::VCMI_2HEX_UP:
 		return speed * 3 / anim->framesInGroup(type);
 
 	case CCreatureAnim::TURN_L:
@@ -93,11 +100,11 @@ float AnimationControls::getCreatureAnimationSpeed(const CCreature * creature, c
 		return speed / 3;
 
 	case CCreatureAnim::DEAD:
+	case CCreatureAnim::DEAD_RANGED:
 		return speed;
 
 	default:
-		assert(0);
-		return 1;
+		return speed;
 	}
 }
 
@@ -128,9 +135,6 @@ CCreatureAnim::EAnimType CCreatureAnimation::getType() const
 
 void CCreatureAnimation::setType(CCreatureAnim::EAnimType type)
 {
-	assert(type >= 0);
-	assert(framesInGroup(type) != 0);
-
 	this->type = type;
 	currentFrame = 0;
 	once = false;
@@ -162,6 +166,12 @@ CCreatureAnimation::CCreatureAnimation(const std::string & name_, TSpeedControll
 		reverse->duplicateImage(CCreatureAnim::DEATH, reverse->size(CCreatureAnim::DEATH)-1, CCreatureAnim::DEAD);
 	}
 
+	if(forward->size(CCreatureAnim::DEAD_RANGED) == 0 && forward->size(CCreatureAnim::DEATH_RANGED) != 0)
+	{
+		forward->duplicateImage(CCreatureAnim::DEATH_RANGED, forward->size(CCreatureAnim::DEATH_RANGED)-1, CCreatureAnim::DEAD_RANGED);
+		reverse->duplicateImage(CCreatureAnim::DEATH_RANGED, reverse->size(CCreatureAnim::DEATH_RANGED)-1, CCreatureAnim::DEAD_RANGED);
+	}
+
 	//TODO: get dimensions form CAnimation
 	IImage * first = forward->getImage(0, type, true);
 
@@ -191,6 +201,7 @@ bool CCreatureAnimation::incrementFrame(float timePassed)
 {
 	elapsedTime += timePassed;
 	currentFrame += timePassed * speed;
+
 	if (currentFrame >= float(framesInGroup(type)))
 	{
 		// just in case of extremely low fps (or insanely high speed)
@@ -271,7 +282,7 @@ void CCreatureAnimation::genBorderPalette(IImage::BorderPallete & target)
 	target[2] = addColors(genShadow(64),  genBorderColor(getBorderStrength(elapsedTime), border));
 }
 
-void CCreatureAnimation::nextFrame(SDL_Surface *dest, bool attacker)
+void CCreatureAnimation::nextFrame(SDL_Surface * dest, bool attacker)
 {
 	size_t frame = floor(currentFrame);
 
@@ -282,12 +293,15 @@ void CCreatureAnimation::nextFrame(SDL_Surface *dest, bool attacker)
 	else
 		image = reverse->getImage(frame, type);
 
-	IImage::BorderPallete borderPallete;
-	genBorderPalette(borderPallete);
+	if(image)
+	{
+		IImage::BorderPallete borderPallete;
+		genBorderPalette(borderPallete);
 
-	image->setBorderPallete(borderPallete);
+		image->setBorderPallete(borderPallete);
 
-	image->draw(dest, pos.x, pos.y);
+		image->draw(dest, pos.x, pos.y);
+	}
 }
 
 int CCreatureAnimation::framesInGroup(CCreatureAnim::EAnimType group) const
@@ -298,7 +312,9 @@ int CCreatureAnimation::framesInGroup(CCreatureAnim::EAnimType group) const
 bool CCreatureAnimation::isDead() const
 {
 	return getType() == CCreatureAnim::DEAD
-	    || getType() == CCreatureAnim::DEATH;
+	    || getType() == CCreatureAnim::DEATH
+	    || getType() == CCreatureAnim::DEAD_RANGED
+	    || getType() == CCreatureAnim::DEATH_RANGED;
 }
 
 bool CCreatureAnimation::isIdle() const

+ 1 - 1
client/battle/CCreatureAnimation.h

@@ -108,7 +108,7 @@ public:
 
 	void playOnce(CCreatureAnim::EAnimType type); //plays once given stage of animation, then resets to 2
 
-	int framesInGroup(CCreatureAnim::EAnimType group) const; //retirns number of fromes in given group
+	int framesInGroup(CCreatureAnim::EAnimType group) const;
 
 	void pause();
 	void play();

+ 99 - 67
client/gui/CAnimation.cpp

@@ -82,6 +82,8 @@ public:
 	SDLImage(CDefFile *data, size_t frame, size_t group=0, bool compressed=false);
 	//Load from bitmap file
 	SDLImage(std::string filename, bool compressed=false);
+
+	SDLImage(const JsonNode & conf);
 	//Create using existing surface, extraRef will increase refcount on SDL_Surface
 	SDLImage(SDL_Surface * from, bool extraRef);
 	~SDLImage();
@@ -803,15 +805,19 @@ void IImage::increaseRef()
 	refCount++;
 }
 
-SDLImage::SDLImage(CDefFile *data, size_t frame, size_t group, bool compressed):
-	surf(nullptr)
+SDLImage::SDLImage(CDefFile * data, size_t frame, size_t group, bool compressed)
+	: surf(nullptr),
+	margins(0, 0),
+	fullSize(0, 0)
 {
 	SDLImageLoader loader(this);
 	data->loadFrame(frame, group, loader);
 }
 
-SDLImage::SDLImage(SDL_Surface * from, bool extraRef):
-	margins(0,0)
+SDLImage::SDLImage(SDL_Surface * from, bool extraRef)
+	: surf(nullptr),
+	margins(0, 0),
+	fullSize(0, 0)
 {
 	surf = from;
 	if (extraRef)
@@ -820,8 +826,42 @@ SDLImage::SDLImage(SDL_Surface * from, bool extraRef):
 	fullSize.y = surf->h;
 }
 
-SDLImage::SDLImage(std::string filename, bool compressed):
-	margins(0,0)
+SDLImage::SDLImage(const JsonNode & conf)
+	: surf(nullptr),
+	margins(0, 0),
+	fullSize(0, 0)
+{
+	std::string filename = conf["file"].String();
+
+	surf = BitmapHandler::loadBitmap(filename);
+
+	if(surf == nullptr)
+		return;
+
+	const JsonNode & jsonMargins = conf["margins"];
+
+	margins.x = jsonMargins["left"].Integer();
+	margins.y = jsonMargins["top"].Integer();
+
+	fullSize.x = conf["width"].Integer();
+	fullSize.y = conf["height"].Integer();
+
+	if(fullSize.x == 0)
+	{
+		fullSize.x = margins.x + surf->w + jsonMargins["right"].Integer();
+	}
+
+	if(fullSize.y == 0)
+	{
+		fullSize.y = margins.y + surf->h + jsonMargins["bottom"].Integer();
+	}
+}
+
+
+SDLImage::SDLImage(std::string filename, bool compressed)
+	: surf(nullptr),
+	margins(0, 0),
+	fullSize(0, 0)
 {
 	surf = BitmapHandler::loadBitmap(filename);
 
@@ -851,9 +891,10 @@ SDLImage::SDLImage(std::string filename, bool compressed):
 	}
 }
 
+
 void SDLImage::draw(SDL_Surface *where, int posX, int posY, Rect *src, ui8 alpha) const
 {
-	if (!surf)
+	if(!surf)
 		return;
 
 	Rect destRect(posX, posY, surf->w, surf->h);
@@ -996,7 +1037,6 @@ void SDLImage::setBorderPallete(const IImage::BorderPallete & borderPallete)
 	}
 }
 
-
 SDLImage::~SDLImage()
 {
 	SDL_FreeSurface(surf);
@@ -1300,34 +1340,34 @@ IImage * CAnimation::getFromExtraDef(std::string filename)
 	return ret;
 }
 
-bool CAnimation::loadFrame(CDefFile * file, size_t frame, size_t group)
+bool CAnimation::loadFrame(size_t frame, size_t group)
 {
-	if (size(group) <= frame)
+	if(size(group) <= frame)
 	{
 		printError(frame, group, "LoadFrame");
 		return false;
 	}
 
-	IImage *image = getImage(frame, group, false);
-	if (image)
+	IImage * image = getImage(frame, group, false);
+	if(image)
 	{
 		image->increaseRef();
 		return true;
 	}
 
 	//try to get image from def
-	if (source[group][frame].getType() == JsonNode::DATA_NULL)
+	if(source[group][frame].getType() == JsonNode::DATA_NULL)
 	{
-		if (file)
+		if(defFile)
 		{
-			auto frameList = file->getEntries();
+			auto frameList = defFile->getEntries();
 
-			if (vstd::contains(frameList, group) && frameList.at(group) > frame) // frame is present
+			if(vstd::contains(frameList, group) && frameList.at(group) > frame) // frame is present
 			{
-				if (compressed)
-					images[group][frame] = new CompImage(file, frame, group);
+				if(compressed)
+					images[group][frame] = new CompImage(defFile, frame, group);
 				else
-					images[group][frame] = new SDLImage(file, frame, group);
+					images[group][frame] = new SDLImage(defFile, frame, group);
 				return true;
 			}
 		}
@@ -1338,11 +1378,9 @@ bool CAnimation::loadFrame(CDefFile * file, size_t frame, size_t group)
 	}
 	else //load from separate file
 	{
-		std::string filename = source[group][frame]["file"].String();
-
-		IImage * img = getFromExtraDef(filename);
-		if (!img)
-			img = new SDLImage(filename, compressed);
+		IImage * img = getFromExtraDef(source[group][frame]["file"].String());
+		if(!img)
+			img = new SDLImage(source[group][frame]);
 
 		images[group][frame] = img;
 		return true;
@@ -1373,30 +1411,37 @@ void CAnimation::initFromJson(const JsonNode & config)
 	std::string basepath;
 	basepath = config["basepath"].String();
 
-	for(const JsonNode &group : config["sequences"].Vector())
+	JsonNode base(JsonNode::DATA_STRUCT);
+	base["margins"] = config["margins"];
+	base["width"] = config["width"];
+	base["height"] = config["height"];
+
+	for(const JsonNode & group : config["sequences"].Vector())
 	{
-		size_t groupID = group["group"].Float();//TODO: string-to-value conversion("moving" -> MOVING)
+		size_t groupID = group["group"].Integer();//TODO: string-to-value conversion("moving" -> MOVING)
 		source[groupID].clear();
 
-		for(const JsonNode &frame : group["frames"].Vector())
+		for(const JsonNode & frame : group["frames"].Vector())
 		{
-			source[groupID].push_back(JsonNode());
-			std::string filename =  frame.String();
-			source[groupID].back()["file"].String() = basepath + filename;
+			JsonNode toAdd(JsonNode::DATA_STRUCT);
+			JsonUtils::inherit(toAdd, base);
+			toAdd["file"].String() = basepath + frame["file"].String();
+			source[groupID].push_back(toAdd);
 		}
 	}
 
-	for(const JsonNode &node : config["images"].Vector())
+	for(const JsonNode & node : config["images"].Vector())
 	{
-		size_t group = node["group"].Float();
-		size_t frame = node["frame"].Float();
+		size_t group = node["group"].Integer();
+		size_t frame = node["frame"].Integer();
 
 		if (source[group].size() <= frame)
 			source[group].resize(frame+1);
 
-		source[group][frame] = node;
-		std::string filename =  node["file"].String();
-		source[group][frame]["file"].String() = basepath + filename;
+		JsonNode toAdd(JsonNode::DATA_STRUCT);
+		JsonUtils::inherit(toAdd, base);
+		toAdd["file"].String() = basepath + node["file"].String();
+		source[group][frame] = toAdd;
 	}
 }
 
@@ -1433,11 +1478,11 @@ void CAnimation::exportBitmaps(const boost::filesystem::path& path) const
 	logGlobal->info("Exported %d frames to %s", counter, actualPath.string());
 }
 
-void CAnimation::init(CDefFile * file)
+void CAnimation::init()
 {
-	if (file)
+	if(defFile)
 	{
-		const std::map<size_t, size_t> defEntries = file->getEntries();
+		const std::map<size_t, size_t> defEntries = defFile->getEntries();
 
 		for (auto & defEntry : defEntries)
 			source[defEntry.first].resize(defEntry.second);
@@ -1462,15 +1507,6 @@ void CAnimation::init(CDefFile * file)
 	}
 }
 
-CDefFile * CAnimation::getFile() const
-{
-	ResourceID identifier(std::string("SPRITES/") + name, EResType::ANIMATION);
-
-	if (CResourceHandler::get()->existsResource(identifier))
-		return new CDefFile(name);
-	return nullptr;
-}
-
 void CAnimation::printError(size_t frame, size_t group, std::string type) const
 {
 	logGlobal->error("%s error: Request for frame not present in CAnimation! File name: %s, Group: %d, Frame: %d", type, name, group, frame);
@@ -1479,15 +1515,20 @@ void CAnimation::printError(size_t frame, size_t group, std::string type) const
 CAnimation::CAnimation(std::string Name, bool Compressed):
 	name(Name),
 	compressed(Compressed),
-	preloaded(false)
+	preloaded(false),
+	defFile(nullptr)
 {
 	size_t dotPos = name.find_last_of('.');
 	if ( dotPos!=-1 )
 		name.erase(dotPos);
 	std::transform(name.begin(), name.end(), name.begin(), toupper);
-	CDefFile * file = getFile();
-	init(file);
-	delete file;
+
+	ResourceID resource(std::string("SPRITES/") + name, EResType::ANIMATION);
+
+	if(CResourceHandler::get()->existsResource(resource))
+		defFile = new CDefFile(name);
+
+	init();
 
 	if(source.empty())
 		logAnim->error("Animation %s failed to load", Name);
@@ -1496,9 +1537,10 @@ CAnimation::CAnimation(std::string Name, bool Compressed):
 CAnimation::CAnimation():
 	name(""),
 	compressed(false),
-	preloaded(false)
+	preloaded(false),
+	defFile(nullptr)
 {
-	init(nullptr);
+	init();
 }
 
 CAnimation::~CAnimation()
@@ -1558,13 +1600,9 @@ IImage * CAnimation::getImage(size_t frame, size_t group, bool verbose) const
 
 void CAnimation::load()
 {
-	CDefFile * file = getFile();
-
 	for (auto & elem : source)
 		for (size_t image=0; image < elem.second.size(); image++)
-			loadFrame(file, image, elem.first);
-
-	delete file;
+			loadFrame(image, elem.first);
 }
 
 void CAnimation::unload()
@@ -1586,13 +1624,9 @@ void CAnimation::preload()
 
 void CAnimation::loadGroup(size_t group)
 {
-	CDefFile * file = getFile();
-
 	if (vstd::contains(source, group))
 		for (size_t image=0; image < source[group].size(); image++)
-			loadFrame(file, image, group);
-
-	delete file;
+			loadFrame(image, group);
 }
 
 void CAnimation::unloadGroup(size_t group)
@@ -1604,9 +1638,7 @@ void CAnimation::unloadGroup(size_t group)
 
 void CAnimation::load(size_t frame, size_t group)
 {
-	CDefFile * file = getFile();
-	loadFrame(file, frame, group);
-	delete file;
+	loadFrame(frame, group);
 }
 
 void CAnimation::unload(size_t frame, size_t group)

+ 4 - 6
client/gui/CAnimation.h

@@ -78,18 +78,17 @@ private:
 
 	bool preloaded;
 
+	CDefFile * defFile;
+
 	//loader, will be called by load(), require opened def file for loading from it. Returns true if image is loaded
-	bool loadFrame(CDefFile * file, size_t frame, size_t group);
+	bool loadFrame(size_t frame, size_t group);
 
 	//unloadFrame, returns true if image has been unloaded ( either deleted or decreased refCount)
 	bool unloadFrame(size_t frame, size_t group);
 
 	//initialize animation from file
 	void initFromJson(const JsonNode & input);
-	void init(CDefFile * file);
-
-	//try to open def file
-	CDefFile * getFile() const;
+	void init();
 
 	//to get rid of copy-pasting error message :]
 	void printError(size_t frame, size_t group, std::string type) const;
@@ -99,7 +98,6 @@ private:
 	IImage * getFromExtraDef(std::string filename);
 
 public:
-
 	CAnimation(std::string Name, bool Compressed = false);
 	CAnimation();
 	~CAnimation();

+ 2 - 0
client/gui/SDL_Extensions.h

@@ -218,8 +218,10 @@ namespace CSDL_Ext
 	void stopTextInput();
 
 	void setColorKey(SDL_Surface * surface, SDL_Color color);
+
 	///set key-color to 0,255,255
 	void setDefaultColorKey(SDL_Surface * surface);
+
 	///set key-color to 0,255,255 only if it exactly mapped
 	void setDefaultColorKeyPresize(SDL_Surface * surface);
 }

+ 10 - 2
client/widgets/Images.h

@@ -181,7 +181,7 @@ public:
 		HITTED=3,
 		DEFENCE=4,
 		DEATH=5,
-		//DEATH2=6, //unused?
+		DEATH_RANGED=6,
 		TURN_L=7,
 		TURN_R=8, //same
 		//TURN_L2=9, //identical to previous?
@@ -197,8 +197,16 @@ public:
 		CAST_DOWN=19,
 		MOVE_START=20,
 		MOVE_END=21,
-		DEAD = 22 // new group, used to show dead stacks. If empty - last frame from "DEATH" will be copied here
 
+		DEAD = 22, // new group, used to show dead stacks. If empty - last frame from "DEATH" will be copied here
+		DEAD_RANGED = 23, // new group, used to show dead stacks (if DEATH_RANGED was used). If empty - last frame from "DEATH_RANGED" will be copied here
+
+		VCMI_CAST_UP    = 30,
+		VCMI_CAST_FRONT = 31,
+		VCMI_CAST_DOWN  = 32,
+		VCMI_2HEX_UP    = 40,
+		VCMI_2HEX_FRONT = 41,
+		VCMI_2HEX_DOWN  = 42
 	};
 
 private:

+ 3 - 0
lib/NetPacks.h

@@ -1636,10 +1636,12 @@ struct BattleSpellCast : public CPackForClient
 		manaGained = 0;
 		casterStack = -1;
 		castByHero = true;
+		activeCast = true;
 	};
 	DLL_LINKAGE void applyGs(CGameState *gs);
 	void applyCl(CClient *cl);
 
+	bool activeCast;
 	ui8 side; //which hero did cast spell: 0 - attacker, 1 - defender
 	ui32 id; //id of spell
 	ui8 skill; //caster's skill level
@@ -1663,6 +1665,7 @@ struct BattleSpellCast : public CPackForClient
 		h & casterStack;
 		h & castByHero;
 		h & battleLog;
+		h & activeCast;
 	}
 };
 

+ 4 - 0
lib/spells/CDefaultSpellMechanics.cpp

@@ -199,6 +199,10 @@ void SpellCastContext::beforeCast()
 			sc.manaGained = (manaChannel * spellCost) / 100;
 		}
 	}
+
+	sc.activeCast = parameters.mode == ECastingMode::HERO_CASTING ||
+		parameters.mode == ECastingMode::CREATURE_ACTIVE_CASTING ||
+		parameters.mode == ECastingMode::ENCHANTER_CASTING;
 }
 
 void SpellCastContext::afterCast()