아무무의 붕대투척으로 알아보는 라이엇 스킬 툴팁 해석

2023. 2. 3. 14:23React/고민거리들

라이엇에서 기본 제공해주는 API가 여러 가지가 있습니다.

계정 정보나 티어 같은 정보를 GET 할 수 있는 API부터 기본 챔피언 정보를 객체로 받아볼 수 있는 ddragon 까지 존재하는데, 그냥 가져다 써서 파싱하는 거보다 챔피언 툴팁에 curly 변수로 선언되어있는 변수를 치환하는 과정을 설명해보려고 합니다.

 

아무무 Q 스킬로 살펴보는 정보 객체 분석


https://ddragon.leagueoflegends.com/cdn/13.1.1/data/ko_KR/champion/Amumu.json

 

주요깊게 봐야할 것은 툴팁인데, 롤 클라이언트나 전적검색 사이트에서 볼 수 있는 스킬 설명이라고 이해하면 됩니다. api에서 받아볼 수 있는  특정 스텟이나 내부에서 관리되는 상태가 있는 경우에는 커스텀된 태그가 있고 그 안에 Curly bracket으로 변수가 선언되어있습니다.

 

아무무의 Q스킬을 좀 더 자세하게 봐보겠습니다.

 

"아무무가 붕대를 던져 처음 적중한 적에게 붕대를 당겨 다가간 뒤 {{ e2 }}초 동안 <status>기절</status>시키고 <magicDamage>{{ totaldamage }}의 마법 피해</magicDamage>를 입힙니다.<br /><br />이 스킬은 2회까지 충전됩니다.{{ spellmodifierdescriptionappend }}"

 

인게임 툴팁에서는 마크업 태그로 텍스트의 색을 바꿔주는 기능이 구현되어 있습니다.

일단 문제는 컬리 변수에 해당하는 리소스를 어디에서 찾을 수 있느냐가 문제인데, Amumu.spell.effect나 Amumu.spell.effectburn을 통해 접근해볼 수 있습니다.

(모든 챔피언에 대해 정보를 받아볼 수 있는 건 아닙니다. 아트록스나 애니는모든 effect가  0으로 초기화 되어있습니다.)

 

effect: [
  [70, 95, 120, 145, 170],
  [1, 1, 1, 1, 1],
  [1800, 1800, 1800, 1800, 1800],
  [0, 0, 0, 0, 0],
  [0, 0, 0, 0, 0],
  [0, 0, 0, 0, 0],
  [0, 0, 0, 0, 0],
  [0, 0, 0, 0, 0],
  [0, 0, 0, 0, 0],
  [0, 0, 0, 0, 0]
]

 

문제는 effect 속성이 객체가 아니라 배열로 관리가 되고 있습니다.

curly variance의 변수명에 따라 적절한 effect를 매핑해줘야하는데 어떻게 알 수 있을까요? 정답은 공식문서에 있었답니다.

 

Curly 변수 해석

숫자가 있냐 없냐를 기준으로 나뉩니다.

예를 들어 아무무 Q 스킬에 기재된 스턴 지속시간에 해당하는 {{ e2 }} 변수는 2번째 effect속성의 index를 의미합니다.

숫자가 없는 경우에는 공식문서에 따로 정의되진 않았지만, 숫자 속성을 먼저 처리한 다음에 순차적으로 처리하면 맞는 리터럴을 얻을 수 있습니다.

 

툴팁에서 변수와 리터럴 분리하기


Curly bracket 처리

// effect에서 유효한 값만 걸러냅니다. (편의를 위해 0번짼 null로 넣습니다)
const effectiveSkillVariances = [null, ...effectBurn.filter(Boolean)];
// curly bracket을 추출합니다.
const curlyVariance = tooltip.match(/{{ [a-zA-Z0-9]+ }}/g);
const isUsedSkillVarinaces = Array.from({ length: effectiveSkillVariances.length - 1 }, () => false);
//..

 

정규표현식으로 걸러내면 순서대로 넣어지기 때문에 index로 접근하는 걸 기본으로 하지만 {{eN}}처럼 숫자가 들어가 있는 경우를 처리하기 위해 isUsedSkillVariances로 관리합니다.

숫자가 기재된 속성을 접근해서 처리한 경우에는 해당하는 index를 true로 바꾸고, 나머지 curly 변수를 처리할 때 true인 값을 스킵합니다.

 

