LeoDolmenRunner = { name = "LeoDolmenRunner", displayName = "Leo's Dolmen Runner", version = "1.0.1", chatPrefix = "|c39B027LeoDolmenRunner|r: ", hidden = true, timeout = 900, defaults = { autoTravel = false, direction = 1, started = false, run = { startedTime = 0, dolmensClosed = 0, xpPerMinute = 0, minutesToLevel = 0, hoursToLevel = 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} } } local LastUpdateTimestamp = GetTimeStamp() function LeoDolmenRunner.log(message) d(LeoDolmenRunner.chatPrefix .. message) end function LeoDolmenRunner.onFastTravelInteraction(currentWayshrine) if LeoDolmenRunner.wayshrines[currentWayshrine] and LeoDolmenRunner.data.autoTravel then local nextWayshrine = nil if LeoDolmenRunner.data.direction == LeoDolmenRunner.directions.cw then nextWayshrine = LeoDolmenRunner.wayshrines[currentWayshrine].cw else nextWayshrine = LeoDolmenRunner.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 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 / LeoDolmenRunner.data.run.xpPerMinute) if minutesToLevel < 60 then return 0, minutesToLevel end local hoursToLevel = math.floor(minutesToLevel / 60) minutesToLevel = minutesToLevel - (hoursToLevel * 60) return hoursToLevel, minutesToLevel end function LeoDolmenRunner:UpdateData() local xpGained = 0 local firstEventTimestamp = 0 if (#LeoDolmenRunner.data.run.events > 0) then for i, event in pairs(LeoDolmenRunner.data.run.events) do if GetDiffBetweenTimeStamps(GetTimeStamp(), event.timestamp) > LeoDolmenRunner.timeout then LeoDolmenRunner.data.run.events[i] = nil else xpGained = xpGained + event.xp if firstEventTimestamp == 0 or event.timestamp < firstEventTimestamp then firstEventTimestamp = event.timestamp end end end end LeoDolmenRunner.data.run.xpPerMinute = calculateXpPerMinute(xpGained, firstEventTimestamp) if xpGained > 0 then LeoDolmenRunner.data.run.hoursToLevel, LeoDolmenRunner.data.run.minutesToLevel = TimeToLevel() end end function LeoDolmenRunner:OnExperienceGain(reason, level, previousExperience, currentExperience, championPoints) local event = { timestamp = GetTimeStamp(), xp = currentExperience - previousExperience } table.insert(LeoDolmenRunner.data.run.events, event) if reason == 7 then LeoDolmenRunner.data.run.dolmensClosed = LeoDolmenRunner.data.run.dolmensClosed + 1 end self:UpdateData() end local function getColor(value, max) local rate = value / max if rate > 0.8 then return '10FF10' end if rate > 0.5 then return 'FFFF00' end return 'FF1010' end function LeoDolmenRunner:UpdateTrainingGear() local stack = 0 local stackRepair = 0 local maxItems = 9 local numItems = 0 local bag = SHARED_INVENTORY:GenerateFullSlotData(nil,BAG_WORN) for _, data in pairs(bag) do local link = GetItemLink(data.bagId, data.slotIndex) if GetItemLinkTraitType(link) == ITEM_TRAIT_TYPE_ARMOR_TRAINING or GetItemLinkTraitType(link) == ITEM_TRAIT_TYPE_WEAPON_TRAINING then local activeWeaponPair = GetActiveWeaponPairInfo() if ( GetItemLinkItemType(link) == ITEMTYPE_ARMOR or (activeWeaponPair == ACTIVE_WEAPON_PAIR_MAIN and (data.slotIndex == EQUIP_SLOT_MAIN_HAND or data.slotIndex == EQUIP_SLOT_OFF_HAND)) or (activeWeaponPair == ACTIVE_WEAPON_PAIR_BACKUP and (data.slotIndex == EQUIP_SLOT_BACKUP_MAIN or data.slotIndex == EQUIP_SLOT_BACKUP_OFF))) then local quality = select(8, GetItemInfo(data.bagId, data.slotIndex)) local condition = GetItemCondition(data.bagId, data.slotIndex) local equipType = select(6, GetItemInfo(data.bagId, data.slotIndex)) numItems = numItems + 1 local xp = 0 if GetItemLinkItemType(link) == ITEMTYPE_ARMOR then xp = 6 + quality elseif equipType == EQUIP_TYPE_TWO_HAND then numItems = numItems + 1 xp = 4 + quality else xp = 2 + (0.5 * quality) end stack = stack + xp if condition == 0 then stackRepair = stackRepair + xp end end end end local text = "" if stackRepair > 0 then text = "|c" .. getColor(stack - stackRepair, stack) .. " " .. (stack - stackRepair) .. " / " .. stack .. "%|r (Repair)" else text = "|c" .. getColor(stack, stack) .. stack .. "%|r" end LeoDolmenRunnerWindowPanelXpFromGear:SetText(text) LeoDolmenRunnerWindowPanelTrainingGear:SetText("|c" .. getColor(numItems, maxItems) .. numItems .. " / " .. maxItems .. "|r") end function LeoDolmenRunner:CreateUI() if LeoDolmenRunner.data.started then LeoDolmenRunnerWindowPanelStartStopLabel:SetText("Stop") else LeoDolmenRunnerWindowPanelStartStopLabel:SetText("Start") end self:UpdateTrainingGear() end local function FormatNumber(num) return zo_strformat("<<1>>", ZO_LocalizeDecimalNumber(num)) end local function FormatTime(seconds, short, colorizeCountdown) if short == nil then short = false end local formats = { dhm = SI_TIME_FORMAT_DDHHMM_DESC_SHORT, day = SI_TIME_FORMAT_DAYS, hm = SI_TIME_FORMAT_HHMM_DESC_SHORT, hms = SI_TIME_FORMAT_HHMMSS_DESC_SHORT, hour = SI_TIME_FORMAT_HOURS, ms = SI_TIME_FORMAT_MMSS_DESC_SHORT, m = SI_TIME_FORMAT_MINUTES } if seconds and seconds > 0 then local ss = seconds % 60 local mm = math.floor(seconds / 60) local hh = math.floor(mm / 60) mm = mm % 60 local dn = math.floor(hh / 24) local hhdn = hh - (dn*24) local ssF = string.format("%02d", ss) local mmF = string.format("%02d", mm) local hhF = string.format("%02d", hh) local hhdnF = string.format("%02d", hhdn) local result = '' if dn > 0 then if short then result = ZO_CachedStrFormat(GetString(formats.day), dn) .." "..ZO_CachedStrFormat(GetString(formats.hour), hhdnF) else result = ZO_CachedStrFormat(GetString(formats.dhm), dn, hhdnF, mmF) end elseif hh > 0 then if short then result = ZO_CachedStrFormat(GetString(formats.hm), hhF, mmF) else result = ZO_CachedStrFormat(GetString(formats.hms), hhF, mmF, ssF) end elseif mm >= 0 then result = ZO_CachedStrFormat(GetString(formats.ms), mmF, ssF) end return result else return ZO_CachedStrFormat(GetString(formats.m), 0) end end function LeoDolmenRunner:UpdateUI() self:UpdateTrainingGear() local currentTimestamp = GetTimeStamp() if LeoDolmenRunner.data.started then if GetDiffBetweenTimeStamps(currentTimestamp, LastUpdateTimestamp) >= 5 then self:UpdateData() LastUpdateTimestamp = currentTimestamp end local secs = currentTimestamp - LeoDolmenRunner.data.run.startedTime if secs > 0 then LeoDolmenRunnerWindowPanelTime:SetText(FormatTime(secs)) end end LeoDolmenRunnerWindowPanelDolmensClosed:SetText(LeoDolmenRunner.data.run.dolmensClosed) LeoDolmenRunnerWindowPanelXP:SetText(FormatNumber(math.ceil(LeoDolmenRunner.data.run.xpPerMinute)) .. " / min") LeoDolmenRunnerWindowPanelXPNext:SetText(ZO_CachedStrFormat(GetString(SI_TIME_FORMAT_HHMM_DESC_SHORT), LeoDolmenRunner.data.run.hoursToLevel, LeoDolmenRunner.data.run.minutesToLevel)) zo_callLater(function() self:UpdateUI() end, 1000) end function LeoDolmenRunner:StartStop() if LeoDolmenRunner.data.started then LeoDolmenRunner.data.started = false LeoDolmenRunner.data.autoTravel = false LeoDolmenRunnerWindowPanelStartStopLabel:SetText("Start") else LeoDolmenRunner.data.run = LeoDolmenRunner.defaults.run LeoDolmenRunner.data.run.startedTime = GetTimeStamp() LeoDolmenRunner.data.started = true LeoDolmenRunner.data.autoTravel = true LeoDolmenRunnerWindowPanelStartStopLabel:SetText("Stop") end end function LeoDolmenRunner:OnWindowMoveStop() LeoDolmenRunner.data.position = { left = LeoDolmenRunnerWindow:GetLeft(), top = LeoDolmenRunnerWindow:GetTop() } end function LeoDolmenRunner:RestorePosition() local position = LeoDolmenRunner.data.position or { left = 200; top = 200; } local left = position.left local top = position.top LeoDolmenRunnerWindow:ClearAnchors() LeoDolmenRunnerWindow:SetAnchor(TOPLEFT, GuiRoot, TOPLEFT, left, top) LeoDolmenRunnerWindow:SetDrawTier(DT_MEDIUM) end function LeoDolmenRunner:Initialize() local showButton, feedbackWindow = LibFeedback:initializeFeedbackWindow(LeoDolmenRunner, LeoDolmenRunner.name,LeoDolmenRunnerWindow, "@LeandroSilva", {TOPRIGHT, LeoDolmenRunnerWindow, TOPRIGHT,-50,3}, {0,1000,10000,"https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=Y9KM4PZU2UZ6A"}, "If you found a bug, have a request or a suggestion, or simply wish to donate, send a mail.") LeoDolmenRunner.feedback = feedbackWindow LeoDolmenRunner.feedback:SetDrawLayer(DL_OVERLAY) LeoDolmenRunner.feedback:SetDrawTier(DT_MEDIUM) LeoDolmenRunnerWindowTitle:SetText(LeoDolmenRunner.displayName .. " v" .. LeoDolmenRunner.version) SLASH_COMMANDS["/ldr"] = function(cmd) LeoDolmenRunnerWindow:ToggleHidden() end if GetDisplayName() == "@LeandroSilva" then SLASH_COMMANDS["/rr"] = function(cmd) ReloadUI() end end self:RestorePosition() self:CreateUI() self:UpdateUI() end local function OnAddOnLoaded(event, addonName) if addonName ~= LeoDolmenRunner.name then return end EVENT_MANAGER:UnregisterForEvent(LeoDolmenRunner.name, EVENT_ADD_ON_LOADED) LeoDolmenRunner.data = LibSavedVars:NewCharacterSettings( LeoDolmenRunner.name .. "_Data", "Characters", LeoDolmenRunner.defaults ) LeoDolmenRunner:Initialize() CALLBACK_MANAGER:RegisterCallback("LAM-PanelControlsCreated", OnSettingsControlsCreated) EVENT_MANAGER:RegisterForEvent(LeoDolmenRunner.name, EVENT_START_FAST_TRAVEL_INTERACTION, function(eventId, ...) LeoDolmenRunner.onFastTravelInteraction(...) end) EVENT_MANAGER:RegisterForEvent(LeoDolmenRunner.name, EVENT_EXPERIENCE_GAIN, function(eventId, ...) LeoDolmenRunner:OnExperienceGain(...) end) LeoDolmenRunner.log("started.") end EVENT_MANAGER:RegisterForEvent(LeoDolmenRunner.name, EVENT_ADD_ON_LOADED, OnAddOnLoaded)