1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371
|
#pragma once
#if defined(WITH_MULTIPLAYER) || defined(DOXYGEN_GENERATING_OUTPUT)
#include "../LevelHandler.h"
#include "MpGameMode.h"
#include "NetworkManager.h"
#include "../Actors/Player.h"
#include "../UI/InGameConsole.h"
#include <Threading/Spinlock.h>
namespace Jazz2::Actors::Multiplayer
{
class MpPlayer;
class PlayerOnServer;
class RemotablePlayer;
class RemoteActor;
class RemotePlayerOnServer;
}
namespace Jazz2::UI::Multiplayer
{
class MpInGameCanvasLayer;
class MpInGameLobby;
class MpHUD;
}
namespace Jazz2::Multiplayer
{
/**
@brief Level handler of an online multiplayer game session
@experimental
*/
class MpLevelHandler : public LevelHandler, public IServerStatusProvider
{
DEATH_RUNTIME_OBJECT(LevelHandler, IServerStatusProvider);
#if defined(WITH_ANGELSCRIPT)
friend class Scripting::LevelScriptLoader;
#endif
friend class Actors::Multiplayer::PlayerOnServer;
friend class Actors::Multiplayer::RemotablePlayer;
friend class Actors::Multiplayer::RemotePlayerOnServer;
friend class UI::Multiplayer::MpInGameCanvasLayer;
friend class UI::Multiplayer::MpInGameLobby;
friend class UI::Multiplayer::MpHUD;
public:
/** @brief Level state */
enum class LevelState {
Unknown, /**< Unknown */
InitialUpdatePending, /**< Level was just created and initial update needs to be sent */
PreGame, /**< Pre-game is active */
WaitingForMinPlayers, /**< Pre-game ended, but not enough players connected yet */
Countdown3, /**< Countdown started (3) */
Countdown2, /**< Countdown continues (2) */
Countdown1, /**< Countdown continues (1) */
Running, /**< Round started */
Ending, /**< Round is ending */
Ended /**< Round ended */
};
/** @brief Asset type */
enum class AssetType {
Unknown,
Level,
TileSet,
Music,
Script
};
MpLevelHandler(IRootController* root, NetworkManager* networkManager, LevelState levelState, bool enableLedgeClimb);
~MpLevelHandler() override;
bool Initialize(const LevelInitialization& levelInit) override;
bool IsLocalSession() const override;
bool IsServer() const override;
bool IsPausable() const override;
bool CanActivateSugarRush() const override;
bool CanEventDisappear(EventType eventType) const override;
float GetHurtInvulnerableTime() const override;
float GetDefaultAmbientLight() const override;
void SetAmbientLight(Actors::Player* player, float value) override;
void OnBeginFrame() override;
void OnEndFrame() override;
void OnInitializeViewport(std::int32_t width, std::int32_t height) override;
bool OnConsoleCommand(StringView line) override;
void OnTouchEvent(const TouchEvent& event) override;
void AddActor(std::shared_ptr<Actors::ActorBase> actor) override;
std::shared_ptr<AudioBufferPlayer> PlaySfx(Actors::ActorBase* self, StringView identifier, AudioBuffer* buffer, const Vector3f& pos, bool sourceRelative, float gain, float pitch) override;
std::shared_ptr<AudioBufferPlayer> PlayCommonSfx(StringView identifier, const Vector3f& pos, float gain = 1.0f, float pitch = 1.0f) override;
void WarpCameraToTarget(Actors::ActorBase* actor, bool fast = false) override;
void BroadcastTriggeredEvent(Actors::ActorBase* initiator, EventType eventType, std::uint8_t* eventParams) override;
void BeginLevelChange(Actors::ActorBase* initiator, ExitType exitType, StringView nextLevel = {}) override;
void SendPacket(const Actors::ActorBase* self, ArrayView<const std::uint8_t> data) override;
void HandleBossActivated(Actors::Bosses::BossBase* boss, Actors::ActorBase* initiator) override;
void HandleLevelChange(LevelInitialization&& levelInit) override;
void HandleGameOver(Actors::Player* player) override;
bool HandlePlayerDied(Actors::Player* player) override;
void HandlePlayerWarped(Actors::Player* player, Vector2f prevPos, WarpFlags flags) override;
void HandlePlayerCoins(Actors::Player* player, std::int32_t prevCount, std::int32_t newCount) override;
void HandlePlayerGems(Actors::Player* player, std::uint8_t gemType, std::int32_t prevCount, std::int32_t newCount) override;
void SetCheckpoint(Actors::Player* player, Vector2f pos) override;
void RollbackToCheckpoint(Actors::Player* player) override;
void HandleActivateSugarRush(Actors::Player* player) override;
void HandleCreateParticleDebrisOnPerish(const Actors::ActorBase* self, Actors::ParticleDebrisEffect effect, Vector2f speed) override;
void HandleCreateSpriteDebris(const Actors::ActorBase* self, AnimState state, std::int32_t count) override;
void ShowLevelText(StringView text, Actors::ActorBase* initiator = nullptr) override;
StringView GetLevelText(std::uint32_t textId, std::int32_t index = -1, std::uint32_t delimiter = 0) override;
void OverrideLevelText(std::uint32_t textId, StringView value) override;
void LimitCameraView(Actors::Player* player, Vector2f playerPos, std::int32_t left, std::int32_t width) override;
void OverrideCameraView(Actors::Player* player, float x, float y, bool topLeft = false) override;
void ShakeCameraView(Actors::Player* player, float duration) override;
void ShakeCameraViewNear(Vector2f pos, float duration) override;
void SetTrigger(std::uint8_t triggerId, bool newState) override;
void SetWeather(WeatherType type, std::uint8_t intensity) override;
bool BeginPlayMusic(StringView path, bool setDefault = false, bool forceReload = false) override;
bool PlayerActionPressed(Actors::Player* player, PlayerAction action, bool includeGamepads, bool& isGamepad) override;
bool PlayerActionHit(Actors::Player* player, PlayerAction action, bool includeGamepads, bool& isGamepad) override;
float PlayerHorizontalMovement(Actors::Player* player) override;
float PlayerVerticalMovement(Actors::Player* player) override;
void PlayerExecuteRumble(Actors::Player* player, StringView rumbleEffect) override;
bool SerializeResumableToStream(Stream& dest) override;
void OnAdvanceDestructibleTileAnimation(std::int32_t tx, std::int32_t ty, std::int32_t amount) override;
StringView GetLevelDisplayName() const override;
/** @brief Returns current game mode */
MpGameMode GetGameMode() const;
/** @brief Sets current game mode */
bool SetGameMode(MpGameMode value);
/** @brief Synchronizes current game mode with all peers without restarting round */
bool SynchronizeGameMode();
/** @brief Returns owner of the specified object or the player itself */
static Actors::Multiplayer::MpPlayer* GetWeaponOwner(Actors::ActorBase* actor);
// Server-only methods
/** @brief Processes the specified server command */
bool ProcessCommand(const Peer& peer, StringView line, bool isAdmin);
/** @brief Sends the message to the specified peer */
void SendMessage(const Peer& peer, UI::MessageLevel level, StringView message);
/** @brief Sends the message to all authenticated peers */
void SendMessageToAll(StringView message, bool asChatFromServer = false);
/** @brief Called when a peer disconnects from the server, see @ref INetworkHandler */
bool OnPeerDisconnected(const Peer& peer);
/** @brief Called when a packet is received, see @ref INetworkHandler */
bool OnPacketReceived(const Peer& peer, std::uint8_t channelId, std::uint8_t packetType, ArrayView<const std::uint8_t> data);
/** @brief Returns full path of the specified asset */
static String GetAssetFullPath(AssetType type, StringView path, StaticArrayView<Uuid::Size, Uuid::Type> remoteServerId = {}, bool forWrite = false);
protected:
void AttachComponents(LevelDescriptor&& descriptor) override;
std::unique_ptr<UI::HUD> CreateHUD() override;
void SpawnPlayers(const LevelInitialization& levelInit) override;
bool IsCheatingAllowed() override;
void BeforeActorDestroyed(Actors::ActorBase* actor) override;
void ProcessEvents(float timeMult) override;
/** @brief Called when a player entered a transition to change the level */
void HandlePlayerLevelChanging(Actors::Player* player, ExitType exitType);
/** @brief Called when a player interacts with a spring */
bool HandlePlayerSpring(Actors::Player* player, Vector2f pos, Vector2f force, bool keepSpeedX, bool keepSpeedY);
/** @brief Called when a player is going to warp */
void HandlePlayerBeforeWarp(Actors::Player* player, Vector2f pos, WarpFlags flags);
/** @brief Called when a player changed modifier */
void HandlePlayerSetModifier(Actors::Player* player, Actors::Player::Modifier modifier, const std::shared_ptr<Actors::ActorBase>& decor);
/** @brief Called when a player freezes */
void HandlePlayerFreeze(Actors::Player* player, float timeLeft);
/** @brief Called when a player sets invulnerability */
void HandlePlayerSetInvulnerability(Actors::Player* player, float timeLeft, Actors::Player::InvulnerableType type);
/** @brief Called when a player sets score */
void HandlePlayerSetScore(Actors::Player* player, std::int32_t value);
/** @brief Called when a player sets health */
void HandlePlayerSetHealth(Actors::Player* player, std::int32_t count);
/** @brief Called when a player sets lives */
void HandlePlayerSetLives(Actors::Player* player, std::int32_t count);
/** @brief Called when a player takes a damage */
void HandlePlayerTakeDamage(Actors::Player* player, std::int32_t amount, float pushForce);
/** @brief Called when a player requests to synchronize weapon ammo */
void HandlePlayerRefreshAmmo(Actors::Player* player, WeaponType weaponType);
/** @brief Called when a player requests to synchronize weapon upgrades */
void HandlePlayerRefreshWeaponUpgrades(Actors::Player* player, WeaponType weaponType);
/** @brief Called when a player requests to morph to another type */
void HandlePlayerMorphTo(Actors::Player* player, PlayerType type);
/** @brief Called when a player changed duration of dizziness */
void HandlePlayerSetDizzy(Actors::Player* player, float timeLeft);
/** @brief Called when a player sets a shield */
void HandlePlayerSetShield(Actors::Player* player, ShieldType shieldType, float timeLeft);
/** @brief Called when a player emits a weapon flare */
void HandlePlayerEmitWeaponFlare(Actors::Player* player);
/** @brief Called when a player changes their current weapon */
void HandlePlayerWeaponChanged(Actors::Player* player, Actors::Player::SetCurrentWeaponReason reason);
private:
#ifndef DOXYGEN_GENERATING_OUTPUT
// Doxygen 1.12.0 outputs also private structs/unions even if it shouldn't
struct RemotingActorInfo {
std::uint32_t ActorID;
std::int32_t LastPosX;
std::int32_t LastPosY;
std::uint32_t LastAnimation;
std::uint16_t LastRotation;
std::uint16_t LastScaleX;
std::uint16_t LastScaleY;
std::uint8_t LastRendererType;
};
struct PlayerPositionInRound {
std::uint32_t ActorID;
std::uint32_t PositionInRound;
std::uint32_t PointsInRound;
};
struct MultiplayerSpawnPoint {
Vector2f Pos;
std::uint8_t Team;
MultiplayerSpawnPoint(Vector2f pos, std::uint8_t team)
: Pos(pos), Team(team) {}
};
struct PendingSfx {
Actors::ActorBase* Actor;
String Identifier;
std::uint16_t Gain;
std::uint16_t Pitch;
PendingSfx(Actors::ActorBase* actor, String identifier, std::uint16_t gain, std::uint16_t pitch)
: Actor(actor), Identifier(std::move(identifier)), Gain(gain), Pitch(pitch) {}
};
struct RequiredAsset {
AssetType Type;
std::uint32_t Crc32;
String Path;
String FullPath;
std::int64_t Size;
RequiredAsset(AssetType type, StringView path, StringView fullPath, std::int64_t size, std::uint32_t crc32)
: Type(type), Crc32(crc32), FullPath(fullPath), Path(path), Size(size) {}
};
enum class VoteType : std::uint8_t {
None,
Restart,
ResetPoints,
Skip,
Kick
};
#endif
//static constexpr float UpdatesPerSecond = 16.0f; // ~62 ms interval
static constexpr float UpdatesPerSecond = 30.0f; // ~33 ms interval
static constexpr std::int64_t ServerDelay = 64;
static constexpr float EndingDuration = 10 * FrameTimer::FramesPerSecond;
NetworkManager* _networkManager;
float _updateTimeLeft;
float _gameTimeLeft;
LevelState _levelState;
bool _isServer;
bool _forceResyncPending;
bool _enableSpawning;
HashMap<std::uint32_t, std::shared_ptr<Actors::ActorBase>> _remoteActors; // Client: Actor ID -> Remote Actor created by server
HashMap<Actors::ActorBase*, RemotingActorInfo> _remotingActors; // Server: Local Actor created by server -> Info
HashMap<std::uint32_t, String> _playerNames; // Client: Actor ID -> Player name
SmallVector<PlayerPositionInRound, 0> _positionsInRound; // Client: Actor ID -> Position In Round
SmallVector<MultiplayerSpawnPoint, 0> _multiplayerSpawnPoints;
SmallVector<Vector2i, 0> _raceCheckpoints;
SmallVector<PendingSfx, 0> _pendingSfx;
std::uint32_t _lastSpawnedActorId; // Server: last assigned actor/player ID, Client: ID assigned by server
std::int32_t _waitingForPlayerCount; // Client: number of players needed to start the game
std::uint32_t _lastUpdated; // Server/Client: last update from the server
std::uint64_t _seqNumWarped; // Client: set to _seqNum from HandlePlayerWarped() when warped
Threading::Spinlock _lock;
bool _suppressRemoting; // Server: if true, actor will not be automatically remoted to other players
bool _ignorePackets;
bool _enableLedgeClimb;
bool _controllableExternal;
bool _autoWeightTreasure;
VoteType _activePoll;
float _activePollTimeLeft;
float _recalcPositionInRoundTime;
std::int32_t _limitCameraLeft;
std::int32_t _limitCameraWidth;
Vector2f _lastCheckpointPos;
float _lastCheckpointLight;
std::int32_t _totalTreasureCount;
SmallVector<RequiredAsset, 0> _requiredAssets;
#if defined(DEATH_DEBUG)
std::int32_t _debugAverageUpdatePacketSize;
#endif
std::unique_ptr<UI::Multiplayer::MpInGameCanvasLayer> _inGameCanvasLayer;
std::unique_ptr<UI::Multiplayer::MpInGameLobby> _inGameLobby;
void InitializeRequiredAssets();
void SynchronizePeers();
std::uint32_t FindFreeActorId();
std::uint8_t FindFreePlayerId();
bool IsLocalPlayer(Actors::ActorBase* actor);
void ApplyGameModeToAllPlayers(MpGameMode gameMode);
void ApplyGameModeToPlayer(MpGameMode gameMode, Actors::Player* player);
void ShowAlertToAllPlayers(StringView text);
void SetControllableToAllPlayers(bool enable);
void SendLevelStateToAllPlayers();
void ResetAllPlayerStats();
Vector2f GetSpawnPoint(PlayerType playerType);
void ConsolidateRaceCheckpoints();
void WarpAllPlayersToStart();
void RollbackLevelState();
void CalculatePositionInRound(bool forceSend = false);
void CheckGameEnds();
void EndGame(Actors::Multiplayer::MpPlayer* winner);
void EndGameOnTimeOut();
bool ApplyFromPlaylist();
void RestartPlaylist();
void SkipInPlaylist();
void ResetPeerPoints();
void SetWelcomeMessage(StringView message);
void SetPlayerReady(PlayerType playerType);
void EndActivePoll();
static bool ActorShouldBeMirrored(Actors::ActorBase* actor);
static std::int32_t GetTreasureWeight(std::uint8_t gemType);
static bool PlayerShouldHaveUnlimitedHealth(MpGameMode gameMode);
void InitializeValidateAssetsPacket(MemoryStream& packet);
void InitializeLoadLevelPacket(MemoryStream& packet);
static void InitializeCreateRemoteActorPacket(MemoryStream& packet, std::uint32_t actorId, const Actors::ActorBase* actor);
#if defined(DEATH_DEBUG) && defined(WITH_IMGUI)
static constexpr std::int32_t PlotValueCount = 512;
std::int32_t _plotIndex;
float _actorsMaxCount;
float _actorsCount[PlotValueCount];
float _remoteActorsCount[PlotValueCount];
float _remotingActorsCount[PlotValueCount];
float _mirroredActorsCount[PlotValueCount];
float _updatePacketMaxSize;
float _updatePacketSize[PlotValueCount];
float _compressedUpdatePacketSize[PlotValueCount];
void ShowDebugWindow();
#endif
};
}
#endif
|