2. 탈주닌자들의 로그는 어떻게 남을까?

chaechae·2023년 5월 23일
0
post-thumbnail

롤 신고사유중 아주 대표적인 이유로 탈주/AFK/자리비움이 있습니다. 해당 이유로 신고를 했을 때 이 유저가 탈주를 했다는 증거가 남아야할텐데 RIOT API에서 제공되는 데이터로 그 증거를 찾을 수 있을까? 궁금했습니다. 신고를 받고 해당 이유로 신고가 들어왔을 때 , 빠르게 파악해야 하니까요!

(출처 : riot)
riot 공식페이지에 게시된 글을 보면 탈주유저/자리비움 했을 때 어떻게 패널티를 주고있는지에 대한 글이 있는데요. "귀하가 경기를 떠나거나 게임 플레이 중에 빈둥거리고 있다고 판단 되는 경우" 다음과 같은 처벌을 받을 수 있습니다. 라고 되어있습니다.

그렇다면 .. 데이터로 어떻게 빈둥거리고 있다는걸 판단할 수 있는거지 ??

현재로선 는 90초이상 아무런 행동을 하지 않으면 아래와 같이 자리비움 경고창이 뜨고 그럼에도 행동을 하지 않으면 탈주 패널티를 받게 되는데요. 만약 탈주를 한 경우, 어떻게 데이터에 남는지 궁금 했습니다.

탈주에 관한 변수

① 진짜 게임을 나간경우, 탈주했다는 변수가 있다.
아주 간단하게 해당 유저가 나갔다면 '탈주여부' 에 대한 데이터 자체가 있지 않을까? 생각했습니다.

그래서! 일단 탈주한 경기의 데이터가 필요합니다. op.gg 커뮤니티에서 최근에 탈주한 유저의 아이디를 찾아, 매치 데이터를 갖고와서 확인 해봤습니다. 2데스하고 탈주 했네요... ()

결론부터 말하자면 직접적으로 확인할 수 있는 변수는 없었습니다.

어떤 시점부터 유저가 나갔다면 탈주했다는 지표(탈주했다면1 안했다면0)가 남을거라고 생각했지만 직접적으로 탈주했다는 데이터는 남지 않았습니다.

또한 가장 직접적으로 관련이 있다고 생각한 'timeplayed' 라는 변수는 해당 게임이 몇분동안 이루어 졌는지에 대한 변수인데요

해당유저가 어느 시점 이후로 나갔지만 다른 유저와 동일한 플레이 시간 으로 측정 되어 있었습니다. 나갔다면 나간시점부터 timeplayed가 측정 되지 않을줄 알았는데 말이죠..

흠.. 어쩔 수 없이, 해당 경기를 뜯어봐야하겠군요! 그래서 어떤 변수를 봐야할지 생각해봤습니다.

어떤 변수를 볼까?

② 잠수/탈주했다면, 성장이 거의 불가능하므로 레벨링이 당연히 낮고(xp), gold의 경우에도 계속 쌓이지만 gold를 안쓸것. 👉 즉 events 로그가 남들과 비교했을 때 차이점이 있을것이다. 어떻게 남는지 확인해보자
③ 잠수/탈주했다면, 나간 시점부터 하루 죙일 우물(집)에 머물었을것 !! 👉 1분마다 남는 position log를 확인 해보자.(position 또한 events 안에!)

events

저번에 불러온 match_data_log 의 events 값들을 파헤쳐봐야합니다. 위 리스트 안 딕셔너리 구조의 데이터를 데이터프레임으로 바꾸고 탈주유저의 participantId 해당하는 데이터만 가져오면 다음과 같습니다.

(여기에 itemId 값이 있는 경우, ddragon 에서 제공하는 item.json 을 따로 가져와서 item 정보를 merge 해줬습니다! 나중에 필요할것 같아서 해놨습니다! 지금은 중요하지 않으니 일단 패스!)

오호라.. 탈주한경우 해당유저의 timestamp 의 값을 보면 7분 이후 갑자기 32분으로 점핑 한것을 볼 수 있었습니다. 25분동안 시간이 멈춰있기 때문에 level도 6렙에서 멈춰있네요. 아무래도 게임 끝나기 직전에 들어왔나봅니다?

그냥 게임을 나간 경우, events 에 남는 timestamp 값을 통해서 해당 유저가 몇분 이후로 나갔는지 대략적으로 확인이 가능하다!

position

아마 라이엇에서 빈둥거린다는것은 events에 남는 데이터가 현저히 적은경우 인것 같습니다. 그런데 이 뿐만 아니라 유저가 빈둥거렸다는 점을 더 뒷받침 되기 위해서는 1분마다 남는 position 값을 통해서 해당유저가 어디에 많이 머물었는지 확인하면 좋을것 같다고 생각이 들었습니다. 물론, 1분마다 남는 log 이기 때문에 한계점이 분명했습니다만 아무리 그래도 70% 이상을 우물에 머물었다면.. 빈둥거렸다고 봐도 되지 않을까요 ㅎㅎ.

