production_manager.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486
  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::on_construction_mouse_move(
  94. qreal sx, qreal sy, const ViewportState &viewport) {
  95. if (!m_is_placing_construction || !m_picking_service || !m_camera) {
  96. return;
  97. }
  98. QPointF screenPt(sx, sy);
  99. QVector3D hit;
  100. if (m_picking_service->screen_to_ground(screenPt, *m_camera, viewport.width,
  101. viewport.height, hit)) {
  102. m_construction_placement_position = hit;
  103. for (auto id : m_pending_construction_builders) {
  104. auto *e = m_world->get_entity(id);
  105. if (!e) {
  106. continue;
  107. }
  108. auto *builder_prod =
  109. e->get_component<Engine::Core::BuilderProductionComponent>();
  110. if (builder_prod) {
  111. builder_prod->construction_site_x = hit.x();
  112. builder_prod->construction_site_z = hit.z();
  113. }
  114. }
  115. }
  116. }
  117. void ProductionManager::on_construction_confirm() {
  118. if (!m_is_placing_construction || m_pending_construction_builders.empty()) {
  119. on_construction_cancel();
  120. return;
  121. }
  122. if (!is_construction_position_valid(
  123. m_construction_placement_position.x(),
  124. m_construction_placement_position.z(),
  125. m_pending_construction_type.toStdString())) {
  126. emit construction_placement_rejected();
  127. return;
  128. }
  129. for (auto id : m_pending_construction_builders) {
  130. auto *e = m_world->get_entity(id);
  131. if (!e) {
  132. continue;
  133. }
  134. auto *builder_prod =
  135. e->get_component<Engine::Core::BuilderProductionComponent>();
  136. if (builder_prod) {
  137. builder_prod->is_placement_preview = false;
  138. builder_prod->construction_site_x = m_construction_placement_position.x();
  139. builder_prod->construction_site_z = m_construction_placement_position.z();
  140. }
  141. auto *mv = e->get_component<Engine::Core::MovementComponent>();
  142. if (mv) {
  143. mv->goal_x = m_construction_placement_position.x();
  144. mv->goal_y = m_construction_placement_position.z();
  145. mv->target_x = m_construction_placement_position.x();
  146. mv->target_y = m_construction_placement_position.z();
  147. }
  148. }
  149. m_is_placing_construction = false;
  150. m_pending_construction_type.clear();
  151. m_pending_construction_builders.clear();
  152. emit placing_construction_changed();
  153. }
  154. void ProductionManager::on_construction_cancel() {
  155. if (!m_is_placing_construction) {
  156. return;
  157. }
  158. for (auto id : m_pending_construction_builders) {
  159. auto *e = m_world->get_entity(id);
  160. if (!e) {
  161. continue;
  162. }
  163. auto *builder_prod =
  164. e->get_component<Engine::Core::BuilderProductionComponent>();
  165. if (builder_prod) {
  166. builder_prod->has_construction_site = false;
  167. builder_prod->construction_site_x = 0.0F;
  168. builder_prod->construction_site_z = 0.0F;
  169. builder_prod->at_construction_site = false;
  170. builder_prod->product_type = "";
  171. builder_prod->is_placement_preview = false;
  172. }
  173. }
  174. m_is_placing_construction = false;
  175. m_pending_construction_type.clear();
  176. m_pending_construction_builders.clear();
  177. emit placing_construction_changed();
  178. }
  179. void ProductionManager::start_builder_construction(const QString &item_type) {
  180. if (!m_world) {
  181. return;
  182. }
  183. m_pending_construction_builders = collect_available_builders();
  184. if (m_pending_construction_builders.empty()) {
  185. return;
  186. }
  187. m_pending_construction_type = item_type;
  188. m_is_placing_construction = true;
  189. m_construction_placement_position =
  190. calculate_builder_center_position(m_pending_construction_builders);
  191. std::string item_str = item_type.toStdString();
  192. float build_time = get_construction_build_time(item_str);
  193. for (auto id : m_pending_construction_builders) {
  194. auto *e = m_world->get_entity(id);
  195. if (!e) {
  196. continue;
  197. }
  198. auto *builder_prod =
  199. e->get_component<Engine::Core::BuilderProductionComponent>();
  200. if (!builder_prod) {
  201. continue;
  202. }
  203. builder_prod->product_type = item_str;
  204. builder_prod->build_time = build_time;
  205. builder_prod->time_remaining = build_time;
  206. builder_prod->has_construction_site = true;
  207. builder_prod->construction_site_x = m_construction_placement_position.x();
  208. builder_prod->construction_site_z = m_construction_placement_position.z();
  209. builder_prod->at_construction_site = false;
  210. builder_prod->in_progress = false;
  211. builder_prod->is_placement_preview = true;
  212. }
  213. emit placing_construction_changed();
  214. }
  215. auto ProductionManager::get_selected_production_state(int local_owner_id) const
  216. -> QVariantMap {
  217. QVariantMap m;
  218. m["has_barracks"] = false;
  219. m["in_progress"] = false;
  220. m["time_remaining"] = 0.0;
  221. m["build_time"] = 0.0;
  222. m["produced_count"] = 0;
  223. m["max_units"] = 0;
  224. m["villager_cost"] = 1;
  225. if (!m_world) {
  226. return m;
  227. }
  228. auto *selection_system =
  229. m_world->get_system<Game::Systems::SelectionSystem>();
  230. if (!selection_system) {
  231. return m;
  232. }
  233. Game::Systems::ProductionState st;
  234. Game::Systems::ProductionService::get_selected_barracks_state(
  235. *m_world, selection_system->get_selected_units(), local_owner_id, st);
  236. m["has_barracks"] = st.has_barracks;
  237. m["in_progress"] = st.in_progress;
  238. m["product_type"] =
  239. QString::fromStdString(Game::Units::troop_typeToString(st.product_type));
  240. m["time_remaining"] = st.time_remaining;
  241. m["build_time"] = st.build_time;
  242. m["produced_count"] = st.produced_count;
  243. m["max_units"] = st.max_units;
  244. m["villager_cost"] = st.villager_cost;
  245. m["queue_size"] = st.queue_size;
  246. m["nation_id"] =
  247. QString::fromStdString(Game::Systems::nation_id_to_string(st.nation_id));
  248. QVariantList queue_list;
  249. for (const auto &unit_type : st.production_queue) {
  250. queue_list.append(
  251. QString::fromStdString(Game::Units::troop_typeToString(unit_type)));
  252. }
  253. m["production_queue"] = queue_list;
  254. return m;
  255. }
  256. auto ProductionManager::get_selected_builder_production_state() const
  257. -> QVariantMap {
  258. QVariantMap m;
  259. m["in_progress"] = false;
  260. m["time_remaining"] = 0.0;
  261. m["build_time"] = 10.0;
  262. m["product_type"] = "";
  263. if (!m_world) {
  264. return m;
  265. }
  266. auto *selection_system =
  267. m_world->get_system<Game::Systems::SelectionSystem>();
  268. if (!selection_system) {
  269. return m;
  270. }
  271. const auto &selected = selection_system->get_selected_units();
  272. for (auto id : selected) {
  273. auto *e = m_world->get_entity(id);
  274. if (!e) {
  275. continue;
  276. }
  277. auto *builder_prod =
  278. e->get_component<Engine::Core::BuilderProductionComponent>();
  279. if (builder_prod) {
  280. m["in_progress"] =
  281. builder_prod->in_progress || builder_prod->is_placement_preview;
  282. m["time_remaining"] = builder_prod->time_remaining;
  283. m["build_time"] = builder_prod->build_time;
  284. m["product_type"] = QString::fromStdString(builder_prod->product_type);
  285. return m;
  286. }
  287. }
  288. return m;
  289. }
  290. auto ProductionManager::get_unit_production_info(
  291. const QString &unit_type, const QString &nation_id) const -> QVariantMap {
  292. QVariantMap info;
  293. const auto &config = Game::Units::TroopConfig::instance();
  294. std::string type_str = unit_type.toStdString();
  295. info["cost"] = config.getProductionCost(type_str);
  296. info["build_time"] = static_cast<double>(config.getBuildTime(type_str));
  297. info["individuals_per_unit"] = config.getIndividualsPerUnit(type_str);
  298. auto troop_type_opt = Game::Units::tryParseTroopType(type_str);
  299. if (troop_type_opt.has_value()) {
  300. auto nation_id_opt =
  301. Game::Systems::nation_id_from_string(nation_id.toStdString());
  302. auto nation_id_enum = nation_id_opt.value_or(
  303. Game::Systems::NationRegistry::instance().default_nation_id());
  304. auto profile = Game::Systems::TroopProfileService::instance().get_profile(
  305. nation_id_enum, *troop_type_opt);
  306. info["display_name"] = QString::fromStdString(profile.display_name);
  307. } else {
  308. info["display_name"] = unit_type;
  309. }
  310. return info;
  311. }
  312. void ProductionManager::set_rally_at_screen(qreal sx, qreal sy,
  313. int local_owner_id,
  314. const ViewportState &viewport) {
  315. if (!m_world || !m_picking_service || !m_camera) {
  316. return;
  317. }
  318. QVector3D hit;
  319. if (!m_picking_service->screen_to_ground(
  320. QPointF(sx, sy), *m_camera, viewport.width, viewport.height, hit)) {
  321. return;
  322. }
  323. auto *selection_system =
  324. m_world->get_system<Game::Systems::SelectionSystem>();
  325. if (!selection_system) {
  326. return;
  327. }
  328. const auto &selected = selection_system->get_selected_units();
  329. for (auto id : selected) {
  330. auto *e = m_world->get_entity(id);
  331. if (!e) {
  332. continue;
  333. }
  334. auto *unit = e->get_component<Engine::Core::UnitComponent>();
  335. if (!unit || unit->owner_id != local_owner_id) {
  336. continue;
  337. }
  338. auto *prod = e->get_component<Engine::Core::ProductionComponent>();
  339. if (prod) {
  340. prod->rally_x = hit.x();
  341. prod->rally_z = hit.z();
  342. prod->rally_set = true;
  343. }
  344. }
  345. }
  346. auto ProductionManager::collect_available_builders()
  347. -> std::vector<Engine::Core::EntityID> {
  348. std::vector<Engine::Core::EntityID> builders;
  349. auto *selection_system =
  350. m_world->get_system<Game::Systems::SelectionSystem>();
  351. if (!selection_system) {
  352. return builders;
  353. }
  354. const auto &selected = selection_system->get_selected_units();
  355. for (auto id : selected) {
  356. auto *e = m_world->get_entity(id);
  357. if (!e) {
  358. continue;
  359. }
  360. auto *builder_prod =
  361. e->get_component<Engine::Core::BuilderProductionComponent>();
  362. if (builder_prod && !builder_prod->in_progress) {
  363. builders.push_back(id);
  364. }
  365. }
  366. return builders;
  367. }
  368. auto ProductionManager::calculate_builder_center_position(
  369. const std::vector<Engine::Core::EntityID> &builder_ids) -> QVector3D {
  370. float sum_x = 0.0F;
  371. float sum_y = 0.0F;
  372. float sum_z = 0.0F;
  373. int valid_count = 0;
  374. for (auto id : builder_ids) {
  375. auto *e = m_world->get_entity(id);
  376. if (!e) {
  377. continue;
  378. }
  379. auto *transform = e->get_component<Engine::Core::TransformComponent>();
  380. if (transform) {
  381. sum_x += transform->position.x;
  382. sum_y += transform->position.y;
  383. sum_z += transform->position.z;
  384. valid_count++;
  385. }
  386. }
  387. if (valid_count > 0) {
  388. return QVector3D(sum_x / static_cast<float>(valid_count),
  389. sum_y / static_cast<float>(valid_count),
  390. sum_z / static_cast<float>(valid_count));
  391. }
  392. return QVector3D(0.0F, 0.0F, 0.0F);
  393. }
  394. auto ProductionManager::get_construction_build_time(
  395. const std::string &item_type) -> float {
  396. constexpr float DEFAULT_BUILD_TIME = 10.0F;
  397. constexpr float CATAPULT_BUILD_TIME = 15.0F;
  398. constexpr float BALLISTA_BUILD_TIME = 12.0F;
  399. constexpr float DEFENSE_TOWER_BUILD_TIME = 20.0F;
  400. if (item_type == "catapult") {
  401. return CATAPULT_BUILD_TIME;
  402. }
  403. if (item_type == "ballista") {
  404. return BALLISTA_BUILD_TIME;
  405. }
  406. if (item_type == "defense_tower") {
  407. return DEFENSE_TOWER_BUILD_TIME;
  408. }
  409. return DEFAULT_BUILD_TIME;
  410. }