WoWInterface SVN NeedyGreedy

[/] [tags/] [1.0.1/] [NeedyGreedy.lua] - Rev 229

Go to most recent revision | Compare with Previous | Blame | View Log

NeedyGreedy = LibStub("AceAddon-3.0"):NewAddon("NeedyGreedy", "AceEvent-3.0", "AceTimer-3.0", "AceConsole-3.0")

local L = LibStub("AceLocale-3.0"):GetLocale("NeedyGreedy", true)

-- Set up DataBroker object
local NeedyGreedyLDB = LibStub("LibDataBroker-1.1"):NewDataObject("NeedyGreedy", {
    type = "launcher",
    label = "Needy Greedy",
    icon = "Interface\\Buttons\\UI-GroupLoot-Dice-Up",
    OnClick = function(frame, button)
        if button == "RightButton" then
            InterfaceOptionsFrame_OpenToCategory("NeedyGreedy")
        elseif IsShiftKeyDown() then
            NeedyGreedy.db.profile.detachedTooltip = not NeedyGreedy.db.profile.detachedTooltip
            LibStub("AceConfigRegistry-3.0"):NotifyChange("NeedyGreedy")
            NeedyGreedy:HideDBTooltip()
            NeedyGreedy:ShowDBTooltip(frame)
            NeedyGreedy:HideDetachedTooltip()
            if NeedyGreedy.db.profile.detachedTooltip then
                NeedyGreedy.db.profile.displayDetached = true
                NeedyGreedy:ShowDetachedTooltip()
            end
        elseif IsAltKeyDown() then
            NeedyGreedy:ClearItems()
        elseif NeedyGreedy.db.profile.detachedTooltip then
            NeedyGreedy:ToggleDisplay()
        end
    end,
    OnEnter = function(frame)
        NeedyGreedy:ShowDBTooltip(frame)
    end,
    OnLeave = function()
        NeedyGreedy:HideDBTooltip()
    end,
})
local ngDBIcon = LibStub("LibDBIcon-1.0")

local report = {}
local items = {}
local nameList = {}

-- Set up configuration window
local options = {
    name = L["NeedyGreedy"],
    desc = L["Displays a table of items and the roll choices players have made on them"],
    handler = NeedyGreedy,
    type = "group",
    args = {
        general = {
            name = L['General'],
            type = 'group',
            args = {
                nItems = {
                    name = L["Display Items"],
                    desc = L["Number of item columns in the display window"],
                    type = "range",
                    order = 100,
                    min = 1,
                    max = 10,
                    step = 1,
                    get = "GetNItems",
                    set = "SetNItems"
                },
                expiry = {
                    name = L["Expiry Time"],
                    desc = L["Minutes after item is received before it is removed from display (0 = forever)"],
                    type = "range",
                    order = 120,
                    min = 0,
                    max = 110,
                    step = 1,
                    get = "GetExpiry",
                    set = "SetExpiry"
                },
                quality = {
                    name = L["Minimum Quality"],
                    desc = L["Minimum quality of item to be displayed"],
                    type = "select",
                    order = 130,
                    values = {
                        [ITEM_QUALITY_UNCOMMON] = ITEM_QUALITY2_DESC,
                        [ITEM_QUALITY_RARE] = ITEM_QUALITY3_DESC,
                        [ITEM_QUALITY_EPIC] = ITEM_QUALITY4_DESC
                    },
                    style = "dropdown",
                    get = "GetQuality",
                    set = "SetQuality"
                },
                displayIcons = {
                    name = L["Graphical Display"],
                    desc = L["Display icons for rolls types instead of text"],
                    type = "toggle",
                    order = 20,
                    get = "GetDisplayIcons",
                    set = "SetDisplayIcons",
                },
                detachedTooltip = {
                    name = L["Detach Tooltip"],
                    desc = L["Display the roll information in a standalone window"],
                    type = "toggle",
                    order = 10,
                    get = "GetDetachedTooltip",
                    set = "SetDetachedTooltip",
                },
                displayTextLink = {
                    name = L["Item Names"],
                    desc = L["Toggle the display of the item name in the header"],
                    order = 30,
                    type = "toggle",
                    get = "GetDisplayTextLink",
                    set = "SetDisplayTextLink",
                },
                hideMinimapIcon = {
                    name = L["Minimap Icon"],
                    desc = L["Toggle the display of the minimap icon"],
                    type = "toggle",
                    order = 99,
                    get = "GetHideMinimapIcon",
                    set = "SetHideMinimapIcon",
                },
                filterLootMsgs = {
                    name = L["Filter Loot Messages"],
                    desc = L["Enable filtering of loot roll messages"],
                    type = "toggle",
                    order = 35,
                    get = "GetFilterLootMsgs",
                    set = "SetFilterLootMsgs",
                },
                onlyShowInParty = {
                    name = L["Show only in party"],
                    desc = L["Only display the roll window when in a party"],
                    type = "toggle",
                    order = 50,
                    get = "GetOnlyShowInParty",
                    set = "SetOnlyShowInParty",
                },
                hideInCombat = {
                    name = L["Hide in combat"],
                    desc = L["Only display the roll window when not in combat"],
                    type = "toggle",
                    order = 60,
                    get = "GetHideInCombat",
                    set = "SetHideInCombat",
                },
                showGroupOnly = {
                    name = L["Hide Non-Members"],
                    desc = L["Only display the names of members currently in your party"],
                    type = "toggle",
                    order = 40,
                    get = "GetShowGroupOnly",
                    set = "SetShowGroupOnly",
                },
            },
        },
    }
}

-- Set profile defaults
local defaults = {
    profile = {
        nItems = 2,
        expiry = 5,
        quality = ITEM_QUALITY_RARE,
        displayIcons = true,
        detachedTooltip = false,
        displayTextLink = false,
        displayDetached = false,
        minimap = { hide = false },
        filterLootMsgs = false,
        onlyShowInParty = false,
        hideInCombat = false,
        showGroupOnly = true,
    }
}

