1. LoL 신고 dashboard 만들기 (데이터 핥기)

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

들어가기전, 해당 글은 프로젝트를 진행하면서 생각해왔던 흐름과 고민을 정리한 글입니다! 주절 주절주의..

데이터를 핥아보자!!!!!!!!!!!

일단 롤 데이터를 한번 불러와 봅시다..!!!

"hmm.. 그냥 아이디(닉네임)만 치면 다 나오는거 아니야?".....

라고 만만하게 생각했지만 거쳐야할 단계가 꽤나 있었습니다.
RIOT API 에 관한 정보는 꽤 많이 나와있기 때문에 설명 비중을 줄이도록 하겠습니다!!

필요한 데이터를 가져오기 위한 메커니즘은 다음과 같습니다.

(필수!) RIOT 개발자 홈페이지에서 API KEY를 받는다
👉 유저 아이디를 입력해서 고유아이디(puuid)를 받는다.
👉 해당 puuid 의 matchid 를 받는다(한번에 최대 100개).
👉 해당 matchid에 대한 타임라인 데이터를 받는다!

아이디만 입력해서 받을 수 있는게 아니였고, 유저별로 고유아이디가 있다는 것도 처음 알았습니다. 이 고유아이디가 있어야 matchid를 불러올 수 있기 때문에 꼭 필요합니다.
(또한 riot api는 1초에 20번 2분에 100번 제한이 있었기 때문에 많은 표본이 필요한 분석을 하려면 데이터수집에 꽤나 시간이 필요합니다.)

timeline data 불러오기

고유아이디 puuid

아래 코드처럼 본인이 받은 api key와 검색하려는 아이디를 포맷으로 해당 url에 입력해주면
아래의 결과와 같은 정보를 받을 수 있습니다. (저는 파이썬을 이용했습니다)

puuid에 대한 URL, matchdata에 대한 URL 등등 각각의 URL이 존재하고 해당 URL 에 필요한 key들(위의 경우는 summoner_name, api_key)을 넣어주고 requests 요청하면 데이터를 얻을 수 있는 형식이었습니다. (게임스트리머 랄로님의 정보를 가져와봤습니다ㅋㅋ)

위 결과에서 볼 수 있듯이 응답 데이터가 JSON 포멧이라 json() 함수를 통해 사전(dictionary) 객체를 얻을 수 있었습니다. 프로필아이콘,레벨 등등 다양한데요. 어차피 puuid 만필요하기 때문에ㅎㅎ ['puuid'] 정보만 쏙 빼놓아 줍니다! 이제 위와 똑같은 방식으로 matchid를 불러올 수 있습니다.

matchid

start=0
count=3

matchid_url = "https://asia.api.riotgames.com/lol/match/v5/matches/by-puuid/{}/ids?type=ranked&start={}&count={}&api_key={}"
url = matchid_url.format(puuid, start, count, api_key)
response = requests.get(url)
match_ids = response.json()

match_ids

# 결과 
['KR_6488450242', 'KR_6487426945', 'KR_6487396875']

최근 몇경기를 가져올지는 start 와 count 를 통해서 정할 수 있고 이전에서 구한 puuid 와 api_key가 꼭 필요합니다. 이렇게 구한 matchid를 통해 경기 데이터를 이제 드디어! 가져올 준비가 된것이에요!!!!

timeline (match_data)

# 가장 최근 경기인 'KR_6488450242'
match_id = match_ids[0] 

time_url = 'https://asia.api.riotgames.com/lol/match/v5/matches/{}/timeline?api_key={}'
url = time_url.format(match_id, api_key)
response = requests.get(url)
match_data = response.json()

드디어!! timeline 데이터를 불러올 수 있게 됐습니다!! 저는 이 데이터를 match_data라고 이름 짓겠습니다

그런데... 데이터를 불러오는 순간.... 도큥..

이 어마무시한..이중 삼중으로 연결된.. 끝없는 딕셔너리의 구조..,, 스크롤바를 넘겨도 넘겨도 끝나지않는 데이터와 눈맞춤을 하였고... 딕셔너리 혐오증에 걸릴번한 순간 (그래도 지금 보니 데이터가 아주 잘 정리 되어있었다!)

나 그래도 롤 시즌3부터 해왔는데 짬빱이 있지..(응 실버~!)

일단 정신 차리고 대략적으로 데이터를 음미해보았습니다. 음~ 10명의 참가자와 puuid, 그리고 participantid 별로 로그가 남아있구나, 포지션 좌표부터, 아이템로그, 킬로그 등등 생각보다 자세하게 남아있네 ㄷㄷ..

에라이~ 무수한 딕셔너리의 악수요청에 기가빨린 저는 일단 데이터프레임(match_data)으로 바꿔서 보도록 했습니다!!

