CAdvMapInt.cpp 43 KB

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