/// <summary>
        /// 매칭 조건이 맞지 않아 대기
        /// </summary>
        /// <param name="session"></param>
        /// <param name="request"></param>
        /// <param name="responseStream"></param>
        /// <returns></returns>
        public static async Task WaitStartPlay(Session session, StartPlayRequest request, IServerStreamWriter <StartPlayReply> responseStream)
        {
            // 대기자로 등록
            await MatchUser.RemoveMatchUser(session.user_no);

            await WaitingList.AddWaitingUser(new WaitingUser()
            {
                map_id = session.map_id, rank = session.rank
            }, session.user_no);
        }
        /// <summary>
        /// 매칭 조건이 맞지 않아 대기
        /// </summary>
        /// <param name="session"></param>
        /// <param name="request"></param>
        /// <param name="responseStream"></param>
        /// <returns></returns>
        public static async Task WaitStartPlay(Session session, StartPlayRequest request, IServerStreamWriter <StartPlayReply> responseStream)
        {
            var db = Cache.Instance.GetDatabase();

            // 대기자로 등록
            await db.KeyDeleteAsync($"match_user:{session.user_no}"); // 매칭 선점 클리어

            await db.SortedSetAddAsync($"waiting_list:{request.MapId}", session.user_no, session.rating);

            await db.StringSetAsync($"waiting:{session.user_no}", 0, startplay_polling_period);


            // 조건에 만족하는 유저가 없다면 대기 (redis puh로 활성화)
            var queue = Cache.Instance.GetSubscriber().Subscribe($"sub_user:{session.user_no}");

            var cts = new CancellationTokenSource();

            cts.CancelAfter((int)startplay_polling_period.TotalMilliseconds);
            try
            {
                var ret = await queue.ReadAsync(cts.Token);

                StartPlayReply reply = JsonParser.Default.Parse <StartPlayReply>(ret.Message);
                try
                {
                    // 매칭이 성공되었음을 알림
                    await responseStream.WriteAsync(reply);

                    // 다른 유저로 부터 매칭이 되었음을 받았다
                    // 매칭 정보를 삭제
                    await SequentialMatchmaking.RemoveMatchUser(session.user_no, request.MapId);
                }
                catch (InvalidOperationException)
                {
                    Log.Information($"StartPlay restore match user {ret.Message}");
                    // 전송 실패시 매칭 데이터를 백업
                    await db.StringSetAsync($"restore_match_user:{session.user_no}", ret.Message, restore_match_user_expire);
                }
            }
            catch (OperationCanceledException)
            {
                await SequentialMatchmaking.RemoveMatchUser(session.user_no, request.MapId);

                // 대기시간 만료 클라이언트에게 타임 아웃 처리를 보낸다.
                await responseStream.WriteAsync(new StartPlayReply()
                {
                    Code    = ErrorCode.Timeout,
                    IsStart = false
                });
            }
        }
Exemple #3
0
 /// <summary>
 /// 게임 시작 요청
 /// </summary>
 /// <param name="request"></param>
 /// <param name="responseStream"></param>
 /// <param name="context"></param>
 /// <returns></returns>
 public override async Task StartPlay(StartPlayRequest request, IServerStreamWriter <StartPlayReply> responseStream, ServerCallContext context)
 {
     try
     {
         //await SequentialMatchmaking.StartPlay(request, responseStream, context);
         await RankMatchmaking.StartPlay(request, responseStream, context);
     }
     catch (Exception ex)
     {
         Log.Error($"StartPlay error {ex.ToString()}");
         var reply = new StartPlayReply();
         reply.Code    = ErrorCode.NotEnough;
         reply.IsStart = false;
         reply.CharacterList.Add(new StartPlayCharacterInfo()); // 자신포함으로 빈슬롯 한개 넣어줌
         await responseStream.WriteAsync(reply);
     }
 }
        public static async Task StartPlaySimulate(StartPlayRequest request, IServerStreamWriter <StartPlayReply> responseStream, ServerCallContext context, Session session, JMapData map_data, JGameModeData game_mode)
        {
            (bool ret, string server_addr, byte worldId, string channel_key, string channel_id) = await Channel.GetAvailableServer(request.MapId);

            if (ret == false)
            {
                // 전투 가능한 서버가 없다
                Log.Error($"Cannot find Server user_no:{session.user_no}");
                await responseStream.WriteAsync(new StartPlayReply()
                {
                    Code = ErrorCode.BusyServer
                });

                return;
            }

            long match_id = await MatchInstanceId.GetMatchInstanceId();


            var matchResult = new MatchResult(match_id, request.MapId);

            if (await matchResult.AddPlayer(session, map_data, game_mode) == false)
            {
                await responseStream.WriteAsync(new StartPlayReply()
                {
                    Code = ErrorCode.NotEnough
                });

                return;
            }

            if (ServerConfiguration.Instance.gameSetting.EnableAIMatch)
            {
                matchResult.AddAI(session, map_data, game_mode);
            }
            await matchResult.Finish(worldId, server_addr, channel_id, game_mode);


            await responseStream.WriteAsync(matchResult.replyToClient);

            await PubStartPlay(matchResult.replyToBattleServer);


            _ = GameResult.WaitGameResult(session, channel_id, map_data, game_mode).ConfigureAwait(false);
        }