편안..!

아무튼 핵심은 match_data의 info 컬럼안에 있었습니다. 위 컬럼안의 딕셔너리들을 똑같이 풀어보면요

['info']['participants'] 에는 참가자별 번호와 각각의 puuid가 있었습니다.

그리고 중요한 정보는 바로 ['info']['frames'] 에 있었습니다.

위의 frame_df 로 정의해놓은 데이터프레임을 보시면
timestamp 별로 (약 1분단위) events 와 participantFrames 이 남겨있는데

position(좌표), 몇분 몇초에 아이템을 샀는지 팔았는지, CHAMPION_KILL, BUILDING_KILL, 바론,드래곤,와드 킬, 챔피언 스탯, 등등 각 participantId(참가자id 1번~10번) 별로 일어난 로그들이 정리되어 있었습니다. 또 롤을 플레이해보신 분들은 아시겠지만 내가 죽었을 때, 누구에게 얼만큼 데미지를 입고 어떤 스킬에 맞고 죽었는지에 대한 통계표가 나오는데 이 데이터 또한 남겨져있었습니다.

그런데 timeline URL 같이 실시간으로 일어난 데이터들 말고 경기가 끝나고 요약된 데이터를 볼 수 없을까? 생각했는데 당근 있었습니다!

MATCH_V5

#  match_v5
v5_url = 'https://asia.api.riotgames.com/lol/match/v5/matches/{}?api_key={}'  # matchid , api_key`
url = v5_url.format(match_id ,api_key)
response = requests.get(url)
match_data_v5 = response.json()

df = pd.DataFrame(match_data_v5['info']['participants'])

sample = df[['teamId','puuid','summonerName','participantId','teamPosition', 'challenges', 
        'championName','lane','kills','deaths','assists','totalMinionsKilled','neutralMinionsKilled','goldEarned','goldSpent','champExperience','item0','item1','item2',
        'item3','item4','item5','item6','totalDamageDealt','totalDamageDealtToChampions','totalDamageTaken','damageDealtToTurrets','damageDealtToBuildings',
        'totalTimeSpentDead','visionScore','win','timePlayed']]

challenge = pd.DataFrame(sample['challenges'].tolist())

col = challenge[['soloKills','abilityUses','damageTakenOnTeamPercentage','skillshotsDodged','skillshotsHit','enemyChampionImmobilizations','laneMinionsFirst10Minutes','controlWardsPlaced','visionScoreAdvantageLaneOpponent'
                    , 'visionScorePerMinute','wardTakedowns','effectiveHealAndShielding','dragonTakedowns','baronTakedowns','teamBaronKills']]
jungle_col = challenge.filter(regex='^jungle|Jungle|kda')

match_info = pd.concat([sample , col, jungle_col], axis = 1)
champion_info = match_info[['participantId','teamId','teamPosition','summonerName','puuid','championName']]
    

match_v5 URL 을 위에서 얻은 match_Id의 요약된 경기기록을 볼수있습니다. 쉽게말해서 경기가 끝나고 나오는 통계표에 대한 데이터입니다. (그런데 매우 자세한! 얼마나 자세하면 핑을 몇번찍었는지 , 스프레이를 몇번 했는지까지 나옵니다.. 변수는 너무많아서 생략!)

또 'challanges' 라는 컬럼 안에 딕셔너리 구조로 더 다양한 변수들이 들어있는데요, jungle 과 관련된변수, 'kda'가 이 안에있어서 따로 빼주었습니다

코드에서 볼 수 있듯이 딕셔너리를 데이터프레임으로 바꿔서 제가 필요하다고 생각되는 column 들만 뽑았습니다ㅎㅎ. 진짜 다양하게 있어서 아이디어만 있다면 재밌는 분석을 할 수 있을것같아요.
그래서 정리된 match_info 를 보면 대략 다음과 같은 형태입니다.


좋아좋아.. 이제 본격적으로 신고를 받았을 때 봐야하는 변수가 뭐가 있을까....?

필요한 변수

내가 RIOT 데마시아의 일원이라 생각해보고 필요한 변수들을 대략적으로 정리해보자..

① 특정 라인에서 타워에 꼴아 박는다.

👉 타워에게 받은피해 , position(x,y) , death log 정도 필요하겠군

② 상대방 챔피언에게 고의로 죽었다.

👉 "고의로 죽은것"과 "정말 못해서 죽은것" 의 차이를 증명 하려면 조금 애매하긴하다. 하지만 어느정도 판별은 가능하지 않을까? 죽었을 때 딜교환을 했는지에 관한 변수가 필요할것 같다. riot 에서 말한 "못하지만 노력한 경우" 본인이 death를 많이 했어도... 적어도! 딜교환을 하지 않았을까? 이에 관한 데이터가 필요하다.

