command_controller.cpp 28 KB

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