알림 : 블리자드 API와 LUA에 대한 사전 지식이 있다는 가정하에 쓰인 글이니 관심이 없으신 분은 뒤로가기를 눌러주세요.
텍스트로만 쓰여있으나 모바일 환경 열람을 권하지 않습니다.


LUA 코딩을 이용한 커스텀 기능을 구현하고자하는 사용자분들을 위한 기능 소개글입니다.
wowace.com의 기술지원 문서를 번역한 #2번 글과 내용이 유사하나 그것에는 부족한 부분 위주로 썼습니다.

사실 WeakAuras에 LUA 코드를 사용해서 원하는 기능을 구현할만한 유저라면 그냥 별도로 애드온을 만들어 사용할 실력이 되는 사람이 많을 것인데도 Blood Legion 운무의 소생 안개 추적기나 Method 야드의 도트 비율 표시기 등 WeakAuras 기반으로 작성되어 Import String으로 공유되는 모듈이 좀 있습니다.
WeakAuras를 이용해 만들면 디스플레이 세팅이나 이벤트 처리기 등등에서 잡일이 줄어드는 대신 에뮬레이터로 구동하는 것처럼 한다리 건너서 동작하기 때문에 기능이 제한되거나 불필요하게 무거워지는 문제가 발생할 수 있습니다. 판단하에 사용하세요.


목차
WA의 트리거
  트리거 - 언트리거 패러다임
  커스텀 트리거와 언트리거
  다이내믹 인포

트리거 외부의 커스텀 기능들
  커스텀 텍스트
  커스텀 액션
  커스텀 애니메이션

커스텀 이벤트


WA의 트리거
Trigger(한국어 버전에는 '조건'으로 번역되었습니다)는 표시기가 화면에 등장할 조건을 설정하는 부분입니다. 트리거는 기능적으로 조건판단 알고리즘으로 OnUpdate 혹은 등록된 이벤트가 발생할 때 판별을 수행하고 결과가 true라면 표시기를 화면에 출력합니다.
WA에는 오라, 이벤트, 상태 세 가지 유형의 프리셋 트리거에 대해 설정 UI가 갖추어져있고 사용자가 직접 LUA 코드로 설정하는 custom(한국어 버전 '개인추가') 유형을 선택할 수도 있습니다.
이 글은 커스텀 트리거를 다룹니다.

커스텀 트리거를 선택하면 아래에 '이벤트 유형' 풀다운 메뉴에서 다시 event와 status 유형으로 나뉘는데 사실 커스텀 트리거에서 event 유형과 status 유형은 그리 차이가 없고 status를 선택하면 OnUpdate로 동작하게 설정하는 메뉴가 있다는 점 정도가 차이입니다.


--------------------------------------------------------------------------------------------------------

트리거 - 언트리거 패러다임

커스텀 트리거를 입력하는 편집창에는 true / false를 반환하는 무명 함수를 function - end의 wrapper 안에 써넣습니다. 트리거 판별을 시도하면 그 함수를 실행하는데 true가 반환되면 표시기를 화면에 나타내고 false나 nil이라면 아무 일도 하지 않습니다. 트리거 코드 내에 return 앞에 다른 동작을 하는 코드가 있다면 그것은 물론 실행됩니다.
'아무 일도 하지 않습니다'라는 표현은 트리거에서 false가 나온다고 해도 화면에 나타난 표시기를 사라지게 하지 않는다는 뜻이기도 합니다. 커스텀 트리거는 단순히 나타내는 조건에만 관여하며 이미 나와있는 표시기를 숨기는 기능은 untrigger에 분리되어 있습니다.

커스텀 트리거를 이벤트 유형으로 사용할 때 Hide(한국어 버전 '숨김') 옵션은 timed나 커스텀을 선택할 수 있습니다. Timed 설정일때는 표시된 순간부터 지정된 시간동안 표시되다가 사라집니다. 숨김 옵션에서 커스텀을 선택하면 트리거가 표시할지 말지 판별하는 것과 반대로 사라지게할지 말지 판별하는 Custom Untrigger 코드를 입력하는 입력창이 생깁니다.