(물론 잘큰 탈론,제드 같이 암살자 캐릭한테 딜교환도 못하고 끔살을 당하는 경우가 있다. 여러 종합적인 상황을 더 고려해야할 것 같다.)

③ 아예 참여하지 않는 우물잠수.

👉 position(x,y) 로그를통해 해당유저가 어디에 많이 머물렀는지 확인이 가능하다! 또한 잠수했다면 딜량, 레벨, 같은 참여도를 파악할 수 있는 변수들이 현저히 낮을것!

④ 비정상적으로 골드를 획득.

👉 현재 league of legends 게임 안에서 골드를 획득하는 방법은
CHAMPION_KILL, BUILDING_KILL(+타워골드), CS(+중립몬스터들), 또 뭐가있더라.. 아! 룬에서도 골드를 소량 빌릴 수 있고(외상), 수확의 낫처럼 특정 조건이 만족되면 골드를 획득하는 ITEM도 있습니다.

무엇보다 가장먼저 현재골드(current gold) 가 총골드(total gold)를 넘을 수 없다는 점을 이용하면 되지 않을까 생각했습니다. 이 말이 무엇이냐면 내가 번만큼만 item을 구매할 수 있다는건데요. 당연합니다.
예를들어 첫 시작할때 350골드를 얻고 CS나, 여러 이벤트를 통해 골드를 획득하게 되면 total gold 는 계속 증가하고, 아이템을 사게되면 그 time에 기록된 current gold는 감소할 수 밖에 없습니다. 물론 골드를 한번도 안쓴다면 현재골드 = 총골드 가 되겠고, 비정상적인 행동을 하지 않는 이상 현재골드는 총골드를 절대로 넘을 수 없습니다.

예시 데이터

timestmapcurrent goldtotal goldtypegold earndgold used
0350350
1650650CHAMPION_KILL300
250650ITEM_BUY600

나중에 나오지만 gold핵을 쓰는 유저들의 로그들을 보면 current gold 가 비정상적으로 치솟는걸 볼 수 있습니다 ㅎㅎ 또한 type 별로 어떻게 골드를 획득했는지도 볼 수 있습니다. (예를들어 스크립트핵을 쓰는경우 정말 비정상적인 속도로 이벤트들이 발동되는데 그러한 부분들이 데이터에 남더군요.)

그래서 제가 생각해본 메커니즘은 다음과 같습니다.

(1) 1차 거름망으로 현재골드(current gold) 가 총골드(total gold)를 넘었는지 확인한다.
(2) 2차 거름망으로 해당유저의 current gold를 이동평균으로 이상치 값을 발견해보자.
특정 rpg 게임 같이 24시간 재화의 가치가 왔다갔다 하는게 아니고 또한 매치경기에 따라 데이터의 양이 정해지지만, 나름 시계열데이터 이기 때문에 이용해보면 어떨까 생각해봤습니다.
(3) 해당유저의 이상치값이 발견되면 그 timestamp 구간에 어떤 type의 이벤트들이 일어났는지 보여주자!

⑤ 알고리즘을 교묘하게 피해서,, 방해하는 유저..

👉 어찌보면 가장 까다로운 리폿사유입니다. 아직까지도 명확하게 해결하지는 못했고 .. 역시 저도 바로 제재를 가하기는 힘들다고 생각했습니다.

이런 부분에 대해서 갈팡질팡 하고 있을때 RIOT은 어떻게 대응하고 있나 찾아봤습니다.
요약해보면

  1. 일단 리폿이 들어왔을 때 저렇게 세세하게 다 처리하면 득보다 실이 더 크다. 특히 자동화는 거의 불가능하다.(⑤번 같은 유저)
  2. 그래서 제재를 한다기 보다는 일단 리폿을 받으면 절대 사라지지 않고, 스택이 쌓인다.
  3. 그 스택이 쌓인 유저들끼리 매칭이 되도록 한다.
  4. 물론 스택이 정말 많이 쌓인 유저들은 매치기록을 보고 제재를 가한다

라고 되어있더군요. (RIOT 에서 19년도 까지 근무하셨던 분의 글입니다. 오피셜인지는 모르겠으나.. 아니더라도 좋은 해결책인것 같습니다.)

그래서 일단 제가 생각한 방법은 게임의 적극성, 참여율을 수치로 나타내는 것입니다. 그리고 계속해서 낮은 점수를 기록 + 리폿스택이 쌓이면 제재를 받는거죠. 어찌보면 op.gg 같은 통계기록을 만들어 보는거라 할 수 있겠네요. 좀더 디테일한부분을 첨가한.

이자식들.. kda 관리는 잘했다.. 하루종일 팀원들만 방해했기 때문에 !!

