Bomberman Multiplayer
Authoritative multiplayer networking layer for Bomberman.
Loading...
Searching...
No Matches
MultiplayerLevelScene.h
Go to the documentation of this file.
1
7#ifndef BOMBERMAN_SCENES_MULTIPLAYER_LEVEL_SCENE_H
8#define BOMBERMAN_SCENES_MULTIPLAYER_LEVEL_SCENE_H
9
10#include <memory>
11#include <optional>
12#include <string>
13#include <string_view>
14#include <deque>
15#include <unordered_map>
16#include <unordered_set>
17#include <vector>
18
19#include "Entities/Sound.h"
22#include "Net/NetCommon.h"
23#include "Scenes/LevelScene.h"
24
25namespace bomberman
26{
27 class Sprite;
28 class Text;
29
34 class MultiplayerLevelScene final : public LevelScene
35 {
36 public:
37 // =============================================================================================================
38 // ===== Construction and Scene Hooks ==========================================================================
39 // =============================================================================================================
40
42 MultiplayerLevelScene(Game* game, unsigned int stage, unsigned int prevScore,
43 std::optional<uint32_t> mapSeed = std::nullopt);
45 MultiplayerLevelScene(Game* game, unsigned int stage, unsigned int prevScore,
46 const net::MsgLevelInfo& levelInfo);
47
49 void onExit() override;
51 void onNetInputQueued(uint32_t inputSeq, uint8_t buttons) override;
52 [[nodiscard]]
54 bool usesEventDrivenLocalMovement() const override { return false; }
55 [[nodiscard]]
57 bool wantsNetworkInputPolling() const override;
58
59 protected:
61 void updateLevel(unsigned int delta) override;
63 void onKeyPressed(SDL_Scancode scancode) override;
64 [[nodiscard]]
66 bool supportsPause() const override { return false; }
67
68 private:
69 // =============================================================================================================
70 // ===== Scene State ===========================================================================================
71 // =============================================================================================================
72
73 // ----- Local and Remote Snapshot State -----
74
76 struct SnapshotSample
77 {
78 sim::TilePos posQ{};
79 uint32_t serverTick = 0;
80 bool valid = false;
81 };
82
84 struct RemotePlayerPresentation
85 {
86 std::shared_ptr<Player> playerSprite = nullptr;
87 std::shared_ptr<Text> playerTag = nullptr;
88 sim::TilePos authoritativePosQ{};
89 SnapshotSample previousSnapshot{};
90 SnapshotSample latestSnapshot{};
91 MovementDirection facingDirection = MovementDirection::Right;
92 float ticksSinceLatestSnapshot = 0.0f;
93 bool receivedSnapshotThisUpdate = false;
94 uint8_t effectFlags = 0;
95 };
96
98 struct BombPresentation
99 {
100 std::shared_ptr<Sprite> bombSprite = nullptr;
101 uint8_t ownerId = 0;
102 uint8_t radius = 0;
103 };
104
106 struct PowerupPresentation
107 {
108 std::shared_ptr<Sprite> powerupSprite = nullptr;
109 sim::PowerupType type = sim::PowerupType::SpeedBoost;
110 };
111
113 struct ExplosionPresentation
114 {
115 std::shared_ptr<Sprite> explosionSprite = nullptr;
116 uint32_t remainingLifetimeMs = 0;
117 };
118
120 struct BombSparkPresentation
121 {
122 std::shared_ptr<Sprite> sparkSprite = nullptr;
123 uint32_t remainingLifetimeMs = 0;
124 };
125
127 struct PendingLocalBombPlacement
128 {
129 uint16_t cellKey = 0;
130 uint32_t remainingLifetimeMs = 0;
131 };
132
134 struct LivePredictionTelemetry
135 {
136 uint32_t lastAckedInputSeq = 0;
137 uint32_t lastCorrectionServerTick = 0;
138 uint32_t lastCorrectionDeltaQ = 0;
139 uint32_t lastReplayCount = 0;
140 uint32_t lastMissingInputs = 0;
141 uint32_t lastRemainingDeferredInputs = 0;
142 uint32_t maxPendingInputDepth = 0;
143 uint32_t recoveryCatchUpSeq = 0;
144 bool recoveryActive = false;
145 };
146
147 // =============================================================================================================
148 // ===== Match Flow and Authority ==============================================================================
149 // =============================================================================================================
150
151 [[nodiscard]]
153 net::NetClient* requireConnectedNetClient();
155 void initializeMultiplayerScenePresentation();
157 void ensureMatchLoadedAckSent(net::NetClient& netClient);
158
159 [[nodiscard]]
161 bool updatePreStartFlow(net::NetClient& netClient);
163 void updateMatchStartFlow(net::NetClient& netClient);
164 [[nodiscard]]
166 bool updateLobbyReturnFlow(net::NetClient& netClient);
168 void updateMatchResultFlow(net::NetClient& netClient);
169
171 void consumeAuthoritativeNetState(net::NetClient& netClient);
173 void applyLatestCorrectionIfAvailable(const net::NetClient& netClient);
174
176 void collectPendingGameplayEvents(net::NetClient& netClient);
178 void applyNextPendingGameplayEvent();
179
180 [[nodiscard]]
182 bool allowsLocalGameplayInput() const;
184 void finalizeFrameUpdate(unsigned int delta);
185
187 void applySnapshot(const net::MsgSnapshot& snapshot);
188 [[nodiscard]]
190 bool shouldApplyGameplayEvent(uint32_t gameplayEventTick, const char* gameplayEventName) const;
191
193 void applyExplosionResolvedTiles(const net::MsgExplosionResolved& explosion);
195 void ensureLocalPresentation(uint8_t localId);
197 void seedLocalSpawnFromAssignedPlayerId();
199 void applyAuthoritativeCorrection(const net::MsgCorrection& correction);
201 static void logCorrectionReplayOutcome(const net::MsgCorrection& correction,
202 const net::CorrectionReplayResult& replayResult);
204 void storeCorrectionTelemetry(const net::MsgCorrection& correction,
205 const net::CorrectionReplayResult& replayResult);
207 void applyBootstrapLocalSnapshot(const net::MsgSnapshot::PlayerEntry& entry);
209 void updateLocalPresentationFromInputButtons(uint8_t buttons);
211 void syncLocalPresentationFromOwnedState(const net::LocalPlayerState& localState);
213 void updateSceneObjects(unsigned int delta);
214
215 // =============================================================================================================
216 // ===== Presentation and Diagnostics ==========================================================================
217 // =============================================================================================================
218
220 void updateGameplayConnectionHealth(const net::NetClient& netClient);
222 void setGameplayConnectionDegraded(bool degraded, uint32_t silenceMs = 0);
224 void logLivePredictionTelemetry(unsigned int delta);
226 void updateMaxPendingInputDepth();
227 [[nodiscard]]
229 uint32_t pendingInputDepth() const;
231 void logPredictionSummary() const;
232 [[nodiscard]]
234 bool shouldProcessOwnerPrediction() const;
235 [[nodiscard]]
237 bool shouldOwnLocalStateFromPrediction() const;
238 [[nodiscard]]
240 uint32_t currentAuthoritativeGameplayTick(const net::NetClient& netClient) const;
241
243 void showCenterBanner(std::string_view message, SDL_Color color);
245 void showCenterBanner(std::string_view mainMessage, std::string_view detailMessage, SDL_Color color);
247 void hideCenterBanner();
248
250 void applyAuthoritativeLocalSnapshot(const net::MsgSnapshot::PlayerEntry& entry);
252 void updateDeathBannerFlow();
254 void setLocalPlayerAlivePresentation(bool alive);
256 void setLocalPlayerInputLock(bool locked);
258 void updateLocalPlayerTagPosition();
260 void updatePowerupEffectPresentations(unsigned int delta);
262 void ensureDebugHudPresentations();
264 void updateDebugHud(unsigned int delta);
266 void removeDebugHudPresentations();
268 void removeRemotePlayerPresentation(uint8_t playerId);
269
270 // ----- Remote Player Presentation -----
271
273 void applySnapshotToRemotePlayers(const net::MsgSnapshot& snapshot, uint8_t localId);
275 void applySnapshotBombs(const net::MsgSnapshot& snapshot);
277 void applySnapshotPowerups(const net::MsgSnapshot& snapshot);
279 void updateOrCreateRemotePlayer(uint8_t playerId, int16_t xQ, int16_t yQ, uint32_t snapshotTick, uint8_t flags);
281 void updateOrCreateBombPresentation(const net::MsgSnapshot::BombEntry& entry);
283 void updateOrCreatePowerupPresentation(const net::MsgSnapshot::PowerupEntry& entry);
285 void pruneMissingRemotePlayers(const std::unordered_set<uint8_t>& seenRemoteIds);
287 void pruneMissingBombPresentations(const std::unordered_set<uint16_t>& seenBombCells);
289 void pruneMissingPowerupPresentations(const std::unordered_set<uint16_t>& seenPowerupCells);
291 void removeAllRemotePlayers();
293 void removeAllSnapshotBombs();
295 void removeAllSnapshotPowerups();
297 void applyBombPlacedEvent(const net::MsgBombPlaced& bombPlaced);
299 void applyExplosionResolvedEvent(const net::MsgExplosionResolved& explosion);
301 void destroyBrickPresentation(uint8_t col, uint8_t row);
303 void removeBombPresentation(uint8_t col, uint8_t row);
304 [[nodiscard]]
306 bool canSpawnLocalBombSparkPresentation() const;
308 void spawnLocalBombSparkPresentation();
310 void spawnExplosionPresentation(const net::MsgCell& cell);
312 void updatePendingLocalBombPlacements(unsigned int delta);
314 void updateLocalBombSparkPresentations(unsigned int delta);
316 void updateExplosionPresentations(unsigned int delta);
318 void removeAllLocalBombSparkPresentations();
320 void removeAllExplosionPresentations();
321
323 static void recordSnapshotSample(RemotePlayerPresentation& presentation,
324 int16_t xQ,
325 int16_t yQ,
326 uint32_t serverTick);
327 [[nodiscard]]
329 static uint32_t gameplayEventServerTick(const net::NetClient::GameplayEvent& gameplayEvent);
331 static void updateRemoteAnimationFromSnapshotDelta(RemotePlayerPresentation& presentation);
333 void updateRemotePlayerPresentations(unsigned int delta);
334 [[nodiscard]]
336 sim::TilePos computeRemotePresentedPosition(const RemotePlayerPresentation& presentation) const;
338 static void updateRemotePlayerTagPosition(RemotePlayerPresentation& presentation);
339 [[nodiscard]]
341 static std::string formatPlayerTag(uint8_t playerId);
343 void returnToMenu(bool disconnectClient, std::string_view reason);
345 void returnToLobby(std::string_view reason);
347 void onCollisionObjectSpawned(Tile tile, const std::shared_ptr<Object>& object) override;
348
349 // =============================================================================================================
350 // ===== Owned Runtime State ===================================================================================
351 // =============================================================================================================
352
353 uint32_t lastAppliedSnapshotTick_ = 0;
354 uint32_t lastAppliedCorrectionTick_ = 0;
355 std::optional<uint8_t> localPlayerId_{};
356 uint32_t matchId_ = 0;
357 uint32_t matchBootstrapStartedMs_ = 0;
358 bool matchStarted_ = true;
359 bool gameplayUnlocked_ = true;
360 bool matchLoadedAckSent_ = true;
361 uint32_t goBannerHideTick_ = 0;
362 std::optional<net::MsgMatchStart> currentMatchStart_{};
363 std::optional<net::MsgMatchResult> currentMatchResult_{};
364 std::shared_ptr<Text> localPlayerTag_ = nullptr;
365 std::shared_ptr<Text> gameplayStatusText_ = nullptr;
366 std::shared_ptr<Text> centerBannerText_ = nullptr;
367 std::shared_ptr<Text> centerBannerDetailText_ = nullptr;
368 std::shared_ptr<Text> debugHudNetText_ = nullptr;
369 std::shared_ptr<Text> debugHudPredictionText_ = nullptr;
370 std::shared_ptr<Text> debugHudSimulationText_ = nullptr;
371 net::ClientPrediction localPrediction_{};
372 LivePredictionTelemetry livePredictionTelemetry_{};
373 MovementDirection localFacingDirection_ = MovementDirection::Right;
374 uint32_t livePredictionLogAccumulatorMs_ = 0;
375 uint32_t debugHudRefreshAccumulatorMs_ = 0;
376 uint32_t lastAppliedGameplayEventTick_ = 0;
377 bool localPlayerAlive_ = true;
378 bool localPlayerInputLocked_ = false;
379 bool localBombHeldOnLastQueuedInput_ = false;
380 uint8_t localPlayerEffectFlags_ = 0;
381 uint32_t powerupBlinkAccumulatorMs_ = 0;
382 std::shared_ptr<Sound> explosionSound_ = nullptr;
383
384 std::unordered_map<uint8_t, RemotePlayerPresentation> remotePlayerPresentations_;
385 std::unordered_map<uint16_t, BombPresentation> bombPresentations_;
386 std::unordered_map<uint16_t, PowerupPresentation> powerupPresentations_;
387 std::unordered_map<uint16_t, std::shared_ptr<Object>> brickPresentations_;
388 std::vector<BombSparkPresentation> bombSparkPresentations_;
389 std::vector<PendingLocalBombPlacement> pendingLocalBombPlacements_;
390 std::vector<ExplosionPresentation> explosionPresentations_;
391 std::deque<net::NetClient::GameplayEvent> pendingGameplayEvents_;
392 bool gameplayConnectionDegraded_ = false;
393 bool exited_ = false;
394 bool returningToMenu_ = false;
395 };
396} // namespace bomberman
397
398#endif // BOMBERMAN_SCENES_MULTIPLAYER_LEVEL_SCENE_H
Client-side local prediction history, correction replay, and recovery state.
Tile
Tile type identifiers used in the tile map.
Definition Const.h:13
Client-side multiplayer connection lifecycle and packet endpoint.
Shared client/server wire contract for the multiplayer protocol.
Definition Game.h:23
Shared level-scene scaffold used by both singleplayer and multiplayer.
Definition LevelScene.h:20
Client-side scene for one authoritative multiplayer match.
Definition MultiplayerLevelScene.h:35
void onExit() override
Release multiplayer scene state before leaving the scene.
Definition MultiplayerLevelScene.cpp:299
bool supportsPause() const override
Multiplayer matches cannot be paused locally.
Definition MultiplayerLevelScene.h:66
bool usesEventDrivenLocalMovement() const override
This scene applies movement from queued network input instead of events.
Definition MultiplayerLevelScene.h:54
bool wantsNetworkInputPolling() const override
Return whether local gameplay input should currently be polled.
Definition MultiplayerLevelScene.cpp:53
void onNetInputQueued(uint32_t inputSeq, uint8_t buttons) override
Consume one locally queued input for prediction and presentation.
Definition MultiplayerLevelScene.Authority.cpp:118
void updateLevel(unsigned int delta) override
Run one multiplayer gameplay frame.
Definition MultiplayerLevelScene.cpp:74
void onKeyPressed(SDL_Scancode scancode) override
Handle scene-local key presses such as leaving the match.
Definition MultiplayerLevelScene.cpp:367
PowerupType
Supported temporary multiplayer powerup effects.
Definition PowerupConfig.h:22
Round-start payload sent reliably by the server when a match begins.
Definition NetCommon.h:546
Tile-space Q8 position representing the CENTER of the entity.
Definition Movement.h:30