BattleAnimationClasses.cpp 31 KB

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