local Runner = { started = false, timeout = 900, activeBuff = 0, data = { direction = 1, startedTime = 0, dolmensClosed = 0, xpPerMinute = 0, minutesToLevel = 0, hoursToLevel = 0, currentDolmen = 0, lastDomen = 0, beforeLastDolmen = 0, events = {} }, defaultData = { direction = 1, startedTime = 0, dolmensClosed = 0, xpPerMinute = 0, minutesToLevel = 0, hoursToLevel = 0, currentDolmen = 0, lastDolmen = 0, beforeLastDolmen = 0, events = {} }, directions = { cw = 1, ccw = -1 }, wayshrines = { -- summerset 353 358 349 357 365 [59] = { region="Alik'r Desert", cw = 60, ccw = 155}, [60] = { region="Alik'r Desert", cw = 155, ccw = 59}, [155] = { region="Alik'r Desert", cw = 59, ccw = 60}, [127] = { region="Auridon", cw = 175, ccw = 176}, [175] = { region="Auridon", cw = 176, ccw = 127}, [176] = { region="Auridon", cw = 127, ccw = 175}, [39] = { region="Bangkorai", cw = 206, ccw = 35}, [206] = { region="Bangkorai", cw = 35, ccw = 39}, [35] = { region="Bangkorai", cw = 39, ccw = 206}, [30] = { region="Deshaan", cw = 27, ccw = 80}, [27] = { region="Deshaan", cw = 80, ccw = 30}, [80] = { region="Deshaan", cw = 30, ccw = 27}, [95] = { region="Eastmarch", cw = 92, ccw = 90}, [92] = { region="Eastmarch", cw = 90, ccw = 95}, [90] = { region="Eastmarch", cw = 95, ccw = 92}, [20] = { region="Glenumbra", cw = 6, ccw = 2}, [6] = { region="Glenumbra", cw = 2, ccw = 20}, [2] = { region="Glenumbra", cw = 20, ccw = 6}, [207] = { region="Grahtwood", cw = 164, ccw = 21}, [164] = { region="Grahtwood", cw = 21, ccw = 207}, [21] = { region="Grahtwood", cw = 207, ccw = 164}, [148] = { region="Greenshade", cw = 152, ccw = 149}, [152] = { region="Greenshade", cw = 149, ccw = 148}, [149] = { region="Greenshade", cw = 148, ccw = 152}, [103] = { region="Malabal Tor", cw = 100, ccw = 105}, [100] = { region="Malabal Tor", cw = 105, ccw = 103}, [105] = { region="Malabal Tor", cw = 103, ccw = 100}, [162] = { region="Reaper's March", cw = 158, ccw = 157}, [158] = { region="Reaper's March", cw = 157, ccw = 162}, [157] = { region="Reaper's March", cw = 162, ccw = 158}, [10] = { region="Ravenspire", cw = 13, ccw = 86}, [13] = { region="Ravenspire", cw = 86, ccw = 10}, [86] = { region="Ravenspire", cw = 10, ccw = 13}, [51] = { region="Shadowfen", cw = 53, ccw = 47}, [53] = { region="Shadowfen", cw = 47, ccw = 51}, [47] = { region="Shadowfen", cw = 51, ccw = 53}, [73] = { region="Stonefalls", cw = 67, ccw = 71}, [67] = { region="Stonefalls", cw = 71, ccw = 73}, [71] = { region="Stonefalls", cw = 73, ccw = 67}, [17] = { region="Stormhaven", cw = 19, ccw = 31}, [19] = { region="Stormhaven", cw = 31, ccw = 17}, [31] = { region="Stormhaven", cw = 17, ccw = 19}, [110] = { region="The Rift", cw = 116, ccw = 114}, [116] = { region="The Rift", cw = 114, ccw = 110}, [114] = { region="The Rift", cw = 110, ccw = 116} } } function Runner:Start() LeoDolmenRunner.log("Starting runner") local direction = Runner.data.direction local lastDolmen = Runner.data.lastDolmen or 0 local beforeLastDolmen = Runner.data.beforeLastDolmen or 0 ZO_ShallowTableCopy(Runner.defaultData, Runner.data) Runner.data.startedTime = GetTimeStamp() Runner.data.direction = direction Runner.data.lastDolmen = lastDolmen Runner.data.beforeLastDolmen = beforeLastDolmen Runner.started = true LeoDolmenRunnerWindowPanelStartStopLabel:SetText("Stop") LeoDolmenRunner.ui:CreateUI() self:GetActiveBuff() end function Runner:Stop() LeoDolmenRunner.log("Stopping runner") Runner.started = false LeoDolmenRunnerWindowPanelStartStopLabel:SetText("Start") end function Runner:StartStop() if Runner.started then Runner:Stop() else Runner:Start() end end function Runner:CW() LeoDolmenRunner.log("Changing direction to clock wise") Runner.data.direction = Runner.directions.cw LeoDolmenRunnerWindowPanelOrientationLabel:SetText("CW") end function Runner:CCW() LeoDolmenRunner.log("Changing direction to counter clock wise") Runner.data.direction = Runner.directions.ccw LeoDolmenRunnerWindowPanelOrientationLabel:SetText("CCW") end function Runner:CWCCW() if Runner.data.direction == Runner.directions.cw then Runner:CCW() else Runner:CW() end end function Runner:OnFastTravelInteraction(currentWayshrine) if not Runner.started or Runner.wayshrines[currentWayshrine] == nil then return end local nextWayshrine = nil if Runner.data.direction == Runner.directions.cw then nextWayshrine = Runner.wayshrines[currentWayshrine].cw else nextWayshrine = Runner.wayshrines[currentWayshrine].ccw end local discovered, name = GetFastTravelNodeInfo(nextWayshrine) if not discovered then LeoDolmenRunner.log("Can't fast travel: " .. name .. " not discovered yet.") return end FastTravelToNode(nextWayshrine) end local function calculateXpPerMinute(xp, timestamp) local diff = GetDiffBetweenTimeStamps(GetTimeStamp(), timestamp) diff = (diff == 0) and 1 or diff return xp / (diff / 60) end local function TimeToLevel() local xpToLevel = 0 if (CanUnitGainChampionPoints("player")) then xpToLevel = GetNumChampionXPInChampionPoint(GetPlayerChampionPointsEarned("player")) - GetPlayerChampionXP() else xpToLevel = GetNumExperiencePointsInLevel(GetUnitLevel("player")) - GetUnitXP("player") end local minutesToLevel = math.ceil(xpToLevel / Runner.data.xpPerMinute) if minutesToLevel < 60 then return 0, minutesToLevel end local hoursToLevel = math.floor(minutesToLevel / 60) minutesToLevel = minutesToLevel - (hoursToLevel * 60) return hoursToLevel, minutesToLevel end function Runner:UpdateData() local xpGained = 0 local firstEventTimestamp = 0 if (#Runner.data.events > 0) then for i, event in pairs(Runner.data.events) do if GetDiffBetweenTimeStamps(GetTimeStamp(), event.timestamp) > Runner.timeout then Runner.data.events[i] = nil else xpGained = xpGained + event.xp if firstEventTimestamp == 0 or event.timestamp < firstEventTimestamp then firstEventTimestamp = event.timestamp end end end end Runner.data.xpPerMinute = calculateXpPerMinute(xpGained, firstEventTimestamp) if xpGained > 0 then Runner.data.hoursToLevel, Runner.data.minutesToLevel = TimeToLevel() end end function Runner:Update(tick) if not Runner.started or tick ~= 5 then return end self:UpdateData() end local assistants = { 267, -- Nuzhimeh 300, -- Fence 301, -- Tythis 6376, -- Ezabi 6378 -- Fezez } local buffs = { { id = 479, from = 1022, to = 1103 }, -- Witchmother's Whistle { id = 1167, from = 326, to = 403 }, -- The Pie of Misrule { id = 1168, from = 1217, to = 1231 }, -- Breda's Bottomless Mead Mug { id = 1168, from = 101, to = 105 }, -- Breda's Bottomless Mead Mug { id = 7619, abilityId = 136348 }, -- Jubilee Cake 2020 { id = 5886 }, -- Jubilee Cake 2019 { id = 4786 }, -- Jubilee Cake 2018 { id = 1109 }, -- Jubilee Cake 2017 { id = 356 }, -- Jubilee Cake 2016 } function Runner:IsBuffActive(collectible) if type(collectible) ~= "table" then for _, buff in ipairs(buffs) do if buff.id == collectible then collectible = buff break end end end for i = 1, GetNumBuffs("player") do local buffName, _, _, _, _, _, _, _, _, _, abilityId = GetUnitBuffInfo("player", i) if collectible.abilityId and abilityId == collectible.abilityId then return true end end return false end function Runner:OnCombatState(inCombat) if not Runner.started then return end if IsUnitDead("player") then zo_callLater(function() Runner:OnCombatState(inCombat) end, 500) return end if not inCombat and LeoDolmenRunner.settings.runner.reapplyBuff then if Runner.activeBuff == 0 then local date = tonumber(os.date("%m%d", GetTimeStamp())) for _, collectible in ipairs(buffs) do local _, _, _, _, unlocked = GetCollectibleInfo(collectible.id) if unlocked and collectible.from and collectible.from <= date and collectible.to > date then Runner.activeBuff = collectible.id break end end end if Runner.activeBuff > 0 and not self:IsBuffActive(Runner.activeBuff) then LeoDolmenRunner.log("Reapplying " .. GetCollectibleInfo(Runner.activeBuff)) UseCollectible(Runner.activeBuff) end end if inCombat and LeoDolmenRunner.settings.runner.autoDismiss then for _, id in pairs(assistants) do if IsCollectibleActive(id) then LeoDolmenRunner.log("Dismissing " .. GetCollectibleInfo(id)) UseCollectible(id) end end end end function Runner:OnExperienceGain(reason, level, previousExperience, currentExperience, championPoints) if not Runner.started then return end local event = { timestamp = GetTimeStamp(), xp = currentExperience - previousExperience } table.insert(Runner.data.events, event) Runner.data.currentDolmen = Runner.data.currentDolmen + event.xp if reason == 7 then Runner.data.dolmensClosed = Runner.data.dolmensClosed + 1 Runner.data.beforeLastDolmen = Runner.data.lastDolmen Runner.data.lastDolmen = Runner.data.currentDolmen Runner.data.currentDolmen = 0 end self:UpdateData() end function Runner:GetActiveBuff() for _, collectible in pairs(buffs) do if self:IsBuffActive(collectible) then Runner.activeBuff = collectible.id -- LeoDolmenRunner.debug("Found buff " .. Runner.activeBuff) return end end end function Runner:Initialize() Runner.activeBuff = 0 self:GetActiveBuff() end LeoDolmenRunner.runner = Runner