changeset 32:7b7b3ca996fd

CrossRealmAssist beta 0.75 release-candidate
author ShadowTheAge
date Mon, 26 Oct 2015 00:47:27 +0300
parents efecddd638fc
children f39b5803b4c1
files .pkgmeta CrossRealmAssist.lua CrossRealmAssistSettings.lua embeds.xml
diffstat 4 files changed, 600 insertions(+), 75 deletions(-) [+]
line wrap: on
line diff
--- a/.pkgmeta	Sun Sep 27 16:06:18 2015 +0300
+++ b/.pkgmeta	Mon Oct 26 00:47:27 2015 +0300
@@ -16,6 +16,10 @@
   url: svn://svn.wowace.com/wow/ace3/mainline/trunk/AceTimer-3.0
  libs/AceDB-3.0:
   url: svn://svn.wowace.com/wow/ace3/mainline/trunk/AceDB-3.0
+ libs/AceConfig-3.0:
+  url: svn://svn.wowace.com/wow/ace3/mainline/trunk/AceConfig-3.0
+ libs/AceGUI-3.0:
+  url: svn://svn.wowace.com/wow/ace3/mainline/trunk/AceGUI-3.0
  libs/LibStub:
   url: svn://svn.wowace.com/wow/libstub/mainline/trunk
  libs/LibWho-2.0:
@@ -24,5 +28,7 @@
   url: git://git.wowace.com/wow/libdatabroker-1-1/mainline.git
  libs/LibDBIcon-1.0:
   svn://svn.wowace.com/wow/libdbicon-1-0/mainline/trunk/LibDBIcon-1.0
+ libs/LibRealmInfo:
+  svn://svn.wowinterface.com/LibRealmInfo-1100/trunk/LibRealmInfo-1.0
   
 enable-nolib-creation: no
\ No newline at end of file
--- a/CrossRealmAssist.lua	Sun Sep 27 16:06:18 2015 +0300
+++ b/CrossRealmAssist.lua	Mon Oct 26 00:47:27 2015 +0300
@@ -1,6 +1,7 @@
 CrossRealmAssist = LibStub("AceAddon-3.0"):NewAddon("CrossRealmAssist", "AceEvent-3.0", "AceConsole-3.0", "AceTimer-3.0")
 local addon = CrossRealmAssist;
 local wholib = LibStub:GetLibrary('LibWho-2.0'):Library()
+local realmInfo = LibStub:GetLibrary('LibRealmInfo')
 local ScrollingTable = LibStub("ScrollingTable");
 local ldb = LibStub("LibDataBroker-1.1")
 local minimapIcon = LibStub("LibDBIcon-1.0")
@@ -9,7 +10,7 @@
 local homeRealm, realmSep, gui, btRefresh, btSettings, btQuick, btManual, lbRealm, lbStatus, playerName, lfgui, lfgTabSelected, lfgTable, lbThrottle, curLfgGroup, sheduledScan, wasInGroup, inInstance
 local settingsMenu
 local hintShown
-local cbHideGroups, cbHideRaids, cbHideNoAutoinv
+local btFilterCount, cbHideNoAutoinv, edFilterText
 
 local tabs = {}
 local curRealmStat
@@ -19,8 +20,12 @@
 local autoScanGroups={}
 local active=false
 local allLanguageTable={}
+local extraColumns
+local tableTemplate
+local filterString
+local filterByCountId=1
 
-local lfgGroups={
+addon.lfgGroups={
     6, -- Custom
     10, -- Ashran
     1, -- Quests
@@ -46,13 +51,34 @@
         quickJoinHint = true,
         quickJoin={[6]=1},
         allLanguages = true,
-        stoplist = {"no hop","kick"}
+        stoplist = {"no hop","nohop","no realm","kick"},
+        priorityList = {"realm hop","realmhop","garrison"},
+        lfgPanelScale = 1.0, uiScale = 1.0, listItemCount=15,
+        extraColumns = {}
     }
 }
+local filterByCountList = {
+    {text="Any number of players",min=1,max=40},
+    {text="Groups (1-5)",min=1,max=5},
+    {text="Raids (6-40)",min=6,max=40},
+    {text="Non-full groups (1-4)",min=1,max=4},
+    {text="Non-full raids (6-35)",min=6,max=35}
+}
 
 
 -- Utils functions
 
+
+local function searchInList(name, list)
+    if list and name then
+        local lname = string.lower(name);
+        for i=1, #list do
+            if string.find(lname, list[i], 1, true) then return true end
+        end
+    end
+    return false;
+end
+
 local function PlayerName(fullname)
     fullname = fullname or "???-???"
     local name, realm = strsplit(realmSep, fullname)
@@ -64,8 +90,12 @@
     return (not IsInGroup()) or (UnitIsGroupLeader('player') and not IsInRaid())
 end
 
-local function sortByWeight(a,b)
-    return b.weight < a.weight
+local function sortByTypeAndName(a,b)
+    if a.type == b.type then
+		return a.name < b.name
+	else 
+		return a.type < b.type;
+	end
 end
 
 
@@ -162,16 +192,19 @@
     end
 end
 
+local function getJoinedAgo(name)
+    if recentgroups[name] then
+        return GetTime() - recentgroups[name].time
+    end
+end
+
 local function WeightLfgItem(id, forAutoJoin)
     local _, action, caption, desc, voice, ilvl, time, bnetfr, charfr, guild, delisted, fullname, pcount, autoinv = C_LFGList.GetSearchResultInfo(id)
     if delisted then return 0 end
     local name,realm = PlayerName(fullname);
