r-type  0.0.0
R-Type main
Loading...
Searching...
No Matches
rtypeServer.cpp
Go to the documentation of this file.
1///
2/// @file rtypeServer.cpp
3/// @brief Implementation of RTypeServer with new entity management system
4/// @namespace gme
5///
6
7#include <algorithm>
8#include <ranges>
9
10#include "ECS/Component.hpp"
13#include "Utils/Common.hpp"
14#include "Utils/EventBus.hpp"
15#include "Utils/Logger.hpp"
17
18namespace gme
19{
20 RTypeServer::RTypeServer() : m_eventBus(utl::EventBus::getInstance())
21 {
22 // Register this component with the event bus
24
25 // Subscribe to events
29
30 // Initialize entity manager
31 m_entityManager = std::make_unique<EntityManager>(m_registry);
32
33 // Initialize game systems
34 m_collisionSystem = std::make_unique<CollisionSystem>(m_registry, *m_entityManager);
35 m_enemyAISystem = std::make_unique<EnemyAISystem>(m_registry, *m_entityManager);
36 m_enemySpawnSystem = std::make_unique<EnemySpawnSystem>(*m_entityManager);
37
38 // Initialize wave manager with default waves
39 m_waveManager = std::make_unique<WaveManager>(*m_entityManager);
40 m_waveManager->setupDefaultWaves();
41
42 utl::Logger::log("RTypeServer: Initialized with entity management system", utl::LogLevel::INFO);
43 }
44
46 {
49
50 // Disable old enemy spawning system, use wave manager instead
51 m_enemySpawnSystem->setEnabled(false);
52
53 // DON'T start wave manager here - wait for GAME_START event from waiting room
54 utl::Logger::log("RTypeServer: Server started - waiting for game to begin", utl::LogLevel::INFO);
55 }
56
58 {
60
61 // Clear all entities
62 m_entityManager->clear();
63
64 // Reset systems
65 m_enemySpawnSystem->reset();
66 m_waveManager->reset();
67
68 // Clear player tracking
69 m_playerShooting.clear();
70 m_lastShotTime.clear();
71
72 utl::Logger::log("RTypeServer: Stopped", utl::LogLevel::INFO);
73 }
74
75 void RTypeServer::update(const float deltaTime)
76 {
77 m_lastBroadcastTime += deltaTime;
78
80 for (const auto &event : events)
81 {
82 if (event.type == utl::EventType::SERVER_START)
83 {
85 }
86 else if (event.type == utl::EventType::PLAYER_INPUT_RECEIVED)
87 {
89 }
90 else if (event.type == utl::EventType::GAME_START)
91 {
93 }
94 else if (event.type == utl::EventType::PLAYER_DISCONNECTED)
95 {
97 }
98 }
99
100 // Update all game systems
101 updateSystems(deltaTime);
102
103 // Update entity physics
104 updateEntities(deltaTime);
105
106 // Update entity lifetimes (for projectiles with limited lifetime)
107 m_entityManager->updateLifetimes(deltaTime);
108
109 // Clean up destroyed entities
110 m_entityManager->cleanupDestroyedEntities();
111
112 // Check for game over conditions
114
115 // Broadcast world state at 60 Hz
117 {
119 m_lastBroadcastTime = 0.0f;
120 }
121 }
122
124 {
125 // Create player entities from sessionIds
126 if (event.data.size() >= sizeof(std::uint32_t))
127 {
128 size_t playerCount = event.data.size() / sizeof(std::uint32_t);
129
130 for (size_t i = 0; i < playerCount; ++i)
131 {
132 std::uint32_t sessionId;
133 std::memcpy(&sessionId, event.data.data() + i * sizeof(std::uint32_t), sizeof(std::uint32_t));
134
135 if (!m_entityManager->hasPlayer(sessionId))
136 {
137 float startX = 200.0f + (i * 200.0f);
138 float startY = 100.0f + (i * 100.0f);
139
140 m_entityManager->createPlayer(sessionId, startX, startY);
141 m_playerShooting[sessionId] = false;
142 m_lastShotTime[sessionId] = 0.0f;
143
144 utl::Logger::log("RTypeServer: Created player entity for sessionId " + std::to_string(sessionId),
146 }
147 }
148
149 // Start enemy spawning when game starts
151 {
153 m_enemySpawnSystem->setEnabled(true);
154 utl::Logger::log("RTypeServer: Game started, enemy spawning enabled", utl::LogLevel::INFO);
155 }
156 }
157 }
158
160 {
161 try
162 {
163 // Input data comes as: [up, down, left, right, shoot]
164 if (event.data.size() >= 5)
165 {
166 bool up = event.data[0] != 0;
167 bool down = event.data[1] != 0;
168 bool left = event.data[2] != 0;
169 bool right = event.data[3] != 0;
170 bool shoot = event.data[4] != 0;
171
172 std::uint32_t sessionId = event.sourceId;
173
174 // Create player entity if it doesn't exist
175 if (!m_entityManager->hasPlayer(sessionId))
176 {
177 // Spread players vertically to avoid spawn overlap
178 float spawnX = 200.0f;
179 float spawnY = 200.0f + ((sessionId % 4) * 200.0f);
180 m_entityManager->createPlayer(sessionId, spawnX, spawnY);
181 m_playerShooting[sessionId] = false;
182 m_lastShotTime[sessionId] = 0.0f;
183 }
184
185 // Apply input to player entity
186 if (sessionId != 0 && m_entityManager->hasPlayer(sessionId))
187 {
188 ecs::Entity playerEntity = m_entityManager->getPlayer(sessionId);
189 auto *velocity = m_registry.getComponent<ecs::Velocity>(playerEntity);
190 auto *transform = m_registry.getComponent<ecs::Transform>(playerEntity);
191
192 if (velocity && transform)
193 {
194 // Calculate velocity based on input
195 constexpr float SPEED = 500.0f;
196 constexpr float DIAGONAL_SPEED = SPEED * 0.707f;
197
198 velocity->x = 0.0f;
199 velocity->y = 0.0f;
200
201 // Handle diagonal movement
202 if (up && right)
203 {
204 velocity->x = DIAGONAL_SPEED;
205 velocity->y = -DIAGONAL_SPEED;
206 }
207 else if (up && left)
208 {
209 velocity->x = -DIAGONAL_SPEED;
210 velocity->y = -DIAGONAL_SPEED;
211 }
212 else if (down && right)
213 {
214 velocity->x = DIAGONAL_SPEED;
215 velocity->y = DIAGONAL_SPEED;
216 }
217 else if (down && left)
218 {
219 velocity->x = -DIAGONAL_SPEED;
220 velocity->y = DIAGONAL_SPEED;
221 }
222 else
223 {
224 // Handle cardinal directions
225 if (up)
226 velocity->y = -SPEED;
227 if (down)
228 velocity->y = SPEED;
229 if (left)
230 velocity->x = -SPEED;
231 if (right)
232 velocity->x = SPEED;
233 }
234
235 // Track shooting state
236 m_playerShooting[sessionId] = shoot;
237 }
238 }
239 }
240 }
241 catch (const std::exception &e)
242 {
243 utl::Logger::log("RTypeServer: Error processing input: " + std::string(e.what()), utl::LogLevel::WARNING);
244 }
245 }
246
248 {
249 (void)event; // Event data contains lobby info if needed
250
251 utl::Logger::log("RTypeServer: GAME_START event received - starting waves!", utl::LogLevel::INFO);
252
253 // Start the wave manager now that the game is actually starting
254 if (!m_waveManager->isActive())
255 {
256 m_waveManager->start();
258 utl::Logger::log("RTypeServer: Wave system started - 3 waves of 30 seconds", utl::LogLevel::INFO);
259 }
260 else
261 {
262 utl::Logger::log("RTypeServer: Wave system already active", utl::LogLevel::WARNING);
263 }
264 }
265
266 void RTypeServer::updateSystems(float deltaTime)
267 {
268 // Update all game systems in order
269
270 m_enemyAISystem->update(m_registry, deltaTime);
271
272 // Update wave manager instead of simple enemy spawning
273 // Assuming 1920 as default screen width (can be made configurable later)
274 m_waveManager->update(m_registry, deltaTime, 1920);
275
276 for (const auto &sessionId : m_entityManager->getPlayers() | std::views::keys)
277 {
278 handlePlayerShooting(sessionId, deltaTime);
279 }
280
281 m_collisionSystem->update(m_registry, deltaTime);
282 }
283
285 {
286 // Don't check if game is not in progress
288 {
289 return;
290 }
291
292 // Check if there are no more players at all (all disconnected)
293 const std::uint32_t totalPlayerCount = static_cast<std::uint32_t>(m_entityManager->getPlayers().size());
294 if (totalPlayerCount == 0)
295 {
296 utl::Logger::log("RTypeServer: No players left in game - resetting", utl::LogLevel::INFO);
298 m_entityManager->clear();
299 if (m_waveManager)
300 {
301 m_waveManager->reset();
302 }
303 return;
304 }
305
306 // Check if all waves are completed
307 const bool allWavesComplete = m_waveManager->isCompleted();
308
309 // Check alive player count
310 const std::uint32_t alivePlayerCount = m_entityManager->getAlivePlayerCount();
311
312 // Game Over conditions:
313 // 1. Only 1 or 0 players alive (in multiplayer context)
314 // 2. All waves completed (victory)
315 bool shouldGameOver = false;
316 std::string reason;
317
318 if (alivePlayerCount == 0)
319 {
320 shouldGameOver = true;
321 reason = "All players died";
323 }
324 else if (totalPlayerCount > 1 && alivePlayerCount == 1)
325 {
326 shouldGameOver = true;
327 reason = "Only one player remaining";
329 }
330 else if (allWavesComplete)
331 {
332 shouldGameOver = true;
333 reason = "All waves completed";
335 }
336
337 if (shouldGameOver)
338 {
339 utl::Logger::log("RTypeServer: Game Over - " + reason, utl::LogLevel::INFO);
340
341 // Broadcast GAME_OVER event to all clients
342 std::vector<std::uint8_t> gameOverData;
343 gameOverData.resize(1);
344 gameOverData[0] = static_cast<std::uint8_t>(m_levelState);
345
347
348 // Reset game state for next round
350 }
351 }
352
354 {
355 std::uint32_t sessionId = event.sourceId;
356
357 utl::Logger::log("RTypeServer: Player " + std::to_string(sessionId) + " disconnected", utl::LogLevel::INFO);
358
359 // Remove player from entity manager
360 if (m_entityManager->hasPlayer(sessionId))
361 {
362 m_entityManager->destroyPlayer(sessionId);
363 utl::Logger::log("RTypeServer: Removed player " + std::to_string(sessionId) + " from game",
365 }
366
367 // Check if there are no more players
368 if (m_entityManager->getPlayers().empty())
369 {
370 utl::Logger::log("RTypeServer: No more players in game - resetting game state", utl::LogLevel::INFO);
372
373 // Clear all game entities
374 m_entityManager->clear();
375
376 // Reset wave manager
377 if (m_waveManager)
378 {
379 m_waveManager->reset();
380 }
381 }
382 else
383 {
384 // Check if game should end with remaining players
386 }
387 }
388
389 void RTypeServer::handlePlayerShooting(std::uint32_t sessionId, float deltaTime)
390 {
391 if (!m_lastShotTime.contains(sessionId))
392 {
393 m_lastShotTime[sessionId] = 0.0f;
394 }
395
396 m_lastShotTime[sessionId] += deltaTime;
397
398 const ecs::Entity playerEntity = m_entityManager->getPlayer(sessionId);
399 if (playerEntity == ecs::INVALID_ENTITY)
400 {
401 return;
402 }
403
404 auto *beamCharge = m_registry.getComponent<ecs::BeamCharge>(playerEntity);
405 auto *transform = m_registry.getComponent<ecs::Transform>(playerEntity);
406
407 if (!beamCharge || !transform)
408 {
409 return;
410 }
411 if (m_playerShooting.contains(sessionId) && m_playerShooting[sessionId])
412 {
413 constexpr float CHARGE_RATE = 2.0f;
414 beamCharge->current_charge += CHARGE_RATE * deltaTime;
415 if (beamCharge->current_charge > beamCharge->max_charge)
416 beamCharge->current_charge = beamCharge->max_charge;
417 }
418 else if (beamCharge->current_charge > 0.01f)
419 {
420 if (m_lastShotTime[sessionId] >= PROJECTILE_COOLDOWN)
421 {
424
425 const float projectileX = transform->x + PLAYER_WIDTH + 10.0f;
426 const float projectileY = transform->y + PLAYER_HEIGHT / 2.0f;
427
428 const bool isSupercharged = beamCharge->current_charge >= 0.5f;
429 const float projectileSpeed = isSupercharged ? 1200.0f : 800.0f;
430
431 m_entityManager->createPlayerProjectile(sessionId, projectileX, projectileY, projectileSpeed, 0.0f,
432 isSupercharged);
433
434 beamCharge->current_charge = 0.0f;
435 m_lastShotTime[sessionId] = 0.0f;
436 }
437 }
438 }
439
440 void RTypeServer::updateEntities(float deltaTime)
441 {
442 for (const auto &players = m_entityManager->getPlayers(); const auto &entity : players | std::views::values)
443 {
444 auto *transform = m_registry.getComponent<ecs::Transform>(entity);
445 auto *velocity = m_registry.getComponent<ecs::Velocity>(entity);
446 auto *hitbox = m_registry.getComponent<ecs::Hitbox>(entity);
447
448 if (transform && velocity)
449 {
450 transform->x += velocity->x * deltaTime;
451 transform->y += velocity->y * deltaTime;
452
453 if (hitbox)
454 {
455 constexpr float PLAYER_WIDTH =
457 constexpr float PLAYER_HEIGHT =
459 const float hitboxRadius = hitbox->radius;
460
461 float maxX = utl::GameConfig::Server::SCREEN_WIDTH - (PLAYER_WIDTH - hitboxRadius);
462 float maxY = utl::GameConfig::Server::SCREEN_HEIGHT - (PLAYER_HEIGHT - hitboxRadius);
463
464 transform->x = std::max(hitboxRadius, std::min(transform->x, maxX));
465 transform->y = std::max(hitboxRadius, std::min(transform->y, maxY));
466 }
467 else
468 {
469 transform->x = std::max(0.0f, std::min(transform->x, utl::GameConfig::Server::SCREEN_WIDTH));
470 transform->y = std::max(0.0f, std::min(transform->y, utl::GameConfig::Server::SCREEN_HEIGHT));
471 }
472 }
473 }
474
475 // Update enemy positions
476 for (const auto &enemies = m_entityManager->getEnemies(); const auto &[enemyId, entity] : enemies)
477 {
478 // Skip inactive entities
479 if (const auto *metadata = m_entityManager->getEntityMetadata(enemyId); !metadata || !metadata->isActive)
480 {
481 continue;
482 }
483
484 auto *transform = m_registry.getComponent<ecs::Transform>(entity);
485 auto *velocity = m_registry.getComponent<ecs::Velocity>(entity);
486
487 if (transform && velocity)
488 {
489 transform->x += velocity->x * deltaTime;
490 transform->y += velocity->y * deltaTime;
491
492 // Destroy enemies that are too far off-screen
493 if (transform->x < -utl::GameConfig::Server::WORLD_MARGIN ||
497 {
498 m_entityManager->destroyEnemy(enemyId);
499 }
500 }
501 }
502
503 const auto &projectiles = m_entityManager->getProjectiles();
504 for (const auto &[projectileId, entity] : projectiles)
505 {
506 if (const auto *metadata = m_entityManager->getEntityMetadata(projectileId);
507 !metadata || !metadata->isActive)
508 {
509 continue;
510 }
511
512 auto *transform = m_registry.getComponent<ecs::Transform>(entity);
513 auto *velocity = m_registry.getComponent<ecs::Velocity>(entity);
514
515 if (transform && velocity)
516 {
517 transform->x += velocity->x * deltaTime;
518 transform->y += velocity->y * deltaTime;
519
520 // Remove projectiles that are off-screen (with margin)
525 {
526 m_entityManager->destroyProjectile(projectileId);
527 }
528 }
529 }
530 }
531
533 {
534 try
535 {
536 rnp::PacketWorldState worldState;
537 worldState.serverTick = static_cast<std::uint32_t>(m_lastBroadcastTime * 60.0f);
538
539 // Get all entity states from EntityManager
540 worldState.entities = m_entityManager->getAllEntityStates();
541 worldState.entityCount = static_cast<std::uint16_t>(worldState.entities.size());
542
543 // Create full packet with header for each client
544 const auto &players = m_entityManager->getPlayers();
545
546 for (const auto &sessionId : players | std::views::keys)
547 {
548 // Create packet with header for this session
549 rnp::Serializer packetSerializer;
550
551 // Create header
552 rnp::PacketHeader header;
553 header.type = static_cast<std::uint8_t>(rnp::PacketType::WORLD_STATE);
554
555 // Serialize world state to calculate length
556 rnp::Serializer tempSerializer;
557 tempSerializer.serializeWorldState(worldState);
558 header.length = static_cast<std::uint16_t>(tempSerializer.getData().size());
559 header.sessionId = sessionId;
560
561 // Serialize header and world state
562 packetSerializer.serializeHeader(header);
563 packetSerializer.serializeWorldState(worldState);
564
565 std::vector<std::uint8_t> packet = packetSerializer.getData();
566
567 // Send packet via EventBus
569 std::vector<std::uint8_t> eventData(sizeof(std::uint32_t) + packet.size());
570 std::memcpy(eventData.data(), &sessionId, sizeof(std::uint32_t));
571 std::memcpy(eventData.data() + sizeof(std::uint32_t), packet.data(), packet.size());
572 broadcastEvent.data = eventData;
573 m_eventBus.publish(broadcastEvent);
574 }
575 }
576 catch (const std::exception &e)
577 {
578 utl::Logger::log("RTypeServer: Error broadcasting world state: " + std::string(e.what()),
580 }
581 }
582
583} // namespace gme
This file contains the component definitions.
Thread-safe event bus implementation for inter-component communication.
Configuration constants for the multiplayer game.
This file contains the Logger class.
Main game server plugin for R-Type multiplayer.
Network packet serializer for RNP protocol.
T * getComponent(Entity e)
Definition Registry.hpp:71
std::unique_ptr< EntityManager > m_entityManager
Centralized entity lifecycle manager.
void update(float deltaTime) override
Update the game server (called each tick)
ecs::Registry m_registry
ECS registry containing all game entities.
State m_gameState
Current server state.
std::unordered_map< std::uint32_t, bool > m_playerShooting
Map of session ID to shooting state.
std::unique_ptr< WaveManager > m_waveManager
Wave-based enemy spawning manager.
RTypeServer()
Constructor.
float m_lastBroadcastTime
Time since last world state broadcast.
LevelState m_levelState
Current level state.
void updateEntities(float deltaTime)
Update entity lifetimes and physics.
void checkGameOver()
Check for game over conditions.
void broadcastWorldState() const
Broadcast complete world state to all clients.
utl::EventBus & m_eventBus
Event bus for inter-system communication.
std::unordered_map< std::uint32_t, float > m_lastShotTime
Map of session ID to last shot timestamp.
void processServerStartEvent(const utl::Event &event)
Process server start event from network layer.
std::unique_ptr< EnemyAISystem > m_enemyAISystem
Enemy behavior and AI system.
void stop() override
Stop the game server.
void start() override
Start the game server.
std::unique_ptr< EnemySpawnSystem > m_enemySpawnSystem
Enemy spawning system (legacy/unused)
void processGameStartEvent(const utl::Event &event)
Process game start event from lobby system.
void updateSystems(float deltaTime)
Update all game systems (AI, collision, spawning)
const float PROJECTILE_COOLDOWN
Cooldown between player shots (seconds)
void processPlayerInputEvent(const utl::Event &event)
Process player input event.
void processPlayerDisconnectEvent(const utl::Event &event)
Process player disconnect event.
void handlePlayerShooting(std::uint32_t sessionId, float deltaTime)
Handle player shooting logic.
std::unique_ptr< CollisionSystem > m_collisionSystem
Collision detection and resolution system.
Binary serializer for RNP protocol packets.
void serializeWorldState(const PacketWorldState &packet)
Serialize WORLD_STATE packet.
void serializeHeader(const PacketHeader &header)
Serialize packet header.
const std::vector< std::uint8_t > & getData() const
Get the serialized data.
bool publish(const Event &event)
Publish an event to the bus.
Definition EventBus.hpp:118
std::vector< Event > consumeForTarget(std::uint32_t targetId, std::uint32_t maxEvents=100)
Consume events targeted to specific component.
Definition EventBus.hpp:357
void registerComponent(std::uint32_t componentId, const std::string &name)
Register component name for better debugging.
Definition EventBus.hpp:423
void subscribe(std::uint32_t componentId, EventType type)
Subscribe component to specific event types.
Definition EventBus.hpp:384
Event structure for inter-component communication.
Definition Event.hpp:89
std::vector< std::uint8_t > data
Serialized event data.
Definition Event.hpp:96
static void log(const std::string &message, const LogLevel &logLevel)
Definition Logger.hpp:51
This file contains common definitions and constants.
std::uint32_t Entity
Definition Entity.hpp:13
constexpr Entity INVALID_ENTITY
Definition Entity.hpp:14
@ WAITING_FOR_PLAYERS
Waiting for players to join before starting.
@ COMPLETED
Level successfully completed (all waves cleared)
@ IN_PROGRESS
Game is actively running.
@ LOOSE
Game over (all players dead)
constexpr float SCALE
constexpr float SPRITE_HEIGHT
constexpr float SPRITE_WIDTH
constexpr float WORLD_MARGIN
constexpr float SCREEN_WIDTH
constexpr float SCREEN_HEIGHT
static constexpr std::uint32_t GAME_LOGIC
Definition Event.hpp:19
static constexpr std::uint32_t NETWORK_SERVER
Definition Event.hpp:18
Packet header according to RNP specification (Big Endian) Total size: 7 bytes (1 + 2 + 4)
Definition Protocol.hpp:128
std::uint32_t sessionId
Definition Protocol.hpp:131
std::uint8_t type
Definition Protocol.hpp:129
std::uint16_t length
Definition Protocol.hpp:130
WORLD_STATE packet payload.
Definition Protocol.hpp:198
std::uint16_t entityCount
Definition Protocol.hpp:200
std::uint32_t serverTick
Definition Protocol.hpp:199
std::vector< EntityState > entities
Definition Protocol.hpp:201