라인에 머문 비율계산

이번에는 해당유저가 어떤 라인을 오래 머물렀는지 각 라인별로 좌표영역을 정의해서 계산해보고 이동경로를 image_plot을 이용해서 시각적으로 표현해보려 합니다. (아마 다음 글에 추가할 것 같습니다.)

각 참가자별로 좌표가 남으니까. 프레임별로 좌표를 이동시켜서 유저가 어떻게 움직였는지 표현하면 재밌겠다고 생각했습니다. 그리고 찾아보니 파이썬에 image plot 과 FuncAnimation 이라는 기능이 있다는것을 알게 되었습니다. 한번에 좌표가 찍히는게 아니라 재생 버튼을 눌러서 좌표가 하나씩 찍히는, 마치 애니메이션 같은 기능이 있더군요. 즉 굉장히 함축된 "다시보기" 기능을 만든다고 생각하면 되겠습니다.

(그런데 사실 파이썬밖에 다루지 못해서 그런건지.. 조금 비효율적이라고 생각이 들었습니다ㅎㅎㅎ;; 용량도 많이들고요 굉장히 시간을 많이 뺏긴..)

아무튼 과정은 다음과 같습니다.

  • 일단 'timestamp' 'participantId' 'position' 이 포함된 데이터프레임을 만듭니다.
  • 그리고 각 영역별로(크게 TOP, MIDDLE, BOTTOM, HOME(RED/BLUE), JUNGLE) 좌표를 대략적으로 정의해서 계산
id_df = pd.DataFrame(match_data_log['info']['participants']) # 참가자의 participantid 와 puuid 에 관한 df
participant_ids = id_df['participantId'].tolist() # participantId는 1부터 10까지 있다. 총 10명의 참가자 [1,2,3,4,5,6,7,8,9,10]

frames_df = pd.DataFrame(match_data_log['info']['frames']) # 이동경로(position)은 match_data_log 의 info 컬럼 frames 행안에 있는것을 확인
frames_list = pd.DataFrame(frames_df['participantFrames'])['participantFrames'].tolist() 

moving_data = [ [] for _ in range(len(participant_ids)) ] # 10개의 빈리스트 컴프리헨션

for frame in frames_list:
    # participantId 별로 루프
    for i, participant_id in enumerate(participant_ids):
        if str(participant_id) in frame: # list 
            moving_data[i].append(frame[str(participant_id)])
            
moving_dfs = [pd.DataFrame(i) for i in moving_data]
moving = pd.concat(moving_dfs)
# 추출한 timestamp열을 frames_timestamp 변수에 저장
frames_timestamp = frames_df['timestamp']
# timestamp 열을 추가하여 moving 데이터프레임의 행과 열 개수를 확인
moving['timestamp'] = frames_timestamp
moving[['level','participantId','position','currentGold','totalGold','xp','timestamp']]

(ㅎㅎ.. 해당 코드들은 다시 정리해서 올려야 할것같습니다 .)

아무튼 핵심 변수들만 가져와서 본다면말이죠. 다음과같은 moving df 가 만들어집니다. (그리고 해당 유저의 position 데이터만 필요하다면, participantId == '위 유저의 participantId' 로 설정 해주면 됩니다. 대부분의데이터가 participantId 별로 정리되어 있기 때문에, 유저의 participantId가 몇인지 미리 알아둬야 합니다.)

(위 데이터만 봐도 알 수 있듯이, 해당 유저의 position 과 xp 가 7분 이후로 변함이 없는것을 확인 할 수 있네요 ㅎㅎ)

이제 시간별 position 데이터는 구했으니 위 좌표를 이용해 유저가 어디에서 얼마나 머물렀는지 계산해야합니다.

그런데 어디서부터 어디를 TOP, MID, BOTTOM, JUNGLE, etc.. 로 봐야하는것인가!!

약간의 노가다 과정이 필요했습니다. 저의 아주 주관적인 입장이 들어갔기 때문에 정확하지 않을수 있습니다ㅎㅎ.아래와같이 각 라인별에 해당하는 좌표들을 일일히 찍어보고 체크해서 해당영역을 대략적으로 정의 해봤는데요. 크게 TOP, MIDDLE, BOTTOM, blue_zone, red_zone 5개 영역으로 나뉘고 나머지에 해당하는 좌표를 JUNGLE로 묶었습니다.

각 라인별로 1차,2차 포탑 위치를 기준으로 삼아서 영역을 지정했고 RED팀, BLUE팀, 각라인 이외의 지역을 정글로 지정했습니다.

(참고한자료)


