game_engine.cpp 73 KB

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