MapRenderer.cpp 25 KB

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