언트리거 판별을 시도하는 이벤트는 트리거 판별에 등록한 이벤트와 동일합니다. 등록된 이벤트가 발생하면 트리거 판별을 시도하고 결과가 true라면 언트리거는 무시됩니다. 트리거에서 false가 나오면 그제서야 언트리거 판별을 시도하고 언트리거가 true를 반환하면 표시기를 화면에서 숨기게 되어있습니다.

따라서 체력이 60% 이하가 되면 표시되었다가 30% 이하가 되면 사라지는 표시기를 만들려고 트리거에 '체력 <= 60%', 언트리거에 '체력 <= 30%'와 같이 설정하면 30% 이하가 된다해도 트리거가 true이기 때문에 언트리거는 무시되어 표시기가 사라지지 않습니다.

반대로 트리거와 언트리거가 분리되어있기 때문에 중립 영역 - grey area - 을 설정할 수 있습니다. 체력이 30% 이하라면 경보를 표시하는데 이게 50% 이상으로 복구되어야 사라지게 하고싶다면 트리거에 '체력 <= 30%', 언트리거에 '체력 >= 50%'를 설정하면 됩니다. 체력이 위에서 내려와서 40%가 된다면 표시되지 않지만 30% 아래에서 표시된 상태로 40%까지 복구되면 여전히 표시된 상태가 됩니다.


커스텀 트리거와 언트리거 함수

트리거 유형을 커스텀으로 설정하면 이벤트/상태 유형 중 세부 선택을 하게됩니다.

이벤트 유형을 선택하면 어떤 이벤트가 발생할 때 그 트리거를 판별할지 이벤트를 등록하는 입력창이 생깁니다.

상태 유형의 커스텀 트리거는 판별을 시도할 상황을 OnUpdate, 혹은 이벤트 중에서 선택할 수 있습니다. 상태 유형일 때는 언트리거로 timed를 선택할 수 없습니다.

주의: 버그인데 상태 유형에서 체크... 옵션에 OnUpdate를 선택해두면 이벤트 유형으로 돌아가도 이벤트를 등록하는 창이 없어집니다. 다시 상태로 가서 이벤트 유형으로 바꿔야합니다.
이런 식으로 A,B 중에서 선택하는 메뉴에서 B쪽에서 하부 설정을 바꾸었다가 다시 A로 돌아와보면 이쪽에서 선택할 수 있는 선택지가 제한되는 버그가 조금 더 있습니다.


트리거 판별 이벤트 창에는 comma로 구분해서 둘 이상의 이벤트를 등록할 수 있습니다.

또한 화면에 등장한 표시기가 어떤 조건에서 사라질지를 Timed, 혹은 커스텀 중에서 선택하는 풀다운 메뉴가 생기는데 timed라면 표시된 후 지정된 시간이 지나면 사라지며 커스텀을 고르면 트리거가 등장할 조건을 지정하듯 untrigger(한국어 버전 '숨김 조건')에 표시기가 사라질 조건을 LUA코드로 설정하게 됩니다.


커스텀 트리거는 function - end 구조의 무명 함수이고 발동 조건이 이벤트라면 이벤트의 인수를 제공받습니다. 인수를 사용하지 않을거라면 그냥 function() - end로 짜도 되지만 function(event, ...) - end를 사용하면 판별을 발동시킨 이벤트의 인수를 함수 내에서 사용할 수 있습니다.

