Air-Trap 1.0.0
A multiplayer R-Type clone game engine built with C++23 and ECS architecture
Loading...
Searching...
No Matches
Room.cpp
Go to the documentation of this file.
1
8#include "Game/Room.hpp"
9#include "RType/Logger.hpp"
12
13#include <cstring>
14
15using namespace rtp::ecs;
16
17#ifdef DEBUG
18 #include <iostream>
19static const char *toString(rtp::server::Room::RoomType t)
20{
21 switch (t) {
22 case rtp::server::Room::RoomType::Lobby: return "Lobby";
23 case rtp::server::Room::RoomType::Public: return "Public";
24 case rtp::server::Room::RoomType::Private: return "Private";
25 default: return "Unknown";
26 }
27}
28
29static const char *toString(rtp::server::Room::PlayerType t)
30{
31 switch (t) {
32 case rtp::server::Room::PlayerType::Player: return "Player";
33 case rtp::server::Room::PlayerType::Spectator: return "Spectator";
34 default: return "Unknown";
35 }
36}
37#endif
38
39namespace rtp::server
40{
42 // Public API
44
45 Room::Room(Registry& registry, NetworkSyncSystem& network, uint32_t id, const std::string &name,
46 uint32_t maxPlayers, float difficulty, float speed, RoomType type,
47 uint32_t creatorSessionId, uint32_t levelId, uint32_t seed,
48 uint32_t durationMinutes)
49 : _registry(registry)
50 , _network(network)
51 , _id(id)
52 , _name(name)
53 , _maxPlayers(maxPlayers)
54 , _state(State::Waiting)
55 , _type(type)
56 , _creatorSessionId(creatorSessionId)
57 , _levelId(levelId)
58 , _seed(seed)
59 , _difficulty(difficulty)
60 , _speed(speed)
61 , durationMinutes(durationMinutes)
62 {
63 log::info("Room '{}' (ID: {}) created with max players: {} by session {}",
64 _name, _id, _maxPlayers, creatorSessionId);
65 }
66
68 {
69 log::info("Room '{}' (ID: {}) destroyed", _name, _id);
70 }
71
72 bool Room::canJoin() const
73 {
74 std::lock_guard lock(_mutex);
75 return _players.size() < _maxPlayers;
76 }
77
78 bool Room::addPlayer(const PlayerPtr &player, PlayerType type)
79 {
80 const uint32_t sessionId = player->getId();
81 bool added = false;
82 std::string username;
83 {
84 std::lock_guard lock(_mutex);
85
86 if (_bannedUsers.find(player->getUsername()) != _bannedUsers.end()) {
87 log::warning("Player {} cannot join Room '{}' (ID: {}): banned",
88 player->getUsername(), _name, _id);
89 } else if (type == PlayerType::Player && _players.size() >= _maxPlayers) {
90 log::warning("Player {} cannot join Room '{}' (ID: {}): Room is full",
91 player->getUsername(), _name, _id);
92 } else if (type == PlayerType::Player && _state != State::Waiting) {
93 log::warning("Player {} cannot join Room '{}' (ID: {}): Game already started",
94 player->getUsername(), _name, _id);
95 } else {
96 auto it = std::find_if(_players.begin(), _players.end(),
97 [sessionId](const auto &entry) { return entry.first->getId() == sessionId; });
98
99 if (it != _players.end()) {
100 log::warning("Player {} already in Room '{}' (ID: {})",
101 player->getUsername(), _name, _id);
102 } else {
103 _players.emplace_back(player, type);
104 log::info("Player {} joined Room '{}' (ID: {})",
105 player->getUsername(), _name, _id);
106 username = player->getUsername();
107 added = true;
108 }
109 }
110 }
111
112 if (_type != RoomType::Lobby) {
114 const uint8_t success = added ? 1 : 0;
115 response << success;
117 }
118
119 if (added && _type != RoomType::Lobby) {
120 if (type == PlayerType::Spectator) {
121 broadcastSystemMessage(username + " (spectator) has joined");
122 } else {
123 broadcastSystemMessage(username + " has joined the room");
124 }
125 }
126
127 return added;
128 }
129
130 void Room::removePlayer(uint32_t sessionId, bool disconnected)
131 {
132 bool removed = false;
133 std::string username;
135 {
136 std::lock_guard lock(_mutex);
137
138 auto before = _players.size();
139 for (const auto &entry : _players) {
140 if (entry.first->getId() == sessionId) {
141 username = entry.first->getUsername();
142 type = entry.second;
143 break;
144 }
145 }
146 _players.remove_if([sessionId](const auto &entry) {
147 return entry.first->getId() == sessionId;
148 });
149
150 if (_players.size() != before) {
151 removed = true;
152 log::info("Player with Session ID {} removed from Room '{}' (ID: {})",
153 sessionId, _name, _id);
154 } else {
155 log::warning("Player with Session ID {} not found in Room '{}' (ID: {})",
156 sessionId, _name, _id);
157 }
158 }
159
160 if (removed && _type != RoomType::Lobby) {
161 const std::string suffix = (type == PlayerType::Spectator) ? " (spectator)" : "";
162 if (disconnected) {
163 broadcastSystemMessage(username + suffix + " has disconnected");
164 } else {
165 broadcastSystemMessage(username + suffix + " has left the room");
166 }
167 }
168 }
169
170 void Room::broadcastSystemMessage(const std::string &message)
171 {
172 std::vector<uint32_t> sessions;
173 {
174 std::lock_guard lock(_mutex);
175 sessions.reserve(_players.size());
176 for (const auto &entry : _players) {
177 sessions.push_back(entry.first->getId());
178 }
179 }
180
182 payload.sessionId = 0;
183 payload.username[0] = '\0';
184 std::strncpy(payload.message, message.c_str(), sizeof(payload.message) - 1);
185 payload.message[sizeof(payload.message) - 1] = '\0';
186
188 packet << payload;
189
190 for (uint32_t sid : sessions) {
192 }
193 }
194
195 void Room::banUser(const std::string &username)
196 {
197 std::lock_guard lock(_mutex);
198 _bannedUsers.insert(username);
199 }
200
201 bool Room::isBanned(const std::string &username) const
202 {
203 std::lock_guard lock(_mutex);
204 return _bannedUsers.find(username) != _bannedUsers.end();
205 }
206
207 const std::list<PlayerPtr> Room::getPlayers(void) const
208 {
209 std::lock_guard lock(_mutex);
210
211 std::list<PlayerPtr> out;
212 out.resize(0);
213 for (const auto &entry : _players) {
214 out.push_back(entry.first);
215 }
216 return out;
217 }
218
219 uint32_t Room::getId(void) const
220 {
221 return _id;
222 }
223
225 {
226 return _type;
227 }
228
230 {
231 return _difficulty;
232 }
233
234 float Room::getSpeed() const
235 {
236 return _speed;
237 }
238
239 uint32_t Room::getLevelId() const
240 {
241 return _levelId;
242 }
243
244 uint32_t Room::getSeed() const
245 {
246 return _seed;
247 }
248
250 {
251 return durationMinutes;
252 }
253
254 Room::PlayerType Room::getPlayerType(uint32_t sessionId) const
255 {
256 std::lock_guard lock(_mutex);
257
258 for (const auto &entry : _players) {
259 if (entry.first->getId() == sessionId) {
260 return entry.second;
261 }
262 }
263
264 log::warning("Player with Session ID {} not found in Room '{}' (ID: {})",
265 sessionId, _name, _id);
266 return PlayerType::None;
267 }
268
269 void Room::setPlayerType(uint32_t sessionId, PlayerType type)
270 {
271 std::lock_guard lock(_mutex);
272
273 for (auto &entry : _players) {
274 if (entry.first->getId() == sessionId) {
275 entry.second = type;
276#ifdef DEBUG
277 log::info("Player with Session ID {} set to type {} in Room '{}' (ID: {})",
278 sessionId, toString(type), _name, _id);
279#endif
280 return;
281 }
282 }
283
284 log::warning("Player with Session ID {} not found in Room '{}' (ID: {})",
285 sessionId, _name, _id);
286 }
287
289 {
290 std::lock_guard lock(_mutex);
291 for (const auto &entry : _players) {
292 if (entry.second == PlayerType::Player) {
293 return true;
294 }
295 }
296 return false;
297 }
298
299 std::string Room::getName(void) const
300 {
301 return _name;
302 }
303
304 uint32_t Room::getMaxPlayers(void) const
305 {
306 return _maxPlayers;
307 }
308
309 uint32_t Room::getCurrentPlayerCount(void) const
310 {
311 std::lock_guard lock(_mutex);
312 return static_cast<uint32_t>(_players.size());
313 }
314
316 {
317 std::lock_guard lock(_mutex);
318 return _state;
319 }
320
321 bool Room::startGame(float dt)
322 {
323 std::lock_guard lock(_mutex);
324 _startedDt += dt;
325
326 if (_state != State::Waiting) {
327 log::warning("Room '{}' (ID: {}) cannot start game: Invalid state",
328 _name, _id);
329 return false;
330 }
331
333 log::info("Game started in Room '{}' (ID: {})", _name, _id);
334 return true;
335 }
336
338 {
339 std::lock_guard lock(_mutex);
340 if (_state != State::InGame) {
341 log::warning("Room '{}' (ID: {}) cannot finish game: Invalid state",
342 _name, _id);
343 return;
344 }
345
347 log::info("Game finished in Room '{}' (ID: {})", _name, _id);
348 }
349
350 void Room::update(uint32_t serverTick, float dt)
351 {
352 if (_state == State::InGame) {
353 _scoreTick += dt;
354 if (_scoreTick >= 1.0f) {
355 const int ticks = static_cast<int>(_scoreTick);
356 _scoreTick -= static_cast<float>(ticks);
357
358 std::lock_guard lock(_mutex);
359 for (auto &entry : _players) {
360 if (entry.second != PlayerType::Player) {
361 continue;
362 }
363 auto &player = entry.first;
364 if (!player) {
365 continue;
366 }
367 player->addScore(ticks);
369 net::ScoreUpdatePayload payload{player->getScore()};
370 packet << payload;
371 _network.sendPacketToSession(player->getId(), packet,
373 }
374 }
375 }
376 broadcastRoomState(serverTick);
377 }
378
379 void Room::broadcastRoomState(uint32_t serverTick)
380 {
381 std::vector<uint32_t> sessions;
382 {
383 std::lock_guard lock(_mutex);
384 if (_type == RoomType::Lobby)
385 return;
386 if (_state != State::InGame)
387 return;
388
389 sessions.reserve(_players.size());
390 for (const auto& entry : _players) {
391 sessions.push_back(entry.first->getId());
392 }
393 }
394
395 std::vector<net::EntitySnapshotPayload> snapshots;
396
397 auto transformsRes = _registry.get<ecs::components::Transform>();
398 auto networkIdsRes = _registry.get<ecs::components::NetworkId>();
399 auto roomIdsRes = _registry.get<ecs::components::RoomId>();
400 auto velocitiesRes = _registry.get<ecs::components::Velocity>();
401
402 if (!transformsRes || !networkIdsRes || !roomIdsRes)
403 return;
404
405 auto& transforms = transformsRes->get();
406 auto& networkIds = networkIdsRes->get();
407 auto& roomIds = roomIdsRes->get();
408
409 for (auto entity : transforms.entities()) {
410 if (!networkIds.has(entity) || !roomIds.has(entity))
411 continue;
412 if (roomIds[entity].id != _id)
413 continue;
414
415 Vec2f velocity{0.0f, 0.0f};
416 if (velocitiesRes) {
417 auto& velocities = velocitiesRes->get();
418 if (velocities.has(entity)) {
419 const auto& vel = velocities[entity];
420 velocity = vel.direction * vel.speed;
421 }
422 }
423
424 snapshots.push_back({
425 networkIds[entity].id,
426 transforms[entity].position,
427 velocity,
428 transforms[entity].rotation
429 });
430 }
431
432 // log::debug("Room '{}' (ID: {}) broadcasting {} entity snapshots",
433 // _name, _id, snapshots.size());
434 if (snapshots.empty())
435 return;
436
438 net::RoomSnapshotPayload header = {
439 serverTick,
440 static_cast<uint16_t>(snapshots.size())
441 };
442 packet << header << snapshots;
443
444 for (uint32_t sid : sessions) {
446 }
447 }
448} // namespace rtp::server
Logger declaration with support for multiple log levels.
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.
float getDifficulty(void) const
Get the difficulty level of the room.
Definition Room.cpp:229
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
@ None
Undefined player type.
Definition Room.hpp:62
void banUser(const std::string &username)
Definition Room.cpp:195
std::list< std::pair< PlayerPtr, PlayerType > > _players
List of player ptr's in the room.
Definition Room.hpp:232
std::mutex _mutex
Mutex to protect access to room state.
Definition Room.hpp:249
bool hasActivePlayers() const
Check if there are any active players (non-spectators)
Definition Room.cpp:288
uint32_t _id
Unique room identifier.
Definition Room.hpp:228
uint32_t getLevelId(void) const
Get the level identifier of the room.
Definition Room.cpp:239
PlayerType getPlayerType(uint32_t sessionId) const
Get the type of a player in the room.
Definition Room.cpp:254
float _startedDt
Delta time accumulator since game start.
Definition Room.hpp:246
uint32_t getCurrentPlayerCount(void) const
Get the current number of players in the room.
Definition Room.cpp:309
void removePlayer(uint32_t sessionId, bool disconnected=false)
Remove a player from the room.
Definition Room.cpp:130
uint32_t durationMinutes
Duration of the game in minutes.
Definition Room.hpp:244
uint32_t _maxPlayers
Maximum number of players allowed.
Definition Room.hpp:230
RoomType _type
Type of the room.
Definition Room.hpp:234
RoomType getType(void) const
Get the type of the room.
Definition Room.cpp:224
const std::list< PlayerPtr > getPlayers(void) const
Get the list of players in the room.
Definition Room.cpp:207
uint32_t getDurationMinutes(void) const
Get the duration in minutes of the room.
Definition Room.cpp:249
void broadcastRoomState(uint32_t serverTick)
Broadcast the current room state to all connected players.
Definition Room.cpp:379
float getSpeed(void) const
Get the speed multiplier of the room.
Definition Room.cpp:234
std::string _name
Name of the room.
Definition Room.hpp:229
uint32_t getId(void) const
Get the unique identifier of the room.
Definition Room.cpp:219
bool addPlayer(const PlayerPtr &player, PlayerType type=PlayerType::Player)
Add a player to the room.
Definition Room.cpp:78
State
Enum representing the state of the room.
Definition Room.hpp:41
@ InGame
Game in progress.
Definition Room.hpp:43
@ Waiting
Waiting for players.
Definition Room.hpp:42
@ Finished
Game finished.
Definition Room.hpp:44
~Room()
Destructor for Room.
Definition Room.cpp:67
Room(ecs::Registry &registry, NetworkSyncSystem &network, uint32_t id, const std::string &name, uint32_t maxPlayers, float difficulty, float speed, RoomType type, uint32_t creatorSessionId=0, uint32_t levelId=1, uint32_t seed=0, uint32_t durationMinutes=0)
Constructor for Room.
Definition Room.cpp:45
void broadcastSystemMessage(const std::string &message)
Definition Room.cpp:170
float _scoreTick
Score tick accumulator.
Definition Room.hpp:251
NetworkSyncSystem _network
Reference to the server network manager.
Definition Room.hpp:225
bool startGame(float dt)
Start the game in the room.
Definition Room.cpp:321
bool canJoin(void) const
Add a player to the room.
Definition Room.cpp:72
uint32_t _seed
Seed for random generation in the room.
Definition Room.hpp:241
uint32_t _levelId
Level identifier for the room.
Definition Room.hpp:240
void setPlayerType(uint32_t sessionId, PlayerType type)
Set the type of a player in the room.
Definition Room.cpp:269
State _state
Current state of the room.
Definition Room.hpp:233
float _speed
Speed multiplier for the room 0 -> 2.
Definition Room.hpp:243
std::string getName(void) const
Get the name of the room.
Definition Room.cpp:299
State getState(void) const
Get the current state of the room.
Definition Room.cpp:315
std::unordered_set< std::string > _bannedUsers
Banned usernames.
Definition Room.hpp:250
uint32_t getSeed(void) const
Get the random seed of the room.
Definition Room.cpp:244
void update(uint32_t serverTick, float dt)
Update the room state.
Definition Room.cpp:350
float _difficulty
Difficulty multiplier for the room 0 -> 1.
Definition Room.hpp:242
@ 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
void forceFinishGame(void)
Finish the game in the room.
Definition Room.cpp:337
bool isBanned(const std::string &username) const
Definition Room.cpp:201
ecs::Registry & _registry
Reference to the entity registry.
Definition Room.hpp:226
uint32_t getMaxPlayers(void) const
Get the maximum number of players allowed in the room.
Definition Room.cpp:304
File : RenderSystem.hpp License: MIT Author : Elias Josué HAJJAR LLAUQUEN elias-josue....
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.
@ RoomChatReceived
Chat message received in room.
@ JoinRoom
Request to join a room.
@ RoomUpdate
Notification of room update.
@ ScoreUpdate
Player score update.
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
constexpr std::string_view toString(ErrorCode e) noexcept
Convert an ErrorCode to its string representation.
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
Component representing a 2D velocity.
Definition Velocity.hpp:17
Data for receiving chat messages in a room Server OpCode.
Definition Packet.hpp:326
uint32_t sessionId
Sender's session identifier.
Definition Packet.hpp:327
Player score update notification data.
Definition Packet.hpp:397