롤은 팀원과 협동해서 상대방을 킬하고 오브젝트를 잘챙기고 타워를 미는 게임 입니다. 그래서 상대팀에게 가한 행동들이나, 팀원들에게 도움이 된 행동들이 극단적으로 없다면 낮은점수를 매기고,
미드/바텀(원딜)/탑/정글/서포터 포지션별로 세분화 해서 각 포지션의 역할을 잘 수행 했는지 점수를 매겨 보면 어떨까 생각해 봤습니다.

경기 평점

" 일반적인 경우 "

일단 세분화 해야하는 이유는 각 포지션별로 중요하게 봐야할 변수가 조금씩 다를 수 있기 때문입니다. 예를들어 단순히 KDA(Kill/Death/Assists)에만 초점을 맞출게 아니라

미드/원딜 의 경우 딜량에 초점이 맞춰진 캐리력이 강한 포지션이기 때문에 챔피언에 가한피해량, KILL, CS 등등 에 가중치를 좀더 둬야하고, 정글의 경우 위 변수 뿐만 아니라 얼마나 정글링을 잘 돌았는지, 갱을 갔는지에 대한 변수를 더 봐야합니다 .의 경우 여기에 탱킹력이 추가적인 변수가 될 수 있겠네요. 공통적으로 타워에게 가한피해량 (롤은 타워미는 게임!)도 확인하면 좋겠죠!

또한 서포터의 경우 일단 "일반적인" 라인전을 생각해보면 원딜러를 보조하는 유형의 챔피언이 많습니다. 또 서포터의 경우 CS를 먹는 경우가 거의 없기 때문에 점수를 매길때 이부분에 대한 가중치를 낮추고, 시야점수, assist, cc기나 힐량(스킬이있다면)쪽을 좀 더 보는게 맞지 않을까 생각습니다.

물론 서포터가 딜량이 높다면(딜포터) 잘한거고 다른 포지션이 시야점수, 힐량이 높다면 잘한거죠! 당연히 공통적으로 다 보되 점수를 매길때 어느정도 가중치를 조절해서 점수를 매겨야 된다고 생각했습니다.

하지만... 워낙 롤 트렌드가 다양하고 챔피언도 다양하기 때문에 너무 일반적인 경기만 생각하면 안되려나 생각했습니다. 한때 유행했던 트위치 서포터 같이 바텀에 있기보다 1렙때 은신찍고 탑,미드를 터트리는 메타도 있었구요.

그렇다면 시야점수가 높은 서포터랑 딜량이 높은 서포터중 어떤 서포터를 잘했다고 해야하나.. 둘다 중요하긴 한데 말이죠.

일반적인 경기를 생각하고 서포터의 경우 '시야점수'에 가중치를 높게 준다면 딜량이 높은 서포터유저가 더 잘했음에도 시야점수가 높은 유저보다 점수가 낮아 질 수 도 있어서..너무 한쪽으로 치우치지 않게.. 아주 알잘딱하게.. 조절해야 할 것 같습니다.

이런 애매한 트롤의 경우 해당 유저의 플레이에 점수를 매길때 어떤 기준으로 해야할지.. 생각할게 참 많았습니다. 유저 제재가 걸려있으니까요.

(근데 사실 정말 확실하게 처리하려면 그냥 비디오판독하는게 빠를거에요 ㅋㅋㅋㅋ 시간비용이 좀 들겠지만 최저시급 주고 고용하는게 어떨지..저 하루종일 잘할 수 있는데..🤤 그래도 데이터로 한번 만들어보고 싶었습니다..)

그래서 일단 ⑤번 항목에 대해서 골라본 변수 목록 입니다.

'totalDamageDealtToChampions' ( 챔피언에게 넣은 데미지 ),
'damageDealtToObjectives' 오브젝트(타워/억제기/ 에피몬스터(바론,드래곤) 모든걸 포함)에 넣은데미지
'damageDealtToTurrets' (타워에게 넣은 데미지)
'totalCS', (CS) # 데이터에 정글CS와 미니언CS가 나뉘어져 있더군요 둘을 합해야합니다
'totalGold' (총골드)
'soloKills', (솔로킬 횟수)
'multikills',(멀티킬 횟수)
'kda' (Kda)
'visionScore', (시야점수)
'enemyChampionImmobilizations', (CC기를 몇번 맞췄는지!)
'damageSelfMitigated', (실제 막은 피해량)
'effectiveHealAndShielding', (힐량과 쉴드)
,,,,등등

일단 오늘은 데이터를 불러오고 어떤 변수가 있고 필요한 변수가 뭐가 있을지 정리해봤습니다!! 다음부터는 위 목록들을 하나씩 Streamlit을 통해서 구현해보려고 합니다 !

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

0개의 댓글