Air-Trap 1.0.0
A multiplayer R-Type clone game engine built with C++23 and ECS architecture
Loading...
Searching...
No Matches
RoomSystem.cpp
Go to the documentation of this file.
1
15
16namespace rtp::server
17{
19 // Public API
21
23 : _network(network)
24 , _registry(registry)
25 , _networkSync(networkSync)
26 {
27 auto lobby = std::make_shared<Room>(_registry, _networkSync, _nextRoomId++, "Global Lobby", 9999,
28 0, 0, Room::RoomType::Lobby, 0, 0, 0, 0);
29 _rooms[lobby->getId()] = lobby;
30 _lobbyId = lobby->getId();
31 log::info("Default Lobby created with ID {}", lobby->getId());
32 };
33
34 void RoomSystem::update(float dt)
35 {
37 for (auto &[roomId, roomPtr] : _rooms) {
38 // (void)roomId;
39 roomPtr->update(0, dt);
40 }
41 };
42
43 uint32_t RoomSystem::createRoom(uint32_t sessionId,
44 const std::string &roomName,
45 uint8_t maxPlayers, float difficulty,
46 float speed, Room::RoomType type,
47 uint32_t levelId, uint32_t seed,
48 uint32_t durationMinutes)
49 {
50 uint32_t newId = 0;
51
52 newId = _nextRoomId++;
53
54 auto room = std::make_shared<Room>(_registry, _networkSync, newId, roomName, maxPlayers,
55 difficulty, speed, type, sessionId,
56 levelId, seed, durationMinutes);
57 _rooms.emplace(newId, room);
58
59 log::info("Room '{}' created with ID {} by session {}", roomName, newId,
60 sessionId);
61 return newId;
62 }
63
64 bool RoomSystem::joinRoom(PlayerPtr player, uint32_t roomId, bool asSpectator)
65 {
66 std::shared_ptr<Room> room;
68 "Player {} (Session ID {}) attempting to join Room ID {}{}",
69 player->getUsername(), player->getId(), roomId,
70 asSpectator ? " as spectator" : "");
71 if (!leaveRoom(player)) {
73 " Could not leave previous room, maybe first connection ?. Player {} (Session ID {})",
74 player->getId(), roomId);
75 }
76 {
77 std::lock_guard lock(_mutex);
78
79 auto it = _rooms.find(roomId);
80 if (it == _rooms.end()) {
82 "Failed to join Player {} to Room ID {}. Room does " "not "
83 "exist"
84 ".",
85 player->getId(), roomId);
87 response << static_cast<uint8_t>(0);
88 _network.sendPacket(player->getId(), response, net::NetworkMode::TCP);
89 return false;
90 }
91
92 room = it->second;
93
94 if (room->isBanned(player->getUsername())) {
96 "Failed to join Player {} to Room ID {}. Player is banned.",
97 player->getId(), roomId);
98 if (room->getType() != Room::RoomType::Lobby) {
100 response << static_cast<uint8_t>(0);
101 _network.sendPacket(player->getId(), response, net::NetworkMode::TCP);
102 }
103 return false;
104 }
105
106 if (!asSpectator && room->getState() != Room::State::Waiting) {
108 "Failed to join Player {} to Room ID {}. Game already started.",
109 player->getId(), roomId);
110 if (room->getType() != Room::RoomType::Lobby) {
112 response << static_cast<uint8_t>(0);
113 _network.sendPacket(player->getId(), response, net::NetworkMode::TCP);
114 }
115 return false;
116 }
117
118 if (!asSpectator && !room->canJoin()) {
120 "Failed to join Player {} to Room ID {}. Room is full.",
121 player->getId(), roomId);
122 if (room->getType() != Room::RoomType::Lobby) {
124 response << static_cast<uint8_t>(0);
125 _network.sendPacket(player->getId(), response, net::NetworkMode::TCP);
126 }
127 return false;
128 }
129
130 const Room::PlayerType joinType = asSpectator
133 if (!room->addPlayer(player, joinType)) {
135 "Failed to join Player {} to Room ID {}. Could not add to new room.",
136 player->getId(), roomId);
137 return false;
138 }
139
140 _playerRoomMap[player->getId()] = room->getId();
141 player->setRoomId(room->getId());
142 if (room->getState() == Room::State::InGame || asSpectator) {
143 player->setReady(true);
144 player->setState(PlayerState::InGame);
145 } else {
146 player->setReady(false);
147 player->setState(PlayerState::InLobby);
148 }
149
150 log::info("Player {} (Session ID {}) joined Room ID {}",
151 player->getUsername(), player->getId(), room->getId());
152 }
153 if (room->getType() != Room::RoomType::Lobby) {
155 response << static_cast<uint8_t>(1);
156 _network.sendPacket(player->getId(), response, net::NetworkMode::TCP);
157 }
158 return true;
159 }
160
162 {
163 return joinRoom(player, _lobbyId, false);
164 }
165
167 {
168 if (!player) {
169 return false;
170 }
171
172 std::shared_ptr<Room> previousRoom;
173 {
174 std::lock_guard lock(_mutex);
175 auto mapIt = _playerRoomMap.find(player->getId());
176 if (mapIt == _playerRoomMap.end()) {
177 return false;
178 }
179
180 uint32_t previousRoomId = mapIt->second;
181 auto prevIt = _rooms.find(previousRoomId);
182 if (prevIt != _rooms.end()) {
183 previousRoom = prevIt->second;
184 prevIt->second->removePlayer(player->getId(), false);
185 if (prevIt->second->getType() != Room::RoomType::Lobby &&
186 prevIt->second->getCurrentPlayerCount() == 0) {
187 _rooms.erase(prevIt);
188 }
189 }
190
191 _playerRoomMap.erase(mapIt);
192 }
193
194 despawnPlayerEntity(player, previousRoom);
195 return true;
196 }
197
198 void RoomSystem::disconnectPlayer(uint32_t sessionId)
199 {
200 std::shared_ptr<Room> room;
201 PlayerPtr player;
202 {
203 std::lock_guard lock(_mutex);
204 auto it = _playerRoomMap.find(sessionId);
205 if (it == _playerRoomMap.end()) {
206 return;
207 }
208
209 uint32_t roomId = it->second;
210 auto roomIt = _rooms.find(roomId);
211 if (roomIt != _rooms.end()) {
212 room = roomIt->second;
213 if (room) {
214 for (const auto& roomPlayer : room->getPlayers()) {
215 if (roomPlayer && roomPlayer->getId() == sessionId) {
216 player = roomPlayer;
217 break;
218 }
219 }
220 }
221 roomIt->second->removePlayer(sessionId, true);
222 if (roomIt->second->getType() != Room::RoomType::Lobby &&
223 roomIt->second->getCurrentPlayerCount() == 0) {
224 _rooms.erase(roomIt);
225 }
226 }
227
228 _playerRoomMap.erase(it);
229 }
230
231 despawnPlayerEntity(player, room);
232 }
233
234 void RoomSystem::listAllRooms(uint32_t sessionId)
235 {
236 std::lock_guard lock(_mutex);
237 log::info("Handle List Rooms request from Session ID {}", sessionId);
238
239 net::Packet responsePacket(net::OpCode::RoomList);
240
241 uint32_t roomCount = 0;
242 for (const auto &[roomId, roomPtr] : _rooms) {
243 (void)roomId;
244 auto roomType = roomPtr->getType();
245 log::info("Room ID {}: Type={} (Lobby={}, Public={}, Private={})",
246 roomId, static_cast<int>(roomType),
247 static_cast<int>(Room::RoomType::Lobby),
248 static_cast<int>(Room::RoomType::Public),
249 static_cast<int>(Room::RoomType::Private));
250 if (roomType == Room::RoomType::Lobby)
251 continue;
252 if (roomType == Room::RoomType::Private)
253 continue;
254 ++roomCount;
255 }
256
257 responsePacket << roomCount;
258
259 for (const auto &[roomId, roomPtr] : _rooms) {
260 (void)roomId;
261
262 if (roomPtr->getType() == Room::RoomType::Lobby)
263 continue;
264 if (roomPtr->getType() == Room::RoomType::Private)
265 continue;
266
267 net::RoomInfo roomInfo{};
268 roomInfo.roomId = roomPtr->getId();
269 std::strncpy(roomInfo.roomName, roomPtr->getName().c_str(),
270 sizeof(roomInfo.roomName) - 1);
271 roomInfo.roomName[sizeof(roomInfo.roomName) - 1] = '\0';
272
273 roomInfo.currentPlayers = roomPtr->getCurrentPlayerCount();
274 roomInfo.maxPlayers = roomPtr->getMaxPlayers();
275 roomInfo.difficulty = roomPtr->getDifficulty();
276 roomInfo.speed = roomPtr->getSpeed();
277 roomInfo.inGame = (roomPtr->getState() == Room::State::InGame) ? 1 : 0;
278 roomInfo.duration = roomPtr->getDurationMinutes();
279 roomInfo.seed = roomPtr->getSeed();
280 roomInfo.levelId = roomPtr->getLevelId();
281 roomInfo.roomType = static_cast<uint8_t>(roomPtr->getType());
282
283 responsePacket << roomInfo;
284
285 log::info("Listed Room ID {}: Name='{}', Players={}/{}",
286 roomInfo.roomId, roomInfo.roomName,
287 roomInfo.currentPlayers, roomInfo.maxPlayers);
288 }
289
290 _network.sendPacket(sessionId, responsePacket,
292 log::info("Sent Room List ({} rooms) to Session ID {}", roomCount,
293 sessionId);
294 }
295
296 void RoomSystem::chatInRoom(uint32_t sessionId,
297 const net::Packet &packet)
298 {
299 // Implementation for chatting in a room
300 }
301
303 {
304 std::vector<std::shared_ptr<Room>> toStart;
305
306 {
307 std::lock_guard lock(_mutex);
308
309 for (auto &[roomId, roomPtr] : _rooms) {
310 if (roomPtr->getType() == Room::RoomType::Lobby)
311 continue;
312
313 if (roomPtr->getState() != Room::State::Waiting)
314 continue;
315
316 bool allReady = true;
317 for (const auto &player : roomPtr->getPlayers()) {
318 if (!player->isReady()) {
319 allReady = false;
320 break;
321 }
322 }
323
324 if (allReady && roomPtr->getCurrentPlayerCount() > 0) {
325 toStart.push_back(roomPtr);
326 }
327 }
328 }
329
330 for (auto &roomPtr : toStart) {
331 const bool started = roomPtr->startGame(dt);
332
333 if (started && _onRoomStarted) {
334 _onRoomStarted(roomPtr->getId());
335 }
336
337 if (roomPtr->getState() == Room::State::InGame) {
338 log::info("Launching Room ID {} as all players are ready.",
339 roomPtr->getId());
340
342 for (const auto &player : roomPtr->getPlayers()) {
343 log::info(
344 "Notifying Player {} (Session ID {}) of game start "
345 "in Room ID {}.",
346 player->getUsername(), player->getId(),
347 roomPtr->getId());
348 _network.sendPacket(player->getId(), startPacket,
350 }
351 } else {
353 "Room ID {} did NOT start (canStartGame/state blocked).",
354 roomPtr->getId());
355 }
356 }
357 }
358
359 std::shared_ptr<Room> RoomSystem::getRoom(uint32_t roomId)
360 {
361 std::lock_guard lock(_mutex);
362
363 auto it = _rooms.find(roomId);
364 if (it != _rooms.end()) {
365 return it->second;
366 }
367 return nullptr;
368 }
369
371 const std::shared_ptr<Room>& room)
372 {
373 if (!player) {
374 return;
375 }
376
377 const uint32_t entityId = player->getEntityId();
378 if (entityId == 0) {
379 _networkSync.unbindSession(player->getId());
380 return;
381 }
382
383 // Find the entity by scanning the NetworkId sparse array for matching id
384 // This is necessary because we only store the entity index in Player, not the generation
385 auto netsRes = _registry.get<ecs::components::NetworkId>();
386 if (!netsRes) {
387 _networkSync.unbindSession(player->getId());
388 return;
389 }
390
391 auto &nets = netsRes->get();
393 for (auto e : nets.entities()) {
394 if (nets[e].id == entityId) {
395 entity = e;
396 break;
397 }
398 }
399
400 if (entity.isNull()) {
401 _networkSync.unbindSession(player->getId());
402 return;
403 }
404
405 auto transformsRes = _registry.get<ecs::components::Transform>();
406 auto typesRes = _registry.get<ecs::components::EntityType>();
407 auto roomsRes = _registry.get<ecs::components::RoomId>();
408
409 if (transformsRes && typesRes && roomsRes) {
410 auto &transforms = transformsRes->get();
411 auto &types = typesRes->get();
412 auto &rooms = roomsRes->get();
413 if (transforms.has(entity) && types.has(entity) && nets.has(entity) && rooms.has(entity)) {
414 const bool shouldBroadcast = room && room->getType() != Room::RoomType::Lobby;
415 if (shouldBroadcast) {
416 const auto players = room->getPlayers();
417 if (!players.empty()) {
419 net::EntityDeathPayload payload{};
420 payload.netId = nets[entity].id;
421 payload.type = static_cast<uint8_t>(types[entity].type);
422 payload.position = transforms[entity].position;
423 packet << payload;
424
425 for (const auto& roomPlayer : players) {
427 roomPlayer->getId(), packet, net::NetworkMode::TCP);
428 }
429 }
430 }
431 }
432 }
433
434 _registry.kill(entity);
435 _networkSync.unbindSession(player->getId());
436 player->setEntityId(0);
437 }
438}
Represents an entity in the ECS (Entity-Component-System) architecture.
Definition Entity.hpp:63
constexpr bool isNull(void) const noexcept
void kill(Entity entity)
Definition Registry.cpp:73
auto get(this const Self &self) -> std::expected< std::reference_wrapper< ConstLike< Self, SparseArray< T > > >, rtp::Error >
Network packet with header and serializable body.
Definition Packet.hpp:471
System to handle network-related operations on the server side.
void sendPacketToSession(uint32_t sessionId, const net::Packet &packet, net::NetworkMode mode)
Send a packet to a specific session.
void unbindSession(uint32_t sessionId)
void launchReadyRooms(float dt)
Launch all rooms that are ready to start the game.
bool joinLobby(PlayerPtr player)
Join the lobby room.
RoomSystem(ServerNetwork &network, ecs::Registry &registry, NetworkSyncSystem &networkSync)
Constructor for RoomSystem.
ServerNetwork & _network
Reference to the server network manager.
uint32_t _nextRoomId
Next available room ID.
void disconnectPlayer(uint32_t sessionId)
Disconnect a player from all rooms.
std::mutex _mutex
Mutex for thread-safe operations.
void despawnPlayerEntity(const PlayerPtr &player, const std::shared_ptr< Room > &room)
std::shared_ptr< Room > getRoom(uint32_t roomId)
Get a room by its ID.
bool joinRoom(PlayerPtr player, uint32_t roomId, bool asSpectator=false)
Join an existing room based on client request.
RoomStartedCb _onRoomStarted
Callback for when a room starts.
void update(float dt) override
Update system logic for one frame.
uint32_t _lobbyId
ID of the main lobby room.
uint32_t createRoom(uint32_t sessionId, const std::string &roomName, uint8_t maxPlayers, float difficulty, float speed, Room::RoomType type, uint32_t levelId, uint32_t seed, uint32_t durationMinutes)
Create a new room based on client request.
NetworkSyncSystem & _networkSync
Reference to the network sync system.
std::map< uint32_t, uint32_t > _playerRoomMap
Map of player session ID to room ID.
std::map< uint32_t, std::shared_ptr< Room > > _rooms
Map of room ID to Room instances.
bool leaveRoom(PlayerPtr player)
Leave the current room based on client request.
void listAllRooms(uint32_t sessionId)
List all available rooms based on client request.
void chatInRoom(uint32_t sessionId, const net::Packet &packet)
Handle chat message in the room based on client request.
ecs::Registry & _registry
Reference to the entity registry.
PlayerType
Enum representing the type of player in the room.
Definition Room.hpp:61
@ Spectator
Spectator player.
Definition Room.hpp:64
@ Player
Regular player.
Definition Room.hpp:63
@ InGame
Game in progress.
Definition Room.hpp:43
@ Waiting
Waiting for players.
Definition Room.hpp:42
@ Private
Private room type, hosted by the player.
Definition Room.hpp:54
@ Public
Public room type, can be joined by anyone.
Definition Room.hpp:53
@ Lobby
Lobby room type, Only used for the principal Lobby.
Definition Room.hpp:52
Implementation ASIO du serveur réseau (TCP + UDP)
void sendPacket(uint32_t sessionId, const net::Packet &packet, net::NetworkMode mode)
Send a packet to a specific session.
constexpr Entity NullEntity
Definition Entity.hpp:109
void error(LogFmt< std::type_identity_t< Args >... > fmt, Args &&...args) noexcept
Log an error message.
void warning(LogFmt< std::type_identity_t< Args >... > fmt, Args &&...args) noexcept
Log a warning message.
void info(LogFmt< std::type_identity_t< Args >... > fmt, Args &&...args) noexcept
Log an informational message.
void debug(LogFmt< std::type_identity_t< Args >... > fmt, Args &&...args) noexcept
Log a debug message.
@ JoinRoom
Request to join a room.
@ RoomList
Response with room list.
@ StartGame
Notification to start the game.
@ EntityDeath
Entity death notification.
File : GameManager.hpp License: MIT Author : Elias Josué HAJJAR LLAUQUEN elias-josue....
std::shared_ptr< Player > PlayerPtr
Shared pointer type for Player.
Definition Player.hpp:178
Component representing a network identifier for an entity.
Definition NetworkId.hpp:22
Component representing a network identifier for an entity.
Definition RoomId.hpp:22
Component representing position, rotation, and scale of an entity.
Definition Transform.hpp:23
Entity death notification data.
Definition Packet.hpp:362
uint32_t netId
Network entity identifier.
Definition Packet.hpp:363
Information about a game room Server OpCode.
Definition Packet.hpp:244
uint32_t roomId
Room identifier.
Definition Packet.hpp:245