커스텀 언트리거 함수 역시 function - end 구조로 만들되 표시기를 사라지게 할 조건일 때 true를 반환하게 구성하시면 됩니다.
언트리거 조건을 판별할 때 트리거와 무관한 이벤트를 사용한다면 위쪽의 이벤트 입력창에 그것도 잊지 말고 추가한 뒤 트리거와 언트리거 함수 내에서 event에 따라 분류해서 사용합니다.
다시 말씀드리지만 트리거가 true이면 언트리거는 무시됩니다. 각각의 조건을 적절히 설정하셔야합니다.



Dynamic Information

WA의 트리거는 지속 시간, 아이콘, 이름, 중첩 시간의 최대 네 가지 정보를 생성해서 표시기에 전달합니다. 프리셋 트리거는 트리거 종류에 따라 적당한 다이내믹 인포를 생성합니다. 예를 들어 아이템이나 주문에 관련된 프리셋 트리거라면 해당 주문/아이템의 아이콘이 아이콘 정보로 생성되고 오라 트리거라면 오라의 지속 시간에 관한 정보가 지속 시간 정보로 생성됩니다.
커스텀 트리거를 사용할 때는 다이내믹 인포를 LUA 코드로 직접 생성해야합니다. 커스텀 트리거의 다이내믹 인포에 관련된 함수는 이벤트에서 인수를 전달받지 않습니다.
각각의 커스텀 다이내믹 인포를 생성하는 함수는 트리거 판별을 수행해서 true일 때 실행됩니다.


지속시간

function() - end wrapper 안에 최대 네 개의 값을 반환하는 함수를 넣습니다.
지속시간 함수의 반환값은 다음과 같고 뒤의 두 개는 Boolean 값입니다.
return duration, expirationTime [, static [, inverse]]


지속시간 정보는 타이머 / 상태바 두 가지로 쓸 수 있는데 세 번째 반환값이 false나 nil이면 타이머, true이면 상태바가 됩니다.

진행바나 아이콘에서 타이머 형태로 지속시간 정보를 사용할 때는 duration이 초 단위로 타이머의 전체 시간이고 expirationTime은 GetTime()의 포맷으로 쓰인 완료 시각입니다.

예를 들어 지금 시작하는 15초짜리 카운트다운 타이머를 생성하려면
return 15, GetTime()+15
와 같이 사용하면 됩니다.

return 20, GetTime()+10
으로 타이머를 생성하면 전체 길이는 20초이고 10초 후에 종료되는 타이머가 생성되니 시작할 때부터 타이머가 절반만 차있는 상태로 시작할 것이고요.

지속시간 정보는 트리거가 판별 절차에서 true를 뱉을 때 전체시간과 만료시간을 생성해서 디스플레이에 한 번 전달하고 이후로는 디스플레이쪽에서 미리 받아둔 값을 가지고 OnUpdate로 바가 차있는 비율을 계산해서 그림을 그립니다. 바가 차있는 비율은 남은 시간 / 전체 시간이니까 ( expirationTime - GetTime() ) / duration 이 되겠지요.

static이 true이면 앞의 두 반환값은 각각 현재량, 전체량이 됩니다. 이 경우 바가 차있는 비율은 arg1 / arg2 가 됩니다.
예를 들어 진행바 유형의 디스플레이를 마나바로 사용하고 싶으면 지속시간 정보에서
return UnitPower("player"), UnitPowerMax("player"), true
와 같이 반환하면 되겠습니다.

디스플레이 쪽에서 %c, %n과 같은 값을 넣어서 텍스트를 표시하는 창이 있는데 %p와 %t가 지속시간 정보와 관련된 값입니다. static이 false라면 %p가 '남은 시간'을, %t가 '전체 시간'을 의미하며 static이 true라면 %p는 '현재량', %t는 '전체량'을 숫자로 나타냅니다.


이름

디스플레이 제목에 해당하는 문자열을 반환하는 함수를 넣습니다. 디스플레이쪽에서는 %n에 해당합니다.
프리셋 트리거라면 오라 이름 등이 자동으로 설정되기도 하고 이게 없을 때 %n을 넣으면 그냥 표시기 제목이 나옵니다.


