BattleAnimationClasses.cpp 30 KB

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