game_engine.cpp 46 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485
  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 "../utils/json_vec_utils.h"
  7. #include <QBuffer>
  8. #include <QCoreApplication>
  9. #include <QCursor>
  10. #include <QDebug>
  11. #include <QImage>
  12. #include <QOpenGLContext>
  13. #include <QQuickWindow>
  14. #include <QSize>
  15. #include <QVariant>
  16. #include <set>
  17. #include <unordered_map>
  18. #include "../models/selected_units_model.h"
  19. #include "game/core/component.h"
  20. #include "game/core/event_manager.h"
  21. #include "game/core/world.h"
  22. #include "game/game_config.h"
  23. #include "game/map/environment.h"
  24. #include "game/map/level_loader.h"
  25. #include "game/map/map_catalog.h"
  26. #include "game/map/map_loader.h"
  27. #include "game/map/map_transformer.h"
  28. #include "game/map/skirmish_loader.h"
  29. #include "game/map/terrain_service.h"
  30. #include "game/map/visibility_service.h"
  31. #include "game/map/world_bootstrap.h"
  32. #include "game/systems/ai_system.h"
  33. #include "game/systems/arrow_system.h"
  34. #include "game/systems/building_collision_registry.h"
  35. #include "game/systems/camera_service.h"
  36. #include "game/systems/capture_system.h"
  37. #include "game/systems/cleanup_system.h"
  38. #include "game/systems/combat_system.h"
  39. #include "game/systems/command_service.h"
  40. #include "game/systems/formation_planner.h"
  41. #include "game/systems/game_state_serializer.h"
  42. #include "game/systems/global_stats_registry.h"
  43. #include "game/systems/movement_system.h"
  44. #include "game/systems/nation_registry.h"
  45. #include "game/systems/owner_registry.h"
  46. #include "game/systems/patrol_system.h"
  47. #include "game/systems/picking_service.h"
  48. #include "game/systems/production_service.h"
  49. #include "game/systems/production_system.h"
  50. #include "game/systems/save_load_service.h"
  51. #include "game/systems/selection_system.h"
  52. #include "game/systems/terrain_alignment_system.h"
  53. #include "game/systems/troop_count_registry.h"
  54. #include "game/systems/victory_service.h"
  55. #include "game/units/factory.h"
  56. #include "game/units/troop_config.h"
  57. #include "game/visuals/team_colors.h"
  58. #include "render/geom/arrow.h"
  59. #include "render/geom/patrol_flags.h"
  60. #include "render/gl/bootstrap.h"
  61. #include "render/gl/camera.h"
  62. #include "render/gl/resources.h"
  63. #include "render/ground/biome_renderer.h"
  64. #include "render/ground/bridge_renderer.h"
  65. #include "render/ground/fog_renderer.h"
  66. #include "render/ground/ground_renderer.h"
  67. #include "render/ground/pine_renderer.h"
  68. #include "render/ground/plant_renderer.h"
  69. #include "render/ground/river_renderer.h"
  70. #include "render/ground/riverbank_renderer.h"
  71. #include "render/ground/stone_renderer.h"
  72. #include "render/ground/terrain_renderer.h"
  73. #include "render/scene_renderer.h"
  74. #include <QDir>
  75. #include <QFile>
  76. #include <QJsonArray>
  77. #include <QJsonDocument>
  78. #include <QJsonObject>
  79. #include <QSet>
  80. #include <algorithm>
  81. #include <cmath>
  82. #include <limits>
  83. GameEngine::GameEngine() {
  84. Game::Systems::NationRegistry::instance().initializeDefaults();
  85. Game::Systems::TroopCountRegistry::instance().initialize();
  86. Game::Systems::GlobalStatsRegistry::instance().initialize();
  87. m_world = std::make_unique<Engine::Core::World>();
  88. m_renderer = std::make_unique<Render::GL::Renderer>();
  89. m_camera = std::make_unique<Render::GL::Camera>();
  90. m_ground = std::make_unique<Render::GL::GroundRenderer>();
  91. m_terrain = std::make_unique<Render::GL::TerrainRenderer>();
  92. m_biome = std::make_unique<Render::GL::BiomeRenderer>();
  93. m_river = std::make_unique<Render::GL::RiverRenderer>();
  94. m_riverbank = std::make_unique<Render::GL::RiverbankRenderer>();
  95. m_bridge = std::make_unique<Render::GL::BridgeRenderer>();
  96. m_fog = std::make_unique<Render::GL::FogRenderer>();
  97. m_stone = std::make_unique<Render::GL::StoneRenderer>();
  98. m_plant = std::make_unique<Render::GL::PlantRenderer>();
  99. m_pine = std::make_unique<Render::GL::PineRenderer>();
  100. m_passes = {m_ground.get(), m_terrain.get(), m_river.get(),
  101. m_riverbank.get(), m_bridge.get(), m_biome.get(), m_stone.get(),
  102. m_plant.get(), m_pine.get(), m_fog.get()};
  103. std::unique_ptr<Engine::Core::System> arrowSys =
  104. std::make_unique<Game::Systems::ArrowSystem>();
  105. m_world->addSystem(std::move(arrowSys));
  106. m_world->addSystem(std::make_unique<Game::Systems::MovementSystem>());
  107. m_world->addSystem(std::make_unique<Game::Systems::PatrolSystem>());
  108. m_world->addSystem(std::make_unique<Game::Systems::CombatSystem>());
  109. m_world->addSystem(std::make_unique<Game::Systems::CaptureSystem>());
  110. m_world->addSystem(std::make_unique<Game::Systems::AISystem>());
  111. m_world->addSystem(std::make_unique<Game::Systems::ProductionSystem>());
  112. m_world->addSystem(std::make_unique<Game::Systems::TerrainAlignmentSystem>());
  113. m_world->addSystem(std::make_unique<Game::Systems::CleanupSystem>());
  114. {
  115. std::unique_ptr<Engine::Core::System> selSys =
  116. std::make_unique<Game::Systems::SelectionSystem>();
  117. m_world->addSystem(std::move(selSys));
  118. }
  119. m_selectedUnitsModel = new SelectedUnitsModel(this, this);
  120. m_pickingService = std::make_unique<Game::Systems::PickingService>();
  121. m_victoryService = std::make_unique<Game::Systems::VictoryService>();
  122. m_saveLoadService = std::make_unique<Game::Systems::SaveLoadService>();
  123. m_cameraService = std::make_unique<Game::Systems::CameraService>();
  124. auto *selectionSystem = m_world->getSystem<Game::Systems::SelectionSystem>();
  125. m_selectionController = std::make_unique<Game::Systems::SelectionController>(
  126. m_world.get(), selectionSystem, m_pickingService.get());
  127. m_commandController = std::make_unique<App::Controllers::CommandController>(
  128. m_world.get(), selectionSystem, m_pickingService.get());
  129. m_cursorManager = std::make_unique<CursorManager>();
  130. m_hoverTracker = std::make_unique<HoverTracker>(m_pickingService.get());
  131. m_mapCatalog = std::make_unique<Game::Map::MapCatalog>();
  132. connect(m_mapCatalog.get(), &Game::Map::MapCatalog::mapLoaded, this,
  133. [this](QVariantMap mapData) {
  134. m_availableMaps.append(mapData);
  135. emit availableMapsChanged();
  136. });
  137. connect(m_mapCatalog.get(), &Game::Map::MapCatalog::loadingChanged, this,
  138. [this](bool loading) {
  139. m_mapsLoading = loading;
  140. emit mapsLoadingChanged();
  141. });
  142. connect(m_mapCatalog.get(), &Game::Map::MapCatalog::allMapsLoaded, this,
  143. [this]() { emit availableMapsChanged(); });
  144. connect(m_cursorManager.get(), &CursorManager::modeChanged, this,
  145. &GameEngine::cursorModeChanged);
  146. connect(m_cursorManager.get(), &CursorManager::globalCursorChanged, this,
  147. &GameEngine::globalCursorChanged);
  148. connect(m_selectionController.get(),
  149. &Game::Systems::SelectionController::selectionChanged, this,
  150. &GameEngine::selectedUnitsChanged);
  151. connect(m_selectionController.get(),
  152. &Game::Systems::SelectionController::selectionChanged, this,
  153. &GameEngine::syncSelectionFlags);
  154. connect(m_selectionController.get(),
  155. &Game::Systems::SelectionController::selectionModelRefreshRequested,
  156. this, &GameEngine::selectedUnitsDataChanged);
  157. connect(m_commandController.get(),
  158. &App::Controllers::CommandController::attackTargetSelected, [this]() {
  159. if (auto *selSys =
  160. m_world->getSystem<Game::Systems::SelectionSystem>()) {
  161. const auto &sel = selSys->getSelectedUnits();
  162. if (!sel.empty()) {
  163. auto *cam = m_camera.get();
  164. auto *picking = m_pickingService.get();
  165. if (cam && picking) {
  166. Engine::Core::EntityID targetId = picking->pickUnitFirst(
  167. 0.0f, 0.0f, *m_world, *cam, m_viewport.width,
  168. m_viewport.height, 0);
  169. if (targetId != 0) {
  170. App::Controllers::ActionVFX::spawnAttackArrow(m_world.get(),
  171. targetId);
  172. }
  173. }
  174. }
  175. }
  176. });
  177. connect(m_commandController.get(),
  178. &App::Controllers::CommandController::troopLimitReached, [this]() {
  179. setError("Maximum troop limit reached. Cannot produce more units.");
  180. });
  181. connect(this, SIGNAL(selectedUnitsChanged()), m_selectedUnitsModel,
  182. SLOT(refresh()));
  183. connect(this, SIGNAL(selectedUnitsDataChanged()), m_selectedUnitsModel,
  184. SLOT(refresh()));
  185. emit selectedUnitsChanged();
  186. m_unitDiedSubscription =
  187. Engine::Core::ScopedEventSubscription<Engine::Core::UnitDiedEvent>(
  188. [this](const Engine::Core::UnitDiedEvent &e) {
  189. onUnitDied(e);
  190. if (e.ownerId != m_runtime.localOwnerId) {
  191. int individualsPerUnit =
  192. Game::Units::TroopConfig::instance().getIndividualsPerUnit(
  193. e.unitType);
  194. m_enemyTroopsDefeated += individualsPerUnit;
  195. emit enemyTroopsDefeatedChanged();
  196. }
  197. });
  198. m_unitSpawnedSubscription =
  199. Engine::Core::ScopedEventSubscription<Engine::Core::UnitSpawnedEvent>(
  200. [this](const Engine::Core::UnitSpawnedEvent &e) {
  201. onUnitSpawned(e);
  202. });
  203. }
  204. GameEngine::~GameEngine() = default;
  205. void GameEngine::onMapClicked(qreal sx, qreal sy) {
  206. if (!m_window)
  207. return;
  208. ensureInitialized();
  209. if (m_selectionController && m_camera) {
  210. m_selectionController->onClickSelect(sx, sy, false, m_viewport.width,
  211. m_viewport.height, m_camera.get(),
  212. m_runtime.localOwnerId);
  213. }
  214. }
  215. void GameEngine::onRightClick(qreal sx, qreal sy) {
  216. if (!m_window)
  217. return;
  218. ensureInitialized();
  219. auto *selectionSystem = m_world->getSystem<Game::Systems::SelectionSystem>();
  220. if (!selectionSystem)
  221. return;
  222. if (m_cursorManager->mode() == CursorMode::Patrol ||
  223. m_cursorManager->mode() == CursorMode::Attack) {
  224. setCursorMode(CursorMode::Normal);
  225. return;
  226. }
  227. const auto &sel = selectionSystem->getSelectedUnits();
  228. if (!sel.empty()) {
  229. if (m_selectionController) {
  230. m_selectionController->onRightClickClearSelection();
  231. }
  232. setCursorMode(CursorMode::Normal);
  233. return;
  234. }
  235. }
  236. void GameEngine::onAttackClick(qreal sx, qreal sy) {
  237. if (!m_window)
  238. return;
  239. ensureInitialized();
  240. if (!m_commandController || !m_camera)
  241. return;
  242. auto result = m_commandController->onAttackClick(
  243. sx, sy, m_viewport.width, m_viewport.height, m_camera.get());
  244. auto *selectionSystem = m_world->getSystem<Game::Systems::SelectionSystem>();
  245. if (!selectionSystem || !m_pickingService || !m_camera || !m_world)
  246. return;
  247. const auto &selected = selectionSystem->getSelectedUnits();
  248. if (!selected.empty()) {
  249. Engine::Core::EntityID targetId = m_pickingService->pickUnitFirst(
  250. float(sx), float(sy), *m_world, *m_camera, m_viewport.width,
  251. m_viewport.height, 0);
  252. if (targetId != 0) {
  253. auto *targetEntity = m_world->getEntity(targetId);
  254. if (targetEntity) {
  255. auto *targetUnit =
  256. targetEntity->getComponent<Engine::Core::UnitComponent>();
  257. if (targetUnit && targetUnit->ownerId != m_runtime.localOwnerId) {
  258. App::Controllers::ActionVFX::spawnAttackArrow(m_world.get(),
  259. targetId);
  260. }
  261. }
  262. }
  263. }
  264. if (result.resetCursorToNormal) {
  265. setCursorMode(CursorMode::Normal);
  266. }
  267. }
  268. void GameEngine::resetMovement(Engine::Core::Entity *entity) {
  269. App::Utils::resetMovement(entity);
  270. }
  271. void GameEngine::onStopCommand() {
  272. if (!m_commandController)
  273. return;
  274. ensureInitialized();
  275. auto result = m_commandController->onStopCommand();
  276. if (result.resetCursorToNormal) {
  277. setCursorMode(CursorMode::Normal);
  278. }
  279. }
  280. void GameEngine::onHoldCommand() {
  281. if (!m_commandController)
  282. return;
  283. ensureInitialized();
  284. auto result = m_commandController->onHoldCommand();
  285. if (result.resetCursorToNormal) {
  286. setCursorMode(CursorMode::Normal);
  287. }
  288. }
  289. bool GameEngine::anySelectedInHoldMode() const {
  290. if (!m_commandController)
  291. return false;
  292. return m_commandController->anySelectedInHoldMode();
  293. }
  294. void GameEngine::onPatrolClick(qreal sx, qreal sy) {
  295. if (!m_commandController || !m_camera)
  296. return;
  297. ensureInitialized();
  298. auto result = m_commandController->onPatrolClick(
  299. sx, sy, m_viewport.width, m_viewport.height, m_camera.get());
  300. if (result.resetCursorToNormal) {
  301. setCursorMode(CursorMode::Normal);
  302. }
  303. }
  304. void GameEngine::updateCursor(Qt::CursorShape newCursor) {
  305. if (!m_window)
  306. return;
  307. if (m_runtime.currentCursor != newCursor) {
  308. m_runtime.currentCursor = newCursor;
  309. m_window->setCursor(newCursor);
  310. }
  311. }
  312. void GameEngine::setError(const QString &errorMessage) {
  313. if (m_runtime.lastError != errorMessage) {
  314. m_runtime.lastError = errorMessage;
  315. qCritical() << "GameEngine error:" << errorMessage;
  316. emit lastErrorChanged();
  317. }
  318. }
  319. void GameEngine::setCursorMode(CursorMode mode) {
  320. if (!m_cursorManager)
  321. return;
  322. m_cursorManager->setMode(mode);
  323. m_cursorManager->updateCursorShape(m_window);
  324. }
  325. void GameEngine::setCursorMode(const QString &mode) {
  326. setCursorMode(CursorModeUtils::fromString(mode));
  327. }
  328. QString GameEngine::cursorMode() const {
  329. if (!m_cursorManager)
  330. return "normal";
  331. return m_cursorManager->modeString();
  332. }
  333. qreal GameEngine::globalCursorX() const {
  334. if (!m_cursorManager)
  335. return 0;
  336. return m_cursorManager->globalCursorX(m_window);
  337. }
  338. qreal GameEngine::globalCursorY() const {
  339. if (!m_cursorManager)
  340. return 0;
  341. return m_cursorManager->globalCursorY(m_window);
  342. }
  343. void GameEngine::setHoverAtScreen(qreal sx, qreal sy) {
  344. if (!m_window)
  345. return;
  346. ensureInitialized();
  347. if (!m_hoverTracker || !m_camera || !m_world)
  348. return;
  349. m_cursorManager->updateCursorShape(m_window);
  350. m_hoverTracker->updateHover(float(sx), float(sy), *m_world, *m_camera,
  351. m_viewport.width, m_viewport.height);
  352. }
  353. void GameEngine::onClickSelect(qreal sx, qreal sy, bool additive) {
  354. if (!m_window)
  355. return;
  356. ensureInitialized();
  357. if (m_selectionController && m_camera) {
  358. m_selectionController->onClickSelect(sx, sy, additive, m_viewport.width,
  359. m_viewport.height, m_camera.get(),
  360. m_runtime.localOwnerId);
  361. }
  362. }
  363. void GameEngine::onAreaSelected(qreal x1, qreal y1, qreal x2, qreal y2,
  364. bool additive) {
  365. if (!m_window)
  366. return;
  367. ensureInitialized();
  368. if (m_selectionController && m_camera) {
  369. m_selectionController->onAreaSelected(
  370. x1, y1, x2, y2, additive, m_viewport.width, m_viewport.height,
  371. m_camera.get(), m_runtime.localOwnerId);
  372. }
  373. }
  374. void GameEngine::selectAllTroops() {
  375. ensureInitialized();
  376. if (m_selectionController) {
  377. m_selectionController->selectAllPlayerTroops(m_runtime.localOwnerId);
  378. }
  379. }
  380. void GameEngine::ensureInitialized() {
  381. QString error;
  382. Game::Map::WorldBootstrap::ensureInitialized(
  383. m_runtime.initialized, *m_renderer, *m_camera, m_ground.get(), &error);
  384. if (!error.isEmpty()) {
  385. setError(error);
  386. }
  387. }
  388. int GameEngine::enemyTroopsDefeated() const { return m_enemyTroopsDefeated; }
  389. QVariantMap GameEngine::getPlayerStats(int ownerId) const {
  390. QVariantMap result;
  391. auto &statsRegistry = Game::Systems::GlobalStatsRegistry::instance();
  392. const auto *stats = statsRegistry.getStats(ownerId);
  393. if (stats) {
  394. result["troopsRecruited"] = stats->troopsRecruited;
  395. result["enemiesKilled"] = stats->enemiesKilled;
  396. result["barracksOwned"] = stats->barracksOwned;
  397. result["playTimeSec"] = stats->playTimeSec;
  398. result["gameEnded"] = stats->gameEnded;
  399. } else {
  400. result["troopsRecruited"] = 0;
  401. result["enemiesKilled"] = 0;
  402. result["barracksOwned"] = 0;
  403. result["playTimeSec"] = 0.0f;
  404. result["gameEnded"] = false;
  405. }
  406. return result;
  407. }
  408. void GameEngine::update(float dt) {
  409. if (m_runtime.loading) {
  410. return;
  411. }
  412. if (m_runtime.paused) {
  413. dt = 0.0f;
  414. } else {
  415. dt *= m_runtime.timeScale;
  416. }
  417. if (m_renderer) {
  418. m_renderer->updateAnimationTime(dt);
  419. }
  420. if (m_camera) {
  421. m_camera->update(dt);
  422. }
  423. if (m_world) {
  424. m_world->update(dt);
  425. auto &visibilityService = Game::Map::VisibilityService::instance();
  426. if (visibilityService.isInitialized()) {
  427. m_runtime.visibilityUpdateAccumulator += dt;
  428. const float visibilityUpdateInterval =
  429. Game::GameConfig::instance().gameplay().visibilityUpdateInterval;
  430. if (m_runtime.visibilityUpdateAccumulator >= visibilityUpdateInterval) {
  431. m_runtime.visibilityUpdateAccumulator = 0.0f;
  432. visibilityService.update(*m_world, m_runtime.localOwnerId);
  433. }
  434. const auto newVersion = visibilityService.version();
  435. if (newVersion != m_runtime.visibilityVersion) {
  436. if (m_fog) {
  437. m_fog->updateMask(visibilityService.getWidth(),
  438. visibilityService.getHeight(),
  439. visibilityService.getTileSize(),
  440. visibilityService.snapshotCells());
  441. }
  442. m_runtime.visibilityVersion = newVersion;
  443. }
  444. }
  445. }
  446. if (m_victoryService && m_world) {
  447. m_victoryService->update(*m_world, dt);
  448. }
  449. if (m_followSelectionEnabled && m_camera && m_world && m_cameraService) {
  450. m_cameraService->updateFollow(*m_camera, *m_world,
  451. m_followSelectionEnabled);
  452. }
  453. if (m_selectedUnitsModel) {
  454. auto *selectionSystem =
  455. m_world->getSystem<Game::Systems::SelectionSystem>();
  456. if (selectionSystem && !selectionSystem->getSelectedUnits().empty()) {
  457. m_runtime.selectionRefreshCounter++;
  458. if (m_runtime.selectionRefreshCounter >= 15) {
  459. m_runtime.selectionRefreshCounter = 0;
  460. emit selectedUnitsDataChanged();
  461. }
  462. }
  463. }
  464. }
  465. void GameEngine::render(int pixelWidth, int pixelHeight) {
  466. if (!m_renderer || !m_world || !m_runtime.initialized || m_runtime.loading)
  467. return;
  468. if (pixelWidth > 0 && pixelHeight > 0) {
  469. m_viewport.width = pixelWidth;
  470. m_viewport.height = pixelHeight;
  471. m_renderer->setViewport(pixelWidth, pixelHeight);
  472. }
  473. if (auto *selectionSystem =
  474. m_world->getSystem<Game::Systems::SelectionSystem>()) {
  475. const auto &sel = selectionSystem->getSelectedUnits();
  476. std::vector<unsigned int> ids(sel.begin(), sel.end());
  477. m_renderer->setSelectedEntities(ids);
  478. }
  479. m_renderer->beginFrame();
  480. if (auto *res = m_renderer->resources()) {
  481. for (auto *pass : m_passes) {
  482. if (pass)
  483. pass->submit(*m_renderer, res);
  484. }
  485. }
  486. if (m_renderer && m_hoverTracker)
  487. m_renderer->setHoveredEntityId(m_hoverTracker->getLastHoveredEntity());
  488. if (m_renderer)
  489. m_renderer->setLocalOwnerId(m_runtime.localOwnerId);
  490. m_renderer->renderWorld(m_world.get());
  491. if (auto *arrowSystem = m_world->getSystem<Game::Systems::ArrowSystem>()) {
  492. if (auto *res = m_renderer->resources())
  493. Render::GL::renderArrows(m_renderer.get(), res, *arrowSystem);
  494. }
  495. if (auto *res = m_renderer->resources()) {
  496. std::optional<QVector3D> previewWaypoint;
  497. if (m_commandController && m_commandController->hasPatrolFirstWaypoint()) {
  498. previewWaypoint = m_commandController->getPatrolFirstWaypoint();
  499. }
  500. Render::GL::renderPatrolFlags(m_renderer.get(), res, *m_world,
  501. previewWaypoint);
  502. }
  503. m_renderer->endFrame();
  504. qreal currentX = globalCursorX();
  505. qreal currentY = globalCursorY();
  506. if (currentX != m_runtime.lastCursorX || currentY != m_runtime.lastCursorY) {
  507. m_runtime.lastCursorX = currentX;
  508. m_runtime.lastCursorY = currentY;
  509. emit globalCursorChanged();
  510. }
  511. }
  512. bool GameEngine::screenToGround(const QPointF &screenPt, QVector3D &outWorld) {
  513. return App::Utils::screenToGround(m_pickingService.get(), m_camera.get(),
  514. m_window, m_viewport.width,
  515. m_viewport.height, screenPt, outWorld);
  516. }
  517. bool GameEngine::worldToScreen(const QVector3D &world,
  518. QPointF &outScreen) const {
  519. return App::Utils::worldToScreen(m_pickingService.get(), m_camera.get(),
  520. m_window, m_viewport.width,
  521. m_viewport.height, world, outScreen);
  522. }
  523. void GameEngine::syncSelectionFlags() {
  524. auto *selectionSystem = m_world->getSystem<Game::Systems::SelectionSystem>();
  525. if (!m_world || !selectionSystem)
  526. return;
  527. App::Utils::sanitizeSelection(m_world.get(), selectionSystem);
  528. if (selectionSystem->getSelectedUnits().empty()) {
  529. if (m_cursorManager && m_cursorManager->mode() != CursorMode::Normal) {
  530. setCursorMode(CursorMode::Normal);
  531. }
  532. }
  533. }
  534. void GameEngine::cameraMove(float dx, float dz) {
  535. ensureInitialized();
  536. if (!m_camera || !m_cameraService)
  537. return;
  538. m_cameraService->move(*m_camera, dx, dz);
  539. }
  540. void GameEngine::cameraElevate(float dy) {
  541. ensureInitialized();
  542. if (!m_camera || !m_cameraService)
  543. return;
  544. m_cameraService->elevate(*m_camera, dy);
  545. }
  546. void GameEngine::resetCamera() {
  547. ensureInitialized();
  548. if (!m_camera || !m_world || !m_cameraService)
  549. return;
  550. m_cameraService->resetCamera(*m_camera, *m_world, m_runtime.localOwnerId,
  551. m_level.playerUnitId);
  552. }
  553. void GameEngine::cameraZoom(float delta) {
  554. ensureInitialized();
  555. if (!m_camera || !m_cameraService)
  556. return;
  557. m_cameraService->zoom(*m_camera, delta);
  558. }
  559. float GameEngine::cameraDistance() const {
  560. if (!m_camera || !m_cameraService)
  561. return 0.0f;
  562. return m_cameraService->getDistance(*m_camera);
  563. }
  564. void GameEngine::cameraYaw(float degrees) {
  565. ensureInitialized();
  566. if (!m_camera || !m_cameraService)
  567. return;
  568. m_cameraService->yaw(*m_camera, degrees);
  569. }
  570. void GameEngine::cameraOrbit(float yawDeg, float pitchDeg) {
  571. ensureInitialized();
  572. if (!m_camera || !m_cameraService)
  573. return;
  574. if (!std::isfinite(yawDeg) || !std::isfinite(pitchDeg)) {
  575. qWarning() << "GameEngine::cameraOrbit received invalid input, ignoring:"
  576. << yawDeg << pitchDeg;
  577. return;
  578. }
  579. m_cameraService->orbit(*m_camera, yawDeg, pitchDeg);
  580. }
  581. void GameEngine::cameraOrbitDirection(int direction, bool shift) {
  582. if (!m_camera || !m_cameraService)
  583. return;
  584. m_cameraService->orbitDirection(*m_camera, direction, shift);
  585. }
  586. void GameEngine::cameraFollowSelection(bool enable) {
  587. ensureInitialized();
  588. m_followSelectionEnabled = enable;
  589. if (!m_camera || !m_world || !m_cameraService)
  590. return;
  591. m_cameraService->followSelection(*m_camera, *m_world, enable);
  592. }
  593. void GameEngine::cameraSetFollowLerp(float alpha) {
  594. ensureInitialized();
  595. if (!m_camera || !m_cameraService)
  596. return;
  597. m_cameraService->setFollowLerp(*m_camera, alpha);
  598. }
  599. QObject *GameEngine::selectedUnitsModel() { return m_selectedUnitsModel; }
  600. bool GameEngine::hasUnitsSelected() const {
  601. if (!m_selectionController)
  602. return false;
  603. return m_selectionController->hasUnitsSelected();
  604. }
  605. int GameEngine::playerTroopCount() const {
  606. return m_entityCache.playerTroopCount;
  607. }
  608. bool GameEngine::hasSelectedType(const QString &type) const {
  609. if (!m_selectionController)
  610. return false;
  611. return m_selectionController->hasSelectedType(type);
  612. }
  613. void GameEngine::recruitNearSelected(const QString &unitType) {
  614. ensureInitialized();
  615. if (!m_commandController)
  616. return;
  617. m_commandController->recruitNearSelected(unitType, m_runtime.localOwnerId);
  618. }
  619. QVariantMap GameEngine::getSelectedProductionState() const {
  620. QVariantMap m;
  621. m["hasBarracks"] = false;
  622. m["inProgress"] = false;
  623. m["timeRemaining"] = 0.0;
  624. m["buildTime"] = 0.0;
  625. m["producedCount"] = 0;
  626. m["maxUnits"] = 0;
  627. m["villagerCost"] = 1;
  628. if (!m_world)
  629. return m;
  630. auto *selectionSystem = m_world->getSystem<Game::Systems::SelectionSystem>();
  631. if (!selectionSystem)
  632. return m;
  633. Game::Systems::ProductionState st;
  634. Game::Systems::ProductionService::getSelectedBarracksState(
  635. *m_world, selectionSystem->getSelectedUnits(), m_runtime.localOwnerId,
  636. st);
  637. m["hasBarracks"] = st.hasBarracks;
  638. m["inProgress"] = st.inProgress;
  639. m["productType"] = QString::fromStdString(st.productType);
  640. m["timeRemaining"] = st.timeRemaining;
  641. m["buildTime"] = st.buildTime;
  642. m["producedCount"] = st.producedCount;
  643. m["maxUnits"] = st.maxUnits;
  644. m["villagerCost"] = st.villagerCost;
  645. m["queueSize"] = st.queueSize;
  646. QVariantList queueList;
  647. for (const auto &unitType : st.productionQueue) {
  648. queueList.append(QString::fromStdString(unitType));
  649. }
  650. m["productionQueue"] = queueList;
  651. return m;
  652. }
  653. QString GameEngine::getSelectedUnitsCommandMode() const {
  654. if (!m_world)
  655. return "normal";
  656. auto *selectionSystem = m_world->getSystem<Game::Systems::SelectionSystem>();
  657. if (!selectionSystem)
  658. return "normal";
  659. const auto &sel = selectionSystem->getSelectedUnits();
  660. if (sel.empty())
  661. return "normal";
  662. int attackingCount = 0;
  663. int patrollingCount = 0;
  664. int totalUnits = 0;
  665. for (auto id : sel) {
  666. auto *e = m_world->getEntity(id);
  667. if (!e)
  668. continue;
  669. auto *u = e->getComponent<Engine::Core::UnitComponent>();
  670. if (!u)
  671. continue;
  672. if (u->unitType == "barracks")
  673. continue;
  674. totalUnits++;
  675. if (e->getComponent<Engine::Core::AttackTargetComponent>())
  676. attackingCount++;
  677. auto *patrol = e->getComponent<Engine::Core::PatrolComponent>();
  678. if (patrol && patrol->patrolling)
  679. patrollingCount++;
  680. }
  681. if (totalUnits == 0)
  682. return "normal";
  683. if (patrollingCount == totalUnits)
  684. return "patrol";
  685. if (attackingCount == totalUnits)
  686. return "attack";
  687. return "normal";
  688. }
  689. void GameEngine::setRallyAtScreen(qreal sx, qreal sy) {
  690. ensureInitialized();
  691. if (!m_commandController || !m_camera)
  692. return;
  693. m_commandController->setRallyAtScreen(sx, sy, m_viewport.width,
  694. m_viewport.height, m_camera.get(),
  695. m_runtime.localOwnerId);
  696. }
  697. void GameEngine::startLoadingMaps() {
  698. m_availableMaps.clear();
  699. if (m_mapCatalog) {
  700. m_mapCatalog->loadMapsAsync();
  701. }
  702. }
  703. QVariantList GameEngine::availableMaps() const { return m_availableMaps; }
  704. void GameEngine::startSkirmish(const QString &mapPath,
  705. const QVariantList &playerConfigs) {
  706. clearError();
  707. m_level.mapPath = mapPath;
  708. m_level.mapName = mapPath;
  709. if (!m_runtime.victoryState.isEmpty()) {
  710. m_runtime.victoryState = "";
  711. emit victoryStateChanged();
  712. }
  713. m_enemyTroopsDefeated = 0;
  714. if (!m_runtime.initialized) {
  715. ensureInitialized();
  716. return;
  717. }
  718. if (m_world && m_renderer && m_camera) {
  719. m_runtime.loading = true;
  720. if (m_hoverTracker) {
  721. m_hoverTracker->updateHover(-1, -1, *m_world, *m_camera, 0, 0);
  722. }
  723. m_entityCache.reset();
  724. Game::Map::SkirmishLoader loader(*m_world, *m_renderer, *m_camera);
  725. loader.setGroundRenderer(m_ground.get());
  726. loader.setTerrainRenderer(m_terrain.get());
  727. loader.setBiomeRenderer(m_biome.get());
  728. loader.setRiverRenderer(m_river.get());
  729. loader.setBridgeRenderer(m_bridge.get());
  730. loader.setFogRenderer(m_fog.get());
  731. loader.setStoneRenderer(m_stone.get());
  732. loader.setPlantRenderer(m_plant.get());
  733. loader.setPineRenderer(m_pine.get());
  734. loader.setOnOwnersUpdated([this]() { emit ownerInfoChanged(); });
  735. loader.setOnVisibilityMaskReady([this]() {
  736. m_runtime.visibilityVersion =
  737. Game::Map::VisibilityService::instance().version();
  738. m_runtime.visibilityUpdateAccumulator = 0.0f;
  739. });
  740. int updatedPlayerId = m_selectedPlayerId;
  741. auto result = loader.start(mapPath, playerConfigs, m_selectedPlayerId,
  742. updatedPlayerId);
  743. if (updatedPlayerId != m_selectedPlayerId) {
  744. m_selectedPlayerId = updatedPlayerId;
  745. emit selectedPlayerIdChanged();
  746. }
  747. if (!result.ok && !result.errorMessage.isEmpty()) {
  748. setError(result.errorMessage);
  749. }
  750. m_runtime.localOwnerId = updatedPlayerId;
  751. m_level.mapName = result.mapName;
  752. m_level.playerUnitId = result.playerUnitId;
  753. m_level.camFov = result.camFov;
  754. m_level.camNear = result.camNear;
  755. m_level.camFar = result.camFar;
  756. m_level.maxTroopsPerPlayer = result.maxTroopsPerPlayer;
  757. Game::GameConfig::instance().setMaxTroopsPerPlayer(
  758. result.maxTroopsPerPlayer);
  759. if (m_victoryService) {
  760. m_victoryService->configure(result.victoryConfig, m_runtime.localOwnerId);
  761. m_victoryService->setVictoryCallback([this](const QString &state) {
  762. if (m_runtime.victoryState != state) {
  763. m_runtime.victoryState = state;
  764. emit victoryStateChanged();
  765. }
  766. });
  767. }
  768. if (result.hasFocusPosition && m_camera) {
  769. const auto &camConfig = Game::GameConfig::instance().camera();
  770. m_camera->setRTSView(result.focusPosition, camConfig.defaultDistance,
  771. camConfig.defaultPitch, camConfig.defaultYaw);
  772. }
  773. m_runtime.loading = false;
  774. if (auto *aiSystem = m_world->getSystem<Game::Systems::AISystem>()) {
  775. aiSystem->reinitialize();
  776. }
  777. rebuildEntityCache();
  778. auto &troops = Game::Systems::TroopCountRegistry::instance();
  779. troops.rebuildFromWorld(*m_world);
  780. auto &statsRegistry = Game::Systems::GlobalStatsRegistry::instance();
  781. statsRegistry.rebuildFromWorld(*m_world);
  782. auto &ownerRegistry = Game::Systems::OwnerRegistry::instance();
  783. const auto &allOwners = ownerRegistry.getAllOwners();
  784. for (const auto &owner : allOwners) {
  785. if (owner.type == Game::Systems::OwnerType::Player ||
  786. owner.type == Game::Systems::OwnerType::AI) {
  787. statsRegistry.markGameStart(owner.ownerId);
  788. }
  789. }
  790. emit ownerInfoChanged();
  791. }
  792. }
  793. void GameEngine::openSettings() {
  794. if (m_saveLoadService) {
  795. m_saveLoadService->openSettings();
  796. }
  797. }
  798. void GameEngine::loadSave() { loadFromSlot("savegame"); }
  799. void GameEngine::saveGame(const QString &filename) {
  800. saveToSlot(filename, filename);
  801. }
  802. void GameEngine::saveGameToSlot(const QString &slotName) {
  803. saveToSlot(slotName, slotName);
  804. }
  805. void GameEngine::loadGameFromSlot(const QString &slotName) {
  806. loadFromSlot(slotName);
  807. }
  808. bool GameEngine::loadFromSlot(const QString &slot) {
  809. if (!m_saveLoadService || !m_world) {
  810. setError("Load: not initialized");
  811. return false;
  812. }
  813. m_runtime.loading = true;
  814. if (!m_saveLoadService->loadGameFromSlot(*m_world, slot)) {
  815. setError(m_saveLoadService->getLastError());
  816. m_runtime.loading = false;
  817. return false;
  818. }
  819. const QJsonObject meta = m_saveLoadService->getLastMetadata();
  820. Game::Systems::GameStateSerializer::restoreLevelFromMetadata(meta, m_level);
  821. Game::Systems::GameStateSerializer::restoreCameraFromMetadata(
  822. meta, m_camera.get(), m_viewport.width, m_viewport.height);
  823. Game::Systems::RuntimeSnapshot runtimeSnap = toRuntimeSnapshot();
  824. Game::Systems::GameStateSerializer::restoreRuntimeFromMetadata(meta,
  825. runtimeSnap);
  826. applyRuntimeSnapshot(runtimeSnap);
  827. restoreEnvironmentFromMetadata(meta);
  828. auto unitReg = std::make_shared<Game::Units::UnitFactoryRegistry>();
  829. Game::Units::registerBuiltInUnits(*unitReg);
  830. Game::Map::MapTransformer::setFactoryRegistry(unitReg);
  831. qInfo() << "Factory registry reinitialized after loading saved game";
  832. rebuildRegistriesAfterLoad();
  833. rebuildEntityCache();
  834. if (auto *aiSystem = m_world->getSystem<Game::Systems::AISystem>()) {
  835. qInfo() << "Reinitializing AI system after loading saved game";
  836. aiSystem->reinitialize();
  837. }
  838. if (m_victoryService) {
  839. m_victoryService->configure(Game::Map::VictoryConfig(),
  840. m_runtime.localOwnerId);
  841. }
  842. m_runtime.loading = false;
  843. qInfo() << "Game load complete, victory/defeat checks re-enabled";
  844. emit selectedUnitsChanged();
  845. emit ownerInfoChanged();
  846. return true;
  847. }
  848. bool GameEngine::saveToSlot(const QString &slot, const QString &title) {
  849. if (!m_saveLoadService || !m_world) {
  850. setError("Save: not initialized");
  851. return false;
  852. }
  853. Game::Systems::RuntimeSnapshot runtimeSnap = toRuntimeSnapshot();
  854. QJsonObject meta = Game::Systems::GameStateSerializer::buildMetadata(
  855. *m_world, m_camera.get(), m_level, runtimeSnap);
  856. meta["title"] = title;
  857. const QByteArray screenshot = captureScreenshot();
  858. if (!m_saveLoadService->saveGameToSlot(*m_world, slot, title, m_level.mapName,
  859. meta, screenshot)) {
  860. setError(m_saveLoadService->getLastError());
  861. return false;
  862. }
  863. emit saveSlotsChanged();
  864. return true;
  865. }
  866. QVariantList GameEngine::getSaveSlots() const {
  867. if (!m_saveLoadService) {
  868. qWarning() << "Cannot get save slots: service not initialized";
  869. return QVariantList();
  870. }
  871. return m_saveLoadService->getSaveSlots();
  872. }
  873. void GameEngine::refreshSaveSlots() { emit saveSlotsChanged(); }
  874. bool GameEngine::deleteSaveSlot(const QString &slotName) {
  875. if (!m_saveLoadService) {
  876. qWarning() << "Cannot delete save slot: service not initialized";
  877. return false;
  878. }
  879. bool success = m_saveLoadService->deleteSaveSlot(slotName);
  880. if (!success) {
  881. QString error = m_saveLoadService->getLastError();
  882. qWarning() << "Failed to delete save slot:" << error;
  883. setError(error);
  884. } else {
  885. emit saveSlotsChanged();
  886. }
  887. return success;
  888. }
  889. void GameEngine::exitGame() {
  890. if (m_saveLoadService) {
  891. m_saveLoadService->exitGame();
  892. }
  893. }
  894. QVariantList GameEngine::getOwnerInfo() const {
  895. QVariantList result;
  896. const auto &ownerRegistry = Game::Systems::OwnerRegistry::instance();
  897. const auto &owners = ownerRegistry.getAllOwners();
  898. for (const auto &owner : owners) {
  899. QVariantMap ownerMap;
  900. ownerMap["id"] = owner.ownerId;
  901. ownerMap["name"] = QString::fromStdString(owner.name);
  902. ownerMap["teamId"] = owner.teamId;
  903. QString typeStr;
  904. switch (owner.type) {
  905. case Game::Systems::OwnerType::Player:
  906. typeStr = "Player";
  907. break;
  908. case Game::Systems::OwnerType::AI:
  909. typeStr = "AI";
  910. break;
  911. case Game::Systems::OwnerType::Neutral:
  912. typeStr = "Neutral";
  913. break;
  914. }
  915. ownerMap["type"] = typeStr;
  916. ownerMap["isLocal"] = (owner.ownerId == m_runtime.localOwnerId);
  917. result.append(ownerMap);
  918. }
  919. return result;
  920. }
  921. void GameEngine::getSelectedUnitIds(
  922. std::vector<Engine::Core::EntityID> &out) const {
  923. out.clear();
  924. if (!m_selectionController)
  925. return;
  926. m_selectionController->getSelectedUnitIds(out);
  927. }
  928. bool GameEngine::getUnitInfo(Engine::Core::EntityID id, QString &name,
  929. int &health, int &maxHealth, bool &isBuilding,
  930. bool &alive) const {
  931. if (!m_world)
  932. return false;
  933. auto *e = m_world->getEntity(id);
  934. if (!e)
  935. return false;
  936. isBuilding = e->hasComponent<Engine::Core::BuildingComponent>();
  937. if (auto *u = e->getComponent<Engine::Core::UnitComponent>()) {
  938. name = QString::fromStdString(u->unitType);
  939. health = u->health;
  940. maxHealth = u->maxHealth;
  941. alive = (u->health > 0);
  942. return true;
  943. }
  944. name = QStringLiteral("Entity");
  945. health = maxHealth = 0;
  946. alive = true;
  947. return true;
  948. }
  949. void GameEngine::onUnitSpawned(const Engine::Core::UnitSpawnedEvent &event) {
  950. auto &owners = Game::Systems::OwnerRegistry::instance();
  951. if (event.ownerId == m_runtime.localOwnerId) {
  952. if (event.unitType == "barracks") {
  953. m_entityCache.playerBarracksAlive = true;
  954. } else {
  955. int individualsPerUnit =
  956. Game::Units::TroopConfig::instance().getIndividualsPerUnit(
  957. event.unitType);
  958. m_entityCache.playerTroopCount += individualsPerUnit;
  959. }
  960. } else if (owners.isAI(event.ownerId)) {
  961. if (event.unitType == "barracks") {
  962. m_entityCache.enemyBarracksCount++;
  963. m_entityCache.enemyBarracksAlive = true;
  964. }
  965. }
  966. auto emitIfChanged = [&] {
  967. if (m_entityCache.playerTroopCount != m_runtime.lastTroopCount) {
  968. m_runtime.lastTroopCount = m_entityCache.playerTroopCount;
  969. emit troopCountChanged();
  970. }
  971. };
  972. emitIfChanged();
  973. }
  974. void GameEngine::onUnitDied(const Engine::Core::UnitDiedEvent &event) {
  975. auto &owners = Game::Systems::OwnerRegistry::instance();
  976. if (event.ownerId == m_runtime.localOwnerId) {
  977. if (event.unitType == "barracks") {
  978. m_entityCache.playerBarracksAlive = false;
  979. } else {
  980. int individualsPerUnit =
  981. Game::Units::TroopConfig::instance().getIndividualsPerUnit(
  982. event.unitType);
  983. m_entityCache.playerTroopCount -= individualsPerUnit;
  984. m_entityCache.playerTroopCount =
  985. std::max(0, m_entityCache.playerTroopCount);
  986. }
  987. } else if (owners.isAI(event.ownerId)) {
  988. if (event.unitType == "barracks") {
  989. m_entityCache.enemyBarracksCount--;
  990. m_entityCache.enemyBarracksCount =
  991. std::max(0, m_entityCache.enemyBarracksCount);
  992. m_entityCache.enemyBarracksAlive = (m_entityCache.enemyBarracksCount > 0);
  993. }
  994. }
  995. syncSelectionFlags();
  996. auto emitIfChanged = [&] {
  997. if (m_entityCache.playerTroopCount != m_runtime.lastTroopCount) {
  998. m_runtime.lastTroopCount = m_entityCache.playerTroopCount;
  999. emit troopCountChanged();
  1000. }
  1001. };
  1002. emitIfChanged();
  1003. }
  1004. void GameEngine::rebuildEntityCache() {
  1005. if (!m_world) {
  1006. m_entityCache.reset();
  1007. return;
  1008. }
  1009. m_entityCache.reset();
  1010. auto &owners = Game::Systems::OwnerRegistry::instance();
  1011. auto entities = m_world->getEntitiesWith<Engine::Core::UnitComponent>();
  1012. for (auto *e : entities) {
  1013. auto *unit = e->getComponent<Engine::Core::UnitComponent>();
  1014. if (!unit || unit->health <= 0)
  1015. continue;
  1016. if (unit->ownerId == m_runtime.localOwnerId) {
  1017. if (unit->unitType == "barracks") {
  1018. m_entityCache.playerBarracksAlive = true;
  1019. } else {
  1020. int individualsPerUnit =
  1021. Game::Units::TroopConfig::instance().getIndividualsPerUnit(
  1022. unit->unitType);
  1023. m_entityCache.playerTroopCount += individualsPerUnit;
  1024. }
  1025. } else if (owners.isAI(unit->ownerId)) {
  1026. if (unit->unitType == "barracks") {
  1027. m_entityCache.enemyBarracksCount++;
  1028. m_entityCache.enemyBarracksAlive = true;
  1029. }
  1030. }
  1031. }
  1032. auto emitIfChanged = [&] {
  1033. if (m_entityCache.playerTroopCount != m_runtime.lastTroopCount) {
  1034. m_runtime.lastTroopCount = m_entityCache.playerTroopCount;
  1035. emit troopCountChanged();
  1036. }
  1037. };
  1038. emitIfChanged();
  1039. }
  1040. void GameEngine::rebuildRegistriesAfterLoad() {
  1041. if (!m_world)
  1042. return;
  1043. auto &ownerRegistry = Game::Systems::OwnerRegistry::instance();
  1044. m_runtime.localOwnerId = ownerRegistry.getLocalPlayerId();
  1045. auto &troops = Game::Systems::TroopCountRegistry::instance();
  1046. troops.rebuildFromWorld(*m_world);
  1047. auto &statsRegistry = Game::Systems::GlobalStatsRegistry::instance();
  1048. statsRegistry.rebuildFromWorld(*m_world);
  1049. const auto &allOwners = ownerRegistry.getAllOwners();
  1050. for (const auto &owner : allOwners) {
  1051. if (owner.type == Game::Systems::OwnerType::Player ||
  1052. owner.type == Game::Systems::OwnerType::AI) {
  1053. statsRegistry.markGameStart(owner.ownerId);
  1054. }
  1055. }
  1056. rebuildBuildingCollisions();
  1057. m_level.playerUnitId = 0;
  1058. auto units = m_world->getEntitiesWith<Engine::Core::UnitComponent>();
  1059. for (auto *entity : units) {
  1060. auto *unit = entity->getComponent<Engine::Core::UnitComponent>();
  1061. if (!unit)
  1062. continue;
  1063. if (unit->ownerId == m_runtime.localOwnerId) {
  1064. m_level.playerUnitId = entity->getId();
  1065. break;
  1066. }
  1067. }
  1068. if (m_selectedPlayerId != m_runtime.localOwnerId) {
  1069. m_selectedPlayerId = m_runtime.localOwnerId;
  1070. emit selectedPlayerIdChanged();
  1071. }
  1072. }
  1073. void GameEngine::rebuildBuildingCollisions() {
  1074. auto &registry = Game::Systems::BuildingCollisionRegistry::instance();
  1075. registry.clear();
  1076. if (!m_world)
  1077. return;
  1078. auto buildings = m_world->getEntitiesWith<Engine::Core::BuildingComponent>();
  1079. for (auto *entity : buildings) {
  1080. auto *transform = entity->getComponent<Engine::Core::TransformComponent>();
  1081. auto *unit = entity->getComponent<Engine::Core::UnitComponent>();
  1082. if (!transform || !unit)
  1083. continue;
  1084. registry.registerBuilding(entity->getId(), unit->unitType,
  1085. transform->position.x, transform->position.z,
  1086. unit->ownerId);
  1087. }
  1088. }
  1089. Game::Systems::RuntimeSnapshot GameEngine::toRuntimeSnapshot() const {
  1090. Game::Systems::RuntimeSnapshot snap;
  1091. snap.paused = m_runtime.paused;
  1092. snap.timeScale = m_runtime.timeScale;
  1093. snap.localOwnerId = m_runtime.localOwnerId;
  1094. snap.victoryState = m_runtime.victoryState;
  1095. snap.cursorMode = CursorModeUtils::toInt(m_runtime.cursorMode);
  1096. snap.selectedPlayerId = m_selectedPlayerId;
  1097. snap.followSelection = m_followSelectionEnabled;
  1098. return snap;
  1099. }
  1100. void GameEngine::applyRuntimeSnapshot(
  1101. const Game::Systems::RuntimeSnapshot &snapshot) {
  1102. m_runtime.localOwnerId = snapshot.localOwnerId;
  1103. setPaused(snapshot.paused);
  1104. setGameSpeed(snapshot.timeScale);
  1105. if (snapshot.victoryState != m_runtime.victoryState) {
  1106. m_runtime.victoryState = snapshot.victoryState;
  1107. emit victoryStateChanged();
  1108. }
  1109. setCursorMode(CursorModeUtils::fromInt(snapshot.cursorMode));
  1110. if (snapshot.selectedPlayerId != m_selectedPlayerId) {
  1111. m_selectedPlayerId = snapshot.selectedPlayerId;
  1112. emit selectedPlayerIdChanged();
  1113. }
  1114. if (snapshot.followSelection != m_followSelectionEnabled) {
  1115. m_followSelectionEnabled = snapshot.followSelection;
  1116. if (m_camera && m_cameraService && m_world) {
  1117. m_cameraService->followSelection(*m_camera, *m_world,
  1118. m_followSelectionEnabled);
  1119. }
  1120. }
  1121. }
  1122. QByteArray GameEngine::captureScreenshot() const {
  1123. if (!m_window) {
  1124. return {};
  1125. }
  1126. QImage image = m_window->grabWindow();
  1127. if (image.isNull()) {
  1128. return {};
  1129. }
  1130. const QSize targetSize(320, 180);
  1131. QImage scaled =
  1132. image.scaled(targetSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
  1133. QByteArray buffer;
  1134. QBuffer qBuffer(&buffer);
  1135. if (!qBuffer.open(QIODevice::WriteOnly)) {
  1136. return {};
  1137. }
  1138. if (!scaled.save(&qBuffer, "PNG")) {
  1139. return {};
  1140. }
  1141. return buffer;
  1142. }
  1143. void GameEngine::restoreEnvironmentFromMetadata(const QJsonObject &metadata) {
  1144. if (!m_world)
  1145. return;
  1146. const auto fallbackGridWidth = metadata.value("gridWidth").toInt(50);
  1147. const auto fallbackGridHeight = metadata.value("gridHeight").toInt(50);
  1148. const float fallbackTileSize =
  1149. static_cast<float>(metadata.value("tileSize").toDouble(1.0));
  1150. Game::Map::MapDefinition def;
  1151. QString mapError;
  1152. bool loadedDefinition = false;
  1153. const QString &mapPath = m_level.mapPath;
  1154. if (!mapPath.isEmpty()) {
  1155. loadedDefinition =
  1156. Game::Map::MapLoader::loadFromJsonFile(mapPath, def, &mapError);
  1157. if (!loadedDefinition) {
  1158. qWarning() << "GameEngine: Failed to load map definition from" << mapPath
  1159. << "during save load:" << mapError;
  1160. }
  1161. }
  1162. auto &terrainService = Game::Map::TerrainService::instance();
  1163. if (loadedDefinition) {
  1164. terrainService.initialize(def);
  1165. if (!def.name.isEmpty()) {
  1166. m_level.mapName = def.name;
  1167. }
  1168. m_level.camFov = def.camera.fovY;
  1169. m_level.camNear = def.camera.nearPlane;
  1170. m_level.camFar = def.camera.farPlane;
  1171. if (m_renderer && m_camera) {
  1172. Game::Map::Environment::apply(def, *m_renderer, *m_camera);
  1173. }
  1174. if (m_ground) {
  1175. m_ground->configure(def.grid.tileSize, def.grid.width, def.grid.height);
  1176. if (terrainService.isInitialized()) {
  1177. m_ground->setBiome(terrainService.biomeSettings());
  1178. }
  1179. }
  1180. if (auto *heightMap = terrainService.getHeightMap()) {
  1181. if (m_terrain) {
  1182. m_terrain->configure(*heightMap, terrainService.biomeSettings());
  1183. }
  1184. if (m_river) {
  1185. m_river->configure(heightMap->getRiverSegments(),
  1186. heightMap->getTileSize());
  1187. }
  1188. if (m_riverbank) {
  1189. m_riverbank->configure(heightMap->getRiverSegments(),
  1190. heightMap->getTileSize());
  1191. }
  1192. if (m_biome) {
  1193. m_biome->configure(*heightMap, terrainService.biomeSettings());
  1194. m_biome->refreshGrass();
  1195. }
  1196. if (m_stone) {
  1197. m_stone->configure(*heightMap, terrainService.biomeSettings());
  1198. }
  1199. }
  1200. Game::Systems::CommandService::initialize(def.grid.width, def.grid.height);
  1201. auto &visibilityService = Game::Map::VisibilityService::instance();
  1202. visibilityService.initialize(def.grid.width, def.grid.height,
  1203. def.grid.tileSize);
  1204. visibilityService.computeImmediate(*m_world, m_runtime.localOwnerId);
  1205. if (m_fog && visibilityService.isInitialized()) {
  1206. m_fog->updateMask(
  1207. visibilityService.getWidth(), visibilityService.getHeight(),
  1208. visibilityService.getTileSize(), visibilityService.snapshotCells());
  1209. }
  1210. m_runtime.visibilityVersion = visibilityService.version();
  1211. m_runtime.visibilityUpdateAccumulator = 0.0f;
  1212. } else {
  1213. if (m_renderer && m_camera) {
  1214. Game::Map::Environment::applyDefault(*m_renderer, *m_camera);
  1215. }
  1216. Game::Map::MapDefinition fallbackDef;
  1217. fallbackDef.grid.width = fallbackGridWidth;
  1218. fallbackDef.grid.height = fallbackGridHeight;
  1219. fallbackDef.grid.tileSize = fallbackTileSize;
  1220. fallbackDef.maxTroopsPerPlayer = m_level.maxTroopsPerPlayer;
  1221. terrainService.initialize(fallbackDef);
  1222. if (m_ground) {
  1223. m_ground->configure(fallbackTileSize, fallbackGridWidth,
  1224. fallbackGridHeight);
  1225. }
  1226. Game::Systems::CommandService::initialize(fallbackGridWidth,
  1227. fallbackGridHeight);
  1228. auto &visibilityService = Game::Map::VisibilityService::instance();
  1229. visibilityService.initialize(fallbackGridWidth, fallbackGridHeight,
  1230. fallbackTileSize);
  1231. visibilityService.computeImmediate(*m_world, m_runtime.localOwnerId);
  1232. if (m_fog && visibilityService.isInitialized()) {
  1233. m_fog->updateMask(
  1234. visibilityService.getWidth(), visibilityService.getHeight(),
  1235. visibilityService.getTileSize(), visibilityService.snapshotCells());
  1236. }
  1237. m_runtime.visibilityVersion = visibilityService.version();
  1238. m_runtime.visibilityUpdateAccumulator = 0.0f;
  1239. }
  1240. }
  1241. bool GameEngine::hasPatrolPreviewWaypoint() const {
  1242. return m_commandController && m_commandController->hasPatrolFirstWaypoint();
  1243. }
  1244. QVector3D GameEngine::getPatrolPreviewWaypoint() const {
  1245. if (!m_commandController)
  1246. return QVector3D();
  1247. return m_commandController->getPatrolFirstWaypoint();
  1248. }