Beispiel #1
0
        /// <summary>
        /// Handles a spawn game packet, updating the local player data state.
        /// </summary>
        private void HandleSpawn(MessageReader reader)
        {
            var spawnId = (SpawnableObjects)reader.ReadPackedUInt32();
            var owner   = reader.ReadPackedUInt32();

            reader.ReadByte();        // flags
            reader.ReadPackedInt32(); // component length

            if (spawnId == SpawnableObjects.GameData)
            {
                reader.ReadPackedInt32(); // game data net id
                var gameData   = reader.ReadMessage();
                var numPlayers = gameData.ReadPackedInt32();

                for (var i = 0; i < numPlayers; i++)
                {
                    var playerId = gameData.ReadByte();
                    UpdateOrCreatePlayerData(gameData, playerId);
                }
            }
            else if (spawnId == SpawnableObjects.PlayerControl)
            {
                var netId       = reader.ReadPackedInt32(); // player control net id
                var controlData = reader.ReadMessage();
                controlData.ReadByte();                     // unk, seems to be 1 if us, else 0
                var playerId = controlData.ReadByte();      // this is us, we got to ignore us

                _playerControlNetIdToPlayerId[netId] = playerId;

                // If this is us (only for the brief initial connect), ignore it.
                if (owner == _clientId)
                {
                    return;
                }

                // Either join an existing entry or create a new one.
                var existing = _playerData.Find(x => x.id == playerId);
                if (existing != null)
                {
                    existing.clientId = owner;
                }
                else
                {
                    _playerData.Add(new PlayerData(owner, playerId));
                }

                // Update if this is not the initial data gathering state.
                if (_hasPlayerData)
                {
                    OnPlayerDataUpdate?.Invoke(_playerData);
                }

                // Check if we have the data on everyone, and if yes disconnect and reconnect.
                if (!_hasPlayerData && _playerData.All(x => x.clientId != 0))
                {
                    _hasPlayerData = true;
                    DisconnectAndReconnect();
                }
            }
        }
Beispiel #2
0
        /// <summary>
        /// Invoked when the game has ended. Attempts to rejoin the same lobby.
        /// </summary>
        private void HandleEndGame(MessageReader message)
        {
            _playerData.ForEach(x => x.tasks.Clear()); // nobody has tasks any more
            OnPlayerDataUpdate?.Invoke(_playerData);
            OnGameEnd?.Invoke();

            // Simply rejoin the same lobby.
            _connection.SendReliableMessage(JoinGame);
        }
Beispiel #3
0
        /// <summary>
        /// Handles an RPC game packet, dispatching the results when appropriate.
        /// </summary>
        private void HandleRPC(MessageReader reader)
        {
            reader.ReadPackedInt32(); // rpc target
            var action = (RPCCalls)reader.ReadByte();

            if (action == RPCCalls.Close)
            {
                OnTalkingEnd?.Invoke();
            }
            else if (action == RPCCalls.StartMeeting)
            {
                OnTalkingStart?.Invoke();
            }
            else if (action == RPCCalls.UpdateGameData)
            {
                if (!_hasReconnectedAfterPlayerData)
                {
                    return;                                  // don't handle UpdateGameData earlier.
                }
                foreach (var dataEntry in reader.Messages())
                {
                    UpdateOrCreatePlayerData(dataEntry, dataEntry.Tag);
                }

                OnPlayerDataUpdate?.Invoke(_playerData);
            }
            else if (action == RPCCalls.MurderPlayer)
            {
                var victim         = reader.ReadPackedInt32();
                var victimPlayerId = _playerControlNetIdToPlayerId[victim];

                _playerData.Find(x => x.id == victimPlayerId).statusBitField |= 4; // dead
                OnPlayerDataUpdate?.Invoke(_playerData);
            }
            else if (action == RPCCalls.VotingComplete)
            {
                reader.ReadBytesAndSize(); // voting data
                var victim = reader.ReadByte();
                if (victim != 0xFF)
                {
                    _playerData.Find(x => x.id == victim).statusBitField |= 4; // dead
                    OnPlayerDataUpdate?.Invoke(_playerData);
                }
            }
        }
Beispiel #4
0
        /// <summary>
        /// Invoked when a player left the lobby. Handles situations where we
        /// end up becoming the host.
        /// </summary>
        private async void HandleRemovePlayer(MessageReader reader)
        {
            reader.ReadInt32();                  // room code
            var idThatLeft = reader.ReadInt32(); // id that left
            var newHost    = reader.ReadUInt32();

            reader.ReadByte(); // disconnect reason

            // Update the game data by removing the player that left.
            _hostId     = newHost;
            _playerData = _playerData.Where(x => x.clientId != idThatLeft).ToList();
            OnPlayerDataUpdate?.Invoke(_playerData);

            // If we're the host now, leave and attempt to rejoin to make someone else host.
            if (newHost == _clientId)
            {
                await DisconnectAndReconnect();
            }
        }
        /// <summary>
        /// Initializes this client by connecting to the specified host and attempting
        /// to join the specified lobby code. Will throw if connection fails, else will
        /// start servicing messages in the background. The caller is responsible for
        /// ensuring that the application stays running as long as the client is active.
        /// </summary>
        public async Task Connect(IPAddress address, string lobbyName, ushort port = MATCHMAKER_PORT)
        {
            _address   = address;
            _lobbyName = lobbyName;
            _lobbyCode = GameCode.GameNameToIntV2(lobbyName);

            var(connection, response) = await ConnectToMMAndSend(address, port, JoinGame);

            _port = (ushort)connection.EndPoint.Port;

            _connection = connection;
            _connection.DataReceived += OnMessageReceived;
            _connection.Disconnected += (sender, args) => { OnDisconnect?.Invoke(); };

            HandleJoinGameResult(response);

            if (!_hasPlayerData)
            {
                // If we don't have user data, send a SceneChange so that we receive a spawn.
                _connection.SendReliableMessage(writer =>
                {
                    writer.StartMessage((byte)MMTags.GameData);
                    writer.Write(_lobbyCode);
                    writer.StartMessage((byte)GameDataTags.SceneChange);
                    writer.WritePacked(_clientId); // note: must be _clientId since localplayer is not set yet
                    writer.Write("OnlineGame");
                    writer.EndMessage();
                    writer.EndMessage();
                });
            }
            else
            {
                // We have user data, invoke listeners.
                _hasReconnectedAfterPlayerData = true;

                OnConnect?.Invoke();
                OnPlayerDataUpdate?.Invoke(_playerData);
            }
        }