-    local stoplist = db.global.stoplist or {}
-    local ldesc = string.lower(caption) .. "|" .. string.lower(desc);
-    local isHopGroup = string.find(ldesc, "realm hop")
-    for i=1, #stoplist do
-        if string.find(ldesc, stoplist[i], 1, true) then return 0 end
-    end
+    local ldesc = caption .. "|" .. desc;
+    if searchInList(ldesc, db.global.stoplist) then return 0 end
+    local isHopGroup = searchInList(ldesc, db.global.priorityList);
     if forAutoJoin then
         if not autoinv or recentgroups[fullname] or (not isHopGroup and (pcount >= 35 or pcount <= 5)) then return 0 end
     end
@@ -181,7 +214,7 @@
 
     local visCoef = 1
     if recentgroups[fullname] then
-        local ago = GetTime() - recentgroups[fullname].time;
+        local ago = getJoinedAgo(fullname)
         visCoef = math.min(1,ago/600)
     end
 
@@ -227,7 +260,7 @@
         addTooltipLineIcon(ilvl > 0, "Min. ilvl: "..ilvl, READY_CHECK_WAITING_TEXTURE, 1, 1, 0)
         addTooltipLineIcon(voice ~= "", "Voice: "..voice, READY_CHECK_WAITING_TEXTURE, 1, 1, 0)
         addTooltipLineIcon(friends > 0, "Friends: "..friends, READY_CHECK_WAITING_TEXTURE, 1, 1, 0)
-        addTooltipLineIcon(autoinv, "Autoinvite!", READY_CHECK_READY_TEXTURE, 0, 1, 0)
+        addTooltipLineIcon(autoinv, "Autoaccept!", READY_CHECK_READY_TEXTURE, 0, 1, 0)
         local visitinfo = recentgroups[fullname]
         if visitinfo then
             local ago = GetTime() - visitinfo.time;
@@ -268,6 +301,63 @@
     return select(3, C_LFGList.GetSearchResultInfo(id))
 end
 
+local function updateRealmType(id)
+    local name = select(12, C_LFGList.GetSearchResultInfo(id))
+    local pname, realm = PlayerName(name);
+    return select(4, realmInfo:GetRealmInfo(realm)) or "???"
+end
+
+local function updateGroupAge(id)
+    local age = select(7, C_LFGList.GetSearchResultInfo(id))
+    return SecondsToTime(age, false, false, 1, false)
+end
+
+local function sortBase(col, value)
+    local column = lfgTable.cols[col];
+    local direction = column.sort or column.defaultsort or "asc"
+    if direction == "asc" then
+        return value
+    else
+        return not value;
+    end
+end
+
+
+local function sortByAge(self, rowa, rowb, sortbycol)
+    if rowa == nil or rowb == nil then return false end; -- wtf ScrollingTable???
+    local row1 = lfgTable:GetRow(rowa);
+    local row2 = lfgTable:GetRow(rowb);
+    local age1 = select(7, C_LFGList.GetSearchResultInfo(row1.id));
+    local age2 = select(7, C_LFGList.GetSearchResultInfo(row2.id));
+    return sortBase(sortbycol, age1<age2);
+end
+
+local function updateJoinAge(id)
+    local name = select(12, C_LFGList.GetSearchResultInfo(id))
+    local ago = getJoinedAgo(name);
+    if ago then
+        return SecondsToTime(ago, false, false, 1, false)
+    else
+        return ""
+    end
+end
+
+local function sortByJoinAge(self, rowa, rowb, sortbycol)
+    if rowa == nil or rowb == nil then return false end; -- wtf ScrollingTable???
+    local row1 = lfgTable:GetRow(rowa);
+    local row2 = lfgTable:GetRow(rowb);
+    local joinAge1 = getJoinedAgo(select(12, C_LFGList.GetSearchResultInfo(row1.id))) or 1e10;
+    local joinAge2 = getJoinedAgo(select(12, C_LFGList.GetSearchResultInfo(row2.id))) or 1e10;
+    return sortBase(sortbycol, joinAge1<joinAge2);
+end
+
+local function sortByScore(self, rowa, rowb, sortbycol)
+    if rowa == nil or rowb == nil then return false end; -- wtf ScrollingTable???
+    local row1 = lfgTable:GetRow(rowa);
+    local row2 = lfgTable:GetRow(rowb);
+    return row1.weight > row2.weight;
+end
+
 local function updateGroupCount(id)
     return select(13, C_LFGList.GetSearchResultInfo(id))
 end
@@ -275,23 +365,41 @@
 local function updateGroupRealm(id)
     local name = select(12, C_LFGList.GetSearchResultInfo(id))
     local pname, realm = PlayerName(name);
-    return realm
+    return realm;
 end
 
 local COLOR_YELLOW = {r=1,g=1,b=0,a=1}
+local COLOR_WHITE = {r=1,g=1,b=1,a=1}
 local COLOR_GREEN = {r=0,g=1,b=0,a=1}
+local COLOR_RED = {r=1,g=0,b=0,a=1}
+local COLOR_GREY = {r=0.5,g=0.5,b=0.5,a=1}
 local function updateRealmColor(id)
     local name = select(12, C_LFGList.GetSearchResultInfo(id))
     local pname, realm = PlayerName(name);
     return recentRealms[realm] and COLOR_YELLOW or COLOR_GREEN;
 end
 
