game_engine.cpp 52 KB

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