CBattleInterface.cpp 69 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284
  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 "../CBitmapHandler.h"
  21. #include "../CGameInfo.h"
  22. #include "../CMessage.h"
  23. #include "../CMT.h"
  24. #include "../CMusicHandler.h"
  25. #include "../CPlayerInterface.h"
  26. #include "../CVideoHandler.h"
  27. #include "../Graphics.h"
  28. #include "../gui/CAnimation.h"
  29. #include "../gui/CCursorHandler.h"
  30. #include "../gui/CGuiHandler.h"
  31. #include "../gui/SDL_Extensions.h"
  32. #include "../windows/CAdvmapInterface.h"
  33. #include "../windows/CCreatureWindow.h"
  34. #include "../windows/CSpellWindow.h"
  35. #include "../../CCallback.h"
  36. #include "../../lib/CStack.h"
  37. #include "../../lib/CConfigHandler.h"
  38. #include "../../lib/CGeneralTextHandler.h"
  39. #include "../../lib/CHeroHandler.h"
  40. #include "../../lib/CondSh.h"
  41. #include "../../lib/CRandomGenerator.h"
  42. #include "../../lib/spells/CSpellHandler.h"
  43. #include "../../lib/spells/ISpellMechanics.h"
  44. #include "../../lib/spells/Problem.h"
  45. #include "../../lib/CTownHandler.h"
  46. #include "../../lib/BattleFieldHandler.h"
  47. #include "../../lib/ObstacleHandler.h"
  48. #include "../../lib/CGameState.h"
  49. #include "../../lib/mapping/CMap.h"
  50. #include "../../lib/NetPacks.h"
  51. #include "../../lib/UnlockGuard.h"
  52. CondSh<bool> CBattleInterface::animsAreDisplayed(false);
  53. CondSh<BattleAction *> CBattleInterface::givenCommand(nullptr);
  54. static void onAnimationFinished(const CStack *stack, std::weak_ptr<CCreatureAnimation> anim)
  55. {
  56. std::shared_ptr<CCreatureAnimation> animation = anim.lock();
  57. if(!animation)
  58. return;
  59. if (animation->isIdle())
  60. {
  61. const CCreature *creature = stack->getCreature();
  62. if (animation->framesInGroup(CCreatureAnim::MOUSEON) > 0)
  63. {
  64. if (CRandomGenerator::getDefault().nextDouble(99.0) < creature->animation.timeBetweenFidgets *10)
  65. animation->playOnce(CCreatureAnim::MOUSEON);
  66. else
  67. animation->setType(CCreatureAnim::HOLDING);
  68. }
  69. else
  70. {
  71. animation->setType(CCreatureAnim::HOLDING);
  72. }
  73. }
  74. // always reset callback
  75. animation->onAnimationReset += std::bind(&onAnimationFinished, stack, anim);
  76. }
  77. static void transformPalette(SDL_Surface *surf, double rCor, double gCor, double bCor)
  78. {
  79. SDL_Color *colorsToChange = surf->format->palette->colors;
  80. for (int g=0; g<surf->format->palette->ncolors; ++g)
  81. {
  82. SDL_Color *color = &colorsToChange[g];
  83. if (color->b != 132 &&
  84. color->g != 231 &&
  85. color->r != 255) //it's not yellow border
  86. {
  87. color->r = static_cast<Uint8>(color->r * rCor);
  88. color->g = static_cast<Uint8>(color->g * gCor);
  89. color->b = static_cast<Uint8>(color->b * bCor);
  90. }
  91. }
  92. }
  93. void CBattleInterface::addNewAnim(CBattleAnimation *anim)
  94. {
  95. pendingAnims.push_back( std::make_pair(anim, false) );
  96. animsAreDisplayed.setn(true);
  97. }
  98. CBattleInterface::CBattleInterface(const CCreatureSet *army1, const CCreatureSet *army2,
  99. const CGHeroInstance *hero1, const CGHeroInstance *hero2,
  100. const SDL_Rect & myRect,
  101. std::shared_ptr<CPlayerInterface> att, std::shared_ptr<CPlayerInterface> defen, std::shared_ptr<CPlayerInterface> spectatorInt)
  102. : attackingHeroInstance(hero1), defendingHeroInstance(hero2), animCount(0),
  103. activeStack(nullptr), mouseHoveredStack(nullptr), stackToActivate(nullptr), selectedStack(nullptr),
  104. stackCanCastSpell(false), creatureCasting(false), spellDestSelectMode(false), spellToCast(nullptr), sp(nullptr),
  105. creatureSpellToCast(-1),
  106. attackerInt(att), defenderInt(defen), curInt(att), animIDhelper(0),
  107. myTurn(false), moveStarted(false), moveSoundHander(-1), bresult(nullptr), battleActionsStarted(false)
  108. {
  109. OBJ_CONSTRUCTION;
  110. projectilesController.reset(new CBattleProjectileController(this));
  111. if(spectatorInt)
  112. {
  113. curInt = spectatorInt;
  114. }
  115. else if(!curInt)
  116. {
  117. //May happen when we are defending during network MP game -> attacker interface is just not present
  118. curInt = defenderInt;
  119. }
  120. animsAreDisplayed.setn(false);
  121. pos = myRect;
  122. strongInterest = true;
  123. givenCommand.setn(nullptr);
  124. //hot-seat -> check tactics for both players (defender may be local human)
  125. if(attackerInt && attackerInt->cb->battleGetTacticDist())
  126. tacticianInterface = attackerInt;
  127. else if(defenderInt && defenderInt->cb->battleGetTacticDist())
  128. tacticianInterface = defenderInt;
  129. //if we found interface of player with tactics, then enter tactics mode
  130. tacticsMode = static_cast<bool>(tacticianInterface);
  131. //create stack queue
  132. bool embedQueue;
  133. std::string queueSize = settings["battle"]["queueSize"].String();
  134. if(queueSize == "auto")
  135. embedQueue = screen->h < 700;
  136. else
  137. embedQueue = screen->h < 700 || queueSize == "small";
  138. queue = std::make_shared<CStackQueue>(embedQueue, this);
  139. if(!embedQueue)
  140. {
  141. if (settings["battle"]["showQueue"].Bool())
  142. pos.y += queue->pos.h / 2; //center whole window
  143. queue->moveTo(Point(pos.x, pos.y - queue->pos.h));
  144. }
  145. queue->update();
  146. //preparing siege info
  147. const CGTownInstance *town = curInt->cb->battleGetDefendedTown();
  148. if(town && town->hasFort())
  149. siegeController.reset(new CBattleSiegeController(this, town));
  150. CPlayerInterface::battleInt = this;
  151. //initializing armies
  152. this->army1 = army1;
  153. this->army2 = army2;
  154. std::vector<const CStack*> stacks = curInt->cb->battleGetAllStacks(true);
  155. for(const CStack * s : stacks)
  156. {
  157. unitAdded(s);
  158. }
  159. //preparing menu background and terrain
  160. fieldController.reset( new CBattleFieldController(this));
  161. //preparing graphics for displaying amounts of creatures
  162. amountNormal = BitmapHandler::loadBitmap("CMNUMWIN.BMP");
  163. CSDL_Ext::alphaTransform(amountNormal);
  164. transformPalette(amountNormal, 0.59, 0.19, 0.93);
  165. amountPositive = BitmapHandler::loadBitmap("CMNUMWIN.BMP");
  166. CSDL_Ext::alphaTransform(amountPositive);
  167. transformPalette(amountPositive, 0.18, 1.00, 0.18);
  168. amountNegative = BitmapHandler::loadBitmap("CMNUMWIN.BMP");
  169. CSDL_Ext::alphaTransform(amountNegative);
  170. transformPalette(amountNegative, 1.00, 0.18, 0.18);
  171. amountEffNeutral = BitmapHandler::loadBitmap("CMNUMWIN.BMP");
  172. CSDL_Ext::alphaTransform(amountEffNeutral);
  173. transformPalette(amountEffNeutral, 1.00, 1.00, 0.18);
  174. //loading hero animations
  175. if(hero1) // attacking hero
  176. {
  177. std::string battleImage;
  178. if(!hero1->type->battleImage.empty())
  179. {
  180. battleImage = hero1->type->battleImage;
  181. }
  182. else
  183. {
  184. if(hero1->sex)
  185. battleImage = hero1->type->heroClass->imageBattleFemale;
  186. else
  187. battleImage = hero1->type->heroClass->imageBattleMale;
  188. }
  189. attackingHero = std::make_shared<CBattleHero>(battleImage, false, hero1->tempOwner, hero1->tempOwner == curInt->playerID ? hero1 : nullptr, this);
  190. auto img = attackingHero->animation->getImage(0, 0, true);
  191. if(img)
  192. attackingHero->pos = genRect(img->height(), img->width(), pos.x - 43, pos.y - 19);
  193. }
  194. if(hero2) // defending hero
  195. {
  196. std::string battleImage;
  197. if(!hero2->type->battleImage.empty())
  198. {
  199. battleImage = hero2->type->battleImage;
  200. }
  201. else
  202. {
  203. if(hero2->sex)
  204. battleImage = hero2->type->heroClass->imageBattleFemale;
  205. else
  206. battleImage = hero2->type->heroClass->imageBattleMale;
  207. }
  208. defendingHero = std::make_shared<CBattleHero>(battleImage, true, hero2->tempOwner, hero2->tempOwner == curInt->playerID ? hero2 : nullptr, this);
  209. auto img = defendingHero->animation->getImage(0, 0, true);
  210. if(img)
  211. defendingHero->pos = genRect(img->height(), img->width(), pos.x + 693, pos.y - 19);
  212. }
  213. obstacleController.reset(new CBattleObstacleController(this));
  214. if(tacticsMode)
  215. tacticNextStack(nullptr);
  216. CCS->musich->stopMusic();
  217. battleIntroSoundChannel = CCS->soundh->playSoundFromSet(CCS->soundh->battleIntroSounds);
  218. auto onIntroPlayed = [&]()
  219. {
  220. if(LOCPLINT->battleInt)
  221. {
  222. CCS->musich->playMusicFromSet("battle", true, true);
  223. battleActionsStarted = true;
  224. controlPanel->blockUI(settings["session"]["spectate"].Bool());
  225. battleIntroSoundChannel = -1;
  226. }
  227. };
  228. CCS->soundh->setCallback(battleIntroSoundChannel, onIntroPlayed);
  229. currentAction = PossiblePlayerBattleAction::INVALID;
  230. selectedAction = PossiblePlayerBattleAction::INVALID;
  231. addUsedEvents(RCLICK | MOVE | KEYBOARD);
  232. controlPanel->blockUI(true);
  233. }
  234. CBattleInterface::~CBattleInterface()
  235. {
  236. CPlayerInterface::battleInt = nullptr;
  237. givenCommand.cond.notify_all(); //that two lines should make any activeStack waiting thread to finish
  238. if (active) //dirty fix for #485
  239. {
  240. deactivate();
  241. }
  242. SDL_FreeSurface(amountNormal);
  243. SDL_FreeSurface(amountNegative);
  244. SDL_FreeSurface(amountPositive);
  245. SDL_FreeSurface(amountEffNeutral);
  246. //TODO: play AI tracks if battle was during AI turn
  247. //if (!curInt->makingTurn)
  248. //CCS->musich->playMusicFromSet(CCS->musich->aiMusics, -1);
  249. if (adventureInt && adventureInt->selection)
  250. {
  251. const auto & terrain = *(LOCPLINT->cb->getTile(adventureInt->selection->visitablePos())->terType);
  252. CCS->musich->playMusicFromSet("terrain", terrain.name, true, false);
  253. }
  254. animsAreDisplayed.setn(false);
  255. }
  256. void CBattleInterface::setPrintCellBorders(bool set)
  257. {
  258. Settings cellBorders = settings.write["battle"]["cellBorders"];
  259. cellBorders->Bool() = set;
  260. fieldController->redrawBackgroundWithHexes(activeStack);
  261. GH.totalRedraw();
  262. }
  263. void CBattleInterface::setPrintStackRange(bool set)
  264. {
  265. Settings stackRange = settings.write["battle"]["stackRange"];
  266. stackRange->Bool() = set;
  267. fieldController->redrawBackgroundWithHexes(activeStack);
  268. GH.totalRedraw();
  269. }
  270. void CBattleInterface::setPrintMouseShadow(bool set)
  271. {
  272. Settings shadow = settings.write["battle"]["mouseShadow"];
  273. shadow->Bool() = set;
  274. }
  275. void CBattleInterface::activate()
  276. {
  277. controlPanel->activate();
  278. if (curInt->isAutoFightOn)
  279. return;
  280. CIntObject::activate();
  281. if (attackingHero)
  282. attackingHero->activate();
  283. if (defendingHero)
  284. defendingHero->activate();
  285. fieldController->activate();
  286. if (settings["battle"]["showQueue"].Bool())
  287. queue->activate();
  288. LOCPLINT->cingconsole->activate();
  289. }
  290. void CBattleInterface::deactivate()
  291. {
  292. controlPanel->deactivate();
  293. CIntObject::deactivate();
  294. fieldController->deactivate();
  295. if (attackingHero)
  296. attackingHero->deactivate();
  297. if (defendingHero)
  298. defendingHero->deactivate();
  299. if (settings["battle"]["showQueue"].Bool())
  300. queue->deactivate();
  301. LOCPLINT->cingconsole->deactivate();
  302. }
  303. void CBattleInterface::keyPressed(const SDL_KeyboardEvent & key)
  304. {
  305. if(key.keysym.sym == SDLK_q && key.state == SDL_PRESSED)
  306. {
  307. if(settings["battle"]["showQueue"].Bool()) //hide queue
  308. hideQueue();
  309. else
  310. showQueue();
  311. }
  312. else if(key.keysym.sym == SDLK_f && key.state == SDL_PRESSED)
  313. {
  314. enterCreatureCastingMode();
  315. }
  316. else if(key.keysym.sym == SDLK_ESCAPE)
  317. {
  318. if(!battleActionsStarted)
  319. CCS->soundh->stopSound(battleIntroSoundChannel);
  320. else
  321. endCastingSpell();
  322. }
  323. }
  324. void CBattleInterface::mouseMoved(const SDL_MouseMotionEvent &sEvent)
  325. {
  326. BattleHex selectedHex = fieldController->getHoveredHex();
  327. handleHex(selectedHex, MOVE);
  328. }
  329. void CBattleInterface::clickRight(tribool down, bool previousState)
  330. {
  331. if (!down)
  332. {
  333. endCastingSpell();
  334. }
  335. }
  336. void CBattleInterface::unitAdded(const CStack * stack)
  337. {
  338. creDir[stack->ID] = stack->side == BattleSide::ATTACKER; // must be set before getting stack position
  339. Point coords = CClickableHex::getXYUnitAnim(stack->getPosition(), stack, this);
  340. if(stack->initialPosition < 0) //turret
  341. {
  342. assert(siegeController);
  343. const CCreature *turretCreature = siegeController->turretCreature();
  344. creAnims[stack->ID] = AnimationControls::getAnimation(turretCreature);
  345. creAnims[stack->ID]->pos.h = 225;
  346. coords = siegeController->turretCreaturePosition(stack->initialPosition);
  347. }
  348. else
  349. {
  350. creAnims[stack->ID] = AnimationControls::getAnimation(stack->getCreature());
  351. creAnims[stack->ID]->onAnimationReset += std::bind(&onAnimationFinished, stack, creAnims[stack->ID]);
  352. creAnims[stack->ID]->pos.h = creAnims[stack->ID]->getHeight();
  353. }
  354. creAnims[stack->ID]->pos.x = coords.x;
  355. creAnims[stack->ID]->pos.y = coords.y;
  356. creAnims[stack->ID]->pos.w = creAnims[stack->ID]->getWidth();
  357. creAnims[stack->ID]->setType(CCreatureAnim::HOLDING);
  358. //loading projectiles for units
  359. if(stack->isShooter())
  360. {
  361. projectilesController->initStackProjectile(stack);
  362. }
  363. }
  364. void CBattleInterface::stackRemoved(uint32_t stackID)
  365. {
  366. if (activeStack != nullptr)
  367. {
  368. if (activeStack->ID == stackID)
  369. {
  370. BattleAction *action = new BattleAction();
  371. action->side = defendingHeroInstance ? (curInt->playerID == defendingHeroInstance->tempOwner) : false;
  372. action->actionType = EActionType::CANCEL;
  373. action->stackNumber = activeStack->ID;
  374. givenCommand.setn(action);
  375. setActiveStack(nullptr);
  376. }
  377. }
  378. //todo: ensure that ghost stack animation has fadeout effect
  379. fieldController->redrawBackgroundWithHexes(activeStack);
  380. queue->update();
  381. }
  382. void CBattleInterface::stackActivated(const CStack *stack) //TODO: check it all before game state is changed due to abilities
  383. {
  384. stackToActivate = stack;
  385. waitForAnims();
  386. if (stackToActivate) //during waiting stack may have gotten activated through show
  387. activateStack();
  388. }
  389. void CBattleInterface::stackMoved(const CStack *stack, std::vector<BattleHex> destHex, int distance)
  390. {
  391. addNewAnim(new CMovementAnimation(this, stack, destHex, distance));
  392. waitForAnims();
  393. }
  394. void CBattleInterface::stacksAreAttacked(std::vector<StackAttackedInfo> attackedInfos)
  395. {
  396. for(auto & attackedInfo : attackedInfos)
  397. {
  398. //if (!attackedInfo.cloneKilled) //FIXME: play dead animation for cloned creature before it vanishes
  399. addNewAnim(new CDefenceAnimation(attackedInfo, this));
  400. if(attackedInfo.rebirth)
  401. {
  402. displayEffect(50, attackedInfo.defender->getPosition()); //TODO: play reverse death animation
  403. CCS->soundh->playSound(soundBase::RESURECT);
  404. }
  405. }
  406. waitForAnims();
  407. std::array<int, 2> killedBySide = {0, 0};
  408. int targets = 0;
  409. for(const StackAttackedInfo & attackedInfo : attackedInfos)
  410. {
  411. ++targets;
  412. ui8 side = attackedInfo.defender->side;
  413. killedBySide.at(side) += attackedInfo.amountKilled;
  414. }
  415. for(ui8 side = 0; side < 2; side++)
  416. {
  417. if(killedBySide.at(side) > killedBySide.at(1-side))
  418. setHeroAnimation(side, 2);
  419. else if(killedBySide.at(side) < killedBySide.at(1-side))
  420. setHeroAnimation(side, 3);
  421. }
  422. for (auto & attackedInfo : attackedInfos)
  423. {
  424. if (attackedInfo.rebirth)
  425. creAnims[attackedInfo.defender->ID]->setType(CCreatureAnim::HOLDING);
  426. if (attackedInfo.cloneKilled)
  427. stackRemoved(attackedInfo.defender->ID);
  428. }
  429. }
  430. void CBattleInterface::stackAttacking( const CStack *attacker, BattleHex dest, const CStack *attacked, bool shooting )
  431. {
  432. if (shooting)
  433. {
  434. addNewAnim(new CShootingAnimation(this, attacker, dest, attacked));
  435. }
  436. else
  437. {
  438. addNewAnim(new CMeleeAttackAnimation(this, attacker, dest, attacked));
  439. }
  440. //waitForAnims();
  441. }
  442. void CBattleInterface::newRoundFirst( int round )
  443. {
  444. waitForAnims();
  445. }
  446. void CBattleInterface::newRound(int number)
  447. {
  448. controlPanel->console->addText(CGI->generaltexth->allTexts[412]);
  449. }
  450. void CBattleInterface::giveCommand(EActionType action, BattleHex tile, si32 additional)
  451. {
  452. const CStack * actor = nullptr;
  453. if(action != EActionType::HERO_SPELL && action != EActionType::RETREAT && action != EActionType::SURRENDER)
  454. {
  455. actor = activeStack;
  456. }
  457. auto side = curInt->cb->playerToSide(curInt->playerID);
  458. if(!side)
  459. {
  460. logGlobal->error("Player %s is not in battle", curInt->playerID.getStr());
  461. return;
  462. }
  463. auto ba = new BattleAction(); //is deleted in CPlayerInterface::activeStack()
  464. ba->side = side.get();
  465. ba->actionType = action;
  466. ba->aimToHex(tile);
  467. ba->actionSubtype = additional;
  468. sendCommand(ba, actor);
  469. }
  470. void CBattleInterface::sendCommand(BattleAction *& command, const CStack * actor)
  471. {
  472. command->stackNumber = actor ? actor->unitId() : ((command->side == BattleSide::ATTACKER) ? -1 : -2);
  473. if(!tacticsMode)
  474. {
  475. logGlobal->trace("Setting command for %s", (actor ? actor->nodeName() : "hero"));
  476. myTurn = false;
  477. setActiveStack(nullptr);
  478. givenCommand.setn(command);
  479. }
  480. else
  481. {
  482. curInt->cb->battleMakeTacticAction(command);
  483. vstd::clear_pointer(command);
  484. setActiveStack(nullptr);
  485. //next stack will be activated when action ends
  486. }
  487. }
  488. const CGHeroInstance * CBattleInterface::getActiveHero()
  489. {
  490. const CStack *attacker = activeStack;
  491. if(!attacker)
  492. {
  493. return nullptr;
  494. }
  495. if(attacker->side == BattleSide::ATTACKER)
  496. {
  497. return attackingHeroInstance;
  498. }
  499. return defendingHeroInstance;
  500. }
  501. void CBattleInterface::hexLclicked(int whichOne)
  502. {
  503. handleHex(whichOne, LCLICK);
  504. }
  505. void CBattleInterface::stackIsCatapulting(const CatapultAttack & ca)
  506. {
  507. if (siegeController)
  508. siegeController->stackIsCatapulting(ca);
  509. }
  510. void CBattleInterface::gateStateChanged(const EGateState state)
  511. {
  512. if (siegeController)
  513. siegeController->gateStateChanged(state);
  514. }
  515. void CBattleInterface::battleFinished(const BattleResult& br)
  516. {
  517. bresult = &br;
  518. {
  519. auto unlockPim = vstd::makeUnlockGuard(*CPlayerInterface::pim);
  520. animsAreDisplayed.waitUntil(false);
  521. }
  522. setActiveStack(nullptr);
  523. displayBattleFinished();
  524. }
  525. void CBattleInterface::displayBattleFinished()
  526. {
  527. CCS->curh->changeGraphic(ECursor::ADVENTURE,0);
  528. if(settings["session"]["spectate"].Bool() && settings["session"]["spectate-skip-battle-result"].Bool())
  529. {
  530. close();
  531. return;
  532. }
  533. GH.pushInt(std::make_shared<CBattleResultWindow>(*bresult, *(this->curInt)));
  534. curInt->waitWhileDialog(); // Avoid freeze when AI end turn after battle. Check bug #1897
  535. CPlayerInterface::battleInt = nullptr;
  536. }
  537. void CBattleInterface::spellCast(const BattleSpellCast * sc)
  538. {
  539. const SpellID spellID = sc->spellID;
  540. const CSpell * spell = spellID.toSpell();
  541. if(!spell)
  542. return;
  543. const std::string & castSoundPath = spell->getCastSound();
  544. if (!castSoundPath.empty())
  545. CCS->soundh->playSound(castSoundPath);
  546. const auto casterStackID = sc->casterStack;
  547. const CStack * casterStack = nullptr;
  548. if(casterStackID >= 0)
  549. {
  550. casterStack = curInt->cb->battleGetStackByID(casterStackID);
  551. }
  552. Point srccoord = (sc->side ? Point(770, 60) : Point(30, 60)) + pos; //hero position by default
  553. {
  554. if(casterStack != nullptr)
  555. {
  556. srccoord = CClickableHex::getXYUnitAnim(casterStack->getPosition(), casterStack, this);
  557. srccoord.x += 250;
  558. srccoord.y += 240;
  559. }
  560. }
  561. if(casterStack != nullptr && sc->activeCast)
  562. {
  563. //todo: custom cast animation for hero
  564. displaySpellCast(spellID, casterStack->getPosition());
  565. addNewAnim(new CCastAnimation(this, casterStack, sc->tile, curInt->cb->battleGetStackByPos(sc->tile)));
  566. }
  567. waitForAnims(); //wait for cast animation
  568. //playing projectile animation
  569. if (sc->tile.isValid())
  570. {
  571. Point destcoord = CClickableHex::getXYUnitAnim(sc->tile, curInt->cb->battleGetStackByPos(sc->tile), this); //position attacked by projectile
  572. destcoord.x += 250; destcoord.y += 240;
  573. //animation angle
  574. double angle = atan2(static_cast<double>(destcoord.x - srccoord.x), static_cast<double>(destcoord.y - srccoord.y));
  575. bool Vflip = (angle < 0);
  576. if (Vflip)
  577. angle = -angle;
  578. std::string animToDisplay = spell->animationInfo.selectProjectile(angle);
  579. if(!animToDisplay.empty())
  580. {
  581. //TODO: calculate inside CEffectAnimation
  582. std::shared_ptr<CAnimation> tmp = std::make_shared<CAnimation>(animToDisplay);
  583. tmp->load(0, 0);
  584. auto first = tmp->getImage(0, 0);
  585. //displaying animation
  586. double diffX = (destcoord.x - srccoord.x)*(destcoord.x - srccoord.x);
  587. double diffY = (destcoord.y - srccoord.y)*(destcoord.y - srccoord.y);
  588. double distance = sqrt(diffX + diffY);
  589. int steps = static_cast<int>(distance / AnimationControls::getSpellEffectSpeed() + 1);
  590. int dx = (destcoord.x - srccoord.x - first->width())/steps;
  591. int dy = (destcoord.y - srccoord.y - first->height())/steps;
  592. addNewAnim(new CEffectAnimation(this, animToDisplay, srccoord.x, srccoord.y, dx, dy, Vflip));
  593. }
  594. }
  595. waitForAnims(); //wait for projectile animation
  596. displaySpellHit(spellID, sc->tile);
  597. //queuing affect animation
  598. for(auto & elem : sc->affectedCres)
  599. {
  600. auto stack = curInt->cb->battleGetStackByID(elem, false);
  601. if(stack)
  602. displaySpellEffect(spellID, stack->getPosition());
  603. }
  604. //queuing additional animation
  605. for(auto & elem : sc->customEffects)
  606. {
  607. auto stack = curInt->cb->battleGetStackByID(elem.stack, false);
  608. if(stack)
  609. displayEffect(elem.effect, stack->getPosition());
  610. }
  611. waitForAnims();
  612. //mana absorption
  613. if (sc->manaGained > 0)
  614. {
  615. Point leftHero = Point(15, 30) + pos;
  616. Point rightHero = Point(755, 30) + pos;
  617. addNewAnim(new CEffectAnimation(this, sc->side ? "SP07_A.DEF" : "SP07_B.DEF", leftHero.x, leftHero.y, 0, 0, false));
  618. addNewAnim(new CEffectAnimation(this, sc->side ? "SP07_B.DEF" : "SP07_A.DEF", rightHero.x, rightHero.y, 0, 0, false));
  619. }
  620. }
  621. void CBattleInterface::battleStacksEffectsSet(const SetStackEffect & sse)
  622. {
  623. if(activeStack != nullptr)
  624. fieldController->redrawBackgroundWithHexes(activeStack);
  625. }
  626. void CBattleInterface::setHeroAnimation(ui8 side, int phase)
  627. {
  628. if(side == BattleSide::ATTACKER)
  629. {
  630. if(attackingHero)
  631. attackingHero->setPhase(phase);
  632. }
  633. else
  634. {
  635. if(defendingHero)
  636. defendingHero->setPhase(phase);
  637. }
  638. }
  639. void CBattleInterface::castThisSpell(SpellID spellID)
  640. {
  641. spellToCast = std::make_shared<BattleAction>();
  642. spellToCast->actionType = EActionType::HERO_SPELL;
  643. spellToCast->actionSubtype = spellID; //spell number
  644. spellToCast->stackNumber = (attackingHeroInstance->tempOwner == curInt->playerID) ? -1 : -2;
  645. spellToCast->side = defendingHeroInstance ? (curInt->playerID == defendingHeroInstance->tempOwner) : false;
  646. spellDestSelectMode = true;
  647. creatureCasting = false;
  648. //choosing possible targets
  649. const CGHeroInstance *castingHero = (attackingHeroInstance->tempOwner == curInt->playerID) ? attackingHeroInstance : defendingHeroInstance;
  650. assert(castingHero); // code below assumes non-null hero
  651. sp = spellID.toSpell();
  652. PossiblePlayerBattleAction spellSelMode = curInt->cb->getCasterAction(sp, castingHero, spells::Mode::HERO);
  653. if (spellSelMode == PossiblePlayerBattleAction::NO_LOCATION) //user does not have to select location
  654. {
  655. spellToCast->aimToHex(BattleHex::INVALID);
  656. curInt->cb->battleMakeAction(spellToCast.get());
  657. endCastingSpell();
  658. }
  659. else
  660. {
  661. possibleActions.clear();
  662. possibleActions.push_back (spellSelMode); //only this one action can be performed at the moment
  663. GH.fakeMouseMove();//update cursor
  664. }
  665. }
  666. void CBattleInterface::displayBattleLog(const std::vector<MetaString> & battleLog)
  667. {
  668. for(const auto & line : battleLog)
  669. {
  670. std::string formatted = line.toString();
  671. boost::algorithm::trim(formatted);
  672. if(!controlPanel->console->addText(formatted))
  673. logGlobal->warn("Too long battle log line");
  674. }
  675. }
  676. void CBattleInterface::displayCustomEffects(const std::vector<CustomEffectInfo> & customEffects)
  677. {
  678. for(const CustomEffectInfo & one : customEffects)
  679. {
  680. if(one.sound != 0)
  681. CCS->soundh->playSound(soundBase::soundID(one.sound));
  682. const CStack * s = curInt->cb->battleGetStackByID(one.stack, false);
  683. if(s && one.effect != 0)
  684. displayEffect(one.effect, s->getPosition());
  685. }
  686. }
  687. void CBattleInterface::displayEffect(ui32 effect, BattleHex destTile)
  688. {
  689. std::string customAnim = graphics->battleACToDef[effect][0];
  690. addNewAnim(new CEffectAnimation(this, customAnim, destTile));
  691. }
  692. void CBattleInterface::displaySpellAnimationQueue(const CSpell::TAnimationQueue & q, BattleHex destinationTile)
  693. {
  694. for(const CSpell::TAnimation & animation : q)
  695. {
  696. if(animation.pause > 0)
  697. addNewAnim(new CDummyAnimation(this, animation.pause));
  698. else
  699. addNewAnim(new CEffectAnimation(this, animation.resourceName, destinationTile, false, animation.verticalPosition == VerticalPosition::BOTTOM));
  700. }
  701. }
  702. void CBattleInterface::displaySpellCast(SpellID spellID, BattleHex destinationTile)
  703. {
  704. const CSpell * spell = spellID.toSpell();
  705. if(spell)
  706. displaySpellAnimationQueue(spell->animationInfo.cast, destinationTile);
  707. }
  708. void CBattleInterface::displaySpellEffect(SpellID spellID, BattleHex destinationTile)
  709. {
  710. const CSpell *spell = spellID.toSpell();
  711. if(spell)
  712. displaySpellAnimationQueue(spell->animationInfo.affect, destinationTile);
  713. }
  714. void CBattleInterface::displaySpellHit(SpellID spellID, BattleHex destinationTile)
  715. {
  716. const CSpell * spell = spellID.toSpell();
  717. if(spell)
  718. displaySpellAnimationQueue(spell->animationInfo.hit, destinationTile);
  719. }
  720. void CBattleInterface::battleTriggerEffect(const BattleTriggerEffect & bte)
  721. {
  722. const CStack * stack = curInt->cb->battleGetStackByID(bte.stackID);
  723. if(!stack)
  724. {
  725. logGlobal->error("Invalid stack ID %d", bte.stackID);
  726. return;
  727. }
  728. //don't show animation when no HP is regenerated
  729. switch(bte.effect)
  730. {
  731. //TODO: move to bonus type handler
  732. case Bonus::HP_REGENERATION:
  733. displayEffect(74, stack->getPosition());
  734. CCS->soundh->playSound(soundBase::REGENER);
  735. break;
  736. case Bonus::MANA_DRAIN:
  737. displayEffect(77, stack->getPosition());
  738. CCS->soundh->playSound(soundBase::MANADRAI);
  739. break;
  740. case Bonus::POISON:
  741. displayEffect(67, stack->getPosition());
  742. CCS->soundh->playSound(soundBase::POISON);
  743. break;
  744. case Bonus::FEAR:
  745. displayEffect(15, stack->getPosition());
  746. CCS->soundh->playSound(soundBase::FEAR);
  747. break;
  748. case Bonus::MORALE:
  749. {
  750. std::string hlp = CGI->generaltexth->allTexts[33];
  751. boost::algorithm::replace_first(hlp,"%s",(stack->getName()));
  752. displayEffect(20,stack->getPosition());
  753. CCS->soundh->playSound(soundBase::GOODMRLE);
  754. controlPanel->console->addText(hlp);
  755. break;
  756. }
  757. default:
  758. return;
  759. }
  760. //waitForAnims(); //fixme: freezes game :?
  761. }
  762. void CBattleInterface::setAnimSpeed(int set)
  763. {
  764. Settings speed = settings.write["battle"]["animationSpeed"];
  765. speed->Float() = float(set) / 100;
  766. }
  767. int CBattleInterface::getAnimSpeed() const
  768. {
  769. if(settings["session"]["spectate"].Bool() && !settings["session"]["spectate-battle-speed"].isNull())
  770. return static_cast<int>(vstd::round(settings["session"]["spectate-battle-speed"].Float() *100));
  771. return static_cast<int>(vstd::round(settings["battle"]["animationSpeed"].Float() *100));
  772. }
  773. CPlayerInterface *CBattleInterface::getCurrentPlayerInterface() const
  774. {
  775. return curInt.get();
  776. }
  777. bool CBattleInterface::shouldRotate(const CStack * stack, const BattleHex & oldPos, const BattleHex & nextHex)
  778. {
  779. Point begPosition = CClickableHex::getXYUnitAnim(oldPos,stack, this);
  780. Point endPosition = CClickableHex::getXYUnitAnim(nextHex, stack, this);
  781. if((begPosition.x > endPosition.x) && creDir[stack->ID])
  782. return true;
  783. else if((begPosition.x < endPosition.x) && !creDir[stack->ID])
  784. return true;
  785. return false;
  786. }
  787. void CBattleInterface::setActiveStack(const CStack *stack)
  788. {
  789. if (activeStack) // update UI
  790. creAnims[activeStack->ID]->setBorderColor(AnimationControls::getNoBorder());
  791. activeStack = stack;
  792. if (activeStack) // update UI
  793. creAnims[activeStack->ID]->setBorderColor(AnimationControls::getGoldBorder());
  794. controlPanel->blockUI(activeStack == nullptr);
  795. }
  796. void CBattleInterface::setHoveredStack(const CStack *stack)
  797. {
  798. if (mouseHoveredStack)
  799. creAnims[mouseHoveredStack->ID]->setBorderColor(AnimationControls::getNoBorder());
  800. // stack must be alive and not active (which uses gold border instead)
  801. if (stack && stack->alive() && stack != activeStack)
  802. {
  803. mouseHoveredStack = stack;
  804. if (mouseHoveredStack)
  805. {
  806. creAnims[mouseHoveredStack->ID]->setBorderColor(AnimationControls::getBlueBorder());
  807. if (creAnims[mouseHoveredStack->ID]->framesInGroup(CCreatureAnim::MOUSEON) > 0)
  808. creAnims[mouseHoveredStack->ID]->playOnce(CCreatureAnim::MOUSEON);
  809. }
  810. }
  811. else
  812. mouseHoveredStack = nullptr;
  813. }
  814. void CBattleInterface::activateStack()
  815. {
  816. if(!battleActionsStarted)
  817. return; //"show" function should re-call this function
  818. myTurn = true;
  819. if (!!attackerInt && defenderInt) //hotseat -> need to pick which interface "takes over" as active
  820. curInt = attackerInt->playerID == stackToActivate->owner ? attackerInt : defenderInt;
  821. setActiveStack(stackToActivate);
  822. stackToActivate = nullptr;
  823. const CStack * s = activeStack;
  824. if(!s)
  825. return;
  826. queue->update();
  827. fieldController->redrawBackgroundWithHexes(activeStack);
  828. //set casting flag to true if creature can use it to not check it every time
  829. const auto spellcaster = s->getBonusLocalFirst(Selector::type()(Bonus::SPELLCASTER)),
  830. randomSpellcaster = s->getBonusLocalFirst(Selector::type()(Bonus::RANDOM_SPELLCASTER));
  831. if(s->canCast() && (spellcaster || randomSpellcaster))
  832. {
  833. stackCanCastSpell = true;
  834. if(randomSpellcaster)
  835. creatureSpellToCast = -1; //spell will be set later on cast
  836. else
  837. creatureSpellToCast = curInt->cb->battleGetRandomStackSpell(CRandomGenerator::getDefault(), s, CBattleInfoCallback::RANDOM_AIMED); //faerie dragon can cast only one spell until their next move
  838. //TODO: what if creature can cast BOTH random genie spell and aimed spell?
  839. //TODO: faerie dragon type spell should be selected by server
  840. }
  841. else
  842. {
  843. stackCanCastSpell = false;
  844. creatureSpellToCast = -1;
  845. }
  846. possibleActions = getPossibleActionsForStack(s);
  847. GH.fakeMouseMove();
  848. }
  849. void CBattleInterface::endCastingSpell()
  850. {
  851. if(spellDestSelectMode)
  852. {
  853. spellToCast.reset();
  854. sp = nullptr;
  855. spellDestSelectMode = false;
  856. CCS->curh->changeGraphic(ECursor::COMBAT, ECursor::COMBAT_POINTER);
  857. if(activeStack)
  858. {
  859. possibleActions = getPossibleActionsForStack(activeStack); //restore actions after they were cleared
  860. myTurn = true;
  861. }
  862. }
  863. else
  864. {
  865. if(activeStack)
  866. {
  867. possibleActions = getPossibleActionsForStack(activeStack);
  868. GH.fakeMouseMove();
  869. }
  870. }
  871. }
  872. void CBattleInterface::enterCreatureCastingMode()
  873. {
  874. //silently check for possible errors
  875. if (!myTurn)
  876. return;
  877. if (tacticsMode)
  878. return;
  879. //hero is casting a spell
  880. if (spellDestSelectMode)
  881. return;
  882. if (!activeStack)
  883. return;
  884. if (!stackCanCastSpell)
  885. return;
  886. //random spellcaster
  887. if (creatureSpellToCast == -1)
  888. return;
  889. if (vstd::contains(possibleActions, PossiblePlayerBattleAction::NO_LOCATION))
  890. {
  891. const spells::Caster * caster = activeStack;
  892. const CSpell * spell = SpellID(creatureSpellToCast).toSpell();
  893. spells::Target target;
  894. target.emplace_back();
  895. spells::BattleCast cast(curInt->cb.get(), caster, spells::Mode::CREATURE_ACTIVE, spell);
  896. auto m = spell->battleMechanics(&cast);
  897. spells::detail::ProblemImpl ignored;
  898. const bool isCastingPossible = m->canBeCastAt(target, ignored);
  899. if (isCastingPossible)
  900. {
  901. myTurn = false;
  902. giveCommand(EActionType::MONSTER_SPELL, BattleHex::INVALID, creatureSpellToCast);
  903. selectedStack = nullptr;
  904. CCS->curh->changeGraphic(ECursor::COMBAT, ECursor::COMBAT_POINTER);
  905. }
  906. }
  907. else
  908. {
  909. possibleActions = getPossibleActionsForStack(activeStack);
  910. auto actionFilterPredicate = [](const PossiblePlayerBattleAction x)
  911. {
  912. return (x != PossiblePlayerBattleAction::ANY_LOCATION) && (x != PossiblePlayerBattleAction::NO_LOCATION) &&
  913. (x != PossiblePlayerBattleAction::FREE_LOCATION) && (x != PossiblePlayerBattleAction::AIMED_SPELL_CREATURE) &&
  914. (x != PossiblePlayerBattleAction::OBSTACLE);
  915. };
  916. vstd::erase_if(possibleActions, actionFilterPredicate);
  917. GH.fakeMouseMove();
  918. }
  919. }
  920. std::vector<PossiblePlayerBattleAction> CBattleInterface::getPossibleActionsForStack(const CStack *stack)
  921. {
  922. BattleClientInterfaceData data; //hard to get rid of these things so for now they're required data to pass
  923. data.creatureSpellToCast = creatureSpellToCast;
  924. data.tacticsMode = tacticsMode;
  925. auto allActions = curInt->cb->getClientActionsForStack(stack, data);
  926. return std::vector<PossiblePlayerBattleAction>(allActions);
  927. }
  928. void CBattleInterface::reorderPossibleActionsPriority(const CStack * stack, MouseHoveredHexContext context)
  929. {
  930. if(tacticsMode || possibleActions.empty()) return; //this function is not supposed to be called in tactics mode or before getPossibleActionsForStack
  931. auto assignPriority = [&](PossiblePlayerBattleAction const & item) -> uint8_t //large lambda assigning priority which would have to be part of possibleActions without it
  932. {
  933. switch(item)
  934. {
  935. case PossiblePlayerBattleAction::AIMED_SPELL_CREATURE:
  936. case PossiblePlayerBattleAction::ANY_LOCATION:
  937. case PossiblePlayerBattleAction::NO_LOCATION:
  938. case PossiblePlayerBattleAction::FREE_LOCATION:
  939. case PossiblePlayerBattleAction::OBSTACLE:
  940. if(!stack->hasBonusOfType(Bonus::NO_SPELLCAST_BY_DEFAULT) && context == MouseHoveredHexContext::OCCUPIED_HEX)
  941. return 1;
  942. else
  943. return 100;//bottom priority
  944. break;
  945. case PossiblePlayerBattleAction::RANDOM_GENIE_SPELL:
  946. return 2; break;
  947. case PossiblePlayerBattleAction::RISE_DEMONS:
  948. return 3; break;
  949. case PossiblePlayerBattleAction::SHOOT:
  950. return 4; break;
  951. case PossiblePlayerBattleAction::ATTACK_AND_RETURN:
  952. return 5; break;
  953. case PossiblePlayerBattleAction::ATTACK:
  954. return 6; break;
  955. case PossiblePlayerBattleAction::WALK_AND_ATTACK:
  956. return 7; break;
  957. case PossiblePlayerBattleAction::MOVE_STACK:
  958. return 8; break;
  959. case PossiblePlayerBattleAction::CATAPULT:
  960. return 9; break;
  961. case PossiblePlayerBattleAction::HEAL:
  962. return 10; break;
  963. default:
  964. return 200; break;
  965. }
  966. };
  967. auto comparer = [&](PossiblePlayerBattleAction const & lhs, PossiblePlayerBattleAction const & rhs)
  968. {
  969. return assignPriority(lhs) > assignPriority(rhs);
  970. };
  971. std::make_heap(possibleActions.begin(), possibleActions.end(), comparer);
  972. }
  973. void CBattleInterface::endAction(const BattleAction* action)
  974. {
  975. const CStack *stack = curInt->cb->battleGetStackByID(action->stackNumber);
  976. if(action->actionType == EActionType::HERO_SPELL)
  977. setHeroAnimation(action->side, 0);
  978. //check if we should reverse stacks
  979. //for some strange reason, it's not enough
  980. TStacks stacks = curInt->cb->battleGetStacks(CBattleCallback::MINE_AND_ENEMY);
  981. for (const CStack *s : stacks)
  982. {
  983. if (s && creDir[s->ID] != (s->side == BattleSide::ATTACKER) && s->alive()
  984. && creAnims[s->ID]->isIdle())
  985. {
  986. addNewAnim(new CReverseAnimation(this, s, s->getPosition(), false));
  987. }
  988. }
  989. queue->update();
  990. if (tacticsMode) //stack ended movement in tactics phase -> select the next one
  991. tacticNextStack(stack);
  992. 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
  993. fieldController->redrawBackgroundWithHexes(activeStack);
  994. if (activeStack && !animsAreDisplayed.get() && pendingAnims.empty() && !active)
  995. {
  996. logGlobal->warn("Something wrong... interface was deactivated but there is no animation. Reactivating...");
  997. controlPanel->blockUI(false);
  998. }
  999. else
  1000. {
  1001. // block UI if no active stack (e.g. enemy turn);
  1002. controlPanel->blockUI(activeStack == nullptr);
  1003. }
  1004. }
  1005. void CBattleInterface::hideQueue()
  1006. {
  1007. Settings showQueue = settings.write["battle"]["showQueue"];
  1008. showQueue->Bool() = false;
  1009. queue->deactivate();
  1010. if (!queue->embedded)
  1011. {
  1012. moveBy(Point(0, -queue->pos.h / 2));
  1013. GH.totalRedraw();
  1014. }
  1015. }
  1016. void CBattleInterface::showQueue()
  1017. {
  1018. Settings showQueue = settings.write["battle"]["showQueue"];
  1019. showQueue->Bool() = true;
  1020. queue->activate();
  1021. if (!queue->embedded)
  1022. {
  1023. moveBy(Point(0, +queue->pos.h / 2));
  1024. GH.totalRedraw();
  1025. }
  1026. }
  1027. void CBattleInterface::startAction(const BattleAction* action)
  1028. {
  1029. //setActiveStack(nullptr);
  1030. setHoveredStack(nullptr);
  1031. controlPanel->blockUI(true);
  1032. if(action->actionType == EActionType::END_TACTIC_PHASE)
  1033. {
  1034. controlPanel->tacticPhaseEnded();
  1035. return;
  1036. }
  1037. const CStack *stack = curInt->cb->battleGetStackByID(action->stackNumber);
  1038. if (stack)
  1039. {
  1040. queue->update();
  1041. }
  1042. else
  1043. {
  1044. assert(action->actionType == EActionType::HERO_SPELL); //only cast spell is valid action without acting stack number
  1045. }
  1046. auto actionTarget = action->getTarget(curInt->cb.get());
  1047. if(action->actionType == EActionType::WALK
  1048. || (action->actionType == EActionType::WALK_AND_ATTACK && actionTarget.at(0).hexValue != stack->getPosition()))
  1049. {
  1050. assert(stack);
  1051. moveStarted = true;
  1052. if (creAnims[action->stackNumber]->framesInGroup(CCreatureAnim::MOVE_START))
  1053. {
  1054. pendingAnims.push_back(std::make_pair(new CMovementStartAnimation(this, stack), false));
  1055. }
  1056. if(shouldRotate(stack, stack->getPosition(), actionTarget.at(0).hexValue))
  1057. pendingAnims.push_back(std::make_pair(new CReverseAnimation(this, stack, stack->getPosition(), true), false));
  1058. }
  1059. redraw(); // redraw after deactivation, including proper handling of hovered hexes
  1060. if(action->actionType == EActionType::HERO_SPELL) //when hero casts spell
  1061. {
  1062. setHeroAnimation(action->side, 4);
  1063. return;
  1064. }
  1065. if (!stack)
  1066. {
  1067. logGlobal->error("Something wrong with stackNumber in actionStarted. Stack number: %d", action->stackNumber);
  1068. return;
  1069. }
  1070. int txtid = 0;
  1071. switch(action->actionType)
  1072. {
  1073. case EActionType::WAIT:
  1074. txtid = 136;
  1075. break;
  1076. case EActionType::BAD_MORALE:
  1077. txtid = -34; //negative -> no separate singular/plural form
  1078. displayEffect(30, stack->getPosition());
  1079. CCS->soundh->playSound(soundBase::BADMRLE);
  1080. break;
  1081. }
  1082. if(txtid != 0)
  1083. controlPanel->console->addText(stack->formatGeneralMessage(txtid));
  1084. //displaying special abilities
  1085. switch(action->actionType)
  1086. {
  1087. case EActionType::STACK_HEAL:
  1088. displayEffect(74, actionTarget.at(0).hexValue);
  1089. CCS->soundh->playSound(soundBase::REGENER);
  1090. break;
  1091. }
  1092. }
  1093. void CBattleInterface::waitForAnims()
  1094. {
  1095. auto unlockPim = vstd::makeUnlockGuard(*CPlayerInterface::pim);
  1096. animsAreDisplayed.waitWhileTrue();
  1097. }
  1098. void CBattleInterface::tacticPhaseEnd()
  1099. {
  1100. setActiveStack(nullptr);
  1101. controlPanel->blockUI(true);
  1102. tacticsMode = false;
  1103. }
  1104. static bool immobile(const CStack *s)
  1105. {
  1106. return !s->Speed(0, true); //should bound stacks be immobile?
  1107. }
  1108. void CBattleInterface::tacticNextStack(const CStack * current)
  1109. {
  1110. if (!current)
  1111. current = activeStack;
  1112. //no switching stacks when the current one is moving
  1113. waitForAnims();
  1114. TStacks stacksOfMine = tacticianInterface->cb->battleGetStacks(CBattleCallback::ONLY_MINE);
  1115. vstd::erase_if (stacksOfMine, &immobile);
  1116. if (stacksOfMine.empty())
  1117. {
  1118. tacticPhaseEnd();
  1119. return;
  1120. }
  1121. auto it = vstd::find(stacksOfMine, current);
  1122. if (it != stacksOfMine.end() && ++it != stacksOfMine.end())
  1123. stackActivated(*it);
  1124. else
  1125. stackActivated(stacksOfMine.front());
  1126. }
  1127. std::string formatDmgRange(std::pair<ui32, ui32> dmgRange)
  1128. {
  1129. if (dmgRange.first != dmgRange.second)
  1130. return (boost::format("%d - %d") % dmgRange.first % dmgRange.second).str();
  1131. else
  1132. return (boost::format("%d") % dmgRange.first).str();
  1133. }
  1134. bool CBattleInterface::canStackMoveHere(const CStack * activeStack, BattleHex myNumber)
  1135. {
  1136. std::vector<BattleHex> acc = curInt->cb->battleGetAvailableHexes(activeStack);
  1137. BattleHex shiftedDest = myNumber.cloneInDirection(activeStack->destShiftDir(), false);
  1138. if (vstd::contains(acc, myNumber))
  1139. return true;
  1140. else if (activeStack->doubleWide() && vstd::contains(acc, shiftedDest))
  1141. return true;
  1142. else
  1143. return false;
  1144. }
  1145. void CBattleInterface::handleHex(BattleHex myNumber, int eventType)
  1146. {
  1147. if (!myTurn || !battleActionsStarted) //we are not permit to do anything
  1148. return;
  1149. // This function handles mouse move over hexes and l-clicking on them.
  1150. // First we decide what happens if player clicks on this hex and set appropriately
  1151. // consoleMsg, cursorFrame/Type and prepare lambda realizeAction.
  1152. //
  1153. // Then, depending whether it was hover/click we either call the action or set tooltip/cursor.
  1154. //used when hovering -> tooltip message and cursor to be set
  1155. std::string consoleMsg;
  1156. bool setCursor = true; //if we want to suppress setting cursor
  1157. ECursor::ECursorTypes cursorType = ECursor::COMBAT;
  1158. int cursorFrame = ECursor::COMBAT_POINTER; //TODO: is this line used?
  1159. //used when l-clicking -> action to be called upon the click
  1160. std::function<void()> realizeAction;
  1161. //Get stack on the hex - first try to grab the alive one, if not found -> allow dead stacks.
  1162. const CStack * shere = curInt->cb->battleGetStackByPos(myNumber, true);
  1163. if(!shere)
  1164. shere = curInt->cb->battleGetStackByPos(myNumber, false);
  1165. if(!activeStack)
  1166. return;
  1167. bool ourStack = false;
  1168. if (shere)
  1169. ourStack = shere->owner == curInt->playerID;
  1170. //stack changed, update selection border
  1171. if (shere != mouseHoveredStack)
  1172. {
  1173. setHoveredStack(shere);
  1174. }
  1175. localActions.clear();
  1176. illegalActions.clear();
  1177. reorderPossibleActionsPriority(activeStack, shere ? MouseHoveredHexContext::OCCUPIED_HEX : MouseHoveredHexContext::UNOCCUPIED_HEX);
  1178. const bool forcedAction = possibleActions.size() == 1;
  1179. for (PossiblePlayerBattleAction action : possibleActions)
  1180. {
  1181. bool legalAction = false; //this action is legal and can be performed
  1182. bool notLegal = false; //this action is not legal and should display message
  1183. switch (action)
  1184. {
  1185. case PossiblePlayerBattleAction::CHOOSE_TACTICS_STACK:
  1186. if (shere && ourStack)
  1187. legalAction = true;
  1188. break;
  1189. case PossiblePlayerBattleAction::MOVE_TACTICS:
  1190. case PossiblePlayerBattleAction::MOVE_STACK:
  1191. {
  1192. if (!(shere && shere->alive())) //we can walk on dead stacks
  1193. {
  1194. if(canStackMoveHere(activeStack, myNumber))
  1195. legalAction = true;
  1196. }
  1197. break;
  1198. }
  1199. case PossiblePlayerBattleAction::ATTACK:
  1200. case PossiblePlayerBattleAction::WALK_AND_ATTACK:
  1201. case PossiblePlayerBattleAction::ATTACK_AND_RETURN:
  1202. {
  1203. if(curInt->cb->battleCanAttack(activeStack, shere, myNumber))
  1204. {
  1205. if (fieldController->isTileAttackable(myNumber)) // move isTileAttackable to be part of battleCanAttack?
  1206. {
  1207. fieldController->setBattleCursor(myNumber); // temporary - needed for following function :(
  1208. BattleHex attackFromHex = fieldController->fromWhichHexAttack(myNumber);
  1209. if (attackFromHex >= 0) //we can be in this line when unreachable creature is L - clicked (as of revision 1308)
  1210. legalAction = true;
  1211. }
  1212. }
  1213. }
  1214. break;
  1215. case PossiblePlayerBattleAction::SHOOT:
  1216. if(curInt->cb->battleCanShoot(activeStack, myNumber))
  1217. legalAction = true;
  1218. break;
  1219. case PossiblePlayerBattleAction::ANY_LOCATION:
  1220. if (myNumber > -1) //TODO: this should be checked for all actions
  1221. {
  1222. if(isCastingPossibleHere(activeStack, shere, myNumber))
  1223. legalAction = true;
  1224. }
  1225. break;
  1226. case PossiblePlayerBattleAction::AIMED_SPELL_CREATURE:
  1227. if(shere && isCastingPossibleHere(activeStack, shere, myNumber))
  1228. legalAction = true;
  1229. break;
  1230. case PossiblePlayerBattleAction::RANDOM_GENIE_SPELL:
  1231. {
  1232. if(shere && ourStack && shere != activeStack && shere->alive()) //only positive spells for other allied creatures
  1233. {
  1234. int spellID = curInt->cb->battleGetRandomStackSpell(CRandomGenerator::getDefault(), shere, CBattleInfoCallback::RANDOM_GENIE);
  1235. if(spellID > -1)
  1236. {
  1237. legalAction = true;
  1238. }
  1239. }
  1240. }
  1241. break;
  1242. case PossiblePlayerBattleAction::OBSTACLE:
  1243. if(isCastingPossibleHere(activeStack, shere, myNumber))
  1244. legalAction = true;
  1245. break;
  1246. case PossiblePlayerBattleAction::TELEPORT:
  1247. {
  1248. //todo: move to mechanics
  1249. ui8 skill = 0;
  1250. if (creatureCasting)
  1251. skill = activeStack->getEffectLevel(SpellID(SpellID::TELEPORT).toSpell());
  1252. else
  1253. skill = getActiveHero()->getEffectLevel(SpellID(SpellID::TELEPORT).toSpell());
  1254. //TODO: explicitely save power, skill
  1255. if (curInt->cb->battleCanTeleportTo(selectedStack, myNumber, skill))
  1256. legalAction = true;
  1257. else
  1258. notLegal = true;
  1259. }
  1260. break;
  1261. case PossiblePlayerBattleAction::SACRIFICE: //choose our living stack to sacrifice
  1262. if (shere && shere != selectedStack && ourStack && shere->alive())
  1263. legalAction = true;
  1264. else
  1265. notLegal = true;
  1266. break;
  1267. case PossiblePlayerBattleAction::FREE_LOCATION:
  1268. legalAction = true;
  1269. if(!isCastingPossibleHere(activeStack, shere, myNumber))
  1270. {
  1271. legalAction = false;
  1272. notLegal = true;
  1273. }
  1274. break;
  1275. case PossiblePlayerBattleAction::CATAPULT:
  1276. if (siegeController && siegeController->isCatapultAttackable(myNumber))
  1277. legalAction = true;
  1278. break;
  1279. case PossiblePlayerBattleAction::HEAL:
  1280. if (shere && ourStack && shere->canBeHealed())
  1281. legalAction = true;
  1282. break;
  1283. case PossiblePlayerBattleAction::RISE_DEMONS:
  1284. if (shere && ourStack && !shere->alive())
  1285. {
  1286. if (!(shere->hasBonusOfType(Bonus::UNDEAD)
  1287. || shere->hasBonusOfType(Bonus::NON_LIVING)
  1288. || shere->hasBonusOfType(Bonus::GARGOYLE)
  1289. || shere->summoned
  1290. || shere->isClone()
  1291. || shere->hasBonusOfType(Bonus::SIEGE_WEAPON)
  1292. ))
  1293. legalAction = true;
  1294. }
  1295. break;
  1296. }
  1297. if (legalAction)
  1298. localActions.push_back (action);
  1299. else if (notLegal || forcedAction)
  1300. illegalActions.push_back (action);
  1301. }
  1302. illegalAction = PossiblePlayerBattleAction::INVALID; //clear it in first place
  1303. if (vstd::contains(localActions, selectedAction)) //try to use last selected action by default
  1304. currentAction = selectedAction;
  1305. else if (localActions.size()) //if not possible, select first available action (they are sorted by suggested priority)
  1306. currentAction = localActions.front();
  1307. else //no legal action possible
  1308. {
  1309. currentAction = PossiblePlayerBattleAction::INVALID; //don't allow to do anything
  1310. if (vstd::contains(illegalActions, selectedAction))
  1311. illegalAction = selectedAction;
  1312. else if (illegalActions.size())
  1313. illegalAction = illegalActions.front();
  1314. else if (shere && ourStack && shere->alive()) //last possibility - display info about our creature
  1315. {
  1316. currentAction = PossiblePlayerBattleAction::CREATURE_INFO;
  1317. }
  1318. else
  1319. illegalAction = PossiblePlayerBattleAction::INVALID; //we should never be here
  1320. }
  1321. bool isCastingPossible = false;
  1322. bool secondaryTarget = false;
  1323. if (currentAction > PossiblePlayerBattleAction::INVALID)
  1324. {
  1325. switch (currentAction) //display console message, realize selected action
  1326. {
  1327. case PossiblePlayerBattleAction::CHOOSE_TACTICS_STACK:
  1328. consoleMsg = (boost::format(CGI->generaltexth->allTexts[481]) % shere->getName()).str(); //Select %s
  1329. realizeAction = [=](){ stackActivated(shere); };
  1330. break;
  1331. case PossiblePlayerBattleAction::MOVE_TACTICS:
  1332. case PossiblePlayerBattleAction::MOVE_STACK:
  1333. if (activeStack->hasBonusOfType(Bonus::FLYING))
  1334. {
  1335. cursorFrame = ECursor::COMBAT_FLY;
  1336. consoleMsg = (boost::format(CGI->generaltexth->allTexts[295]) % activeStack->getName()).str(); //Fly %s here
  1337. }
  1338. else
  1339. {
  1340. cursorFrame = ECursor::COMBAT_MOVE;
  1341. consoleMsg = (boost::format(CGI->generaltexth->allTexts[294]) % activeStack->getName()).str(); //Move %s here
  1342. }
  1343. realizeAction = [=]()
  1344. {
  1345. if(activeStack->doubleWide())
  1346. {
  1347. std::vector<BattleHex> acc = curInt->cb->battleGetAvailableHexes(activeStack);
  1348. BattleHex shiftedDest = myNumber.cloneInDirection(activeStack->destShiftDir(), false);
  1349. if(vstd::contains(acc, myNumber))
  1350. giveCommand(EActionType::WALK, myNumber);
  1351. else if(vstd::contains(acc, shiftedDest))
  1352. giveCommand(EActionType::WALK, shiftedDest);
  1353. }
  1354. else
  1355. {
  1356. giveCommand(EActionType::WALK, myNumber);
  1357. }
  1358. };
  1359. break;
  1360. case PossiblePlayerBattleAction::ATTACK:
  1361. case PossiblePlayerBattleAction::WALK_AND_ATTACK:
  1362. case PossiblePlayerBattleAction::ATTACK_AND_RETURN: //TODO: allow to disable return
  1363. {
  1364. fieldController->setBattleCursor(myNumber); //handle direction of cursor and attackable tile
  1365. setCursor = false; //don't overwrite settings from the call above //TODO: what does it mean?
  1366. bool returnAfterAttack = currentAction == PossiblePlayerBattleAction::ATTACK_AND_RETURN;
  1367. realizeAction = [=]()
  1368. {
  1369. BattleHex attackFromHex = fieldController->fromWhichHexAttack(myNumber);
  1370. if(attackFromHex.isValid()) //we can be in this line when unreachable creature is L - clicked (as of revision 1308)
  1371. {
  1372. auto command = new BattleAction(BattleAction::makeMeleeAttack(activeStack, myNumber, attackFromHex, returnAfterAttack));
  1373. sendCommand(command, activeStack);
  1374. }
  1375. };
  1376. TDmgRange damage = curInt->cb->battleEstimateDamage(activeStack, shere);
  1377. std::string estDmgText = formatDmgRange(std::make_pair((ui32)damage.first, (ui32)damage.second)); //calculating estimated dmg
  1378. consoleMsg = (boost::format(CGI->generaltexth->allTexts[36]) % shere->getName() % estDmgText).str(); //Attack %s (%s damage)
  1379. }
  1380. break;
  1381. case PossiblePlayerBattleAction::SHOOT:
  1382. {
  1383. if (curInt->cb->battleHasShootingPenalty(activeStack, myNumber))
  1384. cursorFrame = ECursor::COMBAT_SHOOT_PENALTY;
  1385. else
  1386. cursorFrame = ECursor::COMBAT_SHOOT;
  1387. realizeAction = [=](){giveCommand(EActionType::SHOOT, myNumber);};
  1388. TDmgRange damage = curInt->cb->battleEstimateDamage(activeStack, shere);
  1389. std::string estDmgText = formatDmgRange(std::make_pair((ui32)damage.first, (ui32)damage.second)); //calculating estimated dmg
  1390. //printing - Shoot %s (%d shots left, %s damage)
  1391. consoleMsg = (boost::format(CGI->generaltexth->allTexts[296]) % shere->getName() % activeStack->shots.available() % estDmgText).str();
  1392. }
  1393. break;
  1394. case PossiblePlayerBattleAction::AIMED_SPELL_CREATURE:
  1395. sp = CGI->spellh->objects[creatureCasting ? creatureSpellToCast : spellToCast->actionSubtype]; //necessary if creature has random Genie spell at same time
  1396. consoleMsg = boost::str(boost::format(CGI->generaltexth->allTexts[27]) % sp->name % shere->getName()); //Cast %s on %s
  1397. switch (sp->id)
  1398. {
  1399. case SpellID::SACRIFICE:
  1400. case SpellID::TELEPORT:
  1401. selectedStack = shere; //remember first target
  1402. secondaryTarget = true;
  1403. break;
  1404. }
  1405. isCastingPossible = true;
  1406. break;
  1407. case PossiblePlayerBattleAction::ANY_LOCATION:
  1408. sp = CGI->spellh->objects[creatureCasting ? creatureSpellToCast : spellToCast->actionSubtype]; //necessary if creature has random Genie spell at same time
  1409. consoleMsg = boost::str(boost::format(CGI->generaltexth->allTexts[26]) % sp->name); //Cast %s
  1410. isCastingPossible = true;
  1411. break;
  1412. case PossiblePlayerBattleAction::RANDOM_GENIE_SPELL: //we assume that teleport / sacrifice will never be available as random spell
  1413. sp = nullptr;
  1414. consoleMsg = boost::str(boost::format(CGI->generaltexth->allTexts[301]) % shere->getName()); //Cast a spell on %
  1415. creatureCasting = true;
  1416. isCastingPossible = true;
  1417. break;
  1418. case PossiblePlayerBattleAction::TELEPORT:
  1419. consoleMsg = CGI->generaltexth->allTexts[25]; //Teleport Here
  1420. cursorFrame = ECursor::COMBAT_TELEPORT;
  1421. isCastingPossible = true;
  1422. break;
  1423. case PossiblePlayerBattleAction::OBSTACLE:
  1424. consoleMsg = CGI->generaltexth->allTexts[550];
  1425. //TODO: remove obstacle cursor
  1426. isCastingPossible = true;
  1427. break;
  1428. case PossiblePlayerBattleAction::SACRIFICE:
  1429. consoleMsg = (boost::format(CGI->generaltexth->allTexts[549]) % shere->getName()).str(); //sacrifice the %s
  1430. cursorFrame = ECursor::COMBAT_SACRIFICE;
  1431. isCastingPossible = true;
  1432. break;
  1433. case PossiblePlayerBattleAction::FREE_LOCATION:
  1434. consoleMsg = boost::str(boost::format(CGI->generaltexth->allTexts[26]) % sp->name); //Cast %s
  1435. isCastingPossible = true;
  1436. break;
  1437. case PossiblePlayerBattleAction::HEAL:
  1438. cursorFrame = ECursor::COMBAT_HEAL;
  1439. consoleMsg = (boost::format(CGI->generaltexth->allTexts[419]) % shere->getName()).str(); //Apply first aid to the %s
  1440. realizeAction = [=](){ giveCommand(EActionType::STACK_HEAL, myNumber); }; //command healing
  1441. break;
  1442. case PossiblePlayerBattleAction::RISE_DEMONS:
  1443. cursorType = ECursor::SPELLBOOK;
  1444. realizeAction = [=]()
  1445. {
  1446. giveCommand(EActionType::DAEMON_SUMMONING, myNumber);
  1447. };
  1448. break;
  1449. case PossiblePlayerBattleAction::CATAPULT:
  1450. cursorFrame = ECursor::COMBAT_SHOOT_CATAPULT;
  1451. realizeAction = [=](){ giveCommand(EActionType::CATAPULT, myNumber); };
  1452. break;
  1453. case PossiblePlayerBattleAction::CREATURE_INFO:
  1454. {
  1455. cursorFrame = ECursor::COMBAT_QUERY;
  1456. consoleMsg = (boost::format(CGI->generaltexth->allTexts[297]) % shere->getName()).str();
  1457. realizeAction = [=](){ GH.pushIntT<CStackWindow>(shere, false); };
  1458. break;
  1459. }
  1460. }
  1461. }
  1462. else //no possible valid action, display message
  1463. {
  1464. switch (illegalAction)
  1465. {
  1466. case PossiblePlayerBattleAction::AIMED_SPELL_CREATURE:
  1467. case PossiblePlayerBattleAction::RANDOM_GENIE_SPELL:
  1468. cursorFrame = ECursor::COMBAT_BLOCKED;
  1469. consoleMsg = CGI->generaltexth->allTexts[23];
  1470. break;
  1471. case PossiblePlayerBattleAction::TELEPORT:
  1472. cursorFrame = ECursor::COMBAT_BLOCKED;
  1473. consoleMsg = CGI->generaltexth->allTexts[24]; //Invalid Teleport Destination
  1474. break;
  1475. case PossiblePlayerBattleAction::SACRIFICE:
  1476. consoleMsg = CGI->generaltexth->allTexts[543]; //choose army to sacrifice
  1477. break;
  1478. case PossiblePlayerBattleAction::FREE_LOCATION:
  1479. cursorFrame = ECursor::COMBAT_BLOCKED;
  1480. consoleMsg = boost::str(boost::format(CGI->generaltexth->allTexts[181]) % sp->name); //No room to place %s here
  1481. break;
  1482. default:
  1483. if (myNumber == -1)
  1484. CCS->curh->changeGraphic(ECursor::COMBAT, ECursor::COMBAT_POINTER); //set neutral cursor over menu etc.
  1485. else
  1486. cursorFrame = ECursor::COMBAT_BLOCKED;
  1487. break;
  1488. }
  1489. }
  1490. if (isCastingPossible) //common part
  1491. {
  1492. switch (currentAction) //don't use that with teleport / sacrifice
  1493. {
  1494. case PossiblePlayerBattleAction::TELEPORT: //FIXME: more generic solution?
  1495. case PossiblePlayerBattleAction::SACRIFICE:
  1496. break;
  1497. default:
  1498. cursorType = ECursor::SPELLBOOK;
  1499. cursorFrame = 0;
  1500. if (consoleMsg.empty() && sp)
  1501. consoleMsg = boost::str(boost::format(CGI->generaltexth->allTexts[26]) % sp->name); //Cast %s
  1502. break;
  1503. }
  1504. realizeAction = [=]()
  1505. {
  1506. if(secondaryTarget) //select that target now
  1507. {
  1508. possibleActions.clear();
  1509. switch (sp->id.toEnum())
  1510. {
  1511. case SpellID::TELEPORT: //don't cast spell yet, only select target
  1512. spellToCast->aimToUnit(shere);
  1513. possibleActions.push_back(PossiblePlayerBattleAction::TELEPORT);
  1514. break;
  1515. case SpellID::SACRIFICE:
  1516. spellToCast->aimToHex(myNumber);
  1517. possibleActions.push_back(PossiblePlayerBattleAction::SACRIFICE);
  1518. break;
  1519. }
  1520. }
  1521. else
  1522. {
  1523. if (creatureCasting)
  1524. {
  1525. if (sp)
  1526. {
  1527. giveCommand(EActionType::MONSTER_SPELL, myNumber, creatureSpellToCast);
  1528. }
  1529. else //unknown random spell
  1530. {
  1531. giveCommand(EActionType::MONSTER_SPELL, myNumber);
  1532. }
  1533. }
  1534. else
  1535. {
  1536. assert(sp);
  1537. switch (sp->id.toEnum())
  1538. {
  1539. case SpellID::SACRIFICE:
  1540. spellToCast->aimToUnit(shere);//victim
  1541. break;
  1542. default:
  1543. spellToCast->aimToHex(myNumber);
  1544. break;
  1545. }
  1546. curInt->cb->battleMakeAction(spellToCast.get());
  1547. endCastingSpell();
  1548. }
  1549. selectedStack = nullptr;
  1550. }
  1551. };
  1552. }
  1553. {
  1554. if (eventType == MOVE)
  1555. {
  1556. if (setCursor)
  1557. CCS->curh->changeGraphic(cursorType, cursorFrame);
  1558. controlPanel->console->write(consoleMsg);
  1559. }
  1560. if (eventType == LCLICK && realizeAction)
  1561. {
  1562. //opening creature window shouldn't affect myTurn...
  1563. if ((currentAction != PossiblePlayerBattleAction::CREATURE_INFO) && !secondaryTarget)
  1564. {
  1565. myTurn = false; //tends to crash with empty calls
  1566. }
  1567. realizeAction();
  1568. if (!secondaryTarget) //do not replace teleport or sacrifice cursor
  1569. CCS->curh->changeGraphic(ECursor::COMBAT, ECursor::COMBAT_POINTER);
  1570. controlPanel->console->clear();
  1571. }
  1572. }
  1573. }
  1574. bool CBattleInterface::isCastingPossibleHere(const CStack *sactive, const CStack *shere, BattleHex myNumber)
  1575. {
  1576. creatureCasting = stackCanCastSpell && !spellDestSelectMode; //TODO: allow creatures to cast aimed spells
  1577. bool isCastingPossible = true;
  1578. int spellID = -1;
  1579. if (creatureCasting)
  1580. {
  1581. if (creatureSpellToCast > -1 && (shere != sactive)) //can't cast on itself
  1582. spellID = creatureSpellToCast; //TODO: merge with SpellTocast?
  1583. }
  1584. else //hero casting
  1585. {
  1586. spellID = spellToCast->actionSubtype;
  1587. }
  1588. sp = nullptr;
  1589. if (spellID >= 0)
  1590. sp = CGI->spellh->objects[spellID];
  1591. if (sp)
  1592. {
  1593. const spells::Caster *caster = creatureCasting ? static_cast<const spells::Caster *>(sactive) : static_cast<const spells::Caster *>(curInt->cb->battleGetMyHero());
  1594. if (caster == nullptr)
  1595. {
  1596. isCastingPossible = false;//just in case
  1597. }
  1598. else
  1599. {
  1600. const spells::Mode mode = creatureCasting ? spells::Mode::CREATURE_ACTIVE : spells::Mode::HERO;
  1601. spells::Target target;
  1602. target.emplace_back(myNumber);
  1603. spells::BattleCast cast(curInt->cb.get(), caster, mode, sp);
  1604. auto m = sp->battleMechanics(&cast);
  1605. spells::detail::ProblemImpl problem; //todo: display problem in status bar
  1606. isCastingPossible = m->canBeCastAt(target, problem);
  1607. }
  1608. }
  1609. else
  1610. isCastingPossible = false;
  1611. if (!myNumber.isAvailable() && !shere) //empty tile outside battlefield (or in the unavailable border column)
  1612. isCastingPossible = false;
  1613. return isCastingPossible;
  1614. }
  1615. void CBattleInterface::obstaclePlaced(const CObstacleInstance & oi)
  1616. {
  1617. obstacleController->obstaclePlaced(oi);
  1618. }
  1619. const CGHeroInstance *CBattleInterface::currentHero() const
  1620. {
  1621. if (attackingHeroInstance->tempOwner == curInt->playerID)
  1622. return attackingHeroInstance;
  1623. else
  1624. return defendingHeroInstance;
  1625. }
  1626. InfoAboutHero CBattleInterface::enemyHero() const
  1627. {
  1628. InfoAboutHero ret;
  1629. if (attackingHeroInstance->tempOwner == curInt->playerID)
  1630. curInt->cb->getHeroInfo(defendingHeroInstance, ret);
  1631. else
  1632. curInt->cb->getHeroInfo(attackingHeroInstance, ret);
  1633. return ret;
  1634. }
  1635. void CBattleInterface::requestAutofightingAIToTakeAction()
  1636. {
  1637. assert(curInt->isAutoFightOn);
  1638. boost::thread aiThread([&]()
  1639. {
  1640. auto ba = make_unique<BattleAction>(curInt->autofightingAI->activeStack(activeStack));
  1641. if(curInt->cb->battleIsFinished())
  1642. {
  1643. return; // battle finished with spellcast
  1644. }
  1645. if (curInt->isAutoFightOn)
  1646. {
  1647. if (tacticsMode)
  1648. {
  1649. // Always end tactics mode. Player interface is blocked currently, so it's not possible that
  1650. // the AI can take any action except end tactics phase (AI actions won't be triggered)
  1651. //TODO implement the possibility that the AI will be triggered for further actions
  1652. //TODO any solution to merge tactics phase & normal phase in the way it is handled by the player and battle interface?
  1653. setActiveStack(nullptr);
  1654. controlPanel->blockUI(true);
  1655. tacticsMode = false;
  1656. }
  1657. else
  1658. {
  1659. givenCommand.setn(ba.release());
  1660. }
  1661. }
  1662. else
  1663. {
  1664. boost::unique_lock<boost::recursive_mutex> un(*CPlayerInterface::pim);
  1665. activateStack();
  1666. }
  1667. });
  1668. aiThread.detach();
  1669. }
  1670. void CBattleInterface::showAll(SDL_Surface *to)
  1671. {
  1672. show(to);
  1673. }
  1674. void CBattleInterface::show(SDL_Surface *to)
  1675. {
  1676. assert(to);
  1677. SDL_Rect buf;
  1678. SDL_GetClipRect(to, &buf);
  1679. SDL_SetClipRect(to, &pos);
  1680. ++animCount;
  1681. if (activeStack != nullptr && creAnims[activeStack->ID]->isIdle()) //show everything with range
  1682. {
  1683. // FIXME: any *real* reason to keep this separate? Speed difference can't be that big
  1684. fieldController->showBackgroundImageWithHexes(to);
  1685. }
  1686. else
  1687. {
  1688. fieldController->showBackgroundImage(to);
  1689. obstacleController->showAbsoluteObstacles(to);
  1690. if ( siegeController )
  1691. siegeController->showAbsoluteObstacles(to);
  1692. }
  1693. fieldController->showHighlightedHexes(to);
  1694. showBattlefieldObjects(to);
  1695. projectilesController->showProjectiles(to);
  1696. if(battleActionsStarted)
  1697. updateBattleAnimations();
  1698. SDL_SetClipRect(to, &buf); //restoring previous clip_rect
  1699. showInterface(to);
  1700. //activation of next stack
  1701. if (pendingAnims.empty() && stackToActivate != nullptr && battleActionsStarted) //FIXME: watch for recursive infinite loop here when working with this file, this needs rework anyway...
  1702. {
  1703. activateStack();
  1704. //we may have changed active interface (another side in hot-seat),
  1705. // so we can't continue drawing with old setting.
  1706. show(to);
  1707. }
  1708. }
  1709. void CBattleInterface::showBattlefieldObjects(SDL_Surface *to)
  1710. {
  1711. auto showHexEntry = [&](BattleObjectsByHex::HexData & hex)
  1712. {
  1713. if (siegeController)
  1714. siegeController->showPiecesOfWall(to, hex.walls);
  1715. obstacleController->showObstacles(to, hex.obstacles);
  1716. showAliveStacks(to, hex.alive);
  1717. showBattleEffects(to, hex.effects);
  1718. };
  1719. BattleObjectsByHex objects = sortObjectsByHex();
  1720. // dead stacks should be blit first
  1721. showStacks(to, objects.beforeAll.dead);
  1722. for (auto & data : objects.hex)
  1723. showStacks(to, data.dead);
  1724. showStacks(to, objects.afterAll.dead);
  1725. // display objects that must be blit before anything else (e.g. topmost walls)
  1726. showHexEntry(objects.beforeAll);
  1727. // show heroes after "beforeAll" - e.g. topmost wall in siege
  1728. if (attackingHero)
  1729. attackingHero->show(to);
  1730. if (defendingHero)
  1731. defendingHero->show(to);
  1732. // actual blit of most of objects, hex by hex
  1733. // NOTE: row-by-row blitting may be a better approach
  1734. for (auto &data : objects.hex)
  1735. showHexEntry(data);
  1736. // objects that must be blit *after* everything else - e.g. bottom tower or some spell effects
  1737. showHexEntry(objects.afterAll);
  1738. }
  1739. void CBattleInterface::showAliveStacks(SDL_Surface *to, std::vector<const CStack *> stacks)
  1740. {
  1741. BattleHex currentActionTarget;
  1742. if(curInt->curAction)
  1743. {
  1744. auto target = curInt->curAction->getTarget(curInt->cb.get());
  1745. if(!target.empty())
  1746. currentActionTarget = target.at(0).hexValue;
  1747. }
  1748. auto isAmountBoxVisible = [&](const CStack *stack) -> bool
  1749. {
  1750. if(stack->hasBonusOfType(Bonus::SIEGE_WEAPON) && stack->getCount() == 1) //do not show box for singular war machines, stacked war machines with box shown are supported as extension feature
  1751. return false;
  1752. if(stack->getCount() == 0) //hide box when target is going to die anyway - do not display "0 creatures"
  1753. return false;
  1754. for(auto anim : pendingAnims) //no matter what other conditions below are, hide box when creature is playing hit animation
  1755. {
  1756. auto hitAnimation = dynamic_cast<CDefenceAnimation*>(anim.first);
  1757. if(hitAnimation && (hitAnimation->stack->ID == stack->ID)) //we process only "current creature" as other creatures will be processed reliably on their own iteration
  1758. return false;
  1759. }
  1760. if(curInt->curAction)
  1761. {
  1762. if(curInt->curAction->stackNumber == stack->ID) //stack is currently taking action (is not a target of another creature's action etc)
  1763. {
  1764. if(curInt->curAction->actionType == EActionType::WALK || curInt->curAction->actionType == EActionType::SHOOT) //hide when stack walks or shoots
  1765. return false;
  1766. else if(curInt->curAction->actionType == EActionType::WALK_AND_ATTACK && currentActionTarget != stack->getPosition()) //when attacking, hide until walk phase finished
  1767. return false;
  1768. }
  1769. if(curInt->curAction->actionType == EActionType::SHOOT && currentActionTarget == stack->getPosition()) //hide if we are ranged attack target
  1770. return false;
  1771. }
  1772. return true;
  1773. };
  1774. auto getEffectsPositivness = [&](const std::vector<si32> & activeSpells) -> int
  1775. {
  1776. int pos = 0;
  1777. for (const auto & spellId : activeSpells)
  1778. {
  1779. pos += CGI->spellh->objects.at(spellId)->positiveness;
  1780. }
  1781. return pos;
  1782. };
  1783. auto getAmountBoxBackground = [&](int positivness) -> SDL_Surface *
  1784. {
  1785. if (positivness > 0)
  1786. return amountPositive;
  1787. if (positivness < 0)
  1788. return amountNegative;
  1789. return amountEffNeutral;
  1790. };
  1791. showStacks(to, stacks); // Actual display of all stacks
  1792. for (auto & stack : stacks)
  1793. {
  1794. assert(stack);
  1795. //printing amount
  1796. if (isAmountBoxVisible(stack))
  1797. {
  1798. const int sideShift = stack->side == BattleSide::ATTACKER ? 1 : -1;
  1799. const int reverseSideShift = stack->side == BattleSide::ATTACKER ? -1 : 1;
  1800. const BattleHex nextPos = stack->getPosition() + sideShift;
  1801. const bool edge = stack->getPosition() % GameConstants::BFIELD_WIDTH == (stack->side == BattleSide::ATTACKER ? GameConstants::BFIELD_WIDTH - 2 : 1);
  1802. const bool moveInside = !edge && !fieldController->stackCountOutsideHex(nextPos);
  1803. int xAdd = (stack->side == BattleSide::ATTACKER ? 220 : 202) +
  1804. (stack->doubleWide() ? 44 : 0) * sideShift +
  1805. (moveInside ? amountNormal->w + 10 : 0) * reverseSideShift;
  1806. int yAdd = 260 + ((stack->side == BattleSide::ATTACKER || moveInside) ? 0 : -15);
  1807. //blitting amount background box
  1808. SDL_Surface *amountBG = amountNormal;
  1809. std::vector<si32> activeSpells = stack->activeSpells();
  1810. if (!activeSpells.empty())
  1811. amountBG = getAmountBoxBackground(getEffectsPositivness(activeSpells));
  1812. SDL_Rect temp_rect = genRect(amountBG->h, amountBG->w, creAnims[stack->ID]->pos.x + xAdd, creAnims[stack->ID]->pos.y + yAdd);
  1813. SDL_BlitSurface(amountBG, nullptr, to, &temp_rect);
  1814. //blitting amount
  1815. Point textPos(creAnims[stack->ID]->pos.x + xAdd + amountNormal->w/2,
  1816. creAnims[stack->ID]->pos.y + yAdd + amountNormal->h/2);
  1817. graphics->fonts[FONT_TINY]->renderTextCenter(to, makeNumberShort(stack->getCount()), Colors::WHITE, textPos);
  1818. }
  1819. }
  1820. }
  1821. void CBattleInterface::showStacks(SDL_Surface *to, std::vector<const CStack *> stacks)
  1822. {
  1823. for (const CStack *stack : stacks)
  1824. {
  1825. creAnims[stack->ID]->nextFrame(to, creDir[stack->ID]); // do actual blit
  1826. creAnims[stack->ID]->incrementFrame(float(GH.mainFPSmng->getElapsedMilliseconds()) / 1000);
  1827. }
  1828. }
  1829. void CBattleInterface::showBattleEffects(SDL_Surface *to, const std::vector<const BattleEffect *> &battleEffects)
  1830. {
  1831. for (auto & elem : battleEffects)
  1832. {
  1833. int currentFrame = static_cast<int>(floor(elem->currentFrame));
  1834. currentFrame %= elem->animation->size();
  1835. auto img = elem->animation->getImage(currentFrame);
  1836. SDL_Rect temp_rect = genRect(img->height(), img->width(), elem->x, elem->y);
  1837. img->draw(to, &temp_rect, nullptr);
  1838. }
  1839. }
  1840. void CBattleInterface::showInterface(SDL_Surface *to)
  1841. {
  1842. //showing in-game console
  1843. LOCPLINT->cingconsole->show(to);
  1844. controlPanel->show(to);
  1845. Rect posWithQueue = Rect(pos.x, pos.y, 800, 600);
  1846. if (settings["battle"]["showQueue"].Bool())
  1847. {
  1848. if (!queue->embedded)
  1849. {
  1850. posWithQueue.y -= queue->pos.h;
  1851. posWithQueue.h += queue->pos.h;
  1852. }
  1853. queue->showAll(to);
  1854. }
  1855. //printing border around interface
  1856. if (screen->w != 800 || screen->h !=600)
  1857. {
  1858. CMessage::drawBorder(curInt->playerID,to,posWithQueue.w + 28, posWithQueue.h + 28, posWithQueue.x-14, posWithQueue.y-15);
  1859. }
  1860. }
  1861. BattleObjectsByHex CBattleInterface::sortObjectsByHex()
  1862. {
  1863. auto getCurrentPosition = [&](const CStack *stack) -> BattleHex
  1864. {
  1865. for (auto & anim : pendingAnims)
  1866. {
  1867. // certainly ugly workaround but fixes quite annoying bug
  1868. // stack position will be updated only *after* movement is finished
  1869. // before this - stack is always at its initial position. Thus we need to find
  1870. // its current position. Which can be found only in this class
  1871. if (CMovementAnimation *move = dynamic_cast<CMovementAnimation*>(anim.first))
  1872. {
  1873. if (move->stack == stack)
  1874. return move->nextHex;
  1875. }
  1876. }
  1877. return stack->getPosition();
  1878. };
  1879. BattleObjectsByHex sorted;
  1880. auto stacks = curInt->cb->battleGetStacksIf([](const CStack *s)
  1881. {
  1882. return !s->isTurret();
  1883. });
  1884. // Sort creatures
  1885. for (auto & stack : stacks)
  1886. {
  1887. if (creAnims.find(stack->ID) == creAnims.end()) //e.g. for summoned but not yet handled stacks
  1888. continue;
  1889. if (stack->initialPosition < 0) // turret shooters are handled separately
  1890. continue;
  1891. //FIXME: hack to ignore ghost stacks
  1892. if ((creAnims[stack->ID]->getType() == CCreatureAnim::DEAD || creAnims[stack->ID]->getType() == CCreatureAnim::HOLDING) && stack->isGhost())
  1893. ;//ignore
  1894. else if (!creAnims[stack->ID]->isDead())
  1895. {
  1896. if (!creAnims[stack->ID]->isMoving())
  1897. sorted.hex[stack->getPosition()].alive.push_back(stack);
  1898. else
  1899. {
  1900. // flying creature - just blit them over everyone else
  1901. if (stack->hasBonusOfType(Bonus::FLYING))
  1902. sorted.afterAll.alive.push_back(stack);
  1903. else//try to find current location
  1904. sorted.hex[getCurrentPosition(stack)].alive.push_back(stack);
  1905. }
  1906. }
  1907. else
  1908. sorted.hex[stack->getPosition()].dead.push_back(stack);
  1909. }
  1910. // Sort battle effects (spells)
  1911. for (auto & battleEffect : battleEffects)
  1912. {
  1913. if (battleEffect.position.isValid())
  1914. sorted.hex[battleEffect.position].effects.push_back(&battleEffect);
  1915. else
  1916. sorted.afterAll.effects.push_back(&battleEffect);
  1917. }
  1918. // Sort obstacles
  1919. obstacleController->sortObjectsByHex(sorted);
  1920. // Sort wall parts
  1921. if (siegeController)
  1922. siegeController->sortObjectsByHex(sorted);
  1923. return sorted;
  1924. }
  1925. void CBattleInterface::updateBattleAnimations()
  1926. {
  1927. //handle animations
  1928. for (auto & elem : pendingAnims)
  1929. {
  1930. if (!elem.first) //this animation should be deleted
  1931. continue;
  1932. if (!elem.second)
  1933. {
  1934. elem.second = elem.first->init();
  1935. }
  1936. if (elem.second && elem.first)
  1937. elem.first->nextFrame();
  1938. }
  1939. //delete anims
  1940. int preSize = static_cast<int>(pendingAnims.size());
  1941. for (auto it = pendingAnims.begin(); it != pendingAnims.end(); ++it)
  1942. {
  1943. if (it->first == nullptr)
  1944. {
  1945. pendingAnims.erase(it);
  1946. it = pendingAnims.begin();
  1947. break;
  1948. }
  1949. }
  1950. if (preSize > 0 && pendingAnims.empty())
  1951. {
  1952. //anims ended
  1953. controlPanel->blockUI(activeStack == nullptr);
  1954. animsAreDisplayed.setn(false);
  1955. }
  1956. }