WoWInterface SVN NeedyGreedy

[/] [trunk/] [RollWatcher.lua] - Rev 2

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

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

LibStub:GetLibrary("LibDataBroker-1.1"):NewDataObject("RollWatcher", {
        type = "launcher",
        icon = "Interface\\Buttons\\UI-GroupLoot-Dice-Up",
        OnClick = function(clickedframe, button)
                RollWatcher:ToggleDisplay()
        end,
})

local fontsize = 12
local iconheight = 40
local report = nil
local items = {}

local options = {
   name = "RollWatcher",
   desc = "Displays a table of items and the roll choices players have made on them",
   handler = RollWatcher,
   type = "group",
   args = {
      nitems = {
         name = "Display Items",
         desc = "Number of item columns in the display window",
         type = "range",
         min = 1,
         max = 10,
         step = 1,
         get = "GetNItems",
         set = "SetNItems"
      },
      width = {
         name = "Namelist Width",
         desc = "Pixel width of columns containing names (increase if your raid has very long names)",
         type = "range",
         min = 80,
         max = 250,
         step = 1,
         get = "GetNameListWidth",
         set = "SetNameListWidth"
      },
      scale = {
         name = "Window Scale",
         desc = "Overall scaling of the display window",
         type = "range",
         min = 0.1,
         max = 2,
         step = 0.01,
         isPercent = true,
         get = "GetWindowScale",
         set = "SetWindowScale"
      },
      expiry = {
         name = "Expiry Time",
         desc = "Minutes after item is received before it is removed from display (0 = forever)",
         type = "range",
         min = 0,
         max = 60,
         step = 1,
         get = "GetExpiry",
         set = "SetExpiry"
      },
      quality = {
         name = "Minimum Quality",
         desc = "Minimum quality of item to be displayed",
         type = "select",
         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"
      }
   }
}

local defaults = {
   profile = {
      nitems = 2,
      namelistwidth = 100,
      scale = 1,
      expiry = 5,
      quality = ITEM_QUALITY_EPIC
   }
}

function RollWatcher:OnInitialize()
   self.db = LibStub("AceDB-3.0"):New("RollWatcherDB", defaults, true)
   self.db.RegisterCallback(self, "OnProfileChanged", "ResizeFrames")
   self.db.RegisterCallback(self, "OnProfileCopied", "ResizeFrames")
   self.db.RegisterCallback(self, "OnProfileReset", "ResizeFrames")
   options.args.profiles = LibStub("AceDBOptions-3.0"):GetOptionsTable(self.db)
   LibStub("AceConfig-3.0"):RegisterOptionsTable("RollWatcher", options)
   LibStub("AceConfigDialog-3.0"):AddToBlizOptions("RollWatcher")
   self:SetupFrames()
end

function RollWatcher:OnEnable()
   self:RegisterEvent("PARTY_MEMBERS_CHANGED")
   self:RegisterEvent("START_LOOT_ROLL")
   self:RegisterEvent("CHAT_MSG_LOOT")
   self:ScheduleRepeatingTimer("ExpireItems", 1)
   self:UpdateReport()
end

function RollWatcher:OnDisable()
   report:hide()
end

function RollWatcher:ToggleDisplay()
   if report:IsShown() then
      report:Hide()
   else
      report:Show()
   end
end

function RollWatcher:PARTY_MEMBERS_CHANGED()
   self:ResizeFrames()
end

function RollWatcher: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,
         assigned = "",
         received = 0,
         choices = {},
         rolls = {}
      }
      self:UpdateReport()
   end
end

function RollWatcher: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)
      return
   end

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

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

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

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

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

function RollWatcher:RecordChoice(link, player, choice)
   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 RollWatcher:RecordRoll(link, player, number)
   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 RollWatcher:RecordAwarded(link, player)
   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 RollWatcher:RecordReceived(link)
   for rollid, record in pairs(items) do
      if record.received == 0 and record.link == link then
         record.received = GetTime()
         break
      end
   end
   self:UpdateReport()
end

