예제 #1
0
        private static void StartMatchmaking(Session session, Session.EncodingScheme encoding)
        {
            // 로그인 한 Id 를 가져옵니다.
            string id;

            if (!session.GetFromContext("id", out id))
            {
                Log.Warning("Failed to request matchmaking. Not logged in.");
                if (encoding == Session.EncodingScheme.kJsonEncoding)
                {
                    session.SendMessage("error", Utility.MakeResponse("fail", "not logged in"));
                }
                else
                {
                    Log.Assert(encoding == Session.EncodingScheme.kProtobufEncoding);
                    FunMessage       funmsg = new FunMessage();
                    PongErrorMessage msg    = new PongErrorMessage();
                    msg.result = "fail";
                    msg.msg    = "not logged in";
                    funmsg.AppendExtension_pong_error(msg);
                    session.SendMessage("error", funmsg);
                }
                return;
            }

            funapi.Matchmaking.Client.MatchCallback match_cb = (string player_id, funapi.Matchmaking.Match match,
                                                                funapi.Matchmaking.MatchResult result) => {
                JObject         response = new JObject();
                LobbyMatchReply reply    = new LobbyMatchReply();

                if (result == funapi.Matchmaking.MatchResult.kSuccess)
                {
                    // Matchmaking 에 성공했습니다.
                    Log.Info("Succeed in matchmaking: id={0}", player_id);

                    string player_a_id = Utility.ReadStringFromJsonObject(match.Context, "A");
                    string player_b_id = Utility.ReadStringFromJsonObject(match.Context, "B");
                    Log.Assert(player_a_id != null && player_b_id != null);

                    string opponent_id;
                    if (player_a_id == player_id)
                    {
                        opponent_id = player_b_id;
                    }
                    else
                    {
                        opponent_id = player_a_id;
                    }

                    session.AddToContext("opponent", opponent_id);
                    session.AddToContext("matching", "done");
                    session.AddToContext("ready", 0);

                    if (encoding == Session.EncodingScheme.kJsonEncoding)
                    {
                        response      = Utility.MakeResponse("Success");
                        response["A"] = player_a_id;
                        response["B"] = player_b_id;
                    }
                    else
                    {
                        Log.Assert(encoding == Session.EncodingScheme.kProtobufEncoding);
                        reply.result  = "Success";
                        reply.player1 = player_a_id;
                        reply.player2 = player_b_id;
                    }
                    // 유저를 Game 서버로 보냅니다.
                    Common.Redirect(session, "game");
                }
                else if (result == funapi.Matchmaking.MatchResult.kAlreadyRequested)
                {
                    // Matchmaking 요청을 중복으로 보냈습니다.
                    Log.Info("Failed in matchmaking. Already requested: id={0}", player_id);
                    session.AddToContext("matching", "failed");
                    if (encoding == Session.EncodingScheme.kJsonEncoding)
                    {
                        response = Utility.MakeResponse("AlreadyRequested");
                    }
                    else
                    {
                        Log.Assert(encoding == Session.EncodingScheme.kProtobufEncoding);
                        reply.result = "AlreadyRequested";
                    }
                }
                else if (result == funapi.Matchmaking.MatchResult.kTimeout)
                {
                    // Matchmaking 처리가 시간 초과되었습니다.
                    Log.Info("Failed in matchmaking. Timeout: id={0}", player_id);
                    session.AddToContext("matching", "failed");
                    if (encoding == Session.EncodingScheme.kJsonEncoding)
                    {
                        response = Utility.MakeResponse("Timeout");
                    }
                    else
                    {
                        Log.Assert(encoding == Session.EncodingScheme.kProtobufEncoding);
                        reply.result = "Timeout";
                    }
                }
                else
                {
                    // Matchmaking 에 오류가 발생했습니다.
                    Log.Error("Failed in matchmaking. Error: id={0}", player_id);
                    session.AddToContext("matching", "failed");
                    if (encoding == Session.EncodingScheme.kJsonEncoding)
                    {
                        response = Utility.MakeResponse("Error");
                    }
                    else
                    {
                        Log.Assert(encoding == Session.EncodingScheme.kProtobufEncoding);
                        reply.result = "Error";
                    }
                }

                if (encoding == Session.EncodingScheme.kJsonEncoding)
                {
                    session.SendMessage("match", response, Session.Encryption.kDefault);
                }
                else
                {
                    FunMessage funmsg = new FunMessage();
                    funmsg.AppendExtension_lobby_match_repl(reply);
                    session.SendMessage("match", funmsg, Session.Encryption.kDefault);
                }
            };

            // 빈 Player Context 를 만듭니다. 지금 구현에서는 Matchmaking 서버가
            // 조건 없이 Matching 합니다. Level 등의 조건으로 Matching 하려면
            // 여기에 Level 등의 Matching 에 필요한 정보를 넣습니다.
            JObject player_ctxt = new JObject();

            // Matchmaking 을 요청합니다.
            funapi.Matchmaking.Client.Start(
                (int)MatchmakingType.kMatch1vs1, id, player_ctxt, new funapi.Matchmaking.Client.MatchCallback(match_cb),
                funapi.Matchmaking.Client.TargetServerSelection.kMostNumberOfPlayers, null, kMatchmakingTimeout);
        }
        public static void ProcessSpawnOrMatchmaking(
            Session session,
            JObject message,
            SessionResponse.SessionResponseHandler handler)
        {
            Log.Assert(session != null);
            // 메시지 핸들러 함수는 세션 ID 를 이벤트 태그로 하는 이벤트 위에서 실행합니다.
            // 아래는 이 함수를 메시지 핸들러 위에서 실행하게 강제하게 검사하는 식입니다.
            // 세션 ID 별로 이 함수를 직렬화하지 않으면 동시에 서로 다른 곳에서
            // 이 세션에 접근할 수 있습니다.
            Log.Assert(Event.GetCurrentEventTag() == session.Id);

            //
            // 매치메이킹 + 데디케이티드 서버 스폰 요청 예제
            //
            // 유저 2명, 4명, 6명을 대상으로 새로운 데디케이티드 서버 프로세스를 생성합니다.
            //
            // 클라이언트는 다음 메시지 형태로 매치메이킹을 요청합니다.
            // {
            //   // Facebook ID 또는 구글+ ID 등 고유한 ID 를 사용해야 합니다.
            //   "account_id": "id",
            //   // 매치 타입, 이 파일에 있는 MatchType 정의 참조
            //   "match_type": 1
            //   "user_data": {
            //      "level": 70,
            //      "ranking_score": 1500,
            //      ...
            //   },
            // }

            if (message[kAccountId] == null || message[kAccountId].Type != JTokenType.String ||
                message[kMatchType] == null || message[kMatchType].Type != JTokenType.Integer ||
                message[kUserData] == null || message[kUserData].Type != JTokenType.Object)
            {
                Log.Error("Missing required fields: '{0}' / '{1}' / '{2}': session_id={3}, message={4}",
                          kAccountId, kMatchType, kUserData, session.Id, message.ToString());
                handler(SessionResponse.ResponseResult.FAILED,
                        new SessionResponse(session, 400, "Missing required fields.",
                                            new JObject()));
                return;
            }

            //
            // 매치메이킹 요청 인자 설정 가이드
            //

            // 1. 매치 타입
            // 매치 타입을 식별하는 용도로 사용합니다. 정해진 값이 없으므로
            // 서버에서 정의한 매치 타입과 일치하는지 확인할 필요가 있습니다.
            long match_type = message[kMatchType].Value <long>();

            if (!MatchmakingType.IsValidMatchType(match_type))
            {
                Log.Error("Invalid match_type: session_id={0}, message={1}", session.Id, message.ToString());
                handler(SessionResponse.ResponseResult.FAILED,
                        new SessionResponse(session, 400, "Invalid arguments.", new JObject()));
                return;
            }

            // 2. 계정(account) ID
            // AccountManager 로 로그인한 계정 ID 를 입력합니다.
            string account_id = message[kAccountId].Value <string>();

            // 요청한 계정과 로그인 중인 세션이 일치하는 지 검사합니다.
            // 이 검사를 통해 다른 유저 ID 로 매칭 요청하는 것을 방지할 수 있습니다.
            if (AccountManager.FindLocalSession(account_id).Id != session.Id)
            {
                Log.Info("ProcessMatchmaking denied: bad request: session_id={0}, message={1}",
                         session.Id, message.ToString());
                handler(SessionResponse.ResponseResult.FAILED,
                        new SessionResponse(session, 400, "Access denied for this account.", new JObject()));
                return;
            }

            // 3. user_data, 매치메이킹 및 생성한 데디케이티드 서버 안에서 사용할
            // 플레이어의 데이터 입니다. 서버는 클라이언트에서 보낸 user_data 를 복사한 후
            // 요청 시간을 추가합니다. 따라서 매치메이킹 시 사용할 데이터는 최종적으로
            // 다음과 같습니다.
            //   "user_data": {
            //      "level": 70,
            //      "mmr_score": 1500,
            //      "req_time": 1544167339,
            //      ...
            //   },
            JObject user_data = message[kUserData].ToJObject();

            if (user_data[MatchmakingType.kMatchLevel] == null ||
                user_data[MatchmakingType.kMatchLevel].Type != JTokenType.Integer ||
                user_data[MatchmakingType.kMMRScore] == null ||
                user_data[MatchmakingType.kMMRScore].Type != JTokenType.Integer)
            {
                // 매치메이킹 요청에 필요한 인자가 부족합니다.
                Log.Error("Missing required fields: session_id={0}, message={1}", session.Id, message.ToString());
                handler(SessionResponse.ResponseResult.FAILED,
                        new SessionResponse(session, 400, "Missing required fields.", new JObject()));
                return;
            }

            // 매치메이킹 없이 스폰을 진행하는 타입이면, 즉시 스폰을 시작합니다.
            // 매치메이킹은 2명 이상인 경우에만 동작한다는 점에 주의해주세요.
            // 1명이 매치메이킹 큐에 들어가면 매치메이킹 콜백을 호출하지 않습니다.
            // 연습 게임 또는 데디케이티드 서버 내부에서 사람을 채운 후 게임을 시작할 때
            // 이러한 방식을 사용할 수 있습니다.
            if (match_type == (long)MatchmakingType.MatchType.kNoMatching)
            {
                SpawnOrSendUser(account_id, user_data, match_type);
                return;
            }

            // 4. OnMatchCompleted 콜백
            // 매칭을 종료했을 때 결과를 받을 콜백 함수를 지정합니다.
            // 매치 결과는 콜백 함수의 funapi.Matchmaking.Client.MatchResult 타입으로
            // 확인할 수 있습니다.

            // 5. 서버 선택 방법
            //    - kRandom: 서버를 랜덤하게 선택합니다.
            //    - kMostNumberOfPlayers; 사람이 가장 많은 서버에서 매치를 수행합니다.
            //    - kLeastNumberOfPlayers; 사람이 가장 적은 서버에서 매치를 수행합니다.
            funapi.Matchmaking.Client.TargetServerSelection target_selection =
                funapi.Matchmaking.Client.TargetServerSelection.kRandom;

            // 6. OnMatchProgressUpdated 콜백
            // 매치 상태를 업데이트 할 때마다 결과를 받을 콜백 함수를 지정합니다.
            // 타임 아웃 (기본 값: default(TimeSpan))
            TimeSpan timeout = default(TimeSpan);

            // 이 세션에서 요청한 매치 타입을 기록해둡니다.
            LogMatchHistory(session, account_id, match_type);

            Log.Info("Requesting a matchmaking: session_id={0}, account_id={1}, match_type={2}, user_data={3}",
                     session.Id, account_id, match_type, user_data.ToString());

            funapi.Matchmaking.Client.MatchCallback match_cb = new funapi.Matchmaking.Client.MatchCallback(
                (string player_id, funapi.Matchmaking.Match match, funapi.Matchmaking.MatchResult result) => {
                OnMatchCompleted(player_id, match, result, session, handler);
            });
            funapi.Matchmaking.Client.Start2(
                match_type, account_id, user_data,
                match_cb,
                target_selection,
                OnMatchProgressUpdated,
                timeout);
        }