production_manager.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499
  1. #include "production_manager.h"
  2. #include "app/core/input_command_handler.h"
  3. #include "game/core/component.h"
  4. #include "game/core/world.h"
  5. #include "game/map/map_transformer.h"
  6. #include "game/systems/building_collision_registry.h"
  7. #include "game/systems/command_service.h"
  8. #include "game/systems/nation_registry.h"
  9. #include "game/systems/pathfinding.h"
  10. #include "game/systems/picking_service.h"
  11. #include "game/systems/production_service.h"
  12. #include "game/systems/selection_system.h"
  13. #include "game/systems/troop_profile_service.h"
  14. #include "game/units/factory.h"
  15. #include "game/units/spawn_type.h"
  16. #include "game/units/troop_config.h"
  17. #include "game/units/troop_type.h"
  18. #include "render/gl/camera.h"
  19. #include <QDebug>
  20. #include <QPointF>
  21. #include <algorithm>
  22. #include <cmath>
  23. ProductionManager::ProductionManager(
  24. Engine::Core::World *world, Game::Systems::PickingService *picking_service,
  25. Render::GL::Camera *camera, QObject *parent)
  26. : QObject(parent), m_world(world), m_picking_service(picking_service),
  27. m_camera(camera) {}
  28. namespace {
  29. auto is_construction_position_valid(float pos_x, float pos_z,
  30. const std::string &building_type) -> bool {
  31. auto &collision_registry =
  32. Game::Systems::BuildingCollisionRegistry::instance();
  33. auto size = collision_registry.get_building_size(building_type);
  34. if (collision_registry.is_circle_overlapping_building(
  35. pos_x, pos_z, std::max(size.width, size.depth) * 0.5F, 0)) {
  36. return false;
  37. }
  38. Game::Systems::Pathfinding *pathfinder =
  39. Game::Systems::CommandService::get_pathfinder();
  40. if (pathfinder != nullptr) {
  41. Game::Systems::Point const grid =
  42. Game::Systems::CommandService::world_to_grid(pos_x, pos_z);
  43. if (!pathfinder->is_walkable(grid.x, grid.y)) {
  44. return false;
  45. }
  46. }
  47. return true;
  48. }
  49. } // namespace
  50. void ProductionManager::start_building_placement(const QString &building_type) {
  51. if (building_type.isEmpty()) {
  52. return;
  53. }
  54. m_pending_building_type = building_type;
  55. }
  56. void ProductionManager::place_building_at_screen(
  57. qreal sx, qreal sy, int local_owner_id, const ViewportState &viewport) {
  58. if (m_pending_building_type.isEmpty() || !m_world || !m_picking_service ||
  59. !m_camera) {
  60. return;
  61. }
  62. QVector3D hit;
  63. if (!m_picking_service->screen_to_ground(
  64. QPointF(sx, sy), *m_camera, viewport.width, viewport.height, hit)) {
  65. return;
  66. }
  67. Game::Units::SpawnParams params;
  68. params.position = hit;
  69. params.player_id = local_owner_id;
  70. params.ai_controlled = false;
  71. auto &nation_registry = Game::Systems::NationRegistry::instance();
  72. if (const auto *nation =
  73. nation_registry.get_nation_for_player(local_owner_id)) {
  74. params.nation_id = nation->id;
  75. } else {
  76. params.nation_id = nation_registry.default_nation_id();
  77. }
  78. if (m_pending_building_type == QStringLiteral("defense_tower")) {
  79. params.spawn_type = Game::Units::SpawnType::DefenseTower;
  80. auto registry = Game::Map::MapTransformer::getFactoryRegistry();
  81. if (registry) {
  82. auto unit = registry->create(params.spawn_type, *m_world, params);
  83. if (unit) {
  84. qInfo() << "Placed defense tower at" << hit.x() << hit.z();
  85. }
  86. }
  87. }
  88. m_pending_building_type.clear();
  89. }
  90. void ProductionManager::cancel_building_placement() {
  91. m_pending_building_type.clear();
  92. }
  93. void ProductionManager::reset_transient_state() {
  94. cancel_building_placement();
  95. if (m_is_placing_construction) {
  96. on_construction_cancel();
  97. }
  98. m_pending_construction_type.clear();
  99. m_pending_construction_builders.clear();
  100. m_construction_placement_position = QVector3D();
  101. m_is_placing_construction = false;
  102. }
  103. void ProductionManager::on_construction_mouse_move(
  104. qreal sx, qreal sy, const ViewportState &viewport) {
  105. if (!m_is_placing_construction || !m_picking_service || !m_camera) {
  106. return;
  107. }
  108. QPointF screenPt(sx, sy);
  109. QVector3D hit;
  110. if (m_picking_service->screen_to_ground(screenPt, *m_camera, viewport.width,
  111. viewport.height, hit)) {
  112. m_construction_placement_position = hit;
  113. for (auto id : m_pending_construction_builders) {
  114. auto *e = m_world->get_entity(id);
  115. if (!e) {
  116. continue;
  117. }
  118. auto *builder_prod =
  119. e->get_component<Engine::Core::BuilderProductionComponent>();
  120. if (builder_prod) {
  121. builder_prod->construction_site_x = hit.x();
  122. builder_prod->construction_site_z = hit.z();
  123. }
  124. }
  125. }
  126. }
  127. void ProductionManager::on_construction_confirm() {
  128. if (!m_is_placing_construction || m_pending_construction_builders.empty()) {
  129. on_construction_cancel();
  130. return;
  131. }
  132. if (!is_construction_position_valid(
  133. m_construction_placement_position.x(),
  134. m_construction_placement_position.z(),
  135. m_pending_construction_type.toStdString())) {
  136. emit construction_placement_rejected();
  137. return;
  138. }
  139. for (auto id : m_pending_construction_builders) {
  140. auto *e = m_world->get_entity(id);
  141. if (!e) {
  142. continue;
  143. }
  144. auto *builder_prod =
  145. e->get_component<Engine::Core::BuilderProductionComponent>();
  146. if (builder_prod) {
  147. builder_prod->is_placement_preview = false;
  148. builder_prod->construction_site_x = m_construction_placement_position.x();
  149. builder_prod->construction_site_z = m_construction_placement_position.z();
  150. }
  151. auto *mv = e->get_component<Engine::Core::MovementComponent>();
  152. if (mv) {
  153. mv->goal_x = m_construction_placement_position.x();
  154. mv->goal_y = m_construction_placement_position.z();
  155. mv->target_x = m_construction_placement_position.x();
  156. mv->target_y = m_construction_placement_position.z();
  157. }
  158. }
  159. m_is_placing_construction = false;
  160. m_pending_construction_type.clear();
  161. m_pending_construction_builders.clear();
  162. emit placing_construction_changed();
  163. }
  164. void ProductionManager::on_construction_cancel() {
  165. if (!m_is_placing_construction) {
  166. return;
  167. }
  168. for (auto id : m_pending_construction_builders) {
  169. auto *e = m_world->get_entity(id);
  170. if (!e) {
  171. continue;
  172. }
  173. auto *builder_prod =
  174. e->get_component<Engine::Core::BuilderProductionComponent>();
  175. if (builder_prod) {
  176. builder_prod->has_construction_site = false;
  177. builder_prod->construction_site_x = 0.0F;
  178. builder_prod->construction_site_z = 0.0F;
  179. builder_prod->at_construction_site = false;
  180. builder_prod->product_type = "";
  181. builder_prod->is_placement_preview = false;
  182. }
  183. }
  184. m_is_placing_construction = false;
  185. m_pending_construction_type.clear();
  186. m_pending_construction_builders.clear();
  187. emit placing_construction_changed();
  188. }
  189. void ProductionManager::start_builder_construction(const QString &item_type) {
  190. if (!m_world) {
  191. return;
  192. }
  193. m_pending_construction_builders = collect_available_builders();
  194. if (m_pending_construction_builders.empty()) {
  195. return;
  196. }
  197. m_pending_construction_type = item_type;
  198. m_is_placing_construction = true;
  199. m_construction_placement_position =
  200. calculate_builder_center_position(m_pending_construction_builders);
  201. std::string item_str = item_type.toStdString();
  202. float build_time = get_construction_build_time(item_str);
  203. for (auto id : m_pending_construction_builders) {
  204. auto *e = m_world->get_entity(id);
  205. if (!e) {
  206. continue;
  207. }
  208. auto *builder_prod =
  209. e->get_component<Engine::Core::BuilderProductionComponent>();
  210. if (!builder_prod) {
  211. continue;
  212. }
  213. builder_prod->product_type = item_str;
  214. builder_prod->build_time = build_time;
  215. builder_prod->time_remaining = build_time;
  216. builder_prod->has_construction_site = true;
  217. builder_prod->construction_site_x = m_construction_placement_position.x();
  218. builder_prod->construction_site_z = m_construction_placement_position.z();
  219. builder_prod->at_construction_site = false;
  220. builder_prod->in_progress = false;
  221. builder_prod->is_placement_preview = true;
  222. }
  223. emit placing_construction_changed();
  224. }
  225. auto ProductionManager::get_selected_production_state(int local_owner_id) const
  226. -> QVariantMap {
  227. QVariantMap m;
  228. m["has_barracks"] = false;
  229. m["in_progress"] = false;
  230. m["time_remaining"] = 0.0;
  231. m["build_time"] = 0.0;
  232. m["produced_count"] = 0;
  233. m["max_units"] = 0;
  234. m["villager_cost"] = 1;
  235. if (!m_world) {
  236. return m;
  237. }
  238. auto *selection_system =
  239. m_world->get_system<Game::Systems::SelectionSystem>();
  240. if (!selection_system) {
  241. return m;
  242. }
  243. Game::Systems::ProductionState st;
  244. Game::Systems::ProductionService::get_selected_barracks_state(
  245. *m_world, selection_system->get_selected_units(), local_owner_id, st);
  246. m["has_barracks"] = st.has_barracks;
  247. m["in_progress"] = st.in_progress;
  248. m["product_type"] =
  249. QString::fromStdString(Game::Units::troop_typeToString(st.product_type));
  250. m["time_remaining"] = st.time_remaining;
  251. m["build_time"] = st.build_time;
  252. m["produced_count"] = st.produced_count;
  253. m["max_units"] = st.max_units;
  254. m["villager_cost"] = st.villager_cost;
  255. m["queue_size"] = st.queue_size;
  256. m["nation_id"] =
  257. QString::fromStdString(Game::Systems::nation_id_to_string(st.nation_id));
  258. QVariantList queue_list;
  259. for (const auto &unit_type : st.production_queue) {
  260. queue_list.append(
  261. QString::fromStdString(Game::Units::troop_typeToString(unit_type)));
  262. }
  263. m["production_queue"] = queue_list;
  264. return m;
  265. }
  266. auto ProductionManager::get_selected_builder_production_state() const
  267. -> QVariantMap {
  268. QVariantMap m;
  269. m["in_progress"] = false;
  270. m["time_remaining"] = 0.0;
  271. m["build_time"] = 10.0;
  272. m["product_type"] = "";
  273. if (!m_world) {
  274. return m;
  275. }
  276. auto *selection_system =
  277. m_world->get_system<Game::Systems::SelectionSystem>();
  278. if (!selection_system) {
  279. return m;
  280. }
  281. const auto &selected = selection_system->get_selected_units();
  282. for (auto id : selected) {
  283. auto *e = m_world->get_entity(id);
  284. if (!e) {
  285. continue;
  286. }
  287. auto *builder_prod =
  288. e->get_component<Engine::Core::BuilderProductionComponent>();
  289. if (builder_prod) {
  290. m["in_progress"] =
  291. builder_prod->in_progress || builder_prod->is_placement_preview;
  292. m["time_remaining"] = builder_prod->time_remaining;
  293. m["build_time"] = builder_prod->build_time;
  294. m["product_type"] = QString::fromStdString(builder_prod->product_type);
  295. return m;
  296. }
  297. }
  298. return m;
  299. }
  300. auto ProductionManager::get_unit_production_info(
  301. const QString &unit_type, const QString &nation_id) const -> QVariantMap {
  302. QVariantMap info;
  303. const auto &config = Game::Units::TroopConfig::instance();
  304. std::string type_str = unit_type.toStdString();
  305. info["cost"] = config.getProductionCost(type_str);
  306. info["build_time"] = static_cast<double>(config.getBuildTime(type_str));
  307. info["individuals_per_unit"] = config.getIndividualsPerUnit(type_str);
  308. auto troop_type_opt = Game::Units::tryParseTroopType(type_str);
  309. if (troop_type_opt.has_value()) {
  310. auto nation_id_opt =
  311. Game::Systems::nation_id_from_string(nation_id.toStdString());
  312. auto nation_id_enum = nation_id_opt.value_or(
  313. Game::Systems::NationRegistry::instance().default_nation_id());
  314. auto profile = Game::Systems::TroopProfileService::instance().get_profile(
  315. nation_id_enum, *troop_type_opt);
  316. info["display_name"] = QString::fromStdString(profile.display_name);
  317. } else {
  318. info["display_name"] = unit_type;
  319. }
  320. return info;
  321. }
  322. void ProductionManager::set_rally_at_screen(qreal sx, qreal sy,
  323. int local_owner_id,
  324. const ViewportState &viewport) {
  325. if (!m_world || !m_picking_service || !m_camera) {
  326. return;
  327. }
  328. QVector3D hit;
  329. if (!m_picking_service->screen_to_ground(
  330. QPointF(sx, sy), *m_camera, viewport.width, viewport.height, hit)) {
  331. return;
  332. }
  333. auto *selection_system =
  334. m_world->get_system<Game::Systems::SelectionSystem>();
  335. if (!selection_system) {
  336. return;
  337. }
  338. const auto &selected = selection_system->get_selected_units();
  339. for (auto id : selected) {
  340. auto *e = m_world->get_entity(id);
  341. if (!e) {
  342. continue;
  343. }
  344. auto *unit = e->get_component<Engine::Core::UnitComponent>();
  345. if (!unit || unit->owner_id != local_owner_id) {
  346. continue;
  347. }
  348. auto *prod = e->get_component<Engine::Core::ProductionComponent>();
  349. if (prod) {
  350. prod->rally_x = hit.x();
  351. prod->rally_z = hit.z();
  352. prod->rally_set = true;
  353. }
  354. }
  355. }
  356. auto ProductionManager::collect_available_builders()
  357. -> std::vector<Engine::Core::EntityID> {
  358. std::vector<Engine::Core::EntityID> builders;
  359. auto *selection_system =
  360. m_world->get_system<Game::Systems::SelectionSystem>();
  361. if (!selection_system) {
  362. return builders;
  363. }
  364. const auto &selected = selection_system->get_selected_units();
  365. for (auto id : selected) {
  366. auto *e = m_world->get_entity(id);
  367. if (!e) {
  368. continue;
  369. }
  370. auto *builder_prod =
  371. e->get_component<Engine::Core::BuilderProductionComponent>();
  372. if (builder_prod && !builder_prod->in_progress) {
  373. builders.push_back(id);
  374. }
  375. }
  376. return builders;
  377. }
  378. auto ProductionManager::calculate_builder_center_position(
  379. const std::vector<Engine::Core::EntityID> &builder_ids) -> QVector3D {
  380. float sum_x = 0.0F;
  381. float sum_y = 0.0F;
  382. float sum_z = 0.0F;
  383. int valid_count = 0;
  384. for (auto id : builder_ids) {
  385. auto *e = m_world->get_entity(id);
  386. if (!e) {
  387. continue;
  388. }
  389. auto *transform = e->get_component<Engine::Core::TransformComponent>();
  390. if (transform) {
  391. sum_x += transform->position.x;
  392. sum_y += transform->position.y;
  393. sum_z += transform->position.z;
  394. valid_count++;
  395. }
  396. }
  397. if (valid_count > 0) {
  398. return QVector3D(sum_x / static_cast<float>(valid_count),
  399. sum_y / static_cast<float>(valid_count),
  400. sum_z / static_cast<float>(valid_count));
  401. }
  402. return QVector3D(0.0F, 0.0F, 0.0F);
  403. }
  404. auto ProductionManager::get_construction_build_time(
  405. const std::string &item_type) -> float {
  406. constexpr float DEFAULT_BUILD_TIME = 10.0F;
  407. constexpr float CATAPULT_BUILD_TIME = 15.0F;
  408. constexpr float BALLISTA_BUILD_TIME = 12.0F;
  409. constexpr float DEFENSE_TOWER_BUILD_TIME = 20.0F;
  410. if (item_type == "catapult") {
  411. return CATAPULT_BUILD_TIME;
  412. }
  413. if (item_type == "ballista") {
  414. return BALLISTA_BUILD_TIME;
  415. }
  416. if (item_type == "defense_tower") {
  417. return DEFENSE_TOWER_BUILD_TIME;
  418. }
  419. return DEFAULT_BUILD_TIME;
  420. }