아이콘

진행바나 아이콘에 아이콘을 표시하게 할 때, 디스플레이쪽에서 '자동 아이콘'을 체크하면 사용할 텍스쳐 경로를 반환하는 함수입니다.

return "Interface\Icons\Ability_Hunter_RunningShot"
이렇게 넣으시거나

local _,_,icon = GetSpellInfo("서리한의 격노")
return icon
이렇게 반환해도 됩니다.

아이콘 텍스쳐 경로를 검색하시려면 아이콘 표시기 아무거나 만드시고 디스플레이 탭에서 자동 아이콘을 꺼서 선택창을 열어 주문명을 검색하시면 됩니다.
'속사'를 검색해서 나온 아이콘에 마우스를 대보시면 'Ability_Hunter_RunningShot'이라고 파일명이 나옵니다.


중첩

디스플레이쪽에서 %s를 고르면 표시되는 정보를 숫자로 반환하는 함수를 넣습니다. 프리셋 트리거라면 오라 중첩 회수나 아이템 갯수에 해당합니다.

local _,_,_,count = UnitDebuff("player", spell)
return count
이런 식으로 쓰면 됩니다.


이름 정보나 중첩 정보는 그냥 디스플레이에서 텍스트 표시할 때 쓰이는 기능이기 때문에 꼭 이름이나 중첩 회수를 넣을 필요는 없습니다. 표시기에 내 대상 이름을 넣고 싶으면 이름 정보에서
return GetUnitName("target")
해버리셔도 됩니다.

따로 텍스트 표시기 추가하지 않고 내 마나 백분율을 보고 싶으면 중첩 정보에
return floor(UnitPower("player")/UnitPowerMax("player")*100)
이렇게 반환하게 하고 디스플레이 쪽에 %s%%로 써서 '백분율%' 포맷으로 출력할 수도 있습니다.




트리거 외부의 커스텀 기능들
커스텀 텍스트

디스플레이 쪽에서 텍스트를 표시하는 창에 %c를 넣으면 커스텀 텍스트 함수를 넣는 편집창이 생깁니다. 표시할 문자열을 반환하는 함수를 넣으면 되며 인수는 주어지지 않습니다.
function() - end wrapper 안에 써야합니다.



커스텀 액션

표시기가 등장하거나 사라질 때 부수적으로 수행할 동작을 Actions 탭에서 설정할 수 있습니다.
채팅 메시지, 소리 재생, Button Glow의 프리셋 액션이나 커스텀 액션을 설정할 수 있으며(중복 가능) 커스텀 액션을 켜면 역시 텍스트 편집창이 생깁니다.

커스텀 액션 편집창에는 function() - end 함수가 아니라 그냥 수행할 명령을 넣으면 됩니다.

내가 해깃을 쓰면서 공대에 DBM 타이머로 알리고 싶다면 해깃 시전시 표시되는 표시기를 아무렇게나 만들고 액션 탭의 보여짐 커스텀 액션 편집창에 다음처럼 쓰면 DBM 타이머가 생성됩니다.

if IsAdded("DBM-Core") then
    local caster = GetUnitName("player")
    DBM:CreatePizzaTimer(10, "Skull :"..caster, true)
end



커스텀 애니메이션

애니메이션 탭에서 유형을 커스텀으로 설정하면 Fade, Slide, 줌, 색상 등의 애니메이션을 설정할 수 있는데 각각의 하위 메뉴에서 다시 커스텀을 선택하면 적당한 인수가 주어지는 함수의 입력창이 생깁니다.
애니메이션 효과의 진행 시간을 먼저 설정하는데 초 단위 혹은 백분율로 설정합니다. 후자는 지속시간 정보가 있어야되고 10초짜리 타이머에서 백분율 30으로 설정하면 3초짜리 애니메이션이 되는 셈입니다.

