CBattleInterface.cpp 31 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153
  1. /*
  2. * CBattleInterface.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 "CBattleInterface.h"
  12. #include "CBattleAnimations.h"
  13. #include "CBattleActionsController.h"
  14. #include "CBattleInterfaceClasses.h"
  15. #include "CCreatureAnimation.h"
  16. #include "CBattleProjectileController.h"
  17. #include "CBattleObstacleController.h"
  18. #include "CBattleSiegeController.h"
  19. #include "CBattleFieldController.h"
  20. #include "CBattleControlPanel.h"
  21. #include "CBattleStacksController.h"
  22. #include "../CBitmapHandler.h"
  23. #include "../CGameInfo.h"
  24. #include "../CMessage.h"
  25. #include "../CMT.h"
  26. #include "../CMusicHandler.h"
  27. #include "../CPlayerInterface.h"
  28. #include "../CVideoHandler.h"
  29. #include "../Graphics.h"
  30. #include "../gui/CAnimation.h"
  31. #include "../gui/CCursorHandler.h"
  32. #include "../gui/CGuiHandler.h"
  33. #include "../gui/SDL_Extensions.h"
  34. #include "../windows/CAdvmapInterface.h"
  35. #include "../windows/CCreatureWindow.h"
  36. #include "../windows/CSpellWindow.h"
  37. #include "../../CCallback.h"
  38. #include "../../lib/CStack.h"
  39. #include "../../lib/CConfigHandler.h"
  40. #include "../../lib/CGeneralTextHandler.h"
  41. #include "../../lib/CHeroHandler.h"
  42. #include "../../lib/CondSh.h"
  43. #include "../../lib/CRandomGenerator.h"
  44. #include "../../lib/spells/CSpellHandler.h"
  45. #include "../../lib/spells/ISpellMechanics.h"
  46. #include "../../lib/spells/Problem.h"
  47. #include "../../lib/CTownHandler.h"
  48. #include "../../lib/BattleFieldHandler.h"
  49. #include "../../lib/ObstacleHandler.h"
  50. #include "../../lib/CGameState.h"
  51. #include "../../lib/mapping/CMap.h"
  52. #include "../../lib/NetPacks.h"
  53. #include "../../lib/UnlockGuard.h"
  54. CondSh<bool> CBattleInterface::animsAreDisplayed(false);
  55. CondSh<BattleAction *> CBattleInterface::givenCommand(nullptr);
  56. CBattleInterface::CBattleInterface(const CCreatureSet *army1, const CCreatureSet *army2,
  57. const CGHeroInstance *hero1, const CGHeroInstance *hero2,
  58. const SDL_Rect & myRect,
  59. std::shared_ptr<CPlayerInterface> att, std::shared_ptr<CPlayerInterface> defen, std::shared_ptr<CPlayerInterface> spectatorInt)
  60. : attackingHeroInstance(hero1), defendingHeroInstance(hero2), animCount(0),
  61. attackerInt(att), defenderInt(defen), curInt(att),
  62. myTurn(false), moveStarted(false), moveSoundHander(-1), bresult(nullptr), battleActionsStarted(false)
  63. {
  64. OBJ_CONSTRUCTION;
  65. projectilesController.reset(new CBattleProjectileController(this));
  66. if(spectatorInt)
  67. {
  68. curInt = spectatorInt;
  69. }
  70. else if(!curInt)
  71. {
  72. //May happen when we are defending during network MP game -> attacker interface is just not present
  73. curInt = defenderInt;
  74. }
  75. animsAreDisplayed.setn(false);
  76. pos = myRect;
  77. strongInterest = true;
  78. givenCommand.setn(nullptr);
  79. //hot-seat -> check tactics for both players (defender may be local human)
  80. if(attackerInt && attackerInt->cb->battleGetTacticDist())
  81. tacticianInterface = attackerInt;
  82. else if(defenderInt && defenderInt->cb->battleGetTacticDist())
  83. tacticianInterface = defenderInt;
  84. //if we found interface of player with tactics, then enter tactics mode
  85. tacticsMode = static_cast<bool>(tacticianInterface);
  86. //create stack queue
  87. bool embedQueue;
  88. std::string queueSize = settings["battle"]["queueSize"].String();
  89. if(queueSize == "auto")
  90. embedQueue = screen->h < 700;
  91. else
  92. embedQueue = screen->h < 700 || queueSize == "small";
  93. queue = std::make_shared<CStackQueue>(embedQueue, this);
  94. if(!embedQueue)
  95. {
  96. if (settings["battle"]["showQueue"].Bool())
  97. pos.y += queue->pos.h / 2; //center whole window
  98. queue->moveTo(Point(pos.x, pos.y - queue->pos.h));
  99. }
  100. queue->update();
  101. //preparing siege info
  102. const CGTownInstance *town = curInt->cb->battleGetDefendedTown();
  103. if(town && town->hasFort())
  104. siegeController.reset(new CBattleSiegeController(this, town));
  105. CPlayerInterface::battleInt = this;
  106. //initializing armies
  107. this->army1 = army1;
  108. this->army2 = army2;
  109. //preparing menu background and terrain
  110. fieldController.reset( new CBattleFieldController(this));
  111. stacksController.reset( new CBattleStacksController(this));
  112. actionsController.reset( new CBattleActionsController(this));
  113. //loading hero animations
  114. if(hero1) // attacking hero
  115. {
  116. std::string battleImage;
  117. if(!hero1->type->battleImage.empty())
  118. {
  119. battleImage = hero1->type->battleImage;
  120. }
  121. else
  122. {
  123. if(hero1->sex)
  124. battleImage = hero1->type->heroClass->imageBattleFemale;
  125. else
  126. battleImage = hero1->type->heroClass->imageBattleMale;
  127. }
  128. attackingHero = std::make_shared<CBattleHero>(battleImage, false, hero1->tempOwner, hero1->tempOwner == curInt->playerID ? hero1 : nullptr, this);
  129. auto img = attackingHero->animation->getImage(0, 0, true);
  130. if(img)
  131. attackingHero->pos = genRect(img->height(), img->width(), pos.x - 43, pos.y - 19);
  132. }
  133. if(hero2) // defending hero
  134. {
  135. std::string battleImage;
  136. if(!hero2->type->battleImage.empty())
  137. {
  138. battleImage = hero2->type->battleImage;
  139. }
  140. else
  141. {
  142. if(hero2->sex)
  143. battleImage = hero2->type->heroClass->imageBattleFemale;
  144. else
  145. battleImage = hero2->type->heroClass->imageBattleMale;
  146. }
  147. defendingHero = std::make_shared<CBattleHero>(battleImage, true, hero2->tempOwner, hero2->tempOwner == curInt->playerID ? hero2 : nullptr, this);
  148. auto img = defendingHero->animation->getImage(0, 0, true);
  149. if(img)
  150. defendingHero->pos = genRect(img->height(), img->width(), pos.x + 693, pos.y - 19);
  151. }
  152. obstacleController.reset(new CBattleObstacleController(this));
  153. if(tacticsMode)
  154. tacticNextStack(nullptr);
  155. CCS->musich->stopMusic();
  156. battleIntroSoundChannel = CCS->soundh->playSoundFromSet(CCS->soundh->battleIntroSounds);
  157. auto onIntroPlayed = [&]()
  158. {
  159. if(LOCPLINT->battleInt)
  160. {
  161. CCS->musich->playMusicFromSet("battle", true, true);
  162. battleActionsStarted = true;
  163. controlPanel->blockUI(settings["session"]["spectate"].Bool());
  164. battleIntroSoundChannel = -1;
  165. }
  166. };
  167. CCS->soundh->setCallback(battleIntroSoundChannel, onIntroPlayed);
  168. addUsedEvents(RCLICK | MOVE | KEYBOARD);
  169. controlPanel->blockUI(true);
  170. }
  171. CBattleInterface::~CBattleInterface()
  172. {
  173. CPlayerInterface::battleInt = nullptr;
  174. givenCommand.cond.notify_all(); //that two lines should make any stacksController->getActiveStack() waiting thread to finish
  175. if (active) //dirty fix for #485
  176. {
  177. deactivate();
  178. }
  179. //TODO: play AI tracks if battle was during AI turn
  180. //if (!curInt->makingTurn)
  181. //CCS->musich->playMusicFromSet(CCS->musich->aiMusics, -1);
  182. if (adventureInt && adventureInt->selection)
  183. {
  184. const auto & terrain = *(LOCPLINT->cb->getTile(adventureInt->selection->visitablePos())->terType);
  185. CCS->musich->playMusicFromSet("terrain", terrain.name, true, false);
  186. }
  187. animsAreDisplayed.setn(false);
  188. }
  189. void CBattleInterface::setPrintCellBorders(bool set)
  190. {
  191. Settings cellBorders = settings.write["battle"]["cellBorders"];
  192. cellBorders->Bool() = set;
  193. fieldController->redrawBackgroundWithHexes();
  194. GH.totalRedraw();
  195. }
  196. void CBattleInterface::setPrintStackRange(bool set)
  197. {
  198. Settings stackRange = settings.write["battle"]["stackRange"];
  199. stackRange->Bool() = set;
  200. fieldController->redrawBackgroundWithHexes();
  201. GH.totalRedraw();
  202. }
  203. void CBattleInterface::setPrintMouseShadow(bool set)
  204. {
  205. Settings shadow = settings.write["battle"]["mouseShadow"];
  206. shadow->Bool() = set;
  207. }
  208. void CBattleInterface::activate()
  209. {
  210. controlPanel->activate();
  211. if (curInt->isAutoFightOn)
  212. return;
  213. CIntObject::activate();
  214. if (attackingHero)
  215. attackingHero->activate();
  216. if (defendingHero)
  217. defendingHero->activate();
  218. fieldController->activate();
  219. if (settings["battle"]["showQueue"].Bool())
  220. queue->activate();
  221. LOCPLINT->cingconsole->activate();
  222. }
  223. void CBattleInterface::deactivate()
  224. {
  225. controlPanel->deactivate();
  226. CIntObject::deactivate();
  227. fieldController->deactivate();
  228. if (attackingHero)
  229. attackingHero->deactivate();
  230. if (defendingHero)
  231. defendingHero->deactivate();
  232. if (settings["battle"]["showQueue"].Bool())
  233. queue->deactivate();
  234. LOCPLINT->cingconsole->deactivate();
  235. }
  236. void CBattleInterface::keyPressed(const SDL_KeyboardEvent & key)
  237. {
  238. if(key.keysym.sym == SDLK_q && key.state == SDL_PRESSED)
  239. {
  240. if(settings["battle"]["showQueue"].Bool()) //hide queue
  241. hideQueue();
  242. else
  243. showQueue();
  244. }
  245. else if(key.keysym.sym == SDLK_f && key.state == SDL_PRESSED)
  246. {
  247. actionsController->enterCreatureCastingMode();
  248. }
  249. else if(key.keysym.sym == SDLK_ESCAPE)
  250. {
  251. if(!battleActionsStarted)
  252. CCS->soundh->stopSound(battleIntroSoundChannel);
  253. else
  254. actionsController->endCastingSpell();
  255. }
  256. }
  257. void CBattleInterface::mouseMoved(const SDL_MouseMotionEvent &sEvent)
  258. {
  259. BattleHex selectedHex = fieldController->getHoveredHex();
  260. actionsController->handleHex(selectedHex, MOVE);
  261. }
  262. void CBattleInterface::clickRight(tribool down, bool previousState)
  263. {
  264. if (!down)
  265. {
  266. actionsController->endCastingSpell();
  267. }
  268. }
  269. void CBattleInterface::stackReset(const CStack * stack)
  270. {
  271. stacksController->stackReset(stack);
  272. }
  273. void CBattleInterface::stackAdded(const CStack * stack)
  274. {
  275. stacksController->stackAdded(stack);
  276. }
  277. void CBattleInterface::stackRemoved(uint32_t stackID)
  278. {
  279. stacksController->stackRemoved(stackID);
  280. fieldController->redrawBackgroundWithHexes();
  281. queue->update();
  282. }
  283. void CBattleInterface::stackActivated(const CStack *stack) //TODO: check it all before game state is changed due to abilities
  284. {
  285. stacksController->stackActivated(stack);
  286. }
  287. void CBattleInterface::stackMoved(const CStack *stack, std::vector<BattleHex> destHex, int distance)
  288. {
  289. stacksController->stackMoved(stack, destHex, distance);
  290. }
  291. void CBattleInterface::stacksAreAttacked(std::vector<StackAttackedInfo> attackedInfos)
  292. {
  293. stacksController->stacksAreAttacked(attackedInfos);
  294. std::array<int, 2> killedBySide = {0, 0};
  295. int targets = 0;
  296. for(const StackAttackedInfo & attackedInfo : attackedInfos)
  297. {
  298. ++targets;
  299. ui8 side = attackedInfo.defender->side;
  300. killedBySide.at(side) += attackedInfo.amountKilled;
  301. }
  302. for(ui8 side = 0; side < 2; side++)
  303. {
  304. if(killedBySide.at(side) > killedBySide.at(1-side))
  305. setHeroAnimation(side, 2);
  306. else if(killedBySide.at(side) < killedBySide.at(1-side))
  307. setHeroAnimation(side, 3);
  308. }
  309. }
  310. void CBattleInterface::stackAttacking( const CStack *attacker, BattleHex dest, const CStack *attacked, bool shooting )
  311. {
  312. stacksController->stackAttacking(attacker, dest, attacked, shooting);
  313. }
  314. void CBattleInterface::newRoundFirst( int round )
  315. {
  316. waitForAnims();
  317. }
  318. void CBattleInterface::newRound(int number)
  319. {
  320. controlPanel->console->addText(CGI->generaltexth->allTexts[412]);
  321. }
  322. void CBattleInterface::giveCommand(EActionType action, BattleHex tile, si32 additional)
  323. {
  324. const CStack * actor = nullptr;
  325. if(action != EActionType::HERO_SPELL && action != EActionType::RETREAT && action != EActionType::SURRENDER)
  326. {
  327. actor = stacksController->getActiveStack();
  328. }
  329. auto side = curInt->cb->playerToSide(curInt->playerID);
  330. if(!side)
  331. {
  332. logGlobal->error("Player %s is not in battle", curInt->playerID.getStr());
  333. return;
  334. }
  335. auto ba = new BattleAction(); //is deleted in CPlayerInterface::stacksController->getActiveStack()()
  336. ba->side = side.get();
  337. ba->actionType = action;
  338. ba->aimToHex(tile);
  339. ba->actionSubtype = additional;
  340. sendCommand(ba, actor);
  341. }
  342. void CBattleInterface::sendCommand(BattleAction *& command, const CStack * actor)
  343. {
  344. command->stackNumber = actor ? actor->unitId() : ((command->side == BattleSide::ATTACKER) ? -1 : -2);
  345. if(!tacticsMode)
  346. {
  347. logGlobal->trace("Setting command for %s", (actor ? actor->nodeName() : "hero"));
  348. myTurn = false;
  349. stacksController->setActiveStack(nullptr);
  350. givenCommand.setn(command);
  351. }
  352. else
  353. {
  354. curInt->cb->battleMakeTacticAction(command);
  355. vstd::clear_pointer(command);
  356. stacksController->setActiveStack(nullptr);
  357. //next stack will be activated when action ends
  358. }
  359. }
  360. const CGHeroInstance * CBattleInterface::getActiveHero()
  361. {
  362. const CStack *attacker = stacksController->getActiveStack();
  363. if(!attacker)
  364. {
  365. return nullptr;
  366. }
  367. if(attacker->side == BattleSide::ATTACKER)
  368. {
  369. return attackingHeroInstance;
  370. }
  371. return defendingHeroInstance;
  372. }
  373. void CBattleInterface::hexLclicked(int whichOne)
  374. {
  375. actionsController->handleHex(whichOne, LCLICK);
  376. }
  377. void CBattleInterface::stackIsCatapulting(const CatapultAttack & ca)
  378. {
  379. if (siegeController)
  380. siegeController->stackIsCatapulting(ca);
  381. }
  382. void CBattleInterface::gateStateChanged(const EGateState state)
  383. {
  384. if (siegeController)
  385. siegeController->gateStateChanged(state);
  386. }
  387. void CBattleInterface::battleFinished(const BattleResult& br)
  388. {
  389. bresult = &br;
  390. {
  391. auto unlockPim = vstd::makeUnlockGuard(*CPlayerInterface::pim);
  392. animsAreDisplayed.waitUntil(false);
  393. }
  394. stacksController->setActiveStack(nullptr);
  395. displayBattleFinished();
  396. }
  397. void CBattleInterface::displayBattleFinished()
  398. {
  399. CCS->curh->changeGraphic(ECursor::ADVENTURE,0);
  400. if(settings["session"]["spectate"].Bool() && settings["session"]["spectate-skip-battle-result"].Bool())
  401. {
  402. close();
  403. return;
  404. }
  405. GH.pushInt(std::make_shared<CBattleResultWindow>(*bresult, *(this->curInt)));
  406. curInt->waitWhileDialog(); // Avoid freeze when AI end turn after battle. Check bug #1897
  407. CPlayerInterface::battleInt = nullptr;
  408. }
  409. void CBattleInterface::spellCast(const BattleSpellCast * sc)
  410. {
  411. const SpellID spellID = sc->spellID;
  412. const CSpell * spell = spellID.toSpell();
  413. if(!spell)
  414. return;
  415. const std::string & castSoundPath = spell->getCastSound();
  416. if (!castSoundPath.empty())
  417. CCS->soundh->playSound(castSoundPath);
  418. const auto casterStackID = sc->casterStack;
  419. const CStack * casterStack = nullptr;
  420. if(casterStackID >= 0)
  421. {
  422. casterStack = curInt->cb->battleGetStackByID(casterStackID);
  423. }
  424. Point srccoord = (sc->side ? Point(770, 60) : Point(30, 60)) + pos; //hero position by default
  425. {
  426. if(casterStack != nullptr)
  427. {
  428. srccoord = CClickableHex::getXYUnitAnim(casterStack->getPosition(), casterStack, this);
  429. srccoord.x += 250;
  430. srccoord.y += 240;
  431. }
  432. }
  433. if(casterStack != nullptr && sc->activeCast)
  434. {
  435. //todo: custom cast animation for hero
  436. displaySpellCast(spellID, casterStack->getPosition());
  437. stacksController->addNewAnim(new CCastAnimation(this, casterStack, sc->tile, curInt->cb->battleGetStackByPos(sc->tile)));
  438. }
  439. waitForAnims(); //wait for cast animation
  440. //playing projectile animation
  441. if (sc->tile.isValid())
  442. {
  443. Point destcoord = CClickableHex::getXYUnitAnim(sc->tile, curInt->cb->battleGetStackByPos(sc->tile), this); //position attacked by projectile
  444. destcoord.x += 250; destcoord.y += 240;
  445. //animation angle
  446. double angle = atan2(static_cast<double>(destcoord.x - srccoord.x), static_cast<double>(destcoord.y - srccoord.y));
  447. bool Vflip = (angle < 0);
  448. if (Vflip)
  449. angle = -angle;
  450. std::string animToDisplay = spell->animationInfo.selectProjectile(angle);
  451. if(!animToDisplay.empty())
  452. {
  453. //TODO: calculate inside CEffectAnimation
  454. std::shared_ptr<CAnimation> tmp = std::make_shared<CAnimation>(animToDisplay);
  455. tmp->load(0, 0);
  456. auto first = tmp->getImage(0, 0);
  457. //displaying animation
  458. double diffX = (destcoord.x - srccoord.x)*(destcoord.x - srccoord.x);
  459. double diffY = (destcoord.y - srccoord.y)*(destcoord.y - srccoord.y);
  460. double distance = sqrt(diffX + diffY);
  461. int steps = static_cast<int>(distance / AnimationControls::getSpellEffectSpeed() + 1);
  462. int dx = (destcoord.x - srccoord.x - first->width())/steps;
  463. int dy = (destcoord.y - srccoord.y - first->height())/steps;
  464. stacksController->addNewAnim(new CEffectAnimation(this, animToDisplay, srccoord.x, srccoord.y, dx, dy, Vflip));
  465. }
  466. }
  467. waitForAnims(); //wait for projectile animation
  468. displaySpellHit(spellID, sc->tile);
  469. //queuing affect animation
  470. for(auto & elem : sc->affectedCres)
  471. {
  472. auto stack = curInt->cb->battleGetStackByID(elem, false);
  473. if(stack)
  474. displaySpellEffect(spellID, stack->getPosition());
  475. }
  476. //queuing additional animation
  477. for(auto & elem : sc->customEffects)
  478. {
  479. auto stack = curInt->cb->battleGetStackByID(elem.stack, false);
  480. if(stack)
  481. displayEffect(elem.effect, stack->getPosition());
  482. }
  483. waitForAnims();
  484. //mana absorption
  485. if (sc->manaGained > 0)
  486. {
  487. Point leftHero = Point(15, 30) + pos;
  488. Point rightHero = Point(755, 30) + pos;
  489. stacksController->addNewAnim(new CEffectAnimation(this, sc->side ? "SP07_A.DEF" : "SP07_B.DEF", leftHero.x, leftHero.y, 0, 0, false));
  490. stacksController->addNewAnim(new CEffectAnimation(this, sc->side ? "SP07_B.DEF" : "SP07_A.DEF", rightHero.x, rightHero.y, 0, 0, false));
  491. }
  492. }
  493. void CBattleInterface::battleStacksEffectsSet(const SetStackEffect & sse)
  494. {
  495. if(stacksController->getActiveStack() != nullptr)
  496. fieldController->redrawBackgroundWithHexes();
  497. }
  498. void CBattleInterface::setHeroAnimation(ui8 side, int phase)
  499. {
  500. if(side == BattleSide::ATTACKER)
  501. {
  502. if(attackingHero)
  503. attackingHero->setPhase(phase);
  504. }
  505. else
  506. {
  507. if(defendingHero)
  508. defendingHero->setPhase(phase);
  509. }
  510. }
  511. void CBattleInterface::displayBattleLog(const std::vector<MetaString> & battleLog)
  512. {
  513. for(const auto & line : battleLog)
  514. {
  515. std::string formatted = line.toString();
  516. boost::algorithm::trim(formatted);
  517. if(!controlPanel->console->addText(formatted))
  518. logGlobal->warn("Too long battle log line");
  519. }
  520. }
  521. void CBattleInterface::displayCustomEffects(const std::vector<CustomEffectInfo> & customEffects)
  522. {
  523. for(const CustomEffectInfo & one : customEffects)
  524. {
  525. if(one.sound != 0)
  526. CCS->soundh->playSound(soundBase::soundID(one.sound));
  527. const CStack * s = curInt->cb->battleGetStackByID(one.stack, false);
  528. if(s && one.effect != 0)
  529. displayEffect(one.effect, s->getPosition());
  530. }
  531. }
  532. void CBattleInterface::displayEffect(ui32 effect, BattleHex destTile)
  533. {
  534. std::string customAnim = graphics->battleACToDef[effect][0];
  535. stacksController->addNewAnim(new CEffectAnimation(this, customAnim, destTile));
  536. }
  537. void CBattleInterface::displaySpellAnimationQueue(const CSpell::TAnimationQueue & q, BattleHex destinationTile)
  538. {
  539. for(const CSpell::TAnimation & animation : q)
  540. {
  541. if(animation.pause > 0)
  542. stacksController->addNewAnim(new CDummyAnimation(this, animation.pause));
  543. else
  544. stacksController->addNewAnim(new CEffectAnimation(this, animation.resourceName, destinationTile, false, animation.verticalPosition == VerticalPosition::BOTTOM));
  545. }
  546. }
  547. void CBattleInterface::displaySpellCast(SpellID spellID, BattleHex destinationTile)
  548. {
  549. const CSpell * spell = spellID.toSpell();
  550. if(spell)
  551. displaySpellAnimationQueue(spell->animationInfo.cast, destinationTile);
  552. }
  553. void CBattleInterface::displaySpellEffect(SpellID spellID, BattleHex destinationTile)
  554. {
  555. const CSpell *spell = spellID.toSpell();
  556. if(spell)
  557. displaySpellAnimationQueue(spell->animationInfo.affect, destinationTile);
  558. }
  559. void CBattleInterface::displaySpellHit(SpellID spellID, BattleHex destinationTile)
  560. {
  561. const CSpell * spell = spellID.toSpell();
  562. if(spell)
  563. displaySpellAnimationQueue(spell->animationInfo.hit, destinationTile);
  564. }
  565. void CBattleInterface::battleTriggerEffect(const BattleTriggerEffect & bte)
  566. {
  567. const CStack * stack = curInt->cb->battleGetStackByID(bte.stackID);
  568. if(!stack)
  569. {
  570. logGlobal->error("Invalid stack ID %d", bte.stackID);
  571. return;
  572. }
  573. //don't show animation when no HP is regenerated
  574. switch(bte.effect)
  575. {
  576. //TODO: move to bonus type handler
  577. case Bonus::HP_REGENERATION:
  578. displayEffect(74, stack->getPosition());
  579. CCS->soundh->playSound(soundBase::REGENER);
  580. break;
  581. case Bonus::MANA_DRAIN:
  582. displayEffect(77, stack->getPosition());
  583. CCS->soundh->playSound(soundBase::MANADRAI);
  584. break;
  585. case Bonus::POISON:
  586. displayEffect(67, stack->getPosition());
  587. CCS->soundh->playSound(soundBase::POISON);
  588. break;
  589. case Bonus::FEAR:
  590. displayEffect(15, stack->getPosition());
  591. CCS->soundh->playSound(soundBase::FEAR);
  592. break;
  593. case Bonus::MORALE:
  594. {
  595. std::string hlp = CGI->generaltexth->allTexts[33];
  596. boost::algorithm::replace_first(hlp,"%s",(stack->getName()));
  597. displayEffect(20,stack->getPosition());
  598. CCS->soundh->playSound(soundBase::GOODMRLE);
  599. controlPanel->console->addText(hlp);
  600. break;
  601. }
  602. default:
  603. return;
  604. }
  605. //waitForAnims(); //fixme: freezes game :?
  606. }
  607. void CBattleInterface::setAnimSpeed(int set)
  608. {
  609. Settings speed = settings.write["battle"]["animationSpeed"];
  610. speed->Float() = float(set) / 100;
  611. }
  612. int CBattleInterface::getAnimSpeed() const
  613. {
  614. if(settings["session"]["spectate"].Bool() && !settings["session"]["spectate-battle-speed"].isNull())
  615. return static_cast<int>(vstd::round(settings["session"]["spectate-battle-speed"].Float() *100));
  616. return static_cast<int>(vstd::round(settings["battle"]["animationSpeed"].Float() *100));
  617. }
  618. CPlayerInterface *CBattleInterface::getCurrentPlayerInterface() const
  619. {
  620. return curInt.get();
  621. }
  622. void CBattleInterface::trySetActivePlayer( PlayerColor player )
  623. {
  624. if ( attackerInt && attackerInt->playerID == player )
  625. curInt = attackerInt;
  626. if ( defenderInt && defenderInt->playerID == player )
  627. curInt = defenderInt;
  628. }
  629. void CBattleInterface::activateStack()
  630. {
  631. if(!battleActionsStarted)
  632. return; //"show" function should re-call this function
  633. stacksController->activateStack();
  634. const CStack * s = stacksController->getActiveStack();
  635. if(!s)
  636. return;
  637. myTurn = true;
  638. queue->update();
  639. fieldController->redrawBackgroundWithHexes();
  640. actionsController->activateStack();
  641. GH.fakeMouseMove();
  642. }
  643. void CBattleInterface::endAction(const BattleAction* action)
  644. {
  645. const CStack *stack = curInt->cb->battleGetStackByID(action->stackNumber);
  646. if(action->actionType == EActionType::HERO_SPELL)
  647. setHeroAnimation(action->side, 0);
  648. stacksController->endAction(action);
  649. queue->update();
  650. if (tacticsMode) //stack ended movement in tactics phase -> select the next one
  651. tacticNextStack(stack);
  652. if(action->actionType == EActionType::HERO_SPELL) //we have activated next stack after sending request that has been just realized -> blockmap due to movement has changed
  653. fieldController->redrawBackgroundWithHexes();
  654. // if (stacksController->getActiveStack() && !animsAreDisplayed.get() && pendingAnims.empty() && !active)
  655. // {
  656. // logGlobal->warn("Something wrong... interface was deactivated but there is no animation. Reactivating...");
  657. // controlPanel->blockUI(false);
  658. // }
  659. // else
  660. // {
  661. // block UI if no active stack (e.g. enemy turn);
  662. controlPanel->blockUI(stacksController->getActiveStack() == nullptr);
  663. // }
  664. }
  665. void CBattleInterface::hideQueue()
  666. {
  667. Settings showQueue = settings.write["battle"]["showQueue"];
  668. showQueue->Bool() = false;
  669. queue->deactivate();
  670. if (!queue->embedded)
  671. {
  672. moveBy(Point(0, -queue->pos.h / 2));
  673. GH.totalRedraw();
  674. }
  675. }
  676. void CBattleInterface::showQueue()
  677. {
  678. Settings showQueue = settings.write["battle"]["showQueue"];
  679. showQueue->Bool() = true;
  680. queue->activate();
  681. if (!queue->embedded)
  682. {
  683. moveBy(Point(0, +queue->pos.h / 2));
  684. GH.totalRedraw();
  685. }
  686. }
  687. void CBattleInterface::startAction(const BattleAction* action)
  688. {
  689. //setActiveStack(nullptr);
  690. controlPanel->blockUI(true);
  691. if(action->actionType == EActionType::END_TACTIC_PHASE)
  692. {
  693. controlPanel->tacticPhaseEnded();
  694. return;
  695. }
  696. const CStack *stack = curInt->cb->battleGetStackByID(action->stackNumber);
  697. if (stack)
  698. {
  699. queue->update();
  700. }
  701. else
  702. {
  703. assert(action->actionType == EActionType::HERO_SPELL); //only cast spell is valid action without acting stack number
  704. }
  705. stacksController->startAction(action);
  706. redraw(); // redraw after deactivation, including proper handling of hovered hexes
  707. if(action->actionType == EActionType::HERO_SPELL) //when hero casts spell
  708. {
  709. setHeroAnimation(action->side, 4);
  710. return;
  711. }
  712. if (!stack)
  713. {
  714. logGlobal->error("Something wrong with stackNumber in actionStarted. Stack number: %d", action->stackNumber);
  715. return;
  716. }
  717. int txtid = 0;
  718. switch(action->actionType)
  719. {
  720. case EActionType::WAIT:
  721. txtid = 136;
  722. break;
  723. case EActionType::BAD_MORALE:
  724. txtid = -34; //negative -> no separate singular/plural form
  725. displayEffect(30, stack->getPosition());
  726. CCS->soundh->playSound(soundBase::BADMRLE);
  727. break;
  728. }
  729. if(txtid != 0)
  730. controlPanel->console->addText(stack->formatGeneralMessage(txtid));
  731. //displaying special abilities
  732. auto actionTarget = action->getTarget(curInt->cb.get());
  733. switch(action->actionType)
  734. {
  735. case EActionType::STACK_HEAL:
  736. displayEffect(74, actionTarget.at(0).hexValue);
  737. CCS->soundh->playSound(soundBase::REGENER);
  738. break;
  739. }
  740. }
  741. void CBattleInterface::waitForAnims()
  742. {
  743. auto unlockPim = vstd::makeUnlockGuard(*CPlayerInterface::pim);
  744. animsAreDisplayed.waitWhileTrue();
  745. }
  746. void CBattleInterface::tacticPhaseEnd()
  747. {
  748. stacksController->setActiveStack(nullptr);
  749. controlPanel->blockUI(true);
  750. tacticsMode = false;
  751. }
  752. static bool immobile(const CStack *s)
  753. {
  754. return !s->Speed(0, true); //should bound stacks be immobile?
  755. }
  756. void CBattleInterface::tacticNextStack(const CStack * current)
  757. {
  758. if (!current)
  759. current = stacksController->getActiveStack();
  760. //no switching stacks when the current one is moving
  761. waitForAnims();
  762. TStacks stacksOfMine = tacticianInterface->cb->battleGetStacks(CBattleCallback::ONLY_MINE);
  763. vstd::erase_if (stacksOfMine, &immobile);
  764. if (stacksOfMine.empty())
  765. {
  766. tacticPhaseEnd();
  767. return;
  768. }
  769. auto it = vstd::find(stacksOfMine, current);
  770. if (it != stacksOfMine.end() && ++it != stacksOfMine.end())
  771. stackActivated(*it);
  772. else
  773. stackActivated(stacksOfMine.front());
  774. }
  775. void CBattleInterface::obstaclePlaced(const CObstacleInstance & oi)
  776. {
  777. obstacleController->obstaclePlaced(oi);
  778. }
  779. const CGHeroInstance *CBattleInterface::currentHero() const
  780. {
  781. if (attackingHeroInstance->tempOwner == curInt->playerID)
  782. return attackingHeroInstance;
  783. else
  784. return defendingHeroInstance;
  785. }
  786. InfoAboutHero CBattleInterface::enemyHero() const
  787. {
  788. InfoAboutHero ret;
  789. if (attackingHeroInstance->tempOwner == curInt->playerID)
  790. curInt->cb->getHeroInfo(defendingHeroInstance, ret);
  791. else
  792. curInt->cb->getHeroInfo(attackingHeroInstance, ret);
  793. return ret;
  794. }
  795. void CBattleInterface::requestAutofightingAIToTakeAction()
  796. {
  797. assert(curInt->isAutoFightOn);
  798. boost::thread aiThread([&]()
  799. {
  800. auto ba = make_unique<BattleAction>(curInt->autofightingAI->activeStack(stacksController->getActiveStack()));
  801. if(curInt->cb->battleIsFinished())
  802. {
  803. return; // battle finished with spellcast
  804. }
  805. if (curInt->isAutoFightOn)
  806. {
  807. if (tacticsMode)
  808. {
  809. // Always end tactics mode. Player interface is blocked currently, so it's not possible that
  810. // the AI can take any action except end tactics phase (AI actions won't be triggered)
  811. //TODO implement the possibility that the AI will be triggered for further actions
  812. //TODO any solution to merge tactics phase & normal phase in the way it is handled by the player and battle interface?
  813. stacksController->setActiveStack(nullptr);
  814. controlPanel->blockUI(true);
  815. tacticsMode = false;
  816. }
  817. else
  818. {
  819. givenCommand.setn(ba.release());
  820. }
  821. }
  822. else
  823. {
  824. boost::unique_lock<boost::recursive_mutex> un(*CPlayerInterface::pim);
  825. activateStack();
  826. }
  827. });
  828. aiThread.detach();
  829. }
  830. void CBattleInterface::showAll(SDL_Surface *to)
  831. {
  832. show(to);
  833. }
  834. void CBattleInterface::show(SDL_Surface *to)
  835. {
  836. assert(to);
  837. SDL_Rect buf;
  838. SDL_GetClipRect(to, &buf);
  839. SDL_SetClipRect(to, &pos);
  840. ++animCount;
  841. if (stacksController->getActiveStack() != nullptr /*&& creAnims[stacksController->getActiveStack()->ID]->isIdle()*/) //show everything with range
  842. {
  843. // FIXME: any *real* reason to keep this separate? Speed difference can't be that big // TODO: move to showAll?
  844. fieldController->showBackgroundImageWithHexes(to);
  845. }
  846. else
  847. {
  848. fieldController->showBackgroundImage(to);
  849. obstacleController->showAbsoluteObstacles(to);
  850. if ( siegeController )
  851. siegeController->showAbsoluteObstacles(to);
  852. }
  853. fieldController->showHighlightedHexes(to);
  854. showBattlefieldObjects(to);
  855. projectilesController->showProjectiles(to);
  856. if(battleActionsStarted)
  857. stacksController->updateBattleAnimations();
  858. SDL_SetClipRect(to, &buf); //restoring previous clip_rect
  859. showInterface(to);
  860. //activation of next stack, if any
  861. //TODO: should be moved to the very start of this method?
  862. activateStack();
  863. }
  864. void CBattleInterface::showBattlefieldObjects(SDL_Surface *to)
  865. {
  866. auto showHexEntry = [&](BattleObjectsByHex::HexData & hex)
  867. {
  868. if (siegeController)
  869. siegeController->showPiecesOfWall(to, hex.walls);
  870. obstacleController->showObstacles(to, hex.obstacles);
  871. stacksController->showAliveStacks(to, hex.alive);
  872. showBattleEffects(to, hex.effects);
  873. };
  874. BattleObjectsByHex objects = sortObjectsByHex();
  875. // dead stacks should be blit first
  876. stacksController->showStacks(to, objects.beforeAll.dead);
  877. for (auto & data : objects.hex)
  878. stacksController->showStacks(to, data.dead);
  879. stacksController->showStacks(to, objects.afterAll.dead);
  880. // display objects that must be blit before anything else (e.g. topmost walls)
  881. showHexEntry(objects.beforeAll);
  882. // show heroes after "beforeAll" - e.g. topmost wall in siege
  883. if (attackingHero)
  884. attackingHero->show(to);
  885. if (defendingHero)
  886. defendingHero->show(to);
  887. // actual blit of most of objects, hex by hex
  888. // NOTE: row-by-row blitting may be a better approach
  889. for (auto &data : objects.hex)
  890. showHexEntry(data);
  891. // objects that must be blit *after* everything else - e.g. bottom tower or some spell effects
  892. showHexEntry(objects.afterAll);
  893. }
  894. void CBattleInterface::showBattleEffects(SDL_Surface *to, const std::vector<const BattleEffect *> &battleEffects)
  895. {
  896. for (auto & elem : battleEffects)
  897. {
  898. int currentFrame = static_cast<int>(floor(elem->currentFrame));
  899. currentFrame %= elem->animation->size();
  900. auto img = elem->animation->getImage(currentFrame);
  901. SDL_Rect temp_rect = genRect(img->height(), img->width(), elem->x, elem->y);
  902. img->draw(to, &temp_rect, nullptr);
  903. }
  904. }
  905. void CBattleInterface::showInterface(SDL_Surface *to)
  906. {
  907. //showing in-game console
  908. LOCPLINT->cingconsole->show(to);
  909. controlPanel->show(to);
  910. Rect posWithQueue = Rect(pos.x, pos.y, 800, 600);
  911. if (settings["battle"]["showQueue"].Bool())
  912. {
  913. if (!queue->embedded)
  914. {
  915. posWithQueue.y -= queue->pos.h;
  916. posWithQueue.h += queue->pos.h;
  917. }
  918. queue->showAll(to);
  919. }
  920. //printing border around interface
  921. if (screen->w != 800 || screen->h !=600)
  922. {
  923. CMessage::drawBorder(curInt->playerID,to,posWithQueue.w + 28, posWithQueue.h + 28, posWithQueue.x-14, posWithQueue.y-15);
  924. }
  925. }
  926. BattleObjectsByHex CBattleInterface::sortObjectsByHex()
  927. {
  928. BattleObjectsByHex sorted;
  929. // Sort creatures
  930. stacksController->sortObjectsByHex(sorted);
  931. // Sort battle effects (spells)
  932. for (auto & battleEffect : battleEffects)
  933. {
  934. if (battleEffect.position.isValid())
  935. sorted.hex[battleEffect.position].effects.push_back(&battleEffect);
  936. else
  937. sorted.afterAll.effects.push_back(&battleEffect);
  938. }
  939. // Sort obstacles
  940. obstacleController->sortObjectsByHex(sorted);
  941. // Sort wall parts
  942. if (siegeController)
  943. siegeController->sortObjectsByHex(sorted);
  944. return sorted;
  945. }
  946. void CBattleInterface::castThisSpell(SpellID spellID)
  947. {
  948. actionsController->castThisSpell(spellID);
  949. }