50Server::Server(uint16_t port,
size_t maxClients) : _port(port), _maxClients(maxClients) {}
63 LOG_INFO(
"Initializing R-Type server...");
66 LOG_ERROR(
"Failed to initialize networking");
74 LOG_ERROR(
"Failed to start network manager");
78 _eventBus = std::make_shared<server::EventBus>();
82 auto matchmaking = std::make_shared<server::MatchmakingService>(2, 4,
_eventBus);
94 LOG_INFO(
"[EVENT] Player joined: ", event.getPlayerName(),
" (ID: ", event.getPlayerId(),
")");
98 LOG_INFO(
"[EVENT] Player left (ID: ", event.getPlayerId(),
")");
102 if (event.getRoomId().empty()) {
103 LOG_INFO(
"[EVENT] Server started!");
105 LOG_INFO(
"[EVENT] Game started in room: ", event.getRoomId());
110 LOG_INFO(
"Game ended - reason: ", event.getReason());
114 LOG_INFO(
"Broadcasting GameOver to ", rooms.size(),
" room(s)");
116 for (
const auto &room : rooms) {
117 auto gameLogic = room->getGameLogic();
120 auto players = room->getPlayers();
121 LOG_INFO(
"Room has ", players.size(),
" player(s)");
122 for (uint32_t playerId : players) {
126 const std::string &sessionId = sessionIt->second;
132 std::vector<uint8_t> payload = gameOverMsg.
serialize();
136 LOG_INFO(
"Sent GameOver to player ", playerId);
144 LOG_INFO(
"✓ Server initialized successfully");
163 switch (messageType) {
213 LOG_WARNING(
"Received unknown message type: ",
static_cast<int>(messageType));
217 }
catch (
const std::exception &e) {
218 LOG_ERROR(
"Error handling packet: ", e.what());
233 uint32_t playerId = session->getPlayerId();
239 LOG_INFO(
"Player ", playerId,
" disconnected, cleaning up...");
241 std::shared_ptr<server::Room> playerRoom =
_roomManager->getRoomByPlayer(playerId);
243 std::shared_ptr<server::IGameLogic> gameLogic = playerRoom->getGameLogic();
245 gameLogic->despawnPlayer(playerId);
248 playerRoom->leave(playerId);
249 LOG_INFO(
"✓ Player ", playerId,
" removed from room '", playerRoom->getId(),
"'");
274 std::string playerName = handshakeData.
playerName;
275 std::string username = handshakeData.
username;
276 std::string password = handshakeData.
password;
278 LOG_INFO(
"Authentication attempt - Username: '", username,
"', Player: '", playerName,
"'");
281 std::string sessionId =
_sessionManager->authenticateAndCreateSession(username, password);
283 if (sessionId.empty()) {
284 LOG_WARNING(
"❌ Authentication FAILED for user: ", username);
287 std::vector<uint8_t> responseData =
306 event.peer->disconnect();
358 LOG_INFO(
"✓ Authentication SUCCESS for user: ", username);
361 std::shared_ptr<server::Session> session =
_sessionManager->getSession(sessionId);
363 LOG_ERROR(
"Session creation failed after authentication");
364 event.peer->disconnect();
369 static std::atomic<uint32_t> nextPlayerId{1000};
370 uint32_t newPlayerId = nextPlayerId.fetch_add(1);
372 session->setPlayerId(newPlayerId);
373 session->setSpectator(
false);
374 session->setActive(
true);
381 std::string displayName;
382 if (username ==
"guest") {
384 std::string hashStr = std::to_string(std::hash<std::string>{}(sessionId));
385 displayName =
"guest_" + hashStr.substr(0, 4);
388 displayName = username;
391 _lobby->addPlayer(newPlayerId, displayName);
393 LOG_INFO(
"✓ Player '", displayName,
"' (", username,
") authenticated (Session: ", sessionId,
394 ", Player ID: ", newPlayerId,
")");
400 response.
serverId =
"r-type-server";
401 response.
message =
"✓ Authentication successful! Welcome to R-Type, " + displayName +
"!";
406 std::vector<uint8_t> responseData = response.
serialize();
407 std::vector<uint8_t> packet =
410 event.peer->send(std::move(responsePacket), 0);
419 LOG_INFO(
" Player is now in lobby - waiting for room selection");
429 std::string username = registerMsg.
username;
430 std::string password = registerMsg.
password;
432 LOG_INFO(
"Registration attempt - Username: '", username,
"'");
435 bool success =
_sessionManager->getAuthService()->registerUser(username, password);
441 response.
message =
"Account created successfully! You can now login.";
442 LOG_INFO(
"Registration SUCCESS for user: ", username);
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);
452 std::vector<uint8_t> responsePayload = response.
serialize();
463 std::string username = loginMsg.
username;
464 std::string password = loginMsg.
password;
466 LOG_INFO(
"Login attempt - Username: '", username,
"'");
469 std::string sessionId =
_sessionManager->authenticateAndCreateSession(username, password);
470 bool success = !sessionId.empty();
476 response.
message =
"✓ Login successful! Welcome back, " + username +
"!";
479 LOG_INFO(
"✓ Login SUCCESS for user: ", username,
" (Session: ", sessionId,
")");
485 std::shared_ptr<server::Session> session =
_sessionManager->getSession(sessionIt->second);
487 uint32_t playerId = session->getPlayerId();
491 std::string displayName;
492 if (username ==
"guest") {
494 std::string hashStr = std::to_string(std::hash<std::string>{}(sessionId));
495 displayName =
"guest_" + hashStr.substr(0, 4);
498 displayName = username;
500 _lobby->updatePlayerName(playerId, displayName);
505 response.
message =
"Login failed! Invalid username or password.";
512 std::vector<uint8_t> responsePayload = response.
serialize();
525 uint32_t playerId = 0;
526 bool isSpectator =
false;
528 playerId = session->getPlayerId();
529 isSpectator = session->isSpectator();
533 if (isSpectator || playerId == 0) {
537 std::shared_ptr<server::Room> playerRoom =
_roomManager->getRoomByPlayer(playerId);
542 std::shared_ptr<server::IGameLogic> gameLogic = playerRoom->getGameLogic();
550 auto snapshots = packet.
inputs;
551 std::sort(snapshots.begin(), snapshots.end(),
553 return a.sequenceId < b.sequenceId;
556 for (
const auto &snapshot : snapshots) {
561 for (
const auto &action : snapshot.actions) {
564 bool actionShoot =
false;
568 shoot = shoot || actionShoot;
571 gameLogic->processPlayerInput(playerId, dx, dy, shoot, snapshot.sequenceId);
573 }
catch (
const std::exception &e) {
574 LOG_WARNING(
"Failed to deserialize input packet: ", e.what());
594 uint32_t playerId = 0;
595 bool isSpectator =
false;
597 playerId = session->getPlayerId();
598 isSpectator = session->isSpectator();
602 LOG_ERROR(
"Failed to find player for room creation");
609 LOG_ERROR(
"Spectators cannot create rooms");
615 static std::atomic<uint32_t> nextRoomId{1};
616 std::string roomId =
"room_" + std::to_string(nextRoomId.fetch_add(1));
627 if (!room->join(playerId)) {
628 LOG_ERROR(
"Failed to join created room");
657 uint32_t playerId = 0;
658 bool isSpectator =
false;
660 playerId = session->getPlayerId();
661 isSpectator = session->isSpectator();
665 LOG_ERROR(
"Failed to find player for room join");
680 bool joinSuccess =
false;
681 bool autoSpectator =
false;
684 joinSuccess = room->joinAsSpectator(playerId);
686 joinSuccess = room->join(playerId);
690 LOG_INFO(
"Game already in progress, joining player ", playerId,
" as spectator");
691 joinSuccess = room->joinAsSpectator(playerId);
692 autoSpectator =
true;
697 std::string errorMsg =
698 isSpectator ?
"Failed to join as spectator" :
"Room is full or game already started";
707 std::string modeStr = (isSpectator || autoSpectator) ?
" as spectator" :
"";
708 LOG_INFO(
"Player ", playerId,
" joined room '", request.
roomId,
"'", modeStr);
715 LOG_INFO(
"Sending current game state to spectator ", playerId);
730 uint32_t playerId = 0;
731 bool isSpectator =
false;
733 playerId = session->getPlayerId();
734 isSpectator = session->isSpectator();
738 LOG_ERROR(
"Failed to find player for auto-matchmaking");
749 LOG_INFO(
"Auto-matchmaking requested by player ", playerId);
753 LOG_INFO(
"Auto-matchmaking disabled for player ", playerId);
760 std::shared_ptr<server::Room> targetRoom =
nullptr;
761 bool joinAsSpectator =
false;
764 if (availableRooms.empty()) {
765 LOG_INFO(
"No rooms available, creating new room for auto-matchmaking");
768 static std::atomic<uint32_t> nextAutoRoomId{1};
769 std::string roomId =
"auto_" + std::to_string(nextAutoRoomId.fetch_add(1));
772 std::string playerName =
"Player";
775 playerName = usernameIt->second;
778 std::string roomName = playerName +
"'s Room";
779 uint32_t maxPlayers = 4;
780 bool isPrivate =
false;
783 targetRoom =
_roomManager->createRoom(roomId, roomName, maxPlayers, isPrivate);
786 LOG_ERROR(
"Failed to create auto-matchmaking room");
792 LOG_INFO(
"✓ Auto-matchmaking room '", roomName,
"' (", roomId,
") created");
795 auto matchmakingService =
_roomManager->getMatchmaking();
796 if (!matchmakingService) {
797 LOG_ERROR(
"MatchmakingService not available");
798 S2C::JoinedRoom response(
"",
false,
"Matchmaking service unavailable");
803 auto [foundRoom, spectator] =
804 matchmakingService->findOrCreateMatch(playerId, availableRooms, !isSpectator);
808 LOG_INFO(
"Player ", playerId,
" added to matchmaking queue");
811 "Searching for match... You have been added to the matchmaking queue and "
812 "will be notified when a match is found.");
817 targetRoom = foundRoom;
818 joinAsSpectator = spectator;
822 bool joinSuccess =
false;
823 if (joinAsSpectator) {
824 joinSuccess = targetRoom->joinAsSpectator(playerId);
826 joinSuccess = targetRoom->join(playerId);
830 LOG_ERROR(
"Failed to join room '", targetRoom->getId(),
"'");
836 std::string modeStr = joinAsSpectator ?
" as spectator" :
"";
837 LOG_INFO(
"Player ", playerId,
" auto-matched to room '", targetRoom->getId(),
"'", modeStr);
839 S2C::JoinedRoom response(targetRoom->getId(),
true,
"", joinAsSpectator);
844 LOG_INFO(
"Sending current game state to spectator ", playerId);
858 LOG_INFO(
"[Matchmaking] Room '", room->getId(),
"' created with ", room->getPlayerCount(),
862 auto playerIds = room->getPlayers();
863 for (uint32_t playerId : playerIds) {
867 LOG_WARNING(
"Player ", playerId,
" session not found for matchmaking notification");
871 const std::string &sessionId = it->second;
874 LOG_WARNING(
"Player ", playerId,
" peer not found for matchmaking notification");
878 IPeer *peer = peerIt->second;
884 LOG_INFO(
"Notified player ", playerId,
" of match in room '", room->getId(),
"'");
898 uint32_t playerId = 0;
900 playerId = session->getPlayerId();
904 LOG_ERROR(
"Failed to find player for auto-matchmaking preference update");
915 const std::string &username = usernameIt->second;
917 if (username !=
"guest") {
919 LOG_INFO(
"✓ Updated auto-matchmaking preference for user '", username,
920 "': ", msg.
enabled ?
"ON" :
"OFF",
" (preference only, NO matchmaking triggered)");
922 LOG_INFO(
"Guest user - auto-matchmaking preference not saved");
925 LOG_WARNING(
"Username not found for player ", playerId);
935 uint32_t playerId = 0;
937 playerId = session->getPlayerId();
941 LOG_WARNING(
"❌ Start game request from unknown session");
945 std::shared_ptr<server::Room> playerRoom =
_roomManager->getRoomByPlayer(playerId);
948 LOG_WARNING(
"❌ Player ", playerId,
" is not in any room");
951 if (playerRoom->getHost() != playerId) {
952 LOG_WARNING(
"❌ Player ", playerId,
" is not the host of room '", playerRoom->getId(),
"'");
956 LOG_WARNING(
"❌ Room '", playerRoom->getId(),
"' is not in WAITING state");
960 playerRoom->requestStartGame();
961 LOG_INFO(
"Room '", playerRoom->getId(),
"' starting game");
968 uint32_t playerId = 0;
970 playerId = session->getPlayerId();
974 LOG_WARNING(
"❌ Leave room request from unknown session");
978 std::shared_ptr<server::Room> playerRoom =
_roomManager->getRoomByPlayer(playerId);
981 LOG_WARNING(
"Player ", playerId,
" is not in any room");
986 playerRoom->leave(playerId);
987 LOG_INFO(
"✓ Player ", playerId,
" left room '", playerRoom->getId(),
"'");
990 S2C::LeftRoom leftRoomMsg(playerId, S2C::LeftRoomReason::VOLUNTARY_LEAVE,
"You left the room");
995 if (playerRoom->getPlayerCount() == 0 && playerRoom->getSpectators().empty()) {
996 std::string roomId = playerRoom->getId();
998 LOG_INFO(
"✓ Room '", roomId,
"' deleted (empty)");
1011 LOG_DEBUG(
"[Server] _handleChatMessage called");
1014 uint32_t playerId = 0;
1015 std::string playerName =
"Unknown";
1018 playerId = session->getPlayerId();
1022 playerName = lobbyPlayer ? lobbyPlayer->
playerName : (
"Player" + std::to_string(playerId));
1025 LOG_DEBUG(
"[Server] Chat message from player ", playerId,
" (", playerName,
")");
1027 if (playerId == 0) {
1028 LOG_WARNING(
"❌ Chat message from unknown session");
1033 std::shared_ptr<server::Room> playerRoom =
_roomManager->getRoomByPlayer(playerId);
1035 LOG_WARNING(
"Player ", playerId,
" is not in any room");
1042 auto chatMsg = C2S::C2SChatMessage::deserialize(payload);
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.");
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");
1061 if (!chatMsg.message.empty() && chatMsg.message[0] ==
'/') {
1063 LOG_INFO(
"[COMMAND] Player ", playerName,
" (", playerId,
"): ", chatMsg.message);
1069 std::string response =
_commandHandler->handleCommand(chatMsg.message, context);
1072 if (!response.empty()) {
1079 LOG_INFO(
"[CHAT] Player ", playerName,
" in room '", playerRoom->getId(),
"': ", chatMsg.message);
1082 playerRoom->broadcastChatMessage(playerId, playerName, chatMsg.message);
1085 auto timestamp = std::chrono::duration_cast<std::chrono::milliseconds>(
1086 std::chrono::system_clock::now().time_since_epoch())
1090 auto responsePayload = chatResponse.
serialize();
1091 auto responsePacket =
1095 std::vector<uint32_t> players = playerRoom->getPlayers();
1096 for (uint32_t targetPlayerId : players) {
1099 std::string sessionId = it->second;
1103 responsePayload,
true);
1108 LOG_INFO(
"✓ Chat message broadcast to ", players.size(),
" players");
1110 }
catch (
const std::exception &e) {
1111 LOG_ERROR(
"Failed to parse ChatMessage: ", e.what());
1119 std::istringstream iss(message);
1121 int messageCount = 0;
1123 while (std::getline(iss, line)) {
1130 auto timestamp = std::chrono::duration_cast<std::chrono::milliseconds>(
1131 std::chrono::system_clock::now().time_since_epoch())
1135 timestamp += messageCount;
1144 std::string sessionId = it->second;
1152 if (messageCount > 0) {
1153 LOG_DEBUG(
"✓ Sent ", messageCount,
" system message(s) to player ", playerId);
1163 LOG_WARNING(
"Cannot notify kicked player ", playerId,
": session not found");
1167 std::string sessionId = it->second;
1170 LOG_WARNING(
"Cannot notify kicked player ", playerId,
": peer not found");
1174 IPeer *peer = peerIt->second;
1177 LOG_INFO(
"Sending LEFT_ROOM (KICKED) to player ", playerId);
1179 S2C::LeftRoom leftRoomMsg(playerId, S2C::LeftRoomReason::KICKED,
"You have been kicked by the host");
1184 LOG_DEBUG(
"✓ Kicked player ", playerId,
" notified with S2C_LEFT_ROOM");
1189 LOG_WARNING(
"notifyRoomUpdate called with null room");
1193 LOG_DEBUG(
"Broadcasting room state update for room ", room->getId());
1202 std::shared_ptr<server::Room> playerRoom =
_roomManager->getRoomByPlayer(playerId);
1204 LOG_WARNING(
"Cannot kick player ", playerId,
": not in any room");
1208 LOG_INFO(
"Kicking player ", playerId,
" from room ", playerRoom->getId());
1214 std::shared_ptr<server::IGameLogic> gameLogic = playerRoom->getGameLogic();
1216 gameLogic->despawnPlayer(playerId);
1220 playerRoom->leave(playerId);
1237 LOG_ERROR(
"Cannot run: not initialized");
1241 LOG_INFO(
"========================================");
1242 LOG_INFO(
"R-Type server running!");
1246 LOG_INFO(
"========================================");
1262 for (
const auto &room : rooms) {
1289 LOG_INFO(
"✓ Stopping all rooms...");
1308 for (
const auto &room : rooms) {
1314 std::shared_ptr<ecs::wrapper::ECSWorld> ecsWorld = roomLoop->
getECSWorld();
1319 std::shared_ptr<server::IGameLogic> gameLogic = room->getGameLogic();
1328 for (
auto &entity : entities) {
1331 state.
entities.push_back(entityState);
1332 }
catch (
const std::exception &e) {
1333 LOG_ERROR(
"Failed to serialize entity: ", e.what());
1339 std::vector<uint8_t> payload = state.
serialize();
1340 std::vector<uint8_t> packet =
1344 auto players = room->getPlayers();
1345 auto spectators = room->getSpectators();
1348 std::vector<uint32_t> allRecipients = players;
1349 allRecipients.insert(allRecipients.end(), spectators.begin(), spectators.end());
1351 for (uint32_t recipientId : allRecipients) {
1356 const std::string &sessionId = sessionIt->second;
1360 std::unique_ptr<IPacket> peerPacket =
1362 peerIt->second->send(std::move(peerPacket), 0);
1373 for (
const auto &room : rooms) {
1379 std::shared_ptr<ecs::wrapper::ECSWorld> ecsWorld = roomLoop->
getECSWorld();
1387 if (pendingEntities.empty()) {
1391 LOG_DEBUG(
"[ProcessPendingDestructions] Room '", room->getId(),
"' - Found ", pendingEntities.size(),
1392 " entities pending destruction");
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());
1401 std::vector<ecs::Address> toDestroy;
1402 for (
auto &entity : pendingEntities) {
1410 networkReason = Shared::DestroyReason::OutOfBounds;
1413 networkReason = Shared::DestroyReason::KilledByPlayer;
1416 networkReason = Shared::DestroyReason::Collision;
1420 networkReason = Shared::DestroyReason::OutOfBounds;
1426 std::vector<uint8_t> payload = destroyedMsg.
serialize();
1427 std::vector<uint8_t> packet =
1431 for (uint32_t recipientId : allRecipients) {
1436 const std::string &sessionId = sessionIt->second;
1440 std::unique_ptr<IPacket> peerPacket =
1442 peerIt->second->send(std::move(peerPacket), 0);
1446 LOG_DEBUG(
"[ProcessPendingDestructions] Sent EntityDestroyed for entity ", entityId);
1447 toDestroy.push_back(entityId);
1452 ecsWorld->destroyEntity(entityId);
1455 if (!toDestroy.empty()) {
1456 LOG_INFO(
"[ProcessPendingDestructions] Destroyed ", toDestroy.size(),
" entities in room '",
1457 room->getId(),
"'");
1471 LOG_ERROR(
"ServerLoop not available for room ", room->getId());
1475 std::shared_ptr<ecs::wrapper::ECSWorld> ecsWorld = roomLoop->
getECSWorld();
1477 LOG_ERROR(
"ECSWorld not available for room ", room->getId());
1481 std::shared_ptr<server::IGameLogic> gameLogic = room->getGameLogic();
1483 LOG_ERROR(
"GameLogic not available for room ", room->getId());
1491 auto sendRulesToRecipient = [&](uint32_t recipientId) {
1496 const std::string &sessionId = sessionIt->second;
1505 float gameSpeedMultiplier = room->getGameSpeedMultiplier();
1507 gameSpeedMultiplier);
1513 if (!mapEntities.empty()) {
1515 mapConfig.
background = mapData.getBackgroundSprite();
1525 auto sendGameStart = [&](uint32_t playerId, uint32_t entityId) {
1534 const std::string &sessionId = sessionIt->second;
1541 if (entityId != 0) {
1542 LOG_INFO(
"✓ Sent GameStart to player ", playerId,
" (entity: ", entityId,
1543 ", room: ", room->getId(),
")");
1545 LOG_INFO(
"✓ Sent GameStart to spectator ", playerId,
" (room: ", room->getId(),
")");
1552 auto players = room->getPlayers();
1553 auto spectators = room->getSpectators();
1555 for (uint32_t playerId : players) {
1556 sendRulesToRecipient(playerId);
1559 uint32_t entityId = 0;
1561 for (
auto &entity : playerEntities) {
1563 if (player.getPlayerId() ==
static_cast<int>(playerId)) {
1564 entityId = entity.getAddress();
1569 if (entityId == 0) {
1570 LOG_ERROR(
"Failed to find entity for player ", playerId);
1574 sendGameStart(playerId, entityId);
1578 for (uint32_t spectatorId : spectators) {
1579 sendGameStart(spectatorId, 0);
1587 LOG_ERROR(
"Cannot send game start to spectator: room not in progress");
1593 LOG_ERROR(
"ServerLoop not available for room ", room->getId());
1597 std::shared_ptr<ecs::wrapper::ECSWorld> ecsWorld = roomLoop->
getECSWorld();
1599 LOG_ERROR(
"ECSWorld not available for room ", room->getId());
1603 std::shared_ptr<server::IGameLogic> gameLogic = room->getGameLogic();
1605 LOG_ERROR(
"GameLogic not available for room ", room->getId());
1612 LOG_ERROR(
"Cannot find session for spectator ", spectatorId);
1616 const std::string &sessionId = sessionIt->second;
1619 LOG_ERROR(
"Cannot find peer for spectator ", spectatorId);
1632 if (!mapEntities.empty()) {
1634 mapConfig.
background = mapData.getBackgroundSprite();
1649 LOG_INFO(
"✓ Sent GameStart to spectator ", spectatorId,
" (room: ", room->getId(),
1650 ", entities: ", entities.size(),
")");
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());
1668 auto players = room->getPlayers();
1669 auto spectators = room->getSpectators();
1670 uint32_t hostId = room->getHost();
1672 roomState.
currentPlayers =
static_cast<uint32_t
>(players.size() + spectators.size());
1675 for (uint32_t playerId : players) {
1691 playerData.
playerName = lobbyPlayer ? lobbyPlayer->
playerName : (
"Player" + std::to_string(playerId));
1693 playerData.
isHost = (playerId == hostId);
1696 roomState.
players.push_back(playerData);
1700 for (uint32_t spectatorId : spectators) {
1717 lobbyPlayer ? lobbyPlayer->
playerName : (
"Spectator" + std::to_string(spectatorId));
1719 playerData.
isHost =
false;
1722 roomState.
players.push_back(playerData);
1726 std::vector<uint8_t> payload = roomState.
serialize();
1729 std::vector<uint32_t> allRecipients = players;
1730 allRecipients.insert(allRecipients.end(), spectators.begin(), spectators.end());
1732 for (uint32_t recipientId : allRecipients) {
1738 const std::string &sessionId = sessionIt->second;
1746 LOG_INFO(
"✓ Broadcast RoomState to ", allRecipients.size(),
" players in room '", room->getId(),
"'");
1758 throw std::runtime_error(
"Entity no longer has Transform component");
1777 entityState.
spriteW = rect.width;
1778 entityState.
spriteH = rect.height;
1789 entityState.
type = Shared::EntityType::Player;
1801 (enemy.
getEnemyType() == 0) ? Shared::EntityType::EnemyType1 : Shared::EntityType::EnemyType1;
1806 projectile.
isFriendly() ? Shared::EntityType::PlayerBullet : Shared::EntityType::EnemyBullet;
1809 entityState.
type = Shared::EntityType::Wall;
1812 entityState.
type = Shared::EntityType::OrbitalModule;
1816 entityState.
type = Shared::EntityType::Player;
1859 std::unique_ptr<IPacket> netPacket =
1861 peer->
send(std::move(netPacket), 0);
1866 std::vector<RType::Messages::S2C::EntityState> entities;
1871 for (
auto &entity : transforms) {
1874 }
catch (
const std::exception &e) {
1875 LOG_ERROR(
"Failed to serialize entity: ", e.what());
1887 for (
const auto &room : publicRooms) {
1889 info.
roomId = room->getId();
1891 info.
playerCount =
static_cast<uint32_t
>(room->getPlayerCount());
1892 info.
maxPlayers =
static_cast<uint32_t
>(room->getMaxPlayers());
1895 auto state = room->getState();
1907 roomList.
rooms.push_back(info);
1910 std::vector<uint8_t> payload = roomList.
serialize();
1913 for (
IPeer *peer : specificPeers) {
1919 if (!specificPeers.empty()) {
1920 LOG_INFO(
"✓ Sent RoomList to ", specificPeers.size(),
" specific peer(s)");
1930 for (
const auto &room : publicRooms) {
1932 info.
roomId = room->getId();
1934 info.
playerCount =
static_cast<uint32_t
>(room->getPlayerCount());
1935 info.
maxPlayers =
static_cast<uint32_t
>(room->getMaxPlayers());
1938 auto state = room->getState();
1950 roomList.
rooms.push_back(info);
1953 std::vector<uint8_t> payload = roomList.
serialize();
1957 size_t sentCount = 0;
1965 LOG_INFO(
"✓ Broadcast RoomList to ", sentCount,
" connected player(s)");
@ 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.
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.
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.
Request to register a new user account.
Server responds to handshake.
std::vector< uint8_t > serialize() const
std::string serverVersion
Entity destruction notification.
std::vector< uint8_t > serialize() const
State of a single entity.
uint32_t lastProcessedInput
std::string currentAnimation
std::optional< int32_t > health
std::vector< uint8_t > serialize() const
Game start notification from server to client.
std::vector< uint8_t > serialize() const
Complete snapshot of the game world.
std::vector< uint8_t > serialize() const
std::vector< EntityState > entities
std::vector< uint8_t > serialize() const
Notification that a player has left a room.
std::vector< uint8_t > serialize() const
Serialize to byte vector.
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
std::vector< RoomInfoData > rooms
std::vector< uint8_t > serialize() const
std::vector< PlayerData > players
Chat message sent from server to clients.
std::vector< uint8_t > serialize() const
Serialize to byte vector.
std::shared_ptr< server::EventBus > _eventBus
void _sendGameStartToRoom(std::shared_ptr< server::Room > room)
Send GameStart message to all players in a room.
std::shared_ptr< server::RoomManager > _roomManager
void _handleLoginRequest(HostNetworkEvent &event)
Handle player login request.
void stop()
Stop the server.
std::unordered_map< uint32_t, std::string > _playerIdToUsername
void _handleAutoMatchmaking(HostNetworkEvent &event)
Handle auto-matchmaking request.
void _handlePlayerInput(HostNetworkEvent &event)
Handle player input packet.
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.
server::FrameTimer _frameTimer
void _broadcastRoomListToAll()
Broadcast room list to ALL connected players on the server.
~Server()
Destructor - clean shutdown.
void _handleStartGame(HostNetworkEvent &event)
Handle start game request (from room host)
void _broadcastRoomState(std::shared_ptr< server::Room > room)
Broadcast room state (player list) to all players in a room.
void handlePacket(HostNetworkEvent &event)
Handle incoming packet from client.
void _actionToInput(RType::Messages::Shared::Action action, int &dx, int &dy, bool &shoot)
Convert Action enum to directional input (dx, dy)
void _handleChatMessage(HostNetworkEvent &event)
Handle chat message from client.
void _handleLeaveRoom(HostNetworkEvent &event)
Handle leave room request.
std::shared_ptr< server::SessionManager > _sessionManager
void _sendPacket(IPeer *peer, NetworkMessages::MessageType type, const std::vector< uint8_t > &payload, bool reliable=true)
Helper to send a packet to a peer.
void notifyRoomUpdate(std::shared_ptr< server::Room > room)
Notify all players in a room of state changes (for commands)
void _handleJoinRoom(HostNetworkEvent &event)
Handle join room request.
void _handleDisconnect(HostNetworkEvent &event)
Handle player disconnect.
std::unordered_map< std::string, IPeer * > _sessionPeers
bool kickPlayer(uint32_t playerId)
Kick a player from their current room (for commands)
RType::Messages::S2C::EntityState _serializeEntity(ecs::wrapper::Entity &entity, server::IGameLogic *gameLogic=nullptr)
Serialize a single entity to network format.
void _broadcastRoomList(const std::vector< IPeer * > &specificPeers)
Broadcast room list to specific peers only.
std::shared_ptr< server::Session > _getSessionFromPeer(IPeer *peer)
Helper to get session from peer.
void _handleHandshakeRequest(HostNetworkEvent &event)
Handle player handshake/connection request.
void _onMatchmakingRoomCreated(std::shared_ptr< server::Room > room)
Called when matchmaking service creates a room with matched players.
void _sendKickedNotification(uint32_t playerId)
Send room list to kicked player to return them to lobby.
std::unordered_map< IPeer *, std::string > _peerToSession
void _processPendingDestructions()
Process entities marked with PendingDestroy component.
void _broadcastGameState()
Broadcast game state to all connected clients.
void run()
Run the server (blocking until exit)
void _sendSystemMessage(uint32_t playerId, const std::string &message)
Send a system message to a specific player.
Server(uint16_t port, size_t maxClients=32)
Constructor.
std::unordered_map< uint32_t, std::string > _playerIdToSessionId
std::unique_ptr< server::CommandHandler > _commandHandler
void _sendGameStartToSpectator(uint32_t spectatorId, std::shared_ptr< server::Room > room)
Send GameStart message to a spectator joining an in-progress game.
void _handleCreateRoom(HostNetworkEvent &event)
Handle create room request.
std::shared_ptr< server::Lobby > _lobby
bool initialize()
Initialize server systems.
std::unique_ptr< ServerNetworkManager > _networkManager
void _handleUpdateAutoMatchmakingPref(HostNetworkEvent &event)
Handle auto-matchmaking preference update (without triggering matchmaking)
void _handleRegisterRequest(HostNetworkEvent &event)
Handle player registration request.
void _handleListRooms(HostNetworkEvent &event)
Handle list rooms request.
Component managing current animation playback state.
Component identifying an entity as an enemy with AI behavior.
int getEnemyType() const
Get enemy type.
Component representing entity health and invincibility.
int getCurrentHealth() const
Get current health points.
Component storing information about the current map/level.
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.
int getPlayerId() const
Get player ID.
Component for projectile entities (bullets, missiles, etc.).
bool isFriendly() const
Check if projectile is friendly.
Component representing a visual sprite from a texture.
Rectangle getSourceRect() const
Get the source rectangle.
Component for static or destructible walls/obstacles.
High-level entity wrapper providing fluent interface.
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.
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.
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.
@ C2S_UPDATE_AUTO_MM_PREF
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.
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).
NetworkEventType type
Type of the event.
std::unique_ptr< IPacket > packet
Packet received (only for RECEIVE events).
IPeer * peer
Peer associated with the event.
Map background configuration sent to client.
std::string parallaxBackground
Path to parallax layer texture (empty = none)
float parallaxSpeedFactor
Parallax layer speed factor.
float scrollSpeed
Background scroll speed in pixels/second.
std::string background
Path to main background texture.
Context information for command execution.
Information about a player in the lobby.