You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
861 lines
28 KiB
861 lines
28 KiB
local U, _, T = {}, ...
|
|
local EV = T.Evie
|
|
local L = newproxy(true) do
|
|
local LT = T.LT or {}
|
|
getmetatable(L).__call = function(_, k)
|
|
return LT[k] or k
|
|
end
|
|
end
|
|
T.Util, T.L, T.LT = U, L, nil
|
|
|
|
local overdesc = {
|
|
[ 25]={L"Inflicts {} damage to all enemies in melee, and increases own damage dealt by 20% for three turns.", "damageATK"},
|
|
[ 52]={L"Inflicts {} damage to all enemies at range.", "damageATK"},
|
|
[ 85]=L"Reduces the damage taken by the closest ally by 5000% for two turns.",
|
|
[107]={L"Debuffs all enemies, dealing {1} damage this turn and during each of the next three turns. Additionally, increases all damage taken by the nearest enemy by {2} for three turns.", "damageATK", "plusDamageTakenATK"},
|
|
[121]={L"Reduces all enemies' damage dealt by {}% during the next turn.", "modDamageDealt"},
|
|
[125]={L"Inflicts {} damage to a random enemy.", "damageATK"},
|
|
[194]={L"Buffs the closest ally, increasing all damage dealt by {1} and reducing all damage taken by {2}% for two turns. Inflicts {3} damage to self.", "plusDamageDealtATK", "modDamageTaken", "damageATK"},
|
|
[227]={L"Every other turn, a random enemy is attacked for {}% of their maximum health.", "damagePerc"},
|
|
[242]={L"Heals the closest ally for {1}, and increases all damage taken by the ally by {2}% for two turns.", "healATK", "modDamageTaken"},
|
|
[251]={L"Reduces all enemies' damage dealt by {}% for two turns.", "modDamageDealt"},
|
|
[223]={L"Debuffs all enemies, dealing {1} damage during each of the next {2} turns. Multiple applications of this effect overlap.", "eDamage", "duration1"},
|
|
[300]={L"Debuffs all enemies, dealing {1} damage during each of the next {2} turns. Multiple applications of this effect overlap.", "eDamage", "duration1"},
|
|
[301]={L"Every other turn, a random enemy is attacked for {}% of their maximum health.", "damagePerc"},
|
|
}
|
|
local CLOCK_ICON do
|
|
local ai = C_Texture.GetAtlasInfo("auctionhouse-icon-clock")
|
|
CLOCK_ICON = ("|T%s:0:0:0:-0.5:%d:%d:%d:%d:%d:%d:%%d:%%d:%%d|t "):format(ai.file, 2048, 2048, ai.leftTexCoord*2048, ai.rightTexCoord*2048, ai.topTexCoord*2048, ai.bottomTexCoord*2048)
|
|
end
|
|
local SPC = {} do
|
|
local m = {}
|
|
function m:__index(k)
|
|
if type(VP_SPC) == "table" then
|
|
return VP_SPC[k]
|
|
end
|
|
end
|
|
function m:__newindex(k,v)
|
|
if type(VP_SPC) ~= "table" then
|
|
VP_SPC = {}
|
|
end
|
|
VP_SPC[k] = v
|
|
end
|
|
setmetatable(SPC, m)
|
|
end
|
|
local MS_TIER = {} do
|
|
for i, x in ("01027a152637482716493817394a2829183a4b4c2a3b19"):gmatch("()(..)") do
|
|
MS_TIER[tonumber(x,16)] = (i+1)/2
|
|
end
|
|
end
|
|
|
|
local GetMaskBoard do
|
|
local b, u, om = {}, {curHP=1}
|
|
function GetMaskBoard(bm)
|
|
if om == bm then
|
|
return b
|
|
end
|
|
om = bm
|
|
for i=0,12 do
|
|
b[i] = bm % 2^(i+1) >= 2^i and u or nil
|
|
end
|
|
return b
|
|
end
|
|
U.GetMaskBoard = GetMaskBoard
|
|
end
|
|
local function GetTargetMask(si, casterBoardIndex, boardMask)
|
|
if not (si and casterBoardIndex) then
|
|
return 0
|
|
end
|
|
local board, tm, isForked = GetMaskBoard(boardMask), 0, false
|
|
for i=si.type and 0 or 1,#si do
|
|
local ei = si[i] or si
|
|
local eit = ei and ei.target
|
|
if eit then
|
|
isForked = isForked or T.VSim.forkTargetMap[eit]
|
|
local ta = T.VSim.GetTargets(casterBoardIndex, T.VSim.forkTargetMap[eit] or eit, board)
|
|
for i=1,ta and #ta or 0 do
|
|
tm = bit.bor(tm, 2^ta[i])
|
|
end
|
|
end
|
|
end
|
|
return tm + (isForked and tm > 0 and 2^18 or 0)
|
|
end
|
|
local GetBlipWidth do
|
|
local blipMetric = UIParent:CreateFontString(nil, "BACKGROUND", "GameTooltipText")
|
|
blipMetric:SetPoint("TOPLEFT")
|
|
blipMetric:SetText("|TInterface/Minimap/PartyRaidBlipsV2:8:8|t")
|
|
blipMetric:Hide()
|
|
function GetBlipWidth()
|
|
local _, sh = GetPhysicalScreenSize()
|
|
blipMetric:SetText("|TInterface/Minimap/PartyRaidBlipsV2:8:8|t|TInterface/Minimap/PartyRaidBlipsV2:8:8|t")
|
|
local w2 = blipMetric:GetStringWidth()
|
|
blipMetric:SetText("|TInterface/Minimap/PartyRaidBlipsV2:8:8|t")
|
|
local w1 = blipMetric:GetStringWidth()
|
|
return (w2-w1+select(2,blipMetric:GetFont())*(sh*5/9000-0.7))/0.64*UIParent:GetScale()
|
|
end
|
|
end
|
|
local function FormatSpellPulse(si)
|
|
local t = si.type
|
|
local bm = "|TInterface/Minimap/PartyRaidBlipsV2:8:8:0:0:64:32:0:20:0:20:%s|t"
|
|
local on, off = bm:format("255:120:0"), bm:format("80:80:80")
|
|
if t == "heal" or t == "nuke" or t == "nukem" or (si.duration and si.duration <= 1 and si.echo) then
|
|
if si.echo then
|
|
return on .. (off):rep(si.echo-1) .. on
|
|
end
|
|
elseif (t == "heal" or t == "nuke") and (si.duration and si.duration > 1) then
|
|
return on .. (off):rep(si.duration-1)
|
|
elseif t == "aura" then
|
|
local r, p = (si.noFirstTick or si.period) and (si.damageATK1 and bm:format("255:220:0") or off) or on, si.period or 1
|
|
for i=2, si.duration do
|
|
r = r .. (i % p == 0 and on or off)
|
|
end
|
|
return r
|
|
end
|
|
end
|
|
local FormatAbilityDescriptionOverride do
|
|
local overdescUnscaledKeys = {damagePerc=1, modDamageDealt=1, modDamageTaken=1}
|
|
local function getSpellData(si, vk)
|
|
local vv = si and si[vk]
|
|
for i=1,si and not vv and #si or 0 do
|
|
vv = vv or si[i][vk]
|
|
end
|
|
return vv
|
|
end
|
|
local function getSpellValue(si, vk, atk, ms)
|
|
if vk == "eDamage" and ms and si.cATKb and si.cATKa and si.damageATK then
|
|
return math.floor((si.cATKa+si.cATKb*ms)*si.damageATK/100)
|
|
elseif vk == "duration1" then
|
|
local vv = getSpellData(si, "duration")
|
|
return vv and (vv - 1)
|
|
end
|
|
local vv = getSpellData(si, vk)
|
|
if vv then
|
|
return overdescUnscaledKeys[vk] and (vv < 0 and -vv or vv) or atk and math.floor(vv*(atk or -1)/100)
|
|
end
|
|
end
|
|
local repl = {}
|
|
local function getReplacement(k)
|
|
return repl[k ~= "" and (k+0) or 1]
|
|
end
|
|
function FormatAbilityDescriptionOverride(si, od, atk, ms)
|
|
for i=2, #od do
|
|
local rv = getSpellValue(si, od[i], atk, ms) or "??"
|
|
repl[i-1] = rv
|
|
end
|
|
for i=#repl, #od, -1 do
|
|
repl[i] = nil
|
|
end
|
|
return (od[1]:gsub("{(%d*)}", getReplacement))
|
|
end
|
|
end
|
|
|
|
do -- Tentative Groups
|
|
local groups, followerMissionID = {}, {}
|
|
local autoTroops = {["0xFFFFFFFFFFFFFFFF"]=1, ["0xFFFFFFFFFFFFFFFE"]=1}
|
|
local healthyCompanions = {}
|
|
function EV:GARRISON_MISSION_NPC_CLOSED()
|
|
groups, followerMissionID = {}, {}
|
|
end
|
|
function EV:GARRISON_MISSION_STARTED(_, mid)
|
|
local g = groups[mid]
|
|
if g then
|
|
for i=0,4 do
|
|
followerMissionID[g[i] or 0] = nil
|
|
end
|
|
groups[mid], followerMissionID[0] = nil
|
|
EV("I_TENTATIVE_GROUPS_CHANGED")
|
|
end
|
|
end
|
|
local function GetFollowerInfo(fid)
|
|
local fi = C_Garrison.GetFollowerInfo(fid)
|
|
fi.autoCombatSpells = C_Garrison.GetFollowerAutoCombatSpells(fid, fi.level)
|
|
fi.autoCombatantStats = C_Garrison.GetFollowerAutoCombatStats(fid)
|
|
return fi
|
|
end
|
|
local function StopBoardAnimations(board)
|
|
local as, af = board.socketsByBoardIndex, board.framesByBoardIndex
|
|
for i=0, 12 do
|
|
local f, f2 = as[i], af[i]
|
|
f.EnemyTargetingIndicatorFrame:Stop()
|
|
f2.EnemyTargetingIndicatorFrame:Stop()
|
|
if f.FriendlyTargetingIndicatorFrame then
|
|
f.FriendlyTargetingIndicatorFrame:Stop()
|
|
end
|
|
for _, v in pairs(f.AuraContainer) do
|
|
if type(v) == "table" and v.FadeIn then
|
|
v.FadeIn:Stop()
|
|
end
|
|
end
|
|
end
|
|
end
|
|
function U.ShowMission(mid, listFrame)
|
|
local mi, mi2, g = C_Garrison.GetMissionDeploymentInfo(mid), C_Garrison.GetBasicMissionInfo(mid), groups[mid]
|
|
if mi and mi2 then
|
|
for k,v in pairs(mi2) do
|
|
mi[k] = mi[k] or v
|
|
end
|
|
mi.missionID = mid
|
|
mi.encounterIconInfo = C_Garrison.GetMissionEncounterIconInfo(mid)
|
|
PlaySound(SOUNDKIT.UI_GARRISON_COMMAND_TABLE_SELECT_MISSION)
|
|
for i=0, g and 4 or -1 do
|
|
followerMissionID[g[i] or 0] = nil
|
|
end
|
|
groups[mid], followerMissionID[0] = nil
|
|
local MP = CovenantMissionFrame.MissionTab.MissionPage
|
|
MP:Show()
|
|
CovenantMissionFrame:ShowMission(mi)
|
|
listFrame:Hide()
|
|
for i=0, g and 4 or -1 do
|
|
local f = g[i]
|
|
if f then
|
|
CovenantMissionFrame:AssignFollowerToMission(MP.Board.framesByBoardIndex[i], GetFollowerInfo(f))
|
|
end
|
|
end
|
|
StopBoardAnimations(CovenantMissionFrame.MissionTab.MissionPage.Board)
|
|
end
|
|
end
|
|
function U.StoreMissionGroup(mid, gt, disbandGroups)
|
|
if gt and next(gt) ~= nil then
|
|
local gn = {}
|
|
for k, v in pairs(gt) do
|
|
U.ReleaseTentativeFollower(v, disbandGroups, true)
|
|
gn[k] = v
|
|
if not autoTroops[v] then
|
|
followerMissionID[v] = mid
|
|
healthyCompanions[v] = nil
|
|
end
|
|
end
|
|
groups[mid] = gn
|
|
EV("I_TENTATIVE_GROUPS_CHANGED")
|
|
elseif gt == nil and groups[mid] then
|
|
for _, v in pairs(groups[mid]) do
|
|
followerMissionID[v] = nil
|
|
end
|
|
groups[mid] = nil
|
|
EV("I_TENTATIVE_GROUPS_CHANGED")
|
|
end
|
|
end
|
|
function U.ReleaseTentativeFollower(fid, disbandGroup, doNotNotify)
|
|
local mid = followerMissionID[fid]
|
|
local g = groups[mid]
|
|
if not g then
|
|
return
|
|
end
|
|
for k,v in pairs(g) do
|
|
if disbandGroup or v == fid then
|
|
g[k], followerMissionID[v] = nil
|
|
end
|
|
end
|
|
if next(g) == nil then
|
|
groups[mid] = nil
|
|
end
|
|
if g and not doNotNotify then
|
|
EV("I_TENTATIVE_GROUPS_CHANGED")
|
|
end
|
|
end
|
|
function U.ReleaseTentativeFollowerForMission(fid, mid, disbandGroup, doNotNotify)
|
|
local omid = followerMissionID[fid]
|
|
if omid and omid ~= mid then
|
|
U.ReleaseTentativeFollower(fid, disbandGroup, doNotNotify)
|
|
end
|
|
end
|
|
function U.MissionHasTentativeGroup(mid)
|
|
return groups[mid] ~= nil
|
|
end
|
|
function U.GetTentativeMissionTroopCount(mid)
|
|
local g, r = groups[mid], 0
|
|
for i=0, g and 4 or -1 do
|
|
r = r + (autoTroops[g[i]] and 1 or 0)
|
|
end
|
|
return r
|
|
end
|
|
function U.FollowerHasTentativeGroup(fid)
|
|
local mid = followerMissionID[fid]
|
|
return groups[mid] and mid
|
|
end
|
|
function U.DisbandTentativeGroups()
|
|
groups, followerMissionID = {}, {}
|
|
EV("I_TENTATIVE_GROUPS_CHANGED")
|
|
end
|
|
function U.HaveTentativeGroups()
|
|
return next(groups) ~= nil
|
|
end
|
|
function U.SendTentativeGroups()
|
|
for mid, g in pairs(groups) do
|
|
U.SendMissionGroup(mid, g)
|
|
end
|
|
end
|
|
function U.SendTentativeGroup(mid)
|
|
local g = groups[mid]
|
|
if g then
|
|
U.SendMissionGroup(mid, g)
|
|
end
|
|
end
|
|
function U.GetTentativeGroup(mid, into)
|
|
local g = groups[mid]
|
|
if g then
|
|
into = type(into) == "table" and wipe(into) or {}
|
|
for i=0,4 do
|
|
into[i] = g[i]
|
|
end
|
|
return into
|
|
end
|
|
end
|
|
local function nextTent(_, k)
|
|
local mid, g = next(groups, k)
|
|
if mid then
|
|
local nt, zeroHealth = 0, false
|
|
for i=0,4 do
|
|
local fid = g[i]
|
|
if autoTroops[fid] then
|
|
nt = nt + 1
|
|
elseif fid and not zeroHealth then
|
|
if healthyCompanions[fid] or C_Garrison.GetFollowerAutoCombatStats(fid).currentHealth ~= 0 then
|
|
healthyCompanions[fid] = true
|
|
else
|
|
zeroHealth = true
|
|
end
|
|
end
|
|
end
|
|
return mid, nt, zeroHealth
|
|
end
|
|
end
|
|
function U.EnumerateTentativeGroups()
|
|
return nextTent
|
|
end
|
|
end
|
|
do -- startQueue
|
|
local startQueue, startQueueLength = {}, 0
|
|
function EV:GARRISON_MISSION_STARTED(_, mid)
|
|
if startQueue[mid] then
|
|
startQueueLength, startQueue[mid] = startQueueLength - 1, nil
|
|
EV("I_MISSION_QUEUE_CHANGED")
|
|
PlaySound(44323)
|
|
end
|
|
end
|
|
function EV:GARRISON_MISSION_NPC_CLOSED()
|
|
startQueue, startQueueLength = {}, 0
|
|
end
|
|
local function startMissionGroup(mid, g)
|
|
local mi = C_Garrison.GetBasicMissionInfo(mid)
|
|
for j=1,mi and mi.followers and #mi.followers or 0 do
|
|
for b=0,4 do
|
|
C_Garrison.RemoveFollowerFromMission(mid, mi.followers[j], b)
|
|
end
|
|
end
|
|
local ok = mi and mi.canStart
|
|
for i=0,mi and 4 or -1 do
|
|
ok = ok and (g[i] == nil or C_Garrison.AddFollowerToMission(mid, g[i], i))
|
|
end
|
|
return ok and (C_Garrison.StartMission(mid) or true) or false
|
|
end
|
|
local function queuePing()
|
|
if next(startQueue) then
|
|
C_Timer.After(0.5, queuePing)
|
|
end
|
|
local oc = startQueueLength
|
|
for mid, g in pairs(startQueue) do
|
|
if not startMissionGroup(mid, g) then
|
|
startQueueLength, startQueue[mid] = startQueueLength-1, nil
|
|
end
|
|
end
|
|
if oc ~= startQueueLength then
|
|
EV("I_MISSION_QUEUE_CHANGED")
|
|
end
|
|
end
|
|
function U.IsStartingMissions()
|
|
return startQueueLength > 0 and startQueueLength
|
|
end
|
|
function U.StopStartingMissions()
|
|
startQueue, startQueueLength = {}, 0
|
|
end
|
|
function U.IsMissionInStartQueue(mid)
|
|
return startQueue[mid] ~= nil
|
|
end
|
|
function U.SendMissionGroup(mid, g)
|
|
local ng, oql = {}, startQueueLength
|
|
for i=0,4 do
|
|
ng[i] = g[i]
|
|
local acs = g[i] and C_Garrison.GetFollowerAutoCombatStats(g[i])
|
|
if acs and acs.currentHealth == 0 then
|
|
return
|
|
end
|
|
end
|
|
startQueue[mid], startQueueLength = ng, oql + (startQueue[mid] and 0 or 1)
|
|
if oql == 0 then
|
|
queuePing()
|
|
end
|
|
EV("I_MISSION_QUEUE_CHANGED")
|
|
end
|
|
end
|
|
do -- delayStart
|
|
local delayedStart, delayTime = {}, nil
|
|
local stickHandle, stickLast, stickNext
|
|
local function checkStart()
|
|
local now = GetTime()
|
|
if delayTime and now >= delayTime then
|
|
delayTime = nil
|
|
for k in pairs(delayedStart) do
|
|
U.SendTentativeGroup(k)
|
|
delayedStart[k] = nil
|
|
end
|
|
EV("I_DELAYED_START_UPDATE")
|
|
elseif delayTime then
|
|
C_Timer.After(math.max(0.005,delayTime-now), checkStart)
|
|
end
|
|
end
|
|
local function tick()
|
|
if not (delayTime and stickNext) then
|
|
return
|
|
end
|
|
local now = GetTime()
|
|
if stickNext > now then
|
|
C_Timer.After(math.max(0.005, stickNext-now), tick)
|
|
else
|
|
stickHandle, stickLast = nil
|
|
local wp, h = PlaySoundFile(1064507)
|
|
if wp then
|
|
stickHandle, stickLast = h, now
|
|
end
|
|
stickNext = nil
|
|
end
|
|
end
|
|
local function cancelTicking()
|
|
if stickHandle and stickLast and GetTime()-stickLast < 1 then
|
|
StopSound(stickHandle)
|
|
stickHandle, stickNext = nil
|
|
end
|
|
end
|
|
local function startTicking(now)
|
|
cancelTicking()
|
|
stickNext = now
|
|
tick()
|
|
stickNext = now+1
|
|
C_Timer.After(1.01, tick)
|
|
end
|
|
function U.HasDelayedStartMissions()
|
|
return not not delayTime
|
|
end
|
|
function U.ClearDelayedStartMissions()
|
|
wipe(delayedStart)
|
|
cancelTicking()
|
|
delayTime = nil
|
|
EV("I_DELAYED_START_UPDATE")
|
|
end
|
|
function U.StartMissionWithDelay(mid, g)
|
|
local mi, now = C_Garrison.GetBasicMissionInfo(mid), GetTime()
|
|
if mi then
|
|
U.StoreMissionGroup(mid, g, true)
|
|
if mi.offerEndTime and (mi.offerEndTime-now) <= 4 then
|
|
U.SendTentativeGroup(mid)
|
|
else
|
|
delayedStart[mid], delayTime = true, now+2
|
|
C_Timer.After(2.01, checkStart)
|
|
startTicking(now)
|
|
EV("I_DELAYED_START_UPDATE")
|
|
return true
|
|
end
|
|
end
|
|
end
|
|
function U.ClearDelayedStartMission(mid)
|
|
delayedStart[mid] = nil
|
|
if next(delayedStart) == nil then
|
|
delayTime = nil
|
|
cancelTicking()
|
|
end
|
|
EV("I_DELAYED_START_UPDATE")
|
|
end
|
|
function U.RushDelayedStartMissions()
|
|
cancelTicking()
|
|
delayTime = GetTime()-1
|
|
return checkStart()
|
|
end
|
|
function U.IsMissionStartingSoon(mid)
|
|
return not not delayedStart[mid]
|
|
end
|
|
function EV:I_TENTATIVE_GROUPS_CHANGED()
|
|
local changed = false
|
|
for k in pairs(delayedStart) do
|
|
if not U.MissionHasTentativeGroup(k) then
|
|
changed, delayedStart[k] = true, nil
|
|
end
|
|
end
|
|
if changed then
|
|
if next(delayedStart) == nil then
|
|
delayTime = nil
|
|
end
|
|
EV("I_DELAYED_START_UPDATE")
|
|
end
|
|
end
|
|
function EV:GARRISON_MISSION_NPC_CLOSED()
|
|
if delayTime then
|
|
delayTime = nil
|
|
for k in pairs(delayedStart) do
|
|
delayedStart[k] = nil
|
|
end
|
|
EV("I_DELAYED_START_UPDATE")
|
|
end
|
|
end
|
|
end
|
|
do -- completeQueue
|
|
local curStack, curState, curIndex
|
|
local completionStep, lastAction, delayIndex, delayMID
|
|
local xpTable
|
|
local function After(t, f)
|
|
if t == 0 then
|
|
securecall(f)
|
|
else
|
|
C_Timer.After(t, f)
|
|
end
|
|
end
|
|
local delayOpen, delayRoll do
|
|
local function delay(state, f, d)
|
|
local function delay(minDelay)
|
|
if curState == state and curIndex == delayIndex and curStack[delayIndex].missionID == delayMID then
|
|
local time = GetTime()
|
|
if not minDelay and (not lastAction or (time-lastAction >= d)) then
|
|
lastAction = GetTime()
|
|
f(curStack[curIndex].missionID)
|
|
After(d, delay)
|
|
else
|
|
After(math.max(0.1, d + lastAction - time, minDelay or 0), delay)
|
|
end
|
|
end
|
|
end
|
|
return delay
|
|
end
|
|
delayOpen = delay("COMPLETE", C_Garrison.MarkMissionComplete, 0.4)
|
|
delayRoll = delay("BONUS", C_Garrison.MissionBonusRoll, 0.4)
|
|
end
|
|
local function delayStep()
|
|
completionStep("GARRISON_MISSION_NPC_OPENED")
|
|
end
|
|
local function delayDone()
|
|
local os = curState
|
|
if os == "ABORT" or os == "DONE" then
|
|
curState, curStack, curIndex, delayMID, delayIndex, xpTable = nil
|
|
EV("I_COMPLETE_QUEUE_UPDATE", os)
|
|
end
|
|
end
|
|
|
|
local function whineAboutUnexpectedState(msg, mid, suf)
|
|
local et = msg .. ": " .. tostring(mid) .. tostring(suf or "") .. " does not fit (" .. curIndex .. ";"
|
|
for i=1,#curStack do
|
|
local e = curStack[i]
|
|
et = et .. " " .. tostring(e and e.missionID or "?") .. (e and e.skipped and "S" or "") .. (e and e.failed and "F" or "")
|
|
end
|
|
return et .. ")"
|
|
end
|
|
local function addFollowerInfo(mi, followers, didWin)
|
|
local xpGain = mi.xp or 0
|
|
local fa = {}
|
|
for i=1, didWin and mi.rewards and #mi.rewards or 0 do
|
|
xpGain = xpGain + (mi.rewards[i].followerXP or 0)
|
|
end
|
|
for i=1,#followers do
|
|
local fi = C_Garrison.GetFollowerMissionCompleteInfo(followers[i].followerID)
|
|
local xp = (fi.currentXP or 0) + xpGain
|
|
if not fi.isTroop and (fi.maxXP or 0) > 0 and xp >= fi.maxXP then
|
|
xpTable = xpTable or C_Garrison.GetFollowerXPTable(123)
|
|
local nl = fi.level
|
|
while (xpTable[nl] or 0) ~= 0 and xp >= xpTable[nl] do
|
|
nl, xp = nl + 1, xp - xpTable[nl]
|
|
end
|
|
fi.newLevel, fi.xpToNextLevel = nl, (xpTable[nl] or 0) ~= 0 and (xpTable[nl]-xp) or nil
|
|
end
|
|
fa[i] = fi
|
|
end
|
|
mi.followerInfo = fa
|
|
end
|
|
function completionStep(ev, ...)
|
|
if not curState then return end
|
|
local mi = curStack[curIndex]
|
|
while mi and (mi.succeeded or mi.failed) do
|
|
mi, curIndex = curStack[curIndex+1], curIndex + 1
|
|
end
|
|
if (ev == "GARRISON_MISSION_NPC_CLOSED" and mi) or not mi then
|
|
curState = mi and "ABORT" or "DONE"
|
|
After(... == "IMMEDIATE" and 0 or 0.1, delayDone)
|
|
elseif curState == "NEXT" and ev == "GARRISON_MISSION_NPC_OPENED" then
|
|
EV("I_COMPLETE_QUEUE_UPDATE", "NEXT")
|
|
if mi.completed then
|
|
curState, delayIndex, delayMID = "BONUS", curIndex, mi.missionID
|
|
C_Garrison.RegenerateCombatLog(delayMID)
|
|
delayRoll(... ~= "IMMEDIATE" and 0.2)
|
|
else
|
|
curState, delayIndex, delayMID = "COMPLETE", curIndex, mi.missionID
|
|
delayOpen(... ~= "IMMEDIATE" and 0.2)
|
|
end
|
|
elseif curState == "COMPLETE" and ev == "GARRISON_MISSION_COMPLETE_RESPONSE" then
|
|
local mid, cc, ok, _brOK, followers, acr = ...
|
|
if mid ~= mi.missionID then return end
|
|
if not (acr and acr.combatLog and #acr.combatLog > 0) then
|
|
C_Garrison.RegenerateCombatLog(mid)
|
|
return
|
|
elseif cc == false and ok == false then
|
|
local bi = C_Garrison.GetBasicMissionInfo(mid)
|
|
if not (bi and bi.completed) then
|
|
return
|
|
end
|
|
cc, ok = true, acr.winner
|
|
end
|
|
addFollowerInfo(mi, followers, acr.winner)
|
|
if ok then
|
|
curState = "BONUS"
|
|
else
|
|
mi.failed, curState, curIndex = cc and true or nil, "NEXT", curIndex + 1
|
|
end
|
|
if ok then
|
|
delayIndex, delayMID = curIndex, mi.missionID
|
|
delayRoll(0.2)
|
|
else
|
|
-- Awkward: need other GMCR handlers to finish before a certain IMCS handler runs
|
|
C_Timer.After(0, function()
|
|
EV("I_MISSION_COMPLETION_STEP", mid, false, mi)
|
|
end)
|
|
After(0.45, delayStep)
|
|
end
|
|
elseif curState == "BONUS" and ev == "GARRISON_MISSION_BONUS_ROLL_COMPLETE" then
|
|
local mid, ok = ...
|
|
if mid ~= mi.missionID then
|
|
securecall(error, whineAboutUnexpectedState("Unexpected bonus roll completion", mid, ok and "K" or "k"), 2)
|
|
elseif ok then
|
|
mi.succeeded, curState, curIndex = true, "NEXT", curIndex + 1
|
|
EV("I_MISSION_COMPLETION_STEP", mid, true, mi)
|
|
end
|
|
end
|
|
end
|
|
EV.GARRISON_MISSION_NPC_OPENED, EV.GARRISON_MISSION_NPC_CLOSED = completionStep, completionStep
|
|
EV.GARRISON_MISSION_BONUS_ROLL_COMPLETE, EV.GARRISON_MISSION_COMPLETE_RESPONSE = completionStep, completionStep
|
|
|
|
function U.IsCompletingMissions()
|
|
return curState ~= nil and (#curStack-curIndex+1) or nil
|
|
end
|
|
function U.StartCompletingMissions()
|
|
curStack = C_Garrison.GetCompleteMissions(123)
|
|
curState, curIndex = "NEXT", 1
|
|
completionStep("GARRISON_MISSION_NPC_OPENED", "IMMEDIATE")
|
|
end
|
|
function U.StopCompletingMissions()
|
|
if curState then
|
|
completionStep("GARRISON_MISSION_NPC_CLOSED", "IMMEDIATE")
|
|
end
|
|
end
|
|
end
|
|
function U.InitiateMissionCompletion(mid)
|
|
local cm = C_Garrison.GetCompleteMissions(123)
|
|
for i=1, cm and #cm or 0 do
|
|
local ci = cm[i]
|
|
if ci.missionID == mid or mid == "first" then
|
|
ci.encounterIconInfo = C_Garrison.GetMissionEncounterIconInfo(ci.missionID)
|
|
return CovenantMissionFrame:InitiateMissionCompletion(ci)
|
|
end
|
|
end
|
|
end
|
|
|
|
function U.GetTimeStringFromSeconds(sec, shorter, roundUp, disallowSeconds)
|
|
local h = roundUp and math.ceil or math.floor
|
|
if sec < 90 and not disallowSeconds then
|
|
return (shorter and COOLDOWN_DURATION_SEC or INT_GENERAL_DURATION_SEC):format(sec < 0 and 0 or h(sec))
|
|
elseif (sec < 3600*(shorter and shorter ~= 2 and 3 or 1.65) and (sec % 3600 >= 1 or sec < 3600)) then
|
|
return (shorter and COOLDOWN_DURATION_MIN or INT_GENERAL_DURATION_MIN):format(h(sec/60))
|
|
elseif sec <= 3600*72 and not shorter then
|
|
sec = h(sec/60)*60
|
|
local m = math.ceil(sec % 3600 / 60)
|
|
return INT_GENERAL_DURATION_HOURS:format(math.floor(sec / 3600)) .. (m > 0 and " " .. INT_GENERAL_DURATION_MIN:format(m) or "")
|
|
elseif sec <= 3600*72 then
|
|
return (shorter and COOLDOWN_DURATION_HOURS or INT_GENERAL_DURATION_HOURS):format(h(sec/3600))
|
|
else
|
|
return (shorter and COOLDOWN_DURATION_DAYS or INT_GENERAL_DURATION_DAYS):format(h(sec/84600))
|
|
end
|
|
end
|
|
function U.SetFollowerInfo(GameTooltip, info, autoCombatSpells, autoCombatantStats, mid, boardIndex, boardMask, showHealthFooter)
|
|
local mhp, hp, atk, role, aat, level
|
|
autoCombatantStats = autoCombatantStats or info and (info.followerID and C_Garrison.GetFollowerAutoCombatStats(info.followerID) or info.autoCombatantStats)
|
|
if info then
|
|
role, level = info.role, info.level and ("|cffa8a8a8" .. UNIT_LEVEL_TEMPLATE:format(info.level)) or ""
|
|
end
|
|
if autoCombatantStats then
|
|
local s1 = autoCombatSpells and autoCombatSpells[1]
|
|
mhp, hp, atk = autoCombatantStats.maxHealth, autoCombatantStats.currentHealth, autoCombatantStats.attack
|
|
aat = T.VSim:GetAutoAttack(role, boardIndex, mid, s1 and s1.autoCombatSpellID)
|
|
end
|
|
|
|
GameTooltip:ClearLines()
|
|
GameTooltip:AddDoubleLine(info.name, level or "")
|
|
|
|
local atype = U.FormatTargetBlips(GetTargetMask(T.KnownSpells[aat], boardIndex, boardMask), boardMask, " ")
|
|
if atype == "" then
|
|
atype = aat == 11 and " " .. L"(melee)" or aat == 15 and " " .. L"(ranged)" or ""
|
|
else
|
|
atype = " " .. atype
|
|
end
|
|
GameTooltip:AddLine("|A:ui_adv_health:20:20|a" .. (hp and BreakUpLargeNumbers(hp) or "???") .. (mhp and mhp ~= hp and ("|cffa0a0a0/|r" .. BreakUpLargeNumbers(mhp)) or "").. " |A:ui_adv_atk:20:20|a" .. (atk and BreakUpLargeNumbers(atk) or "???") .. "|cffa8a8a8" .. atype, 1,1,1)
|
|
if info and info.isMaxLevel == false and info.xp and info.levelXP and info.level and not info.isAutoTroop then
|
|
GameTooltip:AddLine(GARRISON_FOLLOWER_TOOLTIP_XP:gsub("%%[^%%]*d", "%%s"):format(BreakUpLargeNumbers(info.levelXP - info.xp)), 0.7, 0.7, 0.7)
|
|
end
|
|
|
|
for i=1, autoCombatSpells and #autoCombatSpells or 0 do
|
|
local s = autoCombatSpells[i]
|
|
GameTooltip:AddLine(" ")
|
|
local si = T.KnownSpells[s.autoCombatSpellID]
|
|
local pfx = si and "" or "|TInterface/EncounterJournal/UI-EJ-WarningTextIcon:0|t "
|
|
local cdt = s.cooldown ~= 0 and (L"[CD: %dT]"):format(s.cooldown) or SPELL_PASSIVE_EFFECT
|
|
GameTooltip:AddDoubleLine(pfx .. "|T" .. s.icon .. ":0:0:0:0:64:64:4:60:4:60|t " .. NORMAL_FONT_COLOR_CODE .. s.name, "|cffa8a8a8" .. cdt .. "|r")
|
|
local dc, guideLine = 0.95, U.GetAbilityGuide(s.autoCombatSpellID, boardIndex, boardMask)
|
|
local od = U.GetAbilityDescriptionOverride(s.autoCombatSpellID, atk)
|
|
if od then
|
|
dc, guideLine = 0.60, od .. (guideLine and "|n" .. guideLine or "")
|
|
end
|
|
GameTooltip:AddLine(s.description, dc, dc, dc, 1)
|
|
if guideLine then
|
|
GameTooltip:AddLine("|cff73ff00" .. guideLine, 0.45, 1, 0, 1)
|
|
end
|
|
end
|
|
|
|
if showHealthFooter and info and info.status ~= GARRISON_FOLLOWER_ON_MISSION and autoCombatantStats and autoCombatantStats.currentHealth < autoCombatantStats.maxHealth and autoCombatantStats.minutesHealingRemaining then
|
|
local t = " " .. U.GetTimeStringFromSeconds(autoCombatantStats.minutesHealingRemaining*60, false, true, true)
|
|
GameTooltip:AddLine(" ")
|
|
GameTooltip:AddLine("|cffffd926" .. CLOCK_ICON:format(255, 0.85*255, 0.15*255) .. ADVENTURES_FOLLOWER_HEAL_TIME:format(t):gsub(" +", " "), 1, 0.85, 0.15, 1)
|
|
end
|
|
|
|
GameTooltip:Show()
|
|
end
|
|
function U.FormatTargetBlips(tm, bm, prefix, ac, padHeight)
|
|
local isForked = tm >= 2^18
|
|
tm = tm - (isForked and 2^18 or 0)
|
|
ac = ac and ac .. "|t" or (isForked and "200:50:255|t" or "120:255:0|t")
|
|
local r, xs, bw = "", 0, GetBlipWidth()
|
|
local yd = bw/2
|
|
if tm % 32 > 0 then
|
|
local xo = 0
|
|
for i=2,4 do
|
|
local t, p = tm % 2^(i+1) >= 2^i, bm % 2^(i+1) >= 2^i
|
|
r = r .. "|TInterface/Minimap/PartyRaidBlipsV2:8:8:" .. (xo .. ":" .. yd).. ":64:32:0:20:0:20:" .. (t and ac or p and "160:160:160|t" or "40:40:40|t")
|
|
if i < 4 then
|
|
i, xo = i - 2, xo - bw/2
|
|
t, p = tm % 2^(i+1) >= 2^i, bm % 2^(i+1) >= 2^i
|
|
r = r .. "|TInterface/Minimap/PartyRaidBlipsV2:8:8:" .. (xo .. ":" .. -yd).. ":64:32:0:20:0:20:" .. (t and ac or p and "160:160:160|t" or "40:40:40|t")
|
|
xo = xo - bw/2
|
|
end
|
|
end
|
|
xs = -bw
|
|
end
|
|
if tm >= 32 then
|
|
local xo = xs
|
|
for i=5,8 do
|
|
local t, p = tm % 2^(i+1) >= 2^i, bm % 2^(i+1) >= 2^i
|
|
r = r .. "|TInterface/Minimap/PartyRaidBlipsV2:8:8:" .. xo .. ":" .. -yd .. ":64:32:0:20:0:20:" .. (t and ac or p and "160:160:160|t" or "40:40:40|t")
|
|
i, xo = i + 4, xo - bw
|
|
t, p = tm % 2^(i+1) >= 2^i, bm % 2^(i+1) >= 2^i
|
|
r = r .. "|TInterface/Minimap/PartyRaidBlipsV2:8:8:" .. xo .. ":" .. yd .. ":64:32:0:20:0:20:" .. (t and ac or p and "160:160:160|t" or "40:40:40|t")
|
|
end
|
|
end
|
|
if prefix and r ~= "" then
|
|
r = prefix .. r
|
|
end
|
|
if r ~= "" and padHeight ~= false then
|
|
r = r .. "|TInterface/Minimap/PartyRaidBlipsV2:19:1:0:0:64:32:62:63:0:2|t"
|
|
end
|
|
return r
|
|
end
|
|
function U.GetAbilityGuide(spellID, boardIndex, boardMask, padHeight)
|
|
local si, guideLine = T.KnownSpells[spellID]
|
|
if not (si and si.type ~= "nop") then
|
|
return
|
|
end
|
|
if si.firstTurn then
|
|
padHeight = true
|
|
end
|
|
local tm = GetTargetMask(si, boardIndex, boardMask)
|
|
if tm > 0 then
|
|
local b = U.FormatTargetBlips(tm, boardMask, nil, nil, padHeight)
|
|
if b and b ~= "" then
|
|
guideLine = L"Targets:" .. " " .. b
|
|
end
|
|
end
|
|
if si.healATK or si.damageATK or si.healPerc or si.damagePerc then
|
|
local p = FormatSpellPulse(si)
|
|
if p then
|
|
guideLine = L"Ticks:" .. " " .. p .. (guideLine and " " .. guideLine or "")
|
|
end
|
|
end
|
|
if si.firstTurn then
|
|
guideLine = (L"First cast during turn %d."):format(si.firstTurn) .. (guideLine and "|n" .. guideLine or "")
|
|
end
|
|
return guideLine
|
|
end
|
|
function U.GetAbilityDescriptionOverride(spellID, atk, ms)
|
|
local si = T.KnownSpells[spellID]
|
|
if si and si.type == "nop" then
|
|
return L"It does nothing."
|
|
end
|
|
local od = overdesc[spellID]
|
|
if type(od) == "table" then
|
|
od = FormatAbilityDescriptionOverride(si, od, atk, ms)
|
|
end
|
|
return od
|
|
end
|
|
|
|
function U.GetInProgressGroup(followers, into)
|
|
into = type(into) == "table" and wipe(into) or {}
|
|
for i=1, #followers do
|
|
local fid = followers[i]
|
|
local ii = C_Garrison.GetFollowerMissionCompleteInfo(fid)
|
|
into[ii and ii.boardIndex or -1] = fid
|
|
end
|
|
into[-1] = nil
|
|
return into
|
|
end
|
|
|
|
function U.FollowerIsFavorite(id)
|
|
local f = SPC.Favorites
|
|
return f and f[id] or false
|
|
end
|
|
function U.FollowerSetFavorite(id, nv)
|
|
local f = SPC.Favorites or {}
|
|
f[id] = nv or nil
|
|
SPC.Favorites = next(f) ~= nil and f or nil
|
|
end
|
|
|
|
function U.GetShiftedCurrencyValue(id, q)
|
|
if id == 1889 and q and C_Covenants.GetActiveCovenantID() == SPC.ccsCoven then
|
|
local s = SPC.ccsDelta
|
|
q = q - (type(s) == "number" and s <= q and s or 0)
|
|
end
|
|
return q
|
|
end
|
|
function U.SetCurrencyValueShiftTarget(id, s)
|
|
local c, co = C_CurrencyInfo.GetCurrencyInfo(id), C_Covenants.GetActiveCovenantID()
|
|
if id ~= 1889 or not c then return end
|
|
if SPC.ccsCoven ~= co then
|
|
SPC.ccsLV, SPC.ccsLH = nil, nil
|
|
end
|
|
SPC.ccsCoven, SPC.ccsDelta = co, s and (c.quantity-s) or 0
|
|
EV("I_UPDATE_CURRENCY_SHIFT", id)
|
|
end
|
|
function U.ObserveMissionShift(t, q)
|
|
local c = C_Covenants.GetActiveCovenantID()
|
|
local b, l, h = math.ceil((t-3)/4), 0, 0
|
|
if b > 0 then
|
|
l, h = (b-1)*4, b*4 - (b == 5 and 0 or 1)
|
|
end
|
|
if (q or h) <= h then return end
|
|
if b == 0 or ((SPC.ccsLV or -5) == (q-1) and (SPC.ccsLH or 10) < l and SPC.ccsCoven == c) then
|
|
SPC.ccsCoven, SPC.ccsDelta = c, q-l
|
|
EV("I_UPDATE_CURRENCY_SHIFT", 1889)
|
|
elseif U.GetShiftedCurrencyValue(1889, q) > h then
|
|
SPC.ccsCoven, SPC.ccsDelta = c, nil
|
|
end
|
|
SPC.ccsLV, SPC.ccsLH = q, h
|
|
end
|
|
function EV:I_OBSERVE_AVAIL_MISSIONS(ma)
|
|
for i=1,#ma do
|
|
local t = MS_TIER[ma[i].missionID-2173]
|
|
if t then
|
|
local cv = C_CurrencyInfo.GetCurrencyInfo(1889)
|
|
U.ObserveMissionShift(t, cv and cv.quantity)
|
|
break
|
|
end
|
|
end
|
|
end
|