game_engine.cpp 78 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518
  1. #include "game_engine.h"
  2. #include "../controllers/action_vfx.h"
  3. #include "../controllers/command_controller.h"
  4. #include "../models/audio_system_proxy.h"
  5. #include "../models/cursor_manager.h"
  6. #include "../models/hover_tracker.h"
  7. #include "ambient_state_manager.h"
  8. #include "app/models/cursor_mode.h"
  9. #include "app/utils/engine_view_helpers.h"
  10. #include "app/utils/movement_utils.h"
  11. #include "app/utils/selection_utils.h"
  12. #include "audio_event_handler.h"
  13. #include "audio_resource_loader.h"
  14. #include "camera_controller.h"
  15. #include "campaign_manager.h"
  16. #include "core/system.h"
  17. #include "game/audio/audio_system.h"
  18. #include "game/map/mission_context.h"
  19. #include "game/units/spawn_type.h"
  20. #include "game/units/troop_type.h"
  21. #include "game_state_restorer.h"
  22. #include "input_command_handler.h"
  23. #include "level_orchestrator.h"
  24. #include "loading_progress_tracker.h"
  25. #include "minimap_manager.h"
  26. #include "production_manager.h"
  27. #include "renderer_bootstrap.h"
  28. #include "selection_query_service.h"
  29. #include <QBuffer>
  30. #include <QCoreApplication>
  31. #include <QCursor>
  32. #include <QDebug>
  33. #include <QElapsedTimer>
  34. #include <QEventLoop>
  35. #include <QImage>
  36. #include <QOpenGLContext>
  37. #include <QPainter>
  38. #include <QQuickWindow>
  39. #include <QSize>
  40. #include <QTimer>
  41. #include <QVariant>
  42. #include <QVariantMap>
  43. #include <cmath>
  44. #include <memory>
  45. #include <optional>
  46. #include <qbuffer.h>
  47. #include <qcoreapplication.h>
  48. #include <qdir.h>
  49. #include <qevent.h>
  50. #include <qglobal.h>
  51. #include <qimage.h>
  52. #include <qjsonobject.h>
  53. #include <qnamespace.h>
  54. #include <qobject.h>
  55. #include <qobjectdefs.h>
  56. #include <qpoint.h>
  57. #include <qsize.h>
  58. #include <qstringliteral.h>
  59. #include <qstringview.h>
  60. #include <qtmetamacros.h>
  61. #include <qvectornd.h>
  62. #include <unordered_set>
  63. #include "../models/selected_units_model.h"
  64. #include "game/core/component.h"
  65. #include "game/core/event_manager.h"
  66. #include "game/core/world.h"
  67. #include "game/game_config.h"
  68. #include "game/map/campaign_loader.h"
  69. #include "game/map/environment.h"
  70. #include "game/map/level_loader.h"
  71. #include "game/map/map_catalog.h"
  72. #include "game/map/map_loader.h"
  73. #include "game/map/map_transformer.h"
  74. #include "game/map/minimap/map_preview_generator.h"
  75. #include "game/map/minimap/minimap_generator.h"
  76. #include "game/map/minimap/minimap_utils.h"
  77. #include "game/map/minimap/unit_layer.h"
  78. #include "game/map/mission_loader.h"
  79. #include "game/map/skirmish_loader.h"
  80. #include "game/map/terrain_service.h"
  81. #include "game/map/visibility_service.h"
  82. #include "game/map/world_bootstrap.h"
  83. #include "game/systems/ai_system.h"
  84. #include "game/systems/ai_system/ai_strategy.h"
  85. #include "game/systems/arrow_system.h"
  86. #include "game/systems/ballista_attack_system.h"
  87. #include "game/systems/building_collision_registry.h"
  88. #include "game/systems/camera_service.h"
  89. #include "game/systems/camera_visibility_service.h"
  90. #include "game/systems/capture_system.h"
  91. #include "game/systems/catapult_attack_system.h"
  92. #include "game/systems/cleanup_system.h"
  93. #include "game/systems/combat_system.h"
  94. #include "game/systems/command_service.h"
  95. #include "game/systems/formation_planner.h"
  96. #include "game/systems/game_state_serializer.h"
  97. #include "game/systems/global_stats_registry.h"
  98. #include "game/systems/guard_system.h"
  99. #include "game/systems/healing_beam_system.h"
  100. #include "game/systems/healing_system.h"
  101. #include "game/systems/movement_system.h"
  102. #include "game/systems/nation_id.h"
  103. #include "game/systems/nation_registry.h"
  104. #include "game/systems/owner_registry.h"
  105. #include "game/systems/patrol_system.h"
  106. #include "game/systems/picking_service.h"
  107. #include "game/systems/production_service.h"
  108. #include "game/systems/production_system.h"
  109. #include "game/systems/projectile_system.h"
  110. #include "game/systems/rain_manager.h"
  111. #include "game/systems/save_load_service.h"
  112. #include "game/systems/selection_system.h"
  113. #include "game/systems/terrain_alignment_system.h"
  114. #include "game/systems/troop_count_registry.h"
  115. #include "game/systems/troop_profile_service.h"
  116. #include "game/systems/victory_service.h"
  117. #include "game/units/factory.h"
  118. #include "game/units/troop_config.h"
  119. #include "game/visuals/team_colors.h"
  120. #include "render/entity/combat_dust_renderer.h"
  121. #include "render/entity/healer_aura_renderer.h"
  122. #include "render/entity/healing_beam_renderer.h"
  123. #include "render/entity/healing_waves_renderer.h"
  124. #include "render/geom/arrow.h"
  125. #include "render/geom/formation_arrow.h"
  126. #include "render/geom/patrol_flags.h"
  127. #include "render/geom/stone.h"
  128. #include "render/gl/bootstrap.h"
  129. #include "render/gl/camera.h"
  130. #include "render/ground/biome_renderer.h"
  131. #include "render/ground/bridge_renderer.h"
  132. #include "render/ground/firecamp_renderer.h"
  133. #include "render/ground/fog_renderer.h"
  134. #include "render/ground/ground_renderer.h"
  135. #include "render/ground/olive_renderer.h"
  136. #include "render/ground/pine_renderer.h"
  137. #include "render/ground/plant_renderer.h"
  138. #include "render/ground/rain_renderer.h"
  139. #include "render/ground/river_renderer.h"
  140. #include "render/ground/riverbank_renderer.h"
  141. #include "render/ground/road_renderer.h"
  142. #include "render/ground/stone_renderer.h"
  143. #include "render/ground/terrain_renderer.h"
  144. #include "render/scene_renderer.h"
  145. #include "utils/resource_utils.h"
  146. #include <QDir>
  147. #include <QFile>
  148. #include <QJsonArray>
  149. #include <QJsonDocument>
  150. #include <QJsonObject>
  151. #include <QSet>
  152. #include <QStringList>
  153. #include <algorithm>
  154. #include <cmath>
  155. #include <utility>
  156. #include <vector>
  157. GameEngine::GameEngine(QObject *parent)
  158. : QObject(parent),
  159. m_selectedUnitsModel(new SelectedUnitsModel(this, this)) {
  160. Game::Systems::NationRegistry::instance().initialize_defaults();
  161. Game::Systems::TroopCountRegistry::instance().initialize();
  162. Game::Systems::GlobalStatsRegistry::instance().initialize();
  163. m_world = std::make_unique<Engine::Core::World>();
  164. auto rendering = RendererBootstrap::initialize_rendering();
  165. m_renderer = std::move(rendering.renderer);
  166. m_camera = std::move(rendering.camera);
  167. m_ground = std::move(rendering.ground);
  168. m_terrain = std::move(rendering.terrain);
  169. m_biome = std::move(rendering.biome);
  170. m_river = std::move(rendering.river);
  171. m_road = std::move(rendering.road);
  172. m_riverbank = std::move(rendering.riverbank);
  173. m_bridge = std::move(rendering.bridge);
  174. m_fog = std::move(rendering.fog);
  175. m_stone = std::move(rendering.stone);
  176. m_plant = std::move(rendering.plant);
  177. m_pine = std::move(rendering.pine);
  178. m_olive = std::move(rendering.olive);
  179. m_firecamp = std::move(rendering.firecamp);
  180. m_rain = std::move(rendering.rain);
  181. m_passes = std::move(rendering.passes);
  182. RendererBootstrap::initialize_world_systems(*m_world);
  183. m_pickingService = std::make_unique<Game::Systems::PickingService>();
  184. m_victoryService = std::make_unique<Game::Systems::VictoryService>();
  185. m_saveLoadService = std::make_unique<Game::Systems::SaveLoadService>();
  186. m_cameraService = std::make_unique<Game::Systems::CameraService>();
  187. m_rainManager = std::make_unique<Game::Systems::RainManager>();
  188. m_loading_progress_tracker = std::make_unique<LoadingProgressTracker>(this);
  189. connect(m_loading_progress_tracker.get(),
  190. &LoadingProgressTracker::progress_changed, this,
  191. [this](float progress) { emit loading_progress_changed(progress); });
  192. connect(m_loading_progress_tracker.get(),
  193. &LoadingProgressTracker::stage_changed, this,
  194. [this](LoadingProgressTracker::LoadingStage, QString detail) {
  195. emit loading_stage_changed(detail);
  196. });
  197. auto *selection_system =
  198. m_world->get_system<Game::Systems::SelectionSystem>();
  199. m_selectionController = std::make_unique<Game::Systems::SelectionController>(
  200. m_world.get(), selection_system, m_pickingService.get());
  201. m_commandController = std::make_unique<App::Controllers::CommandController>(
  202. m_world.get(), selection_system, m_pickingService.get());
  203. m_cursor_manager = std::make_unique<CursorManager>();
  204. m_hoverTracker = std::make_unique<HoverTracker>(m_pickingService.get());
  205. m_mapCatalog = std::make_unique<Game::Map::MapCatalog>(this);
  206. connect(m_mapCatalog.get(), &Game::Map::MapCatalog::map_loaded, this,
  207. [this](const QVariantMap &mapData) {
  208. m_available_maps.append(mapData);
  209. emit available_maps_changed();
  210. });
  211. connect(m_mapCatalog.get(), &Game::Map::MapCatalog::loading_changed, this,
  212. [this](bool loading) {
  213. m_maps_loading = loading;
  214. emit maps_loading_changed();
  215. });
  216. connect(m_mapCatalog.get(), &Game::Map::MapCatalog::all_maps_loaded, this,
  217. [this]() { emit available_maps_changed(); });
  218. if (AudioSystem::getInstance().initialize()) {
  219. qInfo() << "AudioSystem initialized successfully";
  220. AudioResourceLoader::load_audio_resources();
  221. } else {
  222. qWarning() << "Failed to initialize AudioSystem";
  223. }
  224. m_audio_systemProxy = std::make_unique<App::Models::AudioSystemProxy>(this);
  225. m_minimap_manager = std::make_unique<MinimapManager>();
  226. m_ambient_state_manager = std::make_unique<AmbientStateManager>();
  227. m_input_handler = std::make_unique<InputCommandHandler>(
  228. m_world.get(), m_selectionController.get(), m_commandController.get(),
  229. m_cursor_manager.get(), m_hoverTracker.get(), m_pickingService.get(),
  230. m_camera.get());
  231. m_camera_controller = std::make_unique<CameraController>(
  232. m_camera.get(), m_cameraService.get(), m_world.get());
  233. m_production_manager = std::make_unique<ProductionManager>(
  234. m_world.get(), m_pickingService.get(), m_camera.get(), this);
  235. connect(m_production_manager.get(),
  236. &ProductionManager::placing_construction_changed, this,
  237. &GameEngine::placing_construction_changed);
  238. m_campaign_manager = std::make_unique<CampaignManager>(this);
  239. connect(m_campaign_manager.get(),
  240. &CampaignManager::available_campaigns_changed, this,
  241. &GameEngine::available_campaigns_changed);
  242. m_selection_query_service =
  243. std::make_unique<SelectionQueryService>(m_world.get(), this);
  244. m_audioEventHandler =
  245. std::make_unique<Game::Audio::AudioEventHandler>(m_world.get());
  246. if (m_audioEventHandler->initialize()) {
  247. qInfo() << "AudioEventHandler initialized successfully";
  248. m_audioEventHandler->loadUnitVoiceMapping("archer", "archer_voice");
  249. m_audioEventHandler->loadUnitVoiceMapping("swordsman", "swordsman_voice");
  250. m_audioEventHandler->loadUnitVoiceMapping("swordsman", "swordsman_voice");
  251. m_audioEventHandler->loadUnitVoiceMapping("spearman", "spearman_voice");
  252. m_audioEventHandler->loadAmbientMusic(Engine::Core::AmbientState::PEACEFUL,
  253. "music_peaceful");
  254. m_audioEventHandler->loadAmbientMusic(Engine::Core::AmbientState::TENSE,
  255. "music_tense");
  256. m_audioEventHandler->loadAmbientMusic(Engine::Core::AmbientState::COMBAT,
  257. "music_combat");
  258. m_audioEventHandler->loadAmbientMusic(Engine::Core::AmbientState::VICTORY,
  259. "music_victory");
  260. m_audioEventHandler->loadAmbientMusic(Engine::Core::AmbientState::DEFEAT,
  261. "music_defeat");
  262. qInfo() << "Audio mappings configured";
  263. } else {
  264. qWarning() << "Failed to initialize AudioEventHandler";
  265. }
  266. connect(m_cursor_manager.get(), &CursorManager::mode_changed, this, [this]() {
  267. if (m_cursor_manager && m_window) {
  268. m_cursor_manager->update_cursor_shape(m_window);
  269. }
  270. emit cursor_mode_changed();
  271. });
  272. connect(m_cursor_manager.get(), &CursorManager::global_cursor_changed, this,
  273. &GameEngine::global_cursor_changed);
  274. connect(m_selectionController.get(),
  275. &Game::Systems::SelectionController::selection_changed, this,
  276. &GameEngine::selected_units_changed);
  277. connect(m_selectionController.get(),
  278. &Game::Systems::SelectionController::selection_changed, this,
  279. &GameEngine::sync_selection_flags);
  280. connect(
  281. m_selectionController.get(),
  282. &Game::Systems::SelectionController::selection_model_refresh_requested,
  283. this, &GameEngine::selected_units_data_changed);
  284. connect(m_commandController.get(),
  285. &App::Controllers::CommandController::attack_target_selected,
  286. [this]() {
  287. if (auto *sel_sys =
  288. m_world->get_system<Game::Systems::SelectionSystem>()) {
  289. const auto &sel = sel_sys->get_selected_units();
  290. if (!sel.empty()) {
  291. auto *cam = m_camera.get();
  292. auto *picking = m_pickingService.get();
  293. if ((cam != nullptr) && (picking != nullptr)) {
  294. Engine::Core::EntityID const target_id =
  295. Game::Systems::PickingService::pick_unit_first(
  296. 0.0F, 0.0F, *m_world, *cam, m_viewport.width,
  297. m_viewport.height, 0);
  298. if (target_id != 0) {
  299. App::Controllers::ActionVFX::spawnAttackArrow(m_world.get(),
  300. target_id);
  301. }
  302. }
  303. }
  304. }
  305. });
  306. connect(m_commandController.get(),
  307. &App::Controllers::CommandController::troop_limit_reached, [this]() {
  308. set_error(
  309. "Maximum troop limit reached. Cannot produce more units.");
  310. });
  311. connect(m_commandController.get(),
  312. &App::Controllers::CommandController::hold_mode_changed, this,
  313. &GameEngine::hold_mode_changed);
  314. connect(m_commandController.get(),
  315. &App::Controllers::CommandController::guard_mode_changed, this,
  316. &GameEngine::guard_mode_changed);
  317. connect(m_commandController.get(),
  318. &App::Controllers::CommandController::formation_mode_changed, this,
  319. &GameEngine::formation_mode_changed);
  320. connect(m_commandController.get(),
  321. &App::Controllers::CommandController::formation_placement_started,
  322. [this]() { emit placing_formation_changed(); });
  323. connect(m_commandController.get(),
  324. &App::Controllers::CommandController::formation_placement_ended,
  325. [this]() { emit placing_formation_changed(); });
  326. connect(this, SIGNAL(selected_units_changed()), m_selectedUnitsModel,
  327. SLOT(refresh()));
  328. connect(this, SIGNAL(selected_units_data_changed()), m_selectedUnitsModel,
  329. SLOT(refresh()));
  330. emit selected_units_changed();
  331. m_unit_died_subscription =
  332. Engine::Core::ScopedEventSubscription<Engine::Core::UnitDiedEvent>(
  333. [this](const Engine::Core::UnitDiedEvent &e) {
  334. on_unit_died(e);
  335. if (Game::Units::isTroopSpawn(e.spawn_type) &&
  336. e.owner_id != m_runtime.local_owner_id &&
  337. e.killer_owner_id == m_runtime.local_owner_id) {
  338. int const production_cost =
  339. Game::Units::TroopConfig::instance().getProductionCost(
  340. e.spawn_type);
  341. m_enemyTroopsDefeated += production_cost;
  342. emit enemy_troops_defeated_changed();
  343. }
  344. });
  345. m_unit_spawned_subscription =
  346. Engine::Core::ScopedEventSubscription<Engine::Core::UnitSpawnedEvent>(
  347. [this](const Engine::Core::UnitSpawnedEvent &e) {
  348. on_unit_spawned(e);
  349. });
  350. }
  351. GameEngine::~GameEngine() {
  352. if (m_audioEventHandler) {
  353. m_audioEventHandler->shutdown();
  354. }
  355. AudioSystem::getInstance().shutdown();
  356. qInfo() << "AudioSystem shut down";
  357. }
  358. void GameEngine::cleanup_opengl_resources() {
  359. qInfo() << "Cleaning up OpenGL resources...";
  360. QOpenGLContext *context = QOpenGLContext::currentContext();
  361. const bool has_valid_context = (context != nullptr);
  362. if (!has_valid_context) {
  363. qInfo() << "No valid OpenGL context, skipping OpenGL cleanup";
  364. }
  365. if (m_renderer && has_valid_context) {
  366. m_renderer->shutdown();
  367. qInfo() << "Renderer shut down";
  368. }
  369. m_passes.clear();
  370. m_ground.reset();
  371. m_terrain.reset();
  372. m_biome.reset();
  373. m_river.reset();
  374. m_road.reset();
  375. m_riverbank.reset();
  376. m_bridge.reset();
  377. m_fog.reset();
  378. m_stone.reset();
  379. m_plant.reset();
  380. m_pine.reset();
  381. m_olive.reset();
  382. m_firecamp.reset();
  383. m_rain.reset();
  384. m_rainManager.reset();
  385. m_renderer.reset();
  386. m_resources.reset();
  387. qInfo() << "OpenGL resources cleaned up";
  388. }
  389. void GameEngine::on_map_clicked(qreal sx, qreal sy) {
  390. if (m_window == nullptr) {
  391. return;
  392. }
  393. ensure_initialized();
  394. if (m_input_handler) {
  395. m_input_handler->on_map_clicked(sx, sy, m_runtime.local_owner_id,
  396. m_viewport);
  397. }
  398. }
  399. void GameEngine::on_right_click(qreal sx, qreal sy) {
  400. if (m_window == nullptr) {
  401. return;
  402. }
  403. ensure_initialized();
  404. if (m_input_handler) {
  405. m_input_handler->on_right_click(sx, sy, m_runtime.local_owner_id,
  406. m_viewport);
  407. }
  408. }
  409. void GameEngine::on_right_double_click(qreal sx, qreal sy) {
  410. if (m_window == nullptr) {
  411. return;
  412. }
  413. ensure_initialized();
  414. if (m_input_handler) {
  415. m_input_handler->on_right_double_click(sx, sy, m_runtime.local_owner_id,
  416. m_viewport);
  417. }
  418. }
  419. void GameEngine::on_attack_click(qreal sx, qreal sy) {
  420. if (m_window == nullptr) {
  421. return;
  422. }
  423. ensure_initialized();
  424. if (m_input_handler) {
  425. m_input_handler->on_attack_click(sx, sy, m_viewport);
  426. }
  427. }
  428. void GameEngine::reset_movement(Engine::Core::Entity *entity) {
  429. InputCommandHandler::reset_movement(entity);
  430. }
  431. void GameEngine::on_stop_command() {
  432. if (!m_input_handler) {
  433. return;
  434. }
  435. ensure_initialized();
  436. m_input_handler->on_stop_command();
  437. }
  438. void GameEngine::on_hold_command() {
  439. if (!m_input_handler) {
  440. return;
  441. }
  442. ensure_initialized();
  443. m_input_handler->on_hold_command();
  444. }
  445. void GameEngine::on_guard_command() {
  446. if (!m_input_handler) {
  447. return;
  448. }
  449. ensure_initialized();
  450. m_input_handler->on_guard_command();
  451. }
  452. void GameEngine::on_formation_command() {
  453. if (!m_input_handler) {
  454. return;
  455. }
  456. ensure_initialized();
  457. m_input_handler->on_formation_command();
  458. }
  459. void GameEngine::on_run_command() {
  460. if (!m_input_handler) {
  461. return;
  462. }
  463. ensure_initialized();
  464. m_input_handler->on_run_command();
  465. }
  466. void GameEngine::on_heal_command() {
  467. if (!m_cursor_manager) {
  468. return;
  469. }
  470. ensure_initialized();
  471. m_cursor_manager->set_mode(CursorMode::Heal);
  472. }
  473. void GameEngine::on_build_command() {
  474. if (!m_cursor_manager) {
  475. return;
  476. }
  477. ensure_initialized();
  478. m_cursor_manager->set_mode(CursorMode::Build);
  479. }
  480. void GameEngine::on_guard_click(qreal sx, qreal sy) {
  481. if (!m_input_handler || !m_camera) {
  482. return;
  483. }
  484. ensure_initialized();
  485. m_input_handler->on_guard_click(sx, sy, m_viewport);
  486. }
  487. auto GameEngine::any_selected_in_hold_mode() const -> bool {
  488. if (!m_input_handler) {
  489. return false;
  490. }
  491. return m_input_handler->any_selected_in_hold_mode();
  492. }
  493. auto GameEngine::any_selected_in_guard_mode() const -> bool {
  494. if (!m_input_handler) {
  495. return false;
  496. }
  497. return m_input_handler->any_selected_in_guard_mode();
  498. }
  499. auto GameEngine::any_selected_in_formation_mode() const -> bool {
  500. if (!m_input_handler) {
  501. return false;
  502. }
  503. return m_input_handler->any_selected_in_formation_mode();
  504. }
  505. auto GameEngine::any_selected_in_run_mode() const -> bool {
  506. if (!m_input_handler) {
  507. return false;
  508. }
  509. return m_input_handler->any_selected_in_run_mode();
  510. }
  511. auto GameEngine::is_placing_formation() const -> bool {
  512. if (m_commandController) {
  513. return m_commandController->is_placing_formation();
  514. }
  515. return false;
  516. }
  517. bool GameEngine::is_campaign_mission() const {
  518. if (!m_campaign_manager) {
  519. return false;
  520. }
  521. return m_campaign_manager->current_mission_context().is_campaign();
  522. }
  523. void GameEngine::on_formation_mouse_move(qreal sx, qreal sy) {
  524. if (!m_input_handler) {
  525. return;
  526. }
  527. ensure_initialized();
  528. m_input_handler->on_formation_mouse_move(sx, sy, m_viewport);
  529. }
  530. void GameEngine::on_formation_scroll(float delta) {
  531. if (!m_input_handler) {
  532. return;
  533. }
  534. ensure_initialized();
  535. m_input_handler->on_formation_scroll(delta);
  536. }
  537. void GameEngine::on_formation_confirm() {
  538. if (!m_input_handler) {
  539. return;
  540. }
  541. ensure_initialized();
  542. m_input_handler->on_formation_confirm();
  543. }
  544. void GameEngine::on_formation_cancel() {
  545. if (!m_input_handler) {
  546. return;
  547. }
  548. ensure_initialized();
  549. m_input_handler->on_formation_cancel();
  550. }
  551. auto GameEngine::is_placing_construction() const -> bool {
  552. return m_production_manager ? m_production_manager->is_placing_construction()
  553. : false;
  554. }
  555. void GameEngine::on_construction_mouse_move(qreal sx, qreal sy) {
  556. ensure_initialized();
  557. if (m_production_manager) {
  558. m_production_manager->on_construction_mouse_move(sx, sy, m_viewport);
  559. }
  560. }
  561. void GameEngine::on_construction_confirm() {
  562. ensure_initialized();
  563. if (m_production_manager) {
  564. m_production_manager->on_construction_confirm();
  565. }
  566. }
  567. void GameEngine::on_construction_cancel() {
  568. if (m_production_manager) {
  569. m_production_manager->on_construction_cancel();
  570. }
  571. }
  572. void GameEngine::on_patrol_click(qreal sx, qreal sy) {
  573. if (!m_input_handler || !m_camera) {
  574. return;
  575. }
  576. ensure_initialized();
  577. m_input_handler->on_patrol_click(sx, sy, m_viewport);
  578. }
  579. void GameEngine::update_cursor(Qt::CursorShape newCursor) {
  580. if (m_window == nullptr) {
  581. return;
  582. }
  583. if (m_runtime.current_cursor != newCursor) {
  584. m_runtime.current_cursor = newCursor;
  585. m_window->setCursor(newCursor);
  586. }
  587. }
  588. void GameEngine::set_error(const QString &errorMessage) {
  589. if (m_runtime.last_error != errorMessage) {
  590. m_runtime.last_error = errorMessage;
  591. qCritical() << "GameEngine error:" << errorMessage;
  592. emit last_error_changed();
  593. }
  594. }
  595. void GameEngine::set_cursor_mode(CursorMode mode) {
  596. if (!m_cursor_manager) {
  597. return;
  598. }
  599. m_cursor_manager->set_mode(mode);
  600. m_cursor_manager->update_cursor_shape(m_window);
  601. }
  602. void GameEngine::set_cursor_mode(const QString &mode) {
  603. set_cursor_mode(CursorModeUtils::fromString(mode));
  604. }
  605. auto GameEngine::cursor_mode() const -> QString {
  606. if (!m_cursor_manager) {
  607. return "normal";
  608. }
  609. return m_cursor_manager->mode_string();
  610. }
  611. auto GameEngine::global_cursor_x() const -> qreal {
  612. if (!m_cursor_manager) {
  613. return 0;
  614. }
  615. return m_cursor_manager->global_cursor_x(m_window);
  616. }
  617. auto GameEngine::global_cursor_y() const -> qreal {
  618. if (!m_cursor_manager) {
  619. return 0;
  620. }
  621. return m_cursor_manager->global_cursor_y(m_window);
  622. }
  623. void GameEngine::set_hover_at_screen(qreal sx, qreal sy) {
  624. if (m_window == nullptr) {
  625. return;
  626. }
  627. ensure_initialized();
  628. if (m_input_handler) {
  629. m_input_handler->set_hover_at_screen(sx, sy, m_viewport);
  630. }
  631. }
  632. void GameEngine::on_click_select(qreal sx, qreal sy, bool additive) {
  633. if (m_window == nullptr) {
  634. return;
  635. }
  636. ensure_initialized();
  637. if (m_input_handler) {
  638. m_input_handler->on_click_select(sx, sy, additive, m_runtime.local_owner_id,
  639. m_viewport);
  640. }
  641. }
  642. void GameEngine::on_area_selected(qreal x1, qreal y1, qreal x2, qreal y2,
  643. bool additive) {
  644. if (m_window == nullptr) {
  645. return;
  646. }
  647. ensure_initialized();
  648. if (m_input_handler) {
  649. m_input_handler->on_area_selected(x1, y1, x2, y2, additive,
  650. m_runtime.local_owner_id, m_viewport);
  651. }
  652. }
  653. void GameEngine::select_all_troops() {
  654. ensure_initialized();
  655. if (m_input_handler) {
  656. m_input_handler->select_all_troops(m_runtime.local_owner_id);
  657. }
  658. }
  659. void GameEngine::select_unit_by_id(int unitId) {
  660. ensure_initialized();
  661. if (m_input_handler) {
  662. m_input_handler->select_unit_by_id(unitId, m_runtime.local_owner_id);
  663. }
  664. }
  665. void GameEngine::ensure_initialized() {
  666. QString error;
  667. Game::Map::WorldBootstrap::ensure_initialized(
  668. m_runtime.initialized, *m_renderer, *m_camera, m_ground.get(), &error);
  669. if (!error.isEmpty()) {
  670. set_error(error);
  671. }
  672. }
  673. auto GameEngine::enemy_troops_defeated() const -> int {
  674. return m_enemyTroopsDefeated;
  675. }
  676. auto GameEngine::get_player_stats(int owner_id) -> QVariantMap {
  677. QVariantMap result;
  678. auto &stats_registry = Game::Systems::GlobalStatsRegistry::instance();
  679. const auto *stats = stats_registry.get_stats(owner_id);
  680. if (stats != nullptr) {
  681. result["troopsRecruited"] = stats->troops_recruited;
  682. result["enemiesKilled"] = stats->enemies_killed;
  683. result["losses"] = stats->losses;
  684. result["barracksOwned"] = stats->barracks_owned;
  685. result["playTimeSec"] = stats->play_time_sec;
  686. result["gameEnded"] = stats->game_ended;
  687. } else {
  688. result["troopsRecruited"] = 0;
  689. result["enemiesKilled"] = 0;
  690. result["losses"] = 0;
  691. result["barracksOwned"] = 0;
  692. result["playTimeSec"] = 0.0F;
  693. result["gameEnded"] = false;
  694. }
  695. return result;
  696. }
  697. void GameEngine::update(float dt) {
  698. if (m_runtime.loading) {
  699. return;
  700. }
  701. if (m_runtime.paused) {
  702. dt = 0.0F;
  703. } else {
  704. dt *= m_runtime.time_scale;
  705. }
  706. if (!m_runtime.paused && !m_runtime.loading) {
  707. if (m_ambient_state_manager) {
  708. m_ambient_state_manager->update(dt, m_world.get(),
  709. m_runtime.local_owner_id, m_entity_cache,
  710. m_runtime.victory_state);
  711. }
  712. }
  713. if (m_renderer) {
  714. m_renderer->update_animation_time(dt);
  715. }
  716. if (m_camera) {
  717. m_camera->update(dt);
  718. }
  719. if (m_world) {
  720. m_world->update(dt);
  721. auto &visibility_service = Game::Map::VisibilityService::instance();
  722. if (visibility_service.is_initialized() && !m_level.is_spectator_mode) {
  723. m_runtime.visibility_update_accumulator += dt;
  724. const float visibility_update_interval =
  725. Game::GameConfig::instance().gameplay().visibility_update_interval;
  726. if (m_runtime.visibility_update_accumulator >=
  727. visibility_update_interval) {
  728. m_runtime.visibility_update_accumulator = 0.0F;
  729. visibility_service.update(*m_world, m_runtime.local_owner_id);
  730. }
  731. const auto new_version = visibility_service.version();
  732. if (new_version != m_runtime.visibility_version) {
  733. if (m_fog) {
  734. m_fog->update_mask(visibility_service.getWidth(),
  735. visibility_service.getHeight(),
  736. visibility_service.getTileSize(),
  737. visibility_service.snapshotCells());
  738. }
  739. m_runtime.visibility_version = new_version;
  740. }
  741. }
  742. if (m_minimap_manager) {
  743. if (!m_level.is_spectator_mode) {
  744. m_minimap_manager->update_fog(dt, m_runtime.local_owner_id);
  745. }
  746. auto *selection_system =
  747. m_world->get_system<Game::Systems::SelectionSystem>();
  748. m_minimap_manager->update_units(m_world.get(), selection_system,
  749. m_runtime.local_owner_id);
  750. m_minimap_manager->update_camera_viewport(
  751. m_camera.get(), static_cast<float>(m_viewport.width),
  752. static_cast<float>(m_viewport.height));
  753. if (m_minimap_manager->consume_dirty_flag()) {
  754. emit minimap_image_changed();
  755. }
  756. }
  757. }
  758. if (m_rainManager) {
  759. m_rainManager->update(dt);
  760. if (m_rain) {
  761. m_rain->set_enabled(m_rainManager->is_enabled());
  762. m_rain->set_intensity(m_rainManager->get_intensity());
  763. m_rain->set_weather_type(m_rainManager->get_weather_type());
  764. m_rain->set_wind_strength(m_rainManager->get_wind_strength());
  765. if (m_camera) {
  766. m_rain->set_camera_position(m_camera->get_position());
  767. }
  768. }
  769. }
  770. if (m_victoryService && m_world) {
  771. m_victoryService->update(*m_world, dt);
  772. }
  773. if (m_camera_controller) {
  774. m_camera_controller->update_follow(m_followSelectionEnabled);
  775. }
  776. if (m_selectedUnitsModel != nullptr) {
  777. auto *selection_system =
  778. m_world->get_system<Game::Systems::SelectionSystem>();
  779. if ((selection_system != nullptr) &&
  780. !selection_system->get_selected_units().empty()) {
  781. m_runtime.selection_refresh_counter++;
  782. if (m_runtime.selection_refresh_counter >= 15) {
  783. m_runtime.selection_refresh_counter = 0;
  784. emit selected_units_data_changed();
  785. }
  786. }
  787. }
  788. }
  789. void GameEngine::render(int pixelWidth, int pixelHeight) {
  790. if (!m_renderer || !m_world || !m_runtime.initialized || m_runtime.loading) {
  791. return;
  792. }
  793. Game::Systems::CameraVisibilityService::instance().set_camera(m_camera.get());
  794. if (pixelWidth > 0 && pixelHeight > 0) {
  795. m_viewport.width = pixelWidth;
  796. m_viewport.height = pixelHeight;
  797. m_renderer->set_viewport(pixelWidth, pixelHeight);
  798. }
  799. if (auto *selection_system =
  800. m_world->get_system<Game::Systems::SelectionSystem>()) {
  801. const auto &sel = selection_system->get_selected_units();
  802. std::vector<unsigned int> const ids(sel.begin(), sel.end());
  803. m_renderer->set_selected_entities(ids);
  804. }
  805. m_renderer->begin_frame();
  806. if (auto *res = m_renderer->resources()) {
  807. for (auto *pass : m_passes) {
  808. if (pass != nullptr) {
  809. pass->submit(*m_renderer, res);
  810. }
  811. }
  812. }
  813. if (m_renderer && m_hoverTracker) {
  814. m_renderer->set_hovered_entity_id(m_hoverTracker->getLastHoveredEntity());
  815. }
  816. if (m_renderer) {
  817. m_renderer->set_local_owner_id(m_runtime.local_owner_id);
  818. }
  819. m_renderer->render_world(m_world.get());
  820. render_game_effects();
  821. m_renderer->end_frame();
  822. update_loading_overlay();
  823. update_cursor_position();
  824. }
  825. void GameEngine::render_game_effects() {
  826. auto *res = m_renderer->resources();
  827. if (!res) {
  828. return;
  829. }
  830. if (auto *arrow_system = m_world->get_system<Game::Systems::ArrowSystem>()) {
  831. Render::GL::render_arrows(m_renderer.get(), res, *arrow_system);
  832. }
  833. if (auto *projectile_system =
  834. m_world->get_system<Game::Systems::ProjectileSystem>()) {
  835. Render::GL::render_projectiles(m_renderer.get(), res, *projectile_system);
  836. }
  837. if (auto *healing_beam_system =
  838. m_world->get_system<Game::Systems::HealingBeamSystem>()) {
  839. if (auto *res = m_renderer->resources()) {
  840. Render::GL::render_healing_beams(m_renderer.get(), res,
  841. *healing_beam_system);
  842. Render::GL::render_healing_waves(m_renderer.get(), res,
  843. *healing_beam_system);
  844. }
  845. }
  846. Render::GL::render_healer_auras(m_renderer.get(), res, m_world.get());
  847. Render::GL::render_combat_dust(m_renderer.get(), res, m_world.get());
  848. std::optional<QVector3D> preview_waypoint;
  849. if (m_commandController && m_commandController->has_patrol_first_waypoint()) {
  850. preview_waypoint = m_commandController->get_patrol_first_waypoint();
  851. }
  852. Render::GL::render_patrol_flags(m_renderer.get(), res, *m_world,
  853. preview_waypoint);
  854. if (m_commandController && m_commandController->is_placing_formation()) {
  855. Render::GL::FormationPlacementInfo placement;
  856. placement.position =
  857. m_commandController->get_formation_placement_position();
  858. placement.angle_degrees =
  859. m_commandController->get_formation_placement_angle();
  860. placement.active = true;
  861. Render::GL::render_formation_arrow(m_renderer.get(), res, placement);
  862. }
  863. }
  864. void GameEngine::update_loading_overlay() {
  865. if (!m_loading_overlay_wait_for_first_frame) {
  866. return;
  867. }
  868. if (!m_renderer || !m_renderer->resources()) {
  869. m_loading_overlay_frames_remaining = 5;
  870. m_loading_overlay_timer.restart();
  871. return;
  872. }
  873. if (m_loading_overlay_frames_remaining > 0) {
  874. m_loading_overlay_frames_remaining--;
  875. }
  876. constexpr qint64 k_loading_overlay_max_wait_ms = 15000;
  877. const qint64 elapsed_ms =
  878. m_loading_overlay_timer.isValid() ? m_loading_overlay_timer.elapsed() : 0;
  879. const bool enough_time = m_loading_overlay_timer.isValid() &&
  880. (elapsed_ms >= m_loading_overlay_min_duration_ms);
  881. const bool exceeded_max_wait = m_loading_overlay_timer.isValid() &&
  882. (elapsed_ms >= k_loading_overlay_max_wait_ms);
  883. QStringList pending_components;
  884. const bool biome_ready = !m_biome || m_biome->is_gpu_ready();
  885. const bool pine_ready = !m_pine || m_pine->is_gpu_ready();
  886. const bool olive_ready = !m_olive || m_olive->is_gpu_ready();
  887. const bool plant_ready = !m_plant || m_plant->is_gpu_ready();
  888. const bool stone_ready = !m_stone || m_stone->is_gpu_ready();
  889. const bool firecamp_ready = !m_firecamp || m_firecamp->is_gpu_ready();
  890. if (!biome_ready) {
  891. pending_components << QStringLiteral("biome");
  892. }
  893. if (!pine_ready) {
  894. pending_components << QStringLiteral("pine");
  895. }
  896. if (!olive_ready) {
  897. pending_components << QStringLiteral("olive");
  898. }
  899. if (!plant_ready) {
  900. pending_components << QStringLiteral("plant");
  901. }
  902. if (!stone_ready) {
  903. pending_components << QStringLiteral("stone");
  904. }
  905. if (!firecamp_ready) {
  906. pending_components << QStringLiteral("firecamp");
  907. }
  908. const bool biome_gpu_ready = pending_components.isEmpty();
  909. if (enough_time && m_loading_overlay_frames_remaining <= 0 &&
  910. (biome_gpu_ready || exceeded_max_wait)) {
  911. if (exceeded_max_wait && !biome_gpu_ready) {
  912. qWarning() << "Loading overlay timed out waiting for GPU readiness"
  913. << pending_components.join(", ");
  914. }
  915. m_loading_overlay_wait_for_first_frame = false;
  916. m_loading_overlay_active = false;
  917. if (m_finalize_progress_after_overlay && m_loading_progress_tracker) {
  918. m_loading_progress_tracker->set_stage(
  919. LoadingProgressTracker::LoadingStage::COMPLETED);
  920. }
  921. m_finalize_progress_after_overlay = false;
  922. emit is_loading_changed();
  923. if (m_show_objectives_after_loading) {
  924. m_show_objectives_after_loading = false;
  925. emit campaign_mission_changed();
  926. }
  927. }
  928. }
  929. void GameEngine::update_cursor_position() {
  930. qreal const current_x = global_cursor_x();
  931. qreal const current_y = global_cursor_y();
  932. if (current_x != m_runtime.last_cursor_x ||
  933. current_y != m_runtime.last_cursor_y) {
  934. m_runtime.last_cursor_x = current_x;
  935. m_runtime.last_cursor_y = current_y;
  936. emit global_cursor_changed();
  937. }
  938. }
  939. auto GameEngine::screen_to_ground(const QPointF &screenPt,
  940. QVector3D &outWorld) -> bool {
  941. return App::Utils::screen_to_ground(m_pickingService.get(), m_camera.get(),
  942. m_window, m_viewport.width,
  943. m_viewport.height, screenPt, outWorld);
  944. }
  945. auto GameEngine::world_to_screen(const QVector3D &world,
  946. QPointF &outScreen) const -> bool {
  947. return App::Utils::world_to_screen(m_pickingService.get(), m_camera.get(),
  948. m_window, m_viewport.width,
  949. m_viewport.height, world, outScreen);
  950. }
  951. void GameEngine::sync_selection_flags() {
  952. auto *selection_system =
  953. m_world->get_system<Game::Systems::SelectionSystem>();
  954. if (!m_world || (selection_system == nullptr)) {
  955. return;
  956. }
  957. App::Utils::sanitize_selection(m_world.get(), selection_system);
  958. if (selection_system->get_selected_units().empty()) {
  959. if (m_cursor_manager && m_cursor_manager->mode() != CursorMode::Normal) {
  960. set_cursor_mode(CursorMode::Normal);
  961. }
  962. }
  963. }
  964. void GameEngine::camera_move(float dx, float dz) {
  965. ensure_initialized();
  966. if (m_camera_controller) {
  967. m_camera_controller->move(dx, dz);
  968. }
  969. }
  970. void GameEngine::camera_elevate(float dy) {
  971. ensure_initialized();
  972. if (m_camera_controller) {
  973. m_camera_controller->elevate(dy);
  974. }
  975. }
  976. void GameEngine::reset_camera() {
  977. ensure_initialized();
  978. if (m_camera_controller) {
  979. m_camera_controller->reset(m_runtime.local_owner_id, m_level);
  980. }
  981. }
  982. void GameEngine::camera_zoom(float delta) {
  983. ensure_initialized();
  984. if (m_camera_controller) {
  985. m_camera_controller->zoom(delta);
  986. }
  987. }
  988. auto GameEngine::camera_distance() const -> float {
  989. if (m_camera_controller) {
  990. return m_camera_controller->distance();
  991. }
  992. return 0.0F;
  993. }
  994. void GameEngine::camera_yaw(float degrees) {
  995. ensure_initialized();
  996. if (m_camera_controller) {
  997. m_camera_controller->yaw(degrees);
  998. }
  999. }
  1000. void GameEngine::camera_orbit(float yaw_deg, float pitch_deg) {
  1001. ensure_initialized();
  1002. if (m_camera_controller) {
  1003. m_camera_controller->orbit(yaw_deg, pitch_deg);
  1004. }
  1005. }
  1006. void GameEngine::camera_orbit_direction(int direction, bool shift) {
  1007. if (m_camera_controller) {
  1008. m_camera_controller->orbit_direction(direction, shift);
  1009. }
  1010. }
  1011. void GameEngine::camera_follow_selection(bool enable) {
  1012. ensure_initialized();
  1013. m_followSelectionEnabled = enable;
  1014. if (m_camera_controller) {
  1015. m_camera_controller->follow_selection(enable);
  1016. }
  1017. }
  1018. void GameEngine::camera_set_follow_lerp(float alpha) {
  1019. ensure_initialized();
  1020. if (m_camera_controller) {
  1021. m_camera_controller->set_follow_lerp(alpha);
  1022. }
  1023. }
  1024. void GameEngine::on_minimap_left_click(qreal mx, qreal my, qreal minimap_width,
  1025. qreal minimap_height) {
  1026. ensure_initialized();
  1027. if (!m_camera || !m_minimap_manager || !m_minimap_manager->has_minimap()) {
  1028. return;
  1029. }
  1030. const QImage &minimap_img = m_minimap_manager->get_image();
  1031. if (minimap_img.isNull()) {
  1032. return;
  1033. }
  1034. const float img_width = static_cast<float>(minimap_img.width());
  1035. const float img_height = static_cast<float>(minimap_img.height());
  1036. const float px =
  1037. (static_cast<float>(mx) / static_cast<float>(minimap_width)) * img_width;
  1038. const float py =
  1039. (static_cast<float>(my) / static_cast<float>(minimap_height)) *
  1040. img_height;
  1041. const auto [world_x, world_z] = Game::Map::Minimap::pixel_to_world(
  1042. px, py, m_minimap_manager->get_world_width(),
  1043. m_minimap_manager->get_world_height(), img_width, img_height,
  1044. m_minimap_manager->get_tile_size());
  1045. if (m_camera) {
  1046. const QVector3D new_target(world_x, 0.0F, world_z);
  1047. const QVector3D current_target = m_camera->get_target();
  1048. const QVector3D current_position = m_camera->get_position();
  1049. const QVector3D offset = current_position - current_target;
  1050. m_camera->look_at(new_target + offset, new_target,
  1051. m_camera->get_up_vector());
  1052. }
  1053. m_followSelectionEnabled = false;
  1054. if (m_camera_controller) {
  1055. m_camera_controller->follow_selection(false);
  1056. }
  1057. }
  1058. void GameEngine::on_minimap_right_click(qreal mx, qreal my, qreal minimap_width,
  1059. qreal minimap_height) {
  1060. ensure_initialized();
  1061. if (m_level.is_spectator_mode || !m_world || !m_minimap_manager ||
  1062. !m_minimap_manager->has_minimap()) {
  1063. return;
  1064. }
  1065. const QImage &minimap_img = m_minimap_manager->get_image();
  1066. if (minimap_img.isNull()) {
  1067. return;
  1068. }
  1069. const float img_width = static_cast<float>(minimap_img.width());
  1070. const float img_height = static_cast<float>(minimap_img.height());
  1071. const float px =
  1072. (static_cast<float>(mx) / static_cast<float>(minimap_width)) * img_width;
  1073. const float py =
  1074. (static_cast<float>(my) / static_cast<float>(minimap_height)) *
  1075. img_height;
  1076. const auto [world_x, world_z] = Game::Map::Minimap::pixel_to_world(
  1077. px, py, m_minimap_manager->get_world_width(),
  1078. m_minimap_manager->get_world_height(), img_width, img_height,
  1079. m_minimap_manager->get_tile_size());
  1080. auto *selection_system =
  1081. m_world->get_system<Game::Systems::SelectionSystem>();
  1082. if (!selection_system) {
  1083. return;
  1084. }
  1085. const auto &selected = selection_system->get_selected_units();
  1086. if (selected.empty()) {
  1087. return;
  1088. }
  1089. const QVector3D target_pos(world_x, 0.0F, world_z);
  1090. auto targets = Game::Systems::FormationPlanner::spread_formation_by_nation(
  1091. *m_world, selected, target_pos,
  1092. Game::GameConfig::instance().gameplay().formation_spacing_default);
  1093. Game::Systems::CommandService::MoveOptions opts;
  1094. opts.group_move = selected.size() > 1;
  1095. Game::Systems::CommandService::move_units(*m_world, selected, targets, opts);
  1096. }
  1097. auto GameEngine::selected_units_model() -> QAbstractItemModel * {
  1098. return m_selectedUnitsModel;
  1099. }
  1100. auto GameEngine::audio_system() -> QObject * {
  1101. return m_audio_systemProxy.get();
  1102. }
  1103. auto GameEngine::has_units_selected() const -> bool {
  1104. if (!m_selectionController) {
  1105. return false;
  1106. }
  1107. return m_selectionController->has_units_selected();
  1108. }
  1109. auto GameEngine::player_troop_count() const -> int {
  1110. return m_entity_cache.player_troop_count;
  1111. }
  1112. auto GameEngine::has_selected_type(const QString &type) const -> bool {
  1113. if (!m_selectionController) {
  1114. return false;
  1115. }
  1116. return m_selectionController->has_selected_type(type);
  1117. }
  1118. void GameEngine::recruit_near_selected(const QString &unit_type) {
  1119. ensure_initialized();
  1120. if (!m_commandController) {
  1121. return;
  1122. }
  1123. m_commandController->recruit_near_selected(unit_type,
  1124. m_runtime.local_owner_id);
  1125. }
  1126. void GameEngine::start_building_placement(const QString &building_type) {
  1127. ensure_initialized();
  1128. if (m_production_manager) {
  1129. m_production_manager->start_building_placement(building_type);
  1130. set_cursor_mode(CursorMode::PlaceBuilding);
  1131. }
  1132. }
  1133. void GameEngine::place_building_at_screen(qreal sx, qreal sy) {
  1134. ensure_initialized();
  1135. if (m_production_manager) {
  1136. m_production_manager->place_building_at_screen(
  1137. sx, sy, m_runtime.local_owner_id, m_viewport);
  1138. set_cursor_mode(CursorMode::Normal);
  1139. }
  1140. }
  1141. void GameEngine::cancel_building_placement() {
  1142. if (m_production_manager) {
  1143. m_production_manager->cancel_building_placement();
  1144. }
  1145. set_cursor_mode(CursorMode::Normal);
  1146. }
  1147. auto GameEngine::pending_building_type() const -> QString {
  1148. return m_production_manager ? m_production_manager->pending_building_type()
  1149. : QString();
  1150. }
  1151. auto GameEngine::get_selected_production_state() const -> QVariantMap {
  1152. return m_production_manager
  1153. ? m_production_manager->get_selected_production_state(
  1154. m_runtime.local_owner_id)
  1155. : QVariantMap();
  1156. }
  1157. auto GameEngine::get_unit_production_info(
  1158. const QString &unit_type, const QString &nation_id) const -> QVariantMap {
  1159. return m_production_manager ? m_production_manager->get_unit_production_info(
  1160. unit_type, nation_id)
  1161. : QVariantMap();
  1162. }
  1163. auto GameEngine::get_selected_builder_production_state() const -> QVariantMap {
  1164. return m_production_manager
  1165. ? m_production_manager->get_selected_builder_production_state()
  1166. : QVariantMap();
  1167. }
  1168. void GameEngine::start_builder_construction(const QString &item_type) {
  1169. if (m_production_manager) {
  1170. m_production_manager->start_builder_construction(item_type);
  1171. }
  1172. }
  1173. auto GameEngine::get_selected_units_command_mode() const -> QString {
  1174. return m_selection_query_service
  1175. ? m_selection_query_service->get_selected_units_command_mode()
  1176. : "normal";
  1177. }
  1178. auto GameEngine::get_selected_units_mode_availability() const -> QVariantMap {
  1179. return m_selection_query_service
  1180. ? m_selection_query_service->get_selected_units_mode_availability()
  1181. : QVariantMap();
  1182. }
  1183. void GameEngine::set_rally_at_screen(qreal sx, qreal sy) {
  1184. ensure_initialized();
  1185. if (m_production_manager) {
  1186. m_production_manager->set_rally_at_screen(sx, sy, m_runtime.local_owner_id,
  1187. m_viewport);
  1188. }
  1189. }
  1190. void GameEngine::start_loading_maps() {
  1191. m_available_maps.clear();
  1192. if (m_mapCatalog) {
  1193. m_mapCatalog->load_maps_async();
  1194. }
  1195. load_campaigns();
  1196. }
  1197. auto GameEngine::available_maps() const -> QVariantList {
  1198. return m_available_maps;
  1199. }
  1200. auto GameEngine::available_nations() const -> QVariantList {
  1201. QVariantList nations;
  1202. const auto &registry = Game::Systems::NationRegistry::instance();
  1203. const auto &all = registry.get_all_nations();
  1204. QList<QVariantMap> ordered;
  1205. ordered.reserve(static_cast<int>(all.size()));
  1206. for (const auto &nation : all) {
  1207. QVariantMap entry;
  1208. entry.insert(
  1209. QStringLiteral("id"),
  1210. QString::fromStdString(Game::Systems::nation_id_to_string(nation.id)));
  1211. entry.insert(QStringLiteral("name"),
  1212. QString::fromStdString(nation.display_name));
  1213. ordered.append(entry);
  1214. }
  1215. std::sort(ordered.begin(), ordered.end(),
  1216. [](const QVariantMap &a, const QVariantMap &b) {
  1217. return a.value(QStringLiteral("name"))
  1218. .toString()
  1219. .localeAwareCompare(
  1220. b.value(QStringLiteral("name")).toString()) < 0;
  1221. });
  1222. for (const auto &entry : ordered) {
  1223. nations.append(entry);
  1224. }
  1225. return nations;
  1226. }
  1227. auto GameEngine::available_campaigns() const -> QVariantList {
  1228. return m_campaign_manager ? m_campaign_manager->available_campaigns()
  1229. : QVariantList();
  1230. }
  1231. void GameEngine::load_campaigns() {
  1232. if (!m_saveLoadService) {
  1233. return;
  1234. }
  1235. QString error;
  1236. auto campaigns = m_saveLoadService->list_campaigns(&error);
  1237. if (!error.isEmpty()) {
  1238. qWarning() << "Failed to load campaigns:" << error;
  1239. return;
  1240. }
  1241. if (m_campaign_manager) {
  1242. m_campaign_manager->set_available_campaigns(campaigns);
  1243. }
  1244. }
  1245. void GameEngine::start_campaign_mission(const QString &mission_path) {
  1246. clear_error();
  1247. if (!m_campaign_manager) {
  1248. set_error("Campaign manager not initialized");
  1249. return;
  1250. }
  1251. m_selected_player_id = 1;
  1252. m_campaign_manager->start_campaign_mission(mission_path,
  1253. m_selected_player_id);
  1254. if (!m_campaign_manager->current_mission_definition().has_value()) {
  1255. set_error("Failed to load mission");
  1256. return;
  1257. }
  1258. const auto &mission = *m_campaign_manager->current_mission_definition();
  1259. QVariantList playerConfigs;
  1260. QVariantMap player1;
  1261. player1.insert("player_id", 1);
  1262. player1.insert("playerName", mission.player_setup.nation);
  1263. player1.insert("colorIndex", 0);
  1264. player1.insert("team_id", 0);
  1265. player1.insert("nationId", mission.player_setup.nation);
  1266. player1.insert("isHuman", true);
  1267. playerConfigs.append(player1);
  1268. int player_id = 2;
  1269. int default_team_id = 1;
  1270. for (const auto &ai_setup : mission.ai_setups) {
  1271. QVariantMap ai_player;
  1272. ai_player.insert("player_id", player_id);
  1273. ai_player.insert("playerName", ai_setup.nation);
  1274. ai_player.insert("colorIndex", player_id - 1);
  1275. int team_id;
  1276. if (ai_setup.team_id.has_value()) {
  1277. team_id = ai_setup.team_id.value();
  1278. } else {
  1279. team_id = default_team_id++;
  1280. }
  1281. ai_player.insert("team_id", team_id);
  1282. ai_player.insert("nationId", ai_setup.nation);
  1283. ai_player.insert("isHuman", false);
  1284. playerConfigs.append(ai_player);
  1285. player_id++;
  1286. }
  1287. start_skirmish_internal(mission.map_path, playerConfigs, false);
  1288. }
  1289. void GameEngine::mark_current_mission_completed() {
  1290. if (!m_campaign_manager) {
  1291. return;
  1292. }
  1293. const QString campaign_id = m_campaign_manager->current_campaign_id();
  1294. if (campaign_id.isEmpty()) {
  1295. qWarning() << "No active campaign mission to mark as completed";
  1296. return;
  1297. }
  1298. if (!m_saveLoadService) {
  1299. qWarning() << "Save/Load service not initialized";
  1300. return;
  1301. }
  1302. QString error;
  1303. bool success =
  1304. m_saveLoadService->mark_campaign_completed(campaign_id, &error);
  1305. if (!success) {
  1306. qWarning() << "Failed to mark campaign as completed:" << error;
  1307. } else {
  1308. m_campaign_manager->mark_current_mission_completed();
  1309. load_campaigns();
  1310. }
  1311. }
  1312. QVariantMap GameEngine::get_current_mission_objectives() const {
  1313. QVariantMap result;
  1314. if (!m_campaign_manager) {
  1315. return result;
  1316. }
  1317. const auto &mission_def = m_campaign_manager->current_mission_definition();
  1318. if (!mission_def.has_value()) {
  1319. return result;
  1320. }
  1321. const auto &mission = *mission_def;
  1322. result["title"] = mission.title;
  1323. result["summary"] = mission.summary;
  1324. QVariantList victory_conditions;
  1325. for (const auto &condition : mission.victory_conditions) {
  1326. QVariantMap cond;
  1327. cond["type"] = condition.type;
  1328. cond["description"] = condition.description;
  1329. if (condition.duration.has_value()) {
  1330. cond["duration"] = condition.duration.value();
  1331. }
  1332. victory_conditions.append(cond);
  1333. }
  1334. result["victory_conditions"] = victory_conditions;
  1335. QVariantList defeat_conditions;
  1336. for (const auto &condition : mission.defeat_conditions) {
  1337. QVariantMap cond;
  1338. cond["type"] = condition.type;
  1339. cond["description"] = condition.description;
  1340. defeat_conditions.append(cond);
  1341. }
  1342. result["defeat_conditions"] = defeat_conditions;
  1343. QVariantList optional_objectives;
  1344. for (const auto &condition : mission.optional_objectives) {
  1345. QVariantMap cond;
  1346. cond["type"] = condition.type;
  1347. cond["description"] = condition.description;
  1348. optional_objectives.append(cond);
  1349. }
  1350. result["optional_objectives"] = optional_objectives;
  1351. return result;
  1352. }
  1353. void GameEngine::start_skirmish(const QString &map_path,
  1354. const QVariantList &playerConfigs) {
  1355. start_skirmish_internal(map_path, playerConfigs, true);
  1356. }
  1357. void GameEngine::start_skirmish_internal(const QString &map_path,
  1358. const QVariantList &playerConfigs,
  1359. bool set_skirmish_context) {
  1360. clear_error();
  1361. m_level.map_path = map_path;
  1362. m_level.map_name = map_path;
  1363. if (m_campaign_manager && set_skirmish_context) {
  1364. m_campaign_manager->set_skirmish_context(map_path);
  1365. }
  1366. if (!m_runtime.victory_state.isEmpty()) {
  1367. m_runtime.victory_state = "";
  1368. emit victory_state_changed();
  1369. }
  1370. if (m_victoryService) {
  1371. m_victoryService->reset();
  1372. }
  1373. m_enemyTroopsDefeated = 0;
  1374. if (!m_runtime.initialized) {
  1375. ensure_initialized();
  1376. }
  1377. if (!m_world || !m_renderer || !m_camera) {
  1378. set_error("Cannot start skirmish: renderer not initialized");
  1379. return;
  1380. }
  1381. m_finalize_progress_after_overlay = false;
  1382. m_loading_overlay_active = true;
  1383. m_runtime.loading = true;
  1384. emit is_loading_changed();
  1385. if (m_loading_progress_tracker) {
  1386. m_loading_progress_tracker->start_loading();
  1387. }
  1388. QCoreApplication::processEvents(QEventLoop::AllEvents);
  1389. QTimer::singleShot(50, this, [this, map_path, playerConfigs]() {
  1390. perform_skirmish_load(map_path, playerConfigs);
  1391. });
  1392. }
  1393. void GameEngine::perform_skirmish_load(const QString &map_path,
  1394. const QVariantList &playerConfigs) {
  1395. if (!(m_world && m_renderer && m_camera)) {
  1396. set_error("Cannot start skirmish: renderer not initialized");
  1397. m_runtime.loading = false;
  1398. emit is_loading_changed();
  1399. return;
  1400. }
  1401. if (m_hoverTracker) {
  1402. m_hoverTracker->update_hover(-1, -1, *m_world, *m_camera, 0, 0);
  1403. }
  1404. LevelOrchestrator orchestrator;
  1405. LevelOrchestrator::RendererRefs renderers{
  1406. m_renderer.get(), m_camera.get(), m_ground.get(), m_terrain.get(),
  1407. m_biome.get(), m_river.get(), m_road.get(), m_riverbank.get(),
  1408. m_bridge.get(), m_fog.get(), m_stone.get(), m_plant.get(),
  1409. m_pine.get(), m_olive.get(), m_firecamp.get(), m_rain.get()};
  1410. auto visibility_ready = [this]() {
  1411. m_runtime.visibility_version =
  1412. Game::Map::VisibilityService::instance().version();
  1413. m_runtime.visibility_update_accumulator = 0.0F;
  1414. };
  1415. auto owner_update = [this]() { emit owner_info_changed(); };
  1416. const bool allow_default_player_barracks =
  1417. !m_campaign_manager ||
  1418. !m_campaign_manager->current_mission_context().is_campaign();
  1419. auto load_result = orchestrator.load_skirmish(
  1420. map_path, playerConfigs, m_selected_player_id, *m_world, renderers,
  1421. m_level, m_entity_cache, m_victoryService.get(), m_minimap_manager.get(),
  1422. visibility_ready, owner_update, allow_default_player_barracks,
  1423. m_loading_progress_tracker.get());
  1424. if (load_result.updated_player_id != m_selected_player_id) {
  1425. m_selected_player_id = load_result.updated_player_id;
  1426. emit selected_player_id_changed();
  1427. }
  1428. if (!load_result.success) {
  1429. set_error(load_result.error_message);
  1430. m_runtime.loading = false;
  1431. m_loading_overlay_active = false;
  1432. m_loading_overlay_wait_for_first_frame = false;
  1433. m_finalize_progress_after_overlay = false;
  1434. m_show_objectives_after_loading = false;
  1435. emit is_loading_changed();
  1436. return;
  1437. }
  1438. m_runtime.local_owner_id = load_result.updated_player_id;
  1439. apply_mission_setup();
  1440. configure_mission_victory_conditions();
  1441. configure_rain_system();
  1442. finalize_skirmish_load();
  1443. }
  1444. void GameEngine::apply_mission_setup() {
  1445. if (!m_world || !m_campaign_manager) {
  1446. return;
  1447. }
  1448. if (!m_campaign_manager->current_mission_context().is_campaign()) {
  1449. return;
  1450. }
  1451. if (!m_campaign_manager->current_mission_definition().has_value()) {
  1452. return;
  1453. }
  1454. auto reg = Game::Map::MapTransformer::getFactoryRegistry();
  1455. if (!reg) {
  1456. qWarning() << "Mission setup skipped: unit factory registry missing";
  1457. return;
  1458. }
  1459. const auto &mission = *m_campaign_manager->current_mission_definition();
  1460. auto &owner_registry = Game::Systems::OwnerRegistry::instance();
  1461. auto &nation_registry = Game::Systems::NationRegistry::instance();
  1462. Game::Map::MapDefinition map_def;
  1463. QString map_error;
  1464. const QString resolved_map_path =
  1465. Utils::Resources::resolveResourcePath(m_level.map_path);
  1466. bool map_loaded = Game::Map::MapLoader::loadFromJsonFile(resolved_map_path,
  1467. map_def, &map_error);
  1468. if (!map_loaded) {
  1469. qWarning() << "Mission setup: failed to load map definition for"
  1470. << m_level.map_path << "resolved to" << resolved_map_path << "-"
  1471. << map_error;
  1472. }
  1473. bool has_map_spawns = true;
  1474. if (map_loaded) {
  1475. has_map_spawns = !map_def.spawns.empty();
  1476. }
  1477. bool has_mission_spawns = !mission.player_setup.starting_units.empty() ||
  1478. !mission.player_setup.starting_buildings.empty();
  1479. for (const auto &ai_setup : mission.ai_setups) {
  1480. if (!ai_setup.starting_units.empty() ||
  1481. !ai_setup.starting_buildings.empty()) {
  1482. has_mission_spawns = true;
  1483. break;
  1484. }
  1485. }
  1486. if (has_mission_spawns && !has_map_spawns) {
  1487. std::vector<Engine::Core::EntityID> to_remove;
  1488. auto entities = m_world->get_entities_with<Engine::Core::UnitComponent>();
  1489. to_remove.reserve(entities.size());
  1490. for (auto *entity : entities) {
  1491. if (entity != nullptr) {
  1492. to_remove.push_back(entity->get_id());
  1493. }
  1494. }
  1495. for (const auto id : to_remove) {
  1496. m_world->destroy_entity(id);
  1497. }
  1498. }
  1499. auto resolve_nation_id = [&nation_registry](const QString &nation_str) {
  1500. const auto parsed =
  1501. Game::Systems::nation_id_from_string(nation_str.toStdString());
  1502. if (parsed.has_value()) {
  1503. return parsed.value();
  1504. }
  1505. return nation_registry.default_nation_id();
  1506. };
  1507. auto mission_position_to_world = [&](const Game::Mission::Position &pos) {
  1508. float world_x = pos.x;
  1509. float world_z = pos.z;
  1510. if (map_loaded && map_def.coordSystem == Game::Map::CoordSystem::Grid) {
  1511. const float tile = std::max(0.0001F, map_def.grid.tile_size);
  1512. world_x = (pos.x - (map_def.grid.width * 0.5F - 0.5F)) * tile;
  1513. world_z = (pos.z - (map_def.grid.height * 0.5F - 0.5F)) * tile;
  1514. } else if (!map_loaded) {
  1515. const float tile = std::max(0.0001F, m_level.tile_size);
  1516. world_x = (pos.x - (m_level.grid_width * 0.5F - 0.5F)) * tile;
  1517. world_z = (pos.z - (m_level.grid_height * 0.5F - 0.5F)) * tile;
  1518. }
  1519. return QVector3D(world_x, 0.0F, world_z);
  1520. };
  1521. auto apply_team_color = [&](Engine::Core::Entity *entity, int owner_id) {
  1522. if (entity == nullptr) {
  1523. return;
  1524. }
  1525. auto *renderable =
  1526. entity->get_component<Engine::Core::RenderableComponent>();
  1527. if (renderable == nullptr) {
  1528. return;
  1529. }
  1530. const QVector3D team_color = Game::Visuals::team_colorForOwner(owner_id);
  1531. renderable->color[0] = team_color.x();
  1532. renderable->color[1] = team_color.y();
  1533. renderable->color[2] = team_color.z();
  1534. };
  1535. auto parse_color = [](const QString &color_name,
  1536. std::array<float, 3> &out) -> bool {
  1537. if (color_name.isEmpty()) {
  1538. return false;
  1539. }
  1540. const QString trimmed = color_name.trimmed();
  1541. if (trimmed.startsWith('#') && trimmed.length() == 7) {
  1542. bool ok = false;
  1543. int r = trimmed.mid(1, 2).toInt(&ok, 16);
  1544. if (!ok) {
  1545. return false;
  1546. }
  1547. int g = trimmed.mid(3, 2).toInt(&ok, 16);
  1548. if (!ok) {
  1549. return false;
  1550. }
  1551. int b = trimmed.mid(5, 2).toInt(&ok, 16);
  1552. if (!ok) {
  1553. return false;
  1554. }
  1555. constexpr float scale = 255.0F;
  1556. out = {r / scale, g / scale, b / scale};
  1557. return true;
  1558. }
  1559. const QString lowered = trimmed.toLower();
  1560. if (lowered == "red") {
  1561. out = {1.00F, 0.30F, 0.30F};
  1562. return true;
  1563. }
  1564. if (lowered == "brown") {
  1565. out = {0.55F, 0.36F, 0.18F};
  1566. return true;
  1567. }
  1568. if (lowered == "blue") {
  1569. out = {0.20F, 0.55F, 1.00F};
  1570. return true;
  1571. }
  1572. if (lowered == "green") {
  1573. out = {0.20F, 0.80F, 0.40F};
  1574. return true;
  1575. }
  1576. if (lowered == "yellow") {
  1577. out = {1.00F, 0.80F, 0.20F};
  1578. return true;
  1579. }
  1580. if (lowered == "orange") {
  1581. out = {0.95F, 0.55F, 0.10F};
  1582. return true;
  1583. }
  1584. if (lowered == "white") {
  1585. out = {0.95F, 0.95F, 0.95F};
  1586. return true;
  1587. }
  1588. if (lowered == "black") {
  1589. out = {0.15F, 0.15F, 0.15F};
  1590. return true;
  1591. }
  1592. return false;
  1593. };
  1594. auto apply_owner_color = [&](int owner_id, const QString &color_name) {
  1595. std::array<float, 3> color;
  1596. if (!parse_color(color_name, color)) {
  1597. return;
  1598. }
  1599. owner_registry.set_owner_color(owner_id, color[0], color[1], color[2]);
  1600. };
  1601. auto spawn_units_for_owner =
  1602. [&](int owner_id, const Game::Systems::NationID nation_id,
  1603. const std::vector<Game::Mission::UnitSetup> &units) {
  1604. const bool ai_controlled = owner_registry.is_ai(owner_id);
  1605. for (const auto &unit_setup : units) {
  1606. const auto spawn_type =
  1607. Game::Units::spawn_typeFromString(unit_setup.type.toStdString());
  1608. if (!spawn_type.has_value()) {
  1609. qWarning() << "Mission setup: unknown unit type" << unit_setup.type;
  1610. continue;
  1611. }
  1612. const int count = std::max(1, unit_setup.count);
  1613. const int grid =
  1614. static_cast<int>(std::ceil(std::sqrt(static_cast<float>(count))));
  1615. const QVector3D base_pos =
  1616. mission_position_to_world(unit_setup.position);
  1617. float base_tile_size = m_level.tile_size;
  1618. if (map_loaded &&
  1619. map_def.coordSystem == Game::Map::CoordSystem::Grid) {
  1620. base_tile_size = map_def.grid.tile_size;
  1621. }
  1622. const float spacing = std::max(0.5F, base_tile_size * 1.2F);
  1623. for (int i = 0; i < count; ++i) {
  1624. const int row = i / grid;
  1625. const int col = i % grid;
  1626. const float offset_x = (float(col) - (grid - 1) * 0.5F) * spacing;
  1627. const float offset_z = (float(row) - (grid - 1) * 0.5F) * spacing;
  1628. const QVector3D pos = QVector3D(
  1629. base_pos.x() + offset_x, base_pos.y(), base_pos.z() + offset_z);
  1630. Game::Units::SpawnParams sp;
  1631. sp.position = pos;
  1632. sp.player_id = owner_id;
  1633. sp.spawn_type = spawn_type.value();
  1634. sp.ai_controlled = ai_controlled;
  1635. sp.nation_id = nation_id;
  1636. auto unit = reg->create(sp.spawn_type, *m_world, sp);
  1637. if (!unit) {
  1638. qWarning() << "Mission setup: failed to spawn unit"
  1639. << unit_setup.type << "for owner" << owner_id;
  1640. continue;
  1641. }
  1642. apply_team_color(m_world->get_entity(unit->id()), owner_id);
  1643. }
  1644. }
  1645. };
  1646. auto spawn_buildings_for_owner =
  1647. [&](int owner_id, const Game::Systems::NationID nation_id,
  1648. const std::vector<Game::Mission::BuildingSetup> &buildings) {
  1649. const bool ai_controlled = owner_registry.is_ai(owner_id);
  1650. for (const auto &building_setup : buildings) {
  1651. const auto spawn_type = Game::Units::spawn_typeFromString(
  1652. building_setup.type.toStdString());
  1653. if (!spawn_type.has_value()) {
  1654. qWarning() << "Mission setup: unknown building type"
  1655. << building_setup.type;
  1656. continue;
  1657. }
  1658. const QVector3D pos =
  1659. mission_position_to_world(building_setup.position);
  1660. Game::Units::SpawnParams sp;
  1661. sp.position = pos;
  1662. sp.player_id = owner_id;
  1663. sp.spawn_type = spawn_type.value();
  1664. sp.ai_controlled = ai_controlled;
  1665. sp.nation_id = nation_id;
  1666. sp.max_population = building_setup.max_population;
  1667. auto unit = reg->create(sp.spawn_type, *m_world, sp);
  1668. if (!unit) {
  1669. qWarning() << "Mission setup: failed to spawn building"
  1670. << building_setup.type << "for owner" << owner_id;
  1671. continue;
  1672. }
  1673. apply_team_color(m_world->get_entity(unit->id()), owner_id);
  1674. }
  1675. };
  1676. const int local_owner_id = m_runtime.local_owner_id;
  1677. if (owner_registry.get_owner_type(local_owner_id) ==
  1678. Game::Systems::OwnerType::Neutral) {
  1679. owner_registry.register_owner_with_id(
  1680. local_owner_id, Game::Systems::OwnerType::Player,
  1681. "Player " + std::to_string(local_owner_id));
  1682. }
  1683. owner_registry.set_owner_team(local_owner_id, 0);
  1684. const auto player_nation_id = resolve_nation_id(mission.player_setup.nation);
  1685. nation_registry.set_player_nation(local_owner_id, player_nation_id);
  1686. apply_owner_color(local_owner_id, mission.player_setup.color);
  1687. spawn_units_for_owner(local_owner_id, player_nation_id,
  1688. mission.player_setup.starting_units);
  1689. spawn_buildings_for_owner(local_owner_id, player_nation_id,
  1690. mission.player_setup.starting_buildings);
  1691. int ai_owner_id = 2;
  1692. int default_team_id = 1;
  1693. for (const auto &ai_setup : mission.ai_setups) {
  1694. if (owner_registry.get_owner_type(ai_owner_id) ==
  1695. Game::Systems::OwnerType::Neutral) {
  1696. owner_registry.register_owner_with_id(
  1697. ai_owner_id, Game::Systems::OwnerType::AI,
  1698. "AI Player " + std::to_string(ai_owner_id));
  1699. }
  1700. int team_id;
  1701. if (ai_setup.team_id.has_value()) {
  1702. team_id = ai_setup.team_id.value();
  1703. } else {
  1704. team_id = default_team_id++;
  1705. }
  1706. owner_registry.set_owner_team(ai_owner_id, team_id);
  1707. const auto ai_nation_id = resolve_nation_id(ai_setup.nation);
  1708. nation_registry.set_player_nation(ai_owner_id, ai_nation_id);
  1709. apply_owner_color(ai_owner_id, ai_setup.color);
  1710. spawn_units_for_owner(ai_owner_id, ai_nation_id, ai_setup.starting_units);
  1711. spawn_buildings_for_owner(ai_owner_id, ai_nation_id,
  1712. ai_setup.starting_buildings);
  1713. ai_owner_id++;
  1714. }
  1715. auto entities = m_world->get_entities_with<Engine::Core::UnitComponent>();
  1716. for (auto *entity : entities) {
  1717. if (entity == nullptr) {
  1718. continue;
  1719. }
  1720. auto *unit = entity->get_component<Engine::Core::UnitComponent>();
  1721. if (unit == nullptr) {
  1722. continue;
  1723. }
  1724. apply_team_color(entity, unit->owner_id);
  1725. }
  1726. if (auto *ai_system = m_world->get_system<Game::Systems::AISystem>()) {
  1727. ai_system->reinitialize();
  1728. int ai_id = 2;
  1729. for (const auto &ai_setup : mission.ai_setups) {
  1730. Game::Systems::AI::AIStrategy strategy =
  1731. Game::Systems::AI::AIStrategy::Balanced;
  1732. if (ai_setup.strategy.has_value()) {
  1733. strategy = Game::Systems::AI::AIStrategyFactory::parse_strategy(
  1734. ai_setup.strategy.value());
  1735. }
  1736. ai_system->set_ai_strategy(
  1737. ai_id, strategy, ai_setup.personality.aggression,
  1738. ai_setup.personality.defense, ai_setup.personality.harassment);
  1739. ai_id++;
  1740. }
  1741. }
  1742. int prev_selected_player = m_selected_player_id;
  1743. GameStateRestorer::rebuild_registries_after_load(
  1744. m_world.get(), m_selected_player_id, m_level, m_runtime.local_owner_id);
  1745. GameStateRestorer::rebuild_entity_cache(m_world.get(), m_entity_cache,
  1746. m_runtime.local_owner_id);
  1747. if (m_selected_player_id != prev_selected_player) {
  1748. emit selected_player_id_changed();
  1749. }
  1750. emit troop_count_changed();
  1751. emit owner_info_changed();
  1752. }
  1753. void GameEngine::configure_mission_victory_conditions() {
  1754. if (!m_campaign_manager || !m_victoryService) {
  1755. return;
  1756. }
  1757. m_campaign_manager->configure_mission_victory_conditions(
  1758. m_victoryService.get(), m_runtime.local_owner_id);
  1759. m_victoryService->set_victory_callback([this](const QString &state) {
  1760. if (m_runtime.victory_state != state) {
  1761. m_runtime.victory_state = state;
  1762. emit victory_state_changed();
  1763. if (state == "victory" &&
  1764. !m_campaign_manager->current_campaign_id().isEmpty()) {
  1765. mark_current_mission_completed();
  1766. }
  1767. }
  1768. });
  1769. }
  1770. void GameEngine::configure_rain_system() {
  1771. if (m_rainManager) {
  1772. m_rainManager->configure(m_level.rain, m_level.biome_seed);
  1773. }
  1774. if (!m_rain) {
  1775. return;
  1776. }
  1777. const float world_width =
  1778. static_cast<float>(m_level.grid_width) * m_level.tile_size;
  1779. const float world_height =
  1780. static_cast<float>(m_level.grid_height) * m_level.tile_size;
  1781. m_rain->configure(world_width, world_height, m_level.biome_seed,
  1782. m_level.rain.type);
  1783. m_rain->set_enabled(m_level.rain.enabled);
  1784. m_rain->set_wind_strength(m_level.rain.wind_strength);
  1785. const float initial_intensity =
  1786. m_rainManager ? m_rainManager->get_intensity()
  1787. : (m_level.rain.enabled ? m_level.rain.intensity : 0.0F);
  1788. m_rain->set_intensity(initial_intensity);
  1789. }
  1790. void GameEngine::finalize_skirmish_load() {
  1791. m_runtime.loading = false;
  1792. m_loading_overlay_wait_for_first_frame = true;
  1793. m_loading_overlay_frames_remaining = 5;
  1794. m_loading_overlay_min_duration_ms = 1000;
  1795. m_loading_overlay_timer.restart();
  1796. m_finalize_progress_after_overlay = true;
  1797. m_show_objectives_after_loading = is_campaign_mission();
  1798. emit is_loading_changed();
  1799. GameStateRestorer::rebuild_entity_cache(m_world.get(), m_entity_cache,
  1800. m_runtime.local_owner_id);
  1801. emit troop_count_changed();
  1802. m_ambient_state_manager = std::make_unique<AmbientStateManager>();
  1803. Engine::Core::EventManager::instance().publish(
  1804. Engine::Core::AmbientStateChangedEvent(
  1805. Engine::Core::AmbientState::PEACEFUL,
  1806. Engine::Core::AmbientState::PEACEFUL));
  1807. if (m_input_handler) {
  1808. m_input_handler->set_spectator_mode(m_level.is_spectator_mode);
  1809. }
  1810. emit owner_info_changed();
  1811. emit spectator_mode_changed();
  1812. }
  1813. void GameEngine::open_settings() {
  1814. if (m_saveLoadService) {
  1815. m_saveLoadService->open_settings();
  1816. }
  1817. }
  1818. void GameEngine::load_save() { load_from_slot("savegame"); }
  1819. void GameEngine::save_game(const QString &filename) {
  1820. save_to_slot(filename, filename);
  1821. }
  1822. void GameEngine::save_game_to_slot(const QString &slotName) {
  1823. save_to_slot(slotName, slotName);
  1824. }
  1825. void GameEngine::load_game_from_slot(const QString &slotName) {
  1826. load_from_slot(slotName);
  1827. }
  1828. auto GameEngine::load_from_slot(const QString &slot) -> bool {
  1829. if (!m_saveLoadService || !m_world) {
  1830. set_error("Load: not initialized");
  1831. return false;
  1832. }
  1833. m_finalize_progress_after_overlay = false;
  1834. m_loading_overlay_active = true;
  1835. m_runtime.loading = true;
  1836. emit is_loading_changed();
  1837. if (!m_saveLoadService->load_game_from_slot(*m_world, slot)) {
  1838. set_error(m_saveLoadService->get_last_error());
  1839. m_runtime.loading = false;
  1840. m_loading_overlay_active = false;
  1841. m_loading_overlay_wait_for_first_frame = false;
  1842. m_finalize_progress_after_overlay = false;
  1843. m_show_objectives_after_loading = false;
  1844. emit is_loading_changed();
  1845. return false;
  1846. }
  1847. const QJsonObject meta = m_saveLoadService->get_last_metadata();
  1848. if (m_campaign_manager && meta.contains("mission_mode")) {
  1849. Game::Mission::MissionContext mission_context;
  1850. mission_context.mode = meta["mission_mode"].toString();
  1851. mission_context.campaign_id = meta["mission_campaign_id"].toString();
  1852. mission_context.mission_id = meta["mission_id"].toString();
  1853. mission_context.difficulty = meta["mission_difficulty"].toString();
  1854. m_campaign_manager->set_mission_context(mission_context);
  1855. }
  1856. Game::Systems::GameStateSerializer::restore_level_from_metadata(meta,
  1857. m_level);
  1858. Game::Systems::GameStateSerializer::restore_camera_from_metadata(
  1859. meta, m_camera.get(), m_viewport.width, m_viewport.height);
  1860. Game::Systems::RuntimeSnapshot runtime_snap = to_runtime_snapshot();
  1861. Game::Systems::GameStateSerializer::restore_runtime_from_metadata(
  1862. meta, runtime_snap);
  1863. apply_runtime_snapshot(runtime_snap);
  1864. GameStateRestorer::RendererRefs renderers{
  1865. m_renderer.get(), m_camera.get(), m_ground.get(), m_terrain.get(),
  1866. m_biome.get(), m_river.get(), m_road.get(), m_riverbank.get(),
  1867. m_bridge.get(), m_fog.get(), m_stone.get(), m_plant.get(),
  1868. m_pine.get(), m_olive.get(), m_firecamp.get(), m_rain.get()};
  1869. GameStateRestorer::restore_environment_from_metadata(
  1870. meta, m_world.get(), renderers, m_level, m_runtime.local_owner_id,
  1871. m_viewport);
  1872. auto unit_reg = std::make_shared<Game::Units::UnitFactoryRegistry>();
  1873. Game::Units::registerBuiltInUnits(*unit_reg);
  1874. Game::Map::MapTransformer::setFactoryRegistry(unit_reg);
  1875. qInfo() << "Factory registry reinitialized after loading saved game";
  1876. GameStateRestorer::rebuild_registries_after_load(
  1877. m_world.get(), m_selected_player_id, m_level, m_runtime.local_owner_id);
  1878. GameStateRestorer::rebuild_entity_cache(m_world.get(), m_entity_cache,
  1879. m_runtime.local_owner_id);
  1880. emit troop_count_changed();
  1881. if (auto *ai_system = m_world->get_system<Game::Systems::AISystem>()) {
  1882. qInfo() << "Reinitializing AI system after loading saved game";
  1883. ai_system->reinitialize();
  1884. }
  1885. if (m_victoryService) {
  1886. m_victoryService->configure(Game::Map::VictoryConfig(),
  1887. m_runtime.local_owner_id);
  1888. }
  1889. m_runtime.loading = false;
  1890. m_loading_overlay_wait_for_first_frame = true;
  1891. m_loading_overlay_frames_remaining = 5;
  1892. m_loading_overlay_min_duration_ms = 1000;
  1893. m_loading_overlay_timer.restart();
  1894. m_finalize_progress_after_overlay = true;
  1895. emit is_loading_changed();
  1896. qInfo() << "Game load complete, victory/defeat checks re-enabled";
  1897. emit selected_units_changed();
  1898. emit owner_info_changed();
  1899. return true;
  1900. }
  1901. auto GameEngine::save_to_slot(const QString &slot,
  1902. const QString &title) -> bool {
  1903. if (!m_saveLoadService || !m_world) {
  1904. set_error("Save: not initialized");
  1905. return false;
  1906. }
  1907. Game::Systems::RuntimeSnapshot const runtime_snap = to_runtime_snapshot();
  1908. QJsonObject meta = Game::Systems::GameStateSerializer::build_metadata(
  1909. *m_world, m_camera.get(), m_level, runtime_snap);
  1910. meta["title"] = title;
  1911. if (m_campaign_manager) {
  1912. const auto &mission_context = m_campaign_manager->current_mission_context();
  1913. meta["mission_mode"] = mission_context.mode;
  1914. meta["mission_campaign_id"] = mission_context.campaign_id;
  1915. meta["mission_id"] = mission_context.mission_id;
  1916. meta["mission_difficulty"] = mission_context.difficulty;
  1917. }
  1918. const QByteArray screenshot = capture_screenshot();
  1919. if (!m_saveLoadService->save_game_to_slot(
  1920. *m_world, slot, title, m_level.map_name, meta, screenshot)) {
  1921. set_error(m_saveLoadService->get_last_error());
  1922. return false;
  1923. }
  1924. emit save_slots_changed();
  1925. return true;
  1926. }
  1927. auto GameEngine::get_save_slots() const -> QVariantList {
  1928. if (!m_saveLoadService) {
  1929. qWarning() << "Cannot get save slots: service not initialized";
  1930. return {};
  1931. }
  1932. return m_saveLoadService->get_save_slots();
  1933. }
  1934. void GameEngine::refresh_save_slots() { emit save_slots_changed(); }
  1935. auto GameEngine::delete_save_slot(const QString &slotName) -> bool {
  1936. if (!m_saveLoadService) {
  1937. qWarning() << "Cannot delete save slot: service not initialized";
  1938. return false;
  1939. }
  1940. bool const success = m_saveLoadService->delete_save_slot(slotName);
  1941. if (!success) {
  1942. QString const error = m_saveLoadService->get_last_error();
  1943. qWarning() << "Failed to delete save slot:" << error;
  1944. set_error(error);
  1945. } else {
  1946. emit save_slots_changed();
  1947. }
  1948. return success;
  1949. }
  1950. auto GameEngine::to_runtime_snapshot() const -> Game::Systems::RuntimeSnapshot {
  1951. Game::Systems::RuntimeSnapshot snapshot;
  1952. snapshot.paused = m_runtime.paused;
  1953. snapshot.time_scale = m_runtime.time_scale;
  1954. snapshot.local_owner_id = m_runtime.local_owner_id;
  1955. snapshot.victory_state = m_runtime.victory_state;
  1956. snapshot.cursor_mode = static_cast<int>(m_runtime.cursor_mode);
  1957. snapshot.selected_player_id = m_selected_player_id;
  1958. snapshot.follow_selection = m_followSelectionEnabled;
  1959. return snapshot;
  1960. }
  1961. void GameEngine::apply_runtime_snapshot(
  1962. const Game::Systems::RuntimeSnapshot &snapshot) {
  1963. m_runtime.paused = snapshot.paused;
  1964. m_runtime.time_scale = snapshot.time_scale;
  1965. m_runtime.local_owner_id = snapshot.local_owner_id;
  1966. m_runtime.victory_state = snapshot.victory_state;
  1967. m_selected_player_id = snapshot.selected_player_id;
  1968. m_followSelectionEnabled = snapshot.follow_selection;
  1969. m_runtime.cursor_mode = static_cast<CursorMode>(snapshot.cursor_mode);
  1970. if (m_cursor_manager) {
  1971. m_cursor_manager->set_mode(m_runtime.cursor_mode);
  1972. }
  1973. }
  1974. auto GameEngine::capture_screenshot() const -> QByteArray { return {}; }
  1975. void GameEngine::exit_game() {
  1976. if (m_saveLoadService) {
  1977. m_saveLoadService->exit_game();
  1978. }
  1979. }
  1980. auto GameEngine::get_owner_info() const -> QVariantList {
  1981. QVariantList result;
  1982. const auto &owner_registry = Game::Systems::OwnerRegistry::instance();
  1983. const auto &owners = owner_registry.get_all_owners();
  1984. for (const auto &owner : owners) {
  1985. QVariantMap owner_map;
  1986. owner_map["id"] = owner.owner_id;
  1987. owner_map["name"] = QString::fromStdString(owner.name);
  1988. owner_map["team_id"] = owner.team_id;
  1989. QString type_str;
  1990. switch (owner.type) {
  1991. case Game::Systems::OwnerType::Player:
  1992. type_str = "Player";
  1993. break;
  1994. case Game::Systems::OwnerType::AI:
  1995. type_str = "AI";
  1996. break;
  1997. case Game::Systems::OwnerType::Neutral:
  1998. type_str = "Neutral";
  1999. break;
  2000. }
  2001. owner_map["type"] = type_str;
  2002. owner_map["isLocal"] = (owner.owner_id == m_runtime.local_owner_id);
  2003. result.append(owner_map);
  2004. }
  2005. return result;
  2006. }
  2007. void GameEngine::get_selected_unit_ids(
  2008. std::vector<Engine::Core::EntityID> &out) const {
  2009. out.clear();
  2010. if (!m_selectionController) {
  2011. return;
  2012. }
  2013. m_selectionController->get_selected_unit_ids(out);
  2014. }
  2015. auto GameEngine::get_unit_type_key(Engine::Core::EntityID id,
  2016. QString &type_key) const -> bool {
  2017. type_key.clear();
  2018. if (!m_world) {
  2019. return false;
  2020. }
  2021. auto *e = m_world->get_entity(id);
  2022. if (e == nullptr) {
  2023. return false;
  2024. }
  2025. if (auto *u = e->get_component<Engine::Core::UnitComponent>()) {
  2026. type_key = Game::Units::spawn_typeToQString(u->spawn_type);
  2027. return true;
  2028. }
  2029. return false;
  2030. }
  2031. auto GameEngine::get_unit_info(Engine::Core::EntityID id, QString &name,
  2032. int &health, int &max_health, bool &is_building,
  2033. bool &alive, QString &nation) const -> bool {
  2034. if (!m_world) {
  2035. return false;
  2036. }
  2037. auto *e = m_world->get_entity(id);
  2038. if (e == nullptr) {
  2039. return false;
  2040. }
  2041. is_building = e->has_component<Engine::Core::BuildingComponent>();
  2042. if (auto *u = e->get_component<Engine::Core::UnitComponent>()) {
  2043. auto troop_type_opt = Game::Units::spawn_typeToTroopType(u->spawn_type);
  2044. if (troop_type_opt.has_value()) {
  2045. auto profile = Game::Systems::TroopProfileService::instance().get_profile(
  2046. u->nation_id, *troop_type_opt);
  2047. name = QString::fromStdString(profile.display_name);
  2048. } else {
  2049. name = QString::fromStdString(
  2050. Game::Units::spawn_typeToString(u->spawn_type));
  2051. }
  2052. health = u->health;
  2053. max_health = u->max_health;
  2054. alive = (u->health > 0);
  2055. nation = Game::Systems::nation_id_to_qstring(u->nation_id);
  2056. return true;
  2057. }
  2058. name = QStringLiteral("Entity");
  2059. health = max_health = 0;
  2060. alive = true;
  2061. nation = QStringLiteral("");
  2062. return true;
  2063. }
  2064. auto GameEngine::get_unit_stamina_info(Engine::Core::EntityID id,
  2065. float &stamina_ratio, bool &is_running,
  2066. bool &can_run) const -> bool {
  2067. stamina_ratio = 1.0F;
  2068. is_running = false;
  2069. can_run = false;
  2070. if (!m_world) {
  2071. return false;
  2072. }
  2073. auto *e = m_world->get_entity(id);
  2074. if (e == nullptr) {
  2075. return false;
  2076. }
  2077. auto *unit = e->get_component<Engine::Core::UnitComponent>();
  2078. if (unit == nullptr) {
  2079. return false;
  2080. }
  2081. can_run = Game::Units::can_use_run_mode(unit->spawn_type);
  2082. auto *stamina = e->get_component<Engine::Core::StaminaComponent>();
  2083. if (stamina != nullptr) {
  2084. stamina_ratio = stamina->get_stamina_ratio();
  2085. is_running = stamina->is_running;
  2086. }
  2087. return true;
  2088. }
  2089. void GameEngine::on_unit_spawned(const Engine::Core::UnitSpawnedEvent &event) {
  2090. auto &owners = Game::Systems::OwnerRegistry::instance();
  2091. if (event.owner_id == m_runtime.local_owner_id) {
  2092. if (event.spawn_type == Game::Units::SpawnType::Barracks) {
  2093. m_entity_cache.player_barracks_alive = true;
  2094. } else {
  2095. int const production_cost =
  2096. Game::Units::TroopConfig::instance().getProductionCost(
  2097. event.spawn_type);
  2098. m_entity_cache.player_troop_count += production_cost;
  2099. }
  2100. } else if (owners.is_ai(event.owner_id)) {
  2101. if (event.spawn_type == Game::Units::SpawnType::Barracks) {
  2102. m_entity_cache.enemy_barracks_count++;
  2103. m_entity_cache.enemy_barracks_alive = true;
  2104. }
  2105. }
  2106. auto emit_if_changed = [&] {
  2107. if (m_entity_cache.player_troop_count != m_runtime.last_troop_count) {
  2108. m_runtime.last_troop_count = m_entity_cache.player_troop_count;
  2109. emit troop_count_changed();
  2110. }
  2111. };
  2112. emit_if_changed();
  2113. }
  2114. void GameEngine::on_unit_died(const Engine::Core::UnitDiedEvent &event) {
  2115. auto &owners = Game::Systems::OwnerRegistry::instance();
  2116. if (event.owner_id == m_runtime.local_owner_id) {
  2117. if (event.spawn_type == Game::Units::SpawnType::Barracks) {
  2118. m_entity_cache.player_barracks_alive = false;
  2119. } else {
  2120. int const production_cost =
  2121. Game::Units::TroopConfig::instance().getProductionCost(
  2122. event.spawn_type);
  2123. m_entity_cache.player_troop_count -= production_cost;
  2124. m_entity_cache.player_troop_count =
  2125. std::max(0, m_entity_cache.player_troop_count);
  2126. }
  2127. } else if (owners.is_ai(event.owner_id)) {
  2128. if (event.spawn_type == Game::Units::SpawnType::Barracks) {
  2129. m_entity_cache.enemy_barracks_count--;
  2130. m_entity_cache.enemy_barracks_count =
  2131. std::max(0, m_entity_cache.enemy_barracks_count);
  2132. m_entity_cache.enemy_barracks_alive =
  2133. (m_entity_cache.enemy_barracks_count > 0);
  2134. }
  2135. }
  2136. }
  2137. auto GameEngine::minimap_image() const -> QImage {
  2138. if (m_minimap_manager) {
  2139. return m_minimap_manager->get_image();
  2140. }
  2141. return QImage();
  2142. }
  2143. auto GameEngine::generate_map_preview(const QString &map_path,
  2144. const QVariantList &player_configs) const
  2145. -> QImage {
  2146. Game::Map::Minimap::MapPreviewGenerator generator;
  2147. return generator.generate_preview(map_path, player_configs);
  2148. }
  2149. float GameEngine::loading_progress() const {
  2150. if (m_loading_progress_tracker) {
  2151. return m_loading_progress_tracker->progress();
  2152. }
  2153. return 0.0F;
  2154. }
  2155. QString GameEngine::loading_stage_text() const {
  2156. if (m_loading_progress_tracker) {
  2157. auto stage = m_loading_progress_tracker->current_stage();
  2158. auto stage_name = m_loading_progress_tracker->stage_name(stage);
  2159. auto detail = m_loading_progress_tracker->current_detail();
  2160. if (!detail.isEmpty()) {
  2161. return stage_name + " - " + detail;
  2162. }
  2163. return stage_name;
  2164. }
  2165. return QString();
  2166. }