BattleInterface.cpp 26 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000
  1. /*
  2. * BattleInterface.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 "BattleInterface.h"
  12. #include "BattleAnimationClasses.h"
  13. #include "BattleActionsController.h"
  14. #include "BattleInterfaceClasses.h"
  15. #include "CreatureAnimation.h"
  16. #include "BattleProjectileController.h"
  17. #include "BattleEffectsController.h"
  18. #include "BattleObstacleController.h"
  19. #include "BattleSiegeController.h"
  20. #include "BattleFieldController.h"
  21. #include "BattleControlPanel.h"
  22. #include "BattleStacksController.h"
  23. #include "BattleRenderer.h"
  24. #include "../CGameInfo.h"
  25. #include "../CMessage.h"
  26. #include "../CMusicHandler.h"
  27. #include "../CPlayerInterface.h"
  28. #include "../gui/Canvas.h"
  29. #include "../gui/CCursorHandler.h"
  30. #include "../gui/CGuiHandler.h"
  31. #include "../windows/CAdvmapInterface.h"
  32. #include "../../CCallback.h"
  33. #include "../../lib/CStack.h"
  34. #include "../../lib/CConfigHandler.h"
  35. #include "../../lib/CGeneralTextHandler.h"
  36. #include "../../lib/CHeroHandler.h"
  37. #include "../../lib/CondSh.h"
  38. #include "../../lib/mapObjects/CGTownInstance.h"
  39. #include "../../lib/NetPacks.h"
  40. #include "../../lib/UnlockGuard.h"
  41. CondSh<BattleAction *> BattleInterface::givenCommand(nullptr);
  42. BattleInterface::BattleInterface(const CCreatureSet *army1, const CCreatureSet *army2,
  43. const CGHeroInstance *hero1, const CGHeroInstance *hero2,
  44. const SDL_Rect & myRect,
  45. std::shared_ptr<CPlayerInterface> att,
  46. std::shared_ptr<CPlayerInterface> defen,
  47. std::shared_ptr<CPlayerInterface> spectatorInt)
  48. : attackingHeroInstance(hero1)
  49. , defendingHeroInstance(hero2)
  50. , animCount(0)
  51. , attackerInt(att)
  52. , defenderInt(defen)
  53. , curInt(att)
  54. , myTurn(false)
  55. , moveSoundHander(-1)
  56. , bresult(nullptr)
  57. , battleActionsStarted(false)
  58. {
  59. OBJ_CONSTRUCTION;
  60. for ( auto & event : animationEvents)
  61. event.setn(false);
  62. if(spectatorInt)
  63. {
  64. curInt = spectatorInt;
  65. }
  66. else if(!curInt)
  67. {
  68. //May happen when we are defending during network MP game -> attacker interface is just not present
  69. curInt = defenderInt;
  70. }
  71. pos = myRect;
  72. strongInterest = true;
  73. givenCommand.setn(nullptr);
  74. //hot-seat -> check tactics for both players (defender may be local human)
  75. if(attackerInt && attackerInt->cb->battleGetTacticDist())
  76. tacticianInterface = attackerInt;
  77. else if(defenderInt && defenderInt->cb->battleGetTacticDist())
  78. tacticianInterface = defenderInt;
  79. //if we found interface of player with tactics, then enter tactics mode
  80. tacticsMode = static_cast<bool>(tacticianInterface);
  81. //create stack queue
  82. bool embedQueue;
  83. std::string queueSize = settings["battle"]["queueSize"].String();
  84. if(queueSize == "auto")
  85. embedQueue = screen->h < 700;
  86. else
  87. embedQueue = screen->h < 700 || queueSize == "small";
  88. queue = std::make_shared<StackQueue>(embedQueue, *this);
  89. if(!embedQueue)
  90. {
  91. if (settings["battle"]["showQueue"].Bool())
  92. pos.y += queue->pos.h / 2; //center whole window
  93. queue->moveTo(Point(pos.x, pos.y - queue->pos.h));
  94. }
  95. CPlayerInterface::battleInt = this;
  96. //initializing armies
  97. this->army1 = army1;
  98. this->army2 = army2;
  99. const CGTownInstance *town = curInt->cb->battleGetDefendedTown();
  100. if(town && town->hasFort())
  101. siegeController.reset(new BattleSiegeController(*this, town));
  102. controlPanel = std::make_shared<BattleControlPanel>(*this, Point(0, 556));
  103. projectilesController.reset(new BattleProjectileController(*this));
  104. fieldController.reset( new BattleFieldController(*this));
  105. stacksController.reset( new BattleStacksController(*this));
  106. actionsController.reset( new BattleActionsController(*this));
  107. effectsController.reset(new BattleEffectsController(*this));
  108. //loading hero animations
  109. if(hero1) // attacking hero
  110. {
  111. std::string battleImage;
  112. if(!hero1->type->battleImage.empty())
  113. {
  114. battleImage = hero1->type->battleImage;
  115. }
  116. else
  117. {
  118. if(hero1->sex)
  119. battleImage = hero1->type->heroClass->imageBattleFemale;
  120. else
  121. battleImage = hero1->type->heroClass->imageBattleMale;
  122. }
  123. attackingHero = std::make_shared<BattleHero>(battleImage, false, hero1->tempOwner, hero1->tempOwner == curInt->playerID ? hero1 : nullptr, *this);
  124. }
  125. if(hero2) // defending hero
  126. {
  127. std::string battleImage;
  128. if(!hero2->type->battleImage.empty())
  129. {
  130. battleImage = hero2->type->battleImage;
  131. }
  132. else
  133. {
  134. if(hero2->sex)
  135. battleImage = hero2->type->heroClass->imageBattleFemale;
  136. else
  137. battleImage = hero2->type->heroClass->imageBattleMale;
  138. }
  139. defendingHero = std::make_shared<BattleHero>(battleImage, true, hero2->tempOwner, hero2->tempOwner == curInt->playerID ? hero2 : nullptr, *this);
  140. }
  141. obstacleController.reset(new BattleObstacleController(*this));
  142. if(tacticsMode)
  143. tacticNextStack(nullptr);
  144. CCS->musich->stopMusic();
  145. battleIntroSoundChannel = CCS->soundh->playSoundFromSet(CCS->soundh->battleIntroSounds);
  146. auto onIntroPlayed = [&]()
  147. {
  148. if(LOCPLINT->battleInt)
  149. {
  150. CCS->musich->playMusicFromSet("battle", true, true);
  151. battleActionsStarted = true;
  152. activateStack();
  153. battleIntroSoundChannel = -1;
  154. }
  155. };
  156. CCS->soundh->setCallback(battleIntroSoundChannel, onIntroPlayed);
  157. addUsedEvents(RCLICK | MOVE | KEYBOARD);
  158. queue->update();
  159. controlPanel->blockUI(true);
  160. }
  161. BattleInterface::~BattleInterface()
  162. {
  163. CPlayerInterface::battleInt = nullptr;
  164. givenCommand.cond.notify_all(); //that two lines should make any stacksController->getActiveStack() waiting thread to finish
  165. assert(!active);
  166. if (active) //dirty fix for #485
  167. deactivate();
  168. if (adventureInt && adventureInt->selection)
  169. {
  170. //FIXME: this should be moved to adventureInt which should restore correct track based on selection/active player
  171. const auto & terrain = *(LOCPLINT->cb->getTile(adventureInt->selection->visitablePos())->terType);
  172. CCS->musich->playMusicFromSet("terrain", terrain.name, true, false);
  173. }
  174. // may happen if user decided to close game while in battle
  175. if (getAnimationCondition(EAnimationEvents::ACTION) == true)
  176. logGlobal->error("Shutting down BattleInterface during animation playback!");
  177. setAnimationCondition(EAnimationEvents::ACTION, false);
  178. }
  179. void BattleInterface::setPrintCellBorders(bool set)
  180. {
  181. Settings cellBorders = settings.write["battle"]["cellBorders"];
  182. cellBorders->Bool() = set;
  183. fieldController->redrawBackgroundWithHexes();
  184. GH.totalRedraw();
  185. }
  186. void BattleInterface::setPrintStackRange(bool set)
  187. {
  188. Settings stackRange = settings.write["battle"]["stackRange"];
  189. stackRange->Bool() = set;
  190. fieldController->redrawBackgroundWithHexes();
  191. GH.totalRedraw();
  192. }
  193. void BattleInterface::setPrintMouseShadow(bool set)
  194. {
  195. Settings shadow = settings.write["battle"]["mouseShadow"];
  196. shadow->Bool() = set;
  197. }
  198. void BattleInterface::activate()
  199. {
  200. controlPanel->activate();
  201. if (curInt->isAutoFightOn)
  202. return;
  203. CIntObject::activate();
  204. if (attackingHero)
  205. attackingHero->activate();
  206. if (defendingHero)
  207. defendingHero->activate();
  208. fieldController->activate();
  209. if (settings["battle"]["showQueue"].Bool())
  210. queue->activate();
  211. LOCPLINT->cingconsole->activate();
  212. }
  213. void BattleInterface::deactivate()
  214. {
  215. controlPanel->deactivate();
  216. CIntObject::deactivate();
  217. fieldController->deactivate();
  218. if (attackingHero)
  219. attackingHero->deactivate();
  220. if (defendingHero)
  221. defendingHero->deactivate();
  222. if (settings["battle"]["showQueue"].Bool())
  223. queue->deactivate();
  224. LOCPLINT->cingconsole->deactivate();
  225. }
  226. void BattleInterface::keyPressed(const SDL_KeyboardEvent & key)
  227. {
  228. if(key.keysym.sym == SDLK_q && key.state == SDL_PRESSED)
  229. {
  230. if(settings["battle"]["showQueue"].Bool()) //hide queue
  231. hideQueue();
  232. else
  233. showQueue();
  234. }
  235. else if(key.keysym.sym == SDLK_f && key.state == SDL_PRESSED)
  236. {
  237. actionsController->enterCreatureCastingMode();
  238. }
  239. else if(key.keysym.sym == SDLK_ESCAPE)
  240. {
  241. if(!battleActionsStarted)
  242. CCS->soundh->stopSound(battleIntroSoundChannel);
  243. else
  244. actionsController->endCastingSpell();
  245. }
  246. }
  247. void BattleInterface::mouseMoved(const SDL_MouseMotionEvent &event)
  248. {
  249. BattleHex selectedHex = fieldController->getHoveredHex();
  250. actionsController->handleHex(selectedHex, MOVE);
  251. controlPanel->mouseMoved(event);
  252. }
  253. void BattleInterface::clickRight(tribool down, bool previousState)
  254. {
  255. if (!down)
  256. {
  257. actionsController->endCastingSpell();
  258. }
  259. }
  260. void BattleInterface::stackReset(const CStack * stack)
  261. {
  262. stacksController->stackReset(stack);
  263. }
  264. void BattleInterface::stackAdded(const CStack * stack)
  265. {
  266. stacksController->stackAdded(stack, false);
  267. }
  268. void BattleInterface::stackRemoved(uint32_t stackID)
  269. {
  270. stacksController->stackRemoved(stackID);
  271. fieldController->redrawBackgroundWithHexes();
  272. queue->update();
  273. }
  274. void BattleInterface::stackActivated(const CStack *stack)
  275. {
  276. stacksController->stackActivated(stack);
  277. }
  278. void BattleInterface::stackMoved(const CStack *stack, std::vector<BattleHex> destHex, int distance)
  279. {
  280. stacksController->stackMoved(stack, destHex, distance);
  281. }
  282. void BattleInterface::stacksAreAttacked(std::vector<StackAttackedInfo> attackedInfos)
  283. {
  284. stacksController->stacksAreAttacked(attackedInfos);
  285. std::array<int, 2> killedBySide = {0, 0};
  286. int targets = 0;
  287. for(const StackAttackedInfo & attackedInfo : attackedInfos)
  288. {
  289. ++targets;
  290. ui8 side = attackedInfo.defender->side;
  291. killedBySide.at(side) += attackedInfo.amountKilled;
  292. }
  293. for(ui8 side = 0; side < 2; side++)
  294. {
  295. if(killedBySide.at(side) > killedBySide.at(1-side))
  296. setHeroAnimation(side, EHeroAnimType::DEFEAT);
  297. else if(killedBySide.at(side) < killedBySide.at(1-side))
  298. setHeroAnimation(side, EHeroAnimType::VICTORY);
  299. }
  300. }
  301. void BattleInterface::stackAttacking( const StackAttackInfo & attackInfo )
  302. {
  303. stacksController->stackAttacking(attackInfo);
  304. }
  305. void BattleInterface::newRoundFirst( int round )
  306. {
  307. waitForAnimationCondition(EAnimationEvents::ACTION, false);
  308. }
  309. void BattleInterface::newRound(int number)
  310. {
  311. controlPanel->console->addText(CGI->generaltexth->allTexts[412]);
  312. }
  313. void BattleInterface::giveCommand(EActionType action, BattleHex tile, si32 additional)
  314. {
  315. const CStack * actor = nullptr;
  316. if(action != EActionType::HERO_SPELL && action != EActionType::RETREAT && action != EActionType::SURRENDER)
  317. {
  318. actor = stacksController->getActiveStack();
  319. }
  320. auto side = curInt->cb->playerToSide(curInt->playerID);
  321. if(!side)
  322. {
  323. logGlobal->error("Player %s is not in battle", curInt->playerID.getStr());
  324. return;
  325. }
  326. auto ba = new BattleAction(); //is deleted in CPlayerInterface::stacksController->getActiveStack()()
  327. ba->side = side.get();
  328. ba->actionType = action;
  329. ba->aimToHex(tile);
  330. ba->actionSubtype = additional;
  331. sendCommand(ba, actor);
  332. }
  333. void BattleInterface::sendCommand(BattleAction *& command, const CStack * actor)
  334. {
  335. command->stackNumber = actor ? actor->unitId() : ((command->side == BattleSide::ATTACKER) ? -1 : -2);
  336. if(!tacticsMode)
  337. {
  338. logGlobal->trace("Setting command for %s", (actor ? actor->nodeName() : "hero"));
  339. myTurn = false;
  340. stacksController->setActiveStack(nullptr);
  341. givenCommand.setn(command);
  342. }
  343. else
  344. {
  345. curInt->cb->battleMakeTacticAction(command);
  346. vstd::clear_pointer(command);
  347. stacksController->setActiveStack(nullptr);
  348. //next stack will be activated when action ends
  349. }
  350. }
  351. const CGHeroInstance * BattleInterface::getActiveHero()
  352. {
  353. const CStack *attacker = stacksController->getActiveStack();
  354. if(!attacker)
  355. {
  356. return nullptr;
  357. }
  358. if(attacker->side == BattleSide::ATTACKER)
  359. {
  360. return attackingHeroInstance;
  361. }
  362. return defendingHeroInstance;
  363. }
  364. void BattleInterface::hexLclicked(int whichOne)
  365. {
  366. actionsController->handleHex(whichOne, LCLICK);
  367. }
  368. void BattleInterface::stackIsCatapulting(const CatapultAttack & ca)
  369. {
  370. if (siegeController)
  371. siegeController->stackIsCatapulting(ca);
  372. }
  373. void BattleInterface::gateStateChanged(const EGateState state)
  374. {
  375. if (siegeController)
  376. siegeController->gateStateChanged(state);
  377. }
  378. void BattleInterface::battleFinished(const BattleResult& br)
  379. {
  380. bresult = &br;
  381. assert(getAnimationCondition(EAnimationEvents::ACTION) == false);
  382. waitForAnimationCondition(EAnimationEvents::ACTION, false);
  383. stacksController->setActiveStack(nullptr);
  384. displayBattleFinished();
  385. }
  386. void BattleInterface::displayBattleFinished()
  387. {
  388. CCS->curh->changeGraphic(ECursor::ADVENTURE,0);
  389. if(settings["session"]["spectate"].Bool() && settings["session"]["spectate-skip-battle-result"].Bool())
  390. {
  391. close();
  392. return;
  393. }
  394. GH.pushInt(std::make_shared<BattleResultWindow>(*bresult, *(this->curInt)));
  395. curInt->waitWhileDialog(); // Avoid freeze when AI end turn after battle. Check bug #1897
  396. CPlayerInterface::battleInt = nullptr;
  397. }
  398. void BattleInterface::spellCast(const BattleSpellCast * sc)
  399. {
  400. controlPanel->blockUI(true);
  401. const SpellID spellID = sc->spellID;
  402. const CSpell * spell = spellID.toSpell();
  403. auto targetedTile = sc->tile;
  404. assert(spell);
  405. if(!spell)
  406. return;
  407. const std::string & castSoundPath = spell->getCastSound();
  408. if (!castSoundPath.empty())
  409. {
  410. auto group = spell->animationInfo.projectile.empty() ?
  411. EAnimationEvents::HIT:
  412. EAnimationEvents::BEFORE_HIT;//FIXME: should be on projectile spawning
  413. executeOnAnimationCondition(group, true, [=]() {
  414. CCS->soundh->playSound(castSoundPath);
  415. });
  416. }
  417. if ( sc->activeCast )
  418. {
  419. const CStack * casterStack = curInt->cb->battleGetStackByID(sc->casterStack);
  420. if(casterStack != nullptr )
  421. {
  422. executeOnAnimationCondition(EAnimationEvents::BEFORE_HIT, true, [=]()
  423. {
  424. stacksController->addNewAnim(new CastAnimation(*this, casterStack, targetedTile, curInt->cb->battleGetStackByPos(targetedTile), spell));
  425. displaySpellCast(spellID, casterStack->getPosition());
  426. });
  427. }
  428. else
  429. {
  430. auto hero = sc->side ? defendingHero : attackingHero;
  431. assert(hero);
  432. executeOnAnimationCondition(EAnimationEvents::BEFORE_HIT, true, [=]()
  433. {
  434. stacksController->addNewAnim(new HeroCastAnimation(*this, hero, targetedTile, curInt->cb->battleGetStackByPos(targetedTile), spell));
  435. });
  436. }
  437. }
  438. executeOnAnimationCondition(EAnimationEvents::HIT, true, [=](){
  439. displaySpellHit(spellID, targetedTile);
  440. });
  441. //queuing affect animation
  442. for(auto & elem : sc->affectedCres)
  443. {
  444. auto stack = curInt->cb->battleGetStackByID(elem, false);
  445. assert(stack);
  446. if(stack)
  447. {
  448. executeOnAnimationCondition(EAnimationEvents::HIT, true, [=](){
  449. if (spellID == SpellID::BLOODLUST)
  450. stacksController->addNewAnim( ColorTransformAnimation::bloodlustAnimation(*this, stack, spell));
  451. else if (spellID == SpellID::STONE_GAZE)
  452. stacksController->addNewAnim( ColorTransformAnimation::petrifyAnimation(*this, stack, spell));
  453. else
  454. displaySpellEffect(spellID, stack->getPosition());
  455. });
  456. }
  457. }
  458. //queuing additional animation (magic mirror / resistance)
  459. for(auto & elem : sc->customEffects)
  460. {
  461. auto stack = curInt->cb->battleGetStackByID(elem.stack, false);
  462. assert(stack);
  463. if(stack)
  464. {
  465. executeOnAnimationCondition(EAnimationEvents::HIT, true, [=](){
  466. effectsController->displayEffect(EBattleEffect::EBattleEffect(elem.effect), stack->getPosition());
  467. });
  468. }
  469. }
  470. //mana absorption
  471. if (sc->manaGained > 0)
  472. {
  473. Point leftHero = Point(15, 30) + pos;
  474. Point rightHero = Point(755, 30) + pos;
  475. bool side = sc->side;
  476. executeOnAnimationCondition(EAnimationEvents::AFTER_HIT, true, [=](){
  477. stacksController->addNewAnim(new PointEffectAnimation(*this, "", side ? "SP07_A.DEF" : "SP07_B.DEF", leftHero));
  478. stacksController->addNewAnim(new PointEffectAnimation(*this, "", side ? "SP07_B.DEF" : "SP07_A.DEF", rightHero));
  479. });
  480. }
  481. }
  482. void BattleInterface::battleStacksEffectsSet(const SetStackEffect & sse)
  483. {
  484. if(stacksController->getActiveStack() != nullptr)
  485. fieldController->redrawBackgroundWithHexes();
  486. }
  487. void BattleInterface::setHeroAnimation(ui8 side, EHeroAnimType phase)
  488. {
  489. if(side == BattleSide::ATTACKER)
  490. {
  491. if(attackingHero)
  492. attackingHero->setPhase(phase);
  493. }
  494. else
  495. {
  496. if(defendingHero)
  497. defendingHero->setPhase(phase);
  498. }
  499. }
  500. void BattleInterface::displayBattleLog(const std::vector<MetaString> & battleLog)
  501. {
  502. for(const auto & line : battleLog)
  503. {
  504. std::string formatted = line.toString();
  505. boost::algorithm::trim(formatted);
  506. if(!controlPanel->console->addText(formatted))
  507. logGlobal->warn("Too long battle log line");
  508. }
  509. }
  510. void BattleInterface::displaySpellAnimationQueue(const CSpell::TAnimationQueue & q, BattleHex destinationTile, bool isHit)
  511. {
  512. for(const CSpell::TAnimation & animation : q)
  513. {
  514. if(animation.pause > 0)
  515. stacksController->addNewAnim(new DummyAnimation(*this, animation.pause));
  516. else
  517. {
  518. int flags = 0;
  519. if (isHit)
  520. flags |= PointEffectAnimation::FORCE_ON_TOP;
  521. if (animation.verticalPosition == VerticalPosition::BOTTOM)
  522. flags |= PointEffectAnimation::ALIGN_TO_BOTTOM;
  523. if (!destinationTile.isValid())
  524. flags |= PointEffectAnimation::SCREEN_FILL;
  525. if (!destinationTile.isValid())
  526. stacksController->addNewAnim(new PointEffectAnimation(*this, "", animation.resourceName, flags));
  527. else
  528. stacksController->addNewAnim(new PointEffectAnimation(*this, "", animation.resourceName, destinationTile, flags));
  529. }
  530. }
  531. }
  532. void BattleInterface::displaySpellCast(SpellID spellID, BattleHex destinationTile)
  533. {
  534. const CSpell * spell = spellID.toSpell();
  535. if(spell)
  536. displaySpellAnimationQueue(spell->animationInfo.cast, destinationTile, false);
  537. }
  538. void BattleInterface::displaySpellEffect(SpellID spellID, BattleHex destinationTile)
  539. {
  540. const CSpell *spell = spellID.toSpell();
  541. if(spell)
  542. displaySpellAnimationQueue(spell->animationInfo.affect, destinationTile, false);
  543. }
  544. void BattleInterface::displaySpellHit(SpellID spellID, BattleHex destinationTile)
  545. {
  546. const CSpell * spell = spellID.toSpell();
  547. if(spell)
  548. displaySpellAnimationQueue(spell->animationInfo.hit, destinationTile, true);
  549. }
  550. void BattleInterface::setAnimSpeed(int set)
  551. {
  552. Settings speed = settings.write["battle"]["animationSpeed"];
  553. speed->Float() = float(set) / 100;
  554. }
  555. int BattleInterface::getAnimSpeed() const
  556. {
  557. if(settings["session"]["spectate"].Bool() && !settings["session"]["spectate-battle-speed"].isNull())
  558. return static_cast<int>(vstd::round(settings["session"]["spectate-battle-speed"].Float() *100));
  559. return static_cast<int>(vstd::round(settings["battle"]["animationSpeed"].Float() *100));
  560. }
  561. CPlayerInterface *BattleInterface::getCurrentPlayerInterface() const
  562. {
  563. return curInt.get();
  564. }
  565. void BattleInterface::trySetActivePlayer( PlayerColor player )
  566. {
  567. if ( attackerInt && attackerInt->playerID == player )
  568. curInt = attackerInt;
  569. if ( defenderInt && defenderInt->playerID == player )
  570. curInt = defenderInt;
  571. }
  572. void BattleInterface::activateStack()
  573. {
  574. if(!battleActionsStarted)
  575. return; //"show" function should re-call this function
  576. stacksController->activateStack();
  577. const CStack * s = stacksController->getActiveStack();
  578. if(!s)
  579. return;
  580. myTurn = true;
  581. queue->update();
  582. fieldController->redrawBackgroundWithHexes();
  583. actionsController->activateStack();
  584. GH.fakeMouseMove();
  585. }
  586. void BattleInterface::endAction(const BattleAction* action)
  587. {
  588. const CStack *stack = curInt->cb->battleGetStackByID(action->stackNumber);
  589. stacksController->endAction(action);
  590. queue->update();
  591. //stack ended movement in tactics phase -> select the next one
  592. if (tacticsMode)
  593. tacticNextStack(stack);
  594. //we have activated next stack after sending request that has been just realized -> blockmap due to movement has changed
  595. if(action->actionType == EActionType::HERO_SPELL)
  596. fieldController->redrawBackgroundWithHexes();
  597. }
  598. void BattleInterface::hideQueue()
  599. {
  600. Settings showQueue = settings.write["battle"]["showQueue"];
  601. showQueue->Bool() = false;
  602. queue->deactivate();
  603. if (!queue->embedded)
  604. {
  605. moveBy(Point(0, -queue->pos.h / 2));
  606. GH.totalRedraw();
  607. }
  608. }
  609. void BattleInterface::showQueue()
  610. {
  611. Settings showQueue = settings.write["battle"]["showQueue"];
  612. showQueue->Bool() = true;
  613. queue->activate();
  614. if (!queue->embedded)
  615. {
  616. moveBy(Point(0, +queue->pos.h / 2));
  617. GH.totalRedraw();
  618. }
  619. }
  620. void BattleInterface::startAction(const BattleAction* action)
  621. {
  622. if(action->actionType == EActionType::END_TACTIC_PHASE)
  623. {
  624. controlPanel->tacticPhaseEnded();
  625. return;
  626. }
  627. const CStack *stack = curInt->cb->battleGetStackByID(action->stackNumber);
  628. if (stack)
  629. {
  630. queue->update();
  631. }
  632. else
  633. {
  634. assert(action->actionType == EActionType::HERO_SPELL); //only cast spell is valid action without acting stack number
  635. }
  636. stacksController->startAction(action);
  637. redraw(); // redraw after deactivation, including proper handling of hovered hexes
  638. if(action->actionType == EActionType::HERO_SPELL) //when hero casts spell
  639. return;
  640. if (!stack)
  641. {
  642. logGlobal->error("Something wrong with stackNumber in actionStarted. Stack number: %d", action->stackNumber);
  643. return;
  644. }
  645. effectsController->startAction(action);
  646. }
  647. void BattleInterface::tacticPhaseEnd()
  648. {
  649. stacksController->setActiveStack(nullptr);
  650. tacticsMode = false;
  651. }
  652. static bool immobile(const CStack *s)
  653. {
  654. return !s->Speed(0, true); //should bound stacks be immobile?
  655. }
  656. void BattleInterface::tacticNextStack(const CStack * current)
  657. {
  658. if (!current)
  659. current = stacksController->getActiveStack();
  660. //no switching stacks when the current one is moving
  661. assert(getAnimationCondition(EAnimationEvents::ACTION) == false);
  662. waitForAnimationCondition(EAnimationEvents::ACTION, false);
  663. TStacks stacksOfMine = tacticianInterface->cb->battleGetStacks(CBattleCallback::ONLY_MINE);
  664. vstd::erase_if (stacksOfMine, &immobile);
  665. if (stacksOfMine.empty())
  666. {
  667. tacticPhaseEnd();
  668. return;
  669. }
  670. auto it = vstd::find(stacksOfMine, current);
  671. if (it != stacksOfMine.end() && ++it != stacksOfMine.end())
  672. stackActivated(*it);
  673. else
  674. stackActivated(stacksOfMine.front());
  675. }
  676. void BattleInterface::obstaclePlaced(const std::vector<std::shared_ptr<const CObstacleInstance>> oi)
  677. {
  678. obstacleController->obstaclePlaced(oi);
  679. }
  680. const CGHeroInstance *BattleInterface::currentHero() const
  681. {
  682. if (attackingHeroInstance && attackingHeroInstance->tempOwner == curInt->playerID)
  683. return attackingHeroInstance;
  684. if (defendingHeroInstance && defendingHeroInstance->tempOwner == curInt->playerID)
  685. return defendingHeroInstance;
  686. return nullptr;
  687. }
  688. InfoAboutHero BattleInterface::enemyHero() const
  689. {
  690. InfoAboutHero ret;
  691. if (attackingHeroInstance->tempOwner == curInt->playerID)
  692. curInt->cb->getHeroInfo(defendingHeroInstance, ret);
  693. else
  694. curInt->cb->getHeroInfo(attackingHeroInstance, ret);
  695. return ret;
  696. }
  697. void BattleInterface::requestAutofightingAIToTakeAction()
  698. {
  699. assert(curInt->isAutoFightOn);
  700. boost::thread aiThread([&]()
  701. {
  702. auto ba = make_unique<BattleAction>(curInt->autofightingAI->activeStack(stacksController->getActiveStack()));
  703. if(curInt->cb->battleIsFinished())
  704. {
  705. return; // battle finished with spellcast
  706. }
  707. if (curInt->isAutoFightOn)
  708. {
  709. if (tacticsMode)
  710. {
  711. // Always end tactics mode. Player interface is blocked currently, so it's not possible that
  712. // the AI can take any action except end tactics phase (AI actions won't be triggered)
  713. //TODO implement the possibility that the AI will be triggered for further actions
  714. //TODO any solution to merge tactics phase & normal phase in the way it is handled by the player and battle interface?
  715. stacksController->setActiveStack(nullptr);
  716. tacticsMode = false;
  717. }
  718. else
  719. {
  720. givenCommand.setn(ba.release());
  721. }
  722. }
  723. else
  724. {
  725. boost::unique_lock<boost::recursive_mutex> un(*CPlayerInterface::pim);
  726. activateStack();
  727. }
  728. });
  729. aiThread.detach();
  730. }
  731. void BattleInterface::showAll(SDL_Surface *to)
  732. {
  733. show(to);
  734. }
  735. void BattleInterface::show(SDL_Surface *to)
  736. {
  737. Canvas canvas(to);
  738. assert(to);
  739. SDL_Rect buf;
  740. SDL_GetClipRect(to, &buf);
  741. SDL_SetClipRect(to, &pos);
  742. ++animCount;
  743. fieldController->renderBattlefield(canvas);
  744. if(battleActionsStarted)
  745. stacksController->updateBattleAnimations();
  746. SDL_SetClipRect(to, &buf); //restoring previous clip_rect
  747. showInterface(to);
  748. }
  749. void BattleInterface::collectRenderableObjects(BattleRenderer & renderer)
  750. {
  751. if (attackingHero)
  752. {
  753. renderer.insert(EBattleFieldLayer::HEROES, BattleHex(0),[this](BattleRenderer::RendererRef canvas)
  754. {
  755. attackingHero->render(canvas);
  756. });
  757. }
  758. if (defendingHero)
  759. {
  760. renderer.insert(EBattleFieldLayer::HEROES, BattleHex(GameConstants::BFIELD_WIDTH-1),[this](BattleRenderer::RendererRef canvas)
  761. {
  762. defendingHero->render(canvas);
  763. });
  764. }
  765. }
  766. void BattleInterface::showInterface(SDL_Surface * to)
  767. {
  768. //showing in-game console
  769. LOCPLINT->cingconsole->show(to);
  770. controlPanel->showAll(to);
  771. Rect posWithQueue = Rect(pos.x, pos.y, 800, 600);
  772. if (settings["battle"]["showQueue"].Bool())
  773. {
  774. if (!queue->embedded)
  775. {
  776. posWithQueue.y -= queue->pos.h;
  777. posWithQueue.h += queue->pos.h;
  778. }
  779. queue->showAll(to);
  780. }
  781. //printing border around interface
  782. if (screen->w != 800 || screen->h !=600)
  783. {
  784. CMessage::drawBorder(curInt->playerID,to,posWithQueue.w + 28, posWithQueue.h + 28, posWithQueue.x-14, posWithQueue.y-15);
  785. }
  786. }
  787. void BattleInterface::castThisSpell(SpellID spellID)
  788. {
  789. actionsController->castThisSpell(spellID);
  790. }
  791. void BattleInterface::setAnimationCondition( EAnimationEvents event, bool state)
  792. {
  793. logAnim->info("setAnimationCondition: %d -> %s", static_cast<int>(event), state ? "ON" : "OFF");
  794. size_t index = static_cast<size_t>(event);
  795. animationEvents[index].setn(state);
  796. for (auto it = awaitingEvents.begin(); it != awaitingEvents.end();)
  797. {
  798. if (it->event == event && it->eventState == state)
  799. {
  800. it->action();
  801. it = awaitingEvents.erase(it);
  802. }
  803. else
  804. ++it;
  805. }
  806. }
  807. bool BattleInterface::getAnimationCondition( EAnimationEvents event)
  808. {
  809. size_t index = static_cast<size_t>(event);
  810. return animationEvents[index].get();
  811. }
  812. void BattleInterface::waitForAnimationCondition( EAnimationEvents event, bool state)
  813. {
  814. auto unlockPim = vstd::makeUnlockGuard(*CPlayerInterface::pim);
  815. size_t index = static_cast<size_t>(event);
  816. animationEvents[index].waitUntil(state);
  817. }
  818. void BattleInterface::executeOnAnimationCondition( EAnimationEvents event, bool state, const AwaitingAnimationAction & action)
  819. {
  820. awaitingEvents.push_back({action, event, state});
  821. }