-- Icon textures for Need/Greed/Pass/DE
local iconSize = 27
local NEEDYGREEDY_CHOICE = {
    ["need"] = {
        ["string"] = "|c00FF0000" .. NEED .. "|r",
        ["icon"] = "|TInterface\\Buttons\\UI-GroupLoot-Dice-Up:" .. iconSize .. "|t",
    },
    ["greed"] = {
        ["string"] = "|c0000FF00" .. GREED .. "|r",
        ["icon"] = "|TInterface\\Buttons\\UI-GroupLoot-Coin-Up:" .. iconSize .. "|t",
    },
    ["pass"] = {
        ["string"] = "|c00CCCCCC" .. PASS .. "|r",
        ["icon"] = "|TInterface\\Buttons\\UI-GroupLoot-Pass-Up:" .. iconSize .. "|t",
    },
    ["disenchant"] = {
        ["string"] = "|c00FF00FF" .. ROLL_DISENCHANT .. "|r",
        ["icon"] = "|TInterface\\Buttons\\UI-GroupLoot-DE-Up:" .. iconSize .. "|t",
    }
}
local BLANK_ICON = "|T:27|t"

-- Funky colors for text strings
local yC = "|cffFFCC00" -- Golden
local eC = "|cffEDA55F" -- Orange
local gC = "|cff00FF00" -- Green

-- For tracking original state of detailed loot information
local originalSpamFilterSetting = nil

-- For tracking combat status
local IS_IN_COMBAT = nil

-- Utility functions
local function sanitizePattern(pattern)
    pattern = string.gsub(pattern, "%(", "%%(")
    pattern = string.gsub(pattern, "%)", "%%)")
    pattern = string.gsub(pattern, "%%s", "(.+)")
    pattern = string.gsub(pattern, "%%d", "(%%d+)")
    pattern = string.gsub(pattern, "%-", "%%-")
    return pattern
end

