From 0094949c9104cc9a94bfb0dc4bcc723bf78f2a89 Mon Sep 17 00:00:00 2001 From: Relationship <139162359+Relationship-OctoWoW@users.noreply.github.com> Date: Fri, 10 Jul 2026 12:32:59 +0100 Subject: [PATCH] Add files via upload --- RelationshipsPVP.lua | 966 +++++++++++++++++++++++++++++++++++++++++++ RelationshipsPVP.toc | 9 + RelationshipsPVP.xml | 411 ++++++++++++++++++ 3 files changed, 1386 insertions(+) create mode 100644 RelationshipsPVP.lua create mode 100644 RelationshipsPVP.toc create mode 100644 RelationshipsPVP.xml diff --git a/RelationshipsPVP.lua b/RelationshipsPVP.lua new file mode 100644 index 0000000..91f60d2 --- /dev/null +++ b/RelationshipsPVP.lua @@ -0,0 +1,966 @@ +-- Relationships PVP + +RelationshipsPVPCharDB = RelationshipsPVPCharDB or {} + +local BG_KEYS = { "AV", "WSG", "AB" } +local ARENA_KEYS = { "A2", "A3", "A5", "SK" } +local ALL_KEYS = { "AV", "WSG", "AB", "A2", "A3", "A5", "SK" } + +local BG_NAMES = { + AV = "Alterac Valley", + WSG = "Warsong Gulch", + AB = "Arathi Basin", + A2 = "2v2 Rated", + A3 = "3v3 Rated", + A5 = "5v5 Rated", + SK = "Skirmish", +} + +local BG_COLORS = { + AV = { r = 0.6, g = 0.85, b = 1.0 }, + WSG = { r = 0.6, g = 1.0, b = 0.6 }, + AB = { r = 1.0, g = 0.85, b = 0.4 }, + A2 = { r = 1.0, g = 0.6, b = 0.9 }, + A3 = { r = 1.0, g = 0.5, b = 0.6 }, + A5 = { r = 0.9, g = 0.5, b = 1.0 }, + SK = { r = 0.8, g = 0.8, b = 0.8 }, +} + +-- Arena bracket meta: teamSize + isRated (registeredMatch) +local ARENA_META = { + A2 = { size = 2, rated = 1 }, + A3 = { size = 3, rated = 1 }, + A5 = { size = 5, rated = 1 }, + SK = { size = 2, rated = 0 }, -- default skirmish to 2v2 unrated +} + +-- TurtleWoW / OctoWoW arena finder ids for JoinArenaQueue(id). +-- Skirmish must be 0; passing the team size opens the Blood Ring panel +-- without completing the queue. +local ARENA_IDS = { + SK = 0, + A2 = 1, + A3 = 2, + A5 = 3, +} + +local MARKS_WIN = 3 +local MARKS_LOSS = 1 + +local currentBG = nil +local endReported = {} + +-- Track the last arena bracket the user queued for, so we can classify +-- the resulting match even on clients that don't report teamSize. +local lastQueuedArena = nil + +-- Runtime UI (row FontStrings created dynamically) +local rowStrings = {} +local totalRow = nil + +--------------------------------------------------------------- +-- Utility +--------------------------------------------------------------- +local function NewStatBlock() + return { + wins = 0, losses = 0, + kills = 0, deaths = 0, hk = 0, + marks = 0, honor = 0, + bestKB = 0, streak = 0, bestStreak = 0, + } +end + +local function EnsureDB() + local db = RelationshipsPVPCharDB + db.stats = db.stats or {} + db.icon = db.icon or { point = "CENTER", x = 60, y = -60 } + if db.showIcon == nil then db.showIcon = true end + for i = 1, table.getn(ALL_KEYS) do + local k = ALL_KEYS[i] + local s = db.stats[k] or NewStatBlock() + if s.honor == nil then s.honor = 0 end + if s.bestKB == nil then s.bestKB = 0 end + if s.streak == nil then s.streak = 0 end + if s.bestStreak == nil then s.bestStreak = 0 end + if s.hk == nil then s.hk = 0 end + if s.marks == nil then s.marks = 0 end + db.stats[k] = s + end + return db +end + +function RelationshipsPVP_SaveIconPos() + local db = EnsureDB() + local btn = RelationshipsPVPMinimapButton + if not btn then return end + local point, _, _, x, y = btn:GetPoint() + db.icon.point = point or "CENTER" + db.icon.x = x or 0 + db.icon.y = y or 0 + db.icon.parent = "Minimap" +end + +local function ApplyIconVisibility() + local db = EnsureDB() + local btn = RelationshipsPVPMinimapButton + if not btn then return end + if db.showIcon then btn:Show() else btn:Hide() end +end + +-- The Minimap frame can be briefly hidden during loading screens / entering +-- battlegrounds; when it re-shows, child buttons parented to it sometimes +-- stay hidden until touched. This ticker re-asserts our intended visibility +-- once per second so the icon never "randomly disappears". +local iconWatcher +local function InstallIconWatcher() + if iconWatcher then return end + iconWatcher = CreateFrame("Frame") + iconWatcher.elapsed = 0 + iconWatcher:SetScript("OnUpdate", function() + iconWatcher.elapsed = (iconWatcher.elapsed or 0) + arg1 + if iconWatcher.elapsed < 1.0 then return end + iconWatcher.elapsed = 0 + local db = RelationshipsPVPCharDB + if not db then return end + local btn = RelationshipsPVPMinimapButton + if not btn then return end + if db.showIcon then + if not btn:IsShown() then btn:Show() end + else + if btn:IsShown() then btn:Hide() end + end + end) +end + +local function RestoreIconPos() + local db = EnsureDB() + local btn = RelationshipsPVPMinimapButton + if not btn then return end + local parent = (db.icon.parent == "UIParent") and UIParent or Minimap + btn:ClearAllPoints() + btn:SetPoint(db.icon.point, parent, db.icon.point, db.icon.x, db.icon.y) + ApplyIconVisibility() + InstallIconWatcher() +end + +local function SavePanelPos() + local db = EnsureDB() + db.panel = db.panel or {} + if not RelationshipsPVPFrame then return end + local point, _, _, x, y = RelationshipsPVPFrame:GetPoint() + db.panel.point = point or "CENTER" + db.panel.x = x or 0 + db.panel.y = y or 0 +end + +local function RestorePanelPos() + local db = EnsureDB() + if not RelationshipsPVPFrame or not db.panel or not db.panel.point then return end + RelationshipsPVPFrame:ClearAllPoints() + RelationshipsPVPFrame:SetPoint(db.panel.point, UIParent, db.panel.point, db.panel.x, db.panel.y) +end + +local function Print(msg) + if DEFAULT_CHAT_FRAME then + DEFAULT_CHAT_FRAME:AddMessage("|cff66ccffRelationships PVP:|r " .. msg) + end +end + +local function MapNameToKey(name) + if not name then return nil end + if name == BG_NAMES.AV then return "AV" end + if name == BG_NAMES.WSG then return "WSG" end + if name == BG_NAMES.AB then return "AB" end + return nil +end + +local function FormatNumber(n) + n = n or 0 + if n >= 1000000 then + return string.format("%.1fm", n / 1000000) + elseif n >= 10000 then + return string.format("%.1fk", n / 1000) + end + return tostring(n) +end + +--------------------------------------------------------------- +-- BG / Arena detection +--------------------------------------------------------------- +local function ClassifyBattlefield(i) + local status, mapName, instanceID, minLevel, maxLevel, teamSize, registered = GetBattlefieldStatus(i) + if status ~= "active" then return nil end + + local bgKey = MapNameToKey(mapName) + if bgKey then return bgKey end + + if teamSize and teamSize > 0 then + if registered == 1 or registered == true then + if teamSize == 2 then return "A2" end + if teamSize == 3 then return "A3" end + if teamSize == 5 then return "A5" end + else + return "SK" + end + end + + if lastQueuedArena then return lastQueuedArena end + return nil +end + +local function DetectCurrentBG() + for i = 1, 3 do + local status = GetBattlefieldStatus(i) + if status == "active" then + local key = ClassifyBattlefield(i) + if key then return key, i end + end + end + return nil, nil +end + +local function RecordEnd(key, index) + if not key or endReported[index] then return end + endReported[index] = true + + local db = EnsureDB() + local s = db.stats[key] + if not s then return end + + local playerWon = false + local winner = GetBattlefieldWinner() + if winner then + local myFaction = UnitFactionGroup("player") + local wonAsAlliance = (winner == 1 and myFaction == "Alliance") + local wonAsHorde = (winner == 0 and myFaction == "Horde") + playerWon = wonAsAlliance or wonAsHorde + if playerWon then + s.wins = s.wins + 1 + s.streak = s.streak + 1 + if s.streak > s.bestStreak then s.bestStreak = s.streak end + else + s.losses = s.losses + 1 + s.streak = 0 + end + local isBG = (key == "AV" or key == "WSG" or key == "AB") + if isBG then + s.marks = s.marks + (playerWon and MARKS_WIN or MARKS_LOSS) + end + end + + RequestBattlefieldScoreData() + local me = UnitName("player") + local rows = GetNumBattlefieldScores() or 0 + for r = 1, rows do + local name, killingBlows, honorableKills, deaths, honorGained = GetBattlefieldScore(r) + if name == me then + local kb = killingBlows or 0 + local hk = honorableKills or 0 + local dth = deaths or 0 + local hon = honorGained or 0 + s.kills = s.kills + kb + s.hk = s.hk + hk + s.deaths = s.deaths + dth + s.honor = s.honor + hon + if kb > s.bestKB then s.bestKB = kb end + break + end + end + + local marksMsg = "" + if key == "AV" or key == "WSG" or key == "AB" then + marksMsg = " +" .. (playerWon and MARKS_WIN or MARKS_LOSS) .. " marks" + end + Print(BG_NAMES[key] .. " recorded (" + .. (playerWon and "|cff66ff66win|r" or "|cffff6666loss|r") + .. marksMsg .. ").") +end + +--------------------------------------------------------------- +-- Queue actions +--------------------------------------------------------------- +-- Auto-close the Blizzard PvP panels if they pop up as a side effect +-- of the arena / BG queueing calls. +local function HidePvPPanels() + if BattlefieldFrame and BattlefieldFrame:IsVisible() then HideUIPanel(BattlefieldFrame) end + if PVPBattlegroundFrame and PVPBattlegroundFrame:IsVisible() then HideUIPanel(PVPBattlegroundFrame) end + if PVPFrame and PVPFrame:IsVisible() then HideUIPanel(PVPFrame) end + if HonorFrame and HonorFrame:IsVisible() then HideUIPanel(HonorFrame) end +end + +-- Suppress the PvP panel entirely while we are auto-queueing. Reference-counted +-- so nested queues (Queue All BGs) do not release too early. +local suppressPanels = 0 +local suppressHooked = false +local origBattlefieldShow, origPVPBGShow + +local function InstallPanelSuppressor() + if suppressHooked then return end + suppressHooked = true + if BattlefieldFrame then + origBattlefieldShow = BattlefieldFrame.Show + BattlefieldFrame.Show = function(self, ...) + if suppressPanels > 0 then return end + origBattlefieldShow(self) + end + end + if PVPBattlegroundFrame then + origPVPBGShow = PVPBattlegroundFrame.Show + PVPBattlegroundFrame.Show = function(self, ...) + if suppressPanels > 0 then return end + origPVPBGShow(self) + end + end +end + +local function BeginSuppress() + InstallPanelSuppressor() + suppressPanels = suppressPanels + 1 + HidePvPPanels() +end + +local function EndSuppress() + suppressPanels = suppressPanels - 1 + if suppressPanels < 0 then suppressPanels = 0 end + HidePvPPanels() +end + +-- Central pending-queue state. `pendingQueue` is a FIFO of items: +-- { kind = "bg"|"arena", key = , asGroup = 0/1 } +-- The finder frame processes them one at a time on BATTLEFIELDS_SHOW. +local pendingQueue = {} +local finderFrame + +-- Kick the client into opening the finder for the requested queue type. +-- The BATTLEFIELDS_SHOW handler below immediately completes the queue and +-- hides the Blizzard/Blood Ring panel. +local function KickFinderFor(job) + if job.kind == "arena" then + if type(JoinArenaQueue) == "function" then + local id = ARENA_IDS[job.key] + if id == nil then id = 0 end + JoinArenaQueue(id) + return true + end + if type(JoinBattlegroundQueue) == "function" then + JoinBattlegroundQueue(BG_NAMES[job.key]) + return true + end + return false + end + + if type(JoinBattlegroundQueue) == "function" then + JoinBattlegroundQueue(BG_NAMES[job.key]) + return true + end + return false +end + +local function GetFinderFrame() + if finderFrame then return finderFrame end + finderFrame = CreateFrame("Frame") + finderFrame:SetScript("OnEvent", function() + if event ~= "BATTLEFIELDS_SHOW" then return end + + local job = pendingQueue[1] + if not job then + HidePvPPanels() + EndSuppress() + return + end + table.remove(pendingQueue, 1) + + if SetSelectedBattlefield then SetSelectedBattlefield(0) end + + if job.kind == "arena" then + local meta = ARENA_META[job.key] or ARENA_META.SK + lastQueuedArena = job.key + -- Prefer explicit arena team-size setters where available. + if type(SetArenaTeamSize) == "function" then SetArenaTeamSize(meta.size) end + if JoinBattlefield then + -- Turtle/Octo: JoinArenaQueue(id) opens Blood Ring, then + -- JoinBattlefield(0[, asGroup]) actually joins First Available. + if job.asGroup == 1 then JoinBattlefield(0, 1) else JoinBattlefield(0) end + end + else + if JoinBattlefield then + if job.asGroup == 1 then JoinBattlefield(0, 1) else JoinBattlefield(0) end + end + end + + HidePvPPanels() + EndSuppress() + + -- Chain: if more queued, kick the next one after a very short delay. + if pendingQueue[1] then + local nxt = pendingQueue[1] + BeginSuppress() + finderFrame:RegisterEvent("BATTLEFIELDS_SHOW") + if not KickFinderFor(nxt) then + table.remove(pendingQueue, 1) + finderFrame:UnregisterEvent("BATTLEFIELDS_SHOW") + EndSuppress() + end + else + finderFrame:UnregisterEvent("BATTLEFIELDS_SHOW") + end + end) + return finderFrame +end + +local function IsInGroup() + if RelationshipsPVPGroupCheck and RelationshipsPVPGroupCheck:GetChecked() then return 1 end + return 0 +end + +local function EnqueueAndKick(job) + local f = GetFinderFrame() + -- If another job is already in flight, just append; the handler will chain. + if pendingQueue[1] then + table.insert(pendingQueue, job) + return true + end + table.insert(pendingQueue, job) + BeginSuppress() + f:UnregisterAllEvents() + f:RegisterEvent("BATTLEFIELDS_SHOW") + if not KickFinderFor(job) then + pendingQueue = {} + f:UnregisterEvent("BATTLEFIELDS_SHOW") + EndSuppress() + return false + end + return true +end + +function RelationshipsPVP_Queue(key) + if not BG_NAMES[key] then return end + local asGroup = IsInGroup() + + if EnqueueAndKick({ kind = "bg", key = key, asGroup = asGroup }) then + Print("Queued: |cffffd200" .. BG_NAMES[key] .. "|r" + .. (asGroup == 1 and " |cff66ff66(group)|r" or "")) + return + end + + -- Fallback: legacy vanilla flow (visit a Battlemaster first). + if not JoinBattlefield then + Print("|cffff4444Battleground Finder not available on this client.|r") + return + end + local idx + if key == "AV" then idx = 1 + elseif key == "WSG" then idx = 2 + elseif key == "AB" then idx = 3 end + JoinBattlefield(idx, asGroup) + Print("Requested queue: |cffffd200" .. BG_NAMES[key] .. "|r") +end + +--------------------------------------------------------------- +-- Arena queue: fully automated, no manual panel confirmation +--------------------------------------------------------------- +-- Turtle/Octo Blood Ring panel: JoinArenaQueue(id) opens a custom +-- frame (typically BloodRingFrame). It does NOT fire BATTLEFIELDS_SHOW +-- and JoinBattlefield does not apply, so we simulate a click on the +-- panel's own Join button. + +-- Text labels the Blood Ring / arena finder Join button may carry. +local ARENA_JOIN_TEXTS = { + ["join battle"] = 1, ["join queue"] = 1, ["join"] = 1, + ["enter queue"] = 1, ["enter battle"] = 1, ["queue"] = 1, + ["enter"] = 1, ["join arena"] = 1, ["queue up"] = 1, +} + +local function _btnText(btn) + if not btn then return nil end + if btn.GetText then + local t = btn:GetText() + if t and t ~= "" then return t end + end + local name = btn.GetName and btn:GetName() + if name then + local fs = getglobal(name.."Text") + if fs and fs.GetText then return fs:GetText() end + end + return nil +end + +local function _looksLikeJoin(btn) + if not btn or type(btn.GetObjectType) ~= "function" then return false end + if btn:GetObjectType() ~= "Button" then return false end + if btn.IsVisible and not btn:IsVisible() then return false end + if btn.IsEnabled and btn:IsEnabled() == 0 then return false end + local t = _btnText(btn) + if not t then return false end + return ARENA_JOIN_TEXTS[string.lower(t)] == 1 +end + +-- Locate the Blood Ring frame by likely names or a visible frame carrying +-- a Join-style child button. +local function FindArenaFinderFrame() + local names = { + "BloodRingFrame", "BloodringFrame", "BloodRingArenaFrame", + "ArenaFinderFrame", "ArenaQueueFrame", "SkirmishFrame", + "CustomArenaFrame", "PVPArenaFrame", + } + for i = 1, table.getn(names) do + local f = getglobal(names[i]) + if f and f.IsVisible and f:IsVisible() then return f end + end + return nil +end + +local function FindJoinButtonIn(frame) + if not frame then return nil end + -- Try predictable child names first. + local name = frame.GetName and frame:GetName() + if name then + local suffixes = { + "JoinButton", "JoinBattleButton", "QueueButton", "EnterButton", + "Button1", "OKButton", "AcceptButton", + } + for i = 1, table.getn(suffixes) do + local b = getglobal(name .. suffixes[i]) + if _looksLikeJoin(b) then return b end + end + end + -- Walk direct children. + if frame.GetChildren then + local kids = { frame:GetChildren() } + for i = 1, table.getn(kids) do + if _looksLikeJoin(kids[i]) then return kids[i] end + end + -- One level deeper. + for i = 1, table.getn(kids) do + local k = kids[i] + if k and k.GetChildren then + local gkids = { k:GetChildren() } + for j = 1, table.getn(gkids) do + if _looksLikeJoin(gkids[j]) then return gkids[j] end + end + end + end + end + return nil +end + +-- Global sweep as a last resort: scan all frames for a visible Join-style +-- button attached to a visible top-level frame. +local function GlobalFindArenaJoinButton() + local f = EnumerateFrames() + while f do + if f.IsVisible and f:IsVisible() and f.GetChildren then + local kids = { f:GetChildren() } + for i = 1, table.getn(kids) do + if _looksLikeJoin(kids[i]) then return kids[i] end + end + end + f = EnumerateFrames(f) + end + return nil +end + +local arenaPoll +local arenaPollElapsed = 0 +local arenaPollDeadline = 0 + +local function StopArenaPoll() + if arenaPoll then arenaPoll:Hide() end +end + +local function StartArenaPoll() + if not arenaPoll then + arenaPoll = CreateFrame("Frame") + arenaPoll:Hide() + arenaPoll:SetScript("OnUpdate", function() + arenaPollElapsed = arenaPollElapsed + arg1 + if arenaPollElapsed < 0.05 then return end + arenaPollElapsed = 0 + + local frame = FindArenaFinderFrame() + local btn = FindJoinButtonIn(frame) + if not btn then btn = GlobalFindArenaJoinButton() end + if btn then + -- Click the button as if the user pressed it. + if btn.Click then btn:Click() end + if frame and frame.Hide then frame:Hide() end + HidePvPPanels() + StopArenaPoll() + return + end + + if GetTime() > arenaPollDeadline then + -- Give up quietly; leave the panel open so the user can click. + StopArenaPoll() + end + end) + end + arenaPollElapsed = 0 + arenaPollDeadline = GetTime() + 3.0 + arenaPoll:Show() +end + +function RelationshipsPVP_QueueArena(key) + local meta = ARENA_META[key] + if not meta then return end + local asGroup = IsInGroup() + lastQueuedArena = key + + -- 1) Direct TBC-style API — no panel involved. + if type(JoinArena) == "function" then + if type(SetArenaTeamSize) == "function" then SetArenaTeamSize(meta.size) end + JoinArena(meta.size, meta.rated, asGroup) + Print("Queued arena: |cffffd200" .. BG_NAMES[key] .. "|r" + .. (asGroup == 1 and " |cff66ff66(group)|r" or "")) + return + end + + -- 2) Turtle/Octo custom entry point. JoinArenaQueue(id) opens the + -- Blood Ring finder; we then click its Join button ourselves. + if type(JoinArenaQueue) == "function" then + local id = ARENA_IDS[key]; if id == nil then id = 0 end + if type(SetArenaTeamSize) == "function" then SetArenaTeamSize(meta.size) end + JoinArenaQueue(id) + StartArenaPoll() + Print("Queued arena: |cffffd200" .. BG_NAMES[key] .. "|r" + .. (asGroup == 1 and " |cff66ff66(group)|r" or "")) + return + end + + Print("|cffff4444Arena API not available on this client. Visit an Arena Battlemaster.|r") +end + +--------------------------------------------------------------- +-- Queue for all standard battlegrounds (no arenas, no rated) +--------------------------------------------------------------- +function RelationshipsPVP_QueueAllBGs() + local asGroup = IsInGroup() + local kicked = 0 + for i = 1, table.getn(BG_KEYS) do + local key = BG_KEYS[i] + if EnqueueAndKick({ kind = "bg", key = key, asGroup = asGroup }) then + kicked = kicked + 1 + end + end + if kicked > 0 then + Print("Queueing all standard battlegrounds (" + .. kicked .. "): |cffffd200AV, WSG, AB|r" + .. (asGroup == 1 and " |cff66ff66(group)|r" or "")) + else + Print("|cffff4444Battleground Finder not available on this client.|r") + end +end + +function RelationshipsPVP_LeaveAll() + local left = 0 + for i = 1, 3 do + local status = GetBattlefieldStatus(i) + if status == "queued" or status == "confirm" then + AcceptBattlefieldPort(i, 0) + left = left + 1 + end + end + if left > 0 then + Print("Left " .. left .. " pending queue(s).") + else + Print("No active queues.") + end + pendingQueue = {} + lastQueuedArena = nil +end + +--------------------------------------------------------------- +-- Minimap icon toggle +--------------------------------------------------------------- +function RelationshipsPVP_ToggleMinimapIcon() + local db = EnsureDB() + db.showIcon = not db.showIcon + ApplyIconVisibility() + Print(db.showIcon and "Minimap icon |cff66ff66shown|r." or "Minimap icon |cffff6666hidden|r. Use /rpvp to open.") +end + +--------------------------------------------------------------- +-- UI: build row FontStrings inside the stats panel +--------------------------------------------------------------- +local COL_X = { 14, 158, 208, 258, 308, 354, 400, 446, 496, 550, 614 } +local COL_W = { 140, 46, 46, 46, 46, 46, 46, 46, 50, 60, 58 } +local COL_J = { "LEFT", "CENTER", "CENTER", "CENTER", "CENTER", "CENTER", "CENTER", "CENTER", "CENTER", "CENTER", "CENTER" } +local COL_NAMES = { "bg", "games", "win", "loss", "ratio", "kb", "hk", "dth", "kd", "honor", "mk" } + +local ROW_HEIGHT = 22 +local ROW_TOP = -34 + +local function MakeRow(panel, tag, yOff, striped) + local row = {} + if striped then + local bg = panel:CreateTexture("RPVPRowBG"..tag, "BACKGROUND") + bg:SetTexture(1, 1, 1, 0.04) + bg:SetWidth(660); bg:SetHeight(ROW_HEIGHT - 2) + bg:SetPoint("TOPLEFT", panel, "TOPLEFT", 14, yOff + 2) + end + for c = 1, table.getn(COL_NAMES) do + local fs = panel:CreateFontString( + "RPVPRow"..tag.."_"..COL_NAMES[c], + "ARTWORK", + (c == 1) and "GameFontNormal" or "GameFontHighlight" + ) + fs:SetWidth(COL_W[c]); fs:SetHeight(18) + fs:SetJustifyH(COL_J[c]) + fs:SetPoint("TOPLEFT", panel, "TOPLEFT", COL_X[c], yOff) + row[COL_NAMES[c]] = fs + end + return row +end + +local function BuildStatsRows() + if rowStrings["_built"] then return end + local panel = RelationshipsPVPStatsPanel + if not panel then return end + + for i = 1, table.getn(ALL_KEYS) do + local key = ALL_KEYS[i] + local yOff = ROW_TOP - (i - 1) * ROW_HEIGHT + local row = MakeRow(panel, key, yOff, (math.mod(i, 2) == 0)) + local col = BG_COLORS[key] + row.bg:SetText(BG_NAMES[key]) + row.bg:SetTextColor(col.r, col.g, col.b) + rowStrings[key] = row + end + + local n = table.getn(ALL_KEYS) + local totalY = ROW_TOP - n * ROW_HEIGHT - 4 + local div = panel:CreateTexture("RPVPTotalDivider", "ARTWORK") + div:SetTexture(1, 1, 1, 0.25) + div:SetWidth(660); div:SetHeight(1) + div:SetPoint("TOPLEFT", panel, "TOPLEFT", 14, totalY + 2) + + totalRow = MakeRow(panel, "TOTAL", totalY - 6, false) + totalRow.bg:SetText("Total") + totalRow.bg:SetTextColor(1.0, 0.95, 0.7) + + rowStrings["_built"] = true +end + +local function FormatQueueLine() + local lines = "" + local any = false + for i = 1, 3 do + local status, mapName, _, _, _, teamSize, registered = GetBattlefieldStatus(i) + if status and status ~= "none" then + any = true + local color = "|cffffffff" + if status == "queued" then color = "|cffffff66" end + if status == "confirm" then color = "|cff66ff66" end + if status == "active" then color = "|cff66ccff" end + local label = mapName or "?" + if teamSize and teamSize > 0 then + label = label .. " (" .. teamSize .. "v" .. teamSize + .. ((registered == 1 or registered == true) and " rated" or " skirmish") .. ")" + end + lines = lines .. " [" .. i .. "] " .. label + .. " - " .. color .. status .. "|r\n" + end + end + if not any then return " |cff888888(no active queues)|r\n" end + return lines +end + +local function FillRow(r, s) + local games = s.wins + s.losses + local pct = 0 + if games > 0 then pct = math.floor((s.wins / games) * 100 + 0.5) end + local kd = "-" + if s.deaths > 0 then + kd = string.format("%.2f", (s.kills + s.hk) / s.deaths) + elseif (s.kills + s.hk) > 0 then + kd = "\226\136\158" + end + + r.games:SetText(games) + r.win:SetText(s.wins) + r.loss:SetText(s.losses) + r.ratio:SetText(pct .. "%") + r.kb:SetText(s.kills) + r.hk:SetText(s.hk) + r.dth:SetText(s.deaths) + r.kd:SetText(kd) + r.honor:SetText(FormatNumber(s.honor)) + r.mk:SetText(s.marks) + + if games == 0 then + r.ratio:SetTextColor(0.7, 0.7, 0.7) + elseif pct >= 60 then + r.ratio:SetTextColor(0.4, 1.0, 0.4) + elseif pct >= 40 then + r.ratio:SetTextColor(1.0, 0.9, 0.4) + else + r.ratio:SetTextColor(1.0, 0.5, 0.5) + end +end + +function RelationshipsPVP_Refresh() + if not RelationshipsPVPFrame or not RelationshipsPVPFrame:IsVisible() then return end + local db = EnsureDB() + BuildStatsRows() + + local totals = NewStatBlock() + for i = 1, table.getn(ALL_KEYS) do + local k = ALL_KEYS[i] + local s = db.stats[k] + local r = rowStrings[k] + if r then FillRow(r, s) end + + totals.wins = totals.wins + s.wins + totals.losses = totals.losses + s.losses + totals.kills = totals.kills + s.kills + totals.hk = totals.hk + s.hk + totals.deaths = totals.deaths + s.deaths + totals.honor = totals.honor + s.honor + totals.marks = totals.marks + s.marks + if s.bestStreak > totals.bestStreak then totals.bestStreak = s.bestStreak end + if s.bestKB > totals.bestKB then totals.bestKB = s.bestKB end + end + + if totalRow then FillRow(totalRow, totals) end + + if RelationshipsPVPExtrasText then + RelationshipsPVPExtrasText:SetText( + "|cffffd200Best win streak:|r " .. totals.bestStreak + .. " |cffffd200Best KBs (single match):|r " .. totals.bestKB + .. " |cffffd200Total honor:|r " .. FormatNumber(totals.honor) + ) + end + + local qtext = FormatQueueLine() + if currentBG then + qtext = qtext .. "\n |cff66ff66Currently inside: " .. BG_NAMES[currentBG] .. "|r" + end + if RelationshipsPVPQueueText then + RelationshipsPVPQueueText:SetText(qtext) + end +end + +--------------------------------------------------------------- +-- Reset panel + minimap button to the centre of the screen +--------------------------------------------------------------- +function RelationshipsPVP_ResetPositions() + local db = EnsureDB() + + if RelationshipsPVPFrame then + RelationshipsPVPFrame:ClearAllPoints() + RelationshipsPVPFrame:SetPoint("CENTER", UIParent, "CENTER", 0, 0) + RelationshipsPVPFrame:Show() + end + + if RelationshipsPVPMinimapButton then + RelationshipsPVPMinimapButton:ClearAllPoints() + RelationshipsPVPMinimapButton:SetPoint("CENTER", UIParent, "CENTER", 0, 0) + db.icon.point = "CENTER" + db.icon.x = 0 + db.icon.y = 0 + db.icon.parent = "UIParent" + end + + db.panel = { point = "CENTER", x = 0, y = 0 } + Print("Panel and minimap button reset to screen centre.") +end + +function RelationshipsPVP_ResetStats() + local db = EnsureDB() + for i = 1, table.getn(ALL_KEYS) do + db.stats[ALL_KEYS[i]] = NewStatBlock() + end + Print("PvP statistics cleared.") + RelationshipsPVP_Refresh() +end + +--------------------------------------------------------------- +-- Events +--------------------------------------------------------------- +function RelationshipsPVP_OnLoad() + this:RegisterEvent("PLAYER_LOGIN") + this:RegisterEvent("PLAYER_ENTERING_WORLD") + this:RegisterEvent("UPDATE_BATTLEFIELD_STATUS") + this:RegisterEvent("UPDATE_BATTLEFIELD_SCORE") + this:RegisterEvent("PLAYER_DEAD") + this:RegisterEvent("PLAYER_REGEN_ENABLED") + this:RegisterEvent("CHAT_MSG_BG_SYSTEM_NEUTRAL") + this:RegisterEvent("CHAT_MSG_BG_SYSTEM_ALLIANCE") + this:RegisterEvent("CHAT_MSG_BG_SYSTEM_HORDE") + + SLASH_RELATIONSHIPSPVP1 = "/rpvp" + SLASH_RELATIONSHIPSPVP2 = "/relpvp" + SLASH_RELATIONSHIPSPVP3 = "/relationshipspvp" + SlashCmdList["RELATIONSHIPSPVP"] = function(msg) + msg = string.lower(msg or "") + if msg == "leave" then + RelationshipsPVP_LeaveAll() + elseif msg == "av" then + RelationshipsPVP_Queue("AV") + elseif msg == "wsg" then + RelationshipsPVP_Queue("WSG") + elseif msg == "ab" then + RelationshipsPVP_Queue("AB") + elseif msg == "all" then + RelationshipsPVP_QueueAllBGs() + elseif msg == "2v2" then + RelationshipsPVP_QueueArena("A2") + elseif msg == "3v3" then + RelationshipsPVP_QueueArena("A3") + elseif msg == "5v5" then + RelationshipsPVP_QueueArena("A5") + elseif msg == "skirmish" or msg == "sk" then + RelationshipsPVP_QueueArena("SK") + elseif msg == "icon" or msg == "minimap" then + RelationshipsPVP_ToggleMinimapIcon() + elseif msg == "reset" then + RelationshipsPVP_ResetPositions() + elseif msg == "clear" or msg == "wipe" then + RelationshipsPVP_ResetStats() + else + if RelationshipsPVPFrame:IsVisible() then + RelationshipsPVPFrame:Hide() + else + RelationshipsPVPFrame:Show() + end + end + end + + Print("Loaded v1.8. Type /rpvp to open, /rpvp all to queue all BGs, /rpvp icon to toggle minimap icon.") +end + +function RelationshipsPVP_SavePanelPos() + SavePanelPos() +end + +function RelationshipsPVP_OnEvent(event) + if event == "PLAYER_LOGIN" or event == "PLAYER_ENTERING_WORLD" then + EnsureDB() + RestoreIconPos() + RestorePanelPos() + local key = DetectCurrentBG() + currentBG = key + if not key then endReported = {} end + RelationshipsPVP_Refresh() + + elseif event == "UPDATE_BATTLEFIELD_STATUS" then + local key = DetectCurrentBG() + currentBG = key + RelationshipsPVP_Refresh() + + elseif event == "UPDATE_BATTLEFIELD_SCORE" then + local key, idx = DetectCurrentBG() + if key and idx and GetBattlefieldWinner() then + RecordEnd(key, idx) + end + RelationshipsPVP_Refresh() + + elseif event == "PLAYER_REGEN_ENABLED" then + RelationshipsPVP_Refresh() + + elseif event == "CHAT_MSG_BG_SYSTEM_NEUTRAL" + or event == "CHAT_MSG_BG_SYSTEM_ALLIANCE" + or event == "CHAT_MSG_BG_SYSTEM_HORDE" then + RequestBattlefieldScoreData() + end +end diff --git a/RelationshipsPVP.toc b/RelationshipsPVP.toc new file mode 100644 index 0000000..2539193 --- /dev/null +++ b/RelationshipsPVP.toc @@ -0,0 +1,9 @@ +## Interface: 11200 +## Title: Relationships PVP +## Notes: PvP panel with per-battleground and per-arena stats, marks, kills/deaths, honor, K/D, and queue controls. Supports the Battleground Finder and Arena on OctoWoW. +## Author: Relationship +## Version: 1.0 +## SavedVariablesPerCharacter: RelationshipsPVPCharDB + +RelationshipsPVP.xml +RelationshipsPVP.lua diff --git a/RelationshipsPVP.xml b/RelationshipsPVP.xml new file mode 100644 index 0000000..50b9a50 --- /dev/null +++ b/RelationshipsPVP.xml @@ -0,0 +1,411 @@ + +