R-Type
Distributed multiplayer game engine in C++
Loading...
Searching...
No Matches
Server.cpp
Go to the documentation of this file.
1/*
2** EPITECH PROJECT, 2025
3** Created by mael on 08/12/2025.
4** File description:
5** Server.cpp
6*/
7
9#include <algorithm>
10#include <chrono>
11#include <functional>
12#include <thread>
17#include "NetworkFactory.hpp"
49
50Server::Server(uint16_t port, size_t maxClients) : _port(port), _maxClients(maxClients) {}
51
53 LOG_INFO("Server shutting down...");
54 stop();
56}
57
59 if (_initialized) {
60 return true;
61 }
62
63 LOG_INFO("Initializing R-Type server...");
64
65 if (!initializeNetworking()) {
66 LOG_ERROR("Failed to initialize networking");
67 return false;
68 }
69
70 _networkManager = std::make_unique<ServerNetworkManager>(_port, _maxClients);
71 _networkManager->setPacketHandler([this](HostNetworkEvent &event) { this->handlePacket(event); });
72
73 if (!_networkManager->start()) {
74 LOG_ERROR("Failed to start network manager");
75 return false;
76 }
77
78 _eventBus = std::make_shared<server::EventBus>();
79 _sessionManager = std::make_shared<server::SessionManager>();
80
81 // Create matchmaking service with shared EventBus
82 auto matchmaking = std::make_shared<server::MatchmakingService>(2, 4, _eventBus);
83 _roomManager = std::make_shared<server::RoomManager>(matchmaking, _eventBus);
84
85 _lobby = std::make_shared<server::Lobby>(_roomManager);
86 _commandHandler = std::make_unique<server::CommandHandler>();
87
88 // Set callback for when matchmaking creates a room
89 _roomManager->setRoomCreatedCallback(
90 [this](std::shared_ptr<server::Room> room) { _onMatchmakingRoomCreated(room); });
91
92 // Subscribe to game events on global EventBus
94 LOG_INFO("[EVENT] Player joined: ", event.getPlayerName(), " (ID: ", event.getPlayerId(), ")");
95 });
96
97 _eventBus->subscribe<server::PlayerLeftEvent>([](const server::PlayerLeftEvent &event) {
98 LOG_INFO("[EVENT] Player left (ID: ", event.getPlayerId(), ")");
99 });
100
101 _eventBus->subscribe<server::GameStartedEvent>([](const server::GameStartedEvent &event) {
102 if (event.getRoomId().empty()) {
103 LOG_INFO("[EVENT] Server started!");
104 } else {
105 LOG_INFO("[EVENT] Game started in room: ", event.getRoomId());
106 }
107 });
108
109 _eventBus->subscribe<server::GameEndedEvent>([this](const server::GameEndedEvent &event) {
110 LOG_INFO("Game ended - reason: ", event.getReason());
111
112 // Find the room associated with this game and broadcast Game Over to all players
113 auto rooms = _roomManager->getAllRooms();
114 LOG_INFO("Broadcasting GameOver to ", rooms.size(), " room(s)");
115
116 for (const auto &room : rooms) {
117 auto gameLogic = room->getGameLogic();
118 if (gameLogic) {
119 // Send GameOver message to all players in this room
120 auto players = room->getPlayers();
121 LOG_INFO("Room has ", players.size(), " player(s)");
122 for (uint32_t playerId : players) {
123 // Get session ID from player ID
124 auto sessionIt = _playerIdToSessionId.find(playerId);
125 if (sessionIt != _playerIdToSessionId.end()) {
126 const std::string &sessionId = sessionIt->second;
127 // Get peer from session ID
128 auto peerIt = _sessionPeers.find(sessionId);
129 if (peerIt != _sessionPeers.end() && peerIt->second) {
130 // Create GameOver message
131 RType::Messages::S2C::GameOver gameOverMsg(event.getReason());
132 std::vector<uint8_t> payload = gameOverMsg.serialize();
133
135 true);
136 LOG_INFO("Sent GameOver to player ", playerId);
137 }
138 }
139 }
140 }
141 }
142 });
143 _initialized = true;
144 LOG_INFO("✓ Server initialized successfully");
145 return true;
146}
147
149 if (event.type == NetworkEventType::DISCONNECT) {
150 _handleDisconnect(event);
151 return;
152 }
153
154 if (!event.packet || !event.peer) {
155 return;
156 }
157
158 try {
159 using namespace RType::Messages;
160
162
163 switch (messageType) {
166 break;
167
170 break;
171
173 _handleLoginRequest(event);
174 break;
175
177 _handlePlayerInput(event);
178 break;
179
181 _handleListRooms(event);
182 break;
183
185 _handleCreateRoom(event);
186 break;
187
189 _handleJoinRoom(event);
190 break;
191
194 break;
195
198 break;
199
201 _handleLeaveRoom(event);
202 break;
203
205 _handleStartGame(event);
206 break;
207
209 _handleChatMessage(event);
210 break;
211
212 default:
213 LOG_WARNING("Received unknown message type: ", static_cast<int>(messageType));
214 break;
215 }
216
217 } catch (const std::exception &e) {
218 LOG_ERROR("Error handling packet: ", e.what());
219 }
220}
221
223 if (!event.peer) {
224 return;
225 }
226
227 auto session = _getSessionFromPeer(event.peer);
228 if (!session) {
229 return;
230 }
231
232 std::string sessionId = _peerToSession[event.peer];
233 uint32_t playerId = session->getPlayerId();
234
235 if (playerId == 0) {
236 return;
237 }
238
239 LOG_INFO("Player ", playerId, " disconnected, cleaning up...");
240
241 std::shared_ptr<server::Room> playerRoom = _roomManager->getRoomByPlayer(playerId);
242 if (playerRoom) {
243 std::shared_ptr<server::IGameLogic> gameLogic = playerRoom->getGameLogic();
244 if (gameLogic) {
245 gameLogic->despawnPlayer(playerId);
246 }
247
248 playerRoom->leave(playerId);
249 LOG_INFO("✓ Player ", playerId, " removed from room '", playerRoom->getId(), "'");
250
251 // Broadcast updated room state to remaining players
252 _broadcastRoomState(playerRoom);
253
254 // Broadcast updated room list to ALL connected players (player count changed)
256 }
257
258 _eventBus->publish(server::PlayerLeftEvent(playerId));
259
260 _sessionManager->removeSession(sessionId);
261 _sessionPeers.erase(sessionId);
262 _peerToSession.erase(event.peer);
263 _playerIdToSessionId.erase(playerId);
264}
265
267 using namespace RType::Messages;
268 using namespace ConnectionMessages;
269
270 // Parse handshake request with authentication credentials
271 std::vector<uint8_t> payload = NetworkMessages::getPayload(event.packet->getData());
272 HandshakeRequestData handshakeData = parseHandshakeRequest(payload);
273
274 std::string playerName = handshakeData.playerName;
275 std::string username = handshakeData.username;
276 std::string password = handshakeData.password;
277
278 LOG_INFO("Authentication attempt - Username: '", username, "', Player: '", playerName, "'");
279
280 // Use SessionManager's authenticateAndCreateSession (respects architecture)
281 std::string sessionId = _sessionManager->authenticateAndCreateSession(username, password);
282
283 if (sessionId.empty()) {
284 LOG_WARNING("❌ Authentication FAILED for user: ", username);
285
286 // Send rejection response
287 std::vector<uint8_t> responseData =
288 NetworkMessages::createConnectResponse("Authentication failed! Invalid username or password.");
291 responseData); // Implicit type not ideal here but helper works with payload wrapping... wait.
292 // Helper assumes we are passing the PAYLOAD if we use createMessage inside it.
293 // But createConnectResponse returns the PAYLOAD for HANDSHAKE_RESPONSE?
294 // Let's check createConnectResponse. Usually it serializes the struct.
295 // Wait, the original code was:
296 // NetworkMessages::createConnectResponse(...) -> returns payload?
297 // createPacket(...)
298 // Actually, NetworkMessages::createConnectResponse likely creates the FULL message or just payload.
299 // Looking at usage:
300 // std::vector<uint8_t> responseData = NetworkMessages::createConnectResponse("...");
301 // std::unique_ptr<IPacket> responsePacket = createPacket(responseData, ...);
302 // This implies createConnectResponse returns the FULL message including the type header?
303 // If so, _sendPacket shouldn't wrap it again with createMessage.
304
305 // Disconnect the peer
306 event.peer->disconnect();
307 return;
308 }
309
310 // Checking original code:
311 // std::vector<uint8_t> responseData = NetworkMessages::createConnectResponse("Authentication failed!...");
312 // std::unique_ptr<IPacket> responsePacket = createPacket(responseData, static_cast<int>(PacketFlag::RELIABLE));
313
314 // And other places:
315 // std::vector<uint8_t> packet = NetworkMessages::createMessage(NetworkMessages::MessageType::S2C_ROOM_LIST, payload);
316 // std::unique_ptr<IPacket> netPacket = createPacket(packet, ...);
317
318 // So createConnectResponse must return the full message OR createPacket expects full message.
319 // In _handleListRooms:
320 // payload = roomList.serialize();
321 // packet = createMessage(TYPE, payload);
322 // netPacket = createPacket(packet);
323
324 // So createMessage wraps payload with Type.
325 // createConnectResponse likely wraps with HANDSHAKE_RESPONSE type.
326
327 // So my _sendPacket helper:
328 // void _sendPacket(IPeer *peer, MessageType type, const std::vector<uint8_t> &payload, bool reliable)
329 // Should do:
330 // vector packet = createMessage(type, payload);
331 // ptr netPacket = createPacket(packet, reliable);
332 // peer->send(netPacket);
333
334 // But for HANDSHAKE, createConnectResponse might already include the type?
335 // Let's assume createConnectResponse returns the PAYLOAD for now, or I need to check.
336 // Wait, in the original code:
337 // std::vector<uint8_t> responseData = NetworkMessages::createConnectResponse("Authentication failed! Invalid username or password.");
338 // std::unique_ptr<IPacket> responsePacket = createPacket(responseData, static_cast<int>(PacketFlag::RELIABLE));
339 // It does NOT call createMessage. So createConnectResponse must return the FULL message (Type + Payload).
340
341 // In this case, I cannot use _sendPacket(peer, type, payload) if payload is already the full message.
342 // Or I should make _sendPacket take the full message?
343 // No, duplicate reduction aims to consolidate "createMessage + createPacket + send".
344
345 // If createConnectResponse returns full message, then I can just call createPacket + send.
346 // But most other messages use createMessage.
347
348 // Let's stick to the majority case. Most messages seem to use createMessage.
349 // I will refactor Handshake manually or adjust the helper.
350
351 // Actually, I should probably stick to `_sendPacket` accepting PAYLOAD.
352 // For Handshake, I might need to see if there is a way to get just payload.
353 // Or I just rewrite the handshake part to use createMessage manually if possible.
354 // But createConnectResponse is likely a helper in NetworkMessages.
355
356 // Let's ignore Handshake optimization for `_sendPacket` for a moment and focus on the rest.
357
358 LOG_INFO("✓ Authentication SUCCESS for user: ", username);
359
360 // Get the created session
361 std::shared_ptr<server::Session> session = _sessionManager->getSession(sessionId);
362 if (!session) {
363 LOG_ERROR("Session creation failed after authentication");
364 event.peer->disconnect();
365 return;
366 }
367
368 // Configure session
369 static std::atomic<uint32_t> nextPlayerId{1000};
370 uint32_t newPlayerId = nextPlayerId.fetch_add(1);
371
372 session->setPlayerId(newPlayerId);
373 session->setSpectator(false);
374 session->setActive(true);
375
376 _sessionPeers[sessionId] = event.peer;
377 _peerToSession[event.peer] = sessionId;
378 _playerIdToSessionId[newPlayerId] = sessionId;
379
380 // Determine display name based on authentication type
381 std::string displayName;
382 if (username == "guest") {
383 // For guests, generate unique name with first 4 chars of hash
384 std::string hashStr = std::to_string(std::hash<std::string>{}(sessionId));
385 displayName = "guest_" + hashStr.substr(0, 4);
386 } else {
387 // For registered users, use their username
388 displayName = username;
389 }
390
391 _lobby->addPlayer(newPlayerId, displayName);
392
393 LOG_INFO("✓ Player '", displayName, "' (", username, ") authenticated (Session: ", sessionId,
394 ", Player ID: ", newPlayerId, ")");
395
396 // Send authentication response with playerId and displayName
398 response.accepted = true;
399 response.sessionId = sessionId;
400 response.serverId = "r-type-server";
401 response.message = "✓ Authentication successful! Welcome to R-Type, " + displayName + "!";
402 response.serverVersion = "1.0.0";
403 response.playerId = newPlayerId;
404 response.playerName = displayName;
405
406 std::vector<uint8_t> responseData = response.serialize();
407 std::vector<uint8_t> packet =
409 std::unique_ptr<IPacket> responsePacket = createPacket(packet, static_cast<int>(PacketFlag::RELIABLE));
410 event.peer->send(std::move(responsePacket), 0);
411
412 // Send server constants (game rules) early so client can configure itself before gameplay.
413 // If/when rules become per-room, we also resend them on GameStart.
414 {
415 server::GameRules defaultRules;
417 }
418
419 LOG_INFO(" Player is now in lobby - waiting for room selection");
420}
421
423 using namespace RType::Messages;
424
425 std::vector<uint8_t> payload = NetworkMessages::getPayload(event.packet->getData());
426
427 // Parse using Cap'n Proto RegisterAccount message
428 C2S::RegisterAccount registerMsg = C2S::RegisterAccount::deserialize(payload);
429 std::string username = registerMsg.username;
430 std::string password = registerMsg.password;
431
432 LOG_INFO("Registration attempt - Username: '", username, "'");
433
434 // Try to register the user
435 bool success = _sessionManager->getAuthService()->registerUser(username, password);
436
437 // Create response using Cap'n Proto
438 S2C::RegisterResponse response;
439 if (success) {
440 response.success = true;
441 response.message = "Account created successfully! You can now login.";
442 LOG_INFO("Registration SUCCESS for user: ", username);
443 } else {
444 response.success = false;
445 response.message =
446 "Registration failed! Username may already exist or invalid credentials (min 3 chars "
447 "username, 4 chars password).";
448 LOG_WARNING("Registration FAILED for user: ", username);
449 }
450
451 // Send response
452 std::vector<uint8_t> responsePayload = response.serialize();
454}
455
457 using namespace RType::Messages;
458
459 std::vector<uint8_t> payload = NetworkMessages::getPayload(event.packet->getData());
460
461 // Parse using Cap'n Proto LoginAccount message
462 C2S::LoginAccount loginMsg = C2S::LoginAccount::deserialize(payload);
463 std::string username = loginMsg.username;
464 std::string password = loginMsg.password;
465
466 LOG_INFO("Login attempt - Username: '", username, "'");
467
468 // Try to authenticate and create session
469 std::string sessionId = _sessionManager->authenticateAndCreateSession(username, password);
470 bool success = !sessionId.empty();
471
472 // Create response using Cap'n Proto
473 S2C::LoginResponse response;
474 if (success) {
475 response.success = true;
476 response.message = "✓ Login successful! Welcome back, " + username + "!";
477 response.sessionToken = sessionId;
478 response.autoMatchmaking = _sessionManager->getAuthService()->getAutoMatchmaking(username);
479 LOG_INFO("✓ Login SUCCESS for user: ", username, " (Session: ", sessionId, ")");
480
481 // Update player name in lobby after successful login
482 // Find the peer's session and player ID
483 auto sessionIt = _peerToSession.find(event.peer);
484 if (sessionIt != _peerToSession.end()) {
485 std::shared_ptr<server::Session> session = _sessionManager->getSession(sessionIt->second);
486 if (session) {
487 uint32_t playerId = session->getPlayerId();
488 // Store username for future preference updates
489 _playerIdToUsername[playerId] = username;
490 // Determine display name based on username
491 std::string displayName;
492 if (username == "guest") {
493 // For guests, generate unique name with first 4 chars of session hash
494 std::string hashStr = std::to_string(std::hash<std::string>{}(sessionId));
495 displayName = "guest_" + hashStr.substr(0, 4);
496 } else {
497 // For registered users, use their username
498 displayName = username;
499 }
500 _lobby->updatePlayerName(playerId, displayName);
501 }
502 }
503 } else {
504 response.success = false;
505 response.message = "Login failed! Invalid username or password.";
506 response.sessionToken = "";
507 response.autoMatchmaking = false;
508 LOG_WARNING("Login FAILED for user: ", username);
509 }
510
511 // Send response
512 std::vector<uint8_t> responsePayload = response.serialize();
514}
515
517 using namespace RType::Messages;
518
519 std::vector<uint8_t> payload = NetworkMessages::getPayload(event.packet->getData());
520
521 try {
522 C2S::PlayerInput packet = C2S::PlayerInput::deserialize(payload);
523
524 auto session = _getSessionFromPeer(event.peer);
525 uint32_t playerId = 0;
526 bool isSpectator = false;
527 if (session) {
528 playerId = session->getPlayerId();
529 isSpectator = session->isSpectator();
530 }
531
532 // Spectators cannot send inputs
533 if (isSpectator || playerId == 0) {
534 return;
535 }
536
537 std::shared_ptr<server::Room> playerRoom = _roomManager->getRoomByPlayer(playerId);
538 if (!playerRoom) {
539 return;
540 }
541
542 std::shared_ptr<server::IGameLogic> gameLogic = playerRoom->getGameLogic();
543 if (!gameLogic) {
544 return;
545 }
546
547 // Iterate through all snapshots in the redundant packet
548 // The packet contains a history of inputs. We try to apply all of them.
549 // GameLogic::processPlayerInput has a filter to ignore already processed sequence IDs.
550 auto snapshots = packet.inputs;
551 std::sort(snapshots.begin(), snapshots.end(),
553 return a.sequenceId < b.sequenceId;
554 });
555
556 for (const auto &snapshot : snapshots) {
557 int dx = 0;
558 int dy = 0;
559 bool shoot = false;
560
561 for (const auto &action : snapshot.actions) {
562 int actionDx = 0;
563 int actionDy = 0;
564 bool actionShoot = false;
565 _actionToInput(action, actionDx, actionDy, actionShoot);
566 dx += actionDx;
567 dy += actionDy;
568 shoot = shoot || actionShoot;
569 }
570
571 gameLogic->processPlayerInput(playerId, dx, dy, shoot, snapshot.sequenceId);
572 }
573 } catch (const std::exception &e) {
574 LOG_WARNING("Failed to deserialize input packet: ", e.what());
575 }
576}
577
579 using namespace RType::Messages;
580
581 LOG_INFO("Sending room list...");
582
583 // Send initial room list to the requesting player
584 _broadcastRoomList({event.peer});
585}
586
588 using namespace RType::Messages;
589
590 std::vector<uint8_t> payload = NetworkMessages::getPayload(event.packet->getData());
591 C2S::CreateRoom request = C2S::CreateRoom::deserialize(payload);
592
593 auto session = _getSessionFromPeer(event.peer);
594 uint32_t playerId = 0;
595 bool isSpectator = false;
596 if (session) {
597 playerId = session->getPlayerId();
598 isSpectator = session->isSpectator();
599 }
600
601 if (playerId == 0) {
602 LOG_ERROR("Failed to find player for room creation");
603 S2C::RoomCreated response("", false, "Session not found");
605 return;
606 }
607
608 if (isSpectator) {
609 LOG_ERROR("Spectators cannot create rooms");
610 S2C::RoomCreated response("", false, "Spectators cannot create rooms");
612 return;
613 }
614
615 static std::atomic<uint32_t> nextRoomId{1};
616 std::string roomId = "room_" + std::to_string(nextRoomId.fetch_add(1));
617
618 auto room = _roomManager->createRoom(roomId, request.roomName, request.maxPlayers, request.isPrivate,
619 request.gameSpeedMultiplier);
620 if (!room) {
621 LOG_ERROR("Failed to create room");
622 S2C::RoomCreated response("", false, "Failed to create room");
624 return;
625 }
626
627 if (!room->join(playerId)) {
628 LOG_ERROR("Failed to join created room");
629 _roomManager->removeRoom(roomId); // Clean up the room since it couldn't be joined
630 S2C::RoomCreated response(roomId, false, "Failed to join room");
632 return;
633 }
634
635 // Player stays in lobby - room membership is tracked separately
636 // This allows all players to see all rooms at all times
637
638 LOG_INFO("Room '", request.roomName, "' created by player ", playerId);
639
640 S2C::RoomCreated response(roomId, true);
642
643 // Broadcast room state to the creator (now only player in room)
645
646 // Broadcast updated room list to ALL connected players
648}
649
651 using namespace RType::Messages;
652
653 std::vector<uint8_t> payload = NetworkMessages::getPayload(event.packet->getData());
654 C2S::JoinRoom request = C2S::JoinRoom::deserialize(payload);
655
656 auto session = _getSessionFromPeer(event.peer);
657 uint32_t playerId = 0;
658 bool isSpectator = false;
659 if (session) {
660 playerId = session->getPlayerId();
661 isSpectator = session->isSpectator();
662 }
663
664 if (playerId == 0) {
665 LOG_ERROR("Failed to find player for room join");
666 S2C::JoinedRoom response("", false, "Session not found");
668 return;
669 }
670
671 auto room = _roomManager->getRoom(request.roomId);
672 if (!room) {
673 LOG_ERROR("Room '", request.roomId, "' not found");
674 S2C::JoinedRoom response("", false, "Room not found");
676 return;
677 }
678
679 // Join as spectator or player based on session type
680 bool joinSuccess = false;
681 bool autoSpectator = false;
682
683 if (isSpectator) {
684 joinSuccess = room->joinAsSpectator(playerId);
685 } else {
686 joinSuccess = room->join(playerId);
687
688 // If join fails because game is in progress, automatically join as spectator
689 if (!joinSuccess && room->getState() == server::RoomState::IN_PROGRESS) {
690 LOG_INFO("Game already in progress, joining player ", playerId, " as spectator");
691 joinSuccess = room->joinAsSpectator(playerId);
692 autoSpectator = true;
693 }
694 }
695
696 if (!joinSuccess) {
697 std::string errorMsg =
698 isSpectator ? "Failed to join as spectator" : "Room is full or game already started";
699 LOG_ERROR(errorMsg);
700 S2C::JoinedRoom response("", false, errorMsg);
702 return;
703 }
704
705 // Player stays in lobby - room membership is tracked separately in RoomManager
706
707 std::string modeStr = (isSpectator || autoSpectator) ? " as spectator" : "";
708 LOG_INFO("Player ", playerId, " joined room '", request.roomId, "'", modeStr);
709
710 S2C::JoinedRoom response(request.roomId, true, "", (isSpectator || autoSpectator));
712
713 // If player joined as spectator to an in-progress game, send them the current game state
714 if ((isSpectator || autoSpectator) && room->getState() == server::RoomState::IN_PROGRESS) {
715 LOG_INFO("Sending current game state to spectator ", playerId);
716 _sendGameStartToSpectator(playerId, room);
717 }
718
719 // Broadcast updated room state to all players in the room
721
722 // Broadcast updated room list to ALL connected players (player count changed)
724}
725
727 using namespace RType::Messages;
728
729 auto session = _getSessionFromPeer(event.peer);
730 uint32_t playerId = 0;
731 bool isSpectator = false;
732 if (session) {
733 playerId = session->getPlayerId();
734 isSpectator = session->isSpectator();
735 }
736
737 if (playerId == 0) {
738 LOG_ERROR("Failed to find player for auto-matchmaking");
739 S2C::JoinedRoom response("", false, "Session not found");
741 return;
742 }
743
744 // Deserialize the AutoMatchmaking message
745 std::vector<uint8_t> payload = NetworkMessages::getPayload(event.packet->getData());
746 C2S::AutoMatchmaking msg = C2S::AutoMatchmaking::deserialize(payload);
747
748 // Note: This handler triggers matchmaking. Preference update is handled separately.
749 LOG_INFO("Auto-matchmaking requested by player ", playerId);
750
751 // If auto-matchmaking is disabled, just return
752 if (!msg.enabled) {
753 LOG_INFO("Auto-matchmaking disabled for player ", playerId);
754 return;
755 }
756
757 // Get all available public rooms
758 auto availableRooms = _roomManager->getPublicRooms();
759
760 std::shared_ptr<server::Room> targetRoom = nullptr;
761 bool joinAsSpectator = false;
762
763 // STRATEGY: If no rooms exist at all, create a new room immediately (like "Create Room")
764 if (availableRooms.empty()) {
765 LOG_INFO("No rooms available, creating new room for auto-matchmaking");
766
767 // Generate room ID
768 static std::atomic<uint32_t> nextAutoRoomId{1};
769 std::string roomId = "auto_" + std::to_string(nextAutoRoomId.fetch_add(1));
770
771 // Get player name for room title
772 std::string playerName = "Player";
773 auto usernameIt = _playerIdToUsername.find(playerId);
774 if (usernameIt != _playerIdToUsername.end()) {
775 playerName = usernameIt->second;
776 }
777
778 std::string roomName = playerName + "'s Room";
779 uint32_t maxPlayers = 4; // Default max players
780 bool isPrivate = false; // Public room
781
782 // Create the room via RoomManager (same as Create Room button)
783 targetRoom = _roomManager->createRoom(roomId, roomName, maxPlayers, isPrivate);
784
785 if (!targetRoom) {
786 LOG_ERROR("Failed to create auto-matchmaking room");
787 S2C::JoinedRoom response("", false, "Failed to create room");
789 return;
790 }
791
792 LOG_INFO("✓ Auto-matchmaking room '", roomName, "' (", roomId, ") created");
793 } else {
794 // Rooms exist - use MatchmakingService to find best match
795 auto matchmakingService = _roomManager->getMatchmaking();
796 if (!matchmakingService) {
797 LOG_ERROR("MatchmakingService not available");
798 S2C::JoinedRoom response("", false, "Matchmaking service unavailable");
800 return;
801 }
802
803 auto [foundRoom, spectator] =
804 matchmakingService->findOrCreateMatch(playerId, availableRooms, !isSpectator);
805
806 // If no immediate match found, player was added to queue
807 if (!foundRoom) {
808 LOG_INFO("Player ", playerId, " added to matchmaking queue");
809 S2C::JoinedRoom response(
810 "", true,
811 "Searching for match... You have been added to the matchmaking queue and "
812 "will be notified when a match is found.");
814 return;
815 }
816
817 targetRoom = foundRoom;
818 joinAsSpectator = spectator;
819 }
820
821 // Join the room (either newly created or found)
822 bool joinSuccess = false;
823 if (joinAsSpectator) {
824 joinSuccess = targetRoom->joinAsSpectator(playerId);
825 } else {
826 joinSuccess = targetRoom->join(playerId);
827 }
828
829 if (!joinSuccess) {
830 LOG_ERROR("Failed to join room '", targetRoom->getId(), "'");
831 S2C::JoinedRoom response("", false, "Failed to join room");
833 return;
834 }
835
836 std::string modeStr = joinAsSpectator ? " as spectator" : "";
837 LOG_INFO("Player ", playerId, " auto-matched to room '", targetRoom->getId(), "'", modeStr);
838
839 S2C::JoinedRoom response(targetRoom->getId(), true, "", joinAsSpectator);
841
842 // If player joined as spectator to an in-progress game, send them the current game state
843 if (joinAsSpectator && targetRoom->getState() == server::RoomState::IN_PROGRESS) {
844 LOG_INFO("Sending current game state to spectator ", playerId);
845 _sendGameStartToSpectator(playerId, targetRoom);
846 }
847
848 // Broadcast updated room state to all players in the room
849 _broadcastRoomState(targetRoom);
850
851 // Broadcast updated room list to ALL connected players
853}
854
855void Server::_onMatchmakingRoomCreated(std::shared_ptr<server::Room> room) {
856 using namespace RType::Messages;
857
858 LOG_INFO("[Matchmaking] Room '", room->getId(), "' created with ", room->getPlayerCount(),
859 " matched players");
860
861 // Get all players in the room and notify them
862 auto playerIds = room->getPlayers();
863 for (uint32_t playerId : playerIds) {
864 // Find the peer for this player
865 auto it = _playerIdToSessionId.find(playerId);
866 if (it == _playerIdToSessionId.end()) {
867 LOG_WARNING("Player ", playerId, " session not found for matchmaking notification");
868 continue;
869 }
870
871 const std::string &sessionId = it->second;
872 auto peerIt = _sessionPeers.find(sessionId);
873 if (peerIt == _sessionPeers.end()) {
874 LOG_WARNING("Player ", playerId, " peer not found for matchmaking notification");
875 continue;
876 }
877
878 IPeer *peer = peerIt->second;
879
880 // Send JoinedRoom success response
881 S2C::JoinedRoom response(room->getId(), true, "Match found!", false);
883
884 LOG_INFO("Notified player ", playerId, " of match in room '", room->getId(), "'");
885 }
886
887 // Broadcast room state to all players in the room
889
890 // Broadcast updated room list to ALL connected players
892}
893
895 using namespace RType::Messages;
896
897 auto session = _getSessionFromPeer(event.peer);
898 uint32_t playerId = 0;
899 if (session) {
900 playerId = session->getPlayerId();
901 }
902
903 if (playerId == 0) {
904 LOG_ERROR("Failed to find player for auto-matchmaking preference update");
905 return;
906 }
907
908 // Deserialize the AutoMatchmaking message
909 std::vector<uint8_t> payload = NetworkMessages::getPayload(event.packet->getData());
910 C2S::AutoMatchmaking msg = C2S::AutoMatchmaking::deserialize(payload);
911
912 // Get username from player ID mapping
913 auto usernameIt = _playerIdToUsername.find(playerId);
914 if (usernameIt != _playerIdToUsername.end()) {
915 const std::string &username = usernameIt->second;
916 // Update the user's auto-matchmaking preference (but not for guests)
917 if (username != "guest") {
918 _sessionManager->getAuthService()->updateAutoMatchmaking(username, msg.enabled);
919 LOG_INFO("✓ Updated auto-matchmaking preference for user '", username,
920 "': ", msg.enabled ? "ON" : "OFF", " (preference only, NO matchmaking triggered)");
921 } else {
922 LOG_INFO("Guest user - auto-matchmaking preference not saved");
923 }
924 } else {
925 LOG_WARNING("Username not found for player ", playerId);
926 }
927
928 // DO NOT trigger matchmaking here - this is only a preference update
929}
930
932 using namespace RType::Messages;
933
934 auto session = _getSessionFromPeer(event.peer);
935 uint32_t playerId = 0;
936 if (session) {
937 playerId = session->getPlayerId();
938 }
939
940 if (playerId == 0) {
941 LOG_WARNING("❌ Start game request from unknown session");
942 return;
943 }
944
945 std::shared_ptr<server::Room> playerRoom = _roomManager->getRoomByPlayer(playerId);
946
947 if (!playerRoom) {
948 LOG_WARNING("❌ Player ", playerId, " is not in any room");
949 return;
950 }
951 if (playerRoom->getHost() != playerId) {
952 LOG_WARNING("❌ Player ", playerId, " is not the host of room '", playerRoom->getId(), "'");
953 return;
954 }
955 if (playerRoom->getState() != server::RoomState::WAITING) {
956 LOG_WARNING("❌ Room '", playerRoom->getId(), "' is not in WAITING state");
957 return;
958 }
959
960 playerRoom->requestStartGame();
961 LOG_INFO("Room '", playerRoom->getId(), "' starting game");
962}
963
965 using namespace RType::Messages;
966
967 auto session = _getSessionFromPeer(event.peer);
968 uint32_t playerId = 0;
969 if (session) {
970 playerId = session->getPlayerId();
971 }
972
973 if (playerId == 0) {
974 LOG_WARNING("❌ Leave room request from unknown session");
975 return;
976 }
977
978 std::shared_ptr<server::Room> playerRoom = _roomManager->getRoomByPlayer(playerId);
979
980 if (!playerRoom) {
981 LOG_WARNING("Player ", playerId, " is not in any room");
982 return;
983 }
984
985 // Remove player from room
986 playerRoom->leave(playerId);
987 LOG_INFO("✓ Player ", playerId, " left room '", playerRoom->getId(), "'");
988
989 // Send LEFT_ROOM notification to the player who left
990 S2C::LeftRoom leftRoomMsg(playerId, S2C::LeftRoomReason::VOLUNTARY_LEAVE, "You left the room");
991 auto payload = leftRoomMsg.serialize();
993
994 // Check if room is now empty (no players, no spectators)
995 if (playerRoom->getPlayerCount() == 0 && playerRoom->getSpectators().empty()) {
996 std::string roomId = playerRoom->getId();
997 _roomManager->removeRoom(roomId);
998 LOG_INFO("✓ Room '", roomId, "' deleted (empty)");
999 } else {
1000 // Broadcast updated room state to remaining players in the room
1001 _broadcastRoomState(playerRoom);
1002 }
1003
1004 // Broadcast updated room list to ALL connected players
1006}
1007
1009 using namespace RType::Messages;
1010
1011 LOG_DEBUG("[Server] _handleChatMessage called");
1012
1013 auto session = _getSessionFromPeer(event.peer);
1014 uint32_t playerId = 0;
1015 std::string playerName = "Unknown";
1016
1017 if (session) {
1018 playerId = session->getPlayerId();
1019
1020 // Get player name from lobby
1021 const server::LobbyPlayer *lobbyPlayer = _lobby->getPlayer(playerId);
1022 playerName = lobbyPlayer ? lobbyPlayer->playerName : ("Player" + std::to_string(playerId));
1023 }
1024
1025 LOG_DEBUG("[Server] Chat message from player ", playerId, " (", playerName, ")");
1026
1027 if (playerId == 0) {
1028 LOG_WARNING("❌ Chat message from unknown session");
1029 return;
1030 }
1031
1032 // Get player's room
1033 std::shared_ptr<server::Room> playerRoom = _roomManager->getRoomByPlayer(playerId);
1034 if (!playerRoom) {
1035 LOG_WARNING("Player ", playerId, " is not in any room");
1036 return;
1037 }
1038
1039 // Deserialize chat message
1040 auto payload = NetworkMessages::getPayload(event.packet->getData());
1041 try {
1042 auto chatMsg = C2S::C2SChatMessage::deserialize(payload);
1043
1044 // Validate message length (prevent abuse from malicious clients)
1045 static constexpr size_t MAX_CHAT_MESSAGE_LENGTH = 256;
1046 if (chatMsg.message.length() > MAX_CHAT_MESSAGE_LENGTH) {
1047 LOG_WARNING("Player ", playerId, " sent a message exceeding the maximum length (",
1048 chatMsg.message.length(), " > ", MAX_CHAT_MESSAGE_LENGTH, ")");
1049 _sendSystemMessage(playerId, "Error: Message too long. Maximum length is 256 characters.");
1050 return;
1051 }
1052
1053 // Validate message is not empty (after trimming whitespace)
1054 if (chatMsg.message.empty() || std::all_of(chatMsg.message.begin(), chatMsg.message.end(),
1055 [](unsigned char c) { return std::isspace(c) != 0; })) {
1056 LOG_DEBUG("Player ", playerId, " sent an empty or whitespace-only message");
1057 return; // Silently ignore empty messages
1058 }
1059
1060 // Check if message is a command (starts with "/")
1061 if (!chatMsg.message.empty() && chatMsg.message[0] == '/') {
1062 // Command - process with CommandHandler
1063 LOG_INFO("[COMMAND] Player ", playerName, " (", playerId, "): ", chatMsg.message);
1064
1065 // Create command context
1066 server::CommandContext context(playerId, playerName, playerRoom, this);
1067
1068 // Execute command
1069 std::string response = _commandHandler->handleCommand(chatMsg.message, context);
1070
1071 // Send response to player
1072 if (!response.empty()) {
1073 _sendSystemMessage(playerId, response);
1074 }
1075
1076 return;
1077 }
1078
1079 LOG_INFO("[CHAT] Player ", playerName, " in room '", playerRoom->getId(), "': ", chatMsg.message);
1080
1081 // Call room method (for logging)
1082 playerRoom->broadcastChatMessage(playerId, playerName, chatMsg.message);
1083
1084 // Create S2C ChatMessage
1085 auto timestamp = std::chrono::duration_cast<std::chrono::milliseconds>(
1086 std::chrono::system_clock::now().time_since_epoch())
1087 .count();
1088
1089 S2C::S2CChatMessage chatResponse(playerId, playerName, chatMsg.message, timestamp);
1090 auto responsePayload = chatResponse.serialize();
1091 auto responsePacket =
1093
1094 // Broadcast to all players in the room
1095 std::vector<uint32_t> players = playerRoom->getPlayers();
1096 for (uint32_t targetPlayerId : players) {
1097 auto it = _playerIdToSessionId.find(targetPlayerId);
1098 if (it != _playerIdToSessionId.end()) {
1099 std::string sessionId = it->second;
1100 auto peerIt = _sessionPeers.find(sessionId);
1101 if (peerIt != _sessionPeers.end()) {
1103 responsePayload, true);
1104 }
1105 }
1106 }
1107
1108 LOG_INFO("✓ Chat message broadcast to ", players.size(), " players");
1109
1110 } catch (const std::exception &e) {
1111 LOG_ERROR("Failed to parse ChatMessage: ", e.what());
1112 }
1113}
1114
1115void Server::_sendSystemMessage(uint32_t playerId, const std::string &message) {
1116 using namespace RType::Messages;
1117
1118 // Split multi-line messages into individual messages
1119 std::istringstream iss(message);
1120 std::string line;
1121 int messageCount = 0;
1122
1123 while (std::getline(iss, line)) {
1124 // Skip empty lines
1125 if (line.empty()) {
1126 continue;
1127 }
1128
1129 // Create system chat message (playerId = 0 for system)
1130 auto timestamp = std::chrono::duration_cast<std::chrono::milliseconds>(
1131 std::chrono::system_clock::now().time_since_epoch())
1132 .count();
1133
1134 // Add small increment to timestamp for ordering
1135 timestamp += messageCount;
1136 messageCount++;
1137
1138 S2C::S2CChatMessage systemMsg(0, "SYSTEM", line, timestamp);
1139 auto payload = systemMsg.serialize();
1140
1141 // Find player's session and peer
1142 auto it = _playerIdToSessionId.find(playerId);
1143 if (it != _playerIdToSessionId.end()) {
1144 std::string sessionId = it->second;
1145 auto peerIt = _sessionPeers.find(sessionId);
1146 if (peerIt != _sessionPeers.end()) {
1147 _sendPacket(peerIt->second, NetworkMessages::MessageType::S2C_CHAT_MESSAGE, payload, true);
1148 }
1149 }
1150 }
1151
1152 if (messageCount > 0) {
1153 LOG_DEBUG("✓ Sent ", messageCount, " system message(s) to player ", playerId);
1154 }
1155}
1156
1157void Server::_sendKickedNotification(uint32_t playerId) {
1158 using namespace RType::Messages;
1159
1160 // Find player's session and peer
1161 auto it = _playerIdToSessionId.find(playerId);
1162 if (it == _playerIdToSessionId.end()) {
1163 LOG_WARNING("Cannot notify kicked player ", playerId, ": session not found");
1164 return;
1165 }
1166
1167 std::string sessionId = it->second;
1168 auto peerIt = _sessionPeers.find(sessionId);
1169 if (peerIt == _sessionPeers.end()) {
1170 LOG_WARNING("Cannot notify kicked player ", playerId, ": peer not found");
1171 return;
1172 }
1173
1174 IPeer *peer = peerIt->second;
1175
1176 // Send S2C_LEFT_ROOM with KICKED reason to the kicked player
1177 LOG_INFO("Sending LEFT_ROOM (KICKED) to player ", playerId);
1178
1179 S2C::LeftRoom leftRoomMsg(playerId, S2C::LeftRoomReason::KICKED, "You have been kicked by the host");
1180 auto payload = leftRoomMsg.serialize();
1181
1183
1184 LOG_DEBUG("✓ Kicked player ", playerId, " notified with S2C_LEFT_ROOM");
1185}
1186
1187void Server::notifyRoomUpdate(std::shared_ptr<server::Room> room) {
1188 if (!room) {
1189 LOG_WARNING("notifyRoomUpdate called with null room");
1190 return;
1191 }
1192
1193 LOG_DEBUG("Broadcasting room state update for room ", room->getId());
1194 _broadcastRoomState(room);
1195
1196 // Also update the room list in case player count changed
1198}
1199
1200bool Server::kickPlayer(uint32_t playerId) {
1201 // Find the player's room
1202 std::shared_ptr<server::Room> playerRoom = _roomManager->getRoomByPlayer(playerId);
1203 if (!playerRoom) {
1204 LOG_WARNING("Cannot kick player ", playerId, ": not in any room");
1205 return false;
1206 }
1207
1208 LOG_INFO("Kicking player ", playerId, " from room ", playerRoom->getId());
1209
1210 // Notify the kicked player BEFORE removing them
1211 _sendSystemMessage(playerId, "You have been kicked from the room by the host.");
1212
1213 // Despawn player from game if in progress
1214 std::shared_ptr<server::IGameLogic> gameLogic = playerRoom->getGameLogic();
1215 if (gameLogic) {
1216 gameLogic->despawnPlayer(playerId);
1217 }
1218
1219 // Remove from room
1220 playerRoom->leave(playerId);
1221
1222 // Send empty room state to kicked player to clear their UI
1223 _sendKickedNotification(playerId);
1224
1225 // Broadcast updated room state to remaining players
1226 _broadcastRoomState(playerRoom);
1228
1229 // Publish event
1230 _eventBus->publish(server::PlayerLeftEvent(playerId));
1231
1232 return true;
1233}
1234
1236 if (!_initialized) {
1237 LOG_ERROR("Cannot run: not initialized");
1238 return;
1239 }
1240
1241 LOG_INFO("========================================");
1242 LOG_INFO("R-Type server running!");
1243 LOG_INFO("Port: ", _port);
1244 LOG_INFO("Max clients: ", _maxClients);
1245 LOG_INFO("Press Ctrl+C to stop");
1246 LOG_INFO("========================================");
1247
1249
1250 _running = true;
1252
1253 while (_running && _networkManager->isRunning()) {
1254 float deltaTime = static_cast<float>(_frameTimer.tick());
1255
1256 _networkManager->processMessages();
1257
1258 if (_roomManager) {
1259 _roomManager->update(deltaTime);
1260
1261 auto rooms = _roomManager->getAllRooms();
1262 for (const auto &room : rooms) {
1263 if (room->getState() == server::RoomState::IN_PROGRESS && room->tryMarkGameStartSent()) {
1265
1266 // Broadcast updated room list since state changed to IN_PROGRESS
1268 }
1269 }
1270 }
1271
1274
1275 // Sleep to avoid busy-waiting (network processing is the bottleneck here)
1277 }
1278
1279 LOG_INFO("Server loop stopped.");
1280}
1281
1283 LOG_INFO("Stop requested...");
1284 _running = false;
1285
1286 // Stop all rooms (which will stop their game loops)
1287 if (_roomManager) {
1288 // Each room's destructor will stop its GameLoop
1289 LOG_INFO("✓ Stopping all rooms...");
1290 }
1291
1292 if (_networkManager) {
1293 _networkManager->stop();
1294 }
1295
1296 if (_eventBus) {
1297 _eventBus->clear();
1298 LOG_INFO("✓ EventBus cleared");
1299 }
1300}
1301
1303 using namespace RType::Messages;
1304
1305 // Get all rooms and broadcast each room's game state to its players
1306 auto rooms = _roomManager->getAllRooms();
1307
1308 for (const auto &room : rooms) {
1309 server::ServerLoop *roomLoop = room->getServerLoop();
1310 if (!roomLoop) {
1311 continue;
1312 }
1313
1314 std::shared_ptr<ecs::wrapper::ECSWorld> ecsWorld = roomLoop->getECSWorld();
1315 if (!ecsWorld) {
1316 continue;
1317 }
1318
1319 std::shared_ptr<server::IGameLogic> gameLogic = room->getGameLogic();
1320
1321 S2C::GameState state;
1322 state.serverTick = roomLoop->getCurrentTick();
1323
1324 // Get all entities with Transform component
1325 auto entities = ecsWorld->query<ecs::Transform>();
1326
1327 // Serialize each entity's state
1328 for (auto &entity : entities) {
1329 try {
1330 S2C::EntityState entityState = _serializeEntity(entity, gameLogic.get());
1331 state.entities.push_back(entityState);
1332 } catch (const std::exception &e) {
1333 LOG_ERROR("Failed to serialize entity: ", e.what());
1334 continue;
1335 }
1336 }
1337
1338 // Serialize and create packet
1339 std::vector<uint8_t> payload = state.serialize();
1340 std::vector<uint8_t> packet =
1342
1343 // Broadcast to both players and spectators in this room
1344 auto players = room->getPlayers();
1345 auto spectators = room->getSpectators();
1346
1347 // Combine players and spectators
1348 std::vector<uint32_t> allRecipients = players;
1349 allRecipients.insert(allRecipients.end(), spectators.begin(), spectators.end());
1350
1351 for (uint32_t recipientId : allRecipients) {
1352 auto sessionIt = _playerIdToSessionId.find(recipientId);
1353 if (sessionIt == _playerIdToSessionId.end()) {
1354 continue;
1355 }
1356 const std::string &sessionId = sessionIt->second;
1357
1358 auto peerIt = _sessionPeers.find(sessionId);
1359 if (peerIt != _sessionPeers.end() && peerIt->second) {
1360 std::unique_ptr<IPacket> peerPacket =
1361 createPacket(packet, static_cast<int>(PacketFlag::UNSEQUENCED));
1362 peerIt->second->send(std::move(peerPacket), 0);
1363 }
1364 }
1365 }
1366}
1367
1369 using namespace RType::Messages;
1370
1371 auto rooms = _roomManager->getAllRooms();
1372
1373 for (const auto &room : rooms) {
1374 server::ServerLoop *roomLoop = room->getServerLoop();
1375 if (!roomLoop) {
1376 continue;
1377 }
1378
1379 std::shared_ptr<ecs::wrapper::ECSWorld> ecsWorld = roomLoop->getECSWorld();
1380 if (!ecsWorld) {
1381 continue;
1382 }
1383
1384 // Find all entities marked for destruction
1385 auto pendingEntities = ecsWorld->query<ecs::PendingDestroy>();
1386
1387 if (pendingEntities.empty()) {
1388 continue;
1389 }
1390
1391 LOG_DEBUG("[ProcessPendingDestructions] Room '", room->getId(), "' - Found ", pendingEntities.size(),
1392 " entities pending destruction");
1393
1394 // Get all recipients for this room
1395 auto players = room->getPlayers();
1396 auto spectators = room->getSpectators();
1397 std::vector<uint32_t> allRecipients = players;
1398 allRecipients.insert(allRecipients.end(), spectators.begin(), spectators.end());
1399
1400 // Process each entity marked for destruction
1401 std::vector<ecs::Address> toDestroy;
1402 for (auto &entity : pendingEntities) {
1403 ecs::Address entityId = entity.getAddress();
1404 ecs::PendingDestroy &pendingDestroy = entity.get<ecs::PendingDestroy>();
1405
1406 // Convert internal DestroyReason to network DestroyReason
1407 Shared::DestroyReason networkReason;
1408 switch (pendingDestroy.getReason()) {
1410 networkReason = Shared::DestroyReason::OutOfBounds;
1411 break;
1413 networkReason = Shared::DestroyReason::KilledByPlayer;
1414 break;
1416 networkReason = Shared::DestroyReason::Collision;
1417 break;
1419 default:
1420 networkReason = Shared::DestroyReason::OutOfBounds;
1421 break;
1422 }
1423
1424 // Create and serialize EntityDestroyed message
1425 S2C::EntityDestroyed destroyedMsg(entityId, networkReason);
1426 std::vector<uint8_t> payload = destroyedMsg.serialize();
1427 std::vector<uint8_t> packet =
1429
1430 // Send to all recipients in the room
1431 for (uint32_t recipientId : allRecipients) {
1432 auto sessionIt = _playerIdToSessionId.find(recipientId);
1433 if (sessionIt == _playerIdToSessionId.end()) {
1434 continue;
1435 }
1436 const std::string &sessionId = sessionIt->second;
1437
1438 auto peerIt = _sessionPeers.find(sessionId);
1439 if (peerIt != _sessionPeers.end() && peerIt->second) {
1440 std::unique_ptr<IPacket> peerPacket =
1441 createPacket(packet, static_cast<int>(PacketFlag::RELIABLE));
1442 peerIt->second->send(std::move(peerPacket), 0);
1443 }
1444 }
1445
1446 LOG_DEBUG("[ProcessPendingDestructions] Sent EntityDestroyed for entity ", entityId);
1447 toDestroy.push_back(entityId);
1448 }
1449
1450 // Now actually destroy the entities
1451 for (ecs::Address entityId : toDestroy) {
1452 ecsWorld->destroyEntity(entityId);
1453 }
1454
1455 if (!toDestroy.empty()) {
1456 LOG_INFO("[ProcessPendingDestructions] Destroyed ", toDestroy.size(), " entities in room '",
1457 room->getId(), "'");
1458 }
1459 }
1460}
1461
1462void Server::_sendGameStartToRoom(std::shared_ptr<server::Room> room) {
1463 using namespace RType::Messages;
1464
1465 if (!room || room->getState() != server::RoomState::IN_PROGRESS) {
1466 return;
1467 }
1468
1469 server::ServerLoop *roomLoop = room->getServerLoop();
1470 if (!roomLoop) {
1471 LOG_ERROR("ServerLoop not available for room ", room->getId());
1472 return;
1473 }
1474
1475 std::shared_ptr<ecs::wrapper::ECSWorld> ecsWorld = roomLoop->getECSWorld();
1476 if (!ecsWorld) {
1477 LOG_ERROR("ECSWorld not available for room ", room->getId());
1478 return;
1479 }
1480
1481 std::shared_ptr<server::IGameLogic> gameLogic = room->getGameLogic();
1482 if (!gameLogic) {
1483 LOG_ERROR("GameLogic not available for room ", room->getId());
1484 return;
1485 }
1486
1487 // Use helper to serialize all entities once
1488 auto entities = _serializeEntities(ecsWorld, gameLogic.get());
1489
1490 // (Re)send gamerules right before the game starts, in case they are room-specific.
1491 auto sendRulesToRecipient = [&](uint32_t recipientId) {
1492 auto sessionIt = _playerIdToSessionId.find(recipientId);
1493 if (sessionIt == _playerIdToSessionId.end()) {
1494 return;
1495 }
1496 const std::string &sessionId = sessionIt->second;
1497
1498 auto peerIt = _sessionPeers.find(sessionId);
1499 if (peerIt == _sessionPeers.end() || !peerIt->second) {
1500 return;
1501 }
1502 server::GameruleBroadcaster::sendAllGamerules(peerIt->second, gameLogic->getGameRules());
1503
1504 // Also send the game speed multiplier for this room
1505 float gameSpeedMultiplier = room->getGameSpeedMultiplier();
1507 gameSpeedMultiplier);
1508 };
1509
1510 // Get map configuration from ECS world
1511 S2C::MapConfig mapConfig;
1512 auto mapEntities = ecsWorld->query<ecs::MapData>();
1513 if (!mapEntities.empty()) {
1514 auto &mapData = mapEntities[0].get<ecs::MapData>();
1515 mapConfig.background = mapData.getBackgroundSprite();
1516 mapConfig.parallaxBackground = mapData.getParallaxBackgroundSprite();
1517 mapConfig.scrollSpeed = mapData.getScrollSpeed();
1518 mapConfig.parallaxSpeedFactor = mapData.getParallaxSpeedFactor();
1519 LOG_DEBUG("Map config for GameStart: bg='", mapConfig.background, "', parallax='",
1520 mapConfig.parallaxBackground, "', speed=", mapConfig.scrollSpeed,
1521 ", parallaxFactor=", mapConfig.parallaxSpeedFactor);
1522 }
1523
1524 // Helper to send game start
1525 auto sendGameStart = [&](uint32_t playerId, uint32_t entityId) {
1526 S2C::GameStart gameStart;
1527 gameStart.yourEntityId = entityId;
1528 gameStart.initialState.serverTick = roomLoop->getCurrentTick();
1529 gameStart.initialState.entities = entities;
1530 gameStart.mapConfig = mapConfig;
1531
1532 auto sessionIt = _playerIdToSessionId.find(playerId);
1533 if (sessionIt != _playerIdToSessionId.end()) {
1534 const std::string &sessionId = sessionIt->second;
1535 auto peerIt = _sessionPeers.find(sessionId);
1536
1537 if (peerIt != _sessionPeers.end() && peerIt->second) {
1539 gameStart.serialize());
1540
1541 if (entityId != 0) {
1542 LOG_INFO("✓ Sent GameStart to player ", playerId, " (entity: ", entityId,
1543 ", room: ", room->getId(), ")");
1544 } else {
1545 LOG_INFO("✓ Sent GameStart to spectator ", playerId, " (room: ", room->getId(), ")");
1546 }
1547 }
1548 }
1549 };
1550
1551 // Get all players in room
1552 auto players = room->getPlayers();
1553 auto spectators = room->getSpectators();
1554
1555 for (uint32_t playerId : players) {
1556 sendRulesToRecipient(playerId);
1557
1558 // Find entity ID for this player
1559 uint32_t entityId = 0;
1560 auto playerEntities = ecsWorld->query<ecs::Transform, ecs::Player>();
1561 for (auto &entity : playerEntities) {
1562 auto &player = entity.get<ecs::Player>();
1563 if (player.getPlayerId() == static_cast<int>(playerId)) {
1564 entityId = entity.getAddress();
1565 break;
1566 }
1567 }
1568
1569 if (entityId == 0) {
1570 LOG_ERROR("Failed to find entity for player ", playerId);
1571 continue;
1572 }
1573
1574 sendGameStart(playerId, entityId);
1575 }
1576
1577 // Send to spectators (with entityId = 0)
1578 for (uint32_t spectatorId : spectators) {
1579 sendGameStart(spectatorId, 0);
1580 }
1581}
1582
1583void Server::_sendGameStartToSpectator(uint32_t spectatorId, std::shared_ptr<server::Room> room) {
1584 using namespace RType::Messages;
1585
1586 if (!room || room->getState() != server::RoomState::IN_PROGRESS) {
1587 LOG_ERROR("Cannot send game start to spectator: room not in progress");
1588 return;
1589 }
1590
1591 server::ServerLoop *roomLoop = room->getServerLoop();
1592 if (!roomLoop) {
1593 LOG_ERROR("ServerLoop not available for room ", room->getId());
1594 return;
1595 }
1596
1597 std::shared_ptr<ecs::wrapper::ECSWorld> ecsWorld = roomLoop->getECSWorld();
1598 if (!ecsWorld) {
1599 LOG_ERROR("ECSWorld not available for room ", room->getId());
1600 return;
1601 }
1602
1603 std::shared_ptr<server::IGameLogic> gameLogic = room->getGameLogic();
1604 if (!gameLogic) {
1605 LOG_ERROR("GameLogic not available for room ", room->getId());
1606 return;
1607 }
1608
1609 // Get the peer for this spectator
1610 auto sessionIt = _playerIdToSessionId.find(spectatorId);
1611 if (sessionIt == _playerIdToSessionId.end()) {
1612 LOG_ERROR("Cannot find session for spectator ", spectatorId);
1613 return;
1614 }
1615
1616 const std::string &sessionId = sessionIt->second;
1617 auto peerIt = _sessionPeers.find(sessionId);
1618 if (peerIt == _sessionPeers.end() || !peerIt->second) {
1619 LOG_ERROR("Cannot find peer for spectator ", spectatorId);
1620 return;
1621 }
1622
1623 // Send game rules first
1624 server::GameruleBroadcaster::sendAllGamerules(peerIt->second, gameLogic->getGameRules());
1625
1626 // Serialize all entities
1627 auto entities = _serializeEntities(ecsWorld, gameLogic.get());
1628
1629 // Get map configuration from ECS world
1630 S2C::MapConfig mapConfig;
1631 auto mapEntities = ecsWorld->query<ecs::MapData>();
1632 if (!mapEntities.empty()) {
1633 auto &mapData = mapEntities[0].get<ecs::MapData>();
1634 mapConfig.background = mapData.getBackgroundSprite();
1635 mapConfig.parallaxBackground = mapData.getParallaxBackgroundSprite();
1636 mapConfig.scrollSpeed = mapData.getScrollSpeed();
1637 mapConfig.parallaxSpeedFactor = mapData.getParallaxSpeedFactor();
1638 }
1639
1640 // Send GameStart with entityId = 0 (spectator has no controllable entity)
1641 S2C::GameStart gameStart;
1642 gameStart.yourEntityId = 0;
1643 gameStart.initialState.serverTick = roomLoop->getCurrentTick();
1644 gameStart.initialState.entities = entities;
1645 gameStart.mapConfig = mapConfig;
1646
1648
1649 LOG_INFO("✓ Sent GameStart to spectator ", spectatorId, " (room: ", room->getId(),
1650 ", entities: ", entities.size(), ")");
1651}
1652
1653void Server::_broadcastRoomState(std::shared_ptr<server::Room> room) {
1654 using namespace RType::Messages;
1655
1656 if (!room) {
1657 return;
1658 }
1659
1660 // Create RoomState message
1661 S2C::RoomState roomState;
1662 roomState.roomId = room->getId();
1663 roomState.roomName = room->getName();
1664 roomState.maxPlayers = static_cast<uint32_t>(room->getMaxPlayers());
1665 roomState.state = static_cast<uint8_t>(room->getState());
1666
1667 // Get all players and spectators
1668 auto players = room->getPlayers();
1669 auto spectators = room->getSpectators();
1670 uint32_t hostId = room->getHost();
1671
1672 roomState.currentPlayers = static_cast<uint32_t>(players.size() + spectators.size());
1673
1674 // Build player list
1675 for (uint32_t playerId : players) {
1676 auto sessionIt = _playerIdToSessionId.find(playerId);
1677 if (sessionIt == _playerIdToSessionId.end()) {
1678 continue;
1679 }
1680
1681 auto session = _sessionManager->getSession(sessionIt->second);
1682 if (!session) {
1683 continue;
1684 }
1685
1686 S2C::PlayerData playerData;
1687 playerData.playerId = playerId;
1688
1689 // Get player name from lobby
1690 const server::LobbyPlayer *lobbyPlayer = _lobby->getPlayer(playerId);
1691 playerData.playerName = lobbyPlayer ? lobbyPlayer->playerName : ("Player" + std::to_string(playerId));
1692
1693 playerData.isHost = (playerId == hostId);
1694 playerData.isSpectator = false;
1695
1696 roomState.players.push_back(playerData);
1697 }
1698
1699 // Add spectators
1700 for (uint32_t spectatorId : spectators) {
1701 auto sessionIt = _playerIdToSessionId.find(spectatorId);
1702 if (sessionIt == _playerIdToSessionId.end()) {
1703 continue;
1704 }
1705
1706 auto session = _sessionManager->getSession(sessionIt->second);
1707 if (!session) {
1708 continue;
1709 }
1710
1711 S2C::PlayerData playerData;
1712 playerData.playerId = spectatorId;
1713
1714 // Get player name from lobby
1715 const server::LobbyPlayer *lobbyPlayer = _lobby->getPlayer(spectatorId);
1716 playerData.playerName =
1717 lobbyPlayer ? lobbyPlayer->playerName : ("Spectator" + std::to_string(spectatorId));
1718
1719 playerData.isHost = false;
1720 playerData.isSpectator = true;
1721
1722 roomState.players.push_back(playerData);
1723 }
1724
1725 // Serialize message
1726 std::vector<uint8_t> payload = roomState.serialize();
1727
1728 // Send to all players and spectators
1729 std::vector<uint32_t> allRecipients = players;
1730 allRecipients.insert(allRecipients.end(), spectators.begin(), spectators.end());
1731
1732 for (uint32_t recipientId : allRecipients) {
1733 auto sessionIt = _playerIdToSessionId.find(recipientId);
1734 if (sessionIt == _playerIdToSessionId.end()) {
1735 continue;
1736 }
1737
1738 const std::string &sessionId = sessionIt->second;
1739 auto peerIt = _sessionPeers.find(sessionId);
1740
1741 if (peerIt != _sessionPeers.end() && peerIt->second) {
1743 }
1744 }
1745
1746 LOG_INFO("✓ Broadcast RoomState to ", allRecipients.size(), " players in room '", room->getId(), "'");
1747}
1748
1750 server::IGameLogic *gameLogic) {
1751 using namespace RType::Messages;
1752
1753 S2C::EntityState entityState;
1754 entityState.entityId = entity.getAddress();
1755
1756 // Get Transform (required - entity was queried with it, but might be destroyed by now)
1757 if (!entity.has<ecs::Transform>()) {
1758 throw std::runtime_error("Entity no longer has Transform component");
1759 }
1760 ecs::Transform &transform = entity.get<ecs::Transform>();
1761 entityState.position.x = transform.getPosition().x;
1762 entityState.position.y = transform.getPosition().y;
1763
1764 // Get current animation if available
1765 if (entity.has<ecs::Animation>()) {
1766 entityState.currentAnimation = entity.get<ecs::Animation>().getCurrentClipName();
1767 } else {
1768 entityState.currentAnimation = "idle"; // Default fallback
1769 }
1770
1771 // Get sprite info if available
1772 if (entity.has<ecs::Sprite>()) {
1773 ecs::Sprite &sprite = entity.get<ecs::Sprite>();
1774 const auto &rect = sprite.getSourceRect();
1775 entityState.spriteX = rect.x;
1776 entityState.spriteY = rect.y;
1777 entityState.spriteW = rect.width;
1778 entityState.spriteH = rect.height;
1779 } else {
1780 // Default sprite coords (player ship)
1781 entityState.spriteX = 0;
1782 entityState.spriteY = 0;
1783 entityState.spriteW = 33;
1784 entityState.spriteH = 17;
1785 }
1786
1787 // Determine entity type and get health
1788 if (entity.has<ecs::Player>()) {
1789 entityState.type = Shared::EntityType::Player;
1790 entityState.health = entity.has<ecs::Health>() ? entity.get<ecs::Health>().getCurrentHealth() : -1;
1791
1792 // Retrieve last processed sequence ID for this player
1793 if (gameLogic) {
1794 ecs::Player &player = entity.get<ecs::Player>();
1795 entityState.lastProcessedInput = gameLogic->getLastProcessedInput(player.getPlayerId());
1796 }
1797 } else if (entity.has<ecs::Enemy>()) {
1798 ecs::Enemy &enemy = entity.get<ecs::Enemy>();
1799 // Map enemy type to EntityType enum (simplified)
1800 entityState.type =
1801 (enemy.getEnemyType() == 0) ? Shared::EntityType::EnemyType1 : Shared::EntityType::EnemyType1;
1802 entityState.health = entity.has<ecs::Health>() ? entity.get<ecs::Health>().getCurrentHealth() : -1;
1803 } else if (entity.has<ecs::Projectile>()) {
1804 ecs::Projectile &projectile = entity.get<ecs::Projectile>();
1805 entityState.type =
1806 projectile.isFriendly() ? Shared::EntityType::PlayerBullet : Shared::EntityType::EnemyBullet;
1807 entityState.health = -1; // Projectiles don't have health
1808 } else if (entity.has<ecs::Wall>()) {
1809 entityState.type = Shared::EntityType::Wall;
1810 entityState.health = entity.has<ecs::Health>() ? entity.get<ecs::Health>().getCurrentHealth() : -1;
1811 } else if (entity.has<ecs::OrbitalModule>()) {
1812 entityState.type = Shared::EntityType::OrbitalModule;
1813 entityState.health = entity.has<ecs::Health>() ? entity.get<ecs::Health>().getCurrentHealth() : -1;
1814 } else {
1815 // Unknown entity type - default to generic
1816 entityState.type = Shared::EntityType::Player;
1817 entityState.health = -1;
1818 }
1819
1820 return entityState;
1821}
1822
1823void Server::_actionToInput(RType::Messages::Shared::Action action, int &dx, int &dy, bool &shoot) {
1825 switch (action) {
1826 case MoveUp:
1827 dy = -1;
1828 break;
1829 case MoveDown:
1830 dy = 1;
1831 break;
1832 case MoveLeft:
1833 dx = -1;
1834 break;
1835 case MoveRight:
1836 dx = 1;
1837 break;
1838 case Shoot:
1839 shoot = true;
1840 break;
1841 }
1842}
1843
1844std::shared_ptr<server::Session> Server::_getSessionFromPeer(IPeer *peer) {
1845 auto it = _peerToSession.find(peer);
1846 if (it == _peerToSession.end()) {
1847 return nullptr;
1848 }
1849 return _sessionManager->getSession(it->second);
1850}
1851
1852void Server::_sendPacket(IPeer *peer, NetworkMessages::MessageType type, const std::vector<uint8_t> &payload,
1853 bool reliable) {
1854 using namespace RType::Messages;
1855 if (!peer)
1856 return;
1857
1858 std::vector<uint8_t> packet = NetworkMessages::createMessage(type, payload);
1859 std::unique_ptr<IPacket> netPacket =
1860 createPacket(packet, static_cast<int>(reliable ? PacketFlag::RELIABLE : PacketFlag::UNSEQUENCED));
1861 peer->send(std::move(netPacket), 0);
1862}
1863
1864std::vector<RType::Messages::S2C::EntityState> Server::_serializeEntities(
1865 std::shared_ptr<ecs::wrapper::ECSWorld> world, server::IGameLogic *gameLogic) {
1866 std::vector<RType::Messages::S2C::EntityState> entities;
1867 if (!world)
1868 return entities;
1869
1870 auto transforms = world->query<ecs::Transform>();
1871 for (auto &entity : transforms) {
1872 try {
1873 entities.push_back(_serializeEntity(entity, gameLogic));
1874 } catch (const std::exception &e) {
1875 LOG_ERROR("Failed to serialize entity: ", e.what());
1876 }
1877 }
1878 return entities;
1879}
1880
1881void Server::_broadcastRoomList(const std::vector<IPeer *> &specificPeers) {
1882 using namespace RType::Messages;
1883
1884 auto publicRooms = _roomManager->getPublicRooms();
1885
1886 S2C::RoomList roomList;
1887 for (const auto &room : publicRooms) {
1888 S2C::RoomInfoData info;
1889 info.roomId = room->getId();
1890 info.roomName = room->getName();
1891 info.playerCount = static_cast<uint32_t>(room->getPlayerCount());
1892 info.maxPlayers = static_cast<uint32_t>(room->getMaxPlayers());
1893 info.isPrivate = room->isPrivate();
1894
1895 auto state = room->getState();
1896 if (state == server::RoomState::WAITING)
1897 info.state = 0;
1898 else if (state == server::RoomState::STARTING)
1899 info.state = 1;
1900 else if (state == server::RoomState::IN_PROGRESS)
1901 info.state = 2;
1902 else if (state == server::RoomState::FINISHED)
1903 info.state = 3;
1904 else
1905 info.state = 0;
1906
1907 roomList.rooms.push_back(info);
1908 }
1909
1910 std::vector<uint8_t> payload = roomList.serialize();
1911
1912 // Send to specific peers (used when a player requests the list explicitly)
1913 for (IPeer *peer : specificPeers) {
1914 if (peer) {
1916 }
1917 }
1918
1919 if (!specificPeers.empty()) {
1920 LOG_INFO("✓ Sent RoomList to ", specificPeers.size(), " specific peer(s)");
1921 }
1922}
1923
1925 using namespace RType::Messages;
1926
1927 auto publicRooms = _roomManager->getPublicRooms();
1928
1929 S2C::RoomList roomList;
1930 for (const auto &room : publicRooms) {
1931 S2C::RoomInfoData info;
1932 info.roomId = room->getId();
1933 info.roomName = room->getName();
1934 info.playerCount = static_cast<uint32_t>(room->getPlayerCount());
1935 info.maxPlayers = static_cast<uint32_t>(room->getMaxPlayers());
1936 info.isPrivate = room->isPrivate();
1937
1938 auto state = room->getState();
1939 if (state == server::RoomState::WAITING)
1940 info.state = 0;
1941 else if (state == server::RoomState::STARTING)
1942 info.state = 1;
1943 else if (state == server::RoomState::IN_PROGRESS)
1944 info.state = 2;
1945 else if (state == server::RoomState::FINISHED)
1946 info.state = 3;
1947 else
1948 info.state = 0;
1949
1950 roomList.rooms.push_back(info);
1951 }
1952
1953 std::vector<uint8_t> payload = roomList.serialize();
1954
1955 // Broadcast to ALL connected players (everyone on the server sees all public rooms)
1956 // Players can be in lobby OR in a room, they still see the full room list
1957 size_t sentCount = 0;
1958 for (const auto &[sessionId, peer] : _sessionPeers) {
1959 if (peer) {
1961 sentCount++;
1962 }
1963 }
1964
1965 LOG_INFO("✓ Broadcast RoomList to ", sentCount, " connected player(s)");
1966}
@ GAME_SPEED_MULTIPLIER
Game speed multiplier (0.25 to 1.0, accessibility feature)
@ DISCONNECT
A peer has disconnected.
@ UNSEQUENCED
Packet will not be sequenced with other packets.
@ RELIABLE
Packet must be received by the target peer and resent if dropped.
#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
bool initializeNetworking()
Initialize the networking subsystem.
void deinitializeNetworking()
Cleanup the networking subsystem.
std::unique_ptr< IPacket > createPacket(const std::vector< uint8_t > &data, uint32_t flags)
Create a network packet with the given data and flags.
Interface representing a remote peer in the network.
Definition IPeer.hpp:40
virtual bool send(std::unique_ptr< IPacket > packet, uint8_t channelID=0)=0
Send a packet to this peer.
float gameSpeedMultiplier
Game speed multiplier (0.25 to 1.0, default 1.0)
Request to login with existing account.
Player input message sent from client to server (with redundancy)
std::vector< InputSnapshot > inputs
Request to register a new user account.
std::vector< uint8_t > serialize() const
Entity destruction notification.
std::vector< uint8_t > serialize() const
State of a single entity.
std::optional< int32_t > health
Game over notification.
Definition GameOver.hpp:25
std::vector< uint8_t > serialize() const
Definition GameOver.hpp:32
Game start notification from server to client.
Definition GameStart.hpp:44
std::vector< uint8_t > serialize() const
Definition GameStart.hpp:52
Complete snapshot of the game world.
Definition GameState.hpp:31
std::vector< uint8_t > serialize() const
Definition GameState.hpp:38
std::vector< EntityState > entities
Definition GameState.hpp:34
std::vector< uint8_t > serialize() const
Notification that a player has left a room.
Definition LeftRoom.hpp:38
std::vector< uint8_t > serialize() const
Serialize to byte vector.
Definition LeftRoom.hpp:51
Server response to login request.
std::vector< uint8_t > serialize() const
Serialize to Cap'n Proto binary format.
Server response to registration request.
std::vector< uint8_t > serialize() const
Serialize to Cap'n Proto binary format.
std::vector< uint8_t > serialize() const
std::vector< uint8_t > serialize() const
Definition RoomList.hpp:33
std::vector< RoomInfoData > rooms
Definition RoomList.hpp:29
std::vector< uint8_t > serialize() const
Definition RoomState.hpp:38
std::vector< PlayerData > players
Definition RoomState.hpp:33
Chat message sent from server to clients.
std::vector< uint8_t > serialize() const
Serialize to byte vector.
std::shared_ptr< server::EventBus > _eventBus
Definition Server.hpp:319
void _sendGameStartToRoom(std::shared_ptr< server::Room > room)
Send GameStart message to all players in a room.
Definition Server.cpp:1462
std::shared_ptr< server::RoomManager > _roomManager
Definition Server.hpp:323
void _handleLoginRequest(HostNetworkEvent &event)
Handle player login request.
Definition Server.cpp:456
void stop()
Stop the server.
Definition Server.cpp:1282
std::unordered_map< uint32_t, std::string > _playerIdToUsername
Definition Server.hpp:336
void _handleAutoMatchmaking(HostNetworkEvent &event)
Handle auto-matchmaking request.
Definition Server.cpp:726
void _handlePlayerInput(HostNetworkEvent &event)
Handle player input packet.
Definition Server.cpp:516
bool _initialized
Definition Server.hpp:338
std::vector< RType::Messages::S2C::EntityState > _serializeEntities(std::shared_ptr< ecs::wrapper::ECSWorld > world, server::IGameLogic *gameLogic=nullptr)
Helper to serialize all entities in a world.
Definition Server.cpp:1864
server::FrameTimer _frameTimer
Definition Server.hpp:342
void _broadcastRoomListToAll()
Broadcast room list to ALL connected players on the server.
Definition Server.cpp:1924
~Server()
Destructor - clean shutdown.
Definition Server.cpp:52
void _handleStartGame(HostNetworkEvent &event)
Handle start game request (from room host)
Definition Server.cpp:931
void _broadcastRoomState(std::shared_ptr< server::Room > room)
Broadcast room state (player list) to all players in a room.
Definition Server.cpp:1653
void handlePacket(HostNetworkEvent &event)
Handle incoming packet from client.
Definition Server.cpp:148
void _actionToInput(RType::Messages::Shared::Action action, int &dx, int &dy, bool &shoot)
Convert Action enum to directional input (dx, dy)
Definition Server.cpp:1823
void _handleChatMessage(HostNetworkEvent &event)
Handle chat message from client.
Definition Server.cpp:1008
void _handleLeaveRoom(HostNetworkEvent &event)
Handle leave room request.
Definition Server.cpp:964
std::shared_ptr< server::SessionManager > _sessionManager
Definition Server.hpp:322
void _sendPacket(IPeer *peer, NetworkMessages::MessageType type, const std::vector< uint8_t > &payload, bool reliable=true)
Helper to send a packet to a peer.
Definition Server.cpp:1852
void notifyRoomUpdate(std::shared_ptr< server::Room > room)
Notify all players in a room of state changes (for commands)
Definition Server.cpp:1187
void _handleJoinRoom(HostNetworkEvent &event)
Handle join room request.
Definition Server.cpp:650
void _handleDisconnect(HostNetworkEvent &event)
Handle player disconnect.
Definition Server.cpp:222
std::unordered_map< std::string, IPeer * > _sessionPeers
Definition Server.hpp:330
bool kickPlayer(uint32_t playerId)
Kick a player from their current room (for commands)
Definition Server.cpp:1200
RType::Messages::S2C::EntityState _serializeEntity(ecs::wrapper::Entity &entity, server::IGameLogic *gameLogic=nullptr)
Serialize a single entity to network format.
Definition Server.cpp:1749
void _broadcastRoomList(const std::vector< IPeer * > &specificPeers)
Broadcast room list to specific peers only.
Definition Server.cpp:1881
std::shared_ptr< server::Session > _getSessionFromPeer(IPeer *peer)
Helper to get session from peer.
Definition Server.cpp:1844
void _handleHandshakeRequest(HostNetworkEvent &event)
Handle player handshake/connection request.
Definition Server.cpp:266
size_t _maxClients
Definition Server.hpp:316
void _onMatchmakingRoomCreated(std::shared_ptr< server::Room > room)
Called when matchmaking service creates a room with matched players.
Definition Server.cpp:855
void _sendKickedNotification(uint32_t playerId)
Send room list to kicked player to return them to lobby.
Definition Server.cpp:1157
std::unordered_map< IPeer *, std::string > _peerToSession
Definition Server.hpp:332
void _processPendingDestructions()
Process entities marked with PendingDestroy component.
Definition Server.cpp:1368
bool _running
Definition Server.hpp:339
void _broadcastGameState()
Broadcast game state to all connected clients.
Definition Server.cpp:1302
void run()
Run the server (blocking until exit)
Definition Server.cpp:1235
void _sendSystemMessage(uint32_t playerId, const std::string &message)
Send a system message to a specific player.
Definition Server.cpp:1115
Server(uint16_t port, size_t maxClients=32)
Constructor.
Definition Server.cpp:50
std::unordered_map< uint32_t, std::string > _playerIdToSessionId
Definition Server.hpp:334
std::unique_ptr< server::CommandHandler > _commandHandler
Definition Server.hpp:325
void _sendGameStartToSpectator(uint32_t spectatorId, std::shared_ptr< server::Room > room)
Send GameStart message to a spectator joining an in-progress game.
Definition Server.cpp:1583
void _handleCreateRoom(HostNetworkEvent &event)
Handle create room request.
Definition Server.cpp:587
std::shared_ptr< server::Lobby > _lobby
Definition Server.hpp:324
bool initialize()
Initialize server systems.
Definition Server.cpp:58
uint16_t _port
Definition Server.hpp:315
std::unique_ptr< ServerNetworkManager > _networkManager
Definition Server.hpp:318
void _handleUpdateAutoMatchmakingPref(HostNetworkEvent &event)
Handle auto-matchmaking preference update (without triggering matchmaking)
Definition Server.cpp:894
void _handleRegisterRequest(HostNetworkEvent &event)
Handle player registration request.
Definition Server.cpp:422
void _handleListRooms(HostNetworkEvent &event)
Handle list rooms request.
Definition Server.cpp:578
Component managing current animation playback state.
Definition Animation.hpp:21
Component identifying an entity as an enemy with AI behavior.
Definition Enemy.hpp:20
int getEnemyType() const
Get enemy type.
Definition Enemy.hpp:34
Component representing entity health and invincibility.
Definition Health.hpp:20
int getCurrentHealth() const
Get current health points.
Definition Health.hpp:44
Component storing information about the current map/level.
Definition MapData.hpp:24
Component for entities that orbit around a parent entity (like drones in Isaac).
Marker component indicating entity should be destroyed.
DestroyReason getReason() const
Get the destruction reason.
Component identifying an entity as a player with game statistics.
Definition Player.hpp:20
int getPlayerId() const
Get player ID.
Definition Player.hpp:47
Component for projectile entities (bullets, missiles, etc.).
bool isFriendly() const
Check if projectile is friendly.
Component representing a visual sprite from a texture.
Definition Sprite.hpp:32
Rectangle getSourceRect() const
Get the source rectangle.
Definition Sprite.hpp:66
Component representing position, rotation and scale in 2D space.
Definition Transform.hpp:20
Vector2 getPosition() const
Get the position vector.
Definition Transform.hpp:61
Component for static or destructible walls/obstacles.
Definition Wall.hpp:20
High-level entity wrapper providing fluent interface.
Definition ECSWorld.hpp:33
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
double tick()
Get elapsed time and automatically reset (optimized for game loops)
static void sleepMilliseconds(int milliseconds)
Sleep for specified milliseconds (centralized time management)
void reset()
Reset the timer to the current time.
Event triggered when the game ends.
Centralized game rules and configuration.
Definition GameRules.hpp:21
Event triggered when the game starts in a specific room.
static void sendGamerule(IPeer *peer, GameruleKey key, float value)
Send a single gamerule update to a single client (type-safe)
static void sendAllGamerules(IPeer *peer, const GameRules &rules)
Send all gamerules to a single client.
Interface for server-side game logic orchestration.
virtual uint32_t getLastProcessedInput(uint32_t playerId) const =0
Get the last processed input sequence ID for a player.
Event triggered when a player joins the game.
Event triggered when a player leaves the game.
Deterministic fixed-timestep game loop.
uint32_t getCurrentTick() const
Get the current server tick.
std::shared_ptr< ecs::wrapper::ECSWorld > getECSWorld()
Get reference to ECS world from GameLogic.
Helper functions for connection protocol messages.
MessageType
All message types in the R-Type protocol.
std::vector< uint8_t > createConnectResponse(const std::string &message)
Create HANDSHAKE_RESPONSE message.
MessageType getMessageType(const std::vector< uint8_t > &packet)
Get message type from packet.
std::vector< uint8_t > createMessage(MessageType type, const std::vector< uint8_t > &payload)
Create a message with type and payload.
std::vector< uint8_t > getPayload(const std::vector< uint8_t > &packet)
Get payload from packet (without header)
DestroyReason
Destroy reason enum - matches Cap'n Proto enum.
Action
Player action enum - matches Cap'n Proto enum.
All game messages for R-Type network protocol.
Definition Server.hpp:30
std::uint32_t Address
Type used to represent an entity address/ID.
@ Expired
Entity lifetime expired (e.g., projectile)
@ OutOfBounds
Entity went outside screen boundaries.
@ Manual
Manually destroyed (script, etc.)
@ Killed
Entity was killed (health <= 0)
Represents a network event (connection, disconnection, or received data).
Definition IHost.hpp:32
NetworkEventType type
Type of the event.
Definition IHost.hpp:33
std::unique_ptr< IPacket > packet
Packet received (only for RECEIVE events).
Definition IHost.hpp:35
IPeer * peer
Peer associated with the event.
Definition IHost.hpp:34
Map background configuration sent to client.
Definition GameStart.hpp:24
std::string parallaxBackground
Path to parallax layer texture (empty = none)
Definition GameStart.hpp:26
float parallaxSpeedFactor
Parallax layer speed factor.
Definition GameStart.hpp:28
float scrollSpeed
Background scroll speed in pixels/second.
Definition GameStart.hpp:27
std::string background
Path to main background texture.
Definition GameStart.hpp:25
float x
X coordinate.
Definition Transform.hpp:27
float y
Y coordinate.
Definition Transform.hpp:28
Context information for command execution.
Information about a player in the lobby.
Definition Lobby.hpp:26
std::string playerName
Definition Lobby.hpp:28