CBattleAnimations.cpp 29 KB

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