애니메이션은 시작, mail, 완료 세 부분에 넣을 수 있는데 시작쪽에 있다면 등장할 때부터 3초간 진행하는 애니메이션이고 main에 설정하면 진행되는 동안 3초 단위로 반복되는 애니메이션입니다.

함수 편집창에는 기본 인수와 기본 기능이 예로 주어져있는데 예를 들어 색상 애니메이션의 편집창을 열면 이미 함수가 들어가있습니다.

function(progress, r1, g1, b1, a1, r2, g2, b2, a2)
    return r1 + (progress * (r2 - r1)), g1 + (progress * (g2 - g1)), b1 + (progress * (b2 - b1)), a1 + (progress * (a2 - a1))
end

r1, g1, b1은 디스플레이 탭에서 설정한 색상이며  r2, g2, b2는 애니메이션 탭에서 설정하는 색상입니다.

가장 중요하게 보아야하는 것이 progress인데 이것은 애니메이션의 진척에 따라 전체 시간을 0부터 1로 변환한 값입니다. 이게 OnUpdate로 바뀌며 애니메이션이 돌아가는데 3초짜리 애니메이션으로 10초짜리 타이머 메인에 넣으면 0~3초간 progress가 0~1로 증가하고 다시 3~6초 사이에 0~1로 증가하며 반복되는 식입니다.

기본으로 들어있는 코드는 그냥 색상1에서 색상2까지 중간색을 진행하는 색을 반환하게 되어있습니다. progress가 0이면 그냥 r1, g1, b1이 반환되고 progress가 1이면 r2, g2, b2가 나옵니다. progress가 0.5일 때는 평균값이 반환됩니다.

progress 무시하고 다른 조건에 따라서 원하는 색을 표시하거나 디스플레이 위치를 옮기는 형태로 사용할 수도 있습니다. 소개글 #3에 시간차 총량 표시기에 그런 형태로 사용되었습니다.
계속 표시되는 디스플레이에 커스텀 애니메이션을 반복해서 사용하게 만들면 메모리 점유량이 계속 증가하는 문제가 있으니 주의를 요합니다.



커스텀 이벤트
WA에는 WoW API 외의 이벤트를 발생시키는 함수가 포함되어 있습니다. 프리셋 트리거로 주문 쿨다운 추적할 때처럼 블리자드 API로 구현하기 까다로운 기능을 사용할 때 내부적으로 사용하기도 하지만 커스텀 트리거를 사용할 때 액션쪽에서 이 함수로 이벤트를 발생시켜서 다른 표시기와 의사소통하는 용도로 사용할 수도 있습니다.

WeakAuras.ScanEvents("MY_CUSTOM_EVENT", arg1, arg2, ...)
위 명령을 넣으면 지정된 이름의 이벤트가 인수를 포함해서 발생합니다. 이렇게 발생한 이벤트는 커스텀 트리거의 판별 조건이 되는 이벤트로 사용할 수 있습니다.


--------------------------------------------------------------------------------------------------------

커스텀 이벤트를 Hidden Display와 묶어 쓰는 트릭을 간단하게 소개하겠습니다.

내부쿨이 있는 발동 효과를 추적하려고 한다 칩시다. 사제 환희는 보막이 깨지면 마나를 돌려받는 기능인데 이게 12초 내부쿨이 있습니다. 환희가 발동될 수 있는 상태일 때 아이콘을 표시하는 표시기를 만들고 싶습니다.
환희가 발동되면 전투로그에 SPELL_ENERGIZE가 발생하니 이쪽은 비교적 쉽게 추적할 수 있습니다. 그런데 우리가 원하는 표시기는 이로부터 12초 후에 표시되어야합니다.

이를 위해서는 두 개의 표시기를 사용하는데 하나는(A) 실제 아이콘을 표시하고 다른 하나는(B) 실제 표시되지 않으나 12초 내부쿨을 계산하는 역할을 맡습니다.

