New release

Leandro Silva [11-10-18 - 12:41]
New release
Filename
.gitignore
Bindings.xml
LeoGuildManager.lua
LeoGuildManager.txt
LeoGuildManager.xml
LeoGuildManagerInit.lua
LeoGuildManagerUI.lua
Libs/LibAddonMenu-2.0/LICENSE
Libs/LibAddonMenu-2.0/LibAddonMenu-2.0.lua
Libs/LibAddonMenu-2.0/controls/button.lua
Libs/LibAddonMenu-2.0/controls/checkbox.lua
Libs/LibAddonMenu-2.0/controls/colorpicker.lua
Libs/LibAddonMenu-2.0/controls/custom.lua
Libs/LibAddonMenu-2.0/controls/description.lua
Libs/LibAddonMenu-2.0/controls/divider.lua
Libs/LibAddonMenu-2.0/controls/dropdown.lua
Libs/LibAddonMenu-2.0/controls/editbox.lua
Libs/LibAddonMenu-2.0/controls/header.lua
Libs/LibAddonMenu-2.0/controls/iconpicker.lua
Libs/LibAddonMenu-2.0/controls/panel.lua
Libs/LibAddonMenu-2.0/controls/slider.lua
Libs/LibAddonMenu-2.0/controls/submenu.lua
Libs/LibAddonMenu-2.0/controls/texture.lua
Libs/LibFeedback/LibFeedback.txt
Libs/LibFeedback/feedback.lua
Libs/LibFeedback/libs/LibStub.lua
Libs/LibStub/LibStub.lua
Settings.lua
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..1333ed7
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+TODO
diff --git a/Bindings.xml b/Bindings.xml
index d4d06ae..7763672 100644
--- a/Bindings.xml
+++ b/Bindings.xml
@@ -2,7 +2,7 @@
   <Layer name="SI_KEYBINDINGS_CATEGORY_GENERAL">
     <Category name="Leo's Guild Manager">
       <Action name="LEOGM_TOGGLE_WINDOW">
-        <Down>LeoGM:ToggleUI()</Down>
+        <Down> LeoGuildManagerUI.ToggleUI() </Down>
       </Action>
     </Category>
   </Layer>
diff --git a/LeoGuildManager.lua b/LeoGuildManager.lua
index 09cf0b1..c558e37 100644
--- a/LeoGuildManager.lua
+++ b/LeoGuildManager.lua
@@ -1,18 +1,20 @@

-LeoGM = {}
-LeoGM.name = "LeoGuildManager"
-LeoGM.displayName = "Leo's Guild Manager"
-LeoGM.version = "1.0.1"
-LeoGM.chatPrefix = "|c39B027" .. LeoGM.name .. "|r: "
-LeoGM.panelList = { "Rules", "Purge" }
-LeoGM.members = {}
-LeoGM.guilds = {}
-LeoGM.isScanning = false
-LeoGM.salesEvents = {}
-LeoGM.depositsEvents = {}
-LeoGM.numEvents = {}
-
-function LeoGM.formatNumber(amount)
+LeoGuildManager.members = {}
+LeoGuildManager.guilds = {}
+LeoGuildManager.isScanning = false
+LeoGuildManager.salesEvents = {}
+LeoGuildManager.depositsEvents = {}
+LeoGuildManager.numEvents = {}
+LeoGuildManager.inCombat = false
+LeoGuildManager.nextGuildScan = 1
+
+LeoGuildManager.manualScan = false
+local eventTooOld = {}
+
+local scanInterval = 5 * LeoGuildManager.MS_IN_MINUTE
+local shortScanInterval = 2000
+
+function LeoGuildManager.formatNumber(amount)
     if amount == nil then return nil; end
     if type(amount) == "string" then amount = tonumber( amount ) end
     if type(amount) ~= "number" then return amount; end
@@ -20,8 +22,66 @@ function LeoGM.formatNumber(amount)
     return FormatIntegerWithDigitGrouping( amount, GetString( SI_DIGIT_GROUP_SEPARATOR ) )
 end

-function LeoGM.GetGuilds(gName)
-    guilds = {}
+local function formatMessage(message)
+    return LeoGuildManager.chatPrefix .. message
+end
+
+function LeoGuildManager.log(message)
+    d(formatMessage(message))
+end
+
+function LeoGuildManager.GetCycles()
+    local cycleData = LeoGuildManager.cycleMM
+    if LeoGuildManager.globalData.settings.integration == "Arkadiu's Trade Tools" then
+        cycleData = LeoGuildManager.cycleATT
+    end
+    local cycle = {}
+    for _, data in pairs(cycleData) do
+        table.insert(cycle, data.name)
+    end
+    return cycle
+end
+
+function LeoGuildManager.GetCycleName(id)
+    local cycleData = LeoGuildManager.cycleMM
+    if LeoGuildManager.globalData.settings.integration == "Arkadiu's Trade Tools" then
+        cycleData = LeoGuildManager.cycleATT
+    end
+    for _, data in pairs(cycleData) do
+        if data.id == id then return data.name end
+    end
+    return nil
+end
+
+function LeoGuildManager.GetNewRangeName(value)
+    if value == 7 then return "1 week"
+    elseif value == 14 then return "2 weeks"
+    elseif value == 21 then return "3 weeks"
+    else return "1 month" end
+end
+
+function LeoGuildManager.GetCycleIdByName(name)
+    local cycleData = LeoGuildManager.cycleMM
+    if LeoGuildManager.globalData.settings.integration == "Arkadiu's Trade Tools" then
+        cycleData = LeoGuildManager.cycleATT
+    end
+    for _, data in pairs(cycleData) do
+        if data.name == name then return data.id end
+    end
+    return nil
+end
+
+function LeoGuildManager.GetGuildId(name)
+    for guildId, guildName in pairs(LeoGuildManager.guilds) do
+        if guildName == name then return guildId end
+    end
+    return nil
+end
+
+function LeoGuildManager.GetGuilds()
+    if #LeoGuildManager.guilds > 0 then return LeoGuildManager.guilds end
+
+    LeoGuildManager.guilds = {}
     if GetNumGuilds() > 0 then
         for guild = 1, GetNumGuilds() do
             local guildId = GetGuildId(guild)
@@ -29,18 +89,57 @@ function LeoGM.GetGuilds(gName)
             if(not guildName or (guildName):len() < 1) then
                 guildName = "Guild " .. guildId
             end
-            if gName ~= nil and gName == guildName then
-                return guildId
-            end
-            guilds[guildId] = guildName
+            LeoGuildManager.guilds[guildId] = guildName
+        end
+    end
+
+    return LeoGuildManager.guilds
+end
+
+function LeoGuildManager.GetGuildIndex(guildName)
+    if GetNumGuilds() > 0 then
+        for guild = 1, GetNumGuilds() do
+            local name = GetGuildName(guildId)
+            if name == guildName then return guild end
+        end
+    end
+    return nil
+end
+
+function LeoGuildManager.GetGuildRankName(guildId, rankId)
+    local name = GetGuildRankCustomName(guildId, rankId)
+    if name == "" then
+        name = GetDefaultGuildRankName(guildId, rankId)
+    end
+    return name
+end
+
+function LeoGuildManager.GetGuildRankId(guildId, rankName)
+    for i = 1, GetNumGuildRanks(guildId) do
+        local name = GetGuildRankCustomName(guildId, i)
+        if name == "" then
+            name = GetDefaultGuildRankName(guildId, i)
+        end
+        if name == rankName then return i end
+    end
+    return 0
+end
+
+function LeoGuildManager.GetGuildRanks(guildId)
+    local ranks = {}
+    for i = 1, GetNumGuildRanks(guildId) do
+        local rankName = GetGuildRankCustomName(guildId, i)
+        if rankName == "" then
+            rankName = GetDefaultGuildRankName(guildId, i)
         end
+        ranks[i] = rankName
     end
-    return guilds
+    return ranks
 end

-function LeoGM.GetGuildMembers(guildId)
+function LeoGuildManager.GetGuildMembers(guildId)

-    LeoGM.members = {}
+    LeoGuildManager.members = {}
     local numGuildMembers = GetNumGuildMembers(guildId)
     for guildMemberIndex = 1, numGuildMembers do
         local displayName, note, rankIndex, status, secsSinceLogoff = GetGuildMemberInfo(guildId, guildMemberIndex)
@@ -48,420 +147,566 @@ function LeoGM.GetGuildMembers(guildId)
             secsSinceLogoff = 0
         end
         local rankId = GetGuildRankId(guildId, rankIndex)
