Olympe Engine 2.0
2D Game Engine with ECS Architecture
Loading...
Searching...
No Matches
ECS_Systems.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
8ECS Systems purpose: Define systems that operate on entities with specific components.
9
10*/
11
12#include "ECS_Systems.h"
13#include "ECS_Components.h"
14#include "ECS_Entity.h"
15#include "World.h"
16#include "GameEngine.h" // For delta time (fDt)
17#include "InputsManager.h"
18#include "InputDevice.h"
22#include "system/EventQueue.h"
23#include "VideoGame.h"
24#include "system/GameMenu.h"
27#include "CollisionMap.h"
28#include <iostream>
29#include <bitset>
30#include <cmath>
31#include <algorithm>
32#include <vector>
33#include <cfloat>
34#include "drawing.h"
35#include "RenderContext.h"
36
37#undef min
38#undef max
39
40
41//-------------------------------------------------------------
47{
48 // Early return if no entities
49 if (m_entities.empty())
50 return;
51
52 // Input processing logic here
53}
54//-------------------------------------------------------------
55// InputEventConsumeSystem: Consumes Input domain events and updates ECS components
57{
58 // No specific component signature required - operates on cached input entities
59}
60
62{
63 // Get all Input domain events from the EventQueue
65
66 // Static flags for debouncing
67 static bool s_key_TabPressed = false;
68 static bool s_key_CPressed = false;
69 static bool s_key_NPressed = false;
70
71 // Process each input event
72 queue.ForEachDomainEvent(EventDomain::Input, [](const Message& msg) {
73
74 // Handle TAB key for grid toggle (global, not player-specific)
76 {
77 auto sc = static_cast<SDL_Scancode>(msg.controlId);
78
79 if (sc == SDL_SCANCODE_TAB && !s_key_TabPressed)
80 {
81 s_key_TabPressed = true;
82 World::Get().ToggleGrid();
83 }
85 {
86 s_key_CPressed = true;
88 }
89 else if (sc == SDL_SCANCODE_N && !s_key_NPressed)
90 {
91 s_key_NPressed = true;
93 }
94 }
96 {
97 auto sc = static_cast<SDL_Scancode>(msg.controlId);
98
99 if (sc == SDL_SCANCODE_TAB)
100 {
101 s_key_TabPressed = false;
102 }
103 else if (sc == SDL_SCANCODE_C)
104 {
105 s_key_CPressed = false;
106 }
107 else if (sc == SDL_SCANCODE_N)
108 {
109 s_key_NPressed = false;
110 }
111 }
112
113 // Handle joystick connect/disconnect for InputsManager auto-rebind logic
115 {
116 // Auto reconnect joystick to any player that was disconnected if any
118 {
119 // get 1st Disconnected player
121 if (disconnectedPlayerID >= 0)
122 {
123 SYSTEM_LOG << "InputEventConsumeSystem: try rebinding joystick ID=" << msg.deviceId << " to disconnected player " << disconnectedPlayerID << "\n";
124 if (InputsManager::Get().AutoBindControllerToPlayer(disconnectedPlayerID))
125 {
126 // we can remove the disconnected player now, since he is rebound
128 SYSTEM_LOG << "InputEventConsumeSystem: Joystick ID=" << msg.deviceId << " rebound to player " << disconnectedPlayerID << "\n";
129 }
130 else
131 SYSTEM_LOG << "InputEventConsumeSystem: Failed to rebind joystick ID=" << msg.deviceId << " to disconnected player " << disconnectedPlayerID << "\n";
132 }
133 }
134 }
135
136 // Use optimized input entity cache instead of iterating all entities
138
139 for (EntityID entity : inputEntities)
140 {
141 try
142 {
143 if (!World::Get().HasComponent<PlayerBinding_data>(entity)) continue;
145
146 // Match device id: for keyboard, joystickID may be -1
147 if (binding.controllerID != msg.deviceId) continue;
148
149 // Ensure Controller_data exists
151 {
152 continue;
153 }
155 ctrl.controllerID = static_cast<short>(msg.deviceId);
156
157 // Update connection state
160 ctrl.isConnected = true;
163 ctrl.isConnected = false;
164
165 // Button events
168 {
169 int button = msg.controlId;
171 ctrl.buttons[button] = (msg.state != 0);
172 }
173
174 // Axis motion: update Controller_data axes
176 {
177 int axis = msg.controlId;
178 float value = msg.param1; // normalized [-1,1]
179 // Map axes to Controller_data structure
180 // axis 0 -> leftStick.x, axis 1 -> leftStick.y
181 // axis 2 -> rightStick.x, axis 3 -> rightStick.y
182 // axis 4 -> leftTrigger, axis 5 -> rightTrigger
183 if (axis == 0) ctrl.leftStick.x = value;
184 else if (axis == 1) ctrl.leftStick.y = value;
185 else if (axis == 2) ctrl.rightStick.x = value;
186 else if (axis == 3) ctrl.rightStick.y = value;
187 else if (axis == 4) ctrl.leftTrigger = (value + 1.0f) * 0.5f; // normalize -1..1 to 0..1
188 else if (axis == 5) ctrl.rightTrigger = (value + 1.0f) * 0.5f;
189 }
190
191 // Keyboard events are now handled by InputMappingSystem via Pull API
192 // No longer need to process keyboard events here
193 }
194 catch (const std::exception&)
195 {
196 // ignore per-entity errors
197 }
198 }
199 });
200}
201//-------------------------------------------------------------
202// GameEventConsumeSystem: Consumes Gameplay domain events
204{
205 // No specific component signature required - operates on global game state
206}
207
209{
210 // Get all Gameplay domain events from the EventQueue
212
213 // Process each gameplay event
214 queue.ForEachDomainEvent(EventDomain::Gameplay, [](const Message& msg) {
215
216 switch (msg.msg_type)
217 {
218 case EventType::Olympe_EventType_Game_Pause:
219 VideoGame::Get().Pause();
220 SYSTEM_LOG << "GameEventConsumeSystem: Paused via event\n";
221 break;
222 case EventType::Olympe_EventType_Game_Resume:
223 VideoGame::Get().Resume();
224 SYSTEM_LOG << "GameEventConsumeSystem: Resumed via event\n";
225 break;
226 case EventType::Olympe_EventType_Game_Quit:
227 VideoGame::Get().RequestQuit();
228 SYSTEM_LOG << "GameEventConsumeSystem: Quit requested via event\n";
229 break;
230 case EventType::Olympe_EventType_Game_Restart:
231 SYSTEM_LOG << "GameEventConsumeSystem: Restart requested via event (not implemented)\n";
232 break;
233 case EventType::Olympe_EventType_Game_TakeScreenshot:
234 SYSTEM_LOG << "GameEventConsumeSystem: TakeScreenshot event (not implemented)\n";
235 break;
236 case EventType::Olympe_EventType_Game_SaveState:
237 {
238 int slot = msg.controlId;
239 SYSTEM_LOG << "GameEventConsumeSystem: SaveState event slot=" << slot << "\n";
240 VideoGame::Get().SaveGame(slot);
241 break;
242 }
244 {
245 int slot = msg.controlId;
246 SYSTEM_LOG << "GameEventConsumeSystem: LoadState event slot=" << slot << "\n";
248 break;
249 }
250 default:
251 break;
252 }
253 });
254
255 // Also process keyboard events for add/remove player (Input domain, but game-related)
256 // Static state for debouncing
257 static bool s_key_AddPlayerPressed = false;
258 static bool s_key_RemovePlayerPressed = false;
259
260 queue.ForEachDomainEvent(EventDomain::Input, [](const Message& msg) {
261
263 {
264 auto sc = static_cast<SDL_Scancode>(msg.controlId);
265 if (sc == SDL_SCANCODE_RETURN)
266 {
267 // debounce: only act on initial press
268 if (!s_key_AddPlayerPressed && msg.state == 1)
269 {
272 SYSTEM_LOG << "GameEventConsumeSystem: Enter pressed -> add player (returned " << added << ")\n";
273 }
274 }
275 else if (sc == SDL_SCANCODE_BACKSPACE)
276 {
277 if (!s_key_RemovePlayerPressed && msg.state == 1)
278 {
281 if (!players.empty())
282 {
283 EntityID pid = players.back();
285 SYSTEM_LOG << "GameEventConsumeSystem: Backspace pressed -> remove player " << pid << " -> " << (ok ? "removed" : "failed") << "\n";
286 }
287 }
288 }
289 }
291 {
292 auto sc = static_cast<SDL_Scancode>(msg.controlId);
295 }
296 });
297}
298//-------------------------------------------------------------
299// UIEventConsumeSystem: Consumes UI domain events
301{
302 // No specific component signature required - operates on UI state
303}
304
306{
307 // Get all UI domain events from the EventQueue
309
310 // Process keyboard events from Input domain for menu toggle (ESC key)
311 queue.ForEachDomainEvent(EventDomain::Input, [](const Message& msg) {
312 // Toggle menu with ESC key
314 {
315 auto sc = static_cast<SDL_Scancode>(msg.controlId);
316
317 if (sc == SDL_SCANCODE_ESCAPE)
318 {
319 if (GameMenu::Get().IsActive())
320 {
321 GameMenu::Get().Deactivate(); // Close menu, resume game
322 SYSTEM_LOG << "UIEventConsumeSystem: ESC pressed - menu deactivated\n";
323 }
324 else
325 {
326 GameMenu::Get().Activate(); // Open menu, pause game
327 SYSTEM_LOG << "UIEventConsumeSystem: ESC pressed - menu activated\n";
328 }
329 }
330
331 // Handle menu navigation (if menu is active)
332 if (GameMenu::Get().IsActive())
333 {
334 switch (sc)
335 {
336 case SDL_SCANCODE_UP:
337 case SDL_SCANCODE_W:
339 SYSTEM_LOG << "UIEventConsumeSystem: Menu selection moved up\n";
340 break;
342 case SDL_SCANCODE_S:
344 SYSTEM_LOG << "UIEventConsumeSystem: Menu selection moved down\n";
345 break;
349 SYSTEM_LOG << "UIEventConsumeSystem: Menu selection validated\n";
350 break;
351 default:
352 break;
353 }
354 }
355 }
356 });
357
358 // Process each UI event
359 queue.ForEachDomainEvent(EventDomain::UI, [](const Message& msg) {
360
361 switch (msg.msg_type)
362 {
365 SYSTEM_LOG << "UIEventConsumeSystem: Menu Enter event - activated\n";
366 break;
369 SYSTEM_LOG << "UIEventConsumeSystem: Menu Exit event - deactivated\n";
370 break;
372 if (GameMenu::Get().IsActive())
373 {
374 SYSTEM_LOG << "UIEventConsumeSystem: Menu Validate event\n";
376 }
377 break;
378 default:
379 break;
380 }
381 });
382}
383//-------------------------------------------------------------
384// CameraEventConsumeSystem: Consumes Camera domain events
386{
387 // No specific component signature required - operates on camera entities
388}
389
391{
392 // Get all Camera domain events from the EventQueue
394
395 // Get the CameraSystem instance
397 if (!camSys) return;
398
399 // Process each camera event by forwarding to CameraSystem::OnEvent
400 // This is a transitional approach - eventually CameraSystem should consume events directly
401 queue.ForEachDomainEvent(EventDomain::Camera, [camSys](const Message& msg) {
402 camSys->OnEvent(msg);
403 });
404}
405//-------------------------------------------------------------
413{
414 // Early return if no entities
415 if (m_entities.empty())
416 return;
417
418 // AI processing logic here
419}
420//-------------------------------------------------------------
425{
426 // Early return if no entities
427 if (m_entities.empty())
428 return;
429
430 // Detection processing logic here
431}
432//-------------------------------------------------------------
437{
438 // Early return if no entities
439 if (m_entities.empty())
440 return;
441
442 // Physics processing logic here
443}
444//-------------------------------------------------------------
449{
450 // Early return if no entities
451 if (m_entities.empty())
452 return;
453
454 // Collision processing logic here
455}
456//-------------------------------------------------------------
461{
462 // Early return if no entities
463 if (m_entities.empty())
464 return;
465
466 // Trigger processing logic here
467}
468//-------------------------------------------------------------
470{
471 // Define the required components: Position AND AI_Player
474}
475
477{
478 // Early return if no entities
479 if (m_entities.empty())
480 return;
481
482 // Iterate ONLY over the relevant entities stored in m_entities
483 for (EntityID entity : m_entities)
484 {
485 try
486 {
487 // Direct and fast access to Component data from the Pools
490 // Game logic: simple movement based on speed and delta time
491 pos.position += move.velocity * GameEngine::fDt;
492 }
493 catch (const std::exception& e)
494 {
495 std::cerr << "MovementSystem Error for Entity " << entity << ": " << e.what() << "\n";
496 }
497 }
498}
499//-------------------------------------------------------------
501{
502 // Define the required components: Identity, Position, VisualSprite and BoundingBox
507}
509{
511 if (!renderer) return;
512
513 // Get actual players from ViewportManager
514 const auto& players = ViewportManager::Get().GetPlayers();
515
516 // Check if we have any ECS cameras active
517 // For multi-player: check player cameras
518 // For no-players: check default camera (playerId=-1)
519 bool hasECSCameras = false;
520 if (!players.empty())
521 {
522 // Multi-player case: check if any player has an active camera
523 for (short playerID : players)
524 {
526 if (camTransform.isActive)
527 {
528 hasECSCameras = true;
529 break;
530 }
531 }
532 }
533 else
534 {
535 // No-players case: check if default camera exists
537 hasECSCameras = defaultCam.isActive;
538 }
539
540 // Use ECS camera system if available, otherwise fall back to legacy
541 if (hasECSCameras)
542 {
543 if (!players.empty())
544 {
545 // Multi-camera rendering with ECS camera system
546 for (short playerID : players)
547 {
549
550 if (!camTransform.isActive)
551 continue;
552
553 // Set active camera for drawing functions
555
556 // Set viewport and clip rect
557 SDL_Rect viewportRect = {
558 (int)camTransform.viewport.x,
559 (int)camTransform.viewport.y,
560 (int)camTransform.viewport.w,
561 (int)camTransform.viewport.h
562 };
563
564 SDL_SetRenderViewport(renderer, &viewportRect);
565 //SDL_SetRenderClipRect(renderer, &viewportRect);
566
568 if (grid)
570
571 // Render with parallax layers
573
574 // Clear active camera after rendering this player
576
577 // Reset viewport-specific state
578 //SDL_SetRenderClipRect(renderer, nullptr);
579
580 }
581 }
582 else
583 {
584 // Single-view rendering with default camera (playerId=-1)
586 if (camTransform.isActive)
587 {
588 // Set active camera for drawing functions
590
591 // Set viewport and clip rect
592 SDL_Rect viewportRect = {
593 (int)camTransform.viewport.x,
594 (int)camTransform.viewport.y,
595 (int)camTransform.viewport.w,
596 (int)camTransform.viewport.h
597 };
598
599 SDL_SetRenderViewport(renderer, &viewportRect);
600 //SDL_SetRenderClipRect(renderer, &viewportRect);
601
602 // Render with parallax layers
604
605 // Clear active camera after rendering
607
608 // Reset viewport-specific state
609 //SDL_SetRenderClipRect(renderer, nullptr);
610 }
611 }
612
613 // Final reset
614 //SDL_SetRenderClipRect(renderer, nullptr);
616 }
617}
618
619// ========================================================================
620// Tile Rendering Helper Functions
621// ========================================================================
622
623// Rendering constants
624namespace {
625 // Depth calculation constants
626 constexpr float DEPTH_LAYER_SCALE = 10000.0f; // Scale for layer separation
627 constexpr float DEPTH_DIAGONAL_SCALE = 100.0f; // Scale for isometric diagonal (X+Y)
628 constexpr float DEPTH_X_SCALE = 0.1f; // Scale for X coordinate tie-breaking
629
630 // Frustum culling padding
631 constexpr int ISO_TILE_PADDING = 5; // Padding for isometric tall tiles
632 constexpr int ORTHO_TILE_PADDING = 2; // Padding for orthogonal tiles
633}
634
635// -> Helper: Extract flip flags from GID
636void ExtractFlipFlags(uint32_t gid, bool& flipH, bool& flipV, bool& flipD)
637{
638 constexpr uint32_t FLIPPED_HORIZONTALLY_FLAG = 0x80000000;
639 constexpr uint32_t FLIPPED_VERTICALLY_FLAG = 0x40000000;
640 constexpr uint32_t FLIPPED_DIAGONALLY_FLAG = 0x20000000;
641
642 flipH = (gid & FLIPPED_HORIZONTALLY_FLAG) != 0;
643 flipV = (gid & FLIPPED_VERTICALLY_FLAG) != 0;
644 flipD = (gid & FLIPPED_DIAGONALLY_FLAG) != 0;
645}
646
647// -> Helper: Convert flip flags to SDL flip mode
648// Note: SDL3 only supports horizontal and vertical flips
649// Diagonal flip (flipD) is extracted but not applied - requires rotation
650SDL_FlipMode GetSDLFlip(bool flipH, bool flipV, bool /*flipD*/)
651{
652 int flip = SDL_FLIP_NONE;
653 if (flipH) flip |= SDL_FLIP_HORIZONTAL;
654 if (flipV) flip |= SDL_FLIP_VERTICAL;
655 return static_cast<SDL_FlipMode>(flip);
656}
657
658// -> NEW: Calculate visible tile range with frustum culling
660 const std::string& orientation,
661 int tileWidth, int tileHeight,
662 int& minX, int& minY, int& maxX, int& maxY)
663{
664 if (orientation == "isometric") {
665 // Convert screen corners to world coordinates using CameraTransform::ScreenToWorld()
666 Vector topLeftWorld = cam.ScreenToWorld(Vector(0, 0, 0));
667 Vector topRightWorld = cam.ScreenToWorld(Vector(cam.viewport.w, 0));
668 Vector bottomLeftWorld = cam.ScreenToWorld(Vector(0, cam.viewport.h));
669 Vector bottomRightWorld = cam.ScreenToWorld(Vector(cam.viewport.w, cam.viewport.h));
670
671 // Convert world coordinates to isometric tile coordinates
672 // Inverse of: isoX = (worldX - worldY) * (tileWidth / 2.0f)
673 // isoY = (worldX + worldY) * (tileHeight / 2.0f)
674 // Solution: worldX = (isoX / (tileWidth/2) + isoY / (tileHeight/2)) / 2
675 // worldY = (isoY / (tileHeight/2) - isoX / (tileWidth/2)) / 2
676 float halfTileW = tileWidth / 2.0f;
677 float halfTileH = tileHeight / 2.0f;
678
679 auto worldToTile = [halfTileW, halfTileH](const Vector& world) {
680 float tileX = (world.x / halfTileW + world.y / halfTileH) / 2.0f;
681 float tileY = (world.y / halfTileH - world.x / halfTileW) / 2.0f;
682 return Vector(tileX, tileY, 0);
683 };
684
689
690 // Find bounding box in tile coordinates
691 float tileMinX = std::min({topLeft.x, topRight.x, bottomLeft.x, bottomRight.x});
692 float tileMaxX = std::max({topLeft.x, topRight.x, bottomLeft.x, bottomRight.x});
693 float tileMinY = std::min({topLeft.y, topRight.y, bottomLeft.y, bottomRight.y});
694 float tileMaxY = std::max({topLeft.y, topRight.y, bottomLeft.y, bottomRight.y});
695
696 // Bounding box with padding for tall tiles
697 minX = static_cast<int>(std::floor(tileMinX)) - ISO_TILE_PADDING;
698 minY = static_cast<int>(std::floor(tileMinY)) - ISO_TILE_PADDING;
699 maxX = static_cast<int>(std::ceil(tileMaxX)) + ISO_TILE_PADDING;
700 maxY = static_cast<int>(std::ceil(tileMaxY)) + ISO_TILE_PADDING;
701 }
702 else {
703 // Orthogonal/hex: Use CameraTransform::ScreenToWorld() for consistency
704 // When camera is rotated, all four corners need to be checked to find min/max
705 Vector topLeftWorld = cam.ScreenToWorld(Vector(0, 0, 0));
706 Vector topRightWorld = cam.ScreenToWorld(Vector(cam.viewport.w, 0));
707 Vector bottomLeftWorld = cam.ScreenToWorld(Vector(0, cam.viewport.h));
708 Vector bottomRightWorld = cam.ScreenToWorld(Vector(cam.viewport.w, cam.viewport.h));
709
710 // Find bounding box in world space (handles rotation correctly)
715
716 minX = static_cast<int>(std::floor(worldMinX / tileWidth)) - ORTHO_TILE_PADDING;
717 minY = static_cast<int>(std::floor(worldMinY / tileHeight)) - ORTHO_TILE_PADDING;
718 maxX = static_cast<int>(std::ceil(worldMaxX / tileWidth)) + ORTHO_TILE_PADDING;
719 maxY = static_cast<int>(std::ceil(worldMaxY / tileHeight)) + ORTHO_TILE_PADDING;
720 }
721}
722
723// -> NEW: Calculate depth for a tile
724float CalculateTileDepth(const std::string& orientation,
725 int worldX, int worldY,
726 int layerZOrder,
727 int tileWidth, int tileHeight)
728{
729 float baseDepth = static_cast<float>(layerZOrder) * DEPTH_LAYER_SCALE;
730
731 if (orientation == "isometric") {
732 // Isometric diagonal sort: X+Y then X
733 int diagonalSum = worldX + worldY;
735 }
736 else {
737 // Orthogonal/hex: Y position
738 return baseDepth + static_cast<float>(worldY * tileHeight);
739 }
740}
741
742// -> NEW: Calculate depth for an entity
743float CalculateEntityDepth(const std::string& orientation,
744 const Vector& position,
745 int tileWidth, int tileHeight)
746{
747 float baseDepth = position.z * DEPTH_LAYER_SCALE; // Layer zOrder
748
749 if (orientation == "isometric") {
750 // Use exact worldPosY for fine sorting
751 // This matches the tile depth calculation for proper integration
752 return baseDepth + position.y;
753 }
754 else {
755 // Orthogonal: Y position
756 return baseDepth + position.y;
757 }
758}
759
760// -> NEW: Render individual tile immediately (unified for all orientations)
761void RenderTileImmediate(SDL_Texture* texture, const SDL_Rect& srcRect,
762 int worldX, int worldY, uint32_t gid,
763 int tileoffsetX, int tileoffsetY,
764 const CameraTransform& cam,
765 const std::string& orientation,
766 int tileWidth, int tileHeight)
767{
768 if (!texture) return;
769
770 // Extract flip flags
771 bool flipH, flipV, flipD;
772 ExtractFlipFlags(gid, flipH, flipV, flipD);
773 SDL_FlipMode flip = GetSDLFlip(flipH, flipV, flipD);
774
775 SDL_FRect srcFRect = {(float)srcRect.x, (float)srcRect.y,
776 (float)srcRect.w, (float)srcRect.h};
777
778 // Calculate world position for this tile (orientation-specific)
779 Vector worldPos;
780
781 if (orientation == "isometric") {
782 // Convert tile coordinates to isometric world coordinates
783 // Uses the standard isometric projection formula
784 float isoX = (worldX - worldY) * (tileWidth / 2.0f);
785 float isoY = (worldX + worldY) * (tileHeight / 2.0f);
786
787 // Tiles use standard isometric projection with origin at tile (0,0)
788 // Objects are transformed from TMJ coordinates to match this system
789 // (see TransformObjectPosition in TiledToOlympe.cpp)
790 worldPos = Vector(isoX, isoY, 0.0f);
791 }
792 else if (orientation == "hexagonal") {
793 // Hexagonal axial to world (pointy-top)
794 float hexRadius = tileWidth / 2.0f;
795 float worldXPos = hexRadius * (sqrtf(3.0f) * worldX + sqrtf(3.0f) / 2.0f * worldY);
796 float worldYPos = hexRadius * (3.0f / 2.0f * worldY);
797 worldPos = Vector(worldXPos, worldYPos, 0.0f);
798 }
799 else {
800 // Orthogonal
801 worldPos = Vector((float) worldX * tileWidth, (float) worldY * tileHeight, 0.0f);
802 }
803
804 // Calculate screen position with zoom only (no rotation)
805 // Rotation is applied separately via SDL_RenderTextureRotated with viewport-centered pivot
806 float screenX = (worldPos.x - cam.worldPosition.x) * cam.zoom - cam.screenOffset.x + cam.viewport.w / 2.0f;
807 float screenY = (worldPos.y - cam.worldPosition.y) * cam.zoom - cam.screenOffset.y + cam.viewport.h / 2.0f;
808
809 // Tile offsets are in pixel/texture space, scale by zoom
810 float offsetScreenX = tileoffsetX * cam.zoom;
811 float offsetScreenY = tileoffsetY * cam.zoom;
812
813 // Calculate destination rectangle
815 destRect.w = srcRect.w * cam.zoom;
816 destRect.h = srcRect.h * cam.zoom;
817
818 if (orientation == "isometric") {
819 // Isometric: center tile horizontally, anchor at bottom
820 destRect.x = screenX + offsetScreenX - destRect.w / 2.0f;
821 destRect.y = screenY + offsetScreenY - destRect.h + (tileHeight * cam.zoom);
822 }
823 else {
824 // Orthogonal/hex: top-left anchor
825 // NOTE: Hexagonal tiles may require centered anchoring in the future
826 // depending on the specific hex tileset and map configuration
829 }
830
831 // Rotate around viewport center (not tile center!)
832 // Pivot point is the viewport center expressed in the tile's local coordinate system
834 cam.viewport.w / 2.0f - destRect.x,
835 cam.viewport.h / 2.0f - destRect.y
836 };
837
839 &srcFRect, &destRect,
840 cam.rotation, // Camera rotation angle
841 &pivotInTileSpace, // Pivot = viewport center in tile space
842 flip); // Flip flags
843}
844
845// -> UNIFIED RENDERING PIPELINE - Single-pass sorting with frustum culling
846// Multi-layer rendering with parallax support
848{
850 const std::string& mapOrientation = World::Get().GetMapOrientation();
851 int tileWidth = World::Get().GetTileWidth();
852 int tileHeight = World::Get().GetTileHeight();
853
854 // Unified RenderItem structure for all renderable elements
855 struct RenderItem
856 {
857 enum Type {
858 ParallaxLayer, // Image layers (backgrounds/foregrounds)
859 IndividualTile, // -> NEW: Individual tile with full data
860 Entity // Game objects
861 } type;
862
863 float depth; // Unified sorting key (lower = background)
864
865 // Type-specific data
866 union {
867 struct {
868 int layerIndex;
869 } parallax;
870
871 struct {
872 // -> Complete tile data for immediate rendering
873 SDL_Texture* texture;
874 SDL_Rect srcRect;
875 int worldX, worldY;
876 uint32_t gid;
877 int tileoffsetX; int tileoffsetY;
878 int zOrder;
879 } tile;
880
881 struct {
882 EntityID entityId;
883 } entity;
884 };
885
886 // Factory methods
887 static RenderItem MakeParallax(float depth, int layerIndex) {
889 item.type = ParallaxLayer;
890 item.depth = depth;
891 item.parallax.layerIndex = layerIndex;
892 return item;
893 }
894
895 static RenderItem MakeTile(float depth,
897 int wx, int wy, uint32_t gid,
898 int offX, int offY, int z) {
900 item.type = IndividualTile;
901 item.depth = depth;
902 item.tile.texture = tex;
903 item.tile.srcRect = src;
904 item.tile.worldX = wx;
905 item.tile.worldY = wy;
906 item.tile.gid = gid;
907 item.tile.tileoffsetX = offX;
908 item.tile.tileoffsetY = offY;
909 item.tile.zOrder = z;
910 return item;
911 }
912
913 static RenderItem MakeEntity(float depth, EntityID id) {
915 item.type = Entity;
916 item.depth = depth;
917 item.entity.entityId = id;
918 return item;
919 }
920 };
921
922 std::vector<RenderItem> renderBatch;
923 renderBatch.reserve(1000); // Reserve for typical visible items (parallax + ~200-400 tiles + entities)
924
925 // ================================================================
926 // PHASE 1: FRUSTUM CULLING + POPULATION
927 // ================================================================
928
929 // 1.1 Parallax Layers (always visible)
930 const auto& parallaxLayers = parallaxMgr.GetLayers();
931 for (size_t i = 0; i < parallaxLayers.size(); ++i) {
932 const auto& layer = parallaxLayers[i];
933 if (!layer.visible) continue;
934
935 float depth;
936 if (layer.scrollFactorX < 1.0f || layer.zOrder < 0) {
937 // Background (distant)
938 depth = -1000.0f + layer.zOrder;
939 } else if (layer.scrollFactorX > 1.0f || layer.zOrder > 100) {
940 // Foreground (close)
941 depth = 10000.0f + layer.zOrder;
942 } else {
943 // Middle layers
944 depth = static_cast<float>(layer.zOrder);
945 }
946
947 renderBatch.push_back(RenderItem::MakeParallax(depth, static_cast<int>(i)));
948 }
949
950 // 1.2 Tiles (with -> FRUSTUM CULLING)
951 const auto& tileChunks = World::Get().GetTileChunks();
953
954 // -> Calculate visible tile range
955 int minX, minY, maxX, maxY;
956 GetVisibleTileRange(cam, mapOrientation, tileWidth, tileHeight,
957 minX, minY, maxX, maxY);
958
959 for (const auto& chunk : tileChunks) {
960 for (int y = 0; y < chunk.height; ++y) {
961 for (int x = 0; x < chunk.width; ++x) {
962 int worldX = chunk.x + x;
963 int worldY = chunk.y + y;
964
965 // -> FRUSTUM CULLING
968 continue;
969 }
970
971 int tileIndex = y * chunk.width + x;
972 if (tileIndex >= chunk.tileGIDs.size()) continue;
973
974 uint32_t gid = chunk.tileGIDs[tileIndex];
975 if (gid == 0) continue;
976
977 // Get texture
978 SDL_Texture* texture = nullptr;
979 SDL_Rect srcRect;
980 const TilesetManager::TilesetInfo* tileset = nullptr;
981 if (!tilesetMgr.GetTileTexture(gid, texture, srcRect, tileset)) {
982 continue;
983 }
984
985 // -> Calculate depth
986 float depth = CalculateTileDepth(mapOrientation,
987 worldX, worldY,
988 chunk.zOrder,
989 tileWidth, tileHeight);
990
991 // -> Add to batch
992 renderBatch.push_back(RenderItem::MakeTile(
993 depth, texture, srcRect,
994 worldX, worldY, gid,
995 tileset ? tileset->tileoffsetX : 0,
996 tileset ? tileset->tileoffsetY : 0,
997 chunk.zOrder
998 ));
999 }
1000 }
1001 }
1002
1003 // 1.3 Entities (with -> FRUSTUM CULLING)
1004 for (EntityID entity : World::Get().GetSystem<RenderingSystem>()->m_entities) {
1005 try {
1009
1010 // -> NEW: Skip UI entities (rendered in Pass 2)
1011 if (World::Get().HasComponent<Identity_data>(entity))
1012 {
1014 if (id.type == "UIElement")
1015 continue; // Skip UI, will be rendered in UIRenderingSystem
1016 }
1017
1018 if (!visual.visible) continue;
1019
1020 // -> FRUSTUM CULLING
1022 pos.position.x - visual.hotSpot.x,
1023 pos.position.y - visual.hotSpot.y,
1024 bbox.boundingBox.w,
1025 bbox.boundingBox.h
1026 };
1027 if (!cam.IsVisible(worldBounds)) continue;
1028
1029 // -> Calculate depth
1030 float depth = CalculateEntityDepth(mapOrientation,
1031 pos.position,
1032 tileWidth, tileHeight);
1033
1034 renderBatch.push_back(RenderItem::MakeEntity(depth, entity));
1035
1036 } catch (...) {}
1037 }
1038
1039 // ================================================================
1040 // PHASE 2: -> UNIFIED SORT (SINGLE PASS!)
1041 // ================================================================
1042 std::sort(renderBatch.begin(), renderBatch.end(),
1043 [](const RenderItem& a, const RenderItem& b) {
1044 return a.depth < b.depth;
1045 });
1046
1047 // ================================================================
1048 // PHASE 3: BATCH RENDER
1049 // ================================================================
1050 for (const auto& item : renderBatch) {
1051 switch (item.type) {
1052 case RenderItem::ParallaxLayer:
1053 parallaxMgr.RenderLayer(parallaxLayers[item.parallax.layerIndex], cam);
1054 break;
1055
1056 case RenderItem::IndividualTile:
1058 item.tile.texture, item.tile.srcRect,
1059 item.tile.worldX, item.tile.worldY, item.tile.gid,
1060 item.tile.tileoffsetX, item.tile.tileoffsetY,
1061 cam, mapOrientation, tileWidth, tileHeight
1062 );
1063 break;
1064
1065 case RenderItem::Entity:
1066 RenderSingleEntity(cam, item.entity.entityId);
1067 break;
1068 }
1069 }
1070
1071 // ================================================================
1072 // PHASE 4: RENDER OVERLAYS (LAST - ON TOP OF EVERYTHING)
1073 // ================================================================
1075 if (gridSystem)
1076 {
1077 const GridSettings_data* settings = gridSystem->FindSettings();
1078 if (settings)
1079 {
1080 // Render collision overlay
1081 if (settings->showCollisionOverlay)
1082 {
1083 gridSystem->RenderCollisionOverlay(cam, *settings);
1084 }
1085
1086 // Render navigation overlay
1087 if (settings->showNavigationOverlay)
1088 {
1089 gridSystem->RenderNavigationOverlay(cam, *settings);
1090 }
1091 }
1092 }
1093}
1094
1095// Render a single entity
1097{
1098 try
1099 {
1104
1105 // Create world bounds for frustum culling
1107 pos.position.x - visual.hotSpot.x,
1108 pos.position.y - visual.hotSpot.y,
1110 boxComp.boundingBox.h
1111 };
1112
1113 // Frustum culling - skip if not visible
1114 if (!cam.IsVisible(worldBounds))
1115 return;
1116
1117 // Transform position to screen space
1118 Vector centerScreen = cam.WorldToScreen(pos.position);
1119
1120 // Transform size to screen space
1121 Vector screenSize = cam.WorldSizeToScreenSize(
1122 Vector(boxComp.boundingBox.w, boxComp.boundingBox.h, 0.f)
1123 );
1124
1125 // Create destination rectangle
1127 centerScreen.x - visual.hotSpot.x * cam.zoom,
1128 centerScreen.y - visual.hotSpot.y * cam.zoom,
1129 screenSize.x,
1130 screenSize.y
1131 };
1132
1133 SDL_FPoint fpoint = { visual.hotSpot.x * cam.zoom, visual.hotSpot.y * cam.zoom };
1134
1135 // Render based on sprite type
1136 if (visual.sprite)
1137 {
1138 // Apply color modulation
1139 SDL_SetTextureColorMod(visual.sprite, visual.color.r, visual.color.g, visual.color.b);
1140
1141
1143
1144 // Debug: draw position & bounding box
1145 /*switch (id.type)
1146 {
1147 case EntityType::UIElement:
1148 case EntityType::Background:
1149 SDL_SetRenderDrawColor(GameEngine::renderer, 0, 0, 255, 255); // blue
1150 break;
1151 case EntityType::Player:
1152 SDL_SetRenderDrawColor(GameEngine::renderer, 0, 255, 0, 255); // green
1153 break;
1154 case EntityType::Ennemy:
1155 case EntityType::NPC:
1156 SDL_SetRenderDrawColor(GameEngine::renderer, 255, 0, 0, 255); // red
1157 break;
1158 default:
1159 SDL_SetRenderDrawColor(GameEngine::renderer, 255, 255, 0, 255); // yellow
1160 break;
1161 }/**/
1162 }
1163
1164 SDL_SetRenderDrawColor(GameEngine::renderer, 255, 255, 0, 255); // yellow
1165
1166 destRect = { pos.position.x - visual.hotSpot.x, pos.position.y - visual.hotSpot.y, boxComp.boundingBox.w, boxComp.boundingBox.h };
1167
1168 Draw_FilledCircle((int)(destRect.x + destRect.w / 2), (int)(destRect.y + destRect.h / 2), 3); // draw pivot/centre
1169 Draw_Rectangle(&destRect, SDL_Color{ 0, 255, 255, 255 }); // draw bounding box
1170
1171
1172 string _str = id.name + ", pos: (" + std::to_string(pos.position.x) + ", " + std::to_string(pos.position.y) + ") z-order: " + std::to_string(pos.position.z) + ")";
1173 destRect.y -= 25.f;
1174 destRect.w = (_str.length() * 10.f);
1175 destRect.h = 16.f;
1176 destRect.x = pos.position.x - (destRect.w / 2);
1177
1178 Draw_Text(_str, &destRect, SDL_Color{ 255, 0, 0, 255 }, SDL_Color{ 0, 0, 0, 75 }); // draw entity ID above
1179
1181 {
1183 // draw bouding box in purple
1184 destRect = { pos.position.x - visual.hotSpot.x, pos.position.y - visual.hotSpot.y, colZone.bounds.w, colZone.bounds.h };
1185 Draw_Rectangle(&destRect, SDL_Color{ 255, 0, 255, 255 }); // draw bounding box in purple
1186 }
1187
1188 }
1189 catch (const std::exception& e)
1190 {
1191 std::cerr << "RenderSingleEntity Error for Entity " << entity << ": " << e.what() << "\n";
1192 }
1193}
1194
1195// Render entities for a specific camera with frustum culling
1197{
1198 //SDL_Renderer* renderer = GameEngine::renderer;
1199 //if (!renderer) return;
1200
1201 // Get all entities with Position, VisualSprite, and BoundingBox
1202 for (EntityID entity : World::Get().GetSystem<RenderingSystem>()->m_entities)
1203 {
1204 try
1205 {
1210
1211 // Create world bounds for frustum culling
1213 pos.position.x - visual.hotSpot.x,
1214 pos.position.y - visual.hotSpot.y,
1216 boxComp.boundingBox.h
1217 };
1218
1219 // Frustum culling - skip if not visible
1220 if (!cam.IsVisible(worldBounds))
1221 continue;
1222
1223 // Transform position to screen space
1224 //Vector screenPos = cam.WorldToScreen(pos.position - visual.hotSpot);
1225 Vector centerScreen = cam.WorldToScreen(pos.position);
1226
1227 // Transform size to screen space
1228 Vector screenSize = cam.WorldSizeToScreenSize(
1229 Vector(boxComp.boundingBox.w, boxComp.boundingBox.h, 0.f)
1230 );
1231
1232 // Create destination rectangle
1234 //screenPos.x,
1235 //screenPos.y,
1236 centerScreen.x - visual.hotSpot.x * cam.zoom,
1237 centerScreen.y - visual.hotSpot.y * cam.zoom,
1238 screenSize.x,
1239 screenSize.y
1240 };
1241
1242 // Render based on sprite type
1243 if (visual.sprite)
1244 {
1245 // Apply color modulation
1246 SDL_SetTextureColorMod(visual.sprite, visual.color.r, visual.color.g, visual.color.b);
1247
1248 // Render sprite WITHOUT rotation - camera rotation is already applied via WorldToScreen
1249 // The "paper" (viewport) rotates, but sprites stay upright like stamps
1250 //SDL_RenderTexture(renderer, visual.sprite, nullptr, &destRect);
1251 SDL_FPoint fpoint = { visual.hotSpot.x * cam.zoom, visual.hotSpot.y * cam.zoom };
1253
1254 // Debug: draw position & bounding box
1255 /*switch (id.type)
1256 {
1257 case EntityType::UIElement:
1258 case EntityType::Background:
1259
1260 SDL_SetRenderDrawColor(GameEngine::renderer, 0, 0, 255, 255); // blue
1261 break;
1262 case EntityType::Player:
1263 SDL_SetRenderDrawColor(GameEngine::renderer, 0, 255, 0, 255); // green
1264 break;
1265 case EntityType::Ennemy:
1266 case EntityType::NPC:
1267 SDL_SetRenderDrawColor(GameEngine::renderer, 255, 0, 0, 255); // red
1268 break;
1269 default:
1270 SDL_SetRenderDrawColor(GameEngine::renderer, 255, 255, 0, 255); // yellow
1271 break;
1272
1273 }/**/
1274 }
1275
1276 SDL_SetRenderDrawColor(GameEngine::renderer, 255, 0, 255, 255); // magenta
1277
1278 Draw_FilledCircle((int)(destRect.x + destRect.w / 2), (int)(destRect.y + destRect.h / 2), 3); // draw pivot/centre
1279 Draw_Rectangle(&destRect, SDL_Color{ 0, 255, 255, 255 }); // draw bounding box
1280
1281 destRect.y -= 10.f;
1282 Draw_Text(id.name, &destRect, SDL_Color{ 255, 0, 0, 255 }, SDL_Color{ 0, 0, 0, 255 }); // draw entity ID above
1283
1284
1285 }
1286 catch (const std::exception& e)
1287 {
1288 std::cerr << "RenderEntitiesForCamera Error for Entity " << entity << ": " << e.what() << "\n";
1289 }
1290 }
1291}
1292//-------------------------------------------------------------
1303{
1304 // Early return if no entities
1305 if (m_entities.empty())
1306 return;
1307
1308 // Iterate ONLY over the relevant entities stored in m_entities
1309 for (EntityID entity : m_entities)
1310 {
1311 try
1312 {
1313 // Direct and fast access to Component data from the Pools
1319
1320 // check if the controller is bound with right player id
1321 if (binding.controllerID != ctrlData.controllerID )
1322 continue; // skip if not bound
1323
1324 // Game logic: simple movement based on joystick direction and delta time
1325 pos.position += controller.Joydirection * physBody.speed * GameEngine::fDt;
1326 }
1327 catch (const std::exception& e)
1328 {
1329 std::cerr << "MovementSystem Error for Entity " << entity << ": " << e.what() << "\n";
1330 }
1331 }
1332}
1333//-------------------------------------------------------------
1335{
1336 // Required components: PlayerBinding_data + PlayerController_data + Controller_data
1340}
1341
1343{
1344 // Early return if no entities
1345 if (m_entities.empty())
1346 return;
1347
1348 // Check if we should process gameplay input
1351 {
1352 // Don't process gameplay input when in UI or Editor context
1353 return;
1354 }
1355
1356 // Iterate over all entities with input components
1357 for (EntityID entity : m_entities)
1358 {
1359 try
1360 {
1364
1365 // Reset direction each frame
1366 pctrl.Joydirection.x = 0.0f;
1367 pctrl.Joydirection.y = 0.0f;
1368
1369 // Try to get device from new input system
1371
1372 // Get or create InputMapping_data (optional component) - for backward compatibility
1373 InputMapping_data* mapping = nullptr;
1375 {
1377 }
1378
1379 // Keyboard input (controllerID == -1)
1380 if (binding.controllerID == -1)
1381 {
1382 // Use Pull API to read keyboard state
1384
1385 // Try to use new profile system first
1386 if (device && device->profile) {
1387 auto profile = device->profile;
1388
1389 // Movement actions
1390 auto moveUpBinding = profile->GetActionBinding("move_up");
1391 auto moveDownBinding = profile->GetActionBinding("move_down");
1392 auto moveLeftBinding = profile->GetActionBinding("move_left");
1393 auto moveRightBinding = profile->GetActionBinding("move_right");
1394
1395 if (moveUpBinding && moveUpBinding->type == InputType::Key) {
1396 if (km.IsKeyHeld(static_cast<SDL_Scancode>(moveUpBinding->primaryInput)) ||
1397 (moveUpBinding->alternateInput != -1 && km.IsKeyHeld(static_cast<SDL_Scancode>(moveUpBinding->alternateInput))))
1398 pctrl.Joydirection.y = -1.0f;
1399 }
1401 if (km.IsKeyHeld(static_cast<SDL_Scancode>(moveDownBinding->primaryInput)) ||
1402 (moveDownBinding->alternateInput != -1 && km.IsKeyHeld(static_cast<SDL_Scancode>(moveDownBinding->alternateInput))))
1403 pctrl.Joydirection.y = 1.0f;
1404 }
1406 if (km.IsKeyHeld(static_cast<SDL_Scancode>(moveLeftBinding->primaryInput)) ||
1407 (moveLeftBinding->alternateInput != -1 && km.IsKeyHeld(static_cast<SDL_Scancode>(moveLeftBinding->alternateInput))))
1408 pctrl.Joydirection.x = -1.0f;
1409 }
1411 if (km.IsKeyHeld(static_cast<SDL_Scancode>(moveRightBinding->primaryInput)) ||
1412 (moveRightBinding->alternateInput != -1 && km.IsKeyHeld(static_cast<SDL_Scancode>(moveRightBinding->alternateInput))))
1413 pctrl.Joydirection.x = 1.0f;
1414 }
1415
1416 // Action buttons
1417 auto jumpBinding = profile->GetActionBinding("jump");
1418 if (jumpBinding && jumpBinding->type == InputType::Key) {
1419 pctrl.isJumping = km.IsKeyHeld(static_cast<SDL_Scancode>(jumpBinding->primaryInput));
1420 }
1421
1422 auto shootBinding = profile->GetActionBinding("shoot");
1423 if (shootBinding && shootBinding->type == InputType::Key) {
1424 pctrl.isShooting = km.IsKeyHeld(static_cast<SDL_Scancode>(shootBinding->primaryInput));
1425 }
1426
1427 auto interactBinding = profile->GetActionBinding("interact");
1429 pctrl.isInteracting = km.IsKeyPressed(static_cast<SDL_Scancode>(interactBinding->primaryInput));
1430 }
1431 }
1432 // Fallback to old InputMapping_data system
1433 else if (mapping)
1434 {
1435 // Use custom bindings
1436 if (km.IsKeyHeld(mapping->keyboardBindings["up"]) || km.IsKeyHeld(mapping->keyboardBindings["up_alt"]))
1437 pctrl.Joydirection.y = -1.0f;
1438 if (km.IsKeyHeld(mapping->keyboardBindings["down"]) || km.IsKeyHeld(mapping->keyboardBindings["down_alt"]))
1439 pctrl.Joydirection.y = 1.0f;
1440 if (km.IsKeyHeld(mapping->keyboardBindings["left"]) || km.IsKeyHeld(mapping->keyboardBindings["left_alt"]))
1441 pctrl.Joydirection.x = -1.0f;
1442 if (km.IsKeyHeld(mapping->keyboardBindings["right"]) || km.IsKeyHeld(mapping->keyboardBindings["right_alt"]))
1443 pctrl.Joydirection.x = 1.0f;
1444
1445 // Action buttons
1446 pctrl.isJumping = km.IsKeyHeld(mapping->keyboardBindings["jump"]);
1447 pctrl.isShooting = km.IsKeyHeld(mapping->keyboardBindings["shoot"]);
1448 pctrl.isInteracting = km.IsKeyPressed(mapping->keyboardBindings["interact"]);
1449 }
1450 else
1451 {
1452 // Default WASD + Arrows bindings
1453 if (km.IsKeyHeld(SDL_SCANCODE_W) || km.IsKeyHeld(SDL_SCANCODE_UP))
1454 pctrl.Joydirection.y = -1.0f;
1455 if (km.IsKeyHeld(SDL_SCANCODE_S) || km.IsKeyHeld(SDL_SCANCODE_DOWN))
1456 pctrl.Joydirection.y = 1.0f;
1457 if (km.IsKeyHeld(SDL_SCANCODE_A) || km.IsKeyHeld(SDL_SCANCODE_LEFT))
1458 pctrl.Joydirection.x = -1.0f;
1459 if (km.IsKeyHeld(SDL_SCANCODE_D) || km.IsKeyHeld(SDL_SCANCODE_RIGHT))
1460 pctrl.Joydirection.x = 1.0f;
1461
1462 // Default action buttons
1463 pctrl.isJumping = km.IsKeyHeld(SDL_SCANCODE_SPACE);
1464 pctrl.isShooting = km.IsKeyHeld(SDL_SCANCODE_LCTRL);
1465 pctrl.isInteracting = km.IsKeyPressed(SDL_SCANCODE_E);
1466 }
1467 }
1468 // Gamepad input
1469 else if (binding.controllerID >= 0)
1470 {
1471 SDL_JoystickID joyID = static_cast<SDL_JoystickID>(binding.controllerID);
1473
1474 // Read left stick from Controller_data (already populated by event system)
1475 pctrl.Joydirection.x = ctrl.leftStick.x;
1476 pctrl.Joydirection.y = ctrl.leftStick.y;
1477
1478 // Apply deadzone from profile or mapping
1479 float deadzone = 0.15f;
1480 if (device && device->profile) {
1481 deadzone = device->profile->deadzone;
1482 } else if (mapping) {
1483 deadzone = mapping->deadzone;
1484 }
1485
1486 float magnitude = std::sqrt(pctrl.Joydirection.x * pctrl.Joydirection.x +
1487 pctrl.Joydirection.y * pctrl.Joydirection.y);
1488 if (magnitude < deadzone)
1489 {
1490 pctrl.Joydirection.x = 0.0f;
1491 pctrl.Joydirection.y = 0.0f;
1492 }
1493
1494 // Read action buttons with new profile system
1495 if (device && device->profile) {
1496 auto profile = device->profile;
1497
1498 auto jumpBinding = profile->GetActionBinding("jump");
1499 if (jumpBinding && jumpBinding->type == InputType::Button) {
1500 pctrl.isJumping = jm.GetButton(joyID, jumpBinding->primaryInput);
1501 }
1502
1503 auto shootBinding = profile->GetActionBinding("shoot");
1505 pctrl.isShooting = jm.GetButton(joyID, shootBinding->primaryInput);
1506 }
1507
1508 auto interactBinding = profile->GetActionBinding("interact");
1510 pctrl.isInteracting = jm.IsButtonPressed(joyID, interactBinding->primaryInput);
1511 }
1512 }
1513 // Fallback to old mapping system
1514 else if (mapping)
1515 {
1516 pctrl.isJumping = jm.GetButton(joyID, mapping->gamepadBindings["jump"]);
1517 pctrl.isShooting = jm.GetButton(joyID, mapping->gamepadBindings["shoot"]);
1518 pctrl.isInteracting = jm.IsButtonPressed(joyID, mapping->gamepadBindings["interact"]);
1519 }
1520 else
1521 {
1522 // Default gamepad buttons
1523 pctrl.isJumping = jm.GetButton(joyID, 0); // A button
1524 pctrl.isShooting = jm.GetButton(joyID, 1); // B button
1525 pctrl.isInteracting = jm.IsButtonPressed(joyID, 2); // X button
1526 }
1527 }
1528
1529 // Normalize diagonal movement
1530 float magnitude = std::sqrt(pctrl.Joydirection.x * pctrl.Joydirection.x +
1531 pctrl.Joydirection.y * pctrl.Joydirection.y);
1532 if (magnitude > 1.0f)
1533 {
1534 pctrl.Joydirection.x /= magnitude;
1535 pctrl.Joydirection.y /= magnitude;
1536 }
1537 }
1538 catch (const std::exception& e)
1539 {
1540 std::cerr << "InputMappingSystem Error for Entity " << entity << ": " << e.what() << "\n";
1541 }
1542 }
1543}
1544//-------------------------------------------------------------
1545// NavigationSystem: processes entities with NavigationAgent_data
1546//-------------------------------------------------------------
1548{
1549 // Require Position_data and NavigationAgent_data
1552}
1553
1555{
1556 float deltaTime = GameEngine::fDt;
1557
1558 for (EntityID entity : m_entities)
1559 {
1561
1562 // Check if we need to repath
1563 if (agent.needsRepath)
1564 {
1565 RequestPath(entity, agent.targetPosition);
1566 agent.needsRepath = false;
1567 }
1568
1569 // Follow current path if we have one
1570 if (agent.hasPath && !agent.currentPath.empty())
1571 {
1572 FollowPath(entity, deltaTime);
1573 }
1574 }
1575}
1576
1578{
1580 Position_data& position = World::Get().GetComponent<Position_data>(entity);
1581
1583
1584 // Convert world positions to grid coordinates
1585 int startX, startY, goalX, goalY;
1586 navMap.WorldToGrid(position.position.x, position.position.y, startX, startY);
1587 navMap.WorldToGrid(targetPos.x, targetPos.y, goalX, goalY);
1588
1589 // Determine which layer to use based on layerMask
1591 for (int i = 0; i < 8; ++i)
1592 {
1593 if (agent.layerMask & (1 << i))
1594 {
1595 layer = static_cast<CollisionLayer>(i);
1596 break; // Use first active layer
1597 }
1598 }
1599
1600 // Request pathfinding
1601 std::vector<Vector> path;
1602 bool pathFound = navMap.FindPath(startX, startY, goalX, goalY, path, layer);
1603
1604 if (pathFound && !path.empty())
1605 {
1606 agent.currentPath = path;
1607 agent.currentWaypointIndex = 0;
1608 agent.hasPath = true;
1609 agent.targetPosition = targetPos;
1610 }
1611 else
1612 {
1613 agent.hasPath = false;
1614 agent.currentPath.clear();
1615 }
1616}
1617
1618void NavigationSystem::FollowPath(EntityID entity, float deltaTime)
1619{
1621 Position_data& position = World::Get().GetComponent<Position_data>(entity);
1622
1623 if (agent.currentWaypointIndex >= static_cast<int>(agent.currentPath.size()))
1624 {
1625 // Path completed
1626 agent.hasPath = false;
1627 agent.currentPath.clear();
1628 return;
1629 }
1630
1631 // Get current waypoint
1632 Vector& waypoint = agent.currentPath[agent.currentWaypointIndex];
1633
1634 // Calculate direction to waypoint
1635 Vector direction = waypoint - position.position;
1636 float distance = direction.Length();
1637
1638 // Check if we've arrived at this waypoint
1639 if (distance < agent.arrivalThreshold)
1640 {
1641 ++agent.currentWaypointIndex;
1642 return; // Move to next waypoint on next frame
1643 }
1644
1645 // Move towards waypoint
1646 if (distance > 0.0f)
1647 {
1648 direction.Normalize();
1649 float moveSpeed = agent.maxSpeed * deltaTime;
1650
1651 if (moveSpeed > distance)
1652 {
1653 // Don't overshoot
1654 position.position = waypoint;
1655 ++agent.currentWaypointIndex;
1656 }
1657 else
1658 {
1659 position.position.x += direction.x * moveSpeed;
1660 position.position.y += direction.y * moveSpeed;
1661 }
1662 }
1663}
1664
1666{
1667 // TODO: Implement obstacle detection logic
1668 // Planned approach:
1669 // 1. Check if current waypoint is still navigable
1670 // 2. Raycast along path to detect new obstacles
1671 // 3. Check for dynamic tile state changes
1672 // For now, always return false
1673 (void)entity; // Suppress unused parameter warning
1674 return false;
1675}
1676
1677//-------------------------------------------------------------
1679
1681{
1682 // Singleton simple: on prend la 1�re entit� qui a GridSettings_data
1683 for (const auto& kv : World::Get().m_entitySignatures)
1684 {
1685 EntityID e = kv.first;
1688 }
1689 return nullptr;
1690}
1691
1693{
1695 if (!renderer) return;
1696
1697 Vector a = cam.WorldToScreen(aWorld);
1698 Vector b = cam.WorldToScreen(bWorld);
1699
1700 SDL_SetRenderDrawColor(renderer, c.r, c.g, c.b, c.a);
1701 SDL_RenderLine(renderer, a.x, a.y, b.x, b.y);
1702}
1703
1704// Calculate world-space AABB visible in camera viewport
1706{
1707 // Get viewport corners in screen space (viewport-local coordinates)
1708 Vector topLeft = cam.ScreenToWorld(Vector(0.f, 0.f, 0.f));
1709 Vector topRight = cam.ScreenToWorld(Vector(cam.viewport.w, 0.f, 0.f));
1710 Vector bottomLeft = cam.ScreenToWorld(Vector(0.f, cam.viewport.h, 0.f));
1711 Vector bottomRight = cam.ScreenToWorld(Vector(cam.viewport.w, cam.viewport.h, 0.f));
1712
1713 // Compute AABB (min/max) in world space
1714 float minX = std::min({topLeft.x, topRight.x, bottomLeft.x, bottomRight.x});
1715 float maxX = std::max({topLeft.x, topRight.x, bottomLeft.x, bottomRight.x});
1716 float minY = std::min({topLeft.y, topRight.y, bottomLeft.y, bottomRight.y});
1717 float maxY = std::max({topLeft.y, topRight.y, bottomLeft.y, bottomRight.y});
1718
1719 return SDL_FRect{minX, minY, maxX - minX, maxY - minY};
1720}
1721
1723{
1725 if (!renderer) return;
1726
1727 const GridSettings_data* s = FindSettings();
1728 if (!s || !s->enabled) return;
1729
1730 // Get actual players from ViewportManager
1731 const auto& players = ViewportManager::Get().GetPlayers();
1732
1733 if (!players.empty())
1734 {
1735 // Multi-player case: render grid for each player's camera
1736 for (short playerID : players)
1737 {
1739 if (!cam.isActive) continue;
1740
1741 SDL_Rect viewportRect = {
1742 (int)cam.viewport.x,
1743 (int)cam.viewport.y,
1744 (int)cam.viewport.w,
1745 (int)cam.viewport.h
1746 };
1747 SDL_SetRenderViewport(renderer, &viewportRect);
1748 SDL_SetRenderClipRect(renderer, &viewportRect);
1749
1750 switch (s->projection)
1751 {
1752 case GridProjection::Ortho: RenderOrtho(cam, *s); break;
1753 case GridProjection::Iso: RenderIso(cam, *s); break;
1754 case GridProjection::HexAxial: RenderHex(cam, *s); break;
1755 default: RenderOrtho(cam, *s); break;
1756 }
1757
1758 // Reset viewport and clip rect after each viewport
1761 }
1762 }
1763 else
1764 {
1765 // No-players case: render grid with default camera (playerId=-1)
1767 if (cam.isActive)
1768 {
1769 SDL_Rect viewportRect = {
1770 (int)cam.viewport.x,
1771 (int)cam.viewport.y,
1772 (int)cam.viewport.w,
1773 (int)cam.viewport.h
1774 };
1775 SDL_SetRenderViewport(renderer, &viewportRect);
1776 SDL_SetRenderClipRect(renderer, &viewportRect);
1777
1778 switch (s->projection)
1779 {
1780 case GridProjection::Ortho: RenderOrtho(cam, *s); break;
1781 case GridProjection::Iso: RenderIso(cam, *s); break;
1782 case GridProjection::HexAxial: RenderHex(cam, *s); break;
1783 default: RenderOrtho(cam, *s); break;
1784 }
1785
1786 // Reset viewport and clip rect
1789 }
1790 }
1791
1792 // Final reset
1795}
1796
1798{
1800 if (!renderer) return;
1801
1802 const GridSettings_data* s = FindSettings();
1803 if (!s || !s->enabled) return;
1804
1805 // on n'utilise PLUS de boucle sur les players ici !
1806 switch (s->projection)
1807 {
1808 case GridProjection::Ortho: RenderOrtho(cam, *s); break;
1809 case GridProjection::Iso: RenderIso(cam, *s); break;
1810 case GridProjection::HexAxial: RenderHex(cam, *s); break;
1811 default: RenderOrtho(cam, *s); break;
1812 }
1813
1814 // NOTE: Overlays are now rendered in RenderMultiLayerForCamera (after all tiles/entities)
1815 // This ensures they appear on top of all graphics
1816}
1817
1819{
1820 const float csx = std::max(1.f, s.cellSize.x);
1821 const float csy = std::max(1.f, s.cellSize.y);
1822
1823 // Get actual world bounds visible in viewport
1825 float minX = bounds.x;
1826 float maxX = bounds.x + bounds.w;
1827 float minY = bounds.y;
1828 float maxY = bounds.y + bounds.h;
1829
1830 // LOD: if zoomed out, skip lines to avoid visual clutter
1831 int skipFactor = 1;
1832 if (cam.zoom < s.lodZoomThreshold)
1833 {
1834 skipFactor = s.lodSkipFactor;
1835 }
1836
1837 float stepX = csx * skipFactor;
1838 float stepY = csy * skipFactor;
1839
1840 int lines = 0;
1841
1842 // Vertical lines (constant X)
1843 float startX = std::floor(minX / stepX) * stepX;
1844 float endX = std::ceil(maxX / stepX) * stepX;
1845 for (float x = startX; x <= endX && lines < s.maxLines; x += stepX)
1846 {
1847 DrawLineWorld(cam, Vector(x, minY, 0.f), Vector(x, maxY, 0.f), s.color);
1848 ++lines;
1849 }
1850
1851 // Horizontal lines (constant Y)
1852 float startY = std::floor(minY / stepY) * stepY;
1853 float endY = std::ceil(maxY / stepY) * stepY;
1854 for (float y = startY; y <= endY && lines < s.maxLines; y += stepY)
1855 {
1856 DrawLineWorld(cam, Vector(minX, y, 0.f), Vector(maxX, y, 0.f), s.color);
1857 ++lines;
1858 }
1859}
1860
1862{
1863 const float w = std::max(1.f, s.cellSize.x);
1864 const float h = std::max(1.f, s.cellSize.y);
1865
1866 // Iso basis vectors (diamond)
1867 // World position = i*u + j*v
1868 Vector u(w * 0.5f, -h * 0.5f, 0.f); // Right-down diagonal
1869 Vector v(w * 0.5f, h * 0.5f, 0.f); // Right-up diagonal
1870
1871 // Get world bounds from camera (like RenderOrtho)
1873
1874 // Convert world bounds to isometric grid coordinates (i, j)
1875 // Helper: convert world position to grid coordinates using matrix inversion
1876 auto worldToGrid = [&](float wx, float wy) -> std::pair<float, float>
1877 {
1878 // Solve linear system:
1879 // wx = i * u.x + j * v.x
1880 // wy = i * u.y + j * v.y
1881 // Matrix inversion (2x2):
1882 // det = u.x * v.y - u.y * v.x
1883 float det = u.x * v.y - u.y * v.x;
1884 if (std::abs(det) < 0.0001f) return {0.f, 0.f};
1885
1886 float i = (wx * v.y - wy * v.x) / det;
1887 float j = (wy * u.x - wx * u.y) / det;
1888 return {i, j};
1889 };
1890
1891 // Sample bounds corners to find grid range
1892 float minI = FLT_MAX, maxI = -FLT_MAX;
1893 float minJ = FLT_MAX, maxJ = -FLT_MAX;
1894
1895 std::vector<std::pair<float, float>> samples = {
1896 {bounds.x, bounds.y}, // Top-left
1897 {bounds.x + bounds.w, bounds.y}, // Top-right
1898 {bounds.x, bounds.y + bounds.h}, // Bottom-left
1899 {bounds.x + bounds.w, bounds.y + bounds.h}, // Bottom-right
1900 {bounds.x + bounds.w * 0.5f, bounds.y + bounds.h * 0.5f} // Center
1901 };
1902
1903 for (const auto &sample : samples)
1904 {
1905 std::pair<float, float> gridCoords = worldToGrid(sample.first, sample.second);
1906 float i = gridCoords.first;
1907 float j = gridCoords.second;
1908 minI = std::min(minI, i);
1909 maxI = std::max(maxI, i);
1910 minJ = std::min(minJ, j);
1911 maxJ = std::max(maxJ, j);
1912 }
1913
1914 // Add padding to ensure full coverage
1915 int iMin = (int)std::floor(minI) - 2;
1916 int iMax = (int)std::ceil(maxI) + 2;
1917 int jMin = (int)std::floor(minJ) - 2;
1918 int jMax = (int)std::ceil(maxJ) + 2;
1919
1920 // LOD: skip lines when zoomed out
1921 int skipFactor = 1;
1922 if (cam.zoom < s.lodZoomThreshold)
1923 {
1924 skipFactor = s.lodSkipFactor;
1925 }
1926
1927 int lines = 0;
1928
1929 // Draw lines parallel to u (constant j)
1930 for (int j = jMin; j <= jMax && lines < s.maxLines; j += skipFactor)
1931 {
1932 Vector p0 = v * (float)j + u * (float)iMin;
1933 Vector p1 = v * (float)j + u * (float)iMax;
1934 DrawLineWorld(cam, p0, p1, s.color);
1935 ++lines;
1936 }
1937
1938 // Draw lines parallel to v (constant i)
1939 for (int i = iMin; i <= iMax && lines < s.maxLines; i += skipFactor)
1940 {
1941 Vector p0 = u * (float)i + v * (float)jMin;
1942 Vector p1 = u * (float)i + v * (float)jMax;
1943 DrawLineWorld(cam, p0, p1, s.color);
1944 ++lines;
1945 }
1946}
1947
1949{
1950 const float r = std::max(1.f, s.hexRadius);
1951
1952 // Pointy-top axial layout
1953 const float dx = 1.5f * r;
1954 const float dy = std::sqrt(3.0f) * r; // sqrt(3) * r
1955
1956 // Get world bounds
1958 float minX = bounds.x;
1959 float maxX = bounds.x + bounds.w;
1960 float minY = bounds.y;
1961 float maxY = bounds.y + bounds.h;
1962
1963 // Convert bounds to axial hex coords (q, r)
1964 int qMin = (int)std::floor(minX / dx) - 2;
1965 int qMax = (int)std::ceil(maxX / dx) + 2;
1966 int rMin = (int)std::floor(minY / dy) - 2;
1967 int rMax = (int)std::ceil(maxY / dy) + 2;
1968
1969 // LOD: skip hexes when zoomed out
1970 int skipFactor = 1;
1971 if (cam.zoom < s.lodZoomThreshold)
1972 {
1973 skipFactor = s.lodSkipFactor;
1974 }
1975
1976 auto hexCenter = [&](int q, int rr) -> Vector
1977 {
1978 float x = dx * (float)q;
1979 float y = dy * ((float)rr + 0.5f * (float)(q & 1));
1980 return Vector(x, y, 0.f);
1981 };
1982
1983 auto drawHex = [&](const Vector& c)
1984 {
1985 Vector pts[6];
1986 for (int i = 0; i < 6; ++i)
1987 {
1988 float a = (60.f * (float)i + 30.f) * (float)k_PI / 180.f;
1989 pts[i] = Vector(c.x + std::cos(a) * r, c.y + std::sin(a) * r, 0.f);
1990 }
1991 for (int i = 0; i < 6; ++i)
1992 DrawLineWorld(cam, pts[i], pts[(i + 1) % 6], s.color);
1993 };
1994
1995 int lines = 0;
1996 for (int q = qMin; q <= qMax && lines < s.maxLines; q += skipFactor)
1997 {
1998 for (int rr = rMin; rr <= rMax && lines < s.maxLines; rr += skipFactor)
1999 {
2000 Vector c = hexCenter(q, rr);
2001
2002 // Simple AABB culling (hex center ± radius)
2004 continue;
2005
2006 drawHex(c);
2007 lines += 6;
2008 }
2009 }
2010}
2011
2012void GridSystem::DrawFilledRectWorld(const CameraTransform& cam, const Vector& worldPos, float width, float height, const SDL_Color& c)
2013{
2015 if (!renderer) return;
2016
2017 // Convert world coordinates to screen coordinates
2018 Vector topLeft = cam.WorldToScreen(worldPos);
2019 Vector bottomRight = cam.WorldToScreen(Vector(worldPos.x + width, worldPos.y + height, 0.0f));
2020
2022 rect.x = topLeft.x;
2023 rect.y = topLeft.y;
2024 rect.w = bottomRight.x - topLeft.x;
2025 rect.h = bottomRight.y - topLeft.y;
2026
2027 // Set blend mode for transparency
2029 SDL_SetRenderDrawColor(renderer, c.r, c.g, c.b, c.a);
2031}
2032
2033void GridSystem::DrawIsometricTileOverlay(float centerX, float centerY, float width, float height, const SDL_Color& color)
2034{
2036 if (!renderer) return;
2037
2038 // Draw filled isometric diamond
2039 float halfW = width / 2.0f;
2040 float halfH = height / 2.0f;
2041
2042 // Convert SDL_Color (Uint8) to SDL_FColor (float) for SDL3 SDL_Vertex
2043 SDL_FColor fcolor = { color.r / 255.0f, color.g / 255.0f, color.b / 255.0f, color.a / 255.0f };
2044
2046
2047 // Top vertex
2048 vertices[0].position.x = centerX;
2049 vertices[0].position.y = centerY - halfH;
2050 vertices[0].color = fcolor;
2051 vertices[0].tex_coord.x = 0;
2052 vertices[0].tex_coord.y = 0;
2053
2054 // Right vertex
2055 vertices[1].position.x = centerX - halfW;
2056 vertices[1].position.y = centerY;
2057 vertices[1].color = fcolor;
2058 vertices[1].tex_coord.x = 0;
2059 vertices[1].tex_coord.y = 0;
2060
2061 // Bottom vertex
2062 vertices[2].position.x = centerX;
2063 vertices[2].position.y = centerY + halfH;
2064 vertices[2].color = fcolor;
2065 vertices[2].tex_coord.x = 0;
2066 vertices[2].tex_coord.y = 0;
2067
2068 // Left vertex
2069 vertices[3].position.x = centerX + halfW;
2070 vertices[3].position.y = centerY;
2071 vertices[3].color = fcolor;
2072 vertices[3].tex_coord.x = 0;
2073 vertices[3].tex_coord.y = 0;
2074
2075 int indices[] = {0, 1, 2, 0, 2, 3};
2076
2078 SDL_RenderGeometry(renderer, nullptr, vertices, 4, indices, 6);
2079}
2080
2081void GridSystem::DrawHexagonOverlay(float centerX, float centerY, float radius, const SDL_Color& color)
2082{
2084 if (!renderer) return;
2085
2086 // Draw hexagon with center vertex + 6 edge vertices for triangle fan
2087 constexpr int numPoints = 7; // Center + 6 edge vertices
2088 constexpr float HEXAGON_ROTATION_OFFSET = -30.0f; // Rotate hexagon to have flat top
2089 constexpr float DEG_TO_RAD = (float)(k_PI / 180.0f);
2091
2092 // Convert SDL_Color (Uint8) to SDL_FColor (float) for SDL3 SDL_Vertex
2093 SDL_FColor fcolor = { color.r / 255.0f, color.g / 255.0f, color.b / 255.0f, color.a / 255.0f };
2094
2095 // Center vertex - member by member assignment
2096 vertices[0].position.x = centerX;
2097 vertices[0].position.y = centerY;
2098 vertices[0].color = fcolor;
2099 vertices[0].tex_coord.x = 0;
2100 vertices[0].tex_coord.y = 0;
2101
2102 // Edge vertices (1-6) - member by member assignment
2103 for (int i = 0; i < 6; ++i)
2104 {
2105 float angle = (60.0f * i + HEXAGON_ROTATION_OFFSET) * DEG_TO_RAD;
2106 vertices[i + 1].position.x = centerX + radius * std::cos(angle);
2107 vertices[i + 1].position.y = centerY + radius * std::sin(angle);
2108 vertices[i + 1].color = fcolor;
2109 vertices[i + 1].tex_coord.x = 0;
2110 vertices[i + 1].tex_coord.y = 0;
2111 }
2112
2113 // Triangle fan from center vertex (0) to edge vertices (1-6)
2114 // 6 triangles: (0,1,2), (0,2,3), (0,3,4), (0,4,5), (0,5,6), (0,6,1)
2115 int indices[18] = {0,1,2, 0,2,3, 0,3,4, 0,4,5, 0,5,6, 0,6,1};
2116
2119}
2120
2122{
2123 if (!s.showCollisionOverlay) return;
2124
2126
2127 // One-time diagnostic logging
2128 static bool firstCall = true;
2129 static int renderCount = 0; // Tracks render calls for limited debug output
2130
2131 if (firstCall)
2132 {
2133 SYSTEM_LOG << "[GridSystem] RenderCollisionOverlay first call\n";
2134 SYSTEM_LOG << " CollisionMap dimensions: " << collMap.GetWidth()
2135 << "x" << collMap.GetHeight() << "\n";
2136 SYSTEM_LOG << " Active layer: " << (int)s.activeCollisionLayer << "\n";
2137 SYSTEM_LOG << " Overlay color: rgba("
2138 << (int)s.collisionColors[s.activeCollisionLayer].r << ","
2139 << (int)s.collisionColors[s.activeCollisionLayer].g << ","
2140 << (int)s.collisionColors[s.activeCollisionLayer].b << ","
2141 << (int)s.collisionColors[s.activeCollisionLayer].a << ")\n";
2142 SYSTEM_LOG << " Tile size: " << collMap.GetTileWidth()
2143 << "x" << collMap.GetTileHeight() << "\n";
2144 SYSTEM_LOG << " Projection type: " << (int)collMap.GetProjection() << "\n";
2145 firstCall = false;
2146 }
2147
2148 if (collMap.GetWidth() == 0 || collMap.GetHeight() == 0)
2149 {
2150 SYSTEM_LOG << "[GridSystem] ERROR: CollisionMap is EMPTY (not initialized)!\n";
2151 return;
2152 }
2153
2155 if (!renderer) return;
2156
2157 // Get the active collision layer
2158 CollisionLayer layer = static_cast<CollisionLayer>(s.activeCollisionLayer);
2159 const std::vector<std::vector<TileProperties>>& layerData = collMap.GetLayer(layer);
2160
2161 if (layerData.empty()) return;
2162
2163 // Get visible bounds
2165
2166 // Convert world bounds to grid coordinates
2167 // For isometric grids, we need to check all 4 corners to get proper min/max bounds
2169
2170 // Check all four corners of the world visible bounds
2171 int tlX, tlY, trX, trY, blX, blY, brX, brY;
2172 collMap.WorldToGrid(bounds.x, bounds.y, tlX, tlY); // Top-left
2173 collMap.WorldToGrid(bounds.x + bounds.w, bounds.y, trX, trY); // Top-right
2174 collMap.WorldToGrid(bounds.x, bounds.y + bounds.h, blX, blY); // Bottom-left
2175 collMap.WorldToGrid(bounds.x + bounds.w, bounds.y + bounds.h, brX, brY); // Bottom-right
2176
2177 // Find min/max across all corners
2178 minGridX = std::min({tlX, trX, blX, brX});
2179 minGridY = std::min({tlY, trY, blY, brY});
2180 maxGridX = std::max({tlX, trX, blX, brX});
2181 maxGridY = std::max({tlY, trY, blY, brY});
2182
2183 // Clamp to valid grid range
2184 minGridX = std::max(0, minGridX - 1);
2185 minGridY = std::max(0, minGridY - 1);
2186 maxGridX = std::min(collMap.GetWidth() - 1, maxGridX + 1);
2187 maxGridY = std::min(collMap.GetHeight() - 1, maxGridY + 1);
2188
2189 // Get color for this layer
2190 SDL_Color overlayColor = s.collisionColors[s.activeCollisionLayer];
2191
2192 // DEBUG: Log rendering bounds and tile scanning (first 3 calls only)
2193 if (renderCount < 3)
2194 {
2195 SYSTEM_LOG << "[GridSystem] RenderCollisionOverlay call #" << renderCount << "\n";
2196 SYSTEM_LOG << " World bounds: x=" << bounds.x << ", y=" << bounds.y
2197 << ", w=" << bounds.w << ", h=" << bounds.h << "\n";
2198 SYSTEM_LOG << " Corner grid coords: TL(" << tlX << "," << tlY << ") TR("
2199 << trX << "," << trY << ") BL(" << blX << "," << blY << ") BR("
2200 << brX << "," << brY << ")\n";
2201 SYSTEM_LOG << " Grid range: (" << minGridX << "," << minGridY << ") to ("
2202 << maxGridX << "," << maxGridY << ")\n";
2203 SYSTEM_LOG << " Tiles to scan: " << ((maxGridX - minGridX + 1) * (maxGridY - minGridY + 1)) << "\n";
2204 }
2205
2206 // Render collision tiles
2207 int blockedCount = 0;
2208 int tilesScanned = 0;
2209
2210 for (int y = minGridY; y <= maxGridY; ++y)
2211 {
2212 for (int x = minGridX; x <= maxGridX; ++x)
2213 {
2214 tilesScanned++;
2215 const TileProperties& tile = layerData[y][x];
2216
2217 // Only render blocked tiles
2218 if (tile.isBlocked)
2219 {
2220 blockedCount++;
2221
2222 // DEBUG: Log first 5 blocked tiles found
2223 if (renderCount < 3 && blockedCount <= 5)
2224 {
2225 SYSTEM_LOG << " -> Blocked tile #" << blockedCount << " at grid ("
2226 << x << "," << y << ")\n";
2227 SYSTEM_LOG << " Pre-calculated world coords: (" << tile.worldX << "," << tile.worldY << ")\n";
2228 }
2229
2230 // Use pre-calculated world coordinates (tile center)
2231 float worldX = tile.worldX;
2232 float worldY = tile.worldY;
2233
2234 // Transform to screen space with camera
2235 Vector worldCenter(worldX, worldY, 0.0f);
2236 Vector screenCenter = cam.WorldToScreen(worldCenter);
2237
2238 // Get tile dimensions scaled by zoom
2239 float tileWidth = collMap.GetTileWidth();
2240 float tileHeight = collMap.GetTileHeight();
2241 float screenWidth = tileWidth * cam.zoom;
2242 float screenHeight = tileHeight * cam.zoom;
2243
2244 // Render overlay based on projection type
2245 if (collMap.GetProjection() == GridProjectionType::Iso)
2246 {
2247 // Draw isometric diamond shape
2249 screenWidth, screenHeight, overlayColor);
2250 }
2251 else if (collMap.GetProjection() == GridProjectionType::HexAxial)
2252 {
2253 // Draw hexagonal shape
2255 screenWidth * 0.5f, overlayColor);
2256 }
2257 else
2258 {
2259 // Orthogonal: simple rectangle
2261 rect.x = screenCenter.x - screenWidth / 2.0f;
2262 rect.y = screenCenter.y - screenHeight / 2.0f;
2263 rect.w = screenWidth;
2264 rect.h = screenHeight;
2265
2269 }
2270 }
2271 }
2272 }
2273
2274 // DEBUG: Summary logging (first 3 calls only)
2275 if (renderCount < 3)
2276 {
2277 SYSTEM_LOG << " -> Scanned " << tilesScanned << " tiles, found "
2278 << blockedCount << " blocked tiles\n";
2279 renderCount++;
2280 }
2281}
2282
2284{
2285 if (!s.showNavigationOverlay) return;
2286
2288
2289 // One-time diagnostic logging
2290 static bool firstCall = true;
2291 static int renderCount = 0; // Tracks render calls for limited debug output
2292
2293 if (firstCall)
2294 {
2295 SYSTEM_LOG << "[GridSystem] RenderNavigationOverlay first call\n";
2296 SYSTEM_LOG << " NavigationMap dimensions: " << collMap.GetWidth()
2297 << "x" << collMap.GetHeight() << "\n"
2298 << "y" << collMap.GetHeight() << "\n";
2299 SYSTEM_LOG << " Active layer: " << (int)s.activeNavigationLayer << "\n";
2300 SYSTEM_LOG << " Overlay color: rgba("
2301 << (int)s.navigationColors[s.activeNavigationLayer].r << ","
2302 << (int)s.navigationColors[s.activeNavigationLayer].g << ","
2303 << (int)s.navigationColors[s.activeNavigationLayer].b << ","
2304 << (int)s.navigationColors[s.activeNavigationLayer].a << ")\n";
2305 SYSTEM_LOG << " Tile size: " << collMap.GetTileWidth()
2306 << "x" << collMap.GetTileHeight() << "\n";
2307 SYSTEM_LOG << " Projection type: " << (int)collMap.GetProjection() << "\n";
2308 firstCall = false;
2309 }
2310
2311 if (collMap.GetWidth() == 0 || collMap.GetHeight() == 0)
2312 {
2313 SYSTEM_LOG << "[GridSystem] ERROR: NavigationMap is EMPTY (not initialized)!\n";
2314 return;
2315 }
2316
2318 if (!renderer) return;
2319
2320 // Get the active navigation layer
2321 CollisionLayer layer = static_cast<CollisionLayer>(s.activeNavigationLayer);
2322 const std::vector<std::vector<TileProperties>>& layerData = collMap.GetLayer(layer);
2323
2324 if (layerData.empty()) return;
2325
2326 // Get visible bounds
2328
2329 // Convert world bounds to grid coordinates
2330 // For isometric grids, we need to check all 4 corners to get proper min/max bounds
2332
2333 // Check all four corners of the world visible bounds
2334 int tlX, tlY, trX, trY, blX, blY, brX, brY;
2335 collMap.WorldToGrid(bounds.x, bounds.y, tlX, tlY); // Top-left
2336 collMap.WorldToGrid(bounds.x + bounds.w, bounds.y, trX, trY); // Top-right
2337 collMap.WorldToGrid(bounds.x, bounds.y + bounds.h, blX, blY); // Bottom-left
2338 collMap.WorldToGrid(bounds.x + bounds.w, bounds.y + bounds.h, brX, brY); // Bottom-right
2339
2340 // Find min/max across all corners
2341 minGridX = std::min({tlX, trX, blX, brX});
2342 minGridY = std::min({tlY, trY, blY, brY});
2343 maxGridX = std::max({tlX, trX, blX, brX});
2344 maxGridY = std::max({tlY, trY, blY, brY});
2345
2346 // Clamp to valid grid range
2347 minGridX = std::max(0, minGridX - 1);
2348 minGridY = std::max(0, minGridY - 1);
2349 maxGridX = std::min(collMap.GetWidth() - 1, maxGridX + 1);
2350 maxGridY = std::min(collMap.GetHeight() - 1, maxGridY + 1);
2351
2352 // Get color for this layer
2353 SDL_Color overlayColor = s.navigationColors[s.activeNavigationLayer];
2354
2355 // DEBUG: Log rendering bounds and tile scanning (first 3 calls only)
2356 if (renderCount < 3)
2357 {
2358 SYSTEM_LOG << "[GridSystem] RenderNavigationOverlay call #" << renderCount << "\n";
2359 SYSTEM_LOG << " World bounds: x=" << bounds.x << ", y=" << bounds.y
2360 << ", w=" << bounds.w << ", h=" << bounds.h << "\n";
2361 SYSTEM_LOG << " Corner grid coords: TL(" << tlX << "," << tlY << ") TR("
2362 << trX << "," << trY << ") BL(" << blX << "," << blY << ") BR("
2363 << brX << "," << brY << ")\n";
2364 SYSTEM_LOG << " Grid range: (" << minGridX << "," << minGridY << ") to ("
2365 << maxGridX << "," << maxGridY << ")\n";
2366 SYSTEM_LOG << " Tiles to scan: " << ((maxGridX - minGridX + 1) * (maxGridY - minGridY + 1)) << "\n";
2367 }
2368
2369 // Render navigable tiles
2370 int navigableCount = 0;
2371 int tilesScanned = 0;
2372
2373 // DEBUG: Test rendering a known tile position (e.g., player spawn at ~67,39 based on logs)
2374 if (renderCount < 3)
2375 {
2376 // Test if tile (67, 39) is navigable (expected from problem statement)
2377 constexpr int testX = 67, testY = 39;
2378 if (testX >= 0 && testX < collMap.GetWidth() && testY >= 0 && testY < collMap.GetHeight())
2379 {
2381 SYSTEM_LOG << " DEBUG: Test tile (" << testX << "," << testY << "): "
2382 << "navigable=" << (testTile.isNavigable ? "YES" : "NO")
2383 << ", blocked=" << (testTile.isBlocked ? "YES" : "NO") << "\n";
2384
2385 // Convert to world coordinates to see if it's in viewport
2386 float testWorldX, testWorldY;
2387 collMap.GridToWorld(testX, testY, testWorldX, testWorldY);
2388 SYSTEM_LOG << " DEBUG: Test tile world coords: (" << testWorldX << "," << testWorldY << ")\n";
2389 SYSTEM_LOG << " DEBUG: Is in grid scan range? "
2390 << ((testX >= minGridX && testX <= maxGridX && testY >= minGridY && testY <= maxGridY) ? "YES" : "NO") << "\n";
2391 }
2392 }
2393
2394 for (int y = minGridY; y <= maxGridY; ++y)
2395 {
2396 for (int x = minGridX; x <= maxGridX; ++x)
2397 {
2398 tilesScanned++;
2399 const TileProperties& tile = layerData[y][x];
2400
2401 // Only render navigable tiles (and not blocked)
2402 if (tile.isNavigable && !tile.isBlocked)
2403 {
2405
2406 // DEBUG: Log first 10 navigable tiles found
2407 if (renderCount < 3 && navigableCount <= 10)
2408 {
2409 SYSTEM_LOG << " -> Navigable tile #" << navigableCount << " at grid ("
2410 << x << "," << y << ")\n";
2411 SYSTEM_LOG << " Pre-calculated world coords: (" << tile.worldX << "," << tile.worldY << ")\n";
2412 }
2413
2414 // Use pre-calculated world coordinates (tile center)
2415 float worldX = tile.worldX;
2416 float worldY = tile.worldY;
2417
2418 // Transform to screen space with camera
2419 Vector worldCenter(worldX, worldY, 0.0f);
2420 Vector screenCenter = cam.WorldToScreen(worldCenter);
2421
2422 // DEBUG: Log screen coordinates for first few tiles
2423 if (renderCount < 3 && navigableCount <= 3)
2424 {
2425 SYSTEM_LOG << " Screen coords: (" << screenCenter.x << "," << screenCenter.y << ")\n";
2426 }
2427
2428 // Get tile dimensions scaled by zoom
2429 float tileWidth = collMap.GetTileWidth();
2430 float tileHeight = collMap.GetTileHeight();
2431 float screenWidth = tileWidth * cam.zoom;
2432 float screenHeight = tileHeight * cam.zoom;
2433
2434 // Render overlay based on projection type
2435 if (collMap.GetProjection() == GridProjectionType::Iso)
2436 {
2437 // Draw isometric diamond shape
2439 screenWidth, screenHeight, overlayColor);
2440 }
2441 else if (collMap.GetProjection() == GridProjectionType::HexAxial)
2442 {
2443 // Draw hexagonal shape
2445 screenWidth * 0.5f, overlayColor);
2446 }
2447 else
2448 {
2449 // Orthogonal: simple rectangle
2451 rect.x = screenCenter.x - screenWidth / 2.0f;
2452 rect.y = screenCenter.y - screenHeight / 2.0f;
2453 rect.w = screenWidth;
2454 rect.h = screenHeight;
2455
2459 }
2460 }
2461 }
2462 }
2463
2464 // DEBUG: Summary logging (first 3 calls only)
2465 if (renderCount < 3)
2466 {
2467 SYSTEM_LOG << " -> Scanned " << tilesScanned << " tiles, found "
2468 << navigableCount << " navigable tiles\n";
2469 renderCount++;
2470 }
2471}
2472
2473//-------------------------------------------------------------
2474// UIRenderingSystem: Pass 2 rendering for UI/HUD/Menu (always on top)
2475//-------------------------------------------------------------
2484
2486{
2488 if (!renderer) return;
2489
2490 const auto& players = ViewportManager::Get().GetPlayers();
2491
2492 if (!players.empty())
2493 {
2494 // Multi-player: render UI for each player's viewport
2495 for (short playerID : players)
2496 {
2498 if (!cam.isActive) continue;
2499
2500 // Set viewport
2501 SDL_Rect viewportRect = {
2502 (int)cam.viewport.x, (int)cam.viewport.y,
2503 (int)cam.viewport.w, (int)cam.viewport.h
2504 };
2505 SDL_SetRenderViewport(renderer, &viewportRect);
2506 SDL_SetRenderClipRect(renderer, &viewportRect);
2507
2508 // Render UI layers
2509 RenderHUD(cam);
2512 }
2513
2514 // Reset viewport
2517 }
2518 else
2519 {
2520 // Single camera fallback
2522 if (cam.isActive)
2523 {
2524 RenderHUD(cam);
2527 }
2528 }
2529}
2530
2532{
2533 // Render HUD entities (health bars, score, etc.)
2534 for (EntityID entity : m_entities)
2535 {
2536 if (!World::Get().HasComponent<Identity_data>(entity))
2537 continue;
2538
2540
2541 // Only render UI elements
2542 if (id.type != "UIElement")
2543 continue;
2544
2545 // Skip menu-specific elements
2546 if (id.tag == "MenuElement")
2547 continue;
2548
2552
2553 if (!visual.visible)
2554 continue;
2555
2556 // Render in screen space (no camera transform)
2558 pos.position.x, // Already in screen coordinates
2559 pos.position.y,
2560 bbox.boundingBox.w,
2561 bbox.boundingBox.h
2562 };
2563
2564 if (visual.sprite)
2565 {
2566 SDL_SetTextureColorMod(visual.sprite, visual.color.r, visual.color.g, visual.color.b);
2568 }
2569 }
2570}
2571
2573{
2574 // Only render if menu is active
2575 if (!GameMenu::Get().IsActive())
2576 return;
2577
2579
2580 // Semi-transparent background overlay
2581 SDL_FRect overlay = {
2582 0, 0,
2585 };
2586 SDL_SetRenderDrawColor(renderer, 0, 0, 0, 128); // Black, 50% alpha
2588
2589 // Menu panel (centered)
2590 float panelWidth = 400;
2591 float panelHeight = 300;
2592 float panelX = (GameEngine::screenWidth - panelWidth) / 2.0f;
2593 float panelY = (GameEngine::screenHeight - panelHeight) / 2.0f;
2594
2596 SDL_SetRenderDrawColor(renderer, 40, 40, 40, 255); // Dark gray
2598
2599 // Panel border
2600 SDL_SetRenderDrawColor(renderer, 200, 200, 200, 255); // Light gray
2602
2603 // TODO: Render menu text with SDL_ttf or ImGui
2604 // For now, render placeholder rectangles for buttons
2605
2606 float buttonWidth = 300;
2607 float buttonHeight = 50;
2608 float buttonX = panelX + (panelWidth - buttonWidth) / 2.0f;
2609 float buttonY = panelY + 80;
2610 float buttonSpacing = 70;
2611
2613
2614 // Resume button
2616 SDL_SetRenderDrawColor(renderer, 80, 120, 180, 255); // Blue
2619 {
2620 // Selected - draw thick yellow border
2621 SDL_SetRenderDrawColor(renderer, 255, 255, 0, 255);
2623 SDL_FRect innerRect = { buttonX + 2, buttonY + 2, buttonWidth - 4, buttonHeight - 4 };
2625 }
2626 else
2627 {
2628 SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255);
2630 }
2631
2632 // Restart button
2634 SDL_SetRenderDrawColor(renderer, 180, 120, 80, 255); // Orange
2637 {
2638 // Selected - draw thick yellow border
2639 SDL_SetRenderDrawColor(renderer, 255, 255, 0, 255);
2643 }
2644 else
2645 {
2646 SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255);
2648 }
2649
2650 // Quit button
2652 SDL_SetRenderDrawColor(renderer, 180, 80, 80, 255); // Red
2655 {
2656 // Selected - draw thick yellow border
2657 SDL_SetRenderDrawColor(renderer, 255, 255, 0, 255);
2661 }
2662 else
2663 {
2664 SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255);
2666 }
2667
2668 // TODO: Add text rendering with SDL_ttf
2669 // For now, logs indicate menu is active
2670 static bool loggedOnce = false;
2671 if (!loggedOnce)
2672 {
2673 SYSTEM_LOG << "UIRenderingSystem: In-game menu rendered (Resume/Restart/Quit)\n";
2674 loggedOnce = true;
2675 }
2676}
2677
2679{
2680 // Render debug information (FPS, entity count, etc.)
2681 // This is always on top of everything
2682
2683 // TODO: Use SDL_ttf for text rendering
2684 // For now, just a placeholder
2685}
2686//-------------------------------------------------------------
2691{
2693 if (!renderer) return;
2694 // Iterate over all entities with Position and VisualSprite
2696 {
2697 try
2698 {
2701 // Render sprite at position (no camera transform)
2703 pos.position.x - visual.hotSpot.x,
2704 pos.position.y - visual.hotSpot.y,
2705 visual.srcRect.w,
2706 visual.srcRect.h
2707 };
2708 // Apply color modulation
2709 SDL_SetTextureColorMod(visual.sprite, visual.color.r, visual.color.g, visual.color.b);
2710 // Render sprite
2711 SDL_RenderTexture(renderer, visual.sprite, nullptr, &destRect);
2712 }
2713 catch (const std::exception& e)
2714 {
2715 std::cerr << "RenderingEditorSystem Error for Entity " << entity << ": " << e.what() << "\n";
2716 }
2717 }
2718}
@ FollowPath
Follow the path (check progression)
CollisionLayer
Core ECS component definitions.
ComponentTypeID GetComponentTypeID_Static()
Definition ECS_Entity.h:56
std::uint64_t EntityID
Definition ECS_Entity.h:21
SDL_FlipMode GetSDLFlip(bool flipH, bool flipV, bool)
void ExtractFlipFlags(uint32_t gid, bool &flipH, bool &flipV, bool &flipD)
void RenderSingleEntity(const CameraTransform &cam, EntityID entity)
void RenderMultiLayerForCamera(const CameraTransform &cam)
void RenderTileImmediate(SDL_Texture *texture, const SDL_Rect &srcRect, int worldX, int worldY, uint32_t gid, int tileoffsetX, int tileoffsetY, const CameraTransform &cam, const std::string &orientation, int tileWidth, int tileHeight)
void RenderEntitiesForCamera(const CameraTransform &cam)
float CalculateTileDepth(const std::string &orientation, int worldX, int worldY, int layerZOrder, int tileWidth, int tileHeight)
void GetVisibleTileRange(const CameraTransform &cam, const std::string &orientation, int tileWidth, int tileHeight, int &minX, int &minY, int &maxX, int &maxY)
float CalculateEntityDepth(const std::string &orientation, const Vector &position, int tileWidth, int tileHeight)
void RenderMultiLayerForCamera(const CameraTransform &cam)
CameraTransform GetActiveCameraTransform(short playerID)
Core game engine class.
InputContext
static SDL_Renderer * renderer
World and ECS Manager for Olympe Engine.
virtual void Process() override
virtual void Process() override
static CollisionMap & Get()
virtual void Process() override
virtual void Process() override
std::set< EntityID > m_entities
Definition ECS_Systems.h:42
ComponentSignature requiredSignature
Definition ECS_Systems.h:39
static EventQueue & Get()
Definition EventQueue.h:34
static int screenWidth
Screen width in pixels.
Definition GameEngine.h:123
static float fDt
Delta time between frames in seconds.
Definition GameEngine.h:120
static int screenHeight
Screen height in pixels.
Definition GameEngine.h:126
static SDL_Renderer * renderer
Main SDL renderer.
Definition GameEngine.h:129
virtual void Process() override
void Activate()
Definition GameMenu.cpp:22
void SelectPrevious()
Definition GameMenu.cpp:44
int GetSelectedOption() const
Definition GameMenu.h:51
static GameMenu & Get()
Definition GameMenu.h:38
void SelectNext()
Definition GameMenu.cpp:49
void Deactivate()
Definition GameMenu.cpp:33
void ValidateSelection()
Definition GameMenu.cpp:54
@ Restart
Definition GameMenu.h:17
void RenderIso(const CameraTransform &cam, const GridSettings_data &s)
SDL_FRect GetWorldVisibleBounds(const CameraTransform &cam)
void RenderHex(const CameraTransform &cam, const GridSettings_data &s)
void DrawHexagonOverlay(float centerX, float centerY, float radius, const SDL_Color &color)
void RenderOrtho(const CameraTransform &cam, const GridSettings_data &s)
void DrawFilledRectWorld(const CameraTransform &cam, const Vector &worldPos, float width, float height, const SDL_Color &c)
void RenderCollisionOverlay(const CameraTransform &cam, const GridSettings_data &s)
const GridSettings_data * FindSettings() const
void RenderNavigationOverlay(const CameraTransform &cam, const GridSettings_data &s)
void DrawIsometricTileOverlay(float centerX, float centerY, float width, float height, const SDL_Color &color)
void DrawLineWorld(const CameraTransform &cam, const Vector &aWorld, const Vector &bWorld, const SDL_Color &c)
void RenderForCamera(const CameraTransform &cam)
virtual void Render() override
InputDeviceSlot * GetDeviceForPlayer(short playerID)
virtual void Process() override
virtual void Process() override
virtual void Process() override
short GetFirstDisconnectedPlayerID() const
InputDeviceManager & GetDeviceManager()
static InputsManager & Get()
InputContext GetActiveContext() const
short GetDisconnectedPlayersCount() const
const std::vector< EntityID > & GetInputEntities() const
bool RemoveDisconnectedPlayer(short playerID)
static JoystickManager & Get()
static KeyboardManager & Get()
virtual void Process() override
static NavigationMap & Get()
bool NeedsRepath(EntityID entity)
void FollowPath(EntityID entity, float deltaTime)
virtual void Process() override
void RequestPath(EntityID entity, const Vector &targetPos)
static ParallaxLayerManager & Get()
virtual void Process() override
virtual void Process() override
void SetActiveCamera(const CameraTransform &cam)
Set the active camera for the current rendering pass Call this at the start of rendering for each pla...
void ClearActiveCamera()
Clear the active camera (e.g., at end of rendering pass)
static RenderContext & Get()
virtual void Render() override
virtual void Render() override
virtual void Process() override
virtual void Process() override
void RenderInGameMenu(const CameraTransform &cam)
virtual void Render() override
void RenderHUD(const CameraTransform &cam)
void RenderDebugOverlay(const CameraTransform &cam)
float z
Definition vector.h:27
float y
Definition vector.h:27
Vector & Normalize()
Definition vector.h:72
float x
Definition vector.h:27
float Length() const
Definition vector.h:73
std::vector< EntityID > m_playersEntity
Definition VideoGame.h:91
static VideoGame & Get()
Definition VideoGame.h:34
bool LoadGame(int slot=0)
Definition VideoGame.h:64
EntityID AddPlayerEntity(string _playerPrefabName="Player")
Definition VideoGame.cpp:49
bool RemovePlayerEntity(const EntityID eid)
static ViewportManager & Get()
const std::vector< short > & GetPlayers() const
int GetTileHeight() const
Definition World.h:738
void ToggleCollisionOverlay()
Toggle collision overlay visibility.
Definition World.h:564
const std::vector< TileChunk > & GetTileChunks() const
Definition World.h:730
static World & Get()
Get singleton instance (short form)
Definition World.h:232
bool HasComponent(EntityID entity) const
Definition World.h:451
T * GetSystem()
Definition World.h:244
T & GetComponent(EntityID entity)
Definition World.h:438
std::unordered_map< EntityID, ComponentSignature > m_entitySignatures
Definition World.h:636
void ToggleNavigationOverlay()
Toggle navigation overlay visibility.
Definition World.h:582
const std::string & GetMapOrientation() const
Definition World.h:736
int GetTileWidth() const
Definition World.h:737
TilesetManager & GetTilesetManager()
Definition World.h:733
void Draw_FilledCircle(int cx, int cy, int radius)
Definition drawing.cpp:135
void Draw_Text(const std::string &text, const SDL_FRect *rect, SDL_Color textcolor, SDL_Color backgroundcolor)
Definition drawing.cpp:345
void Draw_Rectangle(const SDL_FRect *rect, SDL_Color color)
Definition drawing.cpp:309
Bounding box component for collision detection.
SDL_FRect boundingBox
Collision rectangle.
static constexpr int MAX_BUTTONS
Identity component for entity identification.
Position component for spatial location.
Information about a loaded tileset.
Definition World.h:106
int tileoffsetX
Global X offset for all tiles.
Definition World.h:128
int tileoffsetY
Global Y offset for all tiles.
Definition World.h:129
@ Olympe_EventType_Joystick_ButtonUp
@ Olympe_EventType_Joystick_ButtonDown
@ Olympe_EventType_Joystick_AxisMotion
@ Olympe_EventType_Keyboard_Connected
@ Olympe_EventType_Keyboard_Disconnected
@ Olympe_EventType_Keyboard_KeyDown
@ Olympe_EventType_Menu_Enter
@ Olympe_EventType_Game_LoadState
@ Olympe_EventType_Joystick_Connected
@ Olympe_EventType_Joystick_Disconnected
@ Olympe_EventType_Keyboard_KeyUp
@ Olympe_EventType_Menu_Validate
@ Olympe_EventType_Menu_Exit
constexpr double k_PI
#define SYSTEM_LOG