skirmish_loader.cpp 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557
  1. #include "skirmish_loader.h"
  2. #include "game/core/component.h"
  3. #include "game/core/world.h"
  4. #include "game/map/json_keys.h"
  5. #include "game/map/level_loader.h"
  6. #include "game/map/map_transformer.h"
  7. #include "game/map/terrain_service.h"
  8. #include "game/map/visibility_service.h"
  9. #include "game/systems/building_collision_registry.h"
  10. #include "game/systems/command_service.h"
  11. #include "game/systems/global_stats_registry.h"
  12. #include "game/systems/nation_id.h"
  13. #include "game/systems/nation_registry.h"
  14. #include "game/systems/owner_registry.h"
  15. #include "game/systems/selection_system.h"
  16. #include "game/systems/troop_count_registry.h"
  17. #include "game/visuals/team_colors.h"
  18. #include "render/ground/biome_renderer.h"
  19. #include "render/ground/bridge_renderer.h"
  20. #include "render/ground/firecamp_renderer.h"
  21. #include "render/ground/fog_renderer.h"
  22. #include "render/ground/ground_renderer.h"
  23. #include "render/ground/olive_renderer.h"
  24. #include "render/ground/pine_renderer.h"
  25. #include "render/ground/plant_renderer.h"
  26. #include "render/ground/rain_renderer.h"
  27. #include "render/ground/river_renderer.h"
  28. #include "render/ground/riverbank_renderer.h"
  29. #include "render/ground/road_renderer.h"
  30. #include "render/ground/stone_renderer.h"
  31. #include "render/ground/terrain_renderer.h"
  32. #include "render/scene_renderer.h"
  33. #include "units/spawn_type.h"
  34. #include "units/troop_type.h"
  35. #include "utils/resource_utils.h"
  36. #include <QCoreApplication>
  37. #include <QDebug>
  38. #include <QEventLoop>
  39. #include <QFile>
  40. #include <QJsonArray>
  41. #include <QJsonDocument>
  42. #include <QJsonObject>
  43. #include <QJsonParseError>
  44. #include <QSet>
  45. #include <algorithm>
  46. #include <qdir.h>
  47. #include <qfiledevice.h>
  48. #include <qglobal.h>
  49. #include <qjsonarray.h>
  50. #include <qjsondocument.h>
  51. #include <qjsonobject.h>
  52. #include <qlist.h>
  53. #include <qset.h>
  54. #include <qstringview.h>
  55. #include <qvariant.h>
  56. #include <qvectornd.h>
  57. #include <set>
  58. #include <unordered_map>
  59. #include <vector>
  60. namespace Game::Map {
  61. using namespace JsonKeys;
  62. SkirmishLoader::SkirmishLoader(Engine::Core::World &world,
  63. Render::GL::Renderer &renderer,
  64. Render::GL::Camera &camera)
  65. : m_world(world), m_renderer(renderer), m_camera(camera) {}
  66. void SkirmishLoader::reset_game_state() {
  67. if (auto *selection_system =
  68. m_world.get_system<Game::Systems::SelectionSystem>()) {
  69. selection_system->clear_selection();
  70. }
  71. m_renderer.pause();
  72. m_renderer.lock_world_for_modification();
  73. m_renderer.set_selected_entities({});
  74. m_renderer.set_hovered_entity_id(0);
  75. m_world.clear();
  76. Game::Systems::BuildingCollisionRegistry::instance().clear();
  77. auto &owner_registry = Game::Systems::OwnerRegistry::instance();
  78. owner_registry.clear();
  79. Game::Map::MapTransformer::clearPlayerTeamOverrides();
  80. auto &visibility_service = Game::Map::VisibilityService::instance();
  81. visibility_service.reset();
  82. auto &terrain_service = Game::Map::TerrainService::instance();
  83. terrain_service.clear();
  84. auto &stats_registry = Game::Systems::GlobalStatsRegistry::instance();
  85. stats_registry.clear();
  86. auto &troop_registry = Game::Systems::TroopCountRegistry::instance();
  87. troop_registry.clear();
  88. Game::Systems::NationRegistry::instance().clear_player_assignments();
  89. if (m_fog != nullptr) {
  90. m_fog->setEnabled(true);
  91. m_fog->update_mask(0, 0, 1.0F, {});
  92. }
  93. }
  94. auto SkirmishLoader::start(const QString &map_path,
  95. const QVariantList &playerConfigs,
  96. int selected_player_id,
  97. bool allow_default_player_barracks,
  98. int &out_selected_player_id) -> SkirmishLoadResult {
  99. SkirmishLoadResult result;
  100. auto pump_events = []() {
  101. QCoreApplication::processEvents(QEventLoop::AllEvents);
  102. };
  103. reset_game_state();
  104. pump_events();
  105. QSet<int> map_player_ids;
  106. const QString resolved_map_path =
  107. Utils::Resources::resolveResourcePath(map_path);
  108. QFile map_file(resolved_map_path);
  109. if (map_file.open(QIODevice::ReadOnly)) {
  110. const QByteArray data = map_file.readAll();
  111. map_file.close();
  112. QJsonParseError err;
  113. const QJsonDocument doc = QJsonDocument::fromJson(data, &err);
  114. if (err.error == QJsonParseError::NoError && doc.isObject()) {
  115. QJsonObject obj = doc.object();
  116. if (obj.contains(SPAWNS) && obj[SPAWNS].isArray()) {
  117. const QJsonArray spawns = obj[SPAWNS].toArray();
  118. for (const auto &spawn_val : spawns) {
  119. if (spawn_val.isObject()) {
  120. QJsonObject spawn = spawn_val.toObject();
  121. if (spawn.contains(PLAYER_ID)) {
  122. const int player_id = spawn[PLAYER_ID].toInt();
  123. if (player_id > 0) {
  124. map_player_ids.insert(player_id);
  125. }
  126. }
  127. }
  128. }
  129. }
  130. }
  131. } else {
  132. qWarning() << "Could not open map file for reading player IDs:"
  133. << resolved_map_path;
  134. }
  135. auto &owner_registry = Game::Systems::OwnerRegistry::instance();
  136. int player_owner_id = selected_player_id;
  137. if (!map_player_ids.contains(player_owner_id)) {
  138. if (!map_player_ids.isEmpty()) {
  139. QList<int> sorted_ids = map_player_ids.values();
  140. std::sort(sorted_ids.begin(), sorted_ids.end());
  141. player_owner_id = sorted_ids.first();
  142. qWarning() << "Selected player ID" << selected_player_id
  143. << "not found in map spawns. Using" << player_owner_id
  144. << "instead.";
  145. out_selected_player_id = player_owner_id;
  146. } else {
  147. qWarning() << "No valid player spawns found in map. Using default "
  148. "player ID"
  149. << player_owner_id;
  150. }
  151. }
  152. owner_registry.set_local_player_id(player_owner_id);
  153. std::unordered_map<int, int> team_overrides;
  154. std::unordered_map<int, Game::Systems::NationID> nation_overrides;
  155. QVariantList saved_player_configs;
  156. std::set<int> processed_player_ids;
  157. bool is_spectator_mode = false;
  158. bool has_human_player = false;
  159. if (!playerConfigs.isEmpty()) {
  160. for (const QVariant &config_var : playerConfigs) {
  161. const QVariantMap config = config_var.toMap();
  162. int player_id = config.value("player_id", -1).toInt();
  163. const int team_id = config.value("team_id", 0).toInt();
  164. const QString color_hex = config.value("colorHex", "#FFFFFF").toString();
  165. const bool is_human = config.value("isHuman", false).toBool();
  166. const QString nation_id_str = config.value("nationId").toString();
  167. if (is_human) {
  168. has_human_player = true;
  169. if (player_id != player_owner_id) {
  170. player_id = player_owner_id;
  171. }
  172. }
  173. if (processed_player_ids.contains(player_id)) {
  174. continue;
  175. }
  176. if (player_id >= 0) {
  177. processed_player_ids.insert(player_id);
  178. team_overrides[player_id] = team_id;
  179. Game::Systems::NationID chosen_nation;
  180. if (!nation_id_str.isEmpty()) {
  181. auto parsed =
  182. Game::Systems::nation_id_from_string(nation_id_str.toStdString());
  183. chosen_nation = parsed.value_or(
  184. Game::Systems::NationRegistry::instance().default_nation_id());
  185. } else {
  186. chosen_nation =
  187. Game::Systems::NationRegistry::instance().default_nation_id();
  188. }
  189. nation_overrides[player_id] = chosen_nation;
  190. QVariantMap updated_config = config;
  191. updated_config["player_id"] = player_id;
  192. saved_player_configs.append(updated_config);
  193. }
  194. }
  195. is_spectator_mode = !has_human_player && !saved_player_configs.isEmpty();
  196. }
  197. std::set<int> unique_teams;
  198. for (const auto &[player_id, team_id] : team_overrides) {
  199. unique_teams.insert(team_id);
  200. }
  201. if (team_overrides.size() >= 2 && unique_teams.size() < 2) {
  202. result.errorMessage = "Invalid team configuration: At least two teams must "
  203. "be selected to start a match.";
  204. m_renderer.unlock_world_for_modification();
  205. m_renderer.resume();
  206. qWarning() << "SkirmishLoader: " << result.errorMessage;
  207. return result;
  208. }
  209. Game::Map::MapTransformer::set_local_owner_id(player_owner_id);
  210. Game::Map::MapTransformer::setPlayerTeamOverrides(team_overrides);
  211. auto &nation_registry = Game::Systems::NationRegistry::instance();
  212. for (auto it = map_player_ids.begin(); it != map_player_ids.end(); ++it) {
  213. int player_id = *it;
  214. auto nat_it = nation_overrides.find(player_id);
  215. if (nat_it != nation_overrides.end()) {
  216. nation_registry.set_player_nation(player_id, nat_it->second);
  217. } else {
  218. nation_registry.set_player_nation(player_id,
  219. nation_registry.default_nation_id());
  220. }
  221. }
  222. if (map_player_ids.isEmpty()) {
  223. auto nat_it = nation_overrides.find(player_owner_id);
  224. if (nat_it != nation_overrides.end()) {
  225. nation_registry.set_player_nation(player_owner_id, nat_it->second);
  226. } else {
  227. nation_registry.set_player_nation(player_owner_id,
  228. nation_registry.default_nation_id());
  229. }
  230. }
  231. auto level_result = Game::Map::LevelLoader::loadFromAssets(
  232. map_path, m_world, m_renderer, m_camera, allow_default_player_barracks);
  233. pump_events();
  234. if (!level_result.ok && !level_result.errorMessage.isEmpty()) {
  235. result.errorMessage = level_result.errorMessage;
  236. m_renderer.unlock_world_for_modification();
  237. m_renderer.resume();
  238. return result;
  239. }
  240. constexpr float color_scale = 255.0F;
  241. constexpr int hex_color_length = 7;
  242. constexpr int hex_base = 16;
  243. if (!saved_player_configs.isEmpty()) {
  244. for (const QVariant &config_var : saved_player_configs) {
  245. const QVariantMap config = config_var.toMap();
  246. const int player_id = config.value("player_id", -1).toInt();
  247. const QString color_hex = config.value("colorHex", "#FFFFFF").toString();
  248. if (player_id >= 0 && color_hex.startsWith("#") &&
  249. color_hex.length() == hex_color_length) {
  250. bool conversion_ok = false;
  251. const int red = color_hex.mid(1, 2).toInt(&conversion_ok, hex_base);
  252. const int green = color_hex.mid(3, 2).toInt(&conversion_ok, hex_base);
  253. const int blue = color_hex.mid(5, 2).toInt(&conversion_ok, hex_base);
  254. owner_registry.set_owner_color(player_id, red / color_scale,
  255. green / color_scale, blue / color_scale);
  256. }
  257. }
  258. auto entities = m_world.get_entities_with<Engine::Core::UnitComponent>();
  259. pump_events();
  260. std::unordered_map<int, int> owner_entity_count;
  261. for (auto *entity : entities) {
  262. auto *unit = entity->get_component<Engine::Core::UnitComponent>();
  263. auto *renderable =
  264. entity->get_component<Engine::Core::RenderableComponent>();
  265. if ((unit != nullptr) && (renderable != nullptr)) {
  266. const QVector3D team_color =
  267. Game::Visuals::team_colorForOwner(unit->owner_id);
  268. renderable->color[0] = team_color.x();
  269. renderable->color[1] = team_color.y();
  270. renderable->color[2] = team_color.z();
  271. owner_entity_count[unit->owner_id]++;
  272. }
  273. }
  274. }
  275. pump_events();
  276. if (m_onOwnersUpdated) {
  277. m_onOwnersUpdated();
  278. }
  279. auto &terrain_service = Game::Map::TerrainService::instance();
  280. if (m_ground != nullptr) {
  281. if (level_result.ok) {
  282. m_ground->configure(level_result.tile_size, level_result.grid_width,
  283. level_result.grid_height);
  284. } else {
  285. m_ground->configure_extent(50.0F);
  286. }
  287. if (terrain_service.is_initialized()) {
  288. m_ground->setBiome(terrain_service.biome_settings());
  289. }
  290. }
  291. if (m_terrain != nullptr) {
  292. if (terrain_service.is_initialized() &&
  293. (terrain_service.get_height_map() != nullptr)) {
  294. m_terrain->configure(*terrain_service.get_height_map(),
  295. terrain_service.biome_settings());
  296. }
  297. }
  298. if (m_biome != nullptr) {
  299. if (terrain_service.is_initialized() &&
  300. (terrain_service.get_height_map() != nullptr)) {
  301. m_biome->configure(*terrain_service.get_height_map(),
  302. terrain_service.biome_settings());
  303. }
  304. }
  305. if (m_river != nullptr) {
  306. if (terrain_service.is_initialized() &&
  307. (terrain_service.get_height_map() != nullptr)) {
  308. m_river->configure(terrain_service.get_height_map()->getRiverSegments(),
  309. terrain_service.get_height_map()->getTileSize());
  310. }
  311. }
  312. if (m_road != nullptr) {
  313. if (terrain_service.is_initialized() &&
  314. (terrain_service.get_height_map() != nullptr)) {
  315. m_road->configure(terrain_service.road_segments(),
  316. terrain_service.get_height_map()->getTileSize());
  317. }
  318. }
  319. if (m_riverbank != nullptr) {
  320. if (terrain_service.is_initialized() &&
  321. (terrain_service.get_height_map() != nullptr)) {
  322. m_riverbank->configure(
  323. terrain_service.get_height_map()->getRiverSegments(),
  324. *terrain_service.get_height_map());
  325. }
  326. }
  327. if (m_bridge != nullptr) {
  328. if (terrain_service.is_initialized() &&
  329. (terrain_service.get_height_map() != nullptr)) {
  330. m_bridge->configure(terrain_service.get_height_map()->getBridges(),
  331. terrain_service.get_height_map()->getTileSize());
  332. }
  333. }
  334. if (m_stone != nullptr) {
  335. if (terrain_service.is_initialized() &&
  336. (terrain_service.get_height_map() != nullptr)) {
  337. m_stone->configure(*terrain_service.get_height_map(),
  338. terrain_service.biome_settings());
  339. }
  340. }
  341. if (m_plant != nullptr) {
  342. if (terrain_service.is_initialized() &&
  343. (terrain_service.get_height_map() != nullptr)) {
  344. m_plant->configure(*terrain_service.get_height_map(),
  345. terrain_service.biome_settings());
  346. }
  347. }
  348. if (m_pine != nullptr) {
  349. if (terrain_service.is_initialized() &&
  350. (terrain_service.get_height_map() != nullptr)) {
  351. m_pine->configure(*terrain_service.get_height_map(),
  352. terrain_service.biome_settings());
  353. }
  354. }
  355. if (m_olive != nullptr) {
  356. if (terrain_service.is_initialized() &&
  357. (terrain_service.get_height_map() != nullptr)) {
  358. m_olive->configure(*terrain_service.get_height_map(),
  359. terrain_service.biome_settings());
  360. }
  361. }
  362. if (m_firecamp != nullptr) {
  363. if (terrain_service.is_initialized() &&
  364. (terrain_service.get_height_map() != nullptr)) {
  365. m_firecamp->configure(*terrain_service.get_height_map(),
  366. terrain_service.biome_settings());
  367. const auto &fire_camps = terrain_service.fire_camps();
  368. if (!fire_camps.empty()) {
  369. std::vector<QVector3D> positions;
  370. std::vector<float> intensities;
  371. std::vector<float> radii;
  372. const auto *height_map = terrain_service.get_height_map();
  373. const float tile_size = height_map->getTileSize();
  374. const int width = height_map->getWidth();
  375. const int height = height_map->getHeight();
  376. const float half_width = width * 0.5F;
  377. const float half_height = height * 0.5F;
  378. for (const auto &fc : fire_camps) {
  379. float const world_x = (fc.x - half_width) * tile_size;
  380. float const world_z = (fc.z - half_height) * tile_size;
  381. float const world_y =
  382. terrain_service.get_terrain_height(world_x, world_z);
  383. positions.emplace_back(world_x, world_y, world_z);
  384. intensities.push_back(fc.intensity);
  385. radii.push_back(fc.radius);
  386. }
  387. m_firecamp->setExplicitFireCamps(positions, intensities, radii);
  388. }
  389. }
  390. }
  391. pump_events();
  392. if (m_rain != nullptr) {
  393. const float world_width = level_result.grid_width * level_result.tile_size;
  394. const float world_height =
  395. level_result.grid_height * level_result.tile_size;
  396. m_rain->configure(world_width, world_height, level_result.biome_seed);
  397. m_rain->set_enabled(level_result.rainSettings.enabled);
  398. m_rain->set_intensity(level_result.rainSettings.enabled
  399. ? level_result.rainSettings.intensity
  400. : 0.0F);
  401. }
  402. constexpr int default_map_size = 100;
  403. const int map_width =
  404. level_result.ok ? level_result.grid_width : default_map_size;
  405. const int map_height =
  406. level_result.ok ? level_result.grid_height : default_map_size;
  407. Game::Systems::CommandService::initialize(map_width, map_height);
  408. auto &visibility_service = Game::Map::VisibilityService::instance();
  409. visibility_service.initialize(map_width, map_height, level_result.tile_size);
  410. pump_events();
  411. if (is_spectator_mode) {
  412. visibility_service.reveal_all();
  413. if (m_fog != nullptr) {
  414. m_fog->setEnabled(false);
  415. }
  416. } else {
  417. visibility_service.computeImmediate(m_world, player_owner_id);
  418. if (m_fog != nullptr) {
  419. m_fog->setEnabled(true);
  420. }
  421. }
  422. if ((m_fog != nullptr) && visibility_service.is_initialized()) {
  423. m_fog->update_mask(
  424. visibility_service.getWidth(), visibility_service.getHeight(),
  425. visibility_service.getTileSize(), visibility_service.snapshotCells());
  426. if (m_onVisibilityMaskReady) {
  427. m_onVisibilityMaskReady();
  428. }
  429. }
  430. pump_events();
  431. if (m_biome != nullptr) {
  432. m_biome->refresh_grass();
  433. }
  434. m_renderer.unlock_world_for_modification();
  435. m_renderer.resume();
  436. Engine::Core::Entity *focus_entity = nullptr;
  437. auto candidates = m_world.get_entities_with<Engine::Core::UnitComponent>();
  438. for (auto *entity : candidates) {
  439. if (entity == nullptr) {
  440. continue;
  441. }
  442. auto *unit = entity->get_component<Engine::Core::UnitComponent>();
  443. if (unit == nullptr) {
  444. continue;
  445. }
  446. if (unit->spawn_type == Game::Units::SpawnType::Barracks &&
  447. unit->owner_id == player_owner_id && unit->health > 0) {
  448. focus_entity = entity;
  449. break;
  450. }
  451. }
  452. if ((focus_entity == nullptr) && level_result.player_unit_id != 0U) {
  453. focus_entity = m_world.get_entity(level_result.player_unit_id);
  454. }
  455. if (focus_entity != nullptr) {
  456. if (auto *transform =
  457. focus_entity->get_component<Engine::Core::TransformComponent>()) {
  458. result.focusPosition = QVector3D(
  459. transform->position.x, transform->position.y, transform->position.z);
  460. result.has_focus_position = true;
  461. }
  462. }
  463. result.ok = true;
  464. result.map_name = level_result.map_name;
  465. result.player_unit_id = level_result.player_unit_id;
  466. result.cam_fov = level_result.cam_fov;
  467. result.cam_near = level_result.cam_near;
  468. result.cam_far = level_result.cam_far;
  469. result.grid_width = level_result.grid_width;
  470. result.grid_height = level_result.grid_height;
  471. result.tile_size = level_result.tile_size;
  472. result.max_troops_per_player = level_result.max_troops_per_player;
  473. result.victoryConfig = level_result.victoryConfig;
  474. result.rainSettings = level_result.rainSettings;
  475. result.biome_seed = level_result.biome_seed;
  476. result.is_spectator_mode = is_spectator_mode;
  477. return result;
  478. }
  479. } // namespace Game::Map