function RollWatcher:SetupFrames()
   local f = GameFontNormal:GetFont()
   local spacing = 5

   -- Create outer frame with backdrop
   report = CreateFrame("Frame", "RollWatcherFrame", UIParent)
   report:Hide()
   self:SetupSizes()
   report:SetToplevel(true)
   report:SetWidth(report.totalwidth)
   report:SetHeight(report.totalheight)
   report:SetScale(self.db.profile.scale)
   report:EnableMouse(true)
   report:SetMovable(true)
   report:RegisterForDrag("LeftButton")
   report:SetScript("OnDragStart", function() this:StartMoving() end)
   report:SetScript("OnDragStop", function() this:StopMovingOrSizing() end)
   report:SetBackdrop({
                         bgFile = "Interface\\DialogFrame\\UI-DialogBox-Background", tile = true, tileSize = 16,
                         edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border", edgeSize = 16,
                         insets = {left = 4, right = 4, top = 4, bottom = 4},
                      })
   report:SetBackdropBorderColor(.5, .5, .5)
   report:SetBackdropColor(0,0,0)
   report:ClearAllPoints()
   report:SetPoint("CENTER")

   -- Create player namelist, anchored to outer frame with room at top for icons
   local namelist = report:CreateFontString(nil, "OVERLAY")
   namelist:SetFont(f, fontsize)
   namelist:SetWidth(self.db.profile.namelistwidth)
   namelist:SetHeight(report.namelistheight)
   namelist:SetJustifyV("TOP")
   namelist:SetJustifyH("LEFT")
   namelist:SetText("")
   namelist:ClearAllPoints()
   namelist:SetPoint("TOPLEFT", report, "TOPLEFT", report.spacing, -(report.spacing * 2 + iconheight))
   report.namelist = namelist

   -- Create invisible frame around choice lists, for anchoring purposes
   local itemgroup = CreateFrame("Frame", nil, report)
   itemgroup:SetWidth(self.db.profile.nitems * (self.db.profile.namelistwidth + report.spacing) - report.spacing)
   itemgroup:SetHeight(report.namelistheight)
   itemgroup:ClearAllPoints()
   itemgroup:SetPoint("TOPLEFT", namelist, "TOPRIGHT", report.spacing, 0)
   report.itemgroup = itemgroup

   -- Create icons and choice lists for each item column
   report.choices = {}
   report.icons = {}
   for i = 1,self.db.profile.nitems do
      self:SetupItemFrames(i)
   end

   -- Create text at bottom and page buttons
   local bottomtext = report:CreateFontString(nil, "OVERLAY")
   bottomtext:SetFont(f, fontsize)
   bottomtext:SetWidth(report.bottomtextwidth)
   bottomtext:SetText("")
   bottomtext:ClearAllPoints()
   bottomtext:SetPoint("TOP", itemgroup, "BOTTOM", 0, -report.spacing)
   report.bottomtext = bottomtext

   local leftbutton = CreateFrame("Button", nil, report)
   leftbutton:SetWidth(32)
   leftbutton:SetHeight(32)
   leftbutton:SetNormalTexture("Interface\\Buttons\\UI-SpellbookIcon-PrevPage-Up")
   leftbutton:SetPushedTexture("Interface\\Buttons\\UI-SpellbookIcon-PrevPage-Down")
   leftbutton:SetDisabledTexture("Interface\\Buttons\\UI-SpellbookIcon-PrevPage-Disabled")
   leftbutton:SetHighlightTexture("Interface\\Buttons\\UI-Common-MouseHilight", "ADD")
   leftbutton:ClearAllPoints()
   leftbutton:SetPoint("RIGHT", bottomtext, "LEFT", -report.spacing, 0)
   leftbutton:SetScript("OnClick", function() self:PageLeft() end)
   report.leftbutton = leftbutton

   local rightbutton = CreateFrame("Button", nil, report)
   rightbutton:SetWidth(32)
   rightbutton:SetHeight(32)
   rightbutton:SetNormalTexture("Interface\\Buttons\\UI-SpellbookIcon-NextPage-Up")
   rightbutton:SetPushedTexture("Interface\\Buttons\\UI-SpellbookIcon-NextPage-Down")
   rightbutton:SetDisabledTexture("Interface\\Buttons\\UI-SpellbookIcon-NextPage-Disabled")
   rightbutton:SetHighlightTexture("Interface\\Buttons\\UI-Common-MouseHilight", "ADD")
   rightbutton:ClearAllPoints()
   rightbutton:SetPoint("LEFT", bottomtext, "RIGHT", report.spacing, 0)
   rightbutton:SetScript("OnClick", function() self:PageRight() end)
   report.rightbutton = rightbutton

   report.firstitem = 1
