17#include <unordered_set>
18#include <unordered_map>
21 std::string toLower(std::string value)
23 std::transform(value.begin(), value.end(), value.begin(),
24 [](
unsigned char c) { return static_cast<char>(std::tolower(c)); });
28 std::optional<std::string> readFile(
const std::string& path)
30 std::ifstream in(path);
34 std::ostringstream ss;
39 size_t findMatching(
const std::string& text,
size_t start,
char open,
char close)
41 bool inString =
false;
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] !=
'\\')) {
55 }
else if (c == close) {
62 return std::string::npos;
65 bool extractObject(
const std::string& text,
const std::string& key, std::string& out)
67 const std::string needle =
"\"" + key +
"\"";
68 const size_t keyPos = text.find(needle);
69 if (keyPos == std::string::npos) {
72 const size_t bracePos = text.find(
'{', keyPos);
73 if (bracePos == std::string::npos) {
76 const size_t endPos = findMatching(text, bracePos,
'{',
'}');
77 if (endPos == std::string::npos) {
80 out = text.substr(bracePos, endPos - bracePos + 1);
84 bool extractArray(
const std::string& text,
const std::string& key, std::string& out)
86 const std::string needle =
"\"" + key +
"\"";
87 const size_t keyPos = text.find(needle);
88 if (keyPos == std::string::npos) {
91 const size_t startPos = text.find(
'[', keyPos);
92 if (startPos == std::string::npos) {
95 const size_t endPos = findMatching(text, startPos,
'[',
']');
96 if (endPos == std::string::npos) {
99 out = text.substr(startPos, endPos - startPos + 1);
103 std::vector<std::string> splitObjects(
const std::string& arrayText)
105 std::vector<std::string> out;
106 bool inString =
false;
108 size_t start = std::string::npos;
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;
124 }
else if (c ==
'}') {
126 if (depth == 0 && start != std::string::npos) {
127 out.emplace_back(arrayText.substr(start, i - start + 1));
128 start = std::string::npos;
135 bool extractString(
const std::string& text,
const std::string& key, std::string& out)
137 const std::regex re(
"\"" + key +
"\"\\s*:\\s*\"([^\"]*)\"");
139 if (std::regex_search(text, match, re) && match.size() >= 2) {
140 out = match[1].str();
146 bool extractNumber(
const std::string& text,
const std::string& key,
float& out)
148 const std::regex re(
"\"" + key +
"\"\\s*:\\s*([-0-9.+eE]+)");
150 if (std::regex_search(text, match, re) && match.size() >= 2) {
151 out = std::strtof(match[1].str().c_str(),
nullptr);
157 bool extractInt(
const std::string& text,
const std::string& key,
int& out)
160 if (!extractNumber(text, key, value)) {
163 out =
static_cast<int>(value);
167 bool extractVec2(
const std::string& text,
const std::string& key,
rtp::Vec2f& out)
170 if (!extractObject(text, key, obj)) {
175 if (!extractNumber(obj,
"x", x) || !extractNumber(obj,
"y", y)) {
182 bool extractVec2WH(
const std::string& text,
const std::string& key,
rtp::Vec2f& out)
185 if (!extractObject(text, key, obj)) {
190 if (!extractNumber(obj,
"w", w) || !extractNumber(obj,
"h", h)) {
199 const std::string lower = toLower(value);
200 if (lower ==
"sine" || lower ==
"sin") {
203 if (lower ==
"static") {
206 if (lower ==
"circular" || lower ==
"circle") {
209 if (lower ==
"zigzag" || lower ==
"zig-zag") {
217 const std::string lower = toLower(value);
218 if (lower ==
"tank") {
221 if (lower ==
"boss") {
224 if (lower ==
"boss2") {
227 if (lower ==
"boss_shield") {
230 if (lower ==
"scout") {
234 const std::string templateLower = toLower(templatePath);
235 if (templateLower.find(
"boss_02") != std::string::npos || templateLower.find(
"boss2") != std::string::npos) {
238 if (templateLower.find(
"boss") != std::string::npos) {
241 if (templateLower.find(
"tank") != std::string::npos) {
249 const std::string lower = toLower(value);
250 if (lower ==
"speed") {
256 bool extractIntArray(
const std::string& text,
const std::string& key, std::vector<int>& out)
258 std::string arrayText;
259 if (!extractArray(text, key, arrayText)) {
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()));
272 enum class TileKind {
278 TileKind getTileKind(
const std::string& tileObj)
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;
285 if (isDestructible) {
286 return TileKind::Destructible;
289 return TileKind::Solid;
291 return TileKind::None;
294 void appendTileObstacles(
const std::string& tilesetPath,
295 std::vector<rtp::server::ObstacleEvent>& obstacles)
297 auto data = readFile(tilesetPath);
303 const std::string& json = data.value();
308 extractInt(json,
"height", height);
309 extractInt(json,
"tilewidth", tileWidth);
310 extractInt(json,
"tileheight", tileHeight);
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) {
318 extractInt(tilesetObj,
"firstgid", firstGid);
320 std::string tilesArray;
321 if (!extractArray(tilesetObj,
"tiles", tilesArray)) {
325 const auto tiles = splitObjects(tilesArray);
326 for (
const auto& tileObj : tiles) {
328 if (!extractInt(tileObj,
"id",
id)) {
331 const auto kind = getTileKind(tileObj);
332 if (kind != TileKind::None) {
333 gidKinds[firstGid + id] = kind;
339 std::string layersArray;
340 if (!extractArray(json,
"layers", layersArray)) {
344 const auto layers = splitObjects(layersArray);
345 for (
const auto& layerObj : layers) {
347 if (!extractString(layerObj,
"name", name) || name !=
"Collision_Layer") {
351 std::vector<int> dataList;
352 extractIntArray(layerObj,
"data", dataList);
353 if (dataList.empty() || height <= 0) {
357 const int width =
static_cast<int>(dataList.size()) / height;
362 for (
int row = 0; row < height; ++row) {
364 while (col < width) {
365 const int index = row * width + col;
366 if (index < 0 || index >=
static_cast<int>(dataList.size())) {
369 const int gid = dataList[index];
370 const auto it = gidKinds.find(gid);
371 if (gid == 0 || it == gidKinds.end()) {
376 const TileKind kind = it->second;
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())) {
384 const int nextGid = dataList[nextIndex];
385 const auto nextIt = gidKinds.find(nextGid);
386 if (nextGid == 0 || nextIt == gidKinds.end() || nextIt->second != kind) {
392 const int runLen = endCol - startCol;
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) {
403 event.health = 1000000;
406 obstacles.push_back(event);
419 auto data = readFile(path);
421 error =
"Failed to read level file: " + path;
426 const std::string& json = data.value();
429 if (extractInt(json,
"level_id",
id)) {
430 level.
id =
static_cast<uint32_t
>(id);
432 extractString(json,
"name", level.
name);
434 std::string metadataObj;
435 if (extractObject(json,
"game_metadata", metadataObj)) {
437 if (extractNumber(metadataObj,
"level_width_in_pixels", width)) {
440 extractVec2(metadataObj,
"player_start_position", level.
playerStart);
441 extractString(metadataObj,
"tileset_path", level.
tilesetPath);
444 std::string spawnArray;
445 if (extractArray(json,
"spawn_triggers", spawnArray)) {
446 const auto objects = splitObjects(spawnArray);
447 for (
const auto& obj : objects) {
449 if (!extractNumber(obj,
"at_time", time)) {
450 extractNumber(obj,
"at_position", time);
453 std::string templatePath;
454 extractString(obj,
"template_path", templatePath);
457 extractString(obj,
"entity_type", typeStr);
461 extractVec2(obj,
"start_position", startPos);
464 extractInt(obj,
"count", count);
467 extractNumber(obj,
"delay_between_spawn", delay);
469 std::string patternStr;
470 extractString(obj,
"pattern", patternStr);
471 const auto pattern = patternStr.empty()
473 : parsePattern(patternStr);
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);
482 count = std::max(1, count);
483 for (
int i = 0; i < count; ++i) {
485 spawn.
atTime = time + (delay *
static_cast<float>(i));
486 spawn.type = entityType;
487 spawn.startPosition = startPos;
488 spawn.pattern = pattern;
490 spawn.amplitude = amplitude;
491 spawn.frequency = frequency;
492 level.
spawns.push_back(spawn);
497 std::string powerupArray;
498 if (extractArray(json,
"powerup_triggers", powerupArray)) {
499 const auto objects = splitObjects(powerupArray);
500 for (
const auto& obj : objects) {
502 extractNumber(obj,
"at_time", event.atTime);
505 extractString(obj,
"type", typeStr);
506 event.type = parsePowerupType(typeStr);
508 extractVec2(obj,
"position", event.position);
509 extractNumber(obj,
"value", event.value);
510 extractNumber(obj,
"duration", event.duration);
515 std::string obstacleArray;
516 if (extractArray(json,
"obstacle_triggers", obstacleArray)) {
517 const auto objects = splitObjects(obstacleArray);
518 for (
const auto& obj : objects) {
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);
532 auto sortByTime = [](
const auto& a,
const auto& b) {
return a.atTime < b.atTime; };
533 std::sort(level.
spawns.begin(), level.
spawns.end(), sortByTime);
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.
void warning(LogFmt< std::type_identity_t< Args >... > fmt, Args &&...args) noexcept
Log a warning message.
EntityType
Types of entities in the game.
File : GameManager.hpp License: MIT Author : Elias Josué HAJJAR LLAUQUEN elias-josue....
std::optional< LevelData > loadLevelFromFile(const std::string &path, std::string &error)
std::vector< ObstacleEvent > obstacles
std::vector< PowerupEvent > powerups
std::vector< SpawnEvent > spawns