Przeglądaj źródła

more improvements for battle animations
- synchronized attack/defence animation
- spell animation speed uses game settings
- added logging domain for battle animations

- fixed disrupting ray duration

Ivan Savenko 12 lat temu
rodzic
commit
1a77fee7f7

+ 1 - 1
client/CPlayerInterface.cpp

@@ -866,7 +866,7 @@ void CPlayerInterface::battleStacksAttacked(const std::vector<BattleStackAttacke
 			if (defender && !elem.isSecondary())
 				battleInt->displayEffect(elem.effect, defender->position);
 		}
-		bool shooting = (LOCPLINT->curAction ? LOCPLINT->curAction->actionType == Battle::SHOOT : false); //FIXME: why action is deleted during enchanter cast?
+		bool shooting = (LOCPLINT->curAction ? LOCPLINT->curAction->actionType != Battle::WALK_AND_ATTACK : false); //FIXME: why action is deleted during enchanter cast?
 		StackAttackedInfo to_put = {defender, elem.damageAmount, elem.killedAmount, attacker, shooting, elem.killed(), elem.willRebirth(), elem.cloneKilled()};
 		arg.push_back(to_put);
 	}

+ 85 - 29
client/battle/CBattleAnimations.cpp

@@ -32,10 +32,18 @@
 
 CBattleAnimation::CBattleAnimation(CBattleInterface * _owner)
     : owner(_owner), ID(_owner->animIDhelper++)
-{}
+{
+	logAnim->traceStream() << "Animation #" << ID << " created";
+}
+
+CBattleAnimation::~CBattleAnimation()
+{
+	logAnim->traceStream() << "Animation #" << ID << " deleted";
+}
 
 void CBattleAnimation::endAnim()
 {
+	logAnim->traceStream() << "Animation #" << ID << " ended, type is " << typeid(this).name();
 	for(auto & elem : owner->pendingAnims)
 	{
 		if(elem.first == this)
@@ -58,7 +66,7 @@ bool CBattleAnimation::isEarliest(bool perStackConcurrency)
 		if(perStackConcurrency && stAnim && thAnim && stAnim->stack->ID != thAnim->stack->ID)
 			continue;
 
-		if(sen && thSen && sen != thSen && perStackConcurrency)
+		if(perStackConcurrency && sen && thSen && sen != thSen)
 			continue;
 
 		CReverseAnimation * revAnim = dynamic_cast<CReverseAnimation *>(stAnim);
@@ -105,6 +113,17 @@ void CAttackAnimation::endAnim()
 
 bool CAttackAnimation::checkInitialConditions()
 {
+	for(auto & elem : owner->pendingAnims)
+	{
+		CBattleStackAnimation * stAnim = dynamic_cast<CBattleStackAnimation *>(elem.first);
+		CReverseAnimation * revAnim = dynamic_cast<CReverseAnimation *>(stAnim);
+
+		if(revAnim) // enemy must be fully reversed
+		{
+			if (revAnim->stack->ID == attackedStack->ID)
+				return false;
+		}
+	}
 	return isEarliest(false);
 }
 
@@ -124,7 +143,7 @@ CAttackAnimation::CAttackAnimation(CBattleInterface *_owner, const CStack *attac
 
 CDefenceAnimation::CDefenceAnimation(StackAttackedInfo _attackedInfo, CBattleInterface * _owner)
 : CBattleStackAnimation(_owner, _attackedInfo.defender),
-attacker(_attackedInfo.attacker), byShooting(_attackedInfo.byShooting),
+attacker(_attackedInfo.attacker), rangedAttack(_attackedInfo.rangedAttack),
 killed(_attackedInfo.killed) 
 {}
 
@@ -144,23 +163,15 @@ bool CDefenceAnimation::init()
 		if(attAnim && attAnim->stack->ID != stack->ID)
 			continue;
 
-		if(attacker != nullptr)
-		{
-			int attackerAnimType = owner->creAnims[attacker->ID]->getType();
-			if( ( attackerAnimType == CCreatureAnim::ATTACK_UP ||
-			    attackerAnimType == CCreatureAnim::ATTACK_FRONT ||
-			    attackerAnimType == CCreatureAnim::ATTACK_DOWN ) )
-				return false;
-		}
-
 		CReverseAnimation * animAsRev = dynamic_cast<CReverseAnimation *>(elem.first);
 
-		if(animAsRev && animAsRev->priority)
+		if(animAsRev /*&& animAsRev->priority*/)
 			return false;
 
 		if(elem.first)
 			vstd::amin(lowestMoveID, elem.first->ID);
 	}
+
 	if(ID > lowestMoveID)
 		return false;
 
@@ -172,7 +183,7 @@ bool CDefenceAnimation::init()
 	}
 	//unit reversed
 
-	if(byShooting) //delay hit animation
+	if(rangedAttack) //delay hit animation
 	{		
 		for(std::list<ProjectileInfo>::const_iterator it = owner->projectiles.begin(); it != owner->projectiles.end(); ++it)
 		{
@@ -183,27 +194,61 @@ bool CDefenceAnimation::init()
 		}
 	}
 
-	//initializing
-	if(killed)
+	// synchronize animation with attacker, unless defending or attacked by shooter:
+	// wait for 1/2 of attack animation
+	if (!rangedAttack && getMyAnimType() != CCreatureAnim::DEFENCE)
 	{
-		CCS->soundh->playSound(battle_sound(stack->getCreature(), killed));
-		myAnim->setType(CCreatureAnim::DEATH); //death
+		float fps = AnimationControls::getCreatureAnimationSpeed(
+		                          stack->getCreature(), owner->creAnims[stack->ID], getMyAnimType());
+
+		timeToWait = myAnim->framesInGroup(getMyAnimType()) / fps;
+
+		myAnim->setType(CCreatureAnim::HOLDING);
 	}
 	else
 	{
-		// TODO: this block doesn't seems correct if the unit is defending.
-		CCS->soundh->playSound(battle_sound(stack->getCreature(), wince));
-		myAnim->setType(CCreatureAnim::HITTED); //getting hit
+		timeToWait = 0;
+		startAnimation();
 	}
 
-	myAnim->onAnimationReset += std::bind(&CDefenceAnimation::endAnim, this);
-
 	return true; //initialized successfuly
 }
 
