game_engine.cpp 74 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377
  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 "AudioEventHandler.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 "core/system.h"
  13. #include "game/audio/AudioSystem.h"
  14. #include "game/units/spawn_type.h"
  15. #include "game/units/troop_type.h"
  16. #include <QBuffer>
  17. #include <QCoreApplication>
  18. #include <QCursor>
  19. #include <QDebug>
  20. #include <QImage>
  21. #include <QOpenGLContext>
  22. #include <QPainter>
  23. #include <QQuickWindow>
  24. #include <QSize>
  25. #include <QVariant>
  26. #include <QVariantMap>
  27. #include <memory>
  28. #include <optional>
  29. #include <qbuffer.h>
  30. #include <qcoreapplication.h>
  31. #include <qdir.h>
  32. #include <qevent.h>
  33. #include <qglobal.h>
  34. #include <qimage.h>
  35. #include <qjsonobject.h>
  36. #include <qnamespace.h>
  37. #include <qobject.h>
  38. #include <qobjectdefs.h>
  39. #include <qpoint.h>
  40. #include <qsize.h>
  41. #include <qstringliteral.h>
  42. #include <qstringview.h>
  43. #include <qtmetamacros.h>
  44. #include <qvectornd.h>
  45. #include <unordered_set>
  46. #include "../models/selected_units_model.h"
  47. #include "game/core/component.h"
  48. #include "game/core/event_manager.h"
  49. #include "game/core/world.h"
  50. #include "game/game_config.h"
  51. #include "game/map/environment.h"
  52. #include "game/map/level_loader.h"
  53. #include "game/map/map_catalog.h"
  54. #include "game/map/map_loader.h"
  55. #include "game/map/map_transformer.h"
  56. #include "game/map/minimap/minimap_generator.h"
  57. #include "game/map/minimap/unit_layer.h"
  58. #include "game/map/skirmish_loader.h"
  59. #include "game/map/terrain_service.h"
  60. #include "game/map/visibility_service.h"
  61. #include "game/map/world_bootstrap.h"
  62. #include "game/systems/ai_system.h"
  63. #include "game/systems/arrow_system.h"
  64. #include "game/systems/ballista_attack_system.h"
  65. #include "game/systems/building_collision_registry.h"
  66. #include "game/systems/camera_service.h"
  67. #include "game/systems/capture_system.h"
  68. #include "game/systems/catapult_attack_system.h"
  69. #include "game/systems/cleanup_system.h"
  70. #include "game/systems/combat_system.h"
  71. #include "game/systems/command_service.h"
  72. #include "game/systems/formation_planner.h"
  73. #include "game/systems/game_state_serializer.h"
  74. #include "game/systems/global_stats_registry.h"
  75. #include "game/systems/healing_system.h"
  76. #include "game/systems/movement_system.h"
  77. #include "game/systems/nation_id.h"
  78. #include "game/systems/nation_registry.h"
  79. #include "game/systems/owner_registry.h"
  80. #include "game/systems/patrol_system.h"
  81. #include "game/systems/picking_service.h"
  82. #include "game/systems/production_service.h"
  83. #include "game/systems/production_system.h"
  84. #include "game/systems/projectile_system.h"
  85. #include "game/systems/save_load_service.h"
  86. #include "game/systems/selection_system.h"
  87. #include "game/systems/terrain_alignment_system.h"
  88. #include "game/systems/troop_count_registry.h"
  89. #include "game/systems/victory_service.h"
  90. #include "game/units/factory.h"
  91. #include "game/units/troop_config.h"
  92. #include "render/geom/arrow.h"
  93. #include "render/geom/patrol_flags.h"
  94. #include "render/geom/stone.h"
  95. #include "render/gl/bootstrap.h"
  96. #include "render/gl/camera.h"
  97. #include "render/ground/biome_renderer.h"
  98. #include "render/ground/bridge_renderer.h"
  99. #include "render/ground/firecamp_renderer.h"
  100. #include "render/ground/fog_renderer.h"
  101. #include "render/ground/ground_renderer.h"
  102. #include "render/ground/olive_renderer.h"
  103. #include "render/ground/pine_renderer.h"
  104. #include "render/ground/plant_renderer.h"
  105. #include "render/ground/river_renderer.h"
  106. #include "render/ground/riverbank_renderer.h"
  107. #include "render/ground/road_renderer.h"
  108. #include "render/ground/stone_renderer.h"
  109. #include "render/ground/terrain_renderer.h"
  110. #include "render/scene_renderer.h"
  111. #include <QDir>
  112. #include <QFile>
  113. #include <QJsonArray>
  114. #include <QJsonDocument>
  115. #include <QJsonObject>
  116. #include <QSet>
  117. #include <algorithm>
  118. #include <cmath>
  119. #include <utility>
  120. #include <vector>
  121. GameEngine::GameEngine(QObject *parent)
  122. : QObject(parent),
  123. m_selectedUnitsModel(new SelectedUnitsModel(this, this)) {
  124. Game::Systems::NationRegistry::instance().initialize_defaults();
  125. Game::Systems::TroopCountRegistry::instance().initialize();
  126. Game::Systems::GlobalStatsRegistry::instance().initialize();
  127. m_world = std::make_unique<Engine::Core::World>();
  128. m_renderer = std::make_unique<Render::GL::Renderer>();
  129. m_camera = std::make_unique<Render::GL::Camera>();
  130. m_ground = std::make_unique<Render::GL::GroundRenderer>();
  131. m_terrain = std::make_unique<Render::GL::TerrainRenderer>();
  132. m_biome = std::make_unique<Render::GL::BiomeRenderer>();
  133. m_river = std::make_unique<Render::GL::RiverRenderer>();
  134. m_road = std::make_unique<Render::GL::RoadRenderer>();
  135. m_riverbank = std::make_unique<Render::GL::RiverbankRenderer>();
  136. m_bridge = std::make_unique<Render::GL::BridgeRenderer>();
  137. m_fog = std::make_unique<Render::GL::FogRenderer>();
  138. m_stone = std::make_unique<Render::GL::StoneRenderer>();
  139. m_plant = std::make_unique<Render::GL::PlantRenderer>();
  140. m_pine = std::make_unique<Render::GL::PineRenderer>();
  141. m_olive = std::make_unique<Render::GL::OliveRenderer>();
  142. m_firecamp = std::make_unique<Render::GL::FireCampRenderer>();
  143. m_passes = {m_ground.get(), m_terrain.get(), m_river.get(),
  144. m_road.get(), m_riverbank.get(), m_bridge.get(),
  145. m_biome.get(), m_stone.get(), m_plant.get(),
  146. m_pine.get(), m_olive.get(), m_firecamp.get(),
  147. m_fog.get()};
  148. std::unique_ptr<Engine::Core::System> arrow_sys =
  149. std::make_unique<Game::Systems::ArrowSystem>();
  150. m_world->add_system(std::move(arrow_sys));
  151. std::unique_ptr<Engine::Core::System> projectile_sys =
  152. std::make_unique<Game::Systems::ProjectileSystem>();
  153. m_world->add_system(std::move(projectile_sys));
  154. m_world->add_system(std::make_unique<Game::Systems::MovementSystem>());
  155. m_world->add_system(std::make_unique<Game::Systems::PatrolSystem>());
  156. m_world->add_system(std::make_unique<Game::Systems::CombatSystem>());
  157. m_world->add_system(std::make_unique<Game::Systems::CatapultAttackSystem>());
  158. m_world->add_system(std::make_unique<Game::Systems::BallistaAttackSystem>());
  159. m_world->add_system(std::make_unique<Game::Systems::HealingSystem>());
  160. m_world->add_system(std::make_unique<Game::Systems::CaptureSystem>());
  161. m_world->add_system(std::make_unique<Game::Systems::AISystem>());
  162. m_world->add_system(std::make_unique<Game::Systems::ProductionSystem>());
  163. m_world->add_system(
  164. std::make_unique<Game::Systems::TerrainAlignmentSystem>());
  165. m_world->add_system(std::make_unique<Game::Systems::CleanupSystem>());
  166. {
  167. std::unique_ptr<Engine::Core::System> sel_sys =
  168. std::make_unique<Game::Systems::SelectionSystem>();
  169. m_world->add_system(std::move(sel_sys));
  170. }
  171. m_pickingService = std::make_unique<Game::Systems::PickingService>();
  172. m_victoryService = std::make_unique<Game::Systems::VictoryService>();
  173. m_saveLoadService = std::make_unique<Game::Systems::SaveLoadService>();
  174. m_cameraService = std::make_unique<Game::Systems::CameraService>();
  175. auto *selection_system =
  176. m_world->get_system<Game::Systems::SelectionSystem>();
  177. m_selectionController = std::make_unique<Game::Systems::SelectionController>(
  178. m_world.get(), selection_system, m_pickingService.get());
  179. m_commandController = std::make_unique<App::Controllers::CommandController>(
  180. m_world.get(), selection_system, m_pickingService.get());
  181. m_cursorManager = std::make_unique<CursorManager>();
  182. m_hoverTracker = std::make_unique<HoverTracker>(m_pickingService.get());
  183. m_mapCatalog = std::make_unique<Game::Map::MapCatalog>(this);
  184. connect(m_mapCatalog.get(), &Game::Map::MapCatalog::map_loaded, this,
  185. [this](const QVariantMap &mapData) {
  186. m_available_maps.append(mapData);
  187. emit available_maps_changed();
  188. });
  189. connect(m_mapCatalog.get(), &Game::Map::MapCatalog::loading_changed, this,
  190. [this](bool loading) {
  191. m_maps_loading = loading;
  192. emit maps_loading_changed();
  193. });
  194. connect(m_mapCatalog.get(), &Game::Map::MapCatalog::all_maps_loaded, this,
  195. [this]() { emit available_maps_changed(); });
  196. if (AudioSystem::getInstance().initialize()) {
  197. qInfo() << "AudioSystem initialized successfully";
  198. load_audio_resources();
  199. } else {
  200. qWarning() << "Failed to initialize AudioSystem";
  201. }
  202. m_audio_systemProxy = std::make_unique<App::Models::AudioSystemProxy>(this);
  203. m_audioEventHandler =
  204. std::make_unique<Game::Audio::AudioEventHandler>(m_world.get());
  205. if (m_audioEventHandler->initialize()) {
  206. qInfo() << "AudioEventHandler initialized successfully";
  207. m_audioEventHandler->loadUnitVoiceMapping("archer", "archer_voice");
  208. m_audioEventHandler->loadUnitVoiceMapping("swordsman", "swordsman_voice");
  209. m_audioEventHandler->loadUnitVoiceMapping("swordsman", "swordsman_voice");
  210. m_audioEventHandler->loadUnitVoiceMapping("spearman", "spearman_voice");
  211. m_audioEventHandler->loadAmbientMusic(Engine::Core::AmbientState::PEACEFUL,
  212. "music_peaceful");
  213. m_audioEventHandler->loadAmbientMusic(Engine::Core::AmbientState::TENSE,
  214. "music_tense");
  215. m_audioEventHandler->loadAmbientMusic(Engine::Core::AmbientState::COMBAT,
  216. "music_combat");
  217. m_audioEventHandler->loadAmbientMusic(Engine::Core::AmbientState::VICTORY,
  218. "music_victory");
  219. m_audioEventHandler->loadAmbientMusic(Engine::Core::AmbientState::DEFEAT,
  220. "music_defeat");
  221. qInfo() << "Audio mappings configured";
  222. } else {
  223. qWarning() << "Failed to initialize AudioEventHandler";
  224. }
  225. connect(m_cursorManager.get(), &CursorManager::modeChanged, this,
  226. &GameEngine::cursor_mode_changed);
  227. connect(m_cursorManager.get(), &CursorManager::globalCursorChanged, this,
  228. &GameEngine::global_cursor_changed);
  229. connect(m_selectionController.get(),
  230. &Game::Systems::SelectionController::selection_changed, this,
  231. &GameEngine::selected_units_changed);
  232. connect(m_selectionController.get(),
  233. &Game::Systems::SelectionController::selection_changed, this,
  234. &GameEngine::sync_selection_flags);
  235. connect(
  236. m_selectionController.get(),
  237. &Game::Systems::SelectionController::selection_model_refresh_requested,
  238. this, &GameEngine::selected_units_data_changed);
  239. connect(m_commandController.get(),
  240. &App::Controllers::CommandController::attack_targetSelected,
  241. [this]() {
  242. if (auto *sel_sys =
  243. m_world->get_system<Game::Systems::SelectionSystem>()) {
  244. const auto &sel = sel_sys->get_selected_units();
  245. if (!sel.empty()) {
  246. auto *cam = m_camera.get();
  247. auto *picking = m_pickingService.get();
  248. if ((cam != nullptr) && (picking != nullptr)) {
  249. Engine::Core::EntityID const target_id =
  250. Game::Systems::PickingService::pick_unit_first(
  251. 0.0F, 0.0F, *m_world, *cam, m_viewport.width,
  252. m_viewport.height, 0);
  253. if (target_id != 0) {
  254. App::Controllers::ActionVFX::spawnAttackArrow(m_world.get(),
  255. target_id);
  256. }
  257. }
  258. }
  259. }
  260. });
  261. connect(m_commandController.get(),
  262. &App::Controllers::CommandController::troopLimitReached, [this]() {
  263. set_error(
  264. "Maximum troop limit reached. Cannot produce more units.");
  265. });
  266. connect(m_commandController.get(),
  267. &App::Controllers::CommandController::hold_modeChanged, this,
  268. &GameEngine::hold_mode_changed);
  269. connect(this, SIGNAL(selected_units_changed()), m_selectedUnitsModel,
  270. SLOT(refresh()));
  271. connect(this, SIGNAL(selected_units_data_changed()), m_selectedUnitsModel,
  272. SLOT(refresh()));
  273. emit selected_units_changed();
  274. m_unit_died_subscription =
  275. Engine::Core::ScopedEventSubscription<Engine::Core::UnitDiedEvent>(
  276. [this](const Engine::Core::UnitDiedEvent &e) {
  277. on_unit_died(e);
  278. if (e.owner_id != m_runtime.local_owner_id) {
  279. int const individuals_per_unit =
  280. Game::Units::TroopConfig::instance().getIndividualsPerUnit(
  281. e.spawn_type);
  282. m_enemyTroopsDefeated += individuals_per_unit;
  283. emit enemy_troops_defeated_changed();
  284. }
  285. });
  286. m_unit_spawned_subscription =
  287. Engine::Core::ScopedEventSubscription<Engine::Core::UnitSpawnedEvent>(
  288. [this](const Engine::Core::UnitSpawnedEvent &e) {
  289. on_unit_spawned(e);
  290. });
  291. }
  292. GameEngine::~GameEngine() {
  293. if (m_audioEventHandler) {
  294. m_audioEventHandler->shutdown();
  295. }
  296. AudioSystem::getInstance().shutdown();
  297. qInfo() << "AudioSystem shut down";
  298. }
  299. void GameEngine::cleanup_opengl_resources() {
  300. qInfo() << "Cleaning up OpenGL resources...";
  301. QOpenGLContext *context = QOpenGLContext::currentContext();
  302. const bool has_valid_context = (context != nullptr);
  303. if (!has_valid_context) {
  304. qInfo() << "No valid OpenGL context, skipping OpenGL cleanup";
  305. }
  306. if (m_renderer && has_valid_context) {
  307. m_renderer->shutdown();
  308. qInfo() << "Renderer shut down";
  309. }
  310. m_passes.clear();
  311. m_ground.reset();
  312. m_terrain.reset();
  313. m_biome.reset();
  314. m_river.reset();
  315. m_road.reset();
  316. m_riverbank.reset();
  317. m_bridge.reset();
  318. m_fog.reset();
  319. m_stone.reset();
  320. m_plant.reset();
  321. m_pine.reset();
  322. m_olive.reset();
  323. m_firecamp.reset();
  324. m_renderer.reset();
  325. m_resources.reset();
  326. qInfo() << "OpenGL resources cleaned up";
  327. }
  328. void GameEngine::on_map_clicked(qreal sx, qreal sy) {
  329. if (m_window == nullptr) {
  330. return;
  331. }
  332. ensure_initialized();
  333. if (m_selectionController && m_camera) {
  334. m_selectionController->on_click_select(sx, sy, false, m_viewport.width,
  335. m_viewport.height, m_camera.get(),
  336. m_runtime.local_owner_id);
  337. }
  338. }
  339. void GameEngine::on_right_click(qreal sx, qreal sy) {
  340. if (m_window == nullptr) {
  341. return;
  342. }
  343. ensure_initialized();
  344. auto *selection_system =
  345. m_world->get_system<Game::Systems::SelectionSystem>();
  346. if (selection_system == nullptr) {
  347. return;
  348. }
  349. if (m_cursorManager->mode() == CursorMode::Patrol ||
  350. m_cursorManager->mode() == CursorMode::Attack) {
  351. set_cursor_mode(CursorMode::Normal);
  352. return;
  353. }
  354. const auto &sel = selection_system->get_selected_units();
  355. if (sel.empty()) {
  356. return;
  357. }
  358. if (m_pickingService && m_camera) {
  359. Engine::Core::EntityID const target_id = m_pickingService->pick_unit_first(
  360. float(sx), float(sy), *m_world, *m_camera, m_viewport.width,
  361. m_viewport.height, 0);
  362. if (target_id != 0U) {
  363. auto *target_entity = m_world->get_entity(target_id);
  364. if (target_entity != nullptr) {
  365. auto *target_unit =
  366. target_entity->get_component<Engine::Core::UnitComponent>();
  367. if (target_unit != nullptr) {
  368. bool const is_enemy =
  369. (target_unit->owner_id != m_runtime.local_owner_id);
  370. if (is_enemy) {
  371. Game::Systems::CommandService::attack_target(*m_world, sel,
  372. target_id, true);
  373. return;
  374. }
  375. }
  376. }
  377. }
  378. }
  379. if (m_pickingService && m_camera) {
  380. QVector3D hit;
  381. if (m_pickingService->screen_to_ground(QPointF(sx, sy), *m_camera,
  382. m_viewport.width, m_viewport.height,
  383. hit)) {
  384. auto targets = Game::Systems::FormationPlanner::spreadFormation(
  385. int(sel.size()), hit,
  386. Game::GameConfig::instance().gameplay().formation_spacing_default);
  387. Game::Systems::CommandService::MoveOptions opts;
  388. opts.group_move = sel.size() > 1;
  389. Game::Systems::CommandService::moveUnits(*m_world, sel, targets, opts);
  390. }
  391. }
  392. }
  393. void GameEngine::on_attack_click(qreal sx, qreal sy) {
  394. if (m_window == nullptr) {
  395. return;
  396. }
  397. ensure_initialized();
  398. if (!m_commandController || !m_camera) {
  399. return;
  400. }
  401. auto result = m_commandController->onAttackClick(
  402. sx, sy, m_viewport.width, m_viewport.height, m_camera.get());
  403. auto *selection_system =
  404. m_world->get_system<Game::Systems::SelectionSystem>();
  405. if ((selection_system == nullptr) || !m_pickingService || !m_camera ||
  406. !m_world) {
  407. return;
  408. }
  409. const auto &selected = selection_system->get_selected_units();
  410. if (!selected.empty()) {
  411. Engine::Core::EntityID const target_id = m_pickingService->pick_unit_first(
  412. float(sx), float(sy), *m_world, *m_camera, m_viewport.width,
  413. m_viewport.height, 0);
  414. if (target_id != 0) {
  415. auto *target_entity = m_world->get_entity(target_id);
  416. if (target_entity != nullptr) {
  417. auto *target_unit =
  418. target_entity->get_component<Engine::Core::UnitComponent>();
  419. if ((target_unit != nullptr) &&
  420. target_unit->owner_id != m_runtime.local_owner_id) {
  421. App::Controllers::ActionVFX::spawnAttackArrow(m_world.get(),
  422. target_id);
  423. }
  424. }
  425. }
  426. }
  427. if (result.resetCursorToNormal) {
  428. set_cursor_mode(CursorMode::Normal);
  429. }
  430. }
  431. void GameEngine::reset_movement(Engine::Core::Entity *entity) {
  432. App::Utils::reset_movement(entity);
  433. }
  434. void GameEngine::on_stop_command() {
  435. if (!m_commandController) {
  436. return;
  437. }
  438. ensure_initialized();
  439. auto result = m_commandController->onStopCommand();
  440. if (result.resetCursorToNormal) {
  441. set_cursor_mode(CursorMode::Normal);
  442. }
  443. }
  444. void GameEngine::on_hold_command() {
  445. if (!m_commandController) {
  446. return;
  447. }
  448. ensure_initialized();
  449. auto result = m_commandController->onHoldCommand();
  450. if (result.resetCursorToNormal) {
  451. set_cursor_mode(CursorMode::Normal);
  452. }
  453. }
  454. auto GameEngine::any_selected_in_hold_mode() const -> bool {
  455. if (!m_commandController) {
  456. return false;
  457. }
  458. return m_commandController->anySelectedInHoldMode();
  459. }
  460. void GameEngine::on_patrol_click(qreal sx, qreal sy) {
  461. if (!m_commandController || !m_camera) {
  462. return;
  463. }
  464. ensure_initialized();
  465. auto result = m_commandController->onPatrolClick(
  466. sx, sy, m_viewport.width, m_viewport.height, m_camera.get());
  467. if (result.resetCursorToNormal) {
  468. set_cursor_mode(CursorMode::Normal);
  469. }
  470. }
  471. void GameEngine::update_cursor(Qt::CursorShape newCursor) {
  472. if (m_window == nullptr) {
  473. return;
  474. }
  475. if (m_runtime.current_cursor != newCursor) {
  476. m_runtime.current_cursor = newCursor;
  477. m_window->setCursor(newCursor);
  478. }
  479. }
  480. void GameEngine::set_error(const QString &errorMessage) {
  481. if (m_runtime.last_error != errorMessage) {
  482. m_runtime.last_error = errorMessage;
  483. qCritical() << "GameEngine error:" << errorMessage;
  484. emit last_error_changed();
  485. }
  486. }
  487. void GameEngine::set_cursor_mode(CursorMode mode) {
  488. if (!m_cursorManager) {
  489. return;
  490. }
  491. m_cursorManager->setMode(mode);
  492. m_cursorManager->updateCursorShape(m_window);
  493. }
  494. void GameEngine::set_cursor_mode(const QString &mode) {
  495. set_cursor_mode(CursorModeUtils::fromString(mode));
  496. }
  497. auto GameEngine::cursor_mode() const -> QString {
  498. if (!m_cursorManager) {
  499. return "normal";
  500. }
  501. return m_cursorManager->modeString();
  502. }
  503. auto GameEngine::global_cursor_x() const -> qreal {
  504. if (!m_cursorManager) {
  505. return 0;
  506. }
  507. return m_cursorManager->global_cursor_x(m_window);
  508. }
  509. auto GameEngine::global_cursor_y() const -> qreal {
  510. if (!m_cursorManager) {
  511. return 0;
  512. }
  513. return m_cursorManager->global_cursor_y(m_window);
  514. }
  515. void GameEngine::set_hover_at_screen(qreal sx, qreal sy) {
  516. if (m_window == nullptr) {
  517. return;
  518. }
  519. ensure_initialized();
  520. if (!m_hoverTracker || !m_camera || !m_world) {
  521. return;
  522. }
  523. m_cursorManager->updateCursorShape(m_window);
  524. m_hoverTracker->update_hover(float(sx), float(sy), *m_world, *m_camera,
  525. m_viewport.width, m_viewport.height);
  526. }
  527. void GameEngine::on_click_select(qreal sx, qreal sy, bool additive) {
  528. if (m_window == nullptr) {
  529. return;
  530. }
  531. ensure_initialized();
  532. if (m_selectionController && m_camera) {
  533. m_selectionController->on_click_select(sx, sy, additive, m_viewport.width,
  534. m_viewport.height, m_camera.get(),
  535. m_runtime.local_owner_id);
  536. }
  537. }
  538. void GameEngine::on_area_selected(qreal x1, qreal y1, qreal x2, qreal y2,
  539. bool additive) {
  540. if (m_window == nullptr) {
  541. return;
  542. }
  543. ensure_initialized();
  544. if (m_selectionController && m_camera) {
  545. m_selectionController->on_area_selected(
  546. x1, y1, x2, y2, additive, m_viewport.width, m_viewport.height,
  547. m_camera.get(), m_runtime.local_owner_id);
  548. }
  549. }
  550. void GameEngine::select_all_troops() {
  551. ensure_initialized();
  552. if (m_selectionController) {
  553. m_selectionController->select_all_player_troops(m_runtime.local_owner_id);
  554. }
  555. }
  556. void GameEngine::select_unit_by_id(int unitId) {
  557. ensure_initialized();
  558. if (!m_selectionController || (unitId <= 0)) {
  559. return;
  560. }
  561. m_selectionController->select_single_unit(
  562. static_cast<Engine::Core::EntityID>(unitId), m_runtime.local_owner_id);
  563. }
  564. void GameEngine::ensure_initialized() {
  565. QString error;
  566. Game::Map::WorldBootstrap::ensure_initialized(
  567. m_runtime.initialized, *m_renderer, *m_camera, m_ground.get(), &error);
  568. if (!error.isEmpty()) {
  569. set_error(error);
  570. }
  571. }
  572. auto GameEngine::enemy_troops_defeated() const -> int {
  573. return m_enemyTroopsDefeated;
  574. }
  575. auto GameEngine::get_player_stats(int owner_id) -> QVariantMap {
  576. QVariantMap result;
  577. auto &stats_registry = Game::Systems::GlobalStatsRegistry::instance();
  578. const auto *stats = stats_registry.get_stats(owner_id);
  579. if (stats != nullptr) {
  580. result["troopsRecruited"] = stats->troops_recruited;
  581. result["enemiesKilled"] = stats->enemies_killed;
  582. result["barracksOwned"] = stats->barracks_owned;
  583. result["playTimeSec"] = stats->play_time_sec;
  584. result["gameEnded"] = stats->game_ended;
  585. } else {
  586. result["troopsRecruited"] = 0;
  587. result["enemiesKilled"] = 0;
  588. result["barracksOwned"] = 0;
  589. result["playTimeSec"] = 0.0F;
  590. result["gameEnded"] = false;
  591. }
  592. return result;
  593. }
  594. void GameEngine::update(float dt) {
  595. if (m_runtime.loading) {
  596. return;
  597. }
  598. if (m_runtime.paused) {
  599. dt = 0.0F;
  600. } else {
  601. dt *= m_runtime.time_scale;
  602. }
  603. if (!m_runtime.paused && !m_runtime.loading) {
  604. update_ambient_state(dt);
  605. }
  606. if (m_renderer) {
  607. m_renderer->update_animation_time(dt);
  608. }
  609. if (m_camera) {
  610. m_camera->update(dt);
  611. }
  612. if (m_world) {
  613. m_world->update(dt);
  614. auto &visibility_service = Game::Map::VisibilityService::instance();
  615. if (visibility_service.is_initialized()) {
  616. m_runtime.visibilityUpdateAccumulator += dt;
  617. const float visibility_update_interval =
  618. Game::GameConfig::instance().gameplay().visibility_update_interval;
  619. if (m_runtime.visibilityUpdateAccumulator >= visibility_update_interval) {
  620. m_runtime.visibilityUpdateAccumulator = 0.0F;
  621. visibility_service.update(*m_world, m_runtime.local_owner_id);
  622. }
  623. const auto new_version = visibility_service.version();
  624. if (new_version != m_runtime.visibilityVersion) {
  625. if (m_fog) {
  626. m_fog->updateMask(visibility_service.getWidth(),
  627. visibility_service.getHeight(),
  628. visibility_service.getTileSize(),
  629. visibility_service.snapshotCells());
  630. }
  631. m_runtime.visibilityVersion = new_version;
  632. }
  633. }
  634. update_minimap_fog(dt);
  635. }
  636. if (m_victoryService && m_world) {
  637. m_victoryService->update(*m_world, dt);
  638. }
  639. if (m_followSelectionEnabled && m_camera && m_world && m_cameraService) {
  640. m_cameraService->update_follow(*m_camera, *m_world,
  641. m_followSelectionEnabled);
  642. }
  643. if (m_selectedUnitsModel != nullptr) {
  644. auto *selection_system =
  645. m_world->get_system<Game::Systems::SelectionSystem>();
  646. if ((selection_system != nullptr) &&
  647. !selection_system->get_selected_units().empty()) {
  648. m_runtime.selectionRefreshCounter++;
  649. if (m_runtime.selectionRefreshCounter >= 15) {
  650. m_runtime.selectionRefreshCounter = 0;
  651. emit selected_units_data_changed();
  652. }
  653. }
  654. }
  655. }
  656. void GameEngine::render(int pixelWidth, int pixelHeight) {
  657. if (!m_renderer || !m_world || !m_runtime.initialized || m_runtime.loading) {
  658. return;
  659. }
  660. if (pixelWidth > 0 && pixelHeight > 0) {
  661. m_viewport.width = pixelWidth;
  662. m_viewport.height = pixelHeight;
  663. m_renderer->set_viewport(pixelWidth, pixelHeight);
  664. }
  665. if (auto *selection_system =
  666. m_world->get_system<Game::Systems::SelectionSystem>()) {
  667. const auto &sel = selection_system->get_selected_units();
  668. std::vector<unsigned int> const ids(sel.begin(), sel.end());
  669. m_renderer->set_selected_entities(ids);
  670. }
  671. m_renderer->begin_frame();
  672. if (auto *res = m_renderer->resources()) {
  673. for (auto *pass : m_passes) {
  674. if (pass != nullptr) {
  675. pass->submit(*m_renderer, res);
  676. }
  677. }
  678. }
  679. if (m_renderer && m_hoverTracker) {
  680. m_renderer->set_hovered_entity_id(m_hoverTracker->getLastHoveredEntity());
  681. }
  682. if (m_renderer) {
  683. m_renderer->set_local_owner_id(m_runtime.local_owner_id);
  684. }
  685. m_renderer->render_world(m_world.get());
  686. if (auto *arrow_system = m_world->get_system<Game::Systems::ArrowSystem>()) {
  687. if (auto *res = m_renderer->resources()) {
  688. Render::GL::renderArrows(m_renderer.get(), res, *arrow_system);
  689. }
  690. }
  691. if (auto *projectile_system =
  692. m_world->get_system<Game::Systems::ProjectileSystem>()) {
  693. if (auto *res = m_renderer->resources()) {
  694. Render::GL::render_projectiles(m_renderer.get(), res, *projectile_system);
  695. }
  696. }
  697. if (auto *res = m_renderer->resources()) {
  698. std::optional<QVector3D> preview_waypoint;
  699. if (m_commandController && m_commandController->hasPatrolFirstWaypoint()) {
  700. preview_waypoint = m_commandController->getPatrolFirstWaypoint();
  701. }
  702. Render::GL::renderPatrolFlags(m_renderer.get(), res, *m_world,
  703. preview_waypoint);
  704. }
  705. m_renderer->end_frame();
  706. qreal const current_x = global_cursor_x();
  707. qreal const current_y = global_cursor_y();
  708. if (current_x != m_runtime.lastCursorX ||
  709. current_y != m_runtime.lastCursorY) {
  710. m_runtime.lastCursorX = current_x;
  711. m_runtime.lastCursorY = current_y;
  712. emit global_cursor_changed();
  713. }
  714. }
  715. auto GameEngine::screen_to_ground(const QPointF &screenPt,
  716. QVector3D &outWorld) -> bool {
  717. return App::Utils::screen_to_ground(m_pickingService.get(), m_camera.get(),
  718. m_window, m_viewport.width,
  719. m_viewport.height, screenPt, outWorld);
  720. }
  721. auto GameEngine::world_to_screen(const QVector3D &world,
  722. QPointF &outScreen) const -> bool {
  723. return App::Utils::world_to_screen(m_pickingService.get(), m_camera.get(),
  724. m_window, m_viewport.width,
  725. m_viewport.height, world, outScreen);
  726. }
  727. void GameEngine::sync_selection_flags() {
  728. auto *selection_system =
  729. m_world->get_system<Game::Systems::SelectionSystem>();
  730. if (!m_world || (selection_system == nullptr)) {
  731. return;
  732. }
  733. App::Utils::sanitize_selection(m_world.get(), selection_system);
  734. if (selection_system->get_selected_units().empty()) {
  735. if (m_cursorManager && m_cursorManager->mode() != CursorMode::Normal) {
  736. set_cursor_mode(CursorMode::Normal);
  737. }
  738. }
  739. }
  740. void GameEngine::camera_move(float dx, float dz) {
  741. ensure_initialized();
  742. if (!m_camera || !m_cameraService) {
  743. return;
  744. }
  745. m_cameraService->move(*m_camera, dx, dz);
  746. }
  747. void GameEngine::camera_elevate(float dy) {
  748. ensure_initialized();
  749. if (!m_camera || !m_cameraService) {
  750. return;
  751. }
  752. m_cameraService->elevate(*m_camera, dy);
  753. }
  754. void GameEngine::reset_camera() {
  755. ensure_initialized();
  756. if (!m_camera || !m_world || !m_cameraService) {
  757. return;
  758. }
  759. m_cameraService->resetCamera(*m_camera, *m_world, m_runtime.local_owner_id,
  760. m_level.player_unit_id);
  761. }
  762. void GameEngine::camera_zoom(float delta) {
  763. ensure_initialized();
  764. if (!m_camera || !m_cameraService) {
  765. return;
  766. }
  767. m_cameraService->zoom(*m_camera, delta);
  768. }
  769. auto GameEngine::camera_distance() const -> float {
  770. if (!m_camera || !m_cameraService) {
  771. return 0.0F;
  772. }
  773. return m_cameraService->get_distance(*m_camera);
  774. }
  775. void GameEngine::camera_yaw(float degrees) {
  776. ensure_initialized();
  777. if (!m_camera || !m_cameraService) {
  778. return;
  779. }
  780. m_cameraService->yaw(*m_camera, degrees);
  781. }
  782. void GameEngine::camera_orbit(float yaw_deg, float pitch_deg) {
  783. ensure_initialized();
  784. if (!m_camera || !m_cameraService) {
  785. return;
  786. }
  787. if (!std::isfinite(yaw_deg) || !std::isfinite(pitch_deg)) {
  788. qWarning() << "GameEngine::camera_orbit received invalid input, ignoring:"
  789. << yaw_deg << pitch_deg;
  790. return;
  791. }
  792. m_cameraService->orbit(*m_camera, yaw_deg, pitch_deg);
  793. }
  794. void GameEngine::camera_orbit_direction(int direction, bool shift) {
  795. if (!m_camera || !m_cameraService) {
  796. return;
  797. }
  798. m_cameraService->orbit_direction(*m_camera, direction, shift);
  799. }
  800. void GameEngine::camera_follow_selection(bool enable) {
  801. ensure_initialized();
  802. m_followSelectionEnabled = enable;
  803. if (!m_camera || !m_world || !m_cameraService) {
  804. return;
  805. }
  806. m_cameraService->follow_selection(*m_camera, *m_world, enable);
  807. }
  808. void GameEngine::camera_set_follow_lerp(float alpha) {
  809. ensure_initialized();
  810. if (!m_camera || !m_cameraService) {
  811. return;
  812. }
  813. m_cameraService->set_follow_lerp(*m_camera, alpha);
  814. }
  815. auto GameEngine::selected_units_model() -> QAbstractItemModel * {
  816. return m_selectedUnitsModel;
  817. }
  818. auto GameEngine::audio_system() -> QObject * {
  819. return m_audio_systemProxy.get();
  820. }
  821. auto GameEngine::has_units_selected() const -> bool {
  822. if (!m_selectionController) {
  823. return false;
  824. }
  825. return m_selectionController->has_units_selected();
  826. }
  827. auto GameEngine::player_troop_count() const -> int {
  828. return m_entityCache.playerTroopCount;
  829. }
  830. auto GameEngine::has_selected_type(const QString &type) const -> bool {
  831. if (!m_selectionController) {
  832. return false;
  833. }
  834. return m_selectionController->has_selected_type(type);
  835. }
  836. void GameEngine::recruit_near_selected(const QString &unit_type) {
  837. ensure_initialized();
  838. if (!m_commandController) {
  839. return;
  840. }
  841. m_commandController->recruitNearSelected(unit_type, m_runtime.local_owner_id);
  842. }
  843. auto GameEngine::get_selected_production_state() const -> QVariantMap {
  844. QVariantMap m;
  845. m["has_barracks"] = false;
  846. m["in_progress"] = false;
  847. m["time_remaining"] = 0.0;
  848. m["build_time"] = 0.0;
  849. m["produced_count"] = 0;
  850. m["max_units"] = 0;
  851. m["villager_cost"] = 1;
  852. if (!m_world) {
  853. return m;
  854. }
  855. auto *selection_system =
  856. m_world->get_system<Game::Systems::SelectionSystem>();
  857. if (selection_system == nullptr) {
  858. return m;
  859. }
  860. Game::Systems::ProductionState st;
  861. Game::Systems::ProductionService::getSelectedBarracksState(
  862. *m_world, selection_system->get_selected_units(),
  863. m_runtime.local_owner_id, st);
  864. m["has_barracks"] = st.has_barracks;
  865. m["in_progress"] = st.in_progress;
  866. m["product_type"] =
  867. QString::fromStdString(Game::Units::troop_typeToString(st.product_type));
  868. m["time_remaining"] = st.time_remaining;
  869. m["build_time"] = st.build_time;
  870. m["produced_count"] = st.produced_count;
  871. m["max_units"] = st.max_units;
  872. m["villager_cost"] = st.villager_cost;
  873. m["queue_size"] = st.queue_size;
  874. m["nation_id"] =
  875. QString::fromStdString(Game::Systems::nationIDToString(st.nation_id));
  876. QVariantList queue_list;
  877. for (const auto &unit_type : st.production_queue) {
  878. queue_list.append(
  879. QString::fromStdString(Game::Units::troop_typeToString(unit_type)));
  880. }
  881. m["production_queue"] = queue_list;
  882. return m;
  883. }
  884. auto GameEngine::get_unit_production_info(const QString &unit_type) const
  885. -> QVariantMap {
  886. QVariantMap info;
  887. const auto &config = Game::Units::TroopConfig::instance();
  888. std::string type_str = unit_type.toStdString();
  889. info["cost"] = config.getProductionCost(type_str);
  890. info["build_time"] = static_cast<double>(config.getBuildTime(type_str));
  891. info["individuals_per_unit"] = config.getIndividualsPerUnit(type_str);
  892. return info;
  893. }
  894. auto GameEngine::get_selected_units_command_mode() const -> QString {
  895. if (!m_world) {
  896. return "normal";
  897. }
  898. auto *selection_system =
  899. m_world->get_system<Game::Systems::SelectionSystem>();
  900. if (selection_system == nullptr) {
  901. return "normal";
  902. }
  903. const auto &sel = selection_system->get_selected_units();
  904. if (sel.empty()) {
  905. return "normal";
  906. }
  907. int attacking_count = 0;
  908. int patrolling_count = 0;
  909. int total_units = 0;
  910. for (auto id : sel) {
  911. auto *e = m_world->get_entity(id);
  912. if (e == nullptr) {
  913. continue;
  914. }
  915. auto *u = e->get_component<Engine::Core::UnitComponent>();
  916. if (u == nullptr) {
  917. continue;
  918. }
  919. if (u->spawn_type == Game::Units::SpawnType::Barracks) {
  920. continue;
  921. }
  922. total_units++;
  923. if (e->get_component<Engine::Core::AttackTargetComponent>() != nullptr) {
  924. attacking_count++;
  925. }
  926. auto *patrol = e->get_component<Engine::Core::PatrolComponent>();
  927. if ((patrol != nullptr) && patrol->patrolling) {
  928. patrolling_count++;
  929. }
  930. }
  931. if (total_units == 0) {
  932. return "normal";
  933. }
  934. if (patrolling_count == total_units) {
  935. return "patrol";
  936. }
  937. if (attacking_count == total_units) {
  938. return "attack";
  939. }
  940. return "normal";
  941. }
  942. void GameEngine::set_rally_at_screen(qreal sx, qreal sy) {
  943. ensure_initialized();
  944. if (!m_commandController || !m_camera) {
  945. return;
  946. }
  947. m_commandController->setRallyAtScreen(sx, sy, m_viewport.width,
  948. m_viewport.height, m_camera.get(),
  949. m_runtime.local_owner_id);
  950. }
  951. void GameEngine::start_loading_maps() {
  952. m_available_maps.clear();
  953. if (m_mapCatalog) {
  954. m_mapCatalog->load_maps_async();
  955. }
  956. load_campaigns();
  957. }
  958. auto GameEngine::available_maps() const -> QVariantList {
  959. return m_available_maps;
  960. }
  961. auto GameEngine::available_nations() const -> QVariantList {
  962. QVariantList nations;
  963. const auto &registry = Game::Systems::NationRegistry::instance();
  964. const auto &all = registry.get_all_nations();
  965. QList<QVariantMap> ordered;
  966. ordered.reserve(static_cast<int>(all.size()));
  967. for (const auto &nation : all) {
  968. QVariantMap entry;
  969. entry.insert(
  970. QStringLiteral("id"),
  971. QString::fromStdString(Game::Systems::nationIDToString(nation.id)));
  972. entry.insert(QStringLiteral("name"),
  973. QString::fromStdString(nation.display_name));
  974. ordered.append(entry);
  975. }
  976. std::sort(ordered.begin(), ordered.end(),
  977. [](const QVariantMap &a, const QVariantMap &b) {
  978. return a.value(QStringLiteral("name"))
  979. .toString()
  980. .localeAwareCompare(
  981. b.value(QStringLiteral("name")).toString()) < 0;
  982. });
  983. for (const auto &entry : ordered) {
  984. nations.append(entry);
  985. }
  986. return nations;
  987. }
  988. auto GameEngine::available_campaigns() const -> QVariantList {
  989. return m_available_campaigns;
  990. }
  991. void GameEngine::load_campaigns() {
  992. if (!m_saveLoadService) {
  993. return;
  994. }
  995. QString error;
  996. auto campaigns = m_saveLoadService->list_campaigns(&error);
  997. if (!error.isEmpty()) {
  998. qWarning() << "Failed to load campaigns:" << error;
  999. return;
  1000. }
  1001. m_available_campaigns = campaigns;
  1002. emit available_campaigns_changed();
  1003. }
  1004. void GameEngine::start_campaign_mission(const QString &campaign_id) {
  1005. clear_error();
  1006. if (!m_saveLoadService) {
  1007. set_error("Save/Load service not initialized");
  1008. return;
  1009. }
  1010. QString error;
  1011. auto campaigns = m_saveLoadService->list_campaigns(&error);
  1012. if (!error.isEmpty()) {
  1013. set_error("Failed to load campaign: " + error);
  1014. return;
  1015. }
  1016. QVariantMap selectedCampaign;
  1017. for (const auto &campaign : campaigns) {
  1018. auto campaignMap = campaign.toMap();
  1019. if (campaignMap.value("id").toString() == campaign_id) {
  1020. selectedCampaign = campaignMap;
  1021. break;
  1022. }
  1023. }
  1024. if (selectedCampaign.isEmpty()) {
  1025. set_error("Campaign not found: " + campaign_id);
  1026. return;
  1027. }
  1028. m_current_campaign_id = campaign_id;
  1029. QString mapPath = selectedCampaign.value("mapPath").toString();
  1030. QVariantList playerConfigs;
  1031. QVariantMap player1;
  1032. player1.insert("player_id", 1);
  1033. player1.insert("playerName", "Carthage");
  1034. player1.insert("colorIndex", 0);
  1035. player1.insert("team_id", 0);
  1036. player1.insert("nationId", "carthage");
  1037. player1.insert("isHuman", true);
  1038. playerConfigs.append(player1);
  1039. QVariantMap player2;
  1040. player2.insert("player_id", 2);
  1041. player2.insert("playerName", "Rome");
  1042. player2.insert("colorIndex", 1);
  1043. player2.insert("team_id", 1);
  1044. player2.insert("nationId", "roman_republic");
  1045. player2.insert("isHuman", false);
  1046. playerConfigs.append(player2);
  1047. start_skirmish(mapPath, playerConfigs);
  1048. }
  1049. void GameEngine::mark_current_mission_completed() {
  1050. if (m_current_campaign_id.isEmpty()) {
  1051. qWarning() << "No active campaign mission to mark as completed";
  1052. return;
  1053. }
  1054. if (!m_saveLoadService) {
  1055. qWarning() << "Save/Load service not initialized";
  1056. return;
  1057. }
  1058. QString error;
  1059. bool success =
  1060. m_saveLoadService->mark_campaign_completed(m_current_campaign_id, &error);
  1061. if (!success) {
  1062. qWarning() << "Failed to mark campaign as completed:" << error;
  1063. } else {
  1064. qInfo() << "Campaign mission" << m_current_campaign_id
  1065. << "marked as completed";
  1066. load_campaigns();
  1067. }
  1068. }
  1069. void GameEngine::start_skirmish(const QString &map_path,
  1070. const QVariantList &playerConfigs) {
  1071. clear_error();
  1072. m_level.map_path = map_path;
  1073. m_level.map_name = map_path;
  1074. if (!m_runtime.victory_state.isEmpty()) {
  1075. m_runtime.victory_state = "";
  1076. emit victory_state_changed();
  1077. }
  1078. if (m_victoryService) {
  1079. m_victoryService->reset();
  1080. }
  1081. m_enemyTroopsDefeated = 0;
  1082. if (!m_runtime.initialized) {
  1083. ensure_initialized();
  1084. return;
  1085. }
  1086. if (m_world && m_renderer && m_camera) {
  1087. m_runtime.loading = true;
  1088. if (m_hoverTracker) {
  1089. m_hoverTracker->update_hover(-1, -1, *m_world, *m_camera, 0, 0);
  1090. }
  1091. m_entityCache.reset();
  1092. Game::Map::SkirmishLoader loader(*m_world, *m_renderer, *m_camera);
  1093. loader.set_ground_renderer(m_ground.get());
  1094. loader.set_terrain_renderer(m_terrain.get());
  1095. loader.set_biome_renderer(m_biome.get());
  1096. loader.set_river_renderer(m_river.get());
  1097. loader.set_road_renderer(m_road.get());
  1098. loader.set_riverbank_renderer(m_riverbank.get());
  1099. loader.set_bridge_renderer(m_bridge.get());
  1100. loader.set_fog_renderer(m_fog.get());
  1101. loader.set_stone_renderer(m_stone.get());
  1102. loader.set_plant_renderer(m_plant.get());
  1103. loader.set_pine_renderer(m_pine.get());
  1104. loader.set_olive_renderer(m_olive.get());
  1105. loader.set_fire_camp_renderer(m_firecamp.get());
  1106. loader.set_on_owners_updated([this]() { emit owner_info_changed(); });
  1107. loader.set_on_visibility_mask_ready([this]() {
  1108. m_runtime.visibilityVersion =
  1109. Game::Map::VisibilityService::instance().version();
  1110. m_runtime.visibilityUpdateAccumulator = 0.0F;
  1111. });
  1112. int updated_player_id = m_selected_player_id;
  1113. auto result = loader.start(map_path, playerConfigs, m_selected_player_id,
  1114. updated_player_id);
  1115. if (updated_player_id != m_selected_player_id) {
  1116. m_selected_player_id = updated_player_id;
  1117. emit selected_player_id_changed();
  1118. }
  1119. if (!result.ok && !result.errorMessage.isEmpty()) {
  1120. set_error(result.errorMessage);
  1121. }
  1122. m_runtime.local_owner_id = updated_player_id;
  1123. m_level.map_name = result.map_name;
  1124. m_level.player_unit_id = result.player_unit_id;
  1125. m_level.cam_fov = result.cam_fov;
  1126. m_level.cam_near = result.cam_near;
  1127. m_level.cam_far = result.cam_far;
  1128. m_level.max_troops_per_player = result.max_troops_per_player;
  1129. Game::GameConfig::instance().set_max_troops_per_player(
  1130. result.max_troops_per_player);
  1131. if (m_victoryService) {
  1132. m_victoryService->configure(result.victoryConfig,
  1133. m_runtime.local_owner_id);
  1134. m_victoryService->setVictoryCallback([this](const QString &state) {
  1135. if (m_runtime.victory_state != state) {
  1136. m_runtime.victory_state = state;
  1137. emit victory_state_changed();
  1138. if (state == "victory" && !m_current_campaign_id.isEmpty()) {
  1139. mark_current_mission_completed();
  1140. }
  1141. }
  1142. });
  1143. }
  1144. if (result.has_focus_position && m_camera) {
  1145. const auto &cam_config = Game::GameConfig::instance().camera();
  1146. m_camera->setRTSView(result.focusPosition, cam_config.default_distance,
  1147. cam_config.default_pitch, cam_config.default_yaw);
  1148. }
  1149. Game::Map::MapDefinition map_def;
  1150. QString map_error;
  1151. if (Game::Map::MapLoader::loadFromJsonFile(map_path, map_def, &map_error)) {
  1152. generate_minimap_for_map(map_def);
  1153. } else {
  1154. qWarning() << "GameEngine: Failed to load map for minimap generation:"
  1155. << map_error;
  1156. }
  1157. m_runtime.loading = false;
  1158. if (auto *ai_system = m_world->get_system<Game::Systems::AISystem>()) {
  1159. ai_system->reinitialize();
  1160. }
  1161. rebuild_entity_cache();
  1162. auto &troops = Game::Systems::TroopCountRegistry::instance();
  1163. troops.rebuild_from_world(*m_world);
  1164. auto &stats_registry = Game::Systems::GlobalStatsRegistry::instance();
  1165. stats_registry.rebuild_from_world(*m_world);
  1166. auto &owner_registry = Game::Systems::OwnerRegistry::instance();
  1167. const auto &all_owners = owner_registry.get_all_owners();
  1168. for (const auto &owner : all_owners) {
  1169. if (owner.type == Game::Systems::OwnerType::Player ||
  1170. owner.type == Game::Systems::OwnerType::AI) {
  1171. stats_registry.mark_game_start(owner.owner_id);
  1172. }
  1173. }
  1174. m_currentAmbientState = Engine::Core::AmbientState::PEACEFUL;
  1175. m_ambientCheckTimer = 0.0F;
  1176. Engine::Core::EventManager::instance().publish(
  1177. Engine::Core::AmbientStateChangedEvent(
  1178. Engine::Core::AmbientState::PEACEFUL,
  1179. Engine::Core::AmbientState::PEACEFUL));
  1180. emit owner_info_changed();
  1181. }
  1182. }
  1183. void GameEngine::open_settings() {
  1184. if (m_saveLoadService) {
  1185. m_saveLoadService->openSettings();
  1186. }
  1187. }
  1188. void GameEngine::load_save() { load_from_slot("savegame"); }
  1189. void GameEngine::save_game(const QString &filename) {
  1190. save_to_slot(filename, filename);
  1191. }
  1192. void GameEngine::save_game_to_slot(const QString &slotName) {
  1193. save_to_slot(slotName, slotName);
  1194. }
  1195. void GameEngine::load_game_from_slot(const QString &slotName) {
  1196. load_from_slot(slotName);
  1197. }
  1198. auto GameEngine::load_from_slot(const QString &slot) -> bool {
  1199. if (!m_saveLoadService || !m_world) {
  1200. set_error("Load: not initialized");
  1201. return false;
  1202. }
  1203. m_runtime.loading = true;
  1204. if (!m_saveLoadService->load_game_from_slot(*m_world, slot)) {
  1205. set_error(m_saveLoadService->getLastError());
  1206. m_runtime.loading = false;
  1207. return false;
  1208. }
  1209. const QJsonObject meta = m_saveLoadService->getLastMetadata();
  1210. Game::Systems::GameStateSerializer::restoreLevelFromMetadata(meta, m_level);
  1211. Game::Systems::GameStateSerializer::restoreCameraFromMetadata(
  1212. meta, m_camera.get(), m_viewport.width, m_viewport.height);
  1213. Game::Systems::RuntimeSnapshot runtime_snap = to_runtime_snapshot();
  1214. Game::Systems::GameStateSerializer::restoreRuntimeFromMetadata(meta,
  1215. runtime_snap);
  1216. apply_runtime_snapshot(runtime_snap);
  1217. restore_environment_from_metadata(meta);
  1218. auto unit_reg = std::make_shared<Game::Units::UnitFactoryRegistry>();
  1219. Game::Units::registerBuiltInUnits(*unit_reg);
  1220. Game::Map::MapTransformer::setFactoryRegistry(unit_reg);
  1221. qInfo() << "Factory registry reinitialized after loading saved game";
  1222. rebuild_registries_after_load();
  1223. rebuild_entity_cache();
  1224. if (auto *ai_system = m_world->get_system<Game::Systems::AISystem>()) {
  1225. qInfo() << "Reinitializing AI system after loading saved game";
  1226. ai_system->reinitialize();
  1227. }
  1228. if (m_victoryService) {
  1229. m_victoryService->configure(Game::Map::VictoryConfig(),
  1230. m_runtime.local_owner_id);
  1231. }
  1232. m_runtime.loading = false;
  1233. qInfo() << "Game load complete, victory/defeat checks re-enabled";
  1234. emit selected_units_changed();
  1235. emit owner_info_changed();
  1236. return true;
  1237. }
  1238. auto GameEngine::save_to_slot(const QString &slot,
  1239. const QString &title) -> bool {
  1240. if (!m_saveLoadService || !m_world) {
  1241. set_error("Save: not initialized");
  1242. return false;
  1243. }
  1244. Game::Systems::RuntimeSnapshot const runtime_snap = to_runtime_snapshot();
  1245. QJsonObject meta = Game::Systems::GameStateSerializer::buildMetadata(
  1246. *m_world, m_camera.get(), m_level, runtime_snap);
  1247. meta["title"] = title;
  1248. const QByteArray screenshot = capture_screenshot();
  1249. if (!m_saveLoadService->saveGameToSlot(*m_world, slot, title,
  1250. m_level.map_name, meta, screenshot)) {
  1251. set_error(m_saveLoadService->getLastError());
  1252. return false;
  1253. }
  1254. emit save_slots_changed();
  1255. return true;
  1256. }
  1257. auto GameEngine::get_save_slots() const -> QVariantList {
  1258. if (!m_saveLoadService) {
  1259. qWarning() << "Cannot get save slots: service not initialized";
  1260. return {};
  1261. }
  1262. return m_saveLoadService->get_save_slots();
  1263. }
  1264. void GameEngine::refresh_save_slots() { emit save_slots_changed(); }
  1265. auto GameEngine::delete_save_slot(const QString &slotName) -> bool {
  1266. if (!m_saveLoadService) {
  1267. qWarning() << "Cannot delete save slot: service not initialized";
  1268. return false;
  1269. }
  1270. bool const success = m_saveLoadService->deleteSaveSlot(slotName);
  1271. if (!success) {
  1272. QString const error = m_saveLoadService->getLastError();
  1273. qWarning() << "Failed to delete save slot:" << error;
  1274. set_error(error);
  1275. } else {
  1276. emit save_slots_changed();
  1277. }
  1278. return success;
  1279. }
  1280. void GameEngine::exit_game() {
  1281. if (m_saveLoadService) {
  1282. m_saveLoadService->exitGame();
  1283. }
  1284. }
  1285. auto GameEngine::get_owner_info() const -> QVariantList {
  1286. QVariantList result;
  1287. const auto &owner_registry = Game::Systems::OwnerRegistry::instance();
  1288. const auto &owners = owner_registry.get_all_owners();
  1289. for (const auto &owner : owners) {
  1290. QVariantMap owner_map;
  1291. owner_map["id"] = owner.owner_id;
  1292. owner_map["name"] = QString::fromStdString(owner.name);
  1293. owner_map["team_id"] = owner.team_id;
  1294. QString type_str;
  1295. switch (owner.type) {
  1296. case Game::Systems::OwnerType::Player:
  1297. type_str = "Player";
  1298. break;
  1299. case Game::Systems::OwnerType::AI:
  1300. type_str = "AI";
  1301. break;
  1302. case Game::Systems::OwnerType::Neutral:
  1303. type_str = "Neutral";
  1304. break;
  1305. }
  1306. owner_map["type"] = type_str;
  1307. owner_map["isLocal"] = (owner.owner_id == m_runtime.local_owner_id);
  1308. result.append(owner_map);
  1309. }
  1310. return result;
  1311. }
  1312. void GameEngine::get_selected_unit_ids(
  1313. std::vector<Engine::Core::EntityID> &out) const {
  1314. out.clear();
  1315. if (!m_selectionController) {
  1316. return;
  1317. }
  1318. m_selectionController->get_selected_unit_ids(out);
  1319. }
  1320. auto GameEngine::get_unit_info(Engine::Core::EntityID id, QString &name,
  1321. int &health, int &max_health, bool &isBuilding,
  1322. bool &alive, QString &nation) const -> bool {
  1323. if (!m_world) {
  1324. return false;
  1325. }
  1326. auto *e = m_world->get_entity(id);
  1327. if (e == nullptr) {
  1328. return false;
  1329. }
  1330. isBuilding = e->has_component<Engine::Core::BuildingComponent>();
  1331. if (auto *u = e->get_component<Engine::Core::UnitComponent>()) {
  1332. name =
  1333. QString::fromStdString(Game::Units::spawn_typeToString(u->spawn_type));
  1334. health = u->health;
  1335. max_health = u->max_health;
  1336. alive = (u->health > 0);
  1337. nation = Game::Systems::nationIDToQString(u->nation_id);
  1338. return true;
  1339. }
  1340. name = QStringLiteral("Entity");
  1341. health = max_health = 0;
  1342. alive = true;
  1343. nation = QStringLiteral("");
  1344. return true;
  1345. }
  1346. void GameEngine::on_unit_spawned(const Engine::Core::UnitSpawnedEvent &event) {
  1347. auto &owners = Game::Systems::OwnerRegistry::instance();
  1348. if (event.owner_id == m_runtime.local_owner_id) {
  1349. if (event.spawn_type == Game::Units::SpawnType::Barracks) {
  1350. m_entityCache.playerBarracksAlive = true;
  1351. } else {
  1352. int const production_cost =
  1353. Game::Units::TroopConfig::instance().getProductionCost(
  1354. event.spawn_type);
  1355. m_entityCache.playerTroopCount += production_cost;
  1356. }
  1357. } else if (owners.is_ai(event.owner_id)) {
  1358. if (event.spawn_type == Game::Units::SpawnType::Barracks) {
  1359. m_entityCache.enemyBarracksCount++;
  1360. m_entityCache.enemyBarracksAlive = true;
  1361. }
  1362. }
  1363. auto emit_if_changed = [&] {
  1364. if (m_entityCache.playerTroopCount != m_runtime.lastTroopCount) {
  1365. m_runtime.lastTroopCount = m_entityCache.playerTroopCount;
  1366. emit troop_count_changed();
  1367. }
  1368. };
  1369. emit_if_changed();
  1370. }
  1371. void GameEngine::on_unit_died(const Engine::Core::UnitDiedEvent &event) {
  1372. auto &owners = Game::Systems::OwnerRegistry::instance();
  1373. if (event.owner_id == m_runtime.local_owner_id) {
  1374. if (event.spawn_type == Game::Units::SpawnType::Barracks) {
  1375. m_entityCache.playerBarracksAlive = false;
  1376. } else {
  1377. int const production_cost =
  1378. Game::Units::TroopConfig::instance().getProductionCost(
  1379. event.spawn_type);
  1380. m_entityCache.playerTroopCount -= production_cost;
  1381. m_entityCache.playerTroopCount =
  1382. std::max(0, m_entityCache.playerTroopCount);
  1383. }
  1384. } else if (owners.is_ai(event.owner_id)) {
  1385. if (event.spawn_type == Game::Units::SpawnType::Barracks) {
  1386. m_entityCache.enemyBarracksCount--;
  1387. m_entityCache.enemyBarracksCount =
  1388. std::max(0, m_entityCache.enemyBarracksCount);
  1389. m_entityCache.enemyBarracksAlive = (m_entityCache.enemyBarracksCount > 0);
  1390. }
  1391. }
  1392. sync_selection_flags();
  1393. auto emit_if_changed = [&] {
  1394. if (m_entityCache.playerTroopCount != m_runtime.lastTroopCount) {
  1395. m_runtime.lastTroopCount = m_entityCache.playerTroopCount;
  1396. emit troop_count_changed();
  1397. }
  1398. };
  1399. emit_if_changed();
  1400. }
  1401. void GameEngine::rebuild_entity_cache() {
  1402. if (!m_world) {
  1403. m_entityCache.reset();
  1404. return;
  1405. }
  1406. m_entityCache.reset();
  1407. auto &owners = Game::Systems::OwnerRegistry::instance();
  1408. auto entities = m_world->get_entities_with<Engine::Core::UnitComponent>();
  1409. for (auto *e : entities) {
  1410. auto *unit = e->get_component<Engine::Core::UnitComponent>();
  1411. if ((unit == nullptr) || unit->health <= 0) {
  1412. continue;
  1413. }
  1414. if (unit->owner_id == m_runtime.local_owner_id) {
  1415. if (unit->spawn_type == Game::Units::SpawnType::Barracks) {
  1416. m_entityCache.playerBarracksAlive = true;
  1417. } else {
  1418. int const production_cost =
  1419. Game::Units::TroopConfig::instance().getProductionCost(
  1420. unit->spawn_type);
  1421. m_entityCache.playerTroopCount += production_cost;
  1422. }
  1423. } else if (owners.is_ai(unit->owner_id)) {
  1424. if (unit->spawn_type == Game::Units::SpawnType::Barracks) {
  1425. m_entityCache.enemyBarracksCount++;
  1426. m_entityCache.enemyBarracksAlive = true;
  1427. }
  1428. }
  1429. }
  1430. auto emit_if_changed = [&] {
  1431. if (m_entityCache.playerTroopCount != m_runtime.lastTroopCount) {
  1432. m_runtime.lastTroopCount = m_entityCache.playerTroopCount;
  1433. emit troop_count_changed();
  1434. }
  1435. };
  1436. emit_if_changed();
  1437. }
  1438. void GameEngine::rebuild_registries_after_load() {
  1439. if (!m_world) {
  1440. return;
  1441. }
  1442. auto &owner_registry = Game::Systems::OwnerRegistry::instance();
  1443. m_runtime.local_owner_id = owner_registry.get_local_player_id();
  1444. auto &troops = Game::Systems::TroopCountRegistry::instance();
  1445. troops.rebuild_from_world(*m_world);
  1446. auto &stats_registry = Game::Systems::GlobalStatsRegistry::instance();
  1447. stats_registry.rebuild_from_world(*m_world);
  1448. const auto &all_owners = owner_registry.get_all_owners();
  1449. for (const auto &owner : all_owners) {
  1450. if (owner.type == Game::Systems::OwnerType::Player ||
  1451. owner.type == Game::Systems::OwnerType::AI) {
  1452. stats_registry.mark_game_start(owner.owner_id);
  1453. }
  1454. }
  1455. rebuild_building_collisions();
  1456. m_level.player_unit_id = 0;
  1457. auto units = m_world->get_entities_with<Engine::Core::UnitComponent>();
  1458. for (auto *entity : units) {
  1459. auto *unit = entity->get_component<Engine::Core::UnitComponent>();
  1460. if (unit == nullptr) {
  1461. continue;
  1462. }
  1463. if (unit->owner_id == m_runtime.local_owner_id) {
  1464. m_level.player_unit_id = entity->get_id();
  1465. break;
  1466. }
  1467. }
  1468. if (m_selected_player_id != m_runtime.local_owner_id) {
  1469. m_selected_player_id = m_runtime.local_owner_id;
  1470. emit selected_player_id_changed();
  1471. }
  1472. }
  1473. void GameEngine::rebuild_building_collisions() {
  1474. auto &registry = Game::Systems::BuildingCollisionRegistry::instance();
  1475. registry.clear();
  1476. if (!m_world) {
  1477. return;
  1478. }
  1479. auto buildings =
  1480. m_world->get_entities_with<Engine::Core::BuildingComponent>();
  1481. for (auto *entity : buildings) {
  1482. auto *transform = entity->get_component<Engine::Core::TransformComponent>();
  1483. auto *unit = entity->get_component<Engine::Core::UnitComponent>();
  1484. if ((transform == nullptr) || (unit == nullptr)) {
  1485. continue;
  1486. }
  1487. registry.register_building(
  1488. entity->get_id(), Game::Units::spawn_typeToString(unit->spawn_type),
  1489. transform->position.x, transform->position.z, unit->owner_id);
  1490. }
  1491. }
  1492. auto GameEngine::to_runtime_snapshot() const -> Game::Systems::RuntimeSnapshot {
  1493. Game::Systems::RuntimeSnapshot snap;
  1494. snap.paused = m_runtime.paused;
  1495. snap.time_scale = m_runtime.time_scale;
  1496. snap.local_owner_id = m_runtime.local_owner_id;
  1497. snap.victory_state = m_runtime.victory_state;
  1498. snap.cursor_mode = CursorModeUtils::toInt(m_runtime.cursor_mode);
  1499. snap.selected_player_id = m_selected_player_id;
  1500. snap.follow_selection = m_followSelectionEnabled;
  1501. return snap;
  1502. }
  1503. void GameEngine::apply_runtime_snapshot(
  1504. const Game::Systems::RuntimeSnapshot &snapshot) {
  1505. m_runtime.local_owner_id = snapshot.local_owner_id;
  1506. set_paused(snapshot.paused);
  1507. set_game_speed(snapshot.time_scale);
  1508. if (snapshot.victory_state != m_runtime.victory_state) {
  1509. m_runtime.victory_state = snapshot.victory_state;
  1510. emit victory_state_changed();
  1511. }
  1512. set_cursor_mode(CursorModeUtils::fromInt(snapshot.cursor_mode));
  1513. if (snapshot.selected_player_id != m_selected_player_id) {
  1514. m_selected_player_id = snapshot.selected_player_id;
  1515. emit selected_player_id_changed();
  1516. }
  1517. if (snapshot.follow_selection != m_followSelectionEnabled) {
  1518. m_followSelectionEnabled = snapshot.follow_selection;
  1519. if (m_camera && m_cameraService && m_world) {
  1520. m_cameraService->follow_selection(*m_camera, *m_world,
  1521. m_followSelectionEnabled);
  1522. }
  1523. }
  1524. }
  1525. auto GameEngine::capture_screenshot() const -> QByteArray {
  1526. if (m_window == nullptr) {
  1527. return {};
  1528. }
  1529. QImage const image = m_window->grabWindow();
  1530. if (image.isNull()) {
  1531. return {};
  1532. }
  1533. const QSize target_size(320, 180);
  1534. QImage const scaled =
  1535. image.scaled(target_size, Qt::KeepAspectRatio, Qt::SmoothTransformation);
  1536. QByteArray buffer;
  1537. QBuffer q_buffer(&buffer);
  1538. if (!q_buffer.open(QIODevice::WriteOnly)) {
  1539. return {};
  1540. }
  1541. if (!scaled.save(&q_buffer, "PNG")) {
  1542. return {};
  1543. }
  1544. return buffer;
  1545. }
  1546. void GameEngine::restore_environment_from_metadata(
  1547. const QJsonObject &metadata) {
  1548. if (!m_world) {
  1549. return;
  1550. }
  1551. const auto fallback_grid_width = metadata.value("grid_width").toInt(50);
  1552. const auto fallback_grid_height = metadata.value("grid_height").toInt(50);
  1553. const float fallback_tile_size =
  1554. static_cast<float>(metadata.value("tile_size").toDouble(1.0));
  1555. auto &terrain_service = Game::Map::TerrainService::instance();
  1556. bool const terrain_already_restored = terrain_service.is_initialized();
  1557. Game::Map::MapDefinition def;
  1558. QString map_error;
  1559. bool loaded_definition = false;
  1560. const QString &map_path = m_level.map_path;
  1561. if (!terrain_already_restored && !map_path.isEmpty()) {
  1562. loaded_definition =
  1563. Game::Map::MapLoader::loadFromJsonFile(map_path, def, &map_error);
  1564. if (!loaded_definition) {
  1565. qWarning() << "GameEngine: Failed to load map definition from" << map_path
  1566. << "during save load:" << map_error;
  1567. }
  1568. }
  1569. if (!terrain_already_restored && loaded_definition) {
  1570. terrain_service.initialize(def);
  1571. if (!def.name.isEmpty()) {
  1572. m_level.map_name = def.name;
  1573. }
  1574. m_level.cam_fov = def.camera.fovY;
  1575. m_level.cam_near = def.camera.near_plane;
  1576. m_level.cam_far = def.camera.far_plane;
  1577. }
  1578. if (m_renderer && m_camera) {
  1579. if (loaded_definition) {
  1580. Game::Map::Environment::apply(def, *m_renderer, *m_camera);
  1581. generate_minimap_for_map(def);
  1582. } else {
  1583. Game::Map::Environment::applyDefault(*m_renderer, *m_camera);
  1584. }
  1585. }
  1586. if (terrain_service.is_initialized()) {
  1587. const auto *height_map = terrain_service.get_height_map();
  1588. const int grid_width =
  1589. (height_map != nullptr) ? height_map->getWidth() : fallback_grid_width;
  1590. const int grid_height = (height_map != nullptr) ? height_map->getHeight()
  1591. : fallback_grid_height;
  1592. const float tile_size = (height_map != nullptr) ? height_map->getTileSize()
  1593. : fallback_tile_size;
  1594. if (m_ground) {
  1595. m_ground->configure(tile_size, grid_width, grid_height);
  1596. m_ground->setBiome(terrain_service.biome_settings());
  1597. }
  1598. if (height_map != nullptr) {
  1599. if (m_terrain) {
  1600. m_terrain->configure(*height_map, terrain_service.biome_settings());
  1601. }
  1602. if (m_river) {
  1603. m_river->configure(height_map->getRiverSegments(),
  1604. height_map->getTileSize());
  1605. }
  1606. if (m_road) {
  1607. m_road->configure(terrain_service.road_segments(),
  1608. height_map->getTileSize());
  1609. }
  1610. if (m_riverbank) {
  1611. m_riverbank->configure(height_map->getRiverSegments(), *height_map);
  1612. }
  1613. if (m_bridge) {
  1614. m_bridge->configure(height_map->getBridges(),
  1615. height_map->getTileSize());
  1616. }
  1617. if (m_biome) {
  1618. m_biome->configure(*height_map, terrain_service.biome_settings());
  1619. m_biome->refreshGrass();
  1620. }
  1621. if (m_stone) {
  1622. m_stone->configure(*height_map, terrain_service.biome_settings());
  1623. }
  1624. if (m_plant) {
  1625. m_plant->configure(*height_map, terrain_service.biome_settings());
  1626. }
  1627. if (m_pine) {
  1628. m_pine->configure(*height_map, terrain_service.biome_settings());
  1629. }
  1630. if (m_olive) {
  1631. m_olive->configure(*height_map, terrain_service.biome_settings());
  1632. }
  1633. if (m_firecamp) {
  1634. m_firecamp->configure(*height_map, terrain_service.biome_settings());
  1635. }
  1636. }
  1637. Game::Systems::CommandService::initialize(grid_width, grid_height);
  1638. auto &visibility_service = Game::Map::VisibilityService::instance();
  1639. visibility_service.initialize(grid_width, grid_height, tile_size);
  1640. visibility_service.computeImmediate(*m_world, m_runtime.local_owner_id);
  1641. if (m_fog && visibility_service.is_initialized()) {
  1642. m_fog->updateMask(
  1643. visibility_service.getWidth(), visibility_service.getHeight(),
  1644. visibility_service.getTileSize(), visibility_service.snapshotCells());
  1645. }
  1646. m_runtime.visibilityVersion = visibility_service.version();
  1647. m_runtime.visibilityUpdateAccumulator = 0.0F;
  1648. } else {
  1649. if (m_renderer && m_camera) {
  1650. Game::Map::Environment::applyDefault(*m_renderer, *m_camera);
  1651. }
  1652. Game::Map::MapDefinition fallback_def;
  1653. fallback_def.grid.width = fallback_grid_width;
  1654. fallback_def.grid.height = fallback_grid_height;
  1655. fallback_def.grid.tile_size = fallback_tile_size;
  1656. fallback_def.max_troops_per_player = m_level.max_troops_per_player;
  1657. terrain_service.initialize(fallback_def);
  1658. if (m_ground) {
  1659. m_ground->configure(fallback_tile_size, fallback_grid_width,
  1660. fallback_grid_height);
  1661. }
  1662. Game::Systems::CommandService::initialize(fallback_grid_width,
  1663. fallback_grid_height);
  1664. auto &visibility_service = Game::Map::VisibilityService::instance();
  1665. visibility_service.initialize(fallback_grid_width, fallback_grid_height,
  1666. fallback_tile_size);
  1667. visibility_service.computeImmediate(*m_world, m_runtime.local_owner_id);
  1668. if (m_fog && visibility_service.is_initialized()) {
  1669. m_fog->updateMask(
  1670. visibility_service.getWidth(), visibility_service.getHeight(),
  1671. visibility_service.getTileSize(), visibility_service.snapshotCells());
  1672. }
  1673. m_runtime.visibilityVersion = visibility_service.version();
  1674. m_runtime.visibilityUpdateAccumulator = 0.0F;
  1675. }
  1676. }
  1677. auto GameEngine::has_patrol_preview_waypoint() const -> bool {
  1678. return m_commandController && m_commandController->hasPatrolFirstWaypoint();
  1679. }
  1680. auto GameEngine::get_patrol_preview_waypoint() const -> QVector3D {
  1681. if (!m_commandController) {
  1682. return {};
  1683. }
  1684. return m_commandController->getPatrolFirstWaypoint();
  1685. }
  1686. void GameEngine::update_ambient_state(float dt) {
  1687. m_ambientCheckTimer += dt;
  1688. const float check_interval = 2.0F;
  1689. if (m_ambientCheckTimer < check_interval) {
  1690. return;
  1691. }
  1692. m_ambientCheckTimer = 0.0F;
  1693. Engine::Core::AmbientState new_state = Engine::Core::AmbientState::PEACEFUL;
  1694. if (!m_runtime.victory_state.isEmpty()) {
  1695. if (m_runtime.victory_state == "victory") {
  1696. new_state = Engine::Core::AmbientState::VICTORY;
  1697. } else if (m_runtime.victory_state == "defeat") {
  1698. new_state = Engine::Core::AmbientState::DEFEAT;
  1699. }
  1700. } else if (is_player_in_combat()) {
  1701. new_state = Engine::Core::AmbientState::COMBAT;
  1702. } else if (m_entityCache.enemyBarracksAlive &&
  1703. m_entityCache.playerBarracksAlive) {
  1704. new_state = Engine::Core::AmbientState::TENSE;
  1705. }
  1706. if (new_state != m_currentAmbientState) {
  1707. Engine::Core::AmbientState const previous_state = m_currentAmbientState;
  1708. m_currentAmbientState = new_state;
  1709. Engine::Core::EventManager::instance().publish(
  1710. Engine::Core::AmbientStateChangedEvent(new_state, previous_state));
  1711. qInfo() << "Ambient state changed from" << static_cast<int>(previous_state)
  1712. << "to" << static_cast<int>(new_state);
  1713. }
  1714. }
  1715. auto GameEngine::is_player_in_combat() const -> bool {
  1716. if (!m_world) {
  1717. return false;
  1718. }
  1719. auto units = m_world->get_entities_with<Engine::Core::UnitComponent>();
  1720. const float combat_check_radius = 15.0F;
  1721. for (auto *entity : units) {
  1722. auto *unit = entity->get_component<Engine::Core::UnitComponent>();
  1723. if ((unit == nullptr) || unit->owner_id != m_runtime.local_owner_id ||
  1724. unit->health <= 0) {
  1725. continue;
  1726. }
  1727. if (entity->has_component<Engine::Core::AttackTargetComponent>()) {
  1728. return true;
  1729. }
  1730. auto *transform = entity->get_component<Engine::Core::TransformComponent>();
  1731. if (transform == nullptr) {
  1732. continue;
  1733. }
  1734. for (auto *other_entity : units) {
  1735. auto *other_unit =
  1736. other_entity->get_component<Engine::Core::UnitComponent>();
  1737. if ((other_unit == nullptr) ||
  1738. other_unit->owner_id == m_runtime.local_owner_id ||
  1739. other_unit->health <= 0) {
  1740. continue;
  1741. }
  1742. auto *other_transform =
  1743. other_entity->get_component<Engine::Core::TransformComponent>();
  1744. if (other_transform == nullptr) {
  1745. continue;
  1746. }
  1747. float const dx = transform->position.x - other_transform->position.x;
  1748. float const dz = transform->position.z - other_transform->position.z;
  1749. float const dist_sq = dx * dx + dz * dz;
  1750. if (dist_sq < combat_check_radius * combat_check_radius) {
  1751. return true;
  1752. }
  1753. }
  1754. }
  1755. return false;
  1756. }
  1757. void GameEngine::load_audio_resources() {
  1758. auto &audio_sys = AudioSystem::getInstance();
  1759. QString const base_path =
  1760. QCoreApplication::applicationDirPath() + "/assets/audio/";
  1761. qInfo() << "Loading audio resources from:" << base_path;
  1762. QDir const audio_dir(base_path);
  1763. if (!audio_dir.exists()) {
  1764. qWarning() << "Audio assets directory does not exist:" << base_path;
  1765. qWarning() << "Application directory:"
  1766. << QCoreApplication::applicationDirPath();
  1767. return;
  1768. }
  1769. if (audio_sys.loadSound("archer_voice",
  1770. (base_path + "voices/archer_voice.wav").toStdString(),
  1771. AudioCategory::VOICE)) {
  1772. qInfo() << "Loaded archer voice";
  1773. } else {
  1774. qWarning() << "Failed to load archer voice from:"
  1775. << (base_path + "voices/archer_voice.wav");
  1776. }
  1777. if (audio_sys.loadSound(
  1778. "swordsman_voice",
  1779. (base_path + "voices/swordsman_voice.wav").toStdString(),
  1780. AudioCategory::VOICE)) {
  1781. qInfo() << "Loaded swordsman voice";
  1782. } else {
  1783. qWarning() << "Failed to load swordsman voice from:"
  1784. << (base_path + "voices/swordsman_voice.wav");
  1785. }
  1786. if (audio_sys.loadSound(
  1787. "spearman_voice",
  1788. (base_path + "voices/spearman_voice.wav").toStdString(),
  1789. AudioCategory::VOICE)) {
  1790. qInfo() << "Loaded spearman voice";
  1791. } else {
  1792. qWarning() << "Failed to load spearman voice from:"
  1793. << (base_path + "voices/spearman_voice.wav");
  1794. }
  1795. if (audio_sys.loadMusic("music_peaceful",
  1796. (base_path + "music/peaceful.wav").toStdString())) {
  1797. qInfo() << "Loaded peaceful music";
  1798. } else {
  1799. qWarning() << "Failed to load peaceful music from:"
  1800. << (base_path + "music/peaceful.wav");
  1801. }
  1802. if (audio_sys.loadMusic("music_tense",
  1803. (base_path + "music/tense.wav").toStdString())) {
  1804. qInfo() << "Loaded tense music";
  1805. } else {
  1806. qWarning() << "Failed to load tense music from:"
  1807. << (base_path + "music/tense.wav");
  1808. }
  1809. if (audio_sys.loadMusic("music_combat",
  1810. (base_path + "music/combat.wav").toStdString())) {
  1811. qInfo() << "Loaded combat music";
  1812. } else {
  1813. qWarning() << "Failed to load combat music from:"
  1814. << (base_path + "music/combat.wav");
  1815. }
  1816. if (audio_sys.loadMusic("music_victory",
  1817. (base_path + "music/victory.wav").toStdString())) {
  1818. qInfo() << "Loaded victory music";
  1819. } else {
  1820. qWarning() << "Failed to load victory music from:"
  1821. << (base_path + "music/victory.wav");
  1822. }
  1823. if (audio_sys.loadMusic("music_defeat",
  1824. (base_path + "music/defeat.wav").toStdString())) {
  1825. qInfo() << "Loaded defeat music";
  1826. } else {
  1827. qWarning() << "Failed to load defeat music from:"
  1828. << (base_path + "music/defeat.wav");
  1829. }
  1830. qInfo() << "Audio resources loading complete";
  1831. }
  1832. auto GameEngine::minimap_image() const -> QImage { return m_minimap_image; }
  1833. void GameEngine::generate_minimap_for_map(
  1834. const Game::Map::MapDefinition &map_def) {
  1835. Game::Map::Minimap::MinimapGenerator generator;
  1836. m_minimap_base_image = generator.generate(map_def);
  1837. if (!m_minimap_base_image.isNull()) {
  1838. qDebug() << "GameEngine: Generated minimap of size"
  1839. << m_minimap_base_image.width() << "x"
  1840. << m_minimap_base_image.height();
  1841. m_world_width = static_cast<float>(map_def.grid.width);
  1842. m_world_height = static_cast<float>(map_def.grid.height);
  1843. m_unit_layer = std::make_unique<Game::Map::Minimap::UnitLayer>();
  1844. m_unit_layer->init(m_minimap_base_image.width(),
  1845. m_minimap_base_image.height(), m_world_width,
  1846. m_world_height);
  1847. qDebug() << "GameEngine: Initialized unit layer for world" << m_world_width
  1848. << "x" << m_world_height;
  1849. m_minimap_fog_version = 0;
  1850. m_minimap_update_timer = MINIMAP_UPDATE_INTERVAL;
  1851. update_minimap_fog(0.0F);
  1852. } else {
  1853. qWarning() << "GameEngine: Failed to generate minimap";
  1854. }
  1855. }
  1856. void GameEngine::update_minimap_fog(float dt) {
  1857. if (m_minimap_base_image.isNull()) {
  1858. return;
  1859. }
  1860. m_minimap_update_timer += dt;
  1861. if (m_minimap_update_timer < MINIMAP_UPDATE_INTERVAL) {
  1862. return;
  1863. }
  1864. m_minimap_update_timer = 0.0F;
  1865. auto &visibility_service = Game::Map::VisibilityService::instance();
  1866. if (!visibility_service.is_initialized()) {
  1867. if (m_minimap_image != m_minimap_base_image) {
  1868. m_minimap_image = m_minimap_base_image;
  1869. emit minimap_image_changed();
  1870. }
  1871. return;
  1872. }
  1873. const auto current_version = visibility_service.version();
  1874. if (current_version == m_minimap_fog_version && !m_minimap_image.isNull()) {
  1875. update_minimap_units();
  1876. return;
  1877. }
  1878. m_minimap_fog_version = current_version;
  1879. const int vis_width = visibility_service.getWidth();
  1880. const int vis_height = visibility_service.getHeight();
  1881. const auto cells = visibility_service.snapshotCells();
  1882. if (cells.empty() || vis_width <= 0 || vis_height <= 0) {
  1883. m_minimap_image = m_minimap_base_image;
  1884. emit minimap_image_changed();
  1885. return;
  1886. }
  1887. m_minimap_image = m_minimap_base_image.copy();
  1888. const int img_width = m_minimap_image.width();
  1889. const int img_height = m_minimap_image.height();
  1890. constexpr float k_inv_cos = -0.70710678118F;
  1891. constexpr float k_inv_sin = 0.70710678118F;
  1892. const float scale_x =
  1893. static_cast<float>(vis_width) / static_cast<float>(img_width);
  1894. const float scale_y =
  1895. static_cast<float>(vis_height) / static_cast<float>(img_height);
  1896. constexpr int FOG_R = 45;
  1897. constexpr int FOG_G = 38;
  1898. constexpr int FOG_B = 30;
  1899. constexpr int ALPHA_UNSEEN = 180;
  1900. constexpr int ALPHA_EXPLORED = 60;
  1901. constexpr int ALPHA_VISIBLE = 0;
  1902. constexpr float ALPHA_THRESHOLD = 0.5F;
  1903. constexpr float ALPHA_SCALE = 1.0F / 255.0F;
  1904. auto get_alpha = [&cells, vis_width, ALPHA_VISIBLE, ALPHA_EXPLORED,
  1905. ALPHA_UNSEEN](int vx, int vy) -> float {
  1906. const size_t idx = static_cast<size_t>(vy * vis_width + vx);
  1907. if (idx >= cells.size()) {
  1908. return static_cast<float>(ALPHA_UNSEEN);
  1909. }
  1910. const auto state = static_cast<Game::Map::VisibilityState>(cells[idx]);
  1911. switch (state) {
  1912. case Game::Map::VisibilityState::Visible:
  1913. return static_cast<float>(ALPHA_VISIBLE);
  1914. case Game::Map::VisibilityState::Explored:
  1915. return static_cast<float>(ALPHA_EXPLORED);
  1916. default:
  1917. return static_cast<float>(ALPHA_UNSEEN);
  1918. }
  1919. };
  1920. const float half_img_w = static_cast<float>(img_width) * 0.5F;
  1921. const float half_img_h = static_cast<float>(img_height) * 0.5F;
  1922. const float half_vis_w = static_cast<float>(vis_width) * 0.5F;
  1923. const float half_vis_h = static_cast<float>(vis_height) * 0.5F;
  1924. for (int y = 0; y < img_height; ++y) {
  1925. auto *scanline = reinterpret_cast<QRgb *>(m_minimap_image.scanLine(y));
  1926. for (int x = 0; x < img_width; ++x) {
  1927. const float centered_x = static_cast<float>(x) - half_img_w;
  1928. const float centered_y = static_cast<float>(y) - half_img_h;
  1929. const float world_x = centered_x * k_inv_cos - centered_y * k_inv_sin;
  1930. const float world_y = centered_x * k_inv_sin + centered_y * k_inv_cos;
  1931. const float vis_x = (world_x * scale_x) + half_vis_w;
  1932. const float vis_y = (world_y * scale_y) + half_vis_h;
  1933. const int vx0 = std::clamp(static_cast<int>(vis_x), 0, vis_width - 1);
  1934. const int vx1 = std::clamp(vx0 + 1, 0, vis_width - 1);
  1935. const float fx = vis_x - static_cast<float>(vx0);
  1936. const int vy0 = std::clamp(static_cast<int>(vis_y), 0, vis_height - 1);
  1937. const int vy1 = std::clamp(vy0 + 1, 0, vis_height - 1);
  1938. const float fy = vis_y - static_cast<float>(vy0);
  1939. const float a00 = get_alpha(vx0, vy0);
  1940. const float a10 = get_alpha(vx1, vy0);
  1941. const float a01 = get_alpha(vx0, vy1);
  1942. const float a11 = get_alpha(vx1, vy1);
  1943. const float alpha_top = a00 + (a10 - a00) * fx;
  1944. const float alpha_bot = a01 + (a11 - a01) * fx;
  1945. const float fog_alpha = alpha_top + (alpha_bot - alpha_top) * fy;
  1946. if (fog_alpha > ALPHA_THRESHOLD) {
  1947. const QRgb original = scanline[x];
  1948. const int orig_r = qRed(original);
  1949. const int orig_g = qGreen(original);
  1950. const int orig_b = qBlue(original);
  1951. const float blend = fog_alpha * ALPHA_SCALE;
  1952. const float inv_blend = 1.0F - blend;
  1953. const int new_r = static_cast<int>(orig_r * inv_blend + FOG_R * blend);
  1954. const int new_g = static_cast<int>(orig_g * inv_blend + FOG_G * blend);
  1955. const int new_b = static_cast<int>(orig_b * inv_blend + FOG_B * blend);
  1956. scanline[x] = qRgba(new_r, new_g, new_b, 255);
  1957. }
  1958. }
  1959. }
  1960. update_minimap_units();
  1961. }
  1962. void GameEngine::update_minimap_units() {
  1963. if (m_minimap_image.isNull() || !m_unit_layer || !m_world) {
  1964. emit minimap_image_changed();
  1965. return;
  1966. }
  1967. std::vector<Game::Map::Minimap::UnitMarker> markers;
  1968. markers.reserve(128);
  1969. std::unordered_set<Engine::Core::EntityID> selected_ids;
  1970. if (auto *selection_system =
  1971. m_world->get_system<Game::Systems::SelectionSystem>()) {
  1972. const auto &sel = selection_system->get_selected_units();
  1973. selected_ids.insert(sel.begin(), sel.end());
  1974. }
  1975. {
  1976. const std::lock_guard<std::recursive_mutex> lock(
  1977. m_world->get_entity_mutex());
  1978. const auto &entities = m_world->get_entities();
  1979. for (const auto &[entity_id, entity] : entities) {
  1980. const auto *unit = entity->get_component<Engine::Core::UnitComponent>();
  1981. if (!unit) {
  1982. continue;
  1983. }
  1984. const auto *transform =
  1985. entity->get_component<Engine::Core::TransformComponent>();
  1986. if (!transform) {
  1987. continue;
  1988. }
  1989. Game::Map::Minimap::UnitMarker marker;
  1990. marker.world_x = transform->position.x;
  1991. marker.world_z = transform->position.z;
  1992. marker.owner_id = unit->owner_id;
  1993. marker.is_selected = selected_ids.count(entity_id) > 0;
  1994. marker.is_building = Game::Units::is_building_spawn(unit->spawn_type);
  1995. markers.push_back(marker);
  1996. }
  1997. }
  1998. m_unit_layer->update(markers);
  1999. const QImage &unit_overlay = m_unit_layer->get_image();
  2000. if (!unit_overlay.isNull()) {
  2001. QPainter painter(&m_minimap_image);
  2002. painter.setCompositionMode(QPainter::CompositionMode_SourceOver);
  2003. painter.drawImage(0, 0, unit_overlay);
  2004. }
  2005. emit minimap_image_changed();
  2006. }