map_loader.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452
  1. #include "map_loader.h"
  2. #include <QFile>
  3. #include <QJsonArray>
  4. #include <QJsonDocument>
  5. #include <QJsonObject>
  6. #include <QString>
  7. #include <algorithm>
  8. #include <cstdint>
  9. namespace Game::Map {
  10. static bool readGrid(const QJsonObject &obj, GridDefinition &grid) {
  11. if (obj.contains("width"))
  12. grid.width = obj.value("width").toInt(grid.width);
  13. if (obj.contains("height"))
  14. grid.height = obj.value("height").toInt(grid.height);
  15. if (obj.contains("tileSize"))
  16. grid.tileSize = float(obj.value("tileSize").toDouble(grid.tileSize));
  17. return grid.width > 0 && grid.height > 0 && grid.tileSize > 0.0f;
  18. }
  19. static bool readCamera(const QJsonObject &obj, CameraDefinition &cam) {
  20. if (obj.contains("center")) {
  21. auto arr = obj.value("center").toArray();
  22. if (arr.size() == 3) {
  23. cam.center = {float(arr[0].toDouble(0.0)), float(arr[1].toDouble(0.0)),
  24. float(arr[2].toDouble(0.0))};
  25. }
  26. }
  27. if (obj.contains("distance"))
  28. cam.distance = float(obj.value("distance").toDouble(cam.distance));
  29. if (obj.contains("tiltDeg"))
  30. cam.tiltDeg = float(obj.value("tiltDeg").toDouble(cam.tiltDeg));
  31. if (obj.contains("fovY"))
  32. cam.fovY = float(obj.value("fovY").toDouble(cam.fovY));
  33. if (obj.contains("near"))
  34. cam.nearPlane = float(obj.value("near").toDouble(cam.nearPlane));
  35. if (obj.contains("far"))
  36. cam.farPlane = float(obj.value("far").toDouble(cam.farPlane));
  37. if (obj.contains("yaw") || obj.contains("yawDeg")) {
  38. const QString k = obj.contains("yaw") ? "yaw" : "yawDeg";
  39. cam.yawDeg = float(obj.value(k).toDouble(cam.yawDeg));
  40. }
  41. return true;
  42. }
  43. static QVector3D readVector3(const QJsonValue &value,
  44. const QVector3D &fallback) {
  45. if (!value.isArray())
  46. return fallback;
  47. auto arr = value.toArray();
  48. if (arr.size() != 3)
  49. return fallback;
  50. return QVector3D(float(arr[0].toDouble(fallback.x())),
  51. float(arr[1].toDouble(fallback.y())),
  52. float(arr[2].toDouble(fallback.z())));
  53. }
  54. static void readBiome(const QJsonObject &obj, BiomeSettings &out) {
  55. if (obj.contains("seed"))
  56. out.seed = static_cast<std::uint32_t>(
  57. std::max(0.0, obj.value("seed").toDouble(out.seed)));
  58. if (obj.contains("patchDensity"))
  59. out.patchDensity =
  60. float(obj.value("patchDensity").toDouble(out.patchDensity));
  61. if (obj.contains("patchJitter"))
  62. out.patchJitter = float(obj.value("patchJitter").toDouble(out.patchJitter));
  63. if (obj.contains("bladeHeight")) {
  64. auto arr = obj.value("bladeHeight").toArray();
  65. if (arr.size() == 2) {
  66. out.bladeHeightMin = float(arr[0].toDouble(out.bladeHeightMin));
  67. out.bladeHeightMax = float(arr[1].toDouble(out.bladeHeightMax));
  68. }
  69. }
  70. if (obj.contains("bladeWidth")) {
  71. auto arr = obj.value("bladeWidth").toArray();
  72. if (arr.size() == 2) {
  73. out.bladeWidthMin = float(arr[0].toDouble(out.bladeWidthMin));
  74. out.bladeWidthMax = float(arr[1].toDouble(out.bladeWidthMax));
  75. }
  76. }
  77. if (obj.contains("backgroundBladeDensity"))
  78. out.backgroundBladeDensity =
  79. float(obj.value("backgroundBladeDensity")
  80. .toDouble(out.backgroundBladeDensity));
  81. if (obj.contains("swayStrength"))
  82. out.swayStrength =
  83. float(obj.value("swayStrength").toDouble(out.swayStrength));
  84. if (obj.contains("swaySpeed"))
  85. out.swaySpeed = float(obj.value("swaySpeed").toDouble(out.swaySpeed));
  86. if (obj.contains("heightNoise")) {
  87. auto arr = obj.value("heightNoise").toArray();
  88. if (arr.size() == 2) {
  89. out.heightNoiseAmplitude =
  90. float(arr[0].toDouble(out.heightNoiseAmplitude));
  91. out.heightNoiseFrequency =
  92. float(arr[1].toDouble(out.heightNoiseFrequency));
  93. }
  94. }
  95. if (obj.contains("grassPrimary"))
  96. out.grassPrimary = readVector3(obj.value("grassPrimary"), out.grassPrimary);
  97. if (obj.contains("grassSecondary"))
  98. out.grassSecondary =
  99. readVector3(obj.value("grassSecondary"), out.grassSecondary);
  100. if (obj.contains("grassDry"))
  101. out.grassDry = readVector3(obj.value("grassDry"), out.grassDry);
  102. if (obj.contains("soilColor"))
  103. out.soilColor = readVector3(obj.value("soilColor"), out.soilColor);
  104. if (obj.contains("rockLow"))
  105. out.rockLow = readVector3(obj.value("rockLow"), out.rockLow);
  106. if (obj.contains("rockHigh"))
  107. out.rockHigh = readVector3(obj.value("rockHigh"), out.rockHigh);
  108. if (obj.contains("terrainMacroNoiseScale"))
  109. out.terrainMacroNoiseScale =
  110. float(obj.value("terrainMacroNoiseScale")
  111. .toDouble(out.terrainMacroNoiseScale));
  112. if (obj.contains("terrainDetailNoiseScale"))
  113. out.terrainDetailNoiseScale =
  114. float(obj.value("terrainDetailNoiseScale")
  115. .toDouble(out.terrainDetailNoiseScale));
  116. if (obj.contains("terrainSoilHeight"))
  117. out.terrainSoilHeight =
  118. float(obj.value("terrainSoilHeight").toDouble(out.terrainSoilHeight));
  119. if (obj.contains("terrainSoilSharpness"))
  120. out.terrainSoilSharpness = float(
  121. obj.value("terrainSoilSharpness").toDouble(out.terrainSoilSharpness));
  122. if (obj.contains("terrainRockThreshold"))
  123. out.terrainRockThreshold = float(
  124. obj.value("terrainRockThreshold").toDouble(out.terrainRockThreshold));
  125. if (obj.contains("terrainRockSharpness"))
  126. out.terrainRockSharpness = float(
  127. obj.value("terrainRockSharpness").toDouble(out.terrainRockSharpness));
  128. if (obj.contains("terrainAmbientBoost"))
  129. out.terrainAmbientBoost = float(
  130. obj.value("terrainAmbientBoost").toDouble(out.terrainAmbientBoost));
  131. if (obj.contains("terrainRockDetailStrength"))
  132. out.terrainRockDetailStrength =
  133. float(obj.value("terrainRockDetailStrength")
  134. .toDouble(out.terrainRockDetailStrength));
  135. if (obj.contains("backgroundSwayVariance"))
  136. out.backgroundSwayVariance =
  137. float(obj.value("backgroundSwayVariance")
  138. .toDouble(out.backgroundSwayVariance));
  139. if (obj.contains("backgroundScatterRadius"))
  140. out.backgroundScatterRadius =
  141. float(obj.value("backgroundScatterRadius")
  142. .toDouble(out.backgroundScatterRadius));
  143. if (obj.contains("plantDensity"))
  144. out.plantDensity =
  145. float(obj.value("plantDensity").toDouble(out.plantDensity));
  146. }
  147. static void readVictoryConfig(const QJsonObject &obj, VictoryConfig &out) {
  148. if (obj.contains("type")) {
  149. out.victoryType = obj.value("type").toString("elimination");
  150. }
  151. if (obj.contains("key_structures") && obj.value("key_structures").isArray()) {
  152. out.keyStructures.clear();
  153. auto arr = obj.value("key_structures").toArray();
  154. for (const auto &v : arr) {
  155. out.keyStructures.push_back(v.toString());
  156. }
  157. }
  158. if (obj.contains("duration")) {
  159. out.surviveTimeDuration = float(obj.value("duration").toDouble(0.0));
  160. }
  161. if (obj.contains("defeat_conditions") &&
  162. obj.value("defeat_conditions").isArray()) {
  163. out.defeatConditions.clear();
  164. auto arr = obj.value("defeat_conditions").toArray();
  165. for (const auto &v : arr) {
  166. out.defeatConditions.push_back(v.toString());
  167. }
  168. }
  169. }
  170. static void readSpawns(const QJsonArray &arr, std::vector<UnitSpawn> &out) {
  171. out.clear();
  172. out.reserve(arr.size());
  173. for (const auto &v : arr) {
  174. auto o = v.toObject();
  175. UnitSpawn s;
  176. s.type = o.value("type").toString();
  177. s.x = float(o.value("x").toDouble(0.0));
  178. s.z = float(o.value("z").toDouble(0.0));
  179. if (o.contains("playerId") && !o.value("playerId").isNull()) {
  180. s.playerId = o.value("playerId").toInt(0);
  181. } else {
  182. s.playerId = -1;
  183. }
  184. s.teamId = o.value("teamId").toInt(0);
  185. s.maxPopulation = o.value("maxPopulation").toInt(100);
  186. out.push_back(s);
  187. }
  188. }
  189. static void readTerrain(const QJsonArray &arr, std::vector<TerrainFeature> &out,
  190. const GridDefinition &grid, CoordSystem coordSys) {
  191. out.clear();
  192. out.reserve(arr.size());
  193. for (const auto &v : arr) {
  194. auto o = v.toObject();
  195. TerrainFeature feature;
  196. QString typeStr = o.value("type").toString("flat").toLower();
  197. if (typeStr == "mountain") {
  198. feature.type = TerrainType::Mountain;
  199. } else if (typeStr == "hill") {
  200. feature.type = TerrainType::Hill;
  201. } else {
  202. feature.type = TerrainType::Flat;
  203. }
  204. float x = float(o.value("x").toDouble(0.0));
  205. float z = float(o.value("z").toDouble(0.0));
  206. if (coordSys == CoordSystem::Grid) {
  207. const float tile = std::max(0.0001f, grid.tileSize);
  208. feature.centerX = (x - (grid.width * 0.5f - 0.5f)) * tile;
  209. feature.centerZ = (z - (grid.height * 0.5f - 0.5f)) * tile;
  210. } else {
  211. feature.centerX = x;
  212. feature.centerZ = z;
  213. }
  214. feature.radius = float(o.value("radius").toDouble(5.0));
  215. feature.width = float(o.value("width").toDouble(0.0));
  216. feature.depth = float(o.value("depth").toDouble(0.0));
  217. if (feature.width == 0.0f && feature.depth == 0.0f) {
  218. feature.width = feature.radius;
  219. feature.depth = feature.radius;
  220. }
  221. feature.height = float(o.value("height").toDouble(2.0));
  222. feature.rotationDeg = float(o.value("rotation").toDouble(0.0));
  223. if (o.contains("entrances") && o.value("entrances").isArray()) {
  224. auto entranceArr = o.value("entrances").toArray();
  225. for (const auto &e : entranceArr) {
  226. auto eObj = e.toObject();
  227. float ex = float(eObj.value("x").toDouble(0.0));
  228. float ez = float(eObj.value("z").toDouble(0.0));
  229. feature.entrances.push_back(QVector3D(ex, 0.0f, ez));
  230. }
  231. }
  232. out.push_back(feature);
  233. }
  234. }
  235. static void readRivers(const QJsonArray &arr, std::vector<RiverSegment> &out,
  236. const GridDefinition &grid, CoordSystem coordSys) {
  237. out.clear();
  238. out.reserve(arr.size());
  239. for (const auto &v : arr) {
  240. auto o = v.toObject();
  241. RiverSegment segment;
  242. if (o.contains("start") && o.value("start").isArray()) {
  243. auto startArr = o.value("start").toArray();
  244. if (startArr.size() >= 2) {
  245. float x = float(startArr[0].toDouble(0.0));
  246. float z = float(startArr[1].toDouble(0.0));
  247. if (coordSys == CoordSystem::Grid) {
  248. const float tile = std::max(0.0001f, grid.tileSize);
  249. segment.start.setX((x - (grid.width * 0.5f - 0.5f)) * tile);
  250. segment.start.setY(0.0f);
  251. segment.start.setZ((z - (grid.height * 0.5f - 0.5f)) * tile);
  252. } else {
  253. segment.start = QVector3D(x, 0.0f, z);
  254. }
  255. }
  256. }
  257. if (o.contains("end") && o.value("end").isArray()) {
  258. auto endArr = o.value("end").toArray();
  259. if (endArr.size() >= 2) {
  260. float x = float(endArr[0].toDouble(0.0));
  261. float z = float(endArr[1].toDouble(0.0));
  262. if (coordSys == CoordSystem::Grid) {
  263. const float tile = std::max(0.0001f, grid.tileSize);
  264. segment.end.setX((x - (grid.width * 0.5f - 0.5f)) * tile);
  265. segment.end.setY(0.0f);
  266. segment.end.setZ((z - (grid.height * 0.5f - 0.5f)) * tile);
  267. } else {
  268. segment.end = QVector3D(x, 0.0f, z);
  269. }
  270. }
  271. }
  272. if (o.contains("width")) {
  273. segment.width = float(o.value("width").toDouble(2.0));
  274. }
  275. out.push_back(segment);
  276. }
  277. }
  278. static void readBridges(const QJsonArray &arr, std::vector<Bridge> &out,
  279. const GridDefinition &grid, CoordSystem coordSys) {
  280. out.clear();
  281. out.reserve(arr.size());
  282. for (const auto &v : arr) {
  283. auto o = v.toObject();
  284. Bridge bridge;
  285. if (o.contains("start") && o.value("start").isArray()) {
  286. auto startArr = o.value("start").toArray();
  287. if (startArr.size() >= 2) {
  288. float x = float(startArr[0].toDouble(0.0));
  289. float z = float(startArr[1].toDouble(0.0));
  290. if (coordSys == CoordSystem::Grid) {
  291. const float tile = std::max(0.0001f, grid.tileSize);
  292. bridge.start.setX((x - (grid.width * 0.5f - 0.5f)) * tile);
  293. bridge.start.setY(0.2f);
  294. bridge.start.setZ((z - (grid.height * 0.5f - 0.5f)) * tile);
  295. } else {
  296. bridge.start = QVector3D(x, 0.2f, z);
  297. }
  298. }
  299. }
  300. if (o.contains("end") && o.value("end").isArray()) {
  301. auto endArr = o.value("end").toArray();
  302. if (endArr.size() >= 2) {
  303. float x = float(endArr[0].toDouble(0.0));
  304. float z = float(endArr[1].toDouble(0.0));
  305. if (coordSys == CoordSystem::Grid) {
  306. const float tile = std::max(0.0001f, grid.tileSize);
  307. bridge.end.setX((x - (grid.width * 0.5f - 0.5f)) * tile);
  308. bridge.end.setY(0.2f);
  309. bridge.end.setZ((z - (grid.height * 0.5f - 0.5f)) * tile);
  310. } else {
  311. bridge.end = QVector3D(x, 0.2f, z);
  312. }
  313. }
  314. }
  315. if (o.contains("width")) {
  316. bridge.width = float(o.value("width").toDouble(3.0));
  317. }
  318. if (o.contains("height")) {
  319. bridge.height = float(o.value("height").toDouble(0.5));
  320. }
  321. out.push_back(bridge);
  322. }
  323. }
  324. bool MapLoader::loadFromJsonFile(const QString &path, MapDefinition &outMap,
  325. QString *outError) {
  326. QFile f(path);
  327. if (!f.open(QIODevice::ReadOnly)) {
  328. if (outError)
  329. *outError = QString("Failed to open map file: %1").arg(path);
  330. return false;
  331. }
  332. auto data = f.readAll();
  333. f.close();
  334. QJsonParseError perr;
  335. auto doc = QJsonDocument::fromJson(data, &perr);
  336. if (perr.error != QJsonParseError::NoError) {
  337. if (outError)
  338. *outError = QString("JSON parse error at %1: %2")
  339. .arg(perr.offset)
  340. .arg(perr.errorString());
  341. return false;
  342. }
  343. if (!doc.isObject()) {
  344. if (outError)
  345. *outError = "Map JSON root must be an object";
  346. return false;
  347. }
  348. auto root = doc.object();
  349. outMap.name = root.value("name").toString("Unnamed Map");
  350. if (root.contains("coordSystem")) {
  351. const QString cs = root.value("coordSystem").toString().trimmed().toLower();
  352. if (cs == "world")
  353. outMap.coordSystem = CoordSystem::World;
  354. else
  355. outMap.coordSystem = CoordSystem::Grid;
  356. }
  357. if (root.contains("maxTroopsPerPlayer")) {
  358. outMap.maxTroopsPerPlayer = root.value("maxTroopsPerPlayer").toInt(50);
  359. }
  360. if (root.contains("grid") && root.value("grid").isObject()) {
  361. if (!readGrid(root.value("grid").toObject(), outMap.grid)) {
  362. if (outError)
  363. *outError = "Invalid grid definition";
  364. return false;
  365. }
  366. }
  367. if (root.contains("camera") && root.value("camera").isObject()) {
  368. readCamera(root.value("camera").toObject(), outMap.camera);
  369. }
  370. if (root.contains("spawns") && root.value("spawns").isArray()) {
  371. readSpawns(root.value("spawns").toArray(), outMap.spawns);
  372. }
  373. if (root.contains("terrain") && root.value("terrain").isArray()) {
  374. readTerrain(root.value("terrain").toArray(), outMap.terrain, outMap.grid,
  375. outMap.coordSystem);
  376. }
  377. if (root.contains("rivers") && root.value("rivers").isArray()) {
  378. readRivers(root.value("rivers").toArray(), outMap.rivers, outMap.grid,
  379. outMap.coordSystem);
  380. }
  381. if (root.contains("bridges") && root.value("bridges").isArray()) {
  382. readBridges(root.value("bridges").toArray(), outMap.bridges, outMap.grid,
  383. outMap.coordSystem);
  384. }
  385. if (root.contains("biome") && root.value("biome").isObject()) {
  386. readBiome(root.value("biome").toObject(), outMap.biome);
  387. }
  388. if (root.contains("victory") && root.value("victory").isObject()) {
  389. readVictoryConfig(root.value("victory").toObject(), outMap.victory);
  390. }
  391. return true;
  392. }
  393. } // namespace Game::Map