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.
551 lines
17 KiB
551 lines
17 KiB
local Util = WarpDeplete.Util
|
|
local L = WarpDeplete.L
|
|
|
|
local UPDATE_INTERVAL = 0.1
|
|
local sinceLastUpdate = 0
|
|
|
|
function WarpDeplete:CheckForChallengeMode()
|
|
local _, _, difficulty, _, _, _, _, currentZoneID = GetInstanceInfo()
|
|
|
|
self:PrintDebug("Checking for challenge mode, difficulty: "
|
|
.. difficulty .. ", Zone ID: " .. currentZoneID)
|
|
|
|
local inChallenge = difficulty == 8 and C_ChallengeMode.GetActiveChallengeMapID() ~= nil
|
|
|
|
self:PrintDebug("Challenge state: inChallenge:"
|
|
.. tostring(self.challengeState.inChallenge)
|
|
.. ", new value:" .. tostring(inChallenge))
|
|
|
|
if self.challengeState.inChallenge == inChallenge then return end
|
|
|
|
if inChallenge then self:StartChallengeMode()
|
|
else self:StopChallengeMode() end
|
|
end
|
|
|
|
function WarpDeplete:StartChallengeMode()
|
|
if self.challengeState.demoModeActive then
|
|
self:Print(L["Disabling demo mode because a challenge has started."])
|
|
self:DisableDemoMode()
|
|
end
|
|
|
|
self:PrintDebug("Starting challenge mode")
|
|
self:ResetState()
|
|
self:RegisterChallengeEvents()
|
|
|
|
local gotTimerInfo = self:GetTimerInfo()
|
|
local gotKeyInfo = self:GetKeyInfo()
|
|
local gotObjectivesInfo = self:GetObjectivesInfo()
|
|
self:SetDeaths(C_ChallengeMode.GetDeathCount() or 0)
|
|
|
|
if not gotKeyInfo or not gotObjectivesInfo or not gotTimerInfo then return end
|
|
|
|
self.challengeState.inChallenge = true
|
|
self:Show()
|
|
|
|
if not self.timerState.running then
|
|
self:StartChallengeTimer()
|
|
end
|
|
end
|
|
|
|
function WarpDeplete:StartChallengeTimer()
|
|
self:PrintDebug("Challenge timer started")
|
|
|
|
self.timerState.startTime = GetTime()
|
|
self.timerState.running = true
|
|
|
|
sinceLastUpdate = 0
|
|
self.frames.root:SetScript("OnUpdate", function(self, elapsed)
|
|
WarpDeplete:OnTimerTick(elapsed)
|
|
end)
|
|
end
|
|
|
|
function WarpDeplete:StopChallengeTimer()
|
|
sinceLastUpdate = 0
|
|
self.frames.root:SetScript("OnUpdate", nil)
|
|
end
|
|
|
|
function WarpDeplete:StopChallengeMode()
|
|
self:Hide()
|
|
|
|
self:ResetState()
|
|
self:UnregisterChallengeEvents()
|
|
end
|
|
|
|
function WarpDeplete:CompleteChallengeMode()
|
|
--TODO(happens): Refresh information from blizzard timer so we have
|
|
-- an accurate finish time. If we load in afterwards this is done automatically,
|
|
-- but if we used our own timer we should redo it.
|
|
self.challengeState.challengeCompleted = true
|
|
|
|
self:UpdateTimerDisplay()
|
|
self:UpdateObjectivesDisplay()
|
|
self:UpdateForcesDisplay()
|
|
end
|
|
|
|
function WarpDeplete:GetTimerInfo()
|
|
local mapID = C_ChallengeMode.GetActiveChallengeMapID()
|
|
if not mapID then
|
|
self:PrintDebug("No map id for timer received")
|
|
return false
|
|
end
|
|
|
|
local limit = select(3, C_ChallengeMode.GetMapUIInfo(mapID))
|
|
if not limit then
|
|
self:PrintDebug("No time limit received")
|
|
return false
|
|
end
|
|
|
|
self:SetTimerLimit(limit)
|
|
|
|
local current = select(2, GetWorldElapsedTime(1))
|
|
|
|
-- If there already is time elapsed, we're loading into
|
|
-- a running key. This means that we're now running on the
|
|
-- blizzard timer and should request the current time from
|
|
-- someone else.
|
|
if current > 2 then --TODO(happens): The WA is using 2 here, is that fine?
|
|
self.timerState.isBlizzardTimer = true
|
|
|
|
-- If we call this without any delay, the timer will be off by 10
|
|
-- seconds. The blizzard timer also has this bug and corrects it
|
|
-- after the first death. Lmao
|
|
C_Timer.After(0.5, function()
|
|
local current = select(2, GetWorldElapsedTime(1))
|
|
local deaths = C_ChallengeMode.GetDeathCount()
|
|
local trueTime = current - deaths * 5
|
|
self.timerState.startOffset = trueTime
|
|
self.timerState.startTime = GetTime()
|
|
self.timerState.isBlizzardTimer = true
|
|
|
|
self:RequestTimerSync()
|
|
self:RequestObjectiveSync()
|
|
end)
|
|
end
|
|
|
|
self:SetTimerCurrent(current)
|
|
return true
|
|
end
|
|
|
|
function WarpDeplete:GetKeyInfo()
|
|
self:PrintDebug("Getting key info")
|
|
|
|
local level, affixes = C_ChallengeMode.GetActiveKeystoneInfo()
|
|
|
|
local affixNames = {}
|
|
for i, affixID in ipairs(affixes) do
|
|
local name = C_ChallengeMode.GetAffixInfo(affixID)
|
|
affixNames[i] = name
|
|
end
|
|
|
|
if level <= 0 or #affixNames <= 0 then
|
|
self:PrintDebug("No affixes or key level received")
|
|
return false
|
|
end
|
|
|
|
self:SetKeyDetails(level or 0, affixNames)
|
|
return true
|
|
end
|
|
|
|
function WarpDeplete:GetObjectivesInfo()
|
|
self:PrintDebug("Getting objectives info")
|
|
|
|
local stepCount = select(3, C_Scenario.GetStepInfo())
|
|
if stepCount <= 0 then
|
|
self:PrintDebug("No steps received, can't update objective info")
|
|
return false
|
|
end
|
|
|
|
local currentCount, totalCount = self:GetEnemyForcesCount()
|
|
-- The last step will forces, all previous steps are bosses
|
|
self:PrintDebug("Got forces info: " .. currentCount .. "/" .. totalCount)
|
|
|
|
if totalCount <= 0 then
|
|
self:PrintDebug("No mob count received")
|
|
return false
|
|
end
|
|
|
|
self:SetForcesTotal(totalCount)
|
|
self:SetForcesCurrent(currentCount)
|
|
|
|
local objectives = {}
|
|
for i = 1, stepCount - 1 do
|
|
local name, _, completed = C_Scenario.GetCriteriaInfo(i)
|
|
if not name then break end
|
|
|
|
name = gsub(name, " defeated", "")
|
|
self:PrintDebug("Got boss name for index " .. i .. ": " .. tostring(name))
|
|
objectives[i] = { name = name, time = completed and 0 or nil }
|
|
end
|
|
|
|
if #objectives <= 0 then
|
|
self:PrintDebug("No objectives received")
|
|
return false
|
|
end
|
|
|
|
self:SetObjectives(objectives)
|
|
return true
|
|
end
|
|
|
|
function WarpDeplete:GetEnemyForcesCount()
|
|
local stepCount = select(3, C_Scenario.GetStepInfo())
|
|
local _, _, _, _, totalCount, _, _, mobPointsStr = C_Scenario.GetCriteriaInfo(stepCount)
|
|
if not totalCount or not mobPointsStr then return nil, nil end
|
|
|
|
local currentCountStr = gsub(mobPointsStr, "%%", "")
|
|
local currentCount = tonumber(currentCountStr)
|
|
return currentCount, totalCount
|
|
end
|
|
|
|
function WarpDeplete:UpdateForces()
|
|
if not self.challengeState.inChallenge then return end
|
|
|
|
local stepCount = select(3, C_Scenario.GetStepInfo())
|
|
local currentCount = self:GetEnemyForcesCount()
|
|
-- This mostly happens when we have already completed the dungeon
|
|
if not currentCount then return end
|
|
self:PrintDebug("currentCount: " .. currentCount)
|
|
|
|
if currentCount >= self.forcesState.totalCount and not self.forcesState.completed then
|
|
-- If we just went above the total count (or matched it), we completed it just now
|
|
self.forcesState.completed = true
|
|
self.forcesState.completedTime = self.timerState.current
|
|
end
|
|
|
|
self:SetForcesCurrent(currentCount)
|
|
end
|
|
|
|
function WarpDeplete:UpdateObjectives()
|
|
if not self.challengeState.inChallenge then return end
|
|
|
|
local objectives = Util.copy(self.objectivesState)
|
|
local changed = false
|
|
|
|
local stepCount = select(3, C_Scenario.GetStepInfo())
|
|
for i = 1, stepCount - 1 do
|
|
if not objectives[i] or not objectives[i].time then
|
|
-- If it wasn't completed before and it is now, we've just completed
|
|
-- it and can set the completion time
|
|
local completed = select(3, C_Scenario.GetCriteriaInfo(i))
|
|
if completed then
|
|
objectives[i].time = self.timerState.current
|
|
changed = true
|
|
end
|
|
end
|
|
end
|
|
|
|
if changed then self:SetObjectives(objectives) end
|
|
end
|
|
|
|
function WarpDeplete:ResetCurrentPull()
|
|
for k, _ in pairs(self.forcesState.currentPull) do
|
|
self.forcesState.currentPull[k] = nil
|
|
end
|
|
|
|
self:SetForcesPull(0)
|
|
end
|
|
|
|
function WarpDeplete:AddDeathDetails(time, name, class)
|
|
local len = #self.timerState.deathDetails
|
|
self.timerState.deathDetails[len + 1] = {
|
|
time = time,
|
|
name = name,
|
|
class = class
|
|
}
|
|
end
|
|
|
|
-- These events are used to detect whether we are in challenge mode
|
|
-- or whether we should put a key in the socket, and will always stay registered.
|
|
function WarpDeplete:RegisterGlobalEvents()
|
|
self:RegisterEvent("PLAYER_ENTERING_WORLD", "OnCheckChallengeMode")
|
|
self:RegisterEvent("ZONE_CHANGED_NEW_AREA", "OnCheckChallengeMode")
|
|
|
|
-- Fired when the countdown hits 0 (and for some reason when we die?)
|
|
self:RegisterEvent("WORLD_STATE_TIMER_START", "OnChallengeModeStart")
|
|
|
|
-- Fired when we open the keystone socket
|
|
self:RegisterEvent("CHALLENGE_MODE_KEYSTONE_RECEPTABLE_OPEN", "OnKeystoneOpen")
|
|
|
|
-- Register tooltip count display
|
|
TooltipDataProcessor.AddTooltipPostCall(Enum.TooltipDataType.Unit, WarpDeplete.DisplayCountInTooltip)
|
|
|
|
-- Tooltip events
|
|
self.frames.deathsTooltip:SetScript("OnEnter", WarpDeplete.TooltipOnEnter)
|
|
self.frames.deathsTooltip:SetScript("OnLeave", WarpDeplete.TooltipOnLeave)
|
|
end
|
|
|
|
function WarpDeplete.TooltipOnEnter()
|
|
local self = WarpDeplete
|
|
if not self.db.profile.showDeathsTooltip then return end
|
|
|
|
GameTooltip:SetOwner(self.frames.deathsTooltip, "ANCHOR_BOTTOM")
|
|
GameTooltip:ClearLines()
|
|
|
|
local count = #self.timerState.deathDetails
|
|
if count == 0 then
|
|
GameTooltip:AddLine(L["No Recorded Player Deaths"], 1, 1, 1)
|
|
GameTooltip:Show()
|
|
return
|
|
end
|
|
|
|
if self.db.profile.deathLogStyle == "time" then
|
|
local showFrom = 0
|
|
if count > 20 then
|
|
showFrom = count - 20
|
|
end
|
|
|
|
GameTooltip:AddLine(L["Player Deaths"], 1, 1, 1)
|
|
for i, d in ipairs(self.timerState.deathDetails) do
|
|
if i >= showFrom then
|
|
local color = select(4, GetClassColor(d.class))
|
|
local time = Util.formatTime(d.time)
|
|
GameTooltip:AddLine(time .. " - |c" .. color .. d.name .. "|r")
|
|
end
|
|
end
|
|
elseif self.db.profile.deathLogStyle == "count" then
|
|
local countTable = {}
|
|
for i, d in ipairs(self.timerState.deathDetails) do
|
|
if not countTable[d.name] then
|
|
countTable[d.name] = {
|
|
color = select(4, GetClassColor(d.class)),
|
|
count = 0
|
|
}
|
|
end
|
|
|
|
countTable[d.name].count = countTable[d.name].count + 1
|
|
end
|
|
|
|
for name, deaths in pairs(countTable) do
|
|
GameTooltip:AddLine("|c" .. deaths.color .. name .. "|r|cFFFFFFFF: " .. tostring(deaths.count) .. "|r")
|
|
end
|
|
end
|
|
|
|
GameTooltip:Show()
|
|
end
|
|
|
|
function WarpDeplete.TooltipOnLeave()
|
|
GameTooltip_Hide()
|
|
end
|
|
|
|
function WarpDeplete.DisplayCountInTooltip(tt, data)
|
|
if not tt or tt ~= GameTooltip or not data or not data.guid then return end
|
|
if not WarpDeplete.timerState.running then return end
|
|
if not MDT or not WarpDeplete.db.profile.showTooltipCount then return end
|
|
|
|
local npcID = select(6, strsplit("-", data.guid))
|
|
local count, max = MDT:GetEnemyForces(tonumber(npcID))
|
|
|
|
if count and max and count ~= 0 and max ~= 0 then
|
|
local percentText = ("%.2f"):format(count / max * 100)
|
|
local countText = ("%d"):format(count)
|
|
local result = WarpDeplete.db.profile.tooltipCountFormat ~= ":custom:" and
|
|
WarpDeplete.db.profile.tooltipCountFormat or
|
|
WarpDeplete.db.profile.customTooltipCountFormat
|
|
|
|
result = gsub(result, ":percent:", percentText .. "%%")
|
|
result = gsub(result, ":count:", countText)
|
|
GameTooltip:AddLine("Count: |cFFFFFFFF" .. result .. "|r")
|
|
GameTooltip:Show()
|
|
end
|
|
end
|
|
|
|
function WarpDeplete:RegisterChallengeEvents()
|
|
-- Challenge mode triggers
|
|
self:RegisterEvent("CHALLENGE_MODE_START", "OnChallengeModeStart")
|
|
self:RegisterEvent("CHALLENGE_MODE_RESET", "OnChallengeModeReset")
|
|
self:RegisterEvent("CHALLENGE_MODE_COMPLETED", "OnChallengeModeCompleted")
|
|
|
|
-- Scenario Triggers
|
|
self:RegisterEvent("SCENARIO_POI_UPDATE", "OnScenarioPOIUpdate")
|
|
self:RegisterEvent("SCENARIO_CRITERIA_UPDATE", "OnScenarioCriteriaUpdate")
|
|
|
|
-- Combat triggers
|
|
self:RegisterEvent("PLAYER_DEAD", "OnPlayerDead")
|
|
self:RegisterEvent("ENCOUNTER_END", "OnResetCurrentPull")
|
|
self:RegisterEvent("PLAYER_REGEN_ENABLED", "OnResetCurrentPull")
|
|
|
|
self:RegisterEvent("COMBAT_LOG_EVENT_UNFILTERED", "OnCombatLogEvent")
|
|
self:RegisterEvent("UNIT_THREAT_LIST_UPDATE", "OnThreatListUpdate")
|
|
end
|
|
|
|
function WarpDeplete:UnregisterChallengeEvents()
|
|
self:UnregisterEvent("CHALLENGE_MODE_START")
|
|
self:UnregisterEvent("CHALLENGE_MODE_RESET")
|
|
self:UnregisterEvent("CHALLENGE_MODE_COMPLETED")
|
|
self:UnregisterEvent("SCENARIO_POI_UPDATE")
|
|
self:UnregisterEvent("SCENARIO_CRITERIA_UPDATE")
|
|
self:UnregisterEvent("PLAYER_DEAD")
|
|
self:UnregisterEvent("PLAYER_REGEN_ENABLED")
|
|
self:UnregisterEvent("UNIT_THREAT_LIST_UPDATE")
|
|
self:UnregisterEvent("COMBAT_LOG_EVENT_UNFILTERED")
|
|
end
|
|
|
|
function WarpDeplete:OnTimerTick(elapsed)
|
|
if not self.challengeState.inChallenge or
|
|
self.challengeState.challengeCompleted or
|
|
not self.timerState.running then
|
|
self:StopChallengeTimer()
|
|
return
|
|
end
|
|
|
|
sinceLastUpdate = sinceLastUpdate + elapsed
|
|
if sinceLastUpdate <= UPDATE_INTERVAL then return end
|
|
sinceLastUpdate = 0
|
|
|
|
--TODO(happens): We update this a lot, can we do this
|
|
-- in a better way so it's not called 10 times a second?
|
|
local newDeaths = C_ChallengeMode.GetDeathCount()
|
|
if newDeaths ~= self.timerState.deaths then
|
|
self:SetDeaths(newDeaths)
|
|
end
|
|
|
|
local deathPenalty = self.timerState.deaths * 5
|
|
local current = GetTime() + self.timerState.startOffset - self.timerState.startTime + deathPenalty
|
|
|
|
-- if current < 0 then return end
|
|
|
|
self:SetTimerCurrent(current)
|
|
end
|
|
|
|
function WarpDeplete:OnCheckChallengeMode(ev)
|
|
self:PrintDebugGlobalEvent(ev)
|
|
self:CheckForChallengeMode()
|
|
end
|
|
|
|
function WarpDeplete:OnChallengeModeStart(ev)
|
|
self:PrintDebugGlobalEvent(ev)
|
|
if self.timerState.running then
|
|
self:PrintDebug("Start event received while timer was already running")
|
|
return
|
|
end
|
|
|
|
self:StartChallengeTimer()
|
|
|
|
if not self.challengeState.inChallenge then
|
|
self:StartChallengeMode()
|
|
end
|
|
end
|
|
|
|
function WarpDeplete:OnPlayerDead(ev)
|
|
self:PrintDebugEvent(ev)
|
|
--TODO(happens): It would be better to also broadcast the death
|
|
-- and then deduplicate deaths, since we can also catch deaths
|
|
-- that weren't logged for us that way. We need to figure out a
|
|
-- good method for deduping though.
|
|
-- self:BroadcastDeath()
|
|
self:ResetCurrentPull()
|
|
end
|
|
|
|
function WarpDeplete:OnChallengeModeReset(ev)
|
|
self:PrintDebugEvent(ev)
|
|
self:ResetState()
|
|
end
|
|
|
|
function WarpDeplete:OnChallengeModeCompleted(ev)
|
|
self:PrintDebugEvent(ev)
|
|
self:CompleteChallengeMode()
|
|
end
|
|
|
|
function WarpDeplete:OnKeystoneOpen(ev)
|
|
self:PrintDebugEvent(ev)
|
|
|
|
if not self.db.profile.insertKeystoneAutomatically then
|
|
return
|
|
end
|
|
|
|
local difficulty = select(3, GetInstanceInfo())
|
|
if difficulty ~= 8 and difficulty ~= 23 then
|
|
return
|
|
end
|
|
|
|
local found = nil
|
|
for bagIndex = 0, NUM_BAG_SLOTS do
|
|
for invIndex = 1, C_Container.GetContainerNumSlots(bagIndex) do
|
|
local itemID = C_Container.GetContainerItemID(bagIndex, invIndex)
|
|
|
|
if itemID and C_Item.IsItemKeystoneByID(itemID) then
|
|
self:PrintDebug("Key found at ("
|
|
.. bagIndex .. "," .. invIndex .. ")")
|
|
|
|
found = {
|
|
bagIndex = bagIndex,
|
|
invIndex = invIndex
|
|
}
|
|
|
|
break
|
|
end
|
|
end
|
|
|
|
if found ~= nil then break end
|
|
end
|
|
|
|
if found ~= nil then
|
|
self:PrintDebug("Slotting keystone from ("
|
|
.. found.bagIndex .. "," .. found.invIndex .. ")")
|
|
|
|
C_Container.UseContainerItem(found.bagIndex, found.invIndex)
|
|
end
|
|
end
|
|
|
|
function WarpDeplete:OnScenarioPOIUpdate(ev)
|
|
self:PrintDebugEvent(ev)
|
|
self:UpdateForces()
|
|
self:UpdateObjectives()
|
|
end
|
|
|
|
function WarpDeplete:OnScenarioCriteriaUpdate(ev)
|
|
self:PrintDebugEvent(ev)
|
|
self:UpdateForces()
|
|
self:UpdateObjectives()
|
|
end
|
|
|
|
function WarpDeplete:OnResetCurrentPull(ev)
|
|
self:PrintDebugEvent(ev)
|
|
self:ResetCurrentPull()
|
|
end
|
|
|
|
function WarpDeplete:OnThreatListUpdate(ev, unit)
|
|
self:PrintDebugEvent(ev)
|
|
if not MDT then return end
|
|
if not InCombatLockdown() or not unit or not UnitExists(unit) then return end
|
|
|
|
--NOTE(happens): There seem to be cases where a Unit will throw a threat list update
|
|
-- after it has died, which falsely re-adds it to the current pull. We set that units'
|
|
-- count value to "DEAD" when it dies, and due to the check if the guid already exists
|
|
-- in the table, it won't be overwritten after the unit has died.
|
|
local guid = UnitGUID(unit)
|
|
if not guid or self.forcesState.currentPull[guid] then return end
|
|
|
|
local npcID = select(6, strsplit("-", guid))
|
|
local count = MDT:GetEnemyForces(tonumber(npcID))
|
|
if not count or count <= 0 then return end
|
|
|
|
self:PrintDebug("Adding unit " .. guid .. " to current pull: " .. count)
|
|
self.forcesState.currentPull[guid] = count
|
|
local pullCount = Util.calcPullCount(self.forcesState.currentPull, self.forcesState.totalCount)
|
|
self:SetForcesPull(pullCount)
|
|
end
|
|
|
|
function WarpDeplete:OnCombatLogEvent(ev)
|
|
local _, subEv, _, _, _, _, _, guid, name = CombatLogGetCurrentEventInfo()
|
|
if subEv ~= "UNIT_DIED" then return end
|
|
self:PrintDebugEvent(ev)
|
|
if not guid then return end
|
|
|
|
--NOTE(happens): We have to check health since we'd count feign death otherwise
|
|
if UnitInParty(name) and UnitHealth(name) <= 1 then
|
|
local name = UnitName(name)
|
|
local class = select(2, UnitClass(name))
|
|
local time = self.timerState.current
|
|
|
|
self:PrintDebug("Player died: " .. name .. " class: " .. class .. " time: " .. time)
|
|
self:AddDeathDetails(time, name, class)
|
|
return
|
|
end
|
|
|
|
if not self.forcesState.currentPull[guid] then return end
|
|
self:PrintDebug("removing unit " .. guid .. " from current pull")
|
|
-- See comment above (OnThreadListUpdate)
|
|
self.forcesState.currentPull[guid] = "DEAD"
|
|
local pullCount = Util.calcPullCount(self.forcesState.currentPull, self.forcesState.totalCount)
|
|
self:SetForcesPull(pullCount)
|
|
end
|
|
|