MapRenderer.cpp 27 KB

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