Files
AdeAlts/sync.lua
T

390 lines
12 KiB
Lua

-- Ade Alts: sync
local AA = AdeAlts
----------------------------------------------------------------------
-- sync between accounts (guild / party / raid addon messages)
----------------------------------------------------------------------
local function Encode(c)
local head = "C^" .. c.name .. "^" .. c.realm .. "^" .. (c.class or "") .. "^" .. (c.updated or 0) .. "^"
local msgs, cur = {}, ""
for key, exp in pairs(c.cds) do
local pair = key .. "=" .. exp
if string.len(head) + string.len(cur) + string.len(pair) + 1 > 240 then
table.insert(msgs, head .. cur)
cur = ""
end
if cur ~= "" then cur = cur .. "~" end
cur = cur .. pair
end
table.insert(msgs, head .. cur)
return msgs
end
function AA.Send(msg)
if IsInGuild and IsInGuild() then SendAddonMessage(AA.PREFIX, msg, "GUILD") end
if GetNumRaidMembers() > 0 then
SendAddonMessage(AA.PREFIX, msg, "RAID")
elseif GetNumPartyMembers() > 0 then
SendAddonMessage(AA.PREFIX, msg, "PARTY")
end
end
function AA.Broadcast()
if not AA.me then return end
AA.lastBroadcast = GetTime()
for _, m in ipairs(Encode(AA.me)) do AA.Send(m) end
end
function AA.ReceiveRecord(text)
local _, _, name, realm, class, upd, body = string.find(text, "^C%^([^%^]*)%^([^%^]*)%^([^%^]*)%^(%d*)%^(.*)$")
if not name or name == "" then return false end
upd = tonumber(upd) or 0
local k = name .. "-" .. realm
local c = AA.db.chars[k]
if c and (c.updated or 0) > upd then return false end -- we already have newer data
if not c then
c = { name = name, realm = realm, class = class, cds = {}, remote = true }
AA.db.chars[k] = c
end
if class ~= "" then c.class = class end
c.cds = c.cds or {}
for key, exp in string.gfind(body, "([^~=]+)=(%d+)") do
c.cds[key] = tonumber(exp)
end
c.updated = upd
AA.ClampRecord(c)
return true
end
-- shared file between accounts on the same game folder (needs SuperWoW)
local function InvStr(list)
if not list then return "" end
local parts = {}
for _, e in ipairs(list) do table.insert(parts, e[1] .. ":" .. e[2]) end
return table.concat(parts, "~")
end
local function ParseInv(str)
local list = {}
for id, n in string.gfind(str or "", "(%d+):(%d+)") do
table.insert(list, { tonumber(id), tonumber(n) })
end
return list
end
local function EncodeAlt(c)
return "A^" .. c.name .. "^" .. c.realm .. "^" .. (c.class or "") .. "^" .. (c.level or 0) .. "^" .. (c.gold or 0)
.. "^" .. (c.dataTs or 0) .. "^" .. (c.invTs or 0) .. "^" .. (c.bankTs or 0)
.. "^" .. InvStr(c.inv) .. "^" .. InvStr(c.bank)
end
local function ReceiveAlt(text)
local _, _, name, realm, class, lvl, gold, dts, its, bts, inv, bank =
string.find(text, "^A%^([^%^]*)%^([^%^]*)%^([^%^]*)%^(%d*)%^(%d*)%^(%d*)%^(%d*)%^(%d*)%^([^%^]*)%^(.*)$")
if not name or name == "" then return false end
local k = name .. "-" .. realm
local c = AA.db.chars[k]
if not c then
c = { name = name, realm = realm, class = class, cds = {}, remote = true }
AA.db.chars[k] = c
end
dts, its, bts = tonumber(dts) or 0, tonumber(its) or 0, tonumber(bts) or 0
local ch = false
if dts > (c.dataTs or 0) then
c.dataTs = dts
c.level = tonumber(lvl)
c.gold = tonumber(gold)
if class ~= "" then c.class = class end
ch = true
end
if its > (c.invTs or 0) then
c.invTs = its
c.inv = ParseInv(inv)
ch = true
end
if bts > (c.bankTs or 0) then
c.bankTs = bts
c.bank = ParseInv(bank)
ch = true
end
return ch
end
local function ReceiveItem(text)
local _, _, id, name, tex, q = string.find(text, "^I%^(%d+)%^([^%^]*)%^([^%^]*)%^(%d*)$")
if not id then return end
id = tonumber(id)
AA.db.items = AA.db.items or {}
local it = AA.db.items[id]
if not it or not it.n or not it.t or it.t == "" then
AA.db.items[id] = { n = name, t = tex, q = tonumber(q) or 1 }
end
end
local function EncodeProf(c, pname, p)
return "P^" .. c.name .. "^" .. c.realm .. "^" .. pname .. "^" .. (p.r or 0) .. "^" .. (p.m or 0)
.. "^" .. (p.ts or 0) .. "^" .. (p.rts or 0) .. "^" .. table.concat(p.rec or {}, "~")
end
local function ReceiveProf(text)
local _, _, name, realm, pname, r, m, ts, rts, rec =
string.find(text, "^P%^([^%^]*)%^([^%^]*)%^([^%^]*)%^(%d*)%^(%d*)%^(%d*)%^(%d*)%^(.*)$")
if not name or name == "" or pname == "" then return false end
if AA.BadProf(pname, tonumber(m)) then return false end
local k = name .. "-" .. realm
local c = AA.db.chars[k]
if not c then
c = { name = name, realm = realm, cds = {}, remote = true }
AA.db.chars[k] = c
end
c.prof = c.prof or {}
local p = c.prof[pname]
if not p then
p = {}
c.prof[pname] = p
end
ts, rts = tonumber(ts) or 0, tonumber(rts) or 0
local ch = false
if ts > (p.ts or 0) then
p.ts = ts
p.r = tonumber(r)
p.m = tonumber(m)
ch = true
end
if rts > (p.rts or 0) then
p.rts = rts
local list = {}
for rn in string.gfind(rec, "[^~]+") do table.insert(list, rn) end
p.rec = list
p.sig = nil
ch = true
end
return ch
end
local function EncodeEquip(c)
local parts = {}
for slot = 0, 19 do
if c.equip and c.equip[slot] then table.insert(parts, slot .. "=" .. c.equip[slot]) end
end
return "E^" .. c.name .. "^" .. c.realm .. "^" .. (c.eqTs or 0) .. "^" .. table.concat(parts, "~")
end
local function ReceiveEquip(text)
local _, _, name, realm, ts, body = string.find(text, "^E%^([^%^]*)%^([^%^]*)%^(%d*)%^(.*)$")
if not name or name == "" then return false end
local k = name .. "-" .. realm
local c = AA.db.chars[k]
if not c then
c = { name = name, realm = realm, cds = {}, remote = true }
AA.db.chars[k] = c
end
ts = tonumber(ts) or 0
if ts <= (c.eqTs or 0) then return false end
local eq = {}
for slot, ref in string.gfind(body, "(%d+)=(%-?%d+:%-?%d+:%-?%d+:%-?%d+)") do
eq[tonumber(slot)] = ref
end
c.equip = eq
c.eqTs = ts
return true
end
-- bag sizes / used slots: B^name^realm^invBagsTs^bankBagsTs^inv^bank
local function EncodeBags(c)
return "B^" .. c.name .. "^" .. c.realm .. "^" .. (c.invBagsTs or 0) .. "^" .. (c.bankBagsTs or 0)
.. "^" .. (c.invBags or "") .. "^" .. (c.bankBags or "")
end
local function ReceiveBags(text)
local _, _, name, realm, its, bts, inv, bank =
string.find(text, "^B%^([^%^]*)%^([^%^]*)%^(%d*)%^(%d*)%^([^%^]*)%^(.*)$")
if not name or name == "" then return false end
local k = name .. "-" .. realm
local c = AA.db.chars[k]
if not c then
c = { name = name, realm = realm, cds = {}, remote = true }
AA.db.chars[k] = c
end
its, bts = tonumber(its) or 0, tonumber(bts) or 0
local ch = false
if its > (c.invBagsTs or 0) then
c.invBagsTs = its
c.invBags = inv
ch = true
end
if bts > (c.bankBagsTs or 0) then
c.bankBagsTs = bts
c.bankBags = bank
ch = true
end
return ch
end
local function FileRead(name)
if not (ImportFile and ExportFile) then return nil end
local ok, txt = pcall(ImportFile, name)
if ok and txt and txt ~= "" then return txt end
ok, txt = pcall(ImportFile, name .. ".txt")
if ok and txt and txt ~= "" then return txt end
return nil
end
local function MergeText(txt)
local n = 0
for line in string.gfind(txt, "[^\r\n]+") do
local t = string.sub(line, 1, 2)
if t == "C^" then
if AA.ReceiveRecord(line) then n = n + 1 end
elseif t == "A^" then
if ReceiveAlt(line) then n = n + 1 end
elseif t == "P^" then
if ReceiveProf(line) then n = n + 1 end
elseif t == "E^" then
if ReceiveEquip(line) then n = n + 1 end
elseif t == "B^" then
if ReceiveBags(line) then n = n + 1 end
elseif t == "I^" then
ReceiveItem(line)
end
end
return n
end
local legacyDone = false
AA.FileMerge = function()
if not AA.db then return end
local n = 0
local txt = FileRead(AA.FILE)
if txt then n = n + MergeText(txt) end
if not legacyDone then
legacyDone = true
local old = FileRead(AA.LEGACY)
if old then n = n + MergeText(old) end
end
if n > 0 then AA.Refresh() end
end
local function FileWrite()
if not (ImportFile and ExportFile) or not AA.db then return end
local parts, seen = {}, {}
for k, c in pairs(AA.db.chars) do
for _, m in ipairs(Encode(c)) do table.insert(parts, m) end
if c.dataTs or c.invTs or c.bankTs then table.insert(parts, EncodeAlt(c)) end
local lists = { c.inv or {}, c.bank or {} }
for _, list in ipairs(lists) do
for _, e in ipairs(list) do seen[e[1]] = true end
end
if c.prof then
for pname, p in pairs(c.prof) do table.insert(parts, EncodeProf(c, pname, p)) end
end
if c.invBags or c.bankBags then table.insert(parts, EncodeBags(c)) end
if c.equip then
table.insert(parts, EncodeEquip(c))
for slot, ref in pairs(c.equip) do
local _, _, sid = string.find(ref, "^(%d+)")
if sid then seen[tonumber(sid)] = true end
end
end
end
if AA.db.items then
for id in pairs(seen) do
local it = AA.db.items[id]
if it and it.n then
table.insert(parts, "I^" .. id .. "^" .. it.n .. "^" .. (it.t or "") .. "^" .. (it.q or 1))
end
end
end
pcall(ExportFile, AA.FILE, table.concat(parts, "\n"))
AA.fileDirty = false
AA.lastFileWrite = GetTime()
end
AA.FileSync = function()
AA.FileMerge()
FileWrite()
end
local function ExportString()
local parts = {}
for k, c in pairs(AA.db.chars) do
for _, m in ipairs(Encode(c)) do table.insert(parts, m) end
end
return table.concat(parts, "#")
end
local function ImportString(text)
local n = 0
for rec in string.gfind(text, "[^#]+") do
if AA.ReceiveRecord(rec) then n = n + 1 end
end
return n
end
-- export / import box
local io = CreateFrame("Frame", "AdeAltsIO", UIParent)
io:SetWidth(440)
io:SetHeight(90)
io:SetPoint("CENTER", UIParent, "CENTER", 0, 150)
io:SetFrameStrata("DIALOG")
io:SetBackdrop({
bgFile = "Interface\\Tooltips\\UI-Tooltip-Background",
edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
tile = true, tileSize = 16, edgeSize = 16,
insets = { left = 4, right = 4, top = 4, bottom = 4 },
})
io:SetBackdropColor(0, 0, 0, 0.95)
io:EnableMouse(true)
io:Hide()
table.insert(UISpecialFrames, "AdeAltsIO")
local iotitle = io:CreateFontString(nil, "OVERLAY", "GameFontNormal")
iotitle:SetPoint("TOPLEFT", io, "TOPLEFT", 12, -10)
iotitle:SetText("Copy (Ctrl+C) to export, or paste (Ctrl+V) and press Import")
local eb = CreateFrame("EditBox", "AdeAltsIOEdit", io)
eb:SetWidth(410)
eb:SetHeight(22)
eb:SetPoint("TOPLEFT", io, "TOPLEFT", 15, -30)
eb:SetFontObject(ChatFontNormal)
eb:SetAutoFocus(false)
eb:SetMaxLetters(0)
eb:SetTextInsets(4, 4, 2, 2)
eb:SetBackdrop({
bgFile = "Interface\\Tooltips\\UI-Tooltip-Background",
edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
tile = true, tileSize = 8, edgeSize = 8,
insets = { left = 2, right = 2, top = 2, bottom = 2 },
})
eb:SetBackdropColor(0.1, 0.1, 0.1, 1)
eb:SetScript("OnEscapePressed", function() this:ClearFocus() end)
local bimp = CreateFrame("Button", nil, io, "UIPanelButtonTemplate")
bimp:SetWidth(90); bimp:SetHeight(22)
bimp:SetPoint("BOTTOMLEFT", io, "BOTTOMLEFT", 15, 10)
bimp:SetText("Import")
bimp:SetScript("OnClick", function()
local n = ImportString(eb:GetText() or "")
AA.Print("imported " .. n .. " record(s).")
AA.Refresh()
end)
local bclose = CreateFrame("Button", nil, io, "UIPanelButtonTemplate")
bclose:SetWidth(90); bclose:SetHeight(22)
bclose:SetPoint("BOTTOMRIGHT", io, "BOTTOMRIGHT", -15, 10)
bclose:SetText("Close")
bclose:SetScript("OnClick", function() io:Hide() end)
function AA.ShowIO(exportMode)
io:Show()
if exportMode then
eb:SetText(ExportString())
eb:SetFocus()
eb:HighlightText()
else
eb:SetText("")
eb:SetFocus()
end
end