MapRenderer.cpp 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796
  1. /*
  2. * MapRenderer.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 "MapRenderer.h"
  12. #include "IMapRendererContext.h"
  13. #include "mapHandler.h"
  14. #include "../CGameInfo.h"
  15. #include "../gui/CGuiHandler.h"
  16. #include "../render/CAnimation.h"
  17. #include "../render/Canvas.h"
  18. #include "../render/IImage.h"
  19. #include "../render/IRenderHandler.h"
  20. #include "../render/Colors.h"
  21. #include "../render/Graphics.h"
  22. #include "../../CCallback.h"
  23. #include "../../lib/RiverHandler.h"
  24. #include "../../lib/RoadHandler.h"
  25. #include "../../lib/TerrainHandler.h"
  26. #include "../../lib/mapObjects/CGHeroInstance.h"
  27. #include "../../lib/mapObjects/MiscObjects.h"
  28. #include "../../lib/mapObjects/ObjectTemplate.h"
  29. #include "../../lib/mapping/CMapDefines.h"
  30. #include "../../lib/pathfinder/CGPathNode.h"
  31. struct NeighborTilesInfo
  32. {
  33. //567
  34. //3 4
  35. //012
  36. std::bitset<8> d;
  37. NeighborTilesInfo(IMapRendererContext & context, const int3 & pos)
  38. {
  39. auto checkTile = [&](int dx, int dy)
  40. {
  41. return context.isVisible(pos + int3(dx, dy, 0));
  42. };
  43. // sides
  44. d[1] = checkTile(0, +1);
  45. d[3] = checkTile(-1, 0);
  46. d[4] = checkTile(+1, 0);
  47. d[6] = checkTile(0, -1);
  48. // corners - select visible image if either corner or adjacent sides are visible
  49. d[0] = d[1] || d[3] || checkTile(-1, +1);
  50. d[2] = d[1] || d[4] || checkTile(+1, +1);
  51. d[5] = d[3] || d[6] || checkTile(-1, -1);
  52. d[7] = d[4] || d[6] || checkTile(+1, -1);
  53. }
  54. bool areAllHidden() const
  55. {
  56. return d.none();
  57. }
  58. int getBitmapID() const
  59. {
  60. //NOTE: some images have unused in VCMI pair (same blockmap but a bit different look)
  61. // 0-1, 2-3, 4-5, 11-13, 12-14
  62. static const int visBitmaps[256] = {
  63. -1, 34, 4, 4, 22, 23, 4, 4, 36, 36, 38, 38, 47, 47, 38, 38, //16
  64. 3, 25, 12, 12, 3, 25, 12, 12, 9, 9, 6, 6, 9, 9, 6, 6, //32
  65. 35, 39, 48, 48, 41, 43, 48, 48, 36, 36, 38, 38, 47, 47, 38, 38, //48
  66. 26, 49, 28, 28, 26, 49, 28, 28, 9, 9, 6, 6, 9, 9, 6, 6, //64
  67. 0, 45, 29, 29, 24, 33, 29, 29, 37, 37, 7, 7, 50, 50, 7, 7, //80
  68. 13, 27, 44, 44, 13, 27, 44, 44, 8, 8, 10, 10, 8, 8, 10, 10, //96
  69. 0, 45, 29, 29, 24, 33, 29, 29, 37, 37, 7, 7, 50, 50, 7, 7, //112
  70. 13, 27, 44, 44, 13, 27, 44, 44, 8, 8, 10, 10, 8, 8, 10, 10, //128
  71. 15, 17, 30, 30, 16, 19, 30, 30, 46, 46, 40, 40, 32, 32, 40, 40, //144
  72. 2, 25, 12, 12, 2, 25, 12, 12, 9, 9, 6, 6, 9, 9, 6, 6, //160
  73. 18, 42, 31, 31, 20, 21, 31, 31, 46, 46, 40, 40, 32, 32, 40, 40, //176
  74. 26, 49, 28, 28, 26, 49, 28, 28, 9, 9, 6, 6, 9, 9, 6, 6, //192
  75. 0, 45, 29, 29, 24, 33, 29, 29, 37, 37, 7, 7, 50, 50, 7, 7, //208
  76. 13, 27, 44, 44, 13, 27, 44, 44, 8, 8, 10, 10, 8, 8, 10, 10, //224
  77. 0, 45, 29, 29, 24, 33, 29, 29, 37, 37, 7, 7, 50, 50, 7, 7, //240
  78. 13, 27, 44, 44, 13, 27, 44, 44, 8, 8, 10, 10, 8, 8, 10, 10 //256
  79. };
  80. return visBitmaps[d.to_ulong()]; // >=0 -> partial hide, <0 - full hide
  81. }
  82. };
  83. MapTileStorage::MapTileStorage(size_t capacity)
  84. : animations(capacity)
  85. {
  86. }
  87. void MapTileStorage::load(size_t index, const AnimationPath & filename, EImageBlitMode blitMode)
  88. {
  89. auto & terrainAnimations = animations[index];
  90. for(auto & entry : terrainAnimations)
  91. {
  92. if (!filename.empty())
  93. entry = GH.renderHandler().loadAnimation(filename, blitMode);
  94. }
  95. if (terrainAnimations[1])
  96. terrainAnimations[1]->verticalFlip();
  97. if (terrainAnimations[3])
  98. terrainAnimations[3]->verticalFlip();
  99. if (terrainAnimations[2])
  100. terrainAnimations[2]->horizontalFlip();
  101. if (terrainAnimations[3])
  102. terrainAnimations[3]->horizontalFlip();
  103. }
  104. std::shared_ptr<IImage> MapTileStorage::find(size_t fileIndex, size_t rotationIndex, size_t imageIndex)
  105. {
  106. const auto & animation = animations[fileIndex][rotationIndex];
  107. if (animation)
  108. return animation->getImage(imageIndex);
  109. else
  110. return nullptr;
  111. }
  112. MapRendererTerrain::MapRendererTerrain()
  113. : storage(VLC->terrainTypeHandler->objects.size())
  114. {
  115. logGlobal->debug("Loading map terrains");
  116. for(const auto & terrain : VLC->terrainTypeHandler->objects)
  117. storage.load(terrain->getIndex(), terrain->tilesFilename, EImageBlitMode::OPAQUE);
  118. logGlobal->debug("Done loading map terrains");
  119. }
  120. void MapRendererTerrain::renderTile(IMapRendererContext & context, Canvas & target, const int3 & coordinates)
  121. {
  122. const TerrainTile & mapTile = context.getMapTile(coordinates);
  123. int32_t terrainIndex = mapTile.terType->getIndex();
  124. int32_t imageIndex = mapTile.terView;
  125. int32_t rotationIndex = mapTile.extTileFlags % 4;
  126. const auto & image = storage.find(terrainIndex, rotationIndex, imageIndex);
  127. assert(image);
  128. if (!image)
  129. {
  130. logGlobal->error("Failed to find image %d for terrain %s on tile %s", imageIndex, mapTile.terType->getNameTranslated(), coordinates.toString());
  131. return;
  132. }
  133. for( auto const & element : mapTile.terType->paletteAnimation)
  134. image->shiftPalette(element.start, element.length, context.terrainImageIndex(element.length));
  135. target.draw(image, Point(0, 0));
  136. }
  137. uint8_t MapRendererTerrain::checksum(IMapRendererContext & context, const int3 & coordinates)
  138. {
  139. const TerrainTile & mapTile = context.getMapTile(coordinates);
  140. if(!mapTile.terType->paletteAnimation.empty())
  141. return context.terrainImageIndex(250);
  142. return 0xff - 1;
  143. }
  144. MapRendererRiver::MapRendererRiver()
  145. : storage(VLC->riverTypeHandler->objects.size())
  146. {
  147. logGlobal->debug("Loading map rivers");
  148. for(const auto & river : VLC->riverTypeHandler->objects)
  149. storage.load(river->getIndex(), river->tilesFilename, EImageBlitMode::COLORKEY);
  150. logGlobal->debug("Done loading map rivers");
  151. }
  152. void MapRendererRiver::renderTile(IMapRendererContext & context, Canvas & target, const int3 & coordinates)
  153. {
  154. const TerrainTile & mapTile = context.getMapTile(coordinates);
  155. if(mapTile.riverType->getId() == River::NO_RIVER)
  156. return;
  157. int32_t terrainIndex = mapTile.riverType->getIndex();
  158. int32_t imageIndex = mapTile.riverDir;
  159. int32_t rotationIndex = (mapTile.extTileFlags >> 2) % 4;
  160. const auto & image = storage.find(terrainIndex, rotationIndex, imageIndex);
  161. for( auto const & element : mapTile.riverType->paletteAnimation)
  162. image->shiftPalette(element.start, element.length, context.terrainImageIndex(element.length));
  163. target.draw(image, Point(0, 0));
  164. }
  165. uint8_t MapRendererRiver::checksum(IMapRendererContext & context, const int3 & coordinates)
  166. {
  167. const TerrainTile & mapTile = context.getMapTile(coordinates);
  168. if(!mapTile.riverType->paletteAnimation.empty())
  169. return context.terrainImageIndex(250);
  170. return 0xff-1;
  171. }
  172. MapRendererRoad::MapRendererRoad()
  173. : storage(VLC->roadTypeHandler->objects.size())
  174. {
  175. logGlobal->debug("Loading map roads");
  176. for(const auto & road : VLC->roadTypeHandler->objects)
  177. storage.load(road->getIndex(), road->tilesFilename, EImageBlitMode::COLORKEY);
  178. logGlobal->debug("Done loading map roads");
  179. }
  180. void MapRendererRoad::renderTile(IMapRendererContext & context, Canvas & target, const int3 & coordinates)
  181. {
  182. const int3 coordinatesAbove = coordinates - int3(0, 1, 0);
  183. if(context.isInMap(coordinatesAbove))
  184. {
  185. const TerrainTile & mapTileAbove = context.getMapTile(coordinatesAbove);
  186. if(mapTileAbove.roadType->getId() != Road::NO_ROAD)
  187. {
  188. int32_t terrainIndex = mapTileAbove.roadType->getIndex();
  189. int32_t imageIndex = mapTileAbove.roadDir;
  190. int32_t rotationIndex = (mapTileAbove.extTileFlags >> 4) % 4;
  191. const auto & image = storage.find(terrainIndex, rotationIndex, imageIndex);
  192. target.draw(image, Point(0, 0), Rect(0, 16, 32, 16));
  193. }
  194. }
  195. const TerrainTile & mapTile = context.getMapTile(coordinates);
  196. if(mapTile.roadType->getId() != Road::NO_ROAD)
  197. {
  198. int32_t terrainIndex = mapTile.roadType->getIndex();
  199. int32_t imageIndex = mapTile.roadDir;
  200. int32_t rotationIndex = (mapTile.extTileFlags >> 4) % 4;
  201. const auto & image = storage.find(terrainIndex, rotationIndex, imageIndex);
  202. target.draw(image, Point(0, 16), Rect(0, 0, 32, 16));
  203. }
  204. }
  205. uint8_t MapRendererRoad::checksum(IMapRendererContext & context, const int3 & coordinates)
  206. {
  207. return 0;
  208. }
  209. MapRendererBorder::MapRendererBorder()
  210. {
  211. animation = GH.renderHandler().loadAnimation(AnimationPath::builtin("EDG"), EImageBlitMode::OPAQUE);
  212. }
  213. size_t MapRendererBorder::getIndexForTile(IMapRendererContext & context, const int3 & tile)
  214. {
  215. assert(!context.isInMap(tile));
  216. int3 size = context.getMapSize();
  217. if(tile.x < -1 || tile.x > size.x || tile.y < -1 || tile.y > size.y)
  218. return std::abs(tile.x) % 4 + 4 * (std::abs(tile.y) % 4);
  219. if(tile.x == -1 && tile.y == -1)
  220. return 16;
  221. if(tile.x == size.x && tile.y == -1)
  222. return 17;
  223. if(tile.x == size.x && tile.y == size.y)
  224. return 18;
  225. if(tile.x == -1 && tile.y == size.y)
  226. return 19;
  227. if(tile.y == -1)
  228. return 20 + (tile.x % 4);
  229. if(tile.x == size.x)
  230. return 24 + (tile.y % 4);
  231. if(tile.y == size.y)
  232. return 28 + (tile.x % 4);
  233. if(tile.x == -1)
  234. return 32 + (tile.y % 4);
  235. //else - visible area, no renderable border
  236. assert(0);
  237. return 0;
  238. }
  239. void MapRendererBorder::renderTile(IMapRendererContext & context, Canvas & target, const int3 & coordinates)
  240. {
  241. if (context.showBorder())
  242. {
  243. const auto & image = animation->getImage(getIndexForTile(context, coordinates));
  244. target.draw(image, Point(0, 0));
  245. }
  246. else
  247. {
  248. target.drawColor(Rect(0,0,32,32), Colors::BLACK);
  249. }
  250. }
  251. uint8_t MapRendererBorder::checksum(IMapRendererContext & context, const int3 & coordinates)
  252. {
  253. return 0;
  254. }
  255. MapRendererFow::MapRendererFow()
  256. {
  257. fogOfWarFullHide = GH.renderHandler().loadAnimation(AnimationPath::builtin("TSHRC"), EImageBlitMode::OPAQUE);
  258. fogOfWarPartialHide = GH.renderHandler().loadAnimation(AnimationPath::builtin("TSHRE"), EImageBlitMode::ALPHA);
  259. static const std::vector<int> rotations = {22, 15, 2, 13, 12, 16, 28, 17, 20, 19, 7, 24, 26, 25, 30, 32, 27};
  260. size_t size = fogOfWarPartialHide->size(0); //group size after next rotation
  261. for(const int rotation : rotations)
  262. {
  263. fogOfWarPartialHide->duplicateImage(0, rotation, 0);
  264. fogOfWarPartialHide->verticalFlip(size, 0);
  265. size++;
  266. }
  267. }
  268. void MapRendererFow::renderTile(IMapRendererContext & context, Canvas & target, const int3 & coordinates)
  269. {
  270. assert(!context.isVisible(coordinates));
  271. const NeighborTilesInfo neighborInfo(context, coordinates);
  272. int retBitmapID = neighborInfo.getBitmapID(); // >=0 -> partial hide, <0 - full hide
  273. if(retBitmapID < 0)
  274. {
  275. // generate a number that is predefined for each tile,
  276. // but appears random to break visible pattern in large areas of fow
  277. // current approach (use primes as magic numbers for formula) looks to be suitable
  278. size_t pseudorandomNumber = ((coordinates.x * 997) ^ (coordinates.y * 1009)) / 101;
  279. size_t imageIndex = pseudorandomNumber % fogOfWarFullHide->size();
  280. if (context.showSpellRange(coordinates))
  281. target.drawColor(Rect(0,0,32,32), Colors::BLACK);
  282. else
  283. target.draw(fogOfWarFullHide->getImage(imageIndex), Point(0, 0));
  284. }
  285. else
  286. {
  287. target.draw(fogOfWarPartialHide->getImage(retBitmapID), Point(0, 0));
  288. }
  289. }
  290. uint8_t MapRendererFow::checksum(IMapRendererContext & context, const int3 & coordinates)
  291. {
  292. if (context.showSpellRange(coordinates))
  293. return 0xff - 2;
  294. const NeighborTilesInfo neighborInfo(context, coordinates);
  295. int retBitmapID = neighborInfo.getBitmapID();
  296. if(retBitmapID < 0)
  297. return 0xff - 1;
  298. return retBitmapID;
  299. }
  300. std::shared_ptr<CAnimation> MapRendererObjects::getBaseAnimation(const CGObjectInstance* obj)
  301. {
  302. const auto & info = obj->appearance;
  303. //the only(?) invisible object
  304. if(info->id == Obj::EVENT)
  305. return std::shared_ptr<CAnimation>();
  306. if(info->animationFile.empty())
  307. {
  308. logGlobal->warn("Def name for obj (%d,%d) is empty!", info->id, info->subid);
  309. return std::shared_ptr<CAnimation>();
  310. }
  311. bool generateMovementGroups = (info->id == Obj::BOAT) || (info->id == Obj::HERO);
  312. // Boat appearance files only contain single, unanimated image
  313. // proper boat animations are actually in different file
  314. if (info->id == Obj::BOAT)
  315. if(auto boat = dynamic_cast<const CGBoat*>(obj); boat && !boat->actualAnimation.empty())
  316. return getAnimation(boat->actualAnimation, generateMovementGroups);
  317. return getAnimation(info->animationFile, generateMovementGroups);
  318. }
  319. std::shared_ptr<CAnimation> MapRendererObjects::getAnimation(const AnimationPath & filename, bool generateMovementGroups)
  320. {
  321. auto it = animations.find(filename);
  322. if(it != animations.end())
  323. return it->second;
  324. auto ret = GH.renderHandler().loadAnimation(filename, EImageBlitMode::ALPHA);
  325. animations[filename] = ret;
  326. if(generateMovementGroups)
  327. {
  328. ret->createFlippedGroup(1, 13);
  329. ret->createFlippedGroup(2, 14);
  330. ret->createFlippedGroup(3, 15);
  331. ret->createFlippedGroup(6, 10);
  332. ret->createFlippedGroup(7, 11);
  333. ret->createFlippedGroup(8, 12);
  334. }
  335. return ret;
  336. }
  337. std::shared_ptr<CAnimation> MapRendererObjects::getFlagAnimation(const CGObjectInstance* obj)
  338. {
  339. //TODO: relocate to config file?
  340. static const std::vector<std::string> heroFlags = {
  341. "AF00", "AF01", "AF02", "AF03", "AF04", "AF05", "AF06", "AF07"
  342. };
  343. if(obj->ID == Obj::HERO)
  344. {
  345. assert(dynamic_cast<const CGHeroInstance *>(obj) != nullptr);
  346. assert(obj->tempOwner.isValidPlayer());
  347. return getAnimation(AnimationPath::builtin(heroFlags[obj->tempOwner.getNum()]), true);
  348. }
  349. if(obj->ID == Obj::BOAT)
  350. {
  351. const auto * boat = dynamic_cast<const CGBoat *>(obj);
  352. if(boat && boat->hero && !boat->flagAnimations[boat->hero->tempOwner.getNum()].empty())
  353. return getAnimation(boat->flagAnimations[boat->hero->tempOwner.getNum()], true);
  354. }
  355. return nullptr;
  356. }
  357. std::shared_ptr<CAnimation> MapRendererObjects::getOverlayAnimation(const CGObjectInstance * obj)
  358. {
  359. if(obj->ID == Obj::BOAT)
  360. {
  361. // Boats have additional animation with waves around boat
  362. const auto * boat = dynamic_cast<const CGBoat *>(obj);
  363. if(boat && boat->hero && !boat->overlayAnimation.empty())
  364. return getAnimation(boat->overlayAnimation, true);
  365. }
  366. return nullptr;
  367. }
  368. std::shared_ptr<IImage> MapRendererObjects::getImage(IMapRendererContext & context, const CGObjectInstance * obj, const std::shared_ptr<CAnimation>& animation) const
  369. {
  370. if(!animation)
  371. return nullptr;
  372. size_t groupIndex = context.objectGroupIndex(obj->id);
  373. if(animation->size(groupIndex) == 0)
  374. return nullptr;
  375. size_t frameIndex = context.objectImageIndex(obj->id, animation->size(groupIndex));
  376. return animation->getImage(frameIndex, groupIndex);
  377. }
  378. void MapRendererObjects::renderImage(IMapRendererContext & context, Canvas & target, const int3 & coordinates, const CGObjectInstance * object, const std::shared_ptr<IImage>& image)
  379. {
  380. if(!image)
  381. return;
  382. auto transparency = static_cast<uint8_t>(std::round(255 * context.objectTransparency(object->id, coordinates)));
  383. if (transparency == 0)
  384. return;
  385. image->setAlpha(transparency);
  386. image->setShadowEnabled(true);
  387. if (object->ID != Obj::HERO)
  388. {
  389. image->setOverlayEnabled(object->getOwner().isValidPlayer() || object->getOwner() == PlayerColor::NEUTRAL);
  390. if (object->getOwner().isValidPlayer())
  391. image->setOverlayColor(graphics->playerColors[object->getOwner().getNum()]);
  392. if (object->getOwner() == PlayerColor::NEUTRAL)
  393. image->setOverlayColor(graphics->neutralColor);
  394. }
  395. else
  396. {
  397. // heroes use separate image with flag instead of player-colored palette
  398. image->setOverlayEnabled(false);
  399. }
  400. Point offsetPixels = context.objectImageOffset(object->id, coordinates);
  401. if ( offsetPixels.x < image->dimensions().x && offsetPixels.y < image->dimensions().y)
  402. {
  403. Point imagePos = image->dimensions() - offsetPixels - Point(32, 32);
  404. target.draw(image, Point(0, 0), Rect(imagePos, Point(32,32)));
  405. }
  406. }
  407. void MapRendererObjects::renderObject(IMapRendererContext & context, Canvas & target, const int3 & coordinates, const CGObjectInstance * instance)
  408. {
  409. renderImage(context, target, coordinates, instance, getImage(context, instance, getBaseAnimation(instance)));
  410. renderImage(context, target, coordinates, instance, getImage(context, instance, getFlagAnimation(instance)));
  411. renderImage(context, target, coordinates, instance, getImage(context, instance, getOverlayAnimation(instance)));
  412. }
  413. void MapRendererObjects::renderTile(IMapRendererContext & context, Canvas & target, const int3 & coordinates)
  414. {
  415. for(const auto & objectID : context.getObjects(coordinates))
  416. {
  417. const auto * objectInstance = context.getObject(objectID);
  418. assert(objectInstance);
  419. if(!objectInstance)
  420. {
  421. logGlobal->error("Stray map object that isn't fading");
  422. continue;
  423. }
  424. renderObject(context, target, coordinates, objectInstance);
  425. }
  426. }
  427. uint8_t MapRendererObjects::checksum(IMapRendererContext & context, const int3 & coordinates)
  428. {
  429. for(const auto & objectID : context.getObjects(coordinates))
  430. {
  431. const auto * objectInstance = context.getObject(objectID);
  432. assert(objectInstance);
  433. if(!objectInstance)
  434. {
  435. logGlobal->error("Stray map object that isn't fading");
  436. continue;
  437. }
  438. size_t groupIndex = context.objectGroupIndex(objectInstance->id);
  439. Point offsetPixels = context.objectImageOffset(objectInstance->id, coordinates);
  440. auto base = getBaseAnimation(objectInstance);
  441. auto flag = getFlagAnimation(objectInstance);
  442. if (base && base->size(groupIndex) > 1)
  443. {
  444. auto imageIndex = context.objectImageIndex(objectID, base->size(groupIndex));
  445. auto image = base->getImage(imageIndex, groupIndex);
  446. if ( offsetPixels.x < image->dimensions().x && offsetPixels.y < image->dimensions().y)
  447. return context.objectImageIndex(objectID, 250);
  448. }
  449. if (flag && flag->size(groupIndex) > 1)
  450. {
  451. auto imageIndex = context.objectImageIndex(objectID, flag->size(groupIndex));
  452. auto image = flag->getImage(imageIndex, groupIndex);
  453. if ( offsetPixels.x < image->dimensions().x && offsetPixels.y < image->dimensions().y)
  454. return context.objectImageIndex(objectID, 250);
  455. }
  456. }
  457. return 0xff-1;
  458. }
  459. MapRendererOverlay::MapRendererOverlay()
  460. : imageGrid(GH.renderHandler().loadImage(ImagePath::builtin("debug/grid"), EImageBlitMode::ALPHA))
  461. , imageBlocked(GH.renderHandler().loadImage(ImagePath::builtin("debug/blocked"), EImageBlitMode::ALPHA))
  462. , imageVisitable(GH.renderHandler().loadImage(ImagePath::builtin("debug/visitable"), EImageBlitMode::ALPHA))
  463. , imageSpellRange(GH.renderHandler().loadImage(ImagePath::builtin("debug/spellRange"), EImageBlitMode::ALPHA))
  464. {
  465. }
  466. void MapRendererOverlay::renderTile(IMapRendererContext & context, Canvas & target, const int3 & coordinates)
  467. {
  468. if(context.showGrid())
  469. target.draw(imageGrid, Point(0,0));
  470. if(context.showVisitable() || context.showBlocked())
  471. {
  472. bool blocking = false;
  473. bool visitable = false;
  474. for(const auto & objectID : context.getObjects(coordinates))
  475. {
  476. const auto * object = context.getObject(objectID);
  477. if(context.objectTransparency(objectID, coordinates) > 0 && !context.isActiveHero(object))
  478. {
  479. visitable |= object->visitableAt(coordinates);
  480. blocking |= object->blockingAt(coordinates);
  481. }
  482. }
  483. if (context.showBlocked() && blocking)
  484. target.draw(imageBlocked, Point(0,0));
  485. if (context.showVisitable() && visitable)
  486. target.draw(imageVisitable, Point(0,0));
  487. }
  488. if (context.showSpellRange(coordinates))
  489. target.draw(imageSpellRange, Point(0,0));
  490. }
  491. uint8_t MapRendererOverlay::checksum(IMapRendererContext & context, const int3 & coordinates)
  492. {
  493. uint8_t result = 0;
  494. if (context.showVisitable())
  495. result += 1;
  496. if (context.showBlocked())
  497. result += 2;
  498. if (context.showGrid())
  499. result += 4;
  500. if (context.showSpellRange(coordinates))
  501. result += 8;
  502. return result;
  503. }
  504. MapRendererPath::MapRendererPath()
  505. : pathNodes(GH.renderHandler().loadAnimation(AnimationPath::builtin("ADAG"), EImageBlitMode::ALPHA))
  506. {
  507. }
  508. size_t MapRendererPath::selectImageReachability(bool reachableToday, size_t imageIndex)
  509. {
  510. const static size_t unreachableTodayOffset = 25;
  511. if(!reachableToday)
  512. return unreachableTodayOffset + imageIndex;
  513. return imageIndex;
  514. }
  515. size_t MapRendererPath::selectImageCross(bool reachableToday, const int3 & curr)
  516. {
  517. return selectImageReachability(reachableToday, 0);
  518. }
  519. size_t MapRendererPath::selectImageArrow(bool reachableToday, const int3 & curr, const int3 & prev, const int3 & next)
  520. {
  521. // Vector directions
  522. // 0 1 2
  523. // |
  524. // 3 - 4 - 5
  525. // |
  526. // 6 7 8
  527. //For example:
  528. // |
  529. // +->
  530. // is (directionToArrowIndex[7][5])
  531. //
  532. const static size_t directionToArrowIndex[9][9] = {
  533. {16, 17, 18, 7, 0, 19, 6, 5, 12},
  534. {8, 9, 18, 7, 0, 19, 6, 13, 20},
  535. {8, 1, 10, 7, 0, 19, 14, 21, 20},
  536. {24, 17, 18, 15, 0, 11, 6, 5, 4 },
  537. {0, 0, 0, 0, 0, 0, 0, 0, 0 },
  538. {8, 1, 2, 15, 0, 11, 22, 21, 20},
  539. {24, 17, 10, 23, 0, 3, 14, 5, 4 },
  540. {24, 9, 2, 23, 0, 3, 22, 13, 4 },
  541. {16, 1, 2, 23, 0, 3, 22, 21, 12}
  542. };
  543. size_t enterDirection = (curr.x - next.x + 1) + 3 * (curr.y - next.y + 1);
  544. size_t leaveDirection = (prev.x - curr.x + 1) + 3 * (prev.y - curr.y + 1);
  545. size_t imageIndex = directionToArrowIndex[enterDirection][leaveDirection];
  546. return selectImageReachability(reachableToday, imageIndex);
  547. }
  548. void MapRendererPath::renderTile(IMapRendererContext & context, Canvas & target, const int3 & coordinates)
  549. {
  550. size_t imageID = selectImage(context, coordinates);
  551. if (imageID < pathNodes->size())
  552. target.draw(pathNodes->getImage(imageID), Point(0,0));
  553. }
  554. size_t MapRendererPath::selectImage(IMapRendererContext & context, const int3 & coordinates)
  555. {
  556. const auto & functor = [&](const CGPathNode & node)
  557. {
  558. return node.coord == coordinates;
  559. };
  560. const auto * path = context.currentPath();
  561. if(!path)
  562. return std::numeric_limits<size_t>::max();
  563. const auto & iter = boost::range::find_if(path->nodes, functor);
  564. if(iter == path->nodes.end())
  565. return std::numeric_limits<size_t>::max();
  566. bool reachableToday = iter->turns == 0;
  567. if(iter == path->nodes.begin())
  568. return selectImageCross(reachableToday, iter->coord);
  569. auto next = iter + 1;
  570. auto prev = iter - 1;
  571. // start of path - current hero location
  572. if(next == path->nodes.end())
  573. return std::numeric_limits<size_t>::max();
  574. bool pathContinuous = iter->coord.areNeighbours(next->coord) && iter->coord.areNeighbours(prev->coord);
  575. bool embarking = iter->action == EPathNodeAction::EMBARK || iter->action == EPathNodeAction::DISEMBARK;
  576. if(pathContinuous && !embarking)
  577. return selectImageArrow(reachableToday, iter->coord, prev->coord, next->coord);
  578. return selectImageCross(reachableToday, iter->coord);
  579. }
  580. uint8_t MapRendererPath::checksum(IMapRendererContext & context, const int3 & coordinates)
  581. {
  582. return selectImage(context, coordinates) & 0xff;
  583. }
  584. MapRenderer::TileChecksum MapRenderer::getTileChecksum(IMapRendererContext & context, const int3 & coordinates)
  585. {
  586. // computes basic checksum to determine whether tile needs an update
  587. // if any component gives different value, tile will be updated
  588. TileChecksum result;
  589. boost::range::fill(result, std::numeric_limits<uint8_t>::max());
  590. if(!context.isInMap(coordinates))
  591. {
  592. result[0] = rendererBorder.checksum(context, coordinates);
  593. return result;
  594. }
  595. const NeighborTilesInfo neighborInfo(context, coordinates);
  596. if(!context.isVisible(coordinates) && neighborInfo.areAllHidden())
  597. {
  598. result[7] = rendererFow.checksum(context, coordinates);
  599. }
  600. else
  601. {
  602. result[1] = rendererTerrain.checksum(context, coordinates);
  603. if (context.showRivers())
  604. result[2] = rendererRiver.checksum(context, coordinates);
  605. if (context.showRoads())
  606. result[3] = rendererRoad.checksum(context, coordinates);
  607. result[4] = rendererObjects.checksum(context, coordinates);
  608. result[5] = rendererPath.checksum(context, coordinates);
  609. result[6] = rendererOverlay.checksum(context, coordinates);
  610. if(!context.isVisible(coordinates))
  611. result[7] = rendererFow.checksum(context, coordinates);
  612. }
  613. return result;
  614. }
  615. void MapRenderer::renderTile(IMapRendererContext & context, Canvas & target, const int3 & coordinates)
  616. {
  617. if(!context.isInMap(coordinates))
  618. {
  619. rendererBorder.renderTile(context, target, coordinates);
  620. return;
  621. }
  622. const NeighborTilesInfo neighborInfo(context, coordinates);
  623. if(!context.isVisible(coordinates) && neighborInfo.areAllHidden())
  624. {
  625. rendererFow.renderTile(context, target, coordinates);
  626. }
  627. else
  628. {
  629. rendererTerrain.renderTile(context, target, coordinates);
  630. if (context.showRivers())
  631. rendererRiver.renderTile(context, target, coordinates);
  632. if (context.showRoads())
  633. rendererRoad.renderTile(context, target, coordinates);
  634. rendererObjects.renderTile(context, target, coordinates);
  635. rendererPath.renderTile(context, target, coordinates);
  636. rendererOverlay.renderTile(context, target, coordinates);
  637. if(!context.isVisible(coordinates))
  638. rendererFow.renderTile(context, target, coordinates);
  639. }
  640. }