game_engine.cpp 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044
  1. #include "game_engine.h"
  2. #include "../controllers/action_vfx.h"
  3. #include "../controllers/command_controller.h"
  4. #include "../models/cursor_manager.h"
  5. #include "../models/hover_tracker.h"
  6. #include <QCoreApplication>
  7. #include <QCursor>
  8. #include <QDebug>
  9. #include <QOpenGLContext>
  10. #include <QQuickWindow>
  11. #include <QVariant>
  12. #include <set>
  13. #include <unordered_map>
  14. #include "game/core/component.h"
  15. #include "game/core/event_manager.h"
  16. #include "game/core/world.h"
  17. #include "game/game_config.h"
  18. #include "game/map/level_loader.h"
  19. #include "game/map/map_catalog.h"
  20. #include "game/map/map_transformer.h"
  21. #include "game/map/skirmish_loader.h"
  22. #include "game/map/terrain_service.h"
  23. #include "game/map/visibility_service.h"
  24. #include "game/map/world_bootstrap.h"
  25. #include "game/systems/ai_system.h"
  26. #include "game/systems/arrow_system.h"
  27. #include "game/systems/building_collision_registry.h"
  28. #include "game/systems/camera_service.h"
  29. #include "game/systems/combat_system.h"
  30. #include "game/systems/command_service.h"
  31. #include "game/systems/formation_planner.h"
  32. #include "game/systems/global_stats_registry.h"
  33. #include "game/systems/movement_system.h"
  34. #include "game/systems/nation_registry.h"
  35. #include "game/systems/owner_registry.h"
  36. #include "game/systems/patrol_system.h"
  37. #include "game/systems/picking_service.h"
  38. #include "game/systems/production_service.h"
  39. #include "game/systems/production_system.h"
  40. #include "game/systems/selection_system.h"
  41. #include "game/systems/terrain_alignment_system.h"
  42. #include "game/systems/troop_count_registry.h"
  43. #include "game/systems/victory_service.h"
  44. #include "game/units/troop_config.h"
  45. #include "game/visuals/team_colors.h"
  46. #include "render/geom/arrow.h"
  47. #include "render/geom/patrol_flags.h"
  48. #include "render/gl/bootstrap.h"
  49. #include "render/gl/camera.h"
  50. #include "render/gl/resources.h"
  51. #include "render/ground/biome_renderer.h"
  52. #include "render/ground/fog_renderer.h"
  53. #include "render/ground/ground_renderer.h"
  54. #include "render/ground/stone_renderer.h"
  55. #include "render/ground/terrain_renderer.h"
  56. #include "render/scene_renderer.h"
  57. #include "../models/selected_units_model.h"
  58. #include <QDir>
  59. #include <QFile>
  60. #include <QJsonArray>
  61. #include <QJsonDocument>
  62. #include <QJsonObject>
  63. #include <QSet>
  64. #include <algorithm>
  65. #include <cmath>
  66. #include <limits>
  67. GameEngine::GameEngine() {
  68. Game::Systems::NationRegistry::instance().initializeDefaults();
  69. Game::Systems::TroopCountRegistry::instance().initialize();
  70. Game::Systems::GlobalStatsRegistry::instance().initialize();
  71. m_world = std::make_unique<Engine::Core::World>();
  72. m_renderer = std::make_unique<Render::GL::Renderer>();
  73. m_camera = std::make_unique<Render::GL::Camera>();
  74. m_ground = std::make_unique<Render::GL::GroundRenderer>();
  75. m_terrain = std::make_unique<Render::GL::TerrainRenderer>();
  76. m_biome = std::make_unique<Render::GL::BiomeRenderer>();
  77. m_fog = std::make_unique<Render::GL::FogRenderer>();
  78. m_stone = std::make_unique<Render::GL::StoneRenderer>();
  79. std::unique_ptr<Engine::Core::System> arrowSys =
  80. std::make_unique<Game::Systems::ArrowSystem>();
  81. m_world->addSystem(std::move(arrowSys));
  82. m_world->addSystem(std::make_unique<Game::Systems::MovementSystem>());
  83. m_world->addSystem(std::make_unique<Game::Systems::PatrolSystem>());
  84. m_world->addSystem(std::make_unique<Game::Systems::CombatSystem>());
  85. m_world->addSystem(std::make_unique<Game::Systems::AISystem>());
  86. m_world->addSystem(std::make_unique<Game::Systems::ProductionSystem>());
  87. m_world->addSystem(std::make_unique<Game::Systems::TerrainAlignmentSystem>());
  88. {
  89. std::unique_ptr<Engine::Core::System> selSys =
  90. std::make_unique<Game::Systems::SelectionSystem>();
  91. m_world->addSystem(std::move(selSys));
  92. }
  93. m_selectedUnitsModel = new SelectedUnitsModel(this, this);
  94. m_pickingService = std::make_unique<Game::Systems::PickingService>();
  95. m_victoryService = std::make_unique<Game::Systems::VictoryService>();
  96. m_cameraService = std::make_unique<Game::Systems::CameraService>();
  97. auto *selectionSystem = m_world->getSystem<Game::Systems::SelectionSystem>();
  98. m_selectionController = std::make_unique<Game::Systems::SelectionController>(
  99. m_world.get(), selectionSystem, m_pickingService.get());
  100. m_commandController = std::make_unique<App::Controllers::CommandController>(
  101. m_world.get(), selectionSystem, m_pickingService.get());
  102. m_cursorManager = std::make_unique<CursorManager>();
  103. m_hoverTracker = std::make_unique<HoverTracker>(m_pickingService.get());
  104. m_mapCatalog = std::make_unique<Game::Map::MapCatalog>();
  105. connect(m_mapCatalog.get(), &Game::Map::MapCatalog::mapLoaded, this,
  106. [this](QVariantMap mapData) {
  107. m_availableMaps.append(mapData);
  108. emit availableMapsChanged();
  109. });
  110. connect(m_mapCatalog.get(), &Game::Map::MapCatalog::loadingChanged, this,
  111. [this](bool loading) {
  112. m_mapsLoading = loading;
  113. emit mapsLoadingChanged();
  114. });
  115. connect(m_mapCatalog.get(), &Game::Map::MapCatalog::allMapsLoaded, this,
  116. [this]() { emit availableMapsChanged(); });
  117. connect(m_cursorManager.get(), &CursorManager::modeChanged, this,
  118. &GameEngine::cursorModeChanged);
  119. connect(m_cursorManager.get(), &CursorManager::globalCursorChanged, this,
  120. &GameEngine::globalCursorChanged);
  121. connect(m_selectionController.get(),
  122. &Game::Systems::SelectionController::selectionChanged, this,
  123. &GameEngine::selectedUnitsChanged);
  124. connect(m_selectionController.get(),
  125. &Game::Systems::SelectionController::selectionModelRefreshRequested,
  126. this, &GameEngine::selectedUnitsDataChanged);
  127. connect(m_commandController.get(),
  128. &App::Controllers::CommandController::attackTargetSelected, [this]() {
  129. if (auto *selSys =
  130. m_world->getSystem<Game::Systems::SelectionSystem>()) {
  131. const auto &sel = selSys->getSelectedUnits();
  132. if (!sel.empty()) {
  133. auto *cam = m_camera.get();
  134. auto *picking = m_pickingService.get();
  135. if (cam && picking) {
  136. Engine::Core::EntityID targetId = picking->pickUnitFirst(
  137. 0.0f, 0.0f, *m_world, *cam, m_viewport.width,
  138. m_viewport.height, 0);
  139. if (targetId != 0) {
  140. App::Controllers::ActionVFX::spawnAttackArrow(m_world.get(),
  141. targetId);
  142. }
  143. }
  144. }
  145. }
  146. });
  147. connect(m_commandController.get(),
  148. &App::Controllers::CommandController::troopLimitReached, [this]() {
  149. setError("Maximum troop limit reached. Cannot produce more units.");
  150. });
  151. connect(this, SIGNAL(selectedUnitsChanged()), m_selectedUnitsModel,
  152. SLOT(refresh()));
  153. connect(this, SIGNAL(selectedUnitsDataChanged()), m_selectedUnitsModel,
  154. SLOT(refresh()));
  155. emit selectedUnitsChanged();
  156. m_unitDiedSubscription =
  157. Engine::Core::ScopedEventSubscription<Engine::Core::UnitDiedEvent>(
  158. [this](const Engine::Core::UnitDiedEvent &e) {
  159. onUnitDied(e);
  160. if (e.ownerId != m_runtime.localOwnerId) {
  161. int individualsPerUnit =
  162. Game::Units::TroopConfig::instance().getIndividualsPerUnit(
  163. e.unitType);
  164. m_enemyTroopsDefeated += individualsPerUnit;
  165. emit enemyTroopsDefeatedChanged();
  166. }
  167. });
  168. m_unitSpawnedSubscription =
  169. Engine::Core::ScopedEventSubscription<Engine::Core::UnitSpawnedEvent>(
  170. [this](const Engine::Core::UnitSpawnedEvent &e) {
  171. onUnitSpawned(e);
  172. });
  173. }
  174. GameEngine::~GameEngine() = default;
  175. void GameEngine::onMapClicked(qreal sx, qreal sy) {
  176. if (!m_window)
  177. return;
  178. ensureInitialized();
  179. if (m_selectionController && m_camera) {
  180. m_selectionController->onClickSelect(sx, sy, false, m_viewport.width,
  181. m_viewport.height, m_camera.get(),
  182. m_runtime.localOwnerId);
  183. }
  184. }
  185. void GameEngine::onRightClick(qreal sx, qreal sy) {
  186. if (!m_window)
  187. return;
  188. ensureInitialized();
  189. auto *selectionSystem = m_world->getSystem<Game::Systems::SelectionSystem>();
  190. if (!selectionSystem)
  191. return;
  192. if (m_cursorManager->mode() == "patrol" ||
  193. m_cursorManager->mode() == "attack") {
  194. setCursorMode("normal");
  195. return;
  196. }
  197. const auto &sel = selectionSystem->getSelectedUnits();
  198. if (!sel.empty()) {
  199. if (m_selectionController) {
  200. m_selectionController->onRightClickClearSelection();
  201. }
  202. setCursorMode("normal");
  203. return;
  204. }
  205. }
  206. void GameEngine::onAttackClick(qreal sx, qreal sy) {
  207. if (!m_window)
  208. return;
  209. ensureInitialized();
  210. if (!m_commandController || !m_camera)
  211. return;
  212. auto result = m_commandController->onAttackClick(
  213. sx, sy, m_viewport.width, m_viewport.height, m_camera.get());
  214. auto *selectionSystem = m_world->getSystem<Game::Systems::SelectionSystem>();
  215. if (!selectionSystem || !m_pickingService || !m_camera || !m_world)
  216. return;
  217. const auto &selected = selectionSystem->getSelectedUnits();
  218. if (!selected.empty()) {
  219. Engine::Core::EntityID targetId = m_pickingService->pickUnitFirst(
  220. float(sx), float(sy), *m_world, *m_camera, m_viewport.width,
  221. m_viewport.height, 0);
  222. if (targetId != 0) {
  223. auto *targetEntity = m_world->getEntity(targetId);
  224. if (targetEntity) {
  225. auto *targetUnit =
  226. targetEntity->getComponent<Engine::Core::UnitComponent>();
  227. if (targetUnit && targetUnit->ownerId != m_runtime.localOwnerId) {
  228. App::Controllers::ActionVFX::spawnAttackArrow(m_world.get(),
  229. targetId);
  230. }
  231. }
  232. }
  233. }
  234. if (result.resetCursorToNormal) {
  235. setCursorMode("normal");
  236. }
  237. }
  238. void GameEngine::resetMovement(Engine::Core::Entity *entity) {
  239. App::Utils::resetMovement(entity);
  240. }
  241. void GameEngine::onStopCommand() {
  242. if (!m_commandController)
  243. return;
  244. ensureInitialized();
  245. auto result = m_commandController->onStopCommand();
  246. if (result.resetCursorToNormal) {
  247. setCursorMode("normal");
  248. }
  249. }
  250. void GameEngine::onPatrolClick(qreal sx, qreal sy) {
  251. if (!m_commandController || !m_camera)
  252. return;
  253. ensureInitialized();
  254. auto result = m_commandController->onPatrolClick(
  255. sx, sy, m_viewport.width, m_viewport.height, m_camera.get());
  256. if (result.resetCursorToNormal) {
  257. setCursorMode("normal");
  258. }
  259. }
  260. void GameEngine::updateCursor(Qt::CursorShape newCursor) {
  261. if (!m_window)
  262. return;
  263. if (m_runtime.currentCursor != newCursor) {
  264. m_runtime.currentCursor = newCursor;
  265. m_window->setCursor(newCursor);
  266. }
  267. }
  268. void GameEngine::setError(const QString &errorMessage) {
  269. if (m_runtime.lastError != errorMessage) {
  270. m_runtime.lastError = errorMessage;
  271. qCritical() << "GameEngine error:" << errorMessage;
  272. emit lastErrorChanged();
  273. }
  274. }
  275. void GameEngine::setCursorMode(const QString &mode) {
  276. if (!m_cursorManager)
  277. return;
  278. m_cursorManager->setMode(mode);
  279. m_cursorManager->updateCursorShape(m_window);
  280. }
  281. QString GameEngine::cursorMode() const {
  282. if (!m_cursorManager)
  283. return "normal";
  284. return m_cursorManager->mode();
  285. }
  286. qreal GameEngine::globalCursorX() const {
  287. if (!m_cursorManager)
  288. return 0;
  289. return m_cursorManager->globalCursorX(m_window);
  290. }
  291. qreal GameEngine::globalCursorY() const {
  292. if (!m_cursorManager)
  293. return 0;
  294. return m_cursorManager->globalCursorY(m_window);
  295. }
  296. void GameEngine::setHoverAtScreen(qreal sx, qreal sy) {
  297. if (!m_window)
  298. return;
  299. ensureInitialized();
  300. if (!m_hoverTracker || !m_camera || !m_world)
  301. return;
  302. m_cursorManager->updateCursorShape(m_window);
  303. m_hoverTracker->updateHover(float(sx), float(sy), *m_world, *m_camera,
  304. m_viewport.width, m_viewport.height);
  305. }
  306. void GameEngine::onClickSelect(qreal sx, qreal sy, bool additive) {
  307. if (!m_window)
  308. return;
  309. ensureInitialized();
  310. if (m_selectionController && m_camera) {
  311. m_selectionController->onClickSelect(sx, sy, additive, m_viewport.width,
  312. m_viewport.height, m_camera.get(),
  313. m_runtime.localOwnerId);
  314. }
  315. }
  316. void GameEngine::onAreaSelected(qreal x1, qreal y1, qreal x2, qreal y2,
  317. bool additive) {
  318. if (!m_window)
  319. return;
  320. ensureInitialized();
  321. if (m_selectionController && m_camera) {
  322. m_selectionController->onAreaSelected(
  323. x1, y1, x2, y2, additive, m_viewport.width, m_viewport.height,
  324. m_camera.get(), m_runtime.localOwnerId);
  325. }
  326. }
  327. void GameEngine::selectAllTroops() {
  328. ensureInitialized();
  329. if (m_selectionController) {
  330. m_selectionController->selectAllPlayerTroops(m_runtime.localOwnerId);
  331. }
  332. }
  333. void GameEngine::ensureInitialized() {
  334. QString error;
  335. Game::Map::WorldBootstrap::ensureInitialized(
  336. m_runtime.initialized, *m_renderer, *m_camera, m_ground.get(), &error);
  337. if (!error.isEmpty()) {
  338. setError(error);
  339. }
  340. }
  341. int GameEngine::enemyTroopsDefeated() const { return m_enemyTroopsDefeated; }
  342. QVariantMap GameEngine::getPlayerStats(int ownerId) const {
  343. QVariantMap result;
  344. auto &statsRegistry = Game::Systems::GlobalStatsRegistry::instance();
  345. const auto *stats = statsRegistry.getStats(ownerId);
  346. if (stats) {
  347. result["troopsRecruited"] = stats->troopsRecruited;
  348. result["enemiesKilled"] = stats->enemiesKilled;
  349. result["barracksOwned"] = stats->barracksOwned;
  350. result["playTimeSec"] = stats->playTimeSec;
  351. result["gameEnded"] = stats->gameEnded;
  352. } else {
  353. result["troopsRecruited"] = 0;
  354. result["enemiesKilled"] = 0;
  355. result["barracksOwned"] = 0;
  356. result["playTimeSec"] = 0.0f;
  357. result["gameEnded"] = false;
  358. }
  359. return result;
  360. }
  361. void GameEngine::update(float dt) {
  362. if (m_runtime.loading) {
  363. return;
  364. }
  365. if (m_runtime.paused) {
  366. dt = 0.0f;
  367. } else {
  368. dt *= m_runtime.timeScale;
  369. }
  370. if (m_renderer) {
  371. m_renderer->updateAnimationTime(dt);
  372. }
  373. if (m_camera) {
  374. m_camera->update(dt);
  375. }
  376. if (m_world) {
  377. m_world->update(dt);
  378. auto &visibilityService = Game::Map::VisibilityService::instance();
  379. if (visibilityService.isInitialized()) {
  380. m_runtime.visibilityUpdateAccumulator += dt;
  381. const float visibilityUpdateInterval =
  382. Game::GameConfig::instance().gameplay().visibilityUpdateInterval;
  383. if (m_runtime.visibilityUpdateAccumulator >= visibilityUpdateInterval) {
  384. m_runtime.visibilityUpdateAccumulator = 0.0f;
  385. visibilityService.update(*m_world, m_runtime.localOwnerId);
  386. }
  387. const auto newVersion = visibilityService.version();
  388. if (newVersion != m_runtime.visibilityVersion) {
  389. if (m_fog) {
  390. m_fog->updateMask(visibilityService.getWidth(),
  391. visibilityService.getHeight(),
  392. visibilityService.getTileSize(),
  393. visibilityService.snapshotCells());
  394. }
  395. m_runtime.visibilityVersion = newVersion;
  396. }
  397. }
  398. }
  399. syncSelectionFlags();
  400. if (m_victoryService && m_world) {
  401. m_victoryService->update(*m_world, dt);
  402. }
  403. int currentTroopCount = playerTroopCount();
  404. if (currentTroopCount != m_runtime.lastTroopCount) {
  405. m_runtime.lastTroopCount = currentTroopCount;
  406. emit troopCountChanged();
  407. }
  408. if (m_followSelectionEnabled && m_camera && m_world && m_cameraService) {
  409. m_cameraService->updateFollow(*m_camera, *m_world,
  410. m_followSelectionEnabled);
  411. }
  412. if (m_selectedUnitsModel) {
  413. auto *selectionSystem =
  414. m_world->getSystem<Game::Systems::SelectionSystem>();
  415. if (selectionSystem && !selectionSystem->getSelectedUnits().empty()) {
  416. m_runtime.selectionRefreshCounter++;
  417. if (m_runtime.selectionRefreshCounter >= 15) {
  418. m_runtime.selectionRefreshCounter = 0;
  419. emit selectedUnitsDataChanged();
  420. }
  421. }
  422. }
  423. }
  424. void GameEngine::render(int pixelWidth, int pixelHeight) {
  425. if (!m_renderer || !m_world || !m_runtime.initialized || m_runtime.loading)
  426. return;
  427. if (pixelWidth > 0 && pixelHeight > 0) {
  428. m_viewport.width = pixelWidth;
  429. m_viewport.height = pixelHeight;
  430. m_renderer->setViewport(pixelWidth, pixelHeight);
  431. }
  432. if (auto *selectionSystem =
  433. m_world->getSystem<Game::Systems::SelectionSystem>()) {
  434. const auto &sel = selectionSystem->getSelectedUnits();
  435. std::vector<unsigned int> ids(sel.begin(), sel.end());
  436. m_renderer->setSelectedEntities(ids);
  437. }
  438. m_renderer->beginFrame();
  439. if (m_ground && m_renderer) {
  440. if (auto *res = m_renderer->resources())
  441. m_ground->submit(*m_renderer, *res);
  442. }
  443. if (m_terrain && m_renderer) {
  444. if (auto *res = m_renderer->resources())
  445. m_terrain->submit(*m_renderer, *res);
  446. }
  447. if (m_biome && m_renderer) {
  448. m_biome->submit(*m_renderer);
  449. }
  450. if (m_stone && m_renderer) {
  451. m_stone->submit(*m_renderer);
  452. }
  453. if (m_fog && m_renderer) {
  454. if (auto *res = m_renderer->resources())
  455. m_fog->submit(*m_renderer, *res);
  456. }
  457. if (m_renderer && m_hoverTracker)
  458. m_renderer->setHoveredEntityId(m_hoverTracker->getLastHoveredEntity());
  459. if (m_renderer)
  460. m_renderer->setLocalOwnerId(m_runtime.localOwnerId);
  461. m_renderer->renderWorld(m_world.get());
  462. if (auto *arrowSystem = m_world->getSystem<Game::Systems::ArrowSystem>()) {
  463. if (auto *res = m_renderer->resources())
  464. Render::GL::renderArrows(m_renderer.get(), res, *arrowSystem);
  465. }
  466. if (auto *res = m_renderer->resources()) {
  467. std::optional<QVector3D> previewWaypoint;
  468. if (m_commandController && m_commandController->hasPatrolFirstWaypoint()) {
  469. previewWaypoint = m_commandController->getPatrolFirstWaypoint();
  470. }
  471. Render::GL::renderPatrolFlags(m_renderer.get(), res, *m_world,
  472. previewWaypoint);
  473. }
  474. m_renderer->endFrame();
  475. qreal currentX = globalCursorX();
  476. qreal currentY = globalCursorY();
  477. if (currentX != m_runtime.lastCursorX || currentY != m_runtime.lastCursorY) {
  478. m_runtime.lastCursorX = currentX;
  479. m_runtime.lastCursorY = currentY;
  480. emit globalCursorChanged();
  481. }
  482. }
  483. bool GameEngine::screenToGround(const QPointF &screenPt, QVector3D &outWorld) {
  484. return App::Utils::screenToGround(m_pickingService.get(), m_camera.get(),
  485. m_window, m_viewport.width,
  486. m_viewport.height, screenPt, outWorld);
  487. }
  488. bool GameEngine::worldToScreen(const QVector3D &world,
  489. QPointF &outScreen) const {
  490. return App::Utils::worldToScreen(m_pickingService.get(), m_camera.get(),
  491. m_window, m_viewport.width,
  492. m_viewport.height, world, outScreen);
  493. }
  494. void GameEngine::syncSelectionFlags() {
  495. auto *selectionSystem = m_world->getSystem<Game::Systems::SelectionSystem>();
  496. if (!m_world || !selectionSystem)
  497. return;
  498. App::Utils::sanitizeSelection(m_world.get(), selectionSystem);
  499. if (selectionSystem->getSelectedUnits().empty()) {
  500. if (m_cursorManager && m_cursorManager->mode() != "normal") {
  501. setCursorMode("normal");
  502. }
  503. }
  504. }
  505. void GameEngine::cameraMove(float dx, float dz) {
  506. ensureInitialized();
  507. if (!m_camera || !m_cameraService)
  508. return;
  509. m_cameraService->move(*m_camera, dx, dz);
  510. }
  511. void GameEngine::cameraElevate(float dy) {
  512. ensureInitialized();
  513. if (!m_camera || !m_cameraService)
  514. return;
  515. m_cameraService->elevate(*m_camera, dy);
  516. }
  517. void GameEngine::resetCamera() {
  518. ensureInitialized();
  519. if (!m_camera || !m_world || !m_cameraService)
  520. return;
  521. m_cameraService->resetCamera(*m_camera, *m_world, m_runtime.localOwnerId,
  522. m_level.playerUnitId);
  523. }
  524. void GameEngine::cameraZoom(float delta) {
  525. ensureInitialized();
  526. if (!m_camera || !m_cameraService)
  527. return;
  528. m_cameraService->zoom(*m_camera, delta);
  529. }
  530. float GameEngine::cameraDistance() const {
  531. if (!m_camera || !m_cameraService)
  532. return 0.0f;
  533. return m_cameraService->getDistance(*m_camera);
  534. }
  535. void GameEngine::cameraYaw(float degrees) {
  536. ensureInitialized();
  537. if (!m_camera || !m_cameraService)
  538. return;
  539. m_cameraService->yaw(*m_camera, degrees);
  540. }
  541. void GameEngine::cameraOrbit(float yawDeg, float pitchDeg) {
  542. ensureInitialized();
  543. if (!m_camera || !m_cameraService)
  544. return;
  545. if (!std::isfinite(yawDeg) || !std::isfinite(pitchDeg)) {
  546. qWarning() << "GameEngine::cameraOrbit received invalid input, ignoring:"
  547. << yawDeg << pitchDeg;
  548. return;
  549. }
  550. m_cameraService->orbit(*m_camera, yawDeg, pitchDeg);
  551. }
  552. void GameEngine::cameraOrbitDirection(int direction, bool shift) {
  553. if (!m_camera || !m_cameraService)
  554. return;
  555. m_cameraService->orbitDirection(*m_camera, direction, shift);
  556. }
  557. void GameEngine::cameraFollowSelection(bool enable) {
  558. ensureInitialized();
  559. m_followSelectionEnabled = enable;
  560. if (!m_camera || !m_world || !m_cameraService)
  561. return;
  562. m_cameraService->followSelection(*m_camera, *m_world, enable);
  563. }
  564. void GameEngine::cameraSetFollowLerp(float alpha) {
  565. ensureInitialized();
  566. if (!m_camera || !m_cameraService)
  567. return;
  568. m_cameraService->setFollowLerp(*m_camera, alpha);
  569. }
  570. QObject *GameEngine::selectedUnitsModel() { return m_selectedUnitsModel; }
  571. bool GameEngine::hasUnitsSelected() const {
  572. if (!m_selectionController)
  573. return false;
  574. return m_selectionController->hasUnitsSelected();
  575. }
  576. int GameEngine::playerTroopCount() const {
  577. return m_entityCache.playerTroopCount;
  578. }
  579. bool GameEngine::hasSelectedType(const QString &type) const {
  580. if (!m_selectionController)
  581. return false;
  582. return m_selectionController->hasSelectedType(type);
  583. }
  584. void GameEngine::recruitNearSelected(const QString &unitType) {
  585. ensureInitialized();
  586. if (!m_commandController)
  587. return;
  588. m_commandController->recruitNearSelected(unitType, m_runtime.localOwnerId);
  589. }
  590. QVariantMap GameEngine::getSelectedProductionState() const {
  591. QVariantMap m;
  592. m["hasBarracks"] = false;
  593. m["inProgress"] = false;
  594. m["timeRemaining"] = 0.0;
  595. m["buildTime"] = 0.0;
  596. m["producedCount"] = 0;
  597. m["maxUnits"] = 0;
  598. m["villagerCost"] = 1;
  599. if (!m_world)
  600. return m;
  601. auto *selectionSystem = m_world->getSystem<Game::Systems::SelectionSystem>();
  602. if (!selectionSystem)
  603. return m;
  604. Game::Systems::ProductionState st;
  605. Game::Systems::ProductionService::getSelectedBarracksState(
  606. *m_world, selectionSystem->getSelectedUnits(), m_runtime.localOwnerId,
  607. st);
  608. m["hasBarracks"] = st.hasBarracks;
  609. m["inProgress"] = st.inProgress;
  610. m["timeRemaining"] = st.timeRemaining;
  611. m["buildTime"] = st.buildTime;
  612. m["producedCount"] = st.producedCount;
  613. m["maxUnits"] = st.maxUnits;
  614. m["villagerCost"] = st.villagerCost;
  615. return m;
  616. }
  617. QString GameEngine::getSelectedUnitsCommandMode() const {
  618. if (!m_world)
  619. return "normal";
  620. auto *selectionSystem = m_world->getSystem<Game::Systems::SelectionSystem>();
  621. if (!selectionSystem)
  622. return "normal";
  623. const auto &sel = selectionSystem->getSelectedUnits();
  624. if (sel.empty())
  625. return "normal";
  626. int attackingCount = 0;
  627. int patrollingCount = 0;
  628. int totalUnits = 0;
  629. for (auto id : sel) {
  630. auto *e = m_world->getEntity(id);
  631. if (!e)
  632. continue;
  633. auto *u = e->getComponent<Engine::Core::UnitComponent>();
  634. if (!u)
  635. continue;
  636. if (u->unitType == "barracks")
  637. continue;
  638. totalUnits++;
  639. if (e->getComponent<Engine::Core::AttackTargetComponent>())
  640. attackingCount++;
  641. auto *patrol = e->getComponent<Engine::Core::PatrolComponent>();
  642. if (patrol && patrol->patrolling)
  643. patrollingCount++;
  644. }
  645. if (totalUnits == 0)
  646. return "normal";
  647. if (patrollingCount == totalUnits)
  648. return "patrol";
  649. if (attackingCount == totalUnits)
  650. return "attack";
  651. return "normal";
  652. }
  653. void GameEngine::setRallyAtScreen(qreal sx, qreal sy) {
  654. ensureInitialized();
  655. if (!m_commandController || !m_camera)
  656. return;
  657. m_commandController->setRallyAtScreen(sx, sy, m_viewport.width,
  658. m_viewport.height, m_camera.get(),
  659. m_runtime.localOwnerId);
  660. }
  661. void GameEngine::startLoadingMaps() {
  662. m_availableMaps.clear();
  663. if (m_mapCatalog) {
  664. m_mapCatalog->loadMapsAsync();
  665. }
  666. }
  667. QVariantList GameEngine::availableMaps() const { return m_availableMaps; }
  668. void GameEngine::startSkirmish(const QString &mapPath,
  669. const QVariantList &playerConfigs) {
  670. clearError();
  671. m_level.mapName = mapPath;
  672. m_runtime.victoryState = "";
  673. if (!m_runtime.initialized) {
  674. ensureInitialized();
  675. return;
  676. }
  677. if (m_world && m_renderer && m_camera) {
  678. m_runtime.loading = true;
  679. if (m_hoverTracker) {
  680. m_hoverTracker->updateHover(-1, -1, *m_world, *m_camera, 0, 0);
  681. }
  682. m_entityCache.reset();
  683. Game::Map::SkirmishLoader loader(*m_world, *m_renderer, *m_camera);
  684. loader.setGroundRenderer(m_ground.get());
  685. loader.setTerrainRenderer(m_terrain.get());
  686. loader.setBiomeRenderer(m_biome.get());
  687. loader.setFogRenderer(m_fog.get());
  688. loader.setStoneRenderer(m_stone.get());
  689. loader.setOnOwnersUpdated([this]() { emit ownerInfoChanged(); });
  690. loader.setOnVisibilityMaskReady([this]() {
  691. m_runtime.visibilityVersion =
  692. Game::Map::VisibilityService::instance().version();
  693. m_runtime.visibilityUpdateAccumulator = 0.0f;
  694. });
  695. int updatedPlayerId = m_selectedPlayerId;
  696. auto result = loader.start(mapPath, playerConfigs, m_selectedPlayerId,
  697. updatedPlayerId);
  698. if (updatedPlayerId != m_selectedPlayerId) {
  699. m_selectedPlayerId = updatedPlayerId;
  700. emit selectedPlayerIdChanged();
  701. }
  702. if (!result.ok && !result.errorMessage.isEmpty()) {
  703. setError(result.errorMessage);
  704. }
  705. m_runtime.localOwnerId = updatedPlayerId;
  706. m_level.mapName = result.mapName;
  707. m_level.playerUnitId = result.playerUnitId;
  708. m_level.camFov = result.camFov;
  709. m_level.camNear = result.camNear;
  710. m_level.camFar = result.camFar;
  711. m_level.maxTroopsPerPlayer = result.maxTroopsPerPlayer;
  712. Game::GameConfig::instance().setMaxTroopsPerPlayer(
  713. result.maxTroopsPerPlayer);
  714. if (m_victoryService) {
  715. m_victoryService->configure(result.victoryConfig, m_runtime.localOwnerId);
  716. m_victoryService->setVictoryCallback([this](const QString &state) {
  717. if (m_runtime.victoryState != state) {
  718. m_runtime.victoryState = state;
  719. emit victoryStateChanged();
  720. }
  721. });
  722. }
  723. if (result.hasFocusPosition && m_camera) {
  724. const auto &camConfig = Game::GameConfig::instance().camera();
  725. m_camera->setRTSView(result.focusPosition, camConfig.defaultDistance,
  726. camConfig.defaultPitch, camConfig.defaultYaw);
  727. }
  728. m_runtime.loading = false;
  729. if (auto *aiSystem = m_world->getSystem<Game::Systems::AISystem>()) {
  730. aiSystem->reinitialize();
  731. }
  732. rebuildEntityCache();
  733. Game::Systems::TroopCountRegistry::instance().rebuildFromWorld(*m_world);
  734. // Initialize global stats tracking for all players
  735. auto &statsRegistry = Game::Systems::GlobalStatsRegistry::instance();
  736. statsRegistry.rebuildFromWorld(*m_world);
  737. // Mark game start for all players/AI
  738. auto &ownerRegistry = Game::Systems::OwnerRegistry::instance();
  739. const auto &allOwners = ownerRegistry.getAllOwners();
  740. for (const auto &owner : allOwners) {
  741. if (owner.type == Game::Systems::OwnerType::Player ||
  742. owner.type == Game::Systems::OwnerType::AI) {
  743. statsRegistry.markGameStart(owner.ownerId);
  744. }
  745. }
  746. emit ownerInfoChanged();
  747. }
  748. }
  749. void GameEngine::openSettings() { qInfo() << "Open settings requested"; }
  750. void GameEngine::loadSave() {
  751. qInfo() << "Load save requested (not implemented)";
  752. }
  753. void GameEngine::exitGame() {
  754. qInfo() << "Exit requested";
  755. QCoreApplication::quit();
  756. }
  757. QVariantList GameEngine::getOwnerInfo() const {
  758. QVariantList result;
  759. const auto &ownerRegistry = Game::Systems::OwnerRegistry::instance();
  760. const auto &owners = ownerRegistry.getAllOwners();
  761. for (const auto &owner : owners) {
  762. QVariantMap ownerMap;
  763. ownerMap["id"] = owner.ownerId;
  764. ownerMap["name"] = QString::fromStdString(owner.name);
  765. QString typeStr;
  766. switch (owner.type) {
  767. case Game::Systems::OwnerType::Player:
  768. typeStr = "Player";
  769. break;
  770. case Game::Systems::OwnerType::AI:
  771. typeStr = "AI";
  772. break;
  773. case Game::Systems::OwnerType::Neutral:
  774. typeStr = "Neutral";
  775. break;
  776. }
  777. ownerMap["type"] = typeStr;
  778. ownerMap["isLocal"] = (owner.ownerId == m_runtime.localOwnerId);
  779. result.append(ownerMap);
  780. }
  781. return result;
  782. }
  783. void GameEngine::getSelectedUnitIds(
  784. std::vector<Engine::Core::EntityID> &out) const {
  785. out.clear();
  786. if (!m_selectionController)
  787. return;
  788. m_selectionController->getSelectedUnitIds(out);
  789. }
  790. bool GameEngine::getUnitInfo(Engine::Core::EntityID id, QString &name,
  791. int &health, int &maxHealth, bool &isBuilding,
  792. bool &alive) const {
  793. if (!m_world)
  794. return false;
  795. auto *e = m_world->getEntity(id);
  796. if (!e)
  797. return false;
  798. isBuilding = e->hasComponent<Engine::Core::BuildingComponent>();
  799. if (auto *u = e->getComponent<Engine::Core::UnitComponent>()) {
  800. name = QString::fromStdString(u->unitType);
  801. health = u->health;
  802. maxHealth = u->maxHealth;
  803. alive = (u->health > 0);
  804. return true;
  805. }
  806. name = QStringLiteral("Entity");
  807. health = maxHealth = 0;
  808. alive = true;
  809. return true;
  810. }
  811. void GameEngine::onUnitSpawned(const Engine::Core::UnitSpawnedEvent &event) {
  812. if (event.ownerId == m_runtime.localOwnerId) {
  813. if (event.unitType == "barracks") {
  814. m_entityCache.playerBarracksAlive = true;
  815. } else {
  816. int individualsPerUnit =
  817. Game::Units::TroopConfig::instance().getIndividualsPerUnit(
  818. event.unitType);
  819. m_entityCache.playerTroopCount += individualsPerUnit;
  820. }
  821. } else if (Game::Systems::OwnerRegistry::instance().isAI(event.ownerId)) {
  822. if (event.unitType == "barracks") {
  823. m_entityCache.enemyBarracksCount++;
  824. m_entityCache.enemyBarracksAlive = true;
  825. }
  826. }
  827. }
  828. void GameEngine::onUnitDied(const Engine::Core::UnitDiedEvent &event) {
  829. if (event.ownerId == m_runtime.localOwnerId) {
  830. if (event.unitType == "barracks") {
  831. m_entityCache.playerBarracksAlive = false;
  832. } else {
  833. int individualsPerUnit =
  834. Game::Units::TroopConfig::instance().getIndividualsPerUnit(
  835. event.unitType);
  836. m_entityCache.playerTroopCount -= individualsPerUnit;
  837. m_entityCache.playerTroopCount =
  838. std::max(0, m_entityCache.playerTroopCount);
  839. }
  840. } else if (Game::Systems::OwnerRegistry::instance().isAI(event.ownerId)) {
  841. if (event.unitType == "barracks") {
  842. m_entityCache.enemyBarracksCount--;
  843. m_entityCache.enemyBarracksCount =
  844. std::max(0, m_entityCache.enemyBarracksCount);
  845. m_entityCache.enemyBarracksAlive = (m_entityCache.enemyBarracksCount > 0);
  846. }
  847. }
  848. }
  849. void GameEngine::rebuildEntityCache() {
  850. if (!m_world) {
  851. m_entityCache.reset();
  852. return;
  853. }
  854. m_entityCache.reset();
  855. auto entities = m_world->getEntitiesWith<Engine::Core::UnitComponent>();
  856. for (auto *e : entities) {
  857. auto *unit = e->getComponent<Engine::Core::UnitComponent>();
  858. if (!unit || unit->health <= 0)
  859. continue;
  860. if (unit->ownerId == m_runtime.localOwnerId) {
  861. if (unit->unitType == "barracks") {
  862. m_entityCache.playerBarracksAlive = true;
  863. } else {
  864. int individualsPerUnit =
  865. Game::Units::TroopConfig::instance().getIndividualsPerUnit(
  866. unit->unitType);
  867. m_entityCache.playerTroopCount += individualsPerUnit;
  868. }
  869. } else if (Game::Systems::OwnerRegistry::instance().isAI(unit->ownerId)) {
  870. if (unit->unitType == "barracks") {
  871. m_entityCache.enemyBarracksCount++;
  872. m_entityCache.enemyBarracksAlive = true;
  873. }
  874. }
  875. }
  876. }
  877. bool GameEngine::hasPatrolPreviewWaypoint() const {
  878. return m_commandController && m_commandController->hasPatrolFirstWaypoint();
  879. }
  880. QVector3D GameEngine::getPatrolPreviewWaypoint() const {
  881. if (!m_commandController)
  882. return QVector3D();
  883. return m_commandController->getPatrolFirstWaypoint();
  884. }