command_controller.cpp 29 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012
  1. #include "command_controller.h"
  2. #include "../../game/core/component.h"
  3. #include "../../game/core/entity.h"
  4. #include "../../game/core/world.h"
  5. #include "../../game/systems/command_service.h"
  6. #include "../../game/systems/formation_planner.h"
  7. #include "../../game/systems/picking_service.h"
  8. #include "../../game/systems/production_service.h"
  9. #include "../../game/systems/selection_system.h"
  10. #include "../../game/systems/troop_profile_service.h"
  11. #include "../../render/gl/camera.h"
  12. #include "../utils/movement_utils.h"
  13. #include "game/game_config.h"
  14. #include "units/spawn_type.h"
  15. #include <QPointF>
  16. #include <cmath>
  17. #include <numbers>
  18. #include <qglobal.h>
  19. #include <qobject.h>
  20. #include <qtmetamacros.h>
  21. #include <qvectornd.h>
  22. #include <vector>
  23. namespace App::Controllers {
  24. CommandController::CommandController(
  25. Engine::Core::World *world,
  26. Game::Systems::SelectionSystem *selection_system,
  27. Game::Systems::PickingService *picking_service, QObject *parent)
  28. : QObject(parent), m_world(world), m_selection_system(selection_system),
  29. m_picking_service(picking_service) {}
  30. auto CommandController::on_attack_click(qreal sx, qreal sy, int viewport_width,
  31. int viewport_height,
  32. void *camera) -> CommandResult {
  33. CommandResult result;
  34. if ((m_selection_system == nullptr) || (m_picking_service == nullptr) ||
  35. (camera == nullptr) || (m_world == nullptr)) {
  36. result.reset_cursor_to_normal = true;
  37. return result;
  38. }
  39. const auto &selected = m_selection_system->get_selected_units();
  40. if (selected.empty()) {
  41. result.reset_cursor_to_normal = true;
  42. return result;
  43. }
  44. auto *cam = static_cast<Render::GL::Camera *>(camera);
  45. Engine::Core::EntityID const target_id =
  46. Game::Systems::PickingService::pick_unit_first(
  47. float(sx), float(sy), *m_world, *cam, viewport_width, viewport_height,
  48. 0);
  49. if (target_id == 0) {
  50. result.reset_cursor_to_normal = true;
  51. return result;
  52. }
  53. auto *target_entity = m_world->get_entity(target_id);
  54. if (target_entity == nullptr) {
  55. return result;
  56. }
  57. auto *target_unit =
  58. target_entity->get_component<Engine::Core::UnitComponent>();
  59. if (target_unit == nullptr) {
  60. return result;
  61. }
  62. Game::Systems::CommandService::attack_target(*m_world, selected, target_id,
  63. true);
  64. emit attack_target_selected();
  65. result.input_consumed = true;
  66. result.reset_cursor_to_normal = true;
  67. return result;
  68. }
  69. auto CommandController::on_stop_command() -> CommandResult {
  70. CommandResult result;
  71. if ((m_selection_system == nullptr) || (m_world == nullptr)) {
  72. return result;
  73. }
  74. const auto &selected = m_selection_system->get_selected_units();
  75. if (selected.empty()) {
  76. return result;
  77. }
  78. for (auto id : selected) {
  79. auto *entity = m_world->get_entity(id);
  80. if (entity == nullptr) {
  81. continue;
  82. }
  83. reset_movement(entity);
  84. entity->remove_component<Engine::Core::AttackTargetComponent>();
  85. if (auto *patrol = entity->get_component<Engine::Core::PatrolComponent>()) {
  86. patrol->patrolling = false;
  87. patrol->waypoints.clear();
  88. }
  89. auto *hold_mode = entity->get_component<Engine::Core::HoldModeComponent>();
  90. if ((hold_mode != nullptr) && hold_mode->active) {
  91. hold_mode->active = false;
  92. hold_mode->exit_cooldown = hold_mode->stand_up_duration;
  93. emit hold_mode_changed(false);
  94. }
  95. auto *formation_mode =
  96. entity->get_component<Engine::Core::FormationModeComponent>();
  97. if ((formation_mode != nullptr) && formation_mode->active) {
  98. formation_mode->active = false;
  99. emit formation_mode_changed(false);
  100. }
  101. }
  102. result.input_consumed = true;
  103. result.reset_cursor_to_normal = true;
  104. return result;
  105. }
  106. auto CommandController::on_hold_command() -> CommandResult {
  107. CommandResult result;
  108. if ((m_selection_system == nullptr) || (m_world == nullptr)) {
  109. return result;
  110. }
  111. const auto &selected = m_selection_system->get_selected_units();
  112. if (selected.empty()) {
  113. return result;
  114. }
  115. int eligible_count = 0;
  116. int hold_active_count = 0;
  117. for (auto id : selected) {
  118. auto *entity = m_world->get_entity(id);
  119. if (entity == nullptr) {
  120. continue;
  121. }
  122. auto *unit = entity->get_component<Engine::Core::UnitComponent>();
  123. if (unit == nullptr) {
  124. continue;
  125. }
  126. if (!Game::Units::can_use_hold_mode(unit->spawn_type)) {
  127. continue;
  128. }
  129. eligible_count++;
  130. auto *hold_mode = entity->get_component<Engine::Core::HoldModeComponent>();
  131. if ((hold_mode != nullptr) && hold_mode->active) {
  132. hold_active_count++;
  133. }
  134. }
  135. if (eligible_count == 0) {
  136. return result;
  137. }
  138. const bool should_enable_hold = (hold_active_count < eligible_count);
  139. for (auto id : selected) {
  140. auto *entity = m_world->get_entity(id);
  141. if (entity == nullptr) {
  142. continue;
  143. }
  144. auto *unit = entity->get_component<Engine::Core::UnitComponent>();
  145. if (unit == nullptr) {
  146. continue;
  147. }
  148. if (!Game::Units::can_use_hold_mode(unit->spawn_type)) {
  149. continue;
  150. }
  151. auto *hold_mode = entity->get_component<Engine::Core::HoldModeComponent>();
  152. if (should_enable_hold) {
  153. reset_movement(entity);
  154. entity->remove_component<Engine::Core::AttackTargetComponent>();
  155. auto *attack_comp =
  156. entity->get_component<Engine::Core::AttackComponent>();
  157. if (attack_comp != nullptr) {
  158. attack_comp->in_melee_lock = false;
  159. attack_comp->melee_lock_target_id = 0;
  160. }
  161. if (auto *patrol =
  162. entity->get_component<Engine::Core::PatrolComponent>()) {
  163. patrol->patrolling = false;
  164. patrol->waypoints.clear();
  165. }
  166. if (hold_mode == nullptr) {
  167. hold_mode = entity->add_component<Engine::Core::HoldModeComponent>();
  168. }
  169. hold_mode->active = true;
  170. hold_mode->exit_cooldown = 0.0F;
  171. auto *movement = entity->get_component<Engine::Core::MovementComponent>();
  172. if (movement != nullptr) {
  173. movement->has_target = false;
  174. movement->path.clear();
  175. movement->path_pending = false;
  176. movement->vx = 0.0F;
  177. movement->vz = 0.0F;
  178. }
  179. } else {
  180. if ((hold_mode != nullptr) && hold_mode->active) {
  181. hold_mode->active = false;
  182. hold_mode->exit_cooldown = hold_mode->stand_up_duration;
  183. }
  184. }
  185. }
  186. emit hold_mode_changed(should_enable_hold);
  187. result.input_consumed = true;
  188. result.reset_cursor_to_normal = true;
  189. return result;
  190. }
  191. auto CommandController::on_patrol_click(qreal sx, qreal sy, int viewport_width,
  192. int viewport_height,
  193. void *camera) -> CommandResult {
  194. CommandResult result;
  195. if ((m_selection_system == nullptr) || (m_world == nullptr) ||
  196. (m_picking_service == nullptr) || (camera == nullptr)) {
  197. if (m_has_patrol_first_waypoint) {
  198. clear_patrol_first_waypoint();
  199. result.reset_cursor_to_normal = true;
  200. }
  201. return result;
  202. }
  203. const auto &selected = m_selection_system->get_selected_units();
  204. if (selected.empty()) {
  205. if (m_has_patrol_first_waypoint) {
  206. clear_patrol_first_waypoint();
  207. result.reset_cursor_to_normal = true;
  208. }
  209. return result;
  210. }
  211. auto *cam = static_cast<Render::GL::Camera *>(camera);
  212. QVector3D hit;
  213. if (!Game::Systems::PickingService::screen_to_ground(
  214. QPointF(sx, sy), *cam, viewport_width, viewport_height, hit)) {
  215. if (m_has_patrol_first_waypoint) {
  216. clear_patrol_first_waypoint();
  217. result.reset_cursor_to_normal = true;
  218. }
  219. return result;
  220. }
  221. if (!m_has_patrol_first_waypoint) {
  222. m_has_patrol_first_waypoint = true;
  223. m_patrol_first_waypoint = hit;
  224. result.input_consumed = true;
  225. return result;
  226. }
  227. QVector3D const second_waypoint = hit;
  228. for (auto id : selected) {
  229. auto *entity = m_world->get_entity(id);
  230. if (entity == nullptr) {
  231. continue;
  232. }
  233. auto *building = entity->get_component<Engine::Core::BuildingComponent>();
  234. if (building != nullptr) {
  235. continue;
  236. }
  237. auto *patrol = entity->get_component<Engine::Core::PatrolComponent>();
  238. if (patrol == nullptr) {
  239. patrol = entity->add_component<Engine::Core::PatrolComponent>();
  240. }
  241. if (patrol != nullptr) {
  242. patrol->waypoints.clear();
  243. patrol->waypoints.emplace_back(m_patrol_first_waypoint.x(),
  244. m_patrol_first_waypoint.z());
  245. patrol->waypoints.emplace_back(second_waypoint.x(), second_waypoint.z());
  246. patrol->current_waypoint = 0;
  247. patrol->patrolling = true;
  248. }
  249. reset_movement(entity);
  250. entity->remove_component<Engine::Core::AttackTargetComponent>();
  251. }
  252. clear_patrol_first_waypoint();
  253. result.input_consumed = true;
  254. result.reset_cursor_to_normal = true;
  255. return result;
  256. }
  257. auto CommandController::set_rally_at_screen(
  258. qreal sx, qreal sy, int viewport_width, int viewport_height, void *camera,
  259. int local_owner_id) -> CommandResult {
  260. CommandResult result;
  261. if ((m_world == nullptr) || (m_selection_system == nullptr) ||
  262. (m_picking_service == nullptr) || (camera == nullptr)) {
  263. return result;
  264. }
  265. auto *cam = static_cast<Render::GL::Camera *>(camera);
  266. QVector3D hit;
  267. if (!Game::Systems::PickingService::screen_to_ground(
  268. QPointF(sx, sy), *cam, viewport_width, viewport_height, hit)) {
  269. return result;
  270. }
  271. Game::Systems::ProductionService::set_rally_for_first_selected_barracks(
  272. *m_world, m_selection_system->get_selected_units(), local_owner_id,
  273. hit.x(), hit.z());
  274. result.input_consumed = true;
  275. return result;
  276. }
  277. void CommandController::recruit_near_selected(const QString &unit_type,
  278. int local_owner_id) {
  279. if ((m_world == nullptr) || (m_selection_system == nullptr)) {
  280. return;
  281. }
  282. const auto &sel = m_selection_system->get_selected_units();
  283. if (sel.empty()) {
  284. return;
  285. }
  286. auto result = Game::Systems::ProductionService::
  287. start_production_for_first_selected_barracks(
  288. *m_world, sel, local_owner_id, unit_type.toStdString());
  289. if (result == Game::Systems::ProductionResult::GlobalTroopLimitReached) {
  290. emit troop_limit_reached();
  291. }
  292. }
  293. void CommandController::reset_movement(Engine::Core::Entity *entity) {
  294. App::Utils::reset_movement(entity);
  295. }
  296. auto CommandController::any_selected_in_hold_mode() const -> bool {
  297. if ((m_selection_system == nullptr) || (m_world == nullptr)) {
  298. return false;
  299. }
  300. const auto &selected = m_selection_system->get_selected_units();
  301. for (Engine::Core::EntityID const entity_id : selected) {
  302. Engine::Core::Entity *entity = m_world->get_entity(entity_id);
  303. if (entity == nullptr) {
  304. continue;
  305. }
  306. auto *hold_mode = entity->get_component<Engine::Core::HoldModeComponent>();
  307. if ((hold_mode != nullptr) && hold_mode->active) {
  308. return true;
  309. }
  310. }
  311. return false;
  312. }
  313. auto CommandController::any_selected_in_guard_mode() const -> bool {
  314. if ((m_selection_system == nullptr) || (m_world == nullptr)) {
  315. return false;
  316. }
  317. const auto &selected = m_selection_system->get_selected_units();
  318. for (Engine::Core::EntityID const entity_id : selected) {
  319. Engine::Core::Entity *entity = m_world->get_entity(entity_id);
  320. if (entity == nullptr) {
  321. continue;
  322. }
  323. auto *guard_mode =
  324. entity->get_component<Engine::Core::GuardModeComponent>();
  325. if ((guard_mode != nullptr) && guard_mode->active) {
  326. return true;
  327. }
  328. }
  329. return false;
  330. }
  331. auto CommandController::on_guard_command() -> CommandResult {
  332. CommandResult result;
  333. if ((m_selection_system == nullptr) || (m_world == nullptr)) {
  334. return result;
  335. }
  336. const auto &selected = m_selection_system->get_selected_units();
  337. if (selected.empty()) {
  338. return result;
  339. }
  340. int eligible_count = 0;
  341. int guard_active_count = 0;
  342. for (auto id : selected) {
  343. auto *entity = m_world->get_entity(id);
  344. if (entity == nullptr) {
  345. continue;
  346. }
  347. auto *unit = entity->get_component<Engine::Core::UnitComponent>();
  348. if (unit == nullptr) {
  349. continue;
  350. }
  351. if (unit->spawn_type == Game::Units::SpawnType::Barracks) {
  352. continue;
  353. }
  354. eligible_count++;
  355. auto *guard_mode =
  356. entity->get_component<Engine::Core::GuardModeComponent>();
  357. if ((guard_mode != nullptr) && guard_mode->active) {
  358. guard_active_count++;
  359. }
  360. }
  361. if (eligible_count == 0) {
  362. return result;
  363. }
  364. const bool should_enable_guard = (guard_active_count < eligible_count);
  365. for (auto id : selected) {
  366. auto *entity = m_world->get_entity(id);
  367. if (entity == nullptr) {
  368. continue;
  369. }
  370. auto *unit = entity->get_component<Engine::Core::UnitComponent>();
  371. if (unit == nullptr) {
  372. continue;
  373. }
  374. if (unit->spawn_type == Game::Units::SpawnType::Barracks) {
  375. continue;
  376. }
  377. auto *guard_mode =
  378. entity->get_component<Engine::Core::GuardModeComponent>();
  379. if (should_enable_guard) {
  380. if (guard_mode == nullptr) {
  381. guard_mode = entity->add_component<Engine::Core::GuardModeComponent>();
  382. }
  383. guard_mode->active = true;
  384. guard_mode->returning_to_guard_position = false;
  385. auto *transform =
  386. entity->get_component<Engine::Core::TransformComponent>();
  387. if (transform != nullptr) {
  388. guard_mode->guard_position_x = transform->position.x;
  389. guard_mode->guard_position_z = transform->position.z;
  390. guard_mode->has_guard_target = true;
  391. guard_mode->guarded_entity_id = 0;
  392. }
  393. auto *hold_mode =
  394. entity->get_component<Engine::Core::HoldModeComponent>();
  395. if ((hold_mode != nullptr) && hold_mode->active) {
  396. hold_mode->active = false;
  397. }
  398. if (auto *patrol =
  399. entity->get_component<Engine::Core::PatrolComponent>()) {
  400. patrol->patrolling = false;
  401. patrol->waypoints.clear();
  402. }
  403. } else {
  404. if ((guard_mode != nullptr) && guard_mode->active) {
  405. guard_mode->active = false;
  406. guard_mode->guarded_entity_id = 0;
  407. guard_mode->guard_position_x = 0.0F;
  408. guard_mode->guard_position_z = 0.0F;
  409. guard_mode->returning_to_guard_position = false;
  410. guard_mode->has_guard_target = false;
  411. }
  412. }
  413. }
  414. emit guard_mode_changed(should_enable_guard);
  415. result.input_consumed = true;
  416. result.reset_cursor_to_normal = true;
  417. return result;
  418. }
  419. auto CommandController::on_guard_click(qreal sx, qreal sy, int viewport_width,
  420. int viewport_height,
  421. void *camera) -> CommandResult {
  422. CommandResult result;
  423. if ((m_selection_system == nullptr) || (m_picking_service == nullptr) ||
  424. (camera == nullptr) || (m_world == nullptr)) {
  425. result.reset_cursor_to_normal = true;
  426. return result;
  427. }
  428. const auto &selected = m_selection_system->get_selected_units();
  429. if (selected.empty()) {
  430. result.reset_cursor_to_normal = true;
  431. return result;
  432. }
  433. auto *cam = static_cast<Render::GL::Camera *>(camera);
  434. QVector3D hit;
  435. if (!Game::Systems::PickingService::screen_to_ground(
  436. QPointF(sx, sy), *cam, viewport_width, viewport_height, hit)) {
  437. result.reset_cursor_to_normal = true;
  438. return result;
  439. }
  440. for (auto id : selected) {
  441. auto *entity = m_world->get_entity(id);
  442. if (entity == nullptr) {
  443. continue;
  444. }
  445. auto *building = entity->get_component<Engine::Core::BuildingComponent>();
  446. if (building != nullptr) {
  447. continue;
  448. }
  449. auto *guard_mode =
  450. entity->get_component<Engine::Core::GuardModeComponent>();
  451. if (guard_mode == nullptr) {
  452. guard_mode = entity->add_component<Engine::Core::GuardModeComponent>();
  453. }
  454. guard_mode->active = true;
  455. guard_mode->guarded_entity_id = 0;
  456. guard_mode->guard_position_x = hit.x();
  457. guard_mode->guard_position_z = hit.z();
  458. guard_mode->returning_to_guard_position = false;
  459. guard_mode->has_guard_target = true;
  460. auto *hold_mode = entity->get_component<Engine::Core::HoldModeComponent>();
  461. if ((hold_mode != nullptr) && hold_mode->active) {
  462. hold_mode->active = false;
  463. }
  464. if (auto *patrol = entity->get_component<Engine::Core::PatrolComponent>()) {
  465. patrol->patrolling = false;
  466. patrol->waypoints.clear();
  467. }
  468. reset_movement(entity);
  469. entity->remove_component<Engine::Core::AttackTargetComponent>();
  470. }
  471. emit guard_mode_changed(true);
  472. result.input_consumed = true;
  473. result.reset_cursor_to_normal = true;
  474. return result;
  475. }
  476. auto CommandController::on_formation_command() -> CommandResult {
  477. CommandResult result;
  478. if ((m_selection_system == nullptr) || (m_world == nullptr)) {
  479. return result;
  480. }
  481. const auto &selected = m_selection_system->get_selected_units();
  482. if (selected.size() <= 1) {
  483. return result;
  484. }
  485. int eligible_count = 0;
  486. int formation_active_count = 0;
  487. for (auto id : selected) {
  488. auto *entity = m_world->get_entity(id);
  489. if (entity == nullptr) {
  490. continue;
  491. }
  492. auto *unit = entity->get_component<Engine::Core::UnitComponent>();
  493. if (unit == nullptr) {
  494. continue;
  495. }
  496. if (unit->spawn_type == Game::Units::SpawnType::Barracks) {
  497. continue;
  498. }
  499. eligible_count++;
  500. auto *formation_mode =
  501. entity->get_component<Engine::Core::FormationModeComponent>();
  502. if ((formation_mode != nullptr) && formation_mode->active) {
  503. formation_active_count++;
  504. }
  505. }
  506. if (eligible_count <= 1) {
  507. return result;
  508. }
  509. const bool should_enable_formation =
  510. (formation_active_count < eligible_count);
  511. for (auto id : selected) {
  512. auto *entity = m_world->get_entity(id);
  513. if (entity == nullptr) {
  514. continue;
  515. }
  516. auto *unit = entity->get_component<Engine::Core::UnitComponent>();
  517. if (unit == nullptr) {
  518. continue;
  519. }
  520. if (unit->spawn_type == Game::Units::SpawnType::Barracks) {
  521. continue;
  522. }
  523. auto *formation_mode =
  524. entity->get_component<Engine::Core::FormationModeComponent>();
  525. if (should_enable_formation) {
  526. if (formation_mode == nullptr) {
  527. formation_mode =
  528. entity->add_component<Engine::Core::FormationModeComponent>();
  529. }
  530. formation_mode->active = true;
  531. auto *hold_mode =
  532. entity->get_component<Engine::Core::HoldModeComponent>();
  533. if ((hold_mode != nullptr) && hold_mode->active) {
  534. hold_mode->active = false;
  535. }
  536. auto *guard_mode =
  537. entity->get_component<Engine::Core::GuardModeComponent>();
  538. if ((guard_mode != nullptr) && guard_mode->active) {
  539. guard_mode->active = false;
  540. }
  541. if (auto *patrol =
  542. entity->get_component<Engine::Core::PatrolComponent>()) {
  543. patrol->patrolling = false;
  544. patrol->waypoints.clear();
  545. }
  546. } else {
  547. if ((formation_mode != nullptr) && formation_mode->active) {
  548. formation_mode->active = false;
  549. }
  550. }
  551. }
  552. if (should_enable_formation) {
  553. QVector3D center(0.0F, 0.0F, 0.0F);
  554. int valid_count = 0;
  555. m_formation_units.clear();
  556. for (auto id : selected) {
  557. auto *entity = m_world->get_entity(id);
  558. if (entity == nullptr) {
  559. continue;
  560. }
  561. auto *unit = entity->get_component<Engine::Core::UnitComponent>();
  562. if (unit == nullptr ||
  563. unit->spawn_type == Game::Units::SpawnType::Barracks) {
  564. continue;
  565. }
  566. m_formation_units.push_back(id);
  567. auto *transform =
  568. entity->get_component<Engine::Core::TransformComponent>();
  569. if (transform != nullptr) {
  570. center.setX(center.x() + transform->position.x);
  571. center.setY(center.y() + transform->position.y);
  572. center.setZ(center.z() + transform->position.z);
  573. valid_count++;
  574. }
  575. }
  576. if (valid_count > 0) {
  577. center.setX(center.x() / static_cast<float>(valid_count));
  578. center.setY(center.y() / static_cast<float>(valid_count));
  579. center.setZ(center.z() / static_cast<float>(valid_count));
  580. m_is_placing_formation = true;
  581. m_formation_placement_position = center;
  582. m_formation_placement_angle = 0.0F;
  583. emit formation_placement_started();
  584. emit formation_placement_updated(m_formation_placement_position,
  585. m_formation_placement_angle);
  586. }
  587. }
  588. result.input_consumed = true;
  589. result.reset_cursor_to_normal = false;
  590. return result;
  591. }
  592. bool CommandController::any_selected_in_formation_mode() const {
  593. if ((m_selection_system == nullptr) || (m_world == nullptr)) {
  594. return false;
  595. }
  596. const auto &selected = m_selection_system->get_selected_units();
  597. for (auto id : selected) {
  598. auto *entity = m_world->get_entity(id);
  599. if (entity == nullptr) {
  600. continue;
  601. }
  602. auto *formation_mode =
  603. entity->get_component<Engine::Core::FormationModeComponent>();
  604. if ((formation_mode != nullptr) && formation_mode->active) {
  605. return true;
  606. }
  607. }
  608. return false;
  609. }
  610. void CommandController::update_formation_placement(const QVector3D &position) {
  611. if (!m_is_placing_formation) {
  612. return;
  613. }
  614. m_formation_placement_position = position;
  615. emit formation_placement_updated(m_formation_placement_position,
  616. m_formation_placement_angle);
  617. }
  618. void CommandController::update_formation_rotation(float angle_degrees) {
  619. if (!m_is_placing_formation) {
  620. return;
  621. }
  622. m_formation_placement_angle = angle_degrees;
  623. emit formation_placement_updated(m_formation_placement_position,
  624. m_formation_placement_angle);
  625. }
  626. void CommandController::confirm_formation_placement() {
  627. if (!m_is_placing_formation || m_formation_units.empty()) {
  628. cancel_formation_placement();
  629. return;
  630. }
  631. auto formation_result =
  632. Game::Systems::FormationPlanner::get_formation_with_facing(
  633. *m_world, m_formation_units, m_formation_placement_position,
  634. Game::GameConfig::instance().gameplay().formation_spacing_default);
  635. float const angle_rad =
  636. m_formation_placement_angle * std::numbers::pi_v<float> / 180.0F;
  637. float const cos_a = std::cos(angle_rad);
  638. float const sin_a = std::sin(angle_rad);
  639. for (size_t i = 0; i < m_formation_units.size(); ++i) {
  640. if (i < formation_result.positions.size()) {
  641. QVector3D &pos = formation_result.positions[i];
  642. float const dx = pos.x() - m_formation_placement_position.x();
  643. float const dz = pos.z() - m_formation_placement_position.z();
  644. float const rotated_x = dx * cos_a - dz * sin_a;
  645. float const rotated_z = dx * sin_a + dz * cos_a;
  646. pos.setX(m_formation_placement_position.x() + rotated_x);
  647. pos.setZ(m_formation_placement_position.z() + rotated_z);
  648. }
  649. auto *entity = m_world->get_entity(m_formation_units[i]);
  650. if (entity == nullptr) {
  651. continue;
  652. }
  653. auto *transform = entity->get_component<Engine::Core::TransformComponent>();
  654. if (transform != nullptr) {
  655. float unit_facing = (i < formation_result.facing_angles.size())
  656. ? formation_result.facing_angles[i]
  657. : 0.0F;
  658. transform->desired_yaw = unit_facing + m_formation_placement_angle;
  659. transform->has_desired_yaw = true;
  660. }
  661. }
  662. Game::Systems::CommandService::MoveOptions opts;
  663. opts.group_move = m_formation_units.size() > 1;
  664. opts.clear_attack_intent = true;
  665. Game::Systems::CommandService::move_units(*m_world, m_formation_units,
  666. formation_result.positions, opts);
  667. m_is_placing_formation = false;
  668. m_formation_units.clear();
  669. emit formation_placement_ended();
  670. emit formation_mode_changed(true);
  671. }
  672. void CommandController::cancel_formation_placement() {
  673. if (!m_is_placing_formation) {
  674. return;
  675. }
  676. for (auto id : m_formation_units) {
  677. auto *entity = m_world->get_entity(id);
  678. if (entity == nullptr) {
  679. continue;
  680. }
  681. auto *formation_mode =
  682. entity->get_component<Engine::Core::FormationModeComponent>();
  683. if (formation_mode != nullptr) {
  684. formation_mode->active = false;
  685. }
  686. }
  687. m_is_placing_formation = false;
  688. m_formation_units.clear();
  689. emit formation_placement_ended();
  690. emit formation_mode_changed(false);
  691. }
  692. auto CommandController::on_run_command() -> CommandResult {
  693. CommandResult result;
  694. if (m_selection_system == nullptr || m_world == nullptr) {
  695. return result;
  696. }
  697. const auto &selected = m_selection_system->get_selected_units();
  698. if (selected.empty()) {
  699. return result;
  700. }
  701. struct UnitRunState {
  702. Engine::Core::Entity *entity;
  703. Engine::Core::StaminaComponent *stamina;
  704. Game::Systems::NationID nation_id;
  705. Game::Units::SpawnType spawn_type;
  706. };
  707. std::vector<UnitRunState> eligible_units;
  708. eligible_units.reserve(selected.size());
  709. int run_active_count = 0;
  710. for (const auto id : selected) {
  711. auto *entity = m_world->get_entity(id);
  712. if (entity == nullptr) {
  713. continue;
  714. }
  715. const auto *unit = entity->get_component<Engine::Core::UnitComponent>();
  716. if (unit == nullptr || !Game::Units::can_use_run_mode(unit->spawn_type)) {
  717. continue;
  718. }
  719. auto *stamina = entity->get_component<Engine::Core::StaminaComponent>();
  720. const bool is_active = stamina != nullptr && stamina->run_requested;
  721. run_active_count += is_active ? 1 : 0;
  722. eligible_units.push_back(
  723. {entity, stamina, unit->nation_id, unit->spawn_type});
  724. }
  725. if (eligible_units.empty()) {
  726. return result;
  727. }
  728. const bool should_enable_run =
  729. run_active_count < static_cast<int>(eligible_units.size());
  730. for (auto &[entity, stamina, nation_id, spawn_type] : eligible_units) {
  731. if (should_enable_run) {
  732. if (stamina == nullptr) {
  733. stamina = entity->add_component<Engine::Core::StaminaComponent>();
  734. const auto troop_type = Game::Units::spawn_typeToTroopType(spawn_type);
  735. if (troop_type.has_value()) {
  736. const auto profile =
  737. Game::Systems::TroopProfileService::instance().get_profile(
  738. nation_id, *troop_type);
  739. stamina->initialize_from_stats(profile.combat.max_stamina,
  740. profile.combat.stamina_regen_rate,
  741. profile.combat.stamina_depletion_rate);
  742. }
  743. }
  744. stamina->run_requested = true;
  745. } else if (stamina != nullptr) {
  746. stamina->run_requested = false;
  747. stamina->is_running = false;
  748. }
  749. }
  750. emit run_mode_changed(should_enable_run);
  751. result.input_consumed = true;
  752. result.reset_cursor_to_normal = true;
  753. return result;
  754. }
  755. auto CommandController::any_selected_in_run_mode() const -> bool {
  756. if (m_selection_system == nullptr || m_world == nullptr) {
  757. return false;
  758. }
  759. for (const auto id : m_selection_system->get_selected_units()) {
  760. const auto *entity = m_world->get_entity(id);
  761. if (entity == nullptr) {
  762. continue;
  763. }
  764. const auto *stamina =
  765. entity->get_component<Engine::Core::StaminaComponent>();
  766. if (stamina != nullptr && stamina->run_requested) {
  767. return true;
  768. }
  769. }
  770. return false;
  771. }
  772. void CommandController::enable_run_mode_for_selected() {
  773. if (m_selection_system == nullptr || m_world == nullptr) {
  774. return;
  775. }
  776. const auto &selected = m_selection_system->get_selected_units();
  777. if (selected.empty()) {
  778. return;
  779. }
  780. for (const auto id : selected) {
  781. auto *entity = m_world->get_entity(id);
  782. if (entity == nullptr) {
  783. continue;
  784. }
  785. const auto *unit = entity->get_component<Engine::Core::UnitComponent>();
  786. if (unit == nullptr || !Game::Units::can_use_run_mode(unit->spawn_type)) {
  787. continue;
  788. }
  789. auto *stamina = entity->get_component<Engine::Core::StaminaComponent>();
  790. if (stamina == nullptr) {
  791. stamina = entity->add_component<Engine::Core::StaminaComponent>();
  792. const auto troop_type =
  793. Game::Units::spawn_typeToTroopType(unit->spawn_type);
  794. if (troop_type.has_value()) {
  795. const auto profile =
  796. Game::Systems::TroopProfileService::instance().get_profile(
  797. unit->nation_id, *troop_type);
  798. stamina->initialize_from_stats(profile.combat.max_stamina,
  799. profile.combat.stamina_regen_rate,
  800. profile.combat.stamina_depletion_rate);
  801. }
  802. }
  803. stamina->run_requested = true;
  804. }
  805. emit run_mode_changed(true);
  806. }
  807. void CommandController::disable_run_mode_for_selected() {
  808. if (m_selection_system == nullptr || m_world == nullptr) {
  809. return;
  810. }
  811. const auto &selected = m_selection_system->get_selected_units();
  812. if (selected.empty()) {
  813. return;
  814. }
  815. for (const auto id : selected) {
  816. auto *entity = m_world->get_entity(id);
  817. if (entity == nullptr) {
  818. continue;
  819. }
  820. auto *stamina = entity->get_component<Engine::Core::StaminaComponent>();
  821. if (stamina != nullptr) {
  822. stamina->run_requested = false;
  823. stamina->is_running = false;
  824. }
  825. }
  826. emit run_mode_changed(false);
  827. }
  828. } // namespace App::Controllers