You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1376 lines
38 KiB

--[[---------------------------------------
Loot button click hook
- row: Loot row
- button: Mouse button
- handled: True if any previous caller acted on event
Hook like this:
local XLootButtonOnClick_Orig = XLootButtonOnClick
function XLootButtonOnClick(row, button, handled)
if not handled and thing_i_want_to_check then
handled = true
dostuff()
end
XLootButtonOnClick_Orig(row, button, handled)
end
--]]---------------------------------------
-- Include QDKP2 compatibility by request
function XLootButtonOnClick(row, button, handled)
if not handled
and QDKP2_IsManagingSession
and IsAltKeyDown()
and button == 'LeftButton'
and GetLootSlotLink(row.slot)
and QDKP2_IsManagingSession()
and row.quality > GetLootThreshold()
and (not QDKP2_BidM_isBidding())
then
pcall(QDKP2_BidM_StartBid, row.item)
if QDKP2GUI_Roster then
QDKP2GUI_Roster:ChangeList('bid')
QDKP2GUI_Roster:Show()
end
return true
end
return handled
end
-- Create module
local addon, L = XLoot:NewModule("Frame")
-- Prepare frame/global
local XLootFrame = CreateFrame("Frame", "XLootFrame", UIParent, BackdropTemplateMixin and "BackdropTemplate")
XLootFrame.addon = addon
-- Grab locals
local mouse_focus, opt
local LOOT_SLOT_NONE = LOOT_SLOT_NONE or Enum.LootSlotType.None
local LOOT_SLOT_ITEM = LOOT_SLOT_ITEM or Enum.LootSlotType.Item
local LOOT_SLOT_MONEY = LOOT_SLOT_MONEY or Enum.LootSlotType.Money
local LOOT_SLOT_CURRENCY = LOOT_SLOT_CURRENCY or Enum.LootSlotType.Currency
-- Chat output
local print, wprint = print, print
local function xprint(text)
wprint(('%s: %s'):format('|c2244dd22XLoot|r', tostring(text)))
end
-- Performance blah blah blah
-- Using this function is a pain in the ass.
local BIND_ON_NONE = 0
local BIND_ON_PICKUP = 1
local BIND_ON_EQUIP = 2
local function GetItemInfoTable(link)
-- Variable names match wowpedia documentation
local name, link, rarity, level, minLevel, type, subType, stackCount, equipLoc, fileDataID, itemSellPrice, itemClassID, itemSubClassID, bindType, expacID, itemSetID, isCraftingReagent = GetItemInfo(link)
if not name then return nil end
-- But table names match common usage
return {
name = name,
link = link,
quality = rarity,
level = level,
minLevel = minLevel,
typeName = type, -- type and subType are localized
subTypeName = subType,
stackCount = stackCount,
equipLoc = equipLoc,
icon = fileDataID,
sellPrice = itemSellPrice,
typeID = itemClassID, -- class and subClass are not
subTypeID = itemSubClassID,
bindType = bindType, -- NONE|PICKUP|EQUIP|?
-- expacID = expacID,
-- itemSetID = itemSetID,
isCraftingReagent = isCraftingReagent
}
end
-------------------------------------------------------------------------------
-- Settings
local defaults = {
profile = {
frame_scale = 1.0,
frame_alpha = 1.0,
quality_color_frame = false,
quality_color_slot = true,
loot_texts_info = true,
loot_texts_bind = true,
loot_texts_lock = true,
loot_buttons_auto = true,
font = STANDARD_TEXT_FONT,
font_size_loot = 12,
font_size_info = 10,
font_size_quantity = 10,
font_size_bottombuttons = 10,
font_size_button_auto = 8,
font_flag = "OUTLINE",
loot_icon_size = 34,
loot_row_height = 30,
loot_highlight = true,
loot_alpha = 1.0,
loot_collapse = false,
frame_snap = true,
frame_snap_offset_x = 0,
frame_snap_offset_y = 0,
frame_grow_upwards = false, -- Actually means "Snap to bottom item"
loot_padding_top = 10,
loot_padding_left = 10,
loot_padding_right = 10,
loot_padding_bottom = 10,
frame_width_automatic = true,
frame_width = 150,
frame_position_x = GetScreenWidth()/2,
frame_position_y = GetScreenHeight()/2,
autoloots = {
currency = 'never',
tradegoods = 'never',
quest = 'never',
list = 'solo',
all = 'never',
},
autoloot_item_list = '',
frame_draggable = true,
linkall_threshold = 2, -- Quality from 0 - 6, Poor - Artifact
linkall_channel = 'RAID',
linkall_channel_secondary = 'NONE',
linkall_show = 'group',
linkall_first_only = false,
old_close_button = false,
frame_color_border = { .5, .5, .5, 1 },
frame_color_backdrop = { 0, 0, 0, .7 },
frame_color_gradient = { .5, .5, .5, .3 },
loot_color_border = { .5, .5, .5, 1 },
loot_color_backdrop = { 0, 0, 0, .9 },
loot_color_gradient = { .5, .5, .5, .4 },
loot_color_info = { .5, .5, .5, 1 },
loot_color_button_auto = { .4, .8, .4, .6 },
show_slot_errors = true,
}
}
-------------------------------------------------------------------------------
-- Module init
function addon:OnInitialize()
self:InitializeModule(defaults, XLootFrame)
opt = self.db.profile
XLootFrame.opt = opt
end
function addon:OnEnable()
-- Register events
XLootFrame:RegisterEvent("LOOT_OPENED")
XLootFrame:RegisterEvent("LOOT_CLOSED")
XLootFrame:RegisterEvent("LOOT_SLOT_CLEARED")
XLootFrame:RegisterEvent("MODIFIER_STATE_CHANGED")
XLootFrame:RegisterEvent("GROUP_ROSTER_UPDATE")
-- Disable default frame
LootFrame:UnregisterEvent("LOOT_OPENED")
LootFrame:UnregisterEvent("LOOT_CLOSED")
LootFrame:UnregisterEvent("LOOT_SLOT_CLEARED")
-- Register for escape close
table.insert(UISpecialFrames, "XLootFrame")
-- Reattach master looter frame
MasterLooterFrame:SetScript('OnShow',
function(self)
if XLootFrame:IsVisible() then
MasterLooterFrame:SetFrameLevel(XLootFrame:GetFrameLevel()+2)
MasterLooterFrame:ClearAllPoints()
MasterLooterFrame:SetPoint("BOTTOM",XLootFrame,"TOP")
end
end)
end
local preview_loot = {
{ 52722, false, true, true },
{ 31304, true, false, false },
{ 37254, true, false, false },
{ 13262, true, false, false },
{ 15487, false, false, false }
}
local preview_currency = {
828,
-- 1728,
-- 1767,
}
for i=1,#preview_loot do
XLootTooltip:SetItemByID(preview_loot[i][1])
GetItemInfo(preview_loot[i][1])
end
function addon:ApplyOptions(in_options)
opt, XLootFrame.opt = self.opt, self.opt
if XLootFrame.built then
XLootFrame:UpdateAppearance()
XLootFrame:Update(true)
end
XLootFrame:ParseAutolootList()
-- Update preview frame in options
if in_options then
local Fake = XLootFakeFrame
Fake.opt = opt
Fake:UpdateAppearance()
local slot, max_width, max_quality = 0, 0, 0
for i,v in ipairs(preview_loot) do
local t = GetItemInfoTable(v[1])
-- local itemName, itemLink, itemRarity, itemLevel, itemMinLevel, itemType, itemSubType, itemStackCount, itemEquipLoc, itemTexture, itemSellPrice = GetItemInfo(v[1])
if not t then
-- xprint("Error: Failed to get information for an item in the configuration preview window. Please re-open the options window in a moment.")
else
t.quantity = 1
t.slotType = LOOT_SLOT_ITEM
slot = slot + 1
local row = Fake.rows[slot]
row.item = t.link
row.quality = t.quality
Fake.slots[slot] = row
max_width = math.max(max_width, row:Update(t))
max_quality = math.max(max_quality, t.quality)
end
end
-- !CLASSIC
if C_CurrencyInfo then
for i,id in ipairs(preview_currency) do
local c = C_CurrencyInfo.GetCurrencyInfo(id)
if c and c.name then
local row = Fake.rows[slot+i]
max_width = math.max(max_width, row:Update({
name = c.name,
icon = c.iconFileID,
quality = c.quality,
slotType = LOOT_SLOT_CURRENCY,
quantity = 5,
}))
Fake.slots[#preview_loot+i] = row
end
end
end
Fake:SizeAndColor(max_width, max_quality)
end
end
function addon:OnOptionsShow(panel)
-- Create preview frame
local frame = XLootFakeFrame
if not frame then
frame = CreateFrame('Frame', 'XLootFakeFrame', panel, BackdropTemplateMixin and "BackdropTemplate")
frame.fake = true
frame.opt = XLootFrame.opt
self:BuildLootFrame(frame)
frame:SetPoint('TOPLEFT', panel, 'TOPRIGHT', 25, 25)
self:ApplyOptions(true)
end
frame:Show()
end
function addon:OnOptionsHide(panel)
XLootFakeFrame:Hide()
end
local IsGroupState = {
always = function() return true end,
never = function() return false end,
raid = IsInRaid,
group = IsInGroup,
party = function() return IsInGroup() and not IsInRaid() end,
solo = function() return not IsInGroup() end
}
-------------------------------------------------------------------------------
-- Link All
local LinkLoot, LinkDropdown
do
local output = { }
function LinkLoot(channel, isExtraChannel)
local output, key, buffer = output, 1
local sf = string.format
if UnitExists('target') then
output[1] = sf('%s:', UnitName('target'))
end
local linkthreshold, reached = opt.linkall_threshold
local first_only = opt.linkall_first_only
for i=1, GetNumLootItems() do
if GetLootSlotType(i) == LOOT_SLOT_ITEM then
local _, _, quantity, _, rarity = GetLootSlotInfo(i)
local link = GetLootSlotLink(i)
if rarity >= linkthreshold then
reached = true
buffer = sf('%s%s%s', (output[key] and output[key].." " or ""), (quantity > 1 and quantity.."x" or ""), link)
if strlen(buffer) > 255 then
key = key + 1
output[key] = (quantity > 1 and quantity.."x" or "")..link
else
output[key] = buffer
end
if opt.linkall_first_only then
break
end
end
end
end
if not reached then
xprint(L.linkall_threshold_missed)
return false
end
if (channel == 'RAID' or channel == 'RAID_WARNING') and not IsInRaid() and IsInGroup() then
channel = 'PARTY'
end
for k, v in pairs(output) do
v = string.gsub(v, "\n", " ", 1, true) -- DIE NEWLINES, DIE A HORRIBLE DEATH
SendChatMessage(v, channel)
if opt.linkall_channel_secondary ~= 'NONE' then
SendChatMessage(v, opt.linkall_channel_secondary)
end
output[k] = nil
end
return true
end
local function Click(dropdown, channel)
LinkLoot(channel)
end
LinkDropdown = CreateFrame('Frame', 'XLootLinkDropdown')
LinkDropdown.displayMode = 'MENU'
local channels = {
{ 'SAY', CHAT_MSG_SAY },
{ 'PARTY', CHAT_MSG_PARTY },
{ 'RAID', CHAT_MSG_RAID },
{ 'RAID_WARNING', CHAT_MSG_RAID_WARNING },
{ 'GUILD', CHAT_MSG_GUILD },
{ 'OFFICER', CHAT_MSG_OFFICER },
}
local info = { }
LinkDropdown.initialize = function(self, level)
for i, c in ipairs(channels) do
wipe(info)
info.text = c[2]
info.arg1 = c[1]
info.func = Click
info.notCheckable = 1
UIDropDownMenu_AddButton(info, 1)
end
end
end
-------------------------------------------------------------------------------
-- Frame creation
-- Helpers
-- >Rows
-- >Loot frame
-- Universal events
local function OnDragStart()
if opt.frame_draggable then
XLootFrame:StartMoving()
end
end
local function OnDragStop()
XLootFrame:StopMovingOrSizing()
opt.frame_position_x = XLootFrame:GetLeft()
opt.frame_position_y = opt.frame_grow_upwards and XLootFrame:GetBottom() or XLootFrame:GetTop()
end
-- Fontstring sizes
local function AdjustFontstringSize(self)
local text = self:GetText()
self:SetHeight(self:GetStringHeight())
self:SetText(text)
end
-- Colors
local function Darken(mult, ...)
local r, g, b, a = ...
if type(r) == 'table' then
r, g, b, a = unpack(r)
end
return r * mult, g * mult, b * mult, a or 1
end
local function GetColor(self, key, mult)
local skin, raw, default, t = self.skin, rawget(self.opt, key), defaults.profile[key]
assert(default, "No default color specified for key " .. key)
-- Use options if different from defaults
if raw and (raw[1] ~= default[1] or raw[2] ~= default[2] or raw[3] ~= default[3] or raw[4] ~= default[4]) then
t = raw
-- Use skin if options are defaults
elseif skin[key] then
t = skin[key]
-- Use defaults
else
t = default
end
-- Darken
if mult then
return Darken(mult, t)
end
return unpack(t)
end
-- Build individual loot row
local mouse_focus
local BuildRow
do
local RowPrototype = XLoot.NewPrototype()
-- Text helpers
local function smalltext(text)
text:SetDrawLayer'OVERLAY'
text:SetHeight(10)
text:SetJustifyH'LEFT'
4 years ago
text.text = text
end
local function textpoints(text, item, row, x)
text:SetPoint('LEFT', item, 'RIGHT', x, 0)
text:SetPoint('RIGHT', row, 'RIGHT', -4, 0)
end
function RowPrototype:OffsetText(text, y)
text:SetPoint('TOP', self, 0, y)
end
-- Color overrides
function RowPrototype:SetBorderColor(r, g, b, a)
self:_SetBorderColor(r, g, b, a or 1)
self.frame_item:SetBorderColor(r, g, b, a or 1)
end
function RowPrototype:SetHighlightColor(r, g, b)
self:SetHighlightColor(r, g, b)
self.frame_item:SetHighlightColor(r, g, b)
end
-- Frame events
do
local function Row_ShowTooltip_Inner(self)
local f
local type = GetLootSlotType(self.slot)
if type == LOOT_SLOT_ITEM then
f = GameTooltip.SetLootItem
elseif type == LOOT_SLOT_CURRENCY then
f = GameTooltip.SetLootCurrency
else
return nil
end
GameTooltip:SetOwner(self, 'ANCHOR_TOPLEFT', 32, 0)
f(GameTooltip, self.slot)
CursorUpdate(self)
end
function RowPrototype:ShowTooltip()
pcall(Row_ShowTooltip_Inner, self)
end
end
function RowPrototype:HighlightEnter()
if self._highlights then
self.frame_item:ShowHighlight()
end
end
function RowPrototype:HighlightLeave()
if self._highlights then
self.frame_item:HideHighlight()
end
end
function RowPrototype:OnEnter()
self:HighlightEnter(self)
mouse_focus = self
self:ShowTooltip()
end
function RowPrototype:OnLeave()
mouse_focus = nil
self:HighlightLeave(self)
GameTooltip:Hide()
ResetCursor()
end
function RowPrototype:OnClick(button)
if not XLootButtonOnClick(self, button) then
if IsModifiedClick() then
HandleModifiedItemClick(GetLootSlotLink(self.slot))
elseif LootButton_OnClick then
LootButton_OnClick(self, button)
else
StaticPopup_Hide("CONFIRM_LOOT_DISTRIBUTION")
LootSlot(self.slot)
EventRegistry:TriggerEvent("LootFrame.ItemLooted")
end
end
end
function RowPrototype:Auto_OnClick(button)
self:Hide()
if opt.autoloot_item_list ~= '' then
opt.autoloot_item_list = opt.autoloot_item_list .. ',' .. self.parent.item_name
else
opt.autoloot_item_list = self.parent.item_name
end
self.parent.owner:ParseAutolootList()
self.parent:OnClick(button)
end
function RowPrototype:Auto_OnEnter()
GameTooltip:SetOwner(self, 'ANCHOR_TOPLEFT')
GameTooltip:SetText(L.button_auto_tooltip)
GameTooltip:Show()
self.text:SetTextColor(Darken(2, self.parent.owner:GetColor('loot_color_button_auto')))
end
function RowPrototype:Auto_OnLeave()
GameTooltip:Hide()
self.text:SetTextColor(self.parent.owner:GetColor('loot_color_button_auto'))
end
function RowPrototype:Auto_OnShow()
self.parent.text_name:SetPoint('RIGHT', self, 'LEFT')
end
function RowPrototype:Auto_OnHide()
self.parent.text_name:SetPoint('RIGHT', self.parent, 'RIGHT', -6, 0)
end
-- Appearance/skin updates
local resize_texts = {'text_name', 'text_info'}
function RowPrototype:UpdateAppearance()
local owner, opt = self.owner, self.owner.opt
-- Align frames
self:SetPoint('LEFT', opt.loot_padding_left, 0)
self:SetPoint('RIGHT', -opt.loot_padding_right, 0)
-- Colors
self:SetBorderColor(owner:GetColor('loot_color_border'))
self:SetBackdropColor(owner:GetColor('loot_color_backdrop', 0.7))
self:SetGradientColor(owner:GetColor('loot_color_gradient'))
self.frame_item:SetGradientColor(owner:GetColor('loot_color_gradient'))
self.text_info:SetTextColor(owner:GetColor('loot_color_info'))
self.text_button_auto:SetTextColor(owner:GetColor('loot_color_button_auto'))
self:SetAlpha(opt.loot_alpha)
-- Text
self.text_name:SetFont(opt.font, opt.font_size_loot)
self.text_info:SetFont(opt.font, opt.font_size_info)
self.text_quantity:SetFont(opt.font, opt.font_size_quantity, opt.font_flag)
self.text_bind:SetFont(opt.font, 8, opt.font_flag)
self.text_locked:SetFont(opt.font, 9, opt.font_flag)
self.text_locked:SetText(LOCKED) -- Can't set text until font is set
self.text_button_auto:SetFont(opt.font, opt.font_size_button_auto, opt.font_flag)
self.text_button_auto:SetText(L.button_auto)
self.button_auto:SetWidth(self.text_button_auto:GetStringWidth()+4)
self.button_auto:SetHeight(self.text_button_auto:GetStringHeight()+4)
-- Resize fontstrings
for i=1,#resize_texts do
local fontstring = self[resize_texts[i]]
local text = fontstring:GetText()
fontstring:SetText("A")
AdjustFontstringSize(fontstring)
fontstring:SetText(text)
end
-- Dimensions
self.frame_item:SetWidth(opt.loot_icon_size)
self.frame_item:SetHeight(opt.loot_icon_size)
self:SetHeight(opt.loot_row_height)
-- Calculated row height
owner.row_height = self:GetHeight() + owner.skin.row_spacing
-- Highlight textures
if opt.loot_highlight then
if not self._highlights then
owner:Highlight(self, 'row_highlight')
owner:Highlight(self.frame_item, 'item_highlight')
end
self:SetHighlightColor(.8, .8, .8, .8)
self.frame_item:SetHighlightColor(.8, .8, .8, .8)
elseif self._highlights then
self:SetHighlightColor(0, 0, 0, 0)
self.frame_item:SetHighlightColor(0, 0, 0, 0)
end
-- Clear layout cache
self.layout = nil
end
-- Bind texts
local binds = {
[1] = ('|cffff4422%s|r '):format(L.bind_on_pickup_short),
[2] = ('|cff44ff44%s|r '):format(L.bind_on_equip_short),
[3] = ('|cff2244ff%s|r '):format(L.bind_on_use_short),
-- account = 'BoA'
}
-- Update slot with loot
function RowPrototype:Update(slotData)
local r, g, b, hex
local owner = self:GetParent()
local opt = owner.opt
local text_info, text_name, text_bind = '', '', ''
self.item_name = slotData.name
-- Items
local layout = 'simple'
if slotData.slotType == LOOT_SLOT_ITEM then
r, g, b, hex = GetItemQualityColor(slotData.quality or 0)
text_name = ('|c%s%s|r'):format(hex, slotData.name)
if opt.loot_texts_info then -- This is a bit gnarly
local equip = slotData.typeName == ENCHSLOT_WEAPON and ENCHSLOT_WEAPON or slotData.equipLoc ~= '' and _G[slotData.equipLoc] or ''
local itemtype = (slotData.subTypeName == 'Junk' and slotData.quality > 0) and MISCELLANEOUS or slotData.subTypeName
if itemtype then
text_info = ((type(equip) == 'string' and equip ~= '') and equip..', ' or '') .. itemtype
layout = 'detailed'
end
end
if opt.loot_texts_bind and slotData.bindType then
text_bind = binds[slotData.bindType] or ''
end
-- Currency
else
r, g, b = .4, .4, .4
text_name = slotData.name:gsub('\n', ', ')
end
-- Strings
self.text_name:SetText(text_name)
self.text_info:SetText(text_info)
self.text_bind:SetText(text_bind)
self.text_quantity:SetText(slotData.quantity > 1 and slotData.quantity or nil)
if slotData.questID or slotData.isQuestItem then
self.text_info:SetTextColor(1, .8, .1)
else
self.text_info:SetTextColor(owner:GetColor('loot_color_info'))
end
local name_width = self.text_name:GetStringWidth()
-- Icon
self.texture_item:SetTexture(slotData.icon)
if slotData.locked and opt.loot_texts_lock then
self.text_locked:Show()
else
self.text_locked:Hide()
end
-- Layout
if self.layout ~= layout then
self.layout = layout
if layout == 'simple' then
self.text_name:SetPoint('LEFT', self.frame_item, 'RIGHT', 2, 0)
else
self.text_name:SetPoint('LEFT', self.frame_item, 'RIGHT', 2, (self.text_info:GetHeight()/2))
end
end
-- Quality coloring
if opt.quality_color_slot then
self:SetBorderColor(Darken(owner.skin.color_mod, r, g, b))
end
-- Quest icon
if slotData.questID then
self.texture_bang:Show()
else
self.texture_bang:Hide()
end
-- Autoloot button
if opt.loot_buttons_auto and (self.owner.fake or (opt.autoloots.list ~= 'never' and slotData.slotType == LOOT_SLOT_ITEM and not self.owner.auto_items[slotData.name])) then
self.button_auto:Show()
name_width = name_width + self.button_auto:GetWidth() - 6
else
self.button_auto:Hide()
end
-- Attach
if self.i == 1 then
self:SetPoint('TOP', 0, -opt.loot_padding_top)
else
self:SetPoint('TOP', owner.rows[self.i-1], 'BOTTOM', 0, owner.skin.row_offset)
end
self:Show()
return max(self.text_info:GetStringWidth() + 2, name_width)
end
-- Factory
function BuildRow(frame, i)
local frame_name, opt, fake = frame:GetName()..'Button'..i, frame.opt, frame.fake
-- Create frames
local row = CreateFrame('Button', not fake and frame_name or nil, frame, BackdropTemplateMixin and "BackdropTemplate")
local item = CreateFrame('Frame', nil, row)
local button_auto = CreateFrame('Button', nil, row)
local tex = item:CreateTexture(not fake and frame_name..'IconTexture' or nil, 'BACKGROUND')
local bang = item:CreateTexture(nil, 'OVERLAY')
row.owner = frame
row.frame_item = item
row.texture_item = tex
row.texture_bang = bang
row.button_auto = button_auto
row.i = i
-- Skin row
frame:Skin(row)
frame:Skin(item, 'item')
-- Apply prototype after skin to override method
RowPrototype:New(row)
-- Create fontstrings
local name = row:CreateFontString(not fake and frame_name..'Text' or nil)
local info = row:CreateFontString()
local bind = item:CreateFontString()
local quantity = item:CreateFontString()
local locked = item:CreateFontString()
local auto = button_auto:CreateFontString()
row.text_name = name
row.text_info = info
row.text_bind = bind
row.text_locked = locked
row.text_quantity = quantity
row.text_button_auto = auto
-- Setup fontstrings
smalltext(name)
smalltext(info)
smalltext(bind)
smalltext(locked)
smalltext(quantity)
smalltext(auto)
name:SetPoint('RIGHT', row, 'RIGHT', -6, 0)
info:SetPoint('TOPLEFT', name, 'BOTTOMLEFT', 8, 0)
info:SetPoint('RIGHT', row, 'RIGHT', -4, 0)
textpoints(name, item, row, 2)
textpoints(info, item, row, 8)
info:SetPoint('TOP', name, 'BOTTOM')
bind:SetPoint('BOTTOMLEFT', 2, 2)
quantity:SetPoint('BOTTOMRIGHT', -2, 2)
quantity:SetJustifyH('RIGHT')
locked:SetPoint('CENTER')
locked:SetTextColor(1, .2, .1)
auto:SetPoint('CENTER')
item:SetPoint('LEFT', 0, 0)
tex:SetPoint('TOPLEFT', 3, -3)
tex:SetPoint('BOTTOMRIGHT', -3, 3)
tex:SetTexCoord(.07,.93,.07,.93)
bang:SetPoint('BOTTOMRIGHT')
bang:SetWidth(16)
bang:SetHeight(16)
bang:SetTexture([[Interface\Minimap\ObjectIcons.blp]])
bang:SetTexCoord(1/8, 2/8, 1/8, 2/8)
button_auto:SetPoint('TOPRIGHT', -2, -4)
button_auto:SetScript('OnEnter', row.Auto_OnEnter)
button_auto:SetScript('OnLeave', row.Auto_OnLeave)
button_auto:SetScript('OnShow', row.Auto_OnShow)
button_auto:SetScript('OnHide', row.Auto_OnHide)
button_auto.parent = row
button_auto.text = auto
-- Supplimental events for a configuration instance
if fake then
row:RegisterForClicks()
row:SetScript('OnEnter', row.HighlightEnter)
row:SetScript('OnLeave', row.HighlightLeave)
-- Events for actual loot frame
else
row:RegisterForDrag('LeftButton')
row:RegisterForClicks('LeftButtonUp', 'RightButtonUp')
row:SetScript('OnDragStart', OnDragStart)
row:SetScript('OnDragStop', OnDragStop)
row:SetScript('OnClick', row.OnClick)
row:SetScript('OnEnter', row.OnEnter)
row:SetScript('OnLeave', row.OnLeave)
button_auto:RegisterForClicks('LeftButtonUp')
button_auto:SetScript('OnClick', row.Auto_OnClick)
end
-- Apply appearance
row:UpdateAppearance()
return row
end
end
-- Build frame
do
local FramePrototype = XLoot.NewPrototype()
-- Frame snapping
function FramePrototype:SnapToCursor()
local x, y = GetCursorPosition()
local f = self
local s = f:GetEffectiveScale()
if opt.frame_snap then
-- Horizontal position
if not f:IsShown() then
x = (x / s) - 25
local sWidth, fWidth, uWidth = GetScreenWidth(), f:GetWidth(), UIParent:GetWidth()
if uWidth > sWidth then sWidth = uWidth end
if x + fWidth > sWidth then x = sWidth - fWidth end
if x < 0 then x = 0 end
x = x + opt.frame_snap_offset_x
else
x = f:GetLeft() or x
end
-- Vertical position
y = (y / s) + 25 * (opt.frame_grow_upwards and -1 or 1)
local sHeight, fHeight, uHeight = GetScreenHeight(), f:GetHeight(), UIParent:GetHeight()
if uHeight > sHeight then sHeight = uHeight end
if y > sHeight then y = sHeight end
if y - fHeight < 0 then y = fHeight end
y = y + opt.frame_snap_offset_y
else
x = opt.frame_position_x or x
y = opt.frame_position_y or y
end
-- Apply
f:ClearAllPoints()
f:SetPoint((opt.frame_grow_upwards and "BOTTOMLEFT" or "TOPLEFT"), UIParent, "BOTTOMLEFT", x, y)
end
-- function FramePrototype:SetAlpha(alpha)
-- self.backdrop:SetAlpha(alpha)
-- end
-- Link loot menu
function FramePrototype:LinkClick(button)
if button == 'RightButton' then
ToggleDropDownMenu(1, nil, LinkDropdown, self)--, GetCursorPosition())
else
LinkLoot(self:GetParent().opt.linkall_channel)
end
end
function FramePrototype:OnHide()
pcall(LootFrame_OnHide)
for i,v in ipairs(self.rows) do
v:Hide()
end
-- CloseLoot()
end
-- Bottom buttons
local function BottomButton(frame, name, text, justify)
local b = CreateFrame('Button', name, frame)
b.text = b:CreateFontString(name..'Text', 'OVERLAY')
b.text:SetFont(frame.opt.font, frame.opt.font_size_bottombuttons)
b.text:SetText('|c22AAAAAA'..text)
b.text:SetJustifyH(justify)
b.text:SetAllPoints(b)
b:SetFrameLevel(8)
b:SetHeight(16)
b:SetHighlightTexture([[Interface\Buttons\UI-Panel-MinimizeButton-Highlight]])
b:ClearAllPoints()
b:SetPoint('BOTTOM', 0, 3)
b:SetHitRectInsets(-4, -4, 3, -2)
b:Show()
return b
end
function FramePrototype:UpdateHeight()
if self.row_height then
self:SetHeight(
((self.link:IsShown() or not opt.old_close_button) and 6 or 0)
+ opt.loot_padding_top
+ opt.loot_padding_bottom
+ #self.slots * self.row_height)
end
end
function FramePrototype:UpdateWidth(width_max)
local width = self.opt.frame_width_automatic and (width_max + 70) or (self.opt.frame_width + 50)
self:SetWidth(width)
width = width * 0.5 - 10
self.link:SetWidth(width)
self.close:SetWidth(width)
end
function FramePrototype:UpdateLinkButton()
local target, show, now = self.opt.linkall_channel, self.opt.linkall_show, false
if show == 'auto' then
if target == 'SAY' or
((target == 'GUILD' or target == 'OFFICER') and IsInGuild()) or
((target == 'RAID' or target == 'RAID_WARNING') and IsInRaid()) or
(target == 'PARTY' and IsInGroup())
then
now = true
end
else
now = IsGroupState[show]()
end
if now then
self.link:Show()
else
self.link:Hide()
end
end
function FramePrototype:SizeAndColor(max_width, max_quality)
-- Update frame
self:UpdateLinkButton()
self:UpdateHeight()
self:UpdateWidth(max_width)
-- Color frame
if self.opt.quality_color_frame then
local r, g, b = GetItemQualityColor(max_quality)
self.overlay:SetBorderColor(r, g, b, 1)
end
end
-- Update skin/appearance
function FramePrototype:UpdateAppearance()
self.skin = self:Reskin()
self.skin.row_offset = self.skin.row_spacing * -1
-- Update colors/other
self:SetScale(self.opt.frame_scale)
self.overlay:SetAlpha(self.opt.frame_alpha)
self.overlay:SetBorderColor(self:GetColor('frame_color_border'))
self.overlay:SetGradientColor(self:GetColor('frame_color_gradient'))
self.overlay:SetBackdropColor(self:GetColor('frame_color_backdrop', 0.7))
-- Update loot frames
for i, row in ipairs(self.rows) do
row:UpdateAppearance()
end
-- Resize frame
if #self.slots > 0 and self.opt.frame_width_automatic then
local max_width, max = 0, math.max
for i, slot in ipairs(self.slots) do
max_width = max(max_width, slot.text_name:GetStringWidth(), slot.text_info:GetStringWidth())
end
self:UpdateWidth(max_width)
end
-- Show close buttons
if opt.old_close_button then
self.close:Hide()
self.old_close:Show()
else
self.close:Show()
self.old_close:Hide()
end
-- Text
self.close.text:SetFont(opt.font, opt.font_size_bottombuttons)
self.link.text:SetFont(opt.font, opt.font_size_bottombuttons)
end
-- Factory
function addon:BuildLootFrame(f)
local name = f:GetName()
-- Setup frame
FramePrototype:New(f)
f:SetFrameStrata('DIALOG')
f:SetFrameLevel(5)
f:EnableMouse(1)
-- Set up frame skins
XLoot:MakeSkinner(f, {
item = {
backdrop = false
},
row_highlight = {
type = 'highlight'
},
item_highlight = {
type = 'highlight',
layer = 'OVERLAY'
}
})
-- Use a secondary frame for backdrop/border to allow the "frame" opacity to be changed
local overlay = CreateFrame('Frame', nil, f, BackdropTemplateMixin and "BackdropTemplate")
overlay:SetFrameLevel(5)
overlay:SetAllPoints()
f:Skin(overlay)
f.overlay = overlay
-- Link all button
local link = BottomButton(f, name..'Link', L.button_link, 'MIDDLE')
link:RegisterForClicks('LeftButtonUp', 'RightButtonUp')
link:SetPoint('LEFT', 6, 0)
f.link = link
-- Close button
local close = BottomButton(f, name..'Close', L.button_close, 'MIDDLE')
close:SetPoint('RIGHT', -6, 0)
f.close = close
-- Legacy close button
local x = CreateFrame("Button", nil, f)
x:SetWidth(30)
x:SetHeight(30)
local xtex = [[Interface\Buttons\UI-Panel-MinimizeButton-]]
x:SetNormalTexture(xtex..'Up')
x:SetPushedTexture(xtex..'Down')
x:SetHighlightTexture(xtex..'Highlight')
x:SetPoint('TOPRIGHT', 3, 3)
x:SetHitRectInsets(3, 3, 3, 3)
x:SetFrameLevel(f:GetFrameLevel()+2)
-- f:Skin(x)
-- x:SetBorderColor(.7, .7, .7)
f.old_close = x
-- Events
if not f.fake then
f:SetMovable(1)
f:RegisterForDrag('LeftButton')
f:SetScript('OnDragStart', OnDragStart)
f:SetScript('OnDragStop', OnDragStop)
f:SetScript('OnHide', f.OnHide)
link:SetScript('OnClick', f.LinkClick)
-- WoW now shows an error if any parameter is passed, and OnClick passes one
local function CloseLoot_Nil()
CloseLoot()
end
close:SetScript('OnClick', CloseLoot_Nil)
x:SetScript('OnClick', CloseLoot_Nil)
end
f.GetColor = GetColor
f.rows = setmetatable({}, { __index = function(t, k)
local row = BuildRow(f, k)
t[k] = row
return row
end })
f.slots_index = {}
f.slots = {}
f:UpdateAppearance()
f.built = true
end
end
-- Main loot handler
local auto, auto_items = {}, {}
function XLootFrame:ParseAutolootList()
wipe(auto_items)
for item in opt.autoloot_item_list:gmatch("%s*([^,]+)%s*") do
auto_items[item] = true
end
end
local auto_states = {
always = true,
group = true, -- Secretly "Always" shh
never = false,
solo = nil,
party = nil,
raid = nil
}
function addon:GROUP_ROSTER_UPDATE()
auto_states.solo = not IsInGroup()
auto_states.raid = IsInRaid()
auto_states.party = not auto_states.raid
end
local function clear(slot)
if not slot then return nil end
slot.slot = nil
slot.item = nil
slot.quality = nil
slot:Hide()
end
local function BoPRefresh()
for i, row in pairs(XLootFrame.rows) do
clear(row)
end
XLootFrame:Update(false, true)
end
local _bag_slots, GetItemBindType = {}, XLoot.GetItemBindType
function XLootFrame:Update(no_snap, is_refresh)
local numloot = GetNumLootItems()
if numloot == 0 then return nil end
local max = math.max
-- Construct frame
if not self.built then
addon:BuildLootFrame(self)
self:ParseAutolootList()
self.auto_items = auto_items
addon:GROUP_ROSTER_UPDATE()
end
-- References
local rows, slots, slots_index = self.rows, wipe(self.slots), wipe(self.slots_index)
local bag_slots -- Only assigned if we start autolooting
-- Autolooting options
local auto, auto_items = auto, auto_items
for k,v in pairs(opt.autoloots) do
auto[k] = auto_states[v]
end
-- Update rows
local max_quality, max_width, our_slot, slot, need_refresh = 0, 0, 0
for slot = 1, numloot do
local _, icon, name, quantity, currencyID, quality, locked, isQuestItem, questID, startsQuest = pcall(GetLootSlotInfo, slot)
-- Already looted or erroring slot
if not name then
if not is_refresh and opt.show_slot_errors then
xprint(L.slot_name_error:format(tostring(slot)))
end
elseif not icon then
if not is_refresh and opt.show_slot_errors then
xprint(L.slot_icon_error:format(tostring(slot)))
end
else
local autoloot = false
local slotType, slotData = GetLootSlotType(slot)
if slotType == LOOT_SLOT_ITEM then
slotData = GetItemInfoTable(GetLootSlotLink(slot))
slotData.slotType = slotType
slotData.quantity = quantity
slotData.locked = locked
slotData.questItem = isQuestItem
slotData.questID = questID
slotData.startsQuest = startsQuest
else
slotData = {
name = name,
icon = icon,
slotType = slotType,
quantity = quantity,
quality = quality,
locked = locked,
-- questItem = isQuestItem,
-- questID = questID,
-- startsQuest = startsQuest,
bindType = 0
}
end
-- There's no reason to try to autoloot when refreshing the frame
if not is_refresh then
-- Autolooting currency
if (auto.all or auto.currency) and (slotType == LOOT_SLOT_MONEY or slotType == LOOT_SLOT_CURRENCY) then
autoloot = true
-- Quest items
elseif (auto.all or auto.quest) and (isQuestItem or startsQuest) then
autoloot = true
-- Autolooting items
elseif
auto.all
or (auto.list and auto_items[name])
or (auto.tradegoods and slotData.isCraftingReagent)
then
-- Cache available space
-- Specific bag types make this a bit more annoying
if not bag_slots then
bag_slots = wipe(_bag_slots)
for i = 0, NUM_BAG_SLOTS do
local open, family = GetContainerNumFreeSlots(i)
if family then
bag_slots[family] = bag_slots[family] and bag_slots[family] + open or open
end
end
end
local family = GetItemFamily(slotData.link)
-- Empty slots
family = (family and family <= 4096) and family or 0
if bag_slots[0] > 0 or (bag_slots[family] and bag_slots[family] > 0) then
autoloot = true
-- Update remaining space estimate
family = bag_slots[family] and family or 0
bag_slots[family] = bag_slots[family] - 1
-- Space in existing stacks
else
local partial = GetItemCount(slotData.link) % slotData.stackCount
if partial > 0 and (partial + quantity < slotData.stackCount) then
autoloot = true
end
end
end
if autoloot then
need_refresh = true
LootSlot(slot)
end
end
-- Show slot
if
not autoloot
or is_refresh
then
our_slot = our_slot + 1
local row = rows[our_slot]
slots[our_slot] = row
-- Default UI and tooltip data
row.item = slotData.link
row.quality = slotData.quality
row.slot = slot
row.frame_slot = our_slot
row:SetID(slot)
-- Update row
local width = row:Update(slotData)
max_width = max(width, max_width)
max_quality = max(slotData.quality or 0, max_quality)
end
end
end
if not is_refresh and need_refresh then
C_Timer.After(0.8, BoPRefresh)
end
-- Exit if we autolooted everything
if our_slot == 0 then
-- CloseLoot()
return nil
end
self:SizeAndColor(max_width, max_quality)
-- Show
if not no_snap and not is_refresh then
self:SnapToCursor()
end
self:Show()
end
function addon:LOOT_CLOSED()
if type(XLootFrame.rows) == 'table' then
for i, row in pairs(XLootFrame.rows) do
clear(row)
end
wipe(XLootFrame.slots)
end
XLootFrame:Hide()
StaticPopup_Hide('LOOT_BIND')
if UIDropDownMenu_GetCurrentDropDown() == LinkDropdown then
CloseDropDownMenus()
end
end
function addon:LOOT_OPENED()
if GetNumLootItems() > 0 then
if not XLootFrame:IsShown() and IsFishingLoot() then
PlaySound(SOUNDKIT.FISHING_REEL_IN)
end
XLootFrame:Update()
else
PlaySound(SOUNDKIT.LOOT_WINDOW_OPEN_EMPTY)
CloseLoot()
end
end
function addon:LOOT_SLOT_CLEARED(slot)
local slots = XLootFrame.slots
-- Apparently auto-looting addons like EasyLoot will cause strange issues
if slots == nil then
return
end
for id, row in ipairs(slots) do
if row.slot == slot then
clear(row)
if XLootFrame.opt.loot_collapse then
local prev, next = slots[id-1], slots[id+1]
if prev and next then
next:SetPoint('TOP', prev, 'BOTTOM', nil, XLootFrame.skin.row_offset)
elseif next then
next:SetPoint('TOP', 0, -opt.loot_padding_top)
end
table.remove(slots, id)
XLootFrame:UpdateHeight()
end
end
end
end
-- Show compare tooltip when shift pressed
-- Without using OnUpdate for all frames
function addon:MODIFIER_STATE_CHANGED(self, modifier, state)
if (GetNumLootItems() ~= 0) and mouse_focus and MouseIsOver(mouse_focus) then
mouse_focus:ShowTooltip()
end
end
local function option_handler(msg)
if not addon:SlashHandler(msg) then
addon:ShowOptions()
end
--local what, arg, data = string.split(' ', msg, 3)
--local what, arg, data = msg:match'^(%w+)%s?([A-Za-z\_]*)%s?(.*)$'
end
-- SLASH_XLOOT1 = '/xloot'
-- SlashCmdList['XLOOT'] = option_handler
--[[
Notes:
LootSlotHasItem() -- MoP generic 'check if slot has any loot', meaning item/coin/currency etc
LootSlotIsCoin(slot) etc are replaced by GetLootSlotType(slot) == LOOT_SLOT_* checks
]]