-        local rankName = GetGuildRankCustomName(guildId, rankIndex)
-        table.insert(LeoGM.members, {
+        local rankName = LeoGuildManager.GetGuildRankName(guildId, rankIndex)
+        table.insert(LeoGuildManager.members, {
+            memberIndex = guildMemberIndex, -- just to make sure ...
             name = displayName,
             rankIndex = rankIndex,
             rankId = rankId,
             rankName = rankName,
-            secsSinceLogoff = secsSinceLogoff,
+            memberSince = 0,
+            invitedBy = "",
+            online = secsSinceLogoff,
             deposits = 0,
             taxes = 0,
-            sales = 0
+            sales = 0,
+            joined = 0,
+            endangered = false
         })
     end
 end

-function LeoGM.AddSale(memberName, value)
-    for i = 1, #LeoGM.members do
-        if LeoGM.members[i].name == memberName then
-            LeoGM.members[i].sales = LeoGM.members[i].sales + value
+function LeoGuildManager.SetJoined(memberName, joined)
+    for i = 1, #LeoGuildManager.members do
+        if LeoGuildManager.members[i].name == memberName then
+            LeoGuildManager.members[i].joined = joined
             return
         end
     end
 end

-function LeoGM.AddDeposit(memberName, value)
-    for i = 1, #LeoGM.members do
-        if LeoGM.members[i].name == memberName then
-            LeoGM.members[i].deposits = LeoGM.members[i].deposits + value
-            return
+function LeoGuildManager.IsEndangered(memberName, value)
+    for i = 1, #LeoGuildManager.members do
+        if LeoGuildManager.members[i].name == memberName then
+            return LeoGuildManager.members[i].endangered
         end
     end
 end

-function LeoGM.AddTax(memberName, value)
-    for i = 1, #LeoGM.members do
-        if LeoGM.members[i].name == memberName then
-            LeoGM.members[i].taxes = LeoGM.members[i].taxes + value
+function LeoGuildManager.RemoveMember(memberName)
+    for i = 1, #LeoGuildManager.members do
+        if LeoGuildManager.members[i].name == memberName then
+            table.remove(LeoGuildManager.members, i)
             return
         end
     end
 end

-function LeoGM.IsNewestFirst(guildID)
-    local numEvents = GetNumGuildEvents(guildID, GUILD_HISTORY_STORE)
-    local _, secsSinceFirst, _, _, _, _, _, _ = GetGuildEventInfo(guildID, GUILD_HISTORY_STORE, 1)
-    local _, secsSinceLast, _, _, _, _, _, _ = GetGuildEventInfo(guildID, GUILD_HISTORY_STORE, numEvents)
-    return (secsSinceFirst < secsSinceLast)
+function LeoGuildManager.UpdateMemberRank(guildId, memberName)
+    for i = 1, #LeoGuildManager.members do
+        if LeoGuildManager.members[i].name == memberName then
+            local _, _, rankIndex = GetGuildMemberInfo(guildId, LeoGuildManager.members[i].memberIndex)
+            local rankId = GetGuildRankId(guildId, rankIndex)
+            local rankName = LeoGuildManager.GetGuildRankName(guildId, rankIndex)
+            LeoGuildManager.members[i].rankIndex = rankIndex
+            LeoGuildManager.members[i].rankId = rankId
+            LeoGuildManager.members[i].rankName = rankName
+            return
+        end
+    end
 end

-function LeoGM.BuildSalesHistory(guildId, scanSince)
-
-    if LeoGM.isScanning then return nil end
-
-    LeoGMWindowPurgePanelListResult:SetText("Scanning guild sales ... this can take a few minutes")
+function LeoGuildManager.SetEndangered(memberName, value)
+    for i = 1, #LeoGuildManager.members do
+        if LeoGuildManager.members[i].name == memberName then
+            LeoGuildManager.members[i].endangered = value
+            return
+        end
+    end
+end

-    LeoGMWindowPurgePanelListButton:SetEnabled(false)
-    LeoGMWindowPurgePanelLoadingIcon:SetHidden(false)
-    LeoGMWindowPurgePanelLoadingIcon.animation:PlayForward()
+function LeoGuildManager.AddSale(memberName, value)
+    for i = 1, #LeoGuildManager.members do
+        if LeoGuildManager.members[i].name == memberName then
+            LeoGuildManager.members[i].sales = LeoGuildManager.members[i].sales + value
+            return
+        end
+    end
+end

-    LeoGM.requestTimestamp = GetTimeStamp()
-    RequestGuildHistoryCategoryNewest(guildId, GUILD_HISTORY_STORE)
-    zo_callLater(function() LeoGM.ScanOlderSales(guildId, scanSince) end, 2000)
+function LeoGuildManager.AddDeposit(memberName, value)
+    for i = 1, #LeoGuildManager.members do
+        if LeoGuildManager.members[i].name == memberName then
+            LeoGuildManager.members[i].deposits = LeoGuildManager.members[i].deposits + value
+            return
+        end
+    end
 end

-function LeoGM.ProcessSales(guildNum, lastSaleTime, startIndex, endIndex, loopIncrement)
+function LeoGuildManager.CreatePurgeDescription(guildName)
+    if LeoGuildManager.globalData.selectedGuild == nil or LeoGuildManager.globalData.selectedGuild == "" then
+        return
+    end

-    local addedEvents = 0
-    local guildID = GetGuildId(guildNum)
-    local guildName = GetGuildName(guildID)
+    local cycle = LeoGuildManager.GetCycleName(LeoGuildManager.globalData.settings.guilds[guildName].cycle)

-    local guildMemberInfo = {}
-    for i = 1, GetNumGuildMembers(guildID) do
-        local guildMemInfo, _, _, _, _ = GetGuildMemberInfo(guildID, i)
-        guildMemberInfo[string.lower(guildMemInfo)] = true
-    end
+    local tickets = LeoGuildManager.globalData.settings.guilds[guildName].tickets
+    local sales = LeoGuildManager.globalData.settings.guilds[guildName].sales
+    local inactivity = LeoGuildManager.globalData.settings.guilds[guildName].inactivity
+    local ignoreRank = LeoGuildManager.globalData.settings.guilds[guildName].ignoreRank
+    local ignoreNew = LeoGuildManager.globalData.settings.guilds[guildName].ignoreNew

-    for i = startIndex, endIndex, loopIncrement do
-        local theEvent = {}
-        theEvent.eventType, theEvent.secsSince, theEvent.seller, theEvent.buyer,
-        theEvent.quant, theEvent.itemName, theEvent.salePrice = GetGuildEventInfo(guildID, GUILD_HISTORY_STORE, i)
-        theEvent.guild = guildName
-        theEvent.saleTime = GetTimeStamp() - theEvent.secsSince
+    local descPurge = ""

-        if theEvent.eventType == GUILD_EVENT_ITEM_SOLD then
+    if tickets > 0 and sales > 0 then
+        descPurge = cycle .. ", members were required to buy at least " .. tickets .. "k in raffle tickets OR have " .. sales .. "k sales."
+    elseif tickets > 0 then
+        descPurge = cycle .. ", members were required to buy at least " .. tickets .. "k in raffle tickets."
+    elseif sales > 0 then
+        descPurge = cycle .. ", members were required to have at least " .. sales .. "k sales."
+    end

-            theEvent.kioskSale = (guildMemberInfo[string.lower(theEvent.buyer)] == nil)
-            theEvent.id = Id64ToString(GetGuildEventId(guildID, GUILD_HISTORY_STORE, i))
+    if inactivity > 0 then
+        descPurge = descPurge .. "\r\nInactivity policy of " .. inactivity .." days."
+    end

-            addedEvents = addedEvents + 1
-            table.insert(LeoGM.salesEvents, theEvent)
+    if ignoreRank > 0 then
+        local guildId = LeoGuildManager.GetGuildId(guildName)
+        local rankName = LeoGuildManager.GetGuildRankName(guildId, LeoGuildManager.globalData.settings.guilds[guildName].ignoreRank)
+        descPurge = descPurge .. "\r\nIgnore members with rank equal or above " .. rankName .. "."
+    end
+    descPurge = descPurge .. "\r\nIgnore new members who joined the guild in less than " .. LeoGuildManager.GetNewRangeName(ignoreNew) .. "."
+    return descPurge
+end

-            if addedEvents > 100 then
-                startIndex = i + loopIncrement
+local function normalizeGuilds()
+    for name, data in pairs(LeoGuildManager.globalData.settings.guilds) do
+        local deleted = true
+        for guildId, guildName in pairs(LeoGuildManager.guilds) do
+            if name == guildName then
+                deleted = false
                 break
             end
         end
+        if deleted then
+            LeoGuildManager.log("Not part of guild " .. name .." anymore. Deleting ...")
+            LeoGuildManager.globalData.settings.guilds[name] = nil
+        end
     end

-    if addedEvents <= 100 then
-        startIndex = 0
-        endIndex = 0
-        loopIncrement = 0
+    for guildId, guildName in pairs(LeoGuildManager.guilds) do
+        local found = false
+        for name, data in pairs(LeoGuildManager.globalData.settings.guilds) do
+            if name == guildName then
+                found = true
+                break
+            end
+        end
+        if not found then
+            LeoGuildManager.log("New guild " .. guildName .." found. Initializing ...")
+            LeoGuildManager.globalData.settings.guilds[guildName] = {
+                id = guildId,
+                enabled = false,
+                cycle = 2,
+                ignoreRank = 0,
+                ignoreNew = 1,
+                tickets = 0,
+                sales = 0,
+                inactivity = 30,
+                blacklist = {}
+            }
+        end
     end
-
-    return startIndex, endIndex, loopIncrement
-
 end

-function LeoGM.DoSalesScan(guildNum, checkOlder, scanSince, startIndex, endIndex, loopIncrement)
+function LeoGuildManager.Initialize()
+    LeoGuildManager.globalData = ZO_SavedVars:NewAccountWide("LeoGuildManagerGlobalData", 2, nil, nil, GetWorldName())
+    LeoGuildManager.scanData = ZO_SavedVars:NewAccountWide("LeoGuildManagerScanData", 2, nil, nil, GetWorldName())
+    if not LeoGuildManager.globalData.settings or LeoGuildManager.globalData.settings == nil then
+        LeoGuildManager.globalData.settings = {
+            integration = LeoGuildManager.integrations[1],
+            tooltipRoster = false,
+            scanAutomatically = false,
+            guilds = {},
+        }
+    end
+    if not LeoGuildManager.scanData then
+        LeoGuildManager.scanData = {}
+    end

-    local guildID = GetGuildId(guildNum)
-    local numEvents = GetNumGuildEvents(guildID, GUILD_HISTORY_STORE)
-    local guildName = GetGuildName(guildID)
+    local LibFeedback = LibStub:GetLibrary("LibFeedback")
+    local showButton, feedbackWindow = LibFeedback:initializeFeedbackWindow(LeoGuildManager,
+        LeoGuildManager.name,LeoGuildManagerWindow, "@LeandroSilva",
+        {TOPRIGHT, LeoGuildManagerWindow, TOPRIGHT,-50,3},
+        {0,1000,10000,"https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=Y9KM4PZU2UZ6A"},
+        "If you found a bug, have a request or a suggestion, or simply wish to donate, send a mail.")
+    LeoGuildManager.feedback = feedbackWindow
+    LeoGuildManager.feedback:SetDrawLayer(DL_OVERLAY)
+    LeoGuildManager.feedback:SetDrawTier(DT_LOW)

-    if loopIncrement ~= nil and loopIncrement ~= 0 then
-        startIndex, endIndex, loopIncrement = LeoGM.ProcessSales(guildNum, scanSince, startIndex, endIndex, loopIncrement)
-    else
-        local prevEvents = 0
+    LeoGuildManager.GetGuilds()
+    normalizeGuilds()

-        if LeoGM.numEvents[guildName] ~= nil then prevEvents = LeoGM.numEvents[guildName] end
+    LeoGuildManagerUI.InitializeUI()
+    LeoGuildManagerUI.RestorePosition()

-        if numEvents > prevEvents then
+    LeoGuildManager.settings = LeoGuildManagerSettings:New()
+    LeoGuildManager.settings:CreatePanel()

-            startIndex = prevEvents + 1
-            endIndex = numEvents
-            loopIncrement = 1
+    SLASH_COMMANDS["/leogm"] = function(cmd)
+        LeoGuildManagerUI:ToggleUI()
+    end
+end

-            if LeoGM.IsNewestFirst(guildID) then
-                startIndex = numEvents - prevEvents
-                endIndex = 1
-                loopIncrement = -1
+function LeoGuildManager.AutoKick(kick)
+    for _, guildName in pairs(LeoGuildManager.guilds) do
+        local guildId = LeoGuildManager.GetGuildId(guildName)
+        if DoesPlayerHaveGuildPermission(guildId, GUILD_PERMISSION_REMOVE) and LeoGuildManager.globalData.settings.guilds[guildName].blacklist ~= nil then
+            for _, userId in pairs(LeoGuildManager.globalData.settings.guilds[guildName].blacklist) do
+                local numGuildMembers = GetNumGuildMembers(guildId)
+                for guildMemberIndex = 1, numGuildMembers do
+                    local displayName = GetGuildMemberInfo(guildId, guildMemberIndex)
+                    if displayName == userId then
+                        if kick == true then
+                            d("Found " .. userId .." on " .. guildName .. ". Kicking ...")
+                            GuildRemove(guildId, userId)
+                            d("Bye bye!")
+                        else
+                            d("Found " .. userId .." on " .. guildName)
+                        end
+                        break
+                    end
+                end
             end
-
-            startIndex, endIndex, loopIncrement = LeoGM.ProcessSales(guildNum, scanSince, startIndex, endIndex, loopIncrement)
-        else
-            loopIncrement = 0;
         end
     end
+end

-    if loopIncrement ~= 0 then
-        zo_callLater(function() LeoGM.DoSalesScan(guildNum, checkOlder, scanSince, startIndex, endIndex, loopIncrement) end, 50)
-        return
+local orig_ZO_KeyboardGuildRosterRowDisplayName_OnMouseEnter = ZO_KeyboardGuildRosterRowDisplayName_OnMouseEnter
+local orig_ZO_KeyboardGuildRosterRowDisplayName_OnMouseExit = ZO_KeyboardGuildRosterRowDisplayName_OnMouseExit
+
+function LeoGuildManager.FormatTimeAgo(seconds)
+    local ago
+    if seconds < 3600 then
+        ago = ZO_CachedStrFormat(GetString(SI_TIME_FORMAT_MINUTES), math.floor(seconds / 60))
+    elseif seconds < 86400 then
+        ago = ZO_CachedStrFormat(GetString(SI_TIME_FORMAT_HOURS), math.floor(seconds / 3600))
+    else
+        ago = ZO_CachedStrFormat(GetString(SI_TIME_FORMAT_DAYS), math.floor(seconds / 86400))
     end
+    return ZO_CachedStrFormat(GetString(SI_TIME_DURATION_AGO), ago)
+end

-    LeoGM.numEvents[guildName] = numEvents
+function ZO_KeyboardGuildRosterRowDisplayName_OnMouseEnter(control)
+    orig_ZO_KeyboardGuildRosterRowDisplayName_OnMouseEnter(control)

-    d('|cFFFF00Finished adding ' .. numEvents .. ' sales events from ' .. guildName .. '.|r')
+    if not LeoGuildManager.globalData.settings.tooltipRoster then return end

-    LeoGM.isScanning = false
-    LeoGMWindowPurgePanelListButton:SetEnabled(true)
-    LeoGMWindowPurgePanelLoadingIcon:SetHidden(true)
-    LeoGMWindowPurgePanelLoadingIcon.animation:Stop()
-    LeoGM:PostScan()
-end
+    local parent = control:GetParent()
+    local data = ZO_ScrollList_GetData(parent)
+    local guildName = GetGuildName(GUILD_SELECTOR.guildId)
+    local displayName = data.displayName
+    local timeStamp = GetTimeStamp()

-function LeoGM.ScanOlderSales(guildNum, scanSince, oldNumEvents, badLoads)
-    local guildID = GetGuildId(guildNum)
-    local numEvents = GetNumGuildEvents(guildID, GUILD_HISTORY_STORE)
-    local _, secsSinceFirst, _, _, _, _, _, _ = GetGuildEventInfo(guildID, GUILD_HISTORY_STORE, 1)
-    local _, secsSinceLast, _, _, _, _, _, _ = GetGuildEventInfo(guildID, GUILD_HISTORY_STORE, numEvents)
-    local timeToUse = GetTimeStamp() - math.max(secsSinceFirst, secsSinceLast)
+    local tooltip = data.characterName
+    local num, str

-    badLoads = badLoads or 0
-    oldNumEvents = oldNumEvents or 0
+    if (LeoGuildManager.scanData[guildName] ~= nil) then
+        if (LeoGuildManager.scanData[guildName].members[displayName] ~= nil) then
+            tooltip = tooltip .. "\n\n"

-    if numEvents == 0 then
-        return nil
+            if (LeoGuildManager.scanData[guildName].members[displayName].joined == 0) then
+                str = LeoGuildManager.FormatTimeAgo(timeStamp - LeoGuildManager.scanData[guildName][GUILD_HISTORY_GENERAL].firstEvent)
+                tooltip = tooltip .. "Joined > |cffffff" .. str .. "|r\n"
+            else
+                str = LeoGuildManager.FormatTimeAgo(timeStamp - LeoGuildManager.scanData[guildName].members[displayName].joined)
+                tooltip = tooltip .. "Joined |cffffff" .. str .. "|r\n"
+            end
+            local invitedBy = LeoGuildManager.scanData[guildName].members[displayName].invitedBy
+            if (invitedBy ~= "") then
+                tooltip = tooltip .. "Invited by |cffffff" .. invitedBy .. "|r\n"
+            end
+        end
     end
-    if numEvents > oldNumEvents then badLoads = 0 else badLoads = badLoads + 1 end

-    if DoesGuildHistoryCategoryHaveMoreEvents(guildID, GUILD_HISTORY_STORE) and badLoads < 10 and timeToUse > scanSince then
-        RequestGuildHistoryCategoryOlder(guildID, GUILD_HISTORY_STORE)
-        zo_callLater(function() LeoGM.ScanOlderSales(guildNum, scanSince, numEvents, badLoads) end, 1500)
-    else zo_callLater(function() LeoGM.DoSalesScan(guildNum, true, scanSince) end, 1500) end
+    InitializeTooltip(InformationTooltip, control, BOTTOM, 0, 0, TOPCENTER)
+    SetTooltipText(InformationTooltip, tooltip)
 end

-function LeoGM.BuildDepositsHistory(guildId, scanSince)
-
-    if LeoGM.isScanning then return nil end
+function ZO_KeyboardGuildRosterRowDisplayName_OnMouseExit(control)
+    ClearTooltip(InformationTooltip)

-    LeoGMWindowPurgePanelListResult:SetText("Scanning gold deposits ...")
+    orig_ZO_KeyboardGuildRosterRowDisplayName_OnMouseExit(control)
+end

-    LeoGMWindowPurgePanelListButton:SetEnabled(false)
-    LeoGMWindowPurgePanelLoadingIcon:SetHidden(false)
-    LeoGMWindowPurgePanelLoadingIcon.animation:PlayForward()
+local function initGuildScanData(guildName)
+    if LeoGuildManager.scanData[guildName] == nil then
+        LeoGuildManager.scanData[guildName] = {
+            [GUILD_HISTORY_GENERAL] = {
+                firstEvent = 0,
+                lastEvent = 0,
+                events = {}
+            },
+            [GUILD_HISTORY_BANK] = {
+                firstEvent = 0,
+                lastEvent = 0,
+                events = {}
+            },
+            members = {}
+        }
+    end
+end

-    --if #LeoGM.depositsEvents == 0 then
-    LeoGM.requestTimestamp = GetTimeStamp()
-    RequestGuildHistoryCategoryNewest(guildId, GUILD_HISTORY_BANK)
-    zo_callLater(function() LeoGM.ScanOlderDeposits(guildId, scanSince) end, 2000)
-    --[[else
-        LeoGM:PostScan()
-        LeoGMWindowPurgePanelListButton:SetEnabled(true)
-        LeoGMWindowPurgePanelLoadingIcon:SetHidden(true)
-        LeoGMWindowPurgePanelLoadingIcon.animation:Stop()
-    end]]
+local function initMemberScanData(guildName, displayName)
+    if LeoGuildManager.scanData[guildName].members[displayName] == nil then
+        LeoGuildManager.scanData[guildName].members[displayName] = {
+            joined = 0,
+            invited = 0,
+            invitedBy = ""
+        }
+    end
 end

-function LeoGM.ProcessDeposits(guildNum, lastSaleTime, startIndex, endIndex, loopIncrement)
+function LeoGuildManager.ProcessEvent(guildId, category, eventIndex)

-    local addedEvents = 0
-    local guildID = GetGuildId(guildNum)
-    local guildName = GetGuildName(guildID)
+    local guildName = GetGuildName(guildId)

-    local guildMemberInfo = {}
-    for i = 1, GetNumGuildMembers(guildID) do
-        local guildMemInfo, _, _, _, _ = GetGuildMemberInfo(guildID, i)
-        guildMemberInfo[string.lower(guildMemInfo)] = true
-    end
+    local eventId = GetGuildEventId(guildId, category, eventIndex)
+    local eventIdNum = tonumber(Id64ToString(eventId))

-    for i = startIndex, endIndex, loopIncrement do
-        local theEvent = {}
-        theEvent.eventType, theEvent.secsSince, theEvent.seller, theEvent.value = GetGuildEventInfo(guildID, GUILD_HISTORY_BANK, i)
-        theEvent.guild = guildName
-        theEvent.saleTime = GetTimeStamp() - theEvent.secsSince
+    local evType, evTime, param1, param2 = GetGuildEventInfo(guildId, category, eventIndex)
+    local displayName = param1
+    local timeStamp = GetTimeStamp() - evTime

-        if theEvent.eventType == GUILD_EVENT_BANKGOLD_ADDED then
+    if timeStamp < 0 or timeStamp < 31 * LeoGuildManager.SECONDS_IN_HOUR then
+        eventTooOld[guildId] = true
+        return
+    end
+    eventTooOld[guildId] = false

-            theEvent.id = Id64ToString(GetGuildEventId(guildID, GUILD_HISTORY_BANK, i))
+    if LeoGuildManager.scanData[guildName][category].lastEvent == 0 or
+            LeoGuildManager.scanData[guildName][category].lastEvent < timeStamp then
+        LeoGuildManager.scanData[guildName][category].lastEvent = timeStamp
+    end
+    if LeoGuildManager.scanData[guildName][category].firstEvent == 0 or
+            LeoGuildManager.scanData[guildName][category].firstEvent > timeStamp then
+        LeoGuildManager.scanData[guildName][category].firstEvent = timeStamp
+    end

-            addedEvents = addedEvents + 1
-            table.insert(LeoGM.depositsEvents, theEvent)
+    initMemberScanData(guildName, displayName)
+    if (LeoGuildManager.scanData[guildName].members[displayName] ~= nil) then
+        if (category == GUILD_HISTORY_GENERAL) then
+            if (evType == GUILD_EVENT_GUILD_JOIN) then
+                LeoGuildManager.scanData[guildName].members[displayName].joined = timeStamp
+            elseif (evType == GUILD_EVENT_GUILD_INVITE) then
+                initMemberScanData(guildName, param2)
+                LeoGuildManager.scanData[guildName].members[param2].invited = timeStamp
+                LeoGuildManager.scanData[guildName].members[param2].invitedBy = param1
+            end
+        end

-            if addedEvents > 100 then
-                startIndex = i + loopIncrement
-                break
+        if category == GUILD_HISTORY_BANK and (evType == GUILD_EVENT_BANKGOLD_ADDED or evType == GUILD_EVENT_BANKGOLD_REMOVED) and
+                eventIdNum ~= 0 then
+            if LeoGuildManager.scanData[guildName][category].events[eventIdNum] == nil then
+                LeoGuildManager.scanData[guildName][category].events[eventIdNum] = {
+                    type = evType,
+                    timeStamp = timeStamp,
+                    member = displayName,
+                    gold = param2
+                }
             end
         end
     end
+end

-    if addedEvents <= 100 then
-        startIndex = 0
-        endIndex = 0
-        loopIncrement = 0
+local function onGuildHistoryResponseReceived(eventCode, guildId, category)
+    if (category ~= GUILD_HISTORY_GENERAL) and (category ~= GUILD_HISTORY_BANK) then
+        return
     end

-    return startIndex, endIndex, loopIncrement
-
+    local numEvents = GetNumGuildEvents(guildId, category)
+    for i = 1, numEvents do
+        LeoGuildManager.ProcessEvent(guildId, category, i)
+    end
 end

-function LeoGM.DoDepositsScan(guildNum, checkOlder, scanSince, startIndex, endIndex, loopIncrement)
+local function RequestGuildHistoryCategoryOlderLocal(guildIndex, category, numGuilds)

-    local guildID = GetGuildId(guildNum)
-    local numEvents = GetNumGuildEvents(guildID, GUILD_HISTORY_BANK)
-    local guildName = GetGuildName(guildID)
+    local guildId = GetGuildId(guildIndex)

-    if loopIncrement ~= nil and loopIncrement ~= 0 then
-        startIndex, endIndex, loopIncrement = LeoGM.ProcessDeposits(guildNum, scanSince, startIndex, endIndex, loopIncrement)
-    else
-        local prevEvents = 0
-
-        if LeoGM.numEvents[guildName] ~= nil then prevEvents = LeoGM.numEvents[guildName] end
+    if eventTooOld[guildId] ~= nil and eventTooOld[guildId] == true then
+        if (guildIndex < numGuilds) then
+            LeoGuildManager.nextGuildScan = guildIndex + 1
+        else
+            LeoGuildManager.nextGuildScan = 1
+        end
+        LeoGuildManager.log("Events too old")
+        return false
+    end

-        if numEvents > prevEvents then
+    if (RequestGuildHistoryCategoryOlder(guildId, category)) then
+        if (guildIndex < numGuilds) then
+            LeoGuildManager.nextGuildScan = guildIndex + 1
+        else
+            LeoGuildManager.nextGuildScan = 1
+        end
+        return true
+    end

-            startIndex = prevEvents + 1
-            endIndex = numEvents
-            loopIncrement = 1
+    return false
+end

-            if LeoGM.IsNewestFirst(guildID) then
-                startIndex = numEvents - prevEvents
-                endIndex = 1
-                loopIncrement = -1
-            end
+local function canScanBankHistory(guildId)
+    local guildName = GetGuildName(guildId)
+    return LeoGuildManager.globalData.settings.guilds[guildName].enabled and
+        DoesPlayerHaveGuildPermission(guildId, GUILD_PERMISSION_BANK_VIEW_GOLD)
+end

-            startIndex, endIndex, loopIncrement = LeoGM.ProcessDeposits(guildNum, scanSince, startIndex, endIndex, loopIncrement)
-        else
-            loopIncrement = 0;
+local manualScan = false
+function LeoGuildManager.StartScanGuilds(manual)
+    if LeoGuildManager.isScanning == true then
+        if manual == true then
+            LeoGuildManager.log("A scan is already running ...")
         end
+        return
     end

-    if loopIncrement ~= 0 then
-        zo_callLater(function() LeoGM.DoDepositsScan(guildNum, checkOlder, scanSince, startIndex, endIndex, loopIncrement) end, 50)
+    manualScan = false
+    if manual == true then
+        manualScan = true
+        LeoGuildManager.log("Starting guild scan ...")
+    elseif LeoGuildManager.globalData.settings.scanAutomatically == false then
+        zo_callLater(function() LeoGuildManager.StartScanGuilds() end, 5000)
         return
     end

-    LeoGM.numEvents[guildName] = numEvents
+    LeoGuildManager.isScanning = true

-    d('|cFFFF00Finished adding ' .. numEvents .. ' deposit events from ' .. guildName .. '.|r')
+    LeoGuildManagerWindowPurgePanelScanButton:SetEnabled(false)
+    LeoGuildManagerWindowPurgePanelLoadingIcon:SetHidden(false)
+    LeoGuildManagerWindowPurgePanelLoadingIcon.animation:PlayForward()

-    LeoGM.isScanning = false
-    LeoGMWindowPurgePanelListButton:SetEnabled(true)
-    LeoGMWindowPurgePanelLoadingIcon:SetHidden(true)
-    LeoGMWindowPurgePanelLoadingIcon.animation:Stop()
-    LeoGM.PostScan()
-    --LeoGM.BuildSalesHistory(guildNum, scanSince)
+    LeoGuildManager.ScanGuilds()
 end

-function LeoGM.ScanOlderDeposits(guildNum, scanSince, oldNumEvents, badLoads)
-    local guildID = GetGuildId(guildNum)
-    local numEvents = GetNumGuildEvents(guildID, GUILD_HISTORY_BANK)
-    local _, secsSinceFirst, _, _, _, _, _, _ = GetGuildEventInfo(guildID, GUILD_HISTORY_BANK, 1)
-    local _, secsSinceLast, _, _, _, _, _, _ = GetGuildEventInfo(guildID, GUILD_HISTORY_BANK, numEvents)
-    local timeToUse = GetTimeStamp() - math.max(secsSinceFirst, secsSinceLast)
+function LeoGuildManager.FinishScanGuilds()

-    badLoads = badLoads or 0
-    oldNumEvents = oldNumEvents or 0
+    LeoGuildManager.isScanning = false

-    if numEvents == 0 then
-        return nil
-    end
-    if numEvents > oldNumEvents then badLoads = 0 else badLoads = badLoads + 1 end
+    LeoGuildManagerWindowPurgePanelScanButton:SetEnabled(true)
+    LeoGuildManagerWindowPurgePanelLoadingIcon:SetHidden(true)
+    LeoGuildManagerWindowPurgePanelLoadingIcon.animation:Stop()

-    if DoesGuildHistoryCategoryHaveMoreEvents(guildID, GUILD_HISTORY_BANK) and badLoads < 10 and timeToUse > scanSince then
-        RequestGuildHistoryCategoryOlder(guildID, GUILD_HISTORY_BANK)
-        zo_callLater(function() LeoGM.ScanOlderDeposits(guildNum, scanSince, numEvents, badLoads) end, 1500)
-    else zo_callLater(function() LeoGM.DoDepositsScan(guildNum, true, scanSince) end, 1500) end
+    if manualScan == true then
+        manualScan = false
+        LeoGuildManager.log("Scan finished for all guilds.")
+    else
+        zo_callLater(LeoGuildManager.ScanGuilds, scanInterval)
+    end
 end

-function LeoGM.PostScan()
-    for _, event in pairs(LeoGM.depositsEvents) do
-        for _, member in pairs(LeoGM.members) do
-            if event.seller == member.name then
-                member.deposits = member.deposits + event.value
-                break
+function LeoGuildManager.ScanGuilds()
+    if not LeoGuildManager.inCombat then
+
+        local numGuilds = GetNumGuilds()
+        local guildId
+
+        for i = 1, numGuilds do
+            guildId = GetGuildId(i)
+
+            local guildName = GetGuildName(guildId)
+            if LeoGuildManager.globalData.settings.guilds[guildName].enabled then
+                initGuildScanData(guildName)
+                if (RequestGuildHistoryCategoryNewest(guildId, GUILD_HISTORY_GENERAL)) then
+
+                    zo_callLater(LeoGuildManager.ScanGuilds, shortScanInterval)
+                    return
+                end
+                if (canScanBankHistory(guildId) and RequestGuildHistoryCategoryNewest(guildId, GUILD_HISTORY_BANK)) then
+                    --LeoGuildManager.log("Starting scan for new bank events on " .. guildName .. " ...")
+                    zo_callLater(LeoGuildManager.ScanGuilds, shortScanInterval)
+                    return
+                end
+
             end
         end
-    end

-    local collected = false
-    if MasterMerchant and MasterMerchant.guildSales[LeoGM.savedVariables.selectedGuild] and
-            MasterMerchant.guildSales[LeoGM.savedVariables.selectedGuild].sellers
-    then
-        for sn, sellerData in pairs(MasterMerchant.guildSales[LeoGM.savedVariables.selectedGuild].sellers) do
-            for _, member in pairs(LeoGM.members) do
-                if sn == member.name and sellerData.sales[8] then
-                    member.sales = member.sales + sellerData.sales[8]
-                    collected = true
+        for i = LeoGuildManager.nextGuildScan, numGuilds do
+            guildId = GetGuildId(i)
+            local guildName = GetGuildName(guildId)
+            if LeoGuildManager.globalData.settings.guilds[guildName].enabled then
+                if (RequestGuildHistoryCategoryOlderLocal(i, GUILD_HISTORY_GENERAL, numGuilds)) then
+                    --LeoGuildManager.log("Starting scan for old general events on " .. guildName .. " ...")
+                    zo_callLater(LeoGuildManager.ScanGuilds, shortScanInterval)
+                    return
+                end
+                if (canScanBankHistory(guildId) and RequestGuildHistoryCategoryOlderLocal(i, GUILD_HISTORY_BANK, numGuilds)) then
+                    --LeoGuildManager.log("Starting scan for old bank events on " .. guildName .. " ...")
+                    zo_callLater(LeoGuildManager.ScanGuilds, shortScanInterval)
+                    return
                 end
             end
         end
-    end
-    if collected == false then
-        LeoGMWindowPurgePanelListResult:SetText("|cFF2222MasterMerchant sales data not found for this guild.|r Try again in a few moments if it is still initializing.")
-        return
-    end
-    --[[
-    for _, event in pairs(LeoGM.salesEvents) do
-        for _, member in pairs(LeoGM.members) do
-            if event.seller == member.name then
-                member.sales = member.sales + event.salePrice
-                break
+
+        for i = 1, LeoGuildManager.nextGuildScan - 1 do
+            guildId = GetGuildId(i)
+            local guildName = GetGuildName(guildId)
+            if LeoGuildManager.globalData.settings.guilds[guildName].enabled then
+                if (RequestGuildHistoryCategoryOlderLocal(i, GUILD_HISTORY_GENERAL, numGuilds)) then
+                    --LeoGuildManager.log("Starting scan for old general2 events on " .. guildName .. " ...")
+                    zo_callLater(LeoGuildManager.ScanGuilds, shortScanInterval)
+                    return
+                end
+                if (canScanBankHistory(guildId) and RequestGuildHistoryCategoryOlderLocal(i, GUILD_HISTORY_BANK, numGuilds)) then
+                    --LeoGuildManager.log("Starting scan for old bank2 events on " .. guildName .. " ...")
+                    zo_callLater(LeoGuildManager.ScanGuilds, shortScanInterval)
+                    return
+                end
             end
         end
-    end
-    ]]
-    LeoGM.memberScroll:RefreshData()
-end

-function LeoGM.Initialize()
-    LeoGM.savedVariables = ZO_SavedVars:NewAccountWide("LeoGMSavedVariables", 1)
+        LeoGuildManager.nextGuildScan = 1

-    if not LeoGM.savedVariables.rules then
-        LeoGM.savedVariables.rules = {}
+        LeoGuildManager.FinishScanGuilds()
+    else
+        zo_callLater(LeoGuildManager.ScanGuilds, shortScanInterval)
     end
+end

-    local LibFeedback = LibStub:GetLibrary("LibFeedback")
-    local showButton, feedbackWindow = LibFeedback:initializeFeedbackWindow(LeoGM,
-        LeoGM.name,LeoGMWindow, "@LeandroSilva",
-        {TOPRIGHT, LeoGMWindow, TOPRIGHT,-50,3},
-        {0,1000,10000,"https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=Y9KM4PZU2UZ6A"},
-        "If you found a bug, have a request or a suggestion, or simply wish to donate, send a mail.")
-    LeoGM.feedback = feedbackWindow
-
-    LeoGM.guilds = LeoGM.GetGuilds()
+function LeoGuildManager:OnUpdate()
+    LeoGuildManager.AutoKick(true)
+end

-    LeoGM.InitializeUI()
-    LeoGM.RestorePosition()
+local function onSettingsControlsCreated(panel)
+    LeoGuildManagerSettings:OnSettingsControlsCreated(panel)
+end

-    --[[
-    SLASH_COMMANDS["/rr"] = function(cmd)
-        ReloadUI()
-    end
-    ]]
+local function onNewMovementInUIMode(eventCode)
+    if not LeoGuildManagerWindow:IsHidden() then LeoGuildManagerUI:CloseUI() end
+end

-    SLASH_COMMANDS["/leogm"] = function(cmd)
-        LeoGM:ShowUI()
+local function onChampionPerksSceneStateChange(oldState,newState)
+    if newState == SCENE_SHOWING then
+        if not LeoGuildManagerWindow:IsHidden() then LeoGuildManagerUI:CloseUI() end
     end
 end

-function LeoGM.AutoKick(kick)
-    for _, guildName in pairs(LeoGM.guilds) do
-        local guildId = LeoGM.GetGuilds(guildName)
-        if DoesPlayerHaveGuildPermission(guildId, GUILD_PERMISSION_REMOVE) and LeoGM.savedVariables.rules[guildName].blacklist ~= nil then
-            for _, userId in pairs(LeoGM.savedVariables.rules[guildName].blacklist) do
-                local numGuildMembers = GetNumGuildMembers(guildId)
-                for guildMemberIndex = 1, numGuildMembers do
-                    local displayName = GetGuildMemberInfo(guildId, guildMemberIndex)
-                    if displayName == userId then
-                        if kick == true then
-                            d("Found " .. userId .." on " .. guildName .. ". Kicking ...")
-                            GuildRemove(guildId, userId)
-                            d("Bye bye!")
-                        else
-                            d("Found " .. userId .." on " .. guildName)
-                        end
-                        break
-                    end
-                end
-            end
-        end
-    end
+local function onCombatState(eventCode, inCombat)
+    LeoGuildManager.inCombat = inCombat
 end

-function LeoGM:OnUpdate()
-    LeoGM.AutoKick(true)
+local function onPlayerActivated(eventCode)
+    EVENT_MANAGER:UnregisterForEvent(LeoGuildManager.name, eventCode)
+    zo_callLater(function() LeoGuildManager.StartScanGuilds() end, 5000)
 end

-function LeoGM.OnAddOnLoaded(event, addonName)
-    if addonName == LeoGM.name then
-        EVENT_MANAGER:UnregisterForEvent(LeoGM.Name, EVENT_ADD_ON_LOADED)
-        LeoGM.Initialize()
-        --d(LeoGM.name .. " started.")
+function LeoGuildManager.OnAddOnLoaded(event, addonName)
+    if addonName == LeoGuildManager.name then
+        EVENT_MANAGER:UnregisterForEvent(LeoGuildManager.Name, EVENT_ADD_ON_LOADED)
+        SCENE_MANAGER:RegisterTopLevel(LeoGuildManagerWindow, false)
+
+        LeoGuildManager.Initialize()
+
+        EVENT_MANAGER:RegisterForUpdate(LeoGuildManager.name, 60000, function() LeoGuildManager.OnUpdate() end)
+        EVENT_MANAGER:RegisterForEvent(LeoGuildManager.name, EVENT_PLAYER_ACTIVATED, onPlayerActivated)
+        CALLBACK_MANAGER:RegisterCallback("LAM-PanelControlsCreated", onSettingsControlsCreated)
+        EVENT_MANAGER:RegisterForEvent(LeoGuildManager.name, EVENT_NEW_MOVEMENT_IN_UI_MODE, onNewMovementInUIMode)
+        CHAMPION_PERKS_SCENE:RegisterCallback('StateChange', onChampionPerksSceneStateChange)
+        EVENT_MANAGER:RegisterForEvent(LeoGuildManager.name, EVENT_PLAYER_COMBAT_STATE, onCombatState)
+        EVENT_MANAGER:RegisterForEvent(LeoGuildManager.name, EVENT_GUILD_HISTORY_RESPONSE_RECEIVED, onGuildHistoryResponseReceived)
+
+        LeoGuildManager.log("started.")
     end
 end

-EVENT_MANAGER:RegisterForEvent(LeoGM.name, EVENT_ADD_ON_LOADED, LeoGM.OnAddOnLoaded)
-EVENT_MANAGER:RegisterForUpdate(LeoGM.name, 60000, function() LeoGM.OnUpdate() end)
+EVENT_MANAGER:RegisterForEvent(LeoGuildManager.name, EVENT_ADD_ON_LOADED, LeoGuildManager.OnAddOnLoaded)
diff --git a/LeoGuildManager.txt b/LeoGuildManager.txt
index e5bd7e8..8857170 100644
--- a/LeoGuildManager.txt
+++ b/LeoGuildManager.txt
@@ -1,11 +1,32 @@
 ## Title: Leo's Guild Manager
 ## APIVersion: 100024 100025
-## Version: 1.0.1
+## Version: 1.1.0
+## AddOnVersion: 110
 ## Author: |c39B027@LeandroSilva|r
-## SavedVariables: LeoGMSavedVariables
-## DependsOn: LibStub LibFeedback MasterMerchant
+## SavedVariables: LeoGuildManagerGlobalData LeoGuildManagerScanData
+## OptionalDependsOn: LibStub LibFeedback LibAddonMenu-2.0
+
+Libs\LibStub\LibStub.lua
+libs\LibFeedback\feedback.lua
+Libs\LibAddonMenu-2.0\LibAddonMenu-2.0.lua
+Libs\LibAddonMenu-2.0\controls\panel.lua
+Libs\LibAddonMenu-2.0\controls\submenu.lua
+Libs\LibAddonMenu-2.0\controls\button.lua
+Libs\LibAddonMenu-2.0\controls\checkbox.lua
+Libs\LibAddonMenu-2.0\controls\colorpicker.lua
+Libs\LibAddonMenu-2.0\controls\custom.lua
+Libs\LibAddonMenu-2.0\controls\description.lua
+Libs\LibAddonMenu-2.0\controls\dropdown.lua
+Libs\LibAddonMenu-2.0\controls\editbox.lua
+Libs\LibAddonMenu-2.0\controls\header.lua
+Libs\LibAddonMenu-2.0\controls\slider.lua
+Libs\LibAddonMenu-2.0\controls\texture.lua
+Libs\LibAddonMenu-2.0\controls\iconpicker.lua
+Libs\LibAddonMenu-2.0\controls\divider.lua

 LeoGuildManager.xml
+LeoGuildManagerInit.lua
 LeoGuildManager.lua
 LeoGuildManagerUI.lua
+Settings.lua
 Bindings.xml
diff --git a/LeoGuildManager.xml b/LeoGuildManager.xml
index efb7a64..a1dbfac 100644
--- a/LeoGuildManager.xml
+++ b/LeoGuildManager.xml
@@ -1,279 +1,140 @@
 <GuiXml>
+    <Font name="LeoGuildManagerLargeFont" font="$(MEDIUM_FONT)|18|soft-shadow-thin"/>
+    <Font name="LeoGuildManagerNormalFont" font="$(MEDIUM_FONT)|16|soft-shadow-thin"/>
+    <Font name="LeoGuildManagerSmallFont" font="$(MEDIUM_FONT)|14|soft-shadow-thin"/>
     <Controls>

-        <TopLevelControl name="LeoGMWindow" mouseEnabled="true" movable="true" hidden="true" clampedToScreen="true">
-            <Dimensions x="900" y="700" />
+        <TopLevelControl name="LeoGuildManagerWindow" mouseEnabled="true" movable="true" hidden="true" clampedToScreen="true" allowBringToTop="true" layer="1">
+            <Dimensions x="1000" y="700" />
             <Anchor point="TOP" relativeTo="GuiRoot" relativePoint="CENTER" offsetY="100" />

-            <OnMoveStop> LeoGM:OnWindowMoveStop() </OnMoveStop>
+            <OnMoveStop> LeoGuildManagerUI:OnWindowMoveStop() </OnMoveStop>
+            <OnHide> LeoGuildManagerUI:OnHide(self, hidden) </OnHide>
+            <OnShow> LeoGuildManagerUI:OnShow(self, hidden) </OnShow>

             <Controls>
                 <Backdrop name="$(parent)BG" centerColor="000000" edgeColor="222222">
                     <Anchor point="TOPLEFT" relativePoint="TOPLEFT" relativeTo="$(parent)"/>
-                    <Dimensions x="900" y="50"/>
-                    <Edge edgeSize="1"/>
-                </Backdrop>
-                <Backdrop name="$(parent)HeaderBG" centerColor="111111" edgeColor="222222">
-                    <Anchor point="TOPLEFT" relativePoint="TOPLEFT" relativeTo="$(parent)" offsetX="8" offsetY="5"/>
-                    <Dimensions x="660" y="40"/>
+                    <Dimensions x="1000" y="50"/>
                     <Edge edgeSize="1"/>
                 </Backdrop>
                 <Label name="$(parent)Title" color="39B027" font="ZoFontWinH3" wrapMode="ELLIPSIS"
-                       verticalAlignment="CENTER" mouseEnabled="true">
+                       verticalAlignment="CENTER" mouseEnabled="false">
                     <Anchor point="TOPLEFT" relativeTo="$(parent)" relativePoint="TOPLEFT" offsetX="16" offsetY="10"/>
                 </Label>

-                <Button name="$(parent)RulesButton" clickSound="Click">
-                    <Anchor point="TOPRIGHT" relativePoint="TOPRIGHT" relativeTo="$(parent)HeaderBG" offsetX="47" />
+                <Button name="$(parent)Close" clickSound="Click">
+                    <Anchor point="TOPRIGHT" relativePoint="TOPRIGHT" relativeTo="$(parent)" offsetX="-5" offsetY="4"/>
                     <Dimensions x="40" y="40"/>
-                    <OnMouseEnter>ZO_Tooltips_ShowTextTooltip(self, TOP, 'House Rules')</OnMouseEnter>
-                    <OnMouseExit>ZO_Tooltips_HideTextTooltip()</OnMouseExit>
-                    <OnClicked>LeoGM.ShowTab("Rules")</OnClicked>
+                    <OnClicked>LeoGuildManagerUI:CloseUI()</OnClicked>
                     <Controls>
                         <Backdrop name="$(parent)BG" centerColor="101010" edgeColor="202020">
                             <AnchorFill/>
                             <Edge edgeSize="1"/>
                         </Backdrop>
-                        <Texture name="$(parent)Texture" textureFile="esoui/art/tutorial/menubar_ava_up.dds">
-                            <Dimensions y="35" x="35"/>
+                        <Texture name="$(parent)Texture" textureFile="esoui/art/buttons/decline_up.dds">
+                            <Dimensions y="25" x="25"/>
                             <Anchor point="128"/>
                         </Texture>
                     </Controls>
                 </Button>
-                <Button name="$(parent)PurgeButton" clickSound="Click">
-                    <Anchor point="TOPLEFT" relativePoint="TOPLEFT" relativeTo="$(parent)RulesButton" offsetX="45"/>
+                <Button name="$(parent)FeedbackButton" clickSound="Click">
+                    <Anchor point="TOPRIGHT" relativePoint="TOPLEFT" relativeTo="$(parent)Close" offsetX="-5"/>
                     <Dimensions x="40" y="40"/>
-                    <OnMouseEnter>ZO_Tooltips_ShowTextTooltip(self, TOP, 'Purge')</OnMouseEnter>
-                    <OnMouseExit>ZO_Tooltips_HideTextTooltip()</OnMouseExit>
-                    <OnClicked>LeoGM.ShowTab("Purge")</OnClicked>
                     <Controls>
                         <Backdrop name="$(parent)BG" centerColor="101010" edgeColor="202020">
                             <AnchorFill/>
                             <Edge edgeSize="1"/>
                         </Backdrop>
-                        <Texture name="$(parent)Texture" textureFile="esoui/art/deathrecap/deathrecap_killingblow_icon.dds">
-                            <Dimensions y="35" x="35"/>
-                            <Anchor point="128"/>
-                        </Texture>
                     </Controls>
                 </Button>
-                <Button name="$(parent)RaffleButton" clickSound="Click">
-                    <Anchor point="TOPLEFT" relativePoint="TOPLEFT" relativeTo="$(parent)PurgeButton" offsetX="45"/>
+                <Button name="$(parent)SettingsButton" clickSound="Click">
+                    <Anchor point="TOPRIGHT" relativePoint="TOPLEFT" relativeTo="$(parent)FeedbackButton" offsetX="-5"/>
                     <Dimensions x="40" y="40"/>
-                    <OnMouseEnter>ZO_Tooltips_ShowTextTooltip(self, TOP, 'Raffle')</OnMouseEnter>
+                    <OnMouseEnter>ZO_Tooltips_ShowTextTooltip(self, TOP, 'Settings')</OnMouseEnter>
                     <OnMouseExit>ZO_Tooltips_HideTextTooltip()</OnMouseExit>
-                    <!--<OnClicked>LeoGM.ShowTab("Raffle")</OnClicked>-->
+                    <OnClicked>
+                        SCENE_MANAGER:HideTopLevel(LeoGuildManagerWindow)
+                        DoCommand("/leogmoptions")
+                    </OnClicked>
                     <Controls>
                         <Backdrop name="$(parent)BG" centerColor="101010" edgeColor="202020">
                             <AnchorFill/>
                             <Edge edgeSize="1"/>
                         </Backdrop>
-                        <Texture name="$(parent)Texture" textureFile="esoui/art/mainmenu/menubar_inventory_up.dds">
+                        <Texture name="$(parent)Texture" textureFile="esoui/art/chatwindow/chat_options_up.dds">
                             <Dimensions y="35" x="35"/>
                             <Anchor point="128"/>
                         </Texture>
                     </Controls>
                 </Button>
-                <Button name="$(parent)FeedbackButton" clickSound="Click">
-                    <Anchor point="TOPLEFT" relativePoint="TOPLEFT" relativeTo="$(parent)RaffleButton" offsetX="45"/>
+                <Button name="$(parent)PurgeButton" clickSound="Click">
+                    <Anchor point="TOPRIGHT" relativePoint="TOPLEFT" relativeTo="$(parent)SettingsButton" offsetX="-5"/>
                     <Dimensions x="40" y="40"/>
-                    <OnMouseEnter>ZO_Tooltips_ShowTextTooltip(self, TOP, 'Research')</OnMouseEnter>
+                    <OnMouseEnter>ZO_Tooltips_ShowTextTooltip(self, TOP, 'Purge')</OnMouseEnter>
                     <OnMouseExit>ZO_Tooltips_HideTextTooltip()</OnMouseExit>
+                    <OnClicked>LeoGuildManager.ShowTab("Purge")</OnClicked>
                     <Controls>
                         <Backdrop name="$(parent)BG" centerColor="101010" edgeColor="202020">
                             <AnchorFill/>
                             <Edge edgeSize="1"/>
                         </Backdrop>
-                    </Controls>
-                </Button>
-                <Button name="$(parent)Close" clickSound="Click">
-                    <Anchor point="TOPLEFT" relativePoint="TOPLEFT" relativeTo="$(parent)FeedbackButton" offsetX="45"/>
-                    <Dimensions x="40" y="40"/>
-                    <OnClicked>LeoGM:HideUI()</OnClicked>
-                    <Controls>
-                        <Backdrop name="$(parent)BG" centerColor="101010" edgeColor="202020">
-                            <AnchorFill/>
-                            <Edge edgeSize="1"/>
-                        </Backdrop>
-                        <Texture name="$(parent)Texture" textureFile="esoui/art/buttons/decline_up.dds">
-                            <Dimensions y="25" x="25"/>
+                        <Texture name="$(parent)Texture" textureFile="esoui/art/deathrecap/deathrecap_killingblow_icon.dds">
+                            <Dimensions y="35" x="35"/>
                             <Anchor point="128"/>
                         </Texture>
                     </Controls>
                 </Button>
-
-                <Backdrop name="LeoGMWindowRulesPanel" tier="1" centerColor="000000" edgeColor="202020" hidden="true" clampedToScreen="true" movable="false" mouseEnabled="true">
-                    <Anchor point="TOPLEFT" relativePoint="TOPLEFT" relativeTo="LeoGMWindow" offsetX="0" offsetY="52"/>
-                    <Dimensions x="900" y="648"/>
+                <Backdrop name="$(parent)HeaderBG" centerColor="111111" edgeColor="222222">
+                    <Anchor point="TOPLEFT" relativePoint="TOPLEFT" relativeTo="$(parent)" offsetX="5" offsetY="2"/>
+                    <Anchor point="TOPRIGHT" relativePoint="TOPLEFT" relativeTo="$(parent)PurgeButton" offsetX="-5" offsetY="0"/>
+                    <Dimensions x="390" y="40"/>
                     <Edge edgeSize="1"/>
-                    <Controls>
-
-                        <Label name="$(parent)RequirementsLabel" text="Requirements" font="ZoFontWinH2" wrapMode="ELLIPSIS"
-                               color="E8DFAF" verticalAlignment="CENTER" horizontalAlignment="CENTER" mouseEnabled="false">
-                            <Dimensions x="900" y="35"/>
-                            <Anchor point="TOPLEFT" relativeTo="$(parent)" relativePoint="TOPLEFT" offsetX="20" offsetY="20"/>
-                        </Label>
-
-                        <Label name="$(parent)TicketsLabel" text="Tickets" font="ZoFontWinH3" wrapMode="ELLIPSIS"
-                               color="E8DFAF" verticalAlignment="CENTER" horizontalAlignment="RIGHT" mouseEnabled="false">
-                            <Dimensions x="120" y="35"/>
-                            <Anchor point="TOPLEFT" relativeTo="$(parent)RequirementsLabel" relativePoint="BOTTOMLEFT" offsetY="10"/>
-                        </Label>
-                        <Slider name="$(parent)TicketsSlider" mouseEnabled="true" step="1" orientation="horizontal">
-                            <Anchor point="TOPLEFT" relativeTo="$(parent)TicketsLabel" relativePoint="TOPRIGHT" offsetX="10" offsetY="14" />
-                            <ThumbTexture textureFile="/esoui/art/miscellaneous/scrollbox_elevator.dds" disabledTextureFile="/esoui/art/miscellaneous/scrollbox_elevator.dds" highlightedTextureFile="/esoui/art/miscellaneous/scrollbox_elevator.dds" thumbWidth="8" thumbHeight="16" left="0" top="0" bottom="1" right="1" />
-                            <Dimensions x="160" y="14" />
-                            <Limits min="0" max="100" />
-                            <OnValueChanged>
-                                LeoGMWindow.OnTicketsSliderMoved()
-                            </OnValueChanged>
-                            <Controls>
-                                <Backdrop name="$(parent)Backdrop" centerColor="000000">
-                                    <Edge file="/EsoUI/Art/Tooltips/UI-SliderBackdrop.dds" edgeFileWidth="32" edgeFileHeight="4" />
-                                    <Anchor point="TOPLEFT" relativeTo="$(parent)" relativePoint="TOPLEFT" offsetX="0" offsetY="4" />
-                                    <Anchor point="BOTTOMRIGHT" relativeTo="$(parent)" relativePoint="BOTTOMRIGHT" offsetX="0" offsetY="-4" />
-                                </Backdrop>
-                            </Controls>
-                        </Slider>
-                        <Label name="$(parent)TicketsValue" text="disabled" font="ZoFontWinH3" wrapMode="ELLIPSIS"
-                               verticalAlignment="CENTER" horizontalAlignment="LEFT" mouseEnabled="false">
-                            <Dimensions x="80" y="35"/>
-                            <Anchor point="TOPLEFT" relativeTo="$(parent)TicketsSlider" relativePoint="TOPRIGHT" offsetX="10" offsetY="-10" />
-                        </Label>
-
-                        <Label name="$(parent)SalesLabel" text="Sales" font="ZoFontWinH3" wrapMode="ELLIPSIS"
-                               color="E8DFAF" verticalAlignment="CENTER" horizontalAlignment="RIGHT" mouseEnabled="false">
-                            <Dimensions x="120" y="35"/>
-                            <Anchor point="TOPLEFT" relativeTo="$(parent)TicketsValue" relativePoint="TOPRIGHT" offsetX="10"/>
-                        </Label>
-                        <Slider name="$(parent)SalesSlider" mouseEnabled="true" step="10" orientation="horizontal">
-                            <Anchor point="TOPLEFT" relativeTo="$(parent)SalesLabel" relativePoint="TOPRIGHT" offsetX="10" offsetY="14" />
-                            <ThumbTexture textureFile="/esoui/art/miscellaneous/scrollbox_elevator.dds" disabledTextureFile="/esoui/art/miscellaneous/scrollbox_elevator.dds" highlightedTextureFile="/esoui/art/miscellaneous/scrollbox_elevator.dds" thumbWidth="8" thumbHeight="16" left="0" top="0" bottom="1" right="1" />
-                            <Dimensions x="160" y="14" />
-                            <Limits min="0" max="1000" />
-                            <OnValueChanged>
-                                LeoGMWindow.OnSalesSliderMoved()
-                            </OnValueChanged>
-                            <Controls>
-                                <Backdrop name="$(parent)Backdrop" centerColor="000000">
-                                    <Edge file="/EsoUI/Art/Tooltips/UI-SliderBackdrop.dds" edgeFileWidth="32" edgeFileHeight="4" />
-                                    <Anchor point="TOPLEFT" relativeTo="$(parent)" relativePoint="TOPLEFT" offsetX="0" offsetY="4" />
-                                    <Anchor point="BOTTOMRIGHT" relativeTo="$(parent)" relativePoint="BOTTOMRIGHT" offsetX="0" offsetY="-4" />
-                                </Backdrop>
-                            </Controls>
-                        </Slider>
-                        <Label name="$(parent)SalesValue" text="disabled" font="ZoFontWinH3" wrapMode="ELLIPSIS"
-                               verticalAlignment="CENTER" horizontalAlignment="LEFT" mouseEnabled="false">
-                            <Dimensions x="80" y="35"/>
-                            <Anchor point="TOPLEFT" relativeTo="$(parent)SalesSlider" relativePoint="TOPRIGHT" offsetX="10" offsetY="-10" />
-                        </Label>
-
-                        <Label name="$(parent)OfflineLabel" text="Inactivity" font="ZoFontWinH3" wrapMode="ELLIPSIS"
-                               color="E8DFAF" verticalAlignment="CENTER" horizontalAlignment="RIGHT" mouseEnabled="false">
-                            <Dimensions x="120" y="35"/>
-                            <Anchor point="TOPLEFT" relativeTo="$(parent)TicketsLabel" relativePoint="BOTTOMLEFT" offsetY="10"/>
-                        </Label>
-                        <Slider name="$(parent)OfflineSlider" mouseEnabled="true" step="1" orientation="horizontal">
-                            <Anchor point="TOPLEFT" relativeTo="$(parent)OfflineLabel" relativePoint="TOPRIGHT" offsetX="10" offsetY="14" />
-                            <ThumbTexture textureFile="/esoui/art/miscellaneous/scrollbox_elevator.dds" disabledTextureFile="/esoui/art/miscellaneous/scrollbox_elevator.dds" highlightedTextureFile="/esoui/art/miscellaneous/scrollbox_elevator.dds" thumbWidth="8" thumbHeight="16" left="0" top="0" bottom="1" right="1" />
-                            <Dimensions x="160" y="14" />
-                            <Limits min="0" max="100" />
-                            <OnValueChanged>
-                                LeoGMWindow.OnOfflineSliderMoved()
-                            </OnValueChanged>
-                            <Controls>
-                                <Backdrop name="$(parent)Backdrop" centerColor="000000">
-                                    <Edge file="/EsoUI/Art/Tooltips/UI-SliderBackdrop.dds" edgeFileWidth="32" edgeFileHeight="4" />
-                                    <Anchor point="TOPLEFT" relativeTo="$(parent)" relativePoint="TOPLEFT" offsetX="0" offsetY="4" />
-                                    <Anchor point="BOTTOMRIGHT" relativeTo="$(parent)" relativePoint="BOTTOMRIGHT" offsetX="0" offsetY="-4" />
-                                </Backdrop>
-                            </Controls>
-                        </Slider>
-                        <Label name="$(parent)OfflineValue" text="disabled" font="ZoFontWinH3" wrapMode="ELLIPSIS"
-                               verticalAlignment="CENTER" horizontalAlignment="LEFT" mouseEnabled="false">
-                            <Dimensions x="120" y="35"/>
-                            <Anchor point="TOPLEFT" relativeTo="$(parent)OfflineSlider" relativePoint="TOPRIGHT" offsetX="10" offsetY="-10" />
-                        </Label>
-
-                        <Label name="$(parent)RequirementsDesc" text="" font="ZoFontGame" wrapMode="ELLIPSIS"
-                               verticalAlignment="CENTER" horizontalAlignment="CENTER" mouseEnabled="false">
-                            <Dimensions x="860" y="60"/>
-                            <Anchor point="TOPLEFT" relativeTo="$(parent)" relativePoint="TOPLEFT" offsetX="20" offsetY="160" />
-                        </Label>
-
-                        <Texture name="$(parent)Divider" textureFile="EsoUI/Art/Miscellaneous/centerscreen_topDivider.dds">
-                            <Dimensions x="900" y="5" />
-                            <Anchor point="TOP" relativeTo="$(parent)" relativePoint="TOP" offsetX="0" offsetY="220" />
-                        </Texture>
-
-
-                        <Label name="$(parent)BlacklistLabel" text="Blacklist" font="ZoFontWinH2" wrapMode="ELLIPSIS"
-                               color="E8DFAF" verticalAlignment="CENTER" horizontalAlignment="CENTER" mouseEnabled="false">
-                            <Dimensions x="900" y="35"/>
-                            <Anchor point="TOPLEFT" relativeTo="$(parent)" relativePoint="TOPLEFT" offsetX="20" offsetY="240" />
-                        </Label>
-                        <Label name="$(parent)BlacklistDesc" text="UserIDs that will be kicked |cFF2222automatically|r: " font="ZoFontGame" wrapMode="ELLIPSIS"
-                               color="FFFFFF" verticalAlignment="TOP" horizontalAlignment="LEFT" mouseEnabled="false">
-                            <Dimensions x="320" y="35"/>
-                            <Anchor point="TOPLEFT" relativeTo="$(parent)BlacklistLabel" relativePoint="TOPLEFT" offsetX="0" offsetY="40" />
-                        </Label>
-                        <Label name="$(parent)BlacklistList" text="" font="ZoFontGame" wrapMode="ELLIPSIS"
-                               color="FFFFFF" verticalAlignment="TOP" horizontalAlignment="LEFT" mouseEnabled="false">
-                            <Dimensions x="500" y="90"/>
-                            <Anchor point="TOPLEFT" relativeTo="$(parent)BlacklistDesc" relativePoint="TOPRIGHT" offsetX="0" offsetY="0" />
-                        </Label>
-                        <Button name="$(parent)Save" inherits="ZO_DefaultButton" font="ZoFontWinH4" text="Change">
-                            <Dimensions x="200" y="30" />
-                            <Anchor point="TOPLEFT" relativeTo="$(parent)BlacklistDesc" relativePoint="BOTTOMLEFT" offsetX="2" offsetY="16"/>
-                            <OnClicked>
-                                LeoGMWindow.OnBlackListClick()
-                            </OnClicked>
-                        </Button>
-
-                    </Controls>
                 </Backdrop>

-                <Backdrop name="LeoGMWindowPurgePanel" tier="1" centerColor="000000" edgeColor="202020" hidden="true" clampedToScreen="true" movable="false" mouseEnabled="true">
-                    <Anchor point="TOPLEFT" relativePoint="TOPLEFT" relativeTo="LeoGMWindow" offsetX="0" offsetY="52"/>
-                    <Dimensions x="900" y="648"/>
+                <Backdrop name="LeoGuildManagerWindowPurgePanel" tier="1" centerColor="000000" edgeColor="202020" hidden="true" clampedToScreen="true" movable="false" mouseEnabled="true">
+                    <Anchor point="TOPLEFT" relativePoint="TOPLEFT" relativeTo="LeoGuildManagerWindow" offsetX="0" offsetY="52"/>
+                    <Dimensions x="1000" y="648"/>
                     <Edge edgeSize="1"/>
                     <Controls>

-                        <Label name="$(parent)RankLabel" text="Rank" font="ZoFontWinH3" wrapMode="ELLIPSIS"
-                               color="E8DFAF" verticalAlignment="CENTER" horizontalAlignment="RIGHT" mouseEnabled="false">
-                            <Dimensions x="120" y="35"/>
-                            <Anchor point="TOPLEFT" relativeTo="$(parent)" relativePoint="TOPLEFT" offsetY="10"/>
+                        <Label name="$(parent)PurgeDesc" text="" font="ZoFontGame" wrapMode="ELLIPSIS"
+                               verticalAlignment="CENTER" horizontalAlignment="LEFT" mouseEnabled="false">
+                            <Dimensions x="700" y="100"/>
+                            <Anchor point="TOPLEFT" relativeTo="$(parent)" relativePoint="TOPLEFT" offsetX="20" offsetY="20" />
                         </Label>

-                        <Button name="$(parent)ListButton" text="List" inherits="ZO_DefaultButton">
-                            <Anchor point="TOPLEFT" relativeTo="$(parent)RankLabel" relativePoint="TOPRIGHT" offsetX="300" offsetY="0"/>
+                        <Button name="$(parent)ScanButton" text="Scan Guild" inherits="ZO_DefaultButton" clickSound="Click">
+                            <Anchor point="TOPRIGHT" relativeTo="$(parent)" relativePoint="TOPRIGHT" offsetX="-10" offsetY="20"/>
+                            <Dimensions x="180" />
+                            <OnClicked> LeoGuildManager.StartScanGuilds(true) </OnClicked>
+                        </Button>
+                        <Button name="$(parent)ListButton" text="List Members" inherits="ZO_DefaultButton" clickSound="Click">
+                            <Anchor point="TOPLEFT" relativeTo="$(parent)ScanButton" relativePoint="TOPLEFT" offsetX="0" offsetY="40"/>
                             <Dimensions x="180" />
-                            <OnClicked>
-                                LeoGM:ListPurge()
-                            </OnClicked>
+                            <OnClicked> LeoGuildManager:ListPurge() </OnClicked>
+                        </Button>
+                        <Button name="$(parent)ClearListButton" text="Clear List" inherits="ZO_DefaultButton" clickSound="Click">
+                            <Anchor point="TOPLEFT" relativeTo="$(parent)ListButton" relativePoint="TOPLEFT" offsetX="0" offsetY="40"/>
+                            <Dimensions x="180" />
+                            <OnClicked> LeoGuildManager:ClearList() </OnClicked>
                         </Button>

                         <Texture name="$(parent)LoadingIcon" textureFile="EsoUI/Art/Miscellaneous/wait_icon.dds" hidden="true">
-                            <Anchor point="TOPLEFT" relativeTo="$(parent)ListButton" relativePoint="TOPRIGHT" offsetX="20" offsetY="-4" />
+                            <Anchor point="TOPRIGHT" relativeTo="$(parent)ScanButton" relativePoint="TOPLEFT" offsetX="20" offsetY="-4" />
                             <Dimensions x="32" y="32" />
                         </Texture>

-                        <Label name="$(parent)PurgeDesc" text="" font="ZoFontGame" wrapMode="ELLIPSIS"
-                               verticalAlignment="CENTER" horizontalAlignment="LEFT" mouseEnabled="false">
-                            <Dimensions x="860" y="60"/>
-                            <Anchor point="TOPLEFT" relativeTo="$(parent)" relativePoint="TOPLEFT" offsetX="20" offsetY="50" />
-                        </Label>
-
                         <Control name="$(parent)MemberScroll" inheritAlpha="true">
-                            <Anchor point="TOPLEFT" relativeTo="$(parent)RankLabel" relativePoint="TOPLEFT" offsetX="80" offsetY="100"/>
-                            <Dimensions x="720" y="230" />
+                            <Anchor point="TOPLEFT" relativeTo="$(parent)PurgeDesc" relativePoint="BOTTOMLEFT" offsetX="0" offsetY="20"/>
+                            <Dimensions x="960" y="230" />
                             <Controls>
                                 <Control name="$(parent)Headers">
                                     <Anchor point="TOPLEFT" relativeTo="$(parent)" relativePoint="TOPLEFT" />
                                     <Controls>
                                         <Control name="$(parent)Name" inherits="ZO_SortHeader">
-                                            <Anchor point="TOPLEFT" relativeTo="$(parent)" relativePoint="TOPLEFT" />
-                                            <Dimensions x="250" y="20" />
+                                            <Anchor point="TOPLEFT" relativeTo="$(parent)" relativePoint="TOPLEFT" offsetX="24" />
+                                            <Dimensions x="200" y="20" />
                                             <OnInitialized>
                                                 ZO_SortHeader_Initialize(self, "Name", "name", ZO_SORT_ORDER_DOWN, TEXT_ALIGN_LEFT, "ZoFontGameLargeBold")
                                             </OnInitialized>
@@ -299,28 +160,58 @@
                                                 ZO_SortHeader_Initialize(self, "Online", "online", ZO_SORT_ORDER_DOWN, TEXT_ALIGN_LEFT, "ZoFontGameLargeBold")
                                             </OnInitialized>
                                         </Control>
-                                        <Control name="$(parent)Rank" inherits="ZO_SortHeader">
+                                        <Control name="$(parent)Joined" inherits="ZO_SortHeader">
                                             <Anchor point="TOPLEFT" relativeTo="$(parent)Online" relativePoint="TOPRIGHT" offsetX="10" />
                                             <Dimensions x="100" y="20" />
                                             <OnInitialized>
+                                                ZO_SortHeader_Initialize(self, "Joined", "joined", ZO_SORT_ORDER_DOWN, TEXT_ALIGN_LEFT, "ZoFontGameLargeBold")
+                                            </OnInitialized>
+                                        </Control>
+                                        <Control name="$(parent)Rank" inherits="ZO_SortHeader">
+                                            <Anchor point="TOPLEFT" relativeTo="$(parent)Joined" relativePoint="TOPRIGHT" offsetX="10" />
+                                            <Dimensions x="100" y="20" />
+                                            <OnInitialized>
                                                 ZO_SortHeader_Initialize(self, "Rank", "rankIndex", ZO_SORT_ORDER_DOWN, TEXT_ALIGN_LEFT, "ZoFontGameLargeBold")
                                             </OnInitialized>
                                         </Control>
                                     </Controls>
                                 </Control>
                                 <Control name="$(parent)List" inherits="ZO_ScrollList">
-                                    <Dimensions x="720" y="400" />
+                                    <Dimensions x="960" y="400" />
                                     <Anchor point="TOPLEFT" relativeTo="$(parent)Headers" relativePoint="TOPLEFT" offsetY="30" />
                                 </Control>

                             </Controls>
                         </Control>

-                        <Label name="$(parent)ListResult" text="" font="ZoFontGame" wrapMode="ELLIPSIS"
+                        <Label name="$(parent)ListResult" text="Total: 0" font="ZoFontGame" wrapMode="ELLIPSIS"
                                verticalAlignment="CENTER" horizontalAlignment="LEFT" mouseEnabled="false">
-                            <Dimensions x="860" y="60"/>
+                            <Dimensions x="150" y="60"/>
                             <Anchor point="TOPLEFT" relativeTo="$(parent)MemberScrollList" relativePoint="BOTTOMLEFT" offsetX="0" offsetY="20" />
                         </Label>
+                        <Label name="$(parent)EndangeredCounter" text="Selected 0" font="ZoFontGame" wrapMode="ELLIPSIS"
+                               verticalAlignment="CENTER" horizontalAlignment="LEFT" mouseEnabled="false">
+                            <Dimensions x="150" y="60"/>
+                            <Anchor point="TOPLEFT" relativeTo="$(parent)ListResult" relativePoint="TOPRIGHT" offsetX="20" offsetY="0" />
+                        </Label>
+
+                        <Button name="$(parent)ClearSelected" text="Clear Selected" inherits="ZO_DefaultButton" clickSound="Click">
+                            <Anchor point="TOPLEFT" relativeTo="$(parent)ListResult" relativePoint="TOPRIGHT" offsetX="200" offsetY="16"/>
+                            <Dimensions x="180" />
+                            <OnClicked> LeoGuildManager.ClearSelected() </OnClicked>
+                        </Button>
+                        <Button name="$(parent)DemoteButton" text="Demote Selected" inherits="ZO_DefaultButton" clickSound="Click">
+                            <Anchor point="TOPLEFT" relativeTo="$(parent)ClearSelected" relativePoint="TOPRIGHT" offsetX="0" offsetY="0"/>
+                            <Dimensions x="180" />
+                            <OnInitialized> self:SetEnabled(false) </OnInitialized>
+                            <OnClicked> LeoGuildManager.DemoteAllClick() </OnClicked>
+                        </Button>
+                        <Button name="$(parent)KickButton" text="Remove Selected" inherits="ZO_DefaultButton" clickSound="Click">
+                            <Anchor point="TOPLEFT" relativeTo="$(parent)DemoteButton" relativePoint="TOPRIGHT" offsetX="0" offsetY="0"/>
+                            <Dimensions x="180" />
+                            <OnInitialized> self:SetEnabled(false) </OnInitialized>
+                            <OnClicked> LeoGuildManager.KickAllClick() </OnClicked>
+                        </Button>

                     </Controls>
                 </Backdrop>
@@ -328,36 +219,46 @@
             </Controls>
         </TopLevelControl>

-        <Control name="LeoGMMemberListTemplate" virtual="true" mouseEnabled="true">
-            <Dimensions x="720" y="30" />
+        <Control name="LeoGuildManagerMemberListTemplate" virtual="true" mouseEnabled="true">
+            <Dimensions x="960" y="30" />
             <Controls>
                 <Texture name="$(parent)BG" inherits="ZO_ThinListBgStrip" />

-                <Label name="$(parent)Name" font="ZoFontGame" color="ffffff" verticalAlignment="CENTER" mouseEnabled="true">
-                    <Dimensions x="250" y="32" />
-                    <Anchor point="TOPLEFT" relativeTo="$(parent)BG" relativePoint="TOPLEFT"/>
-                    <OnMouseUp> LeoGM.UserClick(self, button, upInside) </OnMouseUp>
+                <Texture name="$(parent)Checkbox" textureFile="/esoui/art/buttons/checkbox_unchecked.dds" mouseEnabled="true">
+                    <Dimensions x="18" y="18" />
+                    <Anchor point="TOPLEFT" relativeTo="$(parent)BG" relativePoint="TOPLEFT" offsetY="4"/>
+                    <OnMouseUp> LeoGuildManager.CheckboxClick(self, button, upInside) </OnMouseUp>
+                </Texture>
+
+                <Label name="$(parent)Name" font="LeoGuildManagerNormalFont" verticalAlignment="CENTER" mouseEnabled="true">
+                    <Dimensions x="200" y="32" />
+                    <Anchor point="TOPLEFT" relativeTo="$(parent)" relativePoint="TOPLEFT" offsetX="24"/>
+                    <OnMouseUp> LeoGuildManager.UserClick(self, button, upInside) </OnMouseUp>
                 </Label>
-                <Label name="$(parent)Deposits" font="ZoFontGame" verticalAlignment="CENTER">
+                <Label name="$(parent)Deposits" font="LeoGuildManagerNormalFont" verticalAlignment="CENTER">
                     <Dimensions x="100" y="32" />
                     <Anchor point="TOPLEFT" relativeTo="$(parent)Name" relativePoint="TOPRIGHT" offsetX="10"/>
                 </Label>
-                <Label name="$(parent)Sales" font="ZoFontGame" verticalAlignment="CENTER">
+                <Label name="$(parent)Sales" font="LeoGuildManagerNormalFont" verticalAlignment="CENTER">
                     <Dimensions x="100" y="32" />
                     <Anchor point="TOPLEFT" relativeTo="$(parent)Deposits" relativePoint="TOPRIGHT" offsetX="10"/>
                 </Label>
-                <Label name="$(parent)Online" font="ZoFontGame" verticalAlignment="CENTER">
+                <Label name="$(parent)Online" font="LeoGuildManagerNormalFont" verticalAlignment="CENTER">
                     <Dimensions x="100" y="32" />
                     <Anchor point="TOPLEFT" relativeTo="$(parent)Sales" relativePoint="TOPRIGHT" offsetX="10"/>
                 </Label>
-                <Label name="$(parent)Rank" font="ZoFontGame" verticalAlignment="CENTER">
+                <Label name="$(parent)Joined" font="LeoGuildManagerNormalFont" verticalAlignment="CENTER">
                     <Dimensions x="100" y="32" />
                     <Anchor point="TOPLEFT" relativeTo="$(parent)Online" relativePoint="TOPRIGHT" offsetX="10"/>
                 </Label>
+                <Label name="$(parent)Rank" font="LeoGuildManagerNormalFont" verticalAlignment="CENTER">
+                    <Dimensions x="200" y="32" />
+                    <Anchor point="TOPLEFT" relativeTo="$(parent)Joined" relativePoint="TOPRIGHT" offsetX="10"/>
+                </Label>
             </Controls>
         </Control>

-        <Control name="LeoGMWindowDropdown" inherits="ZO_ComboBox" virtual="true">
+        <Control name="LeoGuildManagerWindowDropdown" inherits="ZO_ComboBox" virtual="true">
             <OnInitialized>
                 ZO_ComboBox:New(self)
             </OnInitialized>
diff --git a/LeoGuildManagerInit.lua b/LeoGuildManagerInit.lua
new file mode 100644
index 0000000..5e522a1
--- /dev/null
+++ b/LeoGuildManagerInit.lua
@@ -0,0 +1,102 @@
+
+LeoGuildManager = LeoGuildManager or {}
+LeoGuildManagerUI = LeoGuildManagerUI or {}
+
+LeoGuildManager.name = "LeoGuildManager"
+LeoGuildManager.displayName = "Leo's Guild Manager"
+LeoGuildManager.version = "1.1.0"
+LeoGuildManager.chatPrefix = "|c39B027" .. LeoGuildManager.name .. "|r: "
+
+LeoGuildManager.TAB_PURGE = "Purge"
+
+LeoGuildManager.panelList = {
+    LeoGuildManager.TAB_PURGE
+}
+LeoGuildManager.color = {
+    hex = {
+        green = '10FF10',
+        darkGreen = '21A121',
+        white = 'FFFFFF',
+        red = 'FF1010',
+        darkRed = 'CB110E',
+        yellow = 'FFFF00',
+        orange = 'FFCC00',
+        eso = 'E8DFAF',
+    },
+    rgba = {
+        green = {0,1,0,1},
+        white = {1,1,1,1},
+        red = {1,0.25,0.12},
+        yellow = {1,1,0,1},
+        orange = {1,0.8,0,1},
+    }
+}
+
+LeoGuildManager.MS_IN_MINUTE = 60 * 1000
+LeoGuildManager.SECONDS_IN_HOUR = 60 * 60
+LeoGuildManager.SECONDS_IN_DAY = LeoGuildManager.SECONDS_IN_HOUR * 24
+LeoGuildManager.SECONDS_IN_WEEK = LeoGuildManager.SECONDS_IN_DAY * 7
+
+
+LeoGuildManager.integrations = {
+    "Master Merchant",
+    "Arkadiu's Trade Tools",
+}
+
+LeoGuildManager.cycleMM = {
+    {
+        id = 3,
+        name = "This week"
+    },
+    {
+        id = 4,
+        name = "Last week"
+    },
+    {
+        id = 5,
+        name = "Prior week"
+    },
+    {
+        id = 8,
+        name = "Last 7 days"
+    },
+    {
+        id = 6,
+        name = "Last 10 days"
+    },
+    {
+        id = 7,
+        name = "Last 30 days"
+    }
+}
+
+LeoGuildManager.cycleATT = {
+    {
+        id = 3,
+        name = "This week"
+    },
+    {
+        id = 4,
+        name = "Last week"
+    },
+    {
+        id = 5,
+        name = "Prior week"
+    },
+    {
+        id = 8,
+        name = "Last 7 days"
+    },
+    {
+        id = 6,
+        name = "Last 10 days"
+    },
+    {
+        id = 14,
+        name = "Last 14 days"
+    },
+    {
+        id = 7,
+        name = "Last 30 days"
+    }
+}
diff --git a/LeoGuildManagerUI.lua b/LeoGuildManagerUI.lua
index 8123968..459f5da 100644
--- a/LeoGuildManagerUI.lua
+++ b/LeoGuildManagerUI.lua
@@ -1,10 +1,86 @@

-LeoGM.hidden = true
-LeoGM.memberScroll = nil
-LeoGM.selectedRank = nil
+LeoGuildManager.hidden = true
+LeoGuildManager.memberScroll = nil
+LeoGuildManager.selectedRank = nil
+LeoGuildManager.endangered = {}
+
+function LeoGuildManagerUI:OnWindowMoveStop()
+    LeoGuildManager.globalData.position = {
+        left = LeoGuildManagerWindow:GetLeft(),
+        top = LeoGuildManagerWindow:GetTop()
+    }
+end
+
+function LeoGuildManagerUI:OnHide(control, hidden)
+    if hidden then LeoGuildManagerUI.HideUI() end
+end
+
+function LeoGuildManagerUI:OnShow(control, hidden)
+    if not hidden then LeoGuildManagerUI.ShowUI() end
+end
+
+function LeoGuildManagerUI:isHidden()
+    return LeoGuildManager.hidden
+end
+
+function LeoGuildManagerUI.RestorePosition()
+    local position = LeoGuildManager.globalData.position or { left = 200; top = 200; }
+    local left = position.left
+    local top = position.top
+
+    LeoGuildManagerWindow:ClearAnchors()
+    LeoGuildManagerWindow:SetAnchor(TOPLEFT, GuiRoot, TOPLEFT, left, top)
+    LeoGuildManagerWindow:SetDrawLayer(DL_OVERLAY)
+    LeoGuildManagerWindow:SetDrawTier(DT_LOW)
+end
+
+function LeoGuildManagerUI.CloseUI()
+    SCENE_MANAGER:HideTopLevel(LeoGuildManagerWindow)
+end

-LeoGMMemberList = ZO_SortFilterList:Subclass()
-function LeoGMMemberList:New(control)
+function LeoGuildManagerUI.ShowUI()
+    if not LeoGuildManager.globalData.selectedGuild then
+        SCENE_MANAGER:ToggleTopLevel(LeoGuildManagerWindow)
+        LeoGuildManager.log("No guild enabled on Settings.")
+        DoCommand("/leogmoptions")
+        return
+    end
+    LeoGuildManager.hidden = false;
+    LeoGuildManager.ShowTab(LeoGuildManager.globalData.activeTab or LeoGuildManager.TAB_PURGE)
+end
+
+function LeoGuildManagerUI.HideUI()
+    LeoGuildManager.hidden = true;
+end
+
+function LeoGuildManagerUI.ToggleUI()
+    SCENE_MANAGER:ToggleTopLevel(LeoGuildManagerWindow)
+end
+
+function LeoGuildManager.ShowTab(tab)
+    local control
+    local hasTab = false
+    for _,panel in ipairs(LeoGuildManager.panelList) do
+        control = WINDOW_MANAGER:GetControlByName('LeoGuildManagerWindow' .. panel .. 'Panel')
+        control:SetHidden(true)
+        if tab == panel then hasTab = true end
+    end
+    if not hasTab then tab = LeoGuildManager.TAB_PURGE end
+
+    if tab == LeoGuildManager.TAB_PURGE then
+        local descPurge = LeoGuildManager.CreatePurgeDescription(LeoGuildManager.globalData.selectedGuild)
+        LeoGuildManagerWindowPurgePanelPurgeDesc:SetText("|c"..LeoGuildManager.color.hex.yellow..descPurge.."|r")
+    end
+
+    LeoGuildManager.globalData.activeTab = tab
+    LeoGuildManagerWindowTitle:SetText(LeoGuildManager.displayName .. " v" .. LeoGuildManager.version .. " - " .. tab)
+    control = WINDOW_MANAGER:GetControlByName("LeoGuildManagerWindow" .. tab .. "Panel")
+    control:SetHidden(false)
+end
+
+
+LeoGuildManagerMemberList = ZO_SortFilterList:Subclass()
+function LeoGuildManagerMemberList:New(control)

     ZO_SortFilterList.InitializeSortFilterList(self, control)

@@ -14,11 +90,12 @@ function LeoGMMemberList:New(control)
         ["deposits"] = { isNumeric = true, tiebreaker = "name"},
         ["sales"] = { isNumeric = true, tiebreaker = "name"},
         ["online"] = { isNumeric = true, tiebreaker = "name"},
+        ["joined"] = { isNumeric = true, tiebreaker = "name"},
         ["rankIndex"] = { isNumeric = true, tiebreaker = "name"},
     }

     self.masterList = {}
-    ZO_ScrollList_AddDataType(self.list, 1, "LeoGMMemberListTemplate", 32, function(control, data) self:SetupEntry(control, data) end)
+    ZO_ScrollList_AddDataType(self.list, 1, "LeoGuildManagerMemberListTemplate", 32, function(control, data) self:SetupEntry(control, data) end)

     self.sortFunction = function(listEntry1, listEntry2)
         return ZO_TableOrderingFunction(listEntry1.data, listEntry2.data, self.currentSortKey, sorterKeys, self.currentSortOrder)
@@ -28,16 +105,113 @@ function LeoGMMemberList:New(control)
     return self
 end

-function LeoGM.UserClick(control, button, upInside)
+
+local function updateEndangered()
+    LeoGuildManagerWindowPurgePanelEndangeredCounter:SetText("Selected: " .. #LeoGuildManager.endangered)
+    if #LeoGuildManager.endangered > 0 then
+        LeoGuildManagerWindowPurgePanelDemoteButton:SetEnabled(true)
+        LeoGuildManagerWindowPurgePanelKickButton:SetEnabled(true)
+    else
+        LeoGuildManagerWindowPurgePanelDemoteButton:SetEnabled(false)
+        LeoGuildManagerWindowPurgePanelKickButton:SetEnabled(false)
+    end
+end
+
+local function findCheckboxByName(member)
+    local content = LeoGuildManagerWindowPurgePanelMemberScrollListContents
+    for i = 1, content:GetNumChildren() do
+        local row = content:GetChild(i)
+        local checkbox = GetControl(row, "Checkbox")
+        if member == checkbox.data then return checkbox end
+    end
+end
+
+local function removeEndangered(member, control)
+    LeoGuildManager.SetEndangered(member, false)
+    for i, name in ipairs(LeoGuildManager.endangered) do
+        if name == member then
+            table.remove(LeoGuildManager.endangered, i)
+            break
+        end
+    end
+    if control == nil then
+        control = findCheckboxByName(member)
+    end
+    control:SetTexture('/esoui/art/buttons/checkbox_unchecked.dds')
+    control.isEndangered = false
+end
+
+local function demoteNext()
+    for i, member in pairs(LeoGuildManager.endangered) do
+        LeoGuildManager.log("Demoting " .. member .. " ...")
+        GuildDemote(guildId, member)
+        table.remove(LeoGuildManager.endangered, i)
+        removeEndangered(member)
+
+        local guildId = LeoGuildManager.GetGuildId(LeoGuildManager.globalData.selectedGuild)
+        LeoGuildManager.UpdateMemberRank(guildId, member)
+
+        LeoGuildManagerMemberList:RefreshData()
+        zo_callLater(demoteNext, 1000)
+        return
+    end
+end
+
+local function kickNext()
+    for i, member in pairs(LeoGuildManager.endangered) do
+        LeoGuildManager.log("Removing " .. member .. " ...")
+        GuildRemove(guildId, member)
+        removeEndangered(member)
+        LeoGuildManager.RemoveMember(member)
+
+        LeoGuildManagerMemberList:RefreshData()
+        zo_callLater(kickNext, 1000)
+        return
+    end
+end
+
+function LeoGuildManager.DemoteAllClick(control, button, upInside)
+
+    local guildId = LeoGuildManager.GetGuildId(LeoGuildManager.globalData.selectedGuild)
+    if not DoesPlayerHaveGuildPermission(guildId, GUILD_PERMISSION_DEMOTE) then
+        LeoGuildManager.log("|cFF2222You don't have permission to demote members of this guild.|r")
+        return
+    end
+
+    local LAM = LibStub("LibAddonMenu-2.0")
+    LAM.util.ShowConfirmationDialog("Confirmation", "Do you really want to demote ALL selected members???", function()
+        zo_callLater(demoteNext, 1000)
+    end)
+end
+
+function LeoGuildManager.KickAllClick(control, button, upInside)
+
+    local guildId = LeoGuildManager.GetGuildId(LeoGuildManager.globalData.selectedGuild)
+
+    if not DoesPlayerHaveGuildPermission(guildId, GUILD_PERMISSION_DEMOTE) then
+        LeoGuildManager.log("|cFF2222You don't have permission to remove members from this guild.|r")
+        return
+    end
+
+    local LAM = LibStub("LibAddonMenu-2.0")
+    LAM.util.ShowConfirmationDialog("Confirmation", "Do you really want to remove ALL selected members from the guild???", function()
+        zo_callLater(kickNext, 1000)
+    end)
+end
+
+function LeoGuildManager.UserClick(control, button, upInside)
+
+    if not upInside or button ~= MOUSE_BUTTON_INDEX_RIGHT then return end
+
     local player = control.data
-    local guildId = LeoGM.GetGuilds(LeoGM.savedVariables.selectedGuild)
-    if type(player) == 'string' and button == MOUSE_BUTTON_INDEX_RIGHT then
+    local guildId = LeoGuildManager.GetGuildId(LeoGuildManager.globalData.selectedGuild)
+    if type(player) == 'string' then
         ClearMenu()
         AddMenuItem(GetString(SI_SOCIAL_LIST_SEND_MESSAGE), function() StartChatInput(nil, CHAT_CHANNEL_WHISPER, player) end)
         AddMenuItem(GetString(SI_SOCIAL_MENU_SEND_MAIL), function() MAIL_SEND:ComposeMailTo(player) end)
         if DoesPlayerHaveGuildPermission(guildId, GUILD_PERMISSION_NOTE_EDIT) then
             -- Soon
-            --AddMenuItem(GetString(SI_SOCIAL_MENU_EDIT_NOTE), LeoGM.EditMemberNote(control, player))
+            --AddMenuItem(GetString(SI_SOCIAL_MENU_EDIT_NOTE), LeoGuildManager.EditMemberNote(control, player))
         end
         if DoesPlayerHaveGuildPermission(guildId, GUILD_PERMISSION_DEMOTE) then
             AddMenuItem(GetString(SI_GUILD_DEMOTE), function() GuildDemote(guildId, player) end)
@@ -49,27 +223,73 @@ function LeoGM.UserClick(control, button, upInside)
     end
 end

-function LeoGMMemberList:SetupEntry(control, data)
+function LeoGuildManager.ClearSelected(control, button, upInside)
+    local content = LeoGuildManagerWindowPurgePanelMemberScrollListContents
+    for i = 1, content:GetNumChildren() do
+        local row = content:GetChild(i)
+        local checkbox = GetControl(row, "Checkbox")
+        if checkbox.isEndangered == true then
+            local member = checkbox.data
+            removeEndangered(member, checkbox)
+        end
+    end
+    updateEndangered()
+end
+
+function LeoGuildManager.CheckboxClick(control, button, upInside)
+
+    if not upInside or button ~= MOUSE_BUTTON_INDEX_LEFT then return end
+
+    local member = control.data
+
+    if not LeoGuildManager.IsEndangered(member) then
+        LeoGuildManager.SetEndangered(member, true)
+        table.insert(LeoGuildManager.endangered, member)
+        control:SetTexture('/esoui/art/buttons/checkbox_checked.dds')
+        control.isEndangered = true
+    else
+        removeEndangered(member, control)
+    end
+
+    updateEndangered()
+end
+
+function LeoGuildManagerMemberList:SetupEntry(control, data)

     control.data = data

+    control.checkbox = GetControl(control, "Checkbox")
+    if data.endangered then
+        control.checkbox:SetTexture('/esoui/art/buttons/checkbox_checked.dds')
+    else
+        control.checkbox:SetTexture('/esoui/art/buttons/checkbox_unchecked.dds')
+    end
+    control.checkbox.data = data.name
+
     control.name = GetControl(control, "Name")
     control.name:SetText(data.name)
     control.name.data = data.name

     control.deposits = GetControl(control, "Deposits")
-    control.deposits:SetText(LeoGM.formatNumber(data.deposits))
+    control.deposits:SetText(LeoGuildManager.formatNumber(data.deposits))

     control.sales = GetControl(control, "Sales")
-    control.sales:SetText(LeoGM.formatNumber(data.sales))
+    control.sales:SetText(LeoGuildManager.formatNumber(data.sales))

     control.online = GetControl(control, "Online")
-    control.online:SetText(LeoGM.GetTime(data.secsSinceLogoff))
+    control.online:SetText(LeoGuildManager.GetTime(data.online))
+
+    control.joined = GetControl(control, "Joined")
+    if data.joined > 0 then
+        control.joined:SetText(LeoGuildManager.FormatTimeAgo(GetTimeStamp() - data.joined))
+    else
+        control.joined:SetText("")
+    end

-    local guildId = LeoGM.GetGuilds(LeoGM.savedVariables.selectedGuild)
+    local guildId = LeoGuildManager.GetGuildId(LeoGuildManager.globalData.selectedGuild)
     local rankName = GetGuildRankCustomName(guildId, data.rankIndex)
-    if rankName:len() == 0 then
-        rankName = "Guildmaster"
+    if rankName == "" then
+        rankName = GetDefaultGuildRankName(guildId, data.rankIndex)
     end
     control.rank = GetControl(control, "Rank")
     control.rank:SetText(rankName)
@@ -77,9 +297,9 @@ function LeoGMMemberList:SetupEntry(control, data)
     ZO_SortFilterList.SetupRow(self, control, data)
 end

-function LeoGMMemberList:BuildMasterList()
+function LeoGuildManagerMemberList:BuildMasterList()
     self.masterList = {}
-    local list = LeoGM.members
+    local list = LeoGuildManager.members
     if list then
         for k, v in ipairs(list) do
             local data = v
@@ -88,19 +308,54 @@ function LeoGMMemberList:BuildMasterList()
     end
 end

-function LeoGMMemberList:SortScrollList()
+function LeoGuildManagerMemberList:SortScrollList()
     local scrollData = ZO_ScrollList_GetDataList(self.list)
     table.sort(scrollData, self.sortFunction)
 end

-function LeoGMMemberList:FilterScrollList()
+function LeoGuildManagerMemberList:ColorRow(control, data, mouseIsOver)
+
+    local guildData = LeoGuildManager.globalData.settings.guilds[LeoGuildManager.globalData.selectedGuild]
+
+    local ticketsThreshold = guildData.tickets * 1000
+    local salesThreshold = guildData.sales * 1000
+    local offlineThreshold = guildData.inactivity * LeoGuildManager.SECONDS_IN_DAY
+
+    local child = GetControl(control, "Name")
+    child:SetColor(1,1,1)
+
+    child = GetControl(control, "Deposits")
+    child:SetColor(0,1,0)
+    if ticketsThreshold > 0 and data.deposits < ticketsThreshold then
+        child:SetColor(1,0,0)
+    end
+
+    child = GetControl(control, "Sales")
+    child:SetColor(0,1,0)
+    if salesThreshold > 0 and data.sales < salesThreshold then
+        child:SetColor(1,0,0)
+    end
+
+    child = GetControl(control, "Online")
+    child:SetColor(0,1,0)
+    if offlineThreshold > 0 and data.online > 0 and data.online > offlineThreshold then
+        child:SetColor(1,0,0)
+    end
+end
+
+function LeoGuildManagerMemberList:FilterScrollList()

     local scrollData = ZO_ScrollList_GetDataList(self.list)
     ZO_ClearNumericallyIndexedTable(scrollData)

-    local ticketsThreshold = LeoGMWindowRulesPanelTicketsSlider:GetValue() * 1000
-    local salesThreshold = LeoGMWindowRulesPanelSalesSlider:GetValue() * 1000
-    local offlineThreshold = LeoGMWindowRulesPanelOfflineSlider:GetValue() * 24 * 60 * 60
+    local guildData = LeoGuildManager.globalData.settings.guilds[LeoGuildManager.globalData.selectedGuild]
+
+    local ticketsThreshold = guildData.tickets * 1000
+    local salesThreshold = guildData.sales * 1000
+    local offlineThreshold = guildData.inactivity * LeoGuildManager.SECONDS_IN_DAY
+    local ignoreRank = guildData.ignoreRank
+    local ignoreNew = guildData.ignoreNew * LeoGuildManager.SECONDS_IN_DAY
+    local now = GetTimeStamp()

     for i = 1, #self.masterList do
         local data = self.masterList[i]
@@ -112,276 +367,242 @@ function LeoGMMemberList:FilterScrollList()
         elseif ticketsThreshold > 0 and data.deposits >= ticketsThreshold then
             canAdd = false
         end
-        if not canAdd and offlineThreshold > 0 and data.secsSinceLogoff > offlineThreshold then
+        --Add even if sold a lot??
+        if not canAdd and offlineThreshold > 0 and data.online > 0 and data.online > offlineThreshold then
             canAdd = true
         end
+        --[[ -- Dont add even if dont sell??
         if canAdd and offlineThreshold > 0 and data.secsSinceLogoff <= offlineThreshold then
             canAdd = false
+        end]]
+        if canAdd and ignoreNew > 0 and data.joined > 0 and now - data.joined <= ignoreNew then
+            canAdd = false
         end
-        if canAdd and LeoGM.selectedRank and data.rankIndex <= LeoGM.selectedRank then
+        if canAdd and ignoreRank and ignoreRank > 0 and data.rankIndex <= ignoreRank then
             canAdd = false
         end
         if canAdd then
             table.insert(scrollData, ZO_ScrollList_CreateDataEntry(1, data))
         end
     end
-    LeoGMWindowPurgePanelListResult:SetText("Total: " .. #scrollData)
+    LeoGuildManagerWindowPurgePanelListResult:SetText("Total: " .. #scrollData)
+    LeoGuildManagerWindowPurgePanelEndangeredCounter:SetText("Selected: 0")
 end

-function LeoGM:OnWindowMoveStop()
-    LeoGM.savedVariables.position = {
-        left = LeoGMWindow:GetLeft(),
-        top = LeoGMWindow:GetTop()
-    }
-end
+local function getStartOfDay(relativeDay)
+    relativeDay = relativeDay or 0

-function LeoGM:isHidden()
-    return LeoGM.hidden
-end
+    local timeStamp = GetTimeStamp()
+    local days = math.floor(timeStamp / LeoGuildManager.SECONDS_IN_DAY)

-function LeoGM.RestorePosition()
-    local position = LeoGM.savedVariables.position or { left = 100; top = 100; }
-    local left = position.left
-    local top = position.top
+    timeStamp = days * LeoGuildManager.SECONDS_IN_DAY
+    timeStamp = timeStamp + relativeDay * LeoGuildManager.SECONDS_IN_DAY

-    LeoGMWindow:ClearAnchors()
-    LeoGMWindow:SetAnchor(TOPLEFT, GuiRoot, TOPLEFT, left, top)
+    return timeStamp
 end

-function LeoGM.ShowUI()
-    SCENE_MANAGER:ShowTopLevel(LeoGMWindow)
-    LeoGMWindow:SetHidden(false)
-    LeoGM.hidden = false;
-    if not LeoGM.savedVariables.activeTab then
-        LeoGM.savedVariables.activeTab = "Rules"
+local function getStartOfWeek(relativeWeek, useTradeWeek)
+    relativeWeek = relativeWeek or 0
+
+    local currentTimeStamp = GetTimeStamp()
+    local days = math.floor(currentTimeStamp / LeoGuildManager.SECONDS_IN_DAY)
+    local today = days % 7
+
+    local result = days * LeoGuildManager.SECONDS_IN_DAY
+
+    local past = {}
+    past[0] = 3 * LeoGuildManager.SECONDS_IN_DAY -- Thursday
+    past[1] = 4 * LeoGuildManager.SECONDS_IN_DAY -- Friday
+    past[2] = 5 * LeoGuildManager.SECONDS_IN_DAY -- Saturday
+    past[3] = 6 * LeoGuildManager.SECONDS_IN_DAY -- Sunday
+    past[4] = 0                  -- Monday
+    past[5] = 1 * LeoGuildManager.SECONDS_IN_DAY -- Tuesday
+    past[6] = 2 * LeoGuildManager.SECONDS_IN_DAY -- Wednesday
+
+    -- Monday midnight --
+    result = result - past[today]
+    result = result + relativeWeek * LeoGuildManager.SECONDS_IN_WEEK
+
+    if (useTradeWeek) then
+        -- Adjust to server locations
+        if (GetWorldName() == "EU Megaserver") then
+            -- EU - Sundays 19:00 pm UTC
+            local secondsLeftThisWeek = getStartOfWeek(1) - currentTimeStamp
+            local hoursLeftThisWeek = math.floor(secondsLeftThisWeek / LeoGuildManager.SECONDS_IN_HOUR)
+
+            if (hoursLeftThisWeek < 5) then
+                result = result + LeoGuildManager.SECONDS_IN_WEEK
+            end
+
+            result = result - 5 * LeoGuildManager.SECONDS_IN_HOUR
+        else
+            -- NA/PTS - Mondays 01:00 am UTC
+            local secondsGoneThisWeek = currentTimeStamp - getStartOfWeek(0)
+            local hoursGoneThisWeek = math.floor(secondsGoneThisWeek / LeoGuildManager.SECONDS_IN_HOUR)
+
+            if (hoursGoneThisWeek < 1) then
+                result = result - LeoGuildManager.SECONDS_IN_WEEK
+            end
+
+            result = result + 1 * LeoGuildManager.SECONDS_IN_HOUR
+        end
     end
-    LeoGM.ShowTab(LeoGM.savedVariables.activeTab)
-end

-function LeoGM.HideUI()
-    SCENE_MANAGER:HideTopLevel(LeoGMWindow)
-    LeoGMWindow:SetHidden(true)
-    LeoGM.hidden = true;
+    return result
 end

-function LeoGM.ToggleUI()
-    if LeoGM:isHidden() then
-        LeoGM.ShowUI()
-    else
-        LeoGM.HideUI()
+local function getSalesData(memberName, cycle, start, finish)
+    local guildName = LeoGuildManager.globalData.selectedGuild
+
+    if LeoGuildManager.globalData.settings.integration == LeoGuildManager.integrations[1] then
+        if MasterMerchant and
+                MasterMerchant.guildSales and MasterMerchant.guildSales[guildName] and
+                MasterMerchant.guildSales[guildName].sellers and MasterMerchant.guildSales[guildName].sellers[memberName] and
+                MasterMerchant.guildSales[guildName].sellers[memberName].sales[cycle] then
+            return MasterMerchant.guildSales[guildName].sellers[memberName].sales[cycle]
+        end
+    elseif LeoGuildManager.globalData.settings.integration == LeoGuildManager.integrations[2] then
+        return select(2, ArkadiusTradeTools.Modules.Sales:GetPurchasesAndSalesVolumes(guildName, memberName, start, finish))
     end
+
+    return 0
 end

-function LeoGM.ShowTab(tab)
-    LeoGM.savedVariables.activeTab = tab
-    LeoGMWindowTitle:SetText(LeoGM.displayName .. " v" .. LeoGM.version .. " - " .. tab)
-    local control
-    for _,panel in ipairs(LeoGM.panelList) do
-        control = WINDOW_MANAGER:GetControlByName('LeoGMWindow' .. panel .. 'Panel')
-        control:SetHidden(true)
+local function getStartFinishFromCycle(cycle)
+
+    if cycle == 3 then return getStartOfWeek(0), GetTimeStamp() -- this week
+    elseif cycle == 4 then return getStartOfWeek(-1), getStartOfWeek(0) -- Last week
+    elseif cycle == 5 then return getStartOfWeek(-2), getStartOfWeek(-1) -- Prior week
+    elseif cycle == 8 then return getStartOfDay(-7), GetTimeStamp() -- Last 7 days
+    elseif cycle == 6 then return getStartOfDay(-10), GetTimeStamp() -- Last 10 days
+    elseif cycle == 14 then return getStartOfDay(-14), GetTimeStamp() -- Last 14 days
+    elseif cycle == 7 then return getStartOfDay(-30), GetTimeStamp() -- Last 30 days
     end
-    control = WINDOW_MANAGER:GetControlByName("LeoGMWindow" .. tab .. "Panel")
-    control:SetHidden(false)
-end

-function LeoGMWindow.OnRangeSliderMoved(self)
-    LeoGMWindowPurgePanelRangeValue:SetText(LeoGMWindow.GetRangeText())
-    LeoGMWindow.OnDepositSliderMoved()
-    LeoGMWindow.OnSaleSliderMoved()
+    return nil, nil
 end

-function LeoGMWindow.GetRangeText()
-    local sliderLevel = LeoGMWindowPurgePanelRangeSlider:GetValue()
-    if sliderLevel == 1 then
-        sliderLevel = sliderLevel .. " day"
-    else
-        sliderLevel = sliderLevel .. " days"
-    end
-    return sliderLevel
+function LeoGuildManager.ClearList()
+    LeoGuildManager.members = {}
+    LeoGuildManagerMemberList:RefreshData()
+    LeoGuildManagerWindowPurgePanelDemoteButton:SetEnabled(false)
+    LeoGuildManagerWindowPurgePanelKickButton:SetEnabled(false)
 end

-function LeoGMWindow.OnTicketsSliderMoved()
-    local sliderLevel = LeoGMWindowRulesPanelTicketsSlider:GetValue()
-    LeoGM.savedVariables.rules[LeoGM.savedVariables.selectedGuild].tickets = sliderLevel
-    if sliderLevel > 0 then
-        sliderLevel = sliderLevel .. "k"
-    else
-        sliderLevel = "disabled"
+function LeoGuildManager.ListPurge()
+    local guildId = LeoGuildManager.GetGuildId(LeoGuildManager.globalData.selectedGuild)
+    local guildName = LeoGuildManager.globalData.selectedGuild
+
+    if not DoesPlayerHaveGuildPermission(guildId, GUILD_PERMISSION_BANK_VIEW_GOLD) then
+        LeoGuildManager.log("|cFF2222You don't have permission to scan guild gold deposits.|r")
+        return
     end
-    LeoGMWindowRulesPanelTicketsValue:SetText(sliderLevel)
-    LeoGMWindow.UpdateRequirementsDesc()
-end

-function LeoGMWindow.OnSalesSliderMoved()
-    local sliderLevel = LeoGMWindowRulesPanelSalesSlider:GetValue()
-    LeoGM.savedVariables.rules[LeoGM.savedVariables.selectedGuild].sales = sliderLevel
-    if sliderLevel > 0 then
-        sliderLevel = sliderLevel .. "k"
-    else
-        sliderLevel = "disabled"
+    if LeoGuildManager.globalData.settings.integration == LeoGuildManager.integrations[1] and (MasterMerchant == nil or
+        MasterMerchant.guildSales == nil or
+        MasterMerchant.guildSales[guildName] == nil or
+        MasterMerchant.guildSales[guildName].sellers == nil)
+    then
+        LeoGuildManager.log("|cFF2222MasterMerchant sales data not found for this guild.|r Try again in a few moments if it is still initializing.")
+        return
     end
-    LeoGMWindowRulesPanelSalesValue:SetText(sliderLevel)
-    LeoGMWindow.UpdateRequirementsDesc()
-end

-function LeoGMWindow.OnOfflineSliderMoved()
-    local sliderLevel = LeoGMWindowRulesPanelOfflineSlider:GetValue()
-    LeoGM.savedVariables.rules[LeoGM.savedVariables.selectedGuild].offline = sliderLevel
-    if sliderLevel > 0 then
-        sliderLevel = sliderLevel .. " days"
-    else
-        sliderLevel = "disabled"
+    if LeoGuildManager.globalData.settings.integration == LeoGuildManager.integrations[2] and (ArkadiusTradeTools == nil or
+            ArkadiusTradeTools.Modules == nil or
+            ArkadiusTradeTools.Modules.Sales == nil)
+    then
+        LeoGuildManager.log("|cFF2222ArkadiusTradeTools sales not found for this guild.|r Try again in a few moments if it is still initializing.")
+        return
     end
-    LeoGMWindowRulesPanelOfflineValue:SetText(sliderLevel)
-    LeoGMWindow.UpdateRequirementsDesc()
-end

-function LeoGMWindow.UpdateRequirementsDesc()
-    local tickets = LeoGMWindowRulesPanelTicketsSlider:GetValue()
-    local sales = LeoGMWindowRulesPanelSalesSlider:GetValue()
-    local offline = LeoGMWindowRulesPanelOfflineSlider:GetValue()
-    local descRule = ''
-    local descPurge = 'List members'
+    local cycle = LeoGuildManager.globalData.settings.guilds[guildName].cycle
+    local start, finish = getStartFinishFromCycle(cycle)

-    if tickets > 0 and sales > 0 then
-        descRule = "At least " .. tickets .. "k in raffle tickets OR " .. sales .. "k sales per week"
-        descPurge = descPurge .. " with less than " .. tickets .. "k in raffle tickets OR " .. sales .. "k sales last 7 days"
-    elseif tickets > 0 then
-        descRule = "At least " .. tickets .. "k in raffle tickets per week"
-        descPurge = descPurge .. " with less than " .. tickets .. "k in raffle tickets last 7 days"
-    elseif sales > 0 then
-        descRule = "At least " .. sales .. "k sales per week"
-        descPurge = descPurge .. " with less than " .. sales .. "k sales last 7 days"
+    if start == nil or finish == nil then
+        LeoGuildManager.log("Error fetching cycle settings.")
     end

-    if offline > 0 then
-        if tickets > 0 or sales > 0 then descRule = descRule .. " AND " end
-        descRule = descRule .. "Inactivity policy of " .. offline .. " days"
-        descPurge = descPurge .. ". Inactive for more than " .. offline .. " days"
-    end
-    LeoGMWindowRulesPanelRequirementsDesc:SetText(descRule)
-    LeoGMWindowPurgePanelPurgeDesc:SetText(descPurge)
-end
+    if LeoGuildManager.scanData[guildName] == nil or LeoGuildManager.scanData[guildName][GUILD_HISTORY_BANK] == nil then return end

-function LeoGM.ListPurge()
-    local guildId = LeoGM.GetGuilds(LeoGM.savedVariables.selectedGuild)
+    LeoGuildManager.GetGuildMembers(guildId)

-    if not DoesPlayerHaveGuildPermission(guildId, GUILD_PERMISSION_BANK_VIEW_GOLD) then
-        LeoGMWindowPurgePanelListResult:SetText("|cFF2222You don't have permission to scan guild gold deposits.|r")
-        return
+    for _, event in pairs(LeoGuildManager.scanData[guildName][GUILD_HISTORY_BANK].events) do
+        if event.type == GUILD_EVENT_BANKGOLD_ADDED and event.timeStamp >= start and event.timeStamp <= finish then
+            LeoGuildManager.AddDeposit(event.member, event.gold)
+        end
     end
-    if MasterMerchant == nil or
-        MasterMerchant.guildSales[LeoGM.savedVariables.selectedGuild] == nil or
-        MasterMerchant.guildSales[LeoGM.savedVariables.selectedGuild].sellers == nil
-    then
-        LeoGMWindowPurgePanelListResult:SetText("|cFF2222MasterMerchant sales data not found for this guild.|r Try again in a few moments if it is still initializing.")
-        return
+
+    for i = 1, #LeoGuildManager.members do
+        local name = LeoGuildManager.members[i].name
+        local sale = getSalesData(name, cycle, start, finish)
+        if sale > 0 then
+            LeoGuildManager.AddSale(name, sale)
+        end
+        if LeoGuildManager.scanData[guildName].members[name] ~= nil and
+                LeoGuildManager.scanData[guildName].members[name].joined ~= nil then
+            LeoGuildManager.SetJoined(name, LeoGuildManager.scanData[guildName].members[name].joined)
+        end
     end
-    LeoGM.GetGuildMembers(guildId)
-    local scanSince = GetTimeStamp() - 7 * 24 * 60 * 60
-    LeoGM.BuildDepositsHistory(guildId, scanSince)
+    LeoGuildManagerMemberList:RefreshData()
 end

-function LeoGMWindow.OnBlacklistChanged(displayName, text)
-    LeoGM.savedVariables.rules[LeoGM.savedVariables.selectedGuild].blacklist = {}
+function LeoGuildManagerWindow.OnBlacklistChanged(guildId, guild, text)
+    LeoGuildManager.globalData.settings.guilds[guild].blacklist = {}
     for line in string.gmatch( text, "[^\r\n]+" ) do
         if line ~= nil and line:len() > 1 then
-            table.insert(LeoGM.savedVariables.rules[LeoGM.savedVariables.selectedGuild].blacklist, line)
+            table.insert(LeoGuildManager.globalData.settings.guilds[guild].blacklist, line)
         end
     end
-    LeoGMWindowRulesPanelBlacklistList:SetText(table.concat(LeoGM.savedVariables.rules[LeoGM.savedVariables.selectedGuild].blacklist, ", "))
+    local label = WINDOW_MANAGER:GetControlByName("LeoGuildManagerSettingsBlacklistLabel" .. guildId)
+    label.data.text = "Members: " .. table.concat(LeoGuildManager.globalData.settings.guilds[guild].blacklist, ", ")
+    label:UpdateValue()
 end

-function LeoGM.InitializeUI()
-    LeoGMWindowTitle:SetText(LeoGM.displayName .. " v" .. LeoGM.version)
+function LeoGuildManagerUI.InitializeUI()
+    LeoGuildManagerWindowTitle:SetText(LeoGuildManager.displayName .. " v" .. LeoGuildManager.version)

-    local LeoGMWindowGuildDropdown = CreateControlFromVirtual('LeoGMWindowGuildDropdown', LeoGMWindow, 'LeoGMWindowDropdown')
-    LeoGMWindowGuildDropdown:SetDimensions(200,35)
-    LeoGMWindowGuildDropdown:SetAnchor(LEFT, LeoGMWindowHeaderBG, RIGHT, -204, 0)
-    LeoGMWindowGuildDropdown.m_comboBox:SetSortsItems(false)
-    local guildDropdown = ZO_ComboBox_ObjectFromContainer(LeoGMWindowGuildDropdown)
+    local LeoGuildManagerWindowGuildDropdown = CreateControlFromVirtual('LeoGuildManagerWindowGuildDropdown', LeoGuildManagerWindow, 'LeoGuildManagerWindowDropdown')
+    LeoGuildManagerWindowGuildDropdown:SetDimensions(200,35)
+    LeoGuildManagerWindowGuildDropdown:SetAnchor(LEFT, LeoGuildManagerWindowHeaderBG, RIGHT, -204, 0)
+    LeoGuildManagerWindowGuildDropdown.m_comboBox:SetSortsItems(false)
+    local guildDropdown = ZO_ComboBox_ObjectFromContainer(LeoGuildManagerWindowGuildDropdown)
     guildDropdown:ClearItems()

     local defaultItem
-    for _, guildName in pairs(LeoGM.guilds) do
-        local guildEntry = guildDropdown:CreateItemEntry(guildName, function()
-            LeoGM.savedVariables.selectedGuild = guildName
-
-            LeoGMWindowRulesPanelTicketsSlider:SetValue(LeoGM.savedVariables.rules[LeoGM.savedVariables.selectedGuild].tickets)
-            LeoGMWindowRulesPanelSalesSlider:SetValue(LeoGM.savedVariables.rules[LeoGM.savedVariables.selectedGuild].sales)
-            LeoGMWindowRulesPanelOfflineSlider:SetValue(LeoGM.savedVariables.rules[LeoGM.savedVariables.selectedGuild].offline)
-            LeoGMWindowRulesPanelBlacklistList:SetText(table.concat(LeoGM.savedVariables.rules[LeoGM.savedVariables.selectedGuild].blacklist, ", "))
-
-            local guildId = LeoGM.GetGuilds(guildName)
-            local rankDropdown = ZO_ComboBox_ObjectFromContainer(LeoGMWindowRankDropdown)
-            rankDropdown:ClearItems()
-            rankDropdown:AddItem(rankDropdown:CreateItemEntry(" "))
-            for i = 1, GetNumGuildRanks(guildId) do
-                local rankName = GetGuildRankCustomName(guildId, i)
-                if rankName:len() == 0 then
-                    rankName = "Guildmaster"
+    for _, guildName in pairs(LeoGuildManager.guilds) do
+        if LeoGuildManager.globalData.settings.guilds[guildName].enabled == true then
+            local guildEntry = guildDropdown:CreateItemEntry(guildName, function()
+                LeoGuildManager.globalData.selectedGuild = guildName
+                if LeoGuildManager.globalData.activeTab == LeoGuildManager.TAB_PURGE then
+                    local descPurge = LeoGuildManager.CreatePurgeDescription(guildName)
+                    LeoGuildManagerWindowPurgePanelPurgeDesc:SetText("|c"..LeoGuildManager.color.hex.yellow..descPurge.."|r")
                 end
-                local rankEntry = rankDropdown:CreateItemEntry(rankName, function()
-                    LeoGM.selectedRank = i
-                    LeoGMWindow.UpdateRequirementsDesc()
-                    local desc = LeoGMWindowPurgePanelPurgeDesc:GetText()
-                    if rankName:len() > 0 then
-                        desc = desc .. ". Ignore members with rank " .. rankName .. " or higher"
-                    end
-                    LeoGMWindowPurgePanelPurgeDesc:SetText(desc)
-                end)
-                rankDropdown:AddItem(rankEntry)
+            end)
+            if not LeoGuildManager.globalData.selectedGuild then
+                LeoGuildManager.globalData.selectedGuild = guildName
             end
-        end)
-        if not LeoGM.savedVariables.rules[guildName] then
-            LeoGM.savedVariables.rules[guildName] = {
-                tickets = 0,
-                sales = 0,
-                offline = 0,
-                blacklist = {}
-            }
-        elseif not LeoGM.savedVariables.rules[guildName].blacklist then
-            LeoGM.savedVariables.rules[guildName].blacklist = {}
-        end
-        if not LeoGM.savedVariables.selectedGuild then
-            LeoGM.savedVariables.selectedGuild = guildName
-        end
-        if LeoGM.savedVariables.selectedGuild and LeoGM.savedVariables.selectedGuild == guildName then
-            defaultItem = guildEntry
+            if LeoGuildManager.globalData.selectedGuild and LeoGuildManager.globalData.selectedGuild == guildName then
+                defaultItem = guildEntry
+            end
+            guildDropdown:AddItem(guildEntry)
         end
-        guildDropdown:AddItem(guildEntry)
     end

-    local LeoGMWindowRankDropdown = CreateControlFromVirtual('LeoGMWindowRankDropdown', LeoGMWindowPurgePanel, 'LeoGMWindowDropdown')
-    LeoGMWindowRankDropdown:SetDimensions(270,35)
-    LeoGMWindowRankDropdown:SetAnchor(LEFT, LeoGMWindowPurgePanelRankLabel, RIGHT, 10, 0)
-    LeoGMWindowRankDropdown.m_comboBox:SetSortsItems(false)
-
     if defaultItem ~= nil then
         guildDropdown:SelectItem(defaultItem)
     end

-    if LeoGM.savedVariables.selectedGuild then
-        LeoGMWindowRulesPanelTicketsSlider:SetValue(LeoGM.savedVariables.rules[LeoGM.savedVariables.selectedGuild].tickets)
-        LeoGMWindowRulesPanelSalesSlider:SetValue(LeoGM.savedVariables.rules[LeoGM.savedVariables.selectedGuild].sales)
-        LeoGMWindowRulesPanelOfflineSlider:SetValue(LeoGM.savedVariables.rules[LeoGM.savedVariables.selectedGuild].offline)
-        LeoGMWindowRulesPanelBlacklistList:SetText(table.concat(LeoGM.savedVariables.rules[LeoGM.savedVariables.selectedGuild].blacklist, ", "))
-    end
+    LeoGuildManager.memberScroll = LeoGuildManagerMemberList:New(LeoGuildManagerWindowPurgePanelMemberScroll)

-    LeoGM.memberScroll = LeoGMMemberList:New(LeoGMWindowPurgePanelMemberScroll) -- check
+    local descPurge = LeoGuildManager.CreatePurgeDescription(LeoGuildManager.globalData.selectedGuild)
+    if descPurge ~= nil then
+        LeoGuildManagerWindowPurgePanelPurgeDesc:SetText("|c"..LeoGuildManager.color.hex.yellow..descPurge.."|r")
+    end

-    LeoGMWindowPurgePanelLoadingIcon.animation = ANIMATION_MANAGER:CreateTimelineFromVirtual('LoadIconAnimation', LeoGMWindowPurgePanelLoadingIcon)
+    LeoGuildManagerWindowPurgePanelLoadingIcon.animation = ANIMATION_MANAGER:CreateTimelineFromVirtual('LoadIconAnimation', LeoGuildManagerWindowPurgePanelLoadingIcon)
 end

-function LeoGMWindow.OnBlackListClick()
-    ClearMenu()
-    ZO_Dialogs_ShowDialog("EDIT_NOTE", {
-        displayName = "One UserID per line",
-        note = table.concat(LeoGM.savedVariables.rules[LeoGM.savedVariables.selectedGuild].blacklist, "\n"),
-        changedCallback = LeoGMWindow.OnBlacklistChanged
-    })
-end
-function LeoGM.GetTime(seconds)
+function LeoGuildManager.GetTime(seconds)
     if seconds and seconds > 0 then
         local ss = seconds % 60
         local mm = math.floor(seconds / 60)
diff --git a/Libs/LibAddonMenu-2.0/LICENSE b/Libs/LibAddonMenu-2.0/LICENSE
new file mode 100644
index 0000000..f69cbd4
--- /dev/null
+++ b/Libs/LibAddonMenu-2.0/LICENSE
@@ -0,0 +1,201 @@
+               The Artistic License 2.0
+
+           Copyright (c) 2016 Ryan Lakanen (Seerah)
+
+     Everyone is permitted to copy and distribute verbatim copies
+      of this license document, but changing it is not allowed.
+
+Preamble
+
+This license establishes the terms under which a given free software
+Package may be copied, modified, distributed, and/or redistributed.
+The intent is that the Copyright Holder maintains some artistic
+control over the development of that Package while still keeping the
+Package available as open source and free software.
+
+You are always permitted to make arrangements wholly outside of this
+license directly with the Copyright Holder of a given Package.  If the
+terms of this license do not permit the full use that you propose to
+make of the Package, you should contact the Copyright Holder and seek
+a different licensing arrangement.
+
+Definitions
+
+    "Copyright Holder" means the individual(s) or organization(s)
+    named in the copyright notice for the entire Package.
+
+    "Contributor" means any party that has contributed code or other
+    material to the Package, in accordance with the Copyright Holder's
+    procedures.
+
+    "You" and "your" means any person who would like to copy,
+    distribute, or modify the Package.
+
+    "Package" means the collection of files distributed by the
+    Copyright Holder, and derivatives of that collection and/or of
+    those files. A given Package may consist of either the Standard
+    Version, or a Modified Version.
+
+    "Distribute" means providing a copy of the Package or making it
+    accessible to anyone else, or in the case of a company or
+    organization, to others outside of your company or organization.
+
+    "Distributor Fee" means any fee that you charge for Distributing
+    this Package or providing support for this Package to another
+    party.  It does not mean licensing fees.
+
+    "Standard Version" refers to the Package if it has not been
+    modified, or has been modified only in ways explicitly requested
+    by the Copyright Holder.
+
+    "Modified Version" means the Package, if it has been changed, and
+    such changes were not explicitly requested by the Copyright
+    Holder.
+
+    "Original License" means this Artistic License as Distributed with
+    the Standard Version of the Package, in its current version or as
+    it may be modified by The Perl Foundation in the future.
+
+    "Source" form means the source code, documentation source, and
+    configuration files for the Package.
+
+    "Compiled" form means the compiled bytecode, object code, binary,
+    or any other form resulting from mechanical transformation or
+    translation of the Source form.
+
+
+Permission for Use and Modification Without Distribution
+
+(1)  You are permitted to use the Standard Version and create and use
+Modified Versions for any purpose without restriction, provided that
+you do not Distribute the Modified Version.
+
+
+Permissions for Redistribution of the Standard Version
+
+(2)  You may Distribute verbatim copies of the Source form of the
+Standard Version of this Package in any medium without restriction,
+either gratis or for a Distributor Fee, provided that you duplicate
+all of the original copyright notices and associated disclaimers.  At
+your discretion, such verbatim copies may or may not include a
+Compiled form of the Package.
+
+(3)  You may apply any bug fixes, portability changes, and other
+modifications made available from the Copyright Holder.  The resulting
+Package will still be considered the Standard Version, and as such
+will be subject to the Original License.
+
+
+Distribution of Modified Versions of the Package as Source
+
+(4)  You may Distribute your Modified Version as Source (either gratis
+or for a Distributor Fee, and with or without a Compiled form of the
+Modified Version) provided that you clearly document how it differs
+from the Standard Version, including, but not limited to, documenting
+any non-standard features, executables, or modules, and provided that
+you do at least ONE of the following:
+
+    (a)  make the Modified Version available to the Copyright Holder
+    of the Standard Version, under the Original License, so that the
+    Copyright Holder may include your modifications in the Standard
+    Version.
+
+    (b)  ensure that installation of your Modified Version does not
+    prevent the user installing or running the Standard Version. In
+    addition, the Modified Version must bear a name that is different
+    from the name of the Standard Version.
+
+    (c)  allow anyone who receives a copy of the Modified Version to
+    make the Source form of the Modified Version available to others
+    under
+
+    (i)  the Original License or
+
+    (ii)  a license that permits the licensee to freely copy,
+    modify and redistribute the Modified Version using the same
+    licensing terms that apply to the copy that the licensee
+    received, and requires that the Source form of the Modified
+    Version, and of any works derived from it, be made freely
+    available in that license fees are prohibited but Distributor
+    Fees are allowed.
+
+
+Distribution of Compiled Forms of the Standard Version
+or Modified Versions without the Source
+
+(5)  You may Distribute Compiled forms of the Standard Version without
+the Source, provided that you include complete instructions on how to
+get the Source of the Standard Version.  Such instructions must be
+valid at the time of your distribution.  If these instructions, at any
+time while you are carrying out such distribution, become invalid, you
+must provide new instructions on demand or cease further distribution.
+If you provide valid instructions or cease distribution within thirty
+days after you become aware that the instructions are invalid, then
+you do not forfeit any of your rights under this license.
+
+(6)  You may Distribute a Modified Version in Compiled form without
+the Source, provided that you comply with Section 4 with respect to
+the Source of the Modified Version.
+
+
+Aggregating or Linking the Package
+
+(7)  You may aggregate the Package (either the Standard Version or
+Modified Version) with other packages and Distribute the resulting
+aggregation provided that you do not charge a licensing fee for the
+Package.  Distributor Fees are permitted, and licensing fees for other
+components in the aggregation are permitted. The terms of this license
+apply to the use and Distribution of the Standard or Modified Versions
+as included in the aggregation.
+
+(8) You are permitted to link Modified and Standard Versions with
+other works, to embed the Package in a larger work of your own, or to
+build stand-alone binary or bytecode versions of applications that
+include the Package, and Distribute the result without restriction,
+provided the result does not expose a direct interface to the Package.
+
+
+Items That are Not Considered Part of a Modified Version
+
+(9) Works (including, but not limited to, modules and scripts) that
+merely extend or make use of the Package, do not, by themselves, cause
+the Package to be a Modified Version.  In addition, such works are not
+considered parts of the Package itself, and are not subject to the
+terms of this license.
+
+
+General Provisions
+
+(10)  Any use, modification, and distribution of the Standard or
+Modified Versions is governed by this Artistic License. By using,
+modifying or distributing the Package, you accept this license. Do not
+use, modify, or distribute the Package, if you do not accept this
+license.
+
+(11)  If your Modified Version has been derived from a Modified
+Version made by someone other than you, you are nevertheless required
+to ensure that your Modified Version complies with the requirements of
+this license.
+
+(12)  This license does not grant you the right to use any trademark,
+service mark, tradename, or logo of the Copyright Holder.
+
+(13)  This license includes the non-exclusive, worldwide,
+free-of-charge patent license to make, have made, use, offer to sell,
+sell, import and otherwise transfer the Package with respect to any
+patent claims licensable by the Copyright Holder that are necessarily
+infringed by the Package. If you institute patent litigation
+(including a cross-claim or counterclaim) against any party alleging
+that the Package constitutes direct or contributory patent
+infringement, then this Artistic License to you shall terminate on the
+date that such litigation is filed.
+
+(14)  Disclaimer of Warranty:
+THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS
+IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES. THE IMPLIED
+WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR
+NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY YOUR LOCAL
+LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR CONTRIBUTOR WILL
+BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
+DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE, EVEN IF
+ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/Libs/LibAddonMenu-2.0/LibAddonMenu-2.0.lua b/Libs/LibAddonMenu-2.0/LibAddonMenu-2.0.lua
new file mode 100644
index 0000000..7a88896
--- /dev/null
+++ b/Libs/LibAddonMenu-2.0/LibAddonMenu-2.0.lua
@@ -0,0 +1,1248 @@
+-- LibAddonMenu-2.0 & its files © Ryan Lakanen (Seerah)         --
+-- Distributed under The Artistic License 2.0 (see LICENSE)     --
+------------------------------------------------------------------
+
+
+--Register LAM with LibStub
+local MAJOR, MINOR = "LibAddonMenu-2.0", 26
+local lam, oldminor = LibStub:NewLibrary(MAJOR, MINOR)
+if not lam then return end --the same or newer version of this lib is already loaded into memory
+
+local messages = {}
+local MESSAGE_PREFIX = "[LAM2] "
+local function PrintLater(msg)
+    if CHAT_SYSTEM.primaryContainer then
+        d(MESSAGE_PREFIX .. msg)
+    else
+        messages[#messages + 1] = msg
+    end
+end
+
+local function FlushMessages()
+    for i = 1, #messages do
+        d(MESSAGE_PREFIX .. messages[i])
+    end
+    messages = {}
+end
+
+if LAMSettingsPanelCreated and not LAMCompatibilityWarning then
+    PrintLater("An old version of LibAddonMenu with compatibility issues was detected. For more information on how to proceed search for LibAddonMenu on esoui.com")
+    LAMCompatibilityWarning = true
+end
+
+--UPVALUES--
+local wm = WINDOW_MANAGER
+local em = EVENT_MANAGER
+local sm = SCENE_MANAGER
+local cm = CALLBACK_MANAGER
+local tconcat = table.concat
+local tinsert = table.insert
+
+local MIN_HEIGHT = 26
+local HALF_WIDTH_LINE_SPACING = 2
+local OPTIONS_CREATION_RUNNING = 1
+local OPTIONS_CREATED = 2
+local LAM_CONFIRM_DIALOG = "LAM_CONFIRM_DIALOG"
+local LAM_DEFAULTS_DIALOG = "LAM_DEFAULTS"
+local LAM_RELOAD_DIALOG = "LAM_RELOAD_DIALOG"
+
+local addonsForList = {}
+local addonToOptionsMap = {}
+local optionsState = {}
+lam.widgets = lam.widgets or {}
+local widgets = lam.widgets
+lam.util = lam.util or {}
+local util = lam.util
+lam.controlsForReload = lam.controlsForReload or {}
+local controlsForReload = lam.controlsForReload
+
+local function GetDefaultValue(default)
+    if type(default) == "function" then
+        return default()
+    end
+    return default
+end
+
+local function GetStringFromValue(value)
+    if type(value) == "function" then
+        return value()
+    elseif type(value) == "number" then
+        return GetString(value)
+    end
+    return value
+end
+
+local function CreateBaseControl(parent, controlData, controlName)
+    local control = wm:CreateControl(controlName or controlData.reference, parent.scroll or parent, CT_CONTROL)
+    control.panel = parent.panel or parent -- if this is in a submenu, panel is the submenu's parent
+    control.data = controlData
+
+    control.isHalfWidth = controlData.width == "half"
+    local width = 510 -- set default width in case a custom parent object is passed
+    if control.panel.GetWidth ~= nil then width = control.panel:GetWidth() - 60 end
+    control:SetWidth(width)
+    return control
+end
+
+local function CreateLabelAndContainerControl(parent, controlData, controlName)
+    local control = CreateBaseControl(parent, controlData, controlName)
+    local width = control:GetWidth()
+
+    local container = wm:CreateControl(nil, control, CT_CONTROL)
+    container:SetDimensions(width / 3, MIN_HEIGHT)
+    control.container = container
+
+    local label = wm:CreateControl(nil, control, CT_LABEL)
+    label:SetFont("ZoFontWinH4")
+    label:SetHeight(MIN_HEIGHT)
+    label:SetWrapMode(TEXT_WRAP_MODE_ELLIPSIS)
+    label:SetText(GetStringFromValue(controlData.name))
+    control.label = label
+
+    if control.isHalfWidth then
+        control:SetDimensions(width / 2, MIN_HEIGHT * 2 + HALF_WIDTH_LINE_SPACING)
+        label:SetAnchor(TOPLEFT, control, TOPLEFT, 0, 0)
+        label:SetAnchor(TOPRIGHT, control, TOPRIGHT, 0, 0)
+        container:SetAnchor(TOPRIGHT, control.label, BOTTOMRIGHT, 0, HALF_WIDTH_LINE_SPACING)
+    else
+        control:SetDimensions(width, MIN_HEIGHT)
+        container:SetAnchor(TOPRIGHT, control, TOPRIGHT, 0, 0)
+        label:SetAnchor(TOPLEFT, control, TOPLEFT, 0, 0)
+        label:SetAnchor(TOPRIGHT, container, TOPLEFT, 5, 0)
+    end
+
+    control.data.tooltipText = GetStringFromValue(control.data.tooltip)
+    control:SetMouseEnabled(true)
+    control:SetHandler("OnMouseEnter", ZO_Options_OnMouseEnter)
+    control:SetHandler("OnMouseExit", ZO_Options_OnMouseExit)
+    return control
+end
+
+local function GetTopPanel(panel)
+    while panel.panel and panel.panel ~= panel do
+        panel = panel.panel
+    end
+    return panel
+end
+
+local function IsSame(objA, objB)
+    if #objA ~= #objB then return false end
+    for i = 1, #objA do
+        if objA[i] ~= objB[i] then return false end
+    end
+    return true
+end
+
+local function RefreshReloadUIButton()
+    lam.requiresReload = false
+
+    for i = 1, #controlsForReload do
+        local reloadControl = controlsForReload[i]
+        if not IsSame(reloadControl.startValue, {reloadControl.data.getFunc()}) then
+            lam.requiresReload = true
+            break
+        end
+    end
+
+    lam.applyButton:SetHidden(not lam.requiresReload)
+end
+
+local function RequestRefreshIfNeeded(control)
+    -- if our parent window wants to refresh controls, then fire the callback
+    local panel = GetTopPanel(control.panel)
+    local panelData = panel.data
+    if panelData.registerForRefresh then
+        cm:FireCallbacks("LAM-RefreshPanel", control)
+    end
+    RefreshReloadUIButton()
+end
+
+local function RegisterForRefreshIfNeeded(control)
+    -- if our parent window wants to refresh controls, then add this to the list
+    local panel = GetTopPanel(control.panel)
+    local panelData = panel.data
+    if panelData.registerForRefresh or panelData.registerForDefaults then
+        tinsert(panel.controlsToRefresh or {}, control) -- prevent errors on custom panels
+    end
+end
+
+local function RegisterForReloadIfNeeded(control)
+    if control.data.requiresReload then
+        tinsert(controlsForReload, control)
+        control.startValue = {control.data.getFunc()}
+    end
+end
+
+local function GetConfirmDialog()
+    if(not ESO_Dialogs[LAM_CONFIRM_DIALOG]) then
+        ESO_Dialogs[LAM_CONFIRM_DIALOG] = {
+            canQueue = true,
+            title = {
+                text = "",
+            },
+            mainText = {
+                text = "",
+            },
+            buttons = {
+                [1] = {
+                    text = SI_DIALOG_CONFIRM,
+                    callback = function(dialog) end,
+                },
+                [2] = {
+                    text = SI_DIALOG_CANCEL,
+                }
+            }
+        }
+    end
+    return ESO_Dialogs[LAM_CONFIRM_DIALOG]
+end
+
+local function ShowConfirmationDialog(title, body, callback)
+    local dialog = GetConfirmDialog()
+    dialog.title.text = title
+    dialog.mainText.text = body
+    dialog.buttons[1].callback = callback
+    ZO_Dialogs_ShowDialog(LAM_CONFIRM_DIALOG)
+end
+
+local function GetDefaultsDialog()
+    if(not ESO_Dialogs[LAM_DEFAULTS_DIALOG]) then
+        ESO_Dialogs[LAM_DEFAULTS_DIALOG] = {
+            canQueue = true,
+            title = {
+                text = SI_INTERFACE_OPTIONS_RESET_TO_DEFAULT_TOOLTIP,
+            },
+            mainText = {
+                text = SI_OPTIONS_RESET_PROMPT,
+            },
+            buttons = {
+                [1] = {
+                    text = SI_OPTIONS_RESET,
+                    callback = function(dialog) end,
+                },
+                [2] = {
+                    text = SI_DIALOG_CANCEL,
+                }
+            }
+        }
+    end
+    return ESO_Dialogs[LAM_DEFAULTS_DIALOG]
+end
+
+local function ShowDefaultsDialog(panel)
+    local dialog = GetDefaultsDialog()
+    dialog.buttons[1].callback = function()
+        panel:ForceDefaults()
+        RefreshReloadUIButton()
+    end
+    ZO_Dialogs_ShowDialog(LAM_DEFAULTS_DIALOG)
+end
+
+local function DiscardChangesOnReloadControls()
+    for i = 1, #controlsForReload do
+        local reloadControl = controlsForReload[i]
+        if not IsSame(reloadControl.startValue, {reloadControl.data.getFunc()}) then
+            reloadControl:UpdateValue(false, unpack(reloadControl.startValue))
+        end
+    end
+    lam.requiresReload = false
+    lam.applyButton:SetHidden(true)
+end
+
+local function StorePanelForReopening()
+    local saveData = ZO_Ingame_SavedVariables["LAM"] or {}
+    saveData.reopenPanel = lam.currentAddonPanel:GetName()
+    ZO_Ingame_SavedVariables["LAM"] = saveData
+end
+
+local function RetrievePanelForReopening()
+    local saveData = ZO_Ingame_SavedVariables["LAM"]
+    if(saveData) then
+        ZO_Ingame_SavedVariables["LAM"] = nil
+        return _G[saveData.reopenPanel]
+    end
+end
+
+local function HandleReloadUIPressed()
+    StorePanelForReopening()
+    ReloadUI()
+end
+
+local function HandleLoadDefaultsPressed()
+    ShowDefaultsDialog(lam.currentAddonPanel)
+end
+
+local function GetReloadDialog()
+    if(not ESO_Dialogs[LAM_RELOAD_DIALOG]) then
+        ESO_Dialogs[LAM_RELOAD_DIALOG] = {
+            canQueue = true,
+            title = {
+                text = util.L["RELOAD_DIALOG_TITLE"],
+            },
+            mainText = {
+                text = util.L["RELOAD_DIALOG_TEXT"],
+            },
+            buttons = {
+                [1] = {
+                    text = util.L["RELOAD_DIALOG_RELOAD_BUTTON"],
+                    callback = function() ReloadUI() end,
+                },
+                [2] = {
+                    text = util.L["RELOAD_DIALOG_DISCARD_BUTTON"],
+                    callback = DiscardChangesOnReloadControls,
+                }
+            },
+            noChoiceCallback = DiscardChangesOnReloadControls,
+        }
+    end
+    return ESO_Dialogs[LAM_CONFIRM_DIALOG]
+end
+
+local function ShowReloadDialogIfNeeded()
+    if lam.requiresReload then
+        local dialog = GetReloadDialog()
+        ZO_Dialogs_ShowDialog(LAM_RELOAD_DIALOG)
+    end
+end
+
+local function UpdateWarning(control)
+    local warning
+    if control.data.warning ~= nil then
+        warning = util.GetStringFromValue(control.data.warning)
+    end
+
+    if control.data.requiresReload then
+        if not warning then
+            warning = string.format("|cff0000%s", util.L["RELOAD_UI_WARNING"])
+        else
+            warning = string.format("%s\n\n|cff0000%s", warning, util.L["RELOAD_UI_WARNING"])
+        end
+    end
+
+    if not warning then
+        control.warning:SetHidden(true)
+    else
+        control.warning.data = {tooltipText = warning}
+        control.warning:SetHidden(false)
+    end
+end
+
+local localization = {
+    en = {
+        PANEL_NAME = "Addons",
+        AUTHOR = string.format("%s: <<X:1>>", GetString(SI_ADDON_MANAGER_AUTHOR)), -- "Author: <<X:1>>"
+        VERSION = "Version: <<X:1>>",
+        WEBSITE = "Visit Website",
+        PANEL_INFO_FONT = "$(CHAT_FONT)|14|soft-shadow-thin",
+        RELOAD_UI_WARNING = "Changes to this setting require an UI reload in order to take effect.",
+        RELOAD_DIALOG_TITLE = "UI Reload required",
+        RELOAD_DIALOG_TEXT = "Some changes require an UI reload in order to take effect. Do you want to reload now or discard the changes?",
+        RELOAD_DIALOG_RELOAD_BUTTON = "Reload",
+        RELOAD_DIALOG_DISCARD_BUTTON = "Discard",
+    },
+    it = { -- provided by JohnnyKing
+        PANEL_NAME = "Addon",
+        VERSION = "Versione: <<X:1>>",
+        WEBSITE = "Visita il Sitoweb",
+        RELOAD_UI_WARNING = "Cambiare questa impostazione richiede un Ricarica UI al fine che faccia effetto.",
+        RELOAD_DIALOG_TITLE = "Ricarica UI richiesto",
+        RELOAD_DIALOG_TEXT = "Alcune modifiche richiedono un Ricarica UI al fine che facciano effetto. Sei sicuro di voler ricaricare ora o di voler annullare le modifiche?",
+        RELOAD_DIALOG_RELOAD_BUTTON = "Ricarica",
+        RELOAD_DIALOG_DISCARD_BUTTON = "Annulla",
+    },
+    fr = { -- provided by Ayantir
+        PANEL_NAME = "Extensions",
+        WEBSITE = "Visiter le site Web",
+        RELOAD_UI_WARNING = "La modification de ce paramètre requiert un rechargement de l'UI pour qu'il soit pris en compte.",
+        RELOAD_DIALOG_TITLE = "Reload UI requis",
+        RELOAD_DIALOG_TEXT = "Certaines modifications requièrent un rechargement de l'UI pour qu'ils soient pris en compte. Souhaitez-vous recharger l'interface maintenant ou annuler les modifications ?",
+        RELOAD_DIALOG_RELOAD_BUTTON = "Recharger",
+        RELOAD_DIALOG_DISCARD_BUTTON = "Annuler",
+    },
+    de = { -- provided by sirinsidiator
+        PANEL_NAME = "Erweiterungen",
+        WEBSITE = "Webseite besuchen",
+        RELOAD_UI_WARNING = "Änderungen an dieser Option werden erst übernommen nachdem die Benutzeroberfläche neu geladen wird.",
+        RELOAD_DIALOG_TITLE = "Neuladen benötigt",
+        RELOAD_DIALOG_TEXT = "Einige Änderungen werden erst übernommen nachdem die Benutzeroberfläche neu geladen wird. Wollt Ihr sie jetzt neu laden oder die Änderungen verwerfen?",
+        RELOAD_DIALOG_RELOAD_BUTTON = "Neu laden",
+        RELOAD_DIALOG_DISCARD_BUTTON = "Verwerfen",
+    },
+    ru = { -- provided by TERAB1T
+        PANEL_NAME = "Дополнения",
+        VERSION = "Версия: <<X:1>>",
+        WEBSITE = "Посетить сайт",
+        PANEL_INFO_FONT = "RuESO/fonts/Univers57.otf|14|soft-shadow-thin",
+        RELOAD_UI_WARNING = "Для применения этой настройки необходима перезагрузка интерфейса.",
+        RELOAD_DIALOG_TITLE = "Необходима перезагрузка интерфейса",
+        RELOAD_DIALOG_TEXT = "Для применения некоторых изменений необходима перезагрузка интерфейса. Перезагрузить интерфейс сейчас или отменить изменения?",
+        RELOAD_DIALOG_RELOAD_BUTTON = "Перезагрузить",
+        RELOAD_DIALOG_DISCARD_BUTTON = "Отменить изменения",
+    },
+    es = { -- provided by Morganlefai, checked by Kwisatz
+        PANEL_NAME = "Configuración",
+        VERSION = "Versión: <<X:1>>",
+        WEBSITE = "Visita la página web",
+        RELOAD_UI_WARNING = "Cambiar este ajuste recargará la interfaz del usuario.",
+        RELOAD_DIALOG_TITLE = "Requiere recargar la interfaz",
+        RELOAD_DIALOG_TEXT = "Algunos cambios requieren recargar la interfaz para poder aplicarse. Quieres aplicar los cambios y recargar la interfaz?",
+        RELOAD_DIALOG_RELOAD_BUTTON = "Recargar",
+        RELOAD_DIALOG_DISCARD_BUTTON = "Cancelar",
+    },
+    jp = { -- provided by k0ta0uchi
+        PANEL_NAME = "アドオン設定",
+        WEBSITE = "ウェブサイトを見る",
+    },
+    zh = { -- provided by bssthu
+        PANEL_NAME = "插件",
+        VERSION = "版本: <<X:1>>",
+        WEBSITE = "访问网站",
+        PANEL_INFO_FONT = "EsoZh/fonts/univers57.otf|14|soft-shadow-thin",
+    },
+    pl = { -- provided by EmiruTegryfon
+        PANEL_NAME = "Dodatki",
+        VERSION = "Wersja: <<X:1>>",
+        WEBSITE = "Odwiedź stronę",
+        RELOAD_UI_WARNING = "Zmiany będą widoczne po ponownym załadowaniu UI.",
+        RELOAD_DIALOG_TITLE = "Wymagane przeładowanie UI",
+        RELOAD_DIALOG_TEXT = "Niektóre zmiany wymagają ponownego załadowania UI. Czy chcesz teraz ponownie załadować, czy porzucić zmiany?",
+        RELOAD_DIALOG_RELOAD_BUTTON = "Przeładuj",
+        RELOAD_DIALOG_DISCARD_BUTTON = "Porzuć",
+    },
+    kr = { -- provided by p.walker
+        PANEL_NAME = "蝠盜蠨",
+        VERSION = "纄訄: <<X:1>>",
+        WEBSITE = "裹芬襴钸 縩紸",
+        PANEL_INFO_FONT = "EsoKR/fonts/Univers57.otf|14|soft-shadow-thin",
+        RELOAD_UI_WARNING = "襴 茤訕襄 绀溽靘籴 風滼筼 訁袩靘瀰褄靴 UI 苈穜滠遨襴 靄袔革瓈瓤.",
+        RELOAD_DIALOG_TITLE = "UI 苈穜滠遨 靄袔",
+        RELOAD_DIALOG_TEXT = "绀溽瘜 茤訕 謑 UI 苈穜滠遨襄 靄袔穜靘璔 芬靭襴 覈蒵瓈瓤. 诀瀈 苈穜滠遨靘蓜溠蒵瓈灌? 蝄瓈籴 绀溽襄 迨莌靘蓜溠蒵瓈灌?",
+        RELOAD_DIALOG_RELOAD_BUTTON = "苈穜滠遨",
+        RELOAD_DIALOG_DISCARD_BUTTON = "绀溽迨莌",
+    },
+    br = { -- provided by mlsevero
+        PANEL_NAME = "Addons",
+        AUTHOR = string.format("%s: <<X:1>>", GetString(SI_ADDON_MANAGER_AUTHOR)), -- "Autor: <<X:1>>"
+        VERSION = "Versão: <<X:1>>",
+        WEBSITE = "Visite o Website",
+        RELOAD_UI_WARNING = "Mudanças nessa configuração requer a releitura da UI para ter efeito.",
+        RELOAD_DIALOG_TITLE = "Releitura da UI requerida",
+        RELOAD_DIALOG_TEXT = "Algumas mudanças requerem a releitura da UI para ter efeito. Você deseja reler agora ou descartar as mudanças?",
+        RELOAD_DIALOG_RELOAD_BUTTON = "Relê",
+        RELOAD_DIALOG_DISCARD_BUTTON = "Descarta",
+    },
+}
+
+util.L = ZO_ShallowTableCopy(localization[GetCVar("Language.2")] or {}, localization["en"])
+util.GetTooltipText = GetStringFromValue -- deprecated, use util.GetStringFromValue instead
+util.GetStringFromValue = GetStringFromValue
+util.GetDefaultValue = GetDefaultValue
+util.CreateBaseControl = CreateBaseControl
+util.CreateLabelAndContainerControl = CreateLabelAndContainerControl
+util.RequestRefreshIfNeeded = RequestRefreshIfNeeded
+util.RegisterForRefreshIfNeeded = RegisterForRefreshIfNeeded
+util.RegisterForReloadIfNeeded = RegisterForReloadIfNeeded
+util.GetTopPanel = GetTopPanel
+util.ShowConfirmationDialog = ShowConfirmationDialog
+util.UpdateWarning = UpdateWarning
+
+local ADDON_DATA_TYPE = 1
+local RESELECTING_DURING_REBUILD = true
+local USER_REQUESTED_OPEN = true
+
+
+--INTERNAL FUNCTION
+--scrolls ZO_ScrollList `list` to move the row corresponding to `data`
+-- into view (does nothing if there is no such row in the list)
+--unlike ZO_ScrollList_ScrollDataIntoView, this function accounts for
+-- fading near the list's edges - it avoids the fading area by scrolling
+-- a little further than the ZO function
+local function ScrollDataIntoView(list, data)
+    local targetIndex = data.sortIndex
+    if not targetIndex then return end
+
+    local scrollMin, scrollMax = list.scrollbar:GetMinMax()
+    local scrollTop = list.scrollbar:GetValue()
+    local controlHeight = list.uniformControlHeight or list.controlHeight
+    local targetMin = controlHeight * (targetIndex - 1) - 64
+    -- subtracting 64 ain't arbitrary, it's the maximum fading height
+    -- (libraries/zo_templates/scrolltemplates.lua/UpdateScrollFade)
+
+    if targetMin < scrollTop then
+        ZO_ScrollList_ScrollAbsolute(list, zo_max(targetMin, scrollMin))
+    else
+        local listHeight = ZO_ScrollList_GetHeight(list)
+        local targetMax = controlHeight * targetIndex + 64 - listHeight
+
+        if targetMax > scrollTop then
+            ZO_ScrollList_ScrollAbsolute(list, zo_min(targetMax, scrollMax))
+        end
+    end
+end
+
+
+--INTERNAL FUNCTION
+--constructs a string pattern from the text in `searchEdit` control
+-- * metacharacters are escaped, losing their special meaning
+-- * whitespace matches anything (including empty substring)
+--if there is nothing but whitespace, returns nil
+--otherwise returns a filter function, which takes a `data` table argument
+-- and returns true iff `data.filterText` matches the pattern
+local function GetSearchFilterFunc(searchEdit)
+    local text = searchEdit:GetText():lower()
+    local pattern = text:match("(%S+.-)%s*$")
+
+    if not pattern then -- nothing but whitespace
+        return nil
+    end
+
+    -- escape metacharacters, e.g. "ESO-Datenbank.de" => "ESO%-Datenbank%.de"
+    pattern = pattern:gsub("[-*+?^$().[%]%%]", "%%%0")
+
+    -- replace whitespace with "match shortest anything"
+    pattern = pattern:gsub("%s+", ".-")
+
+    return function(data)
+        return data.filterText:lower():find(pattern) ~= nil
+    end
+end
+
+
+--INTERNAL FUNCTION
+--populates `addonList` with entries from `addonsForList`
+-- addonList = ZO_ScrollList control
+-- filter = [optional] function(data)
+local function PopulateAddonList(addonList, filter)
+    local entryList = ZO_ScrollList_GetDataList(addonList)
+    local numEntries = 0
+    local selectedData = nil
+    local selectionIsFinal = false
+
+    ZO_ScrollList_Clear(addonList)
+
+    for i, data in ipairs(addonsForList) do
+        if not filter or filter(data) then
+            local dataEntry = ZO_ScrollList_CreateDataEntry(ADDON_DATA_TYPE, data)
+            numEntries = numEntries + 1
+            data.sortIndex = numEntries
+            entryList[numEntries] = dataEntry
+            -- select the first panel passing the filter, or the currently
+            -- shown panel, but only if it passes the filter as well
+            if selectedData == nil or data.panel == lam.pendingAddonPanel or data.panel == lam.currentAddonPanel then
+                if not selectionIsFinal then
+                    selectedData = data
+                end
+                if data.panel == lam.pendingAddonPanel then
+                    lam.pendingAddonPanel = nil
+                    selectionIsFinal = true
+                end
+            end
+        else
+            data.sortIndex = nil
+        end
+    end
+
+    ZO_ScrollList_Commit(addonList)
+
+    if selectedData then
+        if selectedData.panel == lam.currentAddonPanel then
+            ZO_ScrollList_SelectData(addonList, selectedData, nil, RESELECTING_DURING_REBUILD)
+        else
+            ZO_ScrollList_SelectData(addonList, selectedData, nil)
+        end
+        ScrollDataIntoView(addonList, selectedData)
+    end
+end
+
+
+--METHOD: REGISTER WIDGET--
+--each widget has its version checked before loading,
+--so we only have the most recent one in memory
+--Usage:
+-- widgetType = "string"; the type of widget being registered
+-- widgetVersion = integer; the widget's version number
+LAMCreateControl = LAMCreateControl or {}
+local lamcc = LAMCreateControl
+
+function lam:RegisterWidget(widgetType, widgetVersion)
+    if widgets[widgetType] and widgets[widgetType] >= widgetVersion then
+        return false
+    else
+        widgets[widgetType] = widgetVersion
+        return true
+    end
+end
+
+-- INTERNAL METHOD: hijacks the handlers for the actions in the OptionsWindow layer if not already done
+local function InitKeybindActions()
+    if not lam.keybindsInitialized then
+        lam.keybindsInitialized = true
+        ZO_PreHook(KEYBOARD_OPTIONS, "ApplySettings", function()
+            if lam.currentPanelOpened then
+                if not lam.applyButton:IsHidden() then
+                    HandleReloadUIPressed()
+                end
+                return true
+            end
+        end)
+        ZO_PreHook("ZO_Dialogs_ShowDialog", function(dialogName)
+            if lam.currentPanelOpened and dialogName == "OPTIONS_RESET_TO_DEFAULTS" then
+                if not lam.defaultButton:IsHidden() then
+                    HandleLoadDefaultsPressed()
+                end
+                return true
+            end
+        end)
+    end
+end
+
+-- INTERNAL METHOD: fires the LAM-PanelOpened callback if not already done
+local function OpenCurrentPanel()
+    if lam.currentAddonPanel and not lam.currentPanelOpened then
+        lam.currentPanelOpened = true
+        lam.defaultButton:SetHidden(not lam.currentAddonPanel.data.registerForDefaults)
+        cm:FireCallbacks("LAM-PanelOpened", lam.currentAddonPanel)
+    end
+end
+
+-- INTERNAL METHOD: fires the LAM-PanelClosed callback if not already done
+local function CloseCurrentPanel()
+    if lam.currentAddonPanel and lam.currentPanelOpened then
+        lam.currentPanelOpened = false
+        cm:FireCallbacks("LAM-PanelClosed", lam.currentAddonPanel)
+    end
+end
+
+--METHOD: OPEN TO ADDON PANEL--
+--opens to a specific addon's option panel
+--Usage:
+-- panel = userdata; the panel returned by the :RegisterOptionsPanel method
+local locSettings = GetString(SI_GAME_MENU_SETTINGS)
+function lam:OpenToPanel(panel)
+
+    -- find and select the panel's row in addon list
+
+    local addonList = lam.addonList
+    local selectedData = nil
+
+    for _, addonData in ipairs(addonsForList) do
+        if addonData.panel == panel then
+            selectedData = addonData
+            ScrollDataIntoView(addonList, selectedData)
+            lam.pendingAddonPanel = addonData.panel
+            break
+        end
+    end
+
+    ZO_ScrollList_SelectData(addonList, selectedData)
+    ZO_ScrollList_RefreshVisible(addonList, selectedData)
+
+    local srchEdit = LAMAddonSettingsWindow:GetNamedChild("SearchFilterEdit")
+    srchEdit:Clear()
+
+    -- note that ZO_ScrollList doesn't require `selectedData` to be actually
+    -- present in the list, and that the list will only be populated once LAM
+    -- "Addon Settings" menu entry is selected for the first time
+
+    local function openAddonSettingsMenu()
+        local gameMenu = ZO_GameMenu_InGame.gameMenu
+        local settingsMenu = gameMenu.headerControls[locSettings]
+
+        if settingsMenu then -- an instance of ZO_TreeNode
+            local children = settingsMenu:GetChildren()
+            for i = 1, (children and #children or 0) do
+                local childNode = children[i]
+                local data = childNode:GetData()
+                if data and data.id == lam.panelId then
+                    -- found LAM "Addon Settings" node, yay!
+                    childNode:GetTree():SelectNode(childNode)
+                    break
+                end
+            end
+        end
+    end
+
+    if sm:GetScene("gameMenuInGame"):GetState() == SCENE_SHOWN then
+        openAddonSettingsMenu()
+    else
+        sm:CallWhen("gameMenuInGame", SCENE_SHOWN, openAddonSettingsMenu)
+        sm:Show("gameMenuInGame")
+    end
+end
+
+local TwinOptionsContainer_Index = 0
+local function TwinOptionsContainer(parent, leftWidget, rightWidget)
+    TwinOptionsContainer_Index = TwinOptionsContainer_Index + 1
+    local cParent = parent.scroll or parent
+    local panel = parent.panel or cParent
+    local container = wm:CreateControl("$(parent)TwinContainer" .. tostring(TwinOptionsContainer_Index),
+        cParent, CT_CONTROL)
+    container:SetResizeToFitDescendents(true)
+    container:SetAnchor(select(2, leftWidget:GetAnchor(0) ))
+
+    leftWidget:ClearAnchors()
+    leftWidget:SetAnchor(TOPLEFT, container, TOPLEFT)
+    rightWidget:SetAnchor(TOPLEFT, leftWidget, TOPRIGHT, 5, 0)
+
+    leftWidget:SetWidth( leftWidget:GetWidth() - 2.5 ) -- fixes bad alignment with 'full' controls
+    rightWidget:SetWidth( rightWidget:GetWidth() - 2.5 )
+
+    leftWidget:SetParent(container)
+    rightWidget:SetParent(container)
+
+    container.data = {type = "container"}
+    container.panel = panel
+    return container
+end
+
+--INTERNAL FUNCTION
+--creates controls when options panel is first shown
+--controls anchoring of these controls in the panel
+local function CreateOptionsControls(panel)
+    local addonID = panel:GetName()
+    if(optionsState[addonID] == OPTIONS_CREATED) then
+        return false
+    elseif(optionsState[addonID] == OPTIONS_CREATION_RUNNING) then
+        return true
+    end
+    optionsState[addonID] = OPTIONS_CREATION_RUNNING
+
+    local function CreationFinished()
+        optionsState[addonID] = OPTIONS_CREATED
+        cm:FireCallbacks("LAM-PanelControlsCreated", panel)
+        OpenCurrentPanel()
+    end
+
+    local optionsTable = addonToOptionsMap[addonID]
+    if optionsTable then
+        local function CreateAndAnchorWidget(parent, widgetData, offsetX, offsetY, anchorTarget, wasHalf)
+            local widget
+            local status, err = pcall(function() widget = LAMCreateControl[widgetData.type](parent, widgetData) end)
+            if not status then
+                return err or true, offsetY, anchorTarget, wasHalf
+            else
+                local isHalf = (widgetData.width == "half")
+                if not anchorTarget then -- the first widget in a panel is just placed in the top left corner
+                    widget:SetAnchor(TOPLEFT)
+                    anchorTarget = widget
+                elseif wasHalf and isHalf then -- when the previous widget was only half width and this one is too, we place it on the right side
+                    widget.lineControl = anchorTarget
+                    isHalf = false
+                    offsetY = 0
+                    anchorTarget = TwinOptionsContainer(parent, anchorTarget, widget)
+                else -- otherwise we just put it below the previous one normally
+                    widget:SetAnchor(TOPLEFT, anchorTarget, BOTTOMLEFT, 0, 15)
+                    offsetY = 0
+                    anchorTarget = widget
+                end
+                return false, offsetY, anchorTarget, isHalf
+            end
+        end
+
+        local THROTTLE_TIMEOUT, THROTTLE_COUNT = 10, 20
+        local fifo = {}
+        local anchorOffset, lastAddedControl, wasHalf
+        local CreateWidgetsInPanel, err
+
+        local function PrepareForNextPanel()
+            anchorOffset, lastAddedControl, wasHalf = 0, nil, false
+        end
+
+        local function SetupCreationCalls(parent, widgetDataTable)
+            fifo[#fifo + 1] = PrepareForNextPanel
+            local count = #widgetDataTable
+            for i = 1, count, THROTTLE_COUNT do
+                fifo[#fifo + 1] = function()
+                    CreateWidgetsInPanel(parent, widgetDataTable, i, zo_min(i + THROTTLE_COUNT - 1, count))
+                end
+            end
+            return count ~= NonContiguousCount(widgetDataTable)
+        end
+
+        CreateWidgetsInPanel = function(parent, widgetDataTable, startIndex, endIndex)
+            for i=startIndex,endIndex do
+                local widgetData = widgetDataTable[i]
+                if not widgetData then
+                    PrintLater("Skipped creation of missing entry in the settings menu of " .. addonID .. ".")
+                else
+                    local widgetType = widgetData.type
+                    local offsetX = 0
+                    local isSubmenu = (widgetType == "submenu")
+                    if isSubmenu then
+                        wasHalf = false
+                        offsetX = 5
+                    end
+
+                    err, anchorOffset, lastAddedControl, wasHalf = CreateAndAnchorWidget(parent, widgetData, offsetX, anchorOffset, lastAddedControl, wasHalf)
+                    if err then
+                        PrintLater(("Could not create %s '%s' of %s."):format(widgetData.type, GetStringFromValue(widgetData.name or "unnamed"), addonID))
+                    end
+
+                    if isSubmenu then
+                        if SetupCreationCalls(lastAddedControl, widgetData.controls) then
+                            PrintLater(("The sub menu '%s' of %s is missing some entries."):format(GetStringFromValue(widgetData.name or "unnamed"), addonID))
+                        end
+                    end
+                end
+            end
+        end
+
+        local function DoCreateSettings()
+            if #fifo > 0 then
+                local nextCall = table.remove(fifo, 1)
+                nextCall()
+                if(nextCall == PrepareForNextPanel) then
+                    DoCreateSettings()
+                else
+                    zo_callLater(DoCreateSettings, THROTTLE_TIMEOUT)
+                end
+            else
+                CreationFinished()
+            end
+        end
+
+        if SetupCreationCalls(panel, optionsTable) then
+            PrintLater(("The settings menu of %s is missing some entries."):format(addonID))
+        end
+        DoCreateSettings()
+    else
+        CreationFinished()
+    end
+
+    return true
+end
+
+--INTERNAL FUNCTION
+--handles switching between panels
+local function ToggleAddonPanels(panel) --called in OnShow of newly shown panel
+    local currentlySelected = lam.currentAddonPanel
+    if currentlySelected and currentlySelected ~= panel then
+        currentlySelected:SetHidden(true)
+        CloseCurrentPanel()
+    end
+    lam.currentAddonPanel = panel
+
+    -- refresh visible rows to reflect panel IsHidden status
+    ZO_ScrollList_RefreshVisible(lam.addonList)
+
+    if not CreateOptionsControls(panel) then
+        OpenCurrentPanel()
+    end
+
+    cm:FireCallbacks("LAM-RefreshPanel", panel)
+end
+
+local CheckSafetyAndInitialize
+
+--METHOD: REGISTER ADDON PANEL
+--registers your addon with LibAddonMenu and creates a panel
+--Usage:
+-- addonID = "string"; unique ID which will be the global name of your panel
+-- panelData = table; data object for your panel - see controls\panel.lua
+function lam:RegisterAddonPanel(addonID, panelData)
+    CheckSafetyAndInitialize(addonID)
+    local container = lam:GetAddonPanelContainer()
+    local panel = lamcc.panel(container, panelData, addonID) --addonID==global name of panel
+    panel:SetHidden(true)
+    panel:SetAnchorFill(container)
+    panel:SetHandler("OnShow", ToggleAddonPanels)
+
+    local function stripMarkup(str)
+        return str:gsub("|[Cc]%x%x%x%x%x%x", ""):gsub("|[Rr]", "")
+    end
+
+    local filterParts = {panelData.name, nil, nil}
+    -- append keywords and author separately, the may be nil
+    filterParts[#filterParts + 1] = panelData.keywords
+    filterParts[#filterParts + 1] = panelData.author
+
+    local addonData = {
+        panel = panel,
+        name = stripMarkup(panelData.name),
+        filterText = stripMarkup(tconcat(filterParts, "\t")):lower(),
+    }
+
+    tinsert(addonsForList, addonData)
+
+    if panelData.slashCommand then
+        SLASH_COMMANDS[panelData.slashCommand] = function()
+            lam:OpenToPanel(panel)
+        end
+    end
+
+    return panel --return for authors creating options manually
+end
+
+
+--METHOD: REGISTER OPTION CONTROLS
+--registers the options you want shown for your addon
+--these are stored in a table where each key-value pair is the order
+--of the options in the panel and the data for that control, respectively
+--see exampleoptions.lua for an example
+--see controls\<widget>.lua for each widget type
+--Usage:
+-- addonID = "string"; the same string passed to :RegisterAddonPanel
+-- optionsTable = table; the table containing all of the options controls and their data
+function lam:RegisterOptionControls(addonID, optionsTable) --optionsTable = {sliderData, buttonData, etc}
+    addonToOptionsMap[addonID] = optionsTable
+end
+
+--INTERNAL FUNCTION
+--creates LAM's Addon Settings entry in ZO_GameMenu
+local function CreateAddonSettingsMenuEntry()
+    local panelData = {
+        id = KEYBOARD_OPTIONS.currentPanelId,
+        name = util.L["PANEL_NAME"],
+    }
+
+    KEYBOARD_OPTIONS.currentPanelId = panelData.id + 1
+    KEYBOARD_OPTIONS.panelNames[panelData.id] = panelData.name
+
+    lam.panelId = panelData.id
+
+    local addonListSorted = false
+
+    function panelData.callback()
+        sm:AddFragment(lam:GetAddonSettingsFragment())
+        KEYBOARD_OPTIONS:ChangePanels(lam.panelId)
+
+        local title = LAMAddonSettingsWindow:GetNamedChild("Title")
+        title:SetText(panelData.name)
+
+        if not addonListSorted and #addonsForList > 0 then
+            local searchEdit = LAMAddonSettingsWindow:GetNamedChild("SearchFilterEdit")
+            --we're about to show our list for the first time - let's sort it
+            table.sort(addonsForList, function(a, b) return a.name < b.name end)
+            PopulateAddonList(lam.addonList, GetSearchFilterFunc(searchEdit))
+            addonListSorted = true
+        end
+    end
+
+    function panelData.unselectedCallback()
+        sm:RemoveFragment(lam:GetAddonSettingsFragment())
+        if SetCameraOptionsPreviewModeEnabled then -- available since API version 100011
+            SetCameraOptionsPreviewModeEnabled(false)
+        end
+    end
+
+    ZO_GameMenu_AddSettingPanel(panelData)
+end
+
+
+--INTERNAL FUNCTION
+--creates the left-hand menu in LAM's window
+local function CreateAddonList(name, parent)
+    local addonList = wm:CreateControlFromVirtual(name, parent, "ZO_ScrollList")
+
+    local function addonListRow_OnMouseDown(control, button)
+        if button == 1 then
+            local data = ZO_ScrollList_GetData(control)
+            ZO_ScrollList_SelectData(addonList, data, control)
+        end
+    end
+
+    local function addonListRow_OnMouseEnter(control)
+        ZO_ScrollList_MouseEnter(addonList, control)
+    end
+
+    local function addonListRow_OnMouseExit(control)
+        ZO_ScrollList_MouseExit(addonList, control)
+    end
+
+    local function addonListRow_Select(previouslySelectedData, selectedData, reselectingDuringRebuild)
+        if not reselectingDuringRebuild then
+            if previouslySelectedData then
+                previouslySelectedData.panel:SetHidden(true)
+            end
+            if selectedData then
+                selectedData.panel:SetHidden(false)
+                PlaySound(SOUNDS.MENU_SUBCATEGORY_SELECTION)
+            end
+        end
+    end
+
+    local function addonListRow_Setup(control, data)
+        control:SetText(data.name)
+        control:SetSelected(not data.panel:IsHidden())
+    end
+
+    ZO_ScrollList_AddDataType(addonList, ADDON_DATA_TYPE, "ZO_SelectableLabel", 28, addonListRow_Setup)
+    -- I don't know how to make highlights clear properly; they often
+    -- get stuck and after a while the list is full of highlighted rows
+    --ZO_ScrollList_EnableHighlight(addonList, "ZO_ThinListHighlight")
+    ZO_ScrollList_EnableSelection(addonList, "ZO_ThinListHighlight", addonListRow_Select)
+
+    local addonDataType = ZO_ScrollList_GetDataTypeTable(addonList, ADDON_DATA_TYPE)
+    local addonListRow_CreateRaw = addonDataType.pool.m_Factory
+
+    local function addonListRow_Create(pool)
+        local control = addonListRow_CreateRaw(pool)
+        control:SetHandler("OnMouseDown", addonListRow_OnMouseDown)
+        --control:SetHandler("OnMouseEnter", addonListRow_OnMouseEnter)
+        --control:SetHandler("OnMouseExit", addonListRow_OnMouseExit)
+        control:SetHeight(28)
+        control:SetFont("ZoFontHeader")
+        control:SetHorizontalAlignment(TEXT_ALIGN_LEFT)
+        control:SetVerticalAlignment(TEXT_ALIGN_CENTER)
+        control:SetWrapMode(TEXT_WRAP_MODE_ELLIPSIS)
+        return control
+    end
+
+    addonDataType.pool.m_Factory = addonListRow_Create
+
+    return addonList
+end
+
+
+--INTERNAL FUNCTION
+local function CreateSearchFilterBox(name, parent)
+    local boxControl = wm:CreateControl(name, parent, CT_CONTROL)
+
+    local srchButton =  wm:CreateControl("$(parent)Button", boxControl, CT_BUTTON)
+    srchButton:SetDimensions(32, 32)
+    srchButton:SetAnchor(LEFT, nil, LEFT, 2, 0)
+    srchButton:SetNormalTexture("EsoUI/Art/LFG/LFG_tabIcon_groupTools_up.dds")
+    srchButton:SetPressedTexture("EsoUI/Art/LFG/LFG_tabIcon_groupTools_down.dds")
+    srchButton:SetMouseOverTexture("EsoUI/Art/LFG/LFG_tabIcon_groupTools_over.dds")
+
+    local srchEdit = wm:CreateControlFromVirtual("$(parent)Edit", boxControl, "ZO_DefaultEdit")
+    srchEdit:SetAnchor(LEFT, srchButton, RIGHT, 4, 1)
+    srchEdit:SetAnchor(RIGHT, nil, RIGHT, -4, 1)
+    srchEdit:SetColor(ZO_NORMAL_TEXT:UnpackRGBA())
+
+    local srchBg = wm:CreateControl("$(parent)Bg", boxControl, CT_BACKDROP)
+    srchBg:SetAnchorFill()
+    srchBg:SetAlpha(0)
+    srchBg:SetCenterColor(0, 0, 0, 0.5)
+    srchBg:SetEdgeColor(ZO_DISABLED_TEXT:UnpackRGBA())
+    srchBg:SetEdgeTexture("", 1, 1, 0, 0)
+
+    -- search backdrop should appear whenever you hover over either
+    -- the magnifying glass button or the edit field (which is only
+    -- visible when it contains some text), and also while the edit
+    -- field has keyboard focus
+
+    local srchActive = false
+    local srchHover = false
+
+    local function srchBgUpdateAlpha()
+        if srchActive or srchEdit:HasFocus() then
+            srchBg:SetAlpha(srchHover and 0.8 or 0.6)
+        else
+            srchBg:SetAlpha(srchHover and 0.6 or 0.0)
+        end
+    end
+
+    local function srchMouseEnter(control)
+        srchHover = true
+        srchBgUpdateAlpha()
+    end
+
+    local function srchMouseExit(control)
+        srchHover = false
+        srchBgUpdateAlpha()
+    end
+
+    boxControl:SetMouseEnabled(true)
+    boxControl:SetHitInsets(1, 1, -1, -1)
+    boxControl:SetHandler("OnMouseEnter", srchMouseEnter)
+    boxControl:SetHandler("OnMouseExit", srchMouseExit)
+
+    srchButton:SetHandler("OnMouseEnter", srchMouseEnter)
+    srchButton:SetHandler("OnMouseExit", srchMouseExit)
+
+    local focusLostTime = 0
+
+    srchButton:SetHandler("OnClicked", function(self)
+        srchEdit:Clear()
+        if GetFrameTimeMilliseconds() - focusLostTime < 100 then
+            -- re-focus the edit box if it lost focus due to this
+            -- button click (note that this handler may run a few
+            -- frames later)
+            srchEdit:TakeFocus()
+        end
+    end)
+
+    srchEdit:SetHandler("OnMouseEnter", srchMouseEnter)
+    srchEdit:SetHandler("OnMouseExit", srchMouseExit)
+    srchEdit:SetHandler("OnFocusGained", srchBgUpdateAlpha)
+
+    srchEdit:SetHandler("OnFocusLost", function()
+        focusLostTime = GetFrameTimeMilliseconds()
+        srchBgUpdateAlpha()
+    end)
+
+    srchEdit:SetHandler("OnEscape", function(self)
+        self:Clear()
+        self:LoseFocus()
+    end)
+
+    srchEdit:SetHandler("OnTextChanged", function(self)
+        local filterFunc = GetSearchFilterFunc(self)
+        if filterFunc then
+            srchActive = true
+            srchBg:SetEdgeColor(ZO_SECOND_CONTRAST_TEXT:UnpackRGBA())
+            srchButton:SetState(BSTATE_PRESSED)
+        else
+            srchActive = false
+            srchBg:SetEdgeColor(ZO_DISABLED_TEXT:UnpackRGBA())
+            srchButton:SetState(BSTATE_NORMAL)
+        end
+        srchBgUpdateAlpha()
+        PopulateAddonList(lam.addonList, filterFunc)
+        PlaySound(SOUNDS.SPINNER_DOWN)
+    end)
+
+    return boxControl
+end
+
+
+--INTERNAL FUNCTION
+--creates LAM's Addon Settings top-level window
+local function CreateAddonSettingsWindow()
+    local tlw = wm:CreateTopLevelWindow("LAMAddonSettingsWindow")
+    tlw:SetHidden(true)
+    tlw:SetDimensions(1010, 914) -- same height as ZO_OptionsWindow
+
+    ZO_ReanchorControlForLeftSidePanel(tlw)
+
+    -- create black background for the window (mimic ZO_RightFootPrintBackground)
+
+    local bgLeft = wm:CreateControl("$(parent)BackgroundLeft", tlw, CT_TEXTURE)
+    bgLeft:SetTexture("EsoUI/Art/Miscellaneous/centerscreen_left.dds")
+    bgLeft:SetDimensions(1024, 1024)
+    bgLeft:SetAnchor(TOPLEFT, nil, TOPLEFT)
+    bgLeft:SetDrawLayer(DL_BACKGROUND)
+    bgLeft:SetExcludeFromResizeToFitExtents(true)
+
+    local bgRight = wm:CreateControl("$(parent)BackgroundRight", tlw, CT_TEXTURE)
+    bgRight:SetTexture("EsoUI/Art/Miscellaneous/centerscreen_right.dds")
+    bgRight:SetDimensions(64, 1024)
+    bgRight:SetAnchor(TOPLEFT, bgLeft, TOPRIGHT)
+    bgRight:SetDrawLayer(DL_BACKGROUND)
+    bgRight:SetExcludeFromResizeToFitExtents(true)
+
+    -- create gray background for addon list (mimic ZO_TreeUnderlay)
+
+    local underlayLeft = wm:CreateControl("$(parent)UnderlayLeft", tlw, CT_TEXTURE)
+    underlayLeft:SetTexture("EsoUI/Art/Miscellaneous/centerscreen_indexArea_left.dds")
+    underlayLeft:SetDimensions(256, 1024)
+    underlayLeft:SetAnchor(TOPLEFT, bgLeft, TOPLEFT)
+    underlayLeft:SetDrawLayer(DL_BACKGROUND)
+    underlayLeft:SetExcludeFromResizeToFitExtents(true)
+
+    local underlayRight = wm:CreateControl("$(parent)UnderlayRight", tlw, CT_TEXTURE)
+    underlayRight:SetTexture("EsoUI/Art/Miscellaneous/centerscreen_indexArea_right.dds")
+    underlayRight:SetDimensions(128, 1024)
+    underlayRight:SetAnchor(TOPLEFT, underlayLeft, TOPRIGHT)
+    underlayRight:SetDrawLayer(DL_BACKGROUND)
+    underlayRight:SetExcludeFromResizeToFitExtents(true)
+
+    -- create title bar (mimic ZO_OptionsWindow)
+
+    local title = wm:CreateControl("$(parent)Title", tlw, CT_LABEL)
+    title:SetAnchor(TOPLEFT, nil, TOPLEFT, 65, 70)
+    title:SetFont("ZoFontWinH1")
+    title:SetModifyTextType(MODIFY_TEXT_TYPE_UPPERCASE)
+
+    local divider = wm:CreateControlFromVirtual("$(parent)Divider", tlw, "ZO_Options_Divider")
+    divider:SetAnchor(TOPLEFT, nil, TOPLEFT, 65, 108)
+
+    -- create search filter box
+
+    local srchBox = CreateSearchFilterBox("$(parent)SearchFilter", tlw)
+    srchBox:SetAnchor(TOPLEFT, nil, TOPLEFT, 63, 120)
+    srchBox:SetDimensions(260, 30)
+
+    -- create scrollable addon list
+
+    local addonList = CreateAddonList("$(parent)AddonList", tlw)
+    addonList:SetAnchor(TOPLEFT, nil, TOPLEFT, 65, 160)
+    addonList:SetDimensions(285, 665)
+
+    lam.addonList = addonList -- for easy access from elsewhere
+
+    -- create container for option panels
+
+    local panelContainer = wm:CreateControl("$(parent)PanelContainer", tlw, CT_CONTROL)
+    panelContainer:SetAnchor(TOPLEFT, nil, TOPLEFT, 365, 120)
+    panelContainer:SetDimensions(645, 675)
+
+    local defaultButton = wm:CreateControlFromVirtual("$(parent)ResetToDefaultButton", tlw, "ZO_DialogButton")
+    ZO_KeybindButtonTemplate_Setup(defaultButton, "OPTIONS_LOAD_DEFAULTS", HandleLoadDefaultsPressed, GetString(SI_OPTIONS_DEFAULTS))
+    defaultButton:SetAnchor(TOPLEFT, panelContainer, BOTTOMLEFT, 0, 2)
+    lam.defaultButton = defaultButton
+
+    local applyButton = wm:CreateControlFromVirtual("$(parent)ApplyButton", tlw, "ZO_DialogButton")
+    ZO_KeybindButtonTemplate_Setup(applyButton, "OPTIONS_APPLY_CHANGES", HandleReloadUIPressed, GetString(SI_ADDON_MANAGER_RELOAD))
+    applyButton:SetAnchor(TOPRIGHT, panelContainer, BOTTOMRIGHT, 0, 2)
+    applyButton:SetHidden(true)
+    lam.applyButton = applyButton
+
+    return tlw
+end
+
+
+--INITIALIZING
+local safeToInitialize = false
+local hasInitialized = false
+
+local eventHandle = table.concat({MAJOR, MINOR}, "r")
+local function OnLoad(_, addonName)
+    -- wait for the first loaded event
+    em:UnregisterForEvent(eventHandle, EVENT_ADD_ON_LOADED)
+    safeToInitialize = true
+end
+em:RegisterForEvent(eventHandle, EVENT_ADD_ON_LOADED, OnLoad)
+
+local function OnActivated(_, initial)
+    em:UnregisterForEvent(eventHandle, EVENT_PLAYER_ACTIVATED)
+    FlushMessages()
+
+    local reopenPanel = RetrievePanelForReopening()
+    if not initial and reopenPanel then
+        lam:OpenToPanel(reopenPanel)
+    end
+end
+em:RegisterForEvent(eventHandle, EVENT_PLAYER_ACTIVATED, OnActivated)
+
+function CheckSafetyAndInitialize(addonID)
+    if not safeToInitialize then
+        local msg = string.format("The panel with id '%s' was registered before addon loading has completed. This might break the AddOn Settings menu.", addonID)
+        PrintLater(msg)
+    end
+    if not hasInitialized then
+        hasInitialized = true
+    end
+end
+
+
+--TODO documentation
+function lam:GetAddonPanelContainer()
+    local fragment = lam:GetAddonSettingsFragment()
+    local window = fragment:GetControl()
+    return window:GetNamedChild("PanelContainer")
+end
+
+
+--TODO documentation
+function lam:GetAddonSettingsFragment()
+    assert(hasInitialized or safeToInitialize)
+    if not LAMAddonSettingsFragment then
+        local window = CreateAddonSettingsWindow()
+        LAMAddonSettingsFragment = ZO_FadeSceneFragment:New(window, true, 100)
+        LAMAddonSettingsFragment:RegisterCallback("StateChange", function(oldState, newState)
+            if(newState == SCENE_FRAGMENT_SHOWN) then
+                InitKeybindActions()
+                PushActionLayerByName("OptionsWindow")
+                OpenCurrentPanel()
+            elseif(newState == SCENE_FRAGMENT_HIDDEN) then
+                CloseCurrentPanel()
+                RemoveActionLayerByName("OptionsWindow")
+                ShowReloadDialogIfNeeded()
+            end
+        end)
+        CreateAddonSettingsMenuEntry()
+    end
+    return LAMAddonSettingsFragment
+end
diff --git a/Libs/LibAddonMenu-2.0/controls/button.lua b/Libs/LibAddonMenu-2.0/controls/button.lua
new file mode 100644
index 0000000..82b5032
--- /dev/null
+++ b/Libs/LibAddonMenu-2.0/controls/button.lua
@@ -0,0 +1,91 @@
+--[[buttonData = {
+    type = "button",
+    name = "My Button", -- string id or function returning a string
+    func = function() end,
+    tooltip = "Button's tooltip text.", -- string id or function returning a string (optional)
+    width = "full", --or "half" (optional)
+    disabled = function() return db.someBooleanSetting end, --or boolean (optional)
+    icon = "icon\\path.dds", --(optional)
+    isDangerous = false, -- boolean, if set to true, the button text will be red and a confirmation dialog with the button label and warning text will show on click before the callback is executed (optional)
+    warning = "Will need to reload the UI.", --(optional)
+    reference = "MyAddonButton", -- unique global reference to control (optional)
+} ]]
+
+local widgetVersion = 11
+local LAM = LibStub("LibAddonMenu-2.0")
+if not LAM:RegisterWidget("button", widgetVersion) then return end
+
+local wm = WINDOW_MANAGER
+
+local function UpdateDisabled(control)
+    local disable = control.data.disabled
+    if type(disable) == "function" then
+        disable = disable()
+    end
+    control.button:SetEnabled(not disable)
+end
+
+--controlName is optional
+local MIN_HEIGHT = 28 -- default_button height
+local HALF_WIDTH_LINE_SPACING = 2
+function LAMCreateControl.button(parent, buttonData, controlName)
+    local control = LAM.util.CreateBaseControl(parent, buttonData, controlName)
+    control:SetMouseEnabled(true)
+
+    local width = control:GetWidth()
+    if control.isHalfWidth then
+        control:SetDimensions(width / 2, MIN_HEIGHT * 2 + HALF_WIDTH_LINE_SPACING)
+    else
+        control:SetDimensions(width, MIN_HEIGHT)
+    end
+
+    if buttonData.icon then
+        control.button = wm:CreateControl(nil, control, CT_BUTTON)
+        control.button:SetDimensions(26, 26)
+        control.button:SetNormalTexture(buttonData.icon)
+        control.button:SetPressedOffset(2, 2)
+    else
+        --control.button = wm:CreateControlFromVirtual(controlName.."Button", control, "ZO_DefaultButton")
+        control.button = wm:CreateControlFromVirtual(nil, control, "ZO_DefaultButton")
+        control.button:SetWidth(width / 3)
+        control.button:SetText(LAM.util.GetStringFromValue(buttonData.name))
+        if buttonData.isDangerous then control.button:SetNormalFontColor(ZO_ERROR_COLOR:UnpackRGBA()) end
+    end
+    local button = control.button
+    button:SetAnchor(control.isHalfWidth and CENTER or RIGHT)
+    button:SetClickSound("Click")
+    button.data = {tooltipText = LAM.util.GetStringFromValue(buttonData.tooltip)}
+    button:SetHandler("OnMouseEnter", ZO_Options_OnMouseEnter)
+    button:SetHandler("OnMouseExit", ZO_Options_OnMouseExit)
+    button:SetHandler("OnClicked", function(...)
+        local args = {...}
+        local function callback()
+            buttonData.func(unpack(args))
+            LAM.util.RequestRefreshIfNeeded(control)
+        end
+
+        if(buttonData.isDangerous) then
+            local title = LAM.util.GetStringFromValue(buttonData.name)
+            local body = LAM.util.GetStringFromValue(buttonData.warning)
+            LAM.util.ShowConfirmationDialog(title, body, callback)
+        else
+            callback()
+        end
+    end)
+
+    if buttonData.warning ~= nil then
+        control.warning = wm:CreateControlFromVirtual(nil, control, "ZO_Options_WarningIcon")
+        control.warning:SetAnchor(RIGHT, button, LEFT, -5, 0)
+        control.UpdateWarning = LAM.util.UpdateWarning
+        control:UpdateWarning()
+    end
+
+    if buttonData.disabled ~= nil then
+        control.UpdateDisabled = UpdateDisabled
+        control:UpdateDisabled()
+    end
+
+    LAM.util.RegisterForRefreshIfNeeded(control)
+
+    return control
+end
diff --git a/Libs/LibAddonMenu-2.0/controls/checkbox.lua b/Libs/LibAddonMenu-2.0/controls/checkbox.lua
new file mode 100644
index 0000000..6696dd7
--- /dev/null
+++ b/Libs/LibAddonMenu-2.0/controls/checkbox.lua
@@ -0,0 +1,142 @@
+--[[checkboxData = {
+    type = "checkbox",
+    name = "My Checkbox", -- or string id or function returning a string
+    getFunc = function() return db.var end,
+    setFunc = function(value) db.var = value doStuff() end,
+    tooltip = "Checkbox's tooltip text.", -- or string id or function returning a string (optional)
+    width = "full", -- or "half" (optional)
+    disabled = function() return db.someBooleanSetting end, --or boolean (optional)
+    warning = "May cause permanent awesomeness.", -- or string id or function returning a string (optional)
+    requiresReload = false, -- boolean, if set to true, the warning text will contain a notice that changes are only applied after an UI reload and any change to the value will make the "Apply Settings" button appear on the panel which will reload the UI when pressed (optional)
+    default = defaults.var, -- a boolean or function that returns a boolean (optional)
+    reference = "MyAddonCheckbox", -- unique global reference to control (optional)
+} ]]
+
+
+local widgetVersion = 14
+local LAM = LibStub("LibAddonMenu-2.0")
+if not LAM:RegisterWidget("checkbox", widgetVersion) then return end
+
+local wm = WINDOW_MANAGER
+
+--label
+local enabledColor = ZO_DEFAULT_ENABLED_COLOR
+local enabledHLcolor = ZO_HIGHLIGHT_TEXT
+local disabledColor = ZO_DEFAULT_DISABLED_COLOR
+local disabledHLcolor = ZO_DEFAULT_DISABLED_MOUSEOVER_COLOR
+--checkbox
+local checkboxColor = ZO_NORMAL_TEXT
+local checkboxHLcolor = ZO_HIGHLIGHT_TEXT
+
+
+local function UpdateDisabled(control)
+    local disable
+    if type(control.data.disabled) == "function" then
+        disable = control.data.disabled()
+    else
+        disable = control.data.disabled
+    end
+
+    control.label:SetColor((disable and ZO_DEFAULT_DISABLED_COLOR or control.value and ZO_DEFAULT_ENABLED_COLOR or ZO_DEFAULT_DISABLED_COLOR):UnpackRGBA())
+    control.checkbox:SetColor((disable and ZO_DEFAULT_DISABLED_COLOR or ZO_NORMAL_TEXT):UnpackRGBA())
+    --control:SetMouseEnabled(not disable)
+    --control:SetMouseEnabled(true)
+
+    control.isDisabled = disable
+end
+
+local function ToggleCheckbox(control)
+    if control.value then
+        control.label:SetColor(ZO_DEFAULT_ENABLED_COLOR:UnpackRGBA())
+        control.checkbox:SetText(control.checkedText)
+    else
+        control.label:SetColor(ZO_DEFAULT_DISABLED_COLOR:UnpackRGBA())
+        control.checkbox:SetText(control.uncheckedText)
+    end
+end
+
+local function UpdateValue(control, forceDefault, value)
+    if forceDefault then --if we are forcing defaults
+        value = LAM.util.GetDefaultValue(control.data.default)
+        control.data.setFunc(value)
+    elseif value ~= nil then --our value could be false
+        control.data.setFunc(value)
+        --after setting this value, let's refresh the others to see if any should be disabled or have their settings changed
+        LAM.util.RequestRefreshIfNeeded(control)
+    else
+        value = control.data.getFunc()
+    end
+    control.value = value
+
+    ToggleCheckbox(control)
+end
+
+local function OnMouseEnter(control)
+    ZO_Options_OnMouseEnter(control)
+
+    if control.isDisabled then return end
+
+    local label = control.label
+    if control.value then
+        label:SetColor(ZO_HIGHLIGHT_TEXT:UnpackRGBA())
+    else
+        label:SetColor(ZO_DEFAULT_DISABLED_MOUSEOVER_COLOR:UnpackRGBA())
+    end
+    control.checkbox:SetColor(ZO_HIGHLIGHT_TEXT:UnpackRGBA())
+end
+
+local function OnMouseExit(control)
+    ZO_Options_OnMouseExit(control)
+
+    if control.isDisabled then return end
+
+    local label = control.label
+    if control.value then
+        label:SetColor(ZO_DEFAULT_ENABLED_COLOR:UnpackRGBA())
+    else
+        label:SetColor(ZO_DEFAULT_DISABLED_COLOR:UnpackRGBA())
+    end
+    control.checkbox:SetColor(ZO_NORMAL_TEXT:UnpackRGBA())
+end
+
+--controlName is optional
+function LAMCreateControl.checkbox(parent, checkboxData, controlName)
+    local control = LAM.util.CreateLabelAndContainerControl(parent, checkboxData, controlName)
+    control:SetHandler("OnMouseEnter", OnMouseEnter)
+    control:SetHandler("OnMouseExit", OnMouseExit)
+    control:SetHandler("OnMouseUp", function(control)
+        if control.isDisabled then return end
+        PlaySound(SOUNDS.DEFAULT_CLICK)
+        control.value = not control.value
+        control:UpdateValue(false, control.value)
+    end)
+
+    control.checkbox = wm:CreateControl(nil, control.container, CT_LABEL)
+    local checkbox = control.checkbox
+    checkbox:SetAnchor(LEFT, control.container, LEFT, 0, 0)
+    checkbox:SetFont("ZoFontGameBold")
+    checkbox:SetColor(ZO_NORMAL_TEXT:UnpackRGBA())
+    control.checkedText = GetString(SI_CHECK_BUTTON_ON):upper()
+    control.uncheckedText = GetString(SI_CHECK_BUTTON_OFF):upper()
+
+    if checkboxData.warning ~= nil or checkboxData.requiresReload then
+        control.warning = wm:CreateControlFromVirtual(nil, control, "ZO_Options_WarningIcon")
+        control.warning:SetAnchor(RIGHT, checkbox, LEFT, -5, 0)
+        control.UpdateWarning = LAM.util.UpdateWarning
+        control:UpdateWarning()
+    end
+
+    control.data.tooltipText = LAM.util.GetStringFromValue(checkboxData.tooltip)
+
+    control.UpdateValue = UpdateValue
+    control:UpdateValue()
+    if checkboxData.disabled ~= nil then
+        control.UpdateDisabled = UpdateDisabled
+        control:UpdateDisabled()
+    end
+
+    LAM.util.RegisterForRefreshIfNeeded(control)
+    LAM.util.RegisterForReloadIfNeeded(control)
+
+    return control
+end
diff --git a/Libs/LibAddonMenu-2.0/controls/colorpicker.lua b/Libs/LibAddonMenu-2.0/controls/colorpicker.lua
new file mode 100644
index 0000000..a57aab0
--- /dev/null
+++ b/Libs/LibAddonMenu-2.0/controls/colorpicker.lua
@@ -0,0 +1,106 @@
+--[[colorpickerData = {
+    type = "colorpicker",
+    name = "My Color Picker", -- or string id or function returning a string
+    getFunc = function() return db.r, db.g, db.b, db.a end, --(alpha is optional)
+    setFunc = function(r,g,b,a) db.r=r, db.g=g, db.b=b, db.a=a end, --(alpha is optional)
+    tooltip = "Color Picker's tooltip text.", -- or string id or function returning a string (optional)
+    width = "full", --or "half" (optional)
+    disabled = function() return db.someBooleanSetting end, --or boolean (optional)
+    warning = "May cause permanent awesomeness.", -- or string id or function returning a string (optional)
+    requiresReload = false, -- boolean, if set to true, the warning text will contain a notice that changes are only applied after an UI reload and any change to the value will make the "Apply Settings" button appear on the panel which will reload the UI when pressed (optional)
+    default = {r = defaults.r, g = defaults.g, b = defaults.b, a = defaults.a}, --(optional) table of default color values (or default = defaultColor, where defaultColor is a table with keys of r, g, b[, a]) or a function that returns the color
+    reference = "MyAddonColorpicker" -- unique global reference to control (optional)
+} ]]
+
+
+local widgetVersion = 13
+local LAM = LibStub("LibAddonMenu-2.0")
+if not LAM:RegisterWidget("colorpicker", widgetVersion) then return end
+
+local wm = WINDOW_MANAGER
+
+local function UpdateDisabled(control)
+    local disable
+    if type(control.data.disabled) == "function" then
+        disable = control.data.disabled()
+    else
+        disable = control.data.disabled
+    end
+
+    if disable then
+        control.label:SetColor(ZO_DEFAULT_DISABLED_COLOR:UnpackRGBA())
+    else
+        control.label:SetColor(ZO_DEFAULT_ENABLED_COLOR:UnpackRGBA())
+    end
+
+    control.isDisabled = disable
+end
+
+local function UpdateValue(control, forceDefault, valueR, valueG, valueB, valueA)
+    if forceDefault then --if we are forcing defaults
+        local color = LAM.util.GetDefaultValue(control.data.default)
+        valueR, valueG, valueB, valueA = color.r, color.g, color.b, color.a
+        control.data.setFunc(valueR, valueG, valueB, valueA)
+    elseif valueR and valueG and valueB then
+        control.data.setFunc(valueR, valueG, valueB, valueA or 1)
+        --after setting this value, let's refresh the others to see if any should be disabled or have their settings changed
+        LAM.util.RequestRefreshIfNeeded(control)
+    else
+        valueR, valueG, valueB, valueA = control.data.getFunc()
+    end
+
+    control.thumb:SetColor(valueR, valueG, valueB, valueA or 1)
+end
+
+function LAMCreateControl.colorpicker(parent, colorpickerData, controlName)
+    local control = LAM.util.CreateLabelAndContainerControl(parent, colorpickerData, controlName)
+
+    control.color = control.container
+    local color = control.color
+
+    control.thumb = wm:CreateControl(nil, color, CT_TEXTURE)
+    local thumb = control.thumb
+    thumb:SetDimensions(36, 18)
+    thumb:SetAnchor(LEFT, color, LEFT, 4, 0)
+
+    color.border = wm:CreateControl(nil, color, CT_TEXTURE)
+    local border = color.border
+    border:SetTexture("EsoUI\\Art\\ChatWindow\\chatOptions_bgColSwatch_frame.dds")
+    border:SetTextureCoords(0, .625, 0, .8125)
+    border:SetDimensions(40, 22)
+    border:SetAnchor(CENTER, thumb, CENTER, 0, 0)
+
+    local function ColorPickerCallback(r, g, b, a)
+        control:UpdateValue(false, r, g, b, a)
+    end
+
+    control:SetHandler("OnMouseUp", function(self, btn, upInside)
+        if self.isDisabled then return end
+
+        if upInside then
+            local r, g, b, a = colorpickerData.getFunc()
+            COLOR_PICKER:Show(ColorPickerCallback, r, g, b, a, LAM.util.GetStringFromValue(colorpickerData.name))
+        end
+    end)
+
+    if colorpickerData.warning ~= nil or colorpickerData.requiresReload then
+        control.warning = wm:CreateControlFromVirtual(nil, control, "ZO_Options_WarningIcon")
+        control.warning:SetAnchor(RIGHT, control.color, LEFT, -5, 0)
+        control.UpdateWarning = LAM.util.UpdateWarning
+        control:UpdateWarning()
+    end
+
+    control.data.tooltipText = LAM.util.GetStringFromValue(colorpickerData.tooltip)
+
+    control.UpdateValue = UpdateValue
+    control:UpdateValue()
+    if colorpickerData.disabled ~= nil then
+        control.UpdateDisabled = UpdateDisabled
+        control:UpdateDisabled()
+    end
+
+    LAM.util.RegisterForRefreshIfNeeded(control)
+    LAM.util.RegisterForReloadIfNeeded(control)
+
+    return control
+end
diff --git a/Libs/LibAddonMenu-2.0/controls/custom.lua b/Libs/LibAddonMenu-2.0/controls/custom.lua
new file mode 100644
index 0000000..40a7c42
--- /dev/null
+++ b/Libs/LibAddonMenu-2.0/controls/custom.lua
@@ -0,0 +1,35 @@
+--[[customData = {
+    type = "custom",
+    reference = "MyAddonCustomControl", --(optional) unique name for your control to use as reference
+    refreshFunc = function(customControl) end, --(optional) function to call when panel/controls refresh
+    width = "full", --or "half" (optional)
+} ]]
+
+local widgetVersion = 7
+local LAM = LibStub("LibAddonMenu-2.0")
+if not LAM:RegisterWidget("custom", widgetVersion) then return end
+
+local function UpdateValue(control)
+    if control.data.refreshFunc then
+        control.data.refreshFunc(control)
+    end
+end
+
+local MIN_HEIGHT = 26
+function LAMCreateControl.custom(parent, customData, controlName)
+    local control = LAM.util.CreateBaseControl(parent, customData, controlName)
+    local width = control:GetWidth()
+    control:SetResizeToFitDescendents(true)
+
+    if control.isHalfWidth then --note these restrictions
+        control:SetDimensionConstraints(width / 2, MIN_HEIGHT, width / 2, MIN_HEIGHT * 4)
+    else
+        control:SetDimensionConstraints(width, MIN_HEIGHT, width, MIN_HEIGHT * 4)
+    end
+
+    control.UpdateValue = UpdateValue
+
+    LAM.util.RegisterForRefreshIfNeeded(control)
+
+    return control
+end
diff --git a/Libs/LibAddonMenu-2.0/controls/description.lua b/Libs/LibAddonMenu-2.0/controls/description.lua
new file mode 100644
index 0000000..da207a0
--- /dev/null
+++ b/Libs/LibAddonMenu-2.0/controls/description.lua
@@ -0,0 +1,60 @@
+--[[descriptionData = {
+    type = "description",
+    text = "My description text to display.", -- or string id or function returning a string
+    title = "My Title", -- or string id or function returning a string (optional)
+    width = "full", --or "half" (optional)
+    reference = "MyAddonDescription" -- unique global reference to control (optional)
+} ]]
+
+
+local widgetVersion = 8
+local LAM = LibStub("LibAddonMenu-2.0")
+if not LAM:RegisterWidget("description", widgetVersion) then return end
+
+local wm = WINDOW_MANAGER
+
+local function UpdateValue(control)
+    if control.title then
+        control.title:SetText(LAM.util.GetStringFromValue(control.data.title))
+    end
+    control.desc:SetText(LAM.util.GetStringFromValue(control.data.text))
+end
+
+function LAMCreateControl.description(parent, descriptionData, controlName)
+    local control = LAM.util.CreateBaseControl(parent, descriptionData, controlName)
+    local isHalfWidth = control.isHalfWidth
+    local width = control:GetWidth()
+    control:SetResizeToFitDescendents(true)
+
+    if isHalfWidth then
+        control:SetDimensionConstraints(width / 2, 0, width / 2, 0)
+    else
+        control:SetDimensionConstraints(width, 0, width, 0)
+    end
+
+    control.desc = wm:CreateControl(nil, control, CT_LABEL)
+    local desc = control.desc
+    desc:SetVerticalAlignment(TEXT_ALIGN_TOP)
+    desc:SetFont("ZoFontGame")
+    desc:SetText(LAM.util.GetStringFromValue(descriptionData.text))
+    desc:SetWidth(isHalfWidth and width / 2 or width)
+
+    if descriptionData.title then
+        control.title = wm:CreateControl(nil, control, CT_LABEL)
+        local title = control.title
+        title:SetWidth(isHalfWidth and width / 2 or width)
+        title:SetAnchor(TOPLEFT, control, TOPLEFT)
+        title:SetFont("ZoFontWinH4")
+        title:SetText(LAM.util.GetStringFromValue(descriptionData.title))
+        desc:SetAnchor(TOPLEFT, title, BOTTOMLEFT)
+    else
+        desc:SetAnchor(TOPLEFT)
+    end
+
+    control.UpdateValue = UpdateValue
+
+    LAM.util.RegisterForRefreshIfNeeded(control)
+
+    return control
+
+end
diff --git a/Libs/LibAddonMenu-2.0/controls/divider.lua b/Libs/LibAddonMenu-2.0/controls/divider.lua
new file mode 100644
index 0000000..8089539
--- /dev/null
+++ b/Libs/LibAddonMenu-2.0/controls/divider.lua
@@ -0,0 +1,45 @@
+--[[dividerData = {
+    type = "divider",
+    width = "full", --or "half" (optional)
+    height = 10, (optional)
+    alpha = 0.25, (optional)
+    reference = "MyAddonDivider" -- unique global reference to control (optional)
+} ]]
+
+
+local widgetVersion = 2
+local LAM = LibStub("LibAddonMenu-2.0")
+if not LAM:RegisterWidget("divider", widgetVersion) then return end
+
+local wm = WINDOW_MANAGER
+
+local MIN_HEIGHT = 10
+local MAX_HEIGHT = 50
+local MIN_ALPHA = 0
+local MAX_ALPHA = 1
+local DEFAULT_ALPHA = 0.25
+
+local function GetValueInRange(value, min, max, default)
+    if not value or type(value) ~= "number" then
+        return default
+    end
+    return math.min(math.max(min, value), max)
+end
+
+function LAMCreateControl.divider(parent, dividerData, controlName)
+    local control = LAM.util.CreateBaseControl(parent, dividerData, controlName)
+    local isHalfWidth = control.isHalfWidth
+    local width = control:GetWidth()
+    local height = GetValueInRange(dividerData.height, MIN_HEIGHT, MAX_HEIGHT, MIN_HEIGHT)
+    local alpha = GetValueInRange(dividerData.alpha, MIN_ALPHA, MAX_ALPHA, DEFAULT_ALPHA)
+
+    control:SetDimensions(isHalfWidth and width / 2 or width, height)
+
+    control.divider = wm:CreateControlFromVirtual(nil, control, "ZO_Options_Divider")
+    local divider = control.divider
+    divider:SetWidth(isHalfWidth and width / 2 or width)
+    divider:SetAnchor(TOPLEFT)
+    divider:SetAlpha(alpha)
+
+    return control
+end
diff --git a/Libs/LibAddonMenu-2.0/controls/dropdown.lua b/Libs/LibAddonMenu-2.0/controls/dropdown.lua
new file mode 100644
index 0000000..70e23bb
--- /dev/null
+++ b/Libs/LibAddonMenu-2.0/controls/dropdown.lua
@@ -0,0 +1,387 @@
+--[[dropdownData = {
+    type = "dropdown",
+    name = "My Dropdown", -- or string id or function returning a string
+    choices = {"table", "of", "choices"},
+    choicesValues = {"foo", 2, "three"}, -- if specified, these values will get passed to setFunc instead (optional)
+    getFunc = function() return db.var end,
+    setFunc = function(var) db.var = var doStuff() end,
+    tooltip = "Dropdown's tooltip text.", -- or string id or function returning a string (optional)
+    choicesTooltips = {"tooltip 1", "tooltip 2", "tooltip 3"}, -- or array of string ids or array of functions returning a string (optional)
+    sort = "name-up", --or "name-down", "numeric-up", "numeric-down", "value-up", "value-down", "numericvalue-up", "numericvalue-down" (optional) - if not provided, list will not be sorted
+    width = "full", --or "half" (optional)
+    scrollable = true, -- boolean or number, if set the dropdown will feature a scroll bar if there are a large amount of choices and limit the visible lines to the specified number or 10 if true is used (optional)
+    disabled = function() return db.someBooleanSetting end, --or boolean (optional)
+    warning = "May cause permanent awesomeness.", -- or string id or function returning a string (optional)
+    requiresReload = false, -- boolean, if set to true, the warning text will contain a notice that changes are only applied after an UI reload and any change to the value will make the "Apply Settings" button appear on the panel which will reload the UI when pressed (optional)
+    default = defaults.var, -- default value or function that returns the default value (optional)
+    reference = "MyAddonDropdown" -- unique global reference to control (optional)
+} ]]
+
+
+local widgetVersion = 18
+local LAM = LibStub("LibAddonMenu-2.0")
+if not LAM:RegisterWidget("dropdown", widgetVersion) then return end
+
+local wm = WINDOW_MANAGER
+local SORT_BY_VALUE         = { ["value"] = {} }
+local SORT_BY_VALUE_NUMERIC = { ["value"] = { isNumeric = true } }
+local SORT_TYPES = {
+    name = ZO_SORT_BY_NAME,
+    numeric = ZO_SORT_BY_NAME_NUMERIC,
+    value = SORT_BY_VALUE,
+    numericvalue = SORT_BY_VALUE_NUMERIC,
+}
+local SORT_ORDERS = {
+    up = ZO_SORT_ORDER_UP,
+    down = ZO_SORT_ORDER_DOWN,
+}
+
+local function UpdateDisabled(control)
+    local disable
+    if type(control.data.disabled) == "function" then
+        disable = control.data.disabled()
+    else
+        disable = control.data.disabled
+    end
+
+    control.dropdown:SetEnabled(not disable)
+    if disable then
+        control.label:SetColor(ZO_DEFAULT_DISABLED_COLOR:UnpackRGBA())
+    else
+        control.label:SetColor(ZO_DEFAULT_ENABLED_COLOR:UnpackRGBA())
+    end
+end
+
+local function UpdateValue(control, forceDefault, value)
+    if forceDefault then --if we are forcing defaults
+        value = LAM.util.GetDefaultValue(control.data.default)
+        control.data.setFunc(value)
+        control.dropdown:SetSelectedItem(control.choices[value])
+    elseif value then
+        control.data.setFunc(value)
+        --after setting this value, let's refresh the others to see if any should be disabled or have their settings changed
+        LAM.util.RequestRefreshIfNeeded(control)
+    else
+        value = control.data.getFunc()
+        control.dropdown:SetSelectedItem(control.choices[value])
+    end
+end
+
+local function DropdownCallback(control, choiceText, choice)
+    choice.control:UpdateValue(false, choice.value or choiceText)
+end
+
+local function SetupTooltips(comboBox, choicesTooltips)
+    local function ShowTooltip(control)
+        InitializeTooltip(InformationTooltip, control, TOPLEFT, 0, 0, BOTTOMRIGHT)
+        SetTooltipText(InformationTooltip, LAM.util.GetStringFromValue(control.tooltip))
+        InformationTooltipTopLevel:BringWindowToTop()
+    end
+    local function HideTooltip(control)
+        ClearTooltip(InformationTooltip)
+    end
+
+    -- allow for tooltips on the drop down entries
+    local originalShow = comboBox.ShowDropdownInternal
+    comboBox.ShowDropdownInternal = function(comboBox)
+        originalShow(comboBox)
+        local entries = ZO_Menu.items
+        for i = 1, #entries do
+            local entry = entries[i]
+            local control = entries[i].item
+            control.tooltip = choicesTooltips[i]
+            entry.onMouseEnter = control:GetHandler("OnMouseEnter")
+            entry.onMouseExit = control:GetHandler("OnMouseExit")
+            ZO_PreHookHandler(control, "OnMouseEnter", ShowTooltip)
+            ZO_PreHookHandler(control, "OnMouseExit", HideTooltip)
+        end
+    end
+
+    local originalHide = comboBox.HideDropdownInternal
+    comboBox.HideDropdownInternal = function(self)
+        local entries = ZO_Menu.items
+        for i = 1, #entries do
+            local entry = entries[i]
+            local control = entries[i].item
+            control:SetHandler("OnMouseEnter", entry.onMouseEnter)
+            control:SetHandler("OnMouseExit", entry.onMouseExit)
+            control.tooltip = nil
+        end
+        originalHide(self)
+    end
+end
+
+local function UpdateChoices(control, choices, choicesValues, choicesTooltips)
+    control.dropdown:ClearItems() --remove previous choices --(need to call :SetSelectedItem()?)
+    ZO_ClearTable(control.choices)
+
+    --build new list of choices
+    local choices = choices or control.data.choices
+    local choicesValues = choicesValues or control.data.choicesValues
+    local choicesTooltips = choicesTooltips or control.data.choicesTooltips
+
+    if choicesValues then
+        assert(#choices == #choicesValues, "choices and choicesValues need to have the same size")
+    end
+
+    if choicesTooltips then
+        assert(#choices == #choicesTooltips, "choices and choicesTooltips need to have the same size")
+        if not control.scrollHelper then -- only do this for non-scrollable
+            SetupTooltips(control.dropdown, choicesTooltips)
+        end
+    end
+
+    for i = 1, #choices do
+        local entry = control.dropdown:CreateItemEntry(choices[i], DropdownCallback)
+        entry.control = control
+        if choicesValues then
+            entry.value = choicesValues[i]
+        end
+        if choicesTooltips and control.scrollHelper then
+            entry.tooltip = choicesTooltips[i]
+        end
+        control.choices[entry.value or entry.name] = entry.name
+        control.dropdown:AddItem(entry, not control.data.sort and ZO_COMBOBOX_SUPRESS_UPDATE) --if sort type/order isn't specified, then don't sort
+    end
+end
+
+local function GrabSortingInfo(sortInfo)
+    local t, i = {}, 1
+    for info in string.gmatch(sortInfo, "([^%-]+)") do
+        t[i] = info
+        i = i + 1
+    end
+
+    return t
+end
+
+local DEFAULT_VISIBLE_ROWS = 10
+local SCROLLABLE_ENTRY_TEMPLATE_HEIGHT = 25 -- same as in zo_combobox.lua
+local CONTENT_PADDING = 24
+local SCROLLBAR_PADDING = 16
+local PADDING = GetMenuPadding() / 2 -- half the amount looks closer to the regular dropdown
+local ROUNDING_MARGIN = 0.01 -- needed to avoid rare issue with too many anchors processed
+local ScrollableDropdownHelper = ZO_Object:Subclass()
+
+function ScrollableDropdownHelper:New(...)
+    local object = ZO_Object.New(self)
+    object:Initialize(...)
+    return object
+end
+
+function ScrollableDropdownHelper:Initialize(parent, control, visibleRows)
+    local combobox = control.combobox
+    local dropdown = control.dropdown
+    self.parent = parent
+    self.control = control
+    self.combobox = combobox
+    self.dropdown = dropdown
+    self.visibleRows = visibleRows
+
+    -- clear anchors so we can adjust the width dynamically
+    dropdown.m_dropdown:ClearAnchors()
+    dropdown.m_dropdown:SetAnchor(TOPLEFT, combobox, BOTTOMLEFT)
+
+    -- handle dropdown or settingsmenu opening/closing
+    local function onShow() self:OnShow() end
+    local function onHide() self:OnHide() end
+    local function doHide() self:DoHide() end
+
+    ZO_PreHook(dropdown, "ShowDropdownOnMouseUp", onShow)
+    ZO_PreHook(dropdown, "HideDropdownInternal", onHide)
+    combobox:SetHandler("OnEffectivelyHidden", onHide)
+    parent:SetHandler("OnEffectivelyHidden", doHide)
+
+    -- dont fade entries near the edges
+    local scrollList = dropdown.m_scroll
+    scrollList.selectionTemplate = nil
+    scrollList.highlightTemplate = nil
+    ZO_ScrollList_EnableSelection(scrollList, "ZO_SelectionHighlight")
+    ZO_ScrollList_EnableHighlight(scrollList, "ZO_SelectionHighlight")
+    ZO_Scroll_SetUseFadeGradient(scrollList, false)
+
+    -- adjust scroll content anchor to mimic menu padding
+    local scroll = dropdown.m_dropdown:GetNamedChild("Scroll")
+    local anchor1 = {scroll:GetAnchor(0)}
+    local anchor2 = {scroll:GetAnchor(1)}
+    scroll:ClearAnchors()
+    scroll:SetAnchor(anchor1[2], anchor1[3], anchor1[4], anchor1[5] + PADDING, anchor1[6] + PADDING)
+    scroll:SetAnchor(anchor2[2], anchor2[3], anchor2[4], anchor2[5] - PADDING, anchor2[6] - PADDING)
+    ZO_ScrollList_Commit(scrollList)
+
+    -- hook mouse enter/exit
+    local function onMouseEnter(control) self:OnMouseEnter(control) end
+    local function onMouseExit(control) self:OnMouseExit(control) end
+
+    -- adjust row setup to mimic the highlight padding
+    local dataType1 = ZO_ScrollList_GetDataTypeTable(dropdown.m_scroll, 1)
+    local dataType2 = ZO_ScrollList_GetDataTypeTable(dropdown.m_scroll, 2)
+    local oSetup = dataType1.setupCallback -- both types have the same setup function
+    local function SetupEntry(control, data, list)
+        oSetup(control, data, list)
+        control.m_label:SetAnchor(LEFT, nil, nil, 2)
+        -- no need to store old ones since we have full ownership of our dropdown controls
+        if not control.hookedMouseHandlers then --only do it once per control
+            control.hookedMouseHandlers = true
+            ZO_PreHookHandler(control, "OnMouseEnter", onMouseEnter)
+            ZO_PreHookHandler(control, "OnMouseExit", onMouseExit)
+            -- we could also just replace the handlers
+            --control:SetHandler("OnMouseEnter", onMouseEnter)
+            --control:SetHandler("OnMouseExit", onMouseExit)
+        end
+    end
+    dataType1.setupCallback = SetupEntry
+    dataType2.setupCallback = SetupEntry
+
+    -- adjust dimensions based on entries
+    local scrollContent = scroll:GetNamedChild("Contents")
+    ZO_PreHook(dropdown, "AddMenuItems", function()
+        local width = PADDING * 2 + zo_max(self:GetMaxWidth(), combobox:GetWidth())
+        local numItems = #dropdown.m_sortedItems
+        local anchorOffset = 0
+        if(numItems > self.visibleRows) then
+            width = width + CONTENT_PADDING + SCROLLBAR_PADDING
+            anchorOffset = -SCROLLBAR_PADDING
+            numItems = self.visibleRows
+        end
+        scrollContent:SetAnchor(BOTTOMRIGHT, nil, nil, anchorOffset)
+        local height = PADDING * 2 + numItems * (SCROLLABLE_ENTRY_TEMPLATE_HEIGHT + dropdown.m_spacing) - dropdown.m_spacing + ROUNDING_MARGIN
+        dropdown.m_dropdown:SetWidth(width)
+        dropdown.m_dropdown:SetHeight(height)
+    end)
+end
+
+function ScrollableDropdownHelper:OnShow()
+    local dropdown = self.dropdown
+    if dropdown.m_lastParent ~= ZO_Menus then
+        dropdown.m_lastParent = dropdown.m_dropdown:GetParent()
+        dropdown.m_dropdown:SetParent(ZO_Menus)
+        ZO_Menus:BringWindowToTop()
+    end
+end
+
+function ScrollableDropdownHelper:OnHide()
+    local dropdown = self.dropdown
+    if dropdown.m_lastParent then
+        dropdown.m_dropdown:SetParent(dropdown.m_lastParent)
+        dropdown.m_lastParent = nil
+    end
+end
+
+function ScrollableDropdownHelper:DoHide()
+    local dropdown = self.dropdown
+    if dropdown:IsDropdownVisible() then
+        dropdown:HideDropdown()
+    end
+end
+
+function ScrollableDropdownHelper:GetMaxWidth()
+    local dropdown = self.dropdown
+    local dataType = ZO_ScrollList_GetDataTypeTable(dropdown.m_scroll, 1)
+
+    local dummy = dataType.pool:AcquireObject()
+    dataType.setupCallback(dummy, {
+        m_owner = dropdown,
+        name = "Dummy"
+    }, dropdown)
+
+    local maxWidth = 0
+    local label = dummy.m_label
+    local entries = dropdown.m_sortedItems
+    local numItems = #entries
+    for index = 1, numItems do
+        label:SetText(entries[index].name)
+        local width = label:GetTextWidth()
+        if (width > maxWidth) then
+            maxWidth = width
+        end
+    end
+
+    dataType.pool:ReleaseObject(dummy.key)
+    return maxWidth
+end
+
+function ScrollableDropdownHelper:OnMouseEnter(control)
+    -- call original code if we replace instead of hook the handler
+        --ZO_ScrollableComboBox_Entry_OnMouseEnter(control)
+    -- show tooltip
+    if control.m_data.tooltip then
+        InitializeTooltip(InformationTooltip, control, TOPLEFT, 0, 0, BOTTOMRIGHT)
+        SetTooltipText(InformationTooltip, LAM.util.GetStringFromValue(control.m_data.tooltip))
+        InformationTooltipTopLevel:BringWindowToTop()
+    end
+end
+function ScrollableDropdownHelper:OnMouseExit(control)
+    -- call original code if we replace instead of hook the handler
+        --ZO_ScrollableComboBox_Entry_OnMouseExit(control)
+    -- hide tooltip
+    if control.m_data.tooltip then
+        ClearTooltip(InformationTooltip)
+    end
+end
+
+function LAMCreateControl.dropdown(parent, dropdownData, controlName)
+    local control = LAM.util.CreateLabelAndContainerControl(parent, dropdownData, controlName)
+    control.choices = {}
+
+    local countControl = parent
+    local name = parent:GetName()
+    if not name or #name == 0 then
+        countControl = LAMCreateControl
+        name = "LAM"
+    end
+    local comboboxCount = (countControl.comboboxCount or 0) + 1
+    countControl.comboboxCount = comboboxCount
+    control.combobox = wm:CreateControlFromVirtual(zo_strjoin(nil, name, "Combobox", comboboxCount), control.container, dropdownData.scrollable and "ZO_ScrollableComboBox" or "ZO_ComboBox")
+
+    local combobox = control.combobox
+    combobox:SetAnchor(TOPLEFT)
+    combobox:SetDimensions(control.container:GetDimensions())
+    combobox:SetHandler("OnMouseEnter", function() ZO_Options_OnMouseEnter(control) end)
+    combobox:SetHandler("OnMouseExit", function() ZO_Options_OnMouseExit(control) end)
+    control.dropdown = ZO_ComboBox_ObjectFromContainer(combobox)
+    local dropdown = control.dropdown
+    dropdown:SetSortsItems(false) -- need to sort ourselves in order to be able to sort by value
+
+    if dropdownData.scrollable then
+        local visibleRows = type(dropdownData.scrollable) == "number" and dropdownData.scrollable or DEFAULT_VISIBLE_ROWS
+        control.scrollHelper = ScrollableDropdownHelper:New(parent, control, visibleRows)
+    end
+
+    ZO_PreHook(dropdown, "UpdateItems", function(self)
+        assert(not self.m_sortsItems, "built-in dropdown sorting was reactivated, sorting is handled by LAM")
+        if control.m_sortOrder ~= nil and control.m_sortType then
+            local sortKey = next(control.m_sortType)
+            local sortFunc = function(item1, item2) return ZO_TableOrderingFunction(item1, item2, sortKey, control.m_sortType, control.m_sortOrder) end
+            table.sort(self.m_sortedItems, sortFunc)
+        end
+    end)
+
+    if dropdownData.sort then
+        local sortInfo = GrabSortingInfo(dropdownData.sort)
+        control.m_sortType, control.m_sortOrder = SORT_TYPES[sortInfo[1]], SORT_ORDERS[sortInfo[2]]
+    elseif dropdownData.choicesValues then
+        control.m_sortType, control.m_sortOrder = ZO_SORT_ORDER_UP, SORT_BY_VALUE
+    end
+
+    if dropdownData.warning ~= nil or dropdownData.requiresReload then
+        control.warning = wm:CreateControlFromVirtual(nil, control, "ZO_Options_WarningIcon")
+        control.warning:SetAnchor(RIGHT, combobox, LEFT, -5, 0)
+        control.UpdateWarning = LAM.util.UpdateWarning
+        control:UpdateWarning()
+    end
+
+    control.UpdateChoices = UpdateChoices
+    control:UpdateChoices(dropdownData.choices, dropdownData.choicesValues)
+    control.UpdateValue = UpdateValue
+    control:UpdateValue()
+    if dropdownData.disabled ~= nil then
+        control.UpdateDisabled = UpdateDisabled
+        control:UpdateDisabled()
+    end
+
+    LAM.util.RegisterForRefreshIfNeeded(control)
+    LAM.util.RegisterForReloadIfNeeded(control)
+
+    return control
+end
diff --git a/Libs/LibAddonMenu-2.0/controls/editbox.lua b/Libs/LibAddonMenu-2.0/controls/editbox.lua
new file mode 100644
index 0000000..d6baf11
--- /dev/null
+++ b/Libs/LibAddonMenu-2.0/controls/editbox.lua
@@ -0,0 +1,156 @@
+--[[editboxData = {
+    type = "editbox",
+    name = "My Editbox", -- or string id or function returning a string
+    getFunc = function() return db.text end,
+    setFunc = function(text) db.text = text doStuff() end,
+    tooltip = "Editbox's tooltip text.", -- or string id or function returning a string (optional)
+    isMultiline = true, --boolean (optional)
+    isExtraWide = true, --boolean (optional)
+    width = "full", --or "half" (optional)
+    disabled = function() return db.someBooleanSetting end, --or boolean (optional)
+    warning = "May cause permanent awesomeness.", -- or string id or function returning a string (optional)
+    requiresReload = false, -- boolean, if set to true, the warning text will contain a notice that changes are only applied after an UI reload and any change to the value will make the "Apply Settings" button appear on the panel which will reload the UI when pressed (optional)
+    default = defaults.text, -- default value or function that returns the default value (optional)
+    reference = "MyAddonEditbox" -- unique global reference to control (optional)
+} ]]
+
+
+local widgetVersion = 14
+local LAM = LibStub("LibAddonMenu-2.0")
+if not LAM:RegisterWidget("editbox", widgetVersion) then return end
+
+local wm = WINDOW_MANAGER
+
+local function UpdateDisabled(control)
+    local disable
+    if type(control.data.disabled) == "function" then
+        disable = control.data.disabled()
+    else
+        disable = control.data.disabled
+    end
+
+    if disable then
+        control.label:SetColor(ZO_DEFAULT_DISABLED_COLOR:UnpackRGBA())
+        control.editbox:SetColor(ZO_DEFAULT_DISABLED_MOUSEOVER_COLOR:UnpackRGBA())
+    else
+        control.label:SetColor(ZO_DEFAULT_ENABLED_COLOR:UnpackRGBA())
+        control.editbox:SetColor(ZO_DEFAULT_ENABLED_COLOR:UnpackRGBA())
+    end
+    --control.editbox:SetEditEnabled(not disable)
+    control.editbox:SetMouseEnabled(not disable)
+end
+
+local function UpdateValue(control, forceDefault, value)
+    if forceDefault then --if we are forcing defaults
+        value = LAM.util.GetDefaultValue(control.data.default)
+        control.data.setFunc(value)
+        control.editbox:SetText(value)
+    elseif value then
+        control.data.setFunc(value)
+        --after setting this value, let's refresh the others to see if any should be disabled or have their settings changed
+        LAM.util.RequestRefreshIfNeeded(control)
+    else
+        value = control.data.getFunc()
+        control.editbox:SetText(value)
+    end
+end
+
+local MIN_HEIGHT = 24
+local HALF_WIDTH_LINE_SPACING = 2
+function LAMCreateControl.editbox(parent, editboxData, controlName)
+    local control = LAM.util.CreateLabelAndContainerControl(parent, editboxData, controlName)
+
+    local container = control.container
+    control.bg = wm:CreateControlFromVirtual(nil, container, "ZO_EditBackdrop")
+    local bg = control.bg
+    bg:SetAnchorFill()
+
+    if editboxData.isMultiline then
+        control.editbox = wm:CreateControlFromVirtual(nil, bg, "ZO_DefaultEditMultiLineForBackdrop")
+        control.editbox:SetHandler("OnMouseWheel", function(self, delta)
+            if self:HasFocus() then --only set focus to new spots if the editbox is currently in use
+                local cursorPos = self:GetCursorPosition()
+                local text = self:GetText()
+                local textLen = text:len()
+                local newPos
+                if delta > 0 then --scrolling up
+                    local reverseText = text:reverse()
+                    local revCursorPos = textLen - cursorPos
+                    local revPos = reverseText:find("\n", revCursorPos+1)
+                    newPos = revPos and textLen - revPos
+                else --scrolling down
+                    newPos = text:find("\n", cursorPos+1)
+                end
+                if newPos then --if we found a new line, then scroll, otherwise don't
+                    self:SetCursorPosition(newPos)
+                end
+            end
+        end)
+    else
+        control.editbox = wm:CreateControlFromVirtual(nil, bg, "ZO_DefaultEditForBackdrop")
+    end
+    local editbox = control.editbox
+    editbox:SetText(editboxData.getFunc())
+    editbox:SetMaxInputChars(3000)
+    editbox:SetHandler("OnFocusLost", function(self) control:UpdateValue(false, self:GetText()) end)
+    editbox:SetHandler("OnEscape", function(self) self:LoseFocus() control:UpdateValue(false, self:GetText()) end)
+    editbox:SetHandler("OnMouseEnter", function() ZO_Options_OnMouseEnter(control) end)
+    editbox:SetHandler("OnMouseExit", function() ZO_Options_OnMouseExit(control) end)
+
+    local MIN_WIDTH = (parent.GetWidth and (parent:GetWidth() / 10)) or (parent.panel.GetWidth and (parent.panel:GetWidth() / 10)) or 0
+
+    control.label:ClearAnchors()
+    container:ClearAnchors()
+
+    control.label:SetAnchor(TOPLEFT, control, TOPLEFT, 0, 0)
+    container:SetAnchor(BOTTOMRIGHT, control, BOTTOMRIGHT, 0, 0)
+
+    if control.isHalfWidth then
+        container:SetAnchor(BOTTOMRIGHT, control, BOTTOMRIGHT, 0, 0)
+    end
+
+    if editboxData.isExtraWide then
+        container:SetAnchor(BOTTOMLEFT, control, BOTTOMLEFT, 0, 0)
+    else
+        container:SetWidth(MIN_WIDTH * 3.2)
+    end
+
+    if editboxData.isMultiline then
+        container:SetHeight(MIN_HEIGHT * 3)
+    else
+        container:SetHeight(MIN_HEIGHT)
+    end
+
+    if control.isHalfWidth ~= true and editboxData.isExtraWide ~= true then
+        control:SetHeight(container:GetHeight())
+    else
+        control:SetHeight(container:GetHeight() + control.label:GetHeight())
+    end
+
+    editbox:ClearAnchors()
+    editbox:SetAnchor(TOPLEFT, container, TOPLEFT, 2, 2)
+    editbox:SetAnchor(BOTTOMRIGHT, container, BOTTOMRIGHT, -2, -2)
+
+    if editboxData.warning ~= nil or editboxData.requiresReload then
+        control.warning = wm:CreateControlFromVirtual(nil, control, "ZO_Options_WarningIcon")
+        if editboxData.isExtraWide then
+            control.warning:SetAnchor(BOTTOMRIGHT, control.bg, TOPRIGHT, 2, 0)
+        else
+            control.warning:SetAnchor(TOPRIGHT, control.bg, TOPLEFT, -5, 0)
+        end
+        control.UpdateWarning = LAM.util.UpdateWarning
+        control:UpdateWarning()
+    end
+
+    control.UpdateValue = UpdateValue
+    control:UpdateValue()
+    if editboxData.disabled ~= nil then
+        control.UpdateDisabled = UpdateDisabled
+        control:UpdateDisabled()
+    end
+
+    LAM.util.RegisterForRefreshIfNeeded(control)
+    LAM.util.RegisterForReloadIfNeeded(control)
+
+    return control
+end
diff --git a/Libs/LibAddonMenu-2.0/controls/header.lua b/Libs/LibAddonMenu-2.0/controls/header.lua
new file mode 100644
index 0000000..eadff38
--- /dev/null
+++ b/Libs/LibAddonMenu-2.0/controls/header.lua
@@ -0,0 +1,42 @@
+--[[headerData = {
+    type = "header",
+    name = "My Header", -- or string id or function returning a string
+    width = "full", --or "half" (optional)
+    reference = "MyAddonHeader" -- unique global reference to control (optional)
+} ]]
+
+
+local widgetVersion = 8
+local LAM = LibStub("LibAddonMenu-2.0")
+if not LAM:RegisterWidget("header", widgetVersion) then return end
+
+local wm = WINDOW_MANAGER
+
+local function UpdateValue(control)
+    control.header:SetText(LAM.util.GetStringFromValue(control.data.name))
+end
+
+local MIN_HEIGHT = 30
+function LAMCreateControl.header(parent, headerData, controlName)
+    local control = LAM.util.CreateBaseControl(parent, headerData, controlName)
+    local isHalfWidth = control.isHalfWidth
+    local width = control:GetWidth()
+    control:SetDimensions(isHalfWidth and width / 2 or width, MIN_HEIGHT)
+
+    control.divider = wm:CreateControlFromVirtual(nil, control, "ZO_Options_Divider")
+    local divider = control.divider
+    divider:SetWidth(isHalfWidth and width / 2 or width)
+    divider:SetAnchor(TOPLEFT)
+
+    control.header = wm:CreateControlFromVirtual(nil, control, "ZO_Options_SectionTitleLabel")
+    local header = control.header
+    header:SetAnchor(TOPLEFT, divider, BOTTOMLEFT)
+    header:SetAnchor(BOTTOMRIGHT)
+    header:SetText(LAM.util.GetStringFromValue(headerData.name))
+
+    control.UpdateValue = UpdateValue
+
+    LAM.util.RegisterForRefreshIfNeeded(control)
+
+    return control
+end
diff --git a/Libs/LibAddonMenu-2.0/controls/iconpicker.lua b/Libs/LibAddonMenu-2.0/controls/iconpicker.lua
new file mode 100644
index 0000000..65c7782
--- /dev/null
+++ b/Libs/LibAddonMenu-2.0/controls/iconpicker.lua
@@ -0,0 +1,436 @@
+--[[iconpickerData = {
+    type = "iconpicker",
+    name = "My Icon Picker", -- or string id or function returning a string
+    choices = {"texture path 1", "texture path 2", "texture path 3"},
+    getFunc = function() return db.var end,
+    setFunc = function(var) db.var = var doStuff() end,
+    tooltip = "Color Picker's tooltip text.", -- or string id or function returning a string (optional)
+    choicesTooltips = {"icon tooltip 1", "icon tooltip 2", "icon tooltip 3"}, -- or array of string ids or array of functions returning a string (optional)
+    maxColumns = 5, -- number of icons in one row (optional)
+    visibleRows = 4.5, -- number of visible rows (optional)
+    iconSize = 28, -- size of the icons (optional)
+    defaultColor = ZO_ColorDef:New("FFFFFF"), -- default color of the icons (optional)
+    width = "full", --or "half" (optional)
+    beforeShow = function(control, iconPicker) return preventShow end, --(optional)
+    disabled = function() return db.someBooleanSetting end, --or boolean (optional)
+    warning = "May cause permanent awesomeness.", -- or string id or function returning a string (optional)
+    requiresReload = false, -- boolean, if set to true, the warning text will contain a notice that changes are only applied after an UI reload and any change to the value will make the "Apply Settings" button appear on the panel which will reload the UI when pressed (optional)
+    default = defaults.var, -- default value or function that returns the default value (optional)
+    reference = "MyAddonIconPicker" -- unique global reference to control (optional)
+} ]]
+
+local widgetVersion = 8
+local LAM = LibStub("LibAddonMenu-2.0")
+if not LAM:RegisterWidget("iconpicker", widgetVersion) then return end
+
+local wm = WINDOW_MANAGER
+
+local IconPickerMenu = ZO_Object:Subclass()
+local iconPicker
+LAM.util.GetIconPickerMenu = function()
+    if not iconPicker then
+        iconPicker = IconPickerMenu:New("LAMIconPicker")
+        local sceneFragment = LAM:GetAddonSettingsFragment()
+        ZO_PreHook(sceneFragment, "OnHidden", function()
+            if not iconPicker.control:IsHidden() then
+                iconPicker:Clear()
+            end
+        end)
+    end
+    return iconPicker
+end
+
+function IconPickerMenu:New(...)
+    local object = ZO_Object.New(self)
+    object:Initialize(...)
+    return object
+end
+
+function IconPickerMenu:Initialize(name)
+    local control = wm:CreateTopLevelWindow(name)
+    control:SetDrawTier(DT_HIGH)
+    control:SetHidden(true)
+    self.control = control
+
+    local scrollContainer = wm:CreateControlFromVirtual(name .. "ScrollContainer", control, "ZO_ScrollContainer")
+    -- control:SetDimensions(control.container:GetWidth(), height) -- adjust to icon size / col count
+    scrollContainer:SetAnchorFill()
+    ZO_Scroll_SetUseFadeGradient(scrollContainer, false)
+    ZO_Scroll_SetHideScrollbarOnDisable(scrollContainer, false)
+    ZO_VerticalScrollbarBase_OnMouseExit(scrollContainer:GetNamedChild("ScrollBar")) -- scrollbar initialization seems to be broken so we force it to update the correct alpha value
+    local scroll = GetControl(scrollContainer, "ScrollChild")
+    self.scroll = scroll
+    self.scrollContainer = scrollContainer
+
+    local bg = wm:CreateControl(nil, scrollContainer, CT_BACKDROP)
+    bg:SetAnchor(TOPLEFT, scrollContainer, TOPLEFT, 0, -3)
+    bg:SetAnchor(BOTTOMRIGHT, scrollContainer, BOTTOMRIGHT, 2, 5)
+    bg:SetEdgeTexture("EsoUI\\Art\\Tooltips\\UI-Border.dds", 128, 16)
+    bg:SetCenterTexture("EsoUI\\Art\\Tooltips\\UI-TooltipCenter.dds")
+    bg:SetInsets(16, 16, -16, -16)
+
+    local mungeOverlay = wm:CreateControl(nil, bg, CT_TEXTURE)
+    mungeOverlay:SetTexture("EsoUI/Art/Tooltips/munge_overlay.dds")
+    mungeOverlay:SetDrawLevel(1)
+    mungeOverlay:SetAddressMode(TEX_MODE_WRAP)
+    mungeOverlay:SetAnchorFill()
+
+    local mouseOver = wm:CreateControl(nil, scrollContainer, CT_TEXTURE)
+    mouseOver:SetDrawLevel(2)
+    mouseOver:SetTexture("EsoUI/Art/Buttons/minmax_mouseover.dds")
+    mouseOver:SetHidden(true)
+
+    local function IconFactory(pool)
+        local icon = wm:CreateControl(name .. "Entry" .. pool:GetNextControlId(), scroll, CT_TEXTURE)
+        icon:SetMouseEnabled(true)
+        icon:SetDrawLevel(3)
+        icon:SetHandler("OnMouseEnter", function()
+            mouseOver:SetAnchor(TOPLEFT, icon, TOPLEFT, 0, 0)
+            mouseOver:SetAnchor(BOTTOMRIGHT, icon, BOTTOMRIGHT, 0, 0)
+            mouseOver:SetHidden(false)
+            if self.customOnMouseEnter then
+                self.customOnMouseEnter(icon)
+            else
+                self:OnMouseEnter(icon)
+            end
+        end)
+        icon:SetHandler("OnMouseExit", function()
+            mouseOver:ClearAnchors()
+            mouseOver:SetHidden(true)
+            if self.customOnMouseExit then
+                self.customOnMouseExit(icon)
+            else
+                self:OnMouseExit(icon)
+            end
+        end)
+        icon:SetHandler("OnMouseUp", function(control, ...)
+            PlaySound("Click")
+            icon.OnSelect(icon, icon.texture)
+            self:Clear()
+        end)
+        return icon
+    end
+
+    local function ResetFunction(icon)
+        icon:ClearAnchors()
+    end
+
+    self.iconPool = ZO_ObjectPool:New(IconFactory, ResetFunction)
+    self:SetMaxColumns(1)
+    self.icons = {}
+    self.color = ZO_DEFAULT_ENABLED_COLOR
+
+    EVENT_MANAGER:RegisterForEvent(name .. "_OnGlobalMouseUp", EVENT_GLOBAL_MOUSE_UP, function()
+        if self.refCount ~= nil then
+            local moc = wm:GetMouseOverControl()
+            if(moc:GetOwningWindow() ~= control) then
+                self.refCount = self.refCount - 1
+                if self.refCount <= 0 then
+                    self:Clear()
+                end
+            end
+        end
+    end)
+end
+
+function IconPickerMenu:OnMouseEnter(icon)
+    InitializeTooltip(InformationTooltip, icon, TOPLEFT, 0, 0, BOTTOMRIGHT)
+    SetTooltipText(InformationTooltip, LAM.util.GetStringFromValue(icon.tooltip))
+    InformationTooltipTopLevel:BringWindowToTop()
+end
+
+function IconPickerMenu:OnMouseExit(icon)
+    ClearTooltip(InformationTooltip)
+end
+
+function IconPickerMenu:SetMaxColumns(value)
+    self.maxCols = value ~= nil and value or 5
+end
+
+local DEFAULT_SIZE = 28
+function IconPickerMenu:SetIconSize(value)
+    local iconSize = DEFAULT_SIZE
+    if value ~= nil then iconSize = math.max(iconSize, value) end
+    self.iconSize = iconSize
+end
+
+function IconPickerMenu:SetVisibleRows(value)
+    self.visibleRows = value ~= nil and value or 4.5
+end
+
+function IconPickerMenu:SetMouseHandlers(onEnter, onExit)
+    self.customOnMouseEnter = onEnter
+    self.customOnMouseExit = onExit
+end
+
+function IconPickerMenu:UpdateDimensions()
+    local iconSize = self.iconSize
+    local width = iconSize * self.maxCols + 20
+    local height = iconSize * self.visibleRows
+    self.control:SetDimensions(width, height)
+
+    local icons = self.icons
+    for i = 1, #icons do
+        local icon = icons[i]
+        icon:SetDimensions(iconSize, iconSize)
+    end
+end
+
+function IconPickerMenu:UpdateAnchors()
+    local iconSize = self.iconSize
+    local col, maxCols = 1, self.maxCols
+    local previousCol, previousRow
+    local scroll = self.scroll
+    local icons = self.icons
+
+    for i = 1, #icons do
+        local icon = icons[i]
+        icon:ClearAnchors()
+        if i == 1 then
+            icon:SetAnchor(TOPLEFT, scroll, TOPLEFT, 0, 0)
+            previousRow = icon
+        elseif col == 1 then
+            icon:SetAnchor(TOPLEFT, previousRow, BOTTOMLEFT, 0, 0)
+            previousRow = icon
+        else
+            icon:SetAnchor(TOPLEFT, previousCol, TOPRIGHT, 0, 0)
+        end
+        previousCol = icon
+        col = col >= maxCols and 1 or col + 1
+    end
+end
+
+function IconPickerMenu:Clear()
+    self.icons = {}
+    self.iconPool:ReleaseAllObjects()
+    self.control:SetHidden(true)
+    self.color = ZO_DEFAULT_ENABLED_COLOR
+    self.refCount = nil
+    self.parent = nil
+    self.customOnMouseEnter = nil
+    self.customOnMouseExit = nil
+end
+
+function IconPickerMenu:AddIcon(texturePath, callback, tooltip)
+    local icon, key = self.iconPool:AcquireObject()
+    icon:SetTexture(texturePath)
+    icon:SetColor(self.color:UnpackRGBA())
+    icon.texture = texturePath
+    icon.tooltip = tooltip
+    icon.OnSelect = callback
+    self.icons[#self.icons + 1] = icon
+end
+
+function IconPickerMenu:Show(parent)
+    if #self.icons == 0 then return false end
+    if not self.control:IsHidden() then self:Clear() return false end
+    self:UpdateDimensions()
+    self:UpdateAnchors()
+
+    local control = self.control
+    control:ClearAnchors()
+    control:SetAnchor(TOPLEFT, parent, BOTTOMLEFT, 0, 8)
+    control:SetHidden(false)
+    control:BringWindowToTop()
+    self.parent = parent
+    self.refCount = 2
+
+    return true
+end
+
+function IconPickerMenu:SetColor(color)
+    local icons = self.icons
+    self.color = color
+    for i = 1, #icons do
+        local icon = icons[i]
+        icon:SetColor(color:UnpackRGBA())
+    end
+end
+
+-------------------------------------------------------------
+
+local function UpdateChoices(control, choices, choicesTooltips)
+    local data = control.data
+    if not choices then
+        choices, choicesTooltips = data.choices, data.choicesTooltips or {}
+    end
+    local addedChoices = {}
+
+    local iconPicker = LAM.util.GetIconPickerMenu()
+    iconPicker:Clear()
+    for i = 1, #choices do
+        local texture = choices[i]
+        if not addedChoices[texture] then -- remove duplicates
+            iconPicker:AddIcon(choices[i], function(self, texture)
+                control.icon:SetTexture(texture)
+                data.setFunc(texture)
+                LAM.util.RequestRefreshIfNeeded(control)
+            end, LAM.util.GetStringFromValue(choicesTooltips[i]))
+        addedChoices[texture] = true
+        end
+    end
+end
+
+local function IsDisabled(control)
+    if type(control.data.disabled) == "function" then
+        return control.data.disabled()
+    else
+        return control.data.disabled
+    end
+end
+
+local function SetColor(control, color)
+    local icon = control.icon
+    if IsDisabled(control) then
+        icon:SetColor(ZO_DEFAULT_DISABLED_COLOR:UnpackRGBA())
+    else
+        icon.color = color or control.data.defaultColor or ZO_DEFAULT_ENABLED_COLOR
+        icon:SetColor(icon.color:UnpackRGBA())
+    end
+
+    local iconPicker = LAM.util.GetIconPickerMenu()
+    if iconPicker.parent == control.container and not iconPicker.control:IsHidden() then
+        iconPicker:SetColor(icon.color)
+    end
+end
+
+local function UpdateDisabled(control)
+    local disable = IsDisabled(control)
+
+    control.dropdown:SetMouseEnabled(not disable)
+    control.dropdownButton:SetEnabled(not disable)
+
+    local iconPicker = LAM.util.GetIconPickerMenu()
+    if iconPicker.parent == control.container and not iconPicker.control:IsHidden() then
+        iconPicker:Clear()
+    end
+
+    SetColor(control, control.icon.color)
+    if disable then
+        control.label:SetColor(ZO_DEFAULT_DISABLED_COLOR:UnpackRGBA())
+    else
+        control.label:SetColor(ZO_DEFAULT_ENABLED_COLOR:UnpackRGBA())
+    end
+end
+
+local function UpdateValue(control, forceDefault, value)
+    if forceDefault then --if we are forcing defaults
+        value = LAM.util.GetDefaultValue(control.data.default)
+        control.data.setFunc(value)
+        control.icon:SetTexture(value)
+    elseif value then
+        control.data.setFunc(value)
+        --after setting this value, let's refresh the others to see if any should be disabled or have their settings changed
+        LAM.util.RequestRefreshIfNeeded(control)
+    else
+        value = control.data.getFunc()
+        control.icon:SetTexture(value)
+    end
+end
+
+local MIN_HEIGHT = 26
+local HALF_WIDTH_LINE_SPACING = 2
+local function SetIconSize(control, size)
+    local icon = control.icon
+    icon.size = size
+    icon:SetDimensions(size, size)
+
+    local height = size + 4
+    control.dropdown:SetDimensions(size + 20, height)
+    height = math.max(height, MIN_HEIGHT)
+    control.container:SetHeight(height)
+    if control.lineControl then
+        control.lineControl:SetHeight(MIN_HEIGHT + size + HALF_WIDTH_LINE_SPACING)
+    else
+        control:SetHeight(height)
+    end
+
+    local iconPicker = LAM.util.GetIconPickerMenu()
+    if iconPicker.parent == control.container and not iconPicker.control:IsHidden() then
+        iconPicker:SetIconSize(size)
+        iconPicker:UpdateDimensions()
+        iconPicker:UpdateAnchors()
+    end
+end
+
+function LAMCreateControl.iconpicker(parent, iconpickerData, controlName)
+    local control = LAM.util.CreateLabelAndContainerControl(parent, iconpickerData, controlName)
+
+    local function ShowIconPicker()
+        local iconPicker = LAM.util.GetIconPickerMenu()
+        if iconPicker.parent == control.container then
+            iconPicker:Clear()
+        else
+            iconPicker:SetMaxColumns(iconpickerData.maxColumns)
+            iconPicker:SetVisibleRows(iconpickerData.visibleRows)
+            iconPicker:SetIconSize(control.icon.size)
+            UpdateChoices(control)
+            iconPicker:SetColor(control.icon.color)
+            if iconpickerData.beforeShow then
+                if iconpickerData.beforeShow(control, iconPicker) then
+                    iconPicker:Clear()
+                    return
+                end
+            end
+            iconPicker:Show(control.container)
+        end
+    end
+
+    local iconSize = iconpickerData.iconSize ~= nil and iconpickerData.iconSize or DEFAULT_SIZE
+    control.dropdown = wm:CreateControl(nil, control.container, CT_CONTROL)
+    local dropdown = control.dropdown
+    dropdown:SetAnchor(LEFT, control.container, LEFT, 0, 0)
+    dropdown:SetMouseEnabled(true)
+    dropdown:SetHandler("OnMouseUp", ShowIconPicker)
+    dropdown:SetHandler("OnMouseEnter", function() ZO_Options_OnMouseEnter(control) end)
+    dropdown:SetHandler("OnMouseExit", function() ZO_Options_OnMouseExit(control) end)
+
+    control.icon = wm:CreateControl(nil, dropdown, CT_TEXTURE)
+    local icon = control.icon
+    icon:SetAnchor(LEFT, dropdown, LEFT, 3, 0)
+    icon:SetDrawLevel(2)
+
+    local dropdownButton = wm:CreateControlFromVirtual(nil, dropdown, "ZO_DropdownButton")
+    dropdownButton:SetDimensions(16, 16)
+    dropdownButton:SetHandler("OnClicked", ShowIconPicker)
+    dropdownButton:SetAnchor(RIGHT, dropdown, RIGHT, -3, 0)
+    control.dropdownButton = dropdownButton
+
+    control.bg = wm:CreateControl(nil, dropdown, CT_BACKDROP)
+    local bg = control.bg
+    bg:SetAnchor(TOPLEFT, dropdown, TOPLEFT, 0, -3)
+    bg:SetAnchor(BOTTOMRIGHT, dropdown, BOTTOMRIGHT, 2, 5)
+    bg:SetEdgeTexture("EsoUI/Art/Tooltips/UI-Border.dds", 128, 16)
+    bg:SetCenterTexture("EsoUI/Art/Tooltips/UI-TooltipCenter.dds")
+    bg:SetInsets(16, 16, -16, -16)
+    local mungeOverlay = wm:CreateControl(nil, bg, CT_TEXTURE)
+    mungeOverlay:SetTexture("EsoUI/Art/Tooltips/munge_overlay.dds")
+    mungeOverlay:SetDrawLevel(1)
+    mungeOverlay:SetAddressMode(TEX_MODE_WRAP)
+    mungeOverlay:SetAnchorFill()
+
+    if iconpickerData.warning ~= nil or iconpickerData.requiresReload then
+        control.warning = wm:CreateControlFromVirtual(nil, control, "ZO_Options_WarningIcon")
+        control.warning:SetAnchor(RIGHT, control.container, LEFT, -5, 0)
+        control.UpdateWarning = LAM.util.UpdateWarning
+        control:UpdateWarning()
+    end
+
+    control.UpdateChoices = UpdateChoices
+    control.UpdateValue = UpdateValue
+    control:UpdateValue()
+    control.SetColor = SetColor
+    control:SetColor()
+    control.SetIconSize = SetIconSize
+    control:SetIconSize(iconSize)
+
+    if iconpickerData.disabled ~= nil then
+        control.UpdateDisabled = UpdateDisabled
+        control:UpdateDisabled()
+    end
+
+    LAM.util.RegisterForRefreshIfNeeded(control)
+    LAM.util.RegisterForReloadIfNeeded(control)
+
+    return control
+end
diff --git a/Libs/LibAddonMenu-2.0/controls/panel.lua b/Libs/LibAddonMenu-2.0/controls/panel.lua
new file mode 100644
index 0000000..1404686
--- /dev/null
+++ b/Libs/LibAddonMenu-2.0/controls/panel.lua
@@ -0,0 +1,126 @@
+--[[panelData = {
+    type = "panel",
+    name = "Window Title", -- or string id or function returning a string
+    displayName = "My Longer Window Title",  -- or string id or function returning a string (optional) (can be useful for long addon names or if you want to colorize it)
+    author = "Seerah",  -- or string id or function returning a string (optional)
+    version = "2.0",  -- or string id or function returning a string (optional)
+    website = "http://www.esoui.com/downloads/info7-LibAddonMenu.html", -- URL of website where the addon can be updated (optional)
+    keywords = "settings", -- additional keywords for search filter (it looks for matches in name..keywords..author) (optional)
+    slashCommand = "/myaddon", -- will register a keybind to open to this panel (don't forget to include the slash!) (optional)
+    registerForRefresh = true, --boolean (optional) (will refresh all options controls when a setting is changed and when the panel is shown)
+    registerForDefaults = true, --boolean (optional) (will set all options controls back to default values)
+    resetFunc = function() print("defaults reset") end, --(optional) custom function to run after settings are reset to defaults
+} ]]
+
+
+local widgetVersion = 13
+local LAM = LibStub("LibAddonMenu-2.0")
+if not LAM:RegisterWidget("panel", widgetVersion) then return end
+
+local wm = WINDOW_MANAGER
+local cm = CALLBACK_MANAGER
+
+local function RefreshPanel(control)
+    local panel = LAM.util.GetTopPanel(control) --callback can be fired by a single control, by the panel showing or by a nested submenu
+    local panelControls = panel.controlsToRefresh
+
+    for i = 1, #panelControls do
+        local updateControl = panelControls[i]
+        if updateControl ~= control and updateControl.UpdateValue then
+            updateControl:UpdateValue()
+        end
+        if updateControl.UpdateDisabled then
+            updateControl:UpdateDisabled()
+        end
+        if updateControl.UpdateWarning then
+            updateControl:UpdateWarning()
+        end
+    end
+end
+
+local function ForceDefaults(panel)
+    local panelControls = panel.controlsToRefresh
+
+    for i = 1, #panelControls do
+        local updateControl = panelControls[i]
+        if updateControl.UpdateValue and updateControl.data.default ~= nil then
+            updateControl:UpdateValue(true)
+        end
+    end
+
+    if panel.data.resetFunc then
+        panel.data.resetFunc()
+    end
+
+    cm:FireCallbacks("LAM-RefreshPanel", panel)
+end
+
+local callbackRegistered = false
+LAMCreateControl.scrollCount = LAMCreateControl.scrollCount or 1
+local SEPARATOR = " - "
+local LINK_COLOR = ZO_ColorDef:New("5959D5")
+local LINK_MOUSE_OVER_COLOR = ZO_ColorDef:New("B8B8D3")
+
+function LAMCreateControl.panel(parent, panelData, controlName)
+    local control = wm:CreateControl(controlName, parent, CT_CONTROL)
+
+    control.label = wm:CreateControlFromVirtual(nil, control, "ZO_Options_SectionTitleLabel")
+    local label = control.label
+    label:SetAnchor(TOPLEFT, control, TOPLEFT, 0, 4)
+    label:SetText(LAM.util.GetStringFromValue(panelData.displayName or panelData.name))
+
+    if panelData.author or panelData.version then
+        control.info = wm:CreateControl(nil, control, CT_LABEL)
+        local info = control.info
+        info:SetFont(LAM.util.L["PANEL_INFO_FONT"])
+        info:SetAnchor(TOPLEFT, label, BOTTOMLEFT, 0, -2)
+
+        local output = {}
+        if panelData.author then
+            output[#output + 1] = zo_strformat(LAM.util.L["AUTHOR"], LAM.util.GetStringFromValue(panelData.author))
+        end
+        if panelData.version then
+            output[#output + 1] = zo_strformat(LAM.util.L["VERSION"], LAM.util.GetStringFromValue(panelData.version))
+        end
+        info:SetText(table.concat(output, SEPARATOR))
+    end
+
+    if panelData.website then
+        control.website = wm:CreateControl(nil, control, CT_BUTTON)
+        local website = control.website
+        website:SetClickSound("Click")
+        website:SetFont(LAM.util.L["PANEL_INFO_FONT"])
+        website:SetNormalFontColor(LINK_COLOR:UnpackRGBA())
+        website:SetMouseOverFontColor(LINK_MOUSE_OVER_COLOR:UnpackRGBA())
+        if(control.info) then
+            website:SetAnchor(TOPLEFT, control.info, TOPRIGHT, 0, 0)
+            website:SetText(string.format("|cffffff%s|r%s", SEPARATOR, LAM.util.L["WEBSITE"]))
+        else
+            website:SetAnchor(TOPLEFT, label, BOTTOMLEFT, 0, -2)
+            website:SetText(LAM.util.L["WEBSITE"])
+        end
+        website:SetDimensions(website:GetLabelControl():GetTextDimensions())
+        website:SetHandler("OnClicked", function()
+            RequestOpenUnsafeURL(panelData.website)
+        end)
+    end
+
+    control.container = wm:CreateControlFromVirtual("LAMAddonPanelContainer"..LAMCreateControl.scrollCount, control, "ZO_ScrollContainer")
+    LAMCreateControl.scrollCount = LAMCreateControl.scrollCount + 1
+    local container = control.container
+    container:SetAnchor(TOPLEFT, control.info or label, BOTTOMLEFT, 0, 20)
+    container:SetAnchor(BOTTOMRIGHT, control, BOTTOMRIGHT, -3, -3)
+    control.scroll = GetControl(control.container, "ScrollChild")
+    control.scroll:SetResizeToFitPadding(0, 20)
+
+    if panelData.registerForRefresh and not callbackRegistered then --don't want to register our callback more than once
+        cm:RegisterCallback("LAM-RefreshPanel", RefreshPanel)
+        callbackRegistered = true
+    end
+
+    control.ForceDefaults = ForceDefaults
+    control.data = panelData
+    control.controlsToRefresh = {}
+
+    return control
+end
diff --git a/Libs/LibAddonMenu-2.0/controls/slider.lua b/Libs/LibAddonMenu-2.0/controls/slider.lua
new file mode 100644
index 0000000..bd721c5
--- /dev/null
+++ b/Libs/LibAddonMenu-2.0/controls/slider.lua
@@ -0,0 +1,212 @@
+--[[sliderData = {
+    type = "slider",
+    name = "My Slider", -- or string id or function returning a string
+    getFunc = function() return db.var end,
+    setFunc = function(value) db.var = value doStuff() end,
+    min = 0,
+    max = 20,
+    step = 1, --(optional)
+    clampInput = true, -- boolean, if set to false the input won't clamp to min and max and allow any number instead (optional)
+    decimals = 0, -- when specified the input value is rounded to the specified number of decimals (optional)
+    autoSelect = false, -- boolean, automatically select everything in the text input field when it gains focus (optional)
+    inputLocation = "below", -- or "right", determines where the input field is shown. This should not be used within the addon menu and is for custom sliders (optional)
+    tooltip = "Slider's tooltip text.", -- or string id or function returning a string (optional)
+    width = "full", --or "half" (optional)
+    disabled = function() return db.someBooleanSetting end, --or boolean (optional)
+    warning = "May cause permanent awesomeness.", -- or string id or function returning a string (optional)
+    requiresReload = false, -- boolean, if set to true, the warning text will contain a notice that changes are only applied after an UI reload and any change to the value will make the "Apply Settings" button appear on the panel which will reload the UI when pressed (optional)
+    default = defaults.var, -- default value or function that returns the default value (optional)
+    reference = "MyAddonSlider" -- unique global reference to control (optional)
+} ]]
+
+local widgetVersion = 12
+local LAM = LibStub("LibAddonMenu-2.0")
+if not LAM:RegisterWidget("slider", widgetVersion) then return end
+
+local wm = WINDOW_MANAGER
+local strformat = string.format
+
+local function RoundDecimalToPlace(d, place)
+    return tonumber(strformat("%." .. tostring(place) .. "f", d))
+end
+
+local function UpdateDisabled(control)
+    local disable
+    if type(control.data.disabled) == "function" then
+        disable = control.data.disabled()
+    else
+        disable = control.data.disabled
+    end
+
+    control.slider:SetEnabled(not disable)
+    control.slidervalue:SetEditEnabled(not disable)
+    if disable then
+        control.label:SetColor(ZO_DEFAULT_DISABLED_COLOR:UnpackRGBA())
+        control.minText:SetColor(ZO_DEFAULT_DISABLED_COLOR:UnpackRGBA())
+        control.maxText:SetColor(ZO_DEFAULT_DISABLED_COLOR:UnpackRGBA())
+        control.slidervalue:SetColor(ZO_DEFAULT_DISABLED_MOUSEOVER_COLOR:UnpackRGBA())
+    else
+        control.label:SetColor(ZO_DEFAULT_ENABLED_COLOR:UnpackRGBA())
+        control.minText:SetColor(ZO_DEFAULT_ENABLED_COLOR:UnpackRGBA())
+        control.maxText:SetColor(ZO_DEFAULT_ENABLED_COLOR:UnpackRGBA())
+        control.slidervalue:SetColor(ZO_DEFAULT_ENABLED_COLOR:UnpackRGBA())
+    end
+end
+
+local function UpdateValue(control, forceDefault, value)
+    if forceDefault then --if we are forcing defaults
+        value = LAM.util.GetDefaultValue(control.data.default)
+        control.data.setFunc(value)
+    elseif value then
+        if control.data.decimals then
+            value = RoundDecimalToPlace(value, control.data.decimals)
+        end
+        if control.data.clampInput ~= false then
+            value = math.max(math.min(value, control.data.max), control.data.min)
+        end
+        control.data.setFunc(value)
+        --after setting this value, let's refresh the others to see if any should be disabled or have their settings changed
+        LAM.util.RequestRefreshIfNeeded(control)
+    else
+        value = control.data.getFunc()
+    end
+
+    control.slider:SetValue(value)
+    control.slidervalue:SetText(value)
+end
+
+function LAMCreateControl.slider(parent, sliderData, controlName)
+    local control = LAM.util.CreateLabelAndContainerControl(parent, sliderData, controlName)
+    local isInputOnRight = sliderData.inputLocation == "right"
+
+    --skipping creating the backdrop...  Is this the actual slider texture?
+    control.slider = wm:CreateControl(nil, control.container, CT_SLIDER)
+    local slider = control.slider
+    slider:SetAnchor(TOPLEFT)
+    slider:SetHeight(14)
+    if(isInputOnRight) then
+        slider:SetAnchor(TOPRIGHT, nil, nil, -60)
+    else
+        slider:SetAnchor(TOPRIGHT)
+    end
+    slider:SetMouseEnabled(true)
+    slider:SetOrientation(ORIENTATION_HORIZONTAL)
+    --put nil for highlighted texture file path, and what look to be texture coords
+    slider:SetThumbTexture("EsoUI\\Art\\Miscellaneous\\scrollbox_elevator.dds", "EsoUI\\Art\\Miscellaneous\\scrollbox_elevator_disabled.dds", nil, 8, 16)
+    local minValue = sliderData.min
+    local maxValue = sliderData.max
+    slider:SetMinMax(minValue, maxValue)
+    slider:SetHandler("OnMouseEnter", function() ZO_Options_OnMouseEnter(control) end)
+    slider:SetHandler("OnMouseExit", function() ZO_Options_OnMouseExit(control) end)
+
+    slider.bg = wm:CreateControl(nil, slider, CT_BACKDROP)
+    local bg = slider.bg
+    bg:SetCenterColor(0, 0, 0)
+    bg:SetAnchor(TOPLEFT, slider, TOPLEFT, 0, 4)
+    bg:SetAnchor(BOTTOMRIGHT, slider, BOTTOMRIGHT, 0, -4)
+    bg:SetEdgeTexture("EsoUI\\Art\\Tooltips\\UI-SliderBackdrop.dds", 32, 4)
+
+    control.minText = wm:CreateControl(nil, slider, CT_LABEL)
+    local minText = control.minText
+    minText:SetFont("ZoFontGameSmall")
+    minText:SetAnchor(TOPLEFT, slider, BOTTOMLEFT)
+    minText:SetText(sliderData.min)
+
+    control.maxText = wm:CreateControl(nil, slider, CT_LABEL)
+    local maxText = control.maxText
+    maxText:SetFont("ZoFontGameSmall")
+    maxText:SetAnchor(TOPRIGHT, slider, BOTTOMRIGHT)
+    maxText:SetText(sliderData.max)
+
+    control.slidervalueBG = wm:CreateControlFromVirtual(nil, slider, "ZO_EditBackdrop")
+    if(isInputOnRight) then
+        control.slidervalueBG:SetDimensions(60, 26)
+        control.slidervalueBG:SetAnchor(LEFT, slider, RIGHT, 5, 0)
+    else
+        control.slidervalueBG:SetDimensions(50, 16)
+        control.slidervalueBG:SetAnchor(TOP, slider, BOTTOM, 0, 0)
+    end
+    control.slidervalue = wm:CreateControlFromVirtual(nil, control.slidervalueBG, "ZO_DefaultEditForBackdrop")
+    local slidervalue = control.slidervalue
+    slidervalue:ClearAnchors()
+    slidervalue:SetAnchor(TOPLEFT, control.slidervalueBG, TOPLEFT, 3, 1)
+    slidervalue:SetAnchor(BOTTOMRIGHT, control.slidervalueBG, BOTTOMRIGHT, -3, -1)
+    slidervalue:SetTextType(TEXT_TYPE_NUMERIC)
+    if(isInputOnRight) then
+        slidervalue:SetFont("ZoFontGameLarge")
+    else
+        slidervalue:SetFont("ZoFontGameSmall")
+    end
+
+    local isHandlingChange = false
+    local function HandleValueChanged(value)
+        if isHandlingChange then return end
+        if sliderData.decimals then
+            value = RoundDecimalToPlace(value, sliderData.decimals)
+        end
+        isHandlingChange = true
+        slider:SetValue(value)
+        slidervalue:SetText(value)
+        isHandlingChange = false
+    end
+
+    slidervalue:SetHandler("OnEscape", function(self)
+        HandleValueChanged(sliderData.getFunc())
+        self:LoseFocus()
+    end)
+    slidervalue:SetHandler("OnEnter", function(self)
+        self:LoseFocus()
+    end)
+    slidervalue:SetHandler("OnFocusLost", function(self)
+        local value = tonumber(self:GetText())
+        control:UpdateValue(false, value)
+    end)
+    slidervalue:SetHandler("OnTextChanged", function(self)
+        local input = self:GetText()
+        if(#input > 1 and not input:sub(-1):match("[0-9]")) then return end
+        local value = tonumber(input)
+        if(value) then
+            HandleValueChanged(value)
+        end
+    end)
+    if(sliderData.autoSelect) then
+        ZO_PreHookHandler(slidervalue, "OnFocusGained", function(self)
+            self:SelectAll()
+        end)
+    end
+
+    local range = maxValue - minValue
+    slider:SetValueStep(sliderData.step or 1)
+    slider:SetHandler("OnValueChanged", function(self, value, eventReason)
+        if eventReason == EVENT_REASON_SOFTWARE then return end
+        HandleValueChanged(value)
+    end)
+    slider:SetHandler("OnSliderReleased", function(self, value)
+        control:UpdateValue(false, value)
+    end)
+    slider:SetHandler("OnMouseWheel", function(self, value)
+        if(not self:GetEnabled()) then return end
+        local new_value = (tonumber(slidervalue:GetText()) or sliderData.min or 0) + ((sliderData.step or 1) * value)
+        control:UpdateValue(false, new_value)
+    end)
+
+    if sliderData.warning ~= nil or sliderData.requiresReload then
+        control.warning = wm:CreateControlFromVirtual(nil, control, "ZO_Options_WarningIcon")
+        control.warning:SetAnchor(RIGHT, slider, LEFT, -5, 0)
+        control.UpdateWarning = LAM.util.UpdateWarning
+        control:UpdateWarning()
+    end
+
+    control.UpdateValue = UpdateValue
+    control:UpdateValue()
+
+    if sliderData.disabled ~= nil then
+        control.UpdateDisabled = UpdateDisabled
+        control:UpdateDisabled()
+    end
+
+    LAM.util.RegisterForRefreshIfNeeded(control)
+    LAM.util.RegisterForReloadIfNeeded(control)
+
+    return control
+end
diff --git a/Libs/LibAddonMenu-2.0/controls/submenu.lua b/Libs/LibAddonMenu-2.0/controls/submenu.lua
new file mode 100644
index 0000000..1766a1f
--- /dev/null
+++ b/Libs/LibAddonMenu-2.0/controls/submenu.lua
@@ -0,0 +1,108 @@
+--[[submenuData = {
+    type = "submenu",
+    name = "Submenu Title", -- or string id or function returning a string
+    tooltip = "My submenu tooltip", -- -- or string id or function returning a string (optional)
+    controls = {sliderData, buttonData} --(optional) used by LAM
+    reference = "MyAddonSubmenu" --(optional) unique global reference to control
+} ]]
+
+local widgetVersion = 11
+local LAM = LibStub("LibAddonMenu-2.0")
+if not LAM:RegisterWidget("submenu", widgetVersion) then return end
+
+local wm = WINDOW_MANAGER
+local am = ANIMATION_MANAGER
+
+local function UpdateValue(control)
+    control.label:SetText(LAM.util.GetStringFromValue(control.data.name))
+    if control.data.tooltip then
+        control.label.data.tooltipText = LAM.util.GetStringFromValue(control.data.tooltip)
+    end
+end
+
+local function AnimateSubmenu(clicked)
+    local control = clicked:GetParent()
+    control.open = not control.open
+
+    if control.open then
+        control.animation:PlayFromStart()
+    else
+        control.animation:PlayFromEnd()
+    end
+end
+
+function LAMCreateControl.submenu(parent, submenuData, controlName)
+    local width = parent:GetWidth() - 45
+    local control = wm:CreateControl(controlName or submenuData.reference, parent.scroll or parent, CT_CONTROL)
+    control.panel = parent
+    control.data = submenuData
+
+    control.label = wm:CreateControlFromVirtual(nil, control, "ZO_Options_SectionTitleLabel")
+    local label = control.label
+    label:SetAnchor(TOPLEFT, control, TOPLEFT, 5, 5)
+    label:SetDimensions(width, 30)
+    label:SetWrapMode(TEXT_WRAP_MODE_ELLIPSIS)
+    label:SetText(LAM.util.GetStringFromValue(submenuData.name))
+    label:SetMouseEnabled(true)
+    if submenuData.tooltip then
+        label.data = {tooltipText = LAM.util.GetStringFromValue(submenuData.tooltip)}
+        label:SetHandler("OnMouseEnter", ZO_Options_OnMouseEnter)
+        label:SetHandler("OnMouseExit", ZO_Options_OnMouseExit)
+    end
+
+    control.scroll = wm:CreateControl(nil, control, CT_SCROLL)
+    local scroll = control.scroll
+    scroll:SetParent(control)
+    scroll:SetAnchor(TOPLEFT, label, BOTTOMLEFT, 0, 10)
+    scroll:SetDimensionConstraints(width + 5, 0, width + 5, 0)
+
+    control.bg = wm:CreateControl(nil, label, CT_BACKDROP)
+    local bg = control.bg
+    bg:SetAnchor(TOPLEFT, label, TOPLEFT, -5, -5)
+    bg:SetAnchor(BOTTOMRIGHT, scroll, BOTTOMRIGHT, -7, 0)
+    bg:SetEdgeTexture("EsoUI\\Art\\Tooltips\\UI-Border.dds", 128, 16)
+    bg:SetCenterTexture("EsoUI\\Art\\Tooltips\\UI-TooltipCenter.dds")
+    bg:SetInsets(16, 16, -16, -16)
+
+    control.arrow = wm:CreateControl(nil, bg, CT_TEXTURE)
+    local arrow = control.arrow
+    arrow:SetDimensions(28, 28)
+    arrow:SetTexture("EsoUI\\Art\\Miscellaneous\\list_sortdown.dds") --list_sortup for the other way
+    arrow:SetAnchor(TOPRIGHT, bg, TOPRIGHT, -5, 5)
+
+    --figure out the cool animation later...
+    control.animation = am:CreateTimeline()
+    local animation = control.animation
+    animation:SetPlaybackType(ANIMATION_SIZE, 0) --2nd arg = loop count
+
+    control:SetResizeToFitDescendents(true)
+    control.open = false
+    label:SetHandler("OnMouseUp", AnimateSubmenu)
+    animation:SetHandler("OnStop", function(self, completedPlaying)
+        scroll:SetResizeToFitDescendents(control.open)
+        if control.open then
+            control.arrow:SetTexture("EsoUI\\Art\\Miscellaneous\\list_sortup.dds")
+            scroll:SetResizeToFitPadding(5, 20)
+        else
+            control.arrow:SetTexture("EsoUI\\Art\\Miscellaneous\\list_sortdown.dds")
+            scroll:SetResizeToFitPadding(5, 0)
+            scroll:SetHeight(0)
+        end
+    end)
+
+    --small strip at the bottom of the submenu that you can click to close it
+    control.btmToggle = wm:CreateControl(nil, control, CT_TEXTURE)
+    local btmToggle = control.btmToggle
+    btmToggle:SetMouseEnabled(true)
+    btmToggle:SetAnchor(BOTTOMLEFT, control.scroll, BOTTOMLEFT)
+    btmToggle:SetAnchor(BOTTOMRIGHT, control.scroll, BOTTOMRIGHT)
+    btmToggle:SetHeight(15)
+    btmToggle:SetAlpha(0)
+    btmToggle:SetHandler("OnMouseUp", AnimateSubmenu)
+
+    control.UpdateValue = UpdateValue
+
+    LAM.util.RegisterForRefreshIfNeeded(control)
+
+    return control
+end
diff --git a/Libs/LibAddonMenu-2.0/controls/texture.lua b/Libs/LibAddonMenu-2.0/controls/texture.lua
new file mode 100644
index 0000000..29dda7c
--- /dev/null
+++ b/Libs/LibAddonMenu-2.0/controls/texture.lua
@@ -0,0 +1,45 @@
+--[[textureData = {
+    type = "texture",
+    image = "file/path.dds",
+    imageWidth = 64, --max of 250 for half width, 510 for full
+    imageHeight = 32, --max of 100
+    tooltip = "Image's tooltip text.", -- or string id or function returning a string (optional)
+    width = "full", --or "half" (optional)
+    reference = "MyAddonTexture" --(optional) unique global reference to control
+} ]]
+
+--add texture coords support?
+
+local widgetVersion = 9
+local LAM = LibStub("LibAddonMenu-2.0")
+if not LAM:RegisterWidget("texture", widgetVersion) then return end
+
+local wm = WINDOW_MANAGER
+
+local MIN_HEIGHT = 26
+function LAMCreateControl.texture(parent, textureData, controlName)
+    local control = LAM.util.CreateBaseControl(parent, textureData, controlName)
+    local width = control:GetWidth()
+    control:SetResizeToFitDescendents(true)
+
+    if control.isHalfWidth then --note these restrictions
+        control:SetDimensionConstraints(width / 2, MIN_HEIGHT, width / 2, MIN_HEIGHT * 4)
+    else
+        control:SetDimensionConstraints(width, MIN_HEIGHT, width, MIN_HEIGHT * 4)
+    end
+
+    control.texture = wm:CreateControl(nil, control, CT_TEXTURE)
+    local texture = control.texture
+    texture:SetAnchor(CENTER)
+    texture:SetDimensions(textureData.imageWidth, textureData.imageHeight)
+    texture:SetTexture(textureData.image)
+
+    if textureData.tooltip then
+        texture:SetMouseEnabled(true)
+        texture.data = {tooltipText = LAM.util.GetStringFromValue(textureData.tooltip)}
+        texture:SetHandler("OnMouseEnter", ZO_Options_OnMouseEnter)
+        texture:SetHandler("OnMouseExit", ZO_Options_OnMouseExit)
+    end
+
+    return control
+end
diff --git a/Libs/LibFeedback/LibFeedback.txt b/Libs/LibFeedback/LibFeedback.txt
new file mode 100755
index 0000000..42cde9a
--- /dev/null
+++ b/Libs/LibFeedback/LibFeedback.txt
@@ -0,0 +1,11 @@
+## Title: LibFeedback
+## Author: Dolgubon
+## APIVersion: 100024 100025
+## Version: 1.1
+## Description: This is a library which allows Addon Authors to easily add a feedback window to their addon
+## DependsOn: LibStub
+
+## This Add-On is not created by, affiliated with or sponsored by ZeniMax Media Inc. or its affiliates. The Elder Scrolls® and related logos are registered trademarks or trademarks of ZeniMax Media Inc. in the United States and/or other countries. All rights reserved.
+
+libs/LibStub.lua
+feedback.lua
diff --git a/Libs/LibFeedback/feedback.lua b/Libs/LibFeedback/feedback.lua
new file mode 100755
index 0000000..127b173
--- /dev/null
+++ b/Libs/LibFeedback/feedback.lua
@@ -0,0 +1,250 @@
+-- Modified by Dolgubon based off code from Master Merchant with permission
+-- Master Merchant was written by Dan Stone (aka @khaibit) / Chris Lasswell (aka @Philgo68)
+
+--[[
+
+Use:
+
+local LibFeedback = LibStub:GetLibrary('LibFeedback')
+-- The button is returned so you can modify the button if needed
+-- ExampleAddonNameSpace.feedbackButton = LibFeedback:initializeFeedbackWindow(
+ExampleAddonNameSpace, -- namespace of the addon
+"Example Addon", -- The title string for the feedback window and the mails it sends
+parentControl, -- The parent control to anchor the feedback button(s) + label(s) to
+"@AddonAuthor", -- If this parameter is no table: [1st parameter] like desribed below:
+                    -- The destination for feedback (0 gold attachment) and donation mails, valid for all servers
+                -- If this parameter is a table:
+                -- Example: { addonVars.addonAuthorDisplayNameEU, addonVars.addonAuthorDisplayNameNA, addonVars.addonAuthorDisplayNamePTS },
+                    -- [1st parameter]Addon author name or character name at the EU Megaserver
+                    -- [2nd parameter]Addon author name or character name at the NA Megaserver
+                    -- [3rd parameter]Addon author name or character name at the PTS (Testserver)
+{TOPLEFT, owningWindow, TOPLEFT, x, y}, -- The position of the mail button icon. owningWindow: Parent control for the button. x and y: Integer values for the offsets
+{0,5000,50000, "https://www.genericexampleurl.com/somemoregenericiness"} -- The button info:
+            -- Can theoretically do any number of options, it *should* handle them
+            -- If this parameter is no table: [1st parameter] like desribed below:
+                -- If 0: Will not attach any gold, and will say 'Send Note'
+                -- If non zero: Will auto attach that amount of gold
+                -- If URL: Will show a dialog box and ask the user if they want to go to the URL.
+            -- If this parameter is a table:
+                -- Example: [index]= {[1st parameter]            [2nd parameter]                                 [3rd parameter] },
+                --            [1] = { 0,                         localization.feedbackSendNote,                  false },    -- Send ingame mail
+                --            [2] = { 10000,                     localization.feedbackSendGold,                  true },     -- Send 10000 gold
+                --            [3] = { addonVars.authorPortal,    localization.feedbackOpenAddonAuthorWebsite,    false },    -- Open URL
+                --            [4] = { addonVars.FAQwebsite,      localization.feedbackOpenAddonFAQ,              false }     -- Open URL
+                -- [1st parameter]Integer. When >0: Gold value to send/Integer. Gold will only be send if 3rd parameter is true. / When Integer==0: Show the 2nd parameter string as button text and send ingame mail. / When String <> "": Show the 2nd parameter string as button text and open the URL from 1st parameter in Webbrowser
+                -- [2nd parameter]String to show as button text.
+                -- [3rd parameter]Boolean send gold. True: Send mail with attached gold value from 1st parameter/False: Send normal mail without gold attached
+
+"If you found a bug, have a request or a suggestion, or simply wish to donate, send a mail.", -- Will be displayed as a message below the title.
+600, -- The default width of the feedback window. If more than 4 buttons this should be increased.
+150  -- The default height of the feedback window
+150, -- The default width of the feedback window's buttons
+28   -- The default height of the feedback window's buttons
+)
+]]
+
+
+local libLoaded
+local LIB_NAME, VERSION = "LibFeedback", 1.1
+local LibFeedback, oldminor = LibStub:NewLibrary(LIB_NAME, VERSION)
+if not LibFeedback then return end
+LibFeedback.debug = false
+
+local function SendNote(self)
+
+	local p = self.parent
+	if type(self.amount)=="string" then
+		RequestOpenUnsafeURL(self.amount)
+	else
+		p.parentControl:SetHidden(true)
+		p:SetHidden(true)
+		SCENE_MANAGER:Show('mailSend')
+		zo_callLater(function()
+			ZO_MailSendToField:SetText(p.mailDestination)
+			ZO_MailSendSubjectField:SetText(p.parentAddonName)
+			QueueMoneyAttachment(self.amount)
+			ZO_MailSendBodyField:TakeFocus() end, 200)
+	end
+end
+
+local function createFeedbackButton(name, owningWindow, feedbackWindowButtonWidth, feedbackWindowButtonHeight)
+	local button = WINDOW_MANAGER:CreateControlFromVirtual(name, owningWindow, "ZO_DefaultButton")
+	local b = button
+	b:SetDimensions(feedbackWindowButtonWidth, feedbackWindowButtonHeight)
+	b:SetHandler("OnClicked",function()SendNote(b) end)
+	b:SetAnchor(BOTTOMLEFT,owningWindow, BOTTOMLEFT,5,5)
+	return button
+end
+
+local function createShowFeedbackWindow(owningWindow)
+	local showButton = WINDOW_MANAGER:CreateControl(owningWindow:GetName().."ShowFeedbackWindowButton", owningWindow, CT_BUTTON)
+	local b = showButton
+	b:SetDimensions(34, 34)
+	b:SetNormalTexture("ESOUI/art/chatwindow/chat_mail_up.dds")
+	b:SetMouseOverTexture("ESOUI/art/chatwindow/chat_mail_over.dds")
+	b:SetHandler("OnClicked", function(self) self.feedbackWindow:ToggleHidden() end )
+	return showButton
+end
+
+local function createFeedbackWindow(owningWindow, messageText, feedbackWindowWidth, feedbackWindowHeight)
+	local feedbackWindow = WINDOW_MANAGER:CreateTopLevelWindow(owningWindow:GetName().."FeedbackWindow")
+	local c = feedbackWindow
+	c:SetDimensions(feedbackWindowWidth, feedbackWindowHeight)
+	c:SetMouseEnabled(true)
+	c:SetClampedToScreen(true)
+	c:SetMovable(true)
+
+	WINDOW_MANAGER:CreateControlFromVirtual(c:GetName().."BG", c, "ZO_DefaultBackdrop"):SetAnchorFill(c)
+	local l = WINDOW_MANAGER:CreateControl(c:GetName().."Label", c, CT_LABEL)
+	l:SetFont("ZoFontGame")
+	l:SetAnchor(TOP, c,TOP0, 0, 5)
+	l:SetHorizontalAlignment(TEXT_ALIGN_CENTER)
+	l:SetColor(0.83, 0.76, 0.16)
+	local b = WINDOW_MANAGER:CreateControl(c:GetName().."Close", c, CT_BUTTON)
+	b:SetAnchor(CENTER, c, TOPRIGHT, -20, 20)
+	b:SetDimensions(48, 48)
+	b:SetNormalTexture("/esoui/art/hud/radialicon_cancel_up.dds")
+	b:SetMouseOverTexture("/esoui/art/hud/radialicon_cancel_over.dds")
+	b:SetHandler("OnClicked", function(self) self:GetParent():SetHidden(true) end)
+	local n = WINDOW_MANAGER:CreateControl(c:GetName().."Note", c, CT_LABEL)
+    n:SetAnchor(TOPLEFT, c, TOPLEFT, 10, 30)
+    n:SetDimensions(feedbackWindowWidth - 20, feedbackWindowHeight - 30)
+    n:SetText(messageText)
+    --n:SetAnchorFill()
+	n:SetColor(1, 1, 1)
+	n:SetFont("ZoFontGame")
+	n:SetHorizontalAlignment(TEXT_ALIGN_CENTER)
+	return feedbackWindow
+end
+
+function LibFeedback:initializeFeedbackWindow(parentAddonNameSpace, parentAddonName, parentControl, mailDestination,  mailButtonPosition, buttonInfo,  messageText, feedbackWindowWidth, feedbackWindowHeight, feedbackWindowButtonWidth, feedbackWindowButtonHeight)
+	-- Create Default settings
+	if parentAddonNameSpace == nil or parentAddonNameSpace == "" then
+		d("|cFF0000[LibFeedback] - ERROR:|r Obligatory addon namespace is missing!")
+		return nil
+	end
+	if parentControl == nil or parentControl.GetName == nil then
+		d("|cFF0000[LibFeedback] - ERROR:|r Parent control not found for addon namespace: \"|cFFFFFF" .. tostring(parentAddonName) .. "|r\"")
+		return nil
+	end
+
+	if mailButtonPosition == nil or mailButtonPosition[2] == nil then
+		d("|cFF0000[LibFeedback] - ERROR:|r Mail button data is missing for addon namespace: \"|cFFFFFF" .. tostring(parentAddonName) .. "|r\"")
+		return nil
+	end
+    feedbackWindowHeight = feedbackWindowHeight or 150
+    feedbackWindowWidth = feedbackWindowWidth or 600
+    feedbackWindowButtonWidth = feedbackWindowButtonWidth or 150
+    feedbackWindowButtonHeight = feedbackWindowButtonHeight or 28
+
+	local feedbackWindow = createFeedbackWindow(parentControl, messageText, feedbackWindowWidth, feedbackWindowHeight)
+	parentAddonNameSpace.feedbackWindow = feedbackWindow
+	feedbackWindow.parentControl = parentControl
+	if type(mailDestination) == "table" then
+		--Get the current server and get the email address from the appropriate index of mailDestination[] then
+		--1: EU, 2: NA, 3: PTS
+		local mailAtServer = ""
+		local world = GetWorldName()
+		if world == 'PTS' then
+		    mailAtServer = mailDestination[3] or mailDestination[1] or mailDestination[2]
+		elseif world == 'EU Megaserver' then
+		    mailAtServer = mailDestination[1]
+		else
+		    mailAtServer = mailDestination[2]
+		end
+		-- No destination sepcified for this server, so exit.
+		if not mailAtServer then
+			return
+		end
+		feedbackWindow.mailDestination = mailAtServer
+    else
+	    feedbackWindow.mailDestination = mailDestination
+    end
+	feedbackWindow.parentAddonName = parentAddonName
+
+	feedbackWindow:SetAnchor(TOPLEFT,parentControl, TOPLEFT, 0,0)
+	feedbackWindow:SetHidden(true)
+
+	feedbackWindow:SetDimensions(math.max(#buttonInfo*feedbackWindowHeight, feedbackWindowWidth) , feedbackWindowHeight)
+	feedbackWindow:GetNamedChild("Label"):SetText(parentAddonName)
+
+	local buttons = {}
+	for i = 1, #buttonInfo do
+
+		buttons[#buttons+1] =  createFeedbackButton(feedbackWindow:GetName().."Button"..#buttons, feedbackWindow, feedbackWindowButtonWidth, feedbackWindowButtonHeight)
+		buttons[i]:SetAnchor(BOTTOM, feedbackWindow, BOTTOMLEFT, (i-1)*feedbackWindowHeight+70,-10)
+        local buttonData = buttonInfo[i]
+        if buttonData ~= nil then
+            local amount
+            buttons[i].SendNote = SendNote
+            buttons[i].parent = feedbackWindow
+
+            local buttonText = ""
+            local isButtonInfoDeep = (type(buttonData) == "table") or false
+            if isButtonInfoDeep then
+                if buttonData[2] == nil then buttonData[2] = "n/a" end -- Button text
+                buttonData[3] = buttonData[3] or false -- Send gold
+            end
+            local isString = (not isButtonInfoDeep and (type(buttonData) == "string") or (isButtonInfoDeep and type(buttonData[1]) == "string")) or false
+            local sendGold = (not isButtonInfoDeep and (type(buttonData) == "number" and buttonData > 0) or (isButtonInfoDeep and buttonData[3])) or false
+
+            if LibFeedback.debug then
+                d(zo_strformat("|cFF0000[LibFeedback]|r <<1>> - Button <<2>>: isButtonInfoDeep: <<3>>, isString: <<4>>, sendGold: <<5>>,", tostring(parentAddonName), tostring(i), tostring(isButtonInfoDeep), tostring(isString), tostring(sendGold)))
+                if isButtonInfoDeep then
+                    d(zo_strformat("> Param1: <<1>>, Param2: <<2>>, Param3: <<3>>,", tostring(buttonData[1]), tostring(buttonData[2]), tostring(buttonData[3])))
+                else
+                    d(zo_strformat("> Value: <<1>>", tostring(buttonData)))
+                end
+            end
+
+            --Send gold via mail
+            if sendGold then
+                if isButtonInfoDeep then
+                    buttonText = zo_strformat(buttonData[2], buttonData[1])
+                    amount = buttonData[1]
+                else
+                    buttonText = "Send "..tostring(buttonData).." gold"
+                    amount = buttonData
+                end
+            else
+                --Open URL
+                if isString then
+                    if isButtonInfoDeep then
+                        buttonText = buttonData[2]
+                        amount = buttonData[1]
+                    else
+                        buttonText = "Send $$"
+                        amount = buttonData
+                    end
+                --Show a text and open mail
+                else
+                    if isButtonInfoDeep then
+                        if buttonData[1] == 0 or  buttonData[1] == "" then
+                            buttonText = buttonData[2]
+                            amount = buttonData[1]
+                        end
+                    else
+                        if buttonData == 0 or  buttonData == "" then
+                            buttonText = "Send note"
+                            amount = buttonData
+                        end
+                    end
+                end
+            end
+            buttons[i].amount = amount
+            buttons[i]:SetText(buttonText)
+        end
+	end
+	local showButton = createShowFeedbackWindow(parentControl)
+
+	showButton.feedbackWindow = feedbackWindow
+	showButton:SetAnchor(unpack(mailButtonPosition))
+	showButton:SetDimensions(40,40)
+
+	return showButton, feedbackWindow
+end
+
+function LibFeedback:setDebug(debugValue)
+    debugValue = debugValue or false
+    LibFeedback.debug = debugValue
+end
diff --git a/Libs/LibFeedback/libs/LibStub.lua b/Libs/LibFeedback/libs/LibStub.lua
new file mode 100755
index 0000000..0e6bf67
--- /dev/null
+++ b/Libs/LibFeedback/libs/LibStub.lua
@@ -0,0 +1,38 @@
+-- LibStub is a simple versioning stub meant for use in Libraries.  http://www.wowace.com/wiki/LibStub for more info
+-- LibStub is hereby placed in the Public Domain Credits: Kaelten, Cladhaire, ckknight, Mikk, Ammo, Nevcairiel, joshborke
+-- LibStub developed for World of Warcraft by above members of the WowAce community.
+-- Ported to Elder Scrolls Online by Seerah
+
+local LIBSTUB_MAJOR, LIBSTUB_MINOR = "LibStub", 4
+local LibStub = _G[LIBSTUB_MAJOR]
+
+local strformat = string.format
+if not LibStub or LibStub.minor < LIBSTUB_MINOR then
+	LibStub = LibStub or {libs = {}, minors = {} }
+	_G[LIBSTUB_MAJOR] = LibStub
+	LibStub.minor = LIBSTUB_MINOR
+
+	function LibStub:NewLibrary(major, minor)
+		assert(type(major) == "string", "Bad argument #2 to `NewLibrary' (string expected)")
+		if type(minor) ~= "number" then
+			minor = assert(tonumber(zo_strmatch(minor, "%d+%.?%d*")), "Minor version must either be a number or contain a number.")
+		end
+
+		local oldminor = self.minors[major]
+		if oldminor and oldminor >= minor then return nil end
+		self.minors[major], self.libs[major] = minor, self.libs[major] or {}
+		return self.libs[major], oldminor
+	end
+
+	function LibStub:GetLibrary(major, silent)
+		if not self.libs[major] and not silent then
+			error(strformat("Cannot find a library instance of %q.", tostring(major)), 2)
+		end
+		return self.libs[major], self.minors[major]
+	end
+
+	function LibStub:IterateLibraries() return pairs(self.libs) end
+	setmetatable(LibStub, { __call = LibStub.GetLibrary })
+end
+
+LibStub.SILENT = true
\ No newline at end of file
diff --git a/Libs/LibStub/LibStub.lua b/Libs/LibStub/LibStub.lua
new file mode 100644
index 0000000..0e6bf67
--- /dev/null
+++ b/Libs/LibStub/LibStub.lua
@@ -0,0 +1,38 @@
+-- LibStub is a simple versioning stub meant for use in Libraries.  http://www.wowace.com/wiki/LibStub for more info
+-- LibStub is hereby placed in the Public Domain Credits: Kaelten, Cladhaire, ckknight, Mikk, Ammo, Nevcairiel, joshborke
+-- LibStub developed for World of Warcraft by above members of the WowAce community.
+-- Ported to Elder Scrolls Online by Seerah
+
+local LIBSTUB_MAJOR, LIBSTUB_MINOR = "LibStub", 4
+local LibStub = _G[LIBSTUB_MAJOR]
+
+local strformat = string.format
+if not LibStub or LibStub.minor < LIBSTUB_MINOR then
+	LibStub = LibStub or {libs = {}, minors = {} }
+	_G[LIBSTUB_MAJOR] = LibStub
+	LibStub.minor = LIBSTUB_MINOR
+
+	function LibStub:NewLibrary(major, minor)
+		assert(type(major) == "string", "Bad argument #2 to `NewLibrary' (string expected)")
+		if type(minor) ~= "number" then
+			minor = assert(tonumber(zo_strmatch(minor, "%d+%.?%d*")), "Minor version must either be a number or contain a number.")
+		end
+
+		local oldminor = self.minors[major]
+		if oldminor and oldminor >= minor then return nil end
+		self.minors[major], self.libs[major] = minor, self.libs[major] or {}
+		return self.libs[major], oldminor
+	end
+
+	function LibStub:GetLibrary(major, silent)
+		if not self.libs[major] and not silent then
+			error(strformat("Cannot find a library instance of %q.", tostring(major)), 2)
+		end
+		return self.libs[major], self.minors[major]
+	end
+
+	function LibStub:IterateLibraries() return pairs(self.libs) end
+	setmetatable(LibStub, { __call = LibStub.GetLibrary })
+end
+
+LibStub.SILENT = true
\ No newline at end of file
diff --git a/Settings.lua b/Settings.lua
new file mode 100644
index 0000000..9e6397e
--- /dev/null
+++ b/Settings.lua
@@ -0,0 +1,220 @@
+
+LeoGuildManagerSettings = ZO_Object:Subclass()
+local LAM = LibStub("LibAddonMenu-2.0")
+
+function LeoGuildManagerSettings:New(...)
+    local object = ZO_Object.New(self)
+    object:Initialize(...)
+    return object
+end
+
+function LeoGuildManagerSettings:Initialize()
+end
+
+function LeoGuildManagerSettings:CreatePanel()
+	local OptionsName = "LeoGuildManagerOptions"
+	local panelData = {
+		type = "panel",
+		name = LeoGuildManager.name,
+        slashCommand = "/leogmoptions",
+		displayName = "|c39B027"..LeoGuildManager.displayName.."|r",
+		author = "@LeandroSilva",
+		version = LeoGuildManager.version,
+		registerForRefresh = true,
+		registerForDefaults = false,
+		website = "http://www.esoui.com/downloads/info2140-LeosGuildManager.html"
+	}
+	LAM:RegisterAddonPanel(OptionsName, panelData)
+
+	local optionsData = {
+		{
+			type = "header",
+			name = "|c3f7fffConfiguration|r"
+		},{
+            type = "dropdown",
+            name = "Addon to integrate with",
+            choices = LeoGuildManager.integrations,
+            getFunc = function() return LeoGuildManager.globalData.settings.integration end,
+            setFunc = function(value) LeoGuildManager.globalData.settings.integration = value end,
+            requiresReload = true
+		},{
+            type = "checkbox",
+            name = "Scan guilds automatically",
+            default = false,
+            getFunc = function() return LeoGuildManager.globalData.settings.scanAutomatically end,
+            setFunc = function(value) LeoGuildManager.globalData.settings.scanAutomatically = value end,
+		},{
+            type = "checkbox",
+            name = "Add guild roster tooltip",
+            default = false,
+            getFunc = function() return LeoGuildManager.globalData.settings.tooltipRoster end,
+            setFunc = function(value) LeoGuildManager.globalData.settings.tooltipRoster = value end,
+		},{
+			type = "header",
+			name = "|c3f7fffGuilds|r"
+		}
+	}
+    for guildId, guild in pairs(LeoGuildManager.guilds) do
+        table.insert(optionsData, {
+            type = "checkbox",
+            name = guild,
+            default = false,
+            getFunc = function() return LeoGuildManager.globalData.settings.guilds[guild].enabled end,
+            setFunc = function(value) LeoGuildManager.globalData.settings.guilds[guild].enabled = value end,
+            requiresReload = true
+        })
+    end
+    table.insert(optionsData, {
+        type = "button",
+        name = "Reload UI",
+        width = "full",
+        func = function() ReloadUI() end,
+    })
+
+    for guildId, guild in pairs(LeoGuildManager.guilds) do
+
+        if LeoGuildManager.globalData.settings.guilds[guild].enabled == true then
+
+            local ranks = LeoGuildManager.GetGuildRanks(guildId)
+            ranks[0] = "disabled"
+
+            table.insert(optionsData, {
+                type = "submenu",
+                name = guild,
+                disabled = true,
+                controls = {
+                    {
+                        type = "header",
+                        name = "|c3f7fffPurge|r"
+                    },{
+                        type = "description",
+                        text = "|c"..LeoGuildManager.color.hex.red.."This module requires Master Merchant or Arkadiu's Trade Tools|r",
+                    },{
+                        type = "description",
+                        text = "",
+                        reference = "LeoGuildManagerSettingsRequirementsDescription" .. guildId
+                    },{
+                        type = "dropdown",
+                        name = "Cycle duration",
+                        tooltip = "When running a purge, the search for sales and deposits will use this range of time. The values are taken from the Addon selected above.",
+                        choices = LeoGuildManager.GetCycles(),
+                        getFunc = function() return LeoGuildManager.GetCycleName(LeoGuildManager.globalData.settings.guilds[guild].cycle) end,
+                        setFunc = function(value) LeoGuildManager.globalData.settings.guilds[guild].cycle = LeoGuildManager.GetCycleIdByName(value) end
+                    },{
+                        type = "dropdown",
+                        name = "Ignore members with rank equal or above",
+                        choices = ranks,
+                        getFunc = function()
+                            local name = LeoGuildManager.GetGuildRankName(guildId, LeoGuildManager.globalData.settings.guilds[guild].ignoreRank)
+                            if name == nil then name = "disabled" end
+                            return name
+                        end,
+                        setFunc = function(value)
+                            LeoGuildManager.globalData.settings.guilds[guild].ignoreRank = LeoGuildManager.GetGuildRankId(guildId, value)
+                        end
+                    },{
+                        type = "dropdown",
+                        name = "Ignore new members",
+                        tooltip = "Recently added members need some time to start, right? :)",
+                        choices = {
+                            "1 week",
+                            "2 weeks",
+                            "3 weeks",
+                            "1 month",
+                        },
+                        getFunc = function()
+                            local value = LeoGuildManager.globalData.settings.guilds[guild].ignoreNew
+                            return LeoGuildManager.GetNewRangeName(value)
+                        end,
+                        setFunc = function(value)
+                            if value == "1 week" then value = 7
+                            elseif value == "2 weeks" then value = 14
+                            elseif value == "3 weeks" then value = 21
+                            else value = 30 end
+                            LeoGuildManager.globalData.settings.guilds[guild].ignoreNew = value
+                        end
+                    },{
+                        type = "slider",
+                        name = "Deposits / Tickets bought (in k)",
+                        tooltip = "Required value (deposits or tickets value) to be deposited",
+                        width = "half",
+                        getFunc = function() return LeoGuildManager.globalData.settings.guilds[guild].tickets end,
+                        setFunc = function(value) LeoGuildManager.globalData.settings.guilds[guild].tickets = value end,
+                        min = 0,
+                        max = 100,
+                        default = 10,
+                    },{
+                        type = "slider",
+                        name = "Sales (in k)",
+                        tooltip = "Required value of sales",
+                        width = "half",
+                        getFunc = function() return LeoGuildManager.globalData.settings.guilds[guild].sales end,
+                        setFunc = function(value) LeoGuildManager.globalData.settings.guilds[guild].sales = value end,
+                        min = 0,
+                        max = 1000,
+                        default = 150,
+                    }, {
+                        type = "slider",
+                        name = "Inactivity (in days)",
+                        width = "half",
+                        getFunc = function() return LeoGuildManager.globalData.settings.guilds[guild].inactivity end,
+                        setFunc = function(value) LeoGuildManager.globalData.settings.guilds[guild].inactivity = value end,
+                        min = 0,
+                        max = 120,
+                        default = 30,
+                    },{
+                        type = "header",
+                        name = "|c3f7fffBlacklist|r"
+                    },{
+                        type = "description",
+                        text = "You can add members to a blacklist and be warned when they are online and even automatically kick them from the guild."
+                    },{
+                        type = "checkbox",
+                        name = "Warning in chat when member is online",
+                        default = true,
+                        getFunc = function() return true end,
+                        setFunc = function(value) end,
+                    },{
+                        type = "checkbox",
+                        name = "|c"..LeoGuildManager.color.hex.red.."Automatically|r kick members",
+                        default = true,
+                        getFunc = function() return true end,
+                        setFunc = function(value) end,
+                    },{
+                        type = "button",
+                        name = "Edit List",
+                        width = "half",
+                        func = function()
+                            ZO_Dialogs_ShowDialog("EDIT_NOTE", {
+                                displayName = "One UserID per line",
+                                note = table.concat(LeoGuildManager.globalData.settings.guilds[guild].blacklist, "\n"),
+                                changedCallback = function(displayName, text) LeoGuildManagerWindow.OnBlacklistChanged(guildId, guild, text) end
+                            })
+                        end,
+                    },{
+                        type = "description",
+                        text = "Members: " .. table.concat(LeoGuildManager.globalData.settings.guilds[guild].blacklist, ", "),
+                        reference = "LeoGuildManagerSettingsBlacklistLabel" .. guildId
+                    }
+                }
+            })
+        end
+    end
+
+	LAM:RegisterOptionControls(OptionsName, optionsData)
+end
+
+function LeoGuildManagerSettings_OnMouseEnter(control, tooltip)
+	InitializeTooltip(InformationTooltip, control, BOTTOMLEFT, 0, -2, TOPLEFT)
+	SetTooltipText(InformationTooltip, tooltip)
+end
+
+function LeoGuildManagerSettings:OnSettingsControlsCreated(panel)
+    --Each time an options panel is created, once for each addon viewed
+    if panel:GetName() == "LeoGuildManagerOptions" then
+        for guildId, guild in pairs(LeoGuildManager.guilds) do
+            if LeoGuildManager.globalData.settings.guilds[guild].enabled == true then
+            end
+        end
+    end
+end