diff --git a/PBM/PBM_ClassTabs.lua b/PBM/PBM_ClassTabs.lua index 759e033..7629485 100644 --- a/PBM/PBM_ClassTabs.lua +++ b/PBM/PBM_ClassTabs.lua @@ -1,4 +1,4 @@ -PBM = PBM or {} +PBM = PBM or {} PBM.State = PBM.State or {} -- ── Class-tab drag-to-reorder state (mirrors LichborneTracker) ──── @@ -344,6 +344,12 @@ function PBM.OpenNameMenu(row) -- class (unscanned/empty) simply have no menu. local _rd = row.dbIndex and LichborneTrackerDB.rows[row.dbIndex] if not _rd then return end + if not _rd.name or _rd.name == "" then + if LichborneAddStatus then + LichborneAddStatus:SetText("|cffffaa44Empty slot. Sync the roster or add a target/group first.|r") + end + return + end local opener = CLASS_MENU_OPEN[_rd.cls] if not (opener and PBM[opener]) then return end @@ -918,8 +924,12 @@ function PBM.RefreshRows() -- Delete row.delBtn:SetScript("OnClick", function() local srcData = LichborneTrackerDB.rows[di] - if not srcData or not srcData.name or srcData.name == "" then return end - PBM.RemoveCharacterReferences(srcData.name) + if not srcData then return end + if srcData.name and srcData.name ~= "" then + PBM.RemoveCharacterReferences(srcData.name) + else + table.remove(LichborneTrackerDB.rows, di) + end PBM.RefreshRows() if PBM.State.overviewRowFrames and #PBM.State.overviewRowFrames > 0 then PBM.RefreshOverviewRows() end if PBM.State.raidRowFrames and #PBM.State.raidRowFrames > 0 then PBM.RefreshRaidRows() end diff --git a/PBM/PBM_RosterSync.lua b/PBM/PBM_RosterSync.lua new file mode 100644 index 0000000..b86fa73 --- /dev/null +++ b/PBM/PBM_RosterSync.lua @@ -0,0 +1,113 @@ +local function Trim(value) + return (value:gsub("^%s+", ""):gsub("%s+$", "")) +end + +local function NormalizeClass(name) + name = Trim(name) + if name == "DeathKnight" then name = "Death Knight" end + return PBM.CLASS_COLORS[name] and name or nil +end + +local function FindTrackedBot(name) + local wanted = name:lower() + for _, row in ipairs(LichborneTrackerDB.rows or {}) do + if row.name and row.name ~= "" and row.name:lower() == wanted then + return row + end + end +end + +local function AddTrackedBot(name, cls) + local existing = FindTrackedBot(name) + if existing then return false end + + PBM.EnsureClass(cls) + local slot + for _, row in ipairs(LichborneTrackerDB.rows) do + if row.cls == cls and (not row.name or row.name == "") then + slot = row + break + end + end + if not slot then + slot = PBM.DefaultRow(cls) + table.insert(LichborneTrackerDB.rows, slot) + end + + slot.name = name + return true +end + +-- Parse the exact response emitted by mod-playerbots ListBots(): +-- Bot roster: +OnlineName Class, -OfflineName Class +-- Returns recognized, numberAdded, numberListed. +function PBM.ImportBotRosterMessage(message) + if type(message) ~= "string" then return false end + + local clean = message:gsub("|c%x%x%x%x%x%x%x%x", ""):gsub("|r", "") + local payload = clean:match("^Bot roster:%s*(.*)$") + if payload == nil then return false end + + local added, total = 0, 0 + for rawEntry in payload:gmatch("([^,]+)") do + local entry = Trim(rawEntry) + local _, name, rawClass = entry:match("^([+-])(%S+)%s+(.+)$") + local cls = rawClass and NormalizeClass(rawClass) + if name and cls then + total = total + 1 + if AddTrackedBot(name, cls) then added = added + 1 end + end + end + + if PBM.RefreshRows then PBM.RefreshRows() end + if PBM.State.overviewRowFrames and #PBM.State.overviewRowFrames > 0 and PBM.RefreshOverviewRows then + PBM.RefreshOverviewRows() + end + + return true, added, total +end + +function PBM.RequestBotRoster() + SendChatMessage(".playerbots bot list", "SAY") +end + +local rosterTimerFrame +local rosterScheduleGeneration = 0 + +function PBM.ScheduleBotRosterRequest(delay) + rosterScheduleGeneration = rosterScheduleGeneration + 1 + local generation = rosterScheduleGeneration + local function requestRoster() + if generation == rosterScheduleGeneration then PBM.RequestBotRoster() end + end + + if C_Timer and C_Timer.After then + C_Timer.After(delay, requestRoster) + return + end + + if not rosterTimerFrame then rosterTimerFrame = CreateFrame("Frame") end + local elapsed = 0 + rosterTimerFrame:SetScript("OnUpdate", function(self, delta) + elapsed = elapsed + delta + if elapsed >= delay then + self:SetScript("OnUpdate", nil) + requestRoster() + end + end) +end + +local rosterEventFrame = CreateFrame and CreateFrame("Frame", "PBMRosterSyncEventFrame") +if rosterEventFrame then + rosterEventFrame:RegisterEvent("CHAT_MSG_SYSTEM") + rosterEventFrame:SetScript("OnEvent", function(_, _, message) + local matched, added, total = PBM.ImportBotRosterMessage(message) + if not matched then return end + + local status = "|cff44ff44Roster synced: " .. total .. " found, " .. added .. " added.|r" + if LichborneAddStatus then LichborneAddStatus:SetText(status) end + if LichborneOutput then + LichborneOutput("|cffC69B3APBM:|r Roster synced: " .. total .. " found, " .. added .. " added.", 1, 0.85, 0) + end + end) +end diff --git a/PBM/PBM_TrackerCore.lua b/PBM/PBM_TrackerCore.lua index 3a276d3..2a6e102 100644 --- a/PBM/PBM_TrackerCore.lua +++ b/PBM/PBM_TrackerCore.lua @@ -21,6 +21,7 @@ local function OnFirstShow() if PBM.State.setupDone then return end PBM.State.setupDone = true local f = LichborneTrackerFrame + f:SetToplevel(true) local fl = f:GetFrameLevel() -- Tabs (centered in frame) @@ -1122,13 +1123,18 @@ local function OnFirstShow() local loginBtn = MakeSimpleBtn("LichborneLoginBtn", "|cffd4af37Log in All Bots|r", 0.1, 0.6, 0.2, 335, 110, - 155, {{"Log in All Bots",0.78,0.61,0.23},{".playerbots bot add *",0.8,0.8,0.8}}) - loginBtn:SetScript("OnClick", function() SendChatMessage(".playerbots bot add *", "PARTY") end) + 155, {{"Log in All Bots",0.78,0.61,0.23},{"Logs in bots, then syncs the server roster.",0.8,0.8,0.8}}) + loginBtn:SetScript("OnClick", function() + if LichborneAddStatus then LichborneAddStatus:SetText("|cffffff88Logging in bots and requesting roster...|r") end + -- Use SAY to reach handler outside groups + SendChatMessage(".playerbots bot add *", "SAY") + PBM.ScheduleBotRosterRequest(0.75) + end) local logoutBtn = MakeSimpleBtn("LichborneLogoutBtn", "|cffd4af37Log Out All Bots|r", 0.90, 0.20, 0.20, 335, 76, 155, {{"Log Out All Bots",0.78,0.61,0.23},{".playerbots bot remove *",0.8,0.8,0.8}}) - logoutBtn:SetScript("OnClick", function() SendChatMessage(".playerbots bot remove *", "PARTY") end) + logoutBtn:SetScript("OnClick", function() SendChatMessage(".playerbots bot remove *", "SAY") end) -- ── Remove Orphaned Bots button ──────────────────────────── -- Sends .playerbots bot remove for every character in the Overview tab roster diff --git a/PlayerBotManager.toc b/PlayerBotManager.toc index 0e5170d..7cd4f88 100644 --- a/PlayerBotManager.toc +++ b/PlayerBotManager.toc @@ -18,6 +18,7 @@ LichborneTracker.xml PBM\PBM_Constants.lua PBM\IPTiersColor.lua PBM\PBM_Database.lua +PBM\PBM_RosterSync.lua PBM\PBM_GearScore.lua PBM\PBM_Needs.lua PBM\PBM_Sort.lua