Olympe Engine 2.0
2D Game Engine with ECS Architecture
Loading...
Searching...
No Matches
World.cpp
Go to the documentation of this file.
1/*
2Olympe Engine V2 - 2025
3Nicolas Chereau
4nchereau@gmail.com
5
6This file is part of Olympe Engine V2.
7
8World purpose: Manage the lifecycle of Entities and their interaction with ECS Systems.
9
10*/
11#include "World.h"
12#include "CollisionMap.h"
13#include "VideoGame.h"
14#include "InputsManager.h"
17#include "ECS_Systems_AI.h"
30#include "PrefabScanner.h"
31#include "prefabfactory.h"
32#include "ParameterResolver.h"
33#include "ParameterSchema.h"
34#include "ComponentDefinition.h"
35#include "DataManager.h"
36#include "GameEngine.h"
37#include "../SDL/include/SDL3_image/SDL_image.h"
38#include <algorithm>
39#include <chrono>
40#include <cctype>
41#include <iostream>
42#include <iomanip>
43#include <fstream>
44#include <set>
46
47//---------------------------------------------------------------------------------------------
48// Helper function to register input entities with InputsManager
53//---------------------------------------------------------------------------------------------
55{
57
58 m_mapOrientation = "orthogonal";
59 m_tileWidth = 32;
60 m_tileHeight = 32;
61
62 // Auto-create singleton GridSettings entity if missing
63 bool hasGridSettings = false;
64 for (const auto& kv : m_entitySignatures)
65 {
66 EntityID e = kv.first;
68 {
69 hasGridSettings = true;
70 break;
71 }
72 }
73 if (!hasGridSettings)
74 {
76 AddComponent<GridSettings_data>(e); // enabled=true by default
77 }
78
79 SYSTEM_LOG << "World Initialized\n";
80}
81//---------------------------------------------------------------------------------------------
83{
84 SYSTEM_LOG << "World Destroyed\n";
85}
86//---------------------------------------------------------------------------------------------
88{
89 // Initialization of ECS Systems
90 // WARNING THE ORDER OF SYSTEMS MATTERS!
91
92 /*
93 Order of processing systems:
94 - InputEventConsumeSystem (consumes Input domain events from EventQueue, updates Controller_data)
95 - GameEventConsumeSystem (consumes Gameplay domain events, handles game state and player add/remove)
96 - UIEventConsumeSystem (consumes UI domain events, handles menu activation)
97 - CameraEventConsumeSystem (consumes Camera domain events, forwards to CameraSystem)
98 - InputSystem
99 - InputMappingSystem (maps hardware input to gameplay actions)
100 - PlayerControlSystem
101 - AIStimuliSystem (consumes Gameplay/Detection/Collision events, writes to blackboard)
102 - AIPerceptionSystem (timesliced perception updates)
103 - AIStateTransitionSystem (HFSM state transitions)
104 - BehaviorTreeSystem (tick behavior trees, write intents)
105 - AIMotionSystem (convert intents to Movement_data)
106 - AISystem (legacy)
107 - DetectionSystem
108 - PhysicsSystem
109 - CollisionSystem
110 - TriggerSystem
111 - MovementSystem
112 - AudioSystem
113 - CameraSystem (manages ECS cameras)
114
115 - RenderingSystem
116 */
117
118 // -> NOUVEAU : Précharger tous les prefabs AVANT de créer les systèmes
119 SYSTEM_LOG << "\n";
120 PrefabFactory::Get().PreloadAllPrefabs("Gamedata/EntityPrefab");
121 SYSTEM_LOG << "\n";
122
123 // Load animation banks and graphs
124 SYSTEM_LOG << "\n";
125 SYSTEM_LOG << "+===========================================================+\n";
126 SYSTEM_LOG << "| ANIMATION SYSTEM: LOADING RESOURCES |\n";
127 SYSTEM_LOG << "+===========================================================+\n";
128 OlympeAnimation::AnimationManager::Get().LoadAnimationBanks("Gamedata/Animations/AnimationBanks");
129 OlympeAnimation::AnimationManager::Get().LoadAnimationGraphs("Gamedata/Animations/AnimationGraphs");
130 SYSTEM_LOG << "\n";
131
132 Add_ECS_System(std::make_unique<InputEventConsumeSystem>());
133 Add_ECS_System(std::make_unique<GameEventConsumeSystem>());
134 Add_ECS_System(std::make_unique<UIEventConsumeSystem>());
135 Add_ECS_System(std::make_unique<CameraEventConsumeSystem>());
136 Add_ECS_System(std::make_unique<InputSystem>());
137 Add_ECS_System(std::make_unique<InputMappingSystem>());
138 Add_ECS_System(std::make_unique<PlayerControlSystem>());
139
140 // AI Systems (ECS-friendly NPC AI architecture)
141 Add_ECS_System(std::make_unique<AIStimuliSystem>());
142 Add_ECS_System(std::make_unique<AIPerceptionSystem>());
143 Add_ECS_System(std::make_unique<AIStateTransitionSystem>());
144 Add_ECS_System(std::make_unique<BehaviorTreeSystem>());
145 Add_ECS_System(std::make_unique<AIMotionSystem>());
146
147 // Navigation System (processes NavigationAgent_data)
148 Add_ECS_System(std::make_unique<NavigationSystem>());
149
150 Add_ECS_System(std::make_unique<AISystem>());
151 Add_ECS_System(std::make_unique<DetectionSystem>());
152 Add_ECS_System(std::make_unique<PhysicsSystem>());
153 Add_ECS_System(std::make_unique<CollisionSystem>());
154 Add_ECS_System(std::make_unique<TriggerSystem>());
155 Add_ECS_System(std::make_unique<MovementSystem>());
156
157 // Animation System (updates animation frames before rendering)
158 Add_ECS_System(std::make_unique<AnimationSystem>());
159
160 // Camera System (manages ECS cameras - added before rendering)
161 Add_ECS_System(std::make_unique<CameraSystem>());
162
163 // Rendering systems (order matters - defines render pass sequence!)
164 Add_ECS_System(std::make_unique<RenderingSystem>()); // Pass 1: World (parallax, tiles, entities)
165 Add_ECS_System(std::make_unique<GridSystem>()); // Grid overlay
166 Add_ECS_System(std::make_unique<UIRenderingSystem>()); // -> Pass 2: UI/HUD/Menu (ALWAYS on top)
167
168}
169//---------------------------------------------------------------------------------------------
170void World::Add_ECS_System(std::unique_ptr<ECS_System> system)
171{
172 // Enregistrement d'un syst�me
173 m_systems.push_back(std::move(system));
174}
175//---------------------------------------------------------------------------------------------
177{
178 // update all registered systems in order
179 for (const auto& system : m_systems)
180 {
181 system->Process();
182 }
183}
184//---------------------------------------------------------------------------------------------
186{
187 // Render all registered systems in order
188 for (const auto& system : m_systems)
189 {
190 system->Render();
191
192 }
194}
195//---------------------------------------------------------------------------------------------
197{
198
199 // RenderDebug all registered systems in order
200 for (const auto& system : m_systems)
201 {
202 system->RenderDebug();
203 }
204}
205//---------------------------------------------------------------------------------------------
207{
208 // V�rifie si l'Entit� correspond maintenant aux exigences d'un Syst�me
209 for (const auto& system : m_systems)
210 {
211 // Utilisation de l'op�ration de bits AND pour la comparaison (tr�s rapide)
212 if ((signature & system->requiredSignature) == system->requiredSignature)
213 {
214 // L'Entit� correspond : l'ajouter au Syst�me
215 system->AddEntity(entity);
216 }
217 else
218 {
219 // L'Entit� ne correspond plus : la retirer du Syst�me
220 system->RemoveEntity(entity);
221 }
222 }
223}
224//---------------------------------------------------------------------------------------------
226{
227 // --- UID Generation based on nanosecond timestamp ---
228 using namespace std::chrono;
229
230 // 1. Generate a unique ID (UID) based on current time in nanoseconds
231 // This provides a globally unique ID suitable for serialization and persistence.
232 auto now = system_clock::now();
233 EntityID newID = static_cast<std::uint64_t>(duration_cast<nanoseconds>(now.time_since_epoch()).count());
234
235 // Check for potential collision (extremely rare but possible if two entities are created
236 // in the exact same nanosecond or during deserialization without proper synchronization)
237 while (m_entitySignatures.count(newID))
238 {
239 // If collision occurs (two entities created in the same nanosecond), increment the ID.
240 // This is a simple conflict resolution mechanism.
241 newID++;
242 }
243 // Initialize the Entity's signature as empty
245
246 // Notify Blueprint Editor (if active)
248
249 // Add to entity list
250 m_entities.push_back(newID);
251
252 return newID;
253}
254//---------------------------------------------------------------------------------------------
256{
257 if (entity == INVALID_ENTITY_ID || m_entitySignatures.find(entity) == m_entitySignatures.end())
258 {
259 return;
260 }
261
262 // Notify Blueprint Editor BEFORE destruction (if active)
264
265 // 1. Supprimer les composants de tous les Pools o� l'Entit� existe
267 for (const auto& pair : m_componentPools)
268 {
270 if (signature.test(typeID))
271 {
272 // Utilise la m�thode virtuelle RemoveComponent (Phase 1.2)
273 pair.second->RemoveComponent(entity);
274 }
275 }
276
277 // 2. Notifier les syst�mes (pour la retirer de leurs listes)
278 Notify_ECS_Systems(entity, ComponentSignature{}); // Signature vide pour forcer la suppression
279
280 // 3. Nettoyer les maps
281 m_entitySignatures.erase(entity);
282
283 m_entities.erase(std::remove(m_entities.begin(), m_entities.end(), entity), m_entities.end());
284
285 // 4. Recycler l'ID (gestion de l'information)
286 m_freeEntityIDs.push(entity);
287 std::cout << "Entit� " << entity << " d�truite et ID recycl�.\n";
288}
289//---------------------------------------------------------------------------------------------
290// Layer Management Implementation
291//---------------------------------------------------------------------------------------------
293{
294 if (!HasComponent<Position_data>(entity))
295 {
296 SYSTEM_LOG << "/!\\ World::SetEntityLayer: Entity " << entity
297 << " has no Position_data component\n";
298 return;
299 }
300
302 pos.position.z = LayerToZ(layer);
303
304 SYSTEM_LOG << "World::SetEntityLayer: Entity " << entity
305 << " assigned to layer " << static_cast<int>(layer)
306 << " (z=" << pos.position.z << ")\n";
307}
308
310{
311 // Map zOrder values to appropriate RenderLayer enum
312 // This mapping is designed for Tiled levels where zOrder represents layer depth
313 // Note: Tiled typically uses sequential integers (0, 1, 2, ...) for layer ordering
314
315 // Direct mapping if zOrder matches RenderLayer values
316 // Background layers: negative values
317 if (zOrder <= -2.0f) return RenderLayer::Background_Far;
318 if (zOrder <= -1.0f) return RenderLayer::Background_Near;
319
320 // Main game layers: positive values
321 if (zOrder <= 0.5f) return RenderLayer::Ground;
322 if (zOrder <= 1.5f) return RenderLayer::Objects;
323 if (zOrder <= 2.5f) return RenderLayer::Characters;
324 if (zOrder <= 3.5f) return RenderLayer::Flying;
325 if (zOrder <= 4.5f) return RenderLayer::Effects;
326 if (zOrder <= 7.0f) return RenderLayer::UI_Near;
327 if (zOrder <= 15.0f) return RenderLayer::Foreground_Near;
328
329 // Very high z values
331}
332
333//---------------------------------------------------------------------------------------------
335{
336 if (!HasComponent<Position_data>(entity))
337 {
338 return RenderLayer::Ground;
339 }
340
342 return ZToLayer(pos.position.z);
343}
344//---------------------------------------------------------------------------------------------
345// Blueprint Editor notification hooks
346//---------------------------------------------------------------------------------------------
348{
349 // Only notify if BlueprintEditor is active (avoid linking issues when editor is not included)
350 #ifdef OLYMPE_BLUEPRINT_EDITOR_ENABLED
352 #endif
353}
354//---------------------------------------------------------------------------------------------
356{
357 #ifdef OLYMPE_BLUEPRINT_EDITOR_ENABLED
359 #endif
360}
361//---------------------------------------------------------------------------------------------
362// Grid Management
363//---------------------------------------------------------------------------------------------
365{
366 // Find GridSettings entity
367 bool foundGridSettings = false;
368 for (const auto& kv : m_entitySignatures)
369 {
370 EntityID e = kv.first;
372 {
373 foundGridSettings = true;
375
376 // Extract orientation from mapConfig (primary source)
377 std::string orientation = levelDef.mapConfig.orientation;
378 if (orientation.empty())
379 {
380 orientation = "orthogonal"; // Default fallback
381 SYSTEM_LOG << "World::SyncGridWithLevel: Warning - No orientation specified, using orthogonal\n";
382 }
383
384 // Extract tile dimensions
385 int tileWidth = levelDef.mapConfig.tileWidth > 0 ? levelDef.mapConfig.tileWidth : 32;
386 int tileHeight = levelDef.mapConfig.tileHeight > 0 ? levelDef.mapConfig.tileHeight : 32;
387
388 // Update projection mode
389 if (orientation == "orthogonal")
390 {
391 settings.projection = GridProjection::Ortho;
392 settings.cellSize = Vector(static_cast<float>(tileWidth),
393 static_cast<float>(tileHeight), 0.f);
394 }
395 else if (orientation == "isometric")
396 {
397 settings.projection = GridProjection::Iso;
398 settings.cellSize = Vector(static_cast<float>(tileWidth),
399 static_cast<float>(tileHeight), 0.f);
400 }
401 else if (orientation == "hexagonal")
402 {
404 // Note: This assumes "pointy-top" hexagonal orientation
405 // Radius is half the tile width for pointy-top hexagons
406 settings.hexRadius = static_cast<float>(tileWidth) / 2.0f;
407 }
408 else if (orientation == "staggered")
409 {
410 // Staggered orientation is not fully supported yet
411 // Fall back to orthogonal with a warning
412 SYSTEM_LOG << "World::SyncGridWithLevel: Warning - Staggered orientation not fully supported, using orthogonal\n";
413 settings.projection = GridProjection::Ortho;
414 settings.cellSize = Vector(static_cast<float>(tileWidth),
415 static_cast<float>(tileHeight), 0.f);
416 }
417 else
418 {
419 // Unknown orientation - fallback to orthogonal
420 SYSTEM_LOG << "World::SyncGridWithLevel: Warning - Unknown orientation '"
421 << orientation << "', using orthogonal\n";
422 settings.projection = GridProjection::Ortho;
423 settings.cellSize = Vector(static_cast<float>(tileWidth),
424 static_cast<float>(tileHeight), 0.f);
425 }
426
427 SYSTEM_LOG << "World::SyncGridWithLevel: Grid synced with level\n"
428 << " Orientation: " << orientation << "\n"
429 << " Tile size: " << tileWidth << "x" << tileHeight << "\n";
430
431 break;
432 }
433 }
434
436 {
437 SYSTEM_LOG << "World::SyncGridWithLevel: Warning - No GridSettings entity found, grid sync skipped\n";
438 }
439}
440//---------------------------------------------------------------------------------------------
441// Generate collision and navigation maps from TMJ/TMX level data
442//---------------------------------------------------------------------------------------------
443
444// Helper function to check if a layer is a collision layer
445namespace {
446 bool IsCollisionLayer(const std::shared_ptr<Olympe::Tiled::TiledLayer>& layer)
447 {
448 if (!layer) return false;
449
450 // Check by name (case-insensitive)
451 std::string layerNameLower = layer->name;
452 std::transform(layerNameLower.begin(), layerNameLower.end(),
453 layerNameLower.begin(),
454 [](unsigned char c) { return std::tolower(c); });
455
456 if (layerNameLower.find("collision") != std::string::npos ||
457 layerNameLower.find("walls") != std::string::npos)
458 {
459 return true;
460 }
461
462 // Check by property
463 for (std::map<std::string, Olympe::Tiled::TiledProperty>::const_iterator propIt = layer->properties.begin();
464 propIt != layer->properties.end(); ++propIt)
465 {
466 if (propIt->second.name == "collision" && propIt->second.boolValue == true)
467 {
468 return true;
469 }
470 }
471
472 return false;
473 }
474}
475
478{
479 // Constants for isometric tile offset heuristic
480 // These bounds allow ±10% deviation from theoretical 2:1 ratio (i.e., 1.8 to 2.2)
481 // to accommodate asset variations in isometric tilesets (e.g., 58x27 vs exact 54x27)
482 constexpr float ISO_ASPECT_RATIO_MIN = 1.8f; // Minimum aspect ratio for standard 2:1 isometric
483 constexpr float ISO_ASPECT_RATIO_MAX = 2.2f; // Maximum aspect ratio for standard 2:1 isometric
484 constexpr float STANDARD_ISO_OFFSET_RATIO = 0.5f; // Standard isometric vertical offset (tileHeight/2)
485
486 SYSTEM_LOG << "\n";
487 SYSTEM_LOG << "+==========================================================+\n";
488 SYSTEM_LOG << "| COLLISION & NAVIGATION MAP GENERATION |\n";
489 SYSTEM_LOG << "+==========================================================+\n";
490
491 // Debug TMJ structure
492 SYSTEM_LOG << " [DEBUG] TMJ Structure Analysis:\n";
493 SYSTEM_LOG << " TMJ width=" << tiledMap.width << ", height=" << tiledMap.height << "\n";
494 SYSTEM_LOG << " TMJ tilewidth=" << tiledMap.tilewidth << ", tileheight=" << tiledMap.tileheight << "\n";
495 SYSTEM_LOG << " TMJ infinite=" << (tiledMap.infinite ? "YES" : "NO") << "\n";
496 SYSTEM_LOG << " TMJ orientation=" << tiledMap.orientation << "\n";
497 SYSTEM_LOG << " LevelDef mapWidth=" << levelDef.mapConfig.mapWidth
498 << ", mapHeight=" << levelDef.mapConfig.mapHeight << "\n";
499 SYSTEM_LOG << " LevelDef tileWidth=" << levelDef.mapConfig.tileWidth
500 << ", tileHeight=" << levelDef.mapConfig.tileHeight << "\n";
501
502 // Scan layers to find actual tile grid dimensions
503 int mapWidth = 0;
504 int mapHeight = 0;
505
506 SYSTEM_LOG << " [DEBUG] Scanning tile layers for dimensions:\n";
507
508 for (size_t layerIdx = 0; layerIdx < tiledMap.layers.size(); ++layerIdx)
509 {
510 const std::shared_ptr<Olympe::Tiled::TiledLayer>& layer = tiledMap.layers[layerIdx];
511 if (layer && layer->type == Olympe::Tiled::LayerType::TileLayer)
512 {
513 SYSTEM_LOG << " Layer '" << layer->name << "': "
514 << layer->width << "x" << layer->height
515 << " (data size: " << layer->data.size() << ")\n";
516
517 mapWidth = std::max(mapWidth, layer->width);
518 mapHeight = std::max(mapHeight, layer->height);
519 }
520 }
521
522 // Fallback if no layers found
523 if (mapWidth == 0 || mapHeight == 0)
524 {
525 // Try using tilewidth/tileheight as dimensions (some TMJ files encode it this way)
526 mapWidth = levelDef.mapConfig.tileWidth;
527 mapHeight = levelDef.mapConfig.tileHeight;
528
529 SYSTEM_LOG << " WARNING: No tile layers found, using tileWidth/tileHeight as fallback\n";
530 }
531
532 SYSTEM_LOG << " TMJ declared dimensions (metadata): "
533 << levelDef.mapConfig.mapWidth << "x" << levelDef.mapConfig.mapHeight << "\n";
534 SYSTEM_LOG << " Actual tile grid dimensions (calculated): "
535 << mapWidth << "x" << mapHeight << "\n";
536
537 if (mapWidth == 0 || mapHeight == 0)
538 {
539 SYSTEM_LOG << " X Invalid map dimensions, skipping collision/navigation generation\n\n";
540 return;
541 }
542
543 // Determine projection type
544 std::string orientation = levelDef.mapConfig.orientation;
546 if (orientation == "isometric")
547 projection = GridProjectionType::Iso;
548 else if (orientation == "hexagonal")
549 projection = GridProjectionType::HexAxial;
550
551 // Extract tile pixel dimensions from TMJ
552 // The TMJ file's tilewidth/tileheight fields contain the actual pixel dimensions
553 // These are already correctly stored in levelDef.mapConfig by TiledToOlympe::ExtractMapConfiguration()
554 float tilePixelWidth = static_cast<float>(tiledMap.tilewidth);
555 float tilePixelHeight = static_cast<float>(tiledMap.tileheight);
556
557 // Validate tile pixel dimensions (reasonable range: 4-1024 pixels)
558 if (tilePixelWidth <= 0.0f || tilePixelHeight <= 0.0f ||
559 tilePixelWidth > 1024.0f || tilePixelHeight > 1024.0f)
560 {
561 SYSTEM_LOG << " X Invalid or unreasonable tile pixel dimensions from TMJ ("
562 << tilePixelWidth << "x" << tilePixelHeight << "), using defaults\n";
563 tilePixelWidth = 32.0f;
564 tilePixelHeight = 32.0f;
565 }
566
567 SYSTEM_LOG << " Tile pixel size (from TMJ): " << tilePixelWidth << "x" << tilePixelHeight << " px\n";
568 SYSTEM_LOG << " Projection: " << orientation << " (type=" << static_cast<int>(projection) << ")\n";
569
570 // Extract tileset offset for isometric alignment
571 float tileOffsetX = 0.0f;
572 float tileOffsetY = 0.0f;
573
574 if (projection == GridProjectionType::Iso && !tiledMap.tilesets.empty())
575 {
576 // Get offset from the first loaded tileset
577 // NOTE: We use only the first tileset's offset as collision/navigation overlays
578 // are unified across the entire map. If different layers use different tilesets
579 // with different offsets, they should share the same base tile dimensions for
580 // consistent collision detection. Multi-tileset maps with varying offsets are
581 // not currently supported for overlay alignment.
583 tileOffsetX = static_cast<float>(firstTileset.tileoffsetX);
584 tileOffsetY = static_cast<float>(firstTileset.tileoffsetY);
585
586 if (tileOffsetX != 0.0f || tileOffsetY != 0.0f)
587 {
588 SYSTEM_LOG << " -> Found tileset offset from '" << firstTileset.name << "': ("
589 << tileOffsetX << ", " << tileOffsetY << ")\n";
590 }
591 else
592 {
593 // Fallback heuristic for standard isometric tiles without explicit offset
594 // Most isometric tilesets use tileHeight/2 as vertical offset
595 // Note: Division is safe - tilePixelHeight is validated in the dimension check
596 // earlier in this function and set to 32.0f if invalid or zero
598
600 {
601 // Looks like standard 2:1 isometric
603 SYSTEM_LOG << " -> Applying standard isometric offset heuristic: (0, "
604 << tileOffsetY << ")\n";
605 }
606 }
607 }
608
609 // Initialize collision map (single layer for now, can be extended later)
611 collMap.Initialize(mapWidth, mapHeight, projection, tilePixelWidth, tilePixelHeight, 1,
613
614 // Initialize navigation map
616 navMap.Initialize(mapWidth, mapHeight, projection, tilePixelWidth, tilePixelHeight, 1);
617
618 SYSTEM_LOG << " -> CollisionMap initialized: " << mapWidth << "x" << mapHeight
619 << " (" << (mapWidth * mapHeight) << " tiles)\n";
620 SYSTEM_LOG << " -> NavigationMap initialized: " << mapWidth << "x" << mapHeight << "\n";
621
622 // ========================================================================
623 // PHASE 1: Build navigation map from tile layers with custom properties
624 // ========================================================================
625
626 SYSTEM_LOG << " [1/2] Processing tile layers for navigation...\n";
627
628 int totalNavigableTiles = 0;
629 int totalBlockedTiles = 0;
630 int totalBorderTiles = 0;
631 int layersProcessed = 0;
632 int layersSkipped = 0;
633
634 // 8-directional neighbor offsets (constant for all layers and projections)
635 const int dx[] = {-1, 0, 1, -1, 1, -1, 0, 1};
636 const int dy[] = {-1, -1, -1, 0, 0, 1, 1, 1};
637
638 for (size_t layerIdx = 0; layerIdx < tiledMap.layers.size(); ++layerIdx)
639 {
640 const std::shared_ptr<Olympe::Tiled::TiledLayer>& layer = tiledMap.layers[layerIdx];
641 if (!layer) continue;
642
643 // Skip non-tile layers
644 if (layer->type != Olympe::Tiled::LayerType::TileLayer) continue;
645
646 // Parse layer navigation properties
648
649 // Skip layers without navigation properties (graphic-only layers)
650 if (!props.hasNavigationProperties)
651 {
652 SYSTEM_LOG << " Skipping graphic-only layer: " << layer->name << "\n";
654 continue;
655 }
656
657 SYSTEM_LOG << " Layer '" << layer->name << "' navigation properties:\n";
658 SYSTEM_LOG << " - isTilesetWalkable: " << (props.isTilesetWalkable ? "true" : "false") << "\n";
659 SYSTEM_LOG << " - useTilesetBorder: " << (props.useTilesetBorder ? "true" : "false") << "\n";
660
661 // Use layer dimensions, clamped to map bounds
662 int layerW = std::min(layer->width, mapWidth);
663 int layerH = std::min(layer->height, mapHeight);
664
665 // Process tile data for this layer (data is 1D array: index = y * width + x)
666 int navigableCount = 0;
667 int blockedCount = 0;
668 int borderCount = 0;
669
670 // First pass: Mark non-empty tiles based on isTilesetWalkable
671 int index = 0;
672 for (int y = 0; y < layerH; ++y)
673 {
674 for (int x = 0; x < layerW; ++x)
675 {
676 if (index >= static_cast<int>(layer->data.size()))
677 {
678 SYSTEM_LOG << " WARNING: Layer data truncated at index " << index
679 << " (expected " << (layerW * layerH) << ")\n";
680 break;
681 }
682
683 uint32_t gid = layer->data[index];
684 uint32_t tileId = gid & ~TILE_FLIP_FLAGS_MASK; // Remove flip flags
685
686 if (tileId > 0) // Non-empty tile
687 {
688 // Get current tile properties (may have been set by previous layers)
689 TileProperties tileProps = collMap.GetTileProperties(x, y);
690
691 if (props.isTilesetWalkable)
692 {
693 // Walkable layer: mark non-empty tiles as navigable (unless already blocked)
694 if (!tileProps.isBlocked)
695 {
696 tileProps.isNavigable = true;
697 tileProps.isBlocked = false;
698 tileProps.traversalCost = 1.0f;
699 collMap.SetTileProperties(x, y, tileProps);
701 }
702 }
703 else
704 {
705 // Collision layer: mark non-empty tiles as blocked (override previous settings)
706 tileProps.isBlocked = true;
707 tileProps.isNavigable = false;
708 tileProps.traversalCost = 999.0f;
709 collMap.SetTileProperties(x, y, tileProps);
710 blockedCount++;
711 }
712 }
713
714 ++index;
715 }
716 }
717
718 // Second pass: Mark borders if useTilesetBorder is true
719 if (props.useTilesetBorder)
720 {
721 index = 0;
722 for (int y = 0; y < layerH; ++y)
723 {
724 for (int x = 0; x < layerW; ++x)
725 {
726 if (index >= static_cast<int>(layer->data.size())) break;
727
728 uint32_t gid = layer->data[index];
730
731 // Check if this is an empty tile
732 if (tileId == 0)
733 {
734 // Check all 8 directions for non-empty tiles
735 bool hasNonEmptyNeighbor = false;
736
737 for (int dir = 0; dir < 8; ++dir)
738 {
739 int nx = x + dx[dir];
740 int ny = y + dy[dir];
741
742 // Check bounds
744 continue;
745
746 // Check if neighbor has a tile in this layer
747 int neighborIndex = ny * layerW + nx;
748 if (neighborIndex >= 0 && neighborIndex < static_cast<int>(layer->data.size()))
749 {
750 uint32_t neighborGid = layer->data[neighborIndex];
752
753 if (neighborTileId > 0)
754 {
755 hasNonEmptyNeighbor = true;
756 break;
757 }
758 }
759 }
760
761 // If this empty tile is adjacent to a non-empty tile, mark it as a border
763 {
764 TileProperties tileProps = collMap.GetTileProperties(x, y);
765 tileProps.isBlocked = true;
766 tileProps.isNavigable = false;
767 tileProps.traversalCost = 999.0f;
768 collMap.SetTileProperties(x, y, tileProps);
769 borderCount++;
770 }
771 }
772
773 ++index;
774 }
775 }
776 }
777
778 SYSTEM_LOG << " -> Non-empty tiles: " << navigableCount << " navigable, "
779 << blockedCount << " blocked\n";
780 if (props.useTilesetBorder)
781 {
782 SYSTEM_LOG << " -> Border tiles: " << borderCount << " marked as blocked\n";
783 }
784
789 }
790
791 SYSTEM_LOG << " -> Summary: " << layersProcessed << " layers processed, "
792 << layersSkipped << " skipped (graphic only)\n";
793 SYSTEM_LOG << " -> Total navigable tiles: " << totalNavigableTiles << "\n";
794 SYSTEM_LOG << " -> Total blocked tiles: " << (totalBlockedTiles + totalBorderTiles)
795 << " (" << totalBlockedTiles << " from obstacles + "
796 << totalBorderTiles << " from borders)\n";
797
798
799 // ========================================================================
800 // PHASE 2: Process explicit collision object layers (optional)
801 // ========================================================================
802
803 SYSTEM_LOG << " [2/2] Processing explicit collision object layers (if any)...\n";
804
805 int objectCollisionTiles = 0;
806
807 // B) Collision object layers (optional, legacy support)
808 for (size_t layerIdx = 0; layerIdx < tiledMap.layers.size(); ++layerIdx)
809 {
810 const std::shared_ptr<Olympe::Tiled::TiledLayer>& layer = tiledMap.layers[layerIdx];
811 if (!layer) continue;
812
813 // Only process object group layers
814 if (layer->type != Olympe::Tiled::LayerType::ObjectGroup) continue;
815
816 // Check if this object group is marked as a collision layer (legacy pattern matching)
817 if (!IsCollisionLayer(layer)) continue;
818
819 SYSTEM_LOG << " Processing collision objects from layer: " << layer->name << "\n";
820
821 // Process objects in this group
822 int objectsProcessed = 0;
823 for (size_t objIdx = 0; objIdx < layer->objects.size(); ++objIdx)
824 {
825 const Olympe::Tiled::TiledObject& obj = layer->objects[objIdx];
826
827 // Convert object bounds to grid tiles
828 int gridX, gridY;
829 collMap.WorldToGrid(obj.x, obj.y, gridX, gridY);
830
831 int gridW = static_cast<int>(std::ceil(obj.width / tilePixelWidth));
832 int gridH = static_cast<int>(std::ceil(obj.height / tilePixelHeight));
833
834 // Mark tiles within object bounds as blocked
835 for (int dy = 0; dy < gridH; ++dy)
836 {
837 for (int dx = 0; dx < gridW; ++dx)
838 {
839 int cx = gridX + dx;
840 int cy = gridY + dy;
841
842 if (collMap.IsValidGridPosition(cx, cy))
843 {
844 TileProperties objProps = collMap.GetTileProperties(cx, cy);
845 objProps.isBlocked = true;
846 objProps.isNavigable = false;
847 objProps.traversalCost = 999.0f;
848 collMap.SetTileProperties(cx, cy, objProps);
850 }
851 }
852 }
854 }
855
856 SYSTEM_LOG << " -> Processed " << objectsProcessed << " collision objects\n";
857 }
858
859 if (objectCollisionTiles > 0)
860 {
861 SYSTEM_LOG << " -> Object collision tiles: " << objectCollisionTiles << "\n";
862 }
863
864 // ========================================================================
865 // Summary
866 // ========================================================================
867
868 SYSTEM_LOG << "\n";
869 SYSTEM_LOG << " -> Collision & Navigation maps ready\n";
870 SYSTEM_LOG << " Grid dimensions: " << mapWidth << "x" << mapHeight
871 << " (" << (mapWidth * mapHeight) << " total tiles)\n";
872 SYSTEM_LOG << " Navigable tiles: " << totalNavigableTiles << "\n";
874 << " (obstacles: " << totalBlockedTiles
875 << ", borders: " << totalBorderTiles
876 << ", objects: " << objectCollisionTiles << ")\n";
877
878 // DEBUG: Verify that tiles can be read back correctly
879 SYSTEM_LOG << "\n";
880 SYSTEM_LOG << " DEBUG: Verifying tile data can be read back...\n";
881 constexpr int VERIFY_SCAN_SIZE = 10; // Sample grid size for verification
882 int verifyNavigable = 0;
883 int verifyBlocked = 0;
884 for (int y = 0; y < std::min(VERIFY_SCAN_SIZE, mapHeight); ++y)
885 {
886 for (int x = 0; x < std::min(VERIFY_SCAN_SIZE, mapWidth); ++x)
887 {
888 const TileProperties& tile = collMap.GetTileProperties(x, y);
889 if (tile.isNavigable && !tile.isBlocked)
890 {
892 if (verifyNavigable <= 3)
893 {
894 SYSTEM_LOG << " -> Sample navigable tile at (" << x << "," << y << ")\n";
895 }
896 }
897 else if (tile.isBlocked)
898 {
900 if (verifyBlocked <= 3)
901 {
902 SYSTEM_LOG << " -> Sample blocked tile at (" << x << "," << y << ")\n";
903 }
904 }
905 }
906 }
907 SYSTEM_LOG << " -> In first " << VERIFY_SCAN_SIZE << "x" << VERIFY_SCAN_SIZE
908 << " grid: " << verifyNavigable << " navigable, "
909 << verifyBlocked << " blocked\n";
910
911 SYSTEM_LOG << "+==========================================================+\n";
912 SYSTEM_LOG << "\n";
913}
914//---------------------------------------------------------------------------------------------
915// Tiled MapEditor Integration
916//---------------------------------------------------------------------------------------------
920#include "prefabfactory.h"
921#include "ECS_Components_AI.h"
923#include "AI/BehaviorTree.h"
924#include <fstream>
925
927{
928 std::cout << "\n+===========================================================+\n";
929 std::cout << "| LEVEL DEPENDENCY LOADING |\n";
930 std::cout << "+===========================================================+\n";
931
932 // Step 1: Extract all prefab types used in the level
933 std::cout << "Step 1/3: Extracting prefab types from level...\n";
935
936 if (prefabNames.empty())
937 {
938 std::cout << " -> No prefabs found in level (this may be normal for tile-only levels)\n";
939 std::cout << "+===========================================================+\n\n";
940 return true;
941 }
942
943 std::cout << " -> Found " << prefabNames.size() << " unique prefab type(s):\n";
944 for (const auto& name : prefabNames)
945 {
946 std::cout << " - " << name << "\n";
947 }
948
949 // Step 2: Scan prefabs for behavior tree dependencies
950 std::cout << "\nStep 2/3: Scanning prefabs for behavior tree dependencies...\n";
951 std::vector<std::string> prefabList(prefabNames.begin(), prefabNames.end());
953
954 if (btDeps.empty())
955 {
956 std::cout << " -> No behavior trees required for this level\n";
957 std::cout << "+===========================================================+\n\n";
958 return true;
959 }
960
961 // Step 3: Load all required behavior trees
962 std::cout << "\nStep 3/3: Loading required behavior trees...\n";
963 int loaded = 0;
964 int alreadyLoaded = 0;
965 int failed = 0;
966
967 for (const auto& dep : btDeps)
968 {
969 // Check if already loaded
971 {
972 std::cout << " [CACHED] " << dep.treePath << " (ID=" << dep.suggestedTreeId << ")\n";
974 continue;
975 }
976
977 // Load the tree
978 std::cout << " [LOADING] " << dep.treePath << " (ID=" << dep.suggestedTreeId << ")... ";
979
980 if (BehaviorTreeManager::Get().LoadTreeFromFile(dep.treePath, dep.suggestedTreeId))
981 {
982 std::cout << "SUCCESS\n";
983 loaded++;
984 }
985 else
986 {
987 std::cout << "FAILED\n";
988 std::cerr << " [ERROR] Failed to load behavior tree: " << dep.treePath << "\n";
989 failed++;
990 }
991 }
992
993 // Summary
994 std::cout << "\n+===========================================================+\n";
995 std::cout << "| DEPENDENCY LOADING SUMMARY |\n";
996 std::cout << "+===========================================================+\n";
997 std::cout << "| Behavior Trees Required: " << btDeps.size() << "\n";
998 std::cout << "| Loaded This Session: " << loaded << "\n";
999 std::cout << "| Already Cached: " << alreadyLoaded << "\n";
1000 std::cout << "| Failed: " << failed << "\n";
1001 std::cout << "+===========================================================+\n\n";
1002
1003 return (failed == 0);
1004}
1005
1007{
1008 // Unload current level
1010
1011 std::cout << "\n";
1012 std::cout << "+==========================================================+\n";
1013 std::cout << "| LEVEL LOADING PIPELINE (6 PHASES) |\n";
1014 std::cout << "+==========================================================+\n\n";
1015
1016 // =======================================================================
1017 // PHASE 1: PREFAB SYSTEM INITIALIZATION (Already Done at Startup)
1018 // =======================================================================
1019
1021 SYSTEM_LOG << "Phase 1: -> Prefab System Ready (" << factory.GetPrefabCount() << " prefabs)\n\n";
1022
1023 // =======================================================================
1024 // PHASE 2: LEVEL PARSING (JSON -> Memory, Normalize Immediately)
1025 // =======================================================================
1026
1027 SYSTEM_LOG << "+==========================================================+\n";
1028 SYSTEM_LOG << "| PHASE 2: LEVEL PARSING |\n";
1029 SYSTEM_LOG << "+==========================================================+\n";
1030 SYSTEM_LOG << "File: " << tiledMapPath << "\n\n";
1031
1032 // Load TMJ (ONCE)
1035
1036 if (!loader.LoadFromFile(tiledMapPath, tiledMap))
1037 {
1038 SYSTEM_LOG << " X Failed to load TMJ file\n";
1039 return false;
1040 }
1041
1042 // Convert to LevelDefinition (includes immediate normalization)
1045 if (!converter.Convert(tiledMap, levelDef))
1046 {
1047 SYSTEM_LOG << " X Failed to convert map\n";
1048 return false;
1049 }
1050
1051 SYSTEM_LOG << " -> Parsed " << levelDef.entities.size() << " entities (types normalized)\n";
1052 SYSTEM_LOG << " -> Static objects: " << levelDef.categorizedObjects.staticObjects.size() << "\n";
1053 SYSTEM_LOG << " -> Dynamic objects: " << levelDef.categorizedObjects.dynamicObjects.size() << "\n";
1054 SYSTEM_LOG << " -> Patrol paths: " << levelDef.categorizedObjects.patrolPaths.size() << "\n\n";
1055
1056 // Generate collision and navigation maps
1057 SYSTEM_LOG << "[Phase 5/6] Generating Collision & Navigation Maps...\n";
1059
1060 // Synchronize grid settings with loaded level
1062
1063 // Validate prefabs (after normalization)
1065
1066 // =======================================================================
1067 // PHASE 2.5: BEHAVIOR TREE DEPENDENCY LOADING (NEW!)
1068 // =======================================================================
1069
1070 // Note: We reload the JSON here to access the raw layer data for dependency scanning.
1071 // This is intentional - the TiledMap structure is already converted to LevelDefinition,
1072 // and creating a new API to extract the raw JSON would require larger refactoring.
1073 // The performance impact is negligible for typical level sizes.
1075 std::ifstream jsonFile(tiledMapPath);
1076 if (jsonFile.is_open())
1077 {
1078 try
1079 {
1081 jsonFile.close();
1082
1084 {
1085 std::cerr << "[World] ERROR: Failed to load level dependencies\n";
1086 return false;
1087 }
1088 }
1089 catch (const std::exception& e)
1090 {
1091 std::cerr << "[World] WARNING: Failed to parse level JSON: " << e.what() << "\n";
1092 // Continue anyway - not all levels may have behavior trees
1093 }
1094 }
1095 else
1096 {
1097 std::cerr << "[World] WARNING: Could not open level JSON file for dependency scanning\n";
1098 // Continue anyway - not all levels may have behavior trees
1099 }
1100
1101 // =======================================================================
1102 // PHASE 3: RESOURCE PRELOADING (Centralized)
1103 // =======================================================================
1104
1105 SYSTEM_LOG << "+==========================================================+\n";
1106 SYSTEM_LOG << "| PHASE 3: RESOURCE PRELOADING |\n";
1107 SYSTEM_LOG << "+==========================================================+\n\n";
1108
1109 int resourcesLoaded = 0;
1110 int resourcesFailed = 0;
1111
1113
1114 // Step 1: Tilesets
1115 SYSTEM_LOG << " Step 1/4: Loading tilesets...\n";
1116 if (!levelDef.resources.tilesetPaths.empty())
1117 {
1118 auto tilesetResult = dataManager.PreloadTextures(
1119 levelDef.resources.tilesetPaths,
1121 true);
1122 resourcesLoaded += tilesetResult.successfullyLoaded;
1123 resourcesFailed += tilesetResult.completelyFailed;
1124 SYSTEM_LOG << " -> Loaded " << tilesetResult.successfullyLoaded << " tilesets\n";
1125 }
1126
1127 // Step 2: Parallax layers
1128 SYSTEM_LOG << " Step 2/4: Loading parallax layers...\n";
1129 std::vector<std::string> parallaxPaths;
1130 if (levelDef.metadata.customData.contains("parallaxLayers") && levelDef.metadata.customData["parallaxLayers"].is_array())
1131 {
1132 const auto& parallaxLayersJson = levelDef.metadata.customData["parallaxLayers"];
1133 for (const auto& layerJson : parallaxLayersJson)
1134 {
1135 std::string imagePath = layerJson.value("imagePath", "");
1136 if (!imagePath.empty())
1137 {
1138 parallaxPaths.push_back(imagePath);
1139 }
1140 }
1141 }
1142 if (!parallaxPaths.empty())
1143 {
1144 auto parallaxResult = dataManager.PreloadTextures(parallaxPaths, ResourceCategory::Level, true);
1145 resourcesLoaded += parallaxResult.successfullyLoaded;
1146 resourcesFailed += parallaxResult.completelyFailed;
1147 SYSTEM_LOG << " -> Loaded " << parallaxResult.successfullyLoaded << " parallax layers\n";
1148 }
1149
1150 // Step 3: Prefab sprites
1151 SYSTEM_LOG << " Step 3/4: Loading prefab sprites...\n";
1152 std::vector<std::string> spritePaths;
1153 std::set<std::string> uniqueTypes;
1154 for (const auto& entity : levelDef.entities)
1155 {
1156 if (entity)
1157 {
1158 uniqueTypes.insert(entity->type);
1159 }
1160 }
1161
1162 for (const auto& type : uniqueTypes)
1163 {
1164 const PrefabBlueprint* blueprint = factory.GetPrefabRegistry().Find(type);
1165 if (blueprint)
1166 {
1167 for (const auto& sprite : blueprint->resources.spriteRefs)
1168 {
1169 spritePaths.push_back(sprite);
1170 }
1171 }
1172 }
1173
1174 if (!spritePaths.empty())
1175 {
1177 resourcesLoaded += spriteResult.successfullyLoaded;
1178 resourcesFailed += spriteResult.completelyFailed;
1179 SYSTEM_LOG << " -> Loaded " << spriteResult.successfullyLoaded << " sprites\n";
1180 }
1181
1182 // Step 4: Audio
1183 SYSTEM_LOG << " Step 4/4: Loading audio files...\n";
1184 if (!levelDef.resources.audioPaths.empty())
1185 {
1186 auto audioResult = dataManager.PreloadAudioFiles(levelDef.resources.audioPaths, true);
1187 resourcesLoaded += audioResult.successfullyLoaded;
1188 resourcesFailed += audioResult.completelyFailed;
1189 SYSTEM_LOG << " -> Loaded " << audioResult.successfullyLoaded << " audio files\n";
1190 }
1191
1192 SYSTEM_LOG << "\n -> Total resources loaded: " << resourcesLoaded;
1193 if (resourcesFailed > 0)
1194 {
1195 SYSTEM_LOG << " (" << resourcesFailed << " failed)";
1196 }
1197 SYSTEM_LOG << "\n\n";
1198
1199 // =======================================================================
1200 // PHASE 4: VISUAL STRUCTURE CREATION
1201 // =======================================================================
1202
1203 SYSTEM_LOG << "+==========================================================+\n";
1204 SYSTEM_LOG << "| PHASE 4: VISUAL STRUCTURE CREATION |\n";
1205 SYSTEM_LOG << "+==========================================================+\n\n";
1206
1208
1209 // Create parallax layers and tiles
1210 SYSTEM_LOG << " Pass 1/2: Parallax & Visual Layers...\n";
1212
1213 SYSTEM_LOG << " Pass 2/2: Spatial Structures...\n";
1215
1216 SYSTEM_LOG << " -> Created " << m_tileChunks.size() << " tile chunks\n\n";
1217
1218 // =======================================================================
1219 // PHASE 5: ENTITY INSTANTIATION (Unified)
1220 // =======================================================================
1221
1222 SYSTEM_LOG << "+==========================================================+\n";
1223 SYSTEM_LOG << "| PHASE 5: ENTITY INSTANTIATION |\n";
1224 SYSTEM_LOG << "+==========================================================+\n\n";
1225
1226 // Pass 1: Static objects
1227 SYSTEM_LOG << " Pass 1/3: Static objects...\n";
1228 for (auto& entityInstancePtr : levelDef.categorizedObjects.staticObjects)
1229 {
1230 std::shared_ptr<Olympe::Editor::EntityInstance> entityInstance = std::move(entityInstancePtr);
1231 EntityID entity = InstantiateEntity(entityInstance, factory, instResult.pass3_staticObjects);
1232 if (entity != INVALID_ENTITY_ID && entityInstance)
1233 {
1234 instResult.entityRegistry[entityInstance->name] = entity;
1235 }
1236 }
1237 SYSTEM_LOG << " -> Created " << instResult.pass3_staticObjects.successfullyCreated << " objects\n\n";
1238
1239 // Pass 2: Dynamic objects
1240 SYSTEM_LOG << " Pass 2/3: Dynamic objects...\n";
1241 for (auto& entityInstancePtr : levelDef.categorizedObjects.dynamicObjects)
1242 {
1243 std::shared_ptr<Olympe::Editor::EntityInstance> entityInstance = std::move(entityInstancePtr);
1244 EntityID entity = InstantiateEntity(entityInstance, factory, instResult.pass4_dynamicObjects);
1245
1246 if (entity != INVALID_ENTITY_ID && entityInstance)
1247 {
1248 instResult.entityRegistry[entityInstance->name] = entity;
1249 // Note: Post-processing (player registration, AI init) is now handled in InstantiateEntity
1250 }
1251 }
1252 SYSTEM_LOG << " -> Created " << instResult.pass4_dynamicObjects.successfullyCreated << " objects\n\n";
1253
1254 // Pass 3: Spatial structures (patrol paths, waypoints)
1255 SYSTEM_LOG << " Pass 3/3: Spatial structures (patrol paths)...\n";
1256 for (auto& entityInstancePtr : levelDef.categorizedObjects.patrolPaths)
1257 {
1258 std::shared_ptr<Olympe::Editor::EntityInstance> entityInstance = std::move(entityInstancePtr);
1259 EntityID entity = InstantiateEntity(entityInstance, factory, instResult.pass5_relationships);
1260 if (entity != INVALID_ENTITY_ID && entityInstance)
1261 {
1262 instResult.entityRegistry[entityInstance->name] = entity;
1263 }
1264 }
1265 SYSTEM_LOG << " -> Created " << instResult.pass5_relationships.successfullyCreated << " objects\n\n";
1266
1267 // =======================================================================
1268 // PHASE 6: ENTITY RELATIONSHIPS
1269 // =======================================================================
1270
1271 SYSTEM_LOG << "+==========================================================+\n";
1272 SYSTEM_LOG << "| PHASE 6: ENTITY RELATIONSHIPS |\n";
1273 SYSTEM_LOG << "+==========================================================+\n\n";
1274
1276
1277 SYSTEM_LOG << " -> Linked " << instResult.pass5_relationships.linkedObjects << " relationships\n\n";
1278
1279 instResult.success = true;
1280
1281 // =======================================================================
1282 // FINAL SUMMARY
1283 // =======================================================================
1284
1285 SYSTEM_LOG << "+==========================================================+\n";
1286 SYSTEM_LOG << "| LEVEL LOADING COMPLETE |\n";
1287 SYSTEM_LOG << "+==========================================================+\n";
1288 SYSTEM_LOG << "| Entities Created: " << std::setw(3) << instResult.GetTotalCreated()
1289 << std::string(31, ' ') << "|\n";
1290 SYSTEM_LOG << "| Entities Failed: " << std::setw(3) << instResult.GetTotalFailed()
1291 << std::string(31, ' ') << "|\n";
1292 SYSTEM_LOG << "| Resources Loaded: " << std::setw(3) << resourcesLoaded
1293 << std::string(31, ' ') << "|\n";
1294 SYSTEM_LOG << "| Resources Failed: " << std::setw(3) << resourcesFailed
1295 << std::string(31, ' ') << "|\n";
1296 SYSTEM_LOG << "+==========================================================+\n\n";
1297
1298 SYSTEM_LOG << "World::LoadLevelFromTiled - Level loaded successfully\n";
1299 return true;
1300}
1301
1303{
1304 SYSTEM_LOG << "World::UnloadCurrentLevel - Unloading current level\n";
1305
1306 // Clear tile chunks and tilesets
1307 m_tileChunks.clear();
1309 m_entities.clear();
1310
1311 // Destroy all entities except system entities (like GridSettings)
1312 std::vector<EntityID> entitiesToDestroy;
1313
1314 for (const auto& kv : m_entitySignatures)
1315 {
1316 EntityID e = kv.first;
1317
1318 // Keep system entities (GridSettings, Camera settings, etc.)
1320 {
1321 continue; // Keep grid settings
1322 }
1323
1324 // Keep camera entities if they exist as system entities
1326 {
1327 // Check if it's a player camera or system camera
1328 // For now, we'll keep all cameras
1329 continue;
1330 }
1331
1332 //keep persistent entities
1334 {
1336 if (id.isPersistent)
1337 {
1338 SYSTEM_LOG << "World::UnloadCurrentLevel - Keeping persistent entity: " << e << " (" << id.name << ")\n";
1339 continue; // Keep persistent entities
1340 }
1341 }
1342
1343 // Mark all other entities for destruction
1344 entitiesToDestroy.push_back(e);
1345 }
1346
1347 // Destroy marked entities
1349 {
1351 }
1352
1353 SYSTEM_LOG << "World::UnloadCurrentLevel - Destroyed " << entitiesToDestroy.size()
1354 << " entities\n";
1355}
1356
1357//=============================================================================
1358// Validation & Unified Entity Instantiation Helpers
1359//=============================================================================
1360
1362{
1364 std::set<std::string> missingTypes;
1365
1366 for (const auto& entity : levelDef.entities)
1367 {
1368 if (!entity) continue;
1369
1370 const PrefabBlueprint* blueprint = factory.GetPrefabRegistry().Find(entity->type);
1371 if (!blueprint)
1372 {
1373 missingTypes.insert(entity->type);
1374 }
1375 }
1376
1377 if (!missingTypes.empty())
1378 {
1379 SYSTEM_LOG << " /!\\ Warning: " << missingTypes.size() << " missing prefabs:\n";
1380 for (const auto& type : missingTypes)
1381 {
1382 SYSTEM_LOG << " - " << type << "\n";
1383 }
1384 }
1385}
1386
1388 const std::shared_ptr<Olympe::Editor::EntityInstance>& entityInstance,
1391{
1392 if (!entityInstance)
1393 {
1394 stats.failed++;
1395 return INVALID_ENTITY_ID;
1396 }
1397
1398 stats.totalObjects++;
1399
1400 // -> Type ALREADY normalized - direct lookup (no matching logic needed)
1401 const PrefabBlueprint* blueprint = factory.GetPrefabRegistry().Find(entityInstance->type);
1402
1403 if (!blueprint || !blueprint->isValid)
1404 {
1405 SYSTEM_LOG << " X No prefab for type '" << entityInstance->type << "'\n";
1407 return placeholder;
1408 }
1409
1410 // Check input device availability for Player prefabs before creation
1411 if (blueprint->HasCategory("RequiresRegistration"))
1412 {
1413 if (!VideoGame::Get().IsInputDeviceAvailable())
1414 {
1415 stats.failed++;
1416 stats.failedObjects.push_back(entityInstance->name);
1417 SYSTEM_LOG << " X Cannot create player '" << entityInstance->name
1418 << "': no input device available (all joysticks and keyboard already assigned)\n";
1419 return INVALID_ENTITY_ID;
1420 }
1421 }
1422
1423 // Build instance parameters
1425 instanceParams.position = entityInstance->position;
1427
1428 // Create entity with overrides
1429 // CRITICAL: Disable autoAssignLayer to preserve position.z from Tiled level
1430 // The zOrder from the Tiled level is already stored in position.z and should not be overridden
1431 EntityID entity = factory.CreateEntityWithOverrides(*blueprint, instanceParams, false);
1432
1433 if (entity == INVALID_ENTITY_ID)
1434 {
1435 stats.failed++;
1436 stats.failedObjects.push_back(entityInstance->name);
1437 SYSTEM_LOG << " X Failed: " << entityInstance->name << "\n";
1438 return INVALID_ENTITY_ID;
1439 }
1440
1441 // Update identity
1442 if (HasComponent<Identity_data>(entity))
1443 {
1445 id.name = entityInstance->name;
1446 }
1447
1448 // Category-based post-processing
1449 if (blueprint->HasCategory("RequiresRegistration"))
1450 {
1451 RegisterPlayerEntity(entity);
1452 SYSTEM_LOG << " -> Registered player entity: " << entityInstance->name << "\n";
1453 }
1454
1455 if (blueprint->HasCategory("HasAI"))
1456 {
1457 // Initialize AI systems if needed
1458 SYSTEM_LOG << " -> Entity has AI: " << entityInstance->name << "\n";
1459 }
1460
1461 // Log entity creation with layer information
1462 if (HasComponent<Position_data>(entity))
1463 {
1465 RenderLayer layer = CalculateLayerFromZOrder(pos.position.z);
1466 SYSTEM_LOG << " ✓ " << entityInstance->name
1467 << " [" << blueprint->prefabName << "]"
1468 << " (layer: " << static_cast<int>(layer) << ", z: " << pos.position.z << ")"
1469 << std::endl;
1470 }
1471 else
1472 {
1473 SYSTEM_LOG << " ✓ " << entityInstance->name << " [" << blueprint->prefabName << "]\n";
1474 }
1475
1476 stats.successfullyCreated++;
1477
1478 return entity;
1479}
1480
1481//=============================================================================
1482// Phase 3: Instantiation Pass Implementations
1483//=============================================================================
1484
1488{
1489 // Extract map orientation and tile size from metadata
1490 m_mapOrientation = levelDef.metadata.customData.value("orientation", "orthogonal");
1491 m_tileWidth = levelDef.metadata.customData.value("tilewidth", 32);
1492 m_tileHeight = levelDef.metadata.customData.value("tileheight", 32);
1493
1494 // Extract map bounds and chunk origin for coordinate system alignment
1495 int minTileX = levelDef.metadata.customData.value("minTileX", 0);
1496 int minTileY = levelDef.metadata.customData.value("minTileY", 0);
1497 int maxTileX = levelDef.metadata.customData.value("maxTileX", 0);
1498 int maxTileY = levelDef.metadata.customData.value("maxTileY", 0);
1499 int chunkOriginX = levelDef.metadata.customData.value("chunkOriginX", 0);
1500 int chunkOriginY = levelDef.metadata.customData.value("chunkOriginY", 0);
1501
1502 SetMapBounds(minTileX, minTileY, maxTileX, maxTileY, chunkOriginX, chunkOriginY);
1503
1504 std::cout << "-> Map configuration: " << m_mapOrientation
1505 << " (" << m_tileWidth << "x" << m_tileHeight << ")\n";
1506
1507 // Log isometric origin for verification
1508 if (m_mapOrientation == "isometric")
1509 {
1510 SYSTEM_LOG << "[World] Isometric origin calculated: ("
1511 << GetIsometricOriginX() << ", " << GetIsometricOriginY() << ")\n";
1512 }
1513
1514 // ===== PART 1: Parallax Layers =====
1515 if (levelDef.metadata.customData.contains("parallaxLayers"))
1516 {
1518 parallaxMgr.Clear();
1519
1520 const auto& parallaxLayersJson = levelDef.metadata.customData["parallaxLayers"];
1521 if (parallaxLayersJson.is_array())
1522 {
1523 result.pass1_visualLayers.totalObjects = static_cast<int>(parallaxLayersJson.size());
1524
1525 for (const auto& layerJson : parallaxLayersJson)
1526 {
1527 std::string imagePath = layerJson.value("imagePath", "");
1528 if (imagePath.empty())
1529 {
1530 result.pass1_visualLayers.failed++;
1531 result.pass1_visualLayers.failedObjects.push_back("<missing imagePath>");
1532 std::cout << " x Failed: parallax layer missing imagePath\n";
1533 continue;
1534 }
1535
1536 std::string filename;
1537
1538 // extract only filename if full path is given
1539 size_t lastSlash = imagePath.find_last_of("/\\");
1540 if (lastSlash != std::string::npos)
1541 filename = imagePath.substr(lastSlash + 1);
1542 //recursive search file from Gamedata folder
1544
1545 SDL_Texture* texture = IMG_LoadTexture(GameEngine::renderer, imagePath.c_str());
1546
1547 if (!texture)
1548 {
1549 result.pass1_visualLayers.failed++;
1550 result.pass1_visualLayers.failedObjects.push_back(imagePath);
1551 std::cout << " x Failed to load parallax layer: " << imagePath << "\n";
1552 continue;
1553 }
1554
1556 layer.name = layerJson.value("name", "unnamed_parallax");
1557 layer.imagePath = imagePath;
1558 layer.texture = texture;
1559 layer.scrollFactorX = layerJson.value("scrollFactorX", 1.0f);
1560 layer.scrollFactorY = layerJson.value("scrollFactorY", 0.0f);
1561 layer.repeatX = layerJson.value("repeatX", false);
1562 layer.repeatY = layerJson.value("repeatY", false);
1563 layer.offsetX = layerJson.value("offsetX", 0.0f);
1564 layer.offsetY = layerJson.value("offsetY", 0.0f);
1565 layer.opacity = layerJson.value("opacity", 1.0f);
1566 layer.zOrder = layerJson.value("zOrder", 0);
1567 layer.visible = layerJson.value("visible", true);
1568 layer.tintColor = layerJson.value("tintColor", 0xFFFFFFFF);
1569
1570 parallaxMgr.AddLayer(layer);
1571 result.pass1_visualLayers.successfullyCreated++;
1572 std::cout << " -> Loaded parallax layer: " << layer.name << "\n";
1573 }
1574 }
1575 }
1576
1577 // ===== PART 2: Tilesets =====
1578 if (levelDef.metadata.customData.contains("tilesets"))
1579 {
1580 std::cout << "-> Loading tilesets...\n";
1581 m_tilesetManager.LoadTilesets(levelDef.metadata.customData["tilesets"]);
1582 }
1583
1584 // ===== PART 3: Tile Layers =====
1585 if (levelDef.metadata.customData.contains("tileLayers"))
1586 {
1587 const auto& tileLayersJson = levelDef.metadata.customData["tileLayers"];
1588
1589 if (tileLayersJson.is_array())
1590 {
1591 std::cout << "-> Loading " << tileLayersJson.size() << " tile layers...\n";
1592
1593 for (size_t i = 0; i < tileLayersJson.size(); ++i)
1594 {
1595 const auto& layerJson = tileLayersJson[i];
1596 std::string layerType = layerJson.value("type", "");
1597
1598 if (layerType == "tilelayer")
1599 {
1601 }
1602 }
1603 }
1604 }
1605
1606 // NOTE: Tile chunks are now loaded and stored in m_tileChunks.
1607 // To fully render these tiles, the following work is still needed:
1608 //
1609 // 1. Store tileset metadata (firstgid, tilewidth, tileheight, source texture)
1610 // during level loading in Phase 1 or Phase 2
1611 //
1612 // 2. Create a helper method to map tile GID to (texture, srcRect):
1613 // - Find which tileset the GID belongs to (using firstgid ranges)
1614 // - Calculate source rectangle within the tileset texture
1615 // - Handle collection vs image-based tilesets differently
1616 //
1617 // 3. Integrate tile rendering into RenderMultiLayerForCamera():
1618 // - Add TileLayer render items to the depth-sorted render queue
1619 // - Use IsometricRenderer for isometric maps
1620 // - Use simple quad rendering for orthogonal maps
1621 //
1622 // See: Source/Rendering/IsometricRenderer.h for the rendering interface
1623 // See: Source/DataManager.h - PreloadTilesets() for texture loading
1624
1625 return true;
1626}
1627
1628//=============================================================================
1629// Tile Layer Loading Helper Methods
1630//=============================================================================
1631
1633{
1634 std::string layerName = layerJson.value("name", "unnamed_layer");
1635 int zOrder = layerJson.value("zOrder", 0);
1636 bool visible = layerJson.value("visible", true);
1637 float opacity = layerJson.value("opacity", 1.0f);
1638 std::string encoding = layerJson.value("encoding", ""); // Extract encoding from layer
1639
1640 if (!visible)
1641 {
1642 std::cout << " ⊙ Skipping invisible layer: " << layerName << "\n";
1643 return;
1644 }
1645
1646 result.pass1_visualLayers.totalObjects++;
1647
1648 // Handle infinite maps with chunks
1649 if (layerJson.contains("chunks") && layerJson["chunks"].is_array())
1650 {
1651 const auto& chunks = layerJson["chunks"];
1652
1653 std::cout << " -> Tile Layer (Infinite): " << layerName
1654 << " (" << chunks.size() << " chunks, encoding: " << encoding << ", z: " << zOrder << ")\n";
1655
1656 for (size_t i = 0; i < chunks.size(); ++i)
1657 {
1658 LoadTileChunk(chunks[i], layerName, zOrder, encoding); // Pass encoding
1659 }
1660
1661 result.pass1_visualLayers.successfullyCreated++;
1662 }
1663 // Handle finite maps with regular data
1664 else if (layerJson.contains("data"))
1665 {
1666 int width = layerJson.value("width", 0);
1667 int height = layerJson.value("height", 0);
1668
1669 std::cout << " -> Tile Layer (Finite): " << layerName
1670 << " (" << width << "x" << height << ", z: " << zOrder << ")\n";
1671
1672 LoadTileData(layerJson["data"], layerName, width, height, zOrder, encoding); // Pass encoding
1673 result.pass1_visualLayers.successfullyCreated++;
1674 }
1675 else
1676 {
1677 std::cout << " x Tile layer missing data: " << layerName << "\n";
1678 result.pass1_visualLayers.failed++;
1679 }
1680}
1681
1682void World::LoadTileChunk(const nlohmann::json& chunkJson, const std::string& layerName,
1683 int zOrder, const std::string& encoding)
1684{
1685 int chunkX = chunkJson.value("x", 0);
1686 int chunkY = chunkJson.value("y", 0);
1687 int chunkW = chunkJson.value("width", 0);
1688 int chunkH = chunkJson.value("height", 0);
1689
1690 // Decode tile data
1691 std::vector<uint32_t> tileGIDs;
1692
1693 if (chunkJson.contains("data"))
1694 {
1695 if (chunkJson["data"].is_string())
1696 {
1697 // Base64 encoded data
1698 std::string dataStr = chunkJson["data"].get<std::string>();
1700 dataStr,
1701 encoding, // Use layer encoding
1702 "" // No compression
1703 );
1704 }
1705 else if (chunkJson["data"].is_array())
1706 {
1707 // Direct array of GIDs
1708 for (const auto& gid : chunkJson["data"])
1709 {
1710 tileGIDs.push_back(gid.get<uint32_t>());
1711 }
1712 }
1713 }
1714
1715 if (tileGIDs.empty())
1716 {
1717 std::cout << " x Failed to decode chunk at (" << chunkX << ", " << chunkY << ")\n";
1718 return;
1719 }
1720
1721 // Store chunk for rendering
1723 chunk.layerName = layerName;
1724 chunk.x = chunkX;
1725 chunk.y = chunkY;
1726 chunk.width = chunkW;
1727 chunk.height = chunkH;
1728 chunk.zOrder = zOrder;
1729 chunk.tileGIDs = tileGIDs;
1730
1731 m_tileChunks.push_back(chunk);
1732
1733 std::cout << " ok - Loaded chunk at (" << chunkX << ", " << chunkY
1734 << ") - " << tileGIDs.size() << " tiles\n";
1735}
1736
1737void World::LoadTileData(const nlohmann::json& dataJson, const std::string& layerName,
1738 int width, int height, int zOrder, const std::string& encoding)
1739{
1740 std::vector<uint32_t> tileGIDs;
1741
1742 if (dataJson.is_string())
1743 {
1744 // Base64 encoded
1745 std::string dataStr = dataJson.get<std::string>();
1746 tileGIDs = Olympe::Tiled::TiledDecoder::DecodeTileData(dataStr, encoding, "");
1747 }
1748 else if (dataJson.is_array())
1749 {
1750 // Direct array of tile IDs
1751 for (const auto& tile : dataJson)
1752 {
1753 tileGIDs.push_back(tile.get<uint32_t>());
1754 }
1755 }
1756
1757 if (tileGIDs.empty())
1758 {
1759 std::cout << " x No tile data for layer: " << layerName << "\n";
1760 return;
1761 }
1762
1763 // Store as a single chunk
1765 chunk.layerName = layerName;
1766 chunk.x = 0;
1767 chunk.y = 0;
1768 chunk.width = width;
1769 chunk.height = height;
1770 chunk.zOrder = zOrder;
1771 chunk.tileGIDs = tileGIDs;
1772
1773 m_tileChunks.push_back(chunk);
1774}
1775
1776// ========================================================================
1777// TilesetManager Implementation
1778// ========================================================================
1779
1781{
1782 // Cleanup textures - these are owned by TilesetManager and loaded directly
1783 // via IMG_LoadTexture, not through DataManager's texture cache
1784 for (auto& tileset : m_tilesets)
1785 {
1786 if (tileset.texture)
1787 {
1788 SDL_DestroyTexture(tileset.texture);
1789 tileset.texture = nullptr;
1790 }
1791
1792 for (auto& pair : tileset.individualTiles)
1793 {
1794 if (pair.second)
1795 {
1796 SDL_DestroyTexture(pair.second);
1797 }
1798 }
1799 tileset.individualTiles.clear();
1800 }
1801
1802 m_tilesets.clear();
1803}
1804
1806{
1807 Clear();
1808
1809 if (!tilesetsJson.is_array()) return;
1810
1811 for (const auto& tilesetJson : tilesetsJson)
1812 {
1814 info.firstgid = tilesetJson.value("firstgid", 0);
1815
1816 // ========================================================================
1817 // CRITICAL: Check if tileset is external (has "source" property)
1818 // ========================================================================
1819 if (tilesetJson.contains("source") && !tilesetJson["source"].get<std::string>().empty())
1820 {
1821 std::string sourceFile = tilesetJson["source"].get<std::string>();
1822
1823 SYSTEM_LOG << " -> Loading external tileset: " << sourceFile << "\n";
1824
1825 // Build full path relative to Levels directory
1826 std::string baseDir = "Gamedata/Levels/";
1827 std::string fullPath = baseDir + sourceFile;
1828
1829 // ========================================================================
1830 // CRITICAL: Use TilesetCache to load and parse external tileset file
1831 // ========================================================================
1833
1834 if (cachedTileset)
1835 {
1836 // Copy properties from TiledTileset to TilesetInfo
1837 info.name = cachedTileset->name;
1838 info.tilewidth = cachedTileset->tilewidth;
1839 info.tileheight = cachedTileset->tileheight;
1840 info.columns = cachedTileset->columns;
1841 info.imagewidth = cachedTileset->imagewidth;
1842 info.imageheight = cachedTileset->imageheight;
1843 info.margin = cachedTileset->margin;
1844 info.spacing = cachedTileset->spacing;
1845
1846 // ========================================================================
1847 // CRITICAL: Extract tileoffset from external tileset file
1848 // These offsets come directly from the .tsx/.tsj file's <tileoffset> element
1849 // and must be applied to ALL tiles in this tileset during rendering.
1850 // ========================================================================
1851 info.tileoffsetX = cachedTileset->tileoffsetX;
1852 info.tileoffsetY = cachedTileset->tileoffsetY;
1853
1854 SYSTEM_LOG << "[TilesetManager] ========================================+\n";
1855 SYSTEM_LOG << "[TilesetManager] Loading external tileset: " << info.name << "\n";
1856 SYSTEM_LOG << "[TilesetManager] Parsed global offset from cache: ("
1857 << info.tileoffsetX << ", " << info.tileoffsetY << ")\n";
1858 SYSTEM_LOG << "[TilesetManager] GID range: " << info.firstgid << " - "
1859 << (info.firstgid + cachedTileset->tilecount - 1) << "\n";
1860
1861 // Determine if collection tileset
1862 info.isCollection = !cachedTileset->tiles.empty() && cachedTileset->image.empty();
1863
1864 // Calculate lastgid
1865 uint32_t tilecount = cachedTileset->tilecount;
1866 info.lastgid = info.firstgid + tilecount - 1;
1867
1868 // ========================================================================
1869 // Load textures based on tileset type
1870 // ========================================================================
1871 if (!info.isCollection && !cachedTileset->image.empty())
1872 {
1873 // Image-based tileset
1874 std::string imagePath = cachedTileset->image;
1875
1876 // Extract filename
1877 size_t lastSlash = imagePath.find_last_of("/\\");
1878 std::string filename = (lastSlash != std::string::npos) ?
1879 imagePath.substr(lastSlash + 1) : imagePath;
1880
1881 // Find resource recursively
1883
1884 if (!fullImagePath.empty())
1885 {
1887 if (info.texture)
1888 {
1889 SYSTEM_LOG << "[TilesetManager] ok - Loaded atlas texture: " << filename << "\n";
1890 SYSTEM_LOG << "[TilesetManager] Image-based tileset - All "
1891 << tilecount << " tiles will use offset ("
1892 << info.tileoffsetX << ", " << info.tileoffsetY << ")\n";
1893 SYSTEM_LOG << "[TilesetManager] ========================================+\n";
1894 }
1895 else
1896 {
1897 SYSTEM_LOG << " x Failed to load tileset texture: " << fullImagePath << "\n";
1898 }
1899 }
1900 }
1901 else if (info.isCollection)
1902 {
1903 // ====================================================================
1904 // Collection tileset (individual tile images)
1905 // Each tile in a collection inherits the tileset's global tileoffset
1906 // ====================================================================
1907
1908 int loadedCount = 0;
1909 for (const auto& tile : cachedTileset->tiles)
1910 {
1911 if (tile.image.empty()) continue;
1912
1913 uint32_t tileId = tile.id;
1914 uint32_t gid = info.firstgid + tileId;
1915 std::string imagePath = tile.image;
1916
1917 // Extract filename
1918 size_t lastSlash = imagePath.find_last_of("/\\");
1919 std::string filename = (lastSlash != std::string::npos) ?
1920 imagePath.substr(lastSlash + 1) : imagePath;
1921
1922 // Find resource recursively
1924
1925 if (!fullImagePath.empty())
1926 {
1928 if (tex)
1929 {
1930 info.individualTiles[tileId] = tex;
1931
1932 SDL_Rect srcRect;
1933 srcRect.x = 0;
1934 srcRect.y = 0;
1935 srcRect.w = tile.imagewidth > 0 ? tile.imagewidth : info.tilewidth;
1936 srcRect.h = tile.imageheight > 0 ? tile.imageheight : info.tileheight;
1937 info.individualSrcRects[tileId] = srcRect;
1938
1939 loadedCount++;
1940
1941 // Log first few tiles and any in Trees GID range (127-135)
1942 if (loadedCount <= 3 || (gid >= 127 && gid <= 135))
1943 {
1944 SYSTEM_LOG << "[TilesetManager] GID " << gid
1945 << " (" << srcRect.w << "x" << srcRect.h << ")"
1946 << " - STORED with tileset offset: ("
1947 << info.tileoffsetX << ", " << info.tileoffsetY << ")\n";
1948 }
1949 }
1950 }
1951 }
1952
1953 SYSTEM_LOG << "[TilesetManager] ok - Loaded collection tileset: " << info.name
1954 << " (" << info.individualTiles.size() << " tiles)\n";
1955 SYSTEM_LOG << "[TilesetManager] All tiles stored with global offset: ("
1956 << info.tileoffsetX << ", " << info.tileoffsetY << ")\n";
1957 SYSTEM_LOG << "[TilesetManager] ========================================+\n";
1958 }
1959 }
1960 else
1961 {
1962 SYSTEM_LOG << " x Failed to load external tileset: " << sourceFile << "\n";
1963 continue;
1964 }
1965 }
1966 else
1967 {
1968 // ========================================================================
1969 // Embedded tileset (inline in TMJ) - LEGACY CODE PATH
1970 // ========================================================================
1971 info.name = tilesetJson.value("name", "");
1972 info.tilewidth = tilesetJson.value("tilewidth", 32);
1973 info.tileheight = tilesetJson.value("tileheight", 32);
1974 info.columns = tilesetJson.value("columns", 0);
1975 info.imagewidth = tilesetJson.value("imagewidth", 0);
1976 info.imageheight = tilesetJson.value("imageheight", 0);
1977 info.margin = tilesetJson.value("margin", 0);
1978 info.spacing = tilesetJson.value("spacing", 0);
1979
1980 // ====================================================================
1981 // CRITICAL: Parse tileoffset from embedded tileset
1982 // These offsets come from the embedded tileset's tileoffset property
1983 // ====================================================================
1984 if (tilesetJson.contains("tileoffset"))
1985 {
1986 info.tileoffsetX = tilesetJson["tileoffset"].value("x", 0);
1987 info.tileoffsetY = tilesetJson["tileoffset"].value("y", 0);
1988
1989 SYSTEM_LOG << " [TilesetManager] Embedded tileset '" << info.name << "'"
1990 << " - Global tileoffset: (" << info.tileoffsetX
1991 << ", " << info.tileoffsetY << ")\n";
1992 }
1993 else
1994 {
1995 // Explicit default values
1996 info.tileoffsetX = 0;
1997 info.tileoffsetY = 0;
1998 }
1999
2000 uint32_t tilecount = tilesetJson.value("tilecount", 0);
2001 info.lastgid = info.firstgid + tilecount - 1;
2002
2003 info.isCollection = (info.columns == 0);
2004
2005 // Load embedded tileset textures (existing code)
2006 if (!info.isCollection && tilesetJson.contains("image"))
2007 {
2008 std::string imagePath = tilesetJson["image"].get<std::string>();
2009
2010 size_t lastSlash = imagePath.find_last_of("/\\");
2011 std::string filename = (lastSlash != std::string::npos) ?
2012 imagePath.substr(lastSlash + 1) : imagePath;
2013
2014 std::string fullPath = DataManager::Get().FindResourceRecursive(filename);
2015
2016 if (!fullPath.empty())
2017 {
2018 info.texture = IMG_LoadTexture(GameEngine::renderer, fullPath.c_str());
2019 if (info.texture)
2020 {
2021 SYSTEM_LOG << " ok - Loaded embedded tileset texture: " << filename
2022 << " (gid: " << info.firstgid << "-" << info.lastgid << ")\n";
2023 }
2024 else
2025 {
2026 SYSTEM_LOG << " x Failed to load embedded tileset texture: " << fullPath << "\n";
2027 }
2028 }
2029 }
2030 else if (info.isCollection && tilesetJson.contains("tiles"))
2031 {
2032 const auto& tilesArray = tilesetJson["tiles"];
2033
2034 for (const auto& tileJson : tilesArray)
2035 {
2036 uint32_t tileId = tileJson["id"].get<uint32_t>();
2037 std::string imagePath = tileJson["image"].get<std::string>();
2038
2039 size_t lastSlash = imagePath.find_last_of("/\\");
2040 std::string filename = (lastSlash != std::string::npos) ?
2041 imagePath.substr(lastSlash + 1) : imagePath;
2042
2043 std::string fullPath = DataManager::Get().FindResourceRecursive(filename);
2044
2045 if (!fullPath.empty())
2046 {
2048 if (tex)
2049 {
2050 info.individualTiles[tileId] = tex;
2051
2052 SDL_Rect srcRect;
2053 srcRect.x = 0;
2054 srcRect.y = 0;
2055 srcRect.w = tileJson.value("width", info.tilewidth);
2056 srcRect.h = tileJson.value("height", info.tileheight);
2057 info.individualSrcRects[tileId] = srcRect;
2058 }
2059 }
2060 }
2061
2062 SYSTEM_LOG << " ok - Loaded embedded collection tileset: " << info.name
2063 << " (" << info.individualTiles.size() << " tiles)\n";
2064 }
2065 }
2066
2067 m_tilesets.push_back(info);
2068
2069 // ====================================================================
2070 // POST-INSERTION VERIFICATION: Check that offset values survived storage
2071 // ====================================================================
2072 const TilesetInfo& storedInfo = m_tilesets.back();
2073 if (storedInfo.tileoffsetX != info.tileoffsetX || storedInfo.tileoffsetY != info.tileoffsetY)
2074 {
2075 SYSTEM_LOG << "[TilesetManager] X ERROR: Offset LOST during vector storage!\n";
2076 SYSTEM_LOG << " Expected: (" << info.tileoffsetX << ", " << info.tileoffsetY << ")\n";
2077 SYSTEM_LOG << " Got: (" << storedInfo.tileoffsetX << ", " << storedInfo.tileoffsetY << ")\n";
2078 }
2079 else if (info.tileoffsetX != 0 || info.tileoffsetY != 0)
2080 {
2081 SYSTEM_LOG << "[TilesetManager] ok - POST-INSERT verification: offset=("
2082 << storedInfo.tileoffsetX << ", " << storedInfo.tileoffsetY << ") preserved\n";
2083 }
2084 }
2085
2086 // ========================================================================
2087 // CRITICAL FIX: Sort tilesets by firstgid in DESCENDING order
2088 // This ensures that when searching for a GID in GetTileTexture(), we check
2089 // the most specific (highest firstgid) tileset first.
2090 //
2091 // Example: If we have overlapping ranges:
2092 // - tiles-iso-1: [1-396]
2093 // - Tiles iso cube: [109-126] (embedded in tiles-iso-1)
2094 // - Trees: [127-205] (embedded in tiles-iso-1)
2095 //
2096 // Without sorting, GID 127 would match tiles-iso-1 first (wrong).
2097 // With descending sort, GID 127 matches Trees first (correct).
2098 // ========================================================================
2099 std::sort(m_tilesets.begin(), m_tilesets.end(),
2100 [](const TilesetInfo& a, const TilesetInfo& b) {
2101 return a.firstgid > b.firstgid; // Descending order
2102 });
2103
2104 SYSTEM_LOG << "\n[TilesetManager] ========================================+\n";
2105 SYSTEM_LOG << "[TilesetManager] Tileset load complete. Final ordering (by firstgid DESC):\n";
2106 for (const auto& tileset : m_tilesets)
2107 {
2108 SYSTEM_LOG << " - " << tileset.name
2109 << " [" << tileset.firstgid << " - " << tileset.lastgid << "]"
2110 << " offset=(" << tileset.tileoffsetX << ", " << tileset.tileoffsetY << ")\n";
2111 }
2112
2113 // ========================================================================
2114 // VALIDATION: Detect overlapping GID ranges (warning only)
2115 // This helps identify potential configuration issues in Tiled maps
2116 // ========================================================================
2117 bool hasOverlaps = false;
2118 for (size_t i = 0; i < m_tilesets.size(); ++i)
2119 {
2120 for (size_t j = i + 1; j < m_tilesets.size(); ++j)
2121 {
2122 const auto& ts1 = m_tilesets[i];
2123 const auto& ts2 = m_tilesets[j];
2124
2125 // Check if ranges overlap
2126 // After sorting by descending firstgid:
2127 // ts1.firstgid >= ts2.firstgid, so we only need to check if ts1.firstgid <= ts2.lastgid
2128 if (ts1.firstgid <= ts2.lastgid)
2129 {
2130 hasOverlaps = true;
2131 SYSTEM_LOG << " [WARNING] GID range overlap detected!\n";
2132 SYSTEM_LOG << " Tileset '" << ts1.name
2133 << "' [" << ts1.firstgid << "-" << ts1.lastgid << "]\n";
2134 SYSTEM_LOG << " overlaps with '" << ts2.name
2135 << "' [" << ts2.firstgid << "-" << ts2.lastgid << "]\n";
2136 SYSTEM_LOG << " -> GetTileTexture() will prioritize '" << ts1.name
2137 << "' due to higher firstgid\n";
2138 }
2139 }
2140 }
2141
2142 if (!hasOverlaps)
2143 {
2144 SYSTEM_LOG << " [OK] No GID range overlaps detected\n";
2145 }
2146 SYSTEM_LOG << "[TilesetManager] ========================================+\n";
2147}
2148
2150{
2151 // Strip flip flags (top 3 bits)
2152 uint32_t cleanGid = gid & 0x1FFFFFFF;
2153
2154 if (cleanGid == 0)
2155 {
2156 outTileset = nullptr;
2157 return false; // Empty tile
2158 }
2159
2160 // ====================================================================
2161 // Find the tileset with the HIGHEST firstgid that contains this GID.
2162 // This ensures we get the most specific tileset when ranges overlap.
2163 // For example, if tiles-iso-1 has [1-396] and Trees has [127-205],
2164 // GID 127 should match Trees (firstgid=127) not tiles-iso-1 (firstgid=1).
2165 // ====================================================================
2166 const TilesetInfo* bestMatch = nullptr;
2167
2168 for (const auto& tileset : m_tilesets)
2169 {
2170 if (cleanGid >= tileset.firstgid && cleanGid <= tileset.lastgid)
2171 {
2172 // Select tileset with highest firstgid
2173 if (!bestMatch || tileset.firstgid > bestMatch->firstgid)
2174 {
2175 bestMatch = &tileset;
2176 }
2177 }
2178 }
2179
2180 if (bestMatch)
2181 {
2182 const TilesetInfo& tileset = *bestMatch;
2183 uint32_t localId = cleanGid - tileset.firstgid;
2184
2185 // -> CRITICAL: Set the tileset pointer BEFORE any return
2187
2188 if (tileset.isCollection)
2189 {
2190 // Collection tileset - lookup individual tile
2191 auto it = tileset.individualTiles.find(localId);
2192 if (it != tileset.individualTiles.end())
2193 {
2194 auto srcIt = tileset.individualSrcRects.find(localId);
2195 if (srcIt != tileset.individualSrcRects.end())
2196 {
2197 outTexture = it->second;
2198 outSrcRect = srcIt->second;
2199
2200 if (outTexture == nullptr)
2201 {
2203 "[TILESET] NULL texture for collection tile GID=%u, localId=%u", gid, localId);
2204 return false;
2205 }
2206
2207 return true;
2208 }
2209 }
2210
2211 // Collection tile not found
2213 "[TILESET] Collection tile not found: GID=%u, localId=%u", gid, localId);
2214 return false;
2215 }
2216 else
2217 {
2218 // Image-based tileset - calculate source rect
2219 if (!tileset.texture)
2220 {
2222 "[TILESET] NULL texture for tileset '%s' (GID=%u)", tileset.name.c_str(), gid);
2223 return false;
2224 }
2225
2226 outTexture = tileset.texture;
2227
2228 // Calculate source rect with margin and spacing
2229 int col = localId % tileset.columns;
2230 int row = localId / tileset.columns;
2231
2232 outSrcRect.x = tileset.margin + col * (tileset.tilewidth + tileset.spacing);
2233 outSrcRect.y = tileset.margin + row * (tileset.tileheight + tileset.spacing);
2234 outSrcRect.w = tileset.tilewidth;
2235 outSrcRect.h = tileset.tileheight;
2236
2237 return true;
2238 }
2239 }
2240
2241 // GID not found in any tileset
2242 SYSTEM_LOG << "[TilesetManager::GetTileTexture] X GID " << cleanGid
2243 << " NOT FOUND in any tileset (total tilesets: " << m_tilesets.size() << ")\n";
2244 outTileset = nullptr;
2245 return false;
2246}
2247
2251{
2252 // =========================================================================
2253 // REMOVED: Legacy collision and sector creation
2254 // =========================================================================
2255 // All entities (including Collision and Sector types) are now instantiated
2256 // via PrefabFactory in Phase 5 (unified entity instantiation).
2257 // This ensures consistency and eliminates double instantiation issues.
2258 // =========================================================================
2259
2260 return true;
2261}
2262
2263//=============================================================================
2264// DEPRECATED: Pass3 and Pass4 (Replaced by Unified InstantiateEntity)
2265//=============================================================================
2266// These methods are no longer used in the 6-phase pipeline
2267// Entity instantiation is now handled by the unified InstantiateEntity() helper
2268// in Phase 5 of LoadLevelFromTiled()
2269
2273{
2274 SYSTEM_LOG << "\n[Pass 5] Linking object relationships...\n";
2275
2276 int linksCreated = 0;
2277
2278 // Process object links from level definition
2279 for (const auto& link : levelDef.objectLinks) {
2280 if (link.linkType == "patrol_path") {
2281 // Find guard entity
2282 auto guardIt = result.entityRegistry.find(link.sourceObjectName);
2283 if (guardIt == result.entityRegistry.end()) {
2284 SYSTEM_LOG << " x Guard '" << link.sourceObjectName << "' not found in registry\n";
2285 result.pass5_relationships.failed++;
2286 continue;
2287 }
2288
2289 // Find patrol path entity
2290 auto pathIt = result.entityRegistry.find(link.targetObjectName);
2291 if (pathIt == result.entityRegistry.end()) {
2292 SYSTEM_LOG << " x Patrol path '" << link.targetObjectName << "' not found in registry\n";
2293 result.pass5_relationships.failed++;
2294 continue;
2295 }
2296
2297 EntityID guard = guardIt->second;
2298 EntityID patrolPath = pathIt->second;
2299
2300 // Link guard to patrol path via AIBlackboard_data
2303
2304 // Get patrol points from patrol path entity
2307
2308 // Copy patrol points from path to guard (bounded)
2309 const int maxPoints = static_cast<int>(std::size(guardBlackboard.patrolPoints));
2310 const int count = std::min(pathData.patrolPointCount, maxPoints);
2311 guardBlackboard.patrolPointCount = count;
2312 for (int i = 0; i < count; ++i) {
2313 guardBlackboard.patrolPoints[i] = pathData.patrolPoints[i];
2314 }
2315 guardBlackboard.currentPatrolIndex = 0;
2316 guardBlackboard.hasPatrolPath = (count > 0);
2317
2318 SYSTEM_LOG << " ok - Linked guard '" << link.sourceObjectName
2319 << "' -> patrol '" << link.targetObjectName
2320 << "' (" << guardBlackboard.patrolPointCount << " points)\n";
2321
2322 result.pass5_relationships.linkedObjects++;
2323 linksCreated++;
2324 } else {
2325 SYSTEM_LOG << " x Patrol path '" << link.targetObjectName
2326 << "' missing AIBlackboard_data\n";
2327 result.pass5_relationships.failed++;
2328 }
2329 } else {
2330 SYSTEM_LOG << " x Guard '" << link.sourceObjectName
2331 << "' missing AIBlackboard_data\n";
2332 result.pass5_relationships.failed++;
2333 }
2334 }
2335 // TODO: Handle other link types (trigger_target, etc.) here
2336 }
2337
2338 // Legacy: Assign patrol paths from entity overrides (backward compatibility)
2339 for (const auto& entityInstance : levelDef.entities)
2340 {
2341 if (!entityInstance) continue;
2342
2343 // Look for entity in registry
2344 auto it = result.entityRegistry.find(entityInstance->name);
2345 if (it == result.entityRegistry.end()) continue;
2346
2347 EntityID entity = it->second;
2348
2349 // Handle patrol paths embedded in overrides
2350 if (!entityInstance->overrides.is_null() && entityInstance->overrides.contains("patrolPath"))
2351 {
2353 {
2354 result.pass5_relationships.totalObjects++;
2355
2357 const auto& patrolPath = entityInstance->overrides["patrolPath"];
2358
2359 if (patrolPath.is_array())
2360 {
2361 const int maxPoints = static_cast<int>(std::size(blackboard.patrolPoints));
2362 blackboard.patrolPointCount = 0;
2363 for (size_t i = 0; i < patrolPath.size() && blackboard.patrolPointCount < maxPoints; ++i)
2364 {
2365 if (patrolPath[i].contains("x") && patrolPath[i].contains("y"))
2366 {
2367 blackboard.patrolPoints[blackboard.patrolPointCount].x =
2368 static_cast<float>(patrolPath[i]["x"].get<float>());
2369 blackboard.patrolPoints[blackboard.patrolPointCount].y =
2370 static_cast<float>(patrolPath[i]["y"].get<float>());
2371 blackboard.patrolPoints[blackboard.patrolPointCount].z = 0.0f;
2372 blackboard.patrolPointCount++;
2373 }
2374 }
2375
2376 result.pass5_relationships.successfullyCreated++;
2377 std::cout << " -> Assigned " << blackboard.patrolPointCount
2378 << " patrol points to: " << entityInstance->name << "\n";
2379 }
2380 }
2381 }
2382 }
2383
2384 SYSTEM_LOG << " ok - Created " << linksCreated << " object relationships\n";
2385 return true;
2386}
2387
2388// ========================================================================
2389// Helper Methods for Entity Instantiation
2390// ========================================================================
2391
2392// Helper function to convert JSON value to ComponentParameter consistently
2393namespace {
2395 {
2397
2398 if (value.is_number_float())
2399 {
2400 param = ComponentParameter::FromFloat(value.get<float>());
2401 }
2402 else if (value.is_number_integer())
2403 {
2404 param = ComponentParameter::FromInt(value.get<int>());
2405 }
2406 else if (value.is_boolean())
2407 {
2408 param = ComponentParameter::FromBool(value.get<bool>());
2409 }
2410 else if (value.is_string())
2411 {
2412 param = ComponentParameter::FromString(value.get<std::string>());
2413 }
2414 else if (value.is_array())
2415 {
2416 // Check if this is a numeric array that could be a Vector
2417 if (value.size() >= 2 && value.size() <= 3 && value[0].is_number())
2418 {
2419 // Handle vector types with validation
2420 bool hasInvalidElements = false;
2421
2422 float x = 0.0f;
2423 float y = 0.0f;
2424 float z = 0.0f;
2425
2426 if (value[0].is_number()) {
2427 x = value[0].get<float>();
2428 } else {
2429 hasInvalidElements = true;
2430 }
2431
2432 if (value[1].is_number()) {
2433 y = value[1].get<float>();
2434 } else {
2435 hasInvalidElements = true;
2436 }
2437
2438 if (value.size() >= 3) {
2439 if (value[2].is_number()) {
2440 z = value[2].get<float>();
2441 } else {
2442 hasInvalidElements = true;
2443 }
2444 }
2445
2446 if (hasInvalidElements) {
2447 SYSTEM_LOG << "[World] WARNING: Vector array contains non-numeric elements: "
2448 << value.dump() << ". Non-numeric values defaulted to 0.0f." << std::endl;
2449 }
2450
2452 }
2453 else
2454 {
2455 // Preserve as array (for patrol paths, waypoints, etc.)
2457 }
2458 }
2459 else
2460 {
2461 // Default to string representation
2462 param = ComponentParameter::FromString(value.dump());
2463 }
2464
2465 return param;
2466 }
2467}
2468
2470 const nlohmann::json& overrides,
2473 const PrefabBlueprint* prefab)
2474{
2475 // REQUIREMENT A: Automatic TMJ field mapping (NO cross-component overwrites)
2476 // Map TMJ object fields (x, y, width, height) to component-scoped overrides
2477 // ONLY if entityInstance and prefab are provided, and ONLY if not already set
2478
2479 if (entityInstance && prefab)
2480 {
2481 // Helper lambda to check if prefab has a component
2482 auto prefabHasComponent = [&](const std::string& componentType) -> bool {
2483 for (const auto& comp : prefab->components) {
2484 if (comp.componentType == componentType)
2485 return true;
2486 }
2487 return false;
2488 };
2489
2490 // Helper lambda to check if component-scoped override already set
2491 auto hasComponentOverride = [&](const std::string& componentType, const std::string& paramName) -> bool {
2492 auto compIt = instanceParams.componentOverrides.find(componentType);
2493 if (compIt != instanceParams.componentOverrides.end()) {
2494 return compIt->second.find(paramName) != compIt->second.end();
2495 }
2496 return false;
2497 };
2498
2499 // 1) Position_data: Always map x/y/z from entityInstance.position (TMJ position)
2500 // NOTE: This is already done via instanceParams.position = entityInstance->position
2501 // No need to create component override here as it's handled by ParameterResolver
2502
2503 // Check if this is a point object (spawners, waypoints)
2504 bool isPointObject = false;
2505 if (!overrides.is_null())
2506 {
2507 if (overrides.contains("point") && overrides["point"].is_boolean())
2508 isPointObject = overrides["point"].get<bool>();
2509 // Also check if both width and height are explicitly 0
2510 if (!isPointObject && overrides.contains("width") && overrides.contains("height"))
2511 {
2512 if (overrides["width"].is_number() && overrides["height"].is_number())
2513 {
2514 float w = overrides["width"].get<float>();
2515 float h = overrides["height"].get<float>();
2516 if (w == 0.0f && h == 0.0f)
2517 isPointObject = true;
2518 }
2519 }
2520 }
2521
2522 // 2) BoundingBox_data: Map TMJ width/height AND x/y (offset) if prefab has it
2523 // Skip width/height for point objects
2524 if (prefabHasComponent("BoundingBox_data"))
2525 {
2526 // Get TMJ object dimensions (these come from TMJ object's width/height properties)
2527 // Note: For point objects, width/height may be 0
2528 float tmjWidth = 0.0f;
2529 float tmjHeight = 0.0f;
2530 float tmjX = 0.0f;
2531 float tmjY = 0.0f;
2532
2533 // Extract from entityInstance metadata if available
2534 // TMJ width/height are typically stored in the object definition
2535 // For now, we'll check if they're in overrides or try to extract from context
2536 if (!overrides.is_null())
2537 {
2538 // Check if TMJ provided width/height as flat properties
2539 if (overrides.contains("width") && overrides["width"].is_number())
2540 tmjWidth = overrides["width"].get<float>();
2541 if (overrides.contains("height") && overrides["height"].is_number())
2542 tmjHeight = overrides["height"].get<float>();
2543 if (overrides.contains("x") && overrides["x"].is_number())
2544 tmjX = overrides["x"].get<float>();
2545 if (overrides.contains("y") && overrides["y"].is_number())
2546 tmjY = overrides["y"].get<float>();
2547 }
2548
2549 // Only apply if dimensions are non-zero and not already overridden
2550 // NOTE: BoundingBox_data uses Int type for width/height per schema
2551 // TMJ dimensions (float) are rounded down to match schema type
2552 // SKIP width/height for point objects (spawners)
2553 if (!isPointObject && tmjWidth > 0.0f && !hasComponentOverride("BoundingBox_data", "width"))
2554 {
2555 instanceParams.componentOverrides["BoundingBox_data"]["width"] =
2556 ComponentParameter::FromInt(static_cast<int>(tmjWidth));
2557 }
2558 if (!isPointObject && tmjHeight > 0.0f && !hasComponentOverride("BoundingBox_data", "height"))
2559 {
2560 instanceParams.componentOverrides["BoundingBox_data"]["height"] =
2561 ComponentParameter::FromInt(static_cast<int>(tmjHeight));
2562 }
2563 if (tmjX != 0.0f && !hasComponentOverride("BoundingBox_data", "x"))
2564 {
2565 instanceParams.componentOverrides["BoundingBox_data"]["x"] =
2567 }
2568 if (tmjY != 0.0f && !hasComponentOverride("BoundingBox_data", "y"))
2569 {
2570 instanceParams.componentOverrides["BoundingBox_data"]["y"] =
2572 }
2573 }
2574
2575 // 3) CollisionZone_data: Map TMJ x/y/width/height if prefab has it
2576 // Skip width/height for point objects
2577 if (prefabHasComponent("CollisionZone_data"))
2578 {
2579 float tmjWidth = 0.0f;
2580 float tmjHeight = 0.0f;
2581 float tmjX = 0.0f;
2582 float tmjY = 0.0f;
2583 int overridesApplied = 0;
2584
2585 if (!overrides.is_null())
2586 {
2587 if (overrides.contains("width") && overrides["width"].is_number())
2588 tmjWidth = overrides["width"].get<float>();
2589 if (overrides.contains("height") && overrides["height"].is_number())
2590 tmjHeight = overrides["height"].get<float>();
2591 if (overrides.contains("x") && overrides["x"].is_number())
2592 tmjX = overrides["x"].get<float>();
2593 if (overrides.contains("y") && overrides["y"].is_number())
2594 tmjY = overrides["y"].get<float>();
2595 }
2596
2597 // Apply if not already overridden (component-scoped overrides take precedence)
2598 if (tmjX != 0.0f && !hasComponentOverride("CollisionZone_data", "x"))
2599 {
2600 instanceParams.componentOverrides["CollisionZone_data"]["x"] =
2603 }
2604 if (tmjY != 0.0f && !hasComponentOverride("CollisionZone_data", "y"))
2605 {
2606 instanceParams.componentOverrides["CollisionZone_data"]["y"] =
2609 }
2610 // SKIP width/height for point objects
2611 if (!isPointObject && tmjWidth > 0.0f && !hasComponentOverride("CollisionZone_data", "width"))
2612 {
2613 instanceParams.componentOverrides["CollisionZone_data"]["width"] =
2616 }
2617 if (!isPointObject && tmjHeight > 0.0f && !hasComponentOverride("CollisionZone_data", "height"))
2618 {
2619 instanceParams.componentOverrides["CollisionZone_data"]["height"] =
2622 }
2623
2624 // Log when CollisionZone_data overrides are injected
2625 if (overridesApplied > 0)
2626 {
2627 SYSTEM_LOG << "[World] CollisionZone_data: Applied " << overridesApplied
2628 << " TMJ overrides for '" << entityInstance->name << "'" << std::endl;
2629 }
2630 }
2631
2632 // 4) PhysicsBody_data: Map TMJ rotation if prefab has it
2633 if (prefabHasComponent("PhysicsBody_data"))
2634 {
2635 float tmjRotation = 0.0f;
2636
2637 if (!overrides.is_null())
2638 {
2639 if (overrides.contains("rotation") && overrides["rotation"].is_number())
2640 tmjRotation = overrides["rotation"].get<float>();
2641 }
2642
2643 // Apply rotation if non-zero and not already overridden
2644 if (tmjRotation != 0.0f && !hasComponentOverride("PhysicsBody_data", "rotation"))
2645 {
2646 instanceParams.componentOverrides["PhysicsBody_data"]["rotation"] =
2648 }
2649 }
2650
2651 // 5) VisualSprite_data: Map TMJ visible field if prefab has it
2652 if (prefabHasComponent("VisualSprite_data"))
2653 {
2654 bool tmjVisible = true; // Default to visible
2655
2656 if (!overrides.is_null())
2657 {
2658 if (overrides.contains("visible") && overrides["visible"].is_boolean())
2659 tmjVisible = overrides["visible"].get<bool>();
2660 }
2661
2662 // Apply visible if not already overridden
2663 // Note: We always apply this even if true, to ensure explicit TMJ visibility is respected
2664 if (!hasComponentOverride("VisualSprite_data", "visible"))
2665 {
2666 instanceParams.componentOverrides["VisualSprite_data"]["visible"] =
2668 }
2669 }
2670
2671 // NOTE: Do NOT map TMJ width/height to VisualSprite_data or VisualEditor_data automatically
2672 // These should only be set via explicit component-scoped overrides (e.g., "VisualSprite_data.width")
2673 }
2674
2675 // Now process existing overrides from JSON (explicit overrides take precedence)
2676 if (overrides.is_null())
2677 return;
2678
2679 for (auto it = overrides.begin(); it != overrides.end(); ++it)
2680 {
2681 const std::string& key = it.key();
2682 const auto& value = it.value();
2683
2684 // Skip TMJ metadata fields that were already processed
2685 if (key == "width" || key == "height" || key == "x" || key == "y" || key == "rotation" || key == "visible")
2686 {
2687 // These were handled in automatic TMJ mapping above
2688 // Don't store them as flat properties to avoid confusion
2689 continue;
2690 }
2691
2692 // Check if this is a component-scoped override (nested object)
2693 // Example: overrides["Transform"] = {"width": 32, "height": 64}
2694 if (value.is_object())
2695 {
2696 // This is a component-level override - extract all parameters
2697 std::map<std::string, ComponentParameter> componentParams;
2698
2699 for (auto paramIt = value.begin(); paramIt != value.end(); ++paramIt)
2700 {
2701 const std::string& paramName = paramIt.key();
2702 const auto& paramValue = paramIt.value();
2703
2704 // Use helper function for consistent conversion
2705 componentParams[paramName] = JsonValueToComponentParameter(paramValue);
2706 }
2707
2708 // Merge with component-scoped overrides (explicit overrides take precedence)
2709 auto& targetParams = instanceParams.componentOverrides[key];
2710 for (const auto& pair : componentParams)
2711 {
2712 // Use insert to check existence and add in one operation
2713 targetParams.insert(pair);
2714 }
2715 }
2716 else
2717 {
2718 // This is a flat property - use helper for consistent conversion
2719 instanceParams.properties[key] = JsonValueToComponentParameter(value);
2720 }
2721 }
2722}
2723
2727{
2728 EntityID entity = World::Get().CreateEntity();
2729 if (entity == INVALID_ENTITY_ID)
2730 {
2731 stats.failed++;
2732 stats.failedObjects.push_back(entityInstance.name + " (type: " + entityInstance.type + ")");
2733 return INVALID_ENTITY_ID;
2734 }
2735
2738
2739 // Add visual editor marker with red color for missing prefabs
2741 editorData.sprite = DataManager::Get().GetSprite("location-32.png", ".\\Resources\\Icons\\location-32.png");
2742 editorData.color = { 255, 0, 0, 255 }; // Bright red (RGBA)
2743 editorData.isVisible = true;
2744 if (editorData.sprite)
2745 {
2746 editorData.srcRect = { 0, 0, static_cast<float>(editorData.sprite->w), static_cast<float>(editorData.sprite->h) };
2747 editorData.hotSpot = Vector(editorData.srcRect.w / 2.0f, editorData.srcRect.h / 2.0f, 0.0f);
2748 }
2750
2751 stats.successfullyCreated++;
2752
2753 SYSTEM_LOG << " /!\\ PLACEHOLDER: Created red marker for missing prefab '"
2754 << entityInstance.type << "' (name: " << entityInstance.name
2755 << ") at position: " << entityInstance.position << "\n";
2756
2757 return entity;
2758}
2759
2760std::string World::ExtractPrefabName(const std::string& prefabPath)
2761{
2762 std::string prefabName = prefabPath;
2763
2764 // Remove path
2765 size_t lastSlash = prefabName.find_last_of("/\\");
2766 if (lastSlash != std::string::npos)
2767 prefabName = prefabName.substr(lastSlash + 1);
2768
2769 // Remove extension
2770 size_t lastDot = prefabName.find_last_of(".");
2771 if (lastDot != std::string::npos)
2772 prefabName = prefabName.substr(0, lastDot);
2773
2774 return prefabName;
2775}
2776
2778{
2779 // Validate that the entity has required player components
2781 {
2782 SYSTEM_LOG << "X World::RegisterPlayerEntity: Entity " << entity
2783 << " missing required player components (PlayerBinding_data, Controller_data)\n";
2784 return;
2785 }
2786
2787 // Delegate to VideoGame for full player registration
2789}
2790
2791//=============================================================================
2792// Isometric Origin Calculation (for tile/entity alignment)
2793//=============================================================================
2794
2795void World::SetMapBounds(int minTileX, int minTileY, int maxTileX, int maxTileY, int chunkOriginX, int chunkOriginY)
2796{
2797 m_minTileX = minTileX;
2798 m_minTileY = minTileY;
2799 m_maxTileX = maxTileX;
2800 m_maxTileY = maxTileY;
2803
2804 // Invalidate cached isometric origin (will be recalculated on next access)
2806
2807 SYSTEM_LOG << "[World] Map bounds set: tiles(" << minTileX << "," << minTileY
2808 << ") to (" << maxTileX << "," << maxTileY << "), chunk origin: ("
2809 << chunkOriginX << "," << chunkOriginY << ")\n";
2810}
2811
2813{
2814 // For isometric maps, calculate TMJ origin from the 4 map corners
2815 // using shared utility function to avoid code duplication
2816 //
2817 // NOTE: This caching assumes single-threaded access (typical for game engines).
2818 // If multi-threaded access is needed, synchronization should be added.
2819 if (m_mapOrientation == "isometric")
2820 {
2821 // Cache both X and Y values together to avoid inconsistency
2823 {
2829 }
2831 }
2832 return 0.0f;
2833}
2834
2836{
2837 // For isometric maps, calculate TMJ origin from the 4 map corners
2838 // (See GetIsometricOriginX for detailed explanation)
2839 //
2840 // NOTE: This caching assumes single-threaded access (typical for game engines).
2841 // If multi-threaded access is needed, synchronization should be added.
2842 if (m_mapOrientation == "isometric")
2843 {
2844 // Cache both X and Y values together to avoid inconsistency
2846 {
2852 }
2854 }
2855 return 0.0f;
2856}
Data-driven behavior tree system for AI decision making.
GridProjectionType
float LayerToZ(RenderLayer layer)
Convert layer enum to z-coordinate value.
RenderLayer ZToLayer(float z)
Convert z-coordinate to layer enum (rounds to nearest integer) Note: z-coordinates should exactly mat...
RenderLayer
Render layer enumeration for Z-ordering.
std::bitset< MAX_COMPONENTS > ComponentSignature
Definition ECS_Entity.h:31
ComponentTypeID GetComponentTypeID_Static()
Definition ECS_Entity.h:56
std::uint64_t EntityID
Definition ECS_Entity.h:21
std::uint64_t ComponentTypeID
Definition ECS_Entity.h:27
const EntityID INVALID_ENTITY_ID
Definition ECS_Entity.h:23
Animation system for 2D sprite animation.
Core game engine class.
void NotifyEditorEntityCreated(uint64_t entity)
void NotifyEditorEntityDestroyed(uint64_t entity)
void RegisterInputEntityWithManager(EntityID e)
Definition World.cpp:49
World and ECS Manager for Olympe Engine.
static std::vector< BTDependency > ScanPrefabs(const std::vector< std::string > &prefabNames)
static std::set< std::string > ExtractPrefabsFromLevel(const nlohmann::json &levelJson)
bool LoadTreeFromFile(const std::string &filepath, uint32_t treeId)
static BehaviorTreeManager & Get()
bool IsTreeLoadedByPath(const std::string &treePath) const
static CollisionMap & Get()
static DataManager & Get()
Definition DataManager.h:87
Sprite * GetSprite(const std::string &id, const std::string &path, ResourceCategory category=ResourceCategory::GameEntity)
std::string FindResourceRecursive(const std::string &filename, const std::string &rootDir="GameData") const
static SDL_Renderer * renderer
Main SDL renderer.
Definition GameEngine.h:129
static InputsManager & Get()
void RegisterInputEntity(EntityID e)
static NavigationMap & Get()
static AnimationManager & Get()
void LoadAnimationGraphs(const std::string &directoryPath)
void LoadAnimationBanks(const std::string &directoryPath)
static void CalculateTMJOrigin(int minTileX, int minTileY, int maxTileX, int maxTileY, int tileWidth, int tileHeight, float &outOriginX, float &outOriginY)
static ParallaxLayerManager & Get()
static std::vector< uint32_t > DecodeTileData(const std::string &data, const std::string &encoding, const std::string &compression)
std::shared_ptr< TiledTileset > GetTileset(const std::string &filepath)
static TilesetCache & GetInstance()
Factory class for creating entities from prefab blueprints.
static PrefabFactory & Get()
Get singleton instance.
void PreloadAllPrefabs(const std::string &prefabDirectory="Blueprints/EntityPrefab")
Preload all prefabs from directory.
void Clear()
Clear all loaded tilesets.
Definition World.cpp:1780
std::vector< TilesetInfo > m_tilesets
All loaded tilesets.
Definition World.h:175
void LoadTilesets(const nlohmann::json &tilesetsJson)
Load tilesets from JSON data.
Definition World.cpp:1805
bool GetTileTexture(uint32_t gid, SDL_Texture *&outTexture, SDL_Rect &outSrcRect, const TilesetInfo *&outTileset)
Get texture and source rect for a tile by GID.
Definition World.cpp:2149
static VideoGame & Get()
Definition VideoGame.h:34
void RegisterLoadedPlayerEntity(EntityID entity)
static ViewportManager & Get()
virtual void Render()
void NotifyBlueprintEditorEntityCreated(EntityID entity)
Definition World.cpp:347
std::queue< EntityID > m_freeEntityIDs
Definition World.h:644
bool m_isometricOriginCached
Definition World.h:781
bool InstantiatePass1_VisualLayers(const Olympe::Editor::LevelDefinition &levelDef, InstantiationResult &result)
Definition World.cpp:1485
int m_chunkOriginY
Definition World.h:776
EntityID CreateMissingPrefabPlaceholder(const Olympe::Editor::EntityInstance &entityInstance, InstantiationResult::PassStats &stats)
Create a red placeholder entity for missing prefabs.
Definition World.cpp:2724
EntityID InstantiateEntity(const std::shared_ptr< Olympe::Editor::EntityInstance > &entityInstance, PrefabFactory &factory, InstantiationResult::PassStats &stats)
Unified entity instantiation helper (used by all Phase 5 passes) Types are already normalized,...
Definition World.cpp:1387
void Initialize_ECS_Systems()
Definition World.cpp:87
void UnloadCurrentLevel()
Definition World.cpp:1302
bool LoadLevelFromTiled(const std::string &tiledMapPath)
Definition World.cpp:1006
void NotifyBlueprintEditorEntityDestroyed(EntityID entity)
Definition World.cpp:355
int m_minTileX
Definition World.h:769
void Render_ECS_Systems()
Definition World.cpp:185
void LoadTileLayer(const nlohmann::json &layerJson, InstantiationResult &result)
Definition World.cpp:1632
std::string ExtractPrefabName(const std::string &prefabPath)
Extract prefab name from prefab path (removes path and extension)
Definition World.cpp:2760
int m_tileWidth
Definition World.h:764
void LoadTileData(const nlohmann::json &dataJson, const std::string &layerName, int width, int height, int zOrder, const std::string &encoding)
Definition World.cpp:1737
static World & Get()
Get singleton instance (short form)
Definition World.h:232
int m_maxTileX
Definition World.h:771
T & AddComponent(EntityID entity, Args &&... args)
Definition World.h:393
int m_minTileY
Definition World.h:770
void SyncGridWithLevel(const Olympe::Editor::LevelDefinition &levelDef)
Synchronize grid settings with loaded level Extracts map orientation and tile dimensions from LevelDe...
Definition World.cpp:364
void LoadTileChunk(const nlohmann::json &chunkJson, const std::string &layerName, int zOrder, const std::string &encoding)
Definition World.cpp:1682
bool LoadLevelDependencies(const nlohmann::json &levelJson)
Definition World.cpp:926
std::unordered_map< ComponentTypeID, std::unique_ptr< IComponentPool > > m_componentPools
Definition World.h:640
void ValidateLevelPrefabs(const Olympe::Editor::LevelDefinition &levelDef)
Definition World.cpp:1361
void SetMapBounds(int minTileX, int minTileY, int maxTileX, int maxTileY, int chunkOriginX, int chunkOriginY)
Definition World.cpp:2795
TilesetManager m_tilesetManager
Definition World.h:761
void Notify_ECS_Systems(EntityID entity, ComponentSignature signature)
Definition World.cpp:206
T & GetComponent(EntityID entity)
Definition World.h:438
std::unordered_map< EntityID, ComponentSignature > m_entitySignatures
Definition World.h:636
RenderLayer CalculateLayerFromZOrder(float zOrder) const
Calculate layer index from zOrder value (for Tiled levels) Maps zOrder ranges to layer indices for pr...
Definition World.cpp:309
RenderLayer GetEntityLayer(EntityID entity) const
Get entity render layer.
Definition World.cpp:334
virtual ~World()
Destructor.
Definition World.cpp:82
void GenerateCollisionAndNavigationMaps(const Olympe::Tiled::TiledMap &tiledMap, const Olympe::Editor::LevelDefinition &levelDef)
Definition World.cpp:476
bool InstantiatePass2_SpatialStructure(const Olympe::Editor::LevelDefinition &levelDef, InstantiationResult &result)
Definition World.cpp:2248
int m_maxTileY
Definition World.h:772
float m_cachedIsometricOriginX
Definition World.h:779
void RegisterPlayerEntity(EntityID entity)
Register a player entity that was loaded from a level file Validates required components and delegate...
Definition World.cpp:2777
float m_cachedIsometricOriginY
Definition World.h:780
float GetIsometricOriginX() const
Definition World.cpp:2812
float GetIsometricOriginY() const
Definition World.cpp:2835
int m_tileHeight
Definition World.h:765
std::vector< std::unique_ptr< ECS_System > > m_systems
Definition World.h:648
std::vector< EntityID > m_entities
Definition World.h:645
void Process_ECS_Systems()
Definition World.cpp:176
EntityID CreateEntity()
Definition World.cpp:225
void RenderDebug_ECS_Systems()
Definition World.cpp:196
std::vector< TileChunk > m_tileChunks
Definition World.h:762
int m_chunkOriginX
Definition World.h:775
void Add_ECS_System(std::unique_ptr< ECS_System > system)
Definition World.cpp:170
bool InstantiatePass5_Relationships(const Olympe::Editor::LevelDefinition &levelDef, InstantiationResult &result)
Definition World.cpp:2270
std::string m_mapOrientation
Definition World.h:763
void SetEntityLayer(EntityID entity, RenderLayer layer)
Set entity render layer (updates position.z)
Definition World.cpp:292
void DestroyEntity(EntityID entity)
Definition World.cpp:255
World()
Default constructor.
Definition World.cpp:54
void ExtractCustomProperties(const nlohmann::json &overrides, LevelInstanceParameters &instanceParams, const Olympe::Editor::EntityInstance *entityInstance=nullptr, const PrefabBlueprint *prefab=nullptr)
Extract custom properties from JSON overrides into LevelInstanceParameters.
Definition World.cpp:2469
LayerProperties ParseLayerProperties(const std::map< std::string, TiledProperty > &properties)
bool IsCollisionLayer(const std::shared_ptr< Olympe::Tiled::TiledLayer > &layer)
Definition World.cpp:446
ComponentParameter JsonValueToComponentParameter(const nlohmann::json &value)
Definition World.cpp:2394
nlohmann::json json
Header file for PrefabFactory class, responsible for creating game object prefabs.
static ComponentParameter FromBool(bool value)
static ComponentParameter FromFloat(float value)
static ComponentParameter FromVector3(float x, float y, float z)
static ComponentParameter FromInt(int value)
static ComponentParameter FromArray(const nlohmann::json &arrayData)
static ComponentParameter FromString(const std::string &value)
Identity component for entity identification.
std::vector< std::unique_ptr< EntityInstance > > entities
Position component for spatial location.
Represents a chunk of tiles for rendering.
Definition World.h:73
std::string layerName
Name of the source layer.
Definition World.h:74
Information about a loaded tileset.
Definition World.h:106
int columns
Number of columns in atlas.
Definition World.h:112
std::string name
Tileset name.
Definition World.h:109
int tileoffsetX
Global X offset for all tiles.
Definition World.h:128
SDL_Texture * texture
Atlas texture (image-based)
Definition World.h:132
uint32_t firstgid
First Global ID in this tileset.
Definition World.h:107
int spacing
Spacing between tiles.
Definition World.h:116
int margin
Margin around atlas.
Definition World.h:115
int tileheight
Height of each tile.
Definition World.h:111
bool isCollection
True if collection tileset.
Definition World.h:117
std::map< uint32_t, SDL_Texture * > individualTiles
Per-tile textures.
Definition World.h:135
int tilewidth
Width of each tile.
Definition World.h:110
std::map< uint32_t, SDL_Rect > individualSrcRects
Per-tile source rects.
Definition World.h:136
std::vector< std::string > failedObjects
Definition World.h:329
#define SYSTEM_LOG