+std::string CDefenceAnimation::getMySound()
+{
+	if(killed)
+		return battle_sound(stack->getCreature(), killed);
+
+	if (stack->valOfBonuses(Selector::durationType(Bonus::STACK_GETS_TURN)))
+		return battle_sound(stack->getCreature(), defend);
+	return battle_sound(stack->getCreature(), wince);
+}
+
+CCreatureAnim::EAnimType CDefenceAnimation::getMyAnimType()
+{
+	if(killed)
+		return CCreatureAnim::DEATH;
+
+	if (stack->valOfBonuses(Selector::durationType(Bonus::STACK_GETS_TURN)))
+		return CCreatureAnim::DEFENCE;
+	return CCreatureAnim::HITTED;
+}
+
+void CDefenceAnimation::startAnimation()
+{
+	CCS->soundh->playSound(getMySound());
+	myAnim->setType(getMyAnimType());
+	myAnim->onAnimationReset += std::bind(&CDefenceAnimation::endAnim, this);
+}
+
 void CDefenceAnimation::nextFrame()
 {
-	assert(myAnim->getType() == CCreatureAnim::HITTED || myAnim->getType() == CCreatureAnim::DEATH);
+	if (timeToWait > 0)
+	{
+		timeToWait -= float(GH.mainFPSmng->getElapsedMilliseconds()) / 1000;
+		if (timeToWait <= 0)
+			startAnimation();
+	}
 
 	CBattleAnimation::nextFrame();
 }
@@ -262,6 +307,12 @@ bool CMeleeAttackAnimation::init()
 		owner->addNewAnim(new CReverseAnimation(owner, stack, attackingStackPosBeforeReturn, true));
 		return false;
 	}
+
+	// opponent must face attacker ( = different directions) before he can be attacked
+	if (attackingStack && attackedStack &&
+	    owner->creDir[attackingStack->ID] == owner->creDir[attackedStack->ID])
+		return false;
+
 	//reversed
 
 	shooting = false;
@@ -386,7 +437,7 @@ bool CMovementAnimation::init()
 	{
 		float distance = sqrt(distanceX * distanceX + distanceY * distanceY);
 
-		timeToMove *= distance / AnimationControls::getFlightDistance(stack->getCreature());
+		timeToMove *= AnimationControls::getFlightDistance(stack->getCreature()) / distance;
 	}
 
 	return true;
@@ -614,6 +665,11 @@ bool CShootingAnimation::init()
 		return false;
 	}
 
