CAdventureMapInterface.cpp 42 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439
  1. /*
  2. * CAdvMapInt.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 "CAdventureMapInterface.h"
  12. #include "CAdvMapPanel.h"
  13. #include "CAdventureOptions.h"
  14. #include "CInGameConsole.h"
  15. #include "CMinimap.h"
  16. #include "CResDataBar.h"
  17. #include "CList.h"
  18. #include "CInfoBar.h"
  19. #include "MapAudioPlayer.h"
  20. #include "../mapView/mapHandler.h"
  21. #include "../mapView/MapView.h"
  22. #include "../windows/CKingdomInterface.h"
  23. #include "../windows/CSpellWindow.h"
  24. #include "../windows/CTradeWindow.h"
  25. #include "../windows/GUIClasses.h"
  26. #include "../windows/InfoWindows.h"
  27. #include "../CGameInfo.h"
  28. #include "../CMusicHandler.h"
  29. #include "../CPlayerInterface.h"
  30. #include "../lobby/CSavingScreen.h"
  31. #include "../render/CAnimation.h"
  32. #include "../gui/CursorHandler.h"
  33. #include "../render/IImage.h"
  34. #include "../gui/CGuiHandler.h"
  35. #include "../widgets/TextControls.h"
  36. #include "../widgets/Buttons.h"
  37. #include "../windows/settings/SettingsMainWindow.h"
  38. #include "../CMT.h"
  39. #include "../PlayerLocalState.h"
  40. #include "../../CCallback.h"
  41. #include "../../lib/CConfigHandler.h"
  42. #include "../../lib/CGeneralTextHandler.h"
  43. #include "../../lib/spells/CSpellHandler.h"
  44. #include "../../lib/mapObjects/CGHeroInstance.h"
  45. #include "../../lib/mapObjects/CGTownInstance.h"
  46. #include "../../lib/CPathfinder.h"
  47. #include "../../lib/mapping/CMap.h"
  48. #include "../../lib/UnlockGuard.h"
  49. #include "../../lib/TerrainHandler.h"
  50. #include <SDL_keycode.h>
  51. #define ADVOPT (conf.go()->ac)
  52. std::shared_ptr<CAdventureMapInterface> adventureInt;
  53. CAdventureMapInterface::CAdventureMapInterface():
  54. minimap(new CMinimap(Rect(ADVOPT.minimapX, ADVOPT.minimapY, ADVOPT.minimapW, ADVOPT.minimapH))),
  55. statusbar(CGStatusBar::create(ADVOPT.statusbarX,ADVOPT.statusbarY,ADVOPT.statusbarG)),
  56. heroList(new CHeroList(ADVOPT.hlistSize, Point(ADVOPT.hlistX, ADVOPT.hlistY), ADVOPT.hlistAU, ADVOPT.hlistAD)),
  57. townList(new CTownList(ADVOPT.tlistSize, Point(ADVOPT.tlistX, ADVOPT.tlistY), ADVOPT.tlistAU, ADVOPT.tlistAD)),
  58. infoBar(new CInfoBar(Point(ADVOPT.infoboxX, ADVOPT.infoboxY))),
  59. resdatabar(new CResDataBar),
  60. mapAudio(new MapAudioPlayer()),
  61. terrain(new MapView(Point(ADVOPT.advmapX, ADVOPT.advmapY), Point(ADVOPT.advmapW, ADVOPT.advmapH))),
  62. state(EGameState::NOT_INITIALIZED),
  63. spellBeingCasted(nullptr),
  64. currentSelection(nullptr),
  65. activeMapPanel(nullptr),
  66. scrollingCursorSet(false)
  67. {
  68. pos.x = pos.y = 0;
  69. pos.w = GH.screenDimensions().x;
  70. pos.h = GH.screenDimensions().y;
  71. strongInterest = true; // handle all mouse move events to prevent dead mouse move space in fullscreen mode
  72. townList->onSelect = std::bind(&CAdventureMapInterface::selectionChanged,this);
  73. bg = IImage::createFromFile(ADVOPT.mainGraphic);
  74. if(!ADVOPT.worldViewGraphic.empty())
  75. {
  76. bgWorldView = IImage::createFromFile(ADVOPT.worldViewGraphic);
  77. }
  78. else
  79. {
  80. bgWorldView = nullptr;
  81. logGlobal->warn("ADVOPT.worldViewGraphic is empty => bitmap not loaded");
  82. }
  83. if (!bgWorldView)
  84. {
  85. logGlobal->warn("bgWorldView not defined in resolution config; fallback to VWorld.bmp");
  86. bgWorldView = IImage::createFromFile("VWorld.bmp");
  87. }
  88. worldViewIcons = std::make_shared<CAnimation>("VwSymbol");//todo: customize with ADVOPT
  89. worldViewIcons->preload();
  90. for(int g = 0; g < ADVOPT.gemG.size(); ++g)
  91. {
  92. gems.push_back(std::make_shared<CAnimImage>(ADVOPT.gemG[g], 0, 0, ADVOPT.gemX[g], ADVOPT.gemY[g]));
  93. }
  94. auto makeButton = [&](int textID, std::function<void()> callback, config::ButtonInfo info, int key) -> std::shared_ptr<CButton>
  95. {
  96. auto button = std::make_shared<CButton>(Point(info.x, info.y), info.defName, CGI->generaltexth->zelp[textID], callback, key, info.playerColoured);
  97. for(auto image : info.additionalDefs)
  98. button->addImage(image);
  99. return button;
  100. };
  101. kingOverview = makeButton(293, std::bind(&CAdventureMapInterface::fshowOverview,this), ADVOPT.kingOverview, SDLK_k);
  102. underground = makeButton(294, std::bind(&CAdventureMapInterface::fswitchLevel,this), ADVOPT.underground, SDLK_u);
  103. questlog = makeButton(295, std::bind(&CAdventureMapInterface::fshowQuestlog,this), ADVOPT.questlog, SDLK_q);
  104. sleepWake = makeButton(296, std::bind(&CAdventureMapInterface::fsleepWake,this), ADVOPT.sleepWake, SDLK_w);
  105. moveHero = makeButton(297, std::bind(&CAdventureMapInterface::fmoveHero,this), ADVOPT.moveHero, SDLK_m);
  106. spellbook = makeButton(298, std::bind(&CAdventureMapInterface::fshowSpellbok,this), ADVOPT.spellbook, SDLK_c);
  107. advOptions = makeButton(299, std::bind(&CAdventureMapInterface::fadventureOPtions,this), ADVOPT.advOptions, SDLK_a);
  108. sysOptions = makeButton(300, std::bind(&CAdventureMapInterface::fsystemOptions,this), ADVOPT.sysOptions, SDLK_o);
  109. nextHero = makeButton(301, std::bind(&CAdventureMapInterface::fnextHero,this), ADVOPT.nextHero, SDLK_h);
  110. endTurn = makeButton(302, std::bind(&CAdventureMapInterface::fendTurn,this), ADVOPT.endTurn, SDLK_e);
  111. int panelSpaceBottom = GH.screenDimensions().y - resdatabar->pos.h - 4;
  112. panelMain = std::make_shared<CAdvMapPanel>(nullptr, Point(0, 0));
  113. // TODO correct drawing position
  114. panelWorldView = std::make_shared<CAdvMapWorldViewPanel>(worldViewIcons, bgWorldView, Point(heroList->pos.x - 2, 195), panelSpaceBottom, LOCPLINT->playerID);
  115. panelMain->addChildColorableButton(kingOverview);
  116. panelMain->addChildColorableButton(underground);
  117. panelMain->addChildColorableButton(questlog);
  118. panelMain->addChildColorableButton(sleepWake);
  119. panelMain->addChildColorableButton(moveHero);
  120. panelMain->addChildColorableButton(spellbook);
  121. panelMain->addChildColorableButton(advOptions);
  122. panelMain->addChildColorableButton(sysOptions);
  123. panelMain->addChildColorableButton(nextHero);
  124. panelMain->addChildColorableButton(endTurn);
  125. // TODO move configs to resolutions.json, similarly to previous buttons
  126. config::ButtonInfo worldViewBackConfig = config::ButtonInfo();
  127. worldViewBackConfig.defName = "IOK6432.DEF";
  128. worldViewBackConfig.x = GH.screenDimensions().x - 73;
  129. worldViewBackConfig.y = 343 + 195;
  130. worldViewBackConfig.playerColoured = false;
  131. panelWorldView->addChildToPanel(
  132. makeButton(288, std::bind(&CAdventureMapInterface::fworldViewBack,this), worldViewBackConfig, SDLK_ESCAPE), ACTIVATE | DEACTIVATE);
  133. config::ButtonInfo worldViewPuzzleConfig = config::ButtonInfo();
  134. worldViewPuzzleConfig.defName = "VWPUZ.DEF";
  135. worldViewPuzzleConfig.x = GH.screenDimensions().x - 188;
  136. worldViewPuzzleConfig.y = 343 + 195;
  137. worldViewPuzzleConfig.playerColoured = false;
  138. panelWorldView->addChildToPanel( // no help text for this one
  139. std::make_shared<CButton>(Point(worldViewPuzzleConfig.x, worldViewPuzzleConfig.y), worldViewPuzzleConfig.defName, std::pair<std::string, std::string>(),
  140. std::bind(&CPlayerInterface::showPuzzleMap,LOCPLINT), SDLK_p, worldViewPuzzleConfig.playerColoured), ACTIVATE | DEACTIVATE);
  141. config::ButtonInfo worldViewScale1xConfig = config::ButtonInfo();
  142. worldViewScale1xConfig.defName = "VWMAG1.DEF";
  143. worldViewScale1xConfig.x = GH.screenDimensions().x - 191;
  144. worldViewScale1xConfig.y = 23 + 195;
  145. worldViewScale1xConfig.playerColoured = false;
  146. panelWorldView->addChildToPanel( // help text is wrong for this button
  147. makeButton(291, std::bind(&CAdventureMapInterface::fworldViewScale1x,this), worldViewScale1xConfig, SDLK_1), ACTIVATE | DEACTIVATE);
  148. config::ButtonInfo worldViewScale2xConfig = config::ButtonInfo();
  149. worldViewScale2xConfig.defName = "VWMAG2.DEF";
  150. worldViewScale2xConfig.x = GH.screenDimensions().x- 191 + 63;
  151. worldViewScale2xConfig.y = 23 + 195;
  152. worldViewScale2xConfig.playerColoured = false;
  153. panelWorldView->addChildToPanel( // help text is wrong for this button
  154. makeButton(291, std::bind(&CAdventureMapInterface::fworldViewScale2x,this), worldViewScale2xConfig, SDLK_2), ACTIVATE | DEACTIVATE);
  155. config::ButtonInfo worldViewScale4xConfig = config::ButtonInfo();
  156. worldViewScale4xConfig.defName = "VWMAG4.DEF";
  157. worldViewScale4xConfig.x = GH.screenDimensions().x- 191 + 126;
  158. worldViewScale4xConfig.y = 23 + 195;
  159. worldViewScale4xConfig.playerColoured = false;
  160. panelWorldView->addChildToPanel( // help text is wrong for this button
  161. makeButton(291, std::bind(&CAdventureMapInterface::fworldViewScale4x,this), worldViewScale4xConfig, SDLK_4), ACTIVATE | DEACTIVATE);
  162. config::ButtonInfo worldViewUndergroundConfig = config::ButtonInfo();
  163. worldViewUndergroundConfig.defName = "IAM010.DEF";
  164. worldViewUndergroundConfig.additionalDefs.push_back("IAM003.DEF");
  165. worldViewUndergroundConfig.x = GH.screenDimensions().x - 115;
  166. worldViewUndergroundConfig.y = 343 + 195;
  167. worldViewUndergroundConfig.playerColoured = true;
  168. worldViewUnderground = makeButton(294, std::bind(&CAdventureMapInterface::fswitchLevel,this), worldViewUndergroundConfig, SDLK_u);
  169. panelWorldView->addChildColorableButton(worldViewUnderground);
  170. onCurrentPlayerChanged(LOCPLINT->playerID);
  171. int iconColorMultiplier = currentPlayerID.getNum() * 19;
  172. int wvLeft = heroList->pos.x - 2; // TODO correct drawing position
  173. //int wvTop = 195;
  174. for (int i = 0; i < 5; ++i)
  175. {
  176. panelWorldView->addChildIcon(std::pair<int, Point>(i, Point(5, 58 + i * 20)), iconColorMultiplier);
  177. panelWorldView->addChildToPanel(std::make_shared<CLabel>(wvLeft + 45, 263 + i * 20, EFonts::FONT_SMALL, ETextAlignment::TOPLEFT,
  178. Colors::WHITE, CGI->generaltexth->allTexts[612 + i]));
  179. }
  180. for (int i = 0; i < 7; ++i)
  181. {
  182. panelWorldView->addChildIcon(std::pair<int, Point>(i + 5, Point(5, 182 + i * 20)), iconColorMultiplier);
  183. panelWorldView->addChildIcon(std::pair<int, Point>(i + 12, Point(160, 182 + i * 20)), iconColorMultiplier);
  184. panelWorldView->addChildToPanel(std::make_shared<CLabel>(wvLeft + 45, 387 + i * 20, EFonts::FONT_SMALL, ETextAlignment::TOPLEFT,
  185. Colors::WHITE, CGI->generaltexth->allTexts[619 + i]));
  186. }
  187. panelWorldView->addChildToPanel(std::make_shared<CLabel>(wvLeft + 5, 367, EFonts::FONT_SMALL, ETextAlignment::TOPLEFT,
  188. Colors::WHITE, CGI->generaltexth->allTexts[617]));
  189. panelWorldView->addChildToPanel(std::make_shared<CLabel>(wvLeft + 45, 367, EFonts::FONT_SMALL, ETextAlignment::TOPLEFT,
  190. Colors::WHITE, CGI->generaltexth->allTexts[618]));
  191. activeMapPanel = panelMain;
  192. exitWorldView();
  193. underground->block(!CGI->mh->getMap()->twoLevel);
  194. questlog->block(!CGI->mh->getMap()->quests.size());
  195. worldViewUnderground->block(!CGI->mh->getMap()->twoLevel);
  196. }
  197. void CAdventureMapInterface::fshowOverview()
  198. {
  199. GH.pushIntT<CKingdomInterface>();
  200. }
  201. void CAdventureMapInterface::fworldViewBack()
  202. {
  203. exitWorldView();
  204. auto hero = getCurrentHero();
  205. if (hero)
  206. centerOnObject(hero);
  207. }
  208. void CAdventureMapInterface::fworldViewScale1x()
  209. {
  210. // TODO set corresponding scale button to "selected" mode
  211. openWorldView(7);
  212. }
  213. void CAdventureMapInterface::fworldViewScale2x()
  214. {
  215. openWorldView(11);
  216. }
  217. void CAdventureMapInterface::fworldViewScale4x()
  218. {
  219. openWorldView(16);
  220. }
  221. void CAdventureMapInterface::fswitchLevel()
  222. {
  223. // with support for future multi-level maps :)
  224. int maxLevels = CGI->mh->getMap()->levels();
  225. if (maxLevels < 2)
  226. return;
  227. terrain->onMapLevelSwitched();
  228. }
  229. void CAdventureMapInterface::onMapViewMoved(const Rect & visibleArea, int mapLevel)
  230. {
  231. underground->setIndex(mapLevel, true);
  232. underground->redraw();
  233. worldViewUnderground->setIndex(mapLevel, true);
  234. worldViewUnderground->redraw();
  235. minimap->onMapViewMoved(visibleArea, mapLevel);
  236. }
  237. void CAdventureMapInterface::onAudioResumed()
  238. {
  239. mapAudio->onAudioResumed();
  240. }
  241. void CAdventureMapInterface::onAudioPaused()
  242. {
  243. mapAudio->onAudioPaused();
  244. }
  245. void CAdventureMapInterface::fshowQuestlog()
  246. {
  247. LOCPLINT->showQuestLog();
  248. }
  249. void CAdventureMapInterface::fsleepWake()
  250. {
  251. const CGHeroInstance *h = getCurrentHero();
  252. if (!h)
  253. return;
  254. bool newSleep = !isHeroSleeping(h);
  255. setHeroSleeping(h, newSleep);
  256. updateSleepWake(h);
  257. if (newSleep)
  258. {
  259. fnextHero();
  260. //moveHero.block(true);
  261. //uncomment to enable original HoMM3 behaviour:
  262. //move button is disabled for hero going to sleep, even though it's enabled when you reselect him
  263. }
  264. // redraw to update the image of sleep/wake button
  265. panelMain->redraw();
  266. }
  267. void CAdventureMapInterface::fmoveHero()
  268. {
  269. const CGHeroInstance *h = getCurrentHero();
  270. if (!h || !LOCPLINT->localState->hasPath(h) || CGI->mh->hasOngoingAnimations())
  271. return;
  272. LOCPLINT->moveHero(h, LOCPLINT->localState->getPath(h));
  273. }
  274. void CAdventureMapInterface::fshowSpellbok()
  275. {
  276. if (!getCurrentHero()) //checking necessary values
  277. return;
  278. centerOnObject(currentSelection);
  279. GH.pushIntT<CSpellWindow>(getCurrentHero(), LOCPLINT, false);
  280. }
  281. void CAdventureMapInterface::fadventureOPtions()
  282. {
  283. GH.pushIntT<CAdventureOptions>();
  284. }
  285. void CAdventureMapInterface::fsystemOptions()
  286. {
  287. GH.pushIntT<SettingsMainWindow>();
  288. }
  289. void CAdventureMapInterface::fnextHero()
  290. {
  291. auto hero = dynamic_cast<const CGHeroInstance*>(currentSelection);
  292. int next = getNextHeroIndex(vstd::find_pos(LOCPLINT->localState->wanderingHeroes, hero));
  293. if (next < 0)
  294. return;
  295. setSelection(LOCPLINT->localState->wanderingHeroes[next], true);
  296. }
  297. void CAdventureMapInterface::fendTurn()
  298. {
  299. if(!LOCPLINT->makingTurn)
  300. return;
  301. if(settings["adventure"]["heroReminder"].Bool())
  302. {
  303. for(auto hero : LOCPLINT->localState->wanderingHeroes)
  304. {
  305. if(!isHeroSleeping(hero) && hero->movement > 0)
  306. {
  307. // Only show hero reminder if conditions met:
  308. // - There still movement points
  309. // - Hero don't have a path or there not points for first step on path
  310. LOCPLINT->localState->verifyPath(hero);
  311. if(!LOCPLINT->localState->hasPath(hero))
  312. {
  313. LOCPLINT->showYesNoDialog( CGI->generaltexth->allTexts[55], std::bind(&CAdventureMapInterface::endingTurn, this), nullptr );
  314. return;
  315. }
  316. auto path = LOCPLINT->localState->getPath(hero);
  317. if (path.nodes.size() < 2 || path.nodes[path.nodes.size() - 2].turns)
  318. {
  319. LOCPLINT->showYesNoDialog( CGI->generaltexth->allTexts[55], std::bind(&CAdventureMapInterface::endingTurn, this), nullptr );
  320. return;
  321. }
  322. }
  323. }
  324. }
  325. endingTurn();
  326. }
  327. void CAdventureMapInterface::updateSleepWake(const CGHeroInstance *h)
  328. {
  329. sleepWake->block(!h);
  330. if (!h)
  331. return;
  332. bool state = isHeroSleeping(h);
  333. sleepWake->setIndex(state ? 1 : 0, true);
  334. sleepWake->assignedKeys.clear();
  335. sleepWake->assignedKeys.insert(state ? SDLK_w : SDLK_z);
  336. }
  337. void CAdventureMapInterface::updateSpellbook(const CGHeroInstance *h)
  338. {
  339. spellbook->block(!h);
  340. }
  341. int CAdventureMapInterface::getNextHeroIndex(int startIndex)
  342. {
  343. if (LOCPLINT->localState->wanderingHeroes.size() == 0)
  344. return -1;
  345. if (startIndex < 0)
  346. startIndex = 0;
  347. int i = startIndex;
  348. do
  349. {
  350. i++;
  351. if (i >= LOCPLINT->localState->wanderingHeroes.size())
  352. i = 0;
  353. }
  354. while (((LOCPLINT->localState->wanderingHeroes[i]->movement == 0) || isHeroSleeping(LOCPLINT->localState->wanderingHeroes[i])) && (i != startIndex));
  355. if ((LOCPLINT->localState->wanderingHeroes[i]->movement != 0) && !isHeroSleeping(LOCPLINT->localState->wanderingHeroes[i]))
  356. return i;
  357. else
  358. return -1;
  359. }
  360. void CAdventureMapInterface::onHeroChanged(const CGHeroInstance *h)
  361. {
  362. heroList->update(h);
  363. if (h == getCurrentHero())
  364. infoBar->showSelection();
  365. int start = vstd::find_pos(LOCPLINT->localState->wanderingHeroes, h);
  366. int next = getNextHeroIndex(start);
  367. if (next < 0)
  368. {
  369. nextHero->block(true);
  370. return;
  371. }
  372. const CGHeroInstance *nextH = LOCPLINT->localState->wanderingHeroes[next];
  373. bool noActiveHeroes = (next == start) && ((nextH->movement == 0) || isHeroSleeping(nextH));
  374. nextHero->block(noActiveHeroes);
  375. if(!h)
  376. {
  377. moveHero->block(true);
  378. return;
  379. }
  380. //default value is for everywhere but CPlayerInterface::moveHero, because paths are not updated from there immediately
  381. bool hasPath = LOCPLINT->localState->hasPath(h);
  382. moveHero->block(!(bool)hasPath || (h->movement == 0));
  383. }
  384. void CAdventureMapInterface::onTownChanged(const CGTownInstance * town)
  385. {
  386. townList->update(town);
  387. infoBar->showSelection();
  388. }
  389. void CAdventureMapInterface::showInfoBoxMessage(const std::vector<Component> & components, std::string message, int timer)
  390. {
  391. infoBar->pushComponents(components, message, timer);
  392. }
  393. void CAdventureMapInterface::activate()
  394. {
  395. CIntObject::activate();
  396. if (!(active & KEYBOARD))
  397. CIntObject::activate(KEYBOARD);
  398. screenBuf = screen;
  399. GH.statusbar = statusbar;
  400. if(LOCPLINT)
  401. {
  402. LOCPLINT->cingconsole->activate();
  403. LOCPLINT->cingconsole->pos = this->pos;
  404. }
  405. if(state != EGameState::ENEMY_TURN)
  406. {
  407. assert(state == EGameState::MAKING_TURN);
  408. activeMapPanel->activate();
  409. if (state == EGameState::MAKING_TURN)
  410. {
  411. heroList->activate();
  412. townList->activate();
  413. infoBar->activate();
  414. }
  415. minimap->activate();
  416. terrain->activate();
  417. statusbar->activate();
  418. GH.fakeMouseMove(); //to restore the cursor
  419. }
  420. }
  421. void CAdventureMapInterface::deactivate()
  422. {
  423. CIntObject::deactivate();
  424. if(state != EGameState::ENEMY_TURN)
  425. {
  426. assert(state == EGameState::MAKING_TURN);
  427. CCS->curh->set(Cursor::Map::POINTER);
  428. activeMapPanel->deactivate();
  429. if (state == EGameState::MAKING_TURN)
  430. {
  431. heroList->deactivate();
  432. townList->deactivate();
  433. infoBar->deactivate();
  434. }
  435. minimap->deactivate();
  436. terrain->deactivate();
  437. statusbar->deactivate();
  438. }
  439. }
  440. void CAdventureMapInterface::showAll(SDL_Surface * to)
  441. {
  442. bg->draw(to, 0, 0);
  443. if(state != EGameState::MAKING_TURN)
  444. return;
  445. heroList->showAll(to);
  446. townList->showAll(to);
  447. infoBar->showAll(to);
  448. activeMapPanel->showAll(to);
  449. minimap->showAll(to);
  450. terrain->showAll(to);
  451. show(to);
  452. resdatabar->showAll(to);
  453. statusbar->show(to);
  454. LOCPLINT->cingconsole->show(to);
  455. }
  456. bool CAdventureMapInterface::isHeroSleeping(const CGHeroInstance *hero)
  457. {
  458. if (!hero)
  459. return false;
  460. return vstd::contains(LOCPLINT->localState->sleepingHeroes, hero);
  461. }
  462. void CAdventureMapInterface::onHeroWokeUp(const CGHeroInstance * hero)
  463. {
  464. if (!isHeroSleeping(hero))
  465. return;
  466. sleepWake->clickLeft(true, false);
  467. sleepWake->clickLeft(false, true);
  468. //could've just called
  469. //fsleepWake();
  470. //but no authentic button click/sound ;-)
  471. }
  472. void CAdventureMapInterface::setHeroSleeping(const CGHeroInstance *hero, bool sleep)
  473. {
  474. if (sleep)
  475. LOCPLINT->localState->sleepingHeroes.push_back(hero); //FIXME: should we check for existence?
  476. else
  477. LOCPLINT->localState->sleepingHeroes -= hero;
  478. onHeroChanged(hero);
  479. }
  480. void CAdventureMapInterface::show(SDL_Surface * to)
  481. {
  482. if(state != EGameState::MAKING_TURN)
  483. return;
  484. handleMapScrollingUpdate();
  485. for(int i = 0; i < 4; i++)
  486. {
  487. if(settings["session"]["spectate"].Bool())
  488. gems[i]->setFrame(PlayerColor(1).getNum());
  489. else
  490. gems[i]->setFrame(LOCPLINT->playerID.getNum());
  491. }
  492. minimap->show(to);
  493. terrain->show(to);
  494. for(int i = 0; i < 4; i++)
  495. gems[i]->showAll(to);
  496. LOCPLINT->cingconsole->show(to);
  497. infoBar->show(to);
  498. statusbar->showAll(to);
  499. }
  500. void CAdventureMapInterface::handleMapScrollingUpdate()
  501. {
  502. uint32_t timePassed = GH.mainFPSmng->getElapsedMilliseconds();
  503. uint32_t scrollSpeedPixels = settings["adventure"]["scrollSpeedPixels"].Float();
  504. uint32_t scrollDistance = scrollSpeedPixels * timePassed / 1000;
  505. bool scrollingActive = !GH.isKeyboardCtrlDown() && isActive() && state == EGameState::MAKING_TURN;
  506. Point cursorPosition = GH.getCursorPosition();
  507. Point scrollDirection;
  508. if (cursorPosition.x < 15)
  509. scrollDirection.x = -1;
  510. if (cursorPosition.x > GH.screenDimensions().x - 15)
  511. scrollDirection.x = +1;
  512. if (cursorPosition.y < 15)
  513. scrollDirection.y = -1;
  514. if (cursorPosition.y > GH.screenDimensions().y - 15)
  515. scrollDirection.y = +1;
  516. Point scrollDelta = scrollDirection * scrollDistance;
  517. if (scrollingActive && scrollDelta != Point(0,0))
  518. terrain->onMapScrolled(scrollDelta);
  519. if (scrollDelta == Point(0,0) && !scrollingCursorSet)
  520. return;
  521. if(scrollDelta.x > 0)
  522. {
  523. if(scrollDelta.y < 0)
  524. CCS->curh->set(Cursor::Map::SCROLL_NORTHEAST);
  525. if(scrollDelta.y > 0)
  526. CCS->curh->set(Cursor::Map::SCROLL_SOUTHEAST);
  527. if(scrollDelta.y == 0)
  528. CCS->curh->set(Cursor::Map::SCROLL_EAST);
  529. }
  530. if(scrollDelta.x < 0)
  531. {
  532. if(scrollDelta.y < 0)
  533. CCS->curh->set(Cursor::Map::SCROLL_NORTHWEST);
  534. if(scrollDelta.y > 0)
  535. CCS->curh->set(Cursor::Map::SCROLL_SOUTHWEST);
  536. if(scrollDelta.y == 0)
  537. CCS->curh->set(Cursor::Map::SCROLL_WEST);
  538. }
  539. if (scrollDelta.x == 0)
  540. {
  541. if(scrollDelta.y < 0)
  542. CCS->curh->set(Cursor::Map::SCROLL_NORTH);
  543. if(scrollDelta.y > 0)
  544. CCS->curh->set(Cursor::Map::SCROLL_SOUTH);
  545. if(scrollDelta.y == 0)
  546. CCS->curh->set(Cursor::Map::POINTER);
  547. }
  548. }
  549. void CAdventureMapInterface::selectionChanged()
  550. {
  551. const CGTownInstance *to = LOCPLINT->localState->ownedTowns[townList->getSelectedIndex()];
  552. if (currentSelection != to)
  553. setSelection(to);
  554. }
  555. void CAdventureMapInterface::centerOnTile(int3 on)
  556. {
  557. terrain->onCenteredTile(on);
  558. }
  559. void CAdventureMapInterface::centerOnObject(const CGObjectInstance * obj)
  560. {
  561. terrain->onCenteredObject(obj);
  562. }
  563. void CAdventureMapInterface::keyPressed(const SDL_Keycode & key)
  564. {
  565. if (state != EGameState::MAKING_TURN)
  566. return;
  567. const CGHeroInstance *h = getCurrentHero(); //selected hero
  568. const CGTownInstance *t = getCurrentTown(); //selected town
  569. switch(key)
  570. {
  571. case SDLK_g:
  572. if(GH.topInt()->type & BLOCK_ADV_HOTKEYS)
  573. return;
  574. {
  575. //find first town with tavern
  576. auto itr = range::find_if(LOCPLINT->localState->ownedTowns, [](const CGTownInstance * town)
  577. {
  578. return town->hasBuilt(BuildingID::TAVERN);
  579. });
  580. if(itr != LOCPLINT->localState->ownedTowns.end())
  581. LOCPLINT->showThievesGuildWindow(*itr);
  582. else
  583. LOCPLINT->showInfoDialog(CGI->generaltexth->translate("vcmi.adventureMap.noTownWithTavern"));
  584. }
  585. return;
  586. case SDLK_i:
  587. if(isActive())
  588. CAdventureOptions::showScenarioInfo();
  589. return;
  590. case SDLK_s:
  591. if(isActive())
  592. GH.pushIntT<CSavingScreen>();
  593. return;
  594. case SDLK_l:
  595. if(isActive())
  596. LOCPLINT->proposeLoadingGame();
  597. return;
  598. case SDLK_d:
  599. {
  600. if(h && isActive() && LOCPLINT->makingTurn)
  601. LOCPLINT->tryDiggging(h);
  602. return;
  603. }
  604. case SDLK_p:
  605. if(isActive())
  606. LOCPLINT->showPuzzleMap();
  607. return;
  608. case SDLK_v:
  609. if(isActive())
  610. LOCPLINT->viewWorldMap();
  611. return;
  612. case SDLK_r:
  613. if(isActive() && GH.isKeyboardCtrlDown())
  614. {
  615. LOCPLINT->showYesNoDialog(CGI->generaltexth->translate("vcmi.adventureMap.confirmRestartGame"),
  616. [](){ GH.pushUserEvent(EUserEvent::RESTART_GAME); }, nullptr);
  617. }
  618. return;
  619. case SDLK_SPACE: //space - try to revisit current object with selected hero
  620. {
  621. if(!isActive())
  622. return;
  623. if(h)
  624. {
  625. LOCPLINT->cb->moveHero(h,h->pos);
  626. }
  627. }
  628. return;
  629. case SDLK_RETURN:
  630. {
  631. if(!isActive() || !currentSelection)
  632. return;
  633. if(h)
  634. LOCPLINT->openHeroWindow(h);
  635. else if(t)
  636. LOCPLINT->openTownWindow(t);
  637. return;
  638. }
  639. case SDLK_ESCAPE:
  640. {
  641. //FIXME: this case is never executed since AdvMapInt is disabled while in spellcasting mode
  642. if(!isActive() || GH.topInt().get() != this || !spellBeingCasted)
  643. return;
  644. abortCastingMode();
  645. return;
  646. }
  647. case SDLK_t:
  648. {
  649. //act on key down if marketplace windows is not already opened
  650. if(GH.topInt()->type & BLOCK_ADV_HOTKEYS)
  651. return;
  652. if(GH.isKeyboardCtrlDown()) //CTRL + T => open marketplace
  653. {
  654. //check if we have any marketplace
  655. const CGTownInstance *townWithMarket = nullptr;
  656. for(const CGTownInstance *t : LOCPLINT->cb->getTownsInfo())
  657. {
  658. if(t->hasBuilt(BuildingID::MARKETPLACE))
  659. {
  660. townWithMarket = t;
  661. break;
  662. }
  663. }
  664. if(townWithMarket) //if any town has marketplace, open window
  665. GH.pushIntT<CMarketplaceWindow>(townWithMarket);
  666. else //if not - complain
  667. LOCPLINT->showInfoDialog(CGI->generaltexth->translate("vcmi.adventureMap.noTownWithMarket"));
  668. }
  669. else if(isActive()) //no ctrl, advmapint is on the top => switch to town
  670. {
  671. townList->selectNext();
  672. }
  673. return;
  674. }
  675. case SDLK_LALT:
  676. case SDLK_RALT:
  677. {
  678. //fake mouse use to trigger onTileHovered()
  679. GH.fakeMouseMove();
  680. return;
  681. }
  682. default:
  683. {
  684. auto direction = keyToMoveDirection(key);
  685. if (!direction)
  686. return;
  687. if(!h || !isActive())
  688. return;
  689. if (CGI->mh->hasOngoingAnimations())
  690. return;
  691. if(*direction == Point(0,0))
  692. {
  693. centerOnObject(h);
  694. return;
  695. }
  696. int3 dst = h->visitablePos() + int3(direction->x, direction->y, 0);
  697. if (!CGI->mh->isInMap((dst)))
  698. return;
  699. if ( !LOCPLINT->localState->setPath(h, dst))
  700. return;
  701. const CGPath & path = LOCPLINT->localState->getPath(h);
  702. if (path.nodes.size() > 2)
  703. onHeroChanged(h);
  704. else
  705. if(!path.nodes[0].turns)
  706. LOCPLINT->moveHero(h, path);
  707. }
  708. return;
  709. }
  710. }
  711. std::optional<Point> CAdventureMapInterface::keyToMoveDirection(const SDL_Keycode & key)
  712. {
  713. switch (key) {
  714. case SDLK_DOWN: return Point( 0, +1);
  715. case SDLK_LEFT: return Point(-1, 0);
  716. case SDLK_RIGHT: return Point(+1, 0);
  717. case SDLK_UP: return Point( 0, -1);
  718. case SDLK_KP_1: return Point(-1, +1);
  719. case SDLK_KP_2: return Point( 0, +1);
  720. case SDLK_KP_3: return Point(+1, +1);
  721. case SDLK_KP_4: return Point(-1, 0);
  722. case SDLK_KP_5: return Point( 0, 0);
  723. case SDLK_KP_6: return Point(+1, 0);
  724. case SDLK_KP_7: return Point(-1, -1);
  725. case SDLK_KP_8: return Point( 0, -1);
  726. case SDLK_KP_9: return Point(+1, -1);
  727. }
  728. return std::nullopt;
  729. }
  730. void CAdventureMapInterface::setSelection(const CArmedInstance *sel, bool centerView)
  731. {
  732. assert(sel);
  733. if(currentSelection != sel)
  734. infoBar->popAll();
  735. currentSelection = sel;
  736. mapAudio->onSelectionChanged(sel);
  737. if(centerView)
  738. centerOnObject(sel);
  739. if(sel->ID==Obj::TOWN)
  740. {
  741. auto town = dynamic_cast<const CGTownInstance*>(sel);
  742. infoBar->showTownSelection(town);
  743. townList->select(town);
  744. heroList->select(nullptr);
  745. updateSleepWake(nullptr);
  746. onHeroChanged(nullptr);
  747. updateSpellbook(nullptr);
  748. }
  749. else //hero selected
  750. {
  751. auto hero = dynamic_cast<const CGHeroInstance*>(sel);
  752. infoBar->showHeroSelection(hero);
  753. heroList->select(hero);
  754. townList->select(nullptr);
  755. LOCPLINT->localState->verifyPath(hero);
  756. updateSleepWake(hero);
  757. onHeroChanged(hero);
  758. updateSpellbook(hero);
  759. }
  760. townList->redraw();
  761. heroList->redraw();
  762. }
  763. bool CAdventureMapInterface::isActive()
  764. {
  765. return active & ~CIntObject::KEYBOARD;
  766. }
  767. void CAdventureMapInterface::onMapTilesChanged(boost::optional<std::unordered_set<int3>> positions)
  768. {
  769. if (positions)
  770. minimap->updateTiles(*positions);
  771. else
  772. minimap->update();
  773. }
  774. void CAdventureMapInterface::onHotseatWaitStarted(PlayerColor playerID)
  775. {
  776. onCurrentPlayerChanged(playerID);
  777. state = EGameState::HOTSEAT_WAIT;
  778. }
  779. void CAdventureMapInterface::onEnemyTurnStarted(PlayerColor playerID)
  780. {
  781. if(settings["session"]["spectate"].Bool())
  782. return;
  783. adjustActiveness(true);
  784. mapAudio->onEnemyTurnStarted();
  785. minimap->setAIRadar(true);
  786. infoBar->startEnemyTurn(LOCPLINT->cb->getCurrentPlayer());
  787. minimap->showAll(screen);//force refresh on inactive object
  788. infoBar->showAll(screen);//force refresh on inactive object
  789. }
  790. void CAdventureMapInterface::adjustActiveness(bool aiTurnStart)
  791. {
  792. bool wasActive = isActive();
  793. if(wasActive)
  794. deactivate();
  795. if (aiTurnStart)
  796. state = EGameState::ENEMY_TURN;
  797. else
  798. state = EGameState::MAKING_TURN;
  799. if(wasActive)
  800. activate();
  801. }
  802. void CAdventureMapInterface::onCurrentPlayerChanged(PlayerColor playerID)
  803. {
  804. currentSelection = nullptr;
  805. if (playerID == currentPlayerID)
  806. return;
  807. currentPlayerID = playerID;
  808. bg->playerColored(currentPlayerID);
  809. panelMain->setPlayerColor(currentPlayerID);
  810. panelWorldView->setPlayerColor(currentPlayerID);
  811. panelWorldView->recolorIcons(currentPlayerID, currentPlayerID.getNum() * 19);
  812. resdatabar->colorize(currentPlayerID);
  813. }
  814. void CAdventureMapInterface::onPlayerTurnStarted(PlayerColor playerID)
  815. {
  816. onCurrentPlayerChanged(playerID);
  817. state = EGameState::MAKING_TURN;
  818. if(LOCPLINT->cb->getCurrentPlayer() == LOCPLINT->playerID
  819. || settings["session"]["spectate"].Bool())
  820. {
  821. adjustActiveness(false);
  822. minimap->setAIRadar(false);
  823. infoBar->showSelection();
  824. }
  825. heroList->update();
  826. townList->update();
  827. const CGHeroInstance * heroToSelect = nullptr;
  828. // find first non-sleeping hero
  829. for (auto hero : LOCPLINT->localState->wanderingHeroes)
  830. {
  831. if (boost::range::find(LOCPLINT->localState->sleepingHeroes, hero) == LOCPLINT->localState->sleepingHeroes.end())
  832. {
  833. heroToSelect = hero;
  834. break;
  835. }
  836. }
  837. bool centerView = !settings["session"]["autoSkip"].Bool();
  838. //select first hero if available.
  839. if (heroToSelect != nullptr)
  840. {
  841. setSelection(heroToSelect, centerView);
  842. }
  843. else if (LOCPLINT->localState->ownedTowns.size())
  844. setSelection(LOCPLINT->localState->ownedTowns.front(), centerView);
  845. else
  846. setSelection(LOCPLINT->localState->wanderingHeroes.front());
  847. //show new day animation and sound on infobar
  848. infoBar->showDate();
  849. onHeroChanged(nullptr);
  850. showAll(screen);
  851. mapAudio->onPlayerTurnStarted();
  852. if(settings["session"]["autoSkip"].Bool() && !GH.isKeyboardShiftDown())
  853. {
  854. if(CInfoWindow *iw = dynamic_cast<CInfoWindow *>(GH.topInt().get()))
  855. iw->close();
  856. endingTurn();
  857. }
  858. }
  859. void CAdventureMapInterface::endingTurn()
  860. {
  861. if(settings["session"]["spectate"].Bool())
  862. return;
  863. LOCPLINT->makingTurn = false;
  864. LOCPLINT->cb->endTurn();
  865. mapAudio->onPlayerTurnEnded();
  866. }
  867. const CGObjectInstance* CAdventureMapInterface::getActiveObject(const int3 &mapPos)
  868. {
  869. std::vector < const CGObjectInstance * > bobjs = LOCPLINT->cb->getBlockingObjs(mapPos); //blocking objects at tile
  870. if (bobjs.empty())
  871. return nullptr;
  872. return *boost::range::max_element(bobjs, &CMapHandler::compareObjectBlitOrder);
  873. /*
  874. if (bobjs.back()->ID == Obj::HERO)
  875. return bobjs.back();
  876. else
  877. return bobjs.front();*/
  878. }
  879. void CAdventureMapInterface::onTileLeftClicked(const int3 &mapPos)
  880. {
  881. if(state != EGameState::MAKING_TURN)
  882. return;
  883. //FIXME: this line breaks H3 behavior for Dimension Door
  884. if(!LOCPLINT->cb->isVisible(mapPos))
  885. return;
  886. if(!LOCPLINT->makingTurn)
  887. return;
  888. const TerrainTile *tile = LOCPLINT->cb->getTile(mapPos);
  889. const CGObjectInstance *topBlocking = getActiveObject(mapPos);
  890. int3 selPos = currentSelection->getSightCenter();
  891. if(spellBeingCasted && isInScreenRange(selPos, mapPos))
  892. {
  893. const TerrainTile *heroTile = LOCPLINT->cb->getTile(selPos);
  894. switch(spellBeingCasted->id)
  895. {
  896. case SpellID::SCUTTLE_BOAT: //Scuttle Boat
  897. if(topBlocking && topBlocking->ID == Obj::BOAT)
  898. leaveCastingMode(mapPos);
  899. break;
  900. case SpellID::DIMENSION_DOOR:
  901. if(!tile || tile->isClear(heroTile))
  902. leaveCastingMode(mapPos);
  903. break;
  904. }
  905. return;
  906. }
  907. //check if we can select this object
  908. bool canSelect = topBlocking && topBlocking->ID == Obj::HERO && topBlocking->tempOwner == LOCPLINT->playerID;
  909. canSelect |= topBlocking && topBlocking->ID == Obj::TOWN && LOCPLINT->cb->getPlayerRelations(LOCPLINT->playerID, topBlocking->tempOwner);
  910. bool isHero = false;
  911. if(currentSelection->ID != Obj::HERO) //hero is not selected (presumably town)
  912. {
  913. if(currentSelection == topBlocking) //selected town clicked
  914. LOCPLINT->openTownWindow(static_cast<const CGTownInstance*>(topBlocking));
  915. else if(canSelect)
  916. setSelection(static_cast<const CArmedInstance*>(topBlocking), false);
  917. }
  918. else if(const CGHeroInstance * currentHero = getCurrentHero()) //hero is selected
  919. {
  920. isHero = true;
  921. const CGPathNode *pn = LOCPLINT->cb->getPathsInfo(currentHero)->getPathInfo(mapPos);
  922. if(currentHero == topBlocking) //clicked selected hero
  923. {
  924. LOCPLINT->openHeroWindow(currentHero);
  925. return;
  926. }
  927. else if(canSelect && pn->turns == 255 ) //selectable object at inaccessible tile
  928. {
  929. setSelection(static_cast<const CArmedInstance*>(topBlocking), false);
  930. return;
  931. }
  932. else //still here? we need to move hero if we clicked end of already selected path or calculate a new path otherwise
  933. {
  934. if(LOCPLINT->localState->hasPath(currentHero) &&
  935. LOCPLINT->localState->getPath(currentHero).endPos() == mapPos)//we'll be moving
  936. {
  937. if(!CGI->mh->hasOngoingAnimations())
  938. LOCPLINT->moveHero(currentHero, LOCPLINT->localState->getPath(currentHero));
  939. return;
  940. }
  941. else //remove old path and find a new one if we clicked on accessible tile
  942. {
  943. LOCPLINT->localState->setPath(currentHero, mapPos);
  944. onHeroChanged(currentHero);
  945. }
  946. }
  947. } //end of hero is selected "case"
  948. else
  949. {
  950. throw std::runtime_error("Nothing is selected...");
  951. }
  952. const auto shipyard = ourInaccessibleShipyard(topBlocking);
  953. if(isHero && shipyard != nullptr)
  954. {
  955. LOCPLINT->showShipyardDialogOrProblemPopup(shipyard);
  956. }
  957. }
  958. void CAdventureMapInterface::onTileHovered(const int3 &mapPos)
  959. {
  960. if(state == EGameState::MAKING_TURN)
  961. return;
  962. if(!currentSelection) //may occur just at the start of game (fake move before full intiialization)
  963. return;
  964. if(!LOCPLINT->cb->isVisible(mapPos))
  965. {
  966. CCS->curh->set(Cursor::Map::POINTER);
  967. statusbar->clear();
  968. return;
  969. }
  970. auto objRelations = PlayerRelations::ALLIES;
  971. const CGObjectInstance *objAtTile = getActiveObject(mapPos);
  972. if(objAtTile)
  973. {
  974. objRelations = LOCPLINT->cb->getPlayerRelations(LOCPLINT->playerID, objAtTile->tempOwner);
  975. std::string text = getCurrentHero() ? objAtTile->getHoverText(getCurrentHero()) : objAtTile->getHoverText(LOCPLINT->playerID);
  976. boost::replace_all(text,"\n"," ");
  977. statusbar->write(text);
  978. }
  979. else
  980. {
  981. std::string hlp = CGI->mh->getTerrainDescr(mapPos, false);
  982. statusbar->write(hlp);
  983. }
  984. if(spellBeingCasted)
  985. {
  986. switch(spellBeingCasted->id)
  987. {
  988. case SpellID::SCUTTLE_BOAT:
  989. if(objAtTile && objAtTile->ID == Obj::BOAT)
  990. CCS->curh->set(Cursor::Map::SCUTTLE_BOAT);
  991. else
  992. CCS->curh->set(Cursor::Map::POINTER);
  993. return;
  994. case SpellID::DIMENSION_DOOR:
  995. {
  996. const TerrainTile * t = LOCPLINT->cb->getTile(mapPos, false);
  997. int3 hpos = currentSelection->getSightCenter();
  998. if((!t || t->isClear(LOCPLINT->cb->getTile(hpos))) && isInScreenRange(hpos, mapPos))
  999. CCS->curh->set(Cursor::Map::TELEPORT);
  1000. else
  1001. CCS->curh->set(Cursor::Map::POINTER);
  1002. return;
  1003. }
  1004. }
  1005. }
  1006. if(currentSelection->ID == Obj::TOWN)
  1007. {
  1008. if(objAtTile)
  1009. {
  1010. if(objAtTile->ID == Obj::TOWN && objRelations != PlayerRelations::ENEMIES)
  1011. CCS->curh->set(Cursor::Map::TOWN);
  1012. else if(objAtTile->ID == Obj::HERO && objRelations == PlayerRelations::SAME_PLAYER)
  1013. CCS->curh->set(Cursor::Map::HERO);
  1014. else
  1015. CCS->curh->set(Cursor::Map::POINTER);
  1016. }
  1017. else
  1018. CCS->curh->set(Cursor::Map::POINTER);
  1019. }
  1020. else if(const CGHeroInstance * hero = getCurrentHero())
  1021. {
  1022. std::array<Cursor::Map, 4> cursorMove = { Cursor::Map::T1_MOVE, Cursor::Map::T2_MOVE, Cursor::Map::T3_MOVE, Cursor::Map::T4_MOVE, };
  1023. std::array<Cursor::Map, 4> cursorAttack = { Cursor::Map::T1_ATTACK, Cursor::Map::T2_ATTACK, Cursor::Map::T3_ATTACK, Cursor::Map::T4_ATTACK, };
  1024. std::array<Cursor::Map, 4> cursorSail = { Cursor::Map::T1_SAIL, Cursor::Map::T2_SAIL, Cursor::Map::T3_SAIL, Cursor::Map::T4_SAIL, };
  1025. std::array<Cursor::Map, 4> cursorDisembark = { Cursor::Map::T1_DISEMBARK, Cursor::Map::T2_DISEMBARK, Cursor::Map::T3_DISEMBARK, Cursor::Map::T4_DISEMBARK, };
  1026. std::array<Cursor::Map, 4> cursorExchange = { Cursor::Map::T1_EXCHANGE, Cursor::Map::T2_EXCHANGE, Cursor::Map::T3_EXCHANGE, Cursor::Map::T4_EXCHANGE, };
  1027. std::array<Cursor::Map, 4> cursorVisit = { Cursor::Map::T1_VISIT, Cursor::Map::T2_VISIT, Cursor::Map::T3_VISIT, Cursor::Map::T4_VISIT, };
  1028. std::array<Cursor::Map, 4> cursorSailVisit = { Cursor::Map::T1_SAIL_VISIT, Cursor::Map::T2_SAIL_VISIT, Cursor::Map::T3_SAIL_VISIT, Cursor::Map::T4_SAIL_VISIT, };
  1029. const CGPathNode * pathNode = LOCPLINT->cb->getPathsInfo(hero)->getPathInfo(mapPos);
  1030. assert(pathNode);
  1031. if((GH.isKeyboardAltDown() || settings["gameTweaks"]["forceMovementInfo"].Bool()) && pathNode->reachable()) //overwrite status bar text with movement info
  1032. {
  1033. showMoveDetailsInStatusbar(*hero, *pathNode);
  1034. }
  1035. int turns = pathNode->turns;
  1036. vstd::amin(turns, 3);
  1037. switch(pathNode->action)
  1038. {
  1039. case CGPathNode::NORMAL:
  1040. case CGPathNode::TELEPORT_NORMAL:
  1041. if(pathNode->layer == EPathfindingLayer::LAND)
  1042. CCS->curh->set(cursorMove[turns]);
  1043. else
  1044. CCS->curh->set(cursorSailVisit[turns]);
  1045. break;
  1046. case CGPathNode::VISIT:
  1047. case CGPathNode::BLOCKING_VISIT:
  1048. case CGPathNode::TELEPORT_BLOCKING_VISIT:
  1049. if(objAtTile && objAtTile->ID == Obj::HERO)
  1050. {
  1051. if(currentSelection == objAtTile)
  1052. CCS->curh->set(Cursor::Map::HERO);
  1053. else
  1054. CCS->curh->set(cursorExchange[turns]);
  1055. }
  1056. else if(pathNode->layer == EPathfindingLayer::LAND)
  1057. CCS->curh->set(cursorVisit[turns]);
  1058. else
  1059. CCS->curh->set(cursorSailVisit[turns]);
  1060. break;
  1061. case CGPathNode::BATTLE:
  1062. case CGPathNode::TELEPORT_BATTLE:
  1063. CCS->curh->set(cursorAttack[turns]);
  1064. break;
  1065. case CGPathNode::EMBARK:
  1066. CCS->curh->set(cursorSail[turns]);
  1067. break;
  1068. case CGPathNode::DISEMBARK:
  1069. CCS->curh->set(cursorDisembark[turns]);
  1070. break;
  1071. default:
  1072. if(objAtTile && objRelations != PlayerRelations::ENEMIES)
  1073. {
  1074. if(objAtTile->ID == Obj::TOWN)
  1075. CCS->curh->set(Cursor::Map::TOWN);
  1076. else if(objAtTile->ID == Obj::HERO && objRelations == PlayerRelations::SAME_PLAYER)
  1077. CCS->curh->set(Cursor::Map::HERO);
  1078. else
  1079. CCS->curh->set(Cursor::Map::POINTER);
  1080. }
  1081. else
  1082. CCS->curh->set(Cursor::Map::POINTER);
  1083. break;
  1084. }
  1085. }
  1086. if(ourInaccessibleShipyard(objAtTile))
  1087. {
  1088. CCS->curh->set(Cursor::Map::T1_SAIL);
  1089. }
  1090. }
  1091. void CAdventureMapInterface::showMoveDetailsInStatusbar(const CGHeroInstance & hero, const CGPathNode & pathNode)
  1092. {
  1093. const int maxMovementPointsAtStartOfLastTurn = pathNode.turns > 0 ? hero.maxMovePoints(pathNode.layer == EPathfindingLayer::LAND) : hero.movement;
  1094. const int movementPointsLastTurnCost = maxMovementPointsAtStartOfLastTurn - pathNode.moveRemains;
  1095. const int remainingPointsAfterMove = pathNode.turns == 0 ? pathNode.moveRemains : 0;
  1096. std::string result = VLC->generaltexth->translate("vcmi.adventureMap", pathNode.turns > 0 ? "moveCostDetails" : "moveCostDetailsNoTurns");
  1097. boost::replace_first(result, "%TURNS", std::to_string(pathNode.turns));
  1098. boost::replace_first(result, "%POINTS", std::to_string(movementPointsLastTurnCost));
  1099. boost::replace_first(result, "%REMAINING", std::to_string(remainingPointsAfterMove));
  1100. statusbar->write(result);
  1101. }
  1102. void CAdventureMapInterface::onTileRightClicked(const int3 &mapPos)
  1103. {
  1104. if(state != EGameState::MAKING_TURN)
  1105. return;
  1106. if(spellBeingCasted)
  1107. {
  1108. abortCastingMode();
  1109. return;
  1110. }
  1111. if(!LOCPLINT->cb->isVisible(mapPos))
  1112. {
  1113. CRClickPopup::createAndPush(VLC->generaltexth->allTexts[61]); //Uncharted Territory
  1114. return;
  1115. }
  1116. const CGObjectInstance * obj = getActiveObject(mapPos);
  1117. if(!obj)
  1118. {
  1119. // Bare or undiscovered terrain
  1120. const TerrainTile * tile = LOCPLINT->cb->getTile(mapPos);
  1121. if(tile)
  1122. {
  1123. std::string hlp = CGI->mh->getTerrainDescr(mapPos, true);
  1124. CRClickPopup::createAndPush(hlp);
  1125. }
  1126. return;
  1127. }
  1128. CRClickPopup::createAndPush(obj, GH.getCursorPosition(), ETextAlignment::CENTER);
  1129. }
  1130. void CAdventureMapInterface::enterCastingMode(const CSpell * sp)
  1131. {
  1132. assert(sp->id == SpellID::SCUTTLE_BOAT || sp->id == SpellID::DIMENSION_DOOR);
  1133. spellBeingCasted = sp;
  1134. deactivate();
  1135. terrain->activate();
  1136. GH.fakeMouseMove();
  1137. }
  1138. void CAdventureMapInterface::abortCastingMode()
  1139. {
  1140. assert(spellBeingCasted);
  1141. spellBeingCasted = nullptr;
  1142. terrain->deactivate();
  1143. activate();
  1144. }
  1145. void CAdventureMapInterface::leaveCastingMode(const int3 & dest)
  1146. {
  1147. SpellID id = spellBeingCasted->id;
  1148. abortCastingMode();
  1149. // if(cast)
  1150. LOCPLINT->cb->castSpell(getCurrentHero(), id, dest);
  1151. // else
  1152. // LOCPLINT->showInfoDialog(CGI->generaltexth->allTexts[731]); //Spell cancelled
  1153. }
  1154. const CGHeroInstance * CAdventureMapInterface::getCurrentHero() const
  1155. {
  1156. if(currentSelection && currentSelection->ID == Obj::HERO)
  1157. return dynamic_cast<const CGHeroInstance *>(currentSelection);
  1158. else
  1159. return nullptr;
  1160. }
  1161. const CGTownInstance * CAdventureMapInterface::getCurrentTown() const
  1162. {
  1163. if(currentSelection && currentSelection->ID == Obj::TOWN)
  1164. return dynamic_cast<const CGTownInstance *>(currentSelection);
  1165. else
  1166. return nullptr;
  1167. }
  1168. const CArmedInstance * CAdventureMapInterface::getCurrentArmy() const
  1169. {
  1170. if (currentSelection)
  1171. return dynamic_cast<const CArmedInstance *>(currentSelection);
  1172. else
  1173. return nullptr;
  1174. }
  1175. Rect CAdventureMapInterface::terrainAreaPixels() const
  1176. {
  1177. return terrain->pos;
  1178. }
  1179. const IShipyard * CAdventureMapInterface::ourInaccessibleShipyard(const CGObjectInstance *obj) const
  1180. {
  1181. const IShipyard *ret = IShipyard::castFrom(obj);
  1182. if(!ret ||
  1183. obj->tempOwner != currentPlayerID ||
  1184. (CCS->curh->get<Cursor::Map>() != Cursor::Map::T1_SAIL && CCS->curh->get<Cursor::Map>() != Cursor::Map::POINTER))
  1185. return nullptr;
  1186. return ret;
  1187. }
  1188. void CAdventureMapInterface::exitWorldView()
  1189. {
  1190. state = EGameState::MAKING_TURN;
  1191. panelMain->activate();
  1192. panelWorldView->deactivate();
  1193. activeMapPanel = panelMain;
  1194. townList->activate();
  1195. heroList->activate();
  1196. infoBar->activate();
  1197. redraw();
  1198. terrain->onViewMapActivated();
  1199. }
  1200. void CAdventureMapInterface::openWorldView(int tileSize)
  1201. {
  1202. state = EGameState::WORLD_VIEW;
  1203. panelMain->deactivate();
  1204. panelWorldView->activate();
  1205. activeMapPanel = panelWorldView;
  1206. townList->deactivate();
  1207. heroList->deactivate();
  1208. infoBar->showSelection(); // to prevent new day animation interfering world view mode
  1209. infoBar->deactivate();
  1210. redraw();
  1211. terrain->onViewWorldActivated(tileSize);
  1212. }
  1213. void CAdventureMapInterface::openWorldView()
  1214. {
  1215. openWorldView(11);
  1216. }
  1217. void CAdventureMapInterface::openWorldView(const std::vector<ObjectPosInfo>& objectPositions, bool showTerrain)
  1218. {
  1219. openWorldView(11);
  1220. terrain->onViewSpellActivated(11, objectPositions, showTerrain);
  1221. }