Browse Source

Implemented configurable hit/affect animation
* need more testing

AlexVinS 11 years ago
parent
commit
75b93b070d

+ 69 - 72
client/battle/CBattleAnimations.cpp

@@ -20,6 +20,7 @@
 #include "../../lib/BattleState.h"
 #include "../../lib/CTownHandler.h"
 #include "../../lib/mapObjects/CGTownInstance.h"
+#include "../../lib/CSpellHandler.h"
 
 /*
  * CBattleAnimations.cpp, part of VCMI engine
@@ -865,83 +866,81 @@ void CShootingAnimation::endAnim()
 	delete this;
 }
 
-CSpellEffectAnimation::CSpellEffectAnimation(CBattleInterface * _owner, ui32 _effect, BattleHex _destTile, int _dx, int _dy, bool _Vflip, bool _areaEffect)
-:CBattleAnimation(_owner), effect(_effect), destTile(_destTile), customAnim(""), x(0), y(0), dx(_dx), dy(_dy), Vflip(_Vflip) , areaEffect(_areaEffect)
+CSpellEffectAnimation::CSpellEffectAnimation(CBattleInterface * _owner, ui32 _effect, BattleHex _destTile, int _dx, int _dy, bool _Vflip, bool _alignToBottom)
+	:CBattleAnimation(_owner), effect(_effect), destTile(_destTile), customAnim(""), x(-1), y(-1), dx(_dx), dy(_dy), Vflip(_Vflip), alignToBottom(_alignToBottom)
 {
 	logAnim->debugStream() << "Created spell anim for effect #" << effect;
 }
 
-CSpellEffectAnimation::CSpellEffectAnimation(CBattleInterface * _owner, std::string _customAnim, int _x, int _y, int _dx, int _dy, bool _Vflip, bool _areaEffect)
-:CBattleAnimation(_owner), effect(-1), destTile(0), customAnim(_customAnim), x(_x), y(_y), dx(_dx), dy(_dy), Vflip(_Vflip), areaEffect(_areaEffect)
+CSpellEffectAnimation::CSpellEffectAnimation(CBattleInterface * _owner, std::string _customAnim, int _x, int _y, int _dx, int _dy, bool _Vflip, bool _alignToBottom)
+	:CBattleAnimation(_owner), effect(-1), destTile(BattleHex::INVALID), customAnim(_customAnim), x(_x), y(_y), dx(_dx), dy(_dy), Vflip(_Vflip), alignToBottom(_alignToBottom)
 {
 	logAnim->debugStream() << "Created spell anim for " << customAnim;
 }
 
+CSpellEffectAnimation::CSpellEffectAnimation(CBattleInterface * _owner, std::string _customAnim, BattleHex _destTile, bool _Vflip, bool _alignToBottom)
+	:CBattleAnimation(_owner), effect(-1), destTile(_destTile), customAnim(_customAnim), x(-1), y(-1), dx(0), dy(0), Vflip(_Vflip), alignToBottom(_alignToBottom)
+{
+	logAnim->debugStream() << "Created spell anim for " << customAnim;	
+}
+
+
 bool CSpellEffectAnimation::init()
 {
 	if(!isEarliest(true))
 		return false;
-
-	if(effect == 12) //armageddon
+		
+	if(customAnim.empty() && effect != ui32(-1) && !graphics->battleACToDef[effect].empty())
+	{		
+		customAnim = graphics->battleACToDef[effect][0];
+	}
+	
+	if(customAnim.empty())
 	{
-		if(effect == -1 || graphics->battleACToDef[effect].size() != 0)
-		{
-			CDefHandler * anim;
-			if(customAnim.size())
-				anim = CDefHandler::giveDef(customAnim);
-			else
-				anim = CDefHandler::giveDef(graphics->battleACToDef[effect][0]);
+		endAnim();
+		return false;		
+	}
+	
+	const bool areaEffect = (!destTile.isValid() && x == -1 && y == -1);
 
-			if (Vflip)
-			{
-				for (auto & elem : anim->ourImages)
-				{
-					CSDL_Ext::VflipSurf(elem.bitmap);
-				}
-			}
+	if(areaEffect) //f.e. armageddon 
+	{
+		CDefHandler * anim = CDefHandler::giveDef(customAnim);
 
-			for(int i=0; i * anim->width < owner->pos.w ; ++i)
+		for(int i=0; i * anim->width < owner->pos.w ; ++i)
+		{
+			for(int j=0; j * anim->height < owner->pos.h ; ++j)
 			{
-				for(int j=0; j * anim->height < owner->pos.h ; ++j)
+				BattleEffect be;
+				be.effectID = ID;
+				be.anim = CDefHandler::giveDef(customAnim);
+				if (Vflip)
 				{
-					BattleEffect be;
-					be.effectID = ID;
-					be.anim = CDefHandler::giveDef(graphics->battleACToDef[effect][0]);
-					if (Vflip)
+					for (auto & elem : be.anim->ourImages)
 					{
-						for (auto & elem : be.anim->ourImages)
-						{
-							CSDL_Ext::VflipSurf(elem.bitmap);
-						}
+						CSDL_Ext::VflipSurf(elem.bitmap);
 					}
-					be.currentFrame = 0;
-					be.maxFrame = be.anim->ourImages.size();
-					be.x = i * anim->width + owner->pos.x;
-					be.y = j * anim->height + owner->pos.y;
-					be.position = BattleHex::INVALID;
-
-					owner->battleEffects.push_back(be);
 				}
+				be.currentFrame = 0;
+				be.maxFrame = be.anim->ourImages.size();
+				be.x = i * anim->width + owner->pos.x;
+				be.y = j * anim->height + owner->pos.y;
+				be.position = BattleHex::INVALID;
+
+				owner->battleEffects.push_back(be);
 			}
 		}
-		else //there is nothing to play
-		{
-			endAnim();
-			return false;
-		}
+		
+		delete anim;
 	}
 	else // Effects targeted at a specific creature/hex.
 	{
-		if(effect == -1 || graphics->battleACToDef[effect].size() != 0)
-		{
+
 			const CStack* destStack = owner->getCurrentPlayerInterface()->cb->battleGetStackByPos(destTile, false);
 			Rect &tilePos = owner->bfield[destTile]->pos;
 			BattleEffect be;
 			be.effectID = ID;
-			if(customAnim.size())
-				be.anim = CDefHandler::giveDef(customAnim);
-			else
-				be.anim = CDefHandler::giveDef(graphics->battleACToDef[effect][0]);
+			be.anim = CDefHandler::giveDef(customAnim);
 
 			if (Vflip)
 			{
@@ -953,28 +952,31 @@ bool CSpellEffectAnimation::init()
 
 			be.currentFrame = 0;
 			be.maxFrame = be.anim->ourImages.size();
-			if(effect == 1)
-				be.maxFrame = 3;
+			
+			//todo: lightning anim frame count override
+			
+//			if(effect == 1)
+//				be.maxFrame = 3;
 
-			switch (effect)
+			if(x == -1)
+			{
+				be.x = tilePos.x + tilePos.w/2 - be.anim->width/2;
+			}
+			else
 			{
-			case ui32(-1):
 				be.x = x;
+			}
+			
+			if(y == -1)
+			{
+				if(alignToBottom)
+					be.y = tilePos.y + tilePos.h - be.anim->height;
+				else
+					be.y = tilePos.y - be.anim->height/2;
+			}
+			else
+			{
 				be.y = y;
-				break;
-			case 0: // Prayer and Lightning Bolt.
-			case 1:
-			case 19: // Slow
-				// Position effect with it's bottom center touching the bottom center of affected tile(s).
-				be.x = tilePos.x + tilePos.w/2 - be.anim->width/2;
-				be.y = tilePos.y + tilePos.h - be.anim->height;
-				break;
-
-			default:
-				// Position effect with it's center touching the top center of affected tile(s).
-				be.x = tilePos.x + tilePos.w/2 - be.anim->width/2;
-				be.y = tilePos.y - be.anim->height/2;
-				break;
 			}
 
 			// Correction for 2-hex creatures.
@@ -988,12 +990,7 @@ bool CSpellEffectAnimation::init()
 				be.position = destTile;
 
 			owner->battleEffects.push_back(be);
-		}
-		else //there is nothing to play
-		{
-			endAnim();
-			return false;
-		}
+
 	}
 	//battleEffects 
 	return true;

+ 4 - 3
client/battle/CBattleAnimations.h

@@ -223,13 +223,14 @@ private:
 	std::string	customAnim;
 	int	x, y, dx, dy;
 	bool Vflip;
-	bool areaEffect;
+	bool alignToBottom;
 public:
 	bool init();
 	void nextFrame();
 	void endAnim();
 
-	CSpellEffectAnimation(CBattleInterface * _owner, ui32 _effect, BattleHex _destTile, int _dx = 0, int _dy = 0, bool _Vflip = false, bool _areaEffect = true);
-	CSpellEffectAnimation(CBattleInterface * _owner, std::string _customAnim, int _x, int _y, int _dx = 0, int _dy = 0, bool _Vflip = false, bool _areaEffect = true);
+	CSpellEffectAnimation(CBattleInterface * _owner, ui32 _effect, BattleHex _destTile, int _dx = 0, int _dy = 0, bool _Vflip = false, bool _alignToBottom = false);
+	CSpellEffectAnimation(CBattleInterface * _owner, std::string _customAnim, int _x, int _y, int _dx = 0, int _dy = 0, bool _Vflip = false, bool _alignToBottom = false);
+	CSpellEffectAnimation(CBattleInterface * _owner, std::string _customAnim, BattleHex _destTile, bool _Vflip = false, bool _alignToBottom = false);
 	virtual ~CSpellEffectAnimation(){};
 };

+ 22 - 65
client/battle/CBattleInterface.cpp

@@ -1228,8 +1228,6 @@ void CBattleInterface::spellCast( const BattleSpellCast * sc )
 {
 	const CSpell &spell = *CGI->spellh->objects[sc->id];
 
-	std::vector< std::string > anims; //for magic arrow and ice bolt
-
 	const std::string& castSoundPath = spell.getCastSound();
 	
 	if(!castSoundPath.empty())
@@ -1243,6 +1241,7 @@ void CBattleInterface::spellCast( const BattleSpellCast * sc )
 	//playing projectile animation
 	if(sc->tile.isValid())
 	{	
+		
 		//todo: srccoord of creature caster	
 		Point srccoord = (sc->side ? Point(770, 60) : Point(30, 60)) + pos;
 		Point destcoord = CClickableHex::getXYUnitAnim(sc->tile, curInt->cb->battleGetStackByPos(sc->tile), this); //position attacked by projectile
@@ -1275,58 +1274,31 @@ void CBattleInterface::spellCast( const BattleSpellCast * sc )
 	waitForAnims();
 	
 	//queuing hit animation
-	if(sc->tile.isValid())
-	{
-		for(const CSpell::TAnimation & animation : spell.animationInfo.hit)
-		{
-			//Point destcoord = CClickableHex::getXYUnitAnim(sc->tile, curInt->cb->battleGetStackByPos(sc->tile), this); //position attacked by projectile
-			
-			//addNewAnim(new CSpellEffectAnimation(this, animation, destTile, 0, 0, false, areaEffect));
-		}
-	}
-	else
-	{
-		//whole battlefield
+	for(const CSpell::TAnimation & animation : spell.animationInfo.hit)
+	{			
+		addNewAnim(new CSpellEffectAnimation(this, animation.resourceName, sc->tile, false, animation.verticalPosition == VerticalPosition::BOTTOM));
 	}
 	
-	//queuing affect animation	
+	//queuing affect /resist animation	
 	for (auto & elem : sc->affectedCres) 
 	{
-		for(const CSpell::TAnimation & animation : spell.animationInfo.affect)
+		BattleHex position = curInt->cb->battleGetStackByID(elem, false)->position;
+		
+		if(vstd::contains(sc->resisted,elem))
 		{
-			//Point destcoord = CClickableHex::getXYUnitAnim(sc->tile, curInt->cb->battleGetStackByPos(sc->tile), this); //position attacked by projectile
-			
-			//addNewAnim(new CSpellEffectAnimation(this, animation, destTile, 0, 0, false, areaEffect));
+			displayEffect(78, position);
+		}
+		else
+		{		
+			for(const CSpell::TAnimation & animation : spell.animationInfo.affect)
+			{				
+				addNewAnim(new CSpellEffectAnimation(this, animation.resourceName, position, false, animation.verticalPosition == VerticalPosition::BOTTOM));
+			}			
 		}
-		//displayEffect(spell.mainEffectAnim, curInt->cb->battleGetStackByID(elem, false)->position);
 	}
 
-	
-
 	switch(sc->id)
 	{
-
-	case SpellID::LIGHTNING_BOLT:
-	case SpellID::TITANS_LIGHTNING_BOLT:
-	case SpellID::THUNDERBOLT:
-	case SpellID::CHAIN_LIGHTNING: //TODO: zigzag effect
-		for (auto & elem : sc->affectedCres) //in case we have multiple targets
-		{
-			displayEffect(1, curInt->cb->battleGetStackByID(elem, false)->position);
-			displayEffect(spell.mainEffectAnim, curInt->cb->battleGetStackByID(elem, false)->position);
-		}
-		break;
-	case SpellID::DISPEL:
-	case SpellID::CURE:
-	case SpellID::RESURRECTION:
-	case SpellID::ANIMATE_DEAD:
-	case SpellID::DISPEL_HELPFUL_SPELLS:
-	case SpellID::SACRIFICE: //TODO: animation upon killed stack
-		for(auto & elem : sc->affectedCres)
-		{
-			displayEffect(spell.mainEffectAnim, curInt->cb->battleGetStackByID(elem, false)->position);
-		}
-		break;
 	case SpellID::SUMMON_FIRE_ELEMENTAL:
 	case SpellID::SUMMON_EARTH_ELEMENTAL:
 	case SpellID::SUMMON_WATER_ELEMENTAL:
@@ -1337,18 +1309,12 @@ void CBattleInterface::spellCast( const BattleSpellCast * sc )
 		break;
 	} //switch(sc->id)
 
-	if (spell.isDamageSpell() && sc->affectedCres.empty()) //for example Inferno that causes no BattleStackAttacked
-	{
-		if(sc->tile.isValid() && graphics->battleACToDef.count(spell.mainEffectAnim)) //eg. when casting Lind Mine or Fire Wall
-			displayEffect (spell.mainEffectAnim, sc->tile);
-	}
+//	if (spell.isDamageSpell() && sc->affectedCres.empty()) //for example Inferno that causes no BattleStackAttacked
+//	{
+//		if(sc->tile.isValid() && graphics->battleACToDef.count(spell.mainEffectAnim)) //eg. when casting Lind Mine or Fire Wall
+//			displayEffect (spell.mainEffectAnim, sc->tile);
+//	}
 
-	//support for resistance
-	for(auto & elem : sc->resisted)
-	{
-		int tile = curInt->cb->battleGetStackByID(elem)->position;
-		displayEffect(78, tile);
-	}
 
 	//displaying message in console
 	bool customSpell = false;
@@ -1511,16 +1477,7 @@ void CBattleInterface::spellCast( const BattleSpellCast * sc )
 
 void CBattleInterface::battleStacksEffectsSet(const SetStackEffect & sse)
 {
-	int effID = sse.effect.back().sid;
-	if(effID != -1) //can be -1 for defensive stance effect
-	{
-		for(auto & elem : sse.stacks)
-		{
-			bool areaEffect(CGI->spellh->objects[effID]->getTargetType() == CSpell::ETargetType::NO_TARGET);
-			displayEffect(CGI->spellh->objects[effID]->mainEffectAnim, curInt->cb->battleGetStackByID(elem)->position, areaEffect);
-		}
-	}
-	else if (sse.stacks.size() == 1 && sse.effect.size() == 2)
+	if (sse.effect.back().sid == -1 && sse.stacks.size() == 1 && sse.effect.size() == 2)
 	{
 		const Bonus & bns = sse.effect.front();
 		if (bns.source == Bonus::OTHER && bns.type == Bonus::PRIMARY_SKILL)

+ 15 - 2
config/schemas/spell.json

@@ -11,8 +11,21 @@
 		"animationQueue":{
 			"type": "array",
 			"items":{
-				"type": "string",
-				"format": "defFile"						
+				"anyOf":[
+					{
+						//assumed verticalPosition: top
+						"type": "string",
+						"format": "defFile"				
+					},
+					{
+						"type": "object",
+						"properties":{
+							"verticalPosition": {"type":"string", "enum":["top","bottom"]},
+							"defName": {"type":"string", "format": "defFile"}
+						},					
+						"additionalProperties" : false				
+					}
+				]					
 			}			
 		},
 		"animation":{

+ 3 - 3
config/spells/offensive.json

@@ -68,7 +68,7 @@
 		"targetType": "CREATURE",
 		"anim" : 38,
 		"animation":{
-			"hit":["C03SPA0", "C11SPA1"]
+			"hit":[{"defName":"C03SPA0", "verticalPosition":"bottom"}, "C11SPA1"]
 		},		
 		"sounds": {
 			"cast": "LIGHTBLT"
@@ -119,7 +119,7 @@
 		"targetType": "CREATURE",
 		"anim" : 38,
 		"animation":{
-			"affect":["C03SPA0", "C11SPA1"]
+			"affect":[{"defName":"C03SPA0", "verticalPosition":"bottom"}, "C11SPA1"]
 		},		
 		"sounds": {
 			"cast": "CHAINLTE"
@@ -323,7 +323,7 @@
 		"targetType" : "CREATURE",
 		"anim" : 38,
 		"animation":{
-			"hit":["C03SPA0", "C11SPA1"]
+			"hit":[{"defName":"C03SPA0", "verticalPosition":"bottom"}, "C11SPA1"]
 		},		
 		"sounds": {
 			"cast": "LIGHTBLT"

+ 2 - 2
config/spells/timed.json

@@ -591,7 +591,7 @@
 		"targetType" : "CREATURE",
 		"anim" : 0,
 		"animation":{
-			"affect":["C10SPW"]
+			"affect":[{"defName":"C10SPW", "verticalPosition":"bottom"}]
 		},		
 		"sounds": {
 			"cast": "PRAYER"
@@ -901,7 +901,7 @@
 		"targetType" : "CREATURE",
 		"anim" : 19,
 		"animation":{
-			"affect":["C09SPE0"]
+			"affect":[{"defName":"C09SPE0", "verticalPosition":"bottom"}]
 		},		
 		"sounds": {
 			"cast": "MUCKMIRE"

+ 28 - 5
lib/CSpellHandler.cpp

@@ -570,7 +570,7 @@ std::string CSpell::AnimationInfo::selectProjectile(const double angle) const
 		if(info.minimumAngle < angle && info.minimumAngle > maximum)
 		{
 			maximum = info.minimumAngle;
-			res = info.defName;
+			res = info.resourceName;
 		}
 	}
 	
@@ -886,16 +886,39 @@ CSpell * CSpellHandler::loadFromJson(const JsonNode& json)
 	spell->iconScroll = graphicsNode["iconScroll"].String();
 
 	const JsonNode & animationNode = json["animation"];
-	spell->animationInfo.affect = animationNode["affect"].convertTo<CSpell::TAnimationQueue>();
-	spell->animationInfo.cast = animationNode["cast"].convertTo<CSpell::TAnimationQueue>();
-	spell->animationInfo.hit = animationNode["hit"].convertTo<CSpell::TAnimationQueue>();
+	
+	auto loadAnimationQueue = [&](const std::string & jsonName, CSpell::TAnimationQueue & q)	
+	{
+		auto queueNode = animationNode[jsonName].Vector();		
+		for(const JsonNode & item : queueNode)
+		{	
+			CSpell::TAnimation newItem;
+			newItem.verticalPosition = VerticalPosition::TOP;
+			
+			if(item.getType() == JsonNode::DATA_STRING)
+				newItem.resourceName = item.String();
+			else if(item.getType() == JsonNode::DATA_STRUCT)
+			{
+				newItem.resourceName = item["defName"].String();
+				
+				auto vPosStr = item["verticalPosition"].String();
+				if("bottom" == vPosStr)
+					newItem.verticalPosition = VerticalPosition::BOTTOM;
+			}	
+			q.push_back(newItem);		
+		}
+	};
+	
+	loadAnimationQueue("affect", spell->animationInfo.affect);
+	loadAnimationQueue("cast", spell->animationInfo.cast);
+	loadAnimationQueue("hit", spell->animationInfo.hit);	
 	
 	const JsonVector & projectile = animationNode["projectile"].Vector();
 	
 	for(const JsonNode & item : projectile)
 	{
 		CSpell::ProjectileInfo info;
-		info.defName = item["defName"].String();
+		info.resourceName = item["defName"].String();
 		info.minimumAngle = item["minimumAngle"].Float();
 		
 		spell->animationInfo.projectile.push_back(info);

+ 15 - 3
lib/CSpellHandler.h

@@ -73,6 +73,7 @@ public:
 	const BattleInfo * cb;		
 };
 
+enum class VerticalPosition : ui8{TOP, CENTER, BOTTOM};
 
 class DLL_LINKAGE CSpell
 {
@@ -84,15 +85,26 @@ public:
 		double minimumAngle; 
 		
 		///resource name
-		std::string defName;
+		std::string resourceName;
 		 
 		template <typename Handler> void serialize(Handler &h, const int version)
 		{
-			h & minimumAngle & defName; 
+			h & minimumAngle & resourceName; 
 		}		
 	};
 	
-	typedef std::string TAnimation;
+	struct AnimationItem
+	{
+		std::string resourceName;
+		VerticalPosition verticalPosition;
+		
+		template <typename Handler> void serialize(Handler &h, const int version)
+		{
+			h & resourceName & verticalPosition; 
+		}		
+	};
+	
+	typedef AnimationItem TAnimation;
 	typedef std::vector<TAnimation> TAnimationQueue; 
 	
 	struct DLL_LINKAGE AnimationInfo