end

function RollWatcher:ResizeFrames()
   self:SetupSizes()
   report:SetWidth(report.totalwidth)
   report:SetHeight(report.totalheight)
   report.namelist:SetWidth(self.db.profile.namelistwidth)
   report.namelist:SetHeight(report.namelistheight)
   report.itemgroup:SetWidth(self.db.profile.nitems * (self.db.profile.namelistwidth + report.spacing) - report.spacing)
   report.itemgroup:SetHeight(report.namelistheight)
   report.bottomtext:SetWidth(report.bottomtextwidth)
   for i = 1, self.db.profile.nitems do
      if not report.choices[i] then
         self:SetupItemFrames(i)
      else
         local choices = report.choices[i]
         choices:SetWidth(self.db.profile.namelistwidth)
         choices:SetHeight(report.namelistheight)
         choices:ClearAllPoints()
         choices:SetPoint("TOPLEFT", report.itemgroup, "TOPLEFT", (i - 1) * (self.db.profile.namelistwidth + report.spacing), 0)
      end
   end
   -- Hide any extra item frames we might have
   for i = self.db.profile.nitems + 1, table.maxn(report.choices) do
      report.choices[i]:Hide()
      report.icons[i]:Hide()
   end
   self:UpdateReport()
end

function RollWatcher:SetupItemFrames(i)
   local f = GameFontNormal:GetFont()
   local choices = report.itemgroup:CreateFontString(nil, "OVERLAY")

   choices:SetFont(f, fontsize)
   choices:SetWidth(self.db.profile.namelistwidth)
   choices:SetHeight(report.namelistheight)
   choices:SetJustifyV("TOP")
   choices:SetJustifyH("LEFT")
   choices:SetText("")
   choices:ClearAllPoints()
   choices:SetPoint("TOPLEFT", report.itemgroup, "TOPLEFT", (i - 1) * (self.db.profile.namelistwidth + report.spacing), 0)
   report.choices[i] = choices

   local icon = CreateFrame("Button", "RollWatcherIcon" .. i, report.itemgroup, "ItemButtonTemplate")
   icon:ClearAllPoints()
   icon:SetPoint("BOTTOMLEFT", choices, "TOPLEFT", 0, report.spacing)
   icon:Hide()
   icon:SetScript("OnEnter", function() self:IconEntered(i) end)
   icon:SetScript("OnLeave", function() GameTooltip:Hide() end)
   icon:SetScript("OnClick", function() self:IconClicked(i) end)
   report.icons[i] = icon
end

function RollWatcher:IconEntered(i)
   local sorted = self:SortRollids()
   local count = self:CountItems()
   local ind = report.firstitem + i - 1

   if ind <= count then
      local rollid = sorted[ind]
      GameTooltip:SetOwner(report.icons[i], "ANCHOR_RIGHT")
      GameTooltip:SetHyperlink(items[rollid].link)
   end
end

function RollWatcher:IconClicked(i)
   local sorted = self:SortRollids()
   local count = self:CountItems()
   local ind = report.firstitem + i - 1

   if ind <= count then
      local rollid = sorted[ind]
      if (IsControlKeyDown()) then
         items[rollid] = nil
         self:UpdateReport()
         self:IconEntered(i)
      else
         HandleModifiedItemClick(items[rollid].link)
      end
   end
end

-- Compute sizes based on the number of players in the party/raid
function RollWatcher:SetupSizes()
   local nplayers = self:GetNumPlayers()

   report.namelistheight = (nplayers + 2) * fontsize + 5 -- font sizes aren't quite integers; leave some wiggle room
   report.spacing = 10
   report.totalwidth = report.spacing + (self.db.profile.nitems + 1) * (self.db.profile.namelistwidth + report.spacing)
   report.totalheight = report.spacing * 4 + iconheight + report.namelistheight + fontsize
   if self.db.profile.nitems == 1 then
      report.bottomtextwidth = 10
   else
      report.bottomtextwidth = self.db.profile.namelistwidth
   end
end

function RollWatcher:PageLeft()
   report.firstitem = report.firstitem - self.db.profile.nitems
   if report.firstitem < 1 then
      report.firstitem = 1
   end
   self:UpdateReport()
end

