Air-Trap 1.0.0
A multiplayer R-Type clone game engine built with C++23 and ECS architecture
Loading...
Searching...
No Matches
LevelData.cpp
Go to the documentation of this file.
1/*
2** EPITECH PROJECT, 2025
3** Air-Trap
4** File description:
5** LevelData
6*/
7
8#include "Game/LevelData.hpp"
9#include "RType/Logger.hpp"
10
11#include <algorithm>
12#include <cctype>
13#include <cstdlib>
14#include <fstream>
15#include <regex>
16#include <sstream>
17#include <unordered_set>
18#include <unordered_map>
19
20namespace {
21 std::string toLower(std::string value)
22 {
23 std::transform(value.begin(), value.end(), value.begin(),
24 [](unsigned char c) { return static_cast<char>(std::tolower(c)); });
25 return value;
26 }
27
28 std::optional<std::string> readFile(const std::string& path)
29 {
30 std::ifstream in(path);
31 if (!in) {
32 return std::nullopt;
33 }
34 std::ostringstream ss;
35 ss << in.rdbuf();
36 return ss.str();
37 }
38
39 size_t findMatching(const std::string& text, size_t start, char open, char close)
40 {
41 bool inString = false;
42 int depth = 1;
43
44 for (size_t i = start + 1; i < text.size(); ++i) {
45 const char c = text[i];
46 if (c == '"' && (i == 0 || text[i - 1] != '\\')) {
47 inString = !inString;
48 continue;
49 }
50 if (inString) {
51 continue;
52 }
53 if (c == open) {
54 depth++;
55 } else if (c == close) {
56 depth--;
57 if (depth == 0) {
58 return i;
59 }
60 }
61 }
62 return std::string::npos;
63 }
64
65 bool extractObject(const std::string& text, const std::string& key, std::string& out)
66 {
67 const std::string needle = "\"" + key + "\"";
68 const size_t keyPos = text.find(needle);
69 if (keyPos == std::string::npos) {
70 return false;
71 }
72 const size_t bracePos = text.find('{', keyPos);
73 if (bracePos == std::string::npos) {
74 return false;
75 }
76 const size_t endPos = findMatching(text, bracePos, '{', '}');
77 if (endPos == std::string::npos) {
78 return false;
79 }
80 out = text.substr(bracePos, endPos - bracePos + 1);
81 return true;
82 }
83
84 bool extractArray(const std::string& text, const std::string& key, std::string& out)
85 {
86 const std::string needle = "\"" + key + "\"";
87 const size_t keyPos = text.find(needle);
88 if (keyPos == std::string::npos) {
89 return false;
90 }
91 const size_t startPos = text.find('[', keyPos);
92 if (startPos == std::string::npos) {
93 return false;
94 }
95 const size_t endPos = findMatching(text, startPos, '[', ']');
96 if (endPos == std::string::npos) {
97 return false;
98 }
99 out = text.substr(startPos, endPos - startPos + 1);
100 return true;
101 }
102
103 std::vector<std::string> splitObjects(const std::string& arrayText)
104 {
105 std::vector<std::string> out;
106 bool inString = false;
107 int depth = 0;
108 size_t start = std::string::npos;
109
110 for (size_t i = 0; i < arrayText.size(); ++i) {
111 const char c = arrayText[i];
112 if (c == '"' && (i == 0 || arrayText[i - 1] != '\\')) {
113 inString = !inString;
114 continue;
115 }
116 if (inString) {
117 continue;
118 }
119 if (c == '{') {
120 if (depth == 0) {
121 start = i;
122 }
123 depth++;
124 } else if (c == '}') {
125 depth--;
126 if (depth == 0 && start != std::string::npos) {
127 out.emplace_back(arrayText.substr(start, i - start + 1));
128 start = std::string::npos;
129 }
130 }
131 }
132 return out;
133 }
134
135 bool extractString(const std::string& text, const std::string& key, std::string& out)
136 {
137 const std::regex re("\"" + key + "\"\\s*:\\s*\"([^\"]*)\"");
138 std::smatch match;
139 if (std::regex_search(text, match, re) && match.size() >= 2) {
140 out = match[1].str();
141 return true;
142 }
143 return false;
144 }
145
146 bool extractNumber(const std::string& text, const std::string& key, float& out)
147 {
148 const std::regex re("\"" + key + "\"\\s*:\\s*([-0-9.+eE]+)");
149 std::smatch match;
150 if (std::regex_search(text, match, re) && match.size() >= 2) {
151 out = std::strtof(match[1].str().c_str(), nullptr);
152 return true;
153 }
154 return false;
155 }
156
157 bool extractInt(const std::string& text, const std::string& key, int& out)
158 {
159 float value = 0.0f;
160 if (!extractNumber(text, key, value)) {
161 return false;
162 }
163 out = static_cast<int>(value);
164 return true;
165 }
166
167 bool extractVec2(const std::string& text, const std::string& key, rtp::Vec2f& out)
168 {
169 std::string obj;
170 if (!extractObject(text, key, obj)) {
171 return false;
172 }
173 float x = 0.0f;
174 float y = 0.0f;
175 if (!extractNumber(obj, "x", x) || !extractNumber(obj, "y", y)) {
176 return false;
177 }
178 out = {x, y};
179 return true;
180 }
181
182 bool extractVec2WH(const std::string& text, const std::string& key, rtp::Vec2f& out)
183 {
184 std::string obj;
185 if (!extractObject(text, key, obj)) {
186 return false;
187 }
188 float w = 0.0f;
189 float h = 0.0f;
190 if (!extractNumber(obj, "w", w) || !extractNumber(obj, "h", h)) {
191 return false;
192 }
193 out = {w, h};
194 return true;
195 }
196
197 rtp::ecs::components::Patterns parsePattern(const std::string& value)
198 {
199 const std::string lower = toLower(value);
200 if (lower == "sine" || lower == "sin") {
202 }
203 if (lower == "static") {
205 }
206 if (lower == "circular" || lower == "circle") {
208 }
209 if (lower == "zigzag" || lower == "zig-zag") {
211 }
213 }
214
215 rtp::net::EntityType parseEntityType(const std::string& value, const std::string& templatePath)
216 {
217 const std::string lower = toLower(value);
218 if (lower == "tank") {
220 }
221 if (lower == "boss") {
223 }
224 if (lower == "boss2") {
226 }
227 if (lower == "boss_shield") {
229 }
230 if (lower == "scout") {
232 }
233
234 const std::string templateLower = toLower(templatePath);
235 if (templateLower.find("boss_02") != std::string::npos || templateLower.find("boss2") != std::string::npos) {
237 }
238 if (templateLower.find("boss") != std::string::npos) {
240 }
241 if (templateLower.find("tank") != std::string::npos) {
243 }
245 }
246
247 rtp::ecs::components::PowerupType parsePowerupType(const std::string& value)
248 {
249 const std::string lower = toLower(value);
250 if (lower == "speed") {
252 }
254 }
255
256 bool extractIntArray(const std::string& text, const std::string& key, std::vector<int>& out)
257 {
258 std::string arrayText;
259 if (!extractArray(text, key, arrayText)) {
260 return false;
261 }
262
263 const std::regex re("(-?\\d+)");
264 auto begin = std::sregex_iterator(arrayText.begin(), arrayText.end(), re);
265 auto end = std::sregex_iterator();
266 for (auto it = begin; it != end; ++it) {
267 out.push_back(std::stoi((*it)[1].str()));
268 }
269 return !out.empty();
270 }
271
272 enum class TileKind {
273 None,
274 Solid,
275 Destructible
276 };
277
278 TileKind getTileKind(const std::string& tileObj)
279 {
280 const bool isSolid = tileObj.find("\"is_solid\"") != std::string::npos &&
281 tileObj.find("true") != std::string::npos;
282 const bool isDestructible = tileObj.find("\"is_destructible\"") != std::string::npos &&
283 tileObj.find("true") != std::string::npos;
284
285 if (isDestructible) {
286 return TileKind::Destructible;
287 }
288 if (isSolid) {
289 return TileKind::Solid;
290 }
291 return TileKind::None;
292 }
293
294 void appendTileObstacles(const std::string& tilesetPath,
295 std::vector<rtp::server::ObstacleEvent>& obstacles)
296 {
297 auto data = readFile(tilesetPath);
298 if (!data) {
299 rtp::log::warning("Failed to read tileset file: {}", tilesetPath);
300 return;
301 }
302
303 const std::string& json = data.value();
304
305 int height = 0;
306 int tileWidth = 32;
307 int tileHeight = 32;
308 extractInt(json, "height", height);
309 extractInt(json, "tilewidth", tileWidth);
310 extractInt(json, "tileheight", tileHeight);
311
312 std::unordered_map<int, TileKind> gidKinds;
313 std::string tilesetsArray;
314 if (extractArray(json, "tilesets", tilesetsArray)) {
315 const auto tilesets = splitObjects(tilesetsArray);
316 for (const auto& tilesetObj : tilesets) {
317 int firstGid = 1;
318 extractInt(tilesetObj, "firstgid", firstGid);
319
320 std::string tilesArray;
321 if (!extractArray(tilesetObj, "tiles", tilesArray)) {
322 continue;
323 }
324
325 const auto tiles = splitObjects(tilesArray);
326 for (const auto& tileObj : tiles) {
327 int id = 0;
328 if (!extractInt(tileObj, "id", id)) {
329 continue;
330 }
331 const auto kind = getTileKind(tileObj);
332 if (kind != TileKind::None) {
333 gidKinds[firstGid + id] = kind;
334 }
335 }
336 }
337 }
338
339 std::string layersArray;
340 if (!extractArray(json, "layers", layersArray)) {
341 return;
342 }
343
344 const auto layers = splitObjects(layersArray);
345 for (const auto& layerObj : layers) {
346 std::string name;
347 if (!extractString(layerObj, "name", name) || name != "Collision_Layer") {
348 continue;
349 }
350
351 std::vector<int> dataList;
352 extractIntArray(layerObj, "data", dataList);
353 if (dataList.empty() || height <= 0) {
354 continue;
355 }
356
357 const int width = static_cast<int>(dataList.size()) / height;
358 if (width <= 0) {
359 continue;
360 }
361
362 for (int row = 0; row < height; ++row) {
363 int col = 0;
364 while (col < width) {
365 const int index = row * width + col;
366 if (index < 0 || index >= static_cast<int>(dataList.size())) {
367 break;
368 }
369 const int gid = dataList[index];
370 const auto it = gidKinds.find(gid);
371 if (gid == 0 || it == gidKinds.end()) {
372 ++col;
373 continue;
374 }
375
376 const TileKind kind = it->second;
377 int startCol = col;
378 int endCol = col + 1;
379 while (endCol < width) {
380 const int nextIndex = row * width + endCol;
381 if (nextIndex < 0 || nextIndex >= static_cast<int>(dataList.size())) {
382 break;
383 }
384 const int nextGid = dataList[nextIndex];
385 const auto nextIt = gidKinds.find(nextGid);
386 if (nextGid == 0 || nextIt == gidKinds.end() || nextIt->second != kind) {
387 break;
388 }
389 ++endCol;
390 }
391
392 const int runLen = endCol - startCol;
394 event.atTime = 0.0f;
395 event.position = {static_cast<float>(startCol * tileWidth),
396 static_cast<float>(row * tileHeight)};
397 event.size = {static_cast<float>(runLen * tileWidth),
398 static_cast<float>(tileHeight)};
399 if (kind == TileKind::Destructible) {
400 event.health = 80;
402 } else {
403 event.health = 1000000;
405 }
406 obstacles.push_back(event);
407
408 col = endCol;
409 }
410 }
411 }
412 }
413}
414
415namespace rtp::server {
416
417std::optional<LevelData> loadLevelFromFile(const std::string& path, std::string& error)
418{
419 auto data = readFile(path);
420 if (!data) {
421 error = "Failed to read level file: " + path;
422 return std::nullopt;
423 }
424
425 LevelData level;
426 const std::string& json = data.value();
427
428 int id = 0;
429 if (extractInt(json, "level_id", id)) {
430 level.id = static_cast<uint32_t>(id);
431 }
432 extractString(json, "name", level.name);
433
434 std::string metadataObj;
435 if (extractObject(json, "game_metadata", metadataObj)) {
436 float width = 0.0f;
437 if (extractNumber(metadataObj, "level_width_in_pixels", width)) {
438 level.widthPixels = width;
439 }
440 extractVec2(metadataObj, "player_start_position", level.playerStart);
441 extractString(metadataObj, "tileset_path", level.tilesetPath);
442 }
443
444 std::string spawnArray;
445 if (extractArray(json, "spawn_triggers", spawnArray)) {
446 const auto objects = splitObjects(spawnArray);
447 for (const auto& obj : objects) {
448 float time = 0.0f;
449 if (!extractNumber(obj, "at_time", time)) {
450 extractNumber(obj, "at_position", time);
451 }
452
453 std::string templatePath;
454 extractString(obj, "template_path", templatePath);
455
456 std::string typeStr;
457 extractString(obj, "entity_type", typeStr);
458 const rtp::net::EntityType entityType = parseEntityType(typeStr, templatePath);
459
460 rtp::Vec2f startPos{0.0f, 0.0f};
461 extractVec2(obj, "start_position", startPos);
462
463 int count = 1;
464 extractInt(obj, "count", count);
465
466 float delay = 0.0f;
467 extractNumber(obj, "delay_between_spawn", delay);
468
469 std::string patternStr;
470 extractString(obj, "pattern", patternStr);
471 const auto pattern = patternStr.empty()
473 : parsePattern(patternStr);
474
475 float speed = 120.0f;
476 float amplitude = 40.0f;
477 float frequency = 2.0f;
478 extractNumber(obj, "speed", speed);
479 extractNumber(obj, "amplitude", amplitude);
480 extractNumber(obj, "frequency", frequency);
481
482 count = std::max(1, count);
483 for (int i = 0; i < count; ++i) {
484 SpawnEvent spawn{};
485 spawn.atTime = time + (delay * static_cast<float>(i));
486 spawn.type = entityType;
487 spawn.startPosition = startPos;
488 spawn.pattern = pattern;
489 spawn.speed = speed;
490 spawn.amplitude = amplitude;
491 spawn.frequency = frequency;
492 level.spawns.push_back(spawn);
493 }
494 }
495 }
496
497 std::string powerupArray;
498 if (extractArray(json, "powerup_triggers", powerupArray)) {
499 const auto objects = splitObjects(powerupArray);
500 for (const auto& obj : objects) {
501 PowerupEvent event{};
502 extractNumber(obj, "at_time", event.atTime);
503
504 std::string typeStr;
505 extractString(obj, "type", typeStr);
506 event.type = parsePowerupType(typeStr);
507
508 extractVec2(obj, "position", event.position);
509 extractNumber(obj, "value", event.value);
510 extractNumber(obj, "duration", event.duration);
511 level.powerups.push_back(event);
512 }
513 }
514
515 std::string obstacleArray;
516 if (extractArray(json, "obstacle_triggers", obstacleArray)) {
517 const auto objects = splitObjects(obstacleArray);
518 for (const auto& obj : objects) {
519 ObstacleEvent event{};
520 extractNumber(obj, "at_time", event.atTime);
521 extractVec2(obj, "position", event.position);
522 extractVec2WH(obj, "size", event.size);
523 extractInt(obj, "health", event.health);
524 level.obstacles.push_back(event);
525 }
526 }
527
528 if (!level.tilesetPath.empty()) {
529 appendTileObstacles(level.tilesetPath, level.obstacles);
530 }
531
532 auto sortByTime = [](const auto& a, const auto& b) { return a.atTime < b.atTime; };
533 std::sort(level.spawns.begin(), level.spawns.end(), sortByTime);
534 std::sort(level.powerups.begin(), level.powerups.end(), sortByTime);
535 std::sort(level.obstacles.begin(), level.obstacles.end(), sortByTime);
536
537 return level;
538}
539
540} // namespace rtp::server
Logger declaration with support for multiple log levels.
Patterns
Enum representing different enemy movement patterns.
@ StraightLine
Moves in a straight line.
@ Static
Remains stationary.
@ SineWave
Moves in a sine wave pattern.
@ Circular
Moves in a circular pattern.
@ ZigZag
Moves in a zigzag pattern.
PowerupType
Supported powerup types.
Definition Powerup.hpp:16
void warning(LogFmt< std::type_identity_t< Args >... > fmt, Args &&...args) noexcept
Log a warning message.
EntityType
Types of entities in the game.
Definition Packet.hpp:144
File : GameManager.hpp License: MIT Author : Elias Josué HAJJAR LLAUQUEN elias-josue....
std::optional< LevelData > loadLevelFromFile(const std::string &path, std::string &error)
std::string tilesetPath
Definition LevelData.hpp:52
std::vector< ObstacleEvent > obstacles
Definition LevelData.hpp:55
std::vector< PowerupEvent > powerups
Definition LevelData.hpp:54
std::vector< SpawnEvent > spawns
Definition LevelData.hpp:53