CBattleAnimations.cpp 30 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129
  1. /*
  2. * CBattleAnimations.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 "CBattleAnimations.h"
  12. #include <boost/math/constants/constants.hpp>
  13. #include "CBattleInterfaceClasses.h"
  14. #include "CBattleInterface.h"
  15. #include "CBattleProjectileController.h"
  16. #include "CBattleSiegeController.h"
  17. #include "CCreatureAnimation.h"
  18. #include "../CGameInfo.h"
  19. #include "../CMusicHandler.h"
  20. #include "../CPlayerInterface.h"
  21. #include "../Graphics.h"
  22. #include "../gui/CAnimation.h"
  23. #include "../gui/CCursorHandler.h"
  24. #include "../gui/CGuiHandler.h"
  25. #include "../gui/SDL_Extensions.h"
  26. #include "../../CCallback.h"
  27. #include "../../lib/CStack.h"
  28. #include "../../lib/CTownHandler.h"
  29. #include "../../lib/mapObjects/CGTownInstance.h"
  30. CBattleAnimation::CBattleAnimation(CBattleInterface * _owner)
  31. : owner(_owner), ID(_owner->animIDhelper++)
  32. {
  33. logAnim->trace("Animation #%d created", ID);
  34. }
  35. CBattleAnimation::~CBattleAnimation()
  36. {
  37. logAnim->trace("Animation #%d deleted", ID);
  38. }
  39. void CBattleAnimation::endAnim()
  40. {
  41. logAnim->trace("Animation #%d ended, type is %s", ID, typeid(this).name());
  42. for(auto & elem : owner->pendingAnims)
  43. {
  44. if(elem.first == this)
  45. {
  46. elem.first = nullptr;
  47. }
  48. }
  49. }
  50. bool CBattleAnimation::isEarliest(bool perStackConcurrency)
  51. {
  52. int lowestMoveID = owner->animIDhelper + 5;
  53. CBattleStackAnimation * thAnim = dynamic_cast<CBattleStackAnimation *>(this);
  54. CEffectAnimation * thSen = dynamic_cast<CEffectAnimation *>(this);
  55. for(auto & elem : owner->pendingAnims)
  56. {
  57. CBattleStackAnimation * stAnim = dynamic_cast<CBattleStackAnimation *>(elem.first);
  58. CEffectAnimation * sen = dynamic_cast<CEffectAnimation *>(elem.first);
  59. if(perStackConcurrency && stAnim && thAnim && stAnim->stack->ID != thAnim->stack->ID)
  60. continue;
  61. if(perStackConcurrency && sen && thSen && sen != thSen)
  62. continue;
  63. CReverseAnimation * revAnim = dynamic_cast<CReverseAnimation *>(stAnim);
  64. if(revAnim && thAnim && stAnim && stAnim->stack->ID == thAnim->stack->ID && revAnim->priority)
  65. return false;
  66. if(elem.first)
  67. vstd::amin(lowestMoveID, elem.first->ID);
  68. }
  69. return (ID == lowestMoveID) || (lowestMoveID == (owner->animIDhelper + 5));
  70. }
  71. CBattleStackAnimation::CBattleStackAnimation(CBattleInterface * owner, const CStack * stack)
  72. : CBattleAnimation(owner),
  73. myAnim(owner->creAnims[stack->ID]),
  74. stack(stack)
  75. {
  76. assert(myAnim);
  77. }
  78. void CBattleStackAnimation::shiftColor(const ColorShifter * shifter)
  79. {
  80. assert(myAnim);
  81. myAnim->shiftColor(shifter);
  82. }
  83. void CAttackAnimation::nextFrame()
  84. {
  85. if(myAnim->getType() != group)
  86. {
  87. myAnim->setType(group);
  88. myAnim->onAnimationReset += std::bind(&CAttackAnimation::endAnim, this);
  89. }
  90. if(!soundPlayed)
  91. {
  92. if(shooting)
  93. CCS->soundh->playSound(battle_sound(attackingStack->getCreature(), shoot));
  94. else
  95. CCS->soundh->playSound(battle_sound(attackingStack->getCreature(), attack));
  96. soundPlayed = true;
  97. }
  98. CBattleAnimation::nextFrame();
  99. }
  100. void CAttackAnimation::endAnim()
  101. {
  102. myAnim->setType(CCreatureAnim::HOLDING);
  103. CBattleStackAnimation::endAnim();
  104. }
  105. bool CAttackAnimation::checkInitialConditions()
  106. {
  107. for(auto & elem : owner->pendingAnims)
  108. {
  109. CBattleStackAnimation * stAnim = dynamic_cast<CBattleStackAnimation *>(elem.first);
  110. CReverseAnimation * revAnim = dynamic_cast<CReverseAnimation *>(stAnim);
  111. if(revAnim && attackedStack) // enemy must be fully reversed
  112. {
  113. if (revAnim->stack->ID == attackedStack->ID)
  114. return false;
  115. }
  116. }
  117. return isEarliest(false);
  118. }
  119. CAttackAnimation::CAttackAnimation(CBattleInterface *_owner, const CStack *attacker, BattleHex _dest, const CStack *defender)
  120. : CBattleStackAnimation(_owner, attacker),
  121. shooting(false), group(CCreatureAnim::SHOOT_FRONT),
  122. soundPlayed(false),
  123. dest(_dest), attackedStack(defender), attackingStack(attacker)
  124. {
  125. assert(attackingStack && "attackingStack is nullptr in CBattleAttack::CBattleAttack !\n");
  126. attackingStackPosBeforeReturn = attackingStack->getPosition();
  127. }
  128. CDefenceAnimation::CDefenceAnimation(StackAttackedInfo _attackedInfo, CBattleInterface * _owner)
  129. : CBattleStackAnimation(_owner, _attackedInfo.defender),
  130. attacker(_attackedInfo.attacker), rangedAttack(_attackedInfo.indirectAttack),
  131. killed(_attackedInfo.killed), timeToWait(0)
  132. {
  133. logAnim->debug("Created defence anim for %s", _attackedInfo.defender->getName());
  134. }
  135. bool CDefenceAnimation::init()
  136. {
  137. if(attacker == nullptr && owner->battleEffects.size() > 0)
  138. return false;
  139. ui32 lowestMoveID = owner->animIDhelper + 5;
  140. for(auto & elem : owner->pendingAnims)
  141. {
  142. CDefenceAnimation * defAnim = dynamic_cast<CDefenceAnimation *>(elem.first);
  143. if(defAnim && defAnim->stack->ID != stack->ID)
  144. continue;
  145. CAttackAnimation * attAnim = dynamic_cast<CAttackAnimation *>(elem.first);
  146. if(attAnim && attAnim->stack->ID != stack->ID)
  147. continue;
  148. CEffectAnimation * sen = dynamic_cast<CEffectAnimation *>(elem.first);
  149. if (sen)
  150. continue;
  151. CReverseAnimation * animAsRev = dynamic_cast<CReverseAnimation *>(elem.first);
  152. if(animAsRev)
  153. return false;
  154. if(elem.first)
  155. vstd::amin(lowestMoveID, elem.first->ID);
  156. }
  157. if(ID > lowestMoveID)
  158. return false;
  159. //reverse unit if necessary
  160. if(attacker && owner->getCurrentPlayerInterface()->cb->isToReverse(stack->getPosition(), attacker->getPosition(), owner->creDir[stack->ID], attacker->doubleWide(), owner->creDir[attacker->ID]))
  161. {
  162. owner->addNewAnim(new CReverseAnimation(owner, stack, stack->getPosition(), true));
  163. return false;
  164. }
  165. //unit reversed
  166. if(rangedAttack && attacker != nullptr && owner->projectilesController->hasActiveProjectile(attacker)) //delay hit animation
  167. {
  168. return false;
  169. }
  170. // synchronize animation with attacker, unless defending or attacked by shooter:
  171. // wait for 1/2 of attack animation
  172. if (!rangedAttack && getMyAnimType() != CCreatureAnim::DEFENCE)
  173. {
  174. float frameLength = AnimationControls::getCreatureAnimationSpeed(
  175. stack->getCreature(), owner->creAnims[stack->ID].get(), getMyAnimType());
  176. timeToWait = myAnim->framesInGroup(getMyAnimType()) * frameLength / 2;
  177. myAnim->setType(CCreatureAnim::HOLDING);
  178. }
  179. else
  180. {
  181. timeToWait = 0;
  182. startAnimation();
  183. }
  184. return true; //initialized successfuly
  185. }
  186. std::string CDefenceAnimation::getMySound()
  187. {
  188. if(killed)
  189. return battle_sound(stack->getCreature(), killed);
  190. else if(stack->defendingAnim)
  191. return battle_sound(stack->getCreature(), defend);
  192. else
  193. return battle_sound(stack->getCreature(), wince);
  194. }
  195. CCreatureAnim::EAnimType CDefenceAnimation::getMyAnimType()
  196. {
  197. if(killed)
  198. {
  199. if(rangedAttack && myAnim->framesInGroup(CCreatureAnim::DEATH_RANGED) > 0)
  200. return CCreatureAnim::DEATH_RANGED;
  201. else
  202. return CCreatureAnim::DEATH;
  203. }
  204. if(stack->defendingAnim)
  205. return CCreatureAnim::DEFENCE;
  206. else
  207. return CCreatureAnim::HITTED;
  208. }
  209. void CDefenceAnimation::startAnimation()
  210. {
  211. CCS->soundh->playSound(getMySound());
  212. myAnim->setType(getMyAnimType());
  213. myAnim->onAnimationReset += std::bind(&CDefenceAnimation::endAnim, this);
  214. }
  215. void CDefenceAnimation::nextFrame()
  216. {
  217. if (timeToWait > 0)
  218. {
  219. timeToWait -= float(GH.mainFPSmng->getElapsedMilliseconds()) / 1000;
  220. if (timeToWait <= 0)
  221. startAnimation();
  222. }
  223. CBattleAnimation::nextFrame();
  224. }
  225. void CDefenceAnimation::endAnim()
  226. {
  227. if(killed)
  228. {
  229. if(rangedAttack && myAnim->framesInGroup(CCreatureAnim::DEAD_RANGED) > 0)
  230. myAnim->setType(CCreatureAnim::DEAD_RANGED);
  231. else
  232. myAnim->setType(CCreatureAnim::DEAD);
  233. }
  234. else
  235. {
  236. myAnim->setType(CCreatureAnim::HOLDING);
  237. }
  238. CBattleAnimation::endAnim();
  239. delete this;
  240. }
  241. CDummyAnimation::CDummyAnimation(CBattleInterface * _owner, int howManyFrames)
  242. : CBattleAnimation(_owner), counter(0), howMany(howManyFrames)
  243. {
  244. logAnim->debug("Created dummy animation for %d frames", howManyFrames);
  245. }
  246. bool CDummyAnimation::init()
  247. {
  248. return true;
  249. }
  250. void CDummyAnimation::nextFrame()
  251. {
  252. counter++;
  253. if(counter > howMany)
  254. endAnim();
  255. }
  256. void CDummyAnimation::endAnim()
  257. {
  258. CBattleAnimation::endAnim();
  259. delete this;
  260. }
  261. bool CMeleeAttackAnimation::init()
  262. {
  263. if(!CAttackAnimation::checkInitialConditions())
  264. return false;
  265. if(!attackingStack || myAnim->isDead())
  266. {
  267. endAnim();
  268. return false;
  269. }
  270. bool toReverse = owner->getCurrentPlayerInterface()->cb->isToReverse(attackingStackPosBeforeReturn, attackedStack->getPosition(), owner->creDir[stack->ID], attackedStack->doubleWide(), owner->creDir[attackedStack->ID]);
  271. if(toReverse)
  272. {
  273. owner->addNewAnim(new CReverseAnimation(owner, stack, attackingStackPosBeforeReturn, true));
  274. return false;
  275. }
  276. // opponent must face attacker ( = different directions) before he can be attacked
  277. if(attackingStack && attackedStack &&
  278. owner->creDir[attackingStack->ID] == owner->creDir[attackedStack->ID])
  279. return false;
  280. //reversed
  281. shooting = false;
  282. static const CCreatureAnim::EAnimType mutPosToGroup[] =
  283. {
  284. CCreatureAnim::ATTACK_UP,
  285. CCreatureAnim::ATTACK_UP,
  286. CCreatureAnim::ATTACK_FRONT,
  287. CCreatureAnim::ATTACK_DOWN,
  288. CCreatureAnim::ATTACK_DOWN,
  289. CCreatureAnim::ATTACK_FRONT
  290. };
  291. static const CCreatureAnim::EAnimType mutPosToGroup2H[] =
  292. {
  293. CCreatureAnim::VCMI_2HEX_UP,
  294. CCreatureAnim::VCMI_2HEX_UP,
  295. CCreatureAnim::VCMI_2HEX_FRONT,
  296. CCreatureAnim::VCMI_2HEX_DOWN,
  297. CCreatureAnim::VCMI_2HEX_DOWN,
  298. CCreatureAnim::VCMI_2HEX_FRONT
  299. };
  300. int revShiftattacker = (attackingStack->side == BattleSide::ATTACKER ? -1 : 1);
  301. int mutPos = BattleHex::mutualPosition(attackingStackPosBeforeReturn, dest);
  302. if(mutPos == -1 && attackingStack->doubleWide())
  303. {
  304. mutPos = BattleHex::mutualPosition(attackingStackPosBeforeReturn + revShiftattacker, attackedStack->getPosition());
  305. }
  306. if (mutPos == -1 && attackedStack->doubleWide())
  307. {
  308. mutPos = BattleHex::mutualPosition(attackingStackPosBeforeReturn, attackedStack->occupiedHex());
  309. }
  310. if (mutPos == -1 && attackedStack->doubleWide() && attackingStack->doubleWide())
  311. {
  312. mutPos = BattleHex::mutualPosition(attackingStackPosBeforeReturn + revShiftattacker, attackedStack->occupiedHex());
  313. }
  314. switch(mutPos) //attack direction
  315. {
  316. case 0:
  317. case 1:
  318. case 2:
  319. case 3:
  320. case 4:
  321. case 5:
  322. group = mutPosToGroup[mutPos];
  323. if(attackingStack->hasBonusOfType(Bonus::TWO_HEX_ATTACK_BREATH))
  324. {
  325. CCreatureAnim::EAnimType group2H = mutPosToGroup2H[mutPos];
  326. if(myAnim->framesInGroup(group2H)>0)
  327. group = group2H;
  328. }
  329. break;
  330. default:
  331. logGlobal->error("Critical Error! Wrong dest in stackAttacking! dest: %d; attacking stack pos: %d; mutual pos: %d", dest.hex, attackingStackPosBeforeReturn, mutPos);
  332. group = CCreatureAnim::ATTACK_FRONT;
  333. break;
  334. }
  335. return true;
  336. }
  337. CMeleeAttackAnimation::CMeleeAttackAnimation(CBattleInterface * _owner, const CStack * attacker, BattleHex _dest, const CStack * _attacked)
  338. : CAttackAnimation(_owner, attacker, _dest, _attacked)
  339. {
  340. logAnim->debug("Created melee attack anim for %s", attacker->getName());
  341. }
  342. void CMeleeAttackAnimation::endAnim()
  343. {
  344. CAttackAnimation::endAnim();
  345. delete this;
  346. }
  347. bool CMovementAnimation::init()
  348. {
  349. if( !isEarliest(false) )
  350. return false;
  351. if(!stack || myAnim->isDead())
  352. {
  353. endAnim();
  354. return false;
  355. }
  356. if(owner->creAnims[stack->ID]->framesInGroup(CCreatureAnim::MOVING) == 0 ||
  357. stack->hasBonus(Selector::typeSubtype(Bonus::FLYING, 1)))
  358. {
  359. //no movement or teleport, end immediately
  360. endAnim();
  361. return false;
  362. }
  363. //reverse unit if necessary
  364. if(owner->shouldRotate(stack, oldPos, nextHex))
  365. {
  366. // it seems that H3 does NOT plays full rotation animation here in most situations
  367. // Logical since it takes quite a lot of time
  368. if (curentMoveIndex == 0) // full rotation only for moving towards first tile.
  369. {
  370. owner->addNewAnim(new CReverseAnimation(owner, stack, oldPos, true));
  371. return false;
  372. }
  373. else
  374. {
  375. CReverseAnimation::rotateStack(owner, stack, oldPos);
  376. }
  377. }
  378. if(myAnim->getType() != CCreatureAnim::MOVING)
  379. {
  380. myAnim->setType(CCreatureAnim::MOVING);
  381. }
  382. if (owner->moveSoundHander == -1)
  383. {
  384. owner->moveSoundHander = CCS->soundh->playSound(battle_sound(stack->getCreature(), move), -1);
  385. }
  386. Point begPosition = CClickableHex::getXYUnitAnim(oldPos, stack, owner);
  387. Point endPosition = CClickableHex::getXYUnitAnim(nextHex, stack, owner);
  388. timeToMove = AnimationControls::getMovementDuration(stack->getCreature());
  389. begX = begPosition.x;
  390. begY = begPosition.y;
  391. progress = 0;
  392. distanceX = endPosition.x - begPosition.x;
  393. distanceY = endPosition.y - begPosition.y;
  394. if (stack->hasBonus(Selector::type()(Bonus::FLYING)))
  395. {
  396. float distance = static_cast<float>(sqrt(distanceX * distanceX + distanceY * distanceY));
  397. timeToMove *= AnimationControls::getFlightDistance(stack->getCreature()) / distance;
  398. }
  399. return true;
  400. }
  401. void CMovementAnimation::nextFrame()
  402. {
  403. progress += float(GH.mainFPSmng->getElapsedMilliseconds()) / 1000 * timeToMove;
  404. //moving instructions
  405. myAnim->pos.x = static_cast<Sint16>(begX + distanceX * progress );
  406. myAnim->pos.y = static_cast<Sint16>(begY + distanceY * progress );
  407. CBattleAnimation::nextFrame();
  408. if(progress >= 1.0)
  409. {
  410. // Sets the position of the creature animation sprites
  411. Point coords = CClickableHex::getXYUnitAnim(nextHex, stack, owner);
  412. myAnim->pos = coords;
  413. // true if creature haven't reached the final destination hex
  414. if ((curentMoveIndex + 1) < destTiles.size())
  415. {
  416. // update the next hex field which has to be reached by the stack
  417. curentMoveIndex++;
  418. oldPos = nextHex;
  419. nextHex = destTiles[curentMoveIndex];
  420. // re-init animation
  421. for(auto & elem : owner->pendingAnims)
  422. {
  423. if (elem.first == this)
  424. {
  425. elem.second = false;
  426. break;
  427. }
  428. }
  429. }
  430. else
  431. endAnim();
  432. }
  433. }
  434. void CMovementAnimation::endAnim()
  435. {
  436. assert(stack);
  437. myAnim->pos = CClickableHex::getXYUnitAnim(nextHex, stack, owner);
  438. CBattleAnimation::endAnim();
  439. owner->addNewAnim(new CMovementEndAnimation(owner, stack, nextHex));
  440. if(owner->moveSoundHander != -1)
  441. {
  442. CCS->soundh->stopSound(owner->moveSoundHander);
  443. owner->moveSoundHander = -1;
  444. }
  445. delete this;
  446. }
  447. CMovementAnimation::CMovementAnimation(CBattleInterface *_owner, const CStack *_stack, std::vector<BattleHex> _destTiles, int _distance)
  448. : CBattleStackAnimation(_owner, _stack),
  449. destTiles(_destTiles),
  450. curentMoveIndex(0),
  451. oldPos(stack->getPosition()),
  452. begX(0), begY(0),
  453. distanceX(0), distanceY(0),
  454. timeToMove(0.0),
  455. progress(0.0),
  456. nextHex(destTiles.front())
  457. {
  458. logAnim->debug("Created movement anim for %s", stack->getName());
  459. }
  460. CMovementEndAnimation::CMovementEndAnimation(CBattleInterface * _owner, const CStack * _stack, BattleHex destTile)
  461. : CBattleStackAnimation(_owner, _stack), destinationTile(destTile)
  462. {
  463. logAnim->debug("Created movement end anim for %s", stack->getName());
  464. }
  465. bool CMovementEndAnimation::init()
  466. {
  467. if( !isEarliest(true) )
  468. return false;
  469. if(!stack || myAnim->framesInGroup(CCreatureAnim::MOVE_END) == 0 ||
  470. myAnim->isDead())
  471. {
  472. endAnim();
  473. return false;
  474. }
  475. CCS->soundh->playSound(battle_sound(stack->getCreature(), endMoving));
  476. myAnim->setType(CCreatureAnim::MOVE_END);
  477. myAnim->onAnimationReset += std::bind(&CMovementEndAnimation::endAnim, this);
  478. return true;
  479. }
  480. void CMovementEndAnimation::endAnim()
  481. {
  482. CBattleAnimation::endAnim();
  483. if(myAnim->getType() != CCreatureAnim::DEAD)
  484. myAnim->setType(CCreatureAnim::HOLDING); //resetting to default
  485. CCS->curh->show();
  486. delete this;
  487. }
  488. CMovementStartAnimation::CMovementStartAnimation(CBattleInterface * _owner, const CStack * _stack)
  489. : CBattleStackAnimation(_owner, _stack)
  490. {
  491. logAnim->debug("Created movement start anim for %s", stack->getName());
  492. }
  493. bool CMovementStartAnimation::init()
  494. {
  495. if( !isEarliest(false) )
  496. return false;
  497. if(!stack || myAnim->isDead())
  498. {
  499. CMovementStartAnimation::endAnim();
  500. return false;
  501. }
  502. CCS->soundh->playSound(battle_sound(stack->getCreature(), startMoving));
  503. myAnim->setType(CCreatureAnim::MOVE_START);
  504. myAnim->onAnimationReset += std::bind(&CMovementStartAnimation::endAnim, this);
  505. return true;
  506. }
  507. void CMovementStartAnimation::endAnim()
  508. {
  509. CBattleAnimation::endAnim();
  510. delete this;
  511. }
  512. CReverseAnimation::CReverseAnimation(CBattleInterface * _owner, const CStack * stack, BattleHex dest, bool _priority)
  513. : CBattleStackAnimation(_owner, stack), hex(dest), priority(_priority)
  514. {
  515. logAnim->debug("Created reverse anim for %s", stack->getName());
  516. }
  517. bool CReverseAnimation::init()
  518. {
  519. if(myAnim == nullptr || myAnim->isDead())
  520. {
  521. endAnim();
  522. return false; //there is no such creature
  523. }
  524. if(!priority && !isEarliest(false))
  525. return false;
  526. if(myAnim->framesInGroup(CCreatureAnim::TURN_L))
  527. {
  528. myAnim->setType(CCreatureAnim::TURN_L);
  529. myAnim->onAnimationReset += std::bind(&CReverseAnimation::setupSecondPart, this);
  530. }
  531. else
  532. {
  533. setupSecondPart();
  534. }
  535. return true;
  536. }
  537. void CReverseAnimation::endAnim()
  538. {
  539. CBattleAnimation::endAnim();
  540. if( stack->alive() )//don't do that if stack is dead
  541. myAnim->setType(CCreatureAnim::HOLDING);
  542. delete this;
  543. }
  544. void CReverseAnimation::rotateStack(CBattleInterface * owner, const CStack * stack, BattleHex hex)
  545. {
  546. owner->creDir[stack->ID] = !owner->creDir[stack->ID];
  547. owner->creAnims[stack->ID]->pos = CClickableHex::getXYUnitAnim(hex, stack, owner);
  548. }
  549. void CReverseAnimation::setupSecondPart()
  550. {
  551. if(!stack)
  552. {
  553. endAnim();
  554. return;
  555. }
  556. rotateStack(owner, stack, hex);
  557. if(myAnim->framesInGroup(CCreatureAnim::TURN_R))
  558. {
  559. myAnim->setType(CCreatureAnim::TURN_R);
  560. myAnim->onAnimationReset += std::bind(&CReverseAnimation::endAnim, this);
  561. }
  562. else
  563. endAnim();
  564. }
  565. CRangedAttackAnimation::CRangedAttackAnimation(CBattleInterface * owner_, const CStack * attacker, BattleHex dest_, const CStack * defender)
  566. : CAttackAnimation(owner_, attacker, dest_, defender)
  567. {
  568. }
  569. CShootingAnimation::CShootingAnimation(CBattleInterface * _owner, const CStack * attacker, BattleHex _dest, const CStack * _attacked, bool _catapult, int _catapultDmg)
  570. : CRangedAttackAnimation(_owner, attacker, _dest, _attacked),
  571. catapultDamage(_catapultDmg)
  572. {
  573. logAnim->debug("Created shooting anim for %s", stack->getName());
  574. }
  575. bool CShootingAnimation::init()
  576. {
  577. if( !CAttackAnimation::checkInitialConditions() )
  578. return false;
  579. const CStack * shooter = attackingStack;
  580. if(!shooter || myAnim->isDead())
  581. {
  582. endAnim();
  583. return false;
  584. }
  585. //reverse unit if necessary
  586. if (attackingStack && attackedStack && owner->getCurrentPlayerInterface()->cb->isToReverse(attackingStack->getPosition(), attackedStack->getPosition(), owner->creDir[attackingStack->ID], attackingStack->doubleWide(), owner->creDir[attackedStack->ID]))
  587. {
  588. owner->addNewAnim(new CReverseAnimation(owner, attackingStack, attackingStack->getPosition(), true));
  589. return false;
  590. }
  591. //FIXME: this cause freeze
  592. // opponent must face attacker ( = different directions) before he can be attacked
  593. //if (attackingStack && attackedStack && owner->creDir[attackingStack->ID] == owner->creDir[attackedStack->ID])
  594. // return false;
  595. //maximal angle in radians between straight horizontal line and shooting line for which shot is considered to be straight (absoulte value)
  596. static const double straightAngle = 0.2;
  597. // Get further info about the shooter e.g. relative pos of projectile to unit.
  598. // If the creature id is 149 then it's a arrow tower which has no additional info so get the
  599. // actual arrow tower shooter instead.
  600. const CCreature *shooterInfo = shooter->getCreature();
  601. if(shooterInfo->idNumber == CreatureID::ARROW_TOWERS)
  602. shooterInfo = owner->siegeController->turretCreature();
  603. Point shooterPos;
  604. Point shotPos;
  605. Point destPos;
  606. // NOTE: two lines below return different positions (very notable with 2-hex creatures). Obtaining via creanims seems to be more precise
  607. shooterPos = owner->creAnims[shooter->ID]->pos.topLeft();
  608. //xycoord = CClickableHex::getXYUnitAnim(shooter->position, true, shooter, owner);
  609. destPos = CClickableHex::getXYUnitAnim(dest, attackedStack, owner) + Point(225, 225);
  610. // to properly translate coordinates when shooter is rotated
  611. int multiplier = 0;
  612. if (shooter)
  613. multiplier = owner->creDir[shooter->ID] ? 1 : -1;
  614. else
  615. {
  616. assert(false); // unreachable?
  617. multiplier = shooter->getCreature()->idNumber == CreatureID::ARROW_TOWERS ? -1 : 1;
  618. }
  619. double projectileAngle = atan2(fabs((double)destPos.y - shooterPos.y), fabs((double)destPos.x - shooterPos.x));
  620. if(shooter->getPosition() < dest)
  621. projectileAngle = -projectileAngle;
  622. // Calculate projectile start position. Offsets are read out of the CRANIM.TXT.
  623. if (projectileAngle > straightAngle)
  624. {
  625. //upper shot
  626. shotPos.x = shooterPos.x + 222 + ( -25 + shooterInfo->animation.upperRightMissleOffsetX ) * multiplier;
  627. shotPos.y = shooterPos.y + 265 + shooterInfo->animation.upperRightMissleOffsetY;
  628. }
  629. else if (projectileAngle < -straightAngle)
  630. {
  631. //lower shot
  632. shotPos.x = shooterPos.x + 222 + ( -25 + shooterInfo->animation.lowerRightMissleOffsetX ) * multiplier;
  633. shotPos.y = shooterPos.y + 265 + shooterInfo->animation.lowerRightMissleOffsetY;
  634. }
  635. else
  636. {
  637. //straight shot
  638. shotPos.x = shooterPos.x + 222 + ( -25 + shooterInfo->animation.rightMissleOffsetX ) * multiplier;
  639. shotPos.y = shooterPos.y + 265 + shooterInfo->animation.rightMissleOffsetY;
  640. }
  641. owner->projectilesController->createProjectile(attackingStack, attackedStack, shotPos, destPos);
  642. //attack animation
  643. shooting = true;
  644. if(projectileAngle > straightAngle)
  645. group = CCreatureAnim::SHOOT_UP;
  646. else if(projectileAngle < -straightAngle)
  647. group = CCreatureAnim::SHOOT_DOWN;
  648. else //straight shot
  649. group = CCreatureAnim::SHOOT_FRONT;
  650. return true;
  651. }
  652. void CShootingAnimation::nextFrame()
  653. {
  654. for(auto & it : owner->pendingAnims)
  655. {
  656. CMovementStartAnimation * anim = dynamic_cast<CMovementStartAnimation *>(it.first);
  657. CReverseAnimation * anim2 = dynamic_cast<CReverseAnimation *>(it.first);
  658. if( (anim && anim->stack->ID == stack->ID) || (anim2 && anim2->stack->ID == stack->ID && anim2->priority ) )
  659. return;
  660. }
  661. CAttackAnimation::nextFrame();
  662. }
  663. void CShootingAnimation::endAnim()
  664. {
  665. // play wall hit/miss sound for catapult attack
  666. if(!attackedStack)
  667. {
  668. if(catapultDamage > 0)
  669. {
  670. CCS->soundh->playSound("WALLHIT");
  671. }
  672. else
  673. {
  674. CCS->soundh->playSound("WALLMISS");
  675. }
  676. }
  677. CAttackAnimation::endAnim();
  678. delete this;
  679. }
  680. CCastAnimation::CCastAnimation(CBattleInterface * owner_, const CStack * attacker, BattleHex dest_, const CStack * defender)
  681. : CRangedAttackAnimation(owner_, attacker, dest_, defender)
  682. {
  683. if(!dest_.isValid() && defender)
  684. dest = defender->getPosition();
  685. }
  686. bool CCastAnimation::init()
  687. {
  688. if(!CAttackAnimation::checkInitialConditions())
  689. return false;
  690. if(!attackingStack || myAnim->isDead())
  691. {
  692. endAnim();
  693. return false;
  694. }
  695. //reverse unit if necessary
  696. if(attackedStack)
  697. {
  698. if(owner->getCurrentPlayerInterface()->cb->isToReverse(attackingStack->getPosition(), attackedStack->getPosition(), owner->creDir[attackingStack->ID], attackingStack->doubleWide(), owner->creDir[attackedStack->ID]))
  699. {
  700. owner->addNewAnim(new CReverseAnimation(owner, attackingStack, attackingStack->getPosition(), true));
  701. return false;
  702. }
  703. }
  704. else
  705. {
  706. if(dest.isValid() && owner->getCurrentPlayerInterface()->cb->isToReverse(attackingStack->getPosition(), dest, owner->creDir[attackingStack->ID], false, false))
  707. {
  708. owner->addNewAnim(new CReverseAnimation(owner, attackingStack, attackingStack->getPosition(), true));
  709. return false;
  710. }
  711. }
  712. //TODO: display spell projectile here
  713. static const double straightAngle = 0.2;
  714. Point fromPos;
  715. Point destPos;
  716. // NOTE: two lines below return different positions (very notable with 2-hex creatures). Obtaining via creanims seems to be more precise
  717. fromPos = owner->creAnims[attackingStack->ID]->pos.topLeft();
  718. //xycoord = CClickableHex::getXYUnitAnim(shooter->getPosition(), true, shooter, owner);
  719. destPos = CClickableHex::getXYUnitAnim(dest, attackedStack, owner);
  720. double projectileAngle = atan2(fabs((double)destPos.y - fromPos.y), fabs((double)destPos.x - fromPos.x));
  721. if(attackingStack->getPosition() < dest)
  722. projectileAngle = -projectileAngle;
  723. if(projectileAngle > straightAngle)
  724. group = CCreatureAnim::VCMI_CAST_UP;
  725. else if(projectileAngle < -straightAngle)
  726. group = CCreatureAnim::VCMI_CAST_DOWN;
  727. else
  728. group = CCreatureAnim::VCMI_CAST_FRONT;
  729. //fall back to H3 cast/2hex
  730. //even if creature have 2hex attack instead of cast it is ok since we fall back to attack anyway
  731. if(myAnim->framesInGroup(group) == 0)
  732. {
  733. if(projectileAngle > straightAngle)
  734. group = CCreatureAnim::CAST_UP;
  735. else if(projectileAngle < -straightAngle)
  736. group = CCreatureAnim::CAST_DOWN;
  737. else
  738. group = CCreatureAnim::CAST_FRONT;
  739. }
  740. //fall back to ranged attack
  741. if(myAnim->framesInGroup(group) == 0)
  742. {
  743. if(projectileAngle > straightAngle)
  744. group = CCreatureAnim::SHOOT_UP;
  745. else if(projectileAngle < -straightAngle)
  746. group = CCreatureAnim::SHOOT_DOWN;
  747. else
  748. group = CCreatureAnim::SHOOT_FRONT;
  749. }
  750. //fall back to normal attack
  751. if(myAnim->framesInGroup(group) == 0)
  752. {
  753. if(projectileAngle > straightAngle)
  754. group = CCreatureAnim::ATTACK_UP;
  755. else if(projectileAngle < -straightAngle)
  756. group = CCreatureAnim::ATTACK_DOWN;
  757. else
  758. group = CCreatureAnim::ATTACK_FRONT;
  759. }
  760. return true;
  761. }
  762. void CCastAnimation::nextFrame()
  763. {
  764. for(auto & it : owner->pendingAnims)
  765. {
  766. CReverseAnimation * anim = dynamic_cast<CReverseAnimation *>(it.first);
  767. if(anim && anim->stack->ID == stack->ID && anim->priority)
  768. return;
  769. }
  770. if(myAnim->getType() != group)
  771. {
  772. myAnim->setType(group);
  773. myAnim->onAnimationReset += std::bind(&CAttackAnimation::endAnim, this);
  774. }
  775. CBattleAnimation::nextFrame();
  776. }
  777. void CCastAnimation::endAnim()
  778. {
  779. CAttackAnimation::endAnim();
  780. delete this;
  781. }
  782. CEffectAnimation::CEffectAnimation(CBattleInterface * _owner, std::string _customAnim, int _x, int _y, int _dx, int _dy, bool _Vflip, bool _alignToBottom)
  783. : CBattleAnimation(_owner),
  784. destTile(BattleHex::INVALID),
  785. x(_x),
  786. y(_y),
  787. dx(_dx),
  788. dy(_dy),
  789. Vflip(_Vflip),
  790. alignToBottom(_alignToBottom)
  791. {
  792. logAnim->debug("Created effect animation %s", _customAnim);
  793. customAnim = std::make_shared<CAnimation>(_customAnim);
  794. }
  795. CEffectAnimation::CEffectAnimation(CBattleInterface * _owner, std::shared_ptr<CAnimation> _customAnim, int _x, int _y, int _dx, int _dy)
  796. : CBattleAnimation(_owner),
  797. destTile(BattleHex::INVALID),
  798. customAnim(_customAnim),
  799. x(_x),
  800. y(_y),
  801. dx(_dx),
  802. dy(_dy),
  803. Vflip(false),
  804. alignToBottom(false)
  805. {
  806. logAnim->debug("Created custom effect animation");
  807. }
  808. CEffectAnimation::CEffectAnimation(CBattleInterface * _owner, std::string _customAnim, BattleHex _destTile, bool _Vflip, bool _alignToBottom)
  809. : CBattleAnimation(_owner),
  810. destTile(_destTile),
  811. x(-1),
  812. y(-1),
  813. dx(0),
  814. dy(0),
  815. Vflip(_Vflip),
  816. alignToBottom(_alignToBottom)
  817. {
  818. logAnim->debug("Created effect animation %s", _customAnim);
  819. customAnim = std::make_shared<CAnimation>(_customAnim);
  820. }
  821. bool CEffectAnimation::init()
  822. {
  823. if(!isEarliest(true))
  824. return false;
  825. const bool areaEffect = (!destTile.isValid() && x == -1 && y == -1);
  826. std::shared_ptr<CAnimation> animation = customAnim;
  827. animation->preload();
  828. if(Vflip)
  829. animation->verticalFlip();
  830. auto first = animation->getImage(0, 0, true);
  831. if(!first)
  832. {
  833. endAnim();
  834. return false;
  835. }
  836. if(areaEffect) //f.e. armageddon
  837. {
  838. for(int i=0; i * first->width() < owner->pos.w ; ++i)
  839. {
  840. for(int j=0; j * first->height() < owner->pos.h ; ++j)
  841. {
  842. BattleEffect be;
  843. be.effectID = ID;
  844. be.animation = animation;
  845. be.currentFrame = 0;
  846. be.x = i * first->width() + owner->pos.x;
  847. be.y = j * first->height() + owner->pos.y;
  848. be.position = BattleHex::INVALID;
  849. owner->battleEffects.push_back(be);
  850. }
  851. }
  852. }
  853. else // Effects targeted at a specific creature/hex.
  854. {
  855. const CStack * destStack = owner->getCurrentPlayerInterface()->cb->battleGetStackByPos(destTile, false);
  856. BattleEffect be;
  857. be.effectID = ID;
  858. be.animation = animation;
  859. be.currentFrame = 0;
  860. //todo: lightning anim frame count override
  861. // if(effect == 1)
  862. // be.maxFrame = 3;
  863. be.x = x;
  864. be.y = y;
  865. if(destTile.isValid())
  866. {
  867. Rect & tilePos = owner->bfield[destTile]->pos;
  868. if(x == -1)
  869. be.x = tilePos.x + tilePos.w/2 - first->width()/2;
  870. if(y == -1)
  871. {
  872. if(alignToBottom)
  873. be.y = tilePos.y + tilePos.h - first->height();
  874. else
  875. be.y = tilePos.y - first->height()/2;
  876. }
  877. // Correction for 2-hex creatures.
  878. if(destStack != nullptr && destStack->doubleWide())
  879. be.x += (destStack->side == BattleSide::ATTACKER ? -1 : 1)*tilePos.w/2;
  880. }
  881. assert(be.x != -1 && be.y != -1);
  882. //Indicate if effect should be drawn on top of everything or just on top of the hex
  883. be.position = destTile;
  884. owner->battleEffects.push_back(be);
  885. }
  886. //battleEffects
  887. return true;
  888. }
  889. void CEffectAnimation::nextFrame()
  890. {
  891. //notice: there may be more than one effect in owner->battleEffects correcponding to this animation (ie. armageddon)
  892. for(auto & elem : owner->battleEffects)
  893. {
  894. if(elem.effectID == ID)
  895. {
  896. elem.currentFrame += AnimationControls::getSpellEffectSpeed() * GH.mainFPSmng->getElapsedMilliseconds() / 1000;
  897. if(elem.currentFrame >= elem.animation->size())
  898. {
  899. endAnim();
  900. break;
  901. }
  902. else
  903. {
  904. elem.x += dx;
  905. elem.y += dy;
  906. }
  907. }
  908. }
  909. }
  910. void CEffectAnimation::endAnim()
  911. {
  912. CBattleAnimation::endAnim();
  913. std::vector<std::list<BattleEffect>::iterator> toDel;
  914. for(auto it = owner->battleEffects.begin(); it != owner->battleEffects.end(); ++it)
  915. {
  916. if(it->effectID == ID)
  917. {
  918. toDel.push_back(it);
  919. }
  920. }
  921. for(auto & elem : toDel)
  922. {
  923. owner->battleEffects.erase(elem);
  924. }
  925. delete this;
  926. }