function RollWatcher:PageRight()
   local count = RollWatcher: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 RollWatcher:GetSortedPlayers()
   local list = {}

   if GetNumRaidMembers() > 0 then
      for i = 1,MAX_RAID_MEMBERS do
         name = GetRaidRosterInfo(i)
         if name then
            table.insert(list, name)
         end
      end
   else
      for _, unit in ipairs({"player", "party1", "party2", "party3", "party4"}) do
         local name = UnitName(unit)
         if name then
            table.insert(list, name)
         end
      end
   end
   table.sort(list)
   return list
end

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

function RollWatcher: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 RollWatcher:ChoiceText(choice)
   if choice == "need" then
      return "|c00FF0000" .. NEED .. "|r"
   elseif choice == "greed" then
      return "|c0000FF00" .. GREED .. "|r"
   elseif choice == "pass" then
      return "|c00CCCCCC" .. PASS .. "|r"
   else
      return ""
   end
end

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

function RollWatcher: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 RollWatcher: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 RollWatcher:CountItems()
   local i = 0
   for _, _ in pairs(items) do
      i = i + 1
   end
   return i
end

function RollWatcher:UpdateReport()
   local players = self:GetSortedPlayers()

   -- Place name list into report.namelist
   local text = ""
   for _, name in ipairs(players) do
      text = text .. self:ColorizeName(name) .. "\n"
   end
   text = text .. "\nWinner"
   report.namelist:SetText(text)

   -- Verify that report.firstitem is set reasonably
   local sorted = self:SortRollids()
   local count = self:CountItems()
   if count == 0 then
      report.firstitem = 1
   elseif report.firstitem > count then
      report.firstitem = count
   end

   -- Fill in each item frame
   for i = 1, self.db.profile.nitems do
      local ind = report.firstitem + i - 1
      if ind <= count then
         local rollid = sorted[ind]
         local item = items[rollid]

         SetItemButtonTexture(report.icons[i], item.texture)
         report.icons[i]:Show()
         text = ""
         for _, name in ipairs(players) do
            text = text .. self:ChoiceText(item.choices[name]) .. self:RollText(item.rolls[name]) .. "\n"
         end
         text = text .. "\n" .. self:AssignedText(item)
         report.choices[i]:SetText(text)
      else
         report.icons[i]:Hide()
         report.choices[i]:SetText("")
      end
   end

   -- Set the text at the bottom
   if self.db.profile.nitems == 1 then
      report.bottomtext:SetText(tostring(report.firstitem))
   elseif count == 0 then
      report.bottomtext:SetText("None")
   elseif count == 1 or report.firstitem == count then
      report.bottomtext:SetText(string.format("%d of %d", report.firstitem, count))
   else
      local lastitem = report.firstitem + self.db.profile.nitems - 1
      if (lastitem > count) then
         lastitem = count
      end
      report.bottomtext:SetText(string.format("%d-%d of %d", report.firstitem, lastitem, count))
   end

   -- Enable or disable the page buttons
   if report.firstitem > 1 then
      report.leftbutton:Enable()
   else
      report.leftbutton:Disable()
   end
   if report.firstitem + self.db.profile.nitems - 1 < count then
      report.rightbutton:Enable()
   else
      report.rightbutton:Disable()
   end
end

function RollWatcher: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
         update = true
      end
   end
   if update then
      self:UpdateReport()
   end
end

function RollWatcher:unformat(fmt, msg)
   local pattern = string.gsub(string.gsub(fmt, "(%%s)", "(.+)"), "(%%d)", "(.+)")
   local _, _, a1, a2, a3, a4 = string.find(msg, pattern)
   return a1, a2, a3, a4
end

-- Config option getters and setters

function RollWatcher:GetNItems(info)
   return self.db.profile.nitems
end

function RollWatcher:SetNItems(info, nitems)
   self.db.profile.nitems = nitems
   self:ResizeFrames()
end

function RollWatcher:GetNameListWidth(info)
   return self.db.profile.namelistwidth
end

function RollWatcher:SetNameListWidth(info, width)
   self.db.profile.namelistwidth = width
   self:ResizeFrames()
end

function RollWatcher:GetWindowScale(info)
  return self.db.profile.scale
end

function RollWatcher:SetWindowScale(info, scale)
  self.db.profile.scale = scale
  report:SetScale(scale)
end

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

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

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

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

Go to most recent revision | Compare with Previous | Blame