CBattleInterface.cpp 56 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854
  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 "CBattleInterfaceClasses.h"
  14. #include "CCreatureAnimation.h"
  15. #include "CBattleProjectileController.h"
  16. #include "CBattleObstacleController.h"
  17. #include "CBattleSiegeController.h"
  18. #include "CBattleFieldController.h"
  19. #include "CBattleControlPanel.h"
  20. #include "CBattleStacksController.h"
  21. #include "../CBitmapHandler.h"
  22. #include "../CGameInfo.h"
  23. #include "../CMessage.h"
  24. #include "../CMT.h"
  25. #include "../CMusicHandler.h"
  26. #include "../CPlayerInterface.h"
  27. #include "../CVideoHandler.h"
  28. #include "../Graphics.h"
  29. #include "../gui/CAnimation.h"
  30. #include "../gui/CCursorHandler.h"
  31. #include "../gui/CGuiHandler.h"
  32. #include "../gui/SDL_Extensions.h"
  33. #include "../windows/CAdvmapInterface.h"
  34. #include "../windows/CCreatureWindow.h"
  35. #include "../windows/CSpellWindow.h"
  36. #include "../../CCallback.h"
  37. #include "../../lib/CStack.h"
  38. #include "../../lib/CConfigHandler.h"
  39. #include "../../lib/CGeneralTextHandler.h"
  40. #include "../../lib/CHeroHandler.h"
  41. #include "../../lib/CondSh.h"
  42. #include "../../lib/CRandomGenerator.h"
  43. #include "../../lib/spells/CSpellHandler.h"
  44. #include "../../lib/spells/ISpellMechanics.h"
  45. #include "../../lib/spells/Problem.h"
  46. #include "../../lib/CTownHandler.h"
  47. #include "../../lib/BattleFieldHandler.h"
  48. #include "../../lib/ObstacleHandler.h"
  49. #include "../../lib/CGameState.h"
  50. #include "../../lib/mapping/CMap.h"
  51. #include "../../lib/NetPacks.h"
  52. #include "../../lib/UnlockGuard.h"
  53. CondSh<bool> CBattleInterface::animsAreDisplayed(false);
  54. CondSh<BattleAction *> CBattleInterface::givenCommand(nullptr);
  55. CBattleInterface::CBattleInterface(const CCreatureSet *army1, const CCreatureSet *army2,
  56. const CGHeroInstance *hero1, const CGHeroInstance *hero2,
  57. const SDL_Rect & myRect,
  58. std::shared_ptr<CPlayerInterface> att, std::shared_ptr<CPlayerInterface> defen, std::shared_ptr<CPlayerInterface> spectatorInt)
  59. : attackingHeroInstance(hero1), defendingHeroInstance(hero2), animCount(0),
  60. creatureCasting(false), spellDestSelectMode(false), spellToCast(nullptr), sp(nullptr),
  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. //loading hero animations
  113. if(hero1) // attacking hero
  114. {
  115. std::string battleImage;
  116. if(!hero1->type->battleImage.empty())
  117. {
  118. battleImage = hero1->type->battleImage;
  119. }
  120. else
  121. {
  122. if(hero1->sex)
  123. battleImage = hero1->type->heroClass->imageBattleFemale;
  124. else
  125. battleImage = hero1->type->heroClass->imageBattleMale;
  126. }
  127. attackingHero = std::make_shared<CBattleHero>(battleImage, false, hero1->tempOwner, hero1->tempOwner == curInt->playerID ? hero1 : nullptr, this);
  128. auto img = attackingHero->animation->getImage(0, 0, true);
  129. if(img)
  130. attackingHero->pos = genRect(img->height(), img->width(), pos.x - 43, pos.y - 19);
  131. }
  132. if(hero2) // defending hero
  133. {
  134. std::string battleImage;
  135. if(!hero2->type->battleImage.empty())
  136. {
  137. battleImage = hero2->type->battleImage;
  138. }
  139. else
  140. {
  141. if(hero2->sex)
  142. battleImage = hero2->type->heroClass->imageBattleFemale;
  143. else
  144. battleImage = hero2->type->heroClass->imageBattleMale;
  145. }
  146. defendingHero = std::make_shared<CBattleHero>(battleImage, true, hero2->tempOwner, hero2->tempOwner == curInt->playerID ? hero2 : nullptr, this);
  147. auto img = defendingHero->animation->getImage(0, 0, true);
  148. if(img)
  149. defendingHero->pos = genRect(img->height(), img->width(), pos.x + 693, pos.y - 19);
  150. }
  151. obstacleController.reset(new CBattleObstacleController(this));
  152. if(tacticsMode)
  153. tacticNextStack(nullptr);
  154. CCS->musich->stopMusic();
  155. battleIntroSoundChannel = CCS->soundh->playSoundFromSet(CCS->soundh->battleIntroSounds);
  156. auto onIntroPlayed = [&]()
  157. {
  158. if(LOCPLINT->battleInt)
  159. {
  160. CCS->musich->playMusicFromSet("battle", true, true);
  161. battleActionsStarted = true;
  162. controlPanel->blockUI(settings["session"]["spectate"].Bool());
  163. battleIntroSoundChannel = -1;
  164. }
  165. };
  166. CCS->soundh->setCallback(battleIntroSoundChannel, onIntroPlayed);
  167. currentAction = PossiblePlayerBattleAction::INVALID;
  168. selectedAction = PossiblePlayerBattleAction::INVALID;
  169. addUsedEvents(RCLICK | MOVE | KEYBOARD);
  170. controlPanel->blockUI(true);
  171. }
  172. CBattleInterface::~CBattleInterface()
  173. {
  174. CPlayerInterface::battleInt = nullptr;
  175. givenCommand.cond.notify_all(); //that two lines should make any stacksController->getActiveStack() waiting thread to finish
  176. if (active) //dirty fix for #485
  177. {
  178. deactivate();
  179. }
  180. //TODO: play AI tracks if battle was during AI turn
  181. //if (!curInt->makingTurn)
  182. //CCS->musich->playMusicFromSet(CCS->musich->aiMusics, -1);
  183. if (adventureInt && adventureInt->selection)
  184. {
  185. const auto & terrain = *(LOCPLINT->cb->getTile(adventureInt->selection->visitablePos())->terType);
  186. CCS->musich->playMusicFromSet("terrain", terrain.name, true, false);
  187. }
  188. animsAreDisplayed.setn(false);
  189. }
  190. void CBattleInterface::setPrintCellBorders(bool set)
  191. {
  192. Settings cellBorders = settings.write["battle"]["cellBorders"];
  193. cellBorders->Bool() = set;
  194. fieldController->redrawBackgroundWithHexes();
  195. GH.totalRedraw();
  196. }
  197. void CBattleInterface::setPrintStackRange(bool set)
  198. {
  199. Settings stackRange = settings.write["battle"]["stackRange"];
  200. stackRange->Bool() = set;
  201. fieldController->redrawBackgroundWithHexes();
  202. GH.totalRedraw();
  203. }
  204. void CBattleInterface::setPrintMouseShadow(bool set)
  205. {
  206. Settings shadow = settings.write["battle"]["mouseShadow"];
  207. shadow->Bool() = set;
  208. }
  209. void CBattleInterface::activate()
  210. {
  211. controlPanel->activate();
  212. if (curInt->isAutoFightOn)
  213. return;
  214. CIntObject::activate();
  215. if (attackingHero)
  216. attackingHero->activate();
  217. if (defendingHero)
  218. defendingHero->activate();
  219. fieldController->activate();
  220. if (settings["battle"]["showQueue"].Bool())
  221. queue->activate();
  222. LOCPLINT->cingconsole->activate();
  223. }
  224. void CBattleInterface::deactivate()
  225. {
  226. controlPanel->deactivate();
  227. CIntObject::deactivate();
  228. fieldController->deactivate();
  229. if (attackingHero)
  230. attackingHero->deactivate();
  231. if (defendingHero)
  232. defendingHero->deactivate();
  233. if (settings["battle"]["showQueue"].Bool())
  234. queue->deactivate();
  235. LOCPLINT->cingconsole->deactivate();
  236. }
  237. void CBattleInterface::keyPressed(const SDL_KeyboardEvent & key)
  238. {
  239. if(key.keysym.sym == SDLK_q && key.state == SDL_PRESSED)
  240. {
  241. if(settings["battle"]["showQueue"].Bool()) //hide queue
  242. hideQueue();
  243. else
  244. showQueue();
  245. }
  246. else if(key.keysym.sym == SDLK_f && key.state == SDL_PRESSED)
  247. {
  248. enterCreatureCastingMode();
  249. }
  250. else if(key.keysym.sym == SDLK_ESCAPE)
  251. {
  252. if(!battleActionsStarted)
  253. CCS->soundh->stopSound(battleIntroSoundChannel);
  254. else
  255. endCastingSpell();
  256. }
  257. }
  258. void CBattleInterface::mouseMoved(const SDL_MouseMotionEvent &sEvent)
  259. {
  260. BattleHex selectedHex = fieldController->getHoveredHex();
  261. handleHex(selectedHex, MOVE);
  262. }
  263. void CBattleInterface::clickRight(tribool down, bool previousState)
  264. {
  265. if (!down)
  266. {
  267. endCastingSpell();
  268. }
  269. }
  270. void CBattleInterface::stackReset(const CStack * stack)
  271. {
  272. stacksController->stackReset(stack);
  273. }
  274. void CBattleInterface::stackAdded(const CStack * stack)
  275. {
  276. stacksController->stackAdded(stack);
  277. }
  278. void CBattleInterface::stackRemoved(uint32_t stackID)
  279. {
  280. stacksController->stackRemoved(stackID);
  281. fieldController->redrawBackgroundWithHexes();
  282. queue->update();
  283. }
  284. void CBattleInterface::stackActivated(const CStack *stack) //TODO: check it all before game state is changed due to abilities
  285. {
  286. stacksController->stackActivated(stack);
  287. }
  288. void CBattleInterface::stackMoved(const CStack *stack, std::vector<BattleHex> destHex, int distance)
  289. {
  290. stacksController->stackMoved(stack, destHex, distance);
  291. }
  292. void CBattleInterface::stacksAreAttacked(std::vector<StackAttackedInfo> attackedInfos)
  293. {
  294. stacksController->stacksAreAttacked(attackedInfos);
  295. std::array<int, 2> killedBySide = {0, 0};
  296. int targets = 0;
  297. for(const StackAttackedInfo & attackedInfo : attackedInfos)
  298. {
  299. ++targets;
  300. ui8 side = attackedInfo.defender->side;
  301. killedBySide.at(side) += attackedInfo.amountKilled;
  302. }
  303. for(ui8 side = 0; side < 2; side++)
  304. {
  305. if(killedBySide.at(side) > killedBySide.at(1-side))
  306. setHeroAnimation(side, 2);
  307. else if(killedBySide.at(side) < killedBySide.at(1-side))
  308. setHeroAnimation(side, 3);
  309. }
  310. }
  311. void CBattleInterface::stackAttacking( const CStack *attacker, BattleHex dest, const CStack *attacked, bool shooting )
  312. {
  313. stacksController->stackAttacking(attacker, dest, attacked, shooting);
  314. }
  315. void CBattleInterface::newRoundFirst( int round )
  316. {
  317. waitForAnims();
  318. }
  319. void CBattleInterface::newRound(int number)
  320. {
  321. controlPanel->console->addText(CGI->generaltexth->allTexts[412]);
  322. }
  323. void CBattleInterface::giveCommand(EActionType action, BattleHex tile, si32 additional)
  324. {
  325. const CStack * actor = nullptr;
  326. if(action != EActionType::HERO_SPELL && action != EActionType::RETREAT && action != EActionType::SURRENDER)
  327. {
  328. actor = stacksController->getActiveStack();
  329. }
  330. auto side = curInt->cb->playerToSide(curInt->playerID);
  331. if(!side)
  332. {
  333. logGlobal->error("Player %s is not in battle", curInt->playerID.getStr());
  334. return;
  335. }
  336. auto ba = new BattleAction(); //is deleted in CPlayerInterface::stacksController->getActiveStack()()
  337. ba->side = side.get();
  338. ba->actionType = action;
  339. ba->aimToHex(tile);
  340. ba->actionSubtype = additional;
  341. sendCommand(ba, actor);
  342. }
  343. void CBattleInterface::sendCommand(BattleAction *& command, const CStack * actor)
  344. {
  345. command->stackNumber = actor ? actor->unitId() : ((command->side == BattleSide::ATTACKER) ? -1 : -2);
  346. if(!tacticsMode)
  347. {
  348. logGlobal->trace("Setting command for %s", (actor ? actor->nodeName() : "hero"));
  349. myTurn = false;
  350. stacksController->setActiveStack(nullptr);
  351. givenCommand.setn(command);
  352. }
  353. else
  354. {
  355. curInt->cb->battleMakeTacticAction(command);
  356. vstd::clear_pointer(command);
  357. stacksController->setActiveStack(nullptr);
  358. //next stack will be activated when action ends
  359. }
  360. }
  361. const CGHeroInstance * CBattleInterface::getActiveHero()
  362. {
  363. const CStack *attacker = stacksController->getActiveStack();
  364. if(!attacker)
  365. {
  366. return nullptr;
  367. }
  368. if(attacker->side == BattleSide::ATTACKER)
  369. {
  370. return attackingHeroInstance;
  371. }
  372. return defendingHeroInstance;
  373. }
  374. void CBattleInterface::hexLclicked(int whichOne)
  375. {
  376. handleHex(whichOne, LCLICK);
  377. }
  378. void CBattleInterface::stackIsCatapulting(const CatapultAttack & ca)
  379. {
  380. if (siegeController)
  381. siegeController->stackIsCatapulting(ca);
  382. }
  383. void CBattleInterface::gateStateChanged(const EGateState state)
  384. {
  385. if (siegeController)
  386. siegeController->gateStateChanged(state);
  387. }
  388. void CBattleInterface::battleFinished(const BattleResult& br)
  389. {
  390. bresult = &br;
  391. {
  392. auto unlockPim = vstd::makeUnlockGuard(*CPlayerInterface::pim);
  393. animsAreDisplayed.waitUntil(false);
  394. }
  395. stacksController->setActiveStack(nullptr);
  396. displayBattleFinished();
  397. }
  398. void CBattleInterface::displayBattleFinished()
  399. {
  400. CCS->curh->changeGraphic(ECursor::ADVENTURE,0);
  401. if(settings["session"]["spectate"].Bool() && settings["session"]["spectate-skip-battle-result"].Bool())
  402. {
  403. close();
  404. return;
  405. }
  406. GH.pushInt(std::make_shared<CBattleResultWindow>(*bresult, *(this->curInt)));
  407. curInt->waitWhileDialog(); // Avoid freeze when AI end turn after battle. Check bug #1897
  408. CPlayerInterface::battleInt = nullptr;
  409. }
  410. void CBattleInterface::spellCast(const BattleSpellCast * sc)
  411. {
  412. const SpellID spellID = sc->spellID;
  413. const CSpell * spell = spellID.toSpell();
  414. if(!spell)
  415. return;
  416. const std::string & castSoundPath = spell->getCastSound();
  417. if (!castSoundPath.empty())
  418. CCS->soundh->playSound(castSoundPath);
  419. const auto casterStackID = sc->casterStack;
  420. const CStack * casterStack = nullptr;
  421. if(casterStackID >= 0)
  422. {
  423. casterStack = curInt->cb->battleGetStackByID(casterStackID);
  424. }
  425. Point srccoord = (sc->side ? Point(770, 60) : Point(30, 60)) + pos; //hero position by default
  426. {
  427. if(casterStack != nullptr)
  428. {
  429. srccoord = CClickableHex::getXYUnitAnim(casterStack->getPosition(), casterStack, this);
  430. srccoord.x += 250;
  431. srccoord.y += 240;
  432. }
  433. }
  434. if(casterStack != nullptr && sc->activeCast)
  435. {
  436. //todo: custom cast animation for hero
  437. displaySpellCast(spellID, casterStack->getPosition());
  438. stacksController->addNewAnim(new CCastAnimation(this, casterStack, sc->tile, curInt->cb->battleGetStackByPos(sc->tile)));
  439. }
  440. waitForAnims(); //wait for cast animation
  441. //playing projectile animation
  442. if (sc->tile.isValid())
  443. {
  444. Point destcoord = CClickableHex::getXYUnitAnim(sc->tile, curInt->cb->battleGetStackByPos(sc->tile), this); //position attacked by projectile
  445. destcoord.x += 250; destcoord.y += 240;
  446. //animation angle
  447. double angle = atan2(static_cast<double>(destcoord.x - srccoord.x), static_cast<double>(destcoord.y - srccoord.y));
  448. bool Vflip = (angle < 0);
  449. if (Vflip)
  450. angle = -angle;
  451. std::string animToDisplay = spell->animationInfo.selectProjectile(angle);
  452. if(!animToDisplay.empty())
  453. {
  454. //TODO: calculate inside CEffectAnimation
  455. std::shared_ptr<CAnimation> tmp = std::make_shared<CAnimation>(animToDisplay);
  456. tmp->load(0, 0);
  457. auto first = tmp->getImage(0, 0);
  458. //displaying animation
  459. double diffX = (destcoord.x - srccoord.x)*(destcoord.x - srccoord.x);
  460. double diffY = (destcoord.y - srccoord.y)*(destcoord.y - srccoord.y);
  461. double distance = sqrt(diffX + diffY);
  462. int steps = static_cast<int>(distance / AnimationControls::getSpellEffectSpeed() + 1);
  463. int dx = (destcoord.x - srccoord.x - first->width())/steps;
  464. int dy = (destcoord.y - srccoord.y - first->height())/steps;
  465. stacksController->addNewAnim(new CEffectAnimation(this, animToDisplay, srccoord.x, srccoord.y, dx, dy, Vflip));
  466. }
  467. }
  468. waitForAnims(); //wait for projectile animation
  469. displaySpellHit(spellID, sc->tile);
  470. //queuing affect animation
  471. for(auto & elem : sc->affectedCres)
  472. {
  473. auto stack = curInt->cb->battleGetStackByID(elem, false);
  474. if(stack)
  475. displaySpellEffect(spellID, stack->getPosition());
  476. }
  477. //queuing additional animation
  478. for(auto & elem : sc->customEffects)
  479. {
  480. auto stack = curInt->cb->battleGetStackByID(elem.stack, false);
  481. if(stack)
  482. displayEffect(elem.effect, stack->getPosition());
  483. }
  484. waitForAnims();
  485. //mana absorption
  486. if (sc->manaGained > 0)
  487. {
  488. Point leftHero = Point(15, 30) + pos;
  489. Point rightHero = Point(755, 30) + pos;
  490. stacksController->addNewAnim(new CEffectAnimation(this, sc->side ? "SP07_A.DEF" : "SP07_B.DEF", leftHero.x, leftHero.y, 0, 0, false));
  491. stacksController->addNewAnim(new CEffectAnimation(this, sc->side ? "SP07_B.DEF" : "SP07_A.DEF", rightHero.x, rightHero.y, 0, 0, false));
  492. }
  493. }
  494. void CBattleInterface::battleStacksEffectsSet(const SetStackEffect & sse)
  495. {
  496. if(stacksController->getActiveStack() != nullptr)
  497. fieldController->redrawBackgroundWithHexes();
  498. }
  499. void CBattleInterface::setHeroAnimation(ui8 side, int phase)
  500. {
  501. if(side == BattleSide::ATTACKER)
  502. {
  503. if(attackingHero)
  504. attackingHero->setPhase(phase);
  505. }
  506. else
  507. {
  508. if(defendingHero)
  509. defendingHero->setPhase(phase);
  510. }
  511. }
  512. void CBattleInterface::castThisSpell(SpellID spellID)
  513. {
  514. spellToCast = std::make_shared<BattleAction>();
  515. spellToCast->actionType = EActionType::HERO_SPELL;
  516. spellToCast->actionSubtype = spellID; //spell number
  517. spellToCast->stackNumber = (attackingHeroInstance->tempOwner == curInt->playerID) ? -1 : -2;
  518. spellToCast->side = defendingHeroInstance ? (curInt->playerID == defendingHeroInstance->tempOwner) : false;
  519. spellDestSelectMode = true;
  520. creatureCasting = false;
  521. //choosing possible targets
  522. const CGHeroInstance *castingHero = (attackingHeroInstance->tempOwner == curInt->playerID) ? attackingHeroInstance : defendingHeroInstance;
  523. assert(castingHero); // code below assumes non-null hero
  524. sp = spellID.toSpell();
  525. PossiblePlayerBattleAction spellSelMode = curInt->cb->getCasterAction(sp, castingHero, spells::Mode::HERO);
  526. if (spellSelMode == PossiblePlayerBattleAction::NO_LOCATION) //user does not have to select location
  527. {
  528. spellToCast->aimToHex(BattleHex::INVALID);
  529. curInt->cb->battleMakeAction(spellToCast.get());
  530. endCastingSpell();
  531. }
  532. else
  533. {
  534. possibleActions.clear();
  535. possibleActions.push_back (spellSelMode); //only this one action can be performed at the moment
  536. GH.fakeMouseMove();//update cursor
  537. }
  538. }
  539. void CBattleInterface::displayBattleLog(const std::vector<MetaString> & battleLog)
  540. {
  541. for(const auto & line : battleLog)
  542. {
  543. std::string formatted = line.toString();
  544. boost::algorithm::trim(formatted);
  545. if(!controlPanel->console->addText(formatted))
  546. logGlobal->warn("Too long battle log line");
  547. }
  548. }
  549. void CBattleInterface::displayCustomEffects(const std::vector<CustomEffectInfo> & customEffects)
  550. {
  551. for(const CustomEffectInfo & one : customEffects)
  552. {
  553. if(one.sound != 0)
  554. CCS->soundh->playSound(soundBase::soundID(one.sound));
  555. const CStack * s = curInt->cb->battleGetStackByID(one.stack, false);
  556. if(s && one.effect != 0)
  557. displayEffect(one.effect, s->getPosition());
  558. }
  559. }
  560. void CBattleInterface::displayEffect(ui32 effect, BattleHex destTile)
  561. {
  562. std::string customAnim = graphics->battleACToDef[effect][0];
  563. stacksController->addNewAnim(new CEffectAnimation(this, customAnim, destTile));
  564. }
  565. void CBattleInterface::displaySpellAnimationQueue(const CSpell::TAnimationQueue & q, BattleHex destinationTile)
  566. {
  567. for(const CSpell::TAnimation & animation : q)
  568. {
  569. if(animation.pause > 0)
  570. stacksController->addNewAnim(new CDummyAnimation(this, animation.pause));
  571. else
  572. stacksController->addNewAnim(new CEffectAnimation(this, animation.resourceName, destinationTile, false, animation.verticalPosition == VerticalPosition::BOTTOM));
  573. }
  574. }
  575. void CBattleInterface::displaySpellCast(SpellID spellID, BattleHex destinationTile)
  576. {
  577. const CSpell * spell = spellID.toSpell();
  578. if(spell)
  579. displaySpellAnimationQueue(spell->animationInfo.cast, destinationTile);
  580. }
  581. void CBattleInterface::displaySpellEffect(SpellID spellID, BattleHex destinationTile)
  582. {
  583. const CSpell *spell = spellID.toSpell();
  584. if(spell)
  585. displaySpellAnimationQueue(spell->animationInfo.affect, destinationTile);
  586. }
  587. void CBattleInterface::displaySpellHit(SpellID spellID, BattleHex destinationTile)
  588. {
  589. const CSpell * spell = spellID.toSpell();
  590. if(spell)
  591. displaySpellAnimationQueue(spell->animationInfo.hit, destinationTile);
  592. }
  593. void CBattleInterface::battleTriggerEffect(const BattleTriggerEffect & bte)
  594. {
  595. const CStack * stack = curInt->cb->battleGetStackByID(bte.stackID);
  596. if(!stack)
  597. {
  598. logGlobal->error("Invalid stack ID %d", bte.stackID);
  599. return;
  600. }
  601. //don't show animation when no HP is regenerated
  602. switch(bte.effect)
  603. {
  604. //TODO: move to bonus type handler
  605. case Bonus::HP_REGENERATION:
  606. displayEffect(74, stack->getPosition());
  607. CCS->soundh->playSound(soundBase::REGENER);
  608. break;
  609. case Bonus::MANA_DRAIN:
  610. displayEffect(77, stack->getPosition());
  611. CCS->soundh->playSound(soundBase::MANADRAI);
  612. break;
  613. case Bonus::POISON:
  614. displayEffect(67, stack->getPosition());
  615. CCS->soundh->playSound(soundBase::POISON);
  616. break;
  617. case Bonus::FEAR:
  618. displayEffect(15, stack->getPosition());
  619. CCS->soundh->playSound(soundBase::FEAR);
  620. break;
  621. case Bonus::MORALE:
  622. {
  623. std::string hlp = CGI->generaltexth->allTexts[33];
  624. boost::algorithm::replace_first(hlp,"%s",(stack->getName()));
  625. displayEffect(20,stack->getPosition());
  626. CCS->soundh->playSound(soundBase::GOODMRLE);
  627. controlPanel->console->addText(hlp);
  628. break;
  629. }
  630. default:
  631. return;
  632. }
  633. //waitForAnims(); //fixme: freezes game :?
  634. }
  635. void CBattleInterface::setAnimSpeed(int set)
  636. {
  637. Settings speed = settings.write["battle"]["animationSpeed"];
  638. speed->Float() = float(set) / 100;
  639. }
  640. int CBattleInterface::getAnimSpeed() const
  641. {
  642. if(settings["session"]["spectate"].Bool() && !settings["session"]["spectate-battle-speed"].isNull())
  643. return static_cast<int>(vstd::round(settings["session"]["spectate-battle-speed"].Float() *100));
  644. return static_cast<int>(vstd::round(settings["battle"]["animationSpeed"].Float() *100));
  645. }
  646. CPlayerInterface *CBattleInterface::getCurrentPlayerInterface() const
  647. {
  648. return curInt.get();
  649. }
  650. void CBattleInterface::trySetActivePlayer( PlayerColor player )
  651. {
  652. if ( attackerInt && attackerInt->playerID == player )
  653. curInt = attackerInt;
  654. if ( defenderInt && defenderInt->playerID == player )
  655. curInt = defenderInt;
  656. }
  657. void CBattleInterface::activateStack()
  658. {
  659. if(!battleActionsStarted)
  660. return; //"show" function should re-call this function
  661. stacksController->activateStack();
  662. const CStack * s = stacksController->getActiveStack();
  663. if(!s)
  664. return;
  665. myTurn = true;
  666. queue->update();
  667. fieldController->redrawBackgroundWithHexes();
  668. possibleActions = getPossibleActionsForStack(s);
  669. GH.fakeMouseMove();
  670. }
  671. void CBattleInterface::endCastingSpell()
  672. {
  673. if(spellDestSelectMode)
  674. {
  675. spellToCast.reset();
  676. sp = nullptr;
  677. spellDestSelectMode = false;
  678. CCS->curh->changeGraphic(ECursor::COMBAT, ECursor::COMBAT_POINTER);
  679. if(stacksController->getActiveStack())
  680. {
  681. possibleActions = getPossibleActionsForStack(stacksController->getActiveStack()); //restore actions after they were cleared
  682. myTurn = true;
  683. }
  684. }
  685. else
  686. {
  687. if(stacksController->getActiveStack())
  688. {
  689. possibleActions = getPossibleActionsForStack(stacksController->getActiveStack());
  690. GH.fakeMouseMove();
  691. }
  692. }
  693. }
  694. void CBattleInterface::enterCreatureCastingMode()
  695. {
  696. //silently check for possible errors
  697. if (!myTurn)
  698. return;
  699. if (tacticsMode)
  700. return;
  701. //hero is casting a spell
  702. if (spellDestSelectMode)
  703. return;
  704. if (!stacksController->getActiveStack())
  705. return;
  706. if (!stacksController->activeStackSpellcaster())
  707. return;
  708. //random spellcaster
  709. if (stacksController->activeStackSpellToCast() == SpellID::NONE)
  710. return;
  711. if (vstd::contains(possibleActions, PossiblePlayerBattleAction::NO_LOCATION))
  712. {
  713. const spells::Caster * caster = stacksController->getActiveStack();
  714. const CSpell * spell = stacksController->activeStackSpellToCast().toSpell();
  715. spells::Target target;
  716. target.emplace_back();
  717. spells::BattleCast cast(curInt->cb.get(), caster, spells::Mode::CREATURE_ACTIVE, spell);
  718. auto m = spell->battleMechanics(&cast);
  719. spells::detail::ProblemImpl ignored;
  720. const bool isCastingPossible = m->canBeCastAt(target, ignored);
  721. if (isCastingPossible)
  722. {
  723. myTurn = false;
  724. giveCommand(EActionType::MONSTER_SPELL, BattleHex::INVALID, stacksController->activeStackSpellToCast());
  725. stacksController->setSelectedStack(nullptr);
  726. CCS->curh->changeGraphic(ECursor::COMBAT, ECursor::COMBAT_POINTER);
  727. }
  728. }
  729. else
  730. {
  731. possibleActions = getPossibleActionsForStack(stacksController->getActiveStack());
  732. auto actionFilterPredicate = [](const PossiblePlayerBattleAction x)
  733. {
  734. return (x != PossiblePlayerBattleAction::ANY_LOCATION) && (x != PossiblePlayerBattleAction::NO_LOCATION) &&
  735. (x != PossiblePlayerBattleAction::FREE_LOCATION) && (x != PossiblePlayerBattleAction::AIMED_SPELL_CREATURE) &&
  736. (x != PossiblePlayerBattleAction::OBSTACLE);
  737. };
  738. vstd::erase_if(possibleActions, actionFilterPredicate);
  739. GH.fakeMouseMove();
  740. }
  741. }
  742. std::vector<PossiblePlayerBattleAction> CBattleInterface::getPossibleActionsForStack(const CStack *stack)
  743. {
  744. BattleClientInterfaceData data; //hard to get rid of these things so for now they're required data to pass
  745. data.creatureSpellToCast = stacksController->activeStackSpellToCast();
  746. data.tacticsMode = tacticsMode;
  747. auto allActions = curInt->cb->getClientActionsForStack(stack, data);
  748. return std::vector<PossiblePlayerBattleAction>(allActions);
  749. }
  750. void CBattleInterface::reorderPossibleActionsPriority(const CStack * stack, MouseHoveredHexContext context)
  751. {
  752. if(tacticsMode || possibleActions.empty()) return; //this function is not supposed to be called in tactics mode or before getPossibleActionsForStack
  753. auto assignPriority = [&](PossiblePlayerBattleAction const & item) -> uint8_t //large lambda assigning priority which would have to be part of possibleActions without it
  754. {
  755. switch(item)
  756. {
  757. case PossiblePlayerBattleAction::AIMED_SPELL_CREATURE:
  758. case PossiblePlayerBattleAction::ANY_LOCATION:
  759. case PossiblePlayerBattleAction::NO_LOCATION:
  760. case PossiblePlayerBattleAction::FREE_LOCATION:
  761. case PossiblePlayerBattleAction::OBSTACLE:
  762. if(!stack->hasBonusOfType(Bonus::NO_SPELLCAST_BY_DEFAULT) && context == MouseHoveredHexContext::OCCUPIED_HEX)
  763. return 1;
  764. else
  765. return 100;//bottom priority
  766. break;
  767. case PossiblePlayerBattleAction::RANDOM_GENIE_SPELL:
  768. return 2; break;
  769. case PossiblePlayerBattleAction::RISE_DEMONS:
  770. return 3; break;
  771. case PossiblePlayerBattleAction::SHOOT:
  772. return 4; break;
  773. case PossiblePlayerBattleAction::ATTACK_AND_RETURN:
  774. return 5; break;
  775. case PossiblePlayerBattleAction::ATTACK:
  776. return 6; break;
  777. case PossiblePlayerBattleAction::WALK_AND_ATTACK:
  778. return 7; break;
  779. case PossiblePlayerBattleAction::MOVE_STACK:
  780. return 8; break;
  781. case PossiblePlayerBattleAction::CATAPULT:
  782. return 9; break;
  783. case PossiblePlayerBattleAction::HEAL:
  784. return 10; break;
  785. default:
  786. return 200; break;
  787. }
  788. };
  789. auto comparer = [&](PossiblePlayerBattleAction const & lhs, PossiblePlayerBattleAction const & rhs)
  790. {
  791. return assignPriority(lhs) > assignPriority(rhs);
  792. };
  793. std::make_heap(possibleActions.begin(), possibleActions.end(), comparer);
  794. }
  795. void CBattleInterface::endAction(const BattleAction* action)
  796. {
  797. const CStack *stack = curInt->cb->battleGetStackByID(action->stackNumber);
  798. if(action->actionType == EActionType::HERO_SPELL)
  799. setHeroAnimation(action->side, 0);
  800. stacksController->endAction(action);
  801. queue->update();
  802. if (tacticsMode) //stack ended movement in tactics phase -> select the next one
  803. tacticNextStack(stack);
  804. 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
  805. fieldController->redrawBackgroundWithHexes();
  806. // if (stacksController->getActiveStack() && !animsAreDisplayed.get() && pendingAnims.empty() && !active)
  807. // {
  808. // logGlobal->warn("Something wrong... interface was deactivated but there is no animation. Reactivating...");
  809. // controlPanel->blockUI(false);
  810. // }
  811. // else
  812. // {
  813. // block UI if no active stack (e.g. enemy turn);
  814. controlPanel->blockUI(stacksController->getActiveStack() == nullptr);
  815. // }
  816. }
  817. void CBattleInterface::hideQueue()
  818. {
  819. Settings showQueue = settings.write["battle"]["showQueue"];
  820. showQueue->Bool() = false;
  821. queue->deactivate();
  822. if (!queue->embedded)
  823. {
  824. moveBy(Point(0, -queue->pos.h / 2));
  825. GH.totalRedraw();
  826. }
  827. }
  828. void CBattleInterface::showQueue()
  829. {
  830. Settings showQueue = settings.write["battle"]["showQueue"];
  831. showQueue->Bool() = true;
  832. queue->activate();
  833. if (!queue->embedded)
  834. {
  835. moveBy(Point(0, +queue->pos.h / 2));
  836. GH.totalRedraw();
  837. }
  838. }
  839. void CBattleInterface::startAction(const BattleAction* action)
  840. {
  841. //setActiveStack(nullptr);
  842. controlPanel->blockUI(true);
  843. if(action->actionType == EActionType::END_TACTIC_PHASE)
  844. {
  845. controlPanel->tacticPhaseEnded();
  846. return;
  847. }
  848. const CStack *stack = curInt->cb->battleGetStackByID(action->stackNumber);
  849. if (stack)
  850. {
  851. queue->update();
  852. }
  853. else
  854. {
  855. assert(action->actionType == EActionType::HERO_SPELL); //only cast spell is valid action without acting stack number
  856. }
  857. stacksController->startAction(action);
  858. redraw(); // redraw after deactivation, including proper handling of hovered hexes
  859. if(action->actionType == EActionType::HERO_SPELL) //when hero casts spell
  860. {
  861. setHeroAnimation(action->side, 4);
  862. return;
  863. }
  864. if (!stack)
  865. {
  866. logGlobal->error("Something wrong with stackNumber in actionStarted. Stack number: %d", action->stackNumber);
  867. return;
  868. }
  869. int txtid = 0;
  870. switch(action->actionType)
  871. {
  872. case EActionType::WAIT:
  873. txtid = 136;
  874. break;
  875. case EActionType::BAD_MORALE:
  876. txtid = -34; //negative -> no separate singular/plural form
  877. displayEffect(30, stack->getPosition());
  878. CCS->soundh->playSound(soundBase::BADMRLE);
  879. break;
  880. }
  881. if(txtid != 0)
  882. controlPanel->console->addText(stack->formatGeneralMessage(txtid));
  883. //displaying special abilities
  884. auto actionTarget = action->getTarget(curInt->cb.get());
  885. switch(action->actionType)
  886. {
  887. case EActionType::STACK_HEAL:
  888. displayEffect(74, actionTarget.at(0).hexValue);
  889. CCS->soundh->playSound(soundBase::REGENER);
  890. break;
  891. }
  892. }
  893. void CBattleInterface::waitForAnims()
  894. {
  895. auto unlockPim = vstd::makeUnlockGuard(*CPlayerInterface::pim);
  896. animsAreDisplayed.waitWhileTrue();
  897. }
  898. void CBattleInterface::tacticPhaseEnd()
  899. {
  900. stacksController->setActiveStack(nullptr);
  901. controlPanel->blockUI(true);
  902. tacticsMode = false;
  903. }
  904. static bool immobile(const CStack *s)
  905. {
  906. return !s->Speed(0, true); //should bound stacks be immobile?
  907. }
  908. void CBattleInterface::tacticNextStack(const CStack * current)
  909. {
  910. if (!current)
  911. current = stacksController->getActiveStack();
  912. //no switching stacks when the current one is moving
  913. waitForAnims();
  914. TStacks stacksOfMine = tacticianInterface->cb->battleGetStacks(CBattleCallback::ONLY_MINE);
  915. vstd::erase_if (stacksOfMine, &immobile);
  916. if (stacksOfMine.empty())
  917. {
  918. tacticPhaseEnd();
  919. return;
  920. }
  921. auto it = vstd::find(stacksOfMine, current);
  922. if (it != stacksOfMine.end() && ++it != stacksOfMine.end())
  923. stackActivated(*it);
  924. else
  925. stackActivated(stacksOfMine.front());
  926. }
  927. std::string formatDmgRange(std::pair<ui32, ui32> dmgRange)
  928. {
  929. if (dmgRange.first != dmgRange.second)
  930. return (boost::format("%d - %d") % dmgRange.first % dmgRange.second).str();
  931. else
  932. return (boost::format("%d") % dmgRange.first).str();
  933. }
  934. bool CBattleInterface::canStackMoveHere(const CStack * stackToMove, BattleHex myNumber)
  935. {
  936. std::vector<BattleHex> acc = curInt->cb->battleGetAvailableHexes(stackToMove);
  937. BattleHex shiftedDest = myNumber.cloneInDirection(stackToMove->destShiftDir(), false);
  938. if (vstd::contains(acc, myNumber))
  939. return true;
  940. else if (stackToMove->doubleWide() && vstd::contains(acc, shiftedDest))
  941. return true;
  942. else
  943. return false;
  944. }
  945. void CBattleInterface::handleHex(BattleHex myNumber, int eventType)
  946. {
  947. if (!myTurn || !battleActionsStarted) //we are not permit to do anything
  948. return;
  949. // This function handles mouse move over hexes and l-clicking on them.
  950. // First we decide what happens if player clicks on this hex and set appropriately
  951. // consoleMsg, cursorFrame/Type and prepare lambda realizeAction.
  952. //
  953. // Then, depending whether it was hover/click we either call the action or set tooltip/cursor.
  954. //used when hovering -> tooltip message and cursor to be set
  955. std::string consoleMsg;
  956. bool setCursor = true; //if we want to suppress setting cursor
  957. ECursor::ECursorTypes cursorType = ECursor::COMBAT;
  958. int cursorFrame = ECursor::COMBAT_POINTER; //TODO: is this line used?
  959. //used when l-clicking -> action to be called upon the click
  960. std::function<void()> realizeAction;
  961. //Get stack on the hex - first try to grab the alive one, if not found -> allow dead stacks.
  962. const CStack * shere = curInt->cb->battleGetStackByPos(myNumber, true);
  963. if(!shere)
  964. shere = curInt->cb->battleGetStackByPos(myNumber, false);
  965. if(!stacksController->getActiveStack())
  966. return;
  967. bool ourStack = false;
  968. if (shere)
  969. ourStack = shere->owner == curInt->playerID;
  970. //stack may have changed, update selection border
  971. stacksController->setHoveredStack(shere);
  972. localActions.clear();
  973. illegalActions.clear();
  974. reorderPossibleActionsPriority(stacksController->getActiveStack(), shere ? MouseHoveredHexContext::OCCUPIED_HEX : MouseHoveredHexContext::UNOCCUPIED_HEX);
  975. const bool forcedAction = possibleActions.size() == 1;
  976. for (PossiblePlayerBattleAction action : possibleActions)
  977. {
  978. bool legalAction = false; //this action is legal and can be performed
  979. bool notLegal = false; //this action is not legal and should display message
  980. switch (action)
  981. {
  982. case PossiblePlayerBattleAction::CHOOSE_TACTICS_STACK:
  983. if (shere && ourStack)
  984. legalAction = true;
  985. break;
  986. case PossiblePlayerBattleAction::MOVE_TACTICS:
  987. case PossiblePlayerBattleAction::MOVE_STACK:
  988. {
  989. if (!(shere && shere->alive())) //we can walk on dead stacks
  990. {
  991. if(canStackMoveHere(stacksController->getActiveStack(), myNumber))
  992. legalAction = true;
  993. }
  994. break;
  995. }
  996. case PossiblePlayerBattleAction::ATTACK:
  997. case PossiblePlayerBattleAction::WALK_AND_ATTACK:
  998. case PossiblePlayerBattleAction::ATTACK_AND_RETURN:
  999. {
  1000. if(curInt->cb->battleCanAttack(stacksController->getActiveStack(), shere, myNumber))
  1001. {
  1002. if (fieldController->isTileAttackable(myNumber)) // move isTileAttackable to be part of battleCanAttack?
  1003. {
  1004. fieldController->setBattleCursor(myNumber); // temporary - needed for following function :(
  1005. BattleHex attackFromHex = fieldController->fromWhichHexAttack(myNumber);
  1006. if (attackFromHex >= 0) //we can be in this line when unreachable creature is L - clicked (as of revision 1308)
  1007. legalAction = true;
  1008. }
  1009. }
  1010. }
  1011. break;
  1012. case PossiblePlayerBattleAction::SHOOT:
  1013. if(curInt->cb->battleCanShoot(stacksController->getActiveStack(), myNumber))
  1014. legalAction = true;
  1015. break;
  1016. case PossiblePlayerBattleAction::ANY_LOCATION:
  1017. if (myNumber > -1) //TODO: this should be checked for all actions
  1018. {
  1019. if(isCastingPossibleHere(stacksController->getActiveStack(), shere, myNumber))
  1020. legalAction = true;
  1021. }
  1022. break;
  1023. case PossiblePlayerBattleAction::AIMED_SPELL_CREATURE:
  1024. if(shere && isCastingPossibleHere(stacksController->getActiveStack(), shere, myNumber))
  1025. legalAction = true;
  1026. break;
  1027. case PossiblePlayerBattleAction::RANDOM_GENIE_SPELL:
  1028. {
  1029. if(shere && ourStack && shere != stacksController->getActiveStack() && shere->alive()) //only positive spells for other allied creatures
  1030. {
  1031. int spellID = curInt->cb->battleGetRandomStackSpell(CRandomGenerator::getDefault(), shere, CBattleInfoCallback::RANDOM_GENIE);
  1032. if(spellID > -1)
  1033. {
  1034. legalAction = true;
  1035. }
  1036. }
  1037. }
  1038. break;
  1039. case PossiblePlayerBattleAction::OBSTACLE:
  1040. if(isCastingPossibleHere(stacksController->getActiveStack(), shere, myNumber))
  1041. legalAction = true;
  1042. break;
  1043. case PossiblePlayerBattleAction::TELEPORT:
  1044. {
  1045. //todo: move to mechanics
  1046. ui8 skill = 0;
  1047. if (creatureCasting)
  1048. skill = stacksController->getActiveStack()->getEffectLevel(SpellID(SpellID::TELEPORT).toSpell());
  1049. else
  1050. skill = getActiveHero()->getEffectLevel(SpellID(SpellID::TELEPORT).toSpell());
  1051. //TODO: explicitely save power, skill
  1052. if (curInt->cb->battleCanTeleportTo(stacksController->getSelectedStack(), myNumber, skill))
  1053. legalAction = true;
  1054. else
  1055. notLegal = true;
  1056. }
  1057. break;
  1058. case PossiblePlayerBattleAction::SACRIFICE: //choose our living stack to sacrifice
  1059. if (shere && shere != stacksController->getSelectedStack() && ourStack && shere->alive())
  1060. legalAction = true;
  1061. else
  1062. notLegal = true;
  1063. break;
  1064. case PossiblePlayerBattleAction::FREE_LOCATION:
  1065. legalAction = true;
  1066. if(!isCastingPossibleHere(stacksController->getActiveStack(), shere, myNumber))
  1067. {
  1068. legalAction = false;
  1069. notLegal = true;
  1070. }
  1071. break;
  1072. case PossiblePlayerBattleAction::CATAPULT:
  1073. if (siegeController && siegeController->isCatapultAttackable(myNumber))
  1074. legalAction = true;
  1075. break;
  1076. case PossiblePlayerBattleAction::HEAL:
  1077. if (shere && ourStack && shere->canBeHealed())
  1078. legalAction = true;
  1079. break;
  1080. case PossiblePlayerBattleAction::RISE_DEMONS:
  1081. if (shere && ourStack && !shere->alive())
  1082. {
  1083. if (!(shere->hasBonusOfType(Bonus::UNDEAD)
  1084. || shere->hasBonusOfType(Bonus::NON_LIVING)
  1085. || shere->hasBonusOfType(Bonus::GARGOYLE)
  1086. || shere->summoned
  1087. || shere->isClone()
  1088. || shere->hasBonusOfType(Bonus::SIEGE_WEAPON)
  1089. ))
  1090. legalAction = true;
  1091. }
  1092. break;
  1093. }
  1094. if (legalAction)
  1095. localActions.push_back (action);
  1096. else if (notLegal || forcedAction)
  1097. illegalActions.push_back (action);
  1098. }
  1099. illegalAction = PossiblePlayerBattleAction::INVALID; //clear it in first place
  1100. if (vstd::contains(localActions, selectedAction)) //try to use last selected action by default
  1101. currentAction = selectedAction;
  1102. else if (localActions.size()) //if not possible, select first available action (they are sorted by suggested priority)
  1103. currentAction = localActions.front();
  1104. else //no legal action possible
  1105. {
  1106. currentAction = PossiblePlayerBattleAction::INVALID; //don't allow to do anything
  1107. if (vstd::contains(illegalActions, selectedAction))
  1108. illegalAction = selectedAction;
  1109. else if (illegalActions.size())
  1110. illegalAction = illegalActions.front();
  1111. else if (shere && ourStack && shere->alive()) //last possibility - display info about our creature
  1112. {
  1113. currentAction = PossiblePlayerBattleAction::CREATURE_INFO;
  1114. }
  1115. else
  1116. illegalAction = PossiblePlayerBattleAction::INVALID; //we should never be here
  1117. }
  1118. bool isCastingPossible = false;
  1119. bool secondaryTarget = false;
  1120. if (currentAction > PossiblePlayerBattleAction::INVALID)
  1121. {
  1122. switch (currentAction) //display console message, realize selected action
  1123. {
  1124. case PossiblePlayerBattleAction::CHOOSE_TACTICS_STACK:
  1125. consoleMsg = (boost::format(CGI->generaltexth->allTexts[481]) % shere->getName()).str(); //Select %s
  1126. realizeAction = [=](){ stackActivated(shere); };
  1127. break;
  1128. case PossiblePlayerBattleAction::MOVE_TACTICS:
  1129. case PossiblePlayerBattleAction::MOVE_STACK:
  1130. if (stacksController->getActiveStack()->hasBonusOfType(Bonus::FLYING))
  1131. {
  1132. cursorFrame = ECursor::COMBAT_FLY;
  1133. consoleMsg = (boost::format(CGI->generaltexth->allTexts[295]) % stacksController->getActiveStack()->getName()).str(); //Fly %s here
  1134. }
  1135. else
  1136. {
  1137. cursorFrame = ECursor::COMBAT_MOVE;
  1138. consoleMsg = (boost::format(CGI->generaltexth->allTexts[294]) % stacksController->getActiveStack()->getName()).str(); //Move %s here
  1139. }
  1140. realizeAction = [=]()
  1141. {
  1142. if(stacksController->getActiveStack()->doubleWide())
  1143. {
  1144. std::vector<BattleHex> acc = curInt->cb->battleGetAvailableHexes(stacksController->getActiveStack());
  1145. BattleHex shiftedDest = myNumber.cloneInDirection(stacksController->getActiveStack()->destShiftDir(), false);
  1146. if(vstd::contains(acc, myNumber))
  1147. giveCommand(EActionType::WALK, myNumber);
  1148. else if(vstd::contains(acc, shiftedDest))
  1149. giveCommand(EActionType::WALK, shiftedDest);
  1150. }
  1151. else
  1152. {
  1153. giveCommand(EActionType::WALK, myNumber);
  1154. }
  1155. };
  1156. break;
  1157. case PossiblePlayerBattleAction::ATTACK:
  1158. case PossiblePlayerBattleAction::WALK_AND_ATTACK:
  1159. case PossiblePlayerBattleAction::ATTACK_AND_RETURN: //TODO: allow to disable return
  1160. {
  1161. fieldController->setBattleCursor(myNumber); //handle direction of cursor and attackable tile
  1162. setCursor = false; //don't overwrite settings from the call above //TODO: what does it mean?
  1163. bool returnAfterAttack = currentAction == PossiblePlayerBattleAction::ATTACK_AND_RETURN;
  1164. realizeAction = [=]()
  1165. {
  1166. BattleHex attackFromHex = fieldController->fromWhichHexAttack(myNumber);
  1167. if(attackFromHex.isValid()) //we can be in this line when unreachable creature is L - clicked (as of revision 1308)
  1168. {
  1169. auto command = new BattleAction(BattleAction::makeMeleeAttack(stacksController->getActiveStack(), myNumber, attackFromHex, returnAfterAttack));
  1170. sendCommand(command, stacksController->getActiveStack());
  1171. }
  1172. };
  1173. TDmgRange damage = curInt->cb->battleEstimateDamage(stacksController->getActiveStack(), shere);
  1174. std::string estDmgText = formatDmgRange(std::make_pair((ui32)damage.first, (ui32)damage.second)); //calculating estimated dmg
  1175. consoleMsg = (boost::format(CGI->generaltexth->allTexts[36]) % shere->getName() % estDmgText).str(); //Attack %s (%s damage)
  1176. }
  1177. break;
  1178. case PossiblePlayerBattleAction::SHOOT:
  1179. {
  1180. if (curInt->cb->battleHasShootingPenalty(stacksController->getActiveStack(), myNumber))
  1181. cursorFrame = ECursor::COMBAT_SHOOT_PENALTY;
  1182. else
  1183. cursorFrame = ECursor::COMBAT_SHOOT;
  1184. realizeAction = [=](){giveCommand(EActionType::SHOOT, myNumber);};
  1185. TDmgRange damage = curInt->cb->battleEstimateDamage(stacksController->getActiveStack(), shere);
  1186. std::string estDmgText = formatDmgRange(std::make_pair((ui32)damage.first, (ui32)damage.second)); //calculating estimated dmg
  1187. //printing - Shoot %s (%d shots left, %s damage)
  1188. consoleMsg = (boost::format(CGI->generaltexth->allTexts[296]) % shere->getName() % stacksController->getActiveStack()->shots.available() % estDmgText).str();
  1189. }
  1190. break;
  1191. case PossiblePlayerBattleAction::AIMED_SPELL_CREATURE:
  1192. sp = CGI->spellh->objects[creatureCasting ? stacksController->activeStackSpellToCast() : spellToCast->actionSubtype]; //necessary if creature has random Genie spell at same time
  1193. consoleMsg = boost::str(boost::format(CGI->generaltexth->allTexts[27]) % sp->name % shere->getName()); //Cast %s on %s
  1194. switch (sp->id)
  1195. {
  1196. case SpellID::SACRIFICE:
  1197. case SpellID::TELEPORT:
  1198. stacksController->setSelectedStack(shere); //remember first target
  1199. secondaryTarget = true;
  1200. break;
  1201. }
  1202. isCastingPossible = true;
  1203. break;
  1204. case PossiblePlayerBattleAction::ANY_LOCATION:
  1205. sp = CGI->spellh->objects[creatureCasting ? stacksController->activeStackSpellToCast() : spellToCast->actionSubtype]; //necessary if creature has random Genie spell at same time
  1206. consoleMsg = boost::str(boost::format(CGI->generaltexth->allTexts[26]) % sp->name); //Cast %s
  1207. isCastingPossible = true;
  1208. break;
  1209. case PossiblePlayerBattleAction::RANDOM_GENIE_SPELL: //we assume that teleport / sacrifice will never be available as random spell
  1210. sp = nullptr;
  1211. consoleMsg = boost::str(boost::format(CGI->generaltexth->allTexts[301]) % shere->getName()); //Cast a spell on %
  1212. creatureCasting = true;
  1213. isCastingPossible = true;
  1214. break;
  1215. case PossiblePlayerBattleAction::TELEPORT:
  1216. consoleMsg = CGI->generaltexth->allTexts[25]; //Teleport Here
  1217. cursorFrame = ECursor::COMBAT_TELEPORT;
  1218. isCastingPossible = true;
  1219. break;
  1220. case PossiblePlayerBattleAction::OBSTACLE:
  1221. consoleMsg = CGI->generaltexth->allTexts[550];
  1222. //TODO: remove obstacle cursor
  1223. isCastingPossible = true;
  1224. break;
  1225. case PossiblePlayerBattleAction::SACRIFICE:
  1226. consoleMsg = (boost::format(CGI->generaltexth->allTexts[549]) % shere->getName()).str(); //sacrifice the %s
  1227. cursorFrame = ECursor::COMBAT_SACRIFICE;
  1228. isCastingPossible = true;
  1229. break;
  1230. case PossiblePlayerBattleAction::FREE_LOCATION:
  1231. consoleMsg = boost::str(boost::format(CGI->generaltexth->allTexts[26]) % sp->name); //Cast %s
  1232. isCastingPossible = true;
  1233. break;
  1234. case PossiblePlayerBattleAction::HEAL:
  1235. cursorFrame = ECursor::COMBAT_HEAL;
  1236. consoleMsg = (boost::format(CGI->generaltexth->allTexts[419]) % shere->getName()).str(); //Apply first aid to the %s
  1237. realizeAction = [=](){ giveCommand(EActionType::STACK_HEAL, myNumber); }; //command healing
  1238. break;
  1239. case PossiblePlayerBattleAction::RISE_DEMONS:
  1240. cursorType = ECursor::SPELLBOOK;
  1241. realizeAction = [=]()
  1242. {
  1243. giveCommand(EActionType::DAEMON_SUMMONING, myNumber);
  1244. };
  1245. break;
  1246. case PossiblePlayerBattleAction::CATAPULT:
  1247. cursorFrame = ECursor::COMBAT_SHOOT_CATAPULT;
  1248. realizeAction = [=](){ giveCommand(EActionType::CATAPULT, myNumber); };
  1249. break;
  1250. case PossiblePlayerBattleAction::CREATURE_INFO:
  1251. {
  1252. cursorFrame = ECursor::COMBAT_QUERY;
  1253. consoleMsg = (boost::format(CGI->generaltexth->allTexts[297]) % shere->getName()).str();
  1254. realizeAction = [=](){ GH.pushIntT<CStackWindow>(shere, false); };
  1255. break;
  1256. }
  1257. }
  1258. }
  1259. else //no possible valid action, display message
  1260. {
  1261. switch (illegalAction)
  1262. {
  1263. case PossiblePlayerBattleAction::AIMED_SPELL_CREATURE:
  1264. case PossiblePlayerBattleAction::RANDOM_GENIE_SPELL:
  1265. cursorFrame = ECursor::COMBAT_BLOCKED;
  1266. consoleMsg = CGI->generaltexth->allTexts[23];
  1267. break;
  1268. case PossiblePlayerBattleAction::TELEPORT:
  1269. cursorFrame = ECursor::COMBAT_BLOCKED;
  1270. consoleMsg = CGI->generaltexth->allTexts[24]; //Invalid Teleport Destination
  1271. break;
  1272. case PossiblePlayerBattleAction::SACRIFICE:
  1273. consoleMsg = CGI->generaltexth->allTexts[543]; //choose army to sacrifice
  1274. break;
  1275. case PossiblePlayerBattleAction::FREE_LOCATION:
  1276. cursorFrame = ECursor::COMBAT_BLOCKED;
  1277. consoleMsg = boost::str(boost::format(CGI->generaltexth->allTexts[181]) % sp->name); //No room to place %s here
  1278. break;
  1279. default:
  1280. if (myNumber == -1)
  1281. CCS->curh->changeGraphic(ECursor::COMBAT, ECursor::COMBAT_POINTER); //set neutral cursor over menu etc.
  1282. else
  1283. cursorFrame = ECursor::COMBAT_BLOCKED;
  1284. break;
  1285. }
  1286. }
  1287. if (isCastingPossible) //common part
  1288. {
  1289. switch (currentAction) //don't use that with teleport / sacrifice
  1290. {
  1291. case PossiblePlayerBattleAction::TELEPORT: //FIXME: more generic solution?
  1292. case PossiblePlayerBattleAction::SACRIFICE:
  1293. break;
  1294. default:
  1295. cursorType = ECursor::SPELLBOOK;
  1296. cursorFrame = 0;
  1297. if (consoleMsg.empty() && sp)
  1298. consoleMsg = boost::str(boost::format(CGI->generaltexth->allTexts[26]) % sp->name); //Cast %s
  1299. break;
  1300. }
  1301. realizeAction = [=]()
  1302. {
  1303. if(secondaryTarget) //select that target now
  1304. {
  1305. possibleActions.clear();
  1306. switch (sp->id.toEnum())
  1307. {
  1308. case SpellID::TELEPORT: //don't cast spell yet, only select target
  1309. spellToCast->aimToUnit(shere);
  1310. possibleActions.push_back(PossiblePlayerBattleAction::TELEPORT);
  1311. break;
  1312. case SpellID::SACRIFICE:
  1313. spellToCast->aimToHex(myNumber);
  1314. possibleActions.push_back(PossiblePlayerBattleAction::SACRIFICE);
  1315. break;
  1316. }
  1317. }
  1318. else
  1319. {
  1320. if (creatureCasting)
  1321. {
  1322. if (sp)
  1323. {
  1324. giveCommand(EActionType::MONSTER_SPELL, myNumber, stacksController->activeStackSpellToCast());
  1325. }
  1326. else //unknown random spell
  1327. {
  1328. giveCommand(EActionType::MONSTER_SPELL, myNumber);
  1329. }
  1330. }
  1331. else
  1332. {
  1333. assert(sp);
  1334. switch (sp->id.toEnum())
  1335. {
  1336. case SpellID::SACRIFICE:
  1337. spellToCast->aimToUnit(shere);//victim
  1338. break;
  1339. default:
  1340. spellToCast->aimToHex(myNumber);
  1341. break;
  1342. }
  1343. curInt->cb->battleMakeAction(spellToCast.get());
  1344. endCastingSpell();
  1345. }
  1346. stacksController->setSelectedStack(nullptr);
  1347. }
  1348. };
  1349. }
  1350. {
  1351. if (eventType == MOVE)
  1352. {
  1353. if (setCursor)
  1354. CCS->curh->changeGraphic(cursorType, cursorFrame);
  1355. controlPanel->console->write(consoleMsg);
  1356. }
  1357. if (eventType == LCLICK && realizeAction)
  1358. {
  1359. //opening creature window shouldn't affect myTurn...
  1360. if ((currentAction != PossiblePlayerBattleAction::CREATURE_INFO) && !secondaryTarget)
  1361. {
  1362. myTurn = false; //tends to crash with empty calls
  1363. }
  1364. realizeAction();
  1365. if (!secondaryTarget) //do not replace teleport or sacrifice cursor
  1366. CCS->curh->changeGraphic(ECursor::COMBAT, ECursor::COMBAT_POINTER);
  1367. controlPanel->console->clear();
  1368. }
  1369. }
  1370. }
  1371. bool CBattleInterface::isCastingPossibleHere(const CStack *sactive, const CStack *shere, BattleHex myNumber)
  1372. {
  1373. creatureCasting = stacksController->activeStackSpellcaster() && !spellDestSelectMode; //TODO: allow creatures to cast aimed spells
  1374. bool isCastingPossible = true;
  1375. int spellID = -1;
  1376. if (creatureCasting)
  1377. {
  1378. if (stacksController->activeStackSpellToCast() != SpellID::NONE && (shere != sactive)) //can't cast on itself
  1379. spellID = stacksController->activeStackSpellToCast(); //TODO: merge with SpellTocast?
  1380. }
  1381. else //hero casting
  1382. {
  1383. spellID = spellToCast->actionSubtype;
  1384. }
  1385. sp = nullptr;
  1386. if (spellID >= 0)
  1387. sp = CGI->spellh->objects[spellID];
  1388. if (sp)
  1389. {
  1390. const spells::Caster *caster = creatureCasting ? static_cast<const spells::Caster *>(sactive) : static_cast<const spells::Caster *>(curInt->cb->battleGetMyHero());
  1391. if (caster == nullptr)
  1392. {
  1393. isCastingPossible = false;//just in case
  1394. }
  1395. else
  1396. {
  1397. const spells::Mode mode = creatureCasting ? spells::Mode::CREATURE_ACTIVE : spells::Mode::HERO;
  1398. spells::Target target;
  1399. target.emplace_back(myNumber);
  1400. spells::BattleCast cast(curInt->cb.get(), caster, mode, sp);
  1401. auto m = sp->battleMechanics(&cast);
  1402. spells::detail::ProblemImpl problem; //todo: display problem in status bar
  1403. isCastingPossible = m->canBeCastAt(target, problem);
  1404. }
  1405. }
  1406. else
  1407. isCastingPossible = false;
  1408. if (!myNumber.isAvailable() && !shere) //empty tile outside battlefield (or in the unavailable border column)
  1409. isCastingPossible = false;
  1410. return isCastingPossible;
  1411. }
  1412. void CBattleInterface::obstaclePlaced(const CObstacleInstance & oi)
  1413. {
  1414. obstacleController->obstaclePlaced(oi);
  1415. }
  1416. const CGHeroInstance *CBattleInterface::currentHero() const
  1417. {
  1418. if (attackingHeroInstance->tempOwner == curInt->playerID)
  1419. return attackingHeroInstance;
  1420. else
  1421. return defendingHeroInstance;
  1422. }
  1423. InfoAboutHero CBattleInterface::enemyHero() const
  1424. {
  1425. InfoAboutHero ret;
  1426. if (attackingHeroInstance->tempOwner == curInt->playerID)
  1427. curInt->cb->getHeroInfo(defendingHeroInstance, ret);
  1428. else
  1429. curInt->cb->getHeroInfo(attackingHeroInstance, ret);
  1430. return ret;
  1431. }
  1432. void CBattleInterface::requestAutofightingAIToTakeAction()
  1433. {
  1434. assert(curInt->isAutoFightOn);
  1435. boost::thread aiThread([&]()
  1436. {
  1437. auto ba = make_unique<BattleAction>(curInt->autofightingAI->activeStack(stacksController->getActiveStack()));
  1438. if(curInt->cb->battleIsFinished())
  1439. {
  1440. return; // battle finished with spellcast
  1441. }
  1442. if (curInt->isAutoFightOn)
  1443. {
  1444. if (tacticsMode)
  1445. {
  1446. // Always end tactics mode. Player interface is blocked currently, so it's not possible that
  1447. // the AI can take any action except end tactics phase (AI actions won't be triggered)
  1448. //TODO implement the possibility that the AI will be triggered for further actions
  1449. //TODO any solution to merge tactics phase & normal phase in the way it is handled by the player and battle interface?
  1450. stacksController->setActiveStack(nullptr);
  1451. controlPanel->blockUI(true);
  1452. tacticsMode = false;
  1453. }
  1454. else
  1455. {
  1456. givenCommand.setn(ba.release());
  1457. }
  1458. }
  1459. else
  1460. {
  1461. boost::unique_lock<boost::recursive_mutex> un(*CPlayerInterface::pim);
  1462. activateStack();
  1463. }
  1464. });
  1465. aiThread.detach();
  1466. }
  1467. void CBattleInterface::showAll(SDL_Surface *to)
  1468. {
  1469. show(to);
  1470. }
  1471. void CBattleInterface::show(SDL_Surface *to)
  1472. {
  1473. assert(to);
  1474. SDL_Rect buf;
  1475. SDL_GetClipRect(to, &buf);
  1476. SDL_SetClipRect(to, &pos);
  1477. ++animCount;
  1478. if (stacksController->getActiveStack() != nullptr /*&& creAnims[stacksController->getActiveStack()->ID]->isIdle()*/) //show everything with range
  1479. {
  1480. // FIXME: any *real* reason to keep this separate? Speed difference can't be that big // TODO: move to showAll?
  1481. fieldController->showBackgroundImageWithHexes(to);
  1482. }
  1483. else
  1484. {
  1485. fieldController->showBackgroundImage(to);
  1486. obstacleController->showAbsoluteObstacles(to);
  1487. if ( siegeController )
  1488. siegeController->showAbsoluteObstacles(to);
  1489. }
  1490. fieldController->showHighlightedHexes(to);
  1491. showBattlefieldObjects(to);
  1492. projectilesController->showProjectiles(to);
  1493. if(battleActionsStarted)
  1494. stacksController->updateBattleAnimations();
  1495. SDL_SetClipRect(to, &buf); //restoring previous clip_rect
  1496. showInterface(to);
  1497. //activation of next stack, if any
  1498. //TODO: should be moved to the very start of this method?
  1499. activateStack();
  1500. }
  1501. void CBattleInterface::showBattlefieldObjects(SDL_Surface *to)
  1502. {
  1503. auto showHexEntry = [&](BattleObjectsByHex::HexData & hex)
  1504. {
  1505. if (siegeController)
  1506. siegeController->showPiecesOfWall(to, hex.walls);
  1507. obstacleController->showObstacles(to, hex.obstacles);
  1508. stacksController->showAliveStacks(to, hex.alive);
  1509. showBattleEffects(to, hex.effects);
  1510. };
  1511. BattleObjectsByHex objects = sortObjectsByHex();
  1512. // dead stacks should be blit first
  1513. stacksController->showStacks(to, objects.beforeAll.dead);
  1514. for (auto & data : objects.hex)
  1515. stacksController->showStacks(to, data.dead);
  1516. stacksController->showStacks(to, objects.afterAll.dead);
  1517. // display objects that must be blit before anything else (e.g. topmost walls)
  1518. showHexEntry(objects.beforeAll);
  1519. // show heroes after "beforeAll" - e.g. topmost wall in siege
  1520. if (attackingHero)
  1521. attackingHero->show(to);
  1522. if (defendingHero)
  1523. defendingHero->show(to);
  1524. // actual blit of most of objects, hex by hex
  1525. // NOTE: row-by-row blitting may be a better approach
  1526. for (auto &data : objects.hex)
  1527. showHexEntry(data);
  1528. // objects that must be blit *after* everything else - e.g. bottom tower or some spell effects
  1529. showHexEntry(objects.afterAll);
  1530. }
  1531. void CBattleInterface::showBattleEffects(SDL_Surface *to, const std::vector<const BattleEffect *> &battleEffects)
  1532. {
  1533. for (auto & elem : battleEffects)
  1534. {
  1535. int currentFrame = static_cast<int>(floor(elem->currentFrame));
  1536. currentFrame %= elem->animation->size();
  1537. auto img = elem->animation->getImage(currentFrame);
  1538. SDL_Rect temp_rect = genRect(img->height(), img->width(), elem->x, elem->y);
  1539. img->draw(to, &temp_rect, nullptr);
  1540. }
  1541. }
  1542. void CBattleInterface::showInterface(SDL_Surface *to)
  1543. {
  1544. //showing in-game console
  1545. LOCPLINT->cingconsole->show(to);
  1546. controlPanel->show(to);
  1547. Rect posWithQueue = Rect(pos.x, pos.y, 800, 600);
  1548. if (settings["battle"]["showQueue"].Bool())
  1549. {
  1550. if (!queue->embedded)
  1551. {
  1552. posWithQueue.y -= queue->pos.h;
  1553. posWithQueue.h += queue->pos.h;
  1554. }
  1555. queue->showAll(to);
  1556. }
  1557. //printing border around interface
  1558. if (screen->w != 800 || screen->h !=600)
  1559. {
  1560. CMessage::drawBorder(curInt->playerID,to,posWithQueue.w + 28, posWithQueue.h + 28, posWithQueue.x-14, posWithQueue.y-15);
  1561. }
  1562. }
  1563. BattleObjectsByHex CBattleInterface::sortObjectsByHex()
  1564. {
  1565. BattleObjectsByHex sorted;
  1566. // Sort creatures
  1567. stacksController->sortObjectsByHex(sorted);
  1568. // Sort battle effects (spells)
  1569. for (auto & battleEffect : battleEffects)
  1570. {
  1571. if (battleEffect.position.isValid())
  1572. sorted.hex[battleEffect.position].effects.push_back(&battleEffect);
  1573. else
  1574. sorted.afterAll.effects.push_back(&battleEffect);
  1575. }
  1576. // Sort obstacles
  1577. obstacleController->sortObjectsByHex(sorted);
  1578. // Sort wall parts
  1579. if (siegeController)
  1580. siegeController->sortObjectsByHex(sorted);
  1581. return sorted;
  1582. }