+local function updateNameColor(id)
+    local name = select(3, C_LFGList.GetSearchResultInfo(id))
+    if searchInList(name, db.global.highlightList) then return COLOR_GREEN end;
+    if searchInList(name, db.global.stoplist) then return COLOR_GREY end;
+    if searchInList(name, db.global.priorityList) then return COLOR_YELLOW end;
+    return COLOR_WHITE;
+end
+
 local function filterTable(self, row)
-    local _, _, _, _, _, _, _, _, _, _, delisted, _, pcount, autoinv = C_LFGList.GetSearchResultInfo(row.id)
+    local _, _, caption, desc, _, _, _, _, _, _, delisted, _, pcount, autoinv = C_LFGList.GetSearchResultInfo(row.id)
     if delisted then return false end;
-    if pcount > 5 and cbHideRaids:GetChecked() then return false end;
-    if pcount <= 5 and cbHideGroups:GetChecked() then return false end;
+    if filterByCountId > 1 then
+        local fobj = filterByCountList[filterByCountId];
+        if pcount < fobj.min or pcount > fobj.max then return false end;
+    end
     if not autoinv and cbHideNoAutoinv:GetChecked() then return false end;
+    if filterString then
+        caption = string.lower(caption);
+        desc = string.lower(desc);
+        if not (string.find(caption, filterString, 1, true) or string.find(desc, filterString, 1, true))then return false end
+    end
     return true;
 end
 
@@ -307,19 +415,25 @@
     if lfgScanInProgress or not lfgTable then return end
     local count, list = C_LFGList.GetSearchResults()
     local tableData = {}
+    local extraColumnId = 4;
+
     for i = 1,count do
         local rid = list[i];
         if not rid then break end
         local data = list[i];
         local cols = {}
         local row = {rid}
-        cols[1] = {value=updateGroupName,args=row}
-        cols[2] = {value=updateGroupCount,args=row}
-        cols[3] = {value=updateGroupRealm,args=row,color=updateRealmColor,colorargs=row}
-        cols[4] = {}
+        for i=1, #tableTemplate do
+            local item = tableTemplate[i];
+            local cell = {value=item.value, args=row }
+            if item.color then
+                cell.color = item.color;
+                cell.colorargs = row;
+            end
+            cols[i] = cell
+        end
         table.insert(tableData, {cols=cols,id=rid,weight=WeightLfgItem(rid)})
     end
-    table.sort(tableData, sortByWeight);
     lfgTable:SetData(tableData)
     if #tableData == 0 then
         lbThrottle:SetText("No groups found")
@@ -334,9 +448,13 @@
 end
 
 local function TableCellClick(rowFrame, cellFrame, data, cols, row, realrow, column, scrollingTable, ...)
-    if not realrow or not canJoinGroup() then return end
+    if not realrow then return end
     local rowdata = scrollingTable:GetRow(realrow);
-    JoinGroup(rowdata.id)
+    if canJoinGroup() then
+        JoinGroup(rowdata.id)
+    else
+        LeaveParty()
+    end
 end
 
 
@@ -353,6 +471,7 @@
 
 function addon:OnInitialize()
     db = LibStub("AceDB-3.0"):New("CrossRealmAssistDB", SettingsDefaults)
