BattleSiegeController.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367
  1. /*
  2. * BattleSiegeController.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 "BattleSiegeController.h"
  12. #include "BattleAnimationClasses.h"
  13. #include "BattleInterface.h"
  14. #include "BattleInterfaceClasses.h"
  15. #include "BattleStacksController.h"
  16. #include "BattleFieldController.h"
  17. #include "BattleRenderer.h"
  18. #include "../CMusicHandler.h"
  19. #include "../CGameInfo.h"
  20. #include "../CPlayerInterface.h"
  21. #include "../gui/CGuiHandler.h"
  22. #include "../render/Canvas.h"
  23. #include "../render/IImage.h"
  24. #include "../render/IRenderHandler.h"
  25. #include "../../CCallback.h"
  26. #include "../../lib/NetPacks.h"
  27. #include "../../lib/CStack.h"
  28. #include "../../lib/mapObjects/CGTownInstance.h"
  29. ImagePath BattleSiegeController::getWallPieceImageName(EWallVisual::EWallVisual what, EWallState state) const
  30. {
  31. auto getImageIndex = [&]() -> int
  32. {
  33. bool isTower = (what == EWallVisual::KEEP || what == EWallVisual::BOTTOM_TOWER || what == EWallVisual::UPPER_TOWER);
  34. switch (state)
  35. {
  36. case EWallState::REINFORCED :
  37. return 1;
  38. case EWallState::INTACT :
  39. if (town->hasBuilt(BuildingID::CASTLE))
  40. return 2; // reinforced walls were damaged
  41. else
  42. return 1;
  43. case EWallState::DAMAGED :
  44. // towers don't have separate image here - INTACT and DAMAGED is 1, DESTROYED is 2
  45. if (isTower)
  46. return 1;
  47. else
  48. return 2;
  49. case EWallState::DESTROYED :
  50. if (isTower)
  51. return 2;
  52. else
  53. return 3;
  54. }
  55. return 1;
  56. };
  57. const std::string & prefix = town->town->clientInfo.siegePrefix;
  58. std::string addit = std::to_string(getImageIndex());
  59. switch(what)
  60. {
  61. case EWallVisual::BACKGROUND_WALL:
  62. {
  63. auto faction = town->town->faction->getIndex();
  64. if (faction == ETownType::RAMPART || faction == ETownType::NECROPOLIS || faction == ETownType::DUNGEON || faction == ETownType::STRONGHOLD)
  65. return ImagePath::builtinTODO(prefix + "TPW1.BMP");
  66. else
  67. return ImagePath::builtinTODO(prefix + "TPWL.BMP");
  68. }
  69. case EWallVisual::KEEP:
  70. return ImagePath::builtinTODO(prefix + "MAN" + addit + ".BMP");
  71. case EWallVisual::BOTTOM_TOWER:
  72. return ImagePath::builtinTODO(prefix + "TW1" + addit + ".BMP");
  73. case EWallVisual::BOTTOM_WALL:
  74. return ImagePath::builtinTODO(prefix + "WA1" + addit + ".BMP");
  75. case EWallVisual::WALL_BELLOW_GATE:
  76. return ImagePath::builtinTODO(prefix + "WA3" + addit + ".BMP");
  77. case EWallVisual::WALL_OVER_GATE:
  78. return ImagePath::builtinTODO(prefix + "WA4" + addit + ".BMP");
  79. case EWallVisual::UPPER_WALL:
  80. return ImagePath::builtinTODO(prefix + "WA6" + addit + ".BMP");
  81. case EWallVisual::UPPER_TOWER:
  82. return ImagePath::builtinTODO(prefix + "TW2" + addit + ".BMP");
  83. case EWallVisual::GATE:
  84. return ImagePath::builtinTODO(prefix + "DRW" + addit + ".BMP");
  85. case EWallVisual::GATE_ARCH:
  86. return ImagePath::builtinTODO(prefix + "ARCH.BMP");
  87. case EWallVisual::BOTTOM_STATIC_WALL:
  88. return ImagePath::builtinTODO(prefix + "WA2.BMP");
  89. case EWallVisual::UPPER_STATIC_WALL:
  90. return ImagePath::builtinTODO(prefix + "WA5.BMP");
  91. case EWallVisual::MOAT:
  92. return ImagePath::builtinTODO(prefix + "MOAT.BMP");
  93. case EWallVisual::MOAT_BANK:
  94. return ImagePath::builtinTODO(prefix + "MLIP.BMP");
  95. case EWallVisual::KEEP_BATTLEMENT:
  96. return ImagePath::builtinTODO(prefix + "MANC.BMP");
  97. case EWallVisual::BOTTOM_BATTLEMENT:
  98. return ImagePath::builtinTODO(prefix + "TW1C.BMP");
  99. case EWallVisual::UPPER_BATTLEMENT:
  100. return ImagePath::builtinTODO(prefix + "TW2C.BMP");
  101. default:
  102. return ImagePath();
  103. }
  104. }
  105. void BattleSiegeController::showWallPiece(Canvas & canvas, EWallVisual::EWallVisual what)
  106. {
  107. auto & ci = town->town->clientInfo;
  108. auto const & pos = ci.siegePositions[what];
  109. if ( wallPieceImages[what] && pos.isValid())
  110. canvas.draw(wallPieceImages[what], Point(pos.x, pos.y));
  111. }
  112. ImagePath BattleSiegeController::getBattleBackgroundName() const
  113. {
  114. const std::string & prefix = town->town->clientInfo.siegePrefix;
  115. return ImagePath::builtinTODO(prefix + "BACK.BMP");
  116. }
  117. bool BattleSiegeController::getWallPieceExistance(EWallVisual::EWallVisual what) const
  118. {
  119. //FIXME: use this instead of buildings test?
  120. //ui8 siegeLevel = owner.curInt->cb->battleGetSiegeLevel();
  121. switch (what)
  122. {
  123. case EWallVisual::MOAT: return town->hasBuilt(BuildingID::CITADEL) && town->town->clientInfo.siegePositions.at(EWallVisual::MOAT).isValid();
  124. case EWallVisual::MOAT_BANK: return town->hasBuilt(BuildingID::CITADEL) && town->town->clientInfo.siegePositions.at(EWallVisual::MOAT_BANK).isValid();
  125. case EWallVisual::KEEP_BATTLEMENT: return town->hasBuilt(BuildingID::CITADEL) && owner.getBattle()->battleGetWallState(EWallPart::KEEP) != EWallState::DESTROYED;
  126. case EWallVisual::UPPER_BATTLEMENT: return town->hasBuilt(BuildingID::CASTLE) && owner.getBattle()->battleGetWallState(EWallPart::UPPER_TOWER) != EWallState::DESTROYED;
  127. case EWallVisual::BOTTOM_BATTLEMENT: return town->hasBuilt(BuildingID::CASTLE) && owner.getBattle()->battleGetWallState(EWallPart::BOTTOM_TOWER) != EWallState::DESTROYED;
  128. default: return true;
  129. }
  130. }
  131. BattleHex BattleSiegeController::getWallPiecePosition(EWallVisual::EWallVisual what) const
  132. {
  133. static const std::array<BattleHex, 18> wallsPositions = {
  134. BattleHex::INVALID, // BACKGROUND, // handled separately
  135. BattleHex::HEX_BEFORE_ALL, // BACKGROUND_WALL,
  136. 135, // KEEP,
  137. BattleHex::HEX_AFTER_ALL, // BOTTOM_TOWER,
  138. 182, // BOTTOM_WALL,
  139. 130, // WALL_BELLOW_GATE,
  140. 62, // WALL_OVER_GATE,
  141. 12, // UPPER_WALL,
  142. BattleHex::HEX_BEFORE_ALL, // UPPER_TOWER,
  143. BattleHex::HEX_BEFORE_ALL, // GATE, // 94
  144. 112, // GATE_ARCH,
  145. 165, // BOTTOM_STATIC_WALL,
  146. 45, // UPPER_STATIC_WALL,
  147. BattleHex::INVALID, // MOAT, // printed as absolute obstacle
  148. BattleHex::INVALID, // MOAT_BANK, // printed as absolute obstacle
  149. 135, // KEEP_BATTLEMENT,
  150. BattleHex::HEX_AFTER_ALL, // BOTTOM_BATTLEMENT,
  151. BattleHex::HEX_BEFORE_ALL, // UPPER_BATTLEMENT,
  152. };
  153. return wallsPositions[what];
  154. }
  155. BattleSiegeController::BattleSiegeController(BattleInterface & owner, const CGTownInstance *siegeTown):
  156. owner(owner),
  157. town(siegeTown)
  158. {
  159. assert(owner.fieldController.get() == nullptr); // must be created after this
  160. for (int g = 0; g < wallPieceImages.size(); ++g)
  161. {
  162. if ( g == EWallVisual::GATE ) // gate is initially closed and has no image to display in this state
  163. continue;
  164. if ( !getWallPieceExistance(EWallVisual::EWallVisual(g)) )
  165. continue;
  166. wallPieceImages[g] = GH.renderHandler().loadImage(getWallPieceImageName(EWallVisual::EWallVisual(g), EWallState::REINFORCED));
  167. }
  168. }
  169. const CCreature *BattleSiegeController::getTurretCreature() const
  170. {
  171. return CGI->creh->objects[town->town->clientInfo.siegeShooter];
  172. }
  173. Point BattleSiegeController::getTurretCreaturePosition( BattleHex position ) const
  174. {
  175. // Turret positions are read out of the config/wall_pos.txt
  176. int posID = 0;
  177. switch (position)
  178. {
  179. case BattleHex::CASTLE_CENTRAL_TOWER: // keep creature
  180. posID = EWallVisual::CREATURE_KEEP;
  181. break;
  182. case BattleHex::CASTLE_BOTTOM_TOWER: // bottom creature
  183. posID = EWallVisual::CREATURE_BOTTOM_TOWER;
  184. break;
  185. case BattleHex::CASTLE_UPPER_TOWER: // upper creature
  186. posID = EWallVisual::CREATURE_UPPER_TOWER;
  187. break;
  188. }
  189. if (posID != 0)
  190. {
  191. return {
  192. town->town->clientInfo.siegePositions[posID].x,
  193. town->town->clientInfo.siegePositions[posID].y
  194. };
  195. }
  196. assert(0);
  197. return Point(0,0);
  198. }
  199. void BattleSiegeController::gateStateChanged(const EGateState state)
  200. {
  201. auto oldState = owner.getBattle()->battleGetGateState();
  202. bool playSound = false;
  203. auto stateId = EWallState::NONE;
  204. switch(state)
  205. {
  206. case EGateState::CLOSED:
  207. if (oldState != EGateState::BLOCKED)
  208. playSound = true;
  209. break;
  210. case EGateState::BLOCKED:
  211. if (oldState != EGateState::CLOSED)
  212. playSound = true;
  213. break;
  214. case EGateState::OPENED:
  215. playSound = true;
  216. stateId = EWallState::DAMAGED;
  217. break;
  218. case EGateState::DESTROYED:
  219. stateId = EWallState::DESTROYED;
  220. break;
  221. }
  222. if (oldState != EGateState::NONE && oldState != EGateState::CLOSED && oldState != EGateState::BLOCKED)
  223. wallPieceImages[EWallVisual::GATE] = nullptr;
  224. if (stateId != EWallState::NONE)
  225. wallPieceImages[EWallVisual::GATE] = GH.renderHandler().loadImage(getWallPieceImageName(EWallVisual::GATE, stateId));
  226. if (playSound)
  227. CCS->soundh->playSound(soundBase::DRAWBRG);
  228. }
  229. void BattleSiegeController::showAbsoluteObstacles(Canvas & canvas)
  230. {
  231. if (getWallPieceExistance(EWallVisual::MOAT))
  232. showWallPiece(canvas, EWallVisual::MOAT);
  233. if (getWallPieceExistance(EWallVisual::MOAT_BANK))
  234. showWallPiece(canvas, EWallVisual::MOAT_BANK);
  235. }
  236. BattleHex BattleSiegeController::getTurretBattleHex(EWallVisual::EWallVisual wallPiece) const
  237. {
  238. switch(wallPiece)
  239. {
  240. case EWallVisual::KEEP_BATTLEMENT: return BattleHex::CASTLE_CENTRAL_TOWER;
  241. case EWallVisual::BOTTOM_BATTLEMENT: return BattleHex::CASTLE_BOTTOM_TOWER;
  242. case EWallVisual::UPPER_BATTLEMENT: return BattleHex::CASTLE_UPPER_TOWER;
  243. }
  244. assert(0);
  245. return BattleHex::INVALID;
  246. }
  247. const CStack * BattleSiegeController::getTurretStack(EWallVisual::EWallVisual wallPiece) const
  248. {
  249. for (auto & stack : owner.getBattle()->battleGetAllStacks(true))
  250. {
  251. if ( stack->initialPosition == getTurretBattleHex(wallPiece))
  252. return stack;
  253. }
  254. assert(0);
  255. return nullptr;
  256. }
  257. void BattleSiegeController::collectRenderableObjects(BattleRenderer & renderer)
  258. {
  259. for (int i = EWallVisual::WALL_FIRST; i <= EWallVisual::WALL_LAST; ++i)
  260. {
  261. auto wallPiece = EWallVisual::EWallVisual(i);
  262. if ( !getWallPieceExistance(wallPiece))
  263. continue;
  264. if ( getWallPiecePosition(wallPiece) == BattleHex::INVALID)
  265. continue;
  266. if (wallPiece == EWallVisual::KEEP_BATTLEMENT ||
  267. wallPiece == EWallVisual::BOTTOM_BATTLEMENT ||
  268. wallPiece == EWallVisual::UPPER_BATTLEMENT)
  269. {
  270. renderer.insert( EBattleFieldLayer::STACKS, getWallPiecePosition(wallPiece), [this, wallPiece](BattleRenderer::RendererRef canvas){
  271. owner.stacksController->showStack(canvas, getTurretStack(wallPiece));
  272. });
  273. renderer.insert( EBattleFieldLayer::OBSTACLES, getWallPiecePosition(wallPiece), [this, wallPiece](BattleRenderer::RendererRef canvas){
  274. showWallPiece(canvas, wallPiece);
  275. });
  276. }
  277. renderer.insert( EBattleFieldLayer::WALLS, getWallPiecePosition(wallPiece), [this, wallPiece](BattleRenderer::RendererRef canvas){
  278. showWallPiece(canvas, wallPiece);
  279. });
  280. }
  281. }
  282. bool BattleSiegeController::isAttackableByCatapult(BattleHex hex) const
  283. {
  284. if (owner.tacticsMode)
  285. return false;
  286. auto wallPart = owner.getBattle()->battleHexToWallPart(hex);
  287. return owner.getBattle()->isWallPartAttackable(wallPart);
  288. }
  289. void BattleSiegeController::stackIsCatapulting(const CatapultAttack & ca)
  290. {
  291. if (ca.attacker != -1)
  292. {
  293. const CStack *stack = owner.getBattle()->battleGetStackByID(ca.attacker);
  294. for (auto attackInfo : ca.attackedParts)
  295. {
  296. owner.stacksController->addNewAnim(new CatapultAnimation(owner, stack, attackInfo.destinationTile, nullptr, attackInfo.damageDealt));
  297. }
  298. }
  299. else
  300. {
  301. std::vector<Point> positions;
  302. //no attacker stack, assume spell-related (earthquake) - only hit animation
  303. for (auto attackInfo : ca.attackedParts)
  304. positions.push_back(owner.stacksController->getStackPositionAtHex(attackInfo.destinationTile, nullptr) + Point(99, 120));
  305. CCS->soundh->playSound( AudioPath::builtin("WALLHIT") );
  306. owner.stacksController->addNewAnim(new EffectAnimation(owner, AnimationPath::builtin("SGEXPL.DEF"), positions));
  307. }
  308. owner.waitForAnimations();
  309. for (auto attackInfo : ca.attackedParts)
  310. {
  311. int wallId = static_cast<int>(attackInfo.attackedPart) + EWallVisual::DESTRUCTIBLE_FIRST;
  312. //gate state changing handled separately
  313. if (wallId == EWallVisual::GATE)
  314. continue;
  315. auto wallState = EWallState(owner.getBattle()->battleGetWallState(attackInfo.attackedPart));
  316. wallPieceImages[wallId] = GH.renderHandler().loadImage(getWallPieceImageName(EWallVisual::EWallVisual(wallId), wallState));
  317. }
  318. }
  319. const CGTownInstance *BattleSiegeController::getSiegedTown() const
  320. {
  321. return town;
  322. }