14 : _eventBus(eventBus), _isSpectator(isSpectator), _host(
createClientHost()) {}
28 std::unique_ptr<IAddress> address =
createAddress(host, port);
90 while (!stopToken.stop_requested()) {
92 std::this_thread::sleep_for(std::chrono::milliseconds(10));
97 auto eventOpt =
_host->service(0);
99 std::this_thread::sleep_for(std::chrono::milliseconds(1));
103 auto &
event = *eventOpt;
105 switch (event.type) {
110 LOG_DEBUG(
"[Replicator] Network thread received packet type: ",
111 static_cast<int>(messageType));
120 if (smoothed == 0.0f) {
122 smoothed =
static_cast<float>(enetRtt);
129 _latency.store(
static_cast<uint32_t
>(smoothed));
133 std::string messageContent;
142 messageContent = handshakeResp.message;
144 if (handshakeResp.accepted) {
147 LOG_INFO(
"✓ Authenticated! Player ID: ", handshakeResp.playerId,
148 ", Name: ", handshakeResp.playerName);
155 LOG_ERROR(
"✗ Authentication failed: ", handshakeResp.message);
157 }
catch (
const std::exception &e) {
158 LOG_ERROR(
"Failed to parse HandshakeResponse: ", e.what());
159 messageContent =
"Authentication error";
167 if (registerResp.success) {
168 LOG_INFO(
"✓ Registration successful: ", registerResp.message);
169 messageContent =
"Registration successful: " + registerResp.message;
173 LOG_WARNING(
"✗ Registration failed: ", registerResp.message);
174 messageContent =
"Registration failed: " + registerResp.message;
178 }
catch (
const std::exception &e) {
179 LOG_ERROR(
"Failed to parse RegisterResponse: ", e.what());
180 messageContent =
"Registration error";
188 if (loginResp.success) {
189 LOG_INFO(
"✓ Login successful: ", loginResp.message);
190 messageContent =
"Login successful: " + loginResp.message;
196 loginResp.autoMatchmaking ?
"1" :
"0"));
205 LOG_WARNING(
"✗ Login failed: ", loginResp.message);
206 messageContent =
"Login failed: " + loginResp.message;
209 }
catch (
const std::exception &e) {
210 LOG_ERROR(
"Failed to parse LoginResponse: ", e.what());
211 messageContent =
"Login error";
215 messageContent =
"GameStart received";
255 auto &netEvent = *eventOpt;
261 LOG_DEBUG(
"[Replicator] Popped message type: ",
static_cast<int>(messageType));
268 auto gameStart = S2C::GameStart::deserialize(payload);
271 LOG_INFO(
" - Your entity ID: ", gameStart.yourEntityId);
272 LOG_INFO(
" - Server tick: ", gameStart.initialState.serverTick);
273 LOG_INFO(
" - Total entities: ", gameStart.initialState.entities.size());
276 int players = 0, enemies = 0, bullets = 0;
277 for (
const auto &entity : gameStart.initialState.entities) {
278 if (entity.type == Shared::EntityType::Player)
280 else if (entity.type == Shared::EntityType::EnemyType1)
282 else if (entity.type == Shared::EntityType::PlayerBullet ||
283 entity.type == Shared::EntityType::EnemyBullet)
290 }
catch (
const std::exception &e) {
291 LOG_ERROR(
"Error decoding GameStart: ", e.what());
297 auto roomList = S2C::RoomList::deserialize(payload);
299 LOG_INFO(
"✓ RoomList received with ", roomList.rooms.size(),
" rooms");
303 }
catch (
const std::exception &e) {
304 LOG_ERROR(
"Error decoding RoomList: ", e.what());
310 auto roomState = S2C::RoomState::deserialize(payload);
312 LOG_INFO(
"✓ RoomState received: ", roomState.roomName,
" with ", roomState.players.size(),
317 }
catch (
const std::exception &e) {
318 LOG_ERROR(
"Error decoding RoomState: ", e.what());
320 }
else if (!netEvent.getMessageContent().empty()) {
321 LOG_DEBUG(
"Received from server: ", netEvent.getMessageContent());
340 const std::string &password) {
352 handshakeData.
timestamp = std::chrono::duration_cast<std::chrono::milliseconds>(
353 std::chrono::system_clock::now().time_since_epoch())
356 std::vector<uint8_t> payload = createHandshakeRequest(handshakeData);
369 LOG_ERROR(
"Cannot send RegisterAccount: Not connected");
390 LOG_ERROR(
"Cannot send LoginAccount: Not connected");
431 float gameSpeedMultiplier) {
439 C2S::CreateRoom request(roomName, maxPlayers, isPrivate, gameSpeedMultiplier);
452 LOG_ERROR(
"Cannot send JoinRoom: Not connected");
456 LOG_INFO(
"Sending JoinRoom request for room: ", roomId);
474 LOG_ERROR(
"Cannot send AutoMatchmaking: Not connected");
478 LOG_INFO(
"Sending AutoMatchmaking request");
497 LOG_ERROR(
"Cannot update auto-matchmaking preference: Not connected");
501 LOG_INFO(
"Updating auto-matchmaking preference: ", enabled ?
"ON" :
"OFF",
502 " (preference only, NOT triggering matchmaking)");
521 LOG_ERROR(
"Cannot send StartGame: Not connected");
525 LOG_INFO(
"Sending StartGame request");
554 LOG_INFO(
"[Replicator] Requesting room list from server");
565 LOG_INFO(
"[Replicator] Leaving current room");
581 LOG_ERROR(
"[Replicator] Cannot send chat message: Not connected");
587 LOG_INFO(
"[Replicator] Sending chat message: '", message,
"'");
593 LOG_DEBUG(
"[Replicator] Chat message serialized, payload size: ", payload.size());
599 LOG_DEBUG(
"[Replicator] Network packet created, total size: ", requestData.size());
605 LOG_INFO(
"[Replicator] Chat message sent: ", (sent ?
"SUCCESS" :
"FAILED"));
@ RECEIVE
A packet was received.
@ CONNECT
A peer has connected.
@ DISCONNECT
A peer has disconnected.
@ RELIABLE
Packet must be received by the target peer and resent if dropped.
@ CONNECTION_SUCCEEDED
Connection has succeeded.
@ CONNECTING
Connection to peer is being established.
@ CONNECTED
Peer is connected.
std::unique_ptr< IPacket > createPacket(const std::vector< uint8_t > &data, uint32_t flags)
Create a network packet with the given data and flags.
std::unique_ptr< IAddress > createAddress(const std::string &host, uint16_t port)
Create a network address.
std::unique_ptr< IHost > createClientHost(size_t channelLimit, uint32_t incomingBandwidth, uint32_t outgoingBandwidth)
Create a host for client-side networking.
@ APPLY_AUTO_MATCHMAKING_PREF
Type-safe event publication/subscription system.
void publish(const T &event)
Publish an event to all subscribers.
virtual PeerState getState() const =0
Get the current state of this peer.
virtual bool send(std::unique_ptr< IPacket > packet, uint8_t channelID=0)=0
Send a packet to this peer.
virtual void disconnectNow(uint32_t data=0)=0
Force an immediate disconnect from this peer.
virtual uint32_t getRoundTripTime() const =0
Get the round-trip time (ping) to this peer.
Event representing a network message.
void setMessageContent(const std::string &content)
Set decoded message content.
std::vector< uint8_t > serialize() const
Chat message sent from client to server.
std::vector< uint8_t > serialize() const
Serialize to byte vector.
std::vector< uint8_t > serialize() const
std::vector< uint8_t > serialize() const
std::vector< uint8_t > serialize() const
Request list of available rooms.
std::vector< uint8_t > serialize() const
Request to login with existing account.
std::vector< uint8_t > serialize() const
Serialize to Cap'n Proto binary format.
Request to register a new user account.
std::vector< uint8_t > serialize() const
Serialize to Cap'n Proto binary format.
static HandshakeResponse deserialize(const std::vector< uint8_t > &data)
static LoginResponse deserialize(const std::vector< uint8_t > &data)
Deserialize from Cap'n Proto binary format.
static RegisterResponse deserialize(const std::vector< uint8_t > &data)
Deserialize from Cap'n Proto binary format.
bool isConnected() const
Check if connected to server.
bool sendStartGame()
Send start game request to server.
bool sendLoginAccount(const std::string &username, const std::string &password)
Send login request to server.
bool sendListRooms()
Send list rooms request to server.
bool sendAutoMatchmaking()
Send auto-matchmaking request to server.
bool connect(const std::string &host, uint16_t port)
Connect to the game server.
void startNetworkThread()
Start the dedicated network thread.
void processMessages()
Process incoming network messages.
std::unique_ptr< IHost > _host
ThreadSafeQueue< NetworkEvent > _incomingMessages
Queue for messages from network thread.
void disconnect()
Disconnect from the server.
std::atomic< bool > _autoMatchmakingPreference
void networkThreadLoop(std::stop_token stopToken)
Network thread main loop.
Replicator(EventBus &eventBus, bool isSpectator=false)
Constructor with EventBus reference.
uint32_t getPacketLoss() const
Get packet loss rate as percentage.
void onInputEvent(const InputEvent &event)
Handle an incoming packet from the network.
void processIncomingPacket(const std::vector< uint8_t > &packet)
bool sendRequestRoomList()
Request the list of available rooms from server.
std::jthread _networkThread
Dedicated network thread.
bool isSpectator() const
Check if in spectator mode.
void sendPacket(NetworkMessageType type, const std::vector< uint8_t > &data)
Send a packet to the server.
static constexpr float PING_SMOOTHING_FACTOR
bool sendRegisterAccount(const std::string &username, const std::string &password)
Send register account request to server.
std::atomic< uint32_t > _myPlayerId
bool sendLeaveRoom()
Send request to leave current room.
bool updateAutoMatchmakingPreference(bool enabled)
Update auto-matchmaking preference on server.
uint32_t getLatency() const
Get current latency in milliseconds.
std::atomic< float > _smoothedLatency
bool sendCreateRoom(const std::string &roomName, uint32_t maxPlayers, bool isPrivate, float gameSpeedMultiplier=1.0f)
Send create room request to server.
std::string _lastLoginUsername
void stopNetworkThread()
Stop the dedicated network thread.
std::atomic< bool > _authenticated
bool isAuthenticated() const
Check if authenticated with server.
bool sendConnectRequest(const std::string &playerName, const std::string &username, const std::string &password)
Send connect request to server with player name.
bool sendJoinRoom(const std::string &roomId)
Send join room request to server.
std::atomic< bool > _connected
bool sendChatMessage(const std::string &message)
Send chat message to server.
std::atomic< uint32_t > _latency
void push(T item)
Push an item to the queue.
std::optional< T > tryPop()
Try to pop an item without blocking.
NetworkMessageType
Types of network messages exchanged between client and server.
@ WORLD_STATE
Authoritative world state from server.
@ CONNECT
Client connection request.
@ DISCONNECT
Client disconnection notification.
Helper functions for connection protocol messages.
@ C2S_UPDATE_AUTO_MM_PREF
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)
All game messages for R-Type network protocol.
std::string clientVersion
Request from host to start the game in their room.
std::vector< uint8_t > serialize() const