-- Converts a format string into a pattern and list of capture group indices
-- e.g. %2$s won the %1$s
local function patternFromFormat(format)
    local pattern = ""
    local captureIndices = {}

    local start = 1
    local captureIndex = 0
    repeat
        -- find the next group
        local s, e, group, position = format:find("(%%([%d$]*)[ds])", start)
        if s then
            -- add the text between the last group and this group
            pattern = pattern..sanitizePattern(format:sub(start, s-1))
            -- update the current capture index, using the position bit in the
            -- group if it exists, otherwise just increment
            if #position > 0 then
                -- chop off the $ and convert to a number
                captureIndex = tonumber(position:sub(1, #position-1))
            else
                captureIndex = captureIndex + 1
            end
            -- add the current capture index to our list
            tinsert(captureIndices, captureIndex)
            -- remove the position bit from the group, sanitize the remainder
            -- and add it to the pattern
            pattern = pattern..sanitizePattern(group:gsub("%d%$", "", 1))
            -- start searching again from past the end of the group
            start = e + 1
        else
            -- if no more groups can be found, but there's still more text
            -- remaining in the format string, sanitize the remainder, add it
            -- to the pattern and finish the loop
            if start <= #format then
                pattern = pattern..sanitizePattern(format:sub(start))
            end
            break
        end
    until start > #format

    return pattern, captureIndices
end

-- Like string:find but uses a list of capture indices to re-order the capture
-- groups. For use with converted format strings that use positional args.
-- e.g. %2$s won the %1$s.
local function superFind(text, pattern, captureIndices)
    local results = { text:find(pattern) }
    if #results == 0 then
        return
    end

    local s, e = tremove(results, 1), tremove(results, 1)

    local captures = {}
    for _, index in ipairs(captureIndices) do
        tinsert(captures, results[index])
    end

    return s, e, unpack(captures)
end

-- Strips the item ID out of the item link
-- Needed for items that change their unique identifier
local function itemIdFromLink(itemLink)
    local found, _, itemString = string.find(itemLink, "|H(.+)|h")
    if found then
        local _, itemId = strsplit(":", itemString)
        return tonumber(itemId)
    end
    return nil
end



-- Event handling functions
function NeedyGreedy:OnInitialize()
    self.db = LibStub("AceDB-3.0"):New("NeedyGreedyDB", defaults, true)
    self.db.RegisterCallback(self, "OnProfileChanged", "RefreshTooltip")
    self.db.RegisterCallback(self, "OnProfileCopied", "RefreshTooltip")
    self.db.RegisterCallback(self, "OnProfileReset", "RefreshTooltip")
    options.args.profile = LibStub("AceDBOptions-3.0"):GetOptionsTable(self.db)
    LibStub("AceConfig-3.0"):RegisterOptionsTable("NeedyGreedy", options)
    local ACD = LibStub("AceConfigDialog-3.0")
    ACD:AddToBlizOptions("NeedyGreedy", "NeedyGreedy", nil, "general")
    ACD:AddToBlizOptions("NeedyGreedy", L["Profile"], "NeedyGreedy", "profile")
    self:RegisterChatCommand("needygreedy", function() InterfaceOptionsFrame_OpenToCategory("NeedyGreedy") end)
    self:RegisterChatCommand("ng", function() InterfaceOptionsFrame_OpenToCategory("NeedyGreedy") end)
    self:RegisterChatCommand("ngt", "TestItemList")
    self.items = items

    -- Register the minimap icon
    ngDBIcon:Register("NeedyGreedy", NeedyGreedyLDB, self.db.profile.minimap)
end

function NeedyGreedy:OnEnable()
    self:RegisterEvent("PARTY_MEMBERS_CHANGED")
    self:RegisterEvent("START_LOOT_ROLL")
    self:RegisterEvent("CHAT_MSG_LOOT")
    self:RegisterEvent("PLAYER_REGEN_DISABLED")
    self:RegisterEvent("PLAYER_REGEN_ENABLED")

    self:ScheduleRepeatingTimer("ExpireItems", 10)

    -- Refresh display when class can be determined
    self:RegisterEvent("PLAYER_ENTERING_WORLD")
    self:RegisterEvent("UNIT_CLASSIFICATION_CHANGED", "PLAYER_ENTERING_WORLD")

    self:RegisterEvent("PLAYER_LEAVING_WORLD")

    -- Set up chat filters
    if self.db.profile.filterLootMsgs then
        self:EnableChatFilter()
    end

    self:SetShowLootSpam()
end

function NeedyGreedy:OnDisable()
    self:HideDetachedTooltip()

    -- Turn off chat filters
    if self.db.profile.filterLootMsgs then
        self:DisableChatFilter()
    end

    self:ResetShowLootSpam()
end

function NeedyGreedy:PLAYER_ENTERING_WORLD()
    if self.db.profile.displayDetached and self.db.profile.detachedTooltip then
        self:ShowDetachedTooltip()
    end

    self:SetShowLootSpam()
end

function NeedyGreedy:PLAYER_LEAVING_WORLD()
    self:ResetShowLootSpam()
end

function NeedyGreedy:PARTY_MEMBERS_CHANGED()
    wipe(nameList)
    self:RefreshTooltip()
end

function NeedyGreedy:PLAYER_REGEN_DISABLED()
    IS_IN_COMBAT = true
    self:RefreshTooltip()
end

function NeedyGreedy:PLAYER_REGEN_ENABLED()
    IS_IN_COMBAT = false
    self:RefreshTooltip()
end



-- Chat scanning and loot recording
function NeedyGreedy:START_LOOT_ROLL(event, rollid)
    local texture, name, count, quality = GetLootRollItemInfo(rollid)
    local link = GetLootRollItemLink(rollid)
    if quality >= self.db.profile.quality then
        items[rollid] = {
            texture = texture,
            link = link,
            itemID = itemIdFromLink(link),
            assigned = "",
            received = 0,
            choices = {},
            rolls = {}
        }
        self:UpdateReport()
    end
end

function NeedyGreedy:CHAT_MSG_LOOT(event, msg)
    local me = UnitName("player")
    local player, link, number

    link = self:unformat(LOOT_ROLL_YOU_WON, msg)
    if link then
        self:RecordAwarded(link, me)
        return
    end

    player, link = self:unformat(LOOT_ROLL_WON, msg)
    if player then
        self:RecordAwarded(link, player)
        return
    end

    link = self:unformat(LOOT_ROLL_ALL_PASSED, msg)
    if link then
        self:RecordAwarded(link, "---")
        return
    end

    player, link = self:unformat(LOOT_ROLL_PASSED_AUTO, msg)
    if player then
        self:RecordChoice(link, player, "pass")
        return
    end

    player, link = self:unformat(LOOT_ROLL_PASSED_AUTO_FEMALE, msg)
    if player then
        self:RecordChoice(link, player, "pass")
        return
    end

    link = self:unformat(LOOT_ROLL_NEED_SELF, msg)
    if link then
        self:RecordChoice(link, me, "need")
        return
    end

    link = self:unformat(LOOT_ROLL_GREED_SELF, msg)
    if link then
        self:RecordChoice(link, me, "greed")
        return
    end

    link = self:unformat(LOOT_ROLL_PASSED_SELF, msg)
    if link then
        self:RecordChoice(link, me, "pass")
        return
    end

    link = self:unformat(LOOT_ROLL_PASSED_SELF_AUTO, msg)
    if link then
        self:RecordChoice(link, me, "pass")
        return
    end

    player, link = self:unformat(LOOT_ROLL_NEED, msg)
    if player then
        self:RecordChoice(link, player, "need")
        return
    end

    player, link = self:unformat(LOOT_ROLL_GREED, msg)
    if player then
        self:RecordChoice(link, player, "greed")
        return
    end

    player, link = self:unformat(LOOT_ROLL_PASSED, msg)
    if player then
        self:RecordChoice(link, player, "pass")
        return
    end

    number, link, player = self:unformat(LOOT_ROLL_ROLLED_NEED, msg)
    if number then
        self:RecordRoll(link, player, number)
        return
    end

    number, link, player = self:unformat(LOOT_ROLL_ROLLED_GREED, msg)
    if number then
        self:RecordRoll(link, player, number)
        return
    end

    link = self:unformat(LOOT_ITEM_PUSHED_SELF, msg)
    if link then
        self:RecordReceived(link, me)
        return
    end

    link, number = self:unformat(LOOT_ITEM_PUSHED_SELF_MULTIPLE, msg)
    if link then
        self:RecordReceived(link, me)
        return
    end

    link = self:unformat(LOOT_ITEM_SELF, msg)
    if link then
        self:RecordReceived(link, me)
        return
    end

    link, number = self:unformat(LOOT_ITEM_SELF_MULTIPLE, msg)
    if link then
        self:RecordReceived(link, me)
        return
    end

    player, link = self:unformat(LOOT_ITEM, msg)
    if player then
        self:RecordReceived(link, player)
        return
    end

    player, link, number = self:unformat(LOOT_ITEM_MULTIPLE, msg)
    if player then
        self:RecordReceived(link, player)
        return
    end

    -- To handle new disenchant rules
    link = self:unformat(LOOT_ROLL_DISENCHANT_SELF, msg)
    if link then
        self:RecordChoice(link, me, "disenchant")
        return
    end

    player, link = self:unformat(LOOT_ROLL_DISENCHANT, msg)
    if link then
        self:RecordChoice(link, player, "disenchant")
        return
    end

    player, link = self:unformat(LOOT_ROLL_DISENCHANT, msg)
    if link then
        self:RecordChoice(link, player, "disenchant")
        return
    end

    number, link, player = self:unformat(LOOT_ROLL_ROLLED_DE, msg)
    if number then
        self:RecordRoll(link, player, number)
        return
    end
end

function NeedyGreedy:RecordChoice(link, player, choice)
    local _, _, quality = GetItemInfo(link)
    if quality < self.db.profile.quality then return end

    for rollid, record in pairs(items) do
        if record.assigned == "" and record.link == link then
            record.choices[player] = choice
            break
        end
    end
    self:UpdateReport()
end

function NeedyGreedy:RecordRoll(link, player, number)
    local _, _, quality = GetItemInfo(link)
    if quality < self.db.profile.quality then return end

    for rollid, record in pairs(items) do
        if record.assigned == "" and record.link == link then
            record.rolls[player] = number
            break
        end
    end
    self:UpdateReport()
end

function NeedyGreedy:RecordAwarded(link, player)
    local _, _, quality = GetItemInfo(link)
    if quality < self.db.profile.quality then return end

    for rollid, record in pairs(items) do
        if record.assigned == "" and record.link == link then
            record.assigned = player
            break
        end
    end
    self:UpdateReport()
end

function NeedyGreedy:RecordReceived(link, player)
    local _, _, quality = GetItemInfo(link)
    -- Because disenchanted items can be white >_<9
    if quality < ITEM_QUALITY_COMMON then return end

    local match = false
    for rollid, record in pairs(items) do
        if record.received == 0 and record.link == link then
            record.received = GetTime()
            match = true
            break
        end
    end

    if not match then
        -- For items with weird unique identifiers
        for rollid, record in pairs(items) do
            if record.received == 0 and record.itemID == itemIdFromLink(link) then
                record.received = GetTime()
                match = true
                break
            end
        end
    end

    if not match then
        for rollid, record in pairs(items) do
            -- Since players receive the results of the disenchant, we will never be
            -- able to match the link that triggered the received message against
            -- our list. However, we should cross disenchanted items that have been
            -- assigned off the list as we find them on the assumption that they
            -- would have automatically received the item anyway.
            if record.choices[player] == "disenchant" and record.assigned == player and record.received == 0 then
                record.received = GetTime()
            end
        end
    end

    self:UpdateReport()
end

function NeedyGreedy:ClearItems()
    wipe(items)
    wipe(nameList)
    self:RefreshTooltip()
end



-- Tooltip Information Formatting
function NeedyGreedy:PageLeft()
    report.firstItem = report.firstItem - self.db.profile.nItems
    if report.firstItem < 1 then
        report.firstItem = 1
    end
    self:UpdateReport()
end

function NeedyGreedy:PageRight()
    local count = NeedyGreedy:CountItems()
    report.firstItem = report.firstItem + self.db.profile.nItems
    if count == 0 then
        report.firstItem = 1
    elseif report.firstItem > count then
        report.firstItem = count
    end
    self:UpdateReport()
end

function NeedyGreedy:GetSortedPlayers()
    local list = {}

    if GetNumRaidMembers() > 0 then
        for i = 1,MAX_RAID_MEMBERS do
            name = GetRaidRosterInfo(i)
            if name then
                if not (nameList[name]) and (name ~= UNKNKOWN) then nameList[name] = name end
            end
        end
    else
        for _, unit in ipairs({"player", "party1", "party2", "party3", "party4"}) do
            local name = UnitName(unit)
            if name then
                if not (nameList[name]) and (name ~= UNKNKOWN) then nameList[name] = name end
            end
        end
    end

    if not self.db.profile.showGroupOnly then
        for _, item in pairs(items) do
            for name, _ in pairs(item.choices) do
                if not nameList[name] then nameList[name] = name end
            end
        end
    end

    for name, _ in pairs(nameList) do
        table.insert(list, name)
    end

    table.sort(list)
    return list
end

function NeedyGreedy:GetNumPlayers()
    local nraid = GetNumRaidMembers()
    if nraid > 0 then
        return nraid
    else
        return GetNumPartyMembers() + 1
    end
end

function NeedyGreedy:ColorizeName(name)
    -- Derived by hand from RAID_CLASS_COLORS because deriving it in lua seemed tricky
    -- Might not be that hard: str_format("%02x%02x%02x", r * 255, g * 255, b * 255)
    local map = {
        HUNTER = "|c00ABD473",
        WARLOCK = "|c009482C9",
        PRIEST = "|c00FFFFFF",
        PALADIN = "|c00F58CBA",
        MAGE = "|c0069CCF0",
        ROGUE = "|c00FFF569",
        DRUID = "|c00FF7D0A",
        SHAMAN = "|c002459FF",
        WARRIOR = "|c00C79C6E",
        DEATHKNIGHT = "|c00C41F3A"
    }
    local _, class = UnitClass(name)
    local color

    if class then
        color = map[class]
    end
    if not color then
        color = GRAY_FONT_COLOR_CODE
    end
    return color .. name .. "|r"
end

function NeedyGreedy:ChoiceText(choice)
    local style = "string"
    if self.db.profile.displayIcons == true then style = "icon" end

    if choice then
          if NEEDYGREEDY_CHOICE[choice][style] then
              return NEEDYGREEDY_CHOICE[choice][style]
          end
    end
    return ""
end

function NeedyGreedy:RollText(number)
    if number then
        return " - " .. number
    else
        return ""
    end
end

function NeedyGreedy:AssignedText(item)
    if item.received == 0 then
        return "|c00FF0000" .. item.assigned .. "|r"
    else
        return "|c0000FF00" .. item.assigned .. "|r"
    end
end

-- Return a list of rollids ordered from most recent to least recent
function NeedyGreedy:SortRollids()
    local rollids = {}
    for rollid, _ in pairs(items) do
        table.insert(rollids, rollid)
    end
    table.sort(rollids, function(a, b) return a > b end)
    return rollids
end

function NeedyGreedy:CountItems()
    local i = 0
    for _, _ in pairs(items) do
        i = i + 1
    end
    return i
end

function NeedyGreedy:ExpireItems()
    local now = GetTime()
    local update = false

    if self.db.profile.expiry == 0 then
        return
    end
    for rollid, record in pairs(items) do
        if record.received > 0 and now - record.received >= self.db.profile.expiry * 60 then
            items[rollid] = nil
            wipte(nameList)
            update = true
        end
    end
    if update then
        self:UpdateReport()
    end
end

local CONVERTED_FORMATS = {}
function NeedyGreedy:unformat(fmt, msg)
    local pattern, captureIndices
    if CONVERTED_FORMATS[fmt] then
        pattern, captureIndices = unpack(CONVERTED_FORMATS[fmt])
    else
        pattern, captureIndices = patternFromFormat(fmt)
        CONVERTED_FORMATS[fmt] = {pattern, captureIndices}
    end

    local _, _, a1, a2, a3, a4 = superFind(msg, pattern, captureIndices)
    return a1, a2, a3, a4
end



-- Config option getters and setters
function NeedyGreedy:GetNItems(info)
    return self.db.profile.nItems
end

function NeedyGreedy:SetNItems(info, nItems)
    self.db.profile.nItems = nItems
    self:RefreshTooltip()
end

function NeedyGreedy:GetExpiry(info)
    return self.db.profile.expiry
end

function NeedyGreedy:SetExpiry(info, expiry)
    self.db.profile.expiry = expiry
    self:ExpireItems()
end

function NeedyGreedy:GetQuality(info)
    return self.db.profile.quality
end

function NeedyGreedy:SetQuality(info, quality)
    self.db.profile.quality = quality
end

function NeedyGreedy:GetDisplayIcons(info)
    return self.db.profile.displayIcons
end

function NeedyGreedy:SetDisplayIcons(info, displayIcons)
    self.db.profile.displayIcons = displayIcons
    self:UpdateReport()
end

function NeedyGreedy:GetDetachedTooltip(info)
    return self.db.profile.detachedTooltip
end

function NeedyGreedy:SetDetachedTooltip(info, detachedTooltip)
    self.db.profile.detachedTooltip = detachedTooltip
    if detachedTooltip then
        self.db.profile.displayDetached = true
        self:ShowDetachedTooltip()
    else
        self.db.profile.displayDetached = false
        self:HideDetachedTooltip()
        -- Return to page one
        report.firstItem = 1
    end
end

function NeedyGreedy:GetDisplayTextLink(info)
    return self.db.profile.displayTextLink
end

function NeedyGreedy:SetDisplayTextLink(info, displayTextLink)
    self.db.profile.displayTextLink = displayTextLink
    self:UpdateReport()
end

function NeedyGreedy:GetHideMinimapIcon(info)
    return not self.db.profile.minimap.hide
end

function NeedyGreedy:SetHideMinimapIcon(info, hideMinimapIcon)
    self.db.profile.minimap.hide = not hideMinimapIcon
    if self.db.profile.minimap.hide then
        ngDBIcon:Hide("NeedyGreedy")
    else
        ngDBIcon:Show("NeedyGreedy")
    end
end

function NeedyGreedy:GetFilterLootMsgs(info)
    return self.db.profile.filterLootMsgs
end

function NeedyGreedy:SetFilterLootMsgs(info, filterLootMsgs)
    self.db.profile.filterLootMsgs = filterLootMsgs
    if self.db.profile.filterLootMsgs then
        self:EnableChatFilter()
    else
        self:DisableChatFilter()
    end
end

function NeedyGreedy:GetOnlyShowInParty(info)
    return self.db.profile.onlyShowInParty
end

function NeedyGreedy:SetOnlyShowInParty(info, onlyShowInParty)
    self.db.profile.onlyShowInParty = onlyShowInParty
    self:RefreshTooltip()
end

function NeedyGreedy:GetHideInCombat(info)
    return self.db.profile.hideInCombat
end

function NeedyGreedy:SetHideInCombat(info, hideInCombat)
    self.db.profile.hideInCombat = hideInCombat
    self:RefreshTooltip()
end

function NeedyGreedy:GetShowGroupOnly(info)
    return self.db.profile.showGroupOnly
end

function NeedyGreedy:SetShowGroupOnly(info, showGroupOnly)
    self.db.profile.showGroupOnly = showGroupOnly
    wipe(nameList)
    self:RefreshTooltip()
end



-- Detachable QTip Frames
local LibQTip = LibStub('LibQTip-1.0')

function NeedyGreedy:ShowDetachedTooltip()
    if not self:DisplayRollTableCheck() then return end

    -- Acquire a tooltip
    self.detachedTooltip = LibQTip:Acquire("NeedyGreedyReport", 1, "LEFT")

    -- Add columns here because tooltip:Clear() preserves columns
    for i = 1, self.db.profile.nItems do
        self.detachedTooltip:AddColumn("LEFT")
    end

    -- Add two columns for left and right buttons if detached
    if self.db.profile.detachedTooltip then
        self.detachedTooltip:AddColumn("RIGHT")
        self.detachedTooltip:AddColumn("LEFT")
    end

    -- Fill in the info
    self:PopulateReportTooltip(self.detachedTooltip)

    if self.db.profile.detachedTooltip then
        -- To make tooltip detached
        self.detachedTooltip:ClearAllPoints()
        self.detachedTooltip:SetFrameStrata("FULLSCREEN")
        self.detachedTooltip:EnableMouse(true)
        self.detachedTooltip:SetResizable(true)
        self.detachedTooltip:SetFrameLevel(1)
        self.detachedTooltip:SetMovable(true)
        self.detachedTooltip:SetClampedToScreen(true)

        if not self.db.profile.reportFramePos then
            self.db.profile.reportFramePos = {
                anchor1 = "CENTER",
                anchor2 = "CENTER",
                x = 0,
                y = 0
            }
        end
        self.detachedTooltip:SetPoint(self.db.profile.reportFramePos.anchor1, nil, self.db.profile.reportFramePos.anchor2,
            self.db.profile.reportFramePos.x, self.db.profile.reportFramePos.y)

        -- Make it move !
        self.detachedTooltip:SetScript("OnMouseDown", function() self.detachedTooltip:StartMoving() end)
        self.detachedTooltip:SetScript("OnMouseUp", function()
            -- Make it remember
            self.detachedTooltip:StopMovingOrSizing()
            local anchor1, _, anchor2, x, y = self.detachedTooltip:GetPoint()
            self.db.profile.reportFramePos.anchor1 = anchor1
            self.db.profile.reportFramePos.anchor2 = anchor2
            self.db.profile.reportFramePos.x = x
            self.db.profile.reportFramePos.y = y
        end)
    end

    -- Show it, et voilà !
    self.detachedTooltip:Show()
end

function NeedyGreedy:HideDetachedTooltip()
    if self.detachedTooltip then
        self.detachedTooltip:Hide()
        LibQTip:Release(self.detachedTooltip)
        self.detachedTooltip = nil
    end
end

function NeedyGreedy:ShowDBTooltip(frame)
    -- Acquire a tooltip
    self.dbTooltip = LibQTip:Acquire("NeedyGreedyDBReport", 1, "LEFT")

    self:AddHeaderText(self.dbTooltip)
    if not self.db.profile.detachedTooltip then
        if self:DisplayRollTableCheck() then
            -- Add columns here because tooltip:Clear() preserves columns
            for i = 1, self.db.profile.nItems do
                self.dbTooltip:AddColumn("LEFT")
            end

            -- Fill in the info
            self:PopulateReportTooltip(self.dbTooltip)
        end
    end
    self:AddInfoText(self.dbTooltip)

    if frame then self.dbTooltip:SmartAnchorTo(frame) end

    -- Show it, et voilà !
    self.dbTooltip:Show()
end

function NeedyGreedy:HideDBTooltip()
    if self.dbTooltip then
        self.dbTooltip:Hide()
        LibQTip:Release(self.dbTooltip)
        self.dbTooltip = nil
    end
end

function NeedyGreedy:PopulateReportTooltip(tooltip)
    local nItems = self.db.profile.nItems
    local players = self:GetSortedPlayers()
    tooltip:Clear()

    self:AddHeaderText(tooltip)

    -- Verify that report.firstItem is set reasonably
    local sorted = self:SortRollids()
    local count = self:CountItems()

    if not(report.firstItem) then report.firstItem = 1 end
    if count == 0 then
        report.firstItem = 1
    elseif report.firstItem > count then
        report.firstItem = count
    end

    -- Create icon headers
    local headerline, _ = tooltip:AddLine("")
    for i = 1, nItems do
        local index = report.firstItem + i - 1
        local texture = ""
        local item = nil
        if index <= count then
            local rollID = sorted[index]
            item = items[rollID]
            texture = "|T" .. item.texture .. ":40|t"
        end
        tooltip:SetCell(headerline, i + 1, texture, nil, "CENTER", nil, nil, nil, nil, nil, 60)
        if item then
            tooltip:SetCellScript(headerline, i + 1, "OnEnter", function()
                GameTooltip:SetOwner(tooltip, "ANCHOR_RIGHT")
                GameTooltip:SetHyperlink(item.link)
            end )
                tooltip:SetCellScript(headerline, i + 1, "OnMouseUp", function()
                    if IsShiftKeyDown() then
                        ChatEdit_InsertLink(item.link)
                    end
                end )
            tooltip:SetCellScript(headerline, i + 1, "OnLeave", function()
                GameTooltip:Hide()
            end )
        end
    end

    -- Now add item link names
    if self.db.profile.displayTextLink then
        headerline, _ = tooltip:AddLine("")
        for i = 1, nItems do
            local index = report.firstItem + i - 1
            local text = ""
            local item = nil
            if index <= count then
                local rollID = sorted[index]
                item = items[rollID]
                text= item.link
            end
            tooltip:SetCell(headerline, i + 1, text, nil, nil, nil, nil, nil, nil, nil, 60)
            if item then
                tooltip:SetCellScript(headerline, i + 1, "OnEnter", function()
                    GameTooltip:SetOwner(tooltip, "ANCHOR_RIGHT")
                    GameTooltip:SetHyperlink(item.link)
                end )
                tooltip:SetCellScript(headerline, i + 1, "OnMouseUp", function()
                    if IsShiftKeyDown() then
                        ChatEdit_InsertLink(item.link)
                    end
                end )
                tooltip:SetCellScript(headerline, i + 1, "OnLeave", function()
                    GameTooltip:Hide()
                end )
            end
        end
    end

    tooltip:AddLine("")

    -- Create table with party names and their rolls
    for i, name in ipairs(players) do
        local partyLine = tooltip:AddLine("")
        tooltip:SetCell(partyLine, 1, self:ColorizeName(name) .. " " .. (self.db.profile.displayIcons and BLANK_ICON or ""), nil, "LEFT", nil, nil, nil, nil, nil, 100)

        for i = 1, nItems do
            local index = report.firstItem + i - 1
            if index <= count then
                local rollID = sorted[index]
                local item = items[rollID]
                tooltip:SetCell(partyLine, i + 1, self:ChoiceText(item.choices[name]) .. self:RollText(item.rolls[name]), nil, "LEFT", nil, nil, nil, nil, nil, 60)
            end
        end
    end

    tooltip:AddSeparator()

    -- Display winner
    local winnerTable = {yC .. "Winner|r"}
    for i = 1, nItems do
        local index = report.firstItem + i - 1
        if index <= count then
            local rollID = sorted[index]
            local item = items[rollID]
            table.insert(winnerTable, self:AssignedText(item))
        end
    end
    tooltip:AddLine(unpack(winnerTable))

    -- Display left and right arrows if frame is detached
    if self.db.profile.detachedTooltip then
        self:AddPagerArrows(tooltip)
    end
end

function NeedyGreedy:AddHeaderText(tooltip)
    local headerText = yC .. "NeedyGreedy|r"
    local lineNum = tooltip:AddLine("")
    tooltip:SetCell(lineNum, 1, headerText, tooltip:GetHeaderFont(), tooltip:GetColumnCount())

    tooltip:AddLine("")
end

function NeedyGreedy:AddPagerArrows(tooltip)
    local nItems = self.db.profile.nItems
    local count = self:CountItems()

    local lineNum, _ = tooltip:AddLine("")
    local colNum = nItems + 2

    if report.firstItem > 1 then
        tooltip:SetCell(lineNum, colNum, "|TInterface\\Buttons\\UI-SpellbookIcon-PrevPage-Up:" .. iconSize .. "|t")
        tooltip:SetCellScript(lineNum, colNum, "OnMouseUp", function() self:PageLeft() end)
    else
        tooltip:SetCell(lineNum, colNum, "|TInterface\\Buttons\\UI-SpellbookIcon-PrevPage-Disabled:" .. iconSize .. "|t")
    end

    if report.firstItem + nItems - 1 < count then
        tooltip:SetCell(lineNum, colNum + 1, "|TInterface\\Buttons\\UI-SpellbookIcon-NextPage-Up:" .. iconSize .. "|t")
        tooltip:SetCellScript(lineNum, colNum + 1, "OnMouseUp", function() self:PageRight() end)
    else
        tooltip:SetCell(lineNum, colNum + 1, "|TInterface\\Buttons\\UI-SpellbookIcon-NextPage-Disabled:" .. iconSize .. "|t")
    end

    -- Set the page # text
    local pageText = ""
    if nItems == 1 then
        pageText = tostring(report.firstItem)
    elseif count == 0 then
        pageText = L["None"]
    elseif count == 1 or report.firstItem == count then
        pageText = string.format(L["%d of %d"], report.firstItem, count)
    else
        local lastitem = report.firstItem + nItems - 1
        if (lastitem > count) then
            lastitem = count
        end
        pageText = string.format(L["%d-%d of %d"], report.firstItem, lastitem, count)
    end

    tooltip:SetCell(lineNum, colNum - 1, yC .. pageText)
end

function NeedyGreedy:AddInfoText(tooltip)
    local lineNum
    local helpText

    if self.db.profile.onlyShowInParty and not self:CheckOnlyShowInParty() then
        lineNum = tooltip:AddLine()
        tooltip:SetCell(lineNum, 1, L["You are not in a party"], nil, tooltip:GetColumnCount())
    end

    tooltip:AddLine("")

    helpText = ""
    if self.db.profile.detachedTooltip then
        helpText = helpText .. eC .. L["Click"] .. "|r " .. gC .. L["to hide/show detached tooltip"] .. "|r\n"
    end
    helpText = helpText .. eC .. L["Shift-Click"] .. "|r " .. gC .. L["to attach/detach tooltip"] .. "|r\n"
    helpText = helpText .. eC .. L["Alt-Click"] .. "|r " .. gC .. L["to clear item list"] .. "|r"
    lineNum = tooltip:AddLine()
    tooltip:SetCell(lineNum, 1, helpText, nil, tooltip:GetColumnCount())
end

function NeedyGreedy:UpdateReport()
    local tooltip = nil
    if self.detachedTooltip and self.detachedTooltip:IsShown() then
        tooltip = self.detachedTooltip
    elseif self.dbTooltip and self.dbTooltip:IsShown() and (not self.db.profile.detachedTooltip) then
        tooltip = self.dbTooltip
    else
        return
    end

    self:PopulateReportTooltip(tooltip)
end

function NeedyGreedy:RefreshTooltip()
    if self.db.profile.detachedTooltip and self.db.profile.displayDetached then
        self:HideDetachedTooltip()
        self:ShowDetachedTooltip()
    end
    self:HideDBTooltip()
end

function NeedyGreedy:ToggleDisplay()
    if not self.db.profile.detachedTooltip then return end

    if self.db.profile.displayDetached then
        self:HideDetachedTooltip()
    else
        self:ShowDetachedTooltip()
    end

    self.db.profile.displayDetached = not self.db.profile.displayDetached
end

function NeedyGreedy:DisplayRollTableCheck()
    if self:CheckOnlyShowInParty() and self:CheckShowInCombat() then
        return true
    end

    return false
end

function NeedyGreedy:CheckOnlyShowInParty()
    if self.db.profile.onlyShowInParty and (GetNumPartyMembers() == 0) then
        return false
    end

    return true
end

function NeedyGreedy:CheckShowInCombat()
    if self.db.profile.hideInCombat and IS_IN_COMBAT then
        return false
    end

    return true
end



-- Chat filter functions
local FILTER_CHAT_LOOT_MSGS = {
    --LOOT_ROLL_ALL_PASSED,
    LOOT_ROLL_DISENCHANT,
    LOOT_ROLL_DISENCHANT_SELF,
    LOOT_ROLL_GREED,
    LOOT_ROLL_GREED_SELF,
    LOOT_ROLL_NEED,
    LOOT_ROLL_NEED_SELF,
    LOOT_ROLL_PASSED,
    LOOT_ROLL_PASSED_AUTO,
    LOOT_ROLL_PASSED_AUTO_FEMALE,
    LOOT_ROLL_PASSED_SELF,
    LOOT_ROLL_PASSED_SELF_AUTO,
    LOOT_ROLL_ROLLED_DE,
    LOOT_ROLL_ROLLED_GREED,
    LOOT_ROLL_ROLLED_NEED,
    --LOOT_ROLL_WON,
    --LOOT_ROLL_YOU_WON,
    --LOOT_ITEM,
    --LOOT_ITEM_MULTIPLE,
    --LOOT_ITEM_PUSHED_SELF,
    --LOOT_ITEM_PUSHED_SELF_MULTIPLE,
    --LOOT_ITEM_SELF,
    --LOOT_ITEM_SELF_MULTIPLE,
}

local function FilterLootMsg(ChatFrameSelf, event, ...)
    local msg = arg1
    for _, string in ipairs(FILTER_CHAT_LOOT_MSGS) do
        local match = NeedyGreedy:unformat(string, msg)
        if match then
            return true
        end
    end

    return false, msg, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11
end

function NeedyGreedy:EnableChatFilter()
    ChatFrame_AddMessageEventFilter("CHAT_MSG_LOOT", FilterLootMsg)
end

function NeedyGreedy:DisableChatFilter()
    ChatFrame_RemoveMessageEventFilter("CHAT_MSG_LOOT", FilterLootMsg)
end



-- Automatic enabling of detailed loot information
function NeedyGreedy:SetShowLootSpam()
    local showLootSpam = GetCVar("showLootSpam") -- 0 for filtered, 1 for details
    if showLootSpam == "0" then
        originalSpamFilterSetting = showLootSpam
        SetCVar("showLootSpam", "1")
    end
end

function NeedyGreedy:ResetShowLootSpam()
    if originalSpamFilterSetting then
        SetCVar("showLootSpam", originalSpamFilterSetting)
        originalSpamFilterSetting = nil
    end
end



-- Unit tests
function NeedyGreedy:SetItems(itemList)
    items = itemList
    self:UpdateReport()
end


function NeedyGreedy:TestItemList()
    items = {
        nil, -- [1]
        nil, -- [2]
        nil, -- [3]
        nil, -- [4]
        nil, -- [5]
        {
            ["received"] = 108626.818,
            ["assigned"] = "Evilplaque",
            ["itemID"] = 36040,
            ["link"] = "|cff1eff00|Hitem:36040:0:0:0:0:0:-36:-2033450911:80|h[Condor Pants of the Sorcerer]|h|r",
            ["choices"] = {
                ["Aneeka"] = "greed",
                ["Lubov"] = "greed",
                ["Evilplaque"] = "greed",
                ["Blizzy"] = "disenchant",
                ["Dkmonkey"] = "disenchant",
            },
            ["rolls"] = {
                ["Aneeka"] = "24",
                ["Lubov"] = "66",
                ["Evilplaque"] = "98",
                ["Blizzy"] = "52",
                ["Dkmonkey"] = "3",
            },
            ["texture"] = "Interface\\Icons\\INV_Pants_Cloth_10",
        }, -- [6]
        {
            ["received"] = 109046.237,
            ["assigned"] = "Lubov",
            ["itemID"] = 50228,
            ["link"] = "|cffa335ee|Hitem:50228:0:0:0:0:0:0:1749772928:80|h[Barbed Ymirheim Choker]|h|r",
            ["choices"] = {
                ["Aneeka"] = "greed",
                ["Blizzy"] = "disenchant",
                ["Evilplaque"] = "disenchant",
                ["Lubov"] = "need",
                ["Dkmonkey"] = "disenchant",
            },
            ["rolls"] = {
                ["Lubov"] = "17",
            },
            ["texture"] = "Interface\\Icons\\INV_Jewelry_Necklace_22",
        }, -- [7]
        {
            ["received"] = 109223.287,
            ["assigned"] = "Lubov",
            ["itemID"] = 36260,
            ["link"] = "|cff1eff00|Hitem:36260:0:0:0:0:0:-40:-1617756088:80|h[Cormorant Footwraps of the Bandit]|h|r",
            ["choices"] = {
                ["Aneeka"] = "greed",
                ["Blizzy"] = "disenchant",
                ["Evilplaque"] = "greed",
                ["Lubov"] = "greed",
                ["Dkmonkey"] = "disenchant",
            },
            ["rolls"] = {
                ["Aneeka"] = "61",
                ["Blizzy"] = "54",
                ["Evilplaque"] = "50",
                ["Lubov"] = "87",
                ["Dkmonkey"] = "1",
            },
            ["texture"] = "Interface\\Icons\\INV_Boots_Chain_07",
        }, -- [8]
        {
            ["received"] = 109267.102,
            ["assigned"] = "Dkmonkey",
            ["itemID"] = 50319,
            ["link"] = "|cffa335ee|Hitem:50319:0:0:0:0:0:0:459549600:80|h[Unsharpened Ice Razor]|h|r",
            ["choices"] = {
                ["Aneeka"] = "disenchant",
                ["Lubov"] = "disenchant",
                ["Evilplaque"] = "disenchant",
                ["Blizzy"] = "disenchant",
                ["Dkmonkey"] = "disenchant",
            },
            ["rolls"] = {
                ["Aneeka"] = "69",
                ["Lubov"] = "28",
                ["Evilplaque"] = "30",
                ["Blizzy"] = "21",
                ["Dkmonkey"] = "80",
            },
            ["texture"] = "Interface\\Icons\\inv_weapon_shortblade_61",
        }, -- [9]
        {
            ["received"] = 109483.031,
            ["assigned"] = "Dkmonkey",
            ["itemID"] = 50262,
            ["link"] = "|cffa335ee|Hitem:50262:0:0:0:0:0:0:305102456:80|h[Felglacier Bolter]|h|r",
            ["choices"] = {
                ["Aneeka"] = "disenchant",
                ["Blizzy"] = "disenchant",
                ["Evilplaque"] = "disenchant",
                ["Lubov"] = "disenchant",
                ["Dkmonkey"] = "disenchant",
            },
            ["rolls"] = {
                ["Aneeka"] = "9",
                ["Lubov"] = "40",
                ["Evilplaque"] = "69",
                ["Blizzy"] = "45",
                ["Dkmonkey"] = "77",
            },
            ["texture"] = "Interface\\Icons\\inv_weapon_crossbow_30",
        }, -- [10]
        {
            ["received"] = 109746.236,
            ["assigned"] = "Blizzy",
            ["itemID"] = 37780,
            ["link"] = "|cff0070dd|Hitem:37780:0:0:0:0:0:0:1986751616:80|h[Condor-Bone Chestguard]|h|r",
            ["choices"] = {
                ["Aneeka"] = "greed",
                ["Blizzy"] = "disenchant",
                ["Evilplaque"] = "greed",
                ["Lubov"] = "greed",
                ["Dkmonkey"] = "greed",
            },
            ["rolls"] = {
                ["Aneeka"] = "14",
                ["Lubov"] = "22",
                ["Evilplaque"] = "72",
                ["Blizzy"] = "77",
                ["Dkmonkey"] = "62",
            },
            ["texture"] = "Interface\\Icons\\INV_Chest_Chain_14",
        }, -- [11]
        {
            ["received"] = 110173.528,
            ["assigned"] = "Lubov",
            ["itemID"] = 50272,
            ["link"] = "|cffa335ee|Hitem:50272:0:0:0:0:0:0:349262944:80|h[Frost Wyrm Ribcage]|h|r",
            ["choices"] = {
                ["Aneeka"] = "greed",
                ["Blizzy"] = "disenchant",
                ["Evilplaque"] = "disenchant",
                ["Lubov"] = "need",
                ["Dkmonkey"] = "disenchant",
            },
            ["rolls"] = {
                ["Lubov"] = "2",
            },
            ["texture"] = "Interface\\Icons\\inv_chest_plate23",
        }, -- [12]
        {
            ["received"] = 110155.978,
            ["assigned"] = "Lubov",
            ["itemID"] = 50285,
            ["link"] = "|cffa335ee|Hitem:50285:0:0:0:0:0:0:617698400:80|h[Icebound Bronze Cuirass]|h|r",
            ["choices"] = {
                ["Aneeka"] = "greed",
                ["Blizzy"] = "disenchant",
                ["Evilplaque"] = "disenchant",
                ["Lubov"] = "need",
                ["Dkmonkey"] = "disenchant",
            },
            ["rolls"] = {
                ["Lubov"] = "25",
            },
            ["texture"] = "Interface\\Icons\\inv_chest_plate23",
        }, -- [13]
        {
            ["received"] = 110154.673,
            ["assigned"] = "Evilplaque",
            ["itemID"] = 43102,
            ["link"] = "|cff0070dd|Hitem:43102:0:0:0:0:0:0:1423004768:80|h[Frozen Orb]|h|r",
            ["choices"] = {
                ["Aneeka"] = "greed",
                ["Lubov"] = "greed",
                ["Evilplaque"] = "greed",
                ["Blizzy"] = "greed",
                ["Dkmonkey"] = "greed",
            },
            ["rolls"] = {
                ["Aneeka"] = "83",
                ["Blizzy"] = "96",
                ["Evilplaque"] = "96",
                ["Lubov"] = "60",
                ["Dkmonkey"] = "15",
            },
            ["texture"] = "Interface\\Icons\\Spell_Frost_FrozenCore",
        }, -- [14]
    }
    self:UpdateReport()
end
--]]

-- /dump NeedyGreedy:TestSuperFind()
--[[
function NeedyGreedy:TestSuperFind()
    do
        local pattern, captureIndices = patternFromFormat("%s automatically passed on: %s because he cannot loot that item.")
        DevTools_Dump({pattern, captureIndices})
        DevTools_Dump({superFind("bob automatically passed on: [Tuxedo Jacket] because he cannot loot that item.", pattern, captureIndices)})
    end

    do
        local pattern, captureIndices = patternFromFormat("%1$s gewinnt: %3$s |cff818181(Gier - %2$d)|r")
        DevTools_Dump({pattern, captureIndices})
        DevTools_Dump({superFind("bob gewinnt: [Tuxedo Jacket] |cff818181(Gier - 123)|r", pattern, captureIndices)})
    end

    --DevTools_Dump()

end
]]

Go to most recent revision | Compare with Previous | Blame