combat_system.cpp 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931
  1. #include "combat_system.h"
  2. #include "../core/component.h"
  3. #include "../core/event_manager.h"
  4. #include "../core/world.h"
  5. #include "../units/troop_config.h"
  6. #include "../visuals/team_colors.h"
  7. #include "arrow_system.h"
  8. #include "building_collision_registry.h"
  9. #include "command_service.h"
  10. #include "owner_registry.h"
  11. #include <algorithm>
  12. #include <cmath>
  13. #include <limits>
  14. #include <random>
  15. namespace Game::Systems {
  16. void CombatSystem::update(Engine::Core::World *world, float deltaTime) {
  17. processAttacks(world, deltaTime);
  18. processAutoEngagement(world, deltaTime);
  19. }
  20. void CombatSystem::processAttacks(Engine::Core::World *world, float deltaTime) {
  21. auto units = world->getEntitiesWith<Engine::Core::UnitComponent>();
  22. ArrowSystem *arrowSys = world->getSystem<ArrowSystem>();
  23. for (auto attacker : units) {
  24. if (attacker->hasComponent<Engine::Core::PendingRemovalComponent>()) {
  25. continue;
  26. }
  27. auto attackerUnit = attacker->getComponent<Engine::Core::UnitComponent>();
  28. auto attackerTransform =
  29. attacker->getComponent<Engine::Core::TransformComponent>();
  30. auto attackerAtk = attacker->getComponent<Engine::Core::AttackComponent>();
  31. if (!attackerUnit || !attackerTransform) {
  32. continue;
  33. }
  34. if (attackerUnit->health <= 0) {
  35. continue;
  36. }
  37. if (attackerAtk && attackerAtk->inMeleeLock) {
  38. auto *lockTarget = world->getEntity(attackerAtk->meleeLockTargetId);
  39. if (!lockTarget ||
  40. lockTarget->hasComponent<Engine::Core::PendingRemovalComponent>()) {
  41. attackerAtk->inMeleeLock = false;
  42. attackerAtk->meleeLockTargetId = 0;
  43. } else {
  44. auto *lockTargetUnit =
  45. lockTarget->getComponent<Engine::Core::UnitComponent>();
  46. if (!lockTargetUnit || lockTargetUnit->health <= 0) {
  47. attackerAtk->inMeleeLock = false;
  48. attackerAtk->meleeLockTargetId = 0;
  49. } else {
  50. auto *attT = attackerTransform;
  51. auto *tgtT =
  52. lockTarget->getComponent<Engine::Core::TransformComponent>();
  53. if (attT && tgtT) {
  54. float dx = tgtT->position.x - attT->position.x;
  55. float dz = tgtT->position.z - attT->position.z;
  56. float dist = std::sqrt(dx * dx + dz * dz);
  57. const float IDEAL_MELEE_DISTANCE = 0.6f;
  58. const float MAX_MELEE_SEPARATION = 0.9f;
  59. if (dist > MAX_MELEE_SEPARATION) {
  60. float pullAmount =
  61. (dist - IDEAL_MELEE_DISTANCE) * 0.3f * deltaTime * 5.0f;
  62. if (dist > 0.001f) {
  63. QVector3D direction(dx / dist, 0.0f, dz / dist);
  64. attT->position.x += direction.x() * pullAmount;
  65. attT->position.z += direction.z() * pullAmount;
  66. }
  67. }
  68. }
  69. }
  70. }
  71. }
  72. if (attackerAtk && attackerAtk->inMeleeLock &&
  73. attackerAtk->meleeLockTargetId != 0) {
  74. auto *lockTarget = world->getEntity(attackerAtk->meleeLockTargetId);
  75. if (lockTarget &&
  76. !lockTarget->hasComponent<Engine::Core::PendingRemovalComponent>()) {
  77. auto *attackTarget =
  78. attacker->getComponent<Engine::Core::AttackTargetComponent>();
  79. if (!attackTarget) {
  80. attackTarget =
  81. attacker->addComponent<Engine::Core::AttackTargetComponent>();
  82. }
  83. if (attackTarget) {
  84. attackTarget->targetId = attackerAtk->meleeLockTargetId;
  85. attackTarget->shouldChase = false;
  86. }
  87. }
  88. }
  89. float range = 2.0f;
  90. int damage = 10;
  91. float cooldown = 1.0f;
  92. float *tAccum = nullptr;
  93. float tmpAccum = 0.0f;
  94. if (attackerAtk) {
  95. updateCombatMode(attacker, world, attackerAtk);
  96. range = attackerAtk->getCurrentRange();
  97. damage = attackerAtk->getCurrentDamage();
  98. cooldown = attackerAtk->getCurrentCooldown();
  99. auto *holdMode =
  100. attacker->getComponent<Engine::Core::HoldModeComponent>();
  101. if (holdMode && holdMode->active) {
  102. if (attackerUnit->spawnType == Game::Units::SpawnType::Archer) {
  103. range *= 1.5f;
  104. damage = static_cast<int>(damage * 1.3f);
  105. } else if (attackerUnit->spawnType ==
  106. Game::Units::SpawnType::Spearman) {
  107. damage = static_cast<int>(damage * 1.4f);
  108. }
  109. }
  110. attackerAtk->timeSinceLast += deltaTime;
  111. tAccum = &attackerAtk->timeSinceLast;
  112. } else {
  113. tmpAccum += deltaTime;
  114. tAccum = &tmpAccum;
  115. }
  116. if (*tAccum < cooldown) {
  117. continue;
  118. }
  119. auto *attackTarget =
  120. attacker->getComponent<Engine::Core::AttackTargetComponent>();
  121. Engine::Core::Entity *bestTarget = nullptr;
  122. if (attackTarget && attackTarget->targetId != 0) {
  123. auto *target = world->getEntity(attackTarget->targetId);
  124. if (target &&
  125. !target->hasComponent<Engine::Core::PendingRemovalComponent>()) {
  126. auto *targetUnit = target->getComponent<Engine::Core::UnitComponent>();
  127. auto &ownerRegistry = Game::Systems::OwnerRegistry::instance();
  128. bool isAlly =
  129. ownerRegistry.areAllies(attackerUnit->ownerId, targetUnit->ownerId);
  130. if (targetUnit && targetUnit->health > 0 &&
  131. targetUnit->ownerId != attackerUnit->ownerId && !isAlly) {
  132. if (isInRange(attacker, target, range)) {
  133. bestTarget = target;
  134. bool isRangedUnit = false;
  135. if (attackerAtk && attackerAtk->canRanged &&
  136. attackerAtk->currentMode ==
  137. Engine::Core::AttackComponent::CombatMode::Ranged) {
  138. isRangedUnit = true;
  139. }
  140. if (isRangedUnit) {
  141. auto *movement =
  142. attacker->getComponent<Engine::Core::MovementComponent>();
  143. if (movement && movement->hasTarget) {
  144. movement->hasTarget = false;
  145. movement->vx = 0.0f;
  146. movement->vz = 0.0f;
  147. movement->path.clear();
  148. if (attackerTransform) {
  149. movement->targetX = attackerTransform->position.x;
  150. movement->targetY = attackerTransform->position.z;
  151. movement->goalX = attackerTransform->position.x;
  152. movement->goalY = attackerTransform->position.z;
  153. }
  154. }
  155. }
  156. if (auto *attT =
  157. attacker
  158. ->getComponent<Engine::Core::TransformComponent>()) {
  159. if (auto *tgtT =
  160. target
  161. ->getComponent<Engine::Core::TransformComponent>()) {
  162. float dx = tgtT->position.x - attT->position.x;
  163. float dz = tgtT->position.z - attT->position.z;
  164. float yaw = std::atan2(dx, dz) * 180.0f / 3.14159265f;
  165. attT->desiredYaw = yaw;
  166. attT->hasDesiredYaw = true;
  167. }
  168. }
  169. } else if (attackTarget->shouldChase) {
  170. auto *holdMode =
  171. attacker->getComponent<Engine::Core::HoldModeComponent>();
  172. if (holdMode && holdMode->active) {
  173. if (!isInRange(attacker, target, range)) {
  174. attacker
  175. ->removeComponent<Engine::Core::AttackTargetComponent>();
  176. }
  177. continue;
  178. }
  179. bool isRangedUnit = false;
  180. if (attackerAtk && attackerAtk->canRanged &&
  181. attackerAtk->currentMode ==
  182. Engine::Core::AttackComponent::CombatMode::Ranged) {
  183. isRangedUnit = true;
  184. }
  185. bool currentlyInRange = isInRange(attacker, target, range);
  186. if (isRangedUnit && currentlyInRange) {
  187. auto *movement =
  188. attacker->getComponent<Engine::Core::MovementComponent>();
  189. if (movement) {
  190. movement->hasTarget = false;
  191. movement->vx = 0.0f;
  192. movement->vz = 0.0f;
  193. movement->path.clear();
  194. auto *attackerTransformComponent =
  195. attacker->getComponent<Engine::Core::TransformComponent>();
  196. if (attackerTransformComponent) {
  197. movement->targetX = attackerTransformComponent->position.x;
  198. movement->targetY = attackerTransformComponent->position.z;
  199. movement->goalX = attackerTransformComponent->position.x;
  200. movement->goalY = attackerTransformComponent->position.z;
  201. }
  202. }
  203. bestTarget = target;
  204. } else {
  205. auto *targetTransform =
  206. target->getComponent<Engine::Core::TransformComponent>();
  207. auto *attackerTransformComponent =
  208. attacker->getComponent<Engine::Core::TransformComponent>();
  209. if (targetTransform && attackerTransformComponent) {
  210. QVector3D attackerPos(attackerTransformComponent->position.x,
  211. 0.0f,
  212. attackerTransformComponent->position.z);
  213. QVector3D targetPos(targetTransform->position.x, 0.0f,
  214. targetTransform->position.z);
  215. QVector3D desiredPos = targetPos;
  216. bool holdPosition = false;
  217. bool targetIsBuilding =
  218. target->hasComponent<Engine::Core::BuildingComponent>();
  219. if (targetIsBuilding) {
  220. float scaleX = targetTransform->scale.x;
  221. float scaleZ = targetTransform->scale.z;
  222. float targetRadius = std::max(scaleX, scaleZ) * 0.5f;
  223. QVector3D direction = targetPos - attackerPos;
  224. float distanceSq = direction.lengthSquared();
  225. if (distanceSq > 0.000001f) {
  226. float distance = std::sqrt(distanceSq);
  227. direction /= distance;
  228. float desiredDistance =
  229. targetRadius + std::max(range - 0.2f, 0.2f);
  230. if (distance > desiredDistance + 0.15f) {
  231. desiredPos = targetPos - direction * desiredDistance;
  232. } else {
  233. holdPosition = true;
  234. }
  235. }
  236. } else if (isRangedUnit) {
  237. QVector3D direction = targetPos - attackerPos;
  238. float distanceSq = direction.lengthSquared();
  239. if (distanceSq > 0.000001f) {
  240. float distance = std::sqrt(distanceSq);
  241. direction /= distance;
  242. float optimalRange = range * 0.85f;
  243. if (distance > optimalRange + 0.5f) {
  244. desiredPos = targetPos - direction * optimalRange;
  245. } else {
  246. holdPosition = true;
  247. }
  248. }
  249. }
  250. auto *movement =
  251. attacker->getComponent<Engine::Core::MovementComponent>();
  252. if (!movement) {
  253. movement =
  254. attacker->addComponent<Engine::Core::MovementComponent>();
  255. }
  256. if (movement) {
  257. if (holdPosition) {
  258. movement->hasTarget = false;
  259. movement->vx = 0.0f;
  260. movement->vz = 0.0f;
  261. movement->path.clear();
  262. if (attackerTransformComponent) {
  263. movement->targetX =
  264. attackerTransformComponent->position.x;
  265. movement->targetY =
  266. attackerTransformComponent->position.z;
  267. movement->goalX = attackerTransformComponent->position.x;
  268. movement->goalY = attackerTransformComponent->position.z;
  269. }
  270. } else {
  271. QVector3D plannedTarget(movement->targetX, 0.0f,
  272. movement->targetY);
  273. if (!movement->path.empty()) {
  274. const auto &finalNode = movement->path.back();
  275. plannedTarget =
  276. QVector3D(finalNode.first, 0.0f, finalNode.second);
  277. }
  278. float diffSq = (plannedTarget - desiredPos).lengthSquared();
  279. bool needNewCommand = !movement->pathPending;
  280. if (movement->hasTarget && diffSq <= 0.25f * 0.25f) {
  281. needNewCommand = false;
  282. }
  283. if (needNewCommand) {
  284. CommandService::MoveOptions options;
  285. options.clearAttackIntent = false;
  286. options.allowDirectFallback = true;
  287. std::vector<Engine::Core::EntityID> unitIds = {
  288. attacker->getId()};
  289. std::vector<QVector3D> moveTargets = {desiredPos};
  290. CommandService::moveUnits(*world, unitIds, moveTargets,
  291. options);
  292. }
  293. }
  294. }
  295. }
  296. if (isInRange(attacker, target, range)) {
  297. bestTarget = target;
  298. }
  299. }
  300. } else {
  301. attacker->removeComponent<Engine::Core::AttackTargetComponent>();
  302. }
  303. } else {
  304. attacker->removeComponent<Engine::Core::AttackTargetComponent>();
  305. }
  306. } else {
  307. attacker->removeComponent<Engine::Core::AttackTargetComponent>();
  308. }
  309. }
  310. if (!bestTarget && !attackTarget) {
  311. auto &ownerRegistry = Game::Systems::OwnerRegistry::instance();
  312. for (auto target : units) {
  313. if (target == attacker) {
  314. continue;
  315. }
  316. auto targetUnit = target->getComponent<Engine::Core::UnitComponent>();
  317. if (!targetUnit || targetUnit->health <= 0) {
  318. continue;
  319. }
  320. if (targetUnit->ownerId == attackerUnit->ownerId) {
  321. continue;
  322. }
  323. if (ownerRegistry.areAllies(attackerUnit->ownerId,
  324. targetUnit->ownerId)) {
  325. continue;
  326. }
  327. if (target->hasComponent<Engine::Core::BuildingComponent>()) {
  328. continue;
  329. }
  330. if (isInRange(attacker, target, range)) {
  331. bestTarget = target;
  332. break;
  333. }
  334. }
  335. }
  336. if (bestTarget) {
  337. auto *bestTargetUnit =
  338. bestTarget->getComponent<Engine::Core::UnitComponent>();
  339. if (!attacker->hasComponent<Engine::Core::AttackTargetComponent>()) {
  340. auto *newTarget =
  341. attacker->addComponent<Engine::Core::AttackTargetComponent>();
  342. newTarget->targetId = bestTarget->getId();
  343. newTarget->shouldChase = false;
  344. } else {
  345. auto *existingTarget =
  346. attacker->getComponent<Engine::Core::AttackTargetComponent>();
  347. if (existingTarget->targetId != bestTarget->getId()) {
  348. existingTarget->targetId = bestTarget->getId();
  349. existingTarget->shouldChase = false;
  350. }
  351. }
  352. bool isRangedUnit = false;
  353. if (attackerAtk && attackerAtk->canRanged &&
  354. attackerAtk->currentMode ==
  355. Engine::Core::AttackComponent::CombatMode::Ranged) {
  356. isRangedUnit = true;
  357. }
  358. if (isRangedUnit) {
  359. auto *movement =
  360. attacker->getComponent<Engine::Core::MovementComponent>();
  361. if (movement && movement->hasTarget) {
  362. movement->hasTarget = false;
  363. movement->vx = 0.0f;
  364. movement->vz = 0.0f;
  365. movement->path.clear();
  366. if (attackerTransform) {
  367. movement->targetX = attackerTransform->position.x;
  368. movement->targetY = attackerTransform->position.z;
  369. movement->goalX = attackerTransform->position.x;
  370. movement->goalY = attackerTransform->position.z;
  371. }
  372. }
  373. }
  374. if (auto *attT =
  375. attacker->getComponent<Engine::Core::TransformComponent>()) {
  376. if (auto *tgtT =
  377. bestTarget->getComponent<Engine::Core::TransformComponent>()) {
  378. float dx = tgtT->position.x - attT->position.x;
  379. float dz = tgtT->position.z - attT->position.z;
  380. float yaw = std::atan2(dx, dz) * 180.0f / 3.14159265f;
  381. attT->desiredYaw = yaw;
  382. attT->hasDesiredYaw = true;
  383. }
  384. }
  385. if (arrowSys) {
  386. auto attT = attacker->getComponent<Engine::Core::TransformComponent>();
  387. auto tgtT =
  388. bestTarget->getComponent<Engine::Core::TransformComponent>();
  389. auto attU = attacker->getComponent<Engine::Core::UnitComponent>();
  390. if (!attackerAtk ||
  391. attackerAtk->currentMode !=
  392. Engine::Core::AttackComponent::CombatMode::Melee) {
  393. QVector3D aPos(attT->position.x, attT->position.y, attT->position.z);
  394. QVector3D tPos(tgtT->position.x, tgtT->position.y, tgtT->position.z);
  395. QVector3D dir = (tPos - aPos).normalized();
  396. QVector3D color =
  397. attU ? Game::Visuals::teamColorForOwner(attU->ownerId)
  398. : QVector3D(0.8f, 0.9f, 1.0f);
  399. int arrowCount = 1;
  400. if (attU) {
  401. int troopSize =
  402. Game::Units::TroopConfig::instance().getIndividualsPerUnit(
  403. attU->spawnType);
  404. int maxArrows = std::max(1, troopSize / 3);
  405. static thread_local std::mt19937 gen(std::random_device{}());
  406. std::uniform_int_distribution<> dist(1, maxArrows);
  407. arrowCount = dist(gen);
  408. }
  409. for (int i = 0; i < arrowCount; ++i) {
  410. static thread_local std::mt19937 spreadGen(std::random_device{}());
  411. std::uniform_real_distribution<float> spreadDist(-0.15f, 0.15f);
  412. QVector3D perpendicular(-dir.z(), 0.0f, dir.x());
  413. QVector3D upVector(0.0f, 1.0f, 0.0f);
  414. float lateralOffset = spreadDist(spreadGen);
  415. float verticalOffset = spreadDist(spreadGen) * 1.5f;
  416. float depthOffset = spreadDist(spreadGen) * 1.3f;
  417. QVector3D startOffset =
  418. perpendicular * lateralOffset + upVector * verticalOffset;
  419. QVector3D endOffset = perpendicular * lateralOffset +
  420. upVector * verticalOffset + dir * depthOffset;
  421. QVector3D start =
  422. aPos + QVector3D(0.0f, 0.6f, 0.0f) + dir * 0.35f + startOffset;
  423. QVector3D end = tPos + QVector3D(0.5f, 0.5f, 0.0f) + endOffset;
  424. arrowSys->spawnArrow(start, end, color, 14.0f);
  425. }
  426. }
  427. }
  428. if (attackerAtk && attackerAtk->currentMode ==
  429. Engine::Core::AttackComponent::CombatMode::Melee) {
  430. attackerAtk->inMeleeLock = true;
  431. attackerAtk->meleeLockTargetId = bestTarget->getId();
  432. auto *targetAtk =
  433. bestTarget->getComponent<Engine::Core::AttackComponent>();
  434. if (targetAtk) {
  435. targetAtk->inMeleeLock = true;
  436. targetAtk->meleeLockTargetId = attacker->getId();
  437. }
  438. auto *attT = attacker->getComponent<Engine::Core::TransformComponent>();
  439. auto *tgtT =
  440. bestTarget->getComponent<Engine::Core::TransformComponent>();
  441. if (attT && tgtT) {
  442. float dx = tgtT->position.x - attT->position.x;
  443. float dz = tgtT->position.z - attT->position.z;
  444. float dist = std::sqrt(dx * dx + dz * dz);
  445. const float IDEAL_MELEE_DISTANCE = 0.6f;
  446. if (dist > IDEAL_MELEE_DISTANCE + 0.1f) {
  447. float moveAmount = (dist - IDEAL_MELEE_DISTANCE) * 0.5f;
  448. if (dist > 0.001f) {
  449. QVector3D direction(dx / dist, 0.0f, dz / dist);
  450. attT->position.x += direction.x() * moveAmount;
  451. attT->position.z += direction.z() * moveAmount;
  452. tgtT->position.x -= direction.x() * moveAmount;
  453. tgtT->position.z -= direction.z() * moveAmount;
  454. }
  455. }
  456. }
  457. }
  458. dealDamage(world, bestTarget, damage, attacker->getId());
  459. *tAccum = 0.0f;
  460. } else {
  461. if (!attackTarget &&
  462. attacker->hasComponent<Engine::Core::AttackTargetComponent>()) {
  463. attacker->removeComponent<Engine::Core::AttackTargetComponent>();
  464. }
  465. }
  466. }
  467. }
  468. bool CombatSystem::isInRange(Engine::Core::Entity *attacker,
  469. Engine::Core::Entity *target, float range) {
  470. auto attackerTransform =
  471. attacker->getComponent<Engine::Core::TransformComponent>();
  472. auto targetTransform =
  473. target->getComponent<Engine::Core::TransformComponent>();
  474. if (!attackerTransform || !targetTransform) {
  475. return false;
  476. }
  477. float dx = targetTransform->position.x - attackerTransform->position.x;
  478. float dz = targetTransform->position.z - attackerTransform->position.z;
  479. float dy = targetTransform->position.y - attackerTransform->position.y;
  480. float distanceSquared = dx * dx + dz * dz;
  481. float targetRadius = 0.0f;
  482. if (target->hasComponent<Engine::Core::BuildingComponent>()) {
  483. float scaleX = targetTransform->scale.x;
  484. float scaleZ = targetTransform->scale.z;
  485. targetRadius = std::max(scaleX, scaleZ) * 0.5f;
  486. } else {
  487. float scaleX = targetTransform->scale.x;
  488. float scaleZ = targetTransform->scale.z;
  489. targetRadius = std::max(scaleX, scaleZ) * 0.5f;
  490. }
  491. float effectiveRange = range + targetRadius;
  492. if (distanceSquared > effectiveRange * effectiveRange) {
  493. return false;
  494. }
  495. auto attackerAtk = attacker->getComponent<Engine::Core::AttackComponent>();
  496. if (attackerAtk && attackerAtk->currentMode ==
  497. Engine::Core::AttackComponent::CombatMode::Melee) {
  498. float heightDiff = std::abs(dy);
  499. if (heightDiff > attackerAtk->maxHeightDifference) {
  500. return false;
  501. }
  502. }
  503. return true;
  504. }
  505. void CombatSystem::dealDamage(Engine::Core::World *world,
  506. Engine::Core::Entity *target, int damage,
  507. Engine::Core::EntityID attackerId) {
  508. auto unit = target->getComponent<Engine::Core::UnitComponent>();
  509. if (unit) {
  510. unit->health = std::max(0, unit->health - damage);
  511. int attackerOwnerId = 0;
  512. if (attackerId != 0 && world) {
  513. auto *attacker = world->getEntity(attackerId);
  514. if (attacker) {
  515. auto *attackerUnit =
  516. attacker->getComponent<Engine::Core::UnitComponent>();
  517. if (attackerUnit) {
  518. attackerOwnerId = attackerUnit->ownerId;
  519. }
  520. }
  521. }
  522. if (target->hasComponent<Engine::Core::BuildingComponent>() &&
  523. unit->health > 0) {
  524. Engine::Core::EventManager::instance().publish(
  525. Engine::Core::BuildingAttackedEvent(target->getId(), unit->ownerId,
  526. unit->spawnType, attackerId,
  527. attackerOwnerId, damage));
  528. }
  529. if (unit->health <= 0) {
  530. int killerOwnerId = attackerOwnerId;
  531. Engine::Core::EventManager::instance().publish(
  532. Engine::Core::UnitDiedEvent(target->getId(), unit->ownerId,
  533. unit->spawnType, attackerId,
  534. killerOwnerId));
  535. auto *targetAtk = target->getComponent<Engine::Core::AttackComponent>();
  536. if (targetAtk && targetAtk->inMeleeLock &&
  537. targetAtk->meleeLockTargetId != 0) {
  538. if (world) {
  539. auto *lockPartner = world->getEntity(targetAtk->meleeLockTargetId);
  540. if (lockPartner &&
  541. !lockPartner
  542. ->hasComponent<Engine::Core::PendingRemovalComponent>()) {
  543. auto *partnerAtk =
  544. lockPartner->getComponent<Engine::Core::AttackComponent>();
  545. if (partnerAtk &&
  546. partnerAtk->meleeLockTargetId == target->getId()) {
  547. partnerAtk->inMeleeLock = false;
  548. partnerAtk->meleeLockTargetId = 0;
  549. }
  550. }
  551. }
  552. }
  553. if (target->hasComponent<Engine::Core::BuildingComponent>()) {
  554. BuildingCollisionRegistry::instance().unregisterBuilding(
  555. target->getId());
  556. }
  557. if (auto *r = target->getComponent<Engine::Core::RenderableComponent>()) {
  558. r->visible = false;
  559. }
  560. if (auto *movement =
  561. target->getComponent<Engine::Core::MovementComponent>()) {
  562. movement->hasTarget = false;
  563. movement->vx = 0.0f;
  564. movement->vz = 0.0f;
  565. movement->path.clear();
  566. movement->pathPending = false;
  567. }
  568. target->addComponent<Engine::Core::PendingRemovalComponent>();
  569. }
  570. }
  571. }
  572. void CombatSystem::updateCombatMode(Engine::Core::Entity *attacker,
  573. Engine::Core::World *world,
  574. Engine::Core::AttackComponent *attackComp) {
  575. if (!attackComp) {
  576. return;
  577. }
  578. if (attackComp->preferredMode !=
  579. Engine::Core::AttackComponent::CombatMode::Auto) {
  580. attackComp->currentMode = attackComp->preferredMode;
  581. return;
  582. }
  583. auto attackerTransform =
  584. attacker->getComponent<Engine::Core::TransformComponent>();
  585. if (!attackerTransform) {
  586. return;
  587. }
  588. auto attackerUnit = attacker->getComponent<Engine::Core::UnitComponent>();
  589. if (!attackerUnit) {
  590. return;
  591. }
  592. auto &ownerRegistry = Game::Systems::OwnerRegistry::instance();
  593. auto units = world->getEntitiesWith<Engine::Core::UnitComponent>();
  594. float closestEnemyDistSq = std::numeric_limits<float>::max();
  595. float closestHeightDiff = 0.0f;
  596. for (auto target : units) {
  597. if (target == attacker) {
  598. continue;
  599. }
  600. auto targetUnit = target->getComponent<Engine::Core::UnitComponent>();
  601. if (!targetUnit || targetUnit->health <= 0) {
  602. continue;
  603. }
  604. if (ownerRegistry.areAllies(attackerUnit->ownerId, targetUnit->ownerId)) {
  605. continue;
  606. }
  607. auto targetTransform =
  608. target->getComponent<Engine::Core::TransformComponent>();
  609. if (!targetTransform) {
  610. continue;
  611. }
  612. float dx = targetTransform->position.x - attackerTransform->position.x;
  613. float dz = targetTransform->position.z - attackerTransform->position.z;
  614. float dy = targetTransform->position.y - attackerTransform->position.y;
  615. float distSq = dx * dx + dz * dz;
  616. if (distSq < closestEnemyDistSq) {
  617. closestEnemyDistSq = distSq;
  618. closestHeightDiff = std::abs(dy);
  619. }
  620. }
  621. if (closestEnemyDistSq == std::numeric_limits<float>::max()) {
  622. if (attackComp->canRanged) {
  623. attackComp->currentMode =
  624. Engine::Core::AttackComponent::CombatMode::Ranged;
  625. } else {
  626. attackComp->currentMode =
  627. Engine::Core::AttackComponent::CombatMode::Melee;
  628. }
  629. return;
  630. }
  631. float closestDist = std::sqrt(closestEnemyDistSq);
  632. bool inMeleeRange =
  633. attackComp->isInMeleeRange(closestDist, closestHeightDiff);
  634. bool inRangedRange = attackComp->isInRangedRange(closestDist);
  635. if (inMeleeRange && attackComp->canMelee) {
  636. attackComp->currentMode = Engine::Core::AttackComponent::CombatMode::Melee;
  637. } else if (inRangedRange && attackComp->canRanged) {
  638. attackComp->currentMode = Engine::Core::AttackComponent::CombatMode::Ranged;
  639. } else if (attackComp->canRanged) {
  640. attackComp->currentMode = Engine::Core::AttackComponent::CombatMode::Ranged;
  641. } else {
  642. attackComp->currentMode = Engine::Core::AttackComponent::CombatMode::Melee;
  643. }
  644. }
  645. void CombatSystem::processAutoEngagement(Engine::Core::World *world,
  646. float deltaTime) {
  647. auto units = world->getEntitiesWith<Engine::Core::UnitComponent>();
  648. for (auto it = m_engagementCooldowns.begin();
  649. it != m_engagementCooldowns.end();) {
  650. it->second -= deltaTime;
  651. if (it->second <= 0.0f) {
  652. it = m_engagementCooldowns.erase(it);
  653. } else {
  654. ++it;
  655. }
  656. }
  657. for (auto unit : units) {
  658. if (unit->hasComponent<Engine::Core::PendingRemovalComponent>()) {
  659. continue;
  660. }
  661. auto unitComp = unit->getComponent<Engine::Core::UnitComponent>();
  662. if (!unitComp || unitComp->health <= 0) {
  663. continue;
  664. }
  665. if (unit->hasComponent<Engine::Core::BuildingComponent>()) {
  666. continue;
  667. }
  668. auto attackComp = unit->getComponent<Engine::Core::AttackComponent>();
  669. if (!attackComp || !attackComp->canMelee) {
  670. continue;
  671. }
  672. if (attackComp->canRanged &&
  673. attackComp->preferredMode !=
  674. Engine::Core::AttackComponent::CombatMode::Melee) {
  675. continue;
  676. }
  677. if (m_engagementCooldowns.find(unit->getId()) !=
  678. m_engagementCooldowns.end()) {
  679. continue;
  680. }
  681. if (!isUnitIdle(unit)) {
  682. continue;
  683. }
  684. float visionRange = unitComp->visionRange;
  685. auto *nearestEnemy = findNearestEnemy(unit, world, visionRange);
  686. if (nearestEnemy) {
  687. auto *attackTarget =
  688. unit->getComponent<Engine::Core::AttackTargetComponent>();
  689. if (!attackTarget) {
  690. attackTarget =
  691. unit->addComponent<Engine::Core::AttackTargetComponent>();
  692. }
  693. if (attackTarget) {
  694. attackTarget->targetId = nearestEnemy->getId();
  695. attackTarget->shouldChase = true;
  696. m_engagementCooldowns[unit->getId()] = ENGAGEMENT_COOLDOWN;
  697. }
  698. }
  699. }
  700. }
  701. bool CombatSystem::isUnitIdle(Engine::Core::Entity *unit) {
  702. auto *holdMode = unit->getComponent<Engine::Core::HoldModeComponent>();
  703. if (holdMode && holdMode->active) {
  704. return false;
  705. }
  706. auto *attackTarget =
  707. unit->getComponent<Engine::Core::AttackTargetComponent>();
  708. if (attackTarget && attackTarget->targetId != 0) {
  709. return false;
  710. }
  711. auto *movement = unit->getComponent<Engine::Core::MovementComponent>();
  712. if (movement && movement->hasTarget) {
  713. return false;
  714. }
  715. auto *attackComp = unit->getComponent<Engine::Core::AttackComponent>();
  716. if (attackComp && attackComp->inMeleeLock) {
  717. return false;
  718. }
  719. auto *patrol = unit->getComponent<Engine::Core::PatrolComponent>();
  720. if (patrol && patrol->patrolling) {
  721. return false;
  722. }
  723. return true;
  724. }
  725. Engine::Core::Entity *CombatSystem::findNearestEnemy(Engine::Core::Entity *unit,
  726. Engine::Core::World *world,
  727. float maxRange) {
  728. auto unitComp = unit->getComponent<Engine::Core::UnitComponent>();
  729. auto unitTransform = unit->getComponent<Engine::Core::TransformComponent>();
  730. if (!unitComp || !unitTransform) {
  731. return nullptr;
  732. }
  733. auto &ownerRegistry = Game::Systems::OwnerRegistry::instance();
  734. auto units = world->getEntitiesWith<Engine::Core::UnitComponent>();
  735. Engine::Core::Entity *nearestEnemy = nullptr;
  736. float nearestDistSq = maxRange * maxRange;
  737. for (auto target : units) {
  738. if (target == unit) {
  739. continue;
  740. }
  741. if (target->hasComponent<Engine::Core::PendingRemovalComponent>()) {
  742. continue;
  743. }
  744. auto targetUnit = target->getComponent<Engine::Core::UnitComponent>();
  745. if (!targetUnit || targetUnit->health <= 0) {
  746. continue;
  747. }
  748. if (targetUnit->ownerId == unitComp->ownerId) {
  749. continue;
  750. }
  751. if (ownerRegistry.areAllies(unitComp->ownerId, targetUnit->ownerId)) {
  752. continue;
  753. }
  754. if (target->hasComponent<Engine::Core::BuildingComponent>()) {
  755. continue;
  756. }
  757. auto targetTransform =
  758. target->getComponent<Engine::Core::TransformComponent>();
  759. if (!targetTransform) {
  760. continue;
  761. }
  762. float dx = targetTransform->position.x - unitTransform->position.x;
  763. float dz = targetTransform->position.z - unitTransform->position.z;
  764. float distSq = dx * dx + dz * dz;
  765. if (distSq < nearestDistSq) {
  766. nearestDistSq = distSq;
  767. nearestEnemy = target;
  768. }
  769. }
  770. return nearestEnemy;
  771. }
  772. } // namespace Game::Systems