B 역할을 하는 텍스트 표시기를 하나 만들고 디스플레이 쪽에서는 표시돼도 아무 것도 출력하지 않게 설정합니다. 텍스트 창에 스페이스를 하나 넣으면 됩니다.
트리거 탭에 가서 이벤트 - 전투로그 메시지 - 주문 - Energize, 주문 이름 '환희'를 넣어서 환희 발동시 표시하게 하고 숨김 조건은 12초로 설정합니다.

이제 이 표시기는 환희 내부쿨일 때 표시되고 내부쿨이 종료되면 꺼지는데 실제로는 표시돼도 화면에 아무 것도 나오지 않습니다.

액션 탭으로 가서 보여짐에 커스텀 액션을 다음처럼 넣습니다.
WeakAuras.ScanEvents("RAPTURE_ICD", true)

숨겨짐에는
WeakAuras.ScanEvents("RAPTURE_ICD", nil)

환희 내부쿨이 시작되거나 끝나면 RAPTURE_ICD라는 이벤트가 발생하며 인수로 시작인지 종료인지 알 수 있게 됩니다.

실제로 표시될 아이콘 표시기 A를 만들고 커스텀 이벤트 트리거를 선택, 이벤트 목록에 RAPTURE_ICD를 등록합니다.

커스텀 트리거는
function(event, status)
   return not status
end

커스텀 언트리거는
function(event, status)
   return status
end

환희 내부쿨이 시작되면 인수 true로 이벤트가 발생하니 언트리거가 작동해서 표시기를 숨기고 내부쿨이 끝나면 인수 nil로 발생하니 트리거가 작동해서 표시기를 나타나게 합니다. 제대로 쓰려면 환희 처음 발동 전에는 표시되지 않으니 PLAYER_ENTERING_WORLD같은 이벤트도 추가로 써서 시작할 때 켜져있게 해야합니다.


--------------------------------------------------------------------------------------------------------

애드온 코드에서 WA가 내부적으로 사용하는 추가 이벤트를 찾아보면 다음과 같습니다.
WeakAuras.ScanEvents("SWING_TIMER_END");
WeakAuras.ScanEvents("RUNE_COOLDOWN_READY", id);
WeakAuras.ScanEvents("SPELL_COOLDOWN_READY", id, nil);
WeakAuras.ScanEvents("ITEM_COOLDOWN_READY", id);
WeakAuras.ScanEvents("RUNE_COOLDOWN_STARTED", id);
WeakAuras.ScanEvents("RUNE_COOLDOWN_CHANGED", id);
WeakAuras.ScanEvents("SPELL_COOLDOWN_STARTED", id, match);
WeakAuras.ScanEvents("SPELL_COOLDOWN_CHANGED", id, match);
WeakAuras.ScanEvents("ITEM_COOLDOWN_STARTED", id);
WeakAuras.ScanEvents("ITEM_COOLDOWN_CHANGED", id);
WeakAuras.ScanEvents("COOLDOWN_REMAINING_CHECK");
WeakAuras.ScanEvents("COMBAT_LOG_EVENT_UNFILTERED_CUSTOM", arg1, arg2, ...);
WeakAuras.ScanEvents("FRAME_UPDATE");
WeakAuras.ScanEvents("MAINHAND_TENCH_UPDATE");
WeakAuras.ScanEvents("OFFHAND_TENCH_UPDATE");
WeakAuras.ScanEvents("COOLDOWN_REMAINING_CHECK");
WeakAuras.ScanEvents("MOUNTED_UPDATE");

SPELL_COOLDOWN_READY같은 이벤트는 블리자드 기본 API로 제공하지 않는데 있으면 참 편한 기능입니다.
다만 이런 이벤트들은 해당 기능에 관련된 프리셋 트리거가 사용중이어야 활성화되니 사용이 제한적입니다.



연재 목록:
WeakAuras 소개 - #4 심화 사용자를 위한 기능 소개 (이 글)