CBattleProjectileController.cpp 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315
  1. /*
  2. * CBattleProjectileController.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 "CBattleProjectileController.h"
  12. #include "../gui/Geometries.h"
  13. #include "../../lib/CStack.h"
  14. #include "../../lib/mapObjects/CGTownInstance.h"
  15. #include "../CGameInfo.h"
  16. #include "../gui/CAnimation.h"
  17. #include "CBattleInterface.h"
  18. #include "CBattleSiegeController.h"
  19. #include "CBattleStacksController.h"
  20. #include "CCreatureAnimation.h"
  21. CatapultProjectileInfo::CatapultProjectileInfo(const Point &from, const Point &dest)
  22. {
  23. facA = 0.005; // seems to be constant
  24. // system of 2 linear equations, solutions of which are missing coefficients
  25. // for quadratic equation a*x*x + b*x + c
  26. double eq[2][3] = {
  27. { static_cast<double>(from.x), 1.0, from.y - facA*from.x*from.x },
  28. { static_cast<double>(dest.x), 1.0, dest.y - facA*dest.x*dest.x }
  29. };
  30. // solve system via determinants
  31. double det = eq[0][0] *eq[1][1] - eq[1][0] *eq[0][1];
  32. double detB = eq[0][2] *eq[1][1] - eq[1][2] *eq[0][1];
  33. double detC = eq[0][0] *eq[1][2] - eq[1][0] *eq[0][2];
  34. facB = detB / det;
  35. facC = detC / det;
  36. // make sure that parabola is correct e.g. passes through from and dest
  37. assert(fabs(calculateY(from.x) - from.y) < 1.0);
  38. assert(fabs(calculateY(dest.x) - dest.y) < 1.0);
  39. }
  40. double CatapultProjectileInfo::calculateY(double x)
  41. {
  42. return facA *pow(x, 2.0) + facB *x + facC;
  43. }
  44. CBattleProjectileController::CBattleProjectileController(CBattleInterface * owner):
  45. owner(owner)
  46. {
  47. }
  48. void CBattleProjectileController::initStackProjectile(const CStack * stack)
  49. {
  50. const CCreature * creature;//creature whose shots should be loaded
  51. if(stack->getCreature()->idNumber == CreatureID::ARROW_TOWERS)
  52. creature = owner->siegeController->getTurretCreature();
  53. else
  54. creature = stack->getCreature();
  55. if (creature->animation.projectileRay.empty())
  56. {
  57. std::shared_ptr<CAnimation> projectile = std::make_shared<CAnimation>(creature->animation.projectileImageName);
  58. projectile->preload();
  59. if(projectile->size(1) != 0)
  60. logAnim->error("Expected empty group 1 in stack projectile");
  61. else
  62. projectile->createFlippedGroup(0, 1);
  63. idToProjectile[stack->getCreature()->idNumber] = projectile;
  64. }
  65. else
  66. {
  67. idToRay[stack->getCreature()->idNumber] = creature->animation.projectileRay;
  68. }
  69. }
  70. void CBattleProjectileController::fireStackProjectile(const CStack * stack)
  71. {
  72. for (auto it = projectiles.begin(); it!=projectiles.end(); ++it)
  73. {
  74. if ( !it->shotDone && it->stackID == stack->ID)
  75. {
  76. it->shotDone = true;
  77. return;
  78. }
  79. }
  80. }
  81. void CBattleProjectileController::showProjectiles(SDL_Surface *to)
  82. {
  83. assert(to);
  84. std::list< std::list<ProjectileInfo>::iterator > toBeDeleted;
  85. for (auto it = projectiles.begin(); it!=projectiles.end(); ++it)
  86. {
  87. // Check if projectile is already visible (shooter animation did the shot)
  88. if (!it->shotDone)
  89. continue;
  90. if (idToProjectile.count(it->creID))
  91. {
  92. size_t group = it->reverse ? 1 : 0;
  93. auto image = idToProjectile[it->creID]->getImage(it->frameNum, group, true);
  94. if(image)
  95. {
  96. SDL_Rect dst;
  97. dst.h = image->height();
  98. dst.w = image->width();
  99. dst.x = static_cast<int>(it->x - dst.w / 2);
  100. dst.y = static_cast<int>(it->y - dst.h / 2);
  101. image->draw(to, &dst, nullptr);
  102. }
  103. }
  104. if (idToRay.count(it->creID))
  105. {
  106. auto const & ray = idToRay[it->creID];
  107. if (std::abs(it->dx) > std::abs(it->dy)) // draw in horizontal axis
  108. {
  109. int y1 = it->y0 - ray.size() / 2;
  110. int y2 = it->y - ray.size() / 2;
  111. int x1 = it->x0;
  112. int x2 = it->x;
  113. for (size_t i = 0; i < ray.size(); ++i)
  114. {
  115. SDL_Color beginColor{ ray[i].r1, ray[i].g1, ray[i].b1, ray[i].a1};
  116. SDL_Color endColor { ray[i].r2, ray[i].g2, ray[i].b2, ray[i].a2};
  117. CSDL_Ext::drawLine(to, x1, y1 + i, x2, y2 + i, beginColor, endColor);
  118. }
  119. }
  120. else // draw in vertical axis
  121. {
  122. int x1 = it->x0 - ray.size() / 2;
  123. int x2 = it->x - ray.size() / 2;
  124. int y1 = it->y0;
  125. int y2 = it->y;
  126. for (size_t i = 0; i < ray.size(); ++i)
  127. {
  128. SDL_Color beginColor{ ray[i].r1, ray[i].g1, ray[i].b1, ray[i].a1};
  129. SDL_Color endColor { ray[i].r2, ray[i].g2, ray[i].b2, ray[i].a2};
  130. CSDL_Ext::drawLine(to, x1 + i, y1, x2 + i, y2, beginColor, endColor);
  131. }
  132. }
  133. }
  134. // Update projectile
  135. ++it->step;
  136. if (it->step > it->lastStep)
  137. {
  138. toBeDeleted.insert(toBeDeleted.end(), it);
  139. }
  140. else
  141. {
  142. if (it->catapultInfo)
  143. {
  144. // Parabolic shot of the trajectory, as follows: f(x) = ax^2 + bx + c
  145. it->x += it->dx;
  146. it->y = it->catapultInfo->calculateY(it->x);
  147. ++(it->frameNum);
  148. it->frameNum %= idToProjectile[it->creID]->size(0);
  149. }
  150. else
  151. {
  152. // Normal projectile, just add the calculated "deltas" to the x and y positions.
  153. it->x += it->dx;
  154. it->y += it->dy;
  155. }
  156. }
  157. }
  158. for (auto & elem : toBeDeleted)
  159. projectiles.erase(elem);
  160. }
  161. bool CBattleProjectileController::hasActiveProjectile(const CStack * stack)
  162. {
  163. for(auto const & instance : projectiles)
  164. {
  165. if(instance.creID == stack->getCreature()->idNumber)
  166. {
  167. return true;
  168. }
  169. }
  170. return false;
  171. }
  172. void CBattleProjectileController::createProjectile(const CStack * shooter, const CStack * target, Point from, Point dest)
  173. {
  174. // Get further info about the shooter e.g. relative pos of projectile to unit.
  175. // If the creature id is 149 then it's a arrow tower which has no additional info so get the
  176. // actual arrow tower shooter instead.
  177. const CCreature *shooterInfo = shooter->getCreature();
  178. if(shooterInfo->idNumber == CreatureID::ARROW_TOWERS)
  179. shooterInfo = owner->siegeController->getTurretCreature();
  180. if(!shooterInfo->animation.missleFrameAngles.size())
  181. logAnim->error("Mod error: Creature '%s' on the Archer's tower is not a shooter. Mod should be fixed. Trying to use archer's data instead..."
  182. , shooterInfo->nameSing);
  183. auto & angles = shooterInfo->animation.missleFrameAngles.size()
  184. ? shooterInfo->animation.missleFrameAngles
  185. : CGI->creh->operator[](CreatureID::ARCHER)->animation.missleFrameAngles;
  186. // recalculate angle taking in account offsets
  187. //projectileAngle = atan2(fabs(destPos.y - spi.y), fabs(destPos.x - spi.x));
  188. //if(shooter->position < dest)
  189. // projectileAngle = -projectileAngle;
  190. ProjectileInfo spi;
  191. spi.shotDone = false;
  192. spi.creID = shooter->getCreature()->idNumber;
  193. spi.stackID = shooter->ID;
  194. // reverse if creature is facing right OR this is non-existing stack that is not tower (war machines)
  195. spi.reverse = shooter ? !owner->stacksController->facingRight(shooter) : shooter->getCreature()->idNumber != CreatureID::ARROW_TOWERS;
  196. spi.step = 0;
  197. spi.frameNum = 0;
  198. spi.x0 = from.x;
  199. spi.y0 = from.y;
  200. spi.x = from.x;
  201. spi.y = from.y;
  202. if (target)
  203. {
  204. double animSpeed = AnimationControls::getProjectileSpeed(); // flight speed of projectile
  205. double distanceSquared = (dest.x - spi.x) * (dest.x - spi.x) + (dest.y - spi.y) * (dest.y - spi.y);
  206. double distance = sqrt(distanceSquared);
  207. spi.lastStep = std::round(distance / animSpeed);
  208. if(spi.lastStep == 0)
  209. spi.lastStep = 1;
  210. spi.dx = (dest.x - spi.x) / spi.lastStep;
  211. spi.dy = (dest.y - spi.y) / spi.lastStep;
  212. }
  213. else
  214. {
  215. // Catapult attack
  216. spi.catapultInfo.reset(new CatapultProjectileInfo(Point((int)spi.x, (int)spi.y), dest));
  217. double animSpeed = AnimationControls::getProjectileSpeed() / 10;
  218. spi.lastStep = static_cast<int>(std::abs((dest.x - spi.x) / animSpeed));
  219. spi.dx = animSpeed;
  220. spi.dy = 0;
  221. auto img = idToProjectile[spi.creID]->getImage(0);
  222. // Add explosion anim
  223. Point animPos(dest.x - 126 + img->width() / 2,
  224. dest.y - 105 + img->height() / 2);
  225. //owner->addNewAnim( new CEffectAnimation(owner, catapultDamage ? "SGEXPL.DEF" : "CSGRCK.DEF", animPos.x, animPos.y));
  226. }
  227. double pi = std::atan(1)*4;
  228. //in some cases (known one: hero grants shooter bonus to unit) the shooter stack's projectile may not be properly initialized
  229. if (!idToProjectile.count(spi.creID) && !idToRay.count(spi.creID))
  230. initStackProjectile(shooter);
  231. if (idToProjectile.count(spi.creID))
  232. {
  233. // only frames below maxFrame are usable: anything higher is either no present or we don't know when it should be used
  234. size_t maxFrame = std::min<size_t>(angles.size(), idToProjectile.at(spi.creID)->size(0));
  235. assert(maxFrame > 0);
  236. double projectileAngle = atan2(fabs((double)dest.y - from.y), fabs((double)dest.x - from.x));
  237. //if(shooter->getPosition() < dest)
  238. // projectileAngle = -projectileAngle;
  239. // values in angles array indicate position from which this frame was rendered, in degrees.
  240. // find frame that has closest angle to one that we need for this shot
  241. size_t bestID = 0;
  242. double bestDiff = fabs( angles[0] / 180 * pi - projectileAngle );
  243. for (size_t i=1; i<maxFrame; i++)
  244. {
  245. double currentDiff = fabs( angles[i] / 180 * pi - projectileAngle );
  246. if (currentDiff < bestDiff)
  247. {
  248. bestID = i;
  249. bestDiff = currentDiff;
  250. }
  251. }
  252. spi.frameNum = static_cast<int>(bestID);
  253. }
  254. else if (idToRay.count(spi.creID))
  255. {
  256. // no-op
  257. }
  258. else
  259. {
  260. logGlobal->error("Unable to find valid projectile for shooter %d", spi.creID);
  261. }
  262. // Set projectile animation start delay which is specified in frames
  263. projectiles.push_back(spi);
  264. }