+	// opponent must face attacker ( = different directions) before he can be attacked
+	if (attackingStack && attackedStack &&
+	    owner->creDir[attackingStack->ID] == owner->creDir[attackedStack->ID])
+		return false;
+
 	// Create the projectile animation
 
 	//maximal angle in radians between straight horizontal line and shooting line for which shot is considered to be straight (absoulte value)
@@ -817,7 +873,7 @@ bool CSpellEffectAnimation::init()
 							CSDL_Ext::VflipSurf(elem.bitmap);
 						}
 					}
-					be.frame = 0;
+					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;
@@ -854,7 +910,7 @@ bool CSpellEffectAnimation::init()
 				}
 			}
 
-			be.frame = 0;
+			be.currentFrame = 0;
 			be.maxFrame = be.anim->ourImages.size();
 			if(effect == 1)
 				be.maxFrame = 3;
@@ -909,9 +965,9 @@ void CSpellEffectAnimation::nextFrame()
 	{
 		if(elem.effectID == ID)
 		{
-			++(elem.frame);
+			elem.currentFrame += AnimationControls::getSpellEffectSpeed() * GH.mainFPSmng->getElapsedMilliseconds() / 1000;
 
-			if(elem.frame == elem.maxFrame)
+			if(elem.currentFrame >= elem.maxFrame)
 			{
 				endAnim();
 				break;

+ 9 - 4
client/battle/CBattleAnimations.h

@@ -28,7 +28,7 @@ public:
 	virtual bool init() = 0; //to be called - if returned false, call again until returns true
 	virtual void nextFrame() {} //call every new frame
 	virtual void endAnim(); //to be called mostly internally; in this class it removes animation from pendingAnims list
-	virtual ~CBattleAnimation(){}
+	virtual ~CBattleAnimation();
 
 	bool isEarliest(bool perStackConcurrency); //determines if this animation is earliest of all
 
@@ -69,11 +69,16 @@ public:
 /// Animation of a defending unit
 class CDefenceAnimation : public CBattleStackAnimation
 {
-private:
-	//std::vector<StackAttackedInfo> attackedInfos;
+	CCreatureAnim::EAnimType getMyAnimType();
+	std::string getMySound();
+
+	void startAnimation();
+
 	const CStack * attacker; //attacking stack
-	bool byShooting; //if true, stack has been attacked by shooting
+	bool rangedAttack; //if true, stack has been attacked by shooting
 	bool killed; //if true, stack has been killed
+
+	float timeToWait; // for how long this animation should be paused
 public:
 	bool init();
 	void nextFrame();

+ 12 - 13
client/battle/CBattleInterface.cpp

@@ -1010,7 +1010,10 @@ void CBattleInterface::showBattleEffects(const std::vector<const BattleEffect *>
 {
 	for(auto & elem : battleEffects)
 	{
-		SDL_Surface * bitmapToBlit = elem->anim->ourImages[(elem->frame)%elem->anim->ourImages.size()].bitmap;
+		int currentFrame = floor(elem->currentFrame);
+		currentFrame %= elem->anim->ourImages.size();
+
+		SDL_Surface * bitmapToBlit = elem->anim->ourImages[currentFrame].bitmap;
 		SDL_Rect temp_rect = genRect(bitmapToBlit->h, bitmapToBlit->w, elem->x, elem->y);
 		SDL_BlitSurface(bitmapToBlit, nullptr, to, &temp_rect);
 	}
@@ -1520,16 +1523,11 @@ void CBattleInterface::stackAttacking( const CStack * attacker, BattleHex dest,
 	{
 		addNewAnim(new CMeleeAttackAnimation(this, attacker, dest, attacked));
 	}
-	waitForAnims();
+	//waitForAnims();
 }
 
 void CBattleInterface::newRoundFirst( int round )
 {
-	//handle regeneration
-	std::vector<const CStack*> stacks = curInt->cb->battleGetStacks(); //gets only alive stacks
-//	for(const CStack *s : stacks)
-//	{
-//	}
 	waitForAnims();
 }
 
@@ -1725,12 +1723,13 @@ void CBattleInterface::spellCast( const BattleSpellCast * sc )
 
 			//displaying animation
 			CDefEssential * animDef = CDefHandler::giveDefEss(animToDisplay);
+			double diffX = (destcoord.x - srccoord.x)*(destcoord.x - srccoord.x);
+			double diffY = (destcoord.y - srccoord.y)*(destcoord.y - srccoord.y);
+			double distance = sqrt(diffX + diffY);
 
-			int steps = sqrt(static_cast<double>((destcoord.x - srccoord.x)*(destcoord.x - srccoord.x) + (destcoord.y - srccoord.y) * (destcoord.y - srccoord.y))) / 40;
-			if(steps <= 0)
-				steps = 1;
-
-			int dx = (destcoord.x - srccoord.x - animDef->ourImages[0].bitmap->w)/steps, dy = (destcoord.y - srccoord.y - animDef->ourImages[0].bitmap->h)/steps;
+			int steps = distance / AnimationControls::getSpellEffectSpeed() + 1;
+			int dx = (destcoord.x - srccoord.x - animDef->ourImages[0].bitmap->w)/steps;
+			int dy = (destcoord.y - srccoord.y - animDef->ourImages[0].bitmap->h)/steps;
 
 			delete animDef;
 			addNewAnim(new CSpellEffectAnimation(this, animToDisplay, srccoord.x, srccoord.y, dx, dy, Vflip));
@@ -2614,7 +2613,7 @@ void CBattleInterface::showQueue()
 
 void CBattleInterface::startAction(const BattleAction* action)
 {
-	setActiveStack(nullptr);
+	//setActiveStack(nullptr);
 	setHoveredStack(nullptr);
 
 	if(action->actionType == Battle::END_TACTIC_PHASE)

+ 3 - 2
client/battle/CBattleInterface.h

@@ -57,7 +57,7 @@ struct StackAttackedInfo
 	unsigned int dmg; //damage dealt
 	unsigned int amountKilled; //how many creatures in stack has been killed
 	const CStack * attacker; //attacking stack
-	bool byShooting; //if true, stack has been attacked by shooting
+	bool rangedAttack; //if true, stack has been attacked by shooting
 	bool killed; //if true, stack has been killed
 	bool rebirth; //if true, play rebirth animation after all
 	bool cloneKilled;
@@ -67,7 +67,8 @@ struct StackAttackedInfo
 struct BattleEffect
 {
 	int x, y; //position on the screen
-	int frame, maxFrame;
+	float currentFrame;
+	int maxFrame;
 	CDefHandler * anim; //animation to display
 	int effectID; //uniqueID equal ot ID of appropriate CSpellEffectAnim
 	BattleHex position; //Indicates if effect which hex the effect is drawn on

+ 20 - 12
client/battle/CCreatureAnimation.cpp

@@ -48,11 +48,8 @@ CCreatureAnimation * AnimationControls::getAnimation(const CCreature * creature)
 
 float AnimationControls::getCreatureAnimationSpeed(const CCreature * creature, const CCreatureAnimation * anim, size_t group)
 {
-	// possible new fields for creature format
-	//Shoot Animation Time
-	//Cast Animation Time
-	//Defence and/or Death Animation Time
-
+	// possible new fields for creature format:
+	//split "Attack time" into "Shoot Time" and "Cast Time"
 
 	// a lot of arbitrary multipliers, mostly to make animation speed closer to H3
 	CCreatureAnim::EAnimType type = CCreatureAnim::EAnimType(group);
@@ -69,9 +66,6 @@ float AnimationControls::getCreatureAnimationSpeed(const CCreature * creature, c
 	case CCreatureAnim::HOLDING:
 		return baseSpeed;
 
-	case CCreatureAnim::ATTACK_UP:
-	case CCreatureAnim::ATTACK_FRONT:
-	case CCreatureAnim::ATTACK_DOWN:
 	case CCreatureAnim::SHOOT_UP:
 	case CCreatureAnim::SHOOT_FRONT:
 	case CCreatureAnim::SHOOT_DOWN:
@@ -80,6 +74,18 @@ float AnimationControls::getCreatureAnimationSpeed(const CCreature * creature, c
 	case CCreatureAnim::CAST_DOWN:
 		return speed * 2 / creature->animation.attackAnimationTime / anim->framesInGroup(type);
 
+	// as strange as it looks like "attackAnimationTime" does not affects melee attacks
+	// necessary because length of attack animation must be same for all creatures for synchronization
+	case CCreatureAnim::ATTACK_UP:
+	case CCreatureAnim::ATTACK_FRONT:
+	case CCreatureAnim::ATTACK_DOWN:
+	case CCreatureAnim::DEFENCE:
+		return speed * 2 / anim->framesInGroup(type);
+
+	case CCreatureAnim::DEATH:
+	case CCreatureAnim::HITTED: // time-wise equals 1/2 of attack animation length
+		return speed / anim->framesInGroup(type);
+
 	case CCreatureAnim::TURN_L:
 	case CCreatureAnim::TURN_R:
 		return speed;
@@ -88,9 +94,6 @@ float AnimationControls::getCreatureAnimationSpeed(const CCreature * creature, c
 	case CCreatureAnim::MOVE_END:
 		return speed / 5;
 
-	case CCreatureAnim::HITTED:
-	case CCreatureAnim::DEFENCE:
-	case CCreatureAnim::DEATH:
 	case CCreatureAnim::DEAD:
 		return speed / 5;
 
@@ -105,6 +108,11 @@ float AnimationControls::getProjectileSpeed()
 	return settings["battle"]["animationSpeed"].Float() * 100;
 }
 
+float AnimationControls::getSpellEffectSpeed()
+{
+	return settings["battle"]["animationSpeed"].Float() * 60;
+}
+
 float AnimationControls::getMovementDuration(const CCreature * creature)
 {
 	return settings["battle"]["animationSpeed"].Float() * 4 / creature->animation.walkAnimationTime;
@@ -201,7 +209,7 @@ bool CCreatureAnimation::incrementFrame(float timePassed)
 	currentFrame += timePassed * speed;
 	if (currentFrame >= float(framesInGroup(type)))
 	{
-		// just in case of extremely low fps
+		// just in case of extremely low fps (or insanely high speed)
 		while (currentFrame >= float(framesInGroup(type)))
 			currentFrame -= framesInGroup(type);
 

+ 4 - 1
client/battle/CCreatureAnimation.h

@@ -28,13 +28,16 @@ namespace AnimationControls
 	/// creates animation object with preset speed control
 	CCreatureAnimation * getAnimation(const CCreature * creature);
 
-	/// returns animation speed of specific group, taking in mind game setting
+	/// returns animation speed of specific group, taking in mind game setting (in frames per second)
 	float getCreatureAnimationSpeed(const CCreature * creature, const CCreatureAnimation * anim, size_t groupID);
 
 	/// returns how far projectile should move each frame
 	/// TODO: make it time-based
 	float getProjectileSpeed();
 
+	/// returns speed of any spell effects, including any special effects like morale (in frames per second)
+	float getSpellEffectSpeed();
+
 	/// returns duration of full movement animation, in seconds. Needed to move animation on screen
 	float getMovementDuration(const CCreature * creature);
 

+ 1 - 1
config/spell_info.json

@@ -560,7 +560,7 @@
 					"type": 	"PRIMARY_SKILL",
 					"subtype": "primSkill.defence",
 					"valueType": "ADDITIVE_VALUE",
-					"duration": "N_TURNS",
+					"duration": "ONE_BATTLE",
 					"values":[-3,-3,-4,-5]
 				}
 			]

+ 1 - 2
lib/logging/CLogger.cpp

@@ -54,10 +54,9 @@ boost::recursive_mutex CLogManager::smx;
 DLL_LINKAGE CLogger * logGlobal = CLogger::getGlobalLogger();
 
 DLL_LINKAGE CLogger * logBonus = CLogger::getLogger(CLoggerDomain("bonus"));
-
 DLL_LINKAGE CLogger * logNetwork = CLogger::getLogger(CLoggerDomain("network"));
-
 DLL_LINKAGE CLogger * logAi = CLogger::getLogger(CLoggerDomain("ai"));
+DLL_LINKAGE CLogger * logAnim = CLogger::getLogger(CLoggerDomain("animation"));
 
 CLogger * CLogger::getLogger(const CLoggerDomain & domain)
 {

+ 1 - 0
lib/logging/CLogger.h

@@ -125,6 +125,7 @@ extern DLL_LINKAGE CLogger * logGlobal;
 extern DLL_LINKAGE CLogger * logBonus;
 extern DLL_LINKAGE CLogger * logNetwork;
 extern DLL_LINKAGE CLogger * logAi;
+extern DLL_LINKAGE CLogger * logAnim;
 
 /// RAII class for tracing the program execution.
 /// It prints "Leaving function XYZ" automatically when the object gets destructed.