# 라인을 머문 점수 계산
def calculate_lane(x, y):
    top_ranges = [(500, 2000, 6000, 14000),(600, 9000, 13000, 14500), (1900,4500,11100,13100)]

    bottom_ranges = [(6000, 14000, 500, 2000),(13000,14500, 500,9000),(10500,13000,2000,3800)]

    mid_ranges = [(4500, 6000, 4500, 6000),(5200,6700,5200,6700),(5900,7400,5900,7400),(6000,8500,6000,8500),
                  (7300,8800,7300,8800),(8000,9500,8000,9500),(8700,10200,8700,10200),(9200,10500,9200,10500)]

    blue_zone = [(0,4500,0,4500)]
    red_zone = [(10500,15000,10500,15000)]

    for range_ in top_ranges:
        if range_[0] <= x <= range_[1] and range_[2] <= y <= range_[3]:
            return 'top'
    for range_ in mid_ranges:
        if range_[0] <= x <= range_[1] and range_[2] <= y <= range_[3]:
            return 'mid'
    for range_ in bottom_ranges:
        if range_[0] <= x <= range_[1] and range_[2] <= y <= range_[3]:
            return 'bottom'
    for range_ in blue_zone:
        if range_[0] <= x <= range_[1] and range_[2] <= y <= range_[3]:
            return 'blue_zone'
    for range_ in red_zone:
        if range_[0] <= x <= range_[1] and range_[2] <= y <= range_[3]:
            return 'red_zone'
    return 'jungle' # 나머지는 jungle
    
# summoner_participantId 는 id_df 의 puuid 와 비교해서 지정해주거나, match_info 안에 있는 summonerName를 찾아서 지정해줍시다. 경기마다 다르기 때문에..

summoner_moving = moving[moving['participantId'] == summoner_participantId]
summoner_moving.loc[:, 'lane'] = summoner_moving.apply(lambda row: calculate_lane(row['position']['x'], row['position']['y']), axis=1)

이제 위 position 값들을 calculate_lane 함수에 적용시키면 다음과 같이 유저가 어디 lane 에 머물렀는지 확인할 수 있습니다! (riot api에서 timestamp 의 경우 ms 단위로 되어있기 때문에 분단위로 바꿔주면 보기 좋습니다.)

시각화

위 데이터들을 streamlit에서 표현 해보겠습니다. 간단하게 plotly bar 그래프와 시간별로 데이터 값을 움직여서 확인할 수 있는 streamli의 slide 기능을 이용해서 만들어 보았습니다.

            summoner_moving = moving[moving['participantId'] == summoner_participantId]
            summoner_moving.loc[:, 'lane'] = summoner_moving.apply(lambda row: calculate_lane(row['position']['x'], row['position']['y']), axis=1)

            lane_counts = summoner_moving['lane'].value_counts()
            # x축 범위와 label 정의
            x_range = ['top', 'mid', 'bottom', 'jungle', 'red_zone', 'blue_zone']
            lane_counts = lane_counts.reindex(x_range, fill_value=0)

            # 슬라이더 추가
            st.subheader('Position Log')
            st.caption(f'{summoner_name}({summoner_position}/{summoner_champion}) 소환사가 머물었던 라인 비율입니다. \
                        시간별로 어떤 라인에 머물렀는지 확인할 수 있습니다. ')
            
            slide = st.slider('시간(minute):', 0, len(summoner_moving), (0, len(summoner_moving)))
            lane_counts = summoner_moving[slide[0]:slide[1]]['lane'].value_counts()
            lane_counts = lane_counts.reindex(x_range, fill_value=0)
            fig = go.Figure([go.Bar(x=x_range, y=lane_counts.values)])
            fig.update_layout(title='lane', xaxis_title='position', yaxis_title='ration')
            st.plotly_chart(fig, use_container_width=True)

위에서 볼수 있듯이 top 에서 7분정도 머물렀고, 게임 종료까지 RED팀 우물 안에서 보낸것을 볼 수 있습니다ㅎㅎ... 그래서 만약 유저가 탈주를 했다는 이유로 신고를 받았다면 다음과 같이 events 에 남는 xp데이터를 확인해서 탈주/자리비움 여부를 정해보겠습니다.


    is_afk = False
    for xp, group in summoner_moving.groupby('xp'):
        if len(group) > 10:  # 10분 이상 아무런 행동을 하지 않은 경우
            is_afk = True
            afk = "10분"
            break
        elif len(group) > 5:  # 5분 이상 아무런 행동을 하지 않은 경우
            is_afk = True
            afk = "5분"
            break

    if not is_afk:
        afk = "X"

streamlit match info

streamlit 에 해당 매치 유저의 info를 간단하게 아래와 같이 표현해봤습니다. 의도치 않게 css 공부도 하게 되었네요. 다른 lol 통계사이트와 거의 비슷하지만 아무래도 핵심은 해당 경기에서 유저가 게임을 나갔는지 확인하는 것 이니까요. 'AFK' 항목을 추가해봤습니다.

profile
게임 혹은 다양한 컨텐츠가 있는 곳을 좋아합니다. 시리즈를 참고하시면 편하게 글을 보실 수 있습니다🫠

0개의 댓글