XML 태그 없애기

적용을 위해 툴팁에서 모든 마크업 bracket을 없앱니다. 이때 curly bracket은 남기고, 나중에 변수랑 매칭시켜 치환시키는 방법으로 접근합니다.

// before: "<toggle>활성화/비활성화:</toggle> 아무무가 울기 시작하여 매초 근처 적에게 <magicDamage>{{ e2 }}+최대 체력의 {{ totalhealthdamagepercent }}에 해당하는 마법 피해</magicDamage>를 입히고 <keywordMajor>저주</keywordMajor>를 초기화합니다.{{ spellmodifierdescriptionappend }}"
// after: 활성화/비활성화: 아무무가 울기 시작하여 매초 근처 적에게 {{ e2 }}+최대 체력의 {{ totalhealthdamagepercent }}에 해당하는 마법 피해를 입히고 저주를 초기화합니다.{{ spellmodifierdescriptionappend }}
const toolTipWithoutBracket = tooltip.replace(/<[/]*[a-zA-Z]+>/g, "");
const curlyMap = new Map();

// curly 변수에서 숫자를 뽑아내서 mapping합니다. ex) Map (1): '{{ e2 }}' : '1'
curlyVariance.forEach((curly) => {
  const hasNumber = /[0-9]+/;
  if (hasNumber.test(curly)) {
    const [index] = curly.match(hasNumber);
    curlyMap.set(curly, effectiveSkillVariances[Number(index)];
    isUsedSkillVarinaces[Number(index)] = true;
  }
});

 

치환을 위한 해시맵을 하나 만든 뒤에, curly에 해당하는 값에 해당하는 리터럴을 매핑해줍니다.

이 과정에서 루프를 두 번 돌아야하는 번거로움이 있습니다. 숫자가 있는 속성을 처리해줘야하기 때문입니다.

그 다음으로는 숫자가 없는 속성을 매핑시킵니다.

 

curlyVariance.forEach((curly) => {
  const hasNumber = /[0-9]+/;
  if (!hasNumber.test(curly)) {
    for (let i = 1; i < isUsedSkillVarinaces.length; i++) {
      if (isUsedVariances[i] === false) {
        curlyMap.set(curly, effectiveSkillVariances[i]);
        isUsedVariance[i] = true;
      }
    }
  }
});

 

[조심하세요]
만약 React를 사용한다면 for문을 통해서 배열을 mutate 시킬 때 두 번 변경될 수 있습니다.(React.StrictMode에 의해)
프로덕션 상황에서는 자동으로 StrictMode가 사라지기 때문에 한 번 실행되지만, 미연에 방지하기 위해 isUsedVariance를 let으로 관리하고 상태로 관리해주세요.

 

이제 curlyMap에 모든 curly 변수에 해당하는 리터럴이 매칭되었습니다. 최종적으로 툴팁의 컬리 변수를 치환하겠습니다.

 

let finalTooltip = toolTipWithoutBracket;
for (const [key, val] of curlyMap) {
  finalTooltip = finalTooltip.replace(key, val);
}

// 활성화/비활성화: 아무무가 울기 시작하여 매초 근처 적에게 12/16/20/24/28+최대 체력의 0.5/0.625/0.75/0.875/1에 해당하는 마법 피해를 입히고 저주를 초기화합니다.0

 

좀더 생각해보면 좋을 것들


1. 공격력 퍼센트나 주문력 퍼센트의 반영

spell 객체 외부의 stats 객체에서 공격력(attack), 주문력(spell)에 접근할 수 있고, 성장공격력, 성장주문력에도 접근할 수 있다. 

내부의 leveltip 변수에서 해당 스킬에 영향을 주는 스탯을 접근할 수 있고 그 수식을 확인할 수 있다.

UI에서 레벨이나 아이템을 반영할 수 있다면 해당 요소로 더 정확한 스킬 데미지를 계산할 수 있다.

 

2. 도대체 왜이렇게 만든걸까?

따로 서버에서 live 값을 가져오는게 아니라, json 객체로부터 정적인 값을 받다보니 자연스럽게 연산을 받아보는 서버에서 수행하도록 구성한 것 같다. 다만, 아예 effect 배열을 안내려주는 챔피언들도 많기 때문에 좀 더 의도를 파악해볼 필요는 있어보인다.