Bomberman Multiplayer
Authoritative multiplayer networking layer for Bomberman.
Loading...
Searching...
No Matches
NetCommon.h
Go to the documentation of this file.
1#ifndef BOMBERMAN_NET_NETCOMMON_H
2#define BOMBERMAN_NET_NETCOMMON_H
3
4#include <array>
5#include <cstddef>
6#include <cstdint>
7#include <cstring>
8#include <string_view>
9
10#include "Const.h"
11#include "Sim/PowerupConfig.h"
12
33namespace bomberman::net
34{
35 // =================================================================================================================
36 // ===== Protocol Definition =======================================================================================
37 // =================================================================================================================
38
39 // ----- Protocol Constants -----
40
41 constexpr uint16_t kProtocolVersion = 9;
42
43 constexpr uint16_t kDefaultServerPort = 12345;
44 constexpr std::size_t kMaxPacketSize = 1400;
45 constexpr uint32_t kInputLogEveryN = 30;
46 constexpr uint32_t kSnapshotLogEveryN = 30;
47 constexpr uint32_t kPeerPingIntervalMs = 500;
48 constexpr uint32_t kPeerTimeoutLimit = 8;
49 constexpr uint32_t kPeerTimeoutMinimumMs = 5000;
50 constexpr uint32_t kPeerTimeoutMaximumMs = 10000;
51
53 constexpr std::size_t kPlayerNameMax = 16;
54 constexpr uint8_t kMaxPlayers = 4;
55 constexpr uint8_t kMaxSnapshotBombs = kMaxPlayers * 4;
57 constexpr std::size_t kMaxExplosionBlastCells =
58 1u + 4u * (static_cast<std::size_t>((tileArrayWidth > tileArrayHeight) ?
59 tileArrayWidth :
60 tileArrayHeight) - 1u);
62
64 constexpr uint32_t kFirstInputSeq = 1;
65
67 constexpr uint8_t kMaxInputBatchSize = 16;
68
69 // ----- Input Bitmask Constants -----
70
71 constexpr uint8_t kInputUp = 0x01;
72 constexpr uint8_t kInputDown = 0x02;
73 constexpr uint8_t kInputLeft = 0x04;
74 constexpr uint8_t kInputRight = 0x08;
75 constexpr uint8_t kInputBomb = 0x10;
76
78 constexpr uint8_t kInputKnownBits = kInputUp | kInputDown | kInputLeft | kInputRight | kInputBomb;
79
81 constexpr int8_t buttonsToMoveX(uint8_t buttons)
82 {
83 int8_t dx = 0;
84 if (buttons & kInputRight) dx += 1;
85 if (buttons & kInputLeft) dx -= 1;
86 return dx;
87 }
88
90 constexpr int8_t buttonsToMoveY(uint8_t buttons)
91 {
92 int8_t dy = 0;
93 if (buttons & kInputDown) dy += 1;
94 if (buttons & kInputUp) dy -= 1;
95 return dy;
96 }
97
98 // ----- ENet Channels -----
99
107 enum class EChannel : uint8_t
108 {
109 ControlReliable = 0,
110 GameplayReliable = 1,
111 InputUnreliable = 2,
112 SnapshotUnreliable = 3,
113 CorrectionUnreliable = 4
114 };
115 constexpr std::size_t kChannelCount = 5;
116
118 constexpr std::string_view channelName(uint8_t id)
119 {
120 switch (id)
121 {
122 case static_cast<uint8_t>(EChannel::ControlReliable): return "ControlReliable";
123 case static_cast<uint8_t>(EChannel::GameplayReliable): return "GameplayReliable";
124 case static_cast<uint8_t>(EChannel::InputUnreliable): return "InputUnreliable";
125 case static_cast<uint8_t>(EChannel::SnapshotUnreliable): return "SnapshotUnreliable";
126 case static_cast<uint8_t>(EChannel::CorrectionUnreliable): return "CorrectionUnreliable";
127 default: return "Unknown";
128 }
129 }
130
131 // ----- Wire Size Constants -----
132 //
133 // All current protocol payloads are fixed-size on the wire. That keeps
134 // packet validation cheap and lets both sides reject malformed packets
135 // immediately using only the message type and payload size.
136
137 constexpr std::size_t kPacketHeaderSize =
138 sizeof(uint8_t) + // type
139 sizeof(uint16_t); // payloadSize
140
141 constexpr std::size_t kMsgHelloSize =
142 sizeof(uint16_t) + // protocolVersion
143 kPlayerNameMax; // name (fixed-size field)
144
145 constexpr std::size_t kMsgWelcomeSize =
146 sizeof(uint16_t) + // protocolVersion
147 sizeof(uint8_t) + // playerId
148 sizeof(uint16_t); // serverTickRate
149
150 constexpr std::size_t kMsgRejectSize =
151 sizeof(uint8_t) + // reason
152 sizeof(uint16_t); // expectedProtocolVersion
153
154 constexpr std::size_t kMsgLevelInfoSize =
155 sizeof(uint32_t) + // matchId
156 sizeof(uint32_t); // mapSeed
157
158 constexpr std::size_t kMsgLobbyReadySize =
159 sizeof(uint8_t); // ready
160
161 constexpr std::size_t kMsgMatchLoadedSize =
162 sizeof(uint32_t); // matchId
163
164 constexpr std::size_t kMsgMatchStartSize =
165 sizeof(uint32_t) + // matchId
166 sizeof(uint32_t) + // goShowServerTick
167 sizeof(uint32_t); // unlockServerTick
168
169 constexpr std::size_t kMsgMatchCancelledSize =
170 sizeof(uint32_t); // matchId
171
172 constexpr std::size_t kMsgMatchResultSize =
173 sizeof(uint32_t) + // matchId
174 sizeof(uint8_t) + // result
175 sizeof(uint8_t) + // winnerPlayerId
176 kPlayerNameMax; // winnerName
177
178 constexpr std::size_t kMsgLobbyStateSeatSize =
179 sizeof(uint8_t) + // flags
180 sizeof(uint8_t) + // wins
181 kPlayerNameMax; // name (fixed-size field)
182
183 constexpr std::size_t kMsgLobbyStateSize =
184 sizeof(uint8_t) + // phase
185 sizeof(uint8_t) + // countdownSecondsRemaining
186 sizeof(uint16_t) + // reserved
187 kMaxPlayers * kMsgLobbyStateSeatSize;
188
189 constexpr std::size_t kMsgInputSize =
190 sizeof(uint32_t) + // baseInputSeq
191 sizeof(uint8_t) + // count
192 kMaxInputBatchSize; // inputs[kMaxInputBatchSize]
193
194 constexpr std::size_t kMsgSnapshotSize =
195 sizeof(uint32_t) + // matchId
196 sizeof(uint32_t) + // serverTick
197 sizeof(uint8_t) + // playerCount
198 sizeof(uint8_t) + // bombCount
199 sizeof(uint8_t) + // powerupCount
201 (sizeof(uint8_t) + // playerId
202 sizeof(int16_t) + // xQ
203 sizeof(int16_t) + // yQ
204 sizeof(uint8_t)) + // flags
206 (sizeof(uint8_t) + // ownerId
207 sizeof(uint8_t) + // col
208 sizeof(uint8_t) + // row
209 sizeof(uint8_t)) + // radius
211 (sizeof(uint8_t) + // type
212 sizeof(uint8_t) + // col
213 sizeof(uint8_t)); // row
214
215 constexpr std::size_t kMsgCorrectionSize =
216 sizeof(uint32_t) + // matchId
217 sizeof(uint32_t) + // serverTick
218 sizeof(uint32_t) + // lastProcessedInputSeq
219 sizeof(int16_t) + // xQ
220 sizeof(int16_t) + // yQ
221 sizeof(uint8_t) + // playerFlags
222 3u; // reserved
223
224 constexpr std::size_t kMsgBombPlacedSize =
225 sizeof(uint32_t) + // matchId
226 sizeof(uint32_t) + // serverTick
227 sizeof(uint32_t) + // explodeTick
228 sizeof(uint8_t) + // ownerId
229 sizeof(uint8_t) + // col
230 sizeof(uint8_t) + // row
231 sizeof(uint8_t); // radius
232
233 constexpr std::size_t kMsgExplosionResolvedSize =
234 sizeof(uint32_t) + // matchId
235 sizeof(uint32_t) + // serverTick
236 sizeof(uint8_t) + // ownerId
237 sizeof(uint8_t) + // originCol
238 sizeof(uint8_t) + // originRow
239 sizeof(uint8_t) + // radius
240 sizeof(uint8_t) + // killedPlayerMask
241 sizeof(uint8_t) + // blastCellCount
242 sizeof(uint8_t) + // destroyedBrickCount
244 (sizeof(uint8_t) + // col
245 sizeof(uint8_t)) + // row
247 (sizeof(uint8_t) + // col
248 sizeof(uint8_t)); // row
249
251 static_assert(sizeof(char) == 1, "Unexpected char size");
252
253 static_assert(kPacketHeaderSize == 3, "PacketHeader size mismatch");
254 static_assert(kMsgHelloSize == 18, "MsgHello size mismatch");
255 static_assert(kMsgWelcomeSize == 5, "MsgWelcome size mismatch");
256 static_assert(kMsgRejectSize == 3, "MsgReject size mismatch");
257 static_assert(kMsgLevelInfoSize == 8, "MsgLevelInfo size mismatch");
258 static_assert(kMsgLobbyReadySize == 1, "MsgLobbyReady size mismatch");
259 static_assert(kMsgMatchLoadedSize == 4, "MsgMatchLoaded size mismatch");
260 static_assert(kMsgMatchStartSize == 12, "MsgMatchStart size mismatch");
261 static_assert(kMsgMatchCancelledSize == 4, "MsgMatchCancelled size mismatch");
262 static_assert(kMsgMatchResultSize == 22, "MsgMatchResult size mismatch");
263 static_assert(kMsgLobbyStateSeatSize == 18, "MsgLobbyState seat size mismatch");
264 static_assert(kMsgLobbyStateSize == 76, "MsgLobbyState size mismatch");
265 static_assert(kMsgInputSize == 21, "MsgInput size mismatch");
266 static_assert(kMsgSnapshotSize == 111, "MsgSnapshot size mismatch");
267 static_assert(kMsgCorrectionSize == 20, "MsgCorrection size mismatch");
268 static_assert(kMsgBombPlacedSize == 16, "MsgBombPlaced size mismatch");
269 static_assert(kMsgExplosionResolvedSize == 497,"MsgExplosionResolved size mismatch");
270
271 constexpr std::size_t kSnapshotPlayersOffset =
272 sizeof(uint32_t) + // matchId
273 sizeof(uint32_t) + // serverTick
274 sizeof(uint8_t) + // playerCount
275 sizeof(uint8_t) + // bombCount
276 sizeof(uint8_t); // powerupCount
277 constexpr std::size_t kSnapshotPlayerEntrySize =
278 sizeof(uint8_t) + // playerId
279 sizeof(int16_t) + // xQ
280 sizeof(int16_t) + // yQ
281 sizeof(uint8_t); // flags
282 constexpr std::size_t kSnapshotBombsOffset =
283 kSnapshotPlayersOffset + kMaxPlayers * kSnapshotPlayerEntrySize;
284 constexpr std::size_t kSnapshotBombEntrySize =
285 sizeof(uint8_t) + // ownerId
286 sizeof(uint8_t) + // col
287 sizeof(uint8_t) + // row
288 sizeof(uint8_t); // radius
289 constexpr std::size_t kSnapshotPowerupsOffset =
290 kSnapshotBombsOffset + kMaxSnapshotBombs * kSnapshotBombEntrySize;
291 constexpr std::size_t kSnapshotPowerupEntrySize =
292 sizeof(uint8_t) + // type
293 sizeof(uint8_t) + // col
294 sizeof(uint8_t); // row
295
296 constexpr std::size_t kExplosionBlastCellsOffset =
297 sizeof(uint32_t) + // matchId
298 sizeof(uint32_t) + // serverTick
299 sizeof(uint8_t) + // ownerId
300 sizeof(uint8_t) + // originCol
301 sizeof(uint8_t) + // originRow
302 sizeof(uint8_t) + // radius
303 sizeof(uint8_t) + // killedPlayerMask
304 sizeof(uint8_t) + // blastCellCount
305 sizeof(uint8_t); // destroyedBrickCount
306 constexpr std::size_t kExplosionCellEntrySize =
307 sizeof(uint8_t) + // col
308 sizeof(uint8_t); // row
309 constexpr std::size_t kExplosionDestroyedBricksOffset =
310 kExplosionBlastCellsOffset + kMaxExplosionBlastCells * kExplosionCellEntrySize;
311
312 // ----- Message Types -----
313
315 enum class EMsgType : uint8_t
316 {
317 Invalid = 0x00,
318 Hello = 0x01,
319 Welcome = 0x02,
320 Reject = 0x03,
321 LevelInfo = 0x04,
322 LobbyState = 0x05,
323 LobbyReady = 0x06,
324 MatchLoaded = 0x07,
325 MatchStart = 0x08,
326 MatchCancelled = 0x09,
327 MatchResult = 0x0A,
328
329 Input = 0x10,
330 Snapshot = 0x11,
331 Correction = 0x12,
332 BombPlaced = 0x13,
333 ExplosionResolved = 0x14
334 };
335
337 inline bool isValidMsgType(uint8_t raw)
338 {
339 return raw == static_cast<uint8_t>(EMsgType::Hello) ||
340 raw == static_cast<uint8_t>(EMsgType::Welcome) ||
341 raw == static_cast<uint8_t>(EMsgType::Reject) ||
342 raw == static_cast<uint8_t>(EMsgType::LevelInfo) ||
343 raw == static_cast<uint8_t>(EMsgType::LobbyState) ||
344 raw == static_cast<uint8_t>(EMsgType::LobbyReady) ||
345 raw == static_cast<uint8_t>(EMsgType::MatchLoaded) ||
346 raw == static_cast<uint8_t>(EMsgType::MatchStart) ||
347 raw == static_cast<uint8_t>(EMsgType::MatchCancelled) ||
348 raw == static_cast<uint8_t>(EMsgType::MatchResult) ||
349 raw == static_cast<uint8_t>(EMsgType::Input) ||
350 raw == static_cast<uint8_t>(EMsgType::Snapshot) ||
351 raw == static_cast<uint8_t>(EMsgType::Correction) ||
352 raw == static_cast<uint8_t>(EMsgType::BombPlaced) ||
353 raw == static_cast<uint8_t>(EMsgType::ExplosionResolved);
354 }
355
357 constexpr std::string_view msgTypeName(EMsgType type)
358 {
359 switch (type)
360 {
361 case EMsgType::Invalid: return "Invalid";
362 case EMsgType::Hello: return "Hello";
363 case EMsgType::Welcome: return "Welcome";
364 case EMsgType::Reject: return "Reject";
365 case EMsgType::LevelInfo: return "LevelInfo";
366 case EMsgType::LobbyState: return "LobbyState";
367 case EMsgType::LobbyReady: return "LobbyReady";
368 case EMsgType::MatchLoaded: return "MatchLoaded";
369 case EMsgType::MatchStart: return "MatchStart";
370 case EMsgType::MatchCancelled: return "MatchCancelled";
371 case EMsgType::MatchResult: return "MatchResult";
372 case EMsgType::Input: return "Input";
373 case EMsgType::Snapshot: return "Snapshot";
374 case EMsgType::Correction: return "Correction";
375 case EMsgType::BombPlaced: return "BombPlaced";
376 case EMsgType::ExplosionResolved: return "ExplosionResolved";
377 default: return "Unknown";
378 }
379 }
380
388 {
389 switch (type)
390 {
391 case EMsgType::Hello:
393 case EMsgType::Reject:
401 return EChannel::ControlReliable;
402 case EMsgType::Input:
403 return EChannel::InputUnreliable;
405 return EChannel::SnapshotUnreliable;
407 return EChannel::CorrectionUnreliable;
410 return EChannel::GameplayReliable;
411 default:
412 return EChannel::ControlReliable;
413 }
414 }
415
417 constexpr bool isValidPlayerMask(const uint32_t mask)
418 {
419 return (mask & ~((1u << kMaxPlayers) - 1u)) == 0;
420 }
421
423 constexpr std::size_t explosionBlastCellOffset(std::size_t index)
424 {
425 return kExplosionBlastCellsOffset + index * kExplosionCellEntrySize;
426 }
427
429 constexpr std::size_t explosionDestroyedBrickOffset(std::size_t index)
430 {
431 return kExplosionDestroyedBricksOffset + index * kExplosionCellEntrySize;
432 }
433
435 constexpr bool isValidTileCell(const uint8_t col, const uint8_t row)
436 {
437 return col < ::bomberman::tileArrayWidth && row < ::bomberman::tileArrayHeight;
438 }
439
441 constexpr uint16_t tileCellKey(const uint8_t col, const uint8_t row)
442 {
443 return static_cast<uint16_t>(row) * static_cast<uint16_t>(::bomberman::tileArrayWidth) +
444 static_cast<uint16_t>(col);
445 }
446
448 constexpr bool isExpectedChannelFor(EMsgType type, uint8_t channelId)
449 {
450 return channelId == static_cast<uint8_t>(expectedChannelFor(type));
451 }
452
460 constexpr std::size_t expectedPayloadSize(EMsgType type)
461 {
462 switch (type)
463 {
464 case EMsgType::Hello: return kMsgHelloSize;
465 case EMsgType::Welcome: return kMsgWelcomeSize;
466 case EMsgType::Reject: return kMsgRejectSize;
467 case EMsgType::LevelInfo: return kMsgLevelInfoSize;
468 case EMsgType::LobbyState: return kMsgLobbyStateSize;
469 case EMsgType::LobbyReady: return kMsgLobbyReadySize;
470 case EMsgType::MatchLoaded: return kMsgMatchLoadedSize;
471 case EMsgType::MatchStart: return kMsgMatchStartSize;
472 case EMsgType::MatchCancelled: return kMsgMatchCancelledSize;
473 case EMsgType::MatchResult: return kMsgMatchResultSize;
474 case EMsgType::Input: return kMsgInputSize;
475 case EMsgType::Snapshot: return kMsgSnapshotSize;
476 case EMsgType::Correction: return kMsgCorrectionSize;
477 case EMsgType::BombPlaced: return kMsgBombPlacedSize;
478 case EMsgType::ExplosionResolved: return kMsgExplosionResolvedSize;
479 default: return 0;
480 }
481 }
482
483 // =================================================================================================================
484 // ===== Wire Payload Types ========================================================================================
485 // =================================================================================================================
486 //
487 // These are protocol payload models, not gameplay-domain classes. Their
488 // field layout intentionally mirrors the fixed wire format defined above.
489
492 {
494 uint16_t payloadSize = 0;
495 };
496
497 // ----- Control Message Payloads -----
498
500 struct MsgHello
501 {
503
512 };
513
516 {
518 uint8_t playerId;
519 uint16_t serverTickRate;
520 };
521
524 {
525 enum class EReason : uint8_t
526 {
527 VersionMismatch = 0x01,
528 ServerFull = 0x02,
529 Banned = 0x03,
530 GameInProgress = 0x04,
531 Other = 0xFF
532 };
533
534 EReason reason = EReason::Other;
535
537 };
538
546 {
547 uint32_t matchId = 0;
548 uint32_t mapSeed = 0;
549 };
550
553 {
554 uint8_t ready = 0;
555 };
556
559 {
560 uint32_t matchId = 0;
561 };
562
565 {
566 uint32_t matchId = 0;
567 uint32_t goShowServerTick = 0;
568 uint32_t unlockServerTick = 0;
569 };
570
573 {
574 uint32_t matchId = 0;
575 };
576
579 {
580 enum class EResult : uint8_t
581 {
582 Draw = 0x00,
583 Win = 0x01
584 };
585
586 uint32_t matchId = 0;
587 EResult result = EResult::Draw;
588 uint8_t winnerPlayerId = 0xFF;
590 };
591
599 {
600 enum class EPhase : uint8_t
601 {
602 Idle = 0x00,
603 Countdown = 0x01
604 };
605
607 {
608 enum class ESeatFlags : uint8_t
609 {
610 None = 0x00,
611 Occupied = 0x01,
612 Ready = 0x02
613 };
614
615 static constexpr uint8_t kKnownFlags =
616 static_cast<uint8_t>(ESeatFlags::Occupied) |
617 static_cast<uint8_t>(ESeatFlags::Ready);
618
619 ESeatFlags flags = ESeatFlags::None;
620 uint8_t wins = 0;
622 };
623
624 EPhase phase = EPhase::Idle;
626 uint16_t reserved = 0;
628 };
629
630 static_assert(sizeof(MsgLobbyState::SeatEntry) == kMsgLobbyStateSeatSize, "MsgLobbyState::SeatEntry size mismatch");
631 static_assert(sizeof(MsgLobbyState) == kMsgLobbyStateSize, "MsgLobbyState size mismatch");
632
633 // ----- Gameplay Message Payloads -----
634
641 struct MsgInput
642 {
643 uint32_t baseInputSeq = 0;
644 uint8_t count = 0;
646 };
647
649 struct MsgCell
650 {
651 uint8_t col = 0;
652 uint8_t row = 0;
653 };
654
673 {
674 uint32_t matchId = 0;
675 uint32_t serverTick = 0;
676 uint8_t playerCount = 0;
677 uint8_t bombCount = 0;
678 uint8_t powerupCount = 0;
679
681 {
682 uint8_t playerId = 0;
683 int16_t xQ = 0;
684 int16_t yQ = 0;
685
686 enum class EPlayerFlags : uint8_t
687 {
688 None = 0x00,
689 Alive = 0x01,
690 Invulnerable = 0x02,
691 InputLocked = 0x04,
692 BombRangeBoost = 0x08,
693 MaxBombsBoost = 0x10,
694 SpeedBoost = 0x20
695 };
696
697 static constexpr uint8_t kKnownFlags =
698 static_cast<uint8_t>(EPlayerFlags::Alive) |
699 static_cast<uint8_t>(EPlayerFlags::Invulnerable) |
700 static_cast<uint8_t>(EPlayerFlags::InputLocked) |
701 static_cast<uint8_t>(EPlayerFlags::BombRangeBoost) |
702 static_cast<uint8_t>(EPlayerFlags::MaxBombsBoost) |
703 static_cast<uint8_t>(EPlayerFlags::SpeedBoost);
704
706 };
707
709
711 {
712 uint8_t ownerId = 0;
713 uint8_t col = 0;
714 uint8_t row = 0;
715 uint8_t radius = 0;
716 };
717
719
721 {
722 sim::PowerupType type = sim::PowerupType::SpeedBoost;
723 uint8_t col = 0;
724 uint8_t row = 0;
725 };
726
728 };
729
738 {
739 uint32_t matchId = 0;
740 uint32_t serverTick = 0;
742 int16_t xQ = 0;
743 int16_t yQ = 0;
744 uint8_t playerFlags = 0;
745 uint8_t reserved[3]{};
746 };
747
757 {
758 uint32_t matchId = 0;
759 uint32_t serverTick = 0;
760 uint32_t explodeTick = 0;
761 uint8_t ownerId = 0;
762 uint8_t col = 0;
763 uint8_t row = 0;
764 uint8_t radius = 0;
765 };
766
789
790 // =================================================================================================================
791 // ===== Wire Helpers ==============================================================================================
792 // =================================================================================================================
793
794 // ----- Endian Helpers -----
795
797 constexpr void writeU16LE(uint8_t* out, uint16_t value)
798 {
799 out[0] = static_cast<uint8_t>(value & 0xFFu);
800 out[1] = static_cast<uint8_t>((value >> 8u) & 0xFFu);
801 }
802
804 constexpr void writeU32LE(uint8_t* out, uint32_t value)
805 {
806 out[0] = static_cast<uint8_t>(value & 0xFFu);
807 out[1] = static_cast<uint8_t>((value >> 8u) & 0xFFu);
808 out[2] = static_cast<uint8_t>((value >> 16u) & 0xFFu);
809 out[3] = static_cast<uint8_t>((value >> 24u) & 0xFFu);
810 }
811
813 constexpr uint16_t readU16LE(const uint8_t* in)
814 {
815 return static_cast<uint16_t>(in[0]) |
816 (static_cast<uint16_t>(in[1]) << 8u);
817 }
818
820 constexpr uint32_t readU32LE(const uint8_t* in)
821 {
822 return static_cast<uint32_t>(in[0]) |
823 (static_cast<uint32_t>(in[1]) << 8u) |
824 (static_cast<uint32_t>(in[2]) << 16u) |
825 (static_cast<uint32_t>(in[3]) << 24u);
826 }
827
828 // ----- String Field Helpers -----
829
831 constexpr std::size_t boundedStrLen(const char* s, const std::size_t maxBytes)
832 {
833 if (!s) return 0;
834
835 std::size_t length = 0;
836 while(length < maxBytes && s[length] != '\0')
837 {
838 ++length;
839 }
840 return length;
841 }
842
844 constexpr std::size_t snapshotPlayerOffset(std::size_t index)
845 {
846 return kSnapshotPlayersOffset + index * kSnapshotPlayerEntrySize;
847 }
848
850 constexpr std::size_t snapshotBombOffset(std::size_t index)
851 {
852 return kSnapshotBombsOffset + index * kSnapshotBombEntrySize;
853 }
854
856 constexpr std::size_t snapshotPowerupOffset(std::size_t index)
857 {
858 return kSnapshotPowerupsOffset + index * kSnapshotPowerupEntrySize;
859 }
860
862 inline void setHelloName(MsgHello& hello, std::string_view name)
863 {
864 std::memset(hello.name, 0, kPlayerNameMax);
865
866 const std::size_t copyLen = (name.size() < (kPlayerNameMax - 1)) ? name.size() : (kPlayerNameMax - 1);
867 std::memcpy(hello.name, name.data(), copyLen);
868 }
870 inline void setHelloName(MsgHello& hello, const char* name)
871 {
872 if (!name) {
873 std::memset(hello.name, 0, kPlayerNameMax);
874 return;
875 }
876
877 const std::size_t copyLen = boundedStrLen(name, kPlayerNameMax - 1);
878 setHelloName(hello, std::string_view{name, copyLen});
879 }
880
882 constexpr bool isValidLobbySeatFlags(const uint8_t flags)
883 {
884 return (flags & static_cast<uint8_t>(~MsgLobbyState::SeatEntry::kKnownFlags)) == 0;
885 }
886
888 constexpr bool isValidLobbyPhase(const uint8_t rawPhase)
889 {
890 return rawPhase == static_cast<uint8_t>(MsgLobbyState::EPhase::Idle) ||
891 rawPhase == static_cast<uint8_t>(MsgLobbyState::EPhase::Countdown);
892 }
893
895 inline void setLobbySeatName(MsgLobbyState::SeatEntry& seat, std::string_view name)
896 {
897 std::memset(seat.name, 0, kPlayerNameMax);
898
899 const std::size_t copyLen = (name.size() < (kPlayerNameMax - 1)) ? name.size() : (kPlayerNameMax - 1);
900 std::memcpy(seat.name, name.data(), copyLen);
901 }
902
904 inline void setMatchResultWinnerName(MsgMatchResult& matchResult, std::string_view name)
905 {
906 std::memset(matchResult.winnerName, 0, kPlayerNameMax);
907
908 const std::size_t copyLen = (name.size() < (kPlayerNameMax - 1)) ? name.size() : (kPlayerNameMax - 1);
909 std::memcpy(matchResult.winnerName, name.data(), copyLen);
910 }
911
913 [[nodiscard]]
914 inline std::string_view lobbySeatName(const MsgLobbyState::SeatEntry& seat)
915 {
916 return std::string_view(seat.name, boundedStrLen(seat.name, kPlayerNameMax - 1));
917 }
918
920 [[nodiscard]]
921 inline std::string_view matchResultWinnerName(const MsgMatchResult& matchResult)
922 {
923 return std::string_view(matchResult.winnerName, boundedStrLen(matchResult.winnerName, kPlayerNameMax - 1));
924 }
925
928 {
929 return (static_cast<uint8_t>(seat.flags) &
930 static_cast<uint8_t>(MsgLobbyState::SeatEntry::ESeatFlags::Occupied)) != 0;
931 }
932
934 constexpr bool lobbySeatIsReady(const MsgLobbyState::SeatEntry& seat)
935 {
936 return (static_cast<uint8_t>(seat.flags) &
937 static_cast<uint8_t>(MsgLobbyState::SeatEntry::ESeatFlags::Ready)) != 0;
938 }
939
941 constexpr bool lobbyCountdownActive(const MsgLobbyState& lobbyState)
942 {
943 return lobbyState.phase == MsgLobbyState::EPhase::Countdown &&
944 lobbyState.countdownSecondsRemaining > 0;
945 }
946
947 // =================================================================================================================
948 // ===== Serialization =============================================================================================
949 // =================================================================================================================
950
951 // ----- Header Serialization -----
952
954 inline void serializeHeader(const PacketHeader& header, uint8_t* out) noexcept
955 {
956 out[0] = static_cast<uint8_t>(header.type);
957 writeU16LE(out + 1, header.payloadSize);
958 }
959
965 [[nodiscard]]
966 inline bool deserializeHeader(const uint8_t* in, std::size_t inSize, PacketHeader& outHeader)
967 {
968 if (inSize < kPacketHeaderSize)
969 {
970 return false;
971 }
972
973 const uint8_t rawType = in[0];
974 if (!isValidMsgType(rawType))
975 {
976 return false;
977 }
978
979 const uint16_t payloadSize = readU16LE(in + 1);
980 if (inSize != kPacketHeaderSize + payloadSize)
981 {
982 return false;
983 }
984
985 // Current protocol uses fixed-size payloads for all message types.
986 const auto msgType = static_cast<EMsgType>(rawType);
987 const std::size_t expected = expectedPayloadSize(msgType);
988 if (expected > 0 && payloadSize != expected)
989 {
990 return false;
991 }
992
993 outHeader.type = msgType;
994 outHeader.payloadSize = payloadSize;
995 return true;
996 }
997
998 // ----- Control Payload Serialization -----
999
1001 inline void serializeMsgHello(const MsgHello& hello, uint8_t* out) noexcept
1002 {
1003 writeU16LE(out, hello.protocolVersion);
1004 std::memset(out + 2, 0, kPlayerNameMax);
1005 const std::size_t nameLen = boundedStrLen(hello.name, kPlayerNameMax - 1);
1006 std::memcpy(out + 2, hello.name, nameLen);
1007 }
1008
1010 [[nodiscard]]
1011 inline bool deserializeMsgHello(const uint8_t* in, std::size_t inSize, MsgHello& outHello)
1012 {
1013 if (inSize < kMsgHelloSize)
1014 {
1015 return false;
1016 }
1017
1018 outHello.protocolVersion = readU16LE(in);
1019 std::memcpy(outHello.name, in + 2, kPlayerNameMax);
1020
1021 outHello.name[kPlayerNameMax - 1] = '\0';
1022
1023 const std::size_t n = boundedStrLen(outHello.name, kPlayerNameMax - 1);
1024 std::memset(outHello.name + n, 0, kPlayerNameMax - n);
1025
1026 return true;
1027 }
1028
1030 inline void serializeMsgWelcome(const MsgWelcome& welcome, uint8_t* out) noexcept
1031 {
1032 writeU16LE(out, welcome.protocolVersion);
1033 out[2] = welcome.playerId;
1034 writeU16LE(out + 3, welcome.serverTickRate);
1035 }
1036
1038 [[nodiscard]]
1039 inline bool deserializeMsgWelcome(const uint8_t* in, std::size_t inSize, MsgWelcome& outWelcome)
1040 {
1041 if (inSize < kMsgWelcomeSize)
1042 {
1043 return false;
1044 }
1045 outWelcome.protocolVersion = readU16LE(in);
1046 outWelcome.playerId = in[2];
1047 if (outWelcome.playerId >= kMaxPlayers)
1048 {
1049 return false;
1050 }
1051 outWelcome.serverTickRate = readU16LE(in + 3);
1052 return outWelcome.serverTickRate != 0;
1053 }
1054
1056 inline void serializeMsgReject(const MsgReject& reject, uint8_t* out) noexcept
1057 {
1058 out[0] = static_cast<uint8_t>(reject.reason);
1059 writeU16LE(out + 1, reject.expectedProtocolVersion);
1060 }
1061
1063 [[nodiscard]]
1064 inline bool deserializeMsgReject(const uint8_t* in, std::size_t inSize, MsgReject& outReject)
1065 {
1066 if (inSize < kMsgRejectSize)
1067 {
1068 return false;
1069 }
1070
1071 outReject.reason = static_cast<MsgReject::EReason>(in[0]);
1072
1073 switch (outReject.reason)
1074 {
1075 case MsgReject::EReason::VersionMismatch:
1076 outReject.expectedProtocolVersion = readU16LE(in + 1);
1077 break;
1078 case MsgReject::EReason::ServerFull:
1079 case MsgReject::EReason::Banned:
1081 case MsgReject::EReason::Other:
1082 outReject.expectedProtocolVersion = 0;
1083 break;
1084 default:
1085 return false;
1086 }
1087
1088 return true;
1089 }
1090
1092 inline void serializeMsgLevelInfo(const MsgLevelInfo& info, uint8_t* out) noexcept
1093 {
1094 writeU32LE(out, info.matchId);
1095 writeU32LE(out + 4, info.mapSeed);
1096 }
1097
1099 [[nodiscard]]
1100 inline bool deserializeMsgLevelInfo(const uint8_t* in, std::size_t inSize, MsgLevelInfo& outInfo)
1101 {
1102 if (inSize < kMsgLevelInfoSize)
1103 {
1104 return false;
1105 }
1106 outInfo.matchId = readU32LE(in);
1107 outInfo.mapSeed = readU32LE(in + 4);
1108 if (outInfo.matchId == 0)
1109 {
1110 return false;
1111 }
1112 return true;
1113 }
1114
1116 constexpr bool isValidLobbyReadyValue(const uint8_t ready)
1117 {
1118 return ready <= 1u;
1119 }
1120
1122 inline void serializeMsgLobbyReady(const MsgLobbyReady& ready, uint8_t* out) noexcept
1123 {
1124 out[0] = ready.ready != 0 ? 1u : 0u;
1125 }
1126
1128 [[nodiscard]]
1129 inline bool deserializeMsgLobbyReady(const uint8_t* in, std::size_t inSize, MsgLobbyReady& outReady)
1130 {
1131 if (inSize < kMsgLobbyReadySize)
1132 {
1133 return false;
1134 }
1135
1136 if (!isValidLobbyReadyValue(in[0]))
1137 {
1138 return false;
1139 }
1140
1141 outReady.ready = in[0];
1142 return true;
1143 }
1144
1146 inline void serializeMsgMatchLoaded(const MsgMatchLoaded& loaded, uint8_t* out) noexcept
1147 {
1148 writeU32LE(out, loaded.matchId);
1149 }
1150
1152 [[nodiscard]]
1153 inline bool deserializeMsgMatchLoaded(const uint8_t* in, std::size_t inSize, MsgMatchLoaded& outLoaded)
1154 {
1155 if (inSize < kMsgMatchLoadedSize)
1156 {
1157 return false;
1158 }
1159
1160 outLoaded.matchId = readU32LE(in);
1161 return outLoaded.matchId != 0;
1162 }
1163
1165 inline void serializeMsgMatchStart(const MsgMatchStart& matchStart, uint8_t* out) noexcept
1166 {
1167 writeU32LE(out, matchStart.matchId);
1168 writeU32LE(out + 4, matchStart.goShowServerTick);
1169 writeU32LE(out + 8, matchStart.unlockServerTick);
1170 }
1171
1173 [[nodiscard]]
1174 inline bool deserializeMsgMatchStart(const uint8_t* in, std::size_t inSize, MsgMatchStart& outMatchStart)
1175 {
1176 if (inSize < kMsgMatchStartSize)
1177 {
1178 return false;
1179 }
1180
1181 outMatchStart.matchId = readU32LE(in);
1182 outMatchStart.goShowServerTick = readU32LE(in + 4);
1183 outMatchStart.unlockServerTick = readU32LE(in + 8);
1184 return outMatchStart.matchId != 0 &&
1185 outMatchStart.goShowServerTick != 0 &&
1186 outMatchStart.unlockServerTick != 0 &&
1187 outMatchStart.goShowServerTick <= outMatchStart.unlockServerTick;
1188 }
1189
1191 inline void serializeMsgMatchCancelled(const MsgMatchCancelled& cancelled, uint8_t* out) noexcept
1192 {
1193 writeU32LE(out, cancelled.matchId);
1194 }
1195
1197 [[nodiscard]]
1198 inline bool deserializeMsgMatchCancelled(const uint8_t* in,
1199 std::size_t inSize,
1200 MsgMatchCancelled& outCancelled)
1201 {
1202 if (inSize < kMsgMatchCancelledSize)
1203 {
1204 return false;
1205 }
1206
1207 outCancelled.matchId = readU32LE(in);
1208 return outCancelled.matchId != 0;
1209 }
1210
1212 inline void serializeMsgMatchResult(const MsgMatchResult& matchResult, uint8_t* out) noexcept
1213 {
1214 writeU32LE(out, matchResult.matchId);
1215 out[4] = static_cast<uint8_t>(matchResult.result);
1216 out[5] = matchResult.winnerPlayerId;
1217 std::memset(out + 6, 0, kPlayerNameMax);
1218 const std::size_t nameLen = boundedStrLen(matchResult.winnerName, kPlayerNameMax - 1);
1219 std::memcpy(out + 6, matchResult.winnerName, nameLen);
1220 }
1221
1223 [[nodiscard]]
1224 inline bool deserializeMsgMatchResult(const uint8_t* in, std::size_t inSize, MsgMatchResult& outMatchResult)
1225 {
1226 if (inSize < kMsgMatchResultSize)
1227 {
1228 return false;
1229 }
1230
1231 outMatchResult.matchId = readU32LE(in);
1232 outMatchResult.result = static_cast<MsgMatchResult::EResult>(in[4]);
1233 outMatchResult.winnerPlayerId = in[5];
1234 std::memcpy(outMatchResult.winnerName, in + 6, kPlayerNameMax);
1235 outMatchResult.winnerName[kPlayerNameMax - 1] = '\0';
1236
1237 switch (outMatchResult.result)
1238 {
1239 case MsgMatchResult::EResult::Draw:
1240 outMatchResult.winnerPlayerId = 0xFF;
1241 std::memset(outMatchResult.winnerName, 0, kPlayerNameMax);
1242 return outMatchResult.matchId != 0;
1243 case MsgMatchResult::EResult::Win:
1244 if (outMatchResult.matchId == 0 || outMatchResult.winnerPlayerId >= kMaxPlayers)
1245 {
1246 return false;
1247 }
1248 return !matchResultWinnerName(outMatchResult).empty();
1249 default:
1250 return false;
1251 }
1252 }
1253
1255 inline void serializeMsgLobbyState(const MsgLobbyState& lobbyState, uint8_t* out) noexcept
1256 {
1257 out[0] = static_cast<uint8_t>(lobbyState.phase);
1258 out[1] = lobbyState.countdownSecondsRemaining;
1259 writeU16LE(out + 2, 0);
1260
1261 for (std::size_t i = 0; i < kMaxPlayers; ++i)
1262 {
1263 const auto offset = 4 + i * kMsgLobbyStateSeatSize;
1264 const auto& seat = lobbyState.seats[i];
1265
1266 out[offset] = static_cast<uint8_t>(seat.flags);
1267 out[offset + 1] = seat.wins;
1268 std::memset(out + offset + 2, 0, kPlayerNameMax);
1269 const std::size_t nameLen = boundedStrLen(seat.name, kPlayerNameMax - 1);
1270 std::memcpy(out + offset + 2, seat.name, nameLen);
1271 }
1272 }
1273
1275 [[nodiscard]]
1276 inline bool deserializeMsgLobbyState(const uint8_t* in, std::size_t inSize, MsgLobbyState& outLobbyState)
1277 {
1278 if (inSize < kMsgLobbyStateSize)
1279 {
1280 return false;
1281 }
1282
1283 if (!isValidLobbyPhase(in[0]))
1284 {
1285 return false;
1286 }
1287
1288 outLobbyState.phase = static_cast<MsgLobbyState::EPhase>(in[0]);
1289 outLobbyState.countdownSecondsRemaining = in[1];
1290 outLobbyState.reserved = readU16LE(in + 2);
1291 if (outLobbyState.reserved != 0)
1292 {
1293 return false;
1294 }
1295 if (outLobbyState.phase == MsgLobbyState::EPhase::Countdown)
1296 {
1297 if (outLobbyState.countdownSecondsRemaining == 0)
1298 {
1299 return false;
1300 }
1301 }
1302 else
1303 {
1304 outLobbyState.countdownSecondsRemaining = 0;
1305 }
1306
1307 for (std::size_t i = 0; i < kMaxPlayers; ++i)
1308 {
1309 const auto offset = 4 + i * kMsgLobbyStateSeatSize;
1310 auto& seat = outLobbyState.seats[i];
1311
1312 const uint8_t rawFlags = in[offset];
1313 if (!isValidLobbySeatFlags(rawFlags))
1314 {
1315 return false;
1316 }
1317 if ((rawFlags & static_cast<uint8_t>(MsgLobbyState::SeatEntry::ESeatFlags::Ready)) != 0 &&
1318 (rawFlags & static_cast<uint8_t>(MsgLobbyState::SeatEntry::ESeatFlags::Occupied)) == 0)
1319 {
1320 return false;
1321 }
1322
1323 seat.flags = static_cast<MsgLobbyState::SeatEntry::ESeatFlags>(rawFlags);
1324 seat.wins = in[offset + 1];
1325 std::memcpy(seat.name, in + offset + 2, kPlayerNameMax);
1326 seat.name[kPlayerNameMax - 1] = '\0';
1327
1328 const std::size_t nameLen = boundedStrLen(seat.name, kPlayerNameMax - 1);
1329 std::memset(seat.name + nameLen, 0, kPlayerNameMax - nameLen);
1330
1331 if (!lobbySeatIsOccupied(seat))
1332 {
1333 seat.wins = 0;
1334 std::memset(seat.name, 0, kPlayerNameMax);
1335 continue;
1336 }
1337
1338 }
1339
1340 return true;
1341 }
1342
1343 // ----- Gameplay Payload Serialization -----
1344
1350 inline void serializeMsgInput(const MsgInput& input, uint8_t* out) noexcept
1351 {
1352 writeU32LE(out, input.baseInputSeq);
1353 out[4] = input.count;
1354 std::memcpy(out + 5, input.inputs, kMaxInputBatchSize);
1355 }
1356
1358 [[nodiscard]]
1359 inline bool deserializeMsgInput(const uint8_t* in, std::size_t inSize, MsgInput& outInput)
1360 {
1361 if (inSize < kMsgInputSize)
1362 {
1363 return false;
1364 }
1365
1366 outInput.baseInputSeq = readU32LE(in);
1367 outInput.count = in[4];
1368
1369 if (outInput.count == 0 || outInput.count > kMaxInputBatchSize)
1370 {
1371 return false;
1372 }
1373
1374 // Reject if the batch starts before the first valid sequence.
1375 if (outInput.baseInputSeq < kFirstInputSeq)
1376 {
1377 return false;
1378 }
1379
1380 std::memcpy(outInput.inputs, in + 5, kMaxInputBatchSize);
1381
1382 // Reject if any input entry contains unknown bits outside the defined input mask.
1383 for (uint8_t i = 0; i < outInput.count; ++i)
1384 {
1385 if ((outInput.inputs[i] & ~kInputKnownBits) != 0)
1386 {
1387 return false;
1388 }
1389 }
1390
1391 return true;
1392 }
1393
1401 inline void serializeMsgSnapshot(const MsgSnapshot& snap, uint8_t* out) noexcept
1402 {
1403 writeU32LE(out, snap.matchId);
1404 writeU32LE(out + 4, snap.serverTick);
1405 const uint8_t playerCount = (snap.playerCount <= kMaxPlayers) ? snap.playerCount : kMaxPlayers;
1406 const uint8_t bombCount = (snap.bombCount <= kMaxSnapshotBombs) ? snap.bombCount : kMaxSnapshotBombs;
1407 const uint8_t powerupCount = (snap.powerupCount <= kMaxSnapshotPowerups) ? snap.powerupCount : kMaxSnapshotPowerups;
1408 out[8] = playerCount;
1409 out[9] = bombCount;
1410 out[10] = powerupCount;
1411
1412 for (std::size_t i = 0; i < kMaxPlayers; ++i)
1413 {
1414 const auto& player = snap.players[i];
1415 const std::size_t offset = snapshotPlayerOffset(i);
1416 out[offset] = player.playerId;
1417 writeU16LE(out + offset + 1, static_cast<uint16_t>(player.xQ));
1418 writeU16LE(out + offset + 3, static_cast<uint16_t>(player.yQ));
1419 out[offset + 5] = static_cast<uint8_t>(player.flags);
1420 }
1421
1422 for (std::size_t i = 0; i < kMaxSnapshotBombs; ++i)
1423 {
1424 const auto& bomb = snap.bombs[i];
1425 const std::size_t offset = snapshotBombOffset(i);
1426 out[offset] = bomb.ownerId;
1427 out[offset + 1] = bomb.col;
1428 out[offset + 2] = bomb.row;
1429 out[offset + 3] = bomb.radius;
1430 }
1431
1432 for (std::size_t i = 0; i < kMaxSnapshotPowerups; ++i)
1433 {
1434 const auto& powerup = snap.powerups[i];
1435 const std::size_t offset = snapshotPowerupOffset(i);
1436 out[offset] = static_cast<uint8_t>(powerup.type);
1437 out[offset + 1] = powerup.col;
1438 out[offset + 2] = powerup.row;
1439 }
1440 }
1441
1443 [[nodiscard]]
1444 inline bool deserializeMsgSnapshot(const uint8_t* in, std::size_t inSize, MsgSnapshot& outSnap)
1445 {
1446 if (inSize < kMsgSnapshotSize)
1447 {
1448 return false;
1449 }
1450
1451 outSnap.matchId = readU32LE(in);
1452 outSnap.serverTick = readU32LE(in + 4);
1453 outSnap.playerCount = in[8];
1454 outSnap.bombCount = in[9];
1455 outSnap.powerupCount = in[10];
1456 if (outSnap.playerCount > kMaxPlayers)
1457 {
1458 return false;
1459 }
1460 if (outSnap.bombCount > kMaxSnapshotBombs)
1461 {
1462 return false;
1463 }
1464 if (outSnap.powerupCount > kMaxSnapshotPowerups)
1465 {
1466 return false;
1467 }
1468
1469 uint8_t seenPlayerMask = 0;
1470 uint8_t previousPlayerId = 0;
1471 bool hasPreviousPlayer = false;
1472
1473 for (std::size_t i = 0; i < kMaxPlayers; ++i)
1474 {
1475 const std::size_t offset = snapshotPlayerOffset(i);
1476 auto& player = outSnap.players[i];
1477 player.playerId = in[offset];
1478 player.xQ = static_cast<int16_t>(readU16LE(in + offset + 1));
1479 player.yQ = static_cast<int16_t>(readU16LE(in + offset + 3));
1480 const uint8_t rawFlags = in[offset + 5];
1481
1482 // Only validate active player entries.
1483 if (i < outSnap.playerCount)
1484 {
1485 if (player.playerId >= kMaxPlayers)
1486 {
1487 return false;
1488 }
1489 if (hasPreviousPlayer && player.playerId <= previousPlayerId)
1490 {
1491 return false;
1492 }
1493
1494 const uint8_t playerBit = static_cast<uint8_t>(1u << player.playerId);
1495 if ((seenPlayerMask & playerBit) != 0)
1496 {
1497 return false;
1498 }
1499 seenPlayerMask |= playerBit;
1500 previousPlayerId = player.playerId;
1501 hasPreviousPlayer = true;
1502
1503 if ((rawFlags & ~MsgSnapshot::PlayerEntry::kKnownFlags) != 0)
1504 {
1505 return false;
1506 }
1507 }
1508
1509 player.flags = static_cast<MsgSnapshot::PlayerEntry::EPlayerFlags>(rawFlags);
1510 }
1511
1512 uint16_t previousBombCellKey = 0;
1513 bool hasPreviousBomb = false;
1514 for (std::size_t i = 0; i < kMaxSnapshotBombs; ++i)
1515 {
1516 const std::size_t offset = snapshotBombOffset(i);
1517 auto& bomb = outSnap.bombs[i];
1518 bomb.ownerId = in[offset];
1519 bomb.col = in[offset + 1];
1520 bomb.row = in[offset + 2];
1521 bomb.radius = in[offset + 3];
1522
1523 if (i < outSnap.bombCount)
1524 {
1525 if (bomb.ownerId >= kMaxPlayers)
1526 {
1527 return false;
1528 }
1529 if (bomb.col >= ::bomberman::tileArrayWidth || bomb.row >= ::bomberman::tileArrayHeight)
1530 {
1531 return false;
1532 }
1533 if (bomb.radius == 0)
1534 {
1535 return false;
1536 }
1537
1538 const uint16_t bombCellKey =
1539 static_cast<uint16_t>(bomb.row) * static_cast<uint16_t>(::bomberman::tileArrayWidth) +
1540 static_cast<uint16_t>(bomb.col);
1541 if (hasPreviousBomb && bombCellKey <= previousBombCellKey)
1542 {
1543 return false;
1544 }
1545
1546 previousBombCellKey = bombCellKey;
1547 hasPreviousBomb = true;
1548 }
1549 }
1550
1551 uint16_t previousPowerupCellKey = 0;
1552 bool hasPreviousPowerup = false;
1553 for (std::size_t i = 0; i < kMaxSnapshotPowerups; ++i)
1554 {
1555 const std::size_t offset = snapshotPowerupOffset(i);
1556 auto& powerup = outSnap.powerups[i];
1557 const uint8_t rawType = in[offset];
1558 powerup.type = static_cast<sim::PowerupType>(rawType);
1559 powerup.col = in[offset + 1];
1560 powerup.row = in[offset + 2];
1561
1562 if (i < outSnap.powerupCount)
1563 {
1564 if (!sim::isValidPowerupType(rawType) ||
1565 powerup.col >= ::bomberman::tileArrayWidth ||
1566 powerup.row >= ::bomberman::tileArrayHeight)
1567 {
1568 return false;
1569 }
1570
1571 const uint16_t powerupCellKey =
1572 static_cast<uint16_t>(powerup.row) * static_cast<uint16_t>(::bomberman::tileArrayWidth) +
1573 static_cast<uint16_t>(powerup.col);
1574 if (hasPreviousPowerup && powerupCellKey <= previousPowerupCellKey)
1575 {
1576 return false;
1577 }
1578
1579 previousPowerupCellKey = powerupCellKey;
1580 hasPreviousPowerup = true;
1581 }
1582 }
1583
1584 return true;
1585 }
1586
1588 inline void serializeMsgCorrection(const MsgCorrection& corr, uint8_t* out) noexcept
1589 {
1590 writeU32LE(out, corr.matchId);
1591 writeU32LE(out + 4, corr.serverTick);
1592 writeU32LE(out + 8, corr.lastProcessedInputSeq);
1593 writeU16LE(out + 12, static_cast<uint16_t>(corr.xQ));
1594 writeU16LE(out + 14, static_cast<uint16_t>(corr.yQ));
1595 out[16] = corr.playerFlags;
1596 out[17] = 0;
1597 out[18] = 0;
1598 out[19] = 0;
1599 }
1600
1602 [[nodiscard]]
1603 inline bool deserializeMsgCorrection(const uint8_t* in, std::size_t inSize, MsgCorrection& outCorr)
1604 {
1605 if (inSize < kMsgCorrectionSize)
1606 {
1607 return false;
1608 }
1609 outCorr.matchId = readU32LE(in);
1610 outCorr.serverTick = readU32LE(in + 4);
1611 outCorr.lastProcessedInputSeq = readU32LE(in + 8);
1612 outCorr.xQ = static_cast<int16_t>(readU16LE(in + 12));
1613 outCorr.yQ = static_cast<int16_t>(readU16LE(in + 14));
1614 outCorr.playerFlags = in[16];
1615 return true;
1616 }
1617
1619 inline void serializeMsgBombPlaced(const MsgBombPlaced& bombPlaced, uint8_t* out) noexcept
1620 {
1621 writeU32LE(out, bombPlaced.matchId);
1622 writeU32LE(out + 4, bombPlaced.serverTick);
1623 writeU32LE(out + 8, bombPlaced.explodeTick);
1624 out[12] = bombPlaced.ownerId;
1625 out[13] = bombPlaced.col;
1626 out[14] = bombPlaced.row;
1627 out[15] = bombPlaced.radius;
1628 }
1629
1631 [[nodiscard]]
1632 inline bool deserializeMsgBombPlaced(const uint8_t* in, std::size_t inSize, MsgBombPlaced& outBombPlaced)
1633 {
1634 if (inSize < kMsgBombPlacedSize)
1635 {
1636 return false;
1637 }
1638
1639 outBombPlaced.matchId = readU32LE(in);
1640 outBombPlaced.serverTick = readU32LE(in + 4);
1641 outBombPlaced.explodeTick = readU32LE(in + 8);
1642 outBombPlaced.ownerId = in[12];
1643 outBombPlaced.col = in[13];
1644 outBombPlaced.row = in[14];
1645 outBombPlaced.radius = in[15];
1646
1647 if (outBombPlaced.ownerId >= kMaxPlayers)
1648 {
1649 return false;
1650 }
1651 if (!isValidTileCell(outBombPlaced.col, outBombPlaced.row))
1652 {
1653 return false;
1654 }
1655 if (outBombPlaced.radius == 0 || outBombPlaced.explodeTick < outBombPlaced.serverTick)
1656 {
1657 return false;
1658 }
1659
1660 return true;
1661 }
1662
1664 inline void serializeMsgExplosionResolved(const MsgExplosionResolved& explosion, uint8_t* out) noexcept
1665 {
1666 writeU32LE(out, explosion.matchId);
1667 writeU32LE(out + 4, explosion.serverTick);
1668 out[8] = explosion.ownerId;
1669 out[9] = explosion.originCol;
1670 out[10] = explosion.originRow;
1671 out[11] = explosion.radius;
1672 out[12] = explosion.killedPlayerMask;
1673 out[13] = static_cast<uint8_t>((explosion.blastCellCount <= kMaxExplosionBlastCells) ?
1674 explosion.blastCellCount :
1676 out[14] = static_cast<uint8_t>((explosion.destroyedBrickCount <= kMaxExplosionDestroyedBricks) ?
1677 explosion.destroyedBrickCount :
1679
1680 for (std::size_t i = 0; i < kMaxExplosionBlastCells; ++i)
1681 {
1682 const std::size_t offset = explosionBlastCellOffset(i);
1683 out[offset] = explosion.blastCells[i].col;
1684 out[offset + 1] = explosion.blastCells[i].row;
1685 }
1686
1687 for (std::size_t i = 0; i < kMaxExplosionDestroyedBricks; ++i)
1688 {
1689 const std::size_t offset = explosionDestroyedBrickOffset(i);
1690 out[offset] = explosion.destroyedBricks[i].col;
1691 out[offset + 1] = explosion.destroyedBricks[i].row;
1692 }
1693 }
1694
1696 [[nodiscard]]
1697 inline bool deserializeMsgExplosionResolved(const uint8_t* in,
1698 std::size_t inSize,
1699 MsgExplosionResolved& outExplosion)
1700 {
1701 if (inSize < kMsgExplosionResolvedSize)
1702 {
1703 return false;
1704 }
1705
1706 outExplosion.matchId = readU32LE(in);
1707 outExplosion.serverTick = readU32LE(in + 4);
1708 outExplosion.ownerId = in[8];
1709 outExplosion.originCol = in[9];
1710 outExplosion.originRow = in[10];
1711 outExplosion.radius = in[11];
1712 outExplosion.killedPlayerMask = in[12];
1713 outExplosion.blastCellCount = in[13];
1714 outExplosion.destroyedBrickCount = in[14];
1715
1716 if (outExplosion.ownerId >= kMaxPlayers)
1717 {
1718 return false;
1719 }
1720 if (!isValidTileCell(outExplosion.originCol, outExplosion.originRow))
1721 {
1722 return false;
1723 }
1724 if (outExplosion.radius == 0)
1725 {
1726 return false;
1727 }
1728 if (!isValidPlayerMask(outExplosion.killedPlayerMask))
1729 {
1730 return false;
1731 }
1732 if (outExplosion.blastCellCount == 0 || outExplosion.blastCellCount > kMaxExplosionBlastCells)
1733 {
1734 return false;
1735 }
1737 outExplosion.destroyedBrickCount > outExplosion.blastCellCount)
1738 {
1739 return false;
1740 }
1741
1742 std::array<bool, static_cast<std::size_t>(::bomberman::tileArrayWidth) *
1743 static_cast<std::size_t>(::bomberman::tileArrayHeight)> seenBlastCells{};
1744 std::array<bool, static_cast<std::size_t>(::bomberman::tileArrayWidth) *
1745 static_cast<std::size_t>(::bomberman::tileArrayHeight)> seenDestroyedBricks{};
1746
1747 for (std::size_t i = 0; i < kMaxExplosionBlastCells; ++i)
1748 {
1749 const std::size_t offset = explosionBlastCellOffset(i);
1750 auto& cell = outExplosion.blastCells[i];
1751 cell.col = in[offset];
1752 cell.row = in[offset + 1];
1753
1754 if (i < outExplosion.blastCellCount)
1755 {
1756 if (!isValidTileCell(cell.col, cell.row))
1757 {
1758 return false;
1759 }
1760
1761 const std::size_t cellIndex = tileCellKey(cell.col, cell.row);
1762 if (seenBlastCells[cellIndex])
1763 {
1764 return false;
1765 }
1766
1767 seenBlastCells[cellIndex] = true;
1768 }
1769 }
1770
1771 if (outExplosion.blastCells[0].col != outExplosion.originCol ||
1772 outExplosion.blastCells[0].row != outExplosion.originRow)
1773 {
1774 return false;
1775 }
1776
1777 for (std::size_t i = 0; i < kMaxExplosionDestroyedBricks; ++i)
1778 {
1779 const std::size_t offset = explosionDestroyedBrickOffset(i);
1780 auto& cell = outExplosion.destroyedBricks[i];
1781 cell.col = in[offset];
1782 cell.row = in[offset + 1];
1783
1784 if (i < outExplosion.destroyedBrickCount)
1785 {
1786 if (!isValidTileCell(cell.col, cell.row))
1787 {
1788 return false;
1789 }
1790
1791 const std::size_t cellIndex = tileCellKey(cell.col, cell.row);
1792 if (!seenBlastCells[cellIndex] || seenDestroyedBricks[cellIndex])
1793 {
1794 return false;
1795 }
1796
1797 seenDestroyedBricks[cellIndex] = true;
1798 }
1799 }
1800
1801 return true;
1802 }
1803
1804 // =================================================================================================================
1805 // ===== Packet Builders ===========================================================================================
1806 // =================================================================================================================
1807
1808 // ----- Control Packet Builders -----
1809
1811 inline std::array<uint8_t, kPacketHeaderSize + kMsgHelloSize> makeHelloPacket(const MsgHello& hello)
1812 {
1813 PacketHeader header{};
1814 header.type = EMsgType::Hello;
1815 header.payloadSize = static_cast<uint16_t>(kMsgHelloSize);
1816
1817 std::array<uint8_t, kPacketHeaderSize + kMsgHelloSize> bytes{};
1818 serializeHeader(header, bytes.data());
1819 serializeMsgHello(hello, bytes.data() + kPacketHeaderSize);
1820
1821 return bytes;
1822 }
1824 inline std::array<uint8_t, kPacketHeaderSize + kMsgHelloSize> makeHelloPacket(std::string_view name, uint16_t protocolVersion)
1825 {
1826 MsgHello hello{};
1827 hello.protocolVersion = protocolVersion;
1828 setHelloName(hello, name);
1829 return makeHelloPacket(hello);
1830 }
1831
1833 inline std::array<uint8_t, kPacketHeaderSize + kMsgWelcomeSize> makeWelcomePacket(const MsgWelcome& welcome)
1834 {
1835 PacketHeader header{};
1836 header.type = EMsgType::Welcome;
1837 header.payloadSize = static_cast<uint16_t>(kMsgWelcomeSize);
1838
1839 std::array<uint8_t, kPacketHeaderSize + kMsgWelcomeSize> bytes{};
1840 serializeHeader(header, bytes.data());
1841 serializeMsgWelcome(welcome, bytes.data() + kPacketHeaderSize);
1842
1843 return bytes;
1844 }
1845
1847 inline std::array<uint8_t, kPacketHeaderSize + kMsgRejectSize> makeRejectPacket(const MsgReject& reject)
1848 {
1849 PacketHeader header{};
1850 header.type = EMsgType::Reject;
1851 header.payloadSize = static_cast<uint16_t>(kMsgRejectSize);
1852
1853 std::array<uint8_t, kPacketHeaderSize + kMsgRejectSize> bytes{};
1854 serializeHeader(header, bytes.data());
1855 serializeMsgReject(reject, bytes.data() + kPacketHeaderSize);
1856
1857 return bytes;
1858 }
1859
1861 inline std::array<uint8_t, kPacketHeaderSize + kMsgLevelInfoSize> makeLevelInfoPacket(const MsgLevelInfo& info)
1862 {
1863 PacketHeader header{};
1864 header.type = EMsgType::LevelInfo;
1865 header.payloadSize = static_cast<uint16_t>(kMsgLevelInfoSize);
1866
1867 std::array<uint8_t, kPacketHeaderSize + kMsgLevelInfoSize> bytes{};
1868 serializeHeader(header, bytes.data());
1869 serializeMsgLevelInfo(info, bytes.data() + kPacketHeaderSize);
1870
1871 return bytes;
1872 }
1873
1875 inline std::array<uint8_t, kPacketHeaderSize + kMsgLobbyReadySize> makeLobbyReadyPacket(const MsgLobbyReady& ready)
1876 {
1877 PacketHeader header{};
1878 header.type = EMsgType::LobbyReady;
1879 header.payloadSize = static_cast<uint16_t>(kMsgLobbyReadySize);
1880
1881 std::array<uint8_t, kPacketHeaderSize + kMsgLobbyReadySize> bytes{};
1882 serializeHeader(header, bytes.data());
1883 serializeMsgLobbyReady(ready, bytes.data() + kPacketHeaderSize);
1884
1885 return bytes;
1886 }
1887
1889 inline std::array<uint8_t, kPacketHeaderSize + kMsgLobbyReadySize> makeLobbyReadyPacket(const bool ready)
1890 {
1891 MsgLobbyReady msg{};
1892 msg.ready = ready ? 1u : 0u;
1893 return makeLobbyReadyPacket(msg);
1894 }
1895
1897 inline std::array<uint8_t, kPacketHeaderSize + kMsgMatchLoadedSize> makeMatchLoadedPacket(const MsgMatchLoaded& loaded)
1898 {
1899 PacketHeader header{};
1900 header.type = EMsgType::MatchLoaded;
1901 header.payloadSize = static_cast<uint16_t>(kMsgMatchLoadedSize);
1902
1903 std::array<uint8_t, kPacketHeaderSize + kMsgMatchLoadedSize> bytes{};
1904 serializeHeader(header, bytes.data());
1905 serializeMsgMatchLoaded(loaded, bytes.data() + kPacketHeaderSize);
1906
1907 return bytes;
1908 }
1909
1911 inline std::array<uint8_t, kPacketHeaderSize + kMsgMatchLoadedSize> makeMatchLoadedPacket(const uint32_t matchId)
1912 {
1913 MsgMatchLoaded loaded{};
1914 loaded.matchId = matchId;
1915 return makeMatchLoadedPacket(loaded);
1916 }
1917
1919 inline std::array<uint8_t, kPacketHeaderSize + kMsgMatchStartSize> makeMatchStartPacket(const MsgMatchStart& matchStart)
1920 {
1921 PacketHeader header{};
1922 header.type = EMsgType::MatchStart;
1923 header.payloadSize = static_cast<uint16_t>(kMsgMatchStartSize);
1924
1925 std::array<uint8_t, kPacketHeaderSize + kMsgMatchStartSize> bytes{};
1926 serializeHeader(header, bytes.data());
1927 serializeMsgMatchStart(matchStart, bytes.data() + kPacketHeaderSize);
1928
1929 return bytes;
1930 }
1931
1933 inline std::array<uint8_t, kPacketHeaderSize + kMsgMatchStartSize> makeMatchStartPacket(const uint32_t matchId,
1934 const uint32_t goShowServerTick,
1935 const uint32_t unlockServerTick)
1936 {
1937 MsgMatchStart matchStart{};
1938 matchStart.matchId = matchId;
1939 matchStart.goShowServerTick = goShowServerTick;
1940 matchStart.unlockServerTick = unlockServerTick;
1941 return makeMatchStartPacket(matchStart);
1942 }
1943
1945 inline std::array<uint8_t, kPacketHeaderSize + kMsgMatchCancelledSize> makeMatchCancelledPacket(
1946 const MsgMatchCancelled& cancelled)
1947 {
1948 PacketHeader header{};
1950 header.payloadSize = static_cast<uint16_t>(kMsgMatchCancelledSize);
1951
1952 std::array<uint8_t, kPacketHeaderSize + kMsgMatchCancelledSize> bytes{};
1953 serializeHeader(header, bytes.data());
1954 serializeMsgMatchCancelled(cancelled, bytes.data() + kPacketHeaderSize);
1955
1956 return bytes;
1957 }
1958
1960 inline std::array<uint8_t, kPacketHeaderSize + kMsgMatchCancelledSize> makeMatchCancelledPacket(
1961 const uint32_t matchId)
1962 {
1963 MsgMatchCancelled cancelled{};
1964 cancelled.matchId = matchId;
1965 return makeMatchCancelledPacket(cancelled);
1966 }
1967
1969 inline std::array<uint8_t, kPacketHeaderSize + kMsgMatchResultSize> makeMatchResultPacket(
1970 const MsgMatchResult& matchResult)
1971 {
1972 PacketHeader header{};
1973 header.type = EMsgType::MatchResult;
1974 header.payloadSize = static_cast<uint16_t>(kMsgMatchResultSize);
1975
1976 std::array<uint8_t, kPacketHeaderSize + kMsgMatchResultSize> bytes{};
1977 serializeHeader(header, bytes.data());
1978 serializeMsgMatchResult(matchResult, bytes.data() + kPacketHeaderSize);
1979
1980 return bytes;
1981 }
1982
1984 inline std::array<uint8_t, kPacketHeaderSize + kMsgLobbyStateSize> makeLobbyStatePacket(const MsgLobbyState& lobbyState)
1985 {
1986 PacketHeader header{};
1987 header.type = EMsgType::LobbyState;
1988 header.payloadSize = static_cast<uint16_t>(kMsgLobbyStateSize);
1989
1990 std::array<uint8_t, kPacketHeaderSize + kMsgLobbyStateSize> bytes{};
1991 serializeHeader(header, bytes.data());
1992 serializeMsgLobbyState(lobbyState, bytes.data() + kPacketHeaderSize);
1993
1994 return bytes;
1995 }
1996
1997 // ----- Gameplay Packet Builders -----
1998
2000 inline std::array<uint8_t, kPacketHeaderSize + kMsgInputSize> makeInputPacket(const MsgInput& input)
2001 {
2002 PacketHeader header{};
2003 header.type = EMsgType::Input;
2004 header.payloadSize = static_cast<uint16_t>(kMsgInputSize);
2005
2006 std::array<uint8_t, kPacketHeaderSize + kMsgInputSize> bytes{};
2007 serializeHeader(header, bytes.data());
2008 serializeMsgInput(input, bytes.data() + kPacketHeaderSize);
2009
2010 return bytes;
2011 }
2012
2014 inline std::array<uint8_t, kPacketHeaderSize + kMsgSnapshotSize> makeSnapshotPacket(const MsgSnapshot& snap)
2015 {
2016 PacketHeader header{};
2017 header.type = EMsgType::Snapshot;
2018 header.payloadSize = static_cast<uint16_t>(kMsgSnapshotSize);
2019
2020 std::array<uint8_t, kPacketHeaderSize + kMsgSnapshotSize> bytes{};
2021 serializeHeader(header, bytes.data());
2022 serializeMsgSnapshot(snap, bytes.data() + kPacketHeaderSize);
2023
2024 return bytes;
2025 }
2026
2028 inline std::array<uint8_t, kPacketHeaderSize + kMsgCorrectionSize> makeCorrectionPacket(const MsgCorrection& corr)
2029 {
2030 PacketHeader header{};
2031 header.type = EMsgType::Correction;
2032 header.payloadSize = static_cast<uint16_t>(kMsgCorrectionSize);
2033
2034 std::array<uint8_t, kPacketHeaderSize + kMsgCorrectionSize> bytes{};
2035 serializeHeader(header, bytes.data());
2036 serializeMsgCorrection(corr, bytes.data() + kPacketHeaderSize);
2037
2038 return bytes;
2039 }
2040
2042 inline std::array<uint8_t, kPacketHeaderSize + kMsgBombPlacedSize> makeBombPlacedPacket(const MsgBombPlaced& bombPlaced)
2043 {
2044 PacketHeader header{};
2045 header.type = EMsgType::BombPlaced;
2046 header.payloadSize = static_cast<uint16_t>(kMsgBombPlacedSize);
2047
2048 std::array<uint8_t, kPacketHeaderSize + kMsgBombPlacedSize> bytes{};
2049 serializeHeader(header, bytes.data());
2050 serializeMsgBombPlaced(bombPlaced, bytes.data() + kPacketHeaderSize);
2051
2052 return bytes;
2053 }
2054
2056 inline std::array<uint8_t, kPacketHeaderSize + kMsgExplosionResolvedSize> makeExplosionResolvedPacket(
2057 const MsgExplosionResolved& explosion)
2058 {
2059 PacketHeader header{};
2061 header.payloadSize = static_cast<uint16_t>(kMsgExplosionResolvedSize);
2062
2063 std::array<uint8_t, kPacketHeaderSize + kMsgExplosionResolvedSize> bytes{};
2064 serializeHeader(header, bytes.data());
2065 serializeMsgExplosionResolved(explosion, bytes.data() + kPacketHeaderSize);
2066
2067 return bytes;
2068 }
2069
2070} // namespace bomberman::net
2071
2072#endif // BOMBERMAN_NET_NETCOMMON_H
Game-world configuration constants.
constexpr unsigned int tileArrayWidth
Tile map width in tiles.
Definition Const.h:43
constexpr unsigned int tileArrayHeight
Tile map height in tiles.
Definition Const.h:44
Shared multiplayer powerup types and round-setup tuning.
Shared multiplayer protocol types and transport-facing wire helpers.
Definition ClientPrediction.cpp:13
bool deserializeMsgExplosionResolved(const uint8_t *in, std::size_t inSize, MsgExplosionResolved &outExplosion)
Deserializes MsgExplosionResolved from fixed-size wire payload.
Definition NetCommon.h:1697
constexpr std::string_view msgTypeName(EMsgType type)
Returns a human-readable name for a protocol message type.
Definition NetCommon.h:357
constexpr bool isValidPlayerMask(const uint32_t mask)
Returns true when the given player-id bitmask uses only valid player bits.
Definition NetCommon.h:417
void serializeMsgMatchLoaded(const MsgMatchLoaded &loaded, uint8_t *out) noexcept
Serializes MsgMatchLoaded to fixed-size wire payload.
Definition NetCommon.h:1146
constexpr bool isValidLobbySeatFlags(const uint8_t flags)
Returns true when one lobby-seat flag field contains only known bits.
Definition NetCommon.h:882
bool deserializeHeader(const uint8_t *in, std::size_t inSize, PacketHeader &outHeader)
Deserializes and validates PacketHeader.
Definition NetCommon.h:966
void setHelloName(MsgHello &hello, std::string_view name)
Sets MsgHello::name from string_view with truncation and zero padding.
Definition NetCommon.h:862
constexpr std::size_t boundedStrLen(const char *s, const std::size_t maxBytes)
Returns string length capped to maxBytes for bounded C strings.
Definition NetCommon.h:831
constexpr bool isValidLobbyPhase(const uint8_t rawPhase)
Returns true when one lobby-state phase field contains a known encoding.
Definition NetCommon.h:888
bool deserializeMsgMatchStart(const uint8_t *in, std::size_t inSize, MsgMatchStart &outMatchStart)
Deserializes MsgMatchStart from fixed-size wire payload.
Definition NetCommon.h:1174
bool deserializeMsgCorrection(const uint8_t *in, std::size_t inSize, MsgCorrection &outCorr)
Deserializes MsgCorrection from fixed-size wire payload.
Definition NetCommon.h:1603
constexpr int8_t buttonsToMoveY(uint8_t buttons)
Derives vertical movement {-1, 0, 1} from a button bitmask.
Definition NetCommon.h:90
constexpr bool lobbyCountdownActive(const MsgLobbyState &lobbyState)
Returns true when the authoritative lobby is in the visible pre-match countdown phase.
Definition NetCommon.h:941
constexpr uint8_t kInputKnownBits
Union of all defined input bits. Used for unknown-bit rejection.
Definition NetCommon.h:78
constexpr uint32_t kSnapshotLogEveryN
Snapshot log sampling interval on client and server.
Definition NetCommon.h:46
void serializeMsgLobbyReady(const MsgLobbyReady &ready, uint8_t *out) noexcept
Serializes MsgLobbyReady to a fixed-size wire payload.
Definition NetCommon.h:1122
constexpr bool isValidLobbyReadyValue(const uint8_t ready)
Returns true when one lobby-ready value uses a known wire encoding.
Definition NetCommon.h:1116
constexpr std::size_t kPlayerNameMax
Definition NetCommon.h:53
bool deserializeMsgMatchCancelled(const uint8_t *in, std::size_t inSize, MsgMatchCancelled &outCancelled)
Deserializes MsgMatchCancelled from fixed-size wire payload.
Definition NetCommon.h:1198
bool deserializeMsgMatchResult(const uint8_t *in, std::size_t inSize, MsgMatchResult &outMatchResult)
Deserializes MsgMatchResult from fixed-size wire payload.
Definition NetCommon.h:1224
std::array< uint8_t, kPacketHeaderSize+kMsgMatchCancelledSize > makeMatchCancelledPacket(const MsgMatchCancelled &cancelled)
Builds a full MatchCancelled packet (header + payload).
Definition NetCommon.h:1945
bool deserializeMsgReject(const uint8_t *in, std::size_t inSize, MsgReject &outReject)
Deserializes MsgReject from fixed-size wire payload.
Definition NetCommon.h:1064
void serializeMsgBombPlaced(const MsgBombPlaced &bombPlaced, uint8_t *out) noexcept
Serializes MsgBombPlaced to fixed-size wire payload.
Definition NetCommon.h:1619
constexpr std::size_t explosionBlastCellOffset(std::size_t index)
Returns the payload offset of the explosion blast-cell entry at index.
Definition NetCommon.h:423
constexpr bool isExpectedChannelFor(EMsgType type, uint8_t channelId)
Returns true when a message was received on its expected ENet channel.
Definition NetCommon.h:448
bool deserializeMsgHello(const uint8_t *in, std::size_t inSize, MsgHello &outHello)
Deserializes MsgHello from fixed-size wire payload.
Definition NetCommon.h:1011
bool deserializeMsgWelcome(const uint8_t *in, std::size_t inSize, MsgWelcome &outWelcome)
Deserializes MsgWelcome from fixed-size wire payload.
Definition NetCommon.h:1039
void serializeMsgInput(const MsgInput &input, uint8_t *out) noexcept
Serializes MsgInput to its fixed-size wire payload.
Definition NetCommon.h:1350
std::string_view matchResultWinnerName(const MsgMatchResult &matchResult)
Returns the visible winner display name carried by one match-result payload.
Definition NetCommon.h:921
std::array< uint8_t, kPacketHeaderSize+kMsgExplosionResolvedSize > makeExplosionResolvedPacket(const MsgExplosionResolved &explosion)
Builds a full ExplosionResolved packet (header + payload).
Definition NetCommon.h:2056
constexpr uint8_t kMaxInputBatchSize
Maximum number of inputs in a single batched MsgInput packet.
Definition NetCommon.h:67
constexpr std::size_t kMaxExplosionDestroyedBricks
Upper bound for bricks one blast can destroy.
Definition NetCommon.h:61
std::array< uint8_t, kPacketHeaderSize+kMsgInputSize > makeInputPacket(const MsgInput &input)
Builds a full Input packet (header + payload).
Definition NetCommon.h:2000
bool deserializeMsgInput(const uint8_t *in, std::size_t inSize, MsgInput &outInput)
Deserializes MsgInput from fixed-size wire payload.
Definition NetCommon.h:1359
EChannel
ENet channel identifiers used by the current protocol.
Definition NetCommon.h:108
constexpr bool lobbySeatIsReady(const MsgLobbyState::SeatEntry &seat)
Returns true when one occupied lobby seat is marked ready.
Definition NetCommon.h:934
constexpr std::string_view channelName(uint8_t id)
Returns a human-readable name for a channel ID.
Definition NetCommon.h:118
std::array< uint8_t, kPacketHeaderSize+kMsgMatchResultSize > makeMatchResultPacket(const MsgMatchResult &matchResult)
Builds a full MatchResult packet (header + payload).
Definition NetCommon.h:1969
void serializeMsgLobbyState(const MsgLobbyState &lobbyState, uint8_t *out) noexcept
Serializes MsgLobbyState to fixed-size wire payload.
Definition NetCommon.h:1255
constexpr uint32_t kPeerTimeoutLimit
Consecutive timeout limit before ENet drops a peer.
Definition NetCommon.h:48
constexpr uint32_t readU32LE(const uint8_t *in)
Reads a 32-bit value encoded as little-endian.
Definition NetCommon.h:820
constexpr EChannel expectedChannelFor(EMsgType type)
Returns the required ENet channel for a protocol message type.
Definition NetCommon.h:387
void setMatchResultWinnerName(MsgMatchResult &matchResult, std::string_view name)
Sets one match-result winner display name with truncation and zero padding.
Definition NetCommon.h:904
void serializeMsgReject(const MsgReject &reject, uint8_t *out) noexcept
Serializes MsgReject to fixed-size wire payload.
Definition NetCommon.h:1056
constexpr std::size_t expectedPayloadSize(EMsgType type)
Returns the exact expected payload size for a message type.
Definition NetCommon.h:460
std::array< uint8_t, kPacketHeaderSize+kMsgCorrectionSize > makeCorrectionPacket(const MsgCorrection &corr)
Builds a full Correction packet (header + payload).
Definition NetCommon.h:2028
constexpr uint16_t readU16LE(const uint8_t *in)
Reads a 16-bit value encoded as little-endian.
Definition NetCommon.h:813
constexpr uint32_t kPeerPingIntervalMs
Transport heartbeat interval for connected peers.
Definition NetCommon.h:47
constexpr uint32_t kInputLogEveryN
Input log sampling interval on client and server.
Definition NetCommon.h:45
constexpr std::size_t snapshotBombOffset(std::size_t index)
Returns the payload offset of the snapshot bomb entry at index.
Definition NetCommon.h:850
std::array< uint8_t, kPacketHeaderSize+kMsgHelloSize > makeHelloPacket(const MsgHello &hello)
Builds a full Hello packet (header + payload).
Definition NetCommon.h:1811
void serializeMsgMatchStart(const MsgMatchStart &matchStart, uint8_t *out) noexcept
Serializes MsgMatchStart to fixed-size wire payload.
Definition NetCommon.h:1165
void serializeMsgLevelInfo(const MsgLevelInfo &info, uint8_t *out) noexcept
Serializes MsgLevelInfo to fixed-size wire payload.
Definition NetCommon.h:1092
constexpr int8_t buttonsToMoveX(uint8_t buttons)
Derives horizontal movement {-1, 0, 1} from a button bitmask.
Definition NetCommon.h:81
std::array< uint8_t, kPacketHeaderSize+kMsgMatchStartSize > makeMatchStartPacket(const MsgMatchStart &matchStart)
Builds a full MatchStart packet (header + payload).
Definition NetCommon.h:1919
std::array< uint8_t, kPacketHeaderSize+kMsgWelcomeSize > makeWelcomePacket(const MsgWelcome &welcome)
Builds a full Welcome packet (header + payload).
Definition NetCommon.h:1833
constexpr bool lobbySeatIsOccupied(const MsgLobbyState::SeatEntry &seat)
Returns true when one lobby seat is currently occupied.
Definition NetCommon.h:927
constexpr uint32_t kPeerTimeoutMaximumMs
Upper bound for ENet peer timeout detection.
Definition NetCommon.h:50
constexpr uint16_t tileCellKey(const uint8_t col, const uint8_t row)
Packs one tile cell into a monotonic key for ordering and deduplication.
Definition NetCommon.h:441
constexpr uint32_t kPeerTimeoutMinimumMs
Lower bound for ENet peer timeout detection.
Definition NetCommon.h:49
bool deserializeMsgLevelInfo(const uint8_t *in, std::size_t inSize, MsgLevelInfo &outInfo)
Deserializes MsgLevelInfo from fixed-size wire payload.
Definition NetCommon.h:1100
constexpr std::size_t kMaxPacketSize
Upper packet size bound (below typical 1500-byte MTU).
Definition NetCommon.h:44
void serializeMsgWelcome(const MsgWelcome &welcome, uint8_t *out) noexcept
Serializes MsgWelcome to fixed-size wire payload.
Definition NetCommon.h:1030
constexpr uint8_t kMaxSnapshotPowerups
Maximum revealed powerups carried by one snapshot payload.
Definition NetCommon.h:56
std::array< uint8_t, kPacketHeaderSize+kMsgLevelInfoSize > makeLevelInfoPacket(const MsgLevelInfo &info)
Builds a full LevelInfo packet (header + payload).
Definition NetCommon.h:1861
void serializeMsgCorrection(const MsgCorrection &corr, uint8_t *out) noexcept
Serializes MsgCorrection to fixed-size wire payload.
Definition NetCommon.h:1588
constexpr bool isValidTileCell(const uint8_t col, const uint8_t row)
Returns true when the given cell lies within the current tile-map bounds.
Definition NetCommon.h:435
bool deserializeMsgMatchLoaded(const uint8_t *in, std::size_t inSize, MsgMatchLoaded &outLoaded)
Deserializes MsgMatchLoaded from fixed-size wire payload.
Definition NetCommon.h:1153
std::array< uint8_t, kPacketHeaderSize+kMsgSnapshotSize > makeSnapshotPacket(const MsgSnapshot &snap)
Builds a full Snapshot packet (header + payload).
Definition NetCommon.h:2014
std::array< uint8_t, kPacketHeaderSize+kMsgRejectSize > makeRejectPacket(const MsgReject &reject)
Builds a full Reject packet (header + payload).
Definition NetCommon.h:1847
constexpr std::size_t kChannelCount
Number of ENet channels provisioned by client and server.
Definition NetCommon.h:115
constexpr std::size_t kMaxExplosionBlastCells
Maximum cells a cross-shaped blast can touch.
Definition NetCommon.h:57
EMsgType
Message type identifiers used in packet headers.
Definition NetCommon.h:316
@ LobbyReady
Client message indicating the local player's ready status for the next round.
@ Input
Client message containing a batch of input bitmasks and their base sequence number.
@ MatchStart
Server message indicating the authoritative server tick for round start and the unlock tick for early...
@ LevelInfo
Server message containing map seed and match id for an upcoming round.
@ Invalid
Invalid or uninitialized message type.
@ LobbyState
Server message containing current lobby state.
@ BombPlaced
Server message indicating that a bomb has been placed by a player.
@ MatchResult
Server message containing the result of a completed match.
@ MatchCancelled
Server message indicating that the current match has been cancelled due to a player disconnect.
@ Welcome
Server handshake acceptance message.
@ Hello
Client-initiated handshake message.
@ Correction
Server message containing an authoritative correction to the local player's state.
@ ExplosionResolved
Server message containing the authoritative result of a bomb explosion.
@ MatchLoaded
Server message indicating that all players have loaded the level and the match is ready to start.
@ Snapshot
Server message containing the authoritative game state for a single tick.
@ Reject
Server handshake rejection message.
bool isValidMsgType(uint8_t raw)
Checks whether a raw byte value corresponds to a valid EMsgType.
Definition NetCommon.h:337
void serializeMsgExplosionResolved(const MsgExplosionResolved &explosion, uint8_t *out) noexcept
Serializes MsgExplosionResolved to fixed-size wire payload.
Definition NetCommon.h:1664
constexpr std::size_t snapshotPlayerOffset(std::size_t index)
Returns the payload offset of the snapshot player entry at index.
Definition NetCommon.h:844
void setLobbySeatName(MsgLobbyState::SeatEntry &seat, std::string_view name)
Sets one lobby-seat display name with truncation and zero padding.
Definition NetCommon.h:895
void serializeMsgMatchCancelled(const MsgMatchCancelled &cancelled, uint8_t *out) noexcept
Serializes MsgMatchCancelled to fixed-size wire payload.
Definition NetCommon.h:1191
std::string_view lobbySeatName(const MsgLobbyState::SeatEntry &seat)
Returns the visible lobby-seat display name.
Definition NetCommon.h:914
std::array< uint8_t, kPacketHeaderSize+kMsgBombPlacedSize > makeBombPlacedPacket(const MsgBombPlaced &bombPlaced)
Builds a full BombPlaced packet (header + payload).
Definition NetCommon.h:2042
constexpr std::size_t kSnapshotPlayersOffset
Compile-time checks for expected wire sizes.
Definition NetCommon.h:271
std::array< uint8_t, kPacketHeaderSize+kMsgLobbyStateSize > makeLobbyStatePacket(const MsgLobbyState &lobbyState)
Builds a full LobbyState packet (header + payload).
Definition NetCommon.h:1984
constexpr void writeU16LE(uint8_t *out, uint16_t value)
Writes a 16-bit value using little-endian encoding.
Definition NetCommon.h:797
constexpr std::size_t snapshotPowerupOffset(std::size_t index)
Returns the payload offset of the snapshot powerup entry at index.
Definition NetCommon.h:856
void serializeMsgMatchResult(const MsgMatchResult &matchResult, uint8_t *out) noexcept
Serializes MsgMatchResult to fixed-size wire payload.
Definition NetCommon.h:1212
constexpr uint32_t kFirstInputSeq
First valid input sequence number. Seq 0 means "no input received yet".
Definition NetCommon.h:64
constexpr std::size_t explosionDestroyedBrickOffset(std::size_t index)
Returns the payload offset of the explosion destroyed-brick entry at index.
Definition NetCommon.h:429
constexpr void writeU32LE(uint8_t *out, uint32_t value)
Writes a 32-bit value using little-endian encoding.
Definition NetCommon.h:804
constexpr uint8_t kMaxSnapshotBombs
Maximum bombs carried by one snapshot payload.
Definition NetCommon.h:55
std::array< uint8_t, kPacketHeaderSize+kMsgMatchLoadedSize > makeMatchLoadedPacket(const MsgMatchLoaded &loaded)
Builds a full MatchLoaded packet (header + payload).
Definition NetCommon.h:1897
void serializeMsgHello(const MsgHello &hello, uint8_t *out) noexcept
Serializes MsgHello to fixed-size wire payload.
Definition NetCommon.h:1001
bool deserializeMsgLobbyReady(const uint8_t *in, std::size_t inSize, MsgLobbyReady &outReady)
Deserializes MsgLobbyReady from a fixed-size wire payload.
Definition NetCommon.h:1129
constexpr uint8_t kMaxPlayers
Maximum supported player count in a game instance.
Definition NetCommon.h:54
bool deserializeMsgSnapshot(const uint8_t *in, std::size_t inSize, MsgSnapshot &outSnap)
Deserializes MsgSnapshot from fixed-size wire payload.
Definition NetCommon.h:1444
constexpr uint16_t kDefaultServerPort
Default server port used by both client and server.
Definition NetCommon.h:43
void serializeHeader(const PacketHeader &header, uint8_t *out) noexcept
Serializes PacketHeader into kPacketHeaderSize bytes.
Definition NetCommon.h:954
std::array< uint8_t, kPacketHeaderSize+kMsgLobbyReadySize > makeLobbyReadyPacket(const MsgLobbyReady &ready)
Builds a full LobbyReady packet (header + payload).
Definition NetCommon.h:1875
void serializeMsgSnapshot(const MsgSnapshot &snap, uint8_t *out) noexcept
Serializes MsgSnapshot to its fixed-size wire payload.
Definition NetCommon.h:1401
bool deserializeMsgLobbyState(const uint8_t *in, std::size_t inSize, MsgLobbyState &outLobbyState)
Deserializes MsgLobbyState from fixed-size wire payload.
Definition NetCommon.h:1276
bool deserializeMsgBombPlaced(const uint8_t *in, std::size_t inSize, MsgBombPlaced &outBombPlaced)
Deserializes MsgBombPlaced from fixed-size wire payload.
Definition NetCommon.h:1632
constexpr uint8_t kPowerupsPerRound
Number of hidden powerups seeded into each round when enabled.
Definition PowerupConfig.h:44
PowerupType
Supported temporary multiplayer powerup effects.
Definition PowerupConfig.h:22
constexpr bool isValidPowerupType(const uint8_t rawType)
Returns true when a raw byte encodes one of the defined powerup types.
Definition PowerupConfig.h:81
Reliable discrete bomb-placement event sent by the server.
Definition NetCommon.h:757
uint8_t col
Tile-map column occupied by the bomb.
Definition NetCommon.h:762
uint32_t matchId
Authoritative match identifier that accepted this bomb placement.
Definition NetCommon.h:758
uint8_t radius
Explosion radius snapped at placement time.
Definition NetCommon.h:764
uint32_t serverTick
Server tick at which the bomb placement was accepted.
Definition NetCommon.h:759
uint8_t ownerId
Player identifier [0, kMaxPlayers) that owns this bomb.
Definition NetCommon.h:761
uint32_t explodeTick
Server tick at which this bomb is scheduled to explode.
Definition NetCommon.h:760
uint8_t row
Tile-map row occupied by the bomb.
Definition NetCommon.h:763
Compact tile cell payload reused by reliable gameplay events.
Definition NetCommon.h:650
uint8_t row
Tile-map row.
Definition NetCommon.h:652
uint8_t col
Tile-map column.
Definition NetCommon.h:651
Owner-only correction payload sent by the server.
Definition NetCommon.h:738
int16_t yQ
Corrected Y position in tile-space Q8.
Definition NetCommon.h:743
uint32_t serverTick
Server tick at which this correction applies.
Definition NetCommon.h:740
uint8_t reserved[3]
Currently unused wire padding.
Definition NetCommon.h:745
int16_t xQ
Corrected X position in tile-space Q8.
Definition NetCommon.h:742
uint8_t playerFlags
Owner-local copy of the current replicated player flags.
Definition NetCommon.h:744
uint32_t matchId
Authoritative match identifier that produced this correction.
Definition NetCommon.h:739
uint32_t lastProcessedInputSeq
Highest input seq the server has processed for this player.
Definition NetCommon.h:741
Reliable authoritative explosion-resolution event sent by the server.
Definition NetCommon.h:776
uint8_t ownerId
Player identifier [0, kMaxPlayers) that owned the bomb.
Definition NetCommon.h:779
uint32_t serverTick
Server tick at which the bomb explosion resolved.
Definition NetCommon.h:778
MsgCell destroyedBricks[kMaxExplosionDestroyedBricks]
Brick cells destroyed by this detonation.
Definition NetCommon.h:787
uint8_t originCol
Explosion origin column.
Definition NetCommon.h:780
uint8_t destroyedBrickCount
Number of valid entries stored in destroyedBricks.
Definition NetCommon.h:785
uint8_t originRow
Explosion origin row.
Definition NetCommon.h:781
MsgCell blastCells[kMaxExplosionBlastCells]
Blast footprint cells in propagation order.
Definition NetCommon.h:786
uint8_t blastCellCount
Number of valid entries stored in blastCells.
Definition NetCommon.h:784
uint8_t killedPlayerMask
Bitmask of player ids killed by this resolved explosion.
Definition NetCommon.h:783
uint8_t radius
Radius snapped from the bomb that detonated.
Definition NetCommon.h:782
uint32_t matchId
Authoritative match identifier that resolved this explosion.
Definition NetCommon.h:777
Hello payload sent by client during handshake.
Definition NetCommon.h:501
char name[kPlayerNameMax]
Player display name in a fixed-size wire field.
Definition NetCommon.h:511
uint16_t protocolVersion
Client protocol version.
Definition NetCommon.h:502
Batched input payload sent by client each simulation tick.
Definition NetCommon.h:642
uint32_t baseInputSeq
Sequence number of the first entry in the batch.
Definition NetCommon.h:643
uint8_t inputs[kMaxInputBatchSize]
Button bitmasks, zero-padded beyond count.
Definition NetCommon.h:645
uint8_t count
Number of valid entries (1..kMaxInputBatchSize).
Definition NetCommon.h:644
Round-start payload sent reliably by the server when a match begins.
Definition NetCommon.h:546
uint32_t matchId
Monotonic authoritative match identifier for this round start.
Definition NetCommon.h:547
uint32_t mapSeed
32-bit seed for the shared tile-map generator.
Definition NetCommon.h:548
Client-authored desired ready state for its currently accepted lobby seat.
Definition NetCommon.h:553
uint8_t ready
1 marks ready, 0 clears ready.
Definition NetCommon.h:554
uint8_t wins
Server-owned round wins for the current seat occupant.
Definition NetCommon.h:620
char name[kPlayerNameMax]
Seat display name, NUL-terminated and zero-padded when occupied.
Definition NetCommon.h:621
ESeatFlags flags
Occupancy/ready state for this player-id seat.
Definition NetCommon.h:619
Passive lobby seat snapshot sent reliably by the server.
Definition NetCommon.h:599
SeatEntry seats[kMaxPlayers]
Stable player-id keyed lobby seats.
Definition NetCommon.h:627
uint8_t countdownSecondsRemaining
Remaining whole countdown seconds during Countdown.
Definition NetCommon.h:625
uint16_t reserved
Reserved field. Must be zero in the current protocol version.
Definition NetCommon.h:626
EPhase phase
Lobby presentation phase.
Definition NetCommon.h:624
Explicit pre-start cancel edge sent when the current round start is aborted back to lobby.
Definition NetCommon.h:573
uint32_t matchId
Authoritative match identifier that was cancelled before start.
Definition NetCommon.h:574
Client acknowledgement that the gameplay scene for one round start is loaded.
Definition NetCommon.h:559
uint32_t matchId
Authoritative match identifier being acknowledged.
Definition NetCommon.h:560
Reliable end-of-match result edge sent before the automatic return to lobby.
Definition NetCommon.h:579
EResult result
End-of-match result type.
Definition NetCommon.h:587
uint32_t matchId
Authoritative match identifier that just ended.
Definition NetCommon.h:586
char winnerName[kPlayerNameMax]
Winner display name for Win, otherwise zeroed.
Definition NetCommon.h:589
uint8_t winnerPlayerId
Winning player id for Win, otherwise 0xFF.
Definition NetCommon.h:588
Explicit reliable start edge sent once all current match participants have loaded.
Definition NetCommon.h:565
uint32_t matchId
Authoritative match identifier being started.
Definition NetCommon.h:566
uint32_t unlockServerTick
Authoritative server tick at which gameplay input unlocks.
Definition NetCommon.h:568
uint32_t goShowServerTick
Authoritative server tick at which GO! should appear.
Definition NetCommon.h:567
Reject payload sent by server in response to Hello on failure.
Definition NetCommon.h:524
EReason
Definition NetCommon.h:526
@ GameInProgress
Server is not admitting new players outside lobby/pre-start flow.
EReason reason
Rejection reason code.
Definition NetCommon.h:534
uint16_t expectedProtocolVersion
Expected protocol version.
Definition NetCommon.h:536
Definition NetCommon.h:711
uint8_t radius
Explosion radius snapped at placement time.
Definition NetCommon.h:715
uint8_t col
Tile-map column occupied by the bomb.
Definition NetCommon.h:713
uint8_t row
Tile-map row occupied by the bomb.
Definition NetCommon.h:714
uint8_t ownerId
Player identifier [0, kMaxPlayers) that owns this bomb.
Definition NetCommon.h:712
int16_t yQ
Player Y position in tile-space Q8 (center position).
Definition NetCommon.h:684
int16_t xQ
Player X position in tile-space Q8 (center position).
Definition NetCommon.h:683
uint8_t playerId
Player identifier [0, kMaxPlayers).
Definition NetCommon.h:682
EPlayerFlags flags
Player alive status and other state flags.
Definition NetCommon.h:705
EPlayerFlags
Definition NetCommon.h:687
@ None
No replicated player flags are set.
@ Alive
Player is alive if set, dead if unset.
@ MaxBombsBoost
Player currently has the max-bombs boost active.
@ BombRangeBoost
Player currently has the bomb-range boost active.
@ SpeedBoost
Player currently has the speed boost active.
@ InputLocked
Player input/movement is server-locked if set.
sim::PowerupType type
Revealed powerup type occupying this cell.
Definition NetCommon.h:722
uint8_t row
Tile-map row occupied by the revealed powerup.
Definition NetCommon.h:724
uint8_t col
Tile-map column occupied by the revealed powerup.
Definition NetCommon.h:723
Snapshot payload broadcast by the server to all clients.
Definition NetCommon.h:673
uint8_t playerCount
Number of active entries stored in players.
Definition NetCommon.h:676
uint32_t matchId
Authoritative match identifier that produced this snapshot.
Definition NetCommon.h:674
PlayerEntry players[kMaxPlayers]
Packed active-player entries; slots beyond playerCount are ignored.
Definition NetCommon.h:708
uint8_t bombCount
Number of active entries stored in bombs.
Definition NetCommon.h:677
PowerupEntry powerups[kMaxSnapshotPowerups]
Packed revealed powerups; slots beyond powerupCount are ignored.
Definition NetCommon.h:727
uint32_t serverTick
Authoritative server tick at which this snapshot was produced.
Definition NetCommon.h:675
uint8_t powerupCount
Number of active revealed entries stored in powerups.
Definition NetCommon.h:678
BombEntry bombs[kMaxSnapshotBombs]
Packed active bombs; slots beyond bombCount are ignored.
Definition NetCommon.h:718
Welcome payload sent by server in response to Hello.
Definition NetCommon.h:516
uint16_t protocolVersion
Server protocol version.
Definition NetCommon.h:517
uint8_t playerId
Server-assigned player identifier [0, kMaxPlayers).
Definition NetCommon.h:518
uint16_t serverTickRate
Authoritative server simulation tick rate. Must be greater than zero.
Definition NetCommon.h:519
Packet metadata prefix present on every wire message.
Definition NetCommon.h:492
EMsgType type
Message type identifier.
Definition NetCommon.h:493
uint16_t payloadSize
Payload size in bytes, excluding header.
Definition NetCommon.h:494