-- Create module
local addon , L = XLoot : NewModule ( " Monitor " )
local XLootMonitor = CreateFrame ( " Frame " , " XLootMonitor " , UIParent )
XLootMonitor.addon = addon
-- Grab locals
local print , opt , eframe , anchor = print
local CopperToString , FancyPlayerName = XLoot.CopperToString , XLoot.FancyPlayerName
local table_insert , table_remove = table.insert , table.remove
local me = UnitName ( " player " )
-------------------------------------------------------------------------------
-- Settings
local defaults = {
profile = {
anchor = {
direction = " up " ,
alignment = " left " ,
visible = true ,
scale = 1.0 ,
draggable = true ,
x = UIParent : GetWidth ( ) * .75 ,
y = UIParent : GetHeight ( ) * .15 ,
} ,
name_width = 50 ,
gradients = false ,
threshold_own = 2 ,
threshold_other = 3 ,
show_coin = false ,
show_currency = true ,
show_crafted = false ,
show_totals = false ,
totals_delay = 0.5 ,
use_altoholic = true ,
show_ilvl = false ,
fade_own = 10 ,
fade_other = 5 ,
font = STANDARD_TEXT_FONT ,
font_size_loot = 12 ,
font_size_quantity = 10 ,
font_size_ilvl = 8 ,
font_flag = " OUTLINE " ,
}
}
----------------------------------------------------------------------
-- Helpers
local numberize = function ( v )
if v <= 9999 then return v end
if v >= 1000000 then
local value = string.format ( " %.1fm " , v / 1000000 )
return value
elseif v >= 10000 then
local value = string.format ( " %.0fk " , v / 1000 )
return value
end
end
-------------------------------------------------------------------------------
-- Module init
function addon : OnInitialize ( )
eframe = CreateFrame ( " Frame " )
self : InitializeModule ( defaults , eframe )
XLoot : SetSlashCommand ( " xlm " , self.SlashHandler )
opt = self.db . profile
end
function addon : OnEnable ( )
-- Register for loot events
LibStub ( " LootEvents " ) : RegisterLootCallback ( self.LOOT_EVENT )
eframe : RegisterEvent ( " MODIFIER_STATE_CHANGED " )
-- Set up skins
XLoot : MakeSkinner ( self , {
default = { gradient = opt.gradients } ,
anchor = { r = .4 , g = .4 , b = .4 , a = .6 , gradient = false } ,
anchor_pretty = { r = .6 , g = .6 , b = .6 , a = .8 } ,
item = { backdrop = false , gradient = opt.gradients } ,
item_highlight = { type = " highlight " , layer = " overlay " } ,
row_highlight = { type = " highlight " }
} )
-- Set up anchor
anchor = XLoot.Stack : CreateStaticStack ( self.CreateRow , L.anchor , opt.anchor )
self : Skin ( anchor , XLoot.opt . skin_anchors and ' anchor_pretty ' or ' anchor ' )
end
function addon : ApplyOptions ( )
opt = self.opt
anchor : UpdateSVData ( opt.anchor )
addon : Restack ( )
end
local events = { }
function events . item ( player , link , num )
if link and link : match ( " |Hitem: " ) then -- Proper items
local name , _ , quality , level , _ , _ , _ , _ , _ , icon = GetItemInfo ( link )
if not name or type ( quality ) ~= " number " then
print ( name and " Quality is not a number " or " Name is nil " )
return false
end
if ( player == me and opt.threshold_own or opt.threshold_other ) > quality then
return -- Doesn't meet threshold requirements
end
local r , g , b = GetItemQualityColor ( quality )
local nr , ng , nb
if player ~= me then
player , nr , ng , nb = FancyPlayerName ( player , select ( 2 , UnitClass ( player ) ) , opt )
else
player = nil
end
local row = addon : AddRow ( icon , ( player and opt.fade_other or opt.fade_own ) , r , g , b )
local num = tonumber ( num ) or 1
row : SetTexts ( player , num > 1 and ( " %sx%d " ) : format ( link , num ) or link , nil , nr , ng , nb )
if opt.show_totals then
row.timeToTotal = opt.totals_delay
end
if opt.show_ilvl and level > 1 then
local ilvl = GetDetailedItemLevelInfo ( link )
row.ilvl : SetText ( ilvl )
end
row.item = link
elseif link and link : match ( " |Hbattlepet: " ) then -- Battlepets. Really?
-- local _, speciesID, level, breedQuality, maxHealth, power, speed, battlePetID = strsplit(":", link)
else
-- print("Unknown or invalid link type")
-- return false
end
end
function events . coin ( coin_string , copper )
if opt.show_coin then
addon : AddRow ( GetCoinIcon ( copper ) , opt.fade_own , .5 , .5 , .5 , .5 , .5 , .5 ) : SetTexts ( nil , CopperToString ( copper ) )
end
end
function events . currency ( id , num )
-- Not sure what event is causing us to capture nothing for (%d+), this isn't ideal
if id ~= nil and opt.show_currency then
local num = tonumber ( num ) or 1
local c = C_CurrencyInfo.GetCurrencyInfo ( id )
if c then
addon : AddRow ( c.iconFileID , opt.fade_own , 1 , 1 , 1 , 1 , 1 , 1 ) : SetTexts ( nil , num > 1 and ( " %s x%d " ) : format ( c.name , num ) or c.name , c.quantity )
end
end
end
function events . crafted ( link , num )
if opt.show_crafted then
events.item ( me , link , num )
end
end
function addon . LOOT_EVENT ( event , pattern , ... )
if events [ event ] and events [ event ] ( ... ) == false then
print ( " XLoot Monitor: Error handling event " , event , pattern , ... )
end
end
local mouse_focus
function addon : MODIFIER_STATE_CHANGED ( self , modifier , state )
if mouse_focus and MouseIsOver ( mouse_focus ) then
mouse_focus : ShowTooltip ( )
end
end
local pool , stack , active = { } , { } , false
stack [ 0 ] = anchor
local timer = 0
function addon . EframeUpdate ( self , elapsed )
timer = timer + elapsed
for i , row in ipairs ( stack ) do
-- Deferred total calculation due to GetItemCount reliability
local ttt = row.timeToTotal
if ttt then
ttt = ttt - elapsed
if ttt <= 0 then
ttt = nil
local total
if opt.use_altoholic and Altoholic then
total = Altoholic : GetItemCount ( Altoholic : GetIDFromLink ( row.item ) )
else
total = GetItemCount ( row.item )
end
if total and total > 1 then
row.total : SetText ( numberize ( total ) )
end
end
row.timeToTotal = ttt
end
-- Animation
local remaining = row.expires - timer
if remaining < 0 then
row : SetAlpha ( 0 )
addon : RemoveRow ( row )
elseif remaining <= .5 then
row : SetAlpha ( remaining * 2 )
else
local since = timer - row.started
if since <= .5 then
row : SetAlpha ( since * 2 )
end
end
end
end
function addon : RemoveRow ( row )
row : Hide ( )
for i , v in ipairs ( stack ) do
if v == row then
table_remove ( stack , i )
table_insert ( pool , row )
if stack [ i ] then
anchor : AnchorChild ( stack [ i ] , stack [ i - 1 ] )
end
break
end
end
-- Disable OnUpdate
if # stack == 0 then
active , timer = false , 0
eframe : SetScript ( " OnUpdate " , nil )
end
end
function addon : AddRow ( icon , fade_time , ir , ig , ib , rr , rg , rb )
-- Acquire
local row = table_remove ( pool )
if not row then
row = self.CreateRow ( )
end
-- Set up row
row.icon : SetTexture ( icon )
row.icon_frame : SetBorderColor ( ir , ig , ib )
row : SetBorderColor ( rr or ir , rg or ig , rb or ib )
row.expires = timer + fade_time
row.started = timer
row.item = nil
-- Anchor
anchor : AnchorChild ( row )
table_insert ( stack , 1 , row )
-- Reanchor second newest
if stack [ 2 ] then
anchor : AnchorChild ( stack [ 2 ] , row )
end
row : Show ( )
-- Enable OnUpdate
if not active then
active = true
eframe : SetScript ( " OnUpdate " , self.EframeUpdate )
end
return row
end
function addon : Restack ( )
for i , v in ipairs ( stack ) do
anchor : AnchorChild ( v , i > 1 and stack [ i - 1 ] or nil )
v : ApplyOptions ( )
end
end
-------------------------------------------------------------------------------
-- Frame methods
do
local function ShowTooltip ( self )
if self.item then
GameTooltip : SetOwner ( self , ' ANCHOR_TOPLEFT ' , 28 , 0 )
GameTooltip : SetHyperlink ( self.item )
CursorUpdate ( self )
end
end
local function OnEnter ( self )
mouse_focus = self
if self._highlights then
self.icon_frame : ShowHighlight ( )
end
self : ShowTooltip ( )
end
local function OnLeave ( self )
mouse_focus = nil
if self._highlights then
self.icon_frame : HideHighlight ( )
end
GameTooltip : Hide ( )
ResetCursor ( )
end
local function OnClick ( self , button )
if IsModifiedClick ( ) and self.item then
HandleModifiedItemClick ( self.item )
end
end
local function FitToText ( self )
self : SetWidth ( self.name : GetWidth ( ) + self.text : GetWidth ( ) + 8 + self.icon_frame : GetWidth ( ) )
end
local function SetTexts ( self , name , text , total , nr , ng , nb , tr , tg , tb )
self.name : SetText ( name and name .. " " or nil )
self.text : SetText ( text )
self.ilvl : SetText ( )
-- Show total after 0.5 seconds to get a valid count
self.total : SetText ( )
self.name : SetVertexColor ( nr or 1 , ng or 1 , nb or 1 )
self.text : SetVertexColor ( nr or 1 , ng or 1 , nb or 1 )
if name then
self.name : SetWidth ( opt.name_width )
else
self.name : SetWidth ( 0 )
end
self : FitToText ( )
end
local widgets = {
{ " icon_frame " , 0 } ,
{ " name " , 2 } ,
{ " text " , 0 } ,
}
local function ApplyOptions ( self )
local a , b , xmod = " LEFT " , " RIGHT " , 1
if opt.anchor . alignment == " right " then
a , b , xmod = b , a , - 1
end
self.icon_frame : ClearAllPoints ( )
self.icon_frame : SetPoint ( a )
self.name : ClearAllPoints ( )
self.name : SetPoint ( a , self.icon_frame , b , 2 * xmod , 0 )
self.name : SetJustifyH ( a )
self.name : SetFont ( opt.font , opt.font_size_loot , opt.font_flag )
self.text : ClearAllPoints ( )
self.text : SetPoint ( a , self.name , b )
self.text : SetJustifyH ( a )
self.text : SetFont ( opt.font , opt.font_size_loot , opt.font_flag )
self.ilvl : SetFont ( opt.font , opt.font_size_ilvl , opt.font_flag )
self.total : SetFont ( opt.font , opt.font_size_quantity , opt.font_flag )
self : FitToText ( )
end
function addon . CreateRow ( )
local frame = CreateFrame ( " Button " , nil , UIParent , BackdropTemplateMixin and " BackdropTemplate " )
frame : SetFrameLevel ( anchor : GetFrameLevel ( ) )
frame : SetHeight ( 24 )
frame : SetWidth ( 250 )
addon : Skin ( frame )
addon : Highlight ( frame , " row_highlight " )
frame : SetHighlightColor ( .8 , .8 , .8 )
frame : RegisterForClicks ( " LeftButtonUp " , " RightButtonUp " )
frame : SetScript ( " OnClick " , OnClick )
frame : SetScript ( " OnEnter " , OnEnter )
frame : SetScript ( " OnLeave " , OnLeave )
-- Item icon (For skin border)
local icon_frame = CreateFrame ( " Frame " , nil , frame , BackdropTemplateMixin and " BackdropTemplate " )
icon_frame : SetWidth ( 28 )
icon_frame : SetHeight ( 28 )
addon : Skin ( icon_frame , " item " )
addon : Highlight ( icon_frame , " item_highlight " )
frame.icon_frame = icon_frame
-- Item texture
local icon = icon_frame : CreateTexture ( nil , " BACKGROUND " )
icon : SetPoint ( " TOPLEFT " , 3 , - 3 )
icon : SetPoint ( " BOTTOMRIGHT " , - 3 , 3 )
icon : SetTexCoord ( .07 , .93 , .07 , .93 )
frame.icon = icon
local ilvl = icon_frame : CreateFontString ( nil , " OVERLAY " )
ilvl : SetPoint ( " BOTTOM " , icon_frame , " BOTTOM " , 0 , 0 )
ilvl : SetJustifyH ( " CENTER " )
frame.ilvl = ilvl
frame.name = frame : CreateFontString ( nil , " OVERLAY " )
frame.text = frame : CreateFontString ( nil , " OVERLAY " )
local total = icon_frame : CreateFontString ( nil , " OVERLAY " )
total : SetPoint ( " CENTER " , icon_frame , " CENTER " , 0 , 0 )
total : SetJustifyH ( " CENTER " )
frame.total = total
frame.FitToText = FitToText
frame.SetTexts = SetTexts
frame.ShowTooltip = ShowTooltip
frame.ApplyOptions = ApplyOptions
frame : ApplyOptions ( )
return frame
end
end
function addon : UpdateAnchors ( ... )
if opt.anchor . visible then
anchor : Show ( )
else
anchor : Hide ( )
end
end
function addon . SlashHandler ( msg )
if anchor : IsShown ( ) then
anchor : Hide ( )
else
anchor : Show ( )
end
end
local items = {
{ 52722 } ,
{ 31304 } ,
{ 37254 } ,
{ 13262 } ,
{ 15487 } ,
{ 72120 } ,
{ 2589 }
}
local currencies = {
81 ,
1728
}
for i , v in ipairs ( items ) do
GetItemInfo ( v [ 1 ] )
end
local players = {
{ UnitName ( " player " ) , select ( 2 , UnitClass ( ' player ' ) ) } ,
{ ' Player1 ' , ' MAGE ' } ,
{ ' Player2 ' , ' PRIEST ' } ,
{ ' Player3 ' , ' WARRIOR ' } ,
{ ' Player4 ' , ' SHAMAN ' }
}
local random = math.random
local function random_player ( is_me )
return is_me and players [ 1 ] [ 1 ] or players [ random ( 2 , 5 ) ] [ 1 ]
end
local function random_item_num ( max )
return random ( 1 , 2 ) == 2 and random ( 1 , max or 20 ) or 1
end
local function random_item_link ( )
return select ( 2 , GetItemInfo ( items [ random ( 1 , # items ) ] [ 1 ] ) )
end
local function test_item ( event , is_me )
addon.LOOT_EVENT ( ' item ' , event , random_player ( is_me ) , random_item_link ( ) , random_item_num ( ) )
end
local function test_coin ( event , is_me )
addon.LOOT_EVENT ( ' coin ' , event , random_player ( is_me ) , random ( 1 , 500000 ) , " TODO " )
end
local function test_currency ( event )
for _ , id in ipairs ( currencies ) do
addon.LOOT_EVENT ( ' currency ' , event , id , random_item_num ( 5 ) )
end
end
local function test_crafted ( event )
addon.LOOT_EVENT ( ' crafted ' , event , random_item_link ( ) , random_item_num ( ) )
end
local function test_battlepet ( event )
print ( " Testing battlepet event " )
addon.LOOT_EVENT ( ' item ' , " LOOT_ITEM_PUSHED_SELF " , random_player ( true ) , " |cff0070dd|Hbattlepet:868:1:3:158:10:12:0x0000000000000000|h[Pandaren Water Spirit]|h|r " , 1 )
end
local tests = {
{ test_item , " LOOT_ITEM " } ,
{ test_item , " LOOT_ITEM_SELF " , true } ,
{ test_item , " LOOT_ITEM_MULTIPLE " } ,
{ test_item , " LOOT_ITEM_SELF_MULTIPLE " , true } ,
{ test_item , " LOOT_ITEM_PUSHED_SELF " , true } ,
{ test_item , " LOOT_ITEM_PUSHED_SELF_MULTIPLE " , true } ,
{ test_coin , " LOOT_MONEY " } ,
{ test_coin , " LOOT_MONEY_SPLIT " , true } ,
{ test_coin , " YOU_LOOT_MONEY " , true } ,
{ test_currency , " CURRENCY_GAINED " } ,
{ test_currency , " CURRENCY_GAINED_MULTIPLE " } ,
{ test_crafted , " LOOT_ITEM_CREATED_SELF " } ,
{ test_crafted , " CURRENCY_GAINED_MULTIPLE " } ,
{ test_battlepet }
}
local queue , queueframe , tick = { } , CreateFrame ( " Frame " ) , 0
local function queue_update ( self , elapsed )
tick = tick + elapsed
if tick > 0.5 then
tick = 0
local time = GetTime ( )
for k , v in pairs ( queue ) do
if v [ 1 ] < time then
if v [ 3 ] then
print ( " Testing " .. v [ 3 ] )
end
v [ 2 ] ( select ( 3 , unpack ( v ) ) )
queue [ k ] = nil
end
end
end
end
local qactive = false
function XLootMonitor . TestSettings ( )
local now = GetTime ( )
-- for i=1,15 do
-- table.insert(queue, { now + i, unpack(tests[random(1, #tests)]) })
-- end
if not qactive then
queueframe : SetScript ( " OnUpdate " , queue_update )
qactive = true
end
for i , v in ipairs ( tests ) do
table.insert ( queue , { now + i * .5 , unpack ( v ) } )
end
end
XLoot : SetSlashCommand ( " xlmd " , XLootMonitor.TestSettings )