CSpellWindow.cpp 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886
  1. #include "StdInc.h"
  2. #include "CSpellWindow.h"
  3. #include "Graphics.h"
  4. #include "CDefHandler.h"
  5. #include "../lib/CSpellHandler.h"
  6. #include "../lib/CGeneralTextHandler.h"
  7. #include "CVideoHandler.h"
  8. #include "CAdvmapInterface.h"
  9. #include "battle/CBattleInterface.h"
  10. #include "CGameInfo.h"
  11. #include "gui/SDL_Extensions.h"
  12. #include "CMessage.h"
  13. #include "CPlayerInterface.h"
  14. #include "../CCallback.h"
  15. #include "CBitmapHandler.h"
  16. #include "../lib/CHeroHandler.h"
  17. #include "../lib/BattleState.h"
  18. #include "../lib/GameConstants.h"
  19. #include "gui/CGuiHandler.h"
  20. #include "CMT.h"
  21. /*
  22. * CSpellWindow.cpp, part of VCMI engine
  23. *
  24. * Authors: listed in file AUTHORS in main folder
  25. *
  26. * License: GNU General Public License v2.0 or later
  27. * Full text of license available in license.txt file, in main folder
  28. *
  29. */
  30. SpellbookInteractiveArea::SpellbookInteractiveArea(const SDL_Rect & myRect, std::function<void()> funcL,
  31. const std::string & textR, std::function<void()> funcHon, std::function<void()> funcHoff, CPlayerInterface * _myInt)
  32. {
  33. addUsedEvents(LCLICK | RCLICK | HOVER);
  34. pos = myRect;
  35. onLeft = funcL;
  36. textOnRclick = textR;
  37. onHoverOn = funcHon;
  38. onHoverOff = funcHoff;
  39. myInt = _myInt;
  40. }
  41. void SpellbookInteractiveArea::clickLeft(tribool down, bool previousState)
  42. {
  43. if(!down)
  44. {
  45. onLeft();
  46. }
  47. }
  48. void SpellbookInteractiveArea::clickRight(tribool down, bool previousState)
  49. {
  50. adventureInt->handleRightClick(textOnRclick, down);
  51. }
  52. void SpellbookInteractiveArea::hover(bool on)
  53. {
  54. //Hoverable::hover(on);
  55. if(on)
  56. {
  57. onHoverOn();
  58. }
  59. else
  60. {
  61. onHoverOff();
  62. }
  63. }
  64. CSpellWindow::CSpellWindow(const SDL_Rect &, const CGHeroInstance * _myHero, CPlayerInterface * _myInt, bool openOnBattleSpells):
  65. CWindowObject(PLAYER_COLORED, "SpelBack"),
  66. battleSpellsOnly(openOnBattleSpells),
  67. selectedTab(4),
  68. currentPage(0),
  69. myHero(_myHero),
  70. myInt(_myInt)
  71. {
  72. //initializing castable spells
  73. for(ui32 v=0; v<CGI->spellh->objects.size(); ++v)
  74. {
  75. if( !CGI->spellh->objects[v]->creatureAbility && myHero->canCastThisSpell(CGI->spellh->objects[v]) )
  76. mySpells.insert(SpellID(v));
  77. }
  78. //initializing sizes of spellbook's parts
  79. for(auto & elem : sitesPerTabAdv)
  80. elem = 0;
  81. for(auto & elem : sitesPerTabBattle)
  82. elem = 0;
  83. for(auto g : mySpells)
  84. {
  85. const CSpell &s = *CGI->spellh->objects[g];
  86. Uint8 *sitesPerOurTab = s.combatSpell ? sitesPerTabBattle : sitesPerTabAdv;
  87. ++sitesPerOurTab[4];
  88. if(s.air)
  89. ++sitesPerOurTab[0];
  90. if(s.fire)
  91. ++sitesPerOurTab[1];
  92. if(s.water)
  93. ++sitesPerOurTab[2];
  94. if(s.earth)
  95. ++sitesPerOurTab[3];
  96. }
  97. if(sitesPerTabAdv[4] % 12 == 0)
  98. sitesPerTabAdv[4]/=12;
  99. else
  100. sitesPerTabAdv[4] = sitesPerTabAdv[4]/12 + 1;
  101. for(int v=0; v<4; ++v)
  102. {
  103. if(sitesPerTabAdv[v] <= 10)
  104. sitesPerTabAdv[v] = 1;
  105. else
  106. {
  107. if((sitesPerTabAdv[v] - 10) % 12 == 0)
  108. sitesPerTabAdv[v] = (sitesPerTabAdv[v] - 10) / 12 + 1;
  109. else
  110. sitesPerTabAdv[v] = (sitesPerTabAdv[v] - 10) / 12 + 2;
  111. }
  112. }
  113. if(sitesPerTabBattle[4] % 12 == 0)
  114. sitesPerTabBattle[4]/=12;
  115. else
  116. sitesPerTabBattle[4] = sitesPerTabBattle[4]/12 + 1;
  117. for(int v=0; v<4; ++v)
  118. {
  119. if(sitesPerTabBattle[v] <= 10)
  120. sitesPerTabBattle[v] = 1;
  121. else
  122. {
  123. if((sitesPerTabBattle[v] - 10) % 12 == 0)
  124. sitesPerTabBattle[v] = (sitesPerTabBattle[v] - 10) / 12 + 1;
  125. else
  126. sitesPerTabBattle[v] = (sitesPerTabBattle[v] - 10) / 12 + 2;
  127. }
  128. }
  129. //numbers of spell pages computed
  130. leftCorner = BitmapHandler::loadBitmap("SpelTrnL.bmp", true);
  131. rightCorner = BitmapHandler::loadBitmap("SpelTrnR.bmp", true);
  132. spells = new CAnimation("Spells.def");
  133. spellTab = CDefHandler::giveDef("SpelTab.def");
  134. schools = CDefHandler::giveDef("Schools.def");
  135. schoolBorders[0] = CDefHandler::giveDef("SplevA.def");
  136. schoolBorders[1] = CDefHandler::giveDef("SplevF.def");
  137. schoolBorders[2] = CDefHandler::giveDef("SplevW.def");
  138. schoolBorders[3] = CDefHandler::giveDef("SplevE.def");
  139. statusBar = new CGStatusBar(7 + pos.x, 569 + pos.y, "Spelroll.bmp");
  140. SDL_Rect temp_rect = genRect(45, 35, 479 + pos.x, 405 + pos.y);
  141. exitBtn = new SpellbookInteractiveArea(temp_rect, boost::bind(&CSpellWindow::fexitb, this), CGI->generaltexth->zelp[460].second, boost::bind(&CGStatusBar::setText, statusBar, (CGI->generaltexth->zelp[460].first)), boost::bind(&CGStatusBar::clear, statusBar), myInt);
  142. temp_rect = genRect(45, 35, 221 + pos.x, 405 + pos.y);
  143. battleSpells = new SpellbookInteractiveArea(temp_rect, boost::bind(&CSpellWindow::fbattleSpellsb, this), CGI->generaltexth->zelp[453].second, boost::bind(&CGStatusBar::setText, statusBar, (CGI->generaltexth->zelp[453].first)), boost::bind(&CGStatusBar::clear, statusBar), myInt);
  144. temp_rect = genRect(45, 35, 355 + pos.x, 405 + pos.y);
  145. adventureSpells = new SpellbookInteractiveArea(temp_rect, boost::bind(&CSpellWindow::fadvSpellsb, this), CGI->generaltexth->zelp[452].second, boost::bind(&CGStatusBar::setText, statusBar, (CGI->generaltexth->zelp[452].first)), boost::bind(&CGStatusBar::clear, statusBar), myInt);
  146. temp_rect = genRect(45, 35, 418 + pos.x, 405 + pos.y);
  147. manaPoints = new SpellbookInteractiveArea(temp_rect, boost::bind(&CSpellWindow::fmanaPtsb, this), CGI->generaltexth->zelp[459].second, boost::bind(&CGStatusBar::setText, statusBar, (CGI->generaltexth->zelp[459].first)), boost::bind(&CGStatusBar::clear, statusBar), myInt);
  148. temp_rect = genRect(36, 56, 549 + pos.x, 94 + pos.y);
  149. selectSpellsA = new SpellbookInteractiveArea(temp_rect, boost::bind(&CSpellWindow::selectSchool, this, 0), CGI->generaltexth->zelp[454].second, boost::bind(&CGStatusBar::setText, statusBar, (CGI->generaltexth->zelp[454].first)), boost::bind(&CGStatusBar::clear, statusBar), myInt);
  150. temp_rect = genRect(36, 56, 549 + pos.x, 151 + pos.y);
  151. selectSpellsE = new SpellbookInteractiveArea(temp_rect, boost::bind(&CSpellWindow::selectSchool, this, 3), CGI->generaltexth->zelp[457].second, boost::bind(&CGStatusBar::setText, statusBar, (CGI->generaltexth->zelp[457].first)), boost::bind(&CGStatusBar::clear, statusBar), myInt);
  152. temp_rect = genRect(36, 56, 549 + pos.x, 210 + pos.y);
  153. selectSpellsF = new SpellbookInteractiveArea(temp_rect, boost::bind(&CSpellWindow::selectSchool, this, 1), CGI->generaltexth->zelp[455].second, boost::bind(&CGStatusBar::setText, statusBar, (CGI->generaltexth->zelp[455].first)), boost::bind(&CGStatusBar::clear, statusBar), myInt);
  154. temp_rect = genRect(36, 56, 549 + pos.x, 270 + pos.y);
  155. selectSpellsW = new SpellbookInteractiveArea(temp_rect, boost::bind(&CSpellWindow::selectSchool, this, 2), CGI->generaltexth->zelp[456].second, boost::bind(&CGStatusBar::setText, statusBar, (CGI->generaltexth->zelp[456].first)), boost::bind(&CGStatusBar::clear, statusBar), myInt);
  156. temp_rect = genRect(36, 56, 549 + pos.x, 330 + pos.y);
  157. selectSpellsAll = new SpellbookInteractiveArea(temp_rect, boost::bind(&CSpellWindow::selectSchool, this, 4), CGI->generaltexth->zelp[458].second, boost::bind(&CGStatusBar::setText, statusBar, (CGI->generaltexth->zelp[458].first)), boost::bind(&CGStatusBar::clear, statusBar), myInt);
  158. temp_rect = genRect(leftCorner->h, leftCorner->w, 97 + pos.x, 77 + pos.y);
  159. lCorner = new SpellbookInteractiveArea(temp_rect, boost::bind(&CSpellWindow::fLcornerb, this), CGI->generaltexth->zelp[450].second, boost::bind(&CGStatusBar::setText, statusBar, (CGI->generaltexth->zelp[450].first)), boost::bind(&CGStatusBar::clear, statusBar), myInt);
  160. temp_rect = genRect(rightCorner->h, rightCorner->w, 487 + pos.x, 72 + pos.y);
  161. rCorner = new SpellbookInteractiveArea(temp_rect, boost::bind(&CSpellWindow::fRcornerb, this), CGI->generaltexth->zelp[451].second, boost::bind(&CGStatusBar::setText, statusBar, (CGI->generaltexth->zelp[451].first)), boost::bind(&CGStatusBar::clear, statusBar), myInt);
  162. //areas for spells
  163. int xpos = 117 + pos.x, ypos = 90 + pos.y;
  164. for(int v=0; v<12; ++v)
  165. {
  166. temp_rect = genRect(65, 78, xpos, ypos);
  167. spellAreas[v] = new SpellArea(temp_rect, this);
  168. if(v == 5) //to right page
  169. {
  170. xpos = 336 + pos.x; ypos = 90 + pos.y;
  171. }
  172. else
  173. {
  174. if(v%2 == 0)
  175. {
  176. xpos+=85;
  177. }
  178. else
  179. {
  180. xpos -= 85; ypos+=97;
  181. }
  182. }
  183. }
  184. selectedTab = battleSpellsOnly ? myInt->spellbookSettings.spellbookLastTabBattle : myInt->spellbookSettings.spellbookLastTabAdvmap;
  185. currentPage = battleSpellsOnly ? myInt->spellbookSettings.spellbookLastPageBattle : myInt->spellbookSettings.spellbokLastPageAdvmap;
  186. // spellbook last page battle index is not reset after battle, so this needs to stay here
  187. vstd::abetween(currentPage, 0, pagesWithinCurrentTab() - 1);
  188. computeSpellsPerArea();
  189. addUsedEvents(KEYBOARD);
  190. }
  191. CSpellWindow::~CSpellWindow()
  192. {
  193. SDL_FreeSurface(leftCorner);
  194. SDL_FreeSurface(rightCorner);
  195. spells->unload();
  196. delete spells;
  197. delete spellTab;
  198. delete schools;
  199. for(auto & elem : schoolBorders)
  200. delete elem;
  201. delete exitBtn;
  202. delete battleSpells;
  203. delete adventureSpells;
  204. delete manaPoints;
  205. delete statusBar;
  206. delete selectSpellsA;
  207. delete selectSpellsE;
  208. delete selectSpellsF;
  209. delete selectSpellsW;
  210. delete selectSpellsAll;
  211. delete lCorner;
  212. delete rCorner;
  213. for(auto & elem : spellAreas)
  214. {
  215. delete elem;
  216. }
  217. }
  218. void CSpellWindow::fexitb()
  219. {
  220. (LOCPLINT->battleInt ? myInt->spellbookSettings.spellbookLastTabBattle : myInt->spellbookSettings.spellbookLastTabAdvmap) = selectedTab;
  221. (LOCPLINT->battleInt ? myInt->spellbookSettings.spellbookLastPageBattle : myInt->spellbookSettings.spellbokLastPageAdvmap) = currentPage;
  222. GH.popIntTotally(this);
  223. }
  224. void CSpellWindow::fadvSpellsb()
  225. {
  226. if (battleSpellsOnly == true)
  227. {
  228. turnPageRight();
  229. battleSpellsOnly = false;
  230. currentPage = 0;
  231. }
  232. computeSpellsPerArea();
  233. }
  234. void CSpellWindow::fbattleSpellsb()
  235. {
  236. if (battleSpellsOnly == false)
  237. {
  238. turnPageLeft();
  239. battleSpellsOnly = true;
  240. currentPage = 0;
  241. }
  242. computeSpellsPerArea();
  243. }
  244. void CSpellWindow::fmanaPtsb()
  245. {
  246. }
  247. void CSpellWindow::selectSchool(int school)
  248. {
  249. if (selectedTab != school)
  250. {
  251. if (selectedTab < school)
  252. turnPageLeft();
  253. else
  254. turnPageRight();
  255. selectedTab = school;
  256. currentPage = 0;
  257. }
  258. computeSpellsPerArea();
  259. }
  260. void CSpellWindow::fLcornerb()
  261. {
  262. if(currentPage>0)
  263. {
  264. turnPageLeft();
  265. --currentPage;
  266. }
  267. computeSpellsPerArea();
  268. GH.breakEventHandling();
  269. }
  270. void CSpellWindow::fRcornerb()
  271. {
  272. if((currentPage + 1) < (pagesWithinCurrentTab()))
  273. {
  274. turnPageRight();
  275. ++currentPage;
  276. }
  277. computeSpellsPerArea();
  278. GH.breakEventHandling();
  279. }
  280. void CSpellWindow::showAll(SDL_Surface * to)
  281. {
  282. CWindowObject::showAll(to);
  283. blitAt(spellTab->ourImages[selectedTab].bitmap, 524 + pos.x, 88 + pos.y, to);
  284. std::ostringstream mana;
  285. mana<<myHero->mana;
  286. printAtMiddleLoc(mana.str(), 435, 426, FONT_SMALL, Colors::YELLOW, to);
  287. statusBar->showAll(to);
  288. //printing school images
  289. if(selectedTab!=4 && currentPage == 0)
  290. {
  291. blitAt(schools->ourImages[selectedTab].bitmap, 117 + pos.x, 74 + pos.y, to);
  292. }
  293. //printing corners
  294. if(currentPage!=0)
  295. {
  296. blitAt(leftCorner, lCorner->pos.x, lCorner->pos.y, to);
  297. }
  298. if((currentPage+1) < (pagesWithinCurrentTab()) )
  299. {
  300. blitAt(rightCorner, rCorner->pos.x, rCorner->pos.y, to);
  301. }
  302. //printing spell info
  303. for(auto & elem : spellAreas)
  304. {
  305. elem->showAll(to);
  306. }
  307. }
  308. void CSpellWindow::show(SDL_Surface * to)
  309. {
  310. statusBar->show(to);
  311. }
  312. class SpellbookSpellSorter
  313. {
  314. public:
  315. bool operator()(const int & a, const int & b)
  316. {
  317. const CSpell & A = *CGI->spellh->objects[a];
  318. const CSpell & B = *CGI->spellh->objects[b];
  319. if(A.level<B.level)
  320. return true;
  321. if(A.level>B.level)
  322. return false;
  323. if(A.air && !B.air)
  324. return true;
  325. if(!A.air && B.air)
  326. return false;
  327. if(A.fire && !B.fire)
  328. return true;
  329. if(!A.fire && B.fire)
  330. return false;
  331. if(A.water && !B.water)
  332. return true;
  333. if(!A.water && B.water)
  334. return false;
  335. if(A.earth && !B.earth)
  336. return true;
  337. if(!A.earth && B.earth)
  338. return false;
  339. return A.name < B.name;
  340. }
  341. } spellsorter;
  342. void CSpellWindow::computeSpellsPerArea()
  343. {
  344. std::vector<SpellID> spellsCurSite;
  345. for(auto it = mySpells.cbegin(); it != mySpells.cend(); ++it)
  346. {
  347. if(CGI->spellh->objects[*it]->combatSpell ^ !battleSpellsOnly
  348. && ((CGI->spellh->objects[*it]->air && selectedTab == 0) ||
  349. (CGI->spellh->objects[*it]->fire && selectedTab == 1) ||
  350. (CGI->spellh->objects[*it]->water && selectedTab == 2) ||
  351. (CGI->spellh->objects[*it]->earth && selectedTab == 3) ||
  352. selectedTab == 4 )
  353. )
  354. {
  355. spellsCurSite.push_back(*it);
  356. }
  357. }
  358. std::sort(spellsCurSite.begin(), spellsCurSite.end(), spellsorter);
  359. if(selectedTab == 4)
  360. {
  361. if(spellsCurSite.size() > 12)
  362. {
  363. spellsCurSite = std::vector<SpellID>(spellsCurSite.begin() + currentPage*12, spellsCurSite.end());
  364. if(spellsCurSite.size() > 12)
  365. {
  366. spellsCurSite.erase(spellsCurSite.begin()+12, spellsCurSite.end());
  367. }
  368. }
  369. }
  370. else //selectedTab == 0, 1, 2 or 3
  371. {
  372. if(spellsCurSite.size() > 10)
  373. {
  374. if(currentPage == 0)
  375. {
  376. spellsCurSite.erase(spellsCurSite.begin()+10, spellsCurSite.end());
  377. }
  378. else
  379. {
  380. spellsCurSite = std::vector<SpellID>(spellsCurSite.begin() + (currentPage-1)*12 + 10, spellsCurSite.end());
  381. if(spellsCurSite.size() > 12)
  382. {
  383. spellsCurSite.erase(spellsCurSite.begin()+12, spellsCurSite.end());
  384. }
  385. }
  386. }
  387. }
  388. //applying
  389. if(selectedTab == 4 || currentPage != 0)
  390. {
  391. for(size_t c=0; c<12; ++c)
  392. {
  393. if(c<spellsCurSite.size())
  394. {
  395. spellAreas[c]->setSpell(spellsCurSite[c]);
  396. }
  397. else
  398. {
  399. spellAreas[c]->setSpell(SpellID::NONE);
  400. }
  401. }
  402. }
  403. else
  404. {
  405. spellAreas[0]->setSpell(SpellID::NONE);
  406. spellAreas[1]->setSpell(SpellID::NONE);
  407. for(size_t c=0; c<10; ++c)
  408. {
  409. if(c<spellsCurSite.size())
  410. spellAreas[c+2]->setSpell(spellsCurSite[c]);
  411. else
  412. spellAreas[c+2]->setSpell(SpellID::NONE);
  413. }
  414. }
  415. redraw();
  416. }
  417. void CSpellWindow::activate()
  418. {
  419. CIntObject::activate();
  420. exitBtn->activate();
  421. battleSpells->activate();
  422. adventureSpells->activate();
  423. manaPoints->activate();
  424. selectSpellsA->activate();
  425. selectSpellsE->activate();
  426. selectSpellsF->activate();
  427. selectSpellsW->activate();
  428. selectSpellsAll->activate();
  429. for(auto & elem : spellAreas)
  430. {
  431. elem->activate();
  432. }
  433. lCorner->activate();
  434. rCorner->activate();
  435. }
  436. void CSpellWindow::deactivate()
  437. {
  438. CIntObject::deactivate();
  439. exitBtn->deactivate();
  440. battleSpells->deactivate();
  441. adventureSpells->deactivate();
  442. manaPoints->deactivate();
  443. selectSpellsA->deactivate();
  444. selectSpellsE->deactivate();
  445. selectSpellsF->deactivate();
  446. selectSpellsW->deactivate();
  447. selectSpellsAll->deactivate();
  448. for(auto & elem : spellAreas)
  449. {
  450. elem->deactivate();
  451. }
  452. lCorner->deactivate();
  453. rCorner->deactivate();
  454. }
  455. void CSpellWindow::turnPageLeft()
  456. {
  457. if (settings["video"]["spellbookAnimation"].Bool())
  458. CCS->videoh->openAndPlayVideo("PGTRNLFT.SMK", pos.x+13, pos.y+15, screen);
  459. }
  460. void CSpellWindow::turnPageRight()
  461. {
  462. if (settings["video"]["spellbookAnimation"].Bool())
  463. CCS->videoh->openAndPlayVideo("PGTRNRGH.SMK", pos.x+13, pos.y+15, screen);
  464. }
  465. void CSpellWindow::keyPressed(const SDL_KeyboardEvent & key)
  466. {
  467. if(key.keysym.sym == SDLK_ESCAPE || key.keysym.sym == SDLK_RETURN)
  468. fexitb();
  469. if(key.state == SDL_PRESSED)
  470. {
  471. switch(key.keysym.sym)
  472. {
  473. case SDLK_LEFT:
  474. fLcornerb();
  475. break;
  476. case SDLK_RIGHT:
  477. fRcornerb();
  478. break;
  479. case SDLK_UP:
  480. case SDLK_DOWN:
  481. {
  482. bool down = key.keysym.sym == SDLK_DOWN;
  483. static const int schoolsOrder[] = { 0, 3, 1, 2, 4 };
  484. int index = -1;
  485. while(schoolsOrder[++index] != selectedTab);
  486. index += (down ? 1 : -1);
  487. vstd::abetween(index, 0, ARRAY_COUNT(schoolsOrder) - 1);
  488. if(selectedTab != schoolsOrder[index])
  489. selectSchool(schoolsOrder[index]);
  490. break;
  491. }
  492. case SDLK_c:
  493. fbattleSpellsb();
  494. break;
  495. case SDLK_a:
  496. fadvSpellsb();
  497. break;
  498. default://to get rid of warnings
  499. break;
  500. }
  501. //alt + 1234567890-= casts spell from 1 - 12 slot
  502. if(LOCPLINT->altPressed())
  503. {
  504. SDLKey hlpKey = key.keysym.sym;
  505. if(CGuiHandler::isNumKey(hlpKey, false))
  506. {
  507. if(hlpKey == SDLK_KP_PLUS)
  508. hlpKey = SDLK_EQUALS;
  509. else
  510. hlpKey = CGuiHandler::numToDigit(hlpKey);
  511. }
  512. static const SDLKey spellSelectors[] = {SDLK_1, SDLK_2, SDLK_3, SDLK_4, SDLK_5, SDLK_6, SDLK_7, SDLK_8, SDLK_9, SDLK_0, SDLK_MINUS, SDLK_EQUALS};
  513. int index = -1;
  514. while(++index < ARRAY_COUNT(spellSelectors) && spellSelectors[index] != hlpKey);
  515. if(index >= ARRAY_COUNT(spellSelectors))
  516. return;
  517. //try casting spell
  518. spellAreas[index]->clickLeft(false, true);
  519. }
  520. }
  521. }
  522. Uint8 CSpellWindow::pagesWithinCurrentTab()
  523. {
  524. return battleSpellsOnly ? sitesPerTabBattle[selectedTab] : sitesPerTabAdv[selectedTab];
  525. }
  526. void CSpellWindow::teleportTo( int town, const CGHeroInstance * hero )
  527. {
  528. const CGTownInstance * dest = LOCPLINT->cb->getTown(ObjectInstanceID(town));
  529. LOCPLINT->cb->castSpell(hero, SpellID::TOWN_PORTAL, dest->visitablePos());
  530. }
  531. CSpellWindow::SpellArea::SpellArea(SDL_Rect pos, CSpellWindow * owner)
  532. {
  533. this->pos = pos;
  534. this->owner = owner;
  535. addUsedEvents(LCLICK | RCLICK | HOVER);
  536. spellCost = whichSchool = schoolLevel = -1;
  537. mySpell = SpellID::NONE;
  538. }
  539. void CSpellWindow::SpellArea::clickLeft(tribool down, bool previousState)
  540. {
  541. if(!down && mySpell!=-1)
  542. {
  543. const CSpell *sp = CGI->spellh->objects[mySpell];
  544. int spellCost = owner->myInt->cb->getSpellCost(sp, owner->myHero);
  545. if(spellCost > owner->myHero->mana) //insufficient mana
  546. {
  547. char msgBuf[500];
  548. sprintf(msgBuf, CGI->generaltexth->allTexts[206].c_str(), spellCost, owner->myHero->mana);
  549. owner->myInt->showInfoDialog(std::string(msgBuf));
  550. return;
  551. }
  552. //battle spell on adv map or adventure map spell during combat => display infowindow, not cast
  553. if((sp->combatSpell && !owner->myInt->battleInt)
  554. || (!sp->combatSpell && owner->myInt->battleInt))
  555. {
  556. std::vector<CComponent*> hlp(1, new CComponent(CComponent::spell, mySpell, 0));
  557. LOCPLINT->showInfoDialog(sp->getLevelInfo(schoolLevel).description, hlp);
  558. return;
  559. }
  560. //we will cast a spell
  561. if(sp->combatSpell && owner->myInt->battleInt && owner->myInt->cb->battleCanCastSpell()) //if battle window is open
  562. {
  563. ESpellCastProblem::ESpellCastProblem problem = owner->myInt->cb->battleCanCastThisSpell(sp);
  564. switch (problem)
  565. {
  566. case ESpellCastProblem::OK:
  567. {
  568. int spell = mySpell;
  569. owner->fexitb();
  570. owner->myInt->battleInt->castThisSpell(spell);
  571. }
  572. break;
  573. case ESpellCastProblem::ANOTHER_ELEMENTAL_SUMMONED:
  574. {
  575. std::string text = CGI->generaltexth->allTexts[538], summoner, elemental, caster;
  576. std::vector<const CStack *> stacks = owner->myInt->cb->battleGetStacks();
  577. for(const CStack * s : stacks)
  578. {
  579. if(vstd::contains(s->state, EBattleStackState::SUMMONED))
  580. {
  581. elemental = s->getCreature()->namePl;
  582. summoner = owner->myInt->cb->battleGetHeroInfo(!s->attackerOwned).name;
  583. break;
  584. }
  585. }
  586. if (owner->myHero->type->sex)
  587. { //female
  588. caster = CGI->generaltexth->allTexts[540];
  589. }
  590. else
  591. { //male
  592. caster = CGI->generaltexth->allTexts[539];
  593. }
  594. text = boost::str(boost::format(text) % summoner % elemental % caster);
  595. owner->myInt->showInfoDialog(text);
  596. }
  597. break;
  598. case ESpellCastProblem::SPELL_LEVEL_LIMIT_EXCEEDED:
  599. {
  600. //Recanter's Cloak or similar effect. Try to retrieve bonus
  601. const Bonus *b = owner->myHero->getBonusLocalFirst(Selector::type(Bonus::BLOCK_MAGIC_ABOVE));
  602. //TODO what about other values and non-artifact sources?
  603. if(b && b->val == 2 && b->source == Bonus::ARTIFACT)
  604. {
  605. std::string artName = CGI->arth->artifacts[b->sid]->Name();
  606. //The %s prevents %s from casting 3rd level or higher spells.
  607. owner->myInt->showInfoDialog(boost::str(boost::format(CGI->generaltexth->allTexts[536])
  608. % artName % owner->myHero->name));
  609. }
  610. else
  611. {
  612. // General message:
  613. // %s recites the incantations but they seem to have no effect.
  614. std::string text = CGI->generaltexth->allTexts[541], caster = owner->myHero->name;
  615. text = boost::str(boost::format(text) % caster);
  616. owner->myInt->showInfoDialog(text);
  617. }
  618. }
  619. break;
  620. case ESpellCastProblem::NO_APPROPRIATE_TARGET:
  621. {
  622. owner->myInt->showInfoDialog(CGI->generaltexth->allTexts[185]);
  623. }
  624. break;
  625. }
  626. }
  627. else if(!sp->combatSpell && !owner->myInt->battleInt) //adventure spell
  628. {
  629. SpellID spell = mySpell;
  630. const CGHeroInstance *h = owner->myHero;
  631. owner->fexitb();
  632. switch(spell)
  633. {
  634. case SpellID::SUMMON_BOAT:
  635. {
  636. int3 pos = h->bestLocation();
  637. if(pos.x < 0)
  638. {
  639. LOCPLINT->showInfoDialog(CGI->generaltexth->allTexts[334]); //There is no place to put the boat.
  640. return;
  641. }
  642. }
  643. break;
  644. case SpellID::SCUTTLE_BOAT:
  645. case SpellID::DIMENSION_DOOR:
  646. adventureInt->enterCastingMode(sp);
  647. return;
  648. case SpellID::VISIONS:
  649. case SpellID::VIEW_EARTH:
  650. case SpellID::DISGUISE:
  651. case SpellID::VIEW_AIR:
  652. case SpellID::FLY:
  653. case SpellID::WATER_WALK:
  654. break;
  655. case SpellID::TOWN_PORTAL:
  656. {
  657. std::vector <int> availableTowns;
  658. std::vector <const CGTownInstance*> Towns = LOCPLINT->cb->getTownsInfo(true);
  659. if (Towns.empty())
  660. {
  661. LOCPLINT->showInfoDialog(CGI->generaltexth->allTexts[124]);
  662. return;
  663. }
  664. if (h->getSpellSchoolLevel(CGI->spellh->objects[spell]) < 2) //not advanced or expert - teleport to nearest available city
  665. {
  666. auto nearest = Towns.cbegin(); //nearest town's iterator
  667. si32 dist = LOCPLINT->cb->getTown((*nearest)->id)->pos.dist2dSQ(h->pos);
  668. for (auto i = nearest + 1; i != Towns.cend(); ++i)
  669. {
  670. const CGTownInstance * dest = LOCPLINT->cb->getTown((*i)->id);
  671. si32 curDist = dest->pos.dist2dSQ(h->pos);
  672. if (curDist < dist)
  673. {
  674. nearest = i;
  675. dist = curDist;
  676. }
  677. }
  678. if ((*nearest)->visitingHero)
  679. LOCPLINT->showInfoDialog(CGI->generaltexth->allTexts[123]);
  680. else
  681. {
  682. const CGTownInstance * town = LOCPLINT->cb->getTown((*nearest)->id);
  683. LOCPLINT->cb->castSpell(h, spell, town->visitablePos());// - town->getVisitableOffset());
  684. }
  685. }
  686. else
  687. { //let the player choose
  688. for(auto & Town : Towns)
  689. {
  690. const CGTownInstance *t = Town;
  691. if (t->visitingHero == nullptr) //empty town and this is
  692. {
  693. availableTowns.push_back(t->id.getNum());//add to the list
  694. }
  695. }
  696. if (availableTowns.empty())
  697. LOCPLINT->showInfoDialog(CGI->generaltexth->allTexts[124]);
  698. else
  699. GH.pushInt (new CObjectListWindow(availableTowns,
  700. new CAnimImage("SPELLSCR",spell),
  701. CGI->generaltexth->jktexts[40], CGI->generaltexth->jktexts[41],
  702. boost::bind (&CSpellWindow::teleportTo, owner, _1, h)));
  703. }
  704. return;
  705. }
  706. break;
  707. default:
  708. assert(0);
  709. }
  710. //can return earlier in some cases
  711. LOCPLINT->cb->castSpell(h, spell);
  712. }
  713. }
  714. }
  715. void CSpellWindow::SpellArea::clickRight(tribool down, bool previousState)
  716. {
  717. if(down && mySpell != -1)
  718. {
  719. std::string dmgInfo;
  720. const CGHeroInstance * hero = owner->myHero;
  721. int causedDmg = owner->myInt->cb->estimateSpellDamage( CGI->spellh->objects[mySpell], (hero ? hero : nullptr));
  722. if(causedDmg == 0 || mySpell == 57) //Titan's Lightning Bolt already has damage info included
  723. dmgInfo = "";
  724. else
  725. {
  726. dmgInfo = CGI->generaltexth->allTexts[343];
  727. boost::algorithm::replace_first(dmgInfo, "%d", boost::lexical_cast<std::string>(causedDmg));
  728. }
  729. CRClickPopup::createAndPush(CGI->spellh->objects[mySpell]->getLevelInfo(schoolLevel).description + dmgInfo,
  730. new CComponent(CComponent::spell, mySpell));
  731. }
  732. }
  733. void CSpellWindow::SpellArea::hover(bool on)
  734. {
  735. //Hoverable::hover(on);
  736. if(mySpell != -1)
  737. {
  738. if(on)
  739. {
  740. std::ostringstream ss;
  741. ss<<CGI->spellh->objects[mySpell]->name<<" ("<<CGI->generaltexth->allTexts[171+CGI->spellh->objects[mySpell]->level]<<")";
  742. owner->statusBar->setText(ss.str());
  743. }
  744. else
  745. {
  746. owner->statusBar->clear();
  747. }
  748. }
  749. }
  750. void CSpellWindow::SpellArea::showAll(SDL_Surface * to)
  751. {
  752. if(mySpell < 0)
  753. return;
  754. const CSpell * spell = mySpell.toSpell();
  755. owner->spells->load(mySpell);
  756. IImage * icon = owner->spells->getImage(mySpell,0,false);
  757. if(icon != nullptr)
  758. icon->draw(to, pos.x, pos.y);
  759. else
  760. logGlobal->errorStream() << __FUNCTION__ << ": failed to load spell icon for spell with id " << mySpell;
  761. blitAt(owner->schoolBorders[owner->selectedTab >= 4 ? whichSchool : owner->selectedTab]->ourImages[schoolLevel].bitmap, pos.x, pos.y, to); //printing border (indicates level of magic school)
  762. SDL_Color firstLineColor, secondLineColor;
  763. if(spellCost > owner->myHero->mana) //hero cannot cast this spell
  764. {
  765. static const SDL_Color unavailableSpell = {239, 189, 33, 0};
  766. firstLineColor = Colors::WHITE;
  767. secondLineColor = unavailableSpell;
  768. }
  769. else
  770. {
  771. firstLineColor = Colors::YELLOW;
  772. secondLineColor = Colors::WHITE;
  773. }
  774. //printing spell's name
  775. printAtMiddleLoc(spell->name, 39, 70, FONT_TINY, firstLineColor, to);
  776. //printing lvl
  777. printAtMiddleLoc(CGI->generaltexth->allTexts[171 + spell->level], 39, 82, FONT_TINY, secondLineColor, to);
  778. //printing cost
  779. std::ostringstream ss;
  780. ss << CGI->generaltexth->allTexts[387] << ": " << spellCost;
  781. printAtMiddleLoc(ss.str(), 39, 94, FONT_TINY, secondLineColor, to);
  782. }
  783. void CSpellWindow::SpellArea::setSpell(SpellID spellID)
  784. {
  785. mySpell = spellID;
  786. if(mySpell < 0)
  787. return;
  788. const CSpell * spell = CGI->spellh->objects[mySpell];
  789. schoolLevel = owner->myHero->getSpellSchoolLevel(spell, &whichSchool);
  790. spellCost = owner->myInt->cb->getSpellCost(spell, owner->myHero);
  791. }