game_engine.cpp 35 KB

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