R-Type
Distributed multiplayer game engine in C++
Loading...
Searching...
No Matches
GameLogic.cpp
Go to the documentation of this file.
1/*
2** EPITECH PROJECT, 2025
3** rtype
4** File description:
5** GameLogic.cpp - Server-side game logic implementation
6*/
7
9#include <algorithm>
10#include <atomic>
11#include <cmath>
12#include <thread>
51
52namespace server {
53
54 GameLogic::GameLogic(std::shared_ptr<ecs::wrapper::ECSWorld> world,
55 std::shared_ptr<ThreadPool> threadPool, std::shared_ptr<EventBus> eventBus)
56 : _stateManager(std::make_shared<GameStateManager>()),
57 _threadPool(threadPool),
58 _eventBus(eventBus),
59 _gameActive(false) {
60 // Create ECSWorld if not provided
61 if (!world) {
62 _world = std::make_shared<ecs::wrapper::ECSWorld>();
63 } else {
64 _world = world;
65 }
66
67 if (_threadPool) {
68 LOG_INFO("GameLogic: ThreadPool enabled for parallel execution (", _threadPool->size(),
69 " workers)");
70 } else {
71 LOG_DEBUG("GameLogic: Running in single-threaded mode");
72 }
73
74 // Initialize Lua scripting engine
75 _luaEngine = std::make_unique<scripting::LuaEngine>("server/Scripting/scripts/");
76 _luaEngine->setWorld(_world.get());
77 LOG_INFO("GameLogic: Lua scripting engine initialized");
78 if (_eventBus) {
79 LOG_DEBUG("GameLogic: EventBus enabled for event publishing");
80 }
81
82 LOG_DEBUG("GameLogic: GameStateManager initialized");
83 }
84
85 GameLogic::~GameLogic() = default;
86
88 if (_initialized.exchange(true)) {
89 return true; // Already initialized
90 }
91
92 try {
93 LOG_INFO("Initializing game logic...");
94
95 // Create and register all systems with ECSWorld in execution order
96 _world->createSystem<ecs::MovementSystem>("MovementSystem");
97 _world->createSystem<ecs::OrbitalSystem>("OrbitalSystem");
98 _world->createSystem<ecs::AnimationSystem>("AnimationSystem");
99 _world->createSystem<ecs::MapSystem>("MapSystem");
100 _world->createSystem<ecs::CollisionSystem>("CollisionSystem");
101 _world->createSystem<ecs::BuffSystem>("BuffSystem");
102 _world->createSystem<ecs::HealthSystem>("HealthSystem");
103
104 // Register Lua system BEFORE SpawnSystem so wave_manager can queue spawns
105 auto luaSystem = std::make_unique<scripting::LuaSystemAdapter>(_luaEngine.get(), _world.get());
106 _world->registerSystem("Lua", std::move(luaSystem));
107 LOG_INFO("✓ Lua system registered (executes before spawn processing)");
108
109 // SpawnSystem processes spawn requests queued by Lua scripts
110 _world->createSystem<ecs::SpawnSystem>("SpawnSystem");
111
112 _world->createSystem<ecs::AISystem>("AISystem");
113 _world->createSystem<ecs::BoundarySystem>("BoundarySystem");
114 _world->createSystem<ecs::WeaponSystem>("WeaponSystem");
115
116 LOG_INFO("✓ All systems registered (", _world->getSystemCount(), " systems)");
117 if (_threadPool) {
118 LOG_INFO("✓ Systems will execute in parallel mode (4 groups)");
119 } else {
120 LOG_INFO("✓ Systems will execute sequentially");
121 }
122
123 // Initialize game state manager with states
124 _stateManager->registerState(0, std::make_shared<LobbyState>());
125 _stateManager->registerState(1, std::make_shared<InGameState>());
126 _stateManager->registerState(2, std::make_shared<GameOverState>());
127
128 // Connect EventBus to GameStateManager so it can publish events
129 if (_eventBus) {
130 _stateManager->setEventBus(_eventBus);
131 }
132
133 // Start in InGame state (skip lobby for dev)
134
135 _stateManager->changeState(1);
136
137 LOG_INFO("✓ GameStateManager initialized with 3 states");
138
139 _gameActive = true;
140
141 LOG_INFO("✓ Initialization complete!");
142 return true;
143 } catch (const std::exception &e) {
144 LOG_ERROR("Initialization failed: ", e.what());
145 _initialized.store(false);
146 return false;
147 }
148 }
149
150 void GameLogic::update(float deltaTime, uint32_t lcurrentTick) {
151 (void)lcurrentTick; // Unused for now
152 if (!_gameActive) {
153 return;
154 }
155
156 // 1. Process accumulated player input
158
159 if (_stateManager) {
160 _stateManager->update(deltaTime);
161 }
162
163 _executeSystems(deltaTime);
164
165 _checkGameOverCondition(); // Check BEFORE cleaning up dead entities
166
168 }
169
170 uint32_t GameLogic::spawnPlayer(uint32_t playerId, const std::string &playerName) {
171 try {
172 LOG_INFO("Spawning player: ", playerName, " (ID: ", playerId, ")");
173
174 // Check if player already exists
175 if (_playerMap.contains(playerId)) {
176 LOG_ERROR("Player ", playerId, " already exists!");
177 return 0;
178 }
179
180 // Create new player entity using the wrapper API
182 ecs::wrapper::Entity playerEntity =
183 _world->createEntity()
186 .with(
188 .with(ecs::Player(0, 3, playerId)) // score=0, lives=3
189 .with(ecs::Collider(50.0f, 50.0f, 0.0f, 0.0f, 1, 0xFFFFFFFF, false))
192 .with(ecs::Sprite("PlayerShips.gif", {1, 69, 33, 14}, 3.0f, 0.0f, false, false, 0))
193 .with(playerAnimations)
194 .with(ecs::Animation("player_idle"));
195 ecs::Address entityAddress = playerEntity.getAddress();
196
197 // Register player (protected by mutex for thread safety)
198 {
199 std::scoped_lock lock(_playerMutex);
200 _playerMap[playerId] = entityAddress;
201 _hadPlayers = true; // Mark that at least one player has joined
202 }
203
204 LOG_INFO("✓ Player spawned at (", _gameRules.getPlayerSpawnX(), ", ",
205 _gameRules.getPlayerSpawnY(), ") with entity ID: ", entityAddress);
206
207 // Spawn orbital drone for the player
208 uint32_t droneId = ecs::PrefabFactory::createOrbitalModule(_world->getRegistry(), entityAddress,
209 90.0f, // radius
210 1.2f, // speed (rad/s)
211 0.0f, // start angle
212 15, // damage
213 50 // health
214 );
215
216 if (droneId != 0) {
217 LOG_INFO("✓ Orbital drone spawned for player (ID: ", droneId, ")");
218 }
219
220 return entityAddress;
221 } catch (const std::exception &e) {
222 LOG_ERROR("Failed to spawn player: ", e.what());
223 return 0;
224 }
225 }
226
227 void GameLogic::despawnPlayer(uint32_t playerId) {
228 try {
229 ecs::Address playerEntity = 0;
230
231 // Find and remove player from map (protected by mutex)
232 {
233 std::scoped_lock lock(_playerMutex);
234 auto it = _playerMap.find(playerId);
235 if (it == _playerMap.end()) {
236 LOG_WARNING("Player ", playerId, " not found");
237 return;
238 }
239 playerEntity = it->second;
240 _playerMap.erase(it);
241 }
242
243 LOG_INFO("Despawning player ", playerId, " (entity: ", playerEntity, ")");
244
245 // Mark entity for destruction with proper client notification
246 _world->getEntity(playerEntity)
248 LOG_INFO("✓ Player marked for destruction (will be processed in next tick)");
249 } catch (const std::exception &e) {
250 LOG_ERROR("Failed to despawn player: ", e.what());
251 }
252 }
253
254 void GameLogic::processPlayerInput(uint32_t playerId, int inputX, int inputY, bool isShooting,
255 uint32_t sequenceId) {
256 std::scoped_lock lock(_inputMutex);
257
258 // Filter duplicates (redundant inputs)
259 // If we have already processed this sequence ID or a newer one, ignore it.
260 auto it = _lastReceivedSequenceId.find(playerId);
261 if (it != _lastReceivedSequenceId.end()) {
262 if (sequenceId <= it->second) {
263 return; // Already processed
264 }
265 }
266 _lastReceivedSequenceId[playerId] = sequenceId;
267
268 // Add to queue
269 _pendingInput[playerId].push_back({playerId, inputX, inputY, isShooting, sequenceId});
270 }
271
273 std::scoped_lock lock(_inputMutex);
274
275 for (auto &[playerId, inputs] : _pendingInput) {
276 if (inputs.empty()) {
277 continue;
278 }
279
280 // SMART JITTER BUFFER
281 // Target buffer size: 2-3 frames (~33-50ms buffer)
282 // Strategy:
283 // - If buffer > 5: Speed up (process 2 inputs) -> Catch up lag
284 // - If buffer < 1: Slow down (process 0 inputs) -> Build up buffer (handled by empty check)
285 // - Normal: Process 1 input
286
287 size_t inputsToProcess = 1;
288 if (inputs.size() > 5) {
289 inputsToProcess = 2; // Catch up
290 }
291
292 for (size_t i = 0; i < inputsToProcess && !inputs.empty(); ++i) {
293 const auto &input = inputs.front();
294 _applyPlayerInput(playerId, input);
295 inputs.pop_front();
296 }
297 }
298 }
299
300 void GameLogic::_applyPlayerInput(uint32_t playerId, const PlayerInput &input) {
301 auto it = _playerMap.find(playerId);
302 if (it == _playerMap.end()) {
303 return;
304 }
305
306 ecs::Address playerEntity = it->second;
307 try {
308 // Get entity wrapper and check if it still exists
309 ecs::wrapper::Entity entity = _world->getEntity(playerEntity);
310
311 // Check if entity has required components (entity might be destroyed)
312 if (!entity.has<ecs::Velocity>() || !entity.has<ecs::Transform>()) {
313 LOG_WARNING("Player ", playerId,
314 " entity missing Velocity or Transform component (entity destroyed?)");
315 return;
316 }
317
318 ecs::Velocity &vel = entity.get<ecs::Velocity>();
319 ecs::Transform &transform = entity.get<ecs::Transform>();
320
321 // Update animation based on movement
322 if (entity.has<ecs::Animation>()) {
323 ecs::Animation &anim = entity.get<ecs::Animation>();
324
325 // If no input (0, 0), stop the player and play idle animation
326 if (input.inputX == 0 && input.inputY == 0) {
327 vel.setDirection(0.0f, 0.0f);
328
329 // Switch to idle animation if currently moving
330 if (anim.getCurrentClipName() != "player_idle") {
331 anim.setCurrentClipName("player_idle");
332 anim.setCurrentFrameIndex(0);
333 anim.setTimer(0.0f);
334 }
335 } else {
336 // Normalize diagonal movement
337 float dirX = static_cast<float>(input.inputX);
338 float dirY = static_cast<float>(input.inputY);
339
340 // Normalize if diagonal
341 if (dirX != 0.0f && dirY != 0.0f) {
342 float length = std::sqrt(dirX * dirX + dirY * dirY);
343 dirX /= length;
344 dirY /= length;
345 }
346
347 vel.setDirection(dirX, dirY);
348
349 // Switch to movement animation if currently idle
350 if (anim.getCurrentClipName() != "player_movement") {
351 anim.setCurrentClipName("player_movement");
352 anim.setCurrentFrameIndex(0);
353 anim.setTimer(0.0f);
354 }
355 }
356 } else {
357 // Fallback if no Animation component (shouldn't happen for players)
358 // Normalize diagonal movement
359 float dirX = static_cast<float>(input.inputX);
360 float dirY = static_cast<float>(input.inputY);
361
362 // Normalize if diagonal
363 if (dirX != 0.0f && dirY != 0.0f) {
364 float length = std::sqrt(dirX * dirX + dirY * dirY);
365 dirX /= length;
366 dirY /= length;
367 }
368
369 // CRITICAL: Apply movement directly here to match client-side prediction
370 // The client predicts movement per-input at FIXED_TIMESTEP rate
371 // We must do the same to stay synchronized
372 float speed = vel.getSpeed();
373 auto pos = transform.getPosition();
374 float newX = pos.x + dirX * speed * FIXED_TIMESTEP;
375 float newY = pos.y + dirY * speed * FIXED_TIMESTEP;
376 transform.setPosition(newX, newY);
377
378 // Keep velocity direction at (0,0) so MovementSystem doesn't apply additional movement
379 // Player movement is entirely controlled by input processing, not by MovementSystem
380 vel.setDirection(0.0f, 0.0f);
381 }
382
383 // Handle shooting
384 auto &weapon = entity.get<ecs::Weapon>();
385
386 if (input.isShooting) {
387 if (entity.has<ecs::Weapon>()) {
388 weapon.setShouldShoot(true);
389 }
390 } else {
391 if (entity.has<ecs::Weapon>()) {
392 weapon.setShouldShoot(false);
393 }
394 }
395
396 _lastAppliedSequenceId[playerId] = input.sequenceId;
397 } catch (const std::exception &e) {
398 LOG_ERROR("Error applying input for player ", playerId, ": ", e.what());
399 }
400 }
401
402 void GameLogic::_executeSystems(float deltaTime) {
403 if (!_threadPool) {
404 // Sequential execution (no ThreadPool)
405 _world->update(deltaTime);
406 return;
407 }
408
409 // Parallel execution with ThreadPool
410 // Group systems by dependency - systems in the same group can run in parallel
411
412 // Group 1: Independent systems (can run in parallel)
413 std::vector<std::string> group1 = {"MovementSystem", "OrbitalSystem"};
414
415 // Group 2: Animation must run after Movement to update sprite frames
416 std::vector<std::string> group2 = {"AnimationSystem"};
417
418 // Group 3: Depends on positions (after Movement and Animation)
419 std::vector<std::string> group3 = {"CollisionSystem", "BoundarySystem"};
420
421 // Group 4: Depends on collision results
422 std::vector<std::string> group4 = {"HealthSystem", "WeaponSystem"};
423
424 // Group 5: Lua scripts (wave_manager) must run before spawning
425 std::vector<std::string> group5 = {"Lua"};
426
427 // Group 6: Process spawn requests from Lua and AI
428 std::vector<std::string> group6 = {"AISystem", "SpawnSystem"};
429
430 // Execute each group in order, but parallelize within groups
431 auto executeGroup = [this, deltaTime](const std::vector<std::string> &group) {
432 // Use shared_ptr to safely share the atomic counter between threads
433 auto completed = std::make_shared<std::atomic<int>>(0);
434 const int total = static_cast<int>(group.size());
435
436 for (const auto &systemName : group) {
437 // Capture completed by value (shared_ptr copy is thread-safe)
438 _threadPool->enqueue([this, systemName, deltaTime, completed]() {
439 _world->updateSystem(systemName, deltaTime);
440 (*completed)++;
441 });
442 }
443
444 // Wait for all tasks in this group to complete
445 while (*completed < total) {
446 std::this_thread::yield();
447 }
448 };
449
450 // Execute groups sequentially, systems within groups in parallel
451 executeGroup(group1);
452 executeGroup(group2);
453 executeGroup(group3);
454 executeGroup(group4);
455 executeGroup(group5); // Lua scripts
456 executeGroup(group6); // Spawning (processes Lua's spawn requests)
457 }
458
460 // Query all entities marked for destruction
461 auto entitiesToDestroy = _world->query<ecs::PendingDestroy>();
462
463 for (auto &entity : entitiesToDestroy) {
464 ecs::Address entityAddress = entity.getAddress();
465
466 // Remove from player map if it's a player entity
467 if (entity.has<ecs::Player>()) {
468 std::scoped_lock lock(_playerMutex);
469 for (auto it = _playerMap.begin(); it != _playerMap.end();) {
470 if (it->second == entityAddress) {
471 LOG_INFO("Removing player ", it->first, " from player map (entity destroyed)");
472 it = _playerMap.erase(it);
473 } else {
474 ++it;
475 }
476 }
477 }
478
479 // Actually destroy the entity
480 _world->destroyEntity(entity);
481 }
482 }
483
485 // Only check in InGame state
486 if (_stateManager->getCurrentState() != 1) {
487 return;
488 }
489
490 // Don't check if no players have ever joined
491 if (!_hadPlayers) {
492 return;
493 }
494
495 bool allPlayersDead = true;
496 bool anyEnemiesRemain = false;
497
498 {
499 std::scoped_lock lock(_playerMutex);
500
501 // If player map is empty and we had players, they're all dead
502 if (!_playerMap.empty()) {
503 // Check if all players are dead
504 for (const auto &[playerId, entityAddress] : _playerMap) {
505 try {
506 ecs::wrapper::Entity entity = _world->getEntity(entityAddress);
507 if (entity.has<ecs::Health>() && entity.get<ecs::Health>().getCurrentHealth() > 0) {
508 allPlayersDead = false;
509 break;
510 }
511 } catch (...) {
512 // Entity doesn't exist, consider dead
513 }
514 }
515 }
516 }
517
518 // Check if there are any enemies left alive
519 auto enemies = _world->query<ecs::Enemy, ecs::Health>();
520 for (auto &enemyEntity : enemies) {
521 try {
522 if (enemyEntity.has<ecs::Health>()) {
523 const ecs::Health &health = enemyEntity.get<ecs::Health>();
524 if (health.getCurrentHealth() > 0) {
525 anyEnemiesRemain = true;
526 break;
527 }
528 }
529 } catch (...) {
530 // Skip invalid entities
531 }
532 }
533
534 // Check if any spawner is still active (has more waves to spawn)
535 bool spawnerStillActive = false;
536 auto spawners = _world->query<ecs::Spawner>();
537 for (auto &spawnerEntity : spawners) {
538 try {
539 if (spawnerEntity.has<ecs::Spawner>()) {
540 const ecs::Spawner &spawner = spawnerEntity.get<ecs::Spawner>();
541 if (spawner.isActive) {
542 spawnerStillActive = true;
543 break;
544 }
545 }
546 } catch (...) {
547 // Skip invalid entities
548 }
549 }
550
551 // Victory: All enemies defeated, no more waves to spawn, and at least one player alive
552 if (!anyEnemiesRemain && !spawnerStillActive && !allPlayersDead) {
553 LOG_INFO("Victory! All enemies defeated!");
554 _gameActive = false; // Stop game loop
555 _stateManager->changeState(2);
556 if (_eventBus) {
557 _eventBus->publish(GameEndedEvent("Victory"));
558 }
559 return;
560 }
561
562 // Defeat: All players dead
563 if (allPlayersDead) {
564 LOG_INFO("Defeat! All players eliminated.");
565 _gameActive = false; // Stop game loop
566 _stateManager->changeState(2);
567 if (_eventBus) {
568 _eventBus->publish(GameEndedEvent("Defeat"));
569 }
570 }
571 }
572
574 LOG_INFO("Resetting game...");
575
576 _gameActive = true;
577 _hadPlayers = false; // Reset player tracking
578 _playerMap.clear();
579 _pendingInput.clear();
582
583 // Clear all entities from the world
584 _world->clear();
585 LOG_INFO("✓ Game reset");
586 }
587
589 LOG_INFO("Game started! Loading map...");
590
591 // Charger la map maintenant que le jeu démarre
592 loadMap("assets/maps/level_1.json");
593
594 LOG_INFO("✓ Map loaded and spawning will begin!");
595 }
596
597 bool GameLogic::loadMap(const std::string &mapFilePath) {
598 LOG_INFO("Loading map from: ", mapFilePath);
599
600 // Load map data from JSON file
601 auto mapDataOpt = map::MapLoader::loadFromFile(mapFilePath);
602 if (!mapDataOpt.has_value()) {
603 LOG_ERROR("Failed to load map from: ", mapFilePath);
604 return false;
605 }
606
607 ecs::MapData mapData = mapDataOpt.value();
608 LOG_INFO("✓ Map loaded: '", mapData.getName(), "' (", mapData.getMapId(), ")");
609
610 // Create or update map entity
611 // Check if there's already an active map entity
612 auto mapEntities = _world->query<ecs::MapData>();
613
614 if (!mapEntities.empty()) {
615 // Update existing map entity
616 auto mapEntity = mapEntities[0];
617 mapEntity.with(mapData);
618 LOG_INFO("Updated existing map entity");
619 } else {
620 // Create new map entity
621 _world->createEntity().with(mapData);
622 LOG_INFO("Created new map entity");
623 }
624
625 // Load spawn script if specified
626 const std::string &spawnScript = mapData.getSpawnScript();
627 if (!spawnScript.empty() && _luaEngine) {
628 LOG_INFO("Loading spawn script: ", spawnScript);
629
630 // Create spawner entity with the map's spawn script
631 _world->createEntity().with(ecs::Spawner()).with(ecs::LuaScript(spawnScript));
632
633 LOG_INFO("✓ Spawner created with script: ", spawnScript);
634 }
635
636 LOG_INFO("✓ Map '", mapData.getName(), "' is now active!");
637 LOG_INFO(" - Scroll speed: ", mapData.getScrollSpeed(), " px/s");
638 if (mapData.getDuration() > 0.0f) {
639 LOG_INFO(" - Duration: ", mapData.getDuration(), " seconds");
640 } else {
641 LOG_INFO(" - Duration: Infinite");
642 }
643
644 return true;
645 }
646
647} // namespace server
#define LOG_INFO(...)
Definition Logger.hpp:181
#define LOG_DEBUG(...)
Definition Logger.hpp:180
#define LOG_ERROR(...)
Definition Logger.hpp:183
#define LOG_WARNING(...)
Definition Logger.hpp:182
System managing enemy AI behavior and attack patterns.
Definition AISystem.hpp:23
Component containing all available animations for an entity.
System managing sprite animation playback.
Component managing current animation playback state.
Definition Animation.hpp:21
void setCurrentFrameIndex(int frameIndex)
Set the current frame index.
Definition Animation.hpp:84
std::string getCurrentClipName() const
Get the current animation clip name.
Definition Animation.hpp:42
void setCurrentClipName(const std::string &clipName)
Set the current animation clip name.
Definition Animation.hpp:72
void setTimer(float timer)
Set the playback timer.
Definition Animation.hpp:78
System managing entity boundaries and screen limits.
System managing buff timers and applying buff effects.
Component for collision detection and physics interactions.
Definition Collider.hpp:21
System handling collision detection between entities.
Component identifying an entity as an enemy with AI behavior.
Definition Enemy.hpp:20
System managing entity health, invincibility and death.
Component representing entity health and invincibility.
Definition Health.hpp:20
int getCurrentHealth() const
Get current health points.
Definition Health.hpp:44
Component that holds the path to a Lua script for entity behavior.
Definition LuaScript.hpp:21
Component storing information about the current map/level.
Definition MapData.hpp:24
const std::string & getMapId() const
Get the map unique identifier.
Definition MapData.hpp:100
float getScrollSpeed() const
Get the scroll speed.
Definition MapData.hpp:112
const std::string & getSpawnScript() const
Get the spawn script path.
Definition MapData.hpp:136
float getDuration() const
Get the map duration.
Definition MapData.hpp:142
const std::string & getName() const
Get the map display name.
Definition MapData.hpp:106
System managing map state, scrolling, and transitions.
Definition MapSystem.hpp:28
System handling entity movement based on velocity.
System managing orbital module movement around parent entities.
Marker component indicating entity should be destroyed.
Component identifying an entity as a player with game statistics.
Definition Player.hpp:20
static ecs::Address createOrbitalModule(ecs::Registry &registry, uint32_t parentEntityId, float orbitRadius=50.0f, float orbitSpeed=2.0f, float startAngle=0.0f, int damage=10, int moduleHealth=50)
Create an orbital module (drone) entity.
System managing entity spawning and wave generation.
Component that holds spawn requests to be processed by SpawnSystem.
Definition Spawner.hpp:48
Component representing a visual sprite from a texture.
Definition Sprite.hpp:32
Component representing position, rotation and scale in 2D space.
Definition Transform.hpp:20
Component representing movement direction and speed.
Definition Velocity.hpp:20
float getSpeed() const
Get the movement speed.
Definition Velocity.hpp:51
void setDirection(float dirX, float dirY)
Set the direction vector.
Definition Velocity.hpp:64
System managing weapon firing and cooldowns.
Component for entities capable of shooting projectiles.
Definition Weapon.hpp:28
void setShouldShoot(bool shouldShoot)
Set whether the weapon should shoot.
Definition Weapon.hpp:116
High-level entity wrapper providing fluent interface.
Definition ECSWorld.hpp:33
Entity & with(const T &component)
Add/set a component to this entity.
bool has() const
Check if this entity has a specific component.
T & get()
Get a component from this entity.
Address getAddress() const
Get the entity's address.
Definition ECSWorld.cpp:39
static std::optional< ecs::MapData > loadFromFile(const std::string &filePath)
Load a map from a JSON file.
Definition MapLoader.cpp:13
Event triggered when the game ends.
void _applyPlayerInput(uint32_t playerId, const PlayerInput &input)
Apply a single input snapshot to a player entity.
void _executeSystems(float deltaTime)
Execute all systems in order.
uint32_t spawnPlayer(uint32_t playerId, const std::string &playerName) override
Spawn a player entity.
std::shared_ptr< ThreadPool > _threadPool
void resetGame() override
Reset game state (new game)
std::unordered_map< uint32_t, std::deque< PlayerInput > > _pendingInput
void _checkGameOverCondition()
Check if all players are dead and trigger game over.
std::mutex _playerMutex
~GameLogic() override
bool initialize() override
Initialize game logic and ECS systems.
Definition GameLogic.cpp:87
void despawnPlayer(uint32_t playerId) override
Remove a player from the game.
bool loadMap(const std::string &mapFilePath)
Load and activate a new map from a JSON file.
static constexpr float FIXED_TIMESTEP
void _cleanupDeadEntities()
Clean up dead entities.
std::unordered_map< uint32_t, ecs::Address > _playerMap
std::shared_ptr< EventBus > _eventBus
std::unique_ptr< scripting::LuaEngine > _luaEngine
GameLogic(std::shared_ptr< ecs::wrapper::ECSWorld > world=nullptr, std::shared_ptr< ThreadPool > threadPool=nullptr, std::shared_ptr< EventBus > eventBus=nullptr)
Constructor.
Definition GameLogic.cpp:54
std::shared_ptr< ecs::wrapper::ECSWorld > _world
std::atomic< bool > _initialized
void update(float deltaTime, uint32_t currentTick) override
Update game state for one frame.
std::mutex _inputMutex
GameRules _gameRules
void onGameStart()
Notify Lua scripts that the game has started.
void _processInput()
Process accumulated player input.
std::shared_ptr< GameStateManager > _stateManager
std::unordered_map< uint32_t, uint32_t > _lastReceivedSequenceId
std::unordered_map< uint32_t, uint32_t > _lastAppliedSequenceId
void processPlayerInput(uint32_t playerId, int inputX, int inputY, bool isShooting, uint32_t sequenceId) override
Process player input event.
constexpr uint32_t getPlayerSpawnY() const
Definition GameRules.hpp:30
constexpr uint32_t getPlayerSpawnX() const
Definition GameRules.hpp:29
constexpr uint32_t getDefaultPlayerSpeed() const
Definition GameRules.hpp:28
constexpr uint32_t getDefaultPlayerDamage() const
Definition GameRules.hpp:32
constexpr uint32_t getDefaultPlayerHealth() const
Definition GameRules.hpp:27
constexpr float getDefaultPlayerFireRate() const
Definition GameRules.hpp:31
Handles switching between game states.
ecs::AnimationSet createPlayerAnimations()
Create player ship animations.
std::uint32_t Address
Type used to represent an entity address/ID.
@ Manual
Manually destroyed (script, etc.)