Olympe Engine 2.0
2D Game Engine with ECS Architecture
Loading...
Searching...
No Matches
LevelParser.cpp
Go to the documentation of this file.
1/*
2 * LevelParser.cpp - Phase 1: Parsing & Visual Analysis Implementation
3 *
4 * Implements comprehensive level parsing with visual resource extraction,
5 * object census, and reference analysis.
6 */
7
8#include "../include/LevelParser.h"
9#include "../include/TiledLevelLoader.h"
10#include "../include/TiledStructures.h"
11#include "../../PrefabScanner.h"
12#include "../../PrefabFactory.h"
13#include <iostream>
14#include <fstream>
15#include <algorithm>
16#include <functional>
17#include <cctype>
18
19namespace Olympe {
20namespace Tiled {
21
22 // Helper function for box-drawing padding
23 static std::string GetPadding(int totalWidth, const std::string& content)
24 {
25 int contentLen = static_cast<int>(content.length());
27 return std::string(padding > 0 ? padding : 0, ' ');
28 }
29
33
37
39 {
41
42 std::cout << "\n";
43 std::cout << "+======================================================================+ \n";
44 std::cout << "| PHASE 1: PARSING & VISUAL ANALYSIS | \n";
45 std::cout << "|======================================================================| \n";
46 std::cout << "| File: " << levelPath << std::string(std::max(0, 65 - static_cast<int>(levelPath.length())), ' ') << "| \n";
47 std::cout << "+======================================================================+ \n\n";
48
49 // Step 1: Load the Tiled map using existing loader
52
53 std::cout << "-> Loading Tiled map file...\n";
54 if (!loader.LoadFromFile(levelPath, tiledMap))
55 {
56 result.success = false;
57 result.errors.push_back("Failed to load Tiled map: " + loader.GetLastError());
58 std::cout << "x Failed to load map: " << loader.GetLastError() << "\n";
59 return result;
60 }
61 std::cout << "-> Map loaded successfully\n\n";
62 std::cout << "Extracting metadata and analyzing contents...\n\n";
63
64 // Step 2: Extract map metadata
65 result.width = tiledMap.width;
66 result.height = tiledMap.height;
67 result.tileWidth = tiledMap.tilewidth;
68 result.tileHeight = tiledMap.tileheight;
69
70 switch (tiledMap.orientation)
71 {
72 case MapOrientation::Orthogonal: result.orientation = "orthogonal"; break;
73 case MapOrientation::Isometric: result.orientation = "isometric"; break;
74 case MapOrientation::Staggered: result.orientation = "staggered"; break;
75 case MapOrientation::Hexagonal: result.orientation = "hexagonal"; break;
76 default: result.orientation = "unknown"; break;
77 }
78
79 std::cout << "+======================================================================+ \n";
80 std::cout << "| MAP METADATA | \n";
81 std::cout << "|======================================================================| \n";
82 std::cout << "| Dimensions: " << result.width << " x " << result.height
83 << " tiles (" << (result.width * result.tileWidth) << " x "
84 << (result.height * result.tileHeight) << " pixels)"
85 << std::string(std::max(0, 19 - static_cast<int>(std::to_string(result.width).length() + std::to_string(result.height).length())), ' ') << "| \n";
86 std::cout << "| Tile Size: " << result.tileWidth << " x " << result.tileHeight << " pixels"
87 << std::string(std::max(0, 44 - static_cast<int>(std::to_string(result.tileWidth).length() + std::to_string(result.tileHeight).length())), ' ') << "| \n";
88 std::cout << "| Orientation: " << result.orientation
89 << std::string(54 - result.orientation.length(), ' ') << "|\n";
90 std::cout << "| Infinite: " << (tiledMap.infinite ? "Yes" : "No ")
91 << std::string(53, ' ') << "| \n";
92 std::cout << "+======================================================================+ \n\n";
93
94 // Step 3: Extract visual resources
95 std::cout << "-> Extracting visual resource manifest...\n";
96 ExtractVisualResources(tiledMap, result.visualManifest);
97
98 std::cout << "+======================================================================+ \n";
99 std::cout << "| VISUAL RESOURCES | \n";
100 std::cout << "|======================================================================| \n";
101 std::cout << "| Tilesets: " << result.visualManifest.GetTilesetCount()
102 << std::string(51 - std::to_string(result.visualManifest.GetTilesetCount()).length(), ' ') << "| \n";
103 std::cout << "| Parallax Layers: " << result.visualManifest.GetParallaxLayerCount()
104 << std::string(51 - std::to_string(result.visualManifest.GetParallaxLayerCount()).length(), ' ') << "| \n";
105 std::cout << "| Total Images: " << result.visualManifest.GetTotalImageCount()
106 << std::string(51 - std::to_string(result.visualManifest.GetTotalImageCount()).length(), ' ') << "| \n";
107 std::cout << "+======================================================================+ \n\n";
108
109 // Step 4: Build object census
110 std::cout << "-> Building object census...\n";
111 BuildObjectCensus(tiledMap, result.objectCensus);
112
113 std::cout << "+======================================================================+ \n";
114 std::cout << "| OBJECT CENSUS | \n";
115 std::cout << "|======================================================================| \n";
116 std::cout << "| Total Objects: " << result.objectCensus.GetTotalObjectCount()
117 << std::string(51 - std::to_string(result.objectCensus.GetTotalObjectCount()).length(), ' ') << "| \n";
118 std::cout << "| Unique Types: " << result.objectCensus.GetUniqueTypeCount()
119 << std::string(51 - std::to_string(result.objectCensus.GetUniqueTypeCount()).length(), ' ') << "| \n";
120
121 if (!result.objectCensus.typeCounts.empty())
122 {
123 std::cout << "| | \n";
124 std::cout << "| Type Breakdown: | \n";
125 for (const auto& kv : result.objectCensus.typeCounts)
126 {
127 std::string line = "| " + kv.first + ": " + std::to_string(kv.second);
128 std::cout << line << std::string(70 - line.length(), ' ') << "| \n";
129 }
130 }
131 std::cout << "+======================================================================+ \n\n";
132
133 // Step 5: Extract object references
134 std::cout << "-> Extracting object references...\n";
135 ExtractObjectReferences(tiledMap, result.objectReferences);
136
137 if (!result.objectReferences.empty())
138 {
139 std::cout << "-> Found " << result.objectReferences.size() << " object reference(s)\n\n";
140 }
141
142 result.success = true;
143
144 std::cout << "+======================================================================+ \n";
145 std::cout << "| PHASE 1 COMPLETE | \n";
146 std::cout << "|======================================================================| \n";
147 std::cout << "| Status: -> SUCCESS | \n";
148 std::cout << "| Errors: " << result.GetErrorCount()
149 << std::string(59 - std::to_string(result.GetErrorCount()).length(), ' ') << "| \n";
150 std::cout << "| Warnings: " << result.GetWarningCount()
151 << std::string(59 - std::to_string(result.GetWarningCount()).length(), ' ') << "| \n";
152 std::cout << "+======================================================================+ \n\n";
153
154 return result;
155 }
156
158 {
159 // Extract tilesets
160 for (const auto& tileset : map.tilesets)
161 {
163 ref.sourceFile = tileset.source;
164 ref.imageFile = tileset.image;
165 ref.firstGid = tileset.firstgid;
166
167 // Check if it's a collection tileset
168 if (!tileset.tiles.empty() && tileset.image.empty())
169 {
170 ref.isCollection = true;
171 for (const auto& tile : tileset.tiles)
172 {
173 if (!tile.image.empty())
174 {
175 ref.individualImages.push_back(tile.image);
176 manifest.allImagePaths.insert(tile.image);
177 }
178 }
179 }
180 else if (!tileset.image.empty())
181 {
182 ref.isCollection = false;
183 manifest.allImagePaths.insert(tileset.image);
184 }
185
186 manifest.tilesets.push_back(ref);
187 }
188
189 // Extract image layers (parallax backgrounds)
190 std::function<void(const std::shared_ptr<TiledLayer>&)> processLayer;
191 processLayer = [&](const std::shared_ptr<TiledLayer>& layer)
192 {
193 if (!layer) return;
194
195 if (layer->type == LayerType::ImageLayer && !layer->image.empty())
196 {
197 manifest.parallaxLayers.push_back(layer->image);
198 manifest.allImagePaths.insert(layer->image);
199 }
200
201 // Recursively process group layers
202 if (layer->type == LayerType::Group)
203 {
204 for (const auto& childLayer : layer->layers)
205 {
207 }
208 }
209 };
210
211 for (const auto& layer : map.layers)
212 {
213 processLayer(layer);
214 }
215 }
216
218 {
219 std::function<void(const std::shared_ptr<TiledLayer>&)> processLayer;
220 processLayer = [&](const std::shared_ptr<TiledLayer>& layer)
221 {
222 if (!layer) return;
223
224 if (layer->type == LayerType::ObjectGroup)
225 {
226 for (const auto& obj : layer->objects)
227 {
228 std::string type = obj.type;
229 if (type.empty()) type = "undefined"; // will be normalized by PrefabFactory later
230
231 // Type normalization is now handled by PrefabFactory::NormalizeType() in World::LoadLevelFromTiled()
232
233 census.uniqueTypes.insert(type);
234 census.typeCounts[type]++;
235
236 // Check for template reference
237 auto it = obj.properties.find("template");
238 if (it != obj.properties.end() && it->second.type == PropertyType::String)
239 {
240 census.templates[obj.name] = it->second.stringValue;
241 }
242 }
243 }
244
245 // Recursively process group layers
246 if (layer->type == LayerType::Group)
247 {
248 for (const auto& childLayer : layer->layers)
249 {
251 }
252 }
253 };
254
255 for (const auto& layer : map.layers)
256 {
257 processLayer(layer);
258 }
259 }
260
261 void LevelParser::ExtractObjectReferences(const TiledMap& map, std::vector<ObjectReference>& references)
262 {
263 std::function<void(const std::shared_ptr<TiledLayer>&)> processLayer;
264 processLayer = [&](const std::shared_ptr<TiledLayer>& layer)
265 {
266 if (!layer) return;
267
268 if (layer->type == LayerType::ObjectGroup)
269 {
270 for (const auto& obj : layer->objects)
271 {
272 // Look for custom properties that reference other objects
273 for (const auto& prop : obj.properties)
274 {
275 if (prop.first == "targetObject" ||
276 prop.first == "patrolPath" ||
277 prop.first == "linkedObject")
278 {
281 ref.sourceObjectName = obj.name;
282 ref.referenceType = prop.first;
283
284 if (prop.second.type == PropertyType::Int)
285 {
286 ref.targetObjectId = prop.second.intValue;
287 }
288 else if (prop.second.type == PropertyType::String)
289 {
290 ref.targetObjectName = prop.second.stringValue;
291 }
292
293 references.push_back(ref);
294 }
295 }
296 }
297 }
298
299 // Recursively process group layers
300 if (layer->type == LayerType::Group)
301 {
302 for (const auto& childLayer : layer->layers)
303 {
305 }
306 }
307 };
308
309 for (const auto& layer : map.layers)
310 {
311 processLayer(layer);
312 }
313 }
314
315 std::string LevelParser::GetDirectory(const std::string& filepath)
316 {
317 size_t pos = filepath.find_last_of("/\\");
318 if (pos != std::string::npos)
319 {
320 return filepath.substr(0, pos);
321 }
322 return "";
323 }
324
325 std::string LevelParser::ResolvePath(const std::string& baseDir, const std::string& relativePath)
326 {
327 if (relativePath.empty()) return "";
328 if (baseDir.empty()) return relativePath;
329
330 // If relative path starts with / or \, it's already absolute
331 if (relativePath[0] == '/' || relativePath[0] == '\\')
332 return relativePath;
333
334 return baseDir + "/" + relativePath;
335 }
336
337} // namespace Tiled
338} // namespace Olympe
ComponentTypeID GetComponentTypeID_Static()
Definition ECS_Entity.h:56
void ExtractVisualResources(const TiledMap &map, VisualResourceManifest &manifest)
void BuildObjectCensus(const TiledMap &map, ObjectTypeCensus &census)
std::string ResolvePath(const std::string &baseDir, const std::string &relativePath)
std::string GetDirectory(const std::string &filepath)
LevelParseResult ParseAndAnalyze(const std::string &levelPath)
void ExtractObjectReferences(const TiledMap &map, std::vector< ObjectReference > &references)
const std::string & GetLastError() const
static std::string GetPadding(int totalWidth, const std::string &content)