Olympe Engine 2.0
2D Game Engine with ECS Architecture
Loading...
Searching...
No Matches
LevelManager.cpp
Go to the documentation of this file.
1/*
2 * Olympe Tilemap Editor - Level Manager Implementation
3 */
4
5#include "../include/LevelManager.h"
6#include <fstream>
7#include <iostream>
8#include <sstream>
9#include <chrono>
10#include <iomanip>
11#include <algorithm>
12
13namespace Olympe {
14namespace Editor {
15
16 // ========================================================================
17 // JSON Conversion Helpers
18 // ========================================================================
19
20 // Vector serialization (for EntityInstance.position which is now a Vector)
21 void to_json(json& j, const Vector& v)
22 {
23 j = json::object();
24 j["x"] = v.x;
25 j["y"] = v.y;
26 j["z"] = v.z;
27 }
28
29 void from_json(const json& j, Vector& v)
30 {
31 if (j.contains("x")) v.x = j["x"].get<float>();
32 if (j.contains("y")) v.y = j["y"].get<float>();
33 if (j.contains("z")) v.z = j["z"].get<float>();
34 else v.z = 0.0f; // Default z to 0 for 2D compatibility
35 }
36
37 void to_json(json& j, const EntityInstance& e)
38 {
39 j = json::object();
40 j["id"] = e.id;
41 j["prefabPath"] = e.prefabPath;
42 j["name"] = e.name;
43 j["type"] = e.type;
44 j["rotation"] = e.rotation;
45 if (!e.spritePath.empty()) {
46 j["spritePath"] = e.spritePath;
47 }
49 to_json(posJson, e.position);
50 j["position"] = posJson;
51 if (!e.overrides.is_null() && !e.overrides.empty())
52 {
53 j["overrides"] = e.overrides;
54 }
55 else
56 {
57 j["overrides"] = json::object();
58 }
59 }
60
62 {
63 if (j.contains("id")) e.id = j["id"].get<std::string>();
64 if (j.contains("prefabPath")) e.prefabPath = j["prefabPath"].get<std::string>();
65 if (j.contains("name")) e.name = j["name"].get<std::string>();
66 if (j.contains("type")) e.type = j["type"].get<std::string>();
67 if (j.contains("rotation")) e.rotation = j["rotation"].get<float>();
68 if (j.contains("spritePath")) e.spritePath = j["spritePath"].get<std::string>();
69 if (j.contains("position")) from_json(j["position"], e.position);
70 if (j.contains("overrides")) e.overrides = j["overrides"];
71 else e.overrides = json::object();
72 }
73
74 // ========================================================================
75 // LevelManager Implementation
76 // ========================================================================
77
79 : m_hasUnsavedChanges(false), m_nextEntityId(1)
80 {
81 }
82
86
88 {
89 auto now = std::chrono::system_clock::now();
90 auto time = std::chrono::system_clock::to_time_t(now);
91 std::stringstream ss;
92
93#ifdef _MSC_VER
94 std::tm timeinfo;
96 ss << std::put_time(&timeinfo, "%Y-%m-%dT%H:%M:%S");
97#else
98 ss << std::put_time(std::localtime(&time), "%Y-%m-%dT%H:%M:%S");
99#endif
100
101 return ss.str();
102 }
103
105 {
106 std::stringstream ss;
107 ss << "entity_" << m_nextEntityId++;
108 return ss.str();
109 }
110
111 void LevelManager::NewLevel(const std::string& name)
112 {
114 m_levelDef.name = name;
115 m_levelDef.levelName = name;
116 m_levelDef.metadata.author = "OlympeTilemapEditor";
119
120 // Initialize tile and collision maps (default 32x32)
121 ResizeTileMap(32, 32);
122 ResizeCollisionMap(32, 32);
123
124 m_currentPath = "";
125 m_hasUnsavedChanges = true;
126 m_nextEntityId = 1;
127 }
128
129 bool LevelManager::LoadLevel(const std::string& path)
130 {
131 std::ifstream file(path);
132 if (!file.is_open())
133 {
134 std::cerr << "[LevelManager] Failed to open file: " << path << std::endl;
135 return false;
136 }
137
138 try
139 {
140 std::stringstream buffer;
141 buffer << file.rdbuf();
142 file.close();
143
144 json j = json::parse(buffer.str());
145
147 {
148 std::cerr << "[LevelManager] Failed to deserialize level data" << std::endl;
149 return false;
150 }
151
152 m_currentPath = path;
153 m_hasUnsavedChanges = false;
154
155 std::cout << "[LevelManager] Successfully loaded level: " << path << std::endl;
156 return true;
157 }
158 catch (const std::exception& e)
159 {
160 std::cerr << "[LevelManager] Exception loading level: " << e.what() << std::endl;
161 return false;
162 }
163 }
164
165 bool LevelManager::SaveLevel(const std::string& path)
166 {
167 try
168 {
169 json j;
171
172 std::ofstream file(path);
173 if (!file.is_open())
174 {
175 std::cerr << "[LevelManager] Failed to open file for writing: " << path << std::endl;
176 return false;
177 }
178
179 file << j.dump(2);
180 file.close();
181
182 m_currentPath = path;
183 m_hasUnsavedChanges = false;
185
186 std::cout << "[LevelManager] Successfully saved level: " << path << std::endl;
187 return true;
188 }
189 catch (const std::exception& e)
190 {
191 std::cerr << "[LevelManager] Exception saving level: " << e.what() << std::endl;
192 return false;
193 }
194 }
195
197 {
198 j = json::object();
199 j["schema_version"] = m_levelDef.schema_version;
200 j["type"] = m_levelDef.type;
201 j["blueprintType"] = m_levelDef.blueprintType;
202 j["name"] = m_levelDef.name;
203 j["description"] = m_levelDef.description;
204
205 // Metadata
206 j["metadata"] = json::object();
207 j["metadata"]["author"] = m_levelDef.metadata.author;
208 j["metadata"]["created"] = m_levelDef.metadata.created;
209 j["metadata"]["lastModified"] = m_levelDef.metadata.lastModified;
210 j["metadata"]["tags"] = json::array();
211 for (const auto& tag : m_levelDef.metadata.tags)
212 {
213 j["metadata"]["tags"].push_back(tag);
214 }
215
216 // Editor state
217 j["editorState"] = json::object();
218 j["editorState"]["zoom"] = m_levelDef.editorState.zoom;
221 j["editorState"]["scrollOffset"] = scrollOffsetJson;
222
223 // Level data
224 j["data"] = json::object();
225 j["data"]["levelName"] = m_levelDef.levelName;
228 j["data"]["worldSize"] = worldSizeJson;
229 j["data"]["backgroundMusic"] = m_levelDef.backgroundMusic;
230 j["data"]["ambientColor"] = m_levelDef.ambientColor;
231
232 // Entities
233 j["data"]["entities"] = json::array();
234 for (const auto& entity : m_levelDef.entities)
235 {
237 to_json(entityJson, *entity);
238 j["data"]["entities"].push_back(entityJson);
239 }
240
241 // Tile map
242 j["data"]["tileMap"] = json::array();
243 for (const auto& row : m_levelDef.tileMap)
244 {
245 json rowJson = json::array();
246 for (int tile : row)
247 {
248 rowJson.push_back(tile);
249 }
250 j["data"]["tileMap"].push_back(rowJson);
251 }
252
253 // Collision map
254 j["data"]["collisionMap"] = json::array();
255 for (const auto& row : m_levelDef.collisionMap)
256 {
257 json rowJson = json::array();
258 for (uint8_t collision : row)
259 {
260 rowJson.push_back(static_cast<int>(collision));
261 }
262 j["data"]["collisionMap"].push_back(rowJson);
263 }
264 }
265
267 {
268 try
269 {
271
272 if (j.contains("schema_version"))
273 m_levelDef.schema_version = j["schema_version"].get<int>();
274
275 if (m_levelDef.schema_version != 2)
276 {
277 std::cerr << "[LevelManager] Unsupported schema version: " << m_levelDef.schema_version << std::endl;
278 }
279
280 if (j.contains("type"))
281 m_levelDef.type = j["type"].get<std::string>();
282 if (j.contains("blueprintType"))
283 m_levelDef.blueprintType = j["blueprintType"].get<std::string>();
284 if (j.contains("name"))
285 m_levelDef.name = j["name"].get<std::string>();
286 if (j.contains("description"))
287 m_levelDef.description = j["description"].get<std::string>();
288
289 // Metadata
290 if (j.contains("metadata"))
291 {
292 const auto& metadata = j["metadata"];
293 if (metadata.contains("author"))
294 m_levelDef.metadata.author = metadata["author"].get<std::string>();
295 if (metadata.contains("created"))
296 m_levelDef.metadata.created = metadata["created"].get<std::string>();
297 if (metadata.contains("lastModified"))
298 m_levelDef.metadata.lastModified = metadata["lastModified"].get<std::string>();
299 if (metadata.contains("tags") && metadata["tags"].is_array())
300 {
301 const auto& tagsArray = metadata["tags"];
302 for (size_t i = 0; i < tagsArray.size(); ++i)
303 {
305 }
306 }
307 }
308
309 // Editor state
310 if (j.contains("editorState"))
311 {
312 const auto& editorState = j["editorState"];
313 if (editorState.contains("zoom"))
314 m_levelDef.editorState.zoom = editorState["zoom"].get<double>();
315 if (editorState.contains("scrollOffset"))
316 from_json(editorState["scrollOffset"], m_levelDef.editorState.scrollOffset);
317 }
318
319 // Level data
320 if (j.contains("data"))
321 {
322 const auto& data = j["data"];
323 if (data.contains("levelName"))
324 m_levelDef.levelName = data["levelName"].get<std::string>();
325 if (data.contains("worldSize"))
326 from_json(data["worldSize"], m_levelDef.worldSize);
327 if (data.contains("backgroundMusic"))
328 m_levelDef.backgroundMusic = data["backgroundMusic"].get<std::string>();
329 if (data.contains("ambientColor"))
330 m_levelDef.ambientColor = data["ambientColor"].get<std::string>();
331
332 // Entities
333 if (data.contains("entities") && data["entities"].is_array())
334 {
335 const auto& entitiesArray = data["entities"];
336 for (size_t i = 0; i < entitiesArray.size(); ++i)
337 {
338 auto entity = std::make_unique<EntityInstance>();
339 from_json(entitiesArray[i], *entity);
340
341 // Update entity ID counter
342 if (entity->id.find("entity_") == 0)
343 {
344 int idNum = std::stoi(entity->id.substr(7));
345 m_nextEntityId = std::max(m_nextEntityId, idNum + 1);
346 }
347
348 m_levelDef.entities.push_back(std::move(entity));
349 }
350 }
351
352 // Tile map
353 if (data.contains("tileMap") && data["tileMap"].is_array())
354 {
355 m_levelDef.tileMap.clear();
356 const auto& tileMapArray = data["tileMap"];
357 for (size_t y = 0; y < tileMapArray.size(); ++y)
358 {
359 std::vector<int> row;
360 const auto& rowJson = tileMapArray[y];
361 if (rowJson.is_array())
362 {
363 for (size_t x = 0; x < rowJson.size(); ++x)
364 {
365 row.push_back(rowJson[x].get<int>());
366 }
367 }
368 m_levelDef.tileMap.push_back(row);
369 }
370 }
371
372 // Collision map
373 if (data.contains("collisionMap") && data["collisionMap"].is_array())
374 {
375 m_levelDef.collisionMap.clear();
376 const auto& collisionMapArray = data["collisionMap"];
377 for (size_t y = 0; y < collisionMapArray.size(); ++y)
378 {
379 std::vector<uint8_t> row;
380 const auto& rowJson = collisionMapArray[y];
381 if (rowJson.is_array())
382 {
383 for (size_t x = 0; x < rowJson.size(); ++x)
384 {
385 row.push_back(static_cast<uint8_t>(rowJson[x].get<int>()));
386 }
387 }
388 m_levelDef.collisionMap.push_back(row);
389 }
390 }
391 }
392
393 return true;
394 }
395 catch (const std::exception& e)
396 {
397 std::cerr << "[LevelManager] Exception during deserialization: " << e.what() << std::endl;
398 return false;
399 }
400 }
401
402 // ========================================================================
403 // Entity Management
404 // ========================================================================
405
406 EntityInstance* LevelManager::CreateEntity(const std::string& prefabPath)
407 {
408 auto entity = std::make_unique<EntityInstance>();
409 entity->id = GenerateUniqueEntityId();
410 entity->prefabPath = prefabPath;
411 entity->name = "New Entity";
412 entity->position = Vector();
413 entity->overrides = json::object();
414
415 EntityInstance* entityPtr = entity.get();
416 m_levelDef.entities.push_back(std::move(entity));
417
418 m_hasUnsavedChanges = true;
419 return entityPtr;
420 }
421
422 void LevelManager::DeleteEntity(const std::string& id)
423 {
424 auto it = std::remove_if(m_levelDef.entities.begin(), m_levelDef.entities.end(),
425 [&id](const std::unique_ptr<EntityInstance>& entity) {
426 return entity->id == id;
427 });
428
429 if (it != m_levelDef.entities.end())
430 {
432 m_hasUnsavedChanges = true;
433 }
434 }
435
436 EntityInstance* LevelManager::GetEntity(const std::string& id) const
437 {
438 for (const auto& entity : m_levelDef.entities)
439 {
440 if (entity->id == id)
441 {
442 return entity.get();
443 }
444 }
445 return nullptr;
446 }
447
448 std::vector<EntityInstance*> LevelManager::GetAllEntities()
449 {
450 std::vector<EntityInstance*> result;
451 for (const auto& entity : m_levelDef.entities)
452 {
453 result.push_back(entity.get());
454 }
455 return result;
456 }
457
458 bool LevelManager::UpdateEntityPosition(const std::string& id, const Vector& position)
459 {
460 EntityInstance* entity = GetEntity(id);
461 if (entity)
462 {
463 entity->position = position;
464 m_hasUnsavedChanges = true;
465 return true;
466 }
467 return false;
468 }
469
470 // ========================================================================
471 // Tile Management
472 // ========================================================================
473
474 void LevelManager::SetTile(int x, int y, int tileId)
475 {
476 if (y >= 0 && y < static_cast<int>(m_levelDef.tileMap.size()) &&
477 x >= 0 && x < static_cast<int>(m_levelDef.tileMap[y].size()))
478 {
479 m_levelDef.tileMap[y][x] = tileId;
480 m_hasUnsavedChanges = true;
481 }
482 }
483
484 int LevelManager::GetTile(int x, int y) const
485 {
486 if (y >= 0 && y < static_cast<int>(m_levelDef.tileMap.size()) &&
487 x >= 0 && x < static_cast<int>(m_levelDef.tileMap[y].size()))
488 {
489 return m_levelDef.tileMap[y][x];
490 }
491 return -1;
492 }
493
494 void LevelManager::ResizeTileMap(int width, int height)
495 {
496 m_levelDef.tileMap.clear();
497 m_levelDef.tileMap.resize(height);
498 for (int y = 0; y < height; ++y)
499 {
500 m_levelDef.tileMap[y].resize(width, 0);
501 }
502 m_hasUnsavedChanges = true;
503 }
504
505 // ========================================================================
506 // Collision Management
507 // ========================================================================
508
510 {
511 if (y >= 0 && y < static_cast<int>(m_levelDef.collisionMap.size()) &&
512 x >= 0 && x < static_cast<int>(m_levelDef.collisionMap[y].size()))
513 {
515 m_hasUnsavedChanges = true;
516 }
517 }
518
520 {
521 if (y >= 0 && y < static_cast<int>(m_levelDef.collisionMap.size()) &&
522 x >= 0 && x < static_cast<int>(m_levelDef.collisionMap[y].size()))
523 {
524 return m_levelDef.collisionMap[y][x];
525 }
526 return 0;
527 }
528
529 void LevelManager::ResizeCollisionMap(int width, int height)
530 {
531 m_levelDef.collisionMap.clear();
532 m_levelDef.collisionMap.resize(height);
533 for (int y = 0; y < height; ++y)
534 {
535 m_levelDef.collisionMap[y].resize(width, 0);
536 }
537 m_hasUnsavedChanges = true;
538 }
539
540} // namespace Editor
541} // namespace Olympe
ComponentTypeID GetComponentTypeID_Static()
Definition ECS_Entity.h:56
bool SaveLevel(const std::string &path)
void NewLevel(const std::string &name)
EntityInstance * CreateEntity(const std::string &prefabPath)
void ResizeTileMap(int width, int height)
std::vector< EntityInstance * > GetAllEntities()
void SetTile(int x, int y, int tileId)
int GetTile(int x, int y) const
void SetCollision(int x, int y, uint8_t mask)
uint8_t GetCollision(int x, int y) const
void DeleteEntity(const std::string &id)
bool LoadLevel(const std::string &path)
bool DeserializeFromJson(const json &j)
bool UpdateEntityPosition(const std::string &id, const Vector &position)
void SerializeToJson(json &j) const
void ResizeCollisionMap(int width, int height)
EntityInstance * GetEntity(const std::string &id) const
void to_json(json &j, const Vector &v)
void from_json(const json &j, Vector &v)
nlohmann::json json
std::vector< std::vector< int > > tileMap
std::vector< std::vector< uint8_t > > collisionMap
std::vector< std::unique_ptr< EntityInstance > > entities
std::vector< std::string > tags