+    addon.db = db;
     local minimapData = ldb:NewDataObject("CrossRealmAssistMinimapIcon",{
         type = "data source",
         text = "Cross Realm Assist",
@@ -428,7 +547,8 @@
 end
 
 function addon:CreateUI()
-    gui = setupWidget(CreateFrame("Frame","CrossRealmAssistMainUI",nil,"InsetFrameTemplate3"), {SetFrameStrata="LOW",SetWidth=208,SetHeight=60,EnableMouse=true,SetMovable=true,SetClampedToScreen=true})
+    local scale = db.global.uiScale or 1.0;
+    gui = setupWidget(CreateFrame("Frame","CrossRealmAssistMainUI",nil,"InsetFrameTemplate3"), {SetFrameStrata="LOW",SetWidth=208,SetHeight=60,EnableMouse=true,SetMovable=true,SetClampedToScreen=true,SetScale=scale})
     local title = setupWidget(CreateFrame("Frame",nil,gui), {SetWidth=190,SetHeight=18,EnableMouse=true,RegisterForDrag="LeftButton"}, 0, 6);
     title:SetScript("OnDragStart", function() gui:StartMoving() end)
     title:SetScript("OnDragStop", stopMovingWidget)
@@ -482,20 +602,57 @@
     lfgTable:SetFilter(filterTable)
 end
 
+local function updateFilter(self)
+    SearchBoxTemplate_OnTextChanged(self)
+    filterString = string.lower(edFilterText:GetText());
+    if filterString == "" then filterString = nil end;
+    addon.refreshList()
+end
+
+local function updateFilterByCount(btn, arg1, arg2, checked)
+    filterByCountId = arg1;
+    local filterObj = filterByCountList[arg1];
+    UIDropDownMenu_SetText(btFilterCount, "Count: "..filterObj.min.."-"..filterObj.max);
+    addon.refreshList()
+end
+
+local function numberFilter(self, level, menuList)
+    for i=1,#filterByCountList do
+        UIDropDownMenu_AddButton({text=filterByCountList[i].text, arg1=i,checked=(i==filterByCountId), func=updateFilterByCount}, level)
+    end
+end
+
 function addon:CreateLFGUI()
-    lfgui = setupWidget(CreateFrame("Frame","CrossRealmAssistJoinUI",nil,"UIPanelDialogTemplate"), {SetFrameStrata="DIALOG",SetWidth=405,SetHeight=323,EnableMouse=true,SetMovable=true})
+    local scale = db.global.lfgPanelScale or 1.0;
+    local itemCount = db.global.listItemCount;
+    local shift = itemCount * 16;
+
+    tableTemplate = {
+        {name="Title",width=160, value = updateGroupName, color = updateNameColor},
+        {name="#",width=30,align="CENTER",value = updateGroupCount},
+        {name="Realm",width=120,align="RIGHT",value = updateGroupRealm, color = updateRealmColor}
+    }
+
+    extraColumns = db.global.extraColumns;
+    if extraColumns.rtype then table.insert(tableTemplate, {name="Type",width=40,align="CENTER", value = updateRealmType}) end;
+    if extraColumns.groupAge then table.insert(tableTemplate, {name="Age",width=45,align="CENTER",comparesort=sortByAge, value = updateGroupAge}) end;
+    if extraColumns.joinTime then table.insert(tableTemplate, {name="Joined",width=45,align="CENTER",comparesort=sortByJoinAge, value = updateJoinAge}) end;
+    table.insert(tableTemplate, {name="Misc",width=50,DoCellUpdate=updateTableData,comparesort=sortByScore,align="RIGHT",sort="asc"})
+
+    local tableWidth = 0;
+    for i=1,#tableTemplate do
+        tableWidth = tableWidth + tableTemplate[i].width;
+    end
+
+
+    lfgui = setupWidget(CreateFrame("Frame","CrossRealmAssistJoinUI",nil,"UIPanelDialogTemplate"), {SetFrameStrata="DIALOG",SetWidth=tableWidth+45,SetHeight=shift+95,EnableMouse=true,SetMovable=true,SetScale=scale})
     lfgui.title:SetText("Click to join group")
     lfgui:SetScript("OnUpdate",addon.lfgUpdate)
     local titlereg = lfgui:CreateTitleRegion()
     titlereg:SetAllPoints(lfgui.title)
     addon:CreateTabs()
 
-    lfgTable = ScrollingTable:CreateST({
-        {name="Title",width=160},
-        {name="#",width=30,align="CENTER"},
-        {name="Realm",width=120,align="RIGHT"},
-        {name="",width=50,DoCellUpdate=updateTableData}
-    },15,16,nil,lfgui);
+    lfgTable = ScrollingTable:CreateST(tableTemplate,itemCount,16,nil,lfgui);
 
     lfgTable:RegisterEvents({OnEnter=ShowLfgInfo,OnLeave=HideTooltip,OnClick=TableCellClick})
 
@@ -504,19 +661,36 @@
     lfgui:SetPoint("CENTER",0,0)
     refreshLFGList()
 
-    lbThrottle = setupWidget(lfgui:CreateFontString(nil,"BACKGROUND", "GameFontHighlightSmall"), {SetWidth=300}, 50, 100)
+    lbThrottle = setupWidget(lfgui:CreateFontString(nil,"BACKGROUND", "GameFontHighlightSmall"), {SetWidth=tableWidth}, 0, 100)
     lbThrottle:Hide();
 
-    local btRefresh = setupWidget(CreateFrame("Button",nil,lfgui,"UIPanelSquareButton"),{SetWidth=22,SetHeight=22},379,27)
+    local btRefresh = setupWidget(CreateFrame("Button",nil,lfgui,"UIPanelSquareButton"),{SetWidth=22,SetHeight=22},tableWidth+19,27)
     btRefresh.icon:SetTexture("Interface/BUTTONS/UI-RefreshButton")
     btRefresh.icon:SetTexCoord(0,1,0,1);
     btRefresh:SetScript("OnClick",addon.refreshLfgCurrent)
 
-    setupWidget(CreateFrame("Frame",nil,lfgui,"HorizontalBarTemplate"),{SetWidth=392,SetHeight=2}, 8, 294)
-    local text = setupWidget(lfgui:CreateFontString(nil,"BACKGROUND", "GameFontHighlightSmall"), {SetText="Hide:"}, 20, 302)
+    setupWidget(CreateFrame("Frame",nil,lfgui,"HorizontalBarTemplate"),{SetWidth=tableWidth+32,SetHeight=2}, 8, shift+54)
+    local text = setupWidget(lfgui:CreateFontString(nil,"BACKGROUND", "GameFontHighlightSmall"), {SetText="Filters:"}, 20, shift+68)
 
-    cbHideGroups = setupWidget(CreateFrame("CheckButton",nil,lfgui,"UICheckButtonTemplate"),{SetWidth=22,SetHeight=22})
-    cbHideGroups:SetPoint("TOPLEFT",text,"TOPRIGHT",5,7)
+    btFilterCount = CreateFrame("FRAME", "CrossRealmAssistCountFilter", lfgui, "UIDropDownMenuTemplate")
+    UIDropDownMenu_SetWidth(btFilterCount, 90)
+    UIDropDownMenu_SetText(btFilterCount, "By count")
+    UIDropDownMenu_Initialize(btFilterCount, numberFilter)
+    btFilterCount:SetPoint("TOPLEFT",text,"TOPRIGHT",-10,10)
+
+    cbHideNoAutoinv = setupWidget(CreateFrame("CheckButton",nil,lfgui,"UICheckButtonTemplate"),{SetWidth=22,SetHeight=22})
+    cbHideNoAutoinv:SetPoint("TOPLEFT",btFilterCount,"TOPRIGHT",-10,-3)
+    cbHideNoAutoinv:SetScript("OnClick",addon.refreshList)
+    text = setupWidget(lfgui:CreateFontString(nil,"BACKGROUND", "GameFontHighlightSmall"), {SetText="Autoaccept only"})
+    text:SetPoint("TOPLEFT",cbHideNoAutoinv,"TOPRIGHT",0,-7)
+
+    edFilterText = setupWidget(CreateFrame("EditBox",nil,lfgui,"SearchBoxTemplate"),{SetHeight=22})
+    edFilterText:SetPoint("TOPLEFT",text,"TOPRIGHT",10,7)
+    edFilterText:SetPoint("TOPRIGHT",lfgui,"BOTTOMRIGHT",-10,0)
+    edFilterText:SetScript("OnTextChanged",updateFilter)
+
+    --[[cbHideGroups = setupWidget(CreateFrame("CheckButton",nil,lfgui,"UICheckButtonTemplate"),{SetWidth=22,SetHeight=22})
+    cbHideGroups:SetPoint("TOPLEFT",btFilterCount,"TOPRIGHT",5,7)
     cbHideGroups:SetScript("OnClick",addon.refreshList)
     text = setupWidget(lfgui:CreateFontString(nil,"BACKGROUND", "GameFontHighlightSmall"), {SetText="5p Groups"})
     text:SetPoint("TOPLEFT",cbHideGroups,"TOPRIGHT",0,-7)
@@ -531,14 +705,14 @@
     cbHideNoAutoinv:SetPoint("TOPLEFT",text,"TOPRIGHT",5,7)
     cbHideNoAutoinv:SetScript("OnClick",addon.refreshList)
     text = setupWidget(lfgui:CreateFontString(nil,"BACKGROUND", "GameFontHighlightSmall"), {SetText="Groups w/o Autoinvite"})
-    text:SetPoint("TOPLEFT",cbHideNoAutoinv,"TOPRIGHT",0,-7)
+    text:SetPoint("TOPLEFT",cbHideNoAutoinv,"TOPRIGHT",0,-7)]]
 end
 
 function addon:CreateTabs()
     local prevTab
-    for i=1,#lfgGroups do
+    for i=1,#addon.lfgGroups do
         local tab = CreateFrame("Button","$parentTab"..i,lfgui,"CharacterFrameTabButtonTemplate")
-        tab:SetText((C_LFGList.GetCategoryInfo(lfgGroups[i])));
+        tab:SetText((C_LFGList.GetCategoryInfo(addon.lfgGroups[i])));
         tab.searchID = i;
         tab:SetID(i);
         PanelTemplates_TabResize(tab, 0)
@@ -566,8 +740,8 @@
 end
 
 function addon.GetNextAutoScanCategory()
-    for i=1,#lfgGroups do
-        if not autoScanGroups[i] and db.global.quickJoin[lfgGroups[i]] and i ~= curLfgGroup then return i end
+    for i=1,#addon.lfgGroups do
+        if not autoScanGroups[i] and db.global.quickJoin[addon.lfgGroups[i]] and i ~= curLfgGroup then return i end
     end
     return nil
 end
@@ -580,7 +754,7 @@
     lfgScanInProgress = true
     curLfgGroup = group
     local languages = db.global.allLanguages and allLanguageTable or C_LFGList.GetLanguageSearchFilter();
-    C_LFGList.Search(lfgGroups[curLfgGroup],"",nil,nil,languages)
+    C_LFGList.Search(addon.lfgGroups[curLfgGroup],"",nil,nil,languages)
 end
 
 function addon:LfgScanFailed(event, reason)
@@ -854,7 +1028,7 @@
         addon.LfgScan(self.category)
     end,
     tooltip = function(self)
-        GameTooltip:AddLine("Scan "..(C_LFGList.GetCategoryInfo(lfgGroups[self.category])))
+        GameTooltip:AddLine("Scan "..(C_LFGList.GetCategoryInfo(addon.lfgGroups[self.category])))
     end,
     text = "Scan more"
 }
@@ -881,7 +1055,7 @@
     text = "Quick Join",
     func = function()
         StaticPopupDialogs["CROSS_REALM_ASSIST"] = {
-            text = "Quick Join button will automatically join you to a random group with autoinvite and medium number of players. You never join same group twice, you need to clear join history to do so. This message can be disabled in options.",
+            text = "Quick Join button will automatically join you to a random group with autoaccept and medium number of players. You never join same group twice, you need to clear join history to do so. This message can be disabled in options.",
             button1 = OKAY,
             hideOnEscape = true,
             preferredIndex = 3,
@@ -960,9 +1134,7 @@
 
 -- Settings
 
-local function toggleMinimapIcon(btn, arg1, arg2, checked)
-    local hidden = not checked;
-    db.global.minimap.hide = hidden;
+function addon.toggleMinimapIcon(hidden)
     if hidden then
         DEFAULT_CHAT_FRAME:AddMessage("|cffff0000Type |cffffffff/cra |cffff0000in chat to open Cross Realm Assist without minimap button");
         minimapIcon:Hide("CrossRealmAssistMinimapIcon")
@@ -971,6 +1143,14 @@
     end
 end
 
+function addon.updateLfgScale(scale)
+    if lfgui then lfgui:SetScale(scale) end;
+end
+
+function addon.updateUIScale(scale)
+    if gui then gui:SetScale(scale) end;
+end
+
 local function toggleQuickJoinHint(btn, arg1, arg2, checked)
     db.global.quickJoinHint = checked
 end
@@ -992,6 +1172,10 @@
     if checked then addon:RegisterEvent("PLAYER_FLAGS_CHANGED", "PlayerFlagsUpdate") end
 end
 
+local function joinFriendGroup(btn, rid)
+	JoinGroup(rid);
+end
+
 local function ClearJoinHistory()
     recentgroups = {}
     addon:UpdateAutoButtonStatus()
@@ -1024,38 +1208,41 @@
 local function initMenu(self, level)
     if not level then return end
     if level == 1 then
+		UIDropDownMenu_AddButton({text="Clear join history",notCheckable=1,func=ClearJoinHistory}, level)
         UIDropDownMenu_AddButton({text="Clear realm history",notCheckable=1,func=ClearRealmHistory}, level)
-        UIDropDownMenu_AddButton({text="Clear join history",notCheckable=1,func=ClearJoinHistory}, level)
+        UIDropDownMenu_AddButton({disabled=1,notCheckable=1}, level)
 
-        if canJoinGroup() then
-            UIDropDownMenu_AddButton({disabled=1,notCheckable=1}, level)
+        UIDropDownMenu_AddButton({text="Cross Realm Assist Toolbox",notCheckable=1,func=CreateHopGroup,isTitle=1}, level)
+        UIDropDownMenu_AddButton({text="Show Quick Join Hint",checked=db.global.quickJoinHint, func=toggleQuickJoinHint,keepShownOnClick=true,isNotRadio=true}, level)
+		if canJoinGroup() then
             UIDropDownMenu_AddButton({text="Create a group for realm hoppers",notCheckable=1,func=CreateHopGroup}, level)
+			UIDropDownMenu_AddButton({text="Join to a friend",notCheckable=1,hasArrow=1,value="jtf",keepShownOnClick=true}, level)
         end
 
         UIDropDownMenu_AddButton({disabled=1,notCheckable=1}, level)
-        UIDropDownMenu_AddButton({text="Show Quick Join Hint",checked=db.global.quickJoinHint, func=toggleQuickJoinHint,keepShownOnClick=true,isNotRadio=true}, level)
-        UIDropDownMenu_AddButton({text="Settings",notCheckable=1,hasArrow=1,value="settings",keepShownOnClick=true}, level)
+        UIDropDownMenu_AddButton({text="Settings...",notCheckable=1,func=addon.showSettings}, level)
     elseif level == 2 then
-        if UIDROPDOWNMENU_MENU_VALUE == "settings" then
-            UIDropDownMenu_AddButton({text="Show Minimap Button",checked=not db.global.minimap.hide, func=toggleMinimapIcon,keepShownOnClick=true,isNotRadio=true}, level)
-            UIDropDownMenu_AddButton({text="All language groups",checked=db.global.allLanguages, func=toggleAllLanguages,keepShownOnClick=true,isNotRadio=true}, level)
-            UIDropDownMenu_AddButton({text="Apply to groups as DD only",checked=db.global.applyAsDD, func=toggleApplyAsDD,keepShownOnClick=true,isNotRadio=true}, level)
-            UIDropDownMenu_AddButton({disabled=1,notCheckable=1}, level)
-            UIDropDownMenu_AddButton({text="Quick join categories",notCheckable=1,hasArrow=1,value="qjc",keepShownOnClick=true}, level)
-            UIDropDownMenu_AddButton({text="Edit Quick Join Stoplist",notCheckable=1,func=QuickJoinStoplist}, level)
-            UIDropDownMenu_AddButton({disabled=1,notCheckable=1}, level)
-            UIDropDownMenu_AddButton({text="Experimental features",notCheckable=1,hasArrow=1,value="experimental",keepShownOnClick=true}, level)
-        end
-    elseif level == 3 then
-        if UIDROPDOWNMENU_MENU_VALUE == "qjc" then
-            for i=1,#lfgGroups do
-                local groupId = lfgGroups[i]
-                UIDropDownMenu_AddButton({text=(C_LFGList.GetCategoryInfo(groupId)),checked=db.global.quickJoin[groupId],arg1=groupId,func=toggleQuickJoin,keepShownOnClick=true,isNotRadio=true}, level)
-            end
-        elseif UIDROPDOWNMENU_MENU_VALUE == "experimental" then
-            UIDropDownMenu_AddButton({isTitle=1,text="Use at your own risk!",notCheckable=1}, level)
-            UIDropDownMenu_AddButton({text="Auto create group for realm hopping when AFK at garrison",checked=db.global.autoCreateHopGroup,keepShownOnClick=true,isNotRadio=true,func=toggleCreateHopGroup}, level)
-        end
+        if UIDROPDOWNMENU_MENU_VALUE == "jtf" then
+			local count, list = C_LFGList.GetSearchResults()
+			local friends = {}
+			for i = 1,count do
+				local rid = list[i];
+				if not rid then break end
+				local bnf, chf, gf = C_LFGList.GetSearchResultFriends(rid)
+				for j=1,#bnf do	table.insert(friends,{name="|cFF00B1F0"..bnf[j],type=0,id=rid}) end
+				for j=1,#chf do	table.insert(friends,{name="|cFFFF80FF"..chf[j],type=1,id=rid}) end
+				for j=1,#gf do table.insert(friends,{name="|cFF40FF40"..gf[j],type=2,id=rid}) end
+			end
+			if #friends==0 then
+				UIDropDownMenu_AddButton({text="No friends found (click to refresh)",notCheckable=1,func=addon.refreshLfgCurrent}, level)
+			else
+				UIDropDownMenu_AddButton({text="Refresh",notCheckable=1,func=addon.refreshLfgCurrent}, level)
+				table.sort(friends, sortByTypeAndName);
+				for i=1,#friends do
+					UIDropDownMenu_AddButton({text=friends[i].name,notCheckable=1,func=joinFriendGroup,arg1=friends[i].id}, level)
+				end
+			end
+		end
     end
 end
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/CrossRealmAssistSettings.lua	Mon Oct 26 00:47:27 2015 +0300
@@ -0,0 +1,329 @@
+local version = "v0.75 beta";
+local about = [[About:
+I have started to make this addon as a tool for myself. Few people started to use it when I pushed it to Curse. Then updated it once with 6.2 release.
+After some time I have catched a dialog on my own realm discussing it. I went to check curse page and I was shocked - it was in top-60 monthly downloaded addons with lots of suggestions and few bug reports.
+I wasn't going to support it - because it was working - but now it looks like I have to spend at least some time to make it better.
+
+Realm hopping has become a thing. The idea was rather obvious, but less people were using it at the start of WOD. Now every raid group with autoaccept are going to have some quiet people joining and leaving it. But since we became a huge community, it is up to us to control this.
+The idea is to create groups from realmhoppers to realmhoppers. Taking small steps to not flood LFG too much. There are some ideas how to make realmhopping more comfortably while reducing annoyance to others.
+
+FAQ:
+Q: I cannot loot timeless coins / some other loot
+A: Some items cannot be looted while in a raid group. However, rare timeless items can be.".
+
+Q: I'm on the oceanic region and I can't hop to another realm
+A: Unfortunately, oceanic realms are separated by Blizzard. There is little I can do :(
+
+Changelog:
+
+v0.7 - 0.75
+- New options panel (Groundwork for future customization)
+- Some UI customizations are now possible
+- "Join to a friend" feature
+- "Keywords" feature - blacklist, whitelist and highlight list
+- You can add more info to "Manual Join" list
+- Clicking to join group when you are already in a group causes you to leave it.
+- You can now create a group for other people to use for realm hopping purpose
+- You can now automatically create such group when AFK at garrison
+- Added advanced filters
+- Last column ("Misc") is now a sortable column - it uses base sort (by weight)
+
+http://www.curse.com/addons/wow/crossrealmassist
+https://www.reddit.com/r/crossrealmassist
+]]
+
+local AceConfig = LibStub("AceConfig-3.0")
+local addon = CrossRealmAssist;
+
+local db
+local orderId = 0
+local registered = false;
+local needReloadUI = false;
+
+local function haveToReloadUI()
+    return not needReloadUI;
+end
+
+local function order()
+    orderId = orderId + 1
+    return orderId;
+end
+
+local function traverse(info, value)
+    local node = db.global
+    local last
+    if info.arg then
+        for k,v in ipairs(info.arg) do
+            if last then node = node[last] end
+            last = v;
+        end
+        if value ~= nil then
+            local callback = info.arg.callback
+            if callback then
+                if (type(callback) == "string") then
+                    addon[callback](value);
+                elseif type(callback) == "function" then
+                    callback(value)
+                end
+            end
+            if info.arg.needReloadUI then
+                needReloadUI = true
+            end
+        end
+    else last = info[#info] end
+    return node, last
+end
+
+local function getter(info)
+    local node, last = traverse(info);
+    return node[last]
+end
+
+local function setter(info, value)
+    local node, last = traverse(info, value);
+    node[last] = value;
+end
+
+local function multiGetter(info, key)
+    local node, last = traverse(info);
+    return node[last][key];
+end
+
+local function multiSetter(info, key, value)
+    local node, last = traverse(info, value);
+    node[last][key] = value;
+end
+
+local function setMinimapIcon(hidden)
+    if hidden then
+        DEFAULT_CHAT_FRAME:AddMessage("|cffff0000Type |cffffffff/cra |cffff0000in chat to open Cross Realm Assist without minimap button");
+        minimapIcon:Hide("CrossRealmAssistMinimapIcon")
+    else
+        minimapIcon:Show("CrossRealmAssistMinimapIcon")
+    end
+end
+
+local function createKeywordTable(name, desc, listValue)
+    local list = db.global[listValue];
+    if not list then
+        list = {}
+        db.global[listValue] = list;
+    end
+    local selected = 0;
+
+    local function setSelection(info, value)
+        selected = value
+    end
+
+    local function getSelection()
+        return selected
+    end
+
+    local function deleteSelected()
+        if selected == 0  then return end;
+        table.remove(list, selected);
+        selected = 0;
+    end
+
+    local function addItem(info, value)
+        if string.len(value) >= 3  then
+            value = string.lower(value);
+            for k,v in pairs(list) do
+                if v == value then return end
+            end
+            table.insert(list, value);
+        end
+    end
+
+    return {
+        name = name,
+        type = "group",
+        order=order(),
+        args = {
+            desc = {
+                type = "description",
+                order=order(),
+                name = desc
+            },
+            input = {
+                type = "input",
+                name = "Add new",
+                order=order(),
+                set = addItem
+            },
+            list = {
+                name = "Current list",
+                type = "select",
+                values=list,
+                style="radio",
+                order=order(),
+                set = setSelection,
+                get = getSelection,
+            },
+            delete = {
+                type = "execute",
+                name = "Delete selected",
+                order=order(),
+                func = deleteSelected
+            },
+        },
+    }
+end
+
+local function getLFGGroups()
+    local table = {}
+    for i=1,#addon.lfgGroups do
+        local groupId = addon.lfgGroups[i]
+        table[groupId] = (C_LFGList.GetCategoryInfo(groupId))
+    end
+    return table;
+end
+
+local function register()
+    local CRAOptions = {
+        type = "group",
+        get = getter,
+        set = setter,
+        args = {
+            reload = {
+                type = "execute",
+                name = "Reload UI",
+                order=order(),
+                func = ReloadUI,
+                hidden = haveToReloadUI
+            },
+            reloadDesc = {
+                name = "UI reload is required for some settings to take effect",
+                type = "description",
+                order=order(),
+                width="double",
+                hidden = haveToReloadUI
+            },
+            general={
+                name = "General options",
+                type = "group",
+                order=order(),
+                args = {
+                    quickJoinHint = {
+                        name = "Show Quick Join Hint",
+                        type = "toggle",
+                        order=order(),
+                    },
+                    minimap = {
+                        name = "Hide minimap button",
+                        type = "toggle",
+                        arg = {"minimap","hide",callback="toggleMinimapIcon"},
+                        order=order(),
+                    },
+                    allLanguages = {
+                        name = "All language groups",
+                        type = "toggle",
+                        order=order(),
+                    },
+                    applyAsDD = {
+                        name = "Apply to groups as DD only",
+                        type = "toggle",
+                        width = "double",
+                        order=order(),
+                    },
+                    quickJoin = {
+                        name = "Quick Join Categories",
+                        type = "multiselect",
+                        values = getLFGGroups(),
+                        get = multiGetter,
+                        set = multiSetter,
+                        order=order()
+                    },
+                    hopGroupHeader = {
+                        name = "Realm Hoppers Union",
+                        type = "header",
+                        order=order(),
+                    },
+                    hopGroupDesc = {
+                        name = "Help other CrossRealmAssist users (and other people) and they will help you someday! Open a group for others when you are away. CRA can automatically open such group when following conditions are met:",
+                        type = "description",
+                        order=order(),
+                    },
+                    autoCreateHopGroup = {
+                        name = "You are AFK at garrison",
+                        type = "toggle",
+                        order=order(),
+                    }
+                }
+            },
+            keywords={
+                name = "Keywords",
+                type = "group",
+                order=order(),
+                args={
+                    blacklist = createKeywordTable("Blacklisted keywords","Groups with these keywords will be ignored when quick joining. Don't be evil","stoplist"),
+                    whitelist = createKeywordTable("Priority keywords","Groups with these keywords are high-priority groups and are first to join regardless of group size","priorityList"),
+                    highlight = createKeywordTable("Highlight keywords","Groups with these keywords will be highlighted in green in the Manual Join panel","highlightList")
+                }
+            },
+            appearance={
+                name = "Appearance",
+                type = "group",
+                order=order(),
+                args={
+                    uiScale = {
+                        name = "Mini panel size",
+                        type = "range",
+                        min=0.5, max=1.5,isPercent=true,bigStep=0.05,
+                        order=order(),
+                        arg={"uiScale",callback="updateUIScale"}
+                    },
+                    lfgPanelScale = {
+                        name = "Manual Join panel size",
+                        type = "range",
+                        min=0.5, max=1.5,isPercent=true,bigStep=0.05,
+                        order=order(),
+                        arg={"lfgPanelScale",callback="updateLfgScale"}
+                    },
+                    listItemCount = {
+                        name = "Manual Join list size",
+                        type = "range",
+                        min=5, max=30,step=1,
+                        order=order(),
+                        arg={"listItemCount",needReloadUI=1}
+                    },
+                    extraColumns = {
+                        name = "Show extra info in group table",
+                        type = "multiselect",
+                        values = {rtype="Realm type (PVE/PVP/etc)",groupAge="Group age",joinTime="Time since joining"},
+                        arg={"extraColumns",needReloadUI=1},
+                        get = multiGetter,
+                        set = multiSetter,
+                        order=order()
+                    },
+                }
+            },
+            about={
+                name = "About",
+                type = "group",
+                order=order(),
+                args={
+                    aboutHeader = {
+                        name = "CrossRealmAssist "..version.." by ShadowTheAge",
+                        type = "header",
+                        order=order(),
+                    },
+                    aboutDesc = {
+                        name = about,
+                        type = "description",
+                        width = "full",
+                        order=order(),
+                    }
+                }
+            }
+        }
+    }
+    AceConfig:RegisterOptionsTable("CrossRealmAssist", CRAOptions)
+    registered = true;
+end
+
+addon.showSettings = function()
+    db = addon.db;
+    if not registered then register() end
+    LibStub("AceConfigDialog-3.0"):Open("CrossRealmAssist")
+end
--- a/embeds.xml	Sun Sep 27 16:06:18 2015 +0300
+++ b/embeds.xml	Mon Oct 26 00:47:27 2015 +0300
@@ -1,5 +1,5 @@
 <Ui xsi:schemaLocation="http://www.blizzard.com/wow/ui/ ..\FrameXML\UI.xsd">
-	<Script file="libs\LibStub\LibStub.lua"/>
+	<Script file="Libs\LibStub\LibStub.lua"/>
 	<Include file="Libs\CallbackHandler-1.0\CallbackHandler-1.0.xml"/>
 	<Script file="Libs\LibDataBroker-1.1\LibDataBroker-1.1.lua"/>
 	<Script file="Libs\LibDBIcon-1.0\LibDBIcon-1.0.lua"/>
@@ -10,4 +10,7 @@
 	<Include file="Libs\lib-st\lib-st.xml"/>
 	<Include file="Libs\AceTimer-3.0\AceTimer-3.0.xml"/>
 	<Include file="Libs\AceDB-3.0\AceDB-3.0.xml"/>
+	<Include file="Libs\AceGUI-3.0\AceGUI-3.0.xml"/>
+	<Include file="Libs\AceConfig-3.0\AceConfig-3.0"/>
+	<Script file="Libs\LibRealmInfo\LibRealmInfo.lua"/>
 </Ui>
\ No newline at end of file