mirror of
				https://github.com/vcmi/vcmi.git
				synced 2025-10-31 00:07:39 +02:00 
			
		
		
		
	Multiple fixes & improvements to animation ordering
This commit is contained in:
		| @@ -986,91 +986,36 @@ void CPlayerInterface::battleAttack(const BattleAttack * ba) | ||||
|  | ||||
| 	assert(curAction); | ||||
|  | ||||
| 	const CStack * attacker = cb->battleGetStackByID(ba->stackAttacking); | ||||
| 	StackAttackInfo info; | ||||
| 	info.attacker = cb->battleGetStackByID(ba->stackAttacking); | ||||
| 	info.defender = nullptr; | ||||
| 	info.indirectAttack = ba->shot(); | ||||
| 	info.lucky = ba->lucky(); | ||||
| 	info.unlucky = ba->unlucky(); | ||||
| 	info.deathBlow = ba->deathBlow(); | ||||
| 	info.lifeDrain = ba->lifeDrain(); | ||||
| 	info.tile = ba->tile; | ||||
| 	info.spellEffect = SpellID::NONE; | ||||
|  | ||||
| 	if(!attacker) | ||||
| 	{ | ||||
| 		logGlobal->error("Attacking stack not found"); | ||||
| 		return; | ||||
| 	} | ||||
| 	if (ba->spellLike()) | ||||
| 		info.spellEffect = ba->spellID; | ||||
|  | ||||
| 	if(ba->lucky()) //lucky hit | ||||
| 	for(auto & elem : ba->bsa) | ||||
| 	{ | ||||
| 		battleInt->controlPanel->console->addText(attacker->formatGeneralMessage(-45)); | ||||
| 		battleInt->effectsController->displayEffect(EBattleEffect::GOOD_LUCK, soundBase::GOODLUCK, attacker->getPosition()); | ||||
| 	} | ||||
| 	if(ba->unlucky()) //unlucky hit | ||||
| 	{ | ||||
| 		battleInt->controlPanel->console->addText(attacker->formatGeneralMessage(-44)); | ||||
| 		battleInt->effectsController->displayEffect(EBattleEffect::BAD_LUCK, soundBase::BADLUCK, attacker->getPosition()); | ||||
| 	} | ||||
| 	if(ba->deathBlow()) | ||||
| 	{ | ||||
| 		battleInt->controlPanel->console->addText(attacker->formatGeneralMessage(365)); | ||||
| 		for(auto & elem : ba->bsa) | ||||
| 		if(!elem.isSecondary()) | ||||
| 		{ | ||||
| 			const CStack * attacked = cb->battleGetStackByID(elem.stackAttacked); | ||||
| 			battleInt->effectsController->displayEffect(EBattleEffect::DEATH_BLOW, attacked->getPosition()); | ||||
| 			assert(info.defender == nullptr); | ||||
| 			info.defender = cb->battleGetStackByID(elem.stackAttacked); | ||||
| 		} | ||||
| 		CCS->soundh->playSound(soundBase::deathBlow); | ||||
| 	} | ||||
|  | ||||
| 	battleInt->effectsController->displayCustomEffects(ba->customEffects); | ||||
|  | ||||
| 	battleInt->waitForAnimationCondition(EAnimationEvents::ACTION, false); | ||||
|  | ||||
| 	auto actionTarget = curAction->getTarget(cb.get()); | ||||
|  | ||||
| 	if(actionTarget.empty() || (actionTarget.size() < 2 && !ba->shot())) | ||||
| 	{ | ||||
| 		logNetwork->error("Invalid current action: no destination."); | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	if(ba->shot()) | ||||
| 	{ | ||||
| 		for(auto & elem : ba->bsa) | ||||
| 		else | ||||
| 		{ | ||||
| 			if(!elem.isSecondary()) //display projectile only for primary target | ||||
| 			{ | ||||
| 				const CStack * attacked = cb->battleGetStackByID(elem.stackAttacked); | ||||
| 				battleInt->stackAttacking(attacker, attacked->getPosition(), attacked, true); | ||||
| 				break; | ||||
| 			} | ||||
| 			info.secondaryDefender.push_back(cb->battleGetStackByID(elem.stackAttacked)); | ||||
| 		} | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		auto attackTarget = actionTarget.at(1).hexValue; | ||||
| 	assert(info.defender != nullptr); | ||||
| 	assert(info.attacker != nullptr); | ||||
|  | ||||
| 		int shift = 0; | ||||
| 		if(ba->counter() && BattleHex::mutualPosition(attackTarget, attacker->getPosition()) < 0) | ||||
| 		{ | ||||
| 			int distp = BattleHex::getDistance(attackTarget + 1, attacker->getPosition()); | ||||
| 			int distm = BattleHex::getDistance(attackTarget - 1, attacker->getPosition()); | ||||
|  | ||||
| 			if(distp < distm) | ||||
| 				shift = 1; | ||||
| 			else | ||||
| 				shift = -1; | ||||
| 		} | ||||
|  | ||||
| 		if(!ba->bsa.empty()) | ||||
| 		{ | ||||
| 			const CStack * defender = cb->battleGetStackByID(ba->bsa.begin()->stackAttacked); | ||||
| 			battleInt->stackAttacking(attacker, BattleHex(attackTarget + shift), defender, false); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	//battleInt->waitForAnimationCondition(EAnimationEvents::ACTION, false); // FIXME: freeze | ||||
|  | ||||
| 	if(ba->spellLike()) | ||||
| 	{ | ||||
| 		auto destination = actionTarget.at(0).hexValue; | ||||
| 		//display hit animation | ||||
| 		SpellID spellID = ba->spellID; | ||||
| 		battleInt->displaySpellHit(spellID, destination); | ||||
| 	} | ||||
| 	battleInt->stackAttacking(info); | ||||
| } | ||||
|  | ||||
| void CPlayerInterface::battleGateStateChanged(const EGateState state) | ||||
|   | ||||
| @@ -159,7 +159,7 @@ CDefenceAnimation::CDefenceAnimation(StackAttackedInfo _attackedInfo, BattleInte | ||||
|  | ||||
| bool CDefenceAnimation::init() | ||||
| { | ||||
| 	logAnim->info("CDefenceAnimation::init: stack %d", stack->ID); | ||||
| 	logAnim->info("CDefenceAnimation::init: stack %s", stack->getName()); | ||||
|  | ||||
| 	CCS->soundh->playSound(getMySound()); | ||||
| 	myAnim->setType(getMyAnimType()); | ||||
| @@ -231,13 +231,16 @@ void CDummyAnimation::nextFrame() | ||||
|  | ||||
| bool CMeleeAttackAnimation::init() | ||||
| { | ||||
| 	assert(attackingStack); | ||||
| 	assert(!myAnim->isDeadOrDying()); | ||||
|  | ||||
| 	if(!attackingStack || myAnim->isDeadOrDying()) | ||||
| 	{ | ||||
| 		delete this; | ||||
| 		return false; | ||||
| 	} | ||||
|  | ||||
| 	logAnim->info("CMeleeAttackAnimation::init: stack %d -> stack %d", stack->ID, attackedStack->ID); | ||||
| 	logAnim->info("CMeleeAttackAnimation::init: stack %s -> stack %s", stack->getName(), attackedStack->getName()); | ||||
| 	//reversed | ||||
|  | ||||
| 	shooting = false; | ||||
| @@ -351,7 +354,7 @@ bool CMovementAnimation::init() | ||||
| 		return false; | ||||
| 	} | ||||
|  | ||||
| 	logAnim->info("CMovementAnimation::init: stack %d moves %d -> %d", stack->ID, oldPos, currentHex); | ||||
| 	logAnim->info("CMovementAnimation::init: stack %s moves %d -> %d", stack->getName(), oldPos, currentHex); | ||||
|  | ||||
| 	//reverse unit if necessary | ||||
| 	if(owner.stacksController->shouldRotate(stack, oldPos, currentHex)) | ||||
| @@ -467,7 +470,7 @@ bool CMovementEndAnimation::init() | ||||
| 		return false; | ||||
| 	} | ||||
|  | ||||
| 	logAnim->info("CMovementEndAnimation::init: stack %d", stack->ID); | ||||
| 	logAnim->info("CMovementEndAnimation::init: stack %s", stack->getName()); | ||||
|  | ||||
| 	CCS->soundh->playSound(battle_sound(stack->getCreature(), endMoving)); | ||||
|  | ||||
| @@ -509,7 +512,7 @@ bool CMovementStartAnimation::init() | ||||
| 		return false; | ||||
| 	} | ||||
|  | ||||
| 	logAnim->info("CMovementStartAnimation::init: stack %d", stack->ID); | ||||
| 	logAnim->info("CMovementStartAnimation::init: stack %s", stack->getName()); | ||||
| 	CCS->soundh->playSound(battle_sound(stack->getCreature(), startMoving)); | ||||
|  | ||||
| 	if(!myAnim->framesInGroup(ECreatureAnimType::MOVE_START)) | ||||
| @@ -531,13 +534,16 @@ CReverseAnimation::CReverseAnimation(BattleInterface & owner, const CStack * sta | ||||
|  | ||||
| bool CReverseAnimation::init() | ||||
| { | ||||
| 	assert(myAnim); | ||||
| 	assert(!myAnim->isDeadOrDying()); | ||||
|  | ||||
| 	if(myAnim == nullptr || myAnim->isDeadOrDying()) | ||||
| 	{ | ||||
| 		delete this; | ||||
| 		return false; //there is no such creature | ||||
| 	} | ||||
|  | ||||
| 	logAnim->info("CReverseAnimation::init: stack %d", stack->ID); | ||||
| 	logAnim->info("CReverseAnimation::init: stack %s", stack->getName()); | ||||
| 	if(myAnim->framesInGroup(ECreatureAnimType::TURN_L)) | ||||
| 	{ | ||||
| 		myAnim->playOnce(ECreatureAnimType::TURN_L); | ||||
| @@ -559,6 +565,8 @@ void CBattleStackAnimation::rotateStack(BattleHex hex) | ||||
|  | ||||
| void CReverseAnimation::setupSecondPart() | ||||
| { | ||||
| 	assert(stack); | ||||
|  | ||||
| 	if(!stack) | ||||
| 	{ | ||||
| 		delete this; | ||||
| @@ -578,13 +586,15 @@ void CReverseAnimation::setupSecondPart() | ||||
|  | ||||
| bool CResurrectionAnimation::init() | ||||
| { | ||||
| 	assert(stack); | ||||
|  | ||||
| 	if(!stack) | ||||
| 	{ | ||||
| 		delete this; | ||||
| 		return false; | ||||
| 	} | ||||
|  | ||||
| 	logAnim->info("CResurrectionAnimation::init: stack %d", stack->ID); | ||||
| 	logAnim->info("CResurrectionAnimation::init: stack %s", stack->getName()); | ||||
| 	myAnim->playOnce(ECreatureAnimType::RESURRECTION); | ||||
| 	myAnim->onAnimationReset += [&](){ delete this; }; | ||||
|  | ||||
| @@ -616,7 +626,7 @@ bool CRangedAttackAnimation::init() | ||||
| 		return false; | ||||
| 	} | ||||
|  | ||||
| 	logAnim->info("CRangedAttackAnimation::init: stack %d", stack->ID); | ||||
| 	logAnim->info("CRangedAttackAnimation::init: stack %s", stack->getName()); | ||||
|  | ||||
| 	setAnimationGroup(); | ||||
| 	initializeProjectile(); | ||||
| @@ -1067,6 +1077,9 @@ CWaitingAnimation::CWaitingAnimation(BattleInterface & owner): | ||||
| void CWaitingAnimation::nextFrame() | ||||
| { | ||||
| 	// initialization conditions fulfilled, delay is over | ||||
| 	if(owner.getAnimationCondition(EAnimationEvents::HIT) == false) | ||||
| 		owner.setAnimationCondition(EAnimationEvents::HIT, true); | ||||
|  | ||||
| 	delete this; | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -10,12 +10,15 @@ | ||||
| #pragma once | ||||
|  | ||||
| enum class EAnimationEvents { | ||||
| 	OPENING     = 0, // battle opening sound is playing | ||||
| 	OPENING     = 0, // TODO battle opening sound is playing | ||||
| 	ACTION      = 1, // there are any ongoing animations | ||||
| 	MOVEMENT    = 2, // stacks are moving or turning around | ||||
| 	ATTACK      = 3, // attack and defense animations are playing | ||||
| 	HIT         = 4, // hit & death animations are playing | ||||
| 	PROJECTILES = 5, // there are any flying projectiles | ||||
| 	BEFORE_MOVE = 2, // TODO effects played before stack can act, e.g. regen or bad morale | ||||
| 	MOVEMENT    = 3, // stacks are moving or turning around | ||||
| 	BEFORE_HIT  = 4, // effects played before all attack/defence/hit animations | ||||
| 	ATTACK      = 5, // attack and defence animations are playing | ||||
| 	HIT         = 6, // hit & death animations are playing | ||||
| 	AFTER_HIT   = 7, // after all hit & death animations are over | ||||
| 	PROJECTILES = 8, // TODO there are any flying projectiles | ||||
| 	COUNT | ||||
| }; | ||||
|  | ||||
| @@ -30,7 +33,7 @@ namespace EBattleEffect | ||||
| 		BAD_MORALE   = 30, | ||||
| 		BAD_LUCK     = 48, | ||||
| 		RESURRECT    = 50, | ||||
| 		DRAIN_LIFE   = 52, // hardcoded constant in CGameHandler | ||||
| 		DRAIN_LIFE   = 52, | ||||
| 		POISON       = 67, | ||||
| 		DEATH_BLOW   = 73, | ||||
| 		REGENERATION = 74, | ||||
|   | ||||
| @@ -372,9 +372,9 @@ void BattleInterface::stacksAreAttacked(std::vector<StackAttackedInfo> attackedI | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void BattleInterface::stackAttacking( const CStack *attacker, BattleHex dest, const CStack *attacked, bool shooting ) | ||||
| void BattleInterface::stackAttacking( const StackAttackInfo & attackInfo ) | ||||
| { | ||||
| 	stacksController->stackAttacking(attacker, dest, attacked, shooting); | ||||
| 	stacksController->stackAttacking(attackInfo); | ||||
| } | ||||
|  | ||||
| void BattleInterface::newRoundFirst( int round ) | ||||
| @@ -490,6 +490,7 @@ void BattleInterface::spellCast(const BattleSpellCast * sc) | ||||
| { | ||||
| 	const SpellID spellID = sc->spellID; | ||||
| 	const CSpell * spell = spellID.toSpell(); | ||||
| 	auto targetedTile = sc->tile; | ||||
|  | ||||
| 	assert(spell); | ||||
| 	if(!spell) | ||||
| @@ -498,7 +499,11 @@ void BattleInterface::spellCast(const BattleSpellCast * sc) | ||||
| 	const std::string & castSoundPath = spell->getCastSound(); | ||||
|  | ||||
| 	if (!castSoundPath.empty()) | ||||
| 		CCS->soundh->playSound(castSoundPath); | ||||
| 	{ | ||||
| 		executeOnAnimationCondition(EAnimationEvents::BEFORE_HIT, true, [=]() { | ||||
| 			CCS->soundh->playSound(castSoundPath); | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
| 	if ( sc->activeCast ) | ||||
| 	{ | ||||
| @@ -506,58 +511,74 @@ void BattleInterface::spellCast(const BattleSpellCast * sc) | ||||
|  | ||||
| 		if(casterStack != nullptr ) | ||||
| 		{ | ||||
| 			displaySpellCast(spellID, casterStack->getPosition()); | ||||
|  | ||||
| 			stacksController->addNewAnim(new CCastAnimation(*this, casterStack, sc->tile, curInt->cb->battleGetStackByPos(sc->tile), spell)); | ||||
| 			executeOnAnimationCondition(EAnimationEvents::BEFORE_HIT, true, [=]() | ||||
| 			{ | ||||
| 				stacksController->addNewAnim(new CCastAnimation(*this, casterStack, targetedTile, curInt->cb->battleGetStackByPos(targetedTile), spell)); | ||||
| 				displaySpellCast(spellID, casterStack->getPosition()); | ||||
| 			}); | ||||
| 		} | ||||
| 		else | ||||
| 		if (sc->tile.isValid() && !spell->animationInfo.projectile.empty()) | ||||
| 		if (targetedTile.isValid() && !spell->animationInfo.projectile.empty()) | ||||
| 		{ | ||||
| 			// this is spell cast by hero with valid destination & valid projectile -> play animation | ||||
|  | ||||
| 			const CStack * target = curInt->cb->battleGetStackByPos(sc->tile); | ||||
| 			const CStack * target = curInt->cb->battleGetStackByPos(targetedTile); | ||||
| 			Point srccoord = (sc->side ? Point(770, 60) : Point(30, 60)) + pos;	//hero position | ||||
| 			Point destcoord = stacksController->getStackPositionAtHex(sc->tile, target); //position attacked by projectile | ||||
| 			Point destcoord = stacksController->getStackPositionAtHex(targetedTile, target); //position attacked by projectile | ||||
| 			destcoord += Point(250, 240); // FIXME: what are these constants? | ||||
|  | ||||
| 			projectilesController->createSpellProjectile( nullptr, srccoord, destcoord, spell); | ||||
| 			projectilesController->emitStackProjectile( nullptr ); | ||||
|  | ||||
| 			// wait fo projectile to end | ||||
| 			stacksController->addNewAnim(new CWaitingProjectileAnimation(*this, nullptr)); | ||||
| 			//FIXME: should be replaced with new hero cast animation type | ||||
| 			executeOnAnimationCondition(EAnimationEvents::BEFORE_HIT, true, [=]() | ||||
| 			{ | ||||
| 				projectilesController->createSpellProjectile( nullptr, srccoord, destcoord, spell); | ||||
| 				projectilesController->emitStackProjectile( nullptr ); | ||||
| 				stacksController->addNewAnim(new CWaitingProjectileAnimation(*this, nullptr)); | ||||
| 			}); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	waitForAnimationCondition(EAnimationEvents::ACTION, false);//wait for projectile animation | ||||
|  | ||||
| 	displaySpellHit(spellID, sc->tile); | ||||
| 	executeOnAnimationCondition(EAnimationEvents::HIT, true, [=](){ | ||||
| 		displaySpellHit(spellID, targetedTile); | ||||
| 	}); | ||||
|  | ||||
| 	//queuing affect animation | ||||
| 	for(auto & elem : sc->affectedCres) | ||||
| 	{ | ||||
| 		auto stack = curInt->cb->battleGetStackByID(elem, false); | ||||
| 		assert(stack); | ||||
| 		if(stack) | ||||
| 			displaySpellEffect(spellID, stack->getPosition()); | ||||
| 		{ | ||||
| 			executeOnAnimationCondition(EAnimationEvents::HIT, true, [=](){ | ||||
| 				displaySpellEffect(spellID, stack->getPosition()); | ||||
| 			}); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	//queuing additional animation | ||||
| 	//queuing additional animation (magic mirror / resistance) | ||||
| 	for(auto & elem : sc->customEffects) | ||||
| 	{ | ||||
| 		auto stack = curInt->cb->battleGetStackByID(elem.stack, false); | ||||
| 		assert(stack); | ||||
| 		if(stack) | ||||
| 			effectsController->displayEffect(EBattleEffect::EBattleEffect(elem.effect), stack->getPosition()); | ||||
| 		{ | ||||
| 			executeOnAnimationCondition(EAnimationEvents::HIT, true, [=](){ | ||||
| 				effectsController->displayEffect(EBattleEffect::EBattleEffect(elem.effect), stack->getPosition()); | ||||
| 			}); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	waitForAnimationCondition(EAnimationEvents::ACTION, false); | ||||
| 	//mana absorption | ||||
| 	if (sc->manaGained > 0) | ||||
| 	{ | ||||
| 		Point leftHero = Point(15, 30) + pos; | ||||
| 		Point rightHero = Point(755, 30) + pos; | ||||
| 		stacksController->addNewAnim(new CPointEffectAnimation(*this, soundBase::invalid, sc->side ? "SP07_A.DEF" : "SP07_B.DEF", leftHero)); | ||||
| 		stacksController->addNewAnim(new CPointEffectAnimation(*this, soundBase::invalid, sc->side ? "SP07_B.DEF" : "SP07_A.DEF", rightHero)); | ||||
| 		bool side = sc->side; | ||||
|  | ||||
| 		executeOnAnimationCondition(EAnimationEvents::AFTER_HIT, true, [=](){ | ||||
| 			stacksController->addNewAnim(new CPointEffectAnimation(*this, soundBase::invalid, side ? "SP07_A.DEF" : "SP07_B.DEF", leftHero)); | ||||
| 			stacksController->addNewAnim(new CPointEffectAnimation(*this, soundBase::invalid, side ? "SP07_B.DEF" : "SP07_A.DEF", rightHero)); | ||||
| 		}); | ||||
| 	} | ||||
| 	waitForAnimationCondition(EAnimationEvents::ACTION, false); | ||||
| } | ||||
|  | ||||
| void BattleInterface::battleStacksEffectsSet(const SetStackEffect & sse) | ||||
|   | ||||
| @@ -72,6 +72,24 @@ struct StackAttackedInfo | ||||
| 	bool cloneKilled; | ||||
| }; | ||||
|  | ||||
| struct StackAttackInfo | ||||
| { | ||||
| 	const CStack *attacker; | ||||
| 	const CStack *defender; | ||||
| 	std::vector< const CStack *> secondaryDefender; | ||||
|  | ||||
| 	//EBattleEffect::EBattleEffect battleEffect; | ||||
| 	SpellID spellEffect; | ||||
| 	BattleHex tile; | ||||
|  | ||||
| 	bool indirectAttack; | ||||
| 	bool lucky; | ||||
| 	bool unlucky; | ||||
| 	bool deathBlow; | ||||
| 	bool lifeDrain; | ||||
| }; | ||||
|  | ||||
|  | ||||
| /// Big class which handles the overall battle interface actions and it is also responsible for | ||||
| /// drawing everything correctly. | ||||
| class BattleInterface : public WindowBase | ||||
| @@ -121,6 +139,9 @@ private: | ||||
| 	void showInterface(SDL_Surface * to); | ||||
|  | ||||
| 	void setHeroAnimation(ui8 side, int phase); | ||||
|  | ||||
| 	void executeSpellCast(); //called when a hero casts a spell | ||||
|  | ||||
| public: | ||||
| 	std::unique_ptr<BattleProjectileController> projectilesController; | ||||
| 	std::unique_ptr<BattleSiegeController> siegeController; | ||||
| @@ -182,7 +203,7 @@ public: | ||||
| 	void stackActivated(const CStack *stack); //active stack has been changed | ||||
| 	void stackMoved(const CStack *stack, std::vector<BattleHex> destHex, int distance); //stack with id number moved to destHex | ||||
| 	void stacksAreAttacked(std::vector<StackAttackedInfo> attackedInfos); //called when a certain amount of stacks has been attacked | ||||
| 	void stackAttacking(const CStack *attacker, BattleHex dest, const CStack *attacked, bool shooting); //called when stack with id ID is attacking something on hex dest | ||||
| 	void stackAttacking(const StackAttackInfo & attackInfo); //called when stack with id ID is attacking something on hex dest | ||||
| 	void newRoundFirst( int round ); | ||||
| 	void newRound(int number); //caled when round is ended; number is the number of round | ||||
| 	void hexLclicked(int whichOne); //hex only call-in | ||||
|   | ||||
| @@ -19,11 +19,11 @@ enum class EBattleFieldLayer { | ||||
| 	OBSTACLES     = 0, | ||||
| 	CORPSES       = 0, | ||||
| 	WALLS         = 1, | ||||
| 	HEROES        = 1, | ||||
| 	STACKS        = 1, // after corpses, obstacles | ||||
| 	BATTLEMENTS   = 2, // after stacks | ||||
| 	STACK_AMOUNTS = 2, // after stacks, obstacles, corpses | ||||
| 	EFFECTS       = 3, // after obstacles, battlements | ||||
| 	HEROES        = 2, | ||||
| 	STACKS        = 2, // after corpses, obstacles, walls | ||||
| 	BATTLEMENTS   = 3, // after stacks | ||||
| 	STACK_AMOUNTS = 3, // after stacks, obstacles, corpses | ||||
| 	EFFECTS       = 4, // after obstacles, battlements | ||||
| }; | ||||
|  | ||||
| class BattleRenderer | ||||
|   | ||||
| @@ -146,7 +146,7 @@ BattleHex BattleSiegeController::getWallPiecePosition(EWallVisual::EWallVisual w | ||||
| 		BattleHex::HEX_AFTER_ALL,  // BOTTOM_TOWER, | ||||
| 		182,                       // BOTTOM_WALL, | ||||
| 		130,                       // WALL_BELLOW_GATE, | ||||
| 		78,                        // WALL_OVER_GATE, | ||||
| 		62,                        // WALL_OVER_GATE, | ||||
| 		12,                        // UPPER_WALL, | ||||
| 		BattleHex::HEX_BEFORE_ALL, // UPPER_TOWER, | ||||
| 		BattleHex::HEX_BEFORE_ALL, // GATE,               // 94 | ||||
| @@ -327,6 +327,10 @@ bool BattleSiegeController::isAttackableByCatapult(BattleHex hex) const | ||||
|  | ||||
| void BattleSiegeController::stackIsCatapulting(const CatapultAttack & ca) | ||||
| { | ||||
| 	//FIXME: there should be no more ongoing animations. If not - then some other method created animations but did not wait for them to end | ||||
| 	assert(owner.getAnimationCondition(EAnimationEvents::ACTION) == false); | ||||
| 	owner.waitForAnimationCondition(EAnimationEvents::ACTION, false); | ||||
|  | ||||
| 	if (ca.attacker != -1) | ||||
| 	{ | ||||
| 		const CStack *stack = owner.curInt->cb->battleGetStackByID(ca.attacker); | ||||
| @@ -343,7 +347,6 @@ void BattleSiegeController::stackIsCatapulting(const CatapultAttack & ca) | ||||
| 		for (auto attackInfo : ca.attackedParts) | ||||
| 			positions.push_back(owner.stacksController->getStackPositionAtHex(attackInfo.destinationTile, nullptr) + Point(99, 120)); | ||||
|  | ||||
|  | ||||
| 		owner.stacksController->addNewAnim(new CPointEffectAnimation(owner, soundBase::WALLHIT, "SGEXPL.DEF", positions)); | ||||
| 	} | ||||
|  | ||||
|   | ||||
| @@ -148,8 +148,11 @@ void BattleStacksController::collectRenderableObjects(BattleRenderer & renderer) | ||||
| void BattleStacksController::stackReset(const CStack * stack) | ||||
| { | ||||
| 	//FIXME: there should be no more ongoing animations. If not - then some other method created animations but did not wait for them to end | ||||
| 	assert(owner.getAnimationCondition(EAnimationEvents::ACTION) == false); | ||||
| 	owner.waitForAnimationCondition(EAnimationEvents::ACTION, false); | ||||
| 	//assert(owner.getAnimationCondition(EAnimationEvents::ACTION) == false); | ||||
| 	//owner.waitForAnimationCondition(EAnimationEvents::ACTION, false); | ||||
|  | ||||
| 	//reset orientation? | ||||
| 	//stackFacingRight[stack->ID] = stack->side == BattleSide::ATTACKER; | ||||
|  | ||||
| 	auto iter = stackAnimation.find(stack->ID); | ||||
|  | ||||
| @@ -163,7 +166,10 @@ void BattleStacksController::stackReset(const CStack * stack) | ||||
|  | ||||
| 	if(stack->alive() && animation->isDeadOrDying()) | ||||
| 	{ | ||||
| 		addNewAnim(new CResurrectionAnimation(owner, stack)); | ||||
| 		owner.executeOnAnimationCondition(EAnimationEvents::HIT, true, [=]() | ||||
| 		{ | ||||
| 			addNewAnim(new CResurrectionAnimation(owner, stack)); | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
| 	static const ColorShifterMultiplyAndAdd shifterClone ({255, 255, 0, 255}, {0, 0, 255, 0}); | ||||
| @@ -173,7 +179,7 @@ void BattleStacksController::stackReset(const CStack * stack) | ||||
| 		animation->shiftColor(&shifterClone); | ||||
| 	} | ||||
|  | ||||
| 	owner.waitForAnimationCondition(EAnimationEvents::ACTION, false); | ||||
| 	//owner.waitForAnimationCondition(EAnimationEvents::ACTION, false); | ||||
| } | ||||
|  | ||||
| void BattleStacksController::stackAdded(const CStack * stack) | ||||
| @@ -410,15 +416,14 @@ void BattleStacksController::stacksAreAttacked(std::vector<StackAttackedInfo> at | ||||
| 					facingRight(attackedInfo.attacker)); | ||||
|  | ||||
| 		if (needsReverse) | ||||
| 			addNewAnim(new CReverseAnimation(owner, attackedInfo.defender, attackedInfo.defender->getPosition())); | ||||
| 		{ | ||||
| 			owner.executeOnAnimationCondition(EAnimationEvents::MOVEMENT, true, [=]() | ||||
| 			{ | ||||
| 				addNewAnim(new CReverseAnimation(owner, attackedInfo.defender, attackedInfo.defender->getPosition())); | ||||
| 			}); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// raise flag that movement phase started, starting any queued movements | ||||
| 	owner.setAnimationCondition(EAnimationEvents::MOVEMENT, true); | ||||
|  | ||||
| 	owner.waitForAnimationCondition(EAnimationEvents::ACTION, false); | ||||
| 	owner.setAnimationCondition(EAnimationEvents::MOVEMENT, false); | ||||
|  | ||||
| 	for(auto & attackedInfo : attackedInfos) | ||||
| 	{ | ||||
| 		bool useDefenceAnim = attackedInfo.defender->defendingAnim && !attackedInfo.indirectAttack && !attackedInfo.killed; | ||||
| @@ -437,23 +442,24 @@ void BattleStacksController::stacksAreAttacked(std::vector<StackAttackedInfo> at | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
| 	owner.setAnimationCondition(EAnimationEvents::ATTACK, true); | ||||
| 	owner.waitForAnimationCondition(EAnimationEvents::ACTION, false); | ||||
| 	owner.setAnimationCondition(EAnimationEvents::ATTACK, false); | ||||
| 	owner.setAnimationCondition(EAnimationEvents::HIT, false); | ||||
|  | ||||
| 	for (auto & attackedInfo : attackedInfos) | ||||
| 	{ | ||||
| 		if (attackedInfo.rebirth) | ||||
| 		{ | ||||
| 			owner.effectsController->displayEffect(EBattleEffect::RESURRECT, soundBase::RESURECT, attackedInfo.defender->getPosition()); | ||||
| 			addNewAnim(new CResurrectionAnimation(owner, attackedInfo.defender)); | ||||
| 			owner.executeOnAnimationCondition(EAnimationEvents::AFTER_HIT, true, [=](){ | ||||
| 				owner.effectsController->displayEffect(EBattleEffect::RESURRECT, soundBase::RESURECT, attackedInfo.defender->getPosition()); | ||||
| 				addNewAnim(new CResurrectionAnimation(owner, attackedInfo.defender)); | ||||
| 			}); | ||||
| 		} | ||||
|  | ||||
| 		if (attackedInfo.cloneKilled) | ||||
| 			stackRemoved(attackedInfo.defender->ID); | ||||
| 		{ | ||||
| 			owner.executeOnAnimationCondition(EAnimationEvents::AFTER_HIT, true, [=](){ | ||||
| 				stackRemoved(attackedInfo.defender->ID); | ||||
| 			}); | ||||
| 		} | ||||
| 	} | ||||
| 	owner.waitForAnimationCondition(EAnimationEvents::ACTION, false); | ||||
| 	executeAttackAnimations(); | ||||
| } | ||||
|  | ||||
| void BattleStacksController::stackMoved(const CStack *stack, std::vector<BattleHex> destHex, int distance) | ||||
| @@ -477,40 +483,113 @@ void BattleStacksController::stackMoved(const CStack *stack, std::vector<BattleH | ||||
| 	owner.waitForAnimationCondition(EAnimationEvents::ACTION, false); | ||||
| } | ||||
|  | ||||
| void BattleStacksController::stackAttacking( const CStack *attacker, BattleHex dest, const CStack *defender, bool shooting ) | ||||
| void BattleStacksController::stackAttacking( const StackAttackInfo & info ) | ||||
| { | ||||
| 	//FIXME: there should be no more ongoing animations. If not - then some other method created animations but did not wait for them to end | ||||
| 	assert(owner.getAnimationCondition(EAnimationEvents::ACTION) == false); | ||||
|  | ||||
| 	bool needsReverse = | ||||
| 			owner.curInt->cb->isToReverse( | ||||
| 				attacker->getPosition(), | ||||
| 				defender->getPosition(), | ||||
| 				facingRight(attacker), | ||||
| 				attacker->doubleWide(), | ||||
| 				facingRight(defender)); | ||||
| 				info.attacker->getPosition(), | ||||
| 				info.defender->getPosition(), | ||||
| 				facingRight(info.attacker), | ||||
| 				info.attacker->doubleWide(), | ||||
| 				facingRight(info.defender)); | ||||
|  | ||||
| 	if (needsReverse) | ||||
| 	{ | ||||
| 		owner.executeOnAnimationCondition(EAnimationEvents::MOVEMENT, true, [=]() | ||||
| 		{ | ||||
| 			addNewAnim(new CReverseAnimation(owner, attacker, attacker->getPosition())); | ||||
| 			addNewAnim(new CReverseAnimation(owner, info.attacker, info.attacker->getPosition())); | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
| 	if(info.lucky) | ||||
| 	{ | ||||
| 		owner.executeOnAnimationCondition(EAnimationEvents::BEFORE_HIT, true, [=]() { | ||||
| 			owner.controlPanel->console->addText(info.attacker->formatGeneralMessage(-45)); | ||||
| 			owner.effectsController->displayEffect(EBattleEffect::GOOD_LUCK, soundBase::GOODLUCK, info.attacker->getPosition()); | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
| 	if(info.unlucky) | ||||
| 	{ | ||||
| 		owner.executeOnAnimationCondition(EAnimationEvents::BEFORE_HIT, true, [=]() { | ||||
| 			owner.controlPanel->console->addText(info.attacker->formatGeneralMessage(-44)); | ||||
| 			owner.effectsController->displayEffect(EBattleEffect::BAD_LUCK, soundBase::BADLUCK, info.attacker->getPosition()); | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
| 	if(info.deathBlow) | ||||
| 	{ | ||||
| 		owner.executeOnAnimationCondition(EAnimationEvents::BEFORE_HIT, true, [=]() { | ||||
| 			owner.controlPanel->console->addText(info.attacker->formatGeneralMessage(365)); | ||||
| 			owner.effectsController->displayEffect(EBattleEffect::DEATH_BLOW, soundBase::deathBlow, info.defender->getPosition()); | ||||
| 		}); | ||||
|  | ||||
| 		for(auto & elem : info.secondaryDefender) | ||||
| 		{ | ||||
| 			owner.executeOnAnimationCondition(EAnimationEvents::BEFORE_HIT, true, [=]() { | ||||
| 				owner.effectsController->displayEffect(EBattleEffect::DEATH_BLOW, elem->getPosition()); | ||||
| 			}); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	owner.executeOnAnimationCondition(EAnimationEvents::ATTACK, true, [=]() | ||||
| 	{ | ||||
| 		if (shooting) | ||||
| 		if (info.indirectAttack) | ||||
| 		{ | ||||
| 			addNewAnim(new CShootingAnimation(owner, attacker, dest, defender)); | ||||
| 			addNewAnim(new CShootingAnimation(owner, info.attacker, info.tile, info.defender)); | ||||
| 		} | ||||
| 		else | ||||
| 		{ | ||||
| 			addNewAnim(new CMeleeAttackAnimation(owner, attacker, dest, defender)); | ||||
| 			addNewAnim(new CMeleeAttackAnimation(owner, info.attacker, info.tile, info.defender)); | ||||
| 		} | ||||
| 	}); | ||||
|  | ||||
| 	//waiting will be done in stacksAreAttacked | ||||
| 	if (info.spellEffect) | ||||
| 	{ | ||||
| 		owner.executeOnAnimationCondition(EAnimationEvents::HIT, true, [=]() | ||||
| 		{ | ||||
| 			owner.displaySpellHit(info.spellEffect, info.tile); | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
| 	if (info.lifeDrain) | ||||
| 	{ | ||||
| 		owner.executeOnAnimationCondition(EAnimationEvents::AFTER_HIT, true, [=]() | ||||
| 		{ | ||||
| 			owner.effectsController->displayEffect(EBattleEffect::DRAIN_LIFE, soundBase::DRAINLIF, info.attacker->getPosition()); | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
| 	//return, animation playback will be handled by stacksAreAttacked | ||||
| } | ||||
|  | ||||
| void BattleStacksController::executeAttackAnimations() | ||||
| { | ||||
| 	owner.setAnimationCondition(EAnimationEvents::MOVEMENT, true); | ||||
| 	owner.waitForAnimationCondition(EAnimationEvents::ACTION, false); | ||||
| 	owner.setAnimationCondition(EAnimationEvents::MOVEMENT, false); | ||||
|  | ||||
| 	owner.setAnimationCondition(EAnimationEvents::BEFORE_HIT, true); | ||||
| 	owner.waitForAnimationCondition(EAnimationEvents::ACTION, false); | ||||
| 	owner.setAnimationCondition(EAnimationEvents::BEFORE_HIT, false); | ||||
|  | ||||
| 	owner.setAnimationCondition(EAnimationEvents::ATTACK, true); | ||||
| 	owner.waitForAnimationCondition(EAnimationEvents::ACTION, false); | ||||
| 	owner.setAnimationCondition(EAnimationEvents::ATTACK, false); | ||||
|  | ||||
| 	// Note that HIT event can also be emitted by attack animation | ||||
| 	owner.setAnimationCondition(EAnimationEvents::HIT, true); | ||||
| 	owner.waitForAnimationCondition(EAnimationEvents::ACTION, false); | ||||
| 	owner.setAnimationCondition(EAnimationEvents::HIT, false); | ||||
|  | ||||
| 	owner.setAnimationCondition(EAnimationEvents::AFTER_HIT, true); | ||||
| 	owner.waitForAnimationCondition(EAnimationEvents::ACTION, false); | ||||
| 	owner.setAnimationCondition(EAnimationEvents::AFTER_HIT, false); | ||||
|  | ||||
| 	owner.waitForAnimationCondition(EAnimationEvents::ACTION, false); | ||||
| } | ||||
|  | ||||
| bool BattleStacksController::shouldRotate(const CStack * stack, const BattleHex & oldPos, const BattleHex & nextHex) const | ||||
|   | ||||
| @@ -21,6 +21,7 @@ class SpellID; | ||||
| VCMI_LIB_NAMESPACE_END | ||||
|  | ||||
| struct StackAttackedInfo; | ||||
| struct StackAttackInfo; | ||||
|  | ||||
| class Canvas; | ||||
| class BattleInterface; | ||||
| @@ -77,6 +78,7 @@ class BattleStacksController | ||||
|  | ||||
| 	std::shared_ptr<IImage> getStackAmountBox(const CStack * stack); | ||||
|  | ||||
| 	void executeAttackAnimations(); | ||||
| public: | ||||
| 	BattleStacksController(BattleInterface & owner); | ||||
|  | ||||
| @@ -89,7 +91,7 @@ public: | ||||
| 	void stackActivated(const CStack *stack); //active stack has been changed | ||||
| 	void stackMoved(const CStack *stack, std::vector<BattleHex> destHex, int distance); //stack with id number moved to destHex | ||||
| 	void stacksAreAttacked(std::vector<StackAttackedInfo> attackedInfos); //called when a certain amount of stacks has been attacked | ||||
| 	void stackAttacking(const CStack *attacker, BattleHex dest, const CStack *attacked, bool shooting); //called when stack with id ID is attacking something on hex dest | ||||
| 	void stackAttacking(const StackAttackInfo & info); //called when stack with id ID is attacking something on hex dest | ||||
|  | ||||
| 	void startAction(const BattleAction* action); | ||||
| 	void endAction(const BattleAction* action); | ||||
|   | ||||
| @@ -1647,12 +1647,11 @@ struct BattleAttack : public CPackForClient | ||||
| 	std::vector<BattleStackAttacked> bsa; | ||||
| 	ui32 stackAttacking; | ||||
| 	ui32 flags; //uses Eflags (below) | ||||
| 	enum EFlags{SHOT = 1, COUNTER = 2, LUCKY = 4, UNLUCKY = 8, BALLISTA_DOUBLE_DMG = 16, DEATH_BLOW = 32, SPELL_LIKE = 64}; | ||||
| 	enum EFlags{SHOT = 1, COUNTER = 2, LUCKY = 4, UNLUCKY = 8, BALLISTA_DOUBLE_DMG = 16, DEATH_BLOW = 32, SPELL_LIKE = 64, LIFE_DRAIN = 128}; | ||||
|  | ||||
| 	BattleHex tile; | ||||
| 	SpellID spellID; //for SPELL_LIKE | ||||
|  | ||||
| 	std::vector<CustomEffectInfo> customEffects; | ||||
|  | ||||
| 	bool shot() const//distance attack - decrease number of shots | ||||
| 	{ | ||||
| 		return flags & SHOT; | ||||
| @@ -1681,13 +1680,17 @@ struct BattleAttack : public CPackForClient | ||||
| 	{ | ||||
| 		return flags & SPELL_LIKE; | ||||
| 	} | ||||
| 	bool lifeDrain() const | ||||
| 	{ | ||||
| 		return flags & LIFE_DRAIN; | ||||
| 	} | ||||
| 	template <typename Handler> void serialize(Handler &h, const int version) | ||||
| 	{ | ||||
| 		h & bsa; | ||||
| 		h & stackAttacking; | ||||
| 		h & flags; | ||||
| 		h & tile; | ||||
| 		h & spellID; | ||||
| 		h & customEffects; | ||||
| 		h & attackerChanges; | ||||
| 	} | ||||
| }; | ||||
|   | ||||
| @@ -145,8 +145,8 @@ ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCastSpell(con | ||||
| 	{ | ||||
| 	case spells::Mode::HERO: | ||||
| 	{ | ||||
| 		if(battleCastSpells(side.get()) > 0) | ||||
| 			return ESpellCastProblem::CASTS_PER_TURN_LIMIT; | ||||
| 		//if(battleCastSpells(side.get()) > 0) | ||||
| 		//	return ESpellCastProblem::CASTS_PER_TURN_LIMIT; | ||||
|  | ||||
| 		auto hero = dynamic_cast<const CGHeroInstance *>(caster); | ||||
|  | ||||
|   | ||||
| @@ -330,8 +330,6 @@ void BattleSpellMechanics::cast(ServerCallback * server, const Target & target) | ||||
| 	for(auto & p : effectsToApply) | ||||
| 		p.first->apply(server, this, p.second); | ||||
|  | ||||
| //	afterCast(); | ||||
|  | ||||
| 	if(sc.activeCast) | ||||
| 	{ | ||||
| 		caster->spendMana(server, spellCost); | ||||
| @@ -342,6 +340,11 @@ void BattleSpellMechanics::cast(ServerCallback * server, const Target & target) | ||||
| 			otherHero->spendMana(server, -sc.manaGained); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// send empty event to client | ||||
| 	// temporary(?) workaround to force animations to trigger | ||||
| 	StacksInjured fake_event; | ||||
| 	server->apply(&fake_event); | ||||
| } | ||||
|  | ||||
| void BattleSpellMechanics::beforeCast(BattleSpellCast & sc, vstd::RNG & rng, const Target & target) | ||||
|   | ||||
| @@ -1009,6 +1009,7 @@ void CGameHandler::makeAttack(const CStack * attacker, const CStack * defender, | ||||
| 	BattleAttack bat; | ||||
| 	BattleLogMessage blm; | ||||
| 	bat.stackAttacking = attacker->unitId(); | ||||
| 	bat.tile = targetHex; | ||||
|  | ||||
| 	std::shared_ptr<battle::CUnitState> attackerState = attacker->acquireState(); | ||||
|  | ||||
| @@ -1114,6 +1115,9 @@ void CGameHandler::makeAttack(const CStack * attacker, const CStack * defender, | ||||
| 		bat.attackerChanges.changedStacks.push_back(info); | ||||
| 	} | ||||
|  | ||||
| 	if (drainedLife > 0) | ||||
| 		bat.flags |= BattleAttack::LIFE_DRAIN; | ||||
|  | ||||
| 	sendAndApply(&bat); | ||||
|  | ||||
| 	{ | ||||
| @@ -1142,17 +1146,6 @@ void CGameHandler::makeAttack(const CStack * attacker, const CStack * defender, | ||||
| 	// drain life effect (as well as log entry) must be applied after the attack | ||||
| 	if(drainedLife > 0) | ||||
| 	{ | ||||
| 		BattleAttack bat; | ||||
| 		bat.stackAttacking = attacker->unitId(); | ||||
| 		{ | ||||
| 			CustomEffectInfo customEffect; | ||||
| 			customEffect.sound = soundBase::DRAINLIF; | ||||
| 			customEffect.effect = 52; | ||||
| 			customEffect.stack = attackerState->unitId(); | ||||
| 			bat.customEffects.push_back(std::move(customEffect)); | ||||
| 		} | ||||
| 		sendAndApply(&bat); | ||||
|  | ||||
| 		MetaString text; | ||||
| 		attackerState->addText(text, MetaString::GENERAL_TXT, 361); | ||||
| 		attackerState->addNameReplacement(text, false); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user