map_loader.cpp 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742
  1. #include "map_loader.h"
  2. #include "json_keys.h"
  3. #include "map/map_definition.h"
  4. #include "map/terrain.h"
  5. #include "units/spawn_type.h"
  6. #include <QDebug>
  7. #include <QFile>
  8. #include <QIODevice>
  9. #include <QJsonArray>
  10. #include <QJsonDocument>
  11. #include <QJsonObject>
  12. #include <QJsonParseError>
  13. #include <QString>
  14. #include <algorithm>
  15. #include <cstdint>
  16. #include <qfiledevice.h>
  17. #include <qglobal.h>
  18. #include <qjsondocument.h>
  19. #include <qjsonobject.h>
  20. #include <vector>
  21. namespace Game::Map {
  22. using namespace JsonKeys;
  23. namespace {
  24. auto readGrid(const QJsonObject &obj, GridDefinition &grid) -> bool {
  25. if (obj.contains(WIDTH)) {
  26. grid.width = obj.value(WIDTH).toInt(grid.width);
  27. }
  28. if (obj.contains(HEIGHT)) {
  29. grid.height = obj.value(HEIGHT).toInt(grid.height);
  30. }
  31. if (obj.contains(TILE_SIZE)) {
  32. grid.tile_size = float(obj.value(TILE_SIZE).toDouble(grid.tile_size));
  33. }
  34. return grid.width > 0 && grid.height > 0 && grid.tile_size > 0.0F;
  35. }
  36. auto readCamera(const QJsonObject &obj, CameraDefinition &cam) -> bool {
  37. if (obj.contains(CENTER)) {
  38. auto arr = obj.value(CENTER).toArray();
  39. if (arr.size() == 3) {
  40. cam.center = {float(arr[0].toDouble(0.0)), float(arr[1].toDouble(0.0)),
  41. float(arr[2].toDouble(0.0))};
  42. }
  43. }
  44. if (obj.contains(DISTANCE)) {
  45. cam.distance = float(obj.value(DISTANCE).toDouble(cam.distance));
  46. }
  47. if (obj.contains(TILT_DEG)) {
  48. cam.tiltDeg = float(obj.value(TILT_DEG).toDouble(cam.tiltDeg));
  49. }
  50. if (obj.contains(FOV_Y)) {
  51. cam.fovY = float(obj.value(FOV_Y).toDouble(cam.fovY));
  52. }
  53. if (obj.contains(NEAR)) {
  54. cam.near_plane = float(obj.value(NEAR).toDouble(cam.near_plane));
  55. }
  56. if (obj.contains(FAR)) {
  57. cam.far_plane = float(obj.value(FAR).toDouble(cam.far_plane));
  58. }
  59. if (obj.contains(YAW) || obj.contains("yaw_deg")) {
  60. const QString yaw_key = obj.contains(YAW) ? YAW : "yaw_deg";
  61. cam.yaw_deg = float(obj.value(yaw_key).toDouble(cam.yaw_deg));
  62. }
  63. return true;
  64. }
  65. auto readVector3(const QJsonValue &value,
  66. const QVector3D &fallback) -> QVector3D {
  67. if (!value.isArray()) {
  68. return fallback;
  69. }
  70. auto arr = value.toArray();
  71. if (arr.size() != 3) {
  72. return fallback;
  73. }
  74. return {float(arr[0].toDouble(fallback.x())),
  75. float(arr[1].toDouble(fallback.y())),
  76. float(arr[2].toDouble(fallback.z()))};
  77. }
  78. void readBiome(const QJsonObject &obj, BiomeSettings &out) {
  79. if (obj.contains(GROUND_TYPE)) {
  80. const QString ground_type_str = obj.value(GROUND_TYPE).toString();
  81. GroundType parsed_ground_type;
  82. if (try_parse_ground_type(ground_type_str, parsed_ground_type)) {
  83. apply_ground_type_defaults(out, parsed_ground_type);
  84. } else {
  85. qWarning() << "MapLoader: unknown ground type" << ground_type_str
  86. << "- using default (forest_mud)";
  87. apply_ground_type_defaults(out, GroundType::ForestMud);
  88. }
  89. }
  90. if (obj.contains(SEED)) {
  91. out.seed = static_cast<std::uint32_t>(
  92. std::max(0.0, obj.value(SEED).toDouble(out.seed)));
  93. }
  94. if (obj.contains(PATCH_DENSITY)) {
  95. out.patch_density =
  96. float(obj.value(PATCH_DENSITY).toDouble(out.patch_density));
  97. }
  98. if (obj.contains(PATCH_JITTER)) {
  99. out.patch_jitter =
  100. float(obj.value(PATCH_JITTER).toDouble(out.patch_jitter));
  101. }
  102. if (obj.contains(BLADE_HEIGHT)) {
  103. auto arr = obj.value(BLADE_HEIGHT).toArray();
  104. if (arr.size() == 2) {
  105. out.blade_height_min = float(arr[0].toDouble(out.blade_height_min));
  106. out.blade_height_max = float(arr[1].toDouble(out.blade_height_max));
  107. }
  108. }
  109. if (obj.contains(BLADE_WIDTH)) {
  110. auto arr = obj.value(BLADE_WIDTH).toArray();
  111. if (arr.size() == 2) {
  112. out.blade_width_min = float(arr[0].toDouble(out.blade_width_min));
  113. out.blade_width_max = float(arr[1].toDouble(out.blade_width_max));
  114. }
  115. }
  116. if (obj.contains(BACKGROUND_BLADE_DENSITY)) {
  117. out.background_blade_density =
  118. float(obj.value(BACKGROUND_BLADE_DENSITY)
  119. .toDouble(out.background_blade_density));
  120. }
  121. if (obj.contains(SWAY_STRENGTH)) {
  122. out.sway_strength =
  123. float(obj.value(SWAY_STRENGTH).toDouble(out.sway_strength));
  124. }
  125. if (obj.contains(SWAY_SPEED)) {
  126. out.sway_speed = float(obj.value(SWAY_SPEED).toDouble(out.sway_speed));
  127. }
  128. if (obj.contains(HEIGHT_NOISE)) {
  129. auto arr = obj.value(HEIGHT_NOISE).toArray();
  130. if (arr.size() == 2) {
  131. out.height_noise_amplitude =
  132. float(arr[0].toDouble(out.height_noise_amplitude));
  133. out.height_noise_frequency =
  134. float(arr[1].toDouble(out.height_noise_frequency));
  135. }
  136. }
  137. if (obj.contains(GRASS_PRIMARY)) {
  138. out.grass_primary =
  139. readVector3(obj.value(GRASS_PRIMARY), out.grass_primary);
  140. }
  141. if (obj.contains(GRASS_SECONDARY)) {
  142. out.grass_secondary =
  143. readVector3(obj.value(GRASS_SECONDARY), out.grass_secondary);
  144. }
  145. if (obj.contains(GRASS_DRY)) {
  146. out.grass_dry = readVector3(obj.value(GRASS_DRY), out.grass_dry);
  147. }
  148. if (obj.contains(SOIL_COLOR)) {
  149. out.soil_color = readVector3(obj.value(SOIL_COLOR), out.soil_color);
  150. }
  151. if (obj.contains(ROCK_LOW)) {
  152. out.rock_low = readVector3(obj.value(ROCK_LOW), out.rock_low);
  153. }
  154. if (obj.contains(ROCK_HIGH)) {
  155. out.rock_high = readVector3(obj.value(ROCK_HIGH), out.rock_high);
  156. }
  157. if (obj.contains(TERRAIN_MACRO_NOISE_SCALE)) {
  158. out.terrain_macro_noise_scale =
  159. float(obj.value(TERRAIN_MACRO_NOISE_SCALE)
  160. .toDouble(out.terrain_macro_noise_scale));
  161. }
  162. if (obj.contains(TERRAIN_DETAIL_NOISE_SCALE)) {
  163. out.terrain_detail_noise_scale =
  164. float(obj.value(TERRAIN_DETAIL_NOISE_SCALE)
  165. .toDouble(out.terrain_detail_noise_scale));
  166. }
  167. if (obj.contains(TERRAIN_SOIL_HEIGHT)) {
  168. out.terrain_soil_height =
  169. float(obj.value(TERRAIN_SOIL_HEIGHT).toDouble(out.terrain_soil_height));
  170. }
  171. if (obj.contains(TERRAIN_SOIL_SHARPNESS)) {
  172. out.terrain_soil_sharpness = float(
  173. obj.value(TERRAIN_SOIL_SHARPNESS).toDouble(out.terrain_soil_sharpness));
  174. }
  175. if (obj.contains(TERRAIN_ROCK_THRESHOLD)) {
  176. out.terrain_rock_threshold = float(
  177. obj.value(TERRAIN_ROCK_THRESHOLD).toDouble(out.terrain_rock_threshold));
  178. }
  179. if (obj.contains(TERRAIN_ROCK_SHARPNESS)) {
  180. out.terrain_rock_sharpness = float(
  181. obj.value(TERRAIN_ROCK_SHARPNESS).toDouble(out.terrain_rock_sharpness));
  182. }
  183. if (obj.contains(TERRAIN_AMBIENT_BOOST)) {
  184. out.terrain_ambient_boost = float(
  185. obj.value(TERRAIN_AMBIENT_BOOST).toDouble(out.terrain_ambient_boost));
  186. }
  187. if (obj.contains(TERRAIN_ROCK_DETAIL_STRENGTH)) {
  188. out.terrain_rock_detail_strength =
  189. float(obj.value(TERRAIN_ROCK_DETAIL_STRENGTH)
  190. .toDouble(out.terrain_rock_detail_strength));
  191. }
  192. if (obj.contains(BACKGROUND_SWAY_VARIANCE)) {
  193. out.background_sway_variance =
  194. float(obj.value(BACKGROUND_SWAY_VARIANCE)
  195. .toDouble(out.background_sway_variance));
  196. }
  197. if (obj.contains(BACKGROUND_SCATTER_RADIUS)) {
  198. out.background_scatter_radius =
  199. float(obj.value(BACKGROUND_SCATTER_RADIUS)
  200. .toDouble(out.background_scatter_radius));
  201. }
  202. if (obj.contains(PLANT_DENSITY)) {
  203. out.plant_density =
  204. float(obj.value(PLANT_DENSITY).toDouble(out.plant_density));
  205. }
  206. if (obj.contains(GROUND_IRREGULARITY_ENABLED)) {
  207. out.ground_irregularity_enabled =
  208. obj.value(GROUND_IRREGULARITY_ENABLED)
  209. .toBool(out.ground_irregularity_enabled);
  210. }
  211. if (obj.contains(IRREGULARITY_SCALE)) {
  212. out.irregularity_scale =
  213. float(obj.value(IRREGULARITY_SCALE).toDouble(out.irregularity_scale));
  214. }
  215. if (obj.contains(IRREGULARITY_AMPLITUDE)) {
  216. out.irregularity_amplitude = float(
  217. obj.value(IRREGULARITY_AMPLITUDE).toDouble(out.irregularity_amplitude));
  218. }
  219. }
  220. void readVictoryConfig(const QJsonObject &obj, VictoryConfig &out) {
  221. if (obj.contains("type")) {
  222. out.victoryType = obj.value("type").toString("elimination");
  223. }
  224. if (obj.contains("key_structures") && obj.value("key_structures").isArray()) {
  225. out.keyStructures.clear();
  226. auto arr = obj.value("key_structures").toArray();
  227. for (const auto &val : arr) {
  228. out.keyStructures.push_back(val.toString());
  229. }
  230. }
  231. if (obj.contains("duration")) {
  232. out.surviveTimeDuration = float(obj.value("duration").toDouble(0.0));
  233. }
  234. if (obj.contains("defeat_conditions") &&
  235. obj.value("defeat_conditions").isArray()) {
  236. out.defeatConditions.clear();
  237. auto arr = obj.value("defeat_conditions").toArray();
  238. for (const auto &val : arr) {
  239. out.defeatConditions.push_back(val.toString());
  240. }
  241. }
  242. if (obj.contains("min_count")) {
  243. out.requiredKeyStructures = obj.value("min_count").toInt(0);
  244. }
  245. }
  246. void readRainConfig(const QJsonObject &obj, RainSettings &out) {
  247. if (obj.contains(RAIN_ENABLED)) {
  248. out.enabled = obj.value(RAIN_ENABLED).toBool(out.enabled);
  249. }
  250. if (obj.contains(RAIN_TYPE)) {
  251. QString type_str = obj.value(RAIN_TYPE).toString("rain").toLower();
  252. if (type_str == "snow") {
  253. out.type = WeatherType::Snow;
  254. } else {
  255. out.type = WeatherType::Rain;
  256. }
  257. }
  258. if (obj.contains(RAIN_CYCLE_DURATION)) {
  259. out.cycle_duration =
  260. float(obj.value(RAIN_CYCLE_DURATION).toDouble(out.cycle_duration));
  261. }
  262. if (obj.contains(RAIN_ACTIVE_DURATION)) {
  263. out.active_duration =
  264. float(obj.value(RAIN_ACTIVE_DURATION).toDouble(out.active_duration));
  265. }
  266. if (obj.contains(RAIN_INTENSITY)) {
  267. out.intensity = float(obj.value(RAIN_INTENSITY).toDouble(out.intensity));
  268. }
  269. if (obj.contains(RAIN_FADE_DURATION)) {
  270. out.fade_duration =
  271. float(obj.value(RAIN_FADE_DURATION).toDouble(out.fade_duration));
  272. }
  273. if (obj.contains(RAIN_WIND_STRENGTH)) {
  274. out.wind_strength =
  275. float(obj.value(RAIN_WIND_STRENGTH).toDouble(out.wind_strength));
  276. }
  277. }
  278. void readSpawns(const QJsonArray &arr, std::vector<UnitSpawn> &out) {
  279. out.clear();
  280. out.reserve(arr.size());
  281. for (const auto &spawn_val : arr) {
  282. auto spawn_obj = spawn_val.toObject();
  283. UnitSpawn spawn;
  284. const QString type_str = spawn_obj.value(TYPE).toString();
  285. if (!Game::Units::tryParseSpawnType(type_str, spawn.type)) {
  286. qWarning() << "MapLoader: unknown spawn type" << type_str << "- skipping";
  287. continue;
  288. }
  289. spawn.x = float(spawn_obj.value(X).toDouble(0.0));
  290. spawn.z = float(spawn_obj.value(Z).toDouble(0.0));
  291. if (spawn_obj.contains(PLAYER_ID) && !spawn_obj.value(PLAYER_ID).isNull()) {
  292. spawn.player_id = spawn_obj.value(PLAYER_ID).toInt(0);
  293. } else {
  294. spawn.player_id = -1;
  295. }
  296. spawn.team_id = spawn_obj.value(TEAM_ID).toInt(0);
  297. constexpr int default_max_population = 100;
  298. spawn.max_population =
  299. spawn_obj.value(MAX_POPULATION).toInt(default_max_population);
  300. if (spawn_obj.contains(NATION)) {
  301. const QString nation_str = spawn_obj.value(NATION).toString();
  302. if (Game::Systems::NationID parsed_nation_id;
  303. Game::Systems::try_parse_nation_id(nation_str, parsed_nation_id)) {
  304. spawn.nation = parsed_nation_id;
  305. } else {
  306. qWarning() << "MapLoader: unknown nation" << nation_str
  307. << "- will use default";
  308. }
  309. }
  310. out.push_back(spawn);
  311. }
  312. }
  313. void readFireCamps(const QJsonArray &arr, std::vector<FireCamp> &out) {
  314. out.clear();
  315. out.reserve(arr.size());
  316. for (const auto &camp_val : arr) {
  317. auto camp_obj = camp_val.toObject();
  318. FireCamp fire_camp;
  319. fire_camp.x = float(camp_obj.value("x").toDouble(0.0));
  320. fire_camp.z = float(camp_obj.value("z").toDouble(0.0));
  321. fire_camp.intensity = float(camp_obj.value("intensity").toDouble(1.0));
  322. constexpr double default_firecamp_radius = 3.0;
  323. fire_camp.radius =
  324. float(camp_obj.value("radius").toDouble(default_firecamp_radius));
  325. fire_camp.persistent = camp_obj.value("persistent").toBool(true);
  326. out.push_back(fire_camp);
  327. }
  328. }
  329. void readTerrain(const QJsonArray &arr, std::vector<TerrainFeature> &out,
  330. const GridDefinition &grid, CoordSystem coordSys) {
  331. out.clear();
  332. out.reserve(arr.size());
  333. constexpr float grid_center_offset = 0.5F;
  334. constexpr float min_tile_size = 0.0001F;
  335. constexpr double default_terrain_radius = 5.0;
  336. constexpr double default_terrain_height = 2.0;
  337. for (const auto &terrain_val : arr) {
  338. auto terrain_obj = terrain_val.toObject();
  339. TerrainFeature feature;
  340. const QString type_str = terrain_obj.value("type").toString("flat");
  341. if (!tryParseTerrainType(type_str, feature.type)) {
  342. qWarning() << "MapLoader: unknown terrain type" << type_str
  343. << "- defaulting to flat";
  344. feature.type = TerrainType::Flat;
  345. }
  346. const float coord_x = float(terrain_obj.value("x").toDouble(0.0));
  347. const float coord_z = float(terrain_obj.value("z").toDouble(0.0));
  348. if (coordSys == CoordSystem::Grid) {
  349. const float tile = std::max(min_tile_size, grid.tile_size);
  350. feature.center_x =
  351. (coord_x - (grid.width * grid_center_offset - grid_center_offset)) *
  352. tile;
  353. feature.center_z =
  354. (coord_z - (grid.height * grid_center_offset - grid_center_offset)) *
  355. tile;
  356. } else {
  357. feature.center_x = coord_x;
  358. feature.center_z = coord_z;
  359. }
  360. feature.radius =
  361. float(terrain_obj.value("radius").toDouble(default_terrain_radius));
  362. feature.width = float(terrain_obj.value("width").toDouble(0.0));
  363. feature.depth = float(terrain_obj.value("depth").toDouble(0.0));
  364. if (feature.width == 0.0F && feature.depth == 0.0F) {
  365. feature.width = feature.radius;
  366. feature.depth = feature.radius;
  367. }
  368. feature.height =
  369. float(terrain_obj.value("height").toDouble(default_terrain_height));
  370. feature.rotationDeg = float(terrain_obj.value("rotation").toDouble(0.0));
  371. if (terrain_obj.contains("entrances") &&
  372. terrain_obj.value("entrances").isArray()) {
  373. auto entrance_arr = terrain_obj.value("entrances").toArray();
  374. for (const auto &entrance_val : entrance_arr) {
  375. auto entrance_obj = entrance_val.toObject();
  376. const float entrance_x = float(entrance_obj.value("x").toDouble(0.0));
  377. const float entrance_z = float(entrance_obj.value("z").toDouble(0.0));
  378. float world_x = entrance_x;
  379. float world_z = entrance_z;
  380. if (coordSys == CoordSystem::Grid) {
  381. const float tile = std::max(min_tile_size, grid.tile_size);
  382. world_x = (entrance_x -
  383. (grid.width * grid_center_offset - grid_center_offset)) *
  384. tile;
  385. world_z = (entrance_z -
  386. (grid.height * grid_center_offset - grid_center_offset)) *
  387. tile;
  388. }
  389. feature.entrances.emplace_back(world_x, 0.0F, world_z);
  390. }
  391. }
  392. out.push_back(feature);
  393. }
  394. }
  395. void readRivers(const QJsonArray &arr, std::vector<RiverSegment> &out,
  396. const GridDefinition &grid, CoordSystem coordSys) {
  397. out.clear();
  398. out.reserve(arr.size());
  399. constexpr float grid_center_offset = 0.5F;
  400. constexpr float min_tile_size = 0.0001F;
  401. constexpr double default_river_width = 2.0;
  402. for (const auto &river_val : arr) {
  403. auto river_obj = river_val.toObject();
  404. RiverSegment segment;
  405. if (river_obj.contains("start") && river_obj.value("start").isArray()) {
  406. auto start_arr = river_obj.value("start").toArray();
  407. if (start_arr.size() >= 2) {
  408. const float start_x = float(start_arr[0].toDouble(0.0));
  409. const float start_z = float(start_arr[1].toDouble(0.0));
  410. if (coordSys == CoordSystem::Grid) {
  411. const float tile = std::max(min_tile_size, grid.tile_size);
  412. segment.start.setX((start_x - (grid.width * grid_center_offset -
  413. grid_center_offset)) *
  414. tile);
  415. segment.start.setY(0.0F);
  416. segment.start.setZ((start_z - (grid.height * grid_center_offset -
  417. grid_center_offset)) *
  418. tile);
  419. } else {
  420. segment.start = QVector3D(start_x, 0.0F, start_z);
  421. }
  422. }
  423. }
  424. if (river_obj.contains("end") && river_obj.value("end").isArray()) {
  425. auto end_arr = river_obj.value("end").toArray();
  426. if (end_arr.size() >= 2) {
  427. const float end_x = float(end_arr[0].toDouble(0.0));
  428. const float end_z = float(end_arr[1].toDouble(0.0));
  429. if (coordSys == CoordSystem::Grid) {
  430. const float tile = std::max(min_tile_size, grid.tile_size);
  431. segment.end.setX(
  432. (end_x - (grid.width * grid_center_offset - grid_center_offset)) *
  433. tile);
  434. segment.end.setY(0.0F);
  435. segment.end.setZ((end_z - (grid.height * grid_center_offset -
  436. grid_center_offset)) *
  437. tile);
  438. } else {
  439. segment.end = QVector3D(end_x, 0.0F, end_z);
  440. }
  441. }
  442. }
  443. if (river_obj.contains("width")) {
  444. segment.width =
  445. float(river_obj.value("width").toDouble(default_river_width));
  446. }
  447. out.push_back(segment);
  448. }
  449. }
  450. void read_roads(const QJsonArray &arr, std::vector<RoadSegment> &out,
  451. const GridDefinition &grid, CoordSystem coord_sys) {
  452. out.clear();
  453. out.reserve(arr.size());
  454. constexpr float grid_center_offset = 0.5F;
  455. constexpr float min_tile_size = 0.0001F;
  456. constexpr float default_road_width = 3.0F;
  457. for (const auto &road_val : arr) {
  458. auto road_obj = road_val.toObject();
  459. RoadSegment segment;
  460. if (road_obj.contains("start") && road_obj.value("start").isArray()) {
  461. auto start_arr = road_obj.value("start").toArray();
  462. if (start_arr.size() >= 2) {
  463. const float start_x = float(start_arr[0].toDouble(0.0));
  464. const float start_z = float(start_arr[1].toDouble(0.0));
  465. if (coord_sys == CoordSystem::Grid) {
  466. const float tile = std::max(min_tile_size, grid.tile_size);
  467. segment.start.setX((start_x - (grid.width * grid_center_offset -
  468. grid_center_offset)) *
  469. tile);
  470. segment.start.setY(0.0F);
  471. segment.start.setZ((start_z - (grid.height * grid_center_offset -
  472. grid_center_offset)) *
  473. tile);
  474. } else {
  475. segment.start = QVector3D(start_x, 0.0F, start_z);
  476. }
  477. }
  478. }
  479. if (road_obj.contains("end") && road_obj.value("end").isArray()) {
  480. auto end_arr = road_obj.value("end").toArray();
  481. if (end_arr.size() >= 2) {
  482. const float end_x = float(end_arr[0].toDouble(0.0));
  483. const float end_z = float(end_arr[1].toDouble(0.0));
  484. if (coord_sys == CoordSystem::Grid) {
  485. const float tile = std::max(min_tile_size, grid.tile_size);
  486. segment.end.setX(
  487. (end_x - (grid.width * grid_center_offset - grid_center_offset)) *
  488. tile);
  489. segment.end.setY(0.0F);
  490. segment.end.setZ((end_z - (grid.height * grid_center_offset -
  491. grid_center_offset)) *
  492. tile);
  493. } else {
  494. segment.end = QVector3D(end_x, 0.0F, end_z);
  495. }
  496. }
  497. }
  498. if (road_obj.contains("width")) {
  499. segment.width =
  500. float(road_obj.value("width").toDouble(default_road_width));
  501. }
  502. if (road_obj.contains("style")) {
  503. segment.style = road_obj.value("style").toString("default");
  504. }
  505. out.push_back(segment);
  506. }
  507. }
  508. void readBridges(const QJsonArray &arr, std::vector<Bridge> &out,
  509. const GridDefinition &grid, CoordSystem coordSys) {
  510. out.clear();
  511. out.reserve(arr.size());
  512. constexpr float bridge_y_offset = 0.2F;
  513. constexpr float grid_center_offset = 0.5F;
  514. constexpr float min_tile_size = 0.0001F;
  515. constexpr double default_bridge_width = 3.0;
  516. constexpr double default_bridge_height = 0.5;
  517. for (const auto &bridge_val : arr) {
  518. auto bridge_obj = bridge_val.toObject();
  519. Bridge bridge;
  520. if (bridge_obj.contains("start") && bridge_obj.value("start").isArray()) {
  521. auto start_arr = bridge_obj.value("start").toArray();
  522. if (start_arr.size() >= 2) {
  523. const float start_x = float(start_arr[0].toDouble(0.0));
  524. const float start_z = float(start_arr[1].toDouble(0.0));
  525. if (coordSys == CoordSystem::Grid) {
  526. const float tile = std::max(min_tile_size, grid.tile_size);
  527. bridge.start.setX((start_x - (grid.width * grid_center_offset -
  528. grid_center_offset)) *
  529. tile);
  530. bridge.start.setY(bridge_y_offset);
  531. bridge.start.setZ((start_z - (grid.height * grid_center_offset -
  532. grid_center_offset)) *
  533. tile);
  534. } else {
  535. bridge.start = QVector3D(start_x, bridge_y_offset, start_z);
  536. }
  537. }
  538. }
  539. if (bridge_obj.contains("end") && bridge_obj.value("end").isArray()) {
  540. auto end_arr = bridge_obj.value("end").toArray();
  541. if (end_arr.size() >= 2) {
  542. const float end_x = float(end_arr[0].toDouble(0.0));
  543. const float end_z = float(end_arr[1].toDouble(0.0));
  544. if (coordSys == CoordSystem::Grid) {
  545. const float tile = std::max(min_tile_size, grid.tile_size);
  546. bridge.end.setX(
  547. (end_x - (grid.width * grid_center_offset - grid_center_offset)) *
  548. tile);
  549. bridge.end.setY(bridge_y_offset);
  550. bridge.end.setZ((end_z - (grid.height * grid_center_offset -
  551. grid_center_offset)) *
  552. tile);
  553. } else {
  554. bridge.end = QVector3D(end_x, bridge_y_offset, end_z);
  555. }
  556. }
  557. }
  558. if (bridge_obj.contains("width")) {
  559. bridge.width =
  560. float(bridge_obj.value("width").toDouble(default_bridge_width));
  561. }
  562. if (bridge_obj.contains("height")) {
  563. bridge.height =
  564. float(bridge_obj.value("height").toDouble(default_bridge_height));
  565. }
  566. out.push_back(bridge);
  567. }
  568. }
  569. } // namespace
  570. auto MapLoader::loadFromJsonFile(const QString &path, MapDefinition &outMap,
  571. QString *out_error) -> bool {
  572. QFile map_file(path);
  573. if (!map_file.open(QIODevice::ReadOnly)) {
  574. if (out_error != nullptr) {
  575. *out_error = QString("Failed to open map file: %1").arg(path);
  576. }
  577. return false;
  578. }
  579. auto data = map_file.readAll();
  580. map_file.close();
  581. QJsonParseError perr;
  582. auto doc = QJsonDocument::fromJson(data, &perr);
  583. if (perr.error != QJsonParseError::NoError) {
  584. if (out_error != nullptr) {
  585. *out_error = QString("JSON parse error at %1: %2")
  586. .arg(perr.offset)
  587. .arg(perr.errorString());
  588. }
  589. return false;
  590. }
  591. if (!doc.isObject()) {
  592. if (out_error != nullptr) {
  593. *out_error = "Map JSON root must be an object";
  594. }
  595. return false;
  596. }
  597. auto root = doc.object();
  598. outMap.name = root.value(NAME).toString("Unnamed Map");
  599. if (root.contains(COORD_SYSTEM)) {
  600. const QString coord_system =
  601. root.value(COORD_SYSTEM).toString().trimmed().toLower();
  602. if (coord_system == "world") {
  603. outMap.coordSystem = CoordSystem::World;
  604. } else {
  605. outMap.coordSystem = CoordSystem::Grid;
  606. }
  607. }
  608. constexpr int default_max_troops = 500;
  609. if (root.contains(MAX_TROOPS_PER_PLAYER)) {
  610. outMap.max_troops_per_player =
  611. root.value(MAX_TROOPS_PER_PLAYER).toInt(default_max_troops);
  612. }
  613. if (root.contains(GRID) && root.value(GRID).isObject()) {
  614. if (!readGrid(root.value(GRID).toObject(), outMap.grid)) {
  615. if (out_error != nullptr) {
  616. *out_error = "Invalid grid definition";
  617. }
  618. return false;
  619. }
  620. }
  621. if (root.contains(CAMERA) && root.value(CAMERA).isObject()) {
  622. readCamera(root.value(CAMERA).toObject(), outMap.camera);
  623. }
  624. if (root.contains(SPAWNS) && root.value(SPAWNS).isArray()) {
  625. readSpawns(root.value(SPAWNS).toArray(), outMap.spawns);
  626. }
  627. if (root.contains(FIRECAMPS) && root.value(FIRECAMPS).isArray()) {
  628. readFireCamps(root.value(FIRECAMPS).toArray(), outMap.firecamps);
  629. }
  630. if (root.contains(TERRAIN) && root.value(TERRAIN).isArray()) {
  631. readTerrain(root.value(TERRAIN).toArray(), outMap.terrain, outMap.grid,
  632. outMap.coordSystem);
  633. }
  634. if (root.contains(RIVERS) && root.value(RIVERS).isArray()) {
  635. readRivers(root.value(RIVERS).toArray(), outMap.rivers, outMap.grid,
  636. outMap.coordSystem);
  637. }
  638. if (root.contains(ROADS) && root.value(ROADS).isArray()) {
  639. read_roads(root.value(ROADS).toArray(), outMap.roads, outMap.grid,
  640. outMap.coordSystem);
  641. }
  642. if (root.contains(BRIDGES) && root.value(BRIDGES).isArray()) {
  643. readBridges(root.value(BRIDGES).toArray(), outMap.bridges, outMap.grid,
  644. outMap.coordSystem);
  645. }
  646. if (root.contains(BIOME) && root.value(BIOME).isObject()) {
  647. readBiome(root.value(BIOME).toObject(), outMap.biome);
  648. }
  649. if (root.contains(VICTORY) && root.value(VICTORY).isObject()) {
  650. readVictoryConfig(root.value(VICTORY).toObject(), outMap.victory);
  651. }
  652. if (root.contains(RAIN) && root.value(RAIN).isObject()) {
  653. readRainConfig(root.value(RAIN).toObject(), outMap.rain);
  654. }
  655. return true;
  656. }
  657. } // namespace Game::Map