Exemple #5
0
    void Awake()
    {
        Joystick          = transform.Find("Joystick").GetComponent <ETCJoystick>();
        showTimerRequest  = GetComponent <ShowTimerRequest>();
        startPlayRequest  = GetComponent <StartPlayRequest>();
        gameOverRequest   = GetComponent <GameOverRequest>();
        quitBattleRequest = GetComponent <QuitBattleRequest>();
        quitGameRequest   = GetComponent <QuitGameRequest>();
        destroyRequest    = GetComponent <DestroyRequest>();
        gameChatRequest   = GetComponent <GameChatRequest>();
        knapsack          = transform.Find("KnapsackPanel").GetComponent <Knapsack>();
        for (int i = 0; i < 3; i++)
        {
            skillPos.Add(transform.Find("Skill" + i));
        }

        roleSelectPanel = transform.Find("RoleSelectPanel");

        timer        = transform.Find("TimerPanel/Time").GetComponent <Text>();
        gameOverText = transform.Find("GameOverPanel/GameOver").GetComponent <Text>();
        closeButton  = transform.Find("GameOverPanel/CloseButton").GetComponent <Button>();
        closeButton.onClick.AddListener(OnCloseClick);

        EffectDict    = facade.GetEffectDict();
        SkillItemDict = facade.GetSkillItemDict();

        transform.Find("SettingPanel/QuitGameButton").GetComponent <Button>().onClick.AddListener(OnQuitGameClick);

        chatDialog = transform.Find("ChatDialog");
        sendButton = transform.Find("ChatDialog/InputPanel/SendButton");
        chatButton = transform.Find("ChatDialog/ChatButton").GetComponent <Button>();
        inputField = transform.Find("ChatDialog/InputPanel/InputField").GetComponent <InputField>();
        sendButton.GetComponent <Button>().onClick.AddListener(OnSendClick);
        chatButton.onClick.AddListener(OnChatClick);
        inputField.onEndEdit.AddListener(x => OnSendClick());

        OtherPlayerChatMsgItem = Resources.Load <GameObject>("UIItem/Chat/OtherPlayerChatMsgItem");
        LocalPlayerChatMsgItem = Resources.Load <GameObject>("UIItem/Chat/LocalPlayerChatMsgItem");
        ChangeSeatItem         = Resources.Load <GameObject>("UIItem/ChangeSeatItem");
    }
Exemple #6
0
        public override async Task StartPlay(StartPlayRequest request, IServerStreamWriter <StartPlayReply> responseStream, ServerCallContext context)
        {
            Log.Information("StartPlay {0}", context.Peer);
            var session = await Session.GetSession(request.SessionId);

            if (session == null)
            {
                Log.Warning("lost session");
                await responseStream.WriteAsync(new StartPlayReply()
                {
                    Code = ErrorCode.LostSession
                });

                return;
            }


            var db = Cache.Instance.GetDatabase();

            // 다른 유저가 매칭 시도를 했을 경우
            var value = await db.StringGetAsync(string.Format("match_user:{0}", session.user_no));

            if (value.HasValue)
            {
                // 매칭 시도가 성공했을 경우
                var ch = await GetChannel((long)value);

                if (ch != null)
                {
                    await RemoveMatchUser(session.user_no);


                    Log.Information(string.Format("StartPlay {0}", session.user_no));

                    await responseStream.WriteAsync(new StartPlayReply()
                    {
                        Code             = ErrorCode.Success,
                        IsStart          = true,
                        BattleServerAddr = ch.server_addr,
                        WorldId          = ch.world_id,
                    });

                    return;
                }
                else
                {
                    //db.KeyDelete(string.Format("match_user:{0}", session.user_no));
                    await responseStream.WriteAsync(new StartPlayReply()
                    {
                        Code = ErrorCode.BusyServer,
                    });

                    return;
                }
            }

            long match_id     = 0;
            var  player_list  = new List <long>();
            var  waiting_list = await db.SortedSetRangeByScoreAsync("waiting_list");

            if (waiting_list.Length > MAX_START_PLAYER_COUNT - 1)
            {
                match_id = await db.StringIncrementAsync("match_instance_id");

                for (int i = 0; i < waiting_list.Length; ++i)
                {
                    long user_no = (long)waiting_list[i];
                    Log.Information("searching... waiting user {0}", user_no);
                    // 자신은 스킵
                    if (user_no == session.user_no)
                    {
                        continue;
                    }

                    if ((await db.StringGetAsync(string.Format("waiting:{0}", user_no))).HasValue)
                    {
                        // 매칭에 필요한 유저를 선점한다
                        if (await db.StringSetAsync(string.Format("match_user:{0}", user_no), match_id, match_user_expire, When.NotExists) == true)
                        {
                            player_list.Add(user_no);
                            Log.Information(string.Format("Candidate User {0}", user_no));

                            if (player_list.Count == MAX_START_PLAYER_COUNT - 1)
                            {
                                break;
                            }
                        }
                        else
                        {
                            Log.Information("already other match assign user {0}", user_no);
                        }
                    }
                    else
                    {
                        Log.Information("wait timeout user {0}", user_no);

                        // 유효하지 않는 유저는 대기자 목록에서 삭제한다.
                        await RemoveMatchUser(user_no);
                    }
                }
            }

            Log.Information("StartPlay player_list {0}", player_list);
            if (player_list.Count == MAX_START_PLAYER_COUNT - 1)
            {
                // 매칭에 필요한 인원을 모두 찾았을때
                // 전투 가능한 서버를 찾아 세팅
                (bool ret, string server_addr, byte worldId, string channel_key) = await GetAvailableServer();

                if (ret)
                {
                    var reply = new StartPlayReply()
                    {
                        Code             = ErrorCode.Success,
                        IsStart          = true,
                        BattleServerAddr = server_addr,
                        WorldId          = worldId,
                    };


                    // 매칭된 유저들에게 알림
                    for (int i = 0; i < player_list.Count; ++i)
                    {
                        await Cache.Instance.GetSubscriber().PublishAsync(string.Format("sub_user:{0}", player_list[i]), json_formatter.Format(reply));
                    }

                    await db.StringSetAsync(string.Format("match:{0}", match_id), channel_key, match_expire);

                    await RemoveMatchUser(session.user_no);


                    Log.Information(string.Format("StartPlay {0}", session.user_no));

                    await responseStream.WriteAsync(reply);

                    return;
                }
                else
                {
                    // 전투 가능한 서버가 없다
                    Log.Information(string.Format("Cannot find Server user_no:{0}", session.user_no));
                    await responseStream.WriteAsync(new StartPlayReply()
                    {
                        Code = ErrorCode.BusyServer,
                    });

                    return;
                }
            }


            // 매칭 시도를 실패하면 선점한 유저를 삭제
            await db.KeyDeleteAsync(player_list.Select(key => (RedisKey)string.Format("match_user:{0}", key)).ToArray());

            Log.Information("StartPlay waiting... {0}", session.user_no);

            // 대기자로 등록
            await db.SortedSetAddAsync("waiting_list", session.user_no, session.rating);

            await db.StringSetAsync(string.Format("waiting:{0}", session.user_no), 0, startplay_polling_period);


            // 조건에 만족하는 유저가 없다면 대기 (redis puh로 활성화)
            var queue = Cache.Instance.GetSubscriber().Subscribe(string.Format("sub_user:{0}", session.user_no));

            var cts = new CancellationTokenSource();

            cts.CancelAfter((int)startplay_polling_period.TotalMilliseconds);
            try
            {
                var ret = await queue.ReadAsync(cts.Token);

                // 다른 유저로 부터 매칭이 되었음을 받았다
                // 매칭 정보를 삭제
                await RemoveMatchUser(session.user_no);

                // 매칭이 성공되었음을 알림
                StartPlayReply reply = JsonParser.Default.Parse <StartPlayReply>(ret.Message);
                await responseStream.WriteAsync(reply);
            }
            catch (OperationCanceledException)
            {
                // 대기시간 만료 클라이언트에게 타임 아웃 처리를 보낸다.
                await responseStream.WriteAsync(new StartPlayReply()
                {
                    Code    = ErrorCode.Timeout,
                    IsStart = false
                });
            }
        }
 public bool IsChangeRequest(StartPlayRequest request)
 {
     return(character_type != (byte)request.SelectedCharacter || map_id != request.MapId);
 }
        /// <summary>
        /// 매칭 대상 (플레이어) 찾기
        /// </summary>
        /// <param name="session"></param>
        /// <param name="request"></param>
        /// <returns></returns>
        public static async Task <(List <long>, Dictionary <string, ServerCommon.PlayerInfo>, List <StartPlayCharacterInfo>, bool)> SearchPlayer(Session session, StartPlayRequest request)
        {
            var db = Cache.Instance.GetDatabase();

            Lobby.Session player_session = null;
            var           players        = new Dictionary <string, ServerCommon.PlayerInfo>();
            var           character_list = new List <StartPlayCharacterInfo>();
            long          match_id       = 0;
            var           player_list    = new List <long>();
            var           waiting_list   = await db.SortedSetRangeByScoreAsync($"waiting_list:{request.MapId}");

            Log.Information($"waiting user ({string.Join(",", waiting_list)})");
            bool result = false;

            if (waiting_list.Length >= MAX_START_PLAYER_COUNT - 1)
            {
                match_id = await db.StringIncrementAsync("match_instance_id");

                for (int i = 0; i < waiting_list.Length; ++i)
                {
                    long user_no = (long)waiting_list[i];
                    Log.Information($"searching... waiting user {user_no}");
                    // 자신은 스킵
                    if (user_no == session.user_no)
                    {
                        continue;
                    }

                    if ((await db.StringGetAsync($"waiting:{user_no}")).HasValue)
                    {
                        // 매칭에 필요한 유저를 선점한다
                        if (await db.StringSetAsync($"match_user:{user_no}", match_id, match_user_expire, When.NotExists) == true)
                        {
                            player_session = await Session.GetSession(user_no, false);

                            if (player_session == null)
                            {
                                Log.Information("cannot find Session {0}", user_no);
                                await db.KeyDeleteAsync($"match_user:{user_no}");

                                continue;
                            }

                            ServerCommon.PlayerInfo player = new ServerCommon.PlayerInfo()
                            {
                                user_no        = player_session.user_no,
                                character_type = player_session.character_type,
                                user_id        = player_session.user_name,
                                team           = (byte)(players.Count % (int)core.BaseStruggleTeam.TeamCount),
                            };
                            players.Add(player_session.session_id, player);
                            character_list.Add(new StartPlayCharacterInfo()
                            {
                                SelectedCharacter = player_session.character_type, UserId = player_session.user_name, Team = player.team
                            });

                            player_list.Add(user_no);
                            Log.Information($"Candidate User {user_no}");

                            if (player_list.Count == MAX_START_PLAYER_COUNT - 1)
                            {
                                result = true;
                                break;
                            }
                        }
                        else
                        {
                            Log.Information("already other match assign user {0}", user_no);
                        }
                    }
                    else
                    {
                        Log.Information("wait timeout user {0}", user_no);

                        // 유효하지 않는 유저는 대기자 목록에서 삭제한다.
                        await SequentialMatchmaking.RemoveMatchUser(user_no, request.MapId);
                    }
                }
            }

            Log.Information($"StartPlay player_list ({string.Join(",", player_list)})");
            return(player_list, players, character_list, result);
        }
        public static async Task StartPlay(StartPlayRequest request, IServerStreamWriter <StartPlayReply> responseStream, ServerCallContext context)
        {
            Log.Information($"StartPlay mapId:{request.MapId}, SelectedCharacter:{request.SelectedCharacter}, IsImmediatelyJoin{request.IsImmediatelyJoin}");

            var session = await context.GetSession();

            if (session == null)
            {
                await responseStream.WriteAsync(new StartPlayReply()
                {
                    Code = ErrorCode.LostSession
                });

                return;
            }

            // 게임 시작 요청 정보를 캐싱
            await session.UpdateSessionLock(request.SelectedCharacter, request.MapId, true);

            if (request.IsImmediatelyJoin)
            {
                (bool ret, string server_addr, byte worldId, string channel_key, string channel_id) = await Channel.GetAvailableServer(request.MapId);

                if (ret == false)
                {
                    // 전투 가능한 서버가 없다
                    Log.Error($"Cannot find Server user_no:{session.user_no}");
                    await responseStream.WriteAsync(new StartPlayReply()
                    {
                        Code = ErrorCode.BusyServer
                    });

                    return;
                }

                var tmp_players = new Dictionary <string, ServerCommon.PlayerInfo>();
                ServerCommon.PlayerInfo player = new ServerCommon.PlayerInfo()
                {
                    user_no        = session.user_no,
                    character_type = session.character_type,
                    user_id        = session.user_name,
                    team           = (byte)core.MathHelpers.GetRandomInt((int)core.BaseStruggleTeam.TeamCount),
                };
                tmp_players.Add(session.session_id, player);

                var characters = new List <StartPlayCharacterInfo>();
                characters.Add(new StartPlayCharacterInfo()
                {
                    SelectedCharacter = session.character_type, UserId = session.user_name, Team = player.team
                });
                var reply = new StartPlayReply()
                {
                    Code             = ErrorCode.Success,
                    IsStart          = true,
                    BattleServerAddr = server_addr,
                    WorldId          = worldId,
                    MapId            = request.MapId,
                };
                characters.ForEach(x => reply.CharacterList.Add(x));

                await responseStream.WriteAsync(reply);


                await SequentialMatchmaking.PubStartPlay(tmp_players, worldId, channel_id, session);


#pragma warning disable 4014
                Task.Run(async() => await SequentialMatchmaking.WaitGameResult(channel_id));
#pragma warning restore 4014

                return;
            }

            // 다른 플레이어로 인해 매칭이 시작 되었는지 확인
            if ((await SequentialMatchmaking.RestoreMatchUser(session.user_no, responseStream)) == true)
            {
                return;
            }

            // 대기중이 플레이어 찾기
            (var player_list, var players, var character_list, var search_success) = await SequentialMatchmaking.SearchPlayer(session, request);

            if (search_success == true)
            {
                // 게임 시작
                if ((await SequentialMatchmaking.StartPlay(player_list, players, character_list, session, request, responseStream)) == false)
                {
                    // 예약했던 플레이어 취소
                    await SequentialMatchmaking.ClearReservedPlayer(player_list);
                }
                return;
            }

            // 예약했던 플레이어 취소
            await SequentialMatchmaking.ClearReservedPlayer(player_list);

            Log.Information("StartPlay waiting... {0}", session.user_no);

            // 대기
            await SequentialMatchmaking.WaitStartPlay(session, request, responseStream);
        }
        /// <summary>
        /// 게임 플레이 시작
        /// </summary>
        /// <param name="player_list"></param>
        /// <param name="players"></param>
        /// <param name="character_list"></param>
        /// <param name="session"></param>
        /// <param name="request"></param>
        /// <param name="responseStream"></param>
        /// <returns></returns>
        public static async Task <bool> StartPlay(List <long> player_list, Dictionary <string, ServerCommon.PlayerInfo> players, List <StartPlayCharacterInfo> character_list, Session session, StartPlayRequest request, IServerStreamWriter <StartPlayReply> responseStream)
        {
            // 매칭에 필요한 인원을 모두 찾았을때
            // 전투 가능한 서버를 찾아 세팅
            (bool ret, string server_addr, byte worldId, string channel_key, string channel_id) = await Channel.GetAvailableServer(request.MapId);

            if (ret == false)
            {
                // 전투 가능한 서버가 없다
                Log.Error($"Cannot find Server user_no:{session.user_no}");
                await responseStream.WriteAsync(new StartPlayReply()
                {
                    Code = ErrorCode.BusyServer
                });

                return(false);
            }

            ServerCommon.PlayerInfo player = new ServerCommon.PlayerInfo()
            {
                user_no        = session.user_no,
                character_type = session.character_type,
                user_id        = session.user_name,
                team           = (byte)(players.Count % (int)core.BaseStruggleTeam.TeamCount)
            };
            players.Add(session.session_id, player);

            character_list.Add(new StartPlayCharacterInfo()
            {
                SelectedCharacter = session.character_type, UserId = session.user_name, Team = player.team
            });
            var reply = new StartPlayReply()
            {
                Code             = ErrorCode.Success,
                IsStart          = true,
                BattleServerAddr = server_addr,
                WorldId          = worldId,
                MapId            = request.MapId,
            };

            character_list.ForEach(x => reply.CharacterList.Add(x));


            string reply_str = new JsonFormatter(new JsonFormatter.Settings(true)).Format(reply);

            Log.Information($"StartPlay Reply {reply_str}");

            // 매칭된 유저들에게 알림
            for (int i = 0; i < player_list.Count; ++i)
            {
                await Cache.Instance.GetSubscriber().PublishAsync($"sub_user:{player_list[i]}", reply_str);
            }

            await SequentialMatchmaking.RemoveMatchUser(session.user_no, request.MapId);

            // 배틀서버에 알림
            await PubStartPlay(players, worldId, channel_id, session);

            Log.Information($"StartPlay {session.user_no}, channel_msg:{channel_key}");

            await responseStream.WriteAsync(reply);

            // 매칭이 성공적으로 이루어졌으므로 이후 매칭 결과를 pub/sub으로 전달 받는다.

#pragma warning disable 4014
            Task.Run(async() => await WaitGameResult(channel_id));
#pragma warning restore 4014

            return(true);
        }
        public static async Task StartPlay(StartPlayRequest request, IServerStreamWriter <StartPlayReply> responseStream, ServerCallContext context)
        {
            var mapData = ACDC.MapData[request.MapId];

            if (mapData == null)
            {
                Log.Warning($"StartPlay error map id {request.MapId}");
                await responseStream.WriteAsync(new StartPlayReply()
                {
                    Code = ErrorCode.WrongParam
                });

                return;
            }

            var gameModeData = ACDC.GameModeData[mapData.GameMode];

            if (gameModeData == null)
            {
                Log.Warning($"StartPlay error mode {mapData.GameMode}");
                await responseStream.WriteAsync(new StartPlayReply()
                {
                    Code = ErrorCode.WrongParam
                });

                return;
            }

            MinimumStartPlay minimumStartPlay;

            minimumStartPlayMap.TryGetValue(request.MapId, out minimumStartPlay);

            var session = await context.GetSession();

            if (session == null)
            {
                await responseStream.WriteAsync(new StartPlayReply()
                {
                    Code = ErrorCode.LostSession
                });

                return;
            }

            Log.Information($"StartPlay user_no:{session.user_no}, user_name:{session.user_name}, mapId:{request.MapId}, SelectedCharacter:{request.SelectedCharacter}, IsImmediatelyJoin{request.IsImmediatelyJoin}");

            bool checkSelectCharacter = await session.SelectCharacter(request.SelectedCharacter);

            if (checkSelectCharacter == false)
            {
                Log.Warning($"StartPlay error character id {request.SelectedCharacter}");
                await responseStream.WriteAsync(new StartPlayReply()
                {
                    Code = ErrorCode.WrongParam
                });

                return;
            }

            bool IsAISwitch     = false;
            bool IsFirstRequest = false;
            bool IsWaitingUser  = await WaitingList.IsWaitingUser(session.user_no);

            bool IsMatchTimeout = false;

            // 이미 대기중이 였지만 요청이 달라진 경우,  최초 요청으로 판단
            if (IsWaitingUser == false || session.IsChangeRequest(request))
            {
                IsFirstRequest = true;
                History.Info(session.member_no, session.user_no, session.character_no, HistoryLogAction.TryStartPlay, (byte)HistoryLogReason.None, request.SelectedCharacter, request.MapId, "", "");
                _ = LogProxy.writeActionLog(session, "플레이", "매칭시도", request.MapId.ToString()).ConfigureAwait(false);
            }
            else
            {
                if (DateTime.UtcNow > session.first_request_time.AddMilliseconds(ServerConfiguration.Instance.gameSetting.AIMatchTime))
                {
                    IsMatchTimeout = true;
                    if (ServerConfiguration.Instance.gameSetting.EnableAIMatch == true)
                    {
                        IsAISwitch = true;
                    }
                }

                if (minimumStartPlay != null &&
                    minimumStartPlay.Enable &&
                    DateTime.UtcNow > session.first_request_time.AddSeconds(minimumStartPlay.Timeout))
                {
                    Log.Information($"matchTimeout first_request_time:{session.first_request_time}, now:{DateTime.UtcNow}, timeout:{session.first_request_time.AddSeconds(minimumStartPlay.Timeout)}");
                    IsMatchTimeout = true;
                }
            }

            Log.Information($"flag IsAISwitch:{IsAISwitch}, IsFirstRequest:{IsFirstRequest}, IsWaitingUser{IsWaitingUser}, IsMatchTimeout{IsMatchTimeout}");


            if (request.SelectedCharacter != session.character_type || request.MapId != session.map_id)
            {
                var user = await UserCache.GetUser(session.member_no, session.user_no, false);

                user.character_no = session.character_no;
                user.map_id       = (byte)request.MapId;
                user.IsDirty      = true;
            }

            // 게임 시작 요청 정보를 캐싱
            await session.UpdateSessionLock(request.SelectedCharacter, request.MapId, IsFirstRequest);

            if (request.IsImmediatelyJoin)
            {
                await StartPlaySimulate(request, responseStream, context, session, mapData, gameModeData);

                return;
            }

            long match_id = await MatchInstanceId.GetMatchInstanceId();

            if ((await MatchUser.OccupyMatchUser(session.user_no, match_id)) == false)
            {
                // 다른 플레이어로 인해 매칭이 완료 되었는지 확인
                if ((await RestoreMatchUser(session, responseStream)) == true)
                {
                    if (ServerConfiguration.Instance.gameSetting.EnableReJoin == false)
                    {
                        // 재입장이 불가하면, 게임 시작 직후 매칭 정보 삭제
                        await MatchUser.RemoveMatchUser(session.user_no);
                    }
                    return;
                }
                else
                {
                    await responseStream.WriteAsync(new StartPlayReply()
                    {
                        Code             = ErrorCode.NotEnough,
                        IsStart          = false,
                        BattleServerAddr = "",
                        WorldId          = 0,
                        MapId            = request.MapId,
                    });

                    return;
                }
            }

            if (request.IsCancel)
            {
                // 대기 목록에서 제거
                await WaitingList.RemoveWaitingUser(session.user_no);

                await MatchUser.RemoveMatchUser(session.user_no);


                await responseStream.WriteAsync(new StartPlayReply()
                {
                    Code             = ErrorCode.NotEnough,
                    IsStart          = false,
                    BattleServerAddr = "",
                    WorldId          = 0,
                    MapId            = request.MapId,
                });

                return;
            }

            // 대기중이 플레이어 찾기
            (var matchResult, var search_success) = await SearchPlayer(session, match_id, mapData, gameModeData);

            if (search_success == false && IsAISwitch == true)
            {
                // 부족한 인원 만큼 AI로 채워 넣는다.(자신은 제외)
                int cnt = (GetStartPlayerCount(gameModeData) - 1) - matchResult.replyToClient.CharacterList.Count;
                for (int i = 0; i < cnt; ++i)
                {
                    matchResult.AddAI(session, mapData, gameModeData);
                }
            }

            if (search_success == true || IsAISwitch == true ||
                (ServerConfiguration.Instance.gameSetting.MatchForce && IsMatchTimeout) ||
                (minimumStartPlay != null && minimumStartPlay.Enable && IsMatchTimeout && matchResult.replyToClient.CharacterList.Count + 1 >= minimumStartPlay.PlayerCount)
                )
            {
                // 게임 시작
                if ((await StartPlay(match_id, session, request, responseStream, matchResult, mapData, gameModeData)) == false)
                {
                    // 예약했던 플레이어 취소
                    await MatchUser.CancelOccupiedMatchUser(matchResult.replyToBattleServer.players);
                }
                else
                {
                    if (ServerConfiguration.Instance.gameSetting.EnableReJoin == false)
                    {
                        // 재입장이 불가하면, 게임 시작 직후 매칭 정보 삭제
                        await MatchUser.RemoveMatchUser(session.user_no);
                    }
                }
                return;
            }

            // 예약했던 플레이어 취소
            await MatchUser.CancelOccupiedMatchUser(matchResult.replyToBattleServer.players);

            Log.Information("StartPlay waiting... {0}", session.user_no);

            // 대기
            await WaitStartPlay(session, request, responseStream);

            // 검색 실패시 다음 검색 조건 범위를 넓힌다.
            await session.WideningRangeRankLock();

            // 실패 결과 리턴
            matchResult.replyToClient.Code    = ErrorCode.NotEnough;
            matchResult.replyToClient.IsStart = false;
            matchResult.replyToClient.CharacterList.Add(new StartPlayCharacterInfo()); // 자신포함으로 빈슬롯 한개 넣어줌
            await responseStream.WriteAsync(matchResult.replyToClient);
        }
        public static async Task <bool> StartPlay(long match_id, Session session, StartPlayRequest request, IServerStreamWriter <StartPlayReply> responseStream, MatchResult matchResult, JMapData map_data, JGameModeData game_mode)
        {
            // 매칭에 필요한 인원을 모두 찾았을때
            // 전투 가능한 서버를 찾아 세팅
            (bool ret, string server_addr, byte worldId, string channel_key, string channel_id) = await Channel.GetAvailableServer(request.MapId);

            if (ret == false)
            {
                // 전투 가능한 서버가 없다
                Log.Error($"Cannot find Server user_no:{session.user_no}");
                await responseStream.WriteAsync(new StartPlayReply()
                {
                    Code = ErrorCode.BusyServer
                });

                return(false);
            }

            if (await matchResult.AddPlayer(session, map_data, game_mode) == false)
            {
                Log.Error($"StartPlay error user_no:{session.user_no}");
                await responseStream.WriteAsync(new StartPlayReply()
                {
                    Code = ErrorCode.NotEnough
                });

                return(false);
            }

            await matchResult.Finish(worldId, server_addr, channel_id, game_mode);

            // 매칭된 유저들에게 알림
            await Match.SaveMatch(match_id, matchResult.replyToClient);

            // 대기 목록에서 삭제
            foreach (var p in matchResult.replyToBattleServer.players.Values)
            {
                await WaitingList.RemoveWaitingUser(p.user_no);
            }


            // 배틀서버에 알림
            await PubStartPlay(matchResult.replyToBattleServer);

            Log.Information($"StartPlay {session.user_no}, channel_msg:{channel_key}");

            // 게임 시작 요청에 대한 응답 전송
            await responseStream.WriteAsync(matchResult.replyToClient);

            // 매칭이 성공적으로 이루어졌으므로 이후 매칭 결과를 pub/sub으로 전달 받는다.


            _ = GameResult.WaitGameResult(session, channel_id, map_data, game_mode).ConfigureAwait(false);

            History.Info(session.member_no, session.user_no, session.character_no, HistoryLogAction.StartPlay, (byte)HistoryLogReason.None, matchResult.replyToBattleServer.players.Count, matchResult.replyToClient.MapId, match_id.ToString(), session.character_type.ToString());
            _ = LogProxy.writeActionLog(session, "플레이", "매칭성공", matchResult.replyToClient.MapId.ToString()).ConfigureAwait(false);


            var characterNames  = new List <string>();
            var characterLevels = new List <int>();

            foreach (var player in matchResult.replyToBattleServer.players)
            {
                characterNames.Add(player.Value.user_id);
                characterLevels.Add(player.Value.character_level);
            }

            _ = LogProxy.writeRoundLog(session, game_mode.Name, "", "", "10", 0, 0, 0, characterNames, characterLevels, "").ConfigureAwait(false);

            return(true);
        }