BattleAnimationClasses.cpp 31 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180
  1. /*
  2. * BattleAnimationClasses.cpp, part of VCMI engine
  3. *
  4. * Authors: listed in file AUTHORS in main folder
  5. *
  6. * License: GNU General Public License v2.0 or later
  7. * Full text of license available in license.txt file, in main folder
  8. *
  9. */
  10. #include "StdInc.h"
  11. #include "BattleAnimationClasses.h"
  12. #include "BattleInterface.h"
  13. #include "BattleInterfaceClasses.h"
  14. #include "BattleProjectileController.h"
  15. #include "BattleSiegeController.h"
  16. #include "BattleFieldController.h"
  17. #include "BattleEffectsController.h"
  18. #include "BattleStacksController.h"
  19. #include "CreatureAnimation.h"
  20. #include "../CGameInfo.h"
  21. #include "../CMusicHandler.h"
  22. #include "../CPlayerInterface.h"
  23. #include "../gui/CCursorHandler.h"
  24. #include "../gui/CGuiHandler.h"
  25. #include "../../CCallback.h"
  26. #include "../../lib/CStack.h"
  27. BattleAnimation::BattleAnimation(BattleInterface & owner)
  28. : owner(owner),
  29. ID(owner.stacksController->animIDhelper++),
  30. initialized(false)
  31. {
  32. logAnim->trace("Animation #%d created", ID);
  33. }
  34. bool BattleAnimation::tryInitialize()
  35. {
  36. assert(!initialized);
  37. if ( init() )
  38. {
  39. initialized = true;
  40. return true;
  41. }
  42. return false;
  43. }
  44. bool BattleAnimation::isInitialized()
  45. {
  46. return initialized;
  47. }
  48. BattleAnimation::~BattleAnimation()
  49. {
  50. logAnim->trace("Animation #%d ended, type is %s", ID, typeid(this).name());
  51. for(auto & elem : pendingAnimations())
  52. {
  53. if(elem == this)
  54. elem = nullptr;
  55. }
  56. logAnim->trace("Animation #%d deleted", ID);
  57. }
  58. std::vector<BattleAnimation *> & BattleAnimation::pendingAnimations()
  59. {
  60. return owner.stacksController->currentAnimations;
  61. }
  62. std::shared_ptr<CreatureAnimation> BattleAnimation::stackAnimation(const CStack * stack) const
  63. {
  64. return owner.stacksController->stackAnimation[stack->ID];
  65. }
  66. bool BattleAnimation::stackFacingRight(const CStack * stack)
  67. {
  68. return owner.stacksController->stackFacingRight[stack->ID];
  69. }
  70. void BattleAnimation::setStackFacingRight(const CStack * stack, bool facingRight)
  71. {
  72. owner.stacksController->stackFacingRight[stack->ID] = facingRight;
  73. }
  74. BattleStackAnimation::BattleStackAnimation(BattleInterface & owner, const CStack * stack)
  75. : BattleAnimation(owner),
  76. myAnim(stackAnimation(stack)),
  77. stack(stack)
  78. {
  79. assert(myAnim);
  80. }
  81. ECreatureAnimType::Type AttackAnimation::findValidGroup( const std::vector<ECreatureAnimType::Type> candidates ) const
  82. {
  83. for ( auto group : candidates)
  84. {
  85. if(myAnim->framesInGroup(group) > 0)
  86. return group;
  87. }
  88. assert(0);
  89. return ECreatureAnimType::HOLDING;
  90. }
  91. void AttackAnimation::nextFrame()
  92. {
  93. if(myAnim->getType() != group)
  94. {
  95. myAnim->setType(group);
  96. myAnim->onAnimationReset += [&](){ delete this; };
  97. }
  98. if(!soundPlayed)
  99. {
  100. playSound();
  101. soundPlayed = true;
  102. }
  103. }
  104. AttackAnimation::~AttackAnimation()
  105. {
  106. myAnim->setType(ECreatureAnimType::HOLDING);
  107. }
  108. const CCreature * AttackAnimation::getCreature() const
  109. {
  110. if (attackingStack->getCreature()->idNumber == CreatureID::ARROW_TOWERS)
  111. return owner.siegeController->getTurretCreature();
  112. else
  113. return attackingStack->getCreature();
  114. }
  115. AttackAnimation::AttackAnimation(BattleInterface & owner, const CStack *attacker, BattleHex _dest, const CStack *defender)
  116. : BattleStackAnimation(owner, attacker),
  117. group(ECreatureAnimType::SHOOT_FRONT),
  118. soundPlayed(false),
  119. dest(_dest),
  120. defendingStack(defender),
  121. attackingStack(attacker)
  122. {
  123. assert(attackingStack && "attackingStack is nullptr in CBattleAttack::CBattleAttack !\n");
  124. attackingStackPosBeforeReturn = attackingStack->getPosition();
  125. }
  126. bool HittedAnimation::init()
  127. {
  128. CCS->soundh->playSound(battle_sound(stack->getCreature(), wince));
  129. myAnim->playOnce(ECreatureAnimType::HITTED);
  130. myAnim->onAnimationReset += [&](){ delete this; };
  131. return true;
  132. }
  133. HittedAnimation::HittedAnimation(BattleInterface & owner, const CStack * stack)
  134. : BattleStackAnimation(owner, stack)
  135. {
  136. }
  137. DefenceAnimation::DefenceAnimation(BattleInterface & owner, const CStack * stack)
  138. : BattleStackAnimation(owner, stack)
  139. {
  140. }
  141. bool DefenceAnimation::init()
  142. {
  143. CCS->soundh->playSound(battle_sound(stack->getCreature(), defend));
  144. myAnim->playOnce(ECreatureAnimType::DEFENCE);
  145. myAnim->onAnimationReset += [&](){ delete this; };
  146. return true; //initialized successfuly
  147. }
  148. ECreatureAnimType::Type DeathAnimation::getMyAnimType()
  149. {
  150. if(rangedAttack && myAnim->framesInGroup(ECreatureAnimType::DEATH_RANGED) > 0)
  151. return ECreatureAnimType::DEATH_RANGED;
  152. else
  153. return ECreatureAnimType::DEATH;
  154. }
  155. bool DeathAnimation::init()
  156. {
  157. CCS->soundh->playSound(battle_sound(stack->getCreature(), killed));
  158. myAnim->playOnce(getMyAnimType());
  159. myAnim->onAnimationReset += [&](){ delete this; };
  160. return true;
  161. }
  162. DeathAnimation::DeathAnimation(BattleInterface & owner, const CStack * stack, bool ranged):
  163. BattleStackAnimation(owner, stack),
  164. rangedAttack(ranged)
  165. {
  166. }
  167. DeathAnimation::~DeathAnimation()
  168. {
  169. if(rangedAttack && myAnim->framesInGroup(ECreatureAnimType::DEAD_RANGED) > 0)
  170. myAnim->setType(ECreatureAnimType::DEAD_RANGED);
  171. else
  172. myAnim->setType(ECreatureAnimType::DEAD);
  173. }
  174. DummyAnimation::DummyAnimation(BattleInterface & owner, int howManyFrames)
  175. : BattleAnimation(owner),
  176. counter(0),
  177. howMany(howManyFrames)
  178. {
  179. logAnim->debug("Created dummy animation for %d frames", howManyFrames);
  180. }
  181. bool DummyAnimation::init()
  182. {
  183. return true;
  184. }
  185. void DummyAnimation::nextFrame()
  186. {
  187. counter++;
  188. if(counter > howMany)
  189. delete this;
  190. }
  191. ECreatureAnimType::Type MeleeAttackAnimation::getUpwardsGroup() const
  192. {
  193. if (!multiAttack)
  194. return ECreatureAnimType::ATTACK_UP;
  195. return findValidGroup({
  196. ECreatureAnimType::GROUP_ATTACK_UP,
  197. ECreatureAnimType::SPECIAL_UP,
  198. ECreatureAnimType::ATTACK_UP
  199. });
  200. }
  201. ECreatureAnimType::Type MeleeAttackAnimation::getForwardGroup() const
  202. {
  203. if (!multiAttack)
  204. return ECreatureAnimType::ATTACK_FRONT;
  205. return findValidGroup({
  206. ECreatureAnimType::GROUP_ATTACK_FRONT,
  207. ECreatureAnimType::SPECIAL_FRONT,
  208. ECreatureAnimType::ATTACK_FRONT
  209. });
  210. }
  211. ECreatureAnimType::Type MeleeAttackAnimation::getDownwardsGroup() const
  212. {
  213. if (!multiAttack)
  214. return ECreatureAnimType::ATTACK_DOWN;
  215. return findValidGroup({
  216. ECreatureAnimType::GROUP_ATTACK_DOWN,
  217. ECreatureAnimType::SPECIAL_DOWN,
  218. ECreatureAnimType::ATTACK_DOWN
  219. });
  220. }
  221. bool MeleeAttackAnimation::init()
  222. {
  223. assert(attackingStack);
  224. assert(!myAnim->isDeadOrDying());
  225. if(!attackingStack || myAnim->isDeadOrDying())
  226. {
  227. delete this;
  228. return false;
  229. }
  230. logAnim->info("CMeleeAttackAnimation::init: stack %s -> stack %s", stack->getName(), defendingStack->getName());
  231. const ECreatureAnimType::Type mutPosToGroup[] =
  232. {
  233. getUpwardsGroup(),
  234. getUpwardsGroup(),
  235. getForwardGroup(),
  236. getDownwardsGroup(),
  237. getDownwardsGroup(),
  238. getForwardGroup()
  239. };
  240. int revShiftattacker = (attackingStack->side == BattleSide::ATTACKER ? -1 : 1);
  241. int mutPos = BattleHex::mutualPosition(attackingStackPosBeforeReturn, dest);
  242. if(mutPos == -1 && attackingStack->doubleWide())
  243. {
  244. mutPos = BattleHex::mutualPosition(attackingStackPosBeforeReturn + revShiftattacker, defendingStack->getPosition());
  245. }
  246. if (mutPos == -1 && defendingStack->doubleWide())
  247. {
  248. mutPos = BattleHex::mutualPosition(attackingStackPosBeforeReturn, defendingStack->occupiedHex());
  249. }
  250. if (mutPos == -1 && defendingStack->doubleWide() && attackingStack->doubleWide())
  251. {
  252. mutPos = BattleHex::mutualPosition(attackingStackPosBeforeReturn + revShiftattacker, defendingStack->occupiedHex());
  253. }
  254. assert(mutPos >= 0 && mutPos <=5);
  255. group = mutPosToGroup[mutPos];
  256. return true;
  257. }
  258. void MeleeAttackAnimation::nextFrame()
  259. {
  260. size_t currentFrame = stackAnimation(attackingStack)->getCurrentFrame();
  261. size_t totalFrames = stackAnimation(attackingStack)->framesInGroup(group);
  262. if ( currentFrame * 2 >= totalFrames )
  263. {
  264. if(owner.getAnimationCondition(EAnimationEvents::HIT) == false)
  265. owner.setAnimationCondition(EAnimationEvents::HIT, true);
  266. }
  267. AttackAnimation::nextFrame();
  268. }
  269. void MeleeAttackAnimation::playSound()
  270. {
  271. CCS->soundh->playSound(battle_sound(getCreature(), attack));
  272. }
  273. MeleeAttackAnimation::MeleeAttackAnimation(BattleInterface & owner, const CStack * attacker, BattleHex _dest, const CStack * _attacked, bool multiAttack)
  274. : AttackAnimation(owner, attacker, _dest, _attacked),
  275. multiAttack(multiAttack)
  276. {
  277. logAnim->debug("Created melee attack anim for %s", attacker->getName());
  278. }
  279. StackMoveAnimation::StackMoveAnimation(BattleInterface & owner, const CStack * _stack, BattleHex _currentHex):
  280. BattleStackAnimation(owner, _stack),
  281. currentHex(_currentHex)
  282. {
  283. }
  284. bool MovementAnimation::init()
  285. {
  286. assert(stack);
  287. assert(!myAnim->isDeadOrDying());
  288. if(!stack || myAnim->isDeadOrDying())
  289. {
  290. delete this;
  291. return false;
  292. }
  293. if(stackAnimation(stack)->framesInGroup(ECreatureAnimType::MOVING) == 0 ||
  294. stack->hasBonus(Selector::typeSubtype(Bonus::FLYING, 1)))
  295. {
  296. //no movement or teleport, end immediately
  297. delete this;
  298. return false;
  299. }
  300. logAnim->info("CMovementAnimation::init: stack %s moves %d -> %d", stack->getName(), oldPos, currentHex);
  301. //reverse unit if necessary
  302. if(owner.stacksController->shouldRotate(stack, oldPos, currentHex))
  303. {
  304. // it seems that H3 does NOT plays full rotation animation during movement
  305. // Logical since it takes quite a lot of time
  306. rotateStack(oldPos);
  307. }
  308. if(myAnim->getType() != ECreatureAnimType::MOVING)
  309. {
  310. myAnim->setType(ECreatureAnimType::MOVING);
  311. }
  312. if (owner.moveSoundHander == -1)
  313. {
  314. owner.moveSoundHander = CCS->soundh->playSound(battle_sound(stack->getCreature(), move), -1);
  315. }
  316. Point begPosition = owner.stacksController->getStackPositionAtHex(oldPos, stack);
  317. Point endPosition = owner.stacksController->getStackPositionAtHex(currentHex, stack);
  318. timeToMove = AnimationControls::getMovementDuration(stack->getCreature());
  319. begX = begPosition.x;
  320. begY = begPosition.y;
  321. progress = 0;
  322. distanceX = endPosition.x - begPosition.x;
  323. distanceY = endPosition.y - begPosition.y;
  324. if (stack->hasBonus(Selector::type()(Bonus::FLYING)))
  325. {
  326. float distance = static_cast<float>(sqrt(distanceX * distanceX + distanceY * distanceY));
  327. timeToMove *= AnimationControls::getFlightDistance(stack->getCreature()) / distance;
  328. }
  329. return true;
  330. }
  331. void MovementAnimation::nextFrame()
  332. {
  333. progress += float(GH.mainFPSmng->getElapsedMilliseconds()) / 1000 * timeToMove;
  334. //moving instructions
  335. myAnim->pos.x = static_cast<Sint16>(begX + distanceX * progress );
  336. myAnim->pos.y = static_cast<Sint16>(begY + distanceY * progress );
  337. BattleAnimation::nextFrame();
  338. if(progress >= 1.0)
  339. {
  340. // Sets the position of the creature animation sprites
  341. Point coords = owner.stacksController->getStackPositionAtHex(currentHex, stack);
  342. myAnim->pos = coords;
  343. // true if creature haven't reached the final destination hex
  344. if ((curentMoveIndex + 1) < destTiles.size())
  345. {
  346. // update the next hex field which has to be reached by the stack
  347. curentMoveIndex++;
  348. oldPos = currentHex;
  349. currentHex = destTiles[curentMoveIndex];
  350. // request re-initialization
  351. initialized = false;
  352. }
  353. else
  354. delete this;
  355. }
  356. }
  357. MovementAnimation::~MovementAnimation()
  358. {
  359. assert(stack);
  360. myAnim->pos = owner.stacksController->getStackPositionAtHex(currentHex, stack);
  361. if(owner.moveSoundHander != -1)
  362. {
  363. CCS->soundh->stopSound(owner.moveSoundHander);
  364. owner.moveSoundHander = -1;
  365. }
  366. }
  367. MovementAnimation::MovementAnimation(BattleInterface & owner, const CStack *_stack, std::vector<BattleHex> _destTiles, int _distance)
  368. : StackMoveAnimation(owner, _stack, _destTiles.front()),
  369. destTiles(_destTiles),
  370. curentMoveIndex(0),
  371. oldPos(stack->getPosition()),
  372. begX(0), begY(0),
  373. distanceX(0), distanceY(0),
  374. timeToMove(0.0),
  375. progress(0.0)
  376. {
  377. logAnim->debug("Created movement anim for %s", stack->getName());
  378. }
  379. MovementEndAnimation::MovementEndAnimation(BattleInterface & owner, const CStack * _stack, BattleHex destTile)
  380. : StackMoveAnimation(owner, _stack, destTile)
  381. {
  382. logAnim->debug("Created movement end anim for %s", stack->getName());
  383. }
  384. bool MovementEndAnimation::init()
  385. {
  386. assert(stack);
  387. assert(!myAnim->isDeadOrDying());
  388. if(!stack || myAnim->isDeadOrDying())
  389. {
  390. delete this;
  391. return false;
  392. }
  393. logAnim->info("CMovementEndAnimation::init: stack %s", stack->getName());
  394. CCS->soundh->playSound(battle_sound(stack->getCreature(), endMoving));
  395. if(!myAnim->framesInGroup(ECreatureAnimType::MOVE_END))
  396. {
  397. delete this;
  398. return false;
  399. }
  400. myAnim->setType(ECreatureAnimType::MOVE_END);
  401. myAnim->onAnimationReset += [&](){ delete this; };
  402. return true;
  403. }
  404. MovementEndAnimation::~MovementEndAnimation()
  405. {
  406. if(myAnim->getType() != ECreatureAnimType::DEAD)
  407. myAnim->setType(ECreatureAnimType::HOLDING); //resetting to default
  408. CCS->curh->show();
  409. }
  410. MovementStartAnimation::MovementStartAnimation(BattleInterface & owner, const CStack * _stack)
  411. : StackMoveAnimation(owner, _stack, _stack->getPosition())
  412. {
  413. logAnim->debug("Created movement start anim for %s", stack->getName());
  414. }
  415. bool MovementStartAnimation::init()
  416. {
  417. assert(stack);
  418. assert(!myAnim->isDeadOrDying());
  419. if(!stack || myAnim->isDeadOrDying())
  420. {
  421. delete this;
  422. return false;
  423. }
  424. logAnim->info("CMovementStartAnimation::init: stack %s", stack->getName());
  425. CCS->soundh->playSound(battle_sound(stack->getCreature(), startMoving));
  426. if(!myAnim->framesInGroup(ECreatureAnimType::MOVE_START))
  427. {
  428. delete this;
  429. return false;
  430. }
  431. myAnim->setType(ECreatureAnimType::MOVE_START);
  432. myAnim->onAnimationReset += [&](){ delete this; };
  433. return true;
  434. }
  435. ReverseAnimation::ReverseAnimation(BattleInterface & owner, const CStack * stack, BattleHex dest)
  436. : StackMoveAnimation(owner, stack, dest)
  437. {
  438. logAnim->debug("Created reverse anim for %s", stack->getName());
  439. }
  440. bool ReverseAnimation::init()
  441. {
  442. assert(myAnim);
  443. assert(!myAnim->isDeadOrDying());
  444. if(myAnim == nullptr || myAnim->isDeadOrDying())
  445. {
  446. delete this;
  447. return false; //there is no such creature
  448. }
  449. logAnim->info("CReverseAnimation::init: stack %s", stack->getName());
  450. if(myAnim->framesInGroup(ECreatureAnimType::TURN_L))
  451. {
  452. myAnim->playOnce(ECreatureAnimType::TURN_L);
  453. myAnim->onAnimationReset += std::bind(&ReverseAnimation::setupSecondPart, this);
  454. }
  455. else
  456. {
  457. setupSecondPart();
  458. }
  459. return true;
  460. }
  461. void BattleStackAnimation::rotateStack(BattleHex hex)
  462. {
  463. setStackFacingRight(stack, !stackFacingRight(stack));
  464. stackAnimation(stack)->pos = owner.stacksController->getStackPositionAtHex(hex, stack);
  465. }
  466. void ReverseAnimation::setupSecondPart()
  467. {
  468. assert(stack);
  469. if(!stack)
  470. {
  471. delete this;
  472. return;
  473. }
  474. rotateStack(currentHex);
  475. if(myAnim->framesInGroup(ECreatureAnimType::TURN_R))
  476. {
  477. myAnim->playOnce(ECreatureAnimType::TURN_R);
  478. myAnim->onAnimationReset += [&](){ delete this; };
  479. }
  480. else
  481. delete this;
  482. }
  483. bool ResurrectionAnimation::init()
  484. {
  485. assert(stack);
  486. if(!stack)
  487. {
  488. delete this;
  489. return false;
  490. }
  491. logAnim->info("CResurrectionAnimation::init: stack %s", stack->getName());
  492. myAnim->playOnce(ECreatureAnimType::RESURRECTION);
  493. myAnim->onAnimationReset += [&](){ delete this; };
  494. return true;
  495. }
  496. ResurrectionAnimation::ResurrectionAnimation(BattleInterface & owner, const CStack * _stack):
  497. BattleStackAnimation(owner, _stack)
  498. {
  499. }
  500. bool FadingAnimation::init()
  501. {
  502. logAnim->info("FadingAnimation::init: stack %s", stack->getName());
  503. //TODO: pause animation?
  504. return true;
  505. }
  506. void FadingAnimation::nextFrame()
  507. {
  508. float elapsed = GH.mainFPSmng->getElapsedMilliseconds() / 1000.f;
  509. float fullTime = AnimationControls::getFadeInDuration();
  510. float delta = elapsed / fullTime;
  511. progress += delta;
  512. if (progress > 1.0f)
  513. progress = 1.0f;
  514. uint8_t factor = stack->cloned ? 128 : 255;
  515. uint8_t blue = stack->cloned ? 128 : 0;
  516. uint8_t alpha = CSDL_Ext::lerp(from, dest, progress);
  517. ColorShifterRange shifterFade ({0, 0, blue, 0}, {factor, factor, 255, alpha});
  518. stackAnimation(stack)->shiftColor(&shifterFade);
  519. if (progress == 1.0f)
  520. delete this;
  521. }
  522. FadingAnimation::FadingAnimation(BattleInterface & owner, const CStack * _stack, uint8_t alphaFrom, uint8_t alphaDest):
  523. BattleStackAnimation(owner, _stack),
  524. from(alphaFrom),
  525. dest(alphaDest)
  526. {
  527. }
  528. RangedAttackAnimation::RangedAttackAnimation(BattleInterface & owner_, const CStack * attacker, BattleHex dest_, const CStack * defender)
  529. : AttackAnimation(owner_, attacker, dest_, defender),
  530. projectileEmitted(false)
  531. {
  532. }
  533. void RangedAttackAnimation::playSound()
  534. {
  535. CCS->soundh->playSound(battle_sound(getCreature(), shoot));
  536. }
  537. bool RangedAttackAnimation::init()
  538. {
  539. assert(attackingStack);
  540. assert(!myAnim->isDeadOrDying());
  541. if(!attackingStack || myAnim->isDeadOrDying())
  542. {
  543. //FIXME: how is this possible?
  544. logAnim->warn("Shooting animation has not started yet but attacker is dead! Aborting...");
  545. delete this;
  546. return false;
  547. }
  548. logAnim->info("CRangedAttackAnimation::init: stack %s", stack->getName());
  549. setAnimationGroup();
  550. initializeProjectile();
  551. return true;
  552. }
  553. void RangedAttackAnimation::setAnimationGroup()
  554. {
  555. Point shooterPos = stackAnimation(attackingStack)->pos.topLeft();
  556. Point shotTarget = owner.stacksController->getStackPositionAtHex(dest, defendingStack);
  557. //maximal angle in radians between straight horizontal line and shooting line for which shot is considered to be straight (absoulte value)
  558. static const double straightAngle = 0.2;
  559. double projectileAngle = -atan2(shotTarget.y - shooterPos.y, std::abs(shotTarget.x - shooterPos.x));
  560. // Calculate projectile start position. Offsets are read out of the CRANIM.TXT.
  561. if (projectileAngle > straightAngle)
  562. group = getUpwardsGroup();
  563. else if (projectileAngle < -straightAngle)
  564. group = getDownwardsGroup();
  565. else
  566. group = getForwardGroup();
  567. }
  568. void RangedAttackAnimation::initializeProjectile()
  569. {
  570. const CCreature *shooterInfo = getCreature();
  571. Point shotTarget = owner.stacksController->getStackPositionAtHex(dest, defendingStack) + Point(225, 225);
  572. Point shotOrigin = stackAnimation(attackingStack)->pos.topLeft() + Point(222, 265);
  573. int multiplier = stackFacingRight(attackingStack) ? 1 : -1;
  574. if (group == getUpwardsGroup())
  575. {
  576. shotOrigin.x += ( -25 + shooterInfo->animation.upperRightMissleOffsetX ) * multiplier;
  577. shotOrigin.y += shooterInfo->animation.upperRightMissleOffsetY;
  578. }
  579. else if (group == getDownwardsGroup())
  580. {
  581. shotOrigin.x += ( -25 + shooterInfo->animation.lowerRightMissleOffsetX ) * multiplier;
  582. shotOrigin.y += shooterInfo->animation.lowerRightMissleOffsetY;
  583. }
  584. else if (group == getForwardGroup())
  585. {
  586. shotOrigin.x += ( -25 + shooterInfo->animation.rightMissleOffsetX ) * multiplier;
  587. shotOrigin.y += shooterInfo->animation.rightMissleOffsetY;
  588. }
  589. else
  590. {
  591. assert(0);
  592. }
  593. createProjectile(shotOrigin, shotTarget);
  594. }
  595. void RangedAttackAnimation::emitProjectile()
  596. {
  597. logAnim->info("Ranged attack projectile emitted");
  598. owner.projectilesController->emitStackProjectile(attackingStack);
  599. projectileEmitted = true;
  600. }
  601. void RangedAttackAnimation::nextFrame()
  602. {
  603. // animation should be paused if there is an active projectile
  604. if (projectileEmitted)
  605. {
  606. if (owner.projectilesController->hasActiveProjectile(attackingStack))
  607. stackAnimation(attackingStack)->pause();
  608. else
  609. {
  610. stackAnimation(attackingStack)->play();
  611. if(owner.getAnimationCondition(EAnimationEvents::HIT) == false)
  612. owner.setAnimationCondition(EAnimationEvents::HIT, true);
  613. }
  614. }
  615. AttackAnimation::nextFrame();
  616. if (!projectileEmitted)
  617. {
  618. // emit projectile once animation playback reached "climax" frame
  619. if ( stackAnimation(attackingStack)->getCurrentFrame() >= getAttackClimaxFrame() )
  620. {
  621. emitProjectile();
  622. stackAnimation(attackingStack)->pause();
  623. return;
  624. }
  625. }
  626. }
  627. RangedAttackAnimation::~RangedAttackAnimation()
  628. {
  629. assert(!owner.projectilesController->hasActiveProjectile(attackingStack));
  630. assert(projectileEmitted);
  631. // FIXME: is this possible? Animation is over but we're yet to fire projectile?
  632. if (!projectileEmitted)
  633. {
  634. logAnim->warn("Shooting animation has finished but projectile was not emitted! Emitting it now...");
  635. emitProjectile();
  636. }
  637. }
  638. ShootingAnimation::ShootingAnimation(BattleInterface & owner, const CStack * attacker, BattleHex _dest, const CStack * _attacked)
  639. : RangedAttackAnimation(owner, attacker, _dest, _attacked)
  640. {
  641. logAnim->debug("Created shooting anim for %s", stack->getName());
  642. }
  643. void ShootingAnimation::createProjectile(const Point & from, const Point & dest) const
  644. {
  645. owner.projectilesController->createProjectile(attackingStack, from, dest);
  646. }
  647. uint32_t ShootingAnimation::getAttackClimaxFrame() const
  648. {
  649. const CCreature *shooterInfo = getCreature();
  650. return shooterInfo->animation.attackClimaxFrame;
  651. }
  652. ECreatureAnimType::Type ShootingAnimation::getUpwardsGroup() const
  653. {
  654. return ECreatureAnimType::SHOOT_UP;
  655. }
  656. ECreatureAnimType::Type ShootingAnimation::getForwardGroup() const
  657. {
  658. return ECreatureAnimType::SHOOT_FRONT;
  659. }
  660. ECreatureAnimType::Type ShootingAnimation::getDownwardsGroup() const
  661. {
  662. return ECreatureAnimType::SHOOT_DOWN;
  663. }
  664. CatapultAnimation::CatapultAnimation(BattleInterface & owner, const CStack * attacker, BattleHex _dest, const CStack * _attacked, int _catapultDmg)
  665. : ShootingAnimation(owner, attacker, _dest, _attacked),
  666. catapultDamage(_catapultDmg),
  667. explosionEmitted(false)
  668. {
  669. logAnim->debug("Created shooting anim for %s", stack->getName());
  670. }
  671. void CatapultAnimation::nextFrame()
  672. {
  673. ShootingAnimation::nextFrame();
  674. if ( explosionEmitted)
  675. return;
  676. if ( !projectileEmitted)
  677. return;
  678. if (owner.projectilesController->hasActiveProjectile(attackingStack))
  679. return;
  680. explosionEmitted = true;
  681. Point shotTarget = owner.stacksController->getStackPositionAtHex(dest, defendingStack) + Point(225, 225) - Point(126, 105);
  682. if(catapultDamage > 0)
  683. owner.stacksController->addNewAnim( new PointEffectAnimation(owner, "WALLHIT", "SGEXPL.DEF", shotTarget));
  684. else
  685. owner.stacksController->addNewAnim( new PointEffectAnimation(owner, "WALLMISS", "CSGRCK.DEF", shotTarget));
  686. }
  687. void CatapultAnimation::createProjectile(const Point & from, const Point & dest) const
  688. {
  689. owner.projectilesController->createCatapultProjectile(attackingStack, from, dest);
  690. }
  691. CastAnimation::CastAnimation(BattleInterface & owner_, const CStack * attacker, BattleHex dest_, const CStack * defender, const CSpell * spell)
  692. : RangedAttackAnimation(owner_, attacker, dest_, defender),
  693. spell(spell)
  694. {
  695. assert(dest.isValid());// FIXME: when?
  696. if(!dest_.isValid() && defender)
  697. dest = defender->getPosition();
  698. }
  699. ECreatureAnimType::Type CastAnimation::getUpwardsGroup() const
  700. {
  701. return findValidGroup({
  702. ECreatureAnimType::CAST_UP,
  703. ECreatureAnimType::SPECIAL_UP,
  704. ECreatureAnimType::SHOOT_UP,
  705. ECreatureAnimType::ATTACK_UP
  706. });
  707. }
  708. ECreatureAnimType::Type CastAnimation::getForwardGroup() const
  709. {
  710. return findValidGroup({
  711. ECreatureAnimType::CAST_FRONT,
  712. ECreatureAnimType::SPECIAL_FRONT,
  713. ECreatureAnimType::SHOOT_FRONT,
  714. ECreatureAnimType::ATTACK_FRONT
  715. });
  716. }
  717. ECreatureAnimType::Type CastAnimation::getDownwardsGroup() const
  718. {
  719. return findValidGroup({
  720. ECreatureAnimType::CAST_DOWN,
  721. ECreatureAnimType::SPECIAL_DOWN,
  722. ECreatureAnimType::SHOOT_DOWN,
  723. ECreatureAnimType::ATTACK_DOWN
  724. });
  725. }
  726. void CastAnimation::createProjectile(const Point & from, const Point & dest) const
  727. {
  728. if (!spell->animationInfo.projectile.empty())
  729. owner.projectilesController->createSpellProjectile(attackingStack, from, dest, spell);
  730. }
  731. uint32_t CastAnimation::getAttackClimaxFrame() const
  732. {
  733. //TODO: allow defining this parameter in config file, separately from attackClimaxFrame of missile attacks
  734. uint32_t maxFrames = stackAnimation(attackingStack)->framesInGroup(group);
  735. if (maxFrames > 2)
  736. return maxFrames - 2;
  737. return 0;
  738. }
  739. PointEffectAnimation::PointEffectAnimation(BattleInterface & owner, std::string soundName, std::string animationName, int effects):
  740. BattleAnimation(owner),
  741. animation(std::make_shared<CAnimation>(animationName)),
  742. soundName(soundName),
  743. effectFlags(effects),
  744. soundPlayed(false),
  745. soundFinished(false),
  746. effectFinished(false)
  747. {
  748. logAnim->info("CPointEffectAnimation::init: effect %s", animationName);
  749. }
  750. PointEffectAnimation::PointEffectAnimation(BattleInterface & owner, std::string soundName, std::string animationName, std::vector<BattleHex> hex, int effects):
  751. PointEffectAnimation(owner, soundName, animationName, effects)
  752. {
  753. battlehexes = hex;
  754. }
  755. PointEffectAnimation::PointEffectAnimation(BattleInterface & owner, std::string soundName, std::string animationName, BattleHex hex, int effects):
  756. PointEffectAnimation(owner, soundName, animationName, effects)
  757. {
  758. assert(hex.isValid());
  759. battlehexes.push_back(hex);
  760. }
  761. PointEffectAnimation::PointEffectAnimation(BattleInterface & owner, std::string soundName, std::string animationName, std::vector<Point> pos, int effects):
  762. PointEffectAnimation(owner, soundName, animationName, effects)
  763. {
  764. positions = pos;
  765. }
  766. PointEffectAnimation::PointEffectAnimation(BattleInterface & owner, std::string soundName, std::string animationName, Point pos, int effects):
  767. PointEffectAnimation(owner, soundName, animationName, effects)
  768. {
  769. positions.push_back(pos);
  770. }
  771. PointEffectAnimation::PointEffectAnimation(BattleInterface & owner, std::string soundName, std::string animationName, Point pos, BattleHex hex, int effects):
  772. PointEffectAnimation(owner, soundName, animationName, effects)
  773. {
  774. assert(hex.isValid());
  775. battlehexes.push_back(hex);
  776. positions.push_back(pos);
  777. }
  778. bool PointEffectAnimation::init()
  779. {
  780. animation->preload();
  781. auto first = animation->getImage(0, 0, true);
  782. if(!first)
  783. {
  784. delete this;
  785. return false;
  786. }
  787. if (screenFill())
  788. {
  789. for(int i=0; i * first->width() < owner.pos.w ; ++i)
  790. for(int j=0; j * first->height() < owner.pos.h ; ++j)
  791. positions.push_back(Point( owner.pos.x + i * first->width(), owner.pos.y + j * first->height()));
  792. }
  793. BattleEffect be;
  794. be.effectID = ID;
  795. be.animation = animation;
  796. be.currentFrame = 0;
  797. for (size_t i = 0; i < std::max(battlehexes.size(), positions.size()); ++i)
  798. {
  799. bool hasTile = i < battlehexes.size();
  800. bool hasPosition = i < positions.size();
  801. if (hasTile && !forceOnTop())
  802. be.position = battlehexes[i];
  803. else
  804. be.position = BattleHex::INVALID;
  805. if (hasPosition)
  806. {
  807. be.x = positions[i].x;
  808. be.y = positions[i].y;
  809. }
  810. else
  811. {
  812. const CStack * destStack = owner.getCurrentPlayerInterface()->cb->battleGetStackByPos(battlehexes[i], false);
  813. Rect tilePos = owner.fieldController->hexPositionAbsolute(battlehexes[i]);
  814. be.x = tilePos.x + tilePos.w/2 - first->width()/2;
  815. if(destStack && destStack->doubleWide()) // Correction for 2-hex creatures.
  816. be.x += (destStack->side == BattleSide::ATTACKER ? -1 : 1)*tilePos.w/2;
  817. if (alignToBottom())
  818. be.y = tilePos.y + tilePos.h - first->height();
  819. else
  820. be.y = tilePos.y - first->height()/2;
  821. }
  822. owner.effectsController->battleEffects.push_back(be);
  823. }
  824. return true;
  825. }
  826. void PointEffectAnimation::nextFrame()
  827. {
  828. playSound();
  829. playEffect();
  830. if (soundFinished && effectFinished)
  831. {
  832. //remove visual effect itself only if sound has finished as well - necessary for obstacles like force field
  833. clearEffect();
  834. delete this;
  835. }
  836. }
  837. bool PointEffectAnimation::alignToBottom() const
  838. {
  839. return effectFlags & ALIGN_TO_BOTTOM;
  840. }
  841. bool PointEffectAnimation::waitForSound() const
  842. {
  843. return effectFlags & WAIT_FOR_SOUND;
  844. }
  845. bool PointEffectAnimation::forceOnTop() const
  846. {
  847. return effectFlags & FORCE_ON_TOP;
  848. }
  849. bool PointEffectAnimation::screenFill() const
  850. {
  851. return effectFlags & SCREEN_FILL;
  852. }
  853. void PointEffectAnimation::onEffectFinished()
  854. {
  855. effectFinished = true;
  856. }
  857. void PointEffectAnimation::onSoundFinished()
  858. {
  859. soundFinished = true;
  860. }
  861. void PointEffectAnimation::playSound()
  862. {
  863. if (soundPlayed)
  864. return;
  865. soundPlayed = true;
  866. if (soundName.empty())
  867. {
  868. onSoundFinished();
  869. return;
  870. }
  871. int channel = CCS->soundh->playSound(soundName);
  872. if (!waitForSound() || channel == -1)
  873. onSoundFinished();
  874. else
  875. CCS->soundh->setCallback(channel, [&](){ onSoundFinished(); });
  876. }
  877. void PointEffectAnimation::playEffect()
  878. {
  879. if ( effectFinished )
  880. return;
  881. for(auto & elem : owner.effectsController->battleEffects)
  882. {
  883. if(elem.effectID == ID)
  884. {
  885. elem.currentFrame += AnimationControls::getSpellEffectSpeed() * GH.mainFPSmng->getElapsedMilliseconds() / 1000;
  886. if(elem.currentFrame >= elem.animation->size())
  887. {
  888. elem.currentFrame = elem.animation->size() - 1;
  889. onEffectFinished();
  890. break;
  891. }
  892. }
  893. }
  894. }
  895. void PointEffectAnimation::clearEffect()
  896. {
  897. auto & effects = owner.effectsController->battleEffects;
  898. vstd::erase_if(effects, [&](const BattleEffect & effect){
  899. return effect.effectID == ID;
  900. });
  901. }
  902. PointEffectAnimation::~PointEffectAnimation()
  903. {
  904. assert(effectFinished);
  905. assert(soundFinished);
  906. }
  907. HeroCastAnimation::HeroCastAnimation(BattleInterface & owner, std::shared_ptr<BattleHero> hero, BattleHex dest, const CStack * defender, const CSpell * spell):
  908. BattleAnimation(owner),
  909. projectileEmitted(false),
  910. hero(hero),
  911. target(defender),
  912. tile(dest),
  913. spell(spell)
  914. {
  915. }
  916. bool HeroCastAnimation::init()
  917. {
  918. hero->setPhase(EHeroAnimType::CAST_SPELL);
  919. hero->onPhaseFinished([&](){
  920. assert(owner.getAnimationCondition(EAnimationEvents::HIT) == true);
  921. delete this;
  922. });
  923. initializeProjectile();
  924. return true;
  925. }
  926. void HeroCastAnimation::initializeProjectile()
  927. {
  928. // spell has no projectile to play, ignore this step
  929. if (spell->animationInfo.projectile.empty())
  930. return;
  931. // targeted spells should have well, target
  932. assert(tile.isValid());
  933. Point srccoord = hero->pos.center();
  934. Point destcoord = owner.stacksController->getStackPositionAtHex(tile, target); //position attacked by projectile
  935. destcoord += Point(222, 265); // FIXME: what are these constants?
  936. owner.projectilesController->createSpellProjectile( nullptr, srccoord, destcoord, spell);
  937. }
  938. void HeroCastAnimation::emitProjectile()
  939. {
  940. if (projectileEmitted)
  941. return;
  942. //spell has no projectile to play, skip this step and immediately play hit animations
  943. if (spell->animationInfo.projectile.empty())
  944. emitAnimationEvent();
  945. else
  946. owner.projectilesController->emitStackProjectile( nullptr );
  947. projectileEmitted = true;
  948. }
  949. void HeroCastAnimation::emitAnimationEvent()
  950. {
  951. if(owner.getAnimationCondition(EAnimationEvents::HIT) == false)
  952. owner.setAnimationCondition(EAnimationEvents::HIT, true);
  953. }
  954. void HeroCastAnimation::nextFrame()
  955. {
  956. float frame = hero->getFrame();
  957. if (frame < 4.0f) // middle point of animation //TODO: un-hardcode
  958. return;
  959. if (!projectileEmitted)
  960. {
  961. emitProjectile();
  962. hero->pause();
  963. return;
  964. }
  965. if (!owner.projectilesController->hasActiveProjectile(nullptr))
  966. {
  967. emitAnimationEvent();
  968. //TODO: check H3 - it is possible that hero animation should be paused until hit effect is over, not just projectile
  969. hero->play();
  970. }
  971. }