local LFGBSPrivate = { CommentPlayer = {}, Map = { Cmp = {}, }, TrackerFrame = {}, SizeStat = {}, }; local function Message(s) DEFAULT_CHAT_FRAME:AddMessage(s); end -- -- local function DaysNum2Str(DAYS) local sDays = "MTWTFSS"; local mask, i = 1; local sResult = ""; for i = 1, 7 do if (bit.band(DAYS, mask) == mask) then sResult = sResult .. strsub(sDays, i, i); else sResult = sResult .. "_"; end mask = mask * 2; end return sResult; end -- -- -- local LookingForGuildBrowseSortListLocal; local GUILDLIST = nil; local GUILDNAMES = nil; local GUILDNAMESOLD = nil; local GUILDCOMMENTS = nil; local GUILDSCORE = nil; local GUILDSCOREOLD = nil; local GUILDMEMBERCOUNTS = nil; local GUILDRANKS = nil; local GUILDTRACKEDCUT = nil; local GUILDSORT = "SCORE"; local Cmd; -- forward declaration local function MapReset(sSrc) -- Message("Recruiting guilds: preparing for list update... (" .. tostring(sSrc) .. ")"); if ((GUILDSORT or "SCORE") == "SCORE") then GUILDNAMESOLD = GUILDNAMES; GUILDSCOREOLD = GUILDSCORE; end GUILDLIST = nil; GUILDNAMES = nil; GUILDCOMMENTS = nil; GUILDSCORE = nil; GUILDMEMBERCOUNTS = nil; GUILDRANKS = nil; GUILDTRACKEDCUT = nil; end -- -- -- local CHOICES_SHORTLONG = { Q = "QUEST", D = "DUNGEON", R = "RAID", P = "PVP", O = "RP", Y = "WEEKDAYS", E = "WEEKENDS" }; local CHOICES_SHORTINDEX = { Q = 1, D = 2, R = 3, P = 4, O = 5, Y = 6, E = 7, T = 8, H = 9, S = 10 }; local CHOICES_INDEXLONG = { [1] = "QUEST", [2] = "DUNGEON", [3] = "RAID", [4] = "PVP", [5] = "RP", [6] = "WEEKDAYS", [7] = "WEEKENDS" }; local oMUST = nil; function LFGBSPrivate.CommentPlayer.Show() local cOption, iOption; for cOption, iOption in pairs(CHOICES_SHORTINDEX) do local sOption = CHOICES_SHORTLONG[cOption]; if (sOption) then -- roles are not in CHOICES_SHORTLONG local sTextKey = _G["GUILD_INTEREST_" .. sOption] or _G["GUILD_AVAILABILITY_" .. sOption]; if (not sTextKey) then Message("LFGBS::CPS: " .. sOption .. " => !Group"); else local sOptionCamel = strupper(strsub(sOption, 1, 1)) .. strlower(strsub(sOption, 2)); local iLen = strlen(sOptionCamel); if (strsub(sOptionCamel, iLen, iLen) == 'p') then -- RP, PvP: special capitalization sOptionCamel = strsub(sOptionCamel, 1, iLen - 1) .. "P"; end local oButton = _G["LookingForGuild" .. sOptionCamel .. "Text"] or _G["LookingForGuild" .. sOptionCamel .. "ButtonText"]; if (not oButton) then Message("LFGBS::CPS: " .. sOptionCamel .. " => !Button"); else local sText = oButton:GetText(); if (oMUST[sOption]) then if (strsub(sText, 1, 2) ~= "! ") then oButton:SetText("! " .. sText); end else if (strsub(sText, 1, 2) == "! ") then oButton:SetText(strsub(sText, 3)); end end end end end end end function LFGBSPrivate.CommentPlayer.Parse() local sPComment, bMUSTValid = GetLookingForGuildComment(); if (sPComment == LFGBSPrivate.CommentPlayer.sPComment) then return (oMUST ~= nil) and (next(oMUST) ~= nil); end LFGBSPrivate.CommentPlayer.sPComment = sPComment; local iLen = strlen(sPComment); if (iLen <= 1) then oMUST = nil; return bMUSTValid; end oMUST = {}; if (strsub(sPComment, iLen, iLen) == "$") then -- stronger WANTs/DONTWANTs local i = iLen - 1; while (i > 0 and strsub(sPComment, i, i) ~= "$") do local c = strsub(sPComment, i, i); if (CHOICES_SHORTLONG[c]) then oMUST[CHOICES_SHORTLONG[c]] = true; end if (c >= '0' and c <= '9') then oMUST.DAYS = (oMUST.DAYS or 0) * 8 + tonumber(c); end if ((c == "!") and (i >= 2)) then -- next (previous) char defines the minimum size of the guild (raids) -- simply taking the alphabetic position as min size local c_next = strbyte(sPComment, i - 1, i - 1) - strbyte("A"); if (c_next >= 0 and c_next <= 25) then i = i - 1; oMUST.SIZE = 5 + 2 * c_next; end end if ((c == "#") and (i >= 2)) then -- next (previous) char defines the minimum rank of the guild (perks) -- simply taking the alphabetic position as min rank local c_next = strbyte(sPComment, i - 1, i - 1) - strbyte("A"); if (c_next >= 0 and c_next <= 25) then i = i - 1; oMUST.RANK = c_next; end end if ((c == "?") and (i >= 3)) then -- next (previous) TWO char defines the play time (to, from) -- A-X = 0:00 .. 23:00, a-x = 0:30 .. 23:30 local _A = strbyte("A"); local _a = strbyte("a"); local c_from, i_from = strbyte(sPComment, i - 2, i - 2); if ((c_from >= _A) and (c_from <= _A + 23)) then i_from = c_from - _A; elseif ((c_from >= _a) and (c_from <= _a + 23)) then i_from = c_from - _a + 0.5; end local c_to, i_to = strbyte(sPComment, i - 1, i - 1); if ((c_to >= _A) and (c_to <= _A + 23)) then i_to = c_to - _A; elseif ((c_to >= _a) and (c_to <= _a + 23)) then i_to = c_to - _a + 0.5; end if (i_from and i_to) then i = i - 2; oMUST.TIME = { FROM = i_from, TO = i_to }; end end i = i - 1; end if (i > 0 and strsub(sPComment, i, i) == "$") then bMUSTValid = true; LookingForGuildBrowseSortListLocal.Config.TIME = oMUST.TIME; LFGBSPrivate.CommentPlayer.Show(); end end return bMUSTValid; end -- create comment blob from oMUST function LFGBSPrivate.CommentPlayer.Compile() local sResult = ""; if (oMUST and (next(oMUST) ~= nil)) then local k, v; for k, v in pairs(CHOICES_SHORTLONG) do if (oMUST[v]) then sResult = k .. sResult; end end local iNull = strbyte("0"); local iA = strbyte("A"); local ia = strbyte("a"); if ((type(oMUST.DAYS) == "number") and (oMUST.DAYS > 0)) then local iDays = oMUST.DAYS; local sDays = strchar(iNull + bit.band(iDays, 7)) .. strchar(iNull + bit.band(bit.rshift(iDays, 3), 7)) .. strchar(iNull + bit.rshift(iDays, 6)); sResult = sDays .. sResult; end if ((type(oMUST.SIZE) == "number") and (oMUST.SIZE >= 5)) then sResult = strchar(iA + floor(oMUST.SIZE - 5) / 2) .. "!" .. sResult; end if ((type(oMUST.RANK) == "number") and (oMUST.RANK > 0)) then sResult = strchar(iA + oMUST.RANK) .. "#" .. sResult; end if (type(oMUST.TIME) == "table") then sResult = "?" .. sResult; local i, iTime; for i = 1, 2 do if (i == 1) then iTime = oMUST.TIME.TO; else iTime = oMUST.TIME.FROM; end local bSmall = iTime ~= floor(iTime); if (bSmall) then sResult = strchar(ia + floor(iTime)) .. sResult; else sResult = strchar(iA + iTime ) .. sResult; end end end end return sResult; end -- update comment blob function LFGBSPrivate.CommentPlayer.Update() local sPComment = GetLookingForGuildComment(); LFGBSPrivate.CommentPlayer.sPComment = sPComment; local iCommentDataStartsAt = nil; local iLen = strlen(sPComment); if (iLen > 1) then if (strsub(sPComment, iLen, iLen) == "$") then local i = iLen - 1; while (i > 0 and strsub(sPComment, i, i) ~= "$") do i = i - 1; end if ((i > 0) and (strsub(sPComment, i, i) == "$")) then iCommentDataStartsAt = i; end end end local sBlob = LFGBSPrivate.CommentPlayer.Compile(); if (sBlob ~= "") then sBlob = "$" .. sBlob .. "$"; end -- insert, change or delete comment blob if (iCommentDataStartsAt) then sPComment = strsub(sPComment, 1, iCommentDataStartsAt - 1) .. sBlob; else if (iLen > 0) then sPComment = sPComment .. " " .. sBlob; else sPComment = sPComment .. sBlob; end end if (LFGBSPrivate.CommentPlayer.sPComment ~= sPComment) then LFGBSPrivate.CommentPlayer.sPComment = nil; SetLookingForGuildComment(sPComment); LookingForGuildCommentEditBox:SetText(sPComment); LFGBSPrivate.CommentPlayer.Show(); return true; end end function LFGBSPrivate.CommentPlayer.ButtonToggle(self, btn, holding) -- hooked, so called after default handler -- Message("Btn:OnClick(" .. btn .. ")"); if (btn == "RightButton") then -- checkboxes: toggle MUST|MUSTN'T local sName = self:GetName(); local sLong = strsub(sName, 16, -7); sLong = strupper(sLong); Cmd("toggle " .. sLong, true); -- undo change from regular handler self:Click("LeftButton"); SetLookingForGuildSettings(self:GetID(), self:GetChecked()); MapReset(); end end local GetNumRecruitingGuilds_original; local iGetNumRecruitingGuildsCut; local GetRecruitingGuildInfo_original; local GetRecruitingGuildSettings_original; local GroupGuildnameColors = { [1] = { r = 0.5, g = 1, b = 0.5 }, [2] = { r = 0.5, g = 0.5, b = 1 }, [3] = { r = 1, g = 1, b = 0.5 }, [4] = { r = 1, g = 0.5, b = 0.5 }, [5] = { r = 0.5, g = 0.5, b = 0.5 }, -- grey: too small [9] = { r = 0.5, g = 0.5, b = 0.5 }, -- grey: no members }; local function MapCollate(oG) local iMaxOld = GUILDNAMESOLD and #GUILDNAMESOLD or -1; GUILDLIST = {}; GUILDNAMES = {}; GUILDCOMMENTS = {}; GUILDSCORE = {}; GUILDMEMBERCOUNTS = {}; GUILDRANKS = {}; GUILDTRACKEDCUT = {}; local oTracked = {}; do local iTrackedMax, iTracked = #LookingForGuildBrowseSortListLocal.Tracker; for iTracked = 1, iTrackedMax do oTracked[LookingForGuildBrowseSortListLocal.Tracker[iTracked].Name] = iTracked; end end iGetNumRecruitingGuildsCut = 0; local iG; for iG = 1, #oG do local iMax, i = #oG[iG]; if (iG <= 3) then iGetNumRecruitingGuildsCut = iGetNumRecruitingGuildsCut + iMax; end for i = 1, iMax do tinsert(GUILDLIST, oG[iG][i].index); tinsert(GUILDNAMES, oG[iG][i].name); tinsert(GUILDCOMMENTS, oG[iG][i].infotext); tinsert(GUILDSCORE, oG[iG][i].scoreOuter); tinsert(GUILDMEMBERCOUNTS, oG[iG][i].members); tinsert(GUILDRANKS, oG[iG][i].rank); if (iG > 3) and (next(oTracked) ~= nil) then -- if tracked, add to extra list if (oTracked[oG[iG][i].name]) then tinsert(GUILDTRACKEDCUT, #GUILDLIST); end oTracked[oG[iG][i].name] = nil; end end end if (LookingForGuildBrowseSortListLocal == nil) then LookingForGuildBrowseSortListLocal = {}; end if (LookingForGuildBrowseSortListLocal.Debug) then LookingForGuildBrowseSortListLocal.Score = { M = oMUST, L = GUILDLIST, N = GUILDNAMES }; end local iMax, i = #GUILDNAMES; if (iMax ~= iMaxOld) then return true; else for i = 1, iMax do if ( (GUILDNAMES[i] ~= GUILDNAMESOLD[i]) or (GUILDSCORE[i] ~= GUILDSCOREOLD[i]) ) then return true; end end end return false; end local function MapCreateByScore() local oSettingsPlayer = { GetLookingForGuildSettings() }; local bMUSTValid = LFGBSPrivate.CommentPlayer.Parse(); local iCnt, i = GetNumRecruitingGuilds_original(); local oG = { [1] = {}, [2] = {}, [3] = {}, [4] = {}, [5] = {} }; for i = 1, iCnt do local iScore, iG = 0; local name, rank, members, achieved, infotext, cached, requestPending = GetRecruitingGuildInfo_original(i); if (members == 0) then -- lots of empty guilds on PTR... iG = #oG; iScore = 1000; else local iLen = strlen(infotext); local oREQ, bREQValid = {}; if (iLen > 1) then if (strsub(infotext, iLen, iLen) == "$") then -- stronger WANTs/DONTWANTs local i = iLen - 1; while (i > 0 and strsub(infotext, i, i) ~= "$") do local c = strsub(infotext, i, i); if (c >= '0' and c <= '9') then oREQ.DAYS = (oREQ.DAYS or 0) * 8 + tonumber(c); end i = i - 1; end if (i > 0 and strsub(sPComment, i, i) == "$") then bREQValid = true; end end end -- the server already checks that at least one of each group (interest, schedule, role) is selected by player and guild local oSettingsGuild, iOpt = { GetRecruitingGuildSettings_original(i) }; local bSame = true; for iOpt = 1, 7 do if (oSettingsPlayer[iOpt] ~= oSettingsGuild[iOpt]) then bSame = false; break; end end if (bSame) then -- G1: perfect match iG = 1; else -- G2/G3: guild has what we require, and has not what we refuse (MUST|MUSTN'T is valid) -- G2: guild has some extras player doesn't have -- G3: guild misses an extra player does want -- G4: guild has a fundamentally different orientation (violating one or more MUST|MUSTN'T choices) -- G5: guild is too small iG = 4; --[[ MUST PLAYER GUILD GROUP M P G G 1 0 0 2 1 1 1 2 1 0 1 4 1 1 0 4 0 0 0 2 0 0 1 2 0 1 0 3 0 1 1 2 ]]-- if (bMUSTValid) then local bViolation = false; local bMissing = false; for iOpt = 1, 7 do if (oMUST[CHOICES_INDEXLONG[iOpt]]) then if (oSettingsPlayer[iOpt] ~= oSettingsGuild[iOpt]) then bViolation = true; end elseif (oSettingsPlayer[iOpt] and not oSettingsGuild[iOpt]) then bMissing = true; end end if (oMUST.DAYS and oREQ.DAYS and (bit.band(oMUST.DAYS, oREQ.DAYS) == 0)) then bViolation = true; end if (oMUST.SIZE and (members < oMUST.SIZE)) then bViolation = true; iG = 5; end if (oMUST.RANK and (rank < oMUST.RANK)) then bViolation = true; iG = 5; end if (not bViolation) then iG = 2; if (bMissing) then iG = 3; end if (oMUST.DAYS and oREQ.DAYS and bit.band(oMUST.DAYS, oREQ.DAYS) ~= 0) then local iNum, mask = 0, 1; for i = 1, 7 do if (bit.band(oMUST.DAYS, mask) ~= bit.band(oREQ.DAYS, mask)) then iNum = iNum + 1; end mask = mask * 2; end iScore = iScore + iNum * 25; end end else local bExtra = false; local bMissing = false; for iOpt = 1, 7 do if (not oSettingsPlayer[iOpt] and oSettingsGuild[iOpt]) then bExtra = true; end if ( oSettingsPlayer[iOpt] and not oSettingsGuild[iOpt]) then bMissing = true; end end iG = 1; -- hm, cannot happen, really... if (bExtra) then iG = 2; end if (bMissing) then iG = 3; end end local oMalus = { 10, 10, 10, 25, 10, 15, 15 }; for iOpt = 1, 7 do if (oSettingsPlayer[iOpt] ~= oSettingsGuild[iOpt]) then local iMulti = 1; if (oSettingsPlayer[iOpt]) then iMulti = 2; end if (bMUSTValid and oMUST[CHOICES_INDEXLONG[iOpt]]) then iMulti = 10; end iScore = iScore + oMalus[iOpt] * iMulti; end end end end local fScore = iScore + 10000 * (iG - 1) + 1 / (5000 - members); tinsert(oG[iG], { index = i, name = name, infotext = infotext, scoreInner = iScore, scoreOuter = fScore, rank = rank, members = members } ); end local oTracked = {}; local iTrackedMax, iTracked = #LookingForGuildBrowseSortListLocal.Tracker; for iTracked = 1, iTrackedMax do oTracked[LookingForGuildBrowseSortListLocal.Tracker[iTracked].Name] = iTracked; end local oColor = GroupGuildnameColors[1]; local sColor = format("|cFF%.2X%.2X%.2X", oColor.r * 255, oColor.g * 255, oColor.b * 255); local sTotals = "[Recruiting guilds] " .. sColor .. "perfect matches: "; local iG; for iG = 1, #oG do local iMax, i, j = #oG[iG]; for i = 1, iMax - 1 do for j = i + 1, iMax do if ( (oG[iG][i].scoreInner > oG[iG][j].scoreInner) or ( (oG[iG][i].scoreInner == oG[iG][j].scoreInner) and ( (oG[iG][i].rank < oG[iG][j].rank) or (oG[iG][i].rank == oG[iG][j].rank) and (oG[iG][i].members < oG[iG][j].members) ) ) ) then local tmp = oG[iG][i]; oG[iG][i] = oG[iG][j]; oG[iG][j] = tmp; end end end end local bModified = MapCollate(oG); if (bModified) then local nTrackedTotal = 0; for iG = 1, #oG do local nTracked = 0; local iMax, i, j = #oG[iG]; for i = 1, iMax do if (oTracked[oG[iG][i].name]) then nTracked = nTracked + 1; end end if ((iG ~= 5) or (#oG[5] > 0)) then sTotals = sTotals .. (iMax > 0 and iMax or "none"); if (nTracked > 0) then nTrackedTotal = nTrackedTotal + nTracked; sTotals = sTotals .. " (tracked: " .. nTracked .. ")"; end end if (iG == 1) then local oColor = GroupGuildnameColors[2]; local sColor = format("|cFF%.2X%.2X%.2X", oColor.r * 255, oColor.g * 255, oColor.b * 255); sTotals = sTotals .. "|r, " .. sColor .. "must-and-mustn't-plus-extras matches: "; elseif (iG == 2) then local oColor = GroupGuildnameColors[3]; local sColor = format("|cFF%.2X%.2X%.2X", oColor.r * 255, oColor.g * 255, oColor.b * 255); sTotals = sTotals .. "|r, " .. sColor .. "missing-optional-choices matches: "; elseif (iG == 3) then local oColor = GroupGuildnameColors[4]; local sColor = format("|cFF%.2X%.2X%.2X", oColor.r * 255, oColor.g * 255, oColor.b * 255); sTotals = sTotals .. "|r, " .. sColor .. "strong-differences (mis)matches: "; elseif (iG == 4) and (#oG[5] > 0) then local oColor = GroupGuildnameColors[5]; local sColor = format("|cFF%.2X%.2X%.2X", oColor.r * 255, oColor.g * 255, oColor.b * 255); sTotals = sTotals .. "|r, " .. sColor .. "too small/too low to consider: "; end end sTotals = sTotals .. "|r"; if (LookingForGuildBrowseSortListLocal.Config.HideRedGrey) then if ((type(GUILDTRACKEDCUT) == "table") and (#GUILDTRACKEDCUT > 0)) then sTotals = sTotals .. " - red/grey tracked: " .. #GUILDTRACKEDCUT; end end if (iTrackedMax > nTrackedTotal) then sTotals = sTotals .. " - tracked unlisted: " .. (iTrackedMax - nTrackedTotal); end Message(sTotals); end end function LFGBSPrivate.Map.Cmp.NAME(oA, oB) return strlower(oA.name) > strlower(oB.name); end function LFGBSPrivate.Map.Cmp.SIZE(oA, oB) return (oA.members < oB.members) or (oA.members == oB.members) and (oA.rank < oB.rank); end function LFGBSPrivate.Map.Cmp.RANK(oA, oB) return (oA.rank < oB.rank) or (oA.rank == oB.rank) and (oA.members < oB.members); end local function MapCreateByNameOrSizeOrRank(bName) local iCnt, i = GetNumRecruitingGuilds_original(); local oG, iG = { [1] = {}, [2] = {} }; local iSizeMin = oMUST and oMUST.SIZE or 0; local iRankMin = oMUST and oMUST.RANK or 0; for i = 1, iCnt do local name, rank, members, achieved, infotext, cached, requestPending = GetRecruitingGuildInfo_original(i); iG = 1; if (GUILDSORT == "SIZE") then if (members < iSizeMin) then iG = 2; end elseif (GUILDSORT == "RANK") then if (rank < iRankMin) then iG = 2; end end local fScore = 40000.001 * (iG - 1); tinsert(oG[iG], { index = i, name = name, rank = rank, members = members, infotext = infotext, scoreOuter = fScore } ); end local fSort = LFGBSPrivate.Map.Cmp[GUILDSORT]; local iG; for iG = 1, #oG do local iMax, i, j = #oG[iG]; for i = 1, iMax - 1 do for j = i + 1, iMax do if (fSort(oG[iG][i], oG[iG][j])) then local tmp = oG[iG][i]; oG[iG][i] = oG[iG][j]; oG[iG][j] = tmp; end end end end MapCollate(oG); iGetNumRecruitingGuildsCut = nil; end local function MapCreate() if (GUILDSORT == "NAME") then MapCreateByNameOrSizeOrRank(); elseif (GUILDSORT == "SIZE") then MapCreateByNameOrSizeOrRank(); elseif (GUILDSORT == "RANK") then MapCreateByNameOrSizeOrRank(); else GUILDSORT = "SCORE"; MapCreateByScore(); end end local function MapDo(iNum) if (GUILDLIST == nil) then MapCreate(); end if (GUILDLIST ~= nil) then if (LookingForGuildBrowseSortListLocal.Config.HideRedGrey and (iGetNumRecruitingGuildsCut ~= nil)) then if (iNum > iGetNumRecruitingGuildsCut) then local iSub = iNum - iGetNumRecruitingGuildsCut; if (type(GUILDTRACKEDCUT) == "table") and (iSub <= #GUILDTRACKEDCUT) then iSub = GUILDTRACKEDCUT[iSub]; return GUILDLIST[iSub]; end end end return GUILDLIST[iNum]; else return iNum; end end local function GetNumRecruitingGuilds_ordered() if (LookingForGuildBrowseSortListLocal.Config.HideRedGrey and (iGetNumRecruitingGuildsCut ~= nil)) then local iTotal = iGetNumRecruitingGuildsCut or 0; if (type(GUILDTRACKEDCUT) == "table") then iTotal = iTotal + #GUILDTRACKEDCUT; end return iTotal; end return GetNumRecruitingGuilds_original(); end local function GetRecruitingGuildSettings_ordered(iNum) local _iNum = MapDo(iNum); if (not _iNum) then Message("[LFGBS] GRGS: Index " .. iNum .. " -> nil"); return GetRecruitingGuildSettings_original(iNum); else local bQuetes = GetRecruitingGuildSettings_original(_iNum); if (bQuetes == nil) then Message("[LFGBS] GRGS: Index " .. _iNum .. " (from " .. iNum .. ") -> nil"); return GetRecruitingGuildSettings_original(iNum); end end return GetRecruitingGuildSettings_original(_iNum); end local function GetRecruitingGuildInfo_ordered(iNum) local _iNum = MapDo(iNum); if (not _iNum) then Message("[LFGBS] GRGI: Index " .. iNum .. " -> nil"); return GetRecruitingGuildInfo_original(iNum); else local sName = GetRecruitingGuildInfo_original(_iNum); if (sName == nil) then Message("[LFGBS] GRGI: Index " .. _iNum .. " (from " .. iNum .. ") -> nil"); return GetRecruitingGuildInfo_original(iNum); end end return GetRecruitingGuildInfo_original(_iNum); end local function GuildnameColorize() local sIndices = ""; local oButtons = LookingForGuildBrowseFrameContainer.buttons; local iCnt, i = #oButtons; for i = 1, iCnt do local oButton = oButtons[i]; if (oButton:IsShown()) then local iIndex, oColor = oButton.index; sIndices = sIndices .. ", " .. iIndex; if (not GUILDSCORE or (next(GUILDSCORE) == nil) or (GUILDSCORE[iIndex] == 0)) then -- sorted by name/size oColor = { r = 1, g = 1, b = 0.5 }; else local iScore = GUILDSCORE[iIndex]; local iMembers = GUILDMEMBERCOUNTS[iIndex]; oColor = GroupGuildnameColors[9]; if (iMembers >= 0.5) then local iColorIndex = 1 + math.floor(iScore / 10000); if (GroupGuildnameColors[iColorIndex]) then oColor = GroupGuildnameColors[iColorIndex]; end end end local oName = oButton.name; if (oColor) then oName:SetTextColor(oColor.r, oColor.g, oColor.b, 1); else oName:SetTextColor(1, 1, 0.5, 1); end end end if (GameTooltip:IsShown()) then local oFrame = GetMouseFocus(); if (oFrame and (type(oFrame) == "table") and (type(oFrame.GetName) == "function")) then local sName = oFrame:GetName(); if (strsub(sName, 1, 41) == "LookingForGuildBrowseFrameContainerButton") then -- Message("[LFGBS] Refreshing tooltip..."); local OnLeave = oFrame:GetScript("OnLeave"); if (type(OnLeave) == "function") then OnLeave(oFrame); end local OnEnter = oFrame:GetScript("OnEnter"); if (type(OnEnter) == "function") then OnEnter(oFrame); end end end end -- Message("[LFGBS] GnC -> " .. strsub(sIndices, 2)); end local bLookingForGuildGuild_ShowTooltip_Hooked; local function LookingForGuildGuild_ShowTooltip_Hook(self) if (LookingForGuildBrowseSortListLocal == nil) then LookingForGuildBrowseSortListLocal = {}; end if (LookingForGuildBrowseSortListLocal.Debug) then LookingForGuildBrowseSortListLocal.Tip = {}; end local bShow; local oPLFG = { GetLookingForGuildSettings() }; local oMissing = {}; local iLineCnt, i = GameTooltip:NumLines(); for i = 1, iLineCnt do -- recolorize local oText = _G["GameTooltipTextLeft" .. i]; local sLine = oText:GetText(); if ((strfind(sLine, GUILD_INTEREST, 1, true) == 1) or (strfind(sLine, GUILD_AVAILABILITY, 1, true) == 1)) then local sPrefix; if (strfind(sLine, GUILD_INTEREST, 1, true) == 1) then sPrefix = "GUILD_INTEREST_"; elseif (strfind(sLine, GUILD_AVAILABILITY, 1, true) == 1) then sPrefix = "GUILD_AVAILABILITY_"; end if (sPrefix) then local k, v; if (oMUST ~= nil) then for k, v in pairs(oMUST) do local sSub, iPosA, iPosB if (type(v) == "boolean") then sSub = _G[sPrefix .. k]; end if (sSub) then iPosA, iPosB = strfind(sLine, sSub, 1, true); if (iPosA and iPosB) then local iChoice, l, w; for l, w in pairs(CHOICES_SHORTLONG) do if (w == k) then iChoice = CHOICES_SHORTINDEX[l]; end end if (iChoice) then local sFlag = ""; local sColor = "30FF30"; if (not oPLFG[iChoice]) then sColor = "FF5050"; sFlag = " (MUSTN'T)" end oPLFG[iChoice] = nil; sLine = strsub(sLine, 1, iPosA - 1) .. "|r|cFF" .. sColor .. strsub(sLine, iPosA, iPosB) .. sFlag .. "|r" .. HIGHLIGHT_FONT_COLOR_CODE .. strsub(sLine, iPosB + 1); end else -- Message("LFGBS::LFGG_ST: " .. sPrefix .. "::" .. sSub .. " !€ " .. sLine); -- if (LookingForGuildBrowseSortListLocal.Debug) then -- LookingForGuildBrowseSortListLocal.Tip[100] = { sSub = sSub, sLine = sLine }; -- end local iChoice, l, w; for l, w in pairs(CHOICES_SHORTLONG) do if (w == k) then iChoice = CHOICES_SHORTINDEX[l]; end end if (iChoice and oPLFG[iChoice]) then oMissing[k] = true; oPLFG[iChoice] = nil; end end end end end for k, v in pairs(CHOICES_SHORTLONG) do local iChoice = CHOICES_SHORTINDEX[k]; if (oPLFG[iChoice]) then local sSub, iPosA, iPosB = _G[sPrefix .. v]; if (sSub) then local iPosA, iPosB = strfind(sLine, sSub, 1, true); if (iPosA and iPosB) then sLine = strsub(sLine, 1, iPosA - 1) .. "|r|cFFA0A0FF" .. strsub(sLine, iPosA, iPosB) .. "|r" .. HIGHLIGHT_FONT_COLOR_CODE .. strsub(sLine, iPosB + 1); else sLine = sLine .. "\n" .. QUEST_DASH .. "|r|cFFFFFF00no " .. sSub .. " (wanted)|r"; end end end end for k, v in pairs(oMissing) do local sMissing = _G[sPrefix .. k]; if (sMissing) then sLine = sLine .. "\n" .. QUEST_DASH .. "|r|cFFFF5050no " .. sMissing .. "(MUST)|r"; end end end elseif (strfind(sLine, CLASS_ROLES, 1, true) == 1) then -- TODO. end if (sLine ~= oText:GetText()) then if (LookingForGuildBrowseSortListLocal.Debug) then LookingForGuildBrowseSortListLocal.Tip[i] = { A = oText:GetText(), B = sLine }; end oText:SetText(sLine); bShow = true; else if (LookingForGuildBrowseSortListLocal.Debug) then LookingForGuildBrowseSortListLocal.Tip[i] = sLine; end end end local name, rank, members, achieved, infotext, cached, requestPending = GetRecruitingGuildInfo(self.index); if (infotext ~= "") then -- Message("Infotext: " .. infotext); GameTooltip:AddLine("Information\n"..HIGHLIGHT_FONT_COLOR_CODE..infotext, nil, nil, nil, true); bShow = true; end if ((GUILDSCORE ~= nil) and (oMUST ~= nil)) then local iMembers = GUILDMEMBERCOUNTS[self.index]; local iRank = GUILDRANKS[self.index]; if ((oMUST.SIZE ~= nil) and (oMUST.SIZE > iMembers)) then GameTooltip:AddLine("Minimum size not reached\n" .. QUEST_DASH .. "|r|cFFFF5050" .. iMembers .. " < " .. oMUST.SIZE .. "|r", nil, nil, nil, true); bShow = true; end if ((oMUST.RANK ~= nil) and (oMUST.RANK > iRank)) then GameTooltip:AddLine("Minimum rank not reached\n" .. QUEST_DASH .. "|r|cFFFF5050" .. iRank .. " < " .. oMUST.SIZE .. "|r", nil, nil, nil, true); bShow = true; end end local sName = GameTooltipTextLeft1:GetText(); local iMax, i = #LookingForGuildBrowseSortListLocal.Tracker; for i = 1, iMax do if (LookingForGuildBrowseSortListLocal.Tracker[i].Name == sName) then local oGuild = LookingForGuildBrowseSortListLocal.Tracker[i]; local sSub = QUEST_DASH .. "no data yet"; if (oGuild.SeenMax) then sSub = QUEST_DASH .. "max. online: " .. oGuild.SeenMax .. "/" .. members; if (oGuild.SeenAt) then sSub = sSub .. date(" @ %Y-%m-%d %H:%M", oGuild.SeenAt); end if (oGuild.SeenStatLog) then local oSizes = LFGBSPrivate.SizeStat.Sizes; local iSlotHigh, iSlot = LFGBSPrivate.SizeStat.NumToSlot(oGuild.SeenMax); local iSum = 0; local iGot = 0; for iSlot = iSlotHigh, 1, -1 do local iCount = oGuild.SeenStatLog[iSlot]; if (iCount and iCount > 0) then iSum = iSum + iCount; sSub = sSub .. "\n" .. QUEST_DASH .. oSizes[iSlot] .. "+: " .. format("%.1f", 100.0 * iSum / oGuild.SeenCount) .. "% of the time"; iGot = iGot + 1; if (iGot >= 5) then break end end end end end GameTooltip:AddLine("Guild is being tracked\n" .. HIGHLIGHT_FONT_COLOR_CODE .. sSub .. "|r", nil, nil, nil, true); bShow = true; break; end end if (bShow) then GameTooltip:Show(); end end local function time2str(time) local m = time - math.floor(time); local h = time - m; return format("%02d:%02d", h, m * 60); end function Cmd(sCmd, bSilent) -- Split commands -- %w+: alphanumeric chars, does not include e.g. accented letters -- => only sufficient for command, but not for guild names -- %S+: all but space chars local args, w = {}; for w in string.gmatch(sCmd, "%S+") do tinsert(args, w); end local iCnt = #args; if (iCnt > 0) then if (args[1] == "help") then if ((args[2] == "quick") or (args[2] == "short")) then Message("LookingForGuildBrowseSort: Valid commands are help (sic!), status, toggle, size, findname, findcomment, sort, days, size."); elseif (args[2] == "info") then Message("[LFGBS] The server checks your selections in the settings tab and lists you all the guilds which offer at least one of your choices. But the list isn't ordered in any way, shape or form, nor are better matching guilds which do *exactly* what you want listed before dead guilds which happen to *also* pvp. [LFGBS] is secretly sneaking behind the scenes and reorders the guild list, so better matches are coming before worse. It also allows a more fine-grained matching by marking options as mandatory (MUST/MUSTN'T) vs. optional (default). It allows string search in names and comments, jumping from match to match. And it allows to sort by name or size, should you really feel the urge to."); else Message("LookingForGuildBrowseSort: LFGBS brings additional sort/search/filter functionality to the guild browser."); Message("[LFBGS] 'help' accepts as option 'quick' or 'short' for just a list of the valid commands, and 'info' for why"); Message("[LFBGS] 'status' shows your current MUST/MUSTN'T information (in your comment) in a readable form"); Message("[LFBGS] 'toggle' allows you to modify your MUST/MUSTN'T information"); Message("[LFBGS] 'days' allows you to modify the list of days you plan to play with a guild"); Message("[LFBGS] 'times' allows you to modify the time window you plan to play with a guild"); Message("[LFBGS] 'size' allows you to define a minimum acceptable guild size for top listing."); Message("[LFBGS] 'rank' allows you to define a minimum acceptable guild rank for top listing."); Message("[LFBGS] 'findname' and 'findcomment' accept a search string and jump to the next guild name/comment containing that string"); Message("[LFBGS] 'sort' allows you to choose a different way of sorting"); Message("[LFBGS] 'tracktoggle[full]' toggles tracking status of the currently selected guild (full generates more data)"); Message("[LFBGS] 'tracklist' lists all currently tracked guilds and their max. actually seen size"); Message("[LFBGS] 'trackreset [<guild name>]' resets the data tracked from the given guild or all guilds if none given"); Message("[LFBGS] 'hideredgrey' toggles if the list of guilds (sorted by score) hides red/grey guilds (default: show all)"); end return end if (args[1] == "status") then if (oMUST) then local sSub, k, v, tt = ""; for k, v in pairs(oMUST) do tt = type(v); if (tt == "boolean") then sSub = sSub .. ", " .. k; elseif (tt == "number") then if (k == "DAYS") then sSub = sSub .. ", [" .. DaysNum2Str(v) .. "]"; else sSub = sSub .. ", " .. k .. ": " .. v; end elseif (tt == "table") then if (k == "TIME") then sSub = sSub .. ", " .. k .. ": " .. time2str(v.FROM) .. " => " .. time2str(v.TO); end end end Message("LookingForGuildBrowseSort: Status = { " .. strsub(sSub, 2) .. " }"); else Message("LookingForGuildBrowseSort: Didn't find a valid MUST|N'T marker in your comment (yet)."); end return end if (args[1] == "sort") then if (iCnt == 1) then Message("LookingForGuildBrowseSort: Missing option to sort. Valid options are { NAME, SIZE, RANK, SCORE (default) }"); return end if (args[2] == "NAME" or args[2] == "SIZE" or args[2] == "SCORE" or args[3] == "RANK") then MapReset(); GUILDSORT = args[2]; LookingForGuild_Update(); end Message("LookingForGuildBrowseSort: Sort routine used now is <" .. GUILDSORT .. ">"); return end if ((args[1] == "findname") or (args[1] == "findcomment")) then if (iCnt == 1) then args[2] = ""; end local x, i = args[2]; for i = 3, iCnt do x = x .. " " .. args[i]; end if (strlen(x) < 3) then Message("LookingForGuildBrowseSort: Pattern to find* must be 3+ characters long."); return end x = strlower(x); local oData, sIs; if (args[1] == "findname") then oData = GUILDNAMES; sIs = "name"; else oData = GUILDCOMMENTS; sIs = "comment"; end local iGuildCnt = #oData; local oFound = {}; for i = 1, iGuildCnt do if (strfind(strlower(oData[i]), x, 1, true)) then tinsert(oFound, i); end end if (#oFound == 0) then Message("LookingForGuildBrowseSort: No guild " .. sIs .. " with that pattern found."); return end local offset, iPos = HybridScrollFrame_GetOffset(LookingForGuildBrowseFrameContainer) + 1; if (offset > #oData - 4) then -- get away from the last 4 slots... offset = 0; end for i = 1, #oFound do if (offset < oFound[i]) then iPos = oFound[i]; break; end end if (iPos) then if (#oFound > 1) then Message("LookingForGuildBrowseSort: " .. #oFound .. " guild " .. sIs .. "s with that pattern found. Scrolling to next..."); end else iPos = oFound[1]; if (#oFound > 1) then Message("LookingForGuildBrowseSort: " .. #oFound .. " guild " .. sIs .. "s with that pattern found. Scrolling to first..."); end end -- Message("LookingForGuildBrowseSort: Trying to scroll to guild named <" .. GUILDNAMES[iPos] .. "> (" .. iPos .. ")"); LookingForGuildBrowseFrameContainerScrollBar:SetValue((iPos - 1) * 84); HybridScrollFrame_SetOffset(LookingForGuildBrowseFrameContainer, (iPos - 1) * 84); return end if (args[1] == "toggle") then if (iCnt == 1) then local sOptions, k, v = ""; for k, v in pairs(CHOICES_SHORTLONG) do sOptions = sOptions .. ", " .. v; end Message("LookingForGuildBrowseSort: Missing option to toggle. Valid options are { " .. strsub(sOptions, 2) .. " }"); return end local choice, k, v; for k, v in pairs(CHOICES_SHORTLONG) do if (v == args[2]) then choice = k; end end if (not choice) then if (not bSilent) then Message("LookingForGuildBrowseSort: Unknown choice to toggle."); end return end if ((type(oMUST) == "table") and (next(oMUST) ~= nil)) then local bValue = oMUST[args[2]]; if (bValue) then oMUST[args[2]] = nil; if (next(oMUST) == nil) then oMUST = nil; end else oMUST[args[2]] = true; end else oMUST = {}; oMUST[args[2]] = true; end LFGBSPrivate.CommentPlayer.Update(); if (LookingForGuildFrameTab2:IsShown()) then LookingForGuild_Update(); end return end if (args[1] == "days") then if (not args[2] or strlen(args[2]) ~= 7) then Message("LookingForGuildBrowseSort: This command must be followed by a 7-letter argument, one for each day either upper-case for yes or lower-case for no. (E.g.: mtwtFSs for only on Fridays/Saturdays)"); return end local sDays = args[2]; local sDaysUpper = "MTWTFSS"; local sDaysLower = "mtwtfss"; local iDays, iMulti, i = 0, 1; for i = 1, 7 do local c = strsub(sDays, i, i); if (c == strsub(sDaysUpper, i, i)) then iDays = iDays + iMulti; elseif (c ~= strsub(sDaysLower, i, i)) then Message("LookingForGuildBrowseSort: Unknown letter <" .. c .. "> for day " .. i .. ". Correct sequence is [" .. sDaysUpper .. "]"); return end iMulti = iMulti * 2; end if (iDays == 0) then iDays = nil; end if ((type(oMUST) == "table") and (next(oMUST) ~= nil)) then oMUST.DAYS = iDays; if (next(oMUST) == nil) then oMUST = nil; end else oMUST = { DAYS = iDays }; end local bChanged = LFGBSPrivate.CommentPlayer.Update(); if (LookingForGuildFrameTab2:IsShown()) then LookingForGuild_Update(); end if (bChanged) then if (iDays) then Message("LookingForGuildBrowseSort: Updated comment to include days [" .. DaysNum2Str(iDays) .. "]"); else Message("LookingForGuildBrowseSort: Updated comment to not include days any longer."); end else Message("LookingForGuildBrowseSort: Days remain unchanged [" .. DaysNum2Str(iDays) .. "]"); end return end if (args[1] == "times") then local oTIMES; if ((args[2] == "--") and not args[3]) then args[2] = "--"; args[3] = "--"; args[4] = ""; args[5] = ""; else if (not args[3] or args[4]) then Message("LookingForGuildBrowseSort: This command must be followed by two time arguments in the format HH:MM, MM (minutes) may only be 00 or 30 (e.g. \"/LFGBS times 19:00 23:30\"), or by one argument to reset: --"); return end oTIMES = {}; local ok, j = true; for j = 2, 3 do local s = args[j]; if (strlen(s) < 5) then ok = false; break; end -- 20:30 => 2, 20, 20 * 60, 20 * 60 * 10 + 3, 20 * 60 * 10 * 10 + 30 local v = 0; local i, c; for i = 1, 5 do c = strsub(s, i, i); if (i < 3) then if (c < "0" or c > "9") then ok = false; break; else v = v * 10 + strbyte(c) - strbyte("0"); end elseif (i == 3) then if (c ~= ":") then ok = false; break; end elseif (i == 4) then if (c ~= "0" and c ~= "3") then ok = false; break; elseif (c == "3") then v = v + 0.5; end elseif (c ~= "0") then ok = false; break; end end if (j == 2) then oTIMES.FROM = v; else oTIMES.TO = v; end end if (not ok) then Message("LookingForGuildBrowseSort: This command must be followed by two time arguments in the format HH:MM, MM (minutes) may only be 00 or 30 (e.g. \"/LFGBS times 19:00 23:30\")."); return end end local sOld = "--:-- -> --:--"; if ((type(oMUST) == "table") and (next(oMUST) ~= nil)) then if (type(oMUST.TIME) == "table") then Message("[LFGBS][DEBUG] TIME was set."); sOld = format("%02d:%02d -> %02d:%02d", floor(oMUST.TIME.FROM), 60 * (oMUST.TIME.FROM - floor(oMUST.TIME.FROM)), floor(oMUST.TIME.TO ), 60 * (oMUST.TIME.TO - floor(oMUST.TIME.TO ))); end oMUST.TIME = oTIMES; if (next(oMUST) == nil) then oMUST = nil; end else oMUST = { TIME = oTIMES }; end local bChanged = LFGBSPrivate.CommentPlayer.Update(); if (LookingForGuildFrameTab2:IsShown()) then LookingForGuild_Update(); end if (bChanged) then if (oTIMES) then Message("LookingForGuildBrowseSort: Updated comment to include times " .. args[2] .. " - " .. args[3] .. " (from " .. (sOld or "<??>") .. ")."); else Message("LookingForGuildBrowseSort: Updated comment to not include times any longer (times were " .. (sOld or "<??>") .. ")."); end else Message("LookingForGuildBrowseSort: Times remain unchanged: " .. args[2] .. " - " .. args[3] .. "."); end return end if (args[1] == "size") then local iSize = tonumber(args[2] or "0"); if (((iSize == 0) and (args[2] ~= "0")) or (iSize > 0) and (iSize < 5) or (iSize > 55)) then Message("LookingForGuildBrowseSort: Size argument must be 0 (zero, to clear/reset) or between 5 and 55 (odd, inclusive)."); return end if (bit.band(iSize, 1) == 0) then iSize = iSize + 1; end if (iSize < 5) then iSize = nil; end if ((type(oMUST) == "table") and (next(oMUST) ~= nil)) then oMUST.SIZE = iSize; if (next(oMUST) == nil) then oMUST = nil; end else oMUST = { SIZE = iSize }; end local bChanged = LFGBSPrivate.CommentPlayer.Update(); if (LookingForGuildFrameTab2:IsShown()) then LookingForGuild_Update(); end if (bChanged) then if (iSize) then Message("LookingForGuildBrowseSort: Updated comment to include min. size " .. iSize .. "."); else Message("LookingForGuildBrowseSort: Updated comment to not include a min. size any longer."); end else Message("LookingForGuildBrowseSort: Min. size remains unchanged: " .. iSize .. "."); end return end if (args[1] == "rank") then local iRank = tonumber(args[2] or "0"); if (((iRank == 0) and (args[2] ~= "0")) or (iRank < 1) or (iRank > 30)) then Message("LookingForGuildBrowseSort: Rank argument must be 0 (zero, to clear/reset) or between 1 and 30."); return end if (iRank < 0) then iRank = nil; end if ((type(oMUST) == "table") and (next(oMUST) ~= nil)) then oMUST.RANK = iRank; if (next(oMUST) == nil) then oMUST = nil; end else oMUST = { RANK = iRank }; end local bChanged = LFGBSPrivate.CommentPlayer.Update(); if (LookingForGuildFrameTab2:IsShown()) then LookingForGuild_Update(); end if (bChanged) then if (iRank) then Message("LookingForGuildBrowseSort: Updated comment to include min. guild rank " .. iRank .. "."); else Message("LookingForGuildBrowseSort: Updated comment to not include a min. guild rank any longer."); end else Message("LookingForGuildBrowseSort: Min. guild rank remains unchanged: " .. iRank .. "."); end return end if (strsub(args[1], 1, 11) == "tracktoggle") then local iSelected = GetRecruitingGuildSelection(); if (not iSelected) then Message("LookingForGuildBrowseSort: No guild selected to toggle tracking status."); return end local sName = GetRecruitingGuildInfo(iSelected); if (not sName) then Message("LookingForGuildBrowseSort: Failed to find name of selected guild. Hm!"); else local oTracker = LookingForGuildBrowseSortListLocal.Tracker; local iMax, i, bFound, bData = #oTracker; for i = 1, iMax do if (oTracker[i].Name == sName) then Message("[LFGBS] Cmd::tt: " .. sName .. " => " .. i); bFound = true; if (args[1] == "tracktogglefull") then oTracker[i].SeenCount = 0; bData = oTracker[i].Data ~= nil; if (bData) then oTracker[i].Data = nil; local k, v; for k, v in pairs(oTracker[i]) do if (strsub(k, 1, 7) == "SEENMAX") then oTracker[i].k = nil; end end else oTracker[i].Data = {}; end else tremove(oTracker, i); end break end end if (not bFound) then if (args[1] == "tracktogglefull") then bData = false; oTracker[iMax + 1] = { Name = sName, SeenCount = 0, SeenStatLog = {}, Data = {} }; else oTracker[iMax + 1] = { Name = sName, SeenCount = 0, SeenStatLog = {} }; end end -- not found: bData ~= nil ? full : seen, found: bData == nil ? off : bData && seen || full local sStatus = "<error>"; if (bFound) then if (bData ~= nil) then sStatus = bData and "seen" or "full"; else sStatus = "off"; end else if (bData ~= nil) then sStatus = "full"; else sStatus = "seen"; end end Message("LookingForGuildBrowseSort: Tracking status for guild <" .. sName .. "> toggled to " .. sStatus .. "."); end return end if (args[1] == "tracklist") then local sSub = ""; local oTracker = LookingForGuildBrowseSortListLocal.Tracker; if (oTracker) then local iMax, i = #oTracker; for i = 1, iMax do local sSubSub = oTracker[i].SeenMax and (": " .. oTracker[i].SeenMax) or ""; if (oTracker[i].Data) then sSubSub = sSubSub .. "!"; end sSub = sSub .. " [" .. oTracker[i].Name .. sSubSub .. "]"; end end if (sSub == "") then sSub = " none."; end Message("LookingForGuildBrowseSort: Tracked guilds are " .. strsub(sSub, 2)); return end if (args[1] == "trackreset") then local sName; if (args[2]) then sName = args[2]; local iSub = 3; while (args[iSub]) do sName = sName .. " " .. args[iSub]; iSub = iSub + 1; end end local oTracker = LookingForGuildBrowseSortListLocal.Tracker; local iMax, i = #oTracker; for i = 1, iMax do if ((sName == nil) or (oTracker[i].Name == sName)) then oTracker[i].SeenCount = 0; oTracker[i].SeenStat = nil; oTracker[i].SeenStatLog = nil; if (type(oTracker[i].Data) == "table") then oTracker[i].Data = {}; end local k, v; for k, v in pairs(oTracker[i]) do if (strsub(k, 1, 7) == "SEENMAX") then oTracker[i].k = nil; end end end end Message("LookingForGuildBrowseSort: Tracking statistics haben been re-initialized."); return end if (args[1] == "hideredgrey") then if (not LookingForGuildBrowseSortListLocal.Config) then LookingForGuildBrowseSortListLocal.Config = {}; end LookingForGuildBrowseSortListLocal.Config.HideRedGrey = not LookingForGuildBrowseSortListLocal.Config.HideRedGrey; if (LookingForGuildFrameTab2:IsShown()) then LookingForGuild_Update(); end Message("LookingForGuildBrowseSort: Hiding red/grey guilds (in the score-list) is now " .. (LookingForGuildBrowseSortListLocal.Config.HideRedGrey and "on." or "off.")); return end end Message("LookingForGuildBrowseSort: Missing or unknown command. Try '/LFGBS help'"); end local function LookingForGuildBrowseSortButton_OnClick(self) MapReset(); local oNext = { SCORE = "NAME", NAME = "SIZE", SIZE = "RANK", RANK = "SCORE" }; GUILDSORT = oNext[GUILDSORT or "SCORE"]; self:SetText("by " .. GUILDSORT); LookingForGuild_Update(); end local bGuildnameColorize_Hooked; local bGetRecruitingGuildSettings_Hooked; local bLookingForGuildFooButton_Hooked; local bSortButton_Added; local bSlashCommand_Added; local bLookingForGuild_Update_Hooked; local function GetRecruitingGuildSettings_Overwrite() if (not bGetRecruitingGuildSettings_Hooked) then bGetRecruitingGuildSettings_Hooked = true; -- hooksecurefunc("RequestRecruitingGuildsList", MapReset); -- hooksecurefunc("SetLookingForGuildComment", MapReset); LookingForGuildCommentEditBox:HookScript("OnEditFocusLost", LFGBSPrivate.CommentPlayer.Parse); MapReset(); end -- -- if (GetNumRecruitingGuilds_original == nil) then GetNumRecruitingGuilds_original = GetNumRecruitingGuilds; GetNumRecruitingGuilds = GetNumRecruitingGuilds_ordered; end if (GetRecruitingGuildSettings_original == nil) then GetRecruitingGuildSettings_original = GetRecruitingGuildSettings; GetRecruitingGuildSettings = GetRecruitingGuildSettings_ordered; end if (GetRecruitingGuildInfo_original == nil) then GetRecruitingGuildInfo_original = GetRecruitingGuildInfo; GetRecruitingGuildInfo = GetRecruitingGuildInfo_ordered; end if (not bLookingForGuildGuild_ShowTooltip_Hooked) then bLookingForGuildGuild_ShowTooltip_Hooked = true; local i = 1; while (_G["LookingForGuildBrowseFrameContainerButton" .. i] ~= nil) do local oButton = _G["LookingForGuildBrowseFrameContainerButton" .. i]; oButton:HookScript("OnEnter", LookingForGuildGuild_ShowTooltip_Hook); i = i + 1; end end if (not bGuildnameColorize_Hooked) then bGuildnameColorize_Hooked = true; hooksecurefunc("LookingForGuild_Update", GuildnameColorize); hooksecurefunc(LookingForGuildBrowseFrameContainer, "update", GuildnameColorize); end -- -- if (not bLookingForGuildFooButton_Hooked) then bLookingForGuildFooButton_Hooked = true; local k, v; for k, v in pairs(CHOICES_SHORTLONG) do local sCamel = strupper(strsub(v, 1, 1)) .. strlower(strsub(v, 2)); local iLen = strlen(sCamel); if (strsub(sCamel, iLen, iLen) == "p") then sCamel = strsub(sCamel, 1, iLen - 1) .. "P"; end local oButton = _G["LookingForGuild" .. sCamel .. "Button"]; if (oButton) then oButton:HookScript("OnClick", LFGBSPrivate.CommentPlayer.ButtonToggle); oButton:RegisterForClicks("LeftButtonUp", "RightButtonUp"); end end end if (not bSortButton_Added) then bSortButton_Added = true; local oBtn = CreateFrame("Button", "LookingForGuildBrowseSortButton", LookingForGuildRequestButton:GetParent(), "MagicButtonTemplate"); oBtn:SetPoint("TOPLEFT", LookingForGuildRequestButton, "TOPRIGHT", 3, 0); oBtn:SetText("by SCORE"); oBtn:SetScript("OnClick", LookingForGuildBrowseSortButton_OnClick); oBtn:Show(); end if (not bSlashCommand_Added) then bSlashCommand_Added = true; SLASH_LFGBS1 = "/LFGBS"; SlashCmdList["LFGBS"] = Cmd; Message("LookingForGuildBrowseSort initialized. Try '/LFGBS help' for a list of commands."); end if (not bLookingForGuild_Update_Hooked) then bLookingForGuild_Update_Hooked = true; hooksecurefunc("LookingForGuild_Update", LFGBSPrivate.LookingForGuild_Update2ActualMemberCount); hooksecurefunc(LookingForGuildBrowseFrameContainer, "update", LFGBSPrivate.LookingForGuild_Update2ActualMemberCount); end LFGBSPrivate.CommentPlayer.Parse(); end hooksecurefunc("LookingForGuildFrame_LoadUI", GetRecruitingGuildSettings_Overwrite); -- -- -- local iFrequency = 1200; function LFGBSFrequencySet(iValue) if ((type(iValue) == "number") and (iValue > 60) and (iValue <= 1200)) then iFrequency = iValue; Message("[LFGBS] Frequency set to " .. iValue .. "."); else Message("[LFGBS] Frequency NOT set to [" .. tostring(iValue) .. "] - invalid input."); end end function LFGBSPrivate.TrackerFrame.OnUpdate(oFrame, iElapsed) oFrame.At = oFrame.At - iElapsed; if (oFrame.At < 0) then if (#LookingForGuildBrowseSortListLocal.Tracker > 0) then if (FriendsFrame:IsVisible()) then oFrame.At = oFrame.At + 30; else if (LookingForGuildBrowseSortListLocal.Config and LookingForGuildBrowseSortListLocal.Config.TIME) then local iSecondsNow = time(); if (oFrame.iDayBreak == nil) then local iSecondsSinceDayBreak = tonumber(date("%H", iSecondsNow)) * 3600 + tonumber(date("%M", iSecondsNow)) * 60 + tonumber(date("%S", iSecondsNow)); oFrame.iDayBreak = math.floor((iSecondsNow - iSecondsSinceDayBreak) / 60 + 0.5) * 60; end local iHoursNow = 1.0 * (iSecondsNow - oFrame.iDayBreak) / 3600; -- times are in hours since midnight, fractional local oTIME = LookingForGuildBrowseSortListLocal.Config.TIME; local tFrom = oTIME.FROM; local tTo = oTIME.TO; if (tTo < tFrom) then -- 20:30 - 0:30 => 20.5 - 0.5 if ((tTo < iHoursNow) and (tFrom > iHoursNow)) then oFrame.At = iFrequency / 2; -- Message("[LFGBS] Current time outside preferred raid time (" .. format("%.1f - %.1f", tFrom, tTo) .. ") - no tracking."); return end else -- 20:30 - 23:30 => 20.5 - 23.5 if ((tTo < iHoursNow) or (tFrom > iHoursNow)) then oFrame.At = iFrequency / 2; -- Message("[LFGBS] Current time outside preferred raid time (" .. format("%.1f - %.1f", tFrom, tTo) .. ") - no tracking."); return end end end oFrame.At = oFrame.At + iFrequency / (0.5 + math.min(#LookingForGuildBrowseSortListLocal.Tracker, 5)); oFrame.Pos = oFrame.Pos + 1; local i = oFrame.Pos; if (i > #LookingForGuildBrowseSortListLocal.Tracker) then oFrame.Pos = 1; i = 1; end local oGuild = LookingForGuildBrowseSortListLocal.Tracker[i]; oGuild.SeenCount = (oGuild.SeenCount or 0) + 1; local sWho = "g-\"" .. oGuild.Name .. "\""; oFrame.sWho = sWho; -- order seems to be illogical at first, but compensates for other addons potentially re-registering the event on the SendWho, if *they* took it away SendWho(sWho, "LookingForGuildBrowseSort"); FriendsFrame:UnregisterEvent("WHO_LIST_UPDATE"); SetWhoToUI(1); -- Message("[LFGBS] TF::OU: Sent [" .. sWho .. "]"); end else oFrame.At = iFrequency / 4; end end end function LFGBSPrivate.TrackerFrame.OnEvent(oFrame, sEvent, arg1, ...) if (sEvent == "LF_GUILD_BROWSE_UPDATED") then MapReset("OnEvent:" .. sEvent); elseif (sEvent == "WHO_LIST_UPDATE") then FriendsFrame:RegisterEvent("WHO_LIST_UPDATE"); if (FriendsFrame:IsVisible()) then SetWhoToUI(1); else SetWhoToUI(0); end local _, iMax = GetNumWhoResults(); if (iMax == 0) then return end local bWrong = oFrame.sWho == nil; local sGuild = not bWrong and strsub(oFrame.sWho, 4, -2); local sGuildOops = ""; local oDataBin, oData = {}; local iNow = time(); local oSpots, i, j = {}; for i = 1, iMax do local sName, guild, level, race, class, zone, classEN = GetWhoInfo(i); if (guild ~= sGuild) then sGuildOops = sGuildOops .. ", [" .. tostring(sName) .. "] = <" .. tostring(guild) .. ">"; bWrong = true; end if (guild ~= "") then local jMax, j = #LookingForGuildBrowseSortListLocal.Tracker; for j = 1, jMax do local oGuild = LookingForGuildBrowseSortListLocal.Tracker[j]; if (guild == oGuild.Name) then if (oSpots[j] == nil) then oSpots[j] = {}; end local oData; if (oGuild.Data) then oData = oGuild.Data; else oDataBin[j] = oDataBin[j] or {}; oData = oDataBin[j]; end local kMax, k, bFound = #oData; for k = 1, kMax do if (oData[k].Name == sName) then oData[k].Hit = (oData[k].Hit or 1) + 1; tinsert(oSpots[j], k); bFound = true; end end if (not bFound) then tinsert(oData, { Name = sName, Class = classEN, Level = level, Time = iNow, Hit = 1 } ); tinsert(oSpots[j], #oData); end end end end end if (next(oSpots)) then local sOut, k, v = ""; for k, v in pairs(oSpots) do local oGuild, oCollisions = LookingForGuildBrowseSortListLocal.Tracker[k]; if (oGuild.Data) then oCollisions = {}; local iMax = #v; if (iMax > 1) then local i, iGroup, iBit, iMask; for i = 1, iMax do iBit = v[i] % 32; iGroup = v[i] - iBit; iMask = bit.lshift(1, iBit); oCollisions[iGroup] = bit.bor(oCollisions[iGroup] or 0, iMask); end for i = 1, iMax do local oEntry, l, w = oGuild.Data[v[i]]; for l, w in pairs(oCollisions) do local sKey = "COLL" .. l; oEntry[sKey] = bit.bor(oEntry[sKey] or 0, w); end end end end if (not bWrong) then sOut = sOut .. ", [" .. oGuild.Name .. ": " .. #v .. "]"; LFGBSPrivate.SizeStat.SeenMaxUpdate(oGuild, oCollisions, iMax); end end if (bWrong) then -- Message("[LFGBS] TF::OE(WLU) - " .. strsub(sOut, 3) .. " == {" .. tostring(sGuildOops) .. "}"); else Message("[LFGBS] TF::OE(WLU) + " .. strsub(sOut, 3)); end end oFrame.sWho = nil; elseif (sEvent == "ADDON_LOADED") then if (arg1 == "LookingForGuildBrowseSort") then if (LookingForGuildBrowseSortList == nil) then LookingForGuildBrowseSortList = {}; end local sServer; if (GetRealmName ~= nil) then -- 5.4: realmname is no longer a global variable sServer = GetRealmName(); else sServer = GetCVar("RealmName"); end if (LookingForGuildBrowseSortList[sServer] == nil) then LookingForGuildBrowseSortList[sServer] = { Config = {} }; end LookingForGuildBrowseSortListLocal = LookingForGuildBrowseSortList[sServer]; if (LookingForGuildBrowseSortListLocal.Config == nil) then if (LookingForGuildBrowseSortList.Config) then LookingForGuildBrowseSortListLocal.Config = LookingForGuildBrowseSortList.Config; LookingForGuildBrowseSortList.Config = nil; else LookingForGuildBrowseSortListLocal.Config = {}; end end if (LookingForGuildBrowseSortListLocal.Tracker == nil) then if (LookingForGuildBrowseSortList.Tracker) then LookingForGuildBrowseSortListLocal.Tracker = LookingForGuildBrowseSortList.Tracker; LookingForGuildBrowseSortList.Tracker = nil; else LookingForGuildBrowseSortListLocal.Tracker = {}; end end -- legacy cleanup local k, v; for k, v in pairs(LookingForGuildBrowseSortList) do if ((type(v) == "table") and (type(v.Tracker) == "table")) then local iMax, i = #v.Tracker; for i = 1, iMax do if (v.Tracker[i].Data == nil) then local l, w; for l, w in pairs(v.Tracker[i]) do if (strsub(l, 1, 7) == "SEENMAX") then v.Tracker[i].l = nil; end end end local oSeenStat = v.Tracker[i].SeenStat; if (type(oSeenStat) == "table") then if (type(v.Tracker[i].SeenStatLog) ~= "table") then v.Tracker[i].SeenStatLog = {}; end local oSeenStatLog = v.Tracker[i].SeenStatLog; local l, w; for l, w in pairs(oSeenStat) do if ((type(l) == "number") and (l > 0)) then local k = LFGBSPrivate.SizeStat.NumToSlot(l); oSeenStatLog[k] = (oSeenStatLog[k] or 0) + w; end end v.Tracker[i].SeenStat = nil; end end end end oFrame.At = 60; oFrame.Pos = time() % (1 + #LookingForGuildBrowseSortListLocal.Tracker); oFrame:UnregisterEvent("ADDON_LOADED"); oFrame:RegisterEvent("WHO_LIST_UPDATE"); oFrame:RegisterEvent("LF_GUILD_BROWSE_UPDATED"); oFrame:SetScript("OnUpdate", LFGBSPrivate.TrackerFrame.OnUpdate); if (#LookingForGuildBrowseSortListLocal.Tracker > 0) then Message("LookingForGuildBrowseSort: Tracker initialized with " .. #LookingForGuildBrowseSortListLocal.Tracker .. " guilds to look for."); end end end end function LFGBSPrivate.LookingForGuild_Update2ActualMemberCount() if (#LookingForGuildBrowseSortListLocal.Tracker == 0) then return end local oName2Max = {}; local iMax, i = #LookingForGuildBrowseSortListLocal.Tracker; for i = 1, iMax do local oGuild = LookingForGuildBrowseSortListLocal.Tracker[i]; oName2Max[oGuild.Name] = LFGBSPrivate.SizeStat.AverageMax(oGuild); end local sFormat = gsub(BROWSE_GUILDS_NUM_MEMBERS, "%%d", "%%s"); local oButtons = LookingForGuildBrowseFrameContainer.buttons; local iMax, i = #oButtons; for i = 1, iMax do local oButton = oButtons[i]; if (oButton:IsShown()) then local name, _, numMembers = GetRecruitingGuildInfo(oButton.index); if (oName2Max[name]) then oButton.numMembers:SetFormattedText(sFormat, oName2Max[name] .. " (" .. numMembers .. ")"); end end end end function LFGBSPrivate.TrackerFrame.SendWhoHook(sWho) local oFrame = LFGBSPrivate.TrackerFrame.oFrame; if (not sWho or (sWho ~= oFrame.sWho)) then oFrame.At = math.max(oFrame.At, 7); end end function LFGBSPrivate.SizeStat.NumToSlot(iNum, iFirst, iLast) if (iNum == nil) then Message("[LFGBS] Oops in P:SS:NTS(nil)."); end local oSizes = LFGBSPrivate.SizeStat.Sizes; local iMax, i = #oSizes; iFirst = math.max(iFirst or 1, 1); iLast = math.min(iLast or iMax, iMax); if (iLast - iFirst < 5) then local iContainer; for i = iFirst, iLast do if (oSizes[i] > iNum) then iContainer = i - 1; break; end end if (not iContainer or not oSizes[iContainer]) then Message("[LFGBS] Oops in P:SS:NTS(" .. iNum .. ") =?> " .. (iContainer or "<nil>")); end if ((oSizes[iContainer] > iNum) or ((oSizes[iContainer + 1] ~= nil) and (oSizes[iContainer + 1] <= iNum))) then Message("[LFGBS] Oops in P:SS:NTS(" .. iNum .. ") =!> " .. iContainer); end return iContainer; else iMiddle = math.floor(iFirst + (iLast - iFirst) / 3); if (oSizes[iMiddle] == nil) then Message("[LFGBS] Oops in P:SS:NTS(" .. iNum .. ") =^ { " .. iMiddle .. " }"); end if (oSizes[iMiddle] > iNum) then return LFGBSPrivate.SizeStat.NumToSlot(iNum, iFirst, iMiddle); else return LFGBSPrivate.SizeStat.NumToSlot(iNum, iMiddle, iLast); end end end function LFGBSPrivate.SizeStat.AverageMax(oGuild) local fAverage, fPercentage; if ((oGuild.SeenStat or oGuild.SeenStatLog) and oGuild.SeenMax and oGuild.SeenMax > 0) then local iMax = oGuild.SeenMax; if (oGuild.SeenStatLog) then local iContainer, i = LFGBSPrivate.SizeStat.NumToSlot(iMax); local iCountTotal, iSumWeighted = 0, 0; local iFirst = math.max(1, iContainer - 3); for i = iFirst, iContainer do local iCount = oGuild.SeenStatLog[i]; if (iCount) then iCountTotal = iCountTotal + iCount; iSumWeighted = iSumWeighted + LFGBSPrivate.SizeStat.Sizes[i] * iCount; end end if (iCountTotal > 0) then fAverage = 1.0 * iSumWeighted / iCountTotal; fPercentage = 100.0 * iCountTotal / oGuild.SeenCount; end else local oMax = {}; oMax[iMax ] = (oGuild.SeenStat[iMax ] or 0); oMax[iMax - 1] = (oGuild.SeenStat[iMax - 1] or 0); oMax[iMax - 2] = (oGuild.SeenStat[iMax - 2] or 0); local iCount = oMax[iMax] + oMax[iMax - 1] + oMax[iMax - 2]; if (iCount > 0) then local iSum = iMax * oMax[iMax] + (iMax - 1) * oMax[iMax - 1] + (iMax - 2) * oMax[iMax - 2]; fAverage = 1.0 * iSum / iCount; fPercentage = 100.0 * iCount / oGuild.SeenCount; end end end if (fAverage and fPercentage) then return format("%.1f <%d%%>", fAverage, math.floor(fPercentage + 0.5)); else return oGuild.SeenMax; end end function LFGBSPrivate.SizeStat.SeenMaxUpdate(oGuild, oCollisions, iMax) local iMaxOld = oGuild.SeenMax or 0; if (not oGuild.SeenStat and not oGuild.SeenStatLog) then oGuild.SeenStatLog = {}; if (iMaxOld > 0) then local iContainer = LFGBSPrivate.SizeStat.NumToSlot(iMaxOld); oGuild.SeenStatLog[iContainer] = 1; if ((oGuild.SeenCount or 0) < 2) then oGuild.SeenCount = 2; -- the current /who and the /who who got us the old max. end end end if (iMax >= iMaxOld) then oGuild.SeenAt = time(); oGuild.SeenMax = iMax; if (oGuild.Data and oCollisions) then local l, w; for l, w in pairs(oCollisions) do local sKey = "SEENMAX" .. l; oGuild[sKey] = w; end end end if (oGuild.SeenStatLog) then local iContainer = LFGBSPrivate.SizeStat.NumToSlot(iMax); oGuild.SeenStatLog[iContainer] = (oGuild.SeenStatLog[iContainer] or 0) + 1; else if (iMax > iMaxOld) then local oSeenStatOld = oGuild.SeenStat; oGuild.SeenStat = { [ iMax ] = oSeenStatOld[iMax] or 0 }; if (iMax > 1) then oGuild.SeenStat[iMax - 1] = oSeenStatOld[iMax - 1] or 0; if (iMax > 2) then oGuild.SeenStat[iMax - 2] = oSeenStatOld[iMax - 2] or 0; end end elseif (oGuild.SeenMax - iMax < 3) then oGuild.SeenStat[iMax] = (oGuild.SeenStat[iMax] or 0) + 1; end end end do local oFrame = CreateFrame("Frame", nil, UIParent); oFrame:SetScript("OnEvent", LFGBSPrivate.TrackerFrame.OnEvent); oFrame:RegisterEvent("ADDON_LOADED"); hooksecurefunc("SendWho", LFGBSPrivate.TrackerFrame.SendWhoHook); LFGBSPrivate.TrackerFrame.oFrame = oFrame; LFGBSPrivate.SizeStat.Sizes = { [0] = 0 }; local oSizes, i, j = LFGBSPrivate.SizeStat.Sizes; for i = 1, 10 do for j = 1, 5 do oSizes[(i - 1) * 5 + j] = oSizes[(i - 1) * 5 + j - 1 ] + i; end end end