Загрузить файлы в «/»
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
# Ade Alts
|
||||
|
||||
Alt manager for OctoWoW (1.12 client). It remembers everything about all your characters,
|
||||
including those on other accounts, and shows it in one window.
|
||||
|
||||
- Profession cooldowns – Salt Shaker, Transmutes, Mooncloth; "ready" popup at login.
|
||||
- Alt list – level, class icon, sorted by level or name.
|
||||
- Inventory and bank of every alt, plus **item search** across all of them.
|
||||
- Total gold of all alts.
|
||||
- Professions – skill levels and known recipes (also searchable).
|
||||
- Equipped gear of every alt (exclude cosmetic - tabard and shirt).
|
||||
- Minimap button – docks into pfUI's addon button panel automatically.
|
||||
- Settings – window scale, width, height, background opacity, background colour, cooldown-window opacity.
|
||||
|
||||
## Requirements
|
||||
|
||||
- OctoWoW / any 1.12 client.
|
||||
- Optional but recommended: **[SuperWoW](https://github.com/balakethelock/SuperWoW)** – lets all accounts in the same game folder share data through `imports\AdeAlts.txt`. Without it, only cooldowns sync (guild / party / raid chat).
|
||||
- Optional: pfUI (minimap button docking and skin).
|
||||
|
||||
## Installation
|
||||
|
||||
1. Put the `AdeAlts` folder into `<WoW folder>\Interface\AddOns\` (the folder must be named `AdeAlts` and contain `AdeAlts.toc`).
|
||||
2. Log in with each character once so it gets cached (cached only inside one client\game folder).
|
||||
3. Updating: replace the whole folder. Saved data is kept (`WTF\...\SavedVariables\AdeAlts.lua`).
|
||||
|
||||
## Usage
|
||||
|
||||
Left-click the minimap button (or `/ade`) opens the alt manager. Right-click the button forces a sync.
|
||||
The button tooltip lists only characters that have cooldowns.
|
||||
|
||||
Open each profession window once per character (clear its filters first) so its recipes and cooldowns are recorded.
|
||||
Fishing, Skinning and Herbalism have no recipes and need nothing.
|
||||
|
||||
## Slash commands
|
||||
|
||||
Every alias works the same: `/ade`, `/adealts`, `/pcd`, `/profcd`, `/cdt`.
|
||||
|
||||
| Command | What it does |
|
||||
|---|---|
|
||||
| `/ade` (also `show`, `toggle`, `alts`) | Open / close the alt manager window |
|
||||
| `/ade cd` (also `cooldowns`) | Open / close the profession cooldowns window |
|
||||
| `/ade find <text>` | Open the manager and search items and recipes across all alts |
|
||||
| `/ade list` | Print all cooldowns to the chat frame |
|
||||
| `/ade sync` | Sync now: shared file (SuperWoW) and a chat request to guild/party/raid |
|
||||
| `/ade scan` | Rescan this character (level, gold, bags, gear, professions) and print what the client reports |
|
||||
| `/ade status` | Show whether SuperWoW file sync is on and how many characters are known |
|
||||
| `/ade export` | Open a text box with all data (copy it to move data by hand) |
|
||||
| `/ade import` | Open a text box to paste exported data into |
|
||||
| `/ade minimap` | Show / hide the minimap button |
|
||||
| `/ade alert` | Turn the login "cooldown ready" popup on / off |
|
||||
| `/ade test` | Show the "ready" popup now (also when nothing is ready) |
|
||||
| `/ade debug` | Print raw cooldown values of tracked bag items and stored cooldowns (for troubleshooting) |
|
||||
| `/ade reset` | Set all cooldowns of the current character to Ready |
|
||||
| `/ade del <name>` | Remove a cached character (not the one you are logged in on) |
|
||||
|
||||
## How data is shared
|
||||
|
||||
Each character stores its own record (gold, bags, bank, gear, professions, cooldowns). With SuperWoW all accounts
|
||||
read and write one shared file, `<WoW folder>\imports\AdeAlts.txt`, and the newest data per character wins.
|
||||
Without SuperWoW, cooldowns are also exchanged through guild / party / raid addon messages.
|
||||
|
||||
## Notes
|
||||
|
||||
- Bank contents are updated when you open the bank.
|
||||
- Salt Shaker can occur bug, use /ade scan if it's occur and report if /ade scan didn't help.
|
||||
- Written for Lua 5.0 (1.12 client); the code is split into several files that share one global table `AdeAlts`.
|
||||
|
||||
## Changelog
|
||||
|
||||
See [CHANGELOG.md](CHANGELOG.md).
|
||||
@@ -0,0 +1,125 @@
|
||||
-- Ade Alts: core
|
||||
AdeAlts = {}
|
||||
local AA = AdeAlts
|
||||
|
||||
-- AdeAlts : profession cooldown tracker for Turtle WoW / 1.12 clients (Lua 5.0)
|
||||
-- Tracks Salt Shaker, Transmutes, Mooncloth (and any "Transmute: ..." recipe) per character.
|
||||
-- Command: /pcd Minimap button: auto-docked by pfUI's addon button panel.
|
||||
|
||||
AA.ADDON = "AdeAlts"
|
||||
AA.VERSION = "1.0"
|
||||
AA.PREFIX = "PCDT"
|
||||
AA.DAY = 86400
|
||||
local HOUR = 3600
|
||||
|
||||
-- Fallback durations (seconds). The real remaining time is read from the recipe tooltip when possible.
|
||||
AA.DEFAULTS = {
|
||||
["Mooncloth"] = 4 * AA.DAY,
|
||||
["Transmute: Arcanite"] = 2 * AA.DAY,
|
||||
["Transmute: Iron to Gold"] = 2 * AA.DAY,
|
||||
["Transmute: Mithril to Truesilver"] = 2 * AA.DAY,
|
||||
}
|
||||
|
||||
-- Items tracked through their bag cooldown.
|
||||
AA.ITEMS = {
|
||||
[15846] = { key = "Salt Shaker", cd = 3 * AA.DAY },
|
||||
}
|
||||
|
||||
-- max possible cooldown per item key; anything beyond it is a bogus client value
|
||||
local ITEMCD = {}
|
||||
AA.ITEMNAME = {}
|
||||
for id, it in pairs(AA.ITEMS) do ITEMCD[it.key] = it.cd; AA.ITEMNAME[string.lower(it.key)] = it end
|
||||
|
||||
function AA.ClampRecord(c)
|
||||
local changed = false
|
||||
local lim = time()
|
||||
for key, cd in pairs(ITEMCD) do
|
||||
local e = c.cds and c.cds[key]
|
||||
if e and e > lim + cd + 60 then
|
||||
c.cds[key] = lim + cd
|
||||
changed = true
|
||||
end
|
||||
end
|
||||
return changed
|
||||
end
|
||||
|
||||
-- the hunter "Survival" talent tree is also a skill line (rank 1/1); the real profession has a max of 75+
|
||||
function AA.BadProf(name, maxr)
|
||||
return name == "Survival" and (not maxr or maxr <= 1)
|
||||
end
|
||||
|
||||
-- professions without a recipe list (nothing to scan)
|
||||
AA.NORECIPES = { Fishing = 1, Skinning = 1, Herbalism = 1 }
|
||||
|
||||
AA.timers = {}
|
||||
AA.prov = {}
|
||||
local alerted = {}
|
||||
AA.scanning, AA.dirty = false, false
|
||||
AA.lastScan, AA.lastReply, AA.lastBroadcast = 0, 0, 0
|
||||
AA.bagDirty = false
|
||||
AA.loaded = false
|
||||
AA.fileDirty, AA.lastFileWrite = false, 0
|
||||
AA.LEGACY = "ProfCDTracker"
|
||||
AA.FILE = "AdeAlts" -- <WoW folder>\imports\AdeAlts.txt (SuperWoW)
|
||||
|
||||
----------------------------------------------------------------------
|
||||
-- helpers
|
||||
----------------------------------------------------------------------
|
||||
function AA.Print(m)
|
||||
DEFAULT_CHAT_FRAME:AddMessage("|cff33ffccAdeAlts|r: " .. m)
|
||||
end
|
||||
|
||||
function AA.After(sec, fn)
|
||||
table.insert(AA.timers, { t = GetTime() + sec, fn = fn })
|
||||
end
|
||||
|
||||
function AA.IsTracked(name)
|
||||
return AA.DEFAULTS[name] or string.find(name, "^Transmute")
|
||||
end
|
||||
|
||||
function AA.FormatTime(s)
|
||||
s = math.floor(s)
|
||||
local d = math.floor(s / AA.DAY)
|
||||
local h = math.floor(math.mod(s, AA.DAY) / HOUR)
|
||||
local m = math.floor(math.mod(s, HOUR) / 60)
|
||||
if d > 0 then return d .. "d " .. h .. "h" end
|
||||
if h > 0 then return h .. "h " .. m .. "m" end
|
||||
if m > 0 then return m .. "m" end
|
||||
return s .. "s"
|
||||
end
|
||||
|
||||
function AA.ClassHex(class)
|
||||
local col = RAID_CLASS_COLORS and class and RAID_CLASS_COLORS[class]
|
||||
if col then
|
||||
return string.format("%02x%02x%02x", math.floor(col.r * 255), math.floor(col.g * 255), math.floor(col.b * 255))
|
||||
end
|
||||
return "ffffff"
|
||||
end
|
||||
|
||||
function AA.ParseDuration(text)
|
||||
local total, found = 0, false
|
||||
for n, u in string.gfind(text, "(%d+)%s*(%a+)") do
|
||||
n = tonumber(n)
|
||||
u = string.lower(u)
|
||||
local fm
|
||||
if string.find(u, "^d") then fm = AA.DAY
|
||||
elseif string.find(u, "^h") then fm = HOUR
|
||||
elseif string.find(u, "^m") then fm = 60
|
||||
elseif string.find(u, "^s") then fm = 1 end
|
||||
if AA.f then total = total + n * AA.f; found = true end
|
||||
end
|
||||
if found then return total end
|
||||
return nil
|
||||
end
|
||||
|
||||
function AA.SetCD(key, exp)
|
||||
if not AA.me then return end
|
||||
exp = math.floor(exp)
|
||||
local old = AA.me.cds[key]
|
||||
if old == nil or math.abs(old - exp) > 5 or ((old == 0) ~= (exp == 0)) then
|
||||
AA.me.cds[key] = exp
|
||||
AA.me.updated = time()
|
||||
AA.dirty = true
|
||||
end
|
||||
end
|
||||
|
||||
+271
@@ -0,0 +1,271 @@
|
||||
-- Ade Alts: events
|
||||
local AA = AdeAlts
|
||||
|
||||
----------------------------------------------------------------------
|
||||
-- driver: timers, ready alerts, delayed broadcast
|
||||
----------------------------------------------------------------------
|
||||
local driver = CreateFrame("Frame")
|
||||
local acc, filet, metat = 0, 0, 0
|
||||
driver:SetScript("OnUpdate", function()
|
||||
acc = acc + arg1
|
||||
if acc < 1 then return end
|
||||
acc = 0
|
||||
local now = GetTime()
|
||||
|
||||
for i = table.getn(AA.timers), 1, -1 do
|
||||
local t = AA.timers[i]
|
||||
if t and t.t <= now then
|
||||
table.remove(AA.timers, i)
|
||||
t.fn()
|
||||
end
|
||||
end
|
||||
|
||||
if not AA.me then return end
|
||||
|
||||
if AA.bagDirty then
|
||||
AA.bagDirty = false
|
||||
AA.ScanBags()
|
||||
AA.ScanSelf()
|
||||
end
|
||||
|
||||
if AA.fileDirty and now - AA.lastFileWrite > 10 then
|
||||
AA.FileSync()
|
||||
end
|
||||
|
||||
-- ready alerts for the logged-in character
|
||||
local nowt = time()
|
||||
for key, exp in pairs(AA.me.cds) do
|
||||
if exp > 0 and exp <= nowt then
|
||||
AA.Print("|cff40ff40" .. key .. " is ready!|r")
|
||||
AA.me.cds[key] = 0
|
||||
AA.me.updated = time()
|
||||
AA.dirty = true
|
||||
end
|
||||
end
|
||||
|
||||
metat = metat + 1
|
||||
if metat >= 30 then
|
||||
metat = 0
|
||||
AA.ScanSelf()
|
||||
end
|
||||
|
||||
filet = filet + 1
|
||||
if filet >= 20 then
|
||||
filet = 0
|
||||
AA.FileMerge()
|
||||
end
|
||||
|
||||
if AA.dirty and now - AA.lastBroadcast > 3 then
|
||||
AA.dirty = false
|
||||
AA.Broadcast()
|
||||
AA.FileSync()
|
||||
end
|
||||
end)
|
||||
|
||||
----------------------------------------------------------------------
|
||||
-- events
|
||||
----------------------------------------------------------------------
|
||||
local ev = CreateFrame("Frame")
|
||||
ev:RegisterEvent("ADDON_LOADED")
|
||||
ev:RegisterEvent("PLAYER_ENTERING_WORLD")
|
||||
ev:RegisterEvent("TRADE_SKILL_SHOW")
|
||||
ev:RegisterEvent("TRADE_SKILL_UPDATE")
|
||||
ev:RegisterEvent("BAG_UPDATE")
|
||||
ev:RegisterEvent("BAG_UPDATE_COOLDOWN")
|
||||
ev:RegisterEvent("BANKFRAME_OPENED")
|
||||
ev:RegisterEvent("BANKFRAME_CLOSED")
|
||||
ev:RegisterEvent("PLAYER_MONEY")
|
||||
ev:RegisterEvent("PLAYER_LEVEL_UP")
|
||||
ev:RegisterEvent("PLAYER_LOGOUT")
|
||||
ev:RegisterEvent("UNIT_INVENTORY_CHANGED")
|
||||
ev:RegisterEvent("SKILL_LINES_CHANGED")
|
||||
ev:RegisterEvent("CRAFT_SHOW")
|
||||
ev:RegisterEvent("CRAFT_UPDATE")
|
||||
ev:RegisterEvent("CHAT_MSG_ADDON")
|
||||
|
||||
ev:SetScript("OnEvent", function()
|
||||
if event == "ADDON_LOADED" then
|
||||
if arg1 == AA.ADDON then
|
||||
if not AdeAltsDB then
|
||||
-- migrate from the old ProfCD Tracker saved variables, if present
|
||||
AdeAltsDB = ProfCDTrackerDB or {}
|
||||
end
|
||||
ProfCDTrackerDB = nil
|
||||
AA.db = AdeAltsDB
|
||||
AA.db.chars = AA.db.chars or {}
|
||||
AA.db.items = AA.db.items or {}
|
||||
-- drop the hunter Survival talent line that older versions stored as a profession
|
||||
for _, ch in pairs(AA.db.chars) do
|
||||
local sv = ch.prof and ch.prof.Survival
|
||||
if sv and AA.BadProf("Survival", sv.m) then ch.prof.Survival = nil end
|
||||
end
|
||||
AA.ApplyConfig()
|
||||
AA.db.angle = AA.db.angle or 200
|
||||
if AA.db.hidden then AA.btn:Hide() end
|
||||
AA.PlaceButton()
|
||||
end
|
||||
|
||||
elseif event == "PLAYER_ENTERING_WORLD" then
|
||||
if not AA.db then return end
|
||||
if not AA.loaded then
|
||||
AA.loaded = true
|
||||
local name = UnitName("player")
|
||||
local realm = GetRealmName()
|
||||
local _, class = UnitClass("player")
|
||||
AA.myKey = name .. "-" .. realm
|
||||
AA.me = AA.db.chars[AA.myKey]
|
||||
if not AA.me then
|
||||
AA.me = { name = name, realm = realm, cds = {} }
|
||||
AA.db.chars[AA.myKey] = AA.me
|
||||
end
|
||||
AA.me.class = class
|
||||
AA.me.remote = nil
|
||||
AA.me.updated = AA.me.updated or time()
|
||||
if AA.ClampRecord(AA.me) then AA.me.updated = time(); AA.dirty = true end
|
||||
AA.me.cds = AA.me.cds or {}
|
||||
AA.StyleButton()
|
||||
AA.enterTime = GetTime()
|
||||
AA.After(2, function() AA.ScanBags(); AA.ScanSelf() end)
|
||||
AA.After(5, function() AA.ScanSelf() end)
|
||||
AA.After(8, function() AA.ScanSelf() end)
|
||||
AA.After(13, function() AA.ScanSelf() end)
|
||||
AA.After(20, function() AA.ScanSelf() end)
|
||||
AA.After(4, AA.DockInPfUI)
|
||||
AA.After(10, function() AA.Send("R"); AA.Broadcast() end)
|
||||
AA.After(3, AA.FileSync)
|
||||
AA.After(6, AA.ShowReadyAlert)
|
||||
end
|
||||
AA.bagDirty = true
|
||||
|
||||
elseif event == "TRADE_SKILL_SHOW" or event == "TRADE_SKILL_UPDATE" then
|
||||
AA.ScanTradeSkills()
|
||||
|
||||
elseif event == "CRAFT_SHOW" or event == "CRAFT_UPDATE" then
|
||||
AA.ScanCrafts()
|
||||
|
||||
elseif event == "UNIT_INVENTORY_CHANGED" then
|
||||
if arg1 == "player" then AA.bagDirty = true end
|
||||
|
||||
elseif event == "SKILL_LINES_CHANGED" then
|
||||
AA.bagDirty = true
|
||||
|
||||
elseif event == "BANKFRAME_OPENED" then
|
||||
AA.bankOpen = true
|
||||
AA.bagDirty = true
|
||||
elseif event == "BANKFRAME_CLOSED" then
|
||||
AA.ScanSelf()
|
||||
AA.bankOpen = false
|
||||
elseif event == "PLAYER_MONEY" or event == "PLAYER_LEVEL_UP" then
|
||||
AA.bagDirty = true
|
||||
elseif event == "PLAYER_LOGOUT" then
|
||||
if AA.me then AA.FileSync() end
|
||||
elseif event == "BAG_UPDATE" or event == "BAG_UPDATE_COOLDOWN" then
|
||||
AA.bagDirty = true
|
||||
|
||||
elseif event == "CHAT_MSG_ADDON" then
|
||||
if arg1 == AA.PREFIX and AA.db and arg4 ~= UnitName("player") then
|
||||
if arg2 == "R" then
|
||||
if AA.me and GetTime() - AA.lastReply > 5 then
|
||||
AA.lastReply = GetTime()
|
||||
AA.Broadcast()
|
||||
end
|
||||
else
|
||||
AA.ReceiveRecord(arg2)
|
||||
AA.Refresh()
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
----------------------------------------------------------------------
|
||||
-- slash commands
|
||||
----------------------------------------------------------------------
|
||||
SLASH_PROFCD1 = "/pcd"
|
||||
SLASH_PROFCD2 = "/profcd"
|
||||
SLASH_PROFCD3 = "/cdt"
|
||||
SLASH_PROFCD4 = "/ade"
|
||||
SLASH_PROFCD5 = "/adealts"
|
||||
SlashCmdList["PROFCD"] = function(msg)
|
||||
msg = msg or ""
|
||||
local _, _, cmd, arg = string.find(msg, "^%s*(%S*)%s*(.-)%s*$")
|
||||
cmd = string.lower(cmd or "")
|
||||
|
||||
if cmd == "" or cmd == "show" or cmd == "toggle" or cmd == "alts" then
|
||||
AA.ToggleManager()
|
||||
elseif cmd == "cd" or cmd == "cooldowns" then
|
||||
AA.ToggleWindow()
|
||||
elseif cmd == "find" and arg ~= "" then
|
||||
AA.sbox:SetText(arg)
|
||||
if not AA.M:IsVisible() then AA.M:Show() end
|
||||
elseif cmd == "list" then
|
||||
for _, e in ipairs(AA.Entries()) do AA.Print(e.l .. " " .. e.r) end
|
||||
elseif cmd == "sync" then
|
||||
AA.Send("R")
|
||||
AA.Broadcast()
|
||||
AA.FileSync()
|
||||
AA.Print("synced (shared file: " .. ((ImportFile and ExportFile) and "on" or "off") .. ", chat request sent).")
|
||||
elseif cmd == "scan" then
|
||||
AA.Print("client reports: level=" .. tostring(UnitLevel("player")) .. " gold(copper)=" .. tostring(GetMoney()))
|
||||
AA.ScanSelf()
|
||||
AA.ScanBags(true)
|
||||
AA.FileSync()
|
||||
elseif cmd == "status" then
|
||||
if ImportFile and ExportFile then
|
||||
AA.Print("SuperWoW file sync: |cff40ff40ON|r (imports\\" .. AA.FILE .. ".txt, shared by all accounts in this game folder)")
|
||||
else
|
||||
AA.Print("SuperWoW file sync: |cffff6060OFF|r (SuperWoW not detected). Only guild/party/raid chat sync works.")
|
||||
end
|
||||
local n = 0
|
||||
for k in pairs(AA.db.chars) do n = n + 1 end
|
||||
AA.Print(n .. " character(s) known.")
|
||||
elseif cmd == "export" then
|
||||
AA.ShowIO(true)
|
||||
elseif cmd == "import" then
|
||||
AA.ShowIO(false)
|
||||
elseif cmd == "minimap" then
|
||||
AA.db.hidden = not AA.db.hidden
|
||||
if AA.db.hidden then AA.btn:Hide() else AA.btn:Show(); AA.After(1, AA.DockInPfUI) end
|
||||
AA.Print("minimap button " .. (AA.db.hidden and "hidden" or "shown") .. ".")
|
||||
elseif cmd == "alert" then
|
||||
AA.db.noAlert = not AA.db.noAlert
|
||||
AA.Print("login alert " .. (AA.db.noAlert and "disabled" or "enabled") .. ".")
|
||||
elseif cmd == "test" then
|
||||
AA.ShowReadyAlert(true)
|
||||
elseif cmd == "debug" then
|
||||
local found = 0
|
||||
for bag = 0, 4 do
|
||||
for slot = 1, GetContainerNumSlots(bag) do
|
||||
local link = GetContainerItemLink(bag, slot)
|
||||
local it = AA.ItemFromLink(link)
|
||||
if it then
|
||||
found = found + 1
|
||||
local st, du = GetContainerItemCooldown(bag, slot)
|
||||
AA.Print(it.key .. " in bag " .. bag .. " slot " .. slot .. ": start=" .. tostring(st) .. " duration=" .. tostring(du) .. " now=" .. GetTime() .. " remaining=" .. tostring((st or 0) + (du or 0) - GetTime()))
|
||||
end
|
||||
end
|
||||
end
|
||||
if found == 0 then AA.Print("no tracked item found in bags 0-4 (is it in the bank?)") end
|
||||
for key, exp in pairs(AA.me.cds) do
|
||||
AA.Print("stored " .. key .. ": " .. (exp > 0 and (AA.FormatTime(exp - time()) .. " left") or "ready"))
|
||||
end
|
||||
AA.Print("file sync: " .. ((ImportFile and ExportFile) and "on" or "off"))
|
||||
elseif cmd == "reset" then
|
||||
for k in pairs(AA.me.cds) do AA.me.cds[k] = 0 end
|
||||
AA.dirty = true
|
||||
AA.Print("cooldowns of this character reset to Ready.")
|
||||
elseif cmd == "del" and arg ~= "" then
|
||||
local a = string.lower(arg)
|
||||
local n = 0
|
||||
for k, c in pairs(AA.db.chars) do
|
||||
if k ~= AA.myKey and (string.lower(c.name) == a or string.lower(k) == a) then
|
||||
AA.db.chars[k] = nil
|
||||
n = n + 1
|
||||
end
|
||||
end
|
||||
AA.Print("removed " .. n .. " character(s).")
|
||||
AA.Refresh()
|
||||
else
|
||||
AA.Print("/ade - alts window | cd (cooldowns) | find <item> | list | sync | scan | status | debug | export | import | minimap | alert | reset | del <name>")
|
||||
end
|
||||
end
|
||||
|
||||
+111
@@ -0,0 +1,111 @@
|
||||
-- Ade Alts: minimap
|
||||
local AA = AdeAlts
|
||||
|
||||
----------------------------------------------------------------------
|
||||
-- minimap button (pfUI docks it automatically into its addon panel)
|
||||
----------------------------------------------------------------------
|
||||
AA.btn = CreateFrame("Button", "AdeAltsMinimapButton", Minimap)
|
||||
AA.btn:SetWidth(31)
|
||||
AA.btn:SetHeight(31)
|
||||
AA.btn:SetFrameStrata("MEDIUM")
|
||||
AA.btn:SetFrameLevel(8)
|
||||
AA.btn:RegisterForClicks("LeftButtonUp", "RightButtonUp")
|
||||
AA.btn:RegisterForDrag("LeftButton")
|
||||
|
||||
local icon = AA.btn:CreateTexture(nil, "BACKGROUND")
|
||||
icon:SetTexture("Interface\\Icons\\Trade_Alchemy")
|
||||
local border = AA.btn:CreateTexture(nil, "OVERLAY")
|
||||
border:SetTexture("Interface\\Minimap\\MiniMap-TrackingBorder")
|
||||
|
||||
function AA.StyleButton()
|
||||
if pfUI and pfUI.api and pfUI.api.CreateBackdrop then
|
||||
-- pfUI look: flat icon inside a pfUI backdrop, same as other docked buttons
|
||||
if not AA.btn.pcdBackdrop then
|
||||
AA.btn.pcdBackdrop = true
|
||||
pcall(pfUI.api.CreateBackdrop, AA.btn, nil, true)
|
||||
end
|
||||
icon:SetDrawLayer("ARTWORK")
|
||||
icon:ClearAllPoints()
|
||||
icon:SetPoint("TOPLEFT", AA.btn, "TOPLEFT", 3, -3)
|
||||
icon:SetPoint("BOTTOMRIGHT", AA.btn, "BOTTOMRIGHT", -3, 3)
|
||||
icon:SetTexCoord(0.08, 0.92, 0.08, 0.92)
|
||||
border:Hide()
|
||||
AA.btn:SetHighlightTexture("Interface\\Buttons\\ButtonHilight-Square")
|
||||
elseif pfUI then
|
||||
icon:SetDrawLayer("ARTWORK")
|
||||
icon:ClearAllPoints()
|
||||
icon:SetAllPoints(AA.btn)
|
||||
icon:SetTexCoord(0.08, 0.92, 0.08, 0.92)
|
||||
border:Hide()
|
||||
AA.btn:SetHighlightTexture("Interface\\Buttons\\ButtonHilight-Square")
|
||||
else
|
||||
icon:SetDrawLayer("BACKGROUND")
|
||||
icon:ClearAllPoints()
|
||||
icon:SetWidth(20); icon:SetHeight(20)
|
||||
icon:SetPoint("TOPLEFT", AA.btn, "TOPLEFT", 6, -5)
|
||||
icon:SetTexCoord(0, 1, 0, 1)
|
||||
border:ClearAllPoints()
|
||||
border:SetWidth(53); border:SetHeight(53)
|
||||
border:SetPoint("TOPLEFT", AA.btn, "TOPLEFT", 0, 0)
|
||||
border:Show()
|
||||
AA.btn:SetHighlightTexture("Interface\\Minimap\\UI-Minimap-ZoomButton-Highlight")
|
||||
end
|
||||
end
|
||||
AA.StyleButton()
|
||||
|
||||
function AA.PlaceButton()
|
||||
if AA.btn:GetParent() ~= Minimap then return end -- docked by pfUI
|
||||
local angle = (AA.db and AA.db.angle) or 200
|
||||
local r = Minimap:GetWidth() / 2 + 6
|
||||
local rad = math.rad(angle)
|
||||
AA.btn:ClearAllPoints()
|
||||
AA.btn:SetPoint("CENTER", Minimap, "CENTER", math.cos(rad) * r, math.sin(rad) * r)
|
||||
end
|
||||
AA.PlaceButton()
|
||||
|
||||
AA.btn:SetScript("OnDragStart", function()
|
||||
if this:GetParent() ~= Minimap then return end
|
||||
this:SetScript("OnUpdate", function()
|
||||
local mx, my = GetCursorPosition()
|
||||
local sc = Minimap:GetEffectiveScale()
|
||||
local cx, cy = Minimap:GetCenter()
|
||||
AA.db.angle = math.deg(math.atan2(my / sc - cy, mx / sc - cx))
|
||||
AA.PlaceButton()
|
||||
end)
|
||||
end)
|
||||
AA.btn:SetScript("OnDragStop", function() this:SetScript("OnUpdate", nil) end)
|
||||
|
||||
AA.btn:SetScript("OnClick", function()
|
||||
if arg1 == "RightButton" then
|
||||
AA.Send("R")
|
||||
AA.Broadcast()
|
||||
AA.FileSync()
|
||||
AA.Print("synced.")
|
||||
else
|
||||
AA.ToggleManager()
|
||||
end
|
||||
end)
|
||||
|
||||
AA.btn:SetScript("OnEnter", function()
|
||||
GameTooltip:SetOwner(this, "ANCHOR_LEFT")
|
||||
GameTooltip:AddLine("Ade Alts - cooldowns")
|
||||
if AA.db then
|
||||
local list = AA.Entries(true)
|
||||
if table.getn(list) == 0 then
|
||||
GameTooltip:AddLine("|cff808080No cooldowns recorded yet|r")
|
||||
end
|
||||
for _, e in ipairs(list) do
|
||||
GameTooltip:AddDoubleLine(e.l, e.r, 1, 1, 1, 1, 1, 1)
|
||||
end
|
||||
end
|
||||
GameTooltip:AddLine("|cff808080Left: window Right: sync Drag: move|r")
|
||||
GameTooltip:Show()
|
||||
end)
|
||||
AA.btn:SetScript("OnLeave", function() GameTooltip:Hide() end)
|
||||
|
||||
function AA.DockInPfUI()
|
||||
if pfUI and pfUI.addonbuttons and pfUI.addonbuttons.ProcessButtons then
|
||||
pcall(function() pfUI.addonbuttons:ProcessButtons() end)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,448 @@
|
||||
-- Ade Alts: scan
|
||||
local AA = AdeAlts
|
||||
|
||||
----------------------------------------------------------------------
|
||||
-- scanning: trade skill window
|
||||
----------------------------------------------------------------------
|
||||
local tip = CreateFrame("GameTooltip", "AdeAltsTip", nil, "GameTooltipTemplate")
|
||||
|
||||
local function TipCooldown(index)
|
||||
if GetTradeSkillCooldown then
|
||||
local c = GetTradeSkillCooldown(index)
|
||||
if c and c > 0 then return c end
|
||||
end
|
||||
tip:SetOwner(UIParent, "ANCHOR_NONE")
|
||||
tip:ClearLines()
|
||||
tip:SetTradeSkillItem(index)
|
||||
for i = 1, tip:NumLines() do
|
||||
local fs = getglobal("AdeAltsTipTextLeft" .. i)
|
||||
local t = fs and fs:GetText()
|
||||
if t then
|
||||
local l = string.lower(t)
|
||||
if string.find(l, "cooldown") and string.find(l, "remaining") then
|
||||
local d = AA.ParseDuration(t)
|
||||
if d then return d end
|
||||
end
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
-- remember the known recipes of a profession (read from its trade skill / craft window)
|
||||
local function StoreRecipes(prof, recs, rank, maxrank)
|
||||
if not AA.me or not prof or prof == "" or table.getn(recs) == 0 then return end
|
||||
AA.me.prof = AA.me.prof or {}
|
||||
local p = AA.me.prof[prof]
|
||||
if not p then
|
||||
p = {}
|
||||
AA.me.prof[prof] = p
|
||||
end
|
||||
if rank and rank > 0 and (p.r ~= rank or p.m ~= maxrank) then
|
||||
p.r = rank
|
||||
p.m = maxrank
|
||||
p.ts = time()
|
||||
AA.fileDirty = true
|
||||
end
|
||||
table.sort(recs)
|
||||
local sig = table.concat(recs, "~")
|
||||
if sig ~= p.sig then
|
||||
p.rec = recs
|
||||
p.sig = sig
|
||||
p.rts = time()
|
||||
AA.fileDirty = true
|
||||
end
|
||||
end
|
||||
|
||||
function AA.ScanTradeSkills()
|
||||
if AA.scanning or not AA.me then return end
|
||||
if GetTime() - AA.lastScan < 1 then return end
|
||||
local n = GetNumTradeSkills()
|
||||
if not n or n == 0 then return end
|
||||
AA.scanning = true
|
||||
|
||||
-- expand collapsed headers temporarily so every recipe is visible
|
||||
local expanded = {}
|
||||
for i = n, 1, -1 do
|
||||
local name, ttype, _, isExp = GetTradeSkillInfo(i)
|
||||
if ttype == "header" and not isExp then
|
||||
expanded[name] = true
|
||||
ExpandTradeSkillSubClass(i)
|
||||
end
|
||||
end
|
||||
|
||||
n = GetNumTradeSkills()
|
||||
local nowt = time()
|
||||
local recs = {}
|
||||
for i = 1, n do
|
||||
local name, ttype = GetTradeSkillInfo(i)
|
||||
if name and ttype ~= "header" then table.insert(recs, name) end
|
||||
if name and ttype ~= "header" and AA.IsTracked(name) then
|
||||
local rem = TipCooldown(i)
|
||||
if rem then
|
||||
AA.db.tipWorks = true
|
||||
AA.SetCD(name, nowt + rem)
|
||||
else
|
||||
local cur = AA.me.cds[name]
|
||||
if AA.prov[name] and nowt - AA.prov[name] < 10 then
|
||||
-- just crafted, keep provisional value
|
||||
elseif cur and cur > nowt and not AA.db.tipWorks then
|
||||
-- tooltips don't expose cooldowns on this client: keep the estimate
|
||||
else
|
||||
AA.SetCD(name, 0)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local pn, pr, pm = GetTradeSkillLine()
|
||||
StoreRecipes(pn, recs, pr, pm)
|
||||
|
||||
-- restore collapsed state
|
||||
for i = GetNumTradeSkills(), 1, -1 do
|
||||
local name, ttype, _, isExp = GetTradeSkillInfo(i)
|
||||
if ttype == "header" and expanded[name] and isExp then
|
||||
CollapseTradeSkillSubClass(i)
|
||||
end
|
||||
end
|
||||
|
||||
AA.scanning = false
|
||||
AA.lastScan = GetTime()
|
||||
if AA.Refresh then AA.Refresh() end
|
||||
end
|
||||
|
||||
-- craft window (Enchanting uses it instead of the trade skill window)
|
||||
local scanningCraft, lastCraftScan = false, 0
|
||||
function AA.ScanCrafts()
|
||||
if scanningCraft or not AA.me or not GetNumCrafts then return end
|
||||
if GetTime() - lastCraftScan < 1 then return end
|
||||
local n = GetNumCrafts()
|
||||
if not n or n == 0 then return end
|
||||
local pn, pr, pm
|
||||
if GetCraftDisplaySkillLine then pn, pr, pm = GetCraftDisplaySkillLine() end
|
||||
if not pn or pn == "" then return end -- e.g. hunter pet training
|
||||
scanningCraft = true
|
||||
if ExpandCraftSkillLine then
|
||||
for i = n, 1, -1 do
|
||||
local name, sub, ctype, _, isExp = GetCraftInfo(i)
|
||||
if ctype == "header" and not isExp then ExpandCraftSkillLine(i) end
|
||||
end
|
||||
end
|
||||
n = GetNumCrafts()
|
||||
local recs = {}
|
||||
for i = 1, n do
|
||||
local name, sub, ctype = GetCraftInfo(i)
|
||||
if name and ctype ~= "header" then table.insert(recs, name) end
|
||||
end
|
||||
StoreRecipes(pn, recs, pr, pm)
|
||||
scanningCraft = false
|
||||
lastCraftScan = GetTime()
|
||||
if AA.Refresh then AA.Refresh() end
|
||||
end
|
||||
|
||||
-- crafting hook: provisional cooldown the moment a tracked recipe is started
|
||||
local origDoTradeSkill = DoTradeSkill
|
||||
DoTradeSkill = function(index, count)
|
||||
if AA.me and index then
|
||||
local name, ttype, avail = GetTradeSkillInfo(index)
|
||||
if name and avail and avail > 0 and AA.IsTracked(name) then
|
||||
local d = AA.DEFAULTS[name]
|
||||
if d then
|
||||
AA.SetCD(name, time() + d)
|
||||
AA.prov[name] = time()
|
||||
end
|
||||
AA.After(5, function() if TradeSkillFrame and TradeSkillFrame:IsVisible() then AA.ScanTradeSkills() end end)
|
||||
AA.After(12, function() if TradeSkillFrame and TradeSkillFrame:IsVisible() then AA.ScanTradeSkills() end end)
|
||||
end
|
||||
end
|
||||
return origDoTradeSkill(index, count)
|
||||
end
|
||||
|
||||
----------------------------------------------------------------------
|
||||
-- scanning: bags (Salt Shaker etc.)
|
||||
----------------------------------------------------------------------
|
||||
local BYNAME = {}
|
||||
for id, it in pairs(AA.ITEMS) do BYNAME[it.key] = it end
|
||||
|
||||
-- match a tracked item by id, or by name as a fallback (in case the item id differs on this server)
|
||||
function AA.ItemFromLink(link)
|
||||
if not link then return nil end
|
||||
local _, _, id = string.find(link, "item:(%d+)")
|
||||
local it = id and AA.ITEMS[tonumber(id)]
|
||||
if not it then
|
||||
local _, _, nm = string.find(link, "%[(.-)%]")
|
||||
if nm then it = BYNAME[nm] end
|
||||
end
|
||||
return it
|
||||
end
|
||||
|
||||
AA.bankOpen = false
|
||||
|
||||
local function FindItemDef(link)
|
||||
local _, _, id = string.find(link, "item:(%d+)")
|
||||
local it = id and AA.ITEMS[tonumber(id)]
|
||||
if it then return it end
|
||||
local _, _, nm = string.find(link, "%[(.-)%]")
|
||||
if nm and AA.ITEMNAME[string.lower(nm)] then return AA.ITEMNAME[string.lower(nm)] end
|
||||
return nil
|
||||
end
|
||||
|
||||
function AA.ScanBags(verbose)
|
||||
if not AA.me then return end
|
||||
local bags = { 0, 1, 2, 3, 4 }
|
||||
if AA.bankOpen then
|
||||
table.insert(bags, -1)
|
||||
for b = 5, 10 do table.insert(bags, b) end
|
||||
end
|
||||
local found = 0
|
||||
for _, bag in ipairs(bags) do
|
||||
local slots = GetContainerNumSlots(bag) or 0
|
||||
for slot = 1, slots do
|
||||
local link = GetContainerItemLink(bag, slot)
|
||||
if link then
|
||||
local it = FindItemDef(link)
|
||||
if it then
|
||||
found = found + 1
|
||||
local start, dur = GetContainerItemCooldown(bag, slot)
|
||||
local exp = 0
|
||||
if start and start > 0 and dur and dur > 30 then
|
||||
local rem = start + dur - GetTime()
|
||||
if rem > 0 then
|
||||
exp = time() + rem
|
||||
if exp > time() + it.cd + 60 then
|
||||
-- client reported a nonsense start time: keep our stored value if sane, else assume a fresh use
|
||||
local cur = AA.me.cds[it.key]
|
||||
if cur and cur > time() and cur <= time() + it.cd + 60 then exp = cur else exp = time() + it.cd end
|
||||
end
|
||||
end
|
||||
end
|
||||
if verbose then
|
||||
AA.Print(it.key .. " in bag " .. bag .. " slot " .. slot .. ": start=" .. tostring(start) .. " dur=" .. tostring(dur) .. " -> " .. (exp > 0 and AA.FormatTime(exp - time()) or "Ready"))
|
||||
end
|
||||
AA.SetCD(it.key, exp)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
if verbose and found == 0 then
|
||||
AA.Print("no tracked items found in bags" .. (AA.bankOpen and " / bank" or "") .. ". (Open the bank to scan it too.)")
|
||||
end
|
||||
return found
|
||||
end
|
||||
|
||||
----------------------------------------------------------------------
|
||||
-- scanning: character data, inventory and bank (for the alts manager)
|
||||
----------------------------------------------------------------------
|
||||
local QHEX = { ["9d9d9d"] = 0, ["ffffff"] = 1, ["1eff00"] = 2, ["0070dd"] = 3, ["a335ee"] = 4, ["ff8000"] = 5 }
|
||||
|
||||
local function LearnItem(id, link, tex)
|
||||
AA.db.items = AA.db.items or {}
|
||||
local it = AA.db.items[id]
|
||||
if it and it.n and it.t and it.t ~= "" then return end
|
||||
local _, _, nm = string.find(link, "%[(.-)%]")
|
||||
local _, _, col = string.find(link, "|c%x%x(%x%x%x%x%x%x)|H")
|
||||
local t = tex
|
||||
if t then t = (string.gsub(t, "^Interface\\Icons\\", "")) end
|
||||
AA.db.items[id] = { n = nm or ("item:" .. id), t = t or "", q = (col and QHEX[col]) or 1 }
|
||||
end
|
||||
|
||||
-- returns aggregated list {{id,count},...} and a signature string, or nil if the containers are not available
|
||||
local function ScanContainers(bags)
|
||||
local order, agg = {}, {}
|
||||
local slotsTotal = 0
|
||||
for _, bag in ipairs(bags) do
|
||||
local slots = GetContainerNumSlots(bag) or 0
|
||||
slotsTotal = slotsTotal + slots
|
||||
for slot = 1, slots do
|
||||
local link = GetContainerItemLink(bag, slot)
|
||||
if link then
|
||||
local _, _, sid = string.find(link, "item:(%d+)")
|
||||
local id = tonumber(sid)
|
||||
if id then
|
||||
local tex, count = GetContainerItemInfo(bag, slot)
|
||||
count = count or 1
|
||||
if count < 1 then count = 1 end
|
||||
if not agg[id] then
|
||||
agg[id] = 0
|
||||
table.insert(order, id)
|
||||
end
|
||||
agg[id] = agg[id] + count
|
||||
LearnItem(id, link, tex)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
if slotsTotal == 0 then return nil end
|
||||
local list, parts = {}, {}
|
||||
for _, id in ipairs(order) do
|
||||
table.insert(list, { id, agg[id] })
|
||||
table.insert(parts, id .. ":" .. agg[id])
|
||||
end
|
||||
return list, table.concat(parts, "~")
|
||||
end
|
||||
|
||||
-- equipped gear: slot -> "id:enchant:suffix:unique"
|
||||
local function ScanEquip()
|
||||
if not AA.me then return false end
|
||||
local eq, parts, any = {}, {}, false
|
||||
for slot = 0, 19 do
|
||||
local link = GetInventoryItemLink("player", slot)
|
||||
if link then
|
||||
local _, _, ref = string.find(link, "item:(%-?%d+:%-?%d+:%-?%d+:%-?%d+)")
|
||||
local _, _, sid = string.find(link, "item:(%d+)")
|
||||
if ref and sid then
|
||||
eq[slot] = ref
|
||||
table.insert(parts, slot .. "=" .. ref)
|
||||
LearnItem(tonumber(sid), link, GetInventoryItemTexture("player", slot))
|
||||
any = true
|
||||
end
|
||||
end
|
||||
end
|
||||
if not any then return false end -- gear not loaded yet: keep what we have
|
||||
local sig = table.concat(parts, "~")
|
||||
if sig ~= AA.me.eqSig then
|
||||
AA.me.equip = eq
|
||||
AA.me.eqSig = sig
|
||||
AA.me.eqTs = time()
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
-- profession skill levels from the skill window
|
||||
local PROFN = {
|
||||
["Alchemy"] = 1, ["Blacksmithing"] = 1, ["Enchanting"] = 1, ["Engineering"] = 1, ["Herbalism"] = 1,
|
||||
["Leatherworking"] = 1, ["Mining"] = 1, ["Skinning"] = 1, ["Tailoring"] = 1,
|
||||
["Cooking"] = 1, ["First Aid"] = 1, ["Fishing"] = 1, ["Survival"] = 1,
|
||||
}
|
||||
local function ScanSkills()
|
||||
if not AA.me or not GetNumSkillLines then return false end
|
||||
local n = GetNumSkillLines()
|
||||
if not n or n == 0 then return false end
|
||||
-- collapsed headers hide their skills: expand them (this only happens once)
|
||||
for i = n, 1, -1 do
|
||||
local name, header, isExp = GetSkillLineInfo(i)
|
||||
if header and not isExp then ExpandSkillHeader(i) end
|
||||
end
|
||||
n = GetNumSkillLines()
|
||||
AA.me.prof = AA.me.prof or {}
|
||||
local seen, changed = {}, false
|
||||
local section
|
||||
for i = 1, n do
|
||||
local name, header, _, rank, _, _, maxRank = GetSkillLineInfo(i)
|
||||
if header then section = name end
|
||||
if not header and name and PROFN[name] and not AA.BadProf(name, maxRank)
|
||||
and not (name == "Survival" and section == "Class Skills") then
|
||||
seen[name] = true
|
||||
local p = AA.me.prof[name]
|
||||
if not p then
|
||||
p = {}
|
||||
AA.me.prof[name] = p
|
||||
end
|
||||
if p.r ~= rank or p.m ~= maxRank then
|
||||
p.r = rank
|
||||
p.m = maxRank
|
||||
p.ts = time()
|
||||
changed = true
|
||||
end
|
||||
end
|
||||
end
|
||||
for name, p in pairs(AA.me.prof) do
|
||||
if not seen[name] and PROFN[name] then
|
||||
AA.me.prof[name] = nil -- profession was unlearned
|
||||
changed = true
|
||||
end
|
||||
end
|
||||
return changed
|
||||
end
|
||||
|
||||
AA.enterTime = 0
|
||||
|
||||
function AA.ScanSelf()
|
||||
if not AA.me or not AA.db then return end
|
||||
local changed = false
|
||||
local lvl, gold = UnitLevel("player"), GetMoney()
|
||||
local early = GetTime() - AA.enterTime < 12 -- right after login the client may still report 0
|
||||
local metaChanged = false
|
||||
if lvl and lvl > 0 and AA.me.level ~= lvl then
|
||||
AA.me.level = lvl
|
||||
metaChanged = true
|
||||
end
|
||||
if gold and not (gold == 0 and early) and AA.me.gold ~= gold then
|
||||
AA.me.gold = gold
|
||||
metaChanged = true
|
||||
end
|
||||
if metaChanged then
|
||||
AA.me.dataTs = time()
|
||||
changed = true
|
||||
end
|
||||
if ScanEquip() then changed = true end
|
||||
if ScanSkills() then changed = true end
|
||||
local list, sig = ScanContainers({ 0, 1, 2, 3, 4 })
|
||||
if list and sig ~= AA.me.invSig then
|
||||
AA.me.inv = list
|
||||
AA.me.invSig = sig
|
||||
AA.me.invTs = time()
|
||||
changed = true
|
||||
end
|
||||
if AA.bankOpen then
|
||||
local bags = { -1 }
|
||||
for b = 5, 10 do table.insert(bags, b) end
|
||||
local bl, bs = ScanContainers(bags)
|
||||
if bl and bs ~= AA.me.bankSig then
|
||||
AA.me.bank = bl
|
||||
AA.me.bankSig = bs
|
||||
AA.me.bankTs = time()
|
||||
changed = true
|
||||
end
|
||||
end
|
||||
if changed then
|
||||
AA.fileDirty = true
|
||||
if AA.RefreshManager then AA.RefreshManager() end
|
||||
end
|
||||
end
|
||||
|
||||
----------------------------------------------------------------------
|
||||
-- data -> display
|
||||
----------------------------------------------------------------------
|
||||
function AA.SortedChars()
|
||||
local list = {}
|
||||
for k, c in pairs(AA.db.chars) do table.insert(list, k) end
|
||||
table.sort(list, function(a, b)
|
||||
if a == AA.myKey then return true end
|
||||
if b == AA.myKey then return false end
|
||||
return a < b
|
||||
end)
|
||||
return list
|
||||
end
|
||||
|
||||
function AA.Entries(onlyCd)
|
||||
local out = {}
|
||||
local nowt = time()
|
||||
for _, k in ipairs(AA.SortedChars()) do
|
||||
local c = AA.db.chars[k]
|
||||
local keys = {}
|
||||
for key, exp in pairs(c.cds or {}) do table.insert(keys, key) end
|
||||
table.sort(keys)
|
||||
if table.getn(keys) > 0 or not onlyCd then
|
||||
table.insert(out, { l = "|cff" .. AA.ClassHex(c.class) .. c.name .. "|r |cff808080" .. c.realm .. "|r", r = "" })
|
||||
if table.getn(keys) == 0 then
|
||||
table.insert(out, { l = " |cff808080(no cooldowns recorded)|r", r = "" })
|
||||
end
|
||||
for _, key in ipairs(keys) do
|
||||
local exp = c.cds[key]
|
||||
local rem = exp - nowt
|
||||
local r
|
||||
if exp <= 0 or rem <= 0 then
|
||||
r = "|cff40ff40Ready|r"
|
||||
elseif rem >= AA.DAY then
|
||||
r = "|cffff6060" .. AA.FormatTime(rem) .. "|r"
|
||||
else
|
||||
r = "|cffffd100" .. AA.FormatTime(rem) .. "|r"
|
||||
end
|
||||
table.insert(out, { l = " " .. key, r = r })
|
||||
end
|
||||
end
|
||||
end
|
||||
return out
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user