combat_system.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329
  1. #include "combat_system.h"
  2. #include "../core/event_manager.h"
  3. #include "../core/component.h"
  4. #include "../core/world.h"
  5. #include "../visuals/team_colors.h"
  6. #include "arrow_system.h"
  7. #include "building_collision_registry.h"
  8. #include "command_service.h"
  9. #include <algorithm>
  10. #include <limits>
  11. namespace Game::Systems {
  12. void CombatSystem::update(Engine::Core::World *world, float deltaTime) {
  13. processAttacks(world, deltaTime);
  14. }
  15. void CombatSystem::processAttacks(Engine::Core::World *world, float deltaTime) {
  16. auto units = world->getEntitiesWith<Engine::Core::UnitComponent>();
  17. ArrowSystem *arrowSys = nullptr;
  18. for (auto &sys : world->systems()) {
  19. arrowSys = dynamic_cast<ArrowSystem *>(sys.get());
  20. if (arrowSys)
  21. break;
  22. }
  23. for (auto attacker : units) {
  24. auto attackerUnit = attacker->getComponent<Engine::Core::UnitComponent>();
  25. auto attackerTransform =
  26. attacker->getComponent<Engine::Core::TransformComponent>();
  27. auto attackerAtk = attacker->getComponent<Engine::Core::AttackComponent>();
  28. if (!attackerUnit || !attackerTransform) {
  29. continue;
  30. }
  31. if (attackerUnit->health <= 0)
  32. continue;
  33. float range = 2.0f;
  34. int damage = 10;
  35. float cooldown = 1.0f;
  36. float *tAccum = nullptr;
  37. float tmpAccum = 0.0f;
  38. if (attackerAtk) {
  39. range = std::max(0.1f, attackerAtk->range);
  40. damage = std::max(0, attackerAtk->damage);
  41. cooldown = std::max(0.05f, attackerAtk->cooldown);
  42. attackerAtk->timeSinceLast += deltaTime;
  43. tAccum = &attackerAtk->timeSinceLast;
  44. } else {
  45. tmpAccum += deltaTime;
  46. tAccum = &tmpAccum;
  47. }
  48. if (*tAccum < cooldown) {
  49. continue;
  50. }
  51. auto *attackTarget =
  52. attacker->getComponent<Engine::Core::AttackTargetComponent>();
  53. Engine::Core::Entity *bestTarget = nullptr;
  54. if (attackTarget && attackTarget->targetId != 0) {
  55. auto *target = world->getEntity(attackTarget->targetId);
  56. if (target) {
  57. auto *targetUnit = target->getComponent<Engine::Core::UnitComponent>();
  58. if (targetUnit && targetUnit->health > 0 &&
  59. targetUnit->ownerId != attackerUnit->ownerId) {
  60. if (isInRange(attacker, target, range)) {
  61. bestTarget = target;
  62. if (auto *attT =
  63. attacker
  64. ->getComponent<Engine::Core::TransformComponent>()) {
  65. if (auto *tgtT =
  66. target
  67. ->getComponent<Engine::Core::TransformComponent>()) {
  68. float dx = tgtT->position.x - attT->position.x;
  69. float dz = tgtT->position.z - attT->position.z;
  70. float yaw = std::atan2(dx, dz) * 180.0f / 3.14159265f;
  71. attT->desiredYaw = yaw;
  72. attT->hasDesiredYaw = true;
  73. }
  74. }
  75. } else if (attackTarget->shouldChase) {
  76. auto *targetTransform =
  77. target->getComponent<Engine::Core::TransformComponent>();
  78. auto *attackerTransformComponent =
  79. attacker->getComponent<Engine::Core::TransformComponent>();
  80. if (targetTransform && attackerTransformComponent) {
  81. QVector3D attackerPos(attackerTransformComponent->position.x,
  82. 0.0f,
  83. attackerTransformComponent->position.z);
  84. QVector3D targetPos(targetTransform->position.x, 0.0f,
  85. targetTransform->position.z);
  86. QVector3D desiredPos = targetPos;
  87. bool holdPosition = false;
  88. bool targetIsBuilding =
  89. target->hasComponent<Engine::Core::BuildingComponent>();
  90. if (targetIsBuilding) {
  91. float scaleX = targetTransform->scale.x;
  92. float scaleZ = targetTransform->scale.z;
  93. float targetRadius = std::max(scaleX, scaleZ) * 0.5f;
  94. QVector3D direction = targetPos - attackerPos;
  95. float distance = direction.length();
  96. if (distance > 0.001f) {
  97. direction /= distance;
  98. float desiredDistance =
  99. targetRadius + std::max(range - 0.2f, 0.2f);
  100. if (distance > desiredDistance + 0.15f) {
  101. desiredPos = targetPos - direction * desiredDistance;
  102. } else {
  103. holdPosition = true;
  104. }
  105. }
  106. }
  107. auto *movement =
  108. attacker->getComponent<Engine::Core::MovementComponent>();
  109. if (!movement) {
  110. movement =
  111. attacker->addComponent<Engine::Core::MovementComponent>();
  112. }
  113. if (movement) {
  114. if (holdPosition) {
  115. movement->hasTarget = false;
  116. movement->vx = 0.0f;
  117. movement->vz = 0.0f;
  118. movement->path.clear();
  119. if (attackerTransformComponent) {
  120. movement->targetX = attackerTransformComponent->position.x;
  121. movement->targetY = attackerTransformComponent->position.z;
  122. movement->goalX = attackerTransformComponent->position.x;
  123. movement->goalY = attackerTransformComponent->position.z;
  124. }
  125. } else {
  126. QVector3D plannedTarget(movement->targetX, 0.0f,
  127. movement->targetY);
  128. if (!movement->path.empty()) {
  129. const auto &finalNode = movement->path.back();
  130. plannedTarget =
  131. QVector3D(finalNode.first, 0.0f, finalNode.second);
  132. }
  133. float diffSq = (plannedTarget - desiredPos).lengthSquared();
  134. bool needNewCommand = !movement->pathPending;
  135. if (movement->hasTarget && diffSq <= 0.25f * 0.25f) {
  136. needNewCommand = false;
  137. }
  138. if (needNewCommand) {
  139. CommandService::MoveOptions options;
  140. options.clearAttackIntent = false;
  141. options.allowDirectFallback = true;
  142. std::vector<Engine::Core::EntityID> unitIds = {
  143. attacker->getId()};
  144. std::vector<QVector3D> moveTargets = {desiredPos};
  145. CommandService::moveUnits(*world, unitIds, moveTargets,
  146. options);
  147. }
  148. }
  149. }
  150. }
  151. if (isInRange(attacker, target, range)) {
  152. bestTarget = target;
  153. }
  154. } else {
  155. attacker->removeComponent<Engine::Core::AttackTargetComponent>();
  156. }
  157. } else {
  158. attacker->removeComponent<Engine::Core::AttackTargetComponent>();
  159. }
  160. } else {
  161. attacker->removeComponent<Engine::Core::AttackTargetComponent>();
  162. }
  163. }
  164. if (!bestTarget && !attackTarget) {
  165. for (auto target : units) {
  166. if (target == attacker) {
  167. continue;
  168. }
  169. auto targetUnit = target->getComponent<Engine::Core::UnitComponent>();
  170. if (!targetUnit || targetUnit->health <= 0) {
  171. continue;
  172. }
  173. if (targetUnit->ownerId == attackerUnit->ownerId) {
  174. continue;
  175. }
  176. if (target->hasComponent<Engine::Core::BuildingComponent>()) {
  177. continue;
  178. }
  179. if (isInRange(attacker, target, range)) {
  180. bestTarget = target;
  181. break;
  182. }
  183. }
  184. }
  185. if (bestTarget) {
  186. auto *bestTargetUnit =
  187. bestTarget->getComponent<Engine::Core::UnitComponent>();
  188. if (!attacker->hasComponent<Engine::Core::AttackTargetComponent>()) {
  189. auto *newTarget =
  190. attacker->addComponent<Engine::Core::AttackTargetComponent>();
  191. newTarget->targetId = bestTarget->getId();
  192. newTarget->shouldChase = false;
  193. } else {
  194. auto *existingTarget =
  195. attacker->getComponent<Engine::Core::AttackTargetComponent>();
  196. if (existingTarget->targetId != bestTarget->getId()) {
  197. existingTarget->targetId = bestTarget->getId();
  198. existingTarget->shouldChase = false;
  199. }
  200. }
  201. if (auto *attT =
  202. attacker->getComponent<Engine::Core::TransformComponent>()) {
  203. if (auto *tgtT =
  204. bestTarget->getComponent<Engine::Core::TransformComponent>()) {
  205. float dx = tgtT->position.x - attT->position.x;
  206. float dz = tgtT->position.z - attT->position.z;
  207. float yaw = std::atan2(dx, dz) * 180.0f / 3.14159265f;
  208. attT->desiredYaw = yaw;
  209. attT->hasDesiredYaw = true;
  210. }
  211. }
  212. if (arrowSys) {
  213. auto attT = attacker->getComponent<Engine::Core::TransformComponent>();
  214. auto tgtT =
  215. bestTarget->getComponent<Engine::Core::TransformComponent>();
  216. auto attU = attacker->getComponent<Engine::Core::UnitComponent>();
  217. QVector3D aPos(attT->position.x, attT->position.y, attT->position.z);
  218. QVector3D tPos(tgtT->position.x, tgtT->position.y, tgtT->position.z);
  219. QVector3D dir = (tPos - aPos).normalized();
  220. QVector3D start = aPos + QVector3D(0.0f, 0.6f, 0.0f) + dir * 0.35f;
  221. QVector3D end = tPos + QVector3D(0.5f, 0.5f, 0.0f);
  222. QVector3D color = attU ? Game::Visuals::teamColorForOwner(attU->ownerId)
  223. : QVector3D(0.8f, 0.9f, 1.0f);
  224. arrowSys->spawnArrow(start, end, color, 14.0f);
  225. }
  226. dealDamage(bestTarget, damage);
  227. *tAccum = 0.0f;
  228. } else {
  229. if (!attackTarget &&
  230. attacker->hasComponent<Engine::Core::AttackTargetComponent>()) {
  231. attacker->removeComponent<Engine::Core::AttackTargetComponent>();
  232. }
  233. }
  234. }
  235. }
  236. bool CombatSystem::isInRange(Engine::Core::Entity *attacker,
  237. Engine::Core::Entity *target, float range) {
  238. auto attackerTransform =
  239. attacker->getComponent<Engine::Core::TransformComponent>();
  240. auto targetTransform =
  241. target->getComponent<Engine::Core::TransformComponent>();
  242. if (!attackerTransform || !targetTransform) {
  243. return false;
  244. }
  245. float dx = targetTransform->position.x - attackerTransform->position.x;
  246. float dz = targetTransform->position.z - attackerTransform->position.z;
  247. float distanceSquared = dx * dx + dz * dz;
  248. float targetRadius = 0.0f;
  249. if (target->hasComponent<Engine::Core::BuildingComponent>()) {
  250. float scaleX = targetTransform->scale.x;
  251. float scaleZ = targetTransform->scale.z;
  252. targetRadius = std::max(scaleX, scaleZ) * 0.5f;
  253. } else {
  254. float scaleX = targetTransform->scale.x;
  255. float scaleZ = targetTransform->scale.z;
  256. targetRadius = std::max(scaleX, scaleZ) * 0.5f;
  257. }
  258. float effectiveRange = range + targetRadius;
  259. return distanceSquared <= effectiveRange * effectiveRange;
  260. }
  261. void CombatSystem::dealDamage(Engine::Core::Entity *target, int damage) {
  262. auto unit = target->getComponent<Engine::Core::UnitComponent>();
  263. if (unit) {
  264. unit->health = std::max(0, unit->health - damage);
  265. if (unit->health <= 0) {
  266. // publish unit died event
  267. Engine::Core::EventManager::instance().publish(
  268. Engine::Core::UnitDiedEvent(target->getId(), unit->ownerId));
  269. if (target->hasComponent<Engine::Core::BuildingComponent>()) {
  270. BuildingCollisionRegistry::instance().unregisterBuilding(
  271. target->getId());
  272. }
  273. if (auto *r = target->getComponent<Engine::Core::RenderableComponent>()) {
  274. r->visible = false;
  275. }
  276. }
  277. }
  278. }
  279. } // namespace Game::Systems