-- ====================================== --
-- Shared Functions for Broker_Everything --
-- ====================================== --
local addon , ns = ... ;
local L , _ = ns.L ;
ns.debugMode = " 4.5.14-release " == " @ " .. " project-version " .. " @ " ;
LibStub ( " HizurosSharedTools " ) . RegisterPrint ( ns , addon , " BE " ) ;
-- Lua API 5.1 functions
local setmetatable , tonumber , rawget , rawset , tinsert = setmetatable , tonumber , rawget , rawset , tinsert ;
local tremove , tostring , type , unpack , assert = tremove , tostring , type , unpack , assert ;
local securecall , ipairs , pairs , tconcat , tsort = securecall , ipairs , pairs , table.concat , table.sort ;
local time , wipe , mod , hooksecurefunc , strsplit = time , wipe , mod , hooksecurefunc , strsplit ;
-- WoW Lua API functions
local UnitName , UnitSex , UnitClass , UnitFactionGroup = UnitName , UnitSex , UnitClass , UnitFactionGroup ;
local UnitRace , GetRealmName , GetLocale = UnitRace , GetRealmName , GetLocale ;
local InCombatLockdown , CreateFrame = InCombatLockdown , CreateFrame ;
local GetScreenHeight , GetMouseFocus , GetAddOnInfo = GetScreenHeight , GetMouseFocus , GetAddOnInfo ;
local GetAddOnEnableState , IsAltKeyDown = GetAddOnEnableState , IsAltKeyDown ;
local IsShiftKeyDown , IsControlKeyDown , GetItemInfo = IsShiftKeyDown , IsControlKeyDown , GetItemInfo ;
local GetInventoryItemLink , GetInventoryItemID = GetInventoryItemLink , GetInventoryItemID ;
-- WoW API functions defined in lua files
local CopyTable , SecondsToTime = CopyTable , SecondsToTime ;
-- no longer in retail but in classic client
local GetContainerNumSlots , GetContainerItemCooldown , GetContainerItemLink , GetContainerItemID , GetContainerItemInfo , GetContainerNumFreeSlots = GetContainerNumSlots , GetContainerItemCooldown , GetContainerItemLink , GetContainerItemID , GetContainerItemInfo , GetContainerNumFreeSlots ;
-- could be deprecated in future.
local GetCVar , SetCVar = C_CVar and C_CVar.GetCVar or GetCVar , C_CVar and C_CVar.SetCVar or SetCVar
-------------
--- Libraries ---
-------------
ns.LDB = LibStub ( " LibDataBroker-1.1 " ) ;
ns.LQT = LibStub ( " LibQTip-1.0 " ) ;
ns.LDBI = LibStub ( " LibDBIcon-1.0 " ) ;
ns.LSM = LibStub ( " LibSharedMedia-3.0 " ) ;
ns.LT = LibStub ( " LibTime-1.0 " ) ;
ns.LC = LibStub ( " LibColors-1.0 " ) ;
ns.LRI = LibStub ( " LibRealmInfo " ) ;
-- broker_everything colors
ns.LC . colorset ( {
[ " ltyellow " ] = " fff569 " ,
[ " dkyellow " ] = " ffcc00 " ,
[ " dkyellow2 " ] = " bbbb00 " ,
[ " ltorange " ] = " ff9d6a " ,
[ " dkorange " ] = " 905d0a " ,
[ " dkorange2 " ] = " c06d0a " ,
--["dkred"] = "c41f3b",
[ " ltred " ] = " ff8080 " ,
[ " dkred " ] = " 800000 " ,
[ " violet " ] = " f000f0 " ,
[ " ltviolet " ] = " f060f0 " ,
[ " dkviolet " ] = " 800080 " ,
[ " ltblue " ] = " 69ccf0 " ,
[ " dkblue " ] = " 000088 " ,
[ " dailyblue " ] = " 00b3ff " ,
[ " ltcyan " ] = " 80ffff " ,
[ " dkcyan " ] = " 008080 " ,
[ " ltgreen " ] = " 80ff80 " ,
[ " dkgreen " ] = " 00aa00 " ,
[ " dkgray " ] = " 404040 " ,
[ " gray2 " ] = " A0A0A0 " ,
[ " ltgray " ] = " b0b0b0 " ,
[ " gold " ] = " ffd700 " ,
[ " silver " ] = " eeeeef " ,
[ " copper " ] = " f0a55f " ,
[ " unknown " ] = " ee0000 " ,
} ) ;
---------------------------------------
--- misc shared data ---
---------------------------------------
ns.icon_fallback = 134400 ; -- interface\\icons\\INV_MISC_QUESTIONMARK;
ns.icon_arrow_right = " interface \\ CHATFRAME \\ ChatFrameExpandArrow " ;
ns.media = " Interface \\ AddOns \\ " .. addon .. " \\ media \\ " ;
ns.locale = GetLocale ( ) ;
ns.ui = { size = { UIParent : GetSize ( ) } , center = { UIParent : GetCenter ( ) } } ;
ns.realm = GetRealmName ( ) ;
ns.region = ns.LRI : GetCurrentRegion ( ) or ( { " US " , " KR " , " EU " , " TW " , " CN " } ) [ GetCurrentRegion ( ) ] ;
do
local pattern = " ^ " .. ( ns.realm : gsub ( " (.) " , " [%1]* " ) ) .. " $ " ;
for i , v in ipairs ( GetAutoCompleteRealms ( ) ) do
if v : match ( pattern ) then
ns.realm_short = v ;
break ;
end
end
if not ns.realm_short then
ns.realm_short = ns.realm : gsub ( " " , " " ) : gsub ( " %- " , " " ) ;
end
end
-----------------------
-- Client version checks --
-----------------------
do
local version , build = GetBuildInfo ( ) ;
local v1 , v2 , v3 = strsplit ( " . " , version or " 0.0.0 " ) ;
ns.client_version = tonumber ( v1 .. " . " .. v2 .. v3 .. build ) or 0 ;
end
---@return boolean
function ns . IsRetailClient ( )
return WOW_PROJECT_ID == WOW_PROJECT_MAINLINE ;
end
---@return boolean
function ns . IsClassicClient ( ) -- for AceOptions
return not WOW_PROJECT_ID == WOW_PROJECT_MAINLINE ;
end
---@return boolean
function ns . IsClassicEraClient ( )
return WOW_PROJECT_ID == WOW_PROJECT_CLASSIC ;
end
---@return boolean
function ns . IsClassicWotlkClient ( )
return WOW_PROJECT_ID == WOW_PROJECT_WRATH_CLASSIC ;
end
---@return boolean
function ns . IsNotClassicClient ( ) -- for AceOptions
return WOW_PROJECT_ID == WOW_PROJECT_MAINLINE ;
end
---------------------------------------
--- player and twinks dependent data ---
---------------------------------------
---@param name string
---@return string name
function ns . stripRealm ( name )
name = name : gsub ( " " , " " ) ;
name = name : gsub ( " %- " , " " ) ;
return name ;
end
ns.player = {
name = UnitName ( " player " ) ,
female = UnitSex ( " player " ) == 3 ,
} ;
ns.player . name_realm = ns.player . name .. " - " .. ns.realm ;
ns.player . name_realm_short = ns.player . name .. " - " .. ns.realm_short ;
_ , ns.player . class , ns.player . classId = UnitClass ( " player " ) ;
ns.player . faction , ns.player . factionL = UnitFactionGroup ( " player " ) ;
L [ ns.player . faction ] = ns.player . factionL ;
ns.player . classLocale = ns.player . female and _G [ " LOCALIZED_CLASS_NAMES_FEMALE " ] [ ns.player . class ] or _G [ " LOCALIZED_CLASS_NAMES_MALE " ] [ ns.player . class ] ;
ns.player . raceLocale , ns.player . race , ns.player . raceIndex = UnitRace ( " player " ) ;
ns.LC . colorset ( " suffix " , ns.LC . colorset [ ns.player . class : lower ( ) ] ) ;
ns.realms = { } ;
do
local initFinished = false ;
local function Init ( )
if initFinished then return end
initFinished = true ;
local _ , _ , _ , _ , _ , _ , _ , _ , ids = ns.LRI : GetRealmInfo ( ns.realm , ns.region ) ;
if type ( ids ) == " table " then
for i = 1 , # ids do
local _ , name , apiName = ns.LRI : GetRealmInfoByID ( ids [ i ] ) ;
if type ( name ) == " string " and type ( apiName ) == " string " then
ns.realms [ name ] = apiName ;
if apiName ~= name then
ns.realms [ apiName ] = name ;
end
end
end
else
ns.realms [ ns.realm ] = ns.realm_short ;
if ns.realm ~= ns.realm_short then
ns.realms [ ns.realm_short ] = ns.realm ;
end
end
end
setmetatable ( ns.realms , {
__index = function ( t , k )
Init ( ) ;
return rawget ( t , k ) or false ;
end
} ) ;
end
---@param name string
---@return string name
function ns . realmCheckOrAppend ( name )
if not name : find ( " - " ) then
name = name .. " - " .. ns.realm_short ;
end
return name ;
end
---@param modName string module name
---@param realm string realm name
---@param faction string faction name
---@return boolean
function ns . showThisChar ( modName , realm , faction )
if not ns.profile [ modName ] . showAllFactions and ns.player . faction ~= faction then
return false ;
end
if ns.profile [ modName ] . showCharsFrom == " 1 " and realm ~= ns.realm then -- same realm
return false ;
elseif ns.profile [ modName ] . showCharsFrom == " 2 " and not ns.realms [ realm ] then -- connected realms
return false ;
end
return true ;
end
---@param modName string module name
---@param name string player name
---@param color string color name or color code
---@param prepDash boolean prepend dash
function ns . showRealmName ( modName , name , color , prepDash )
if not ( ns.realm_short == name or ns.realm == name ) then
if ns.profile [ modName ] . showRealmNames then
if type ( name ) == " string " and name : len ( ) > 0 then
local _ , _name = ns.LRI : GetRealmInfo ( name , ns.region ) ;
if _name then
return ( prepDash ~= false and ns.LC . color ( " white " , " - " ) ) .. ns.LC . color ( color or " dkyellow " , ns.scm ( name ) ) ;
end
end
else
return ns.LC . color ( color or " dkyellow " , " * " ) ;
end
end
return " " ;
end
-----------------------------------------
--- SetCVar hook ---
--- Thanks at blizzard for blacklisting ---
--- some cvars on combat... ---
-----------------------------------------
do
local blacklist = { alwaysShowActionBars = true , bloatnameplates = true , bloatTest = true , bloatthreat = true , consolidateBuffs = true , fullSizeFocusFrame = true , maxAlgoplates = true , nameplateMotion = true , nameplateOverlapH = true , nameplateOverlapV = true , nameplateShowEnemies = true , nameplateShowEnemyGuardians = true , nameplateShowEnemyPets = true , nameplateShowEnemyTotems = true , nameplateShowFriendlyGuardians = true , nameplateShowFriendlyPets = true , nameplateShowFriendlyTotems = true , nameplateShowFriends = true , repositionfrequency = true , showArenaEnemyFrames = true , showArenaEnemyPets = true , showPartyPets = true , showTargetOfTarget = true , targetOfTargetMode = true , uiScale = true , useCompactPartyFrames = true , useUiScale = true }
function ns . SetCVar ( ... )
local cvar = ...
if ns.client_version > 5.48 and InCombatLockdown ( ) and blacklist [ cvar ] == true then
local msg
-- usefull blacklisted cvars...
if cvar == " uiScale " or cvar == " useUiScale " then
msg = L [ " CVarScalingInCombat " ] ;
else
-- useless blacklisted cvars...
msg = L [ " CVarInCombat " ] : format ( cvar ) ;
end
ns : print ( ns.LC . color ( " ltorange " , msg ) ) ;
else
SetCVar ( ... )
end
end
end
---------------------------------------
--- Helpful function for extra tooltips ---
---------------------------------------
local brokerDragHooks , openTooltip , hiddenMouseOver = { } ;
---@param frame frame
---@param direction string
---@param parentTT frame
---@return string point
---@return frame|string
---@return string relativePoint
---@return number x
---@return number y
function ns . GetTipAnchor ( frame , direction , parentTT )
local f , u , i , H , h , v , V = { frame : GetCenter ( ) } , { } , 0 ;
if f [ 1 ] == nil or ns.ui . center [ 1 ] == nil then
return " LEFT " , frame , " LEFT " , 0 , 0 ;
end
h = ( f [ 1 ] > ns.ui . center [ 1 ] and " RIGHT " ) or " LEFT " ;
v = ( f [ 2 ] > ns.ui . center [ 2 ] and " TOP " ) or " BOTTOM " ;
u [ 4 ] = ns.ui . center [ 1 ] / 4 ; u [ 5 ] = ns.ui . center [ 2 ] / 4 ; u [ 6 ] = ( ns.ui . center [ 1 ] * 2 ) - u [ 4 ] ; u [ 7 ] = ( ns.ui . center [ 2 ] * 2 ) - u [ 5 ] ;
H = ( f [ 1 ] > u [ 6 ] and " RIGHT " ) or ( f [ 1 ] < u [ 4 ] and " LEFT " ) or " " ;
V = ( f [ 2 ] > u [ 7 ] and " TOP " ) or ( f [ 2 ] < u [ 5 ] and " BOTTOM " ) or " " ;
if parentTT then
local p , ph , pv , pH , pV = { parentTT : GetCenter ( ) } ;
ph , pv = ( p [ 1 ] > ns.ui . center [ 1 ] and " RIGHT " ) or " LEFT " , ( p [ 2 ] > ns.ui . center [ 2 ] and " TOP " ) or " BOTTOM " ;
pH = ( p [ 1 ] > u [ 6 ] and " RIGHT " ) or ( p [ 1 ] < u [ 4 ] and " LEFT " ) or " " ;
pV = ( p [ 2 ] > u [ 7 ] and " TOP " ) or ( p [ 2 ] < u [ 5 ] and " BOTTOM " ) or " " ;
if direction == " horizontal " then
return pV .. ph , parentTT , pV .. ( ph == " LEFT " and " RIGHT " or " LEFT " ) , ph == " LEFT " and i or - i , 0 ;
end
return pv .. pH , parentTT , ( pv == " TOP " and " BOTTOM " or " TOP " ) .. pH , 0 , pv == " TOP " and i or - i ;
else
if direction == " horizontal " then
return V .. h , frame , V .. ( h == " LEFT " and " RIGHT " or " LEFT " ) , h == " LEFT " and i or - i , 0 ;
end
return v .. H , frame , ( v == " TOP " and " BOTTOM " or " TOP " ) .. H , 0 , v == " TOP " and i or - i ;
end
end
----------------------------------
-- ttMode [ 1: close on leave broker button (bool/nil) | 2: dont use hiddenMouseOver (bool/nil) ],
-- ttParent [ 1: parent frame element (frame) | 2: anchor direction (string) | 3: alternative anchor target (frame/optional) ]
local function MouseIsOver ( region , topOffset , bottomOffset , leftOffset , rightOffset )
if region and region.IsMouseOver then -- stupid blizzard does not check if exists...
return region : IsMouseOver ( topOffset , bottomOffset , leftOffset , rightOffset ) ;
end
end
local function hideOnLeave ( self )
local _ , hiddenMouseOverAnchor = hiddenMouseOver : GetPoint ( ) ;
if self.parent and self.parent [ 1 ] and ( MouseIsOver ( self.parent [ 1 ] ) or ( self.parent [ 1 ] == hiddenMouseOverAnchor and MouseIsOver ( hiddenMouseOver ) ) ) then return end -- mouse is over broker and/or extended broker button area
if MouseIsOver ( self ) and ( ( self.slider and self.slider : IsShown ( ) ) or ( self.mode and self.mode [ 1 ] ~= true ) ) then return end -- tooltip with active scrollframe or mouse over tooltip with clickable elements
if self.OnHide then
self.OnHide ( self ) ;
self.OnHide = nil ;
end
ns.hideTooltip ( self ) ;
end
local function hideOnUpdate ( self , elapse )
if not self : IsShown ( ) then
self : SetScript ( " OnUpdate " , nil ) ;
return ;
end
if ( self.elapsed or 1 ) > 0 then
self.elapsed = 0 ;
hideOnLeave ( self ) ;
else
self.elapsed = ( self.elapsed or 0 ) + elapse ;
end
end
local function hookDragStart ( self )
if brokerDragHooks [ self ] and brokerDragHooks [ self ] [ 1 ] == brokerDragHooks [ self ] [ 2 ] . key and brokerDragHooks [ self ] [ 2 ] : IsShown ( ) then
ns.hideTooltip ( brokerDragHooks [ self ] [ 2 ] ) ;
end
end
---@param ttData table
---@param ttMode table
---@param ttParent table
---@param ttScripts table
function ns . acquireTooltip ( ttData , ttMode , ttParent , ttScripts )
if openTooltip and openTooltip.key ~= ttData [ 1 ] and openTooltip.parent and not ( ttParent [ 1 ] == openTooltip or ( ttParent [ 3 ] and ttParent [ 3 ] == openTooltip ) ) then
ns.hideTooltip ( openTooltip ) ;
end
if ns.LQT : IsAcquired ( ttData [ 1 ] ) then
openTooltip = ns.LQT : Acquire ( ttData [ 1 ] )
return openTooltip ;
end
local modifier = ns.profile . GeneralOptions.ttModifierKey2 ;
local tooltip = ns.LQT : Acquire ( unpack ( ttData ) ) ; openTooltip = tooltip ;
tooltip.parent , tooltip.mode , tooltip.scripts = ttParent , ttMode , ttScripts ;
tooltip.mode [ 1 ] = tooltip.mode [ 1 ] == true or ( modifier ~= " NONE " and ns.tooltipChkOnShowModifier ( modifier ) )
if hiddenMouseOver == nil then
hiddenMouseOver = CreateFrame ( " Frame " , addon .. " TooltipHideShowFix2 " , UIParent ) ;
hiddenMouseOver : SetFrameStrata ( " BACKGROUND " ) ;
end
if not tooltip.mode [ 2 ] and ttParent [ 1 ] and not ttParent [ 1 ] . parent then
hiddenMouseOver : SetPoint ( " TOPLEFT " , ttParent [ 1 ] , " TOPLEFT " , 0 , 1 ) ;
hiddenMouseOver : SetPoint ( " BOTTOMRIGHT " , ttParent [ 1 ] , " BOTTOMRIGHT " , 0 , - 1 ) ;
-- TitalPanelAutoHide
if TitanPanelSetVar and TitanUtils_GetWhichBar then
local titanBar , current , ldbName = nil , nil , string.match ( ttParent [ 1 ] : GetName ( ) or " " , " TitanPanel(.*)Button " ) ;
if ldbName then
titanBar = TitanUtils_GetWhichBar ( ldbName ) ;
end
if titanBar then
current = TitanPanelGetVar ( titanBar .. " _Hide " ) ; -- get autohide status
end
if current then
tooltip.TitanBar_AutoHide = titanBar ;
TitanPanelSetVar ( titanBar .. " _Hide " , false ) ;
end
end
end
tooltip : SetScript ( " OnUpdate " , hideOnUpdate ) ;
tooltip : SetScript ( " OnLeave " , hideOnLeave ) ;
local TipTac = _G [ " TipTac " ]
if TipTac and TipTac.AddModifiedTip then
TipTac : AddModifiedTip ( tooltip , true ) ; -- Tiptac Support for LibQTip Tooltips
elseif AddOnSkins and AddOnSkins.SkinTooltip then
AddOnSkins : SkinTooltip ( tooltip ) ; -- AddOnSkins support
end
tooltip : SetClampedToScreen ( true ) ;
tooltip : SetPoint ( ns.GetTipAnchor ( unpack ( ttParent ) ) ) ;
if type ( ttParent [ 1 ] ) == " table " and ttParent [ 1 ] : GetObjectType ( ) == " Button " then
if not brokerDragHooks [ ttParent [ 1 ] ] then
-- close tooltips if broker button fire OnDragStart
ttParent [ 1 ] : HookScript ( " OnDragStart " , hookDragStart ) ;
end
brokerDragHooks [ ttParent [ 1 ] ] = { tooltip.key , tooltip } ;
end
return tooltip ;
end
---@param tooltip frame|LibQTipTooltip
---@param ignoreMaxTooltipHeight boolean
function ns . roundupTooltip ( tooltip , ignoreMaxTooltipHeight )
if not tooltip then return end
if not ignoreMaxTooltipHeight then
tooltip : UpdateScrolling ( GetScreenHeight ( ) * ( ns.profile . GeneralOptions.maxTooltipHeight / 100 ) ) ;
end
tooltip : SetClampedToScreen ( true ) ;
tooltip : Show ( ) ;
end
---@param tooltip frame|LibQTipTooltip
function ns . hideTooltip ( tooltip )
if type ( tooltip ) ~= " table " then return ; end
if type ( tooltip.secureButtons ) == " table " then
local f = GetMouseFocus ( )
if f and not f : IsForbidden ( ) and ( not f : IsProtected ( ) and InCombatLockdown ( ) ) and type ( f.key ) == " string " and type ( tooltip.key ) == " string " and f.key == tooltip.key then
return ; -- why that? tooltip can't be closed in combat with securebuttons as child elements. results in addon_action_blocked...
end
ns.secureButton ( false ) ;
end
tooltip : SetScript ( " OnLeave " , nil ) ;
tooltip : SetScript ( " OnUpdate " , nil ) ;
hiddenMouseOver : ClearAllPoints ( ) ;
if tooltip.scripts and type ( tooltip.scripts . OnHide ) == " function " then
tooltip.scripts . OnHide ( tooltip ) ;
end
-- TitalPanelAutoHide
if tooltip.TitanBar_AutoHide then
TitanPanelSetVar ( tooltip.TitanBar_AutoHide .. " _Hide " , true ) ;
tooltip.TitanBar_AutoHide = nil ;
end
tooltip.parent = nil ;
tooltip.mode = nil ;
tooltip.scripts = nil ;
ns.LQT : Release ( tooltip ) ;
end
----------------------------------------
---@param tooltip frame|LibQTipTooltip
---@param func function
function ns . RegisterMouseWheel ( tooltip , func )
tooltip : EnableMouseWheel ( 1 ) ;
tooltip : SetScript ( " OnMouseWheel " , func ) ;
end
-- L["ModKey" .. ns.tooltipModifiers.<key>.l]
ns.tooltipModifiers = {
SHIFT = { l = " S " , f = " Shift " } ,
LEFTSHIFT = { l = " LS " , f = " LeftShift " } ,
RIGHTSHIFT = { l = " RS " , f = " RightShift " } ,
ALT = { l = " A " , f = " Alt " } ,
LEFTALT = { l = " LA " , f = " LeftAlt " } ,
RIGHTALT = { l = " RA " , f = " RightAlt " } ,
CTRL = { l = " C " , f = " Control " } ,
LEFTCTRL = { l = " LC " , f = " LeftControl " } ,
RIGHTCTRL = { l = " RC " , f = " RightControl " }
}
---@param bool boolean
---@return boolean|string
function ns . tooltipChkOnShowModifier ( bool )
local modifier = ns.profile . GeneralOptions.ttModifierKey1 ;
if ( modifier ~= " NONE " ) then
modifier = ( ns.tooltipModifiers [ modifier ] ) and _G [ " Is " .. ns.tooltipModifiers [ modifier ] . f .. " KeyDown " ] ( ) ;
if ( bool ) then
return modifier ;
else
return not modifier ;
end
end
return false ;
end
---@param tooltip frame|LibQTipTooltip
---@param content string
---@param cells table
---@param align string
---@param font string
---@return number line
function ns . AddSpannedLine ( tooltip , content , cells , align , font )
local line = tooltip : AddLine ( ) ;
cells = cells or { } ;
tooltip : SetCell ( line , cells.start or 1 , content , font , align , cells.count or 0 ) ;
return line ;
end
--------------------------------------------------------------------------
--- coexistence with other addons ---
--- sometimes it is better to let other addons the control about something ---
--------------------------------------------------------------------------
do
local found , list = nil , {
-- ["<addon name>"] = "<msg>",
[ " Carbonite " ] = " CoExistUnsave " ,
[ " DejaMinimap " ] = " CoExistUnsave " ,
[ " Chinchilla " ] = " CoExistSimilar " ,
[ " Dominos_MINIMAP " ] = " CoExistSimilar " ,
[ " gUI4_Minimap " ] = " CoExistOwn " ,
[ " LUI " ] = " CoExistOwn " ,
[ " MinimapButtonFrame " ] = " CoExistUnsave " ,
[ " SexyMap " ] = " CoExistSimilar " ,
[ " SquareMap " ] = " CoExistUnsave " ,
-- L["CoExistUnsave"] L["CoExistSimilar"] L["CoExistOwn"]
} ;
ns.coexist = { } ;
local function search ( )
found = { } ;
for name in pairs ( list ) do
if ( GetAddOnInfo ( name ) ) and ( GetAddOnEnableState ( ns.player . name , name ) == 2 ) then
tinsert ( found , name ) ;
end
end
end
function ns . coexist . IsNotAlone ( info )
if not found then search ( ) end
local b = # found > 0 ;
if info and info [ # info ] : find ( " Info$ " ) then -- for Ace3 Options (<hidden|disabled>=<thisFunction>)
return not b ;
end
return b ;
end
function ns . coexist . optionInfo ( )
if not found then search ( ) end
-- This option is disabled because:
-- <addon> >> <msg>
local msgs = { } ;
for i = 1 , # found do
tinsert ( msgs , ns.LC . color ( " ltblue " , found [ i ] ) .. " \n " .. ns.LC . color ( " ltgray " , " >> " ) .. L [ list [ found [ i ] ] ] ) ;
end
return ns.LC . color ( " orange " , L [ " CoExistDisabled " ] ) .. " \n "
.. tconcat ( msgs , " \n " ) ;
end
end
---------------------------------------
--- suffix colour function ---
---------------------------------------
---@param str string
---@return string
function ns . suffixColour ( str )
if ( ns.profile . GeneralOptions.suffixColour ) then
str = ns.LC . color ( " suffix " , str ) ;
end
return str ;
end
------------------------------------------
--- Icon provider and framework to support ---
--- use of external iconset ---
------------------------------------------
do
ns.I = setmetatable ( { } , {
__index = function ( t , k )
local v = { iconfile = ns.icon_fallback , coords = { 0.05 , 0.95 , 0.05 , 0.95 } }
rawset ( t , k , v )
return v
end ,
__call = function ( t , a )
if ns.profile == nil then
return { } ;
end
local iconset
if a == true then
if ns.profile . GeneralOptions.iconset ~= " NONE " then
iconset = ns.LSM : Fetch ( ( addon .. " _Iconsets " ) : lower ( ) , ns.profile . GeneralOptions.iconset ) or iconset
end
return
end
assert ( type ( a ) == " string " , " argument #1 must be a string, got " .. type ( a ) )
return ( type ( iconset ) == " table " and iconset [ a ] ) or t [ a ]
end
} )
function ns . updateIcons ( name , part )
if name == true then
local result = true ;
for modName , mod in pairs ( ns.modules ) do
if mod.isEnabled and ns.updateIcons ( modName , part ) == false then
result = false ;
end
end
return result ;
elseif type ( name ) == " string " and ns.modules [ name ] and ns.modules [ name ] . isEnabled and ns.modules [ name ] . obj then
local mod = ns.modules [ name ] ;
if part == " color " or part == nil then
mod.obj . iconR , mod.obj . iconG , mod.obj . iconB , mod.obj . iconA = unpack ( ns.profile . GeneralOptions.iconcolor or ns.LC . color ( " white " , " colortable " ) ) ;
end
if part == " icon " or part == nil then
local icon = ns.I ( mod.iconName .. ( mod.icon_suffix or " " ) ) ;
mod.obj . iconCoords = icon.coords or { 0 , 1 , 0 , 1 } ;
mod.obj . icon = icon.iconfile ;
end
return true ;
end
return false ;
end
end
-- ------------------------------ --
-- missing real round function --
-- ------------------------------ --
---@param num number
---@param precision? number
---@return number
function ns . round ( num , precision )
return tonumber ( ( " %. " .. ( tonumber ( precision ) or 0 ) .. " f " ) : format ( num or 0 ) ) or 0 ;
end
-- -------------------------------------------------- --
-- Function to Sort a table by the keys --
-- Sort function fom http://www.lua.org/pil/19.3.html --
-- -------------------------------------------------- --
do
local function invert ( a , b )
return a > b ;
end
---@param t table
---@param f? function|true
function ns . pairsByKeys ( t , f )
local a = { }
for n in pairs ( t ) do
tinsert ( a , n )
end
if f == true then
f = invert ;
end
tsort ( a , f )
local i = 0 -- iterator variable
local function iter ( ) -- iterator function
i = i + 1
if a [ i ] == nil then
return nil
end
return a [ i ] , t [ a [ i ] ]
end
return iter
end
end
function ns . table2string ( tbl )
local tmp = { } ;
for k , v in ns.pairsByKeys ( tbl ) do
tinsert ( tmp , " [ " .. k .. " ]= " .. tostring ( v ) ) ;
end
return " { " .. table.concat ( tmp , " , " ) .. " } " ;
end
---@param modName string module name
---@param opts table
---@return function iterationFunction
function ns . pairsToons ( modName , opts )
-- opts = {currentFirst=<bool>,currentHide=<bool>,forceSameRealm=<bool>,forceSameFaction=<bool>}
-- TODO: add ns.profile options from modules here
local t = { } ;
for index , toonNameRealm in ipairs ( Broker_Everything_CharacterDB.order ) do
local name , realm = strsplit ( " - " , toonNameRealm , 2 ) ;
if ns.showThisChar ( modName , realm , Broker_Everything_CharacterDB [ toonNameRealm ] . faction ) then
if opts.currentHide == true and toonNameRealm == ns.player . name_realm then
-- ignore
elseif opts.currentFirst == true and toonNameRealm == ns.player . name_realm then
tinsert ( t , 1 , index ) ;
elseif not ( opts.forceSameRealm == true and realm ~= ns.realm ) and not ( opts.forceSameFaction == true and ns.faction ~= Broker_Everything_CharacterDB [ toonNameRealm ] . faction ) then
tinsert ( t , index ) ;
end
end
end
local i = 0 ;
local function iter ( )
i = i + 1 ;
local index = t [ i ] ;
if Broker_Everything_CharacterDB.order [ index ] == nil then
return nil ;
end
local toonNameRealm = Broker_Everything_CharacterDB.order [ index ] ;
local toonName , toonRealm = strsplit ( " - " , toonNameRealm , 2 ) ;
return index , toonNameRealm , toonName , toonRealm , Broker_Everything_CharacterDB [ toonNameRealm ] , toonNameRealm == ns.player . name_realm ;
-- index, toonNameRealm, toonName, toonRealm, toonData, isCurrent
end
return iter ;
end
-- ------------------------------------------------------------ --
-- Function to check/create a table structure by given path
-- ------------------------------------------------------------ --
---@param tbl table
---@param a string
---@param ... string
function ns . tablePath ( tbl , a , ... )
if type ( a ) ~= " string " then return end
if type ( tbl [ a ] ) ~= " table " then tbl [ a ] = { } ; end
if ( ... ) then ns.tablePath ( tbl [ a ] , ... ) ; end
end
-- ------------------------------------ --
-- FormatLargeNumber function advanced --
-- ------------------------------------ --
do
-- L["SizeSuffix-10E18"] L["SizeSuffix-10E15"] L["SizeSuffix-10E12"] L["SizeSuffix-10E9"] L["SizeSuffix-10E6"] L["SizeSuffix-10E3"]
local floatformat , sizes = " %0.1f " , {
18 , 15 , 12 , 9 , 6 , 3 -- Qi Qa T B M K (Qi Qa Tr Bi Mi Th?)
} ;
---@param modName string module name
---@param value number|string
---@param tooltip frame|LibQTipTooltip
---@return number|string
function ns . FormatLargeNumber ( modName , value , tooltip )
local shortNumbers , doShortcut = false , not ( tooltip and IsShiftKeyDown ( ) ) ;
if type ( modName ) == " boolean " then
shortNumbers = modName ;
elseif modName and ns.profile [ modName ] then
shortNumbers = ns.profile [ modName ] . shortNumbers ;
end
value = tonumber ( value ) or 0 ;
if shortNumbers and doShortcut then
for i = 1 , # sizes do
if value >= ( 10 ^ sizes [ i ] ) then
value = floatformat : format ( value / ( 10 ^ sizes [ i ] ) ) .. L [ " SizeSuffix-10E " .. sizes [ i ] ] ;
break ;
end
end
elseif ns.profile . GeneralOptions.separateThousands then
value = FormatLargeNumber ( value ) ;
end
return value ;
end
end
-- --------------------- --
-- Some string function --
-- --------------------- --
---@param text string
---@param limit number
---@param insetCount? number
---@param insetChr? string
---@param insetLastChr? string
---@return string
function ns . strWrap ( text , limit , insetCount , insetChr , insetLastChr )
if not text then return " " ; end
if text : match ( " \n " ) or text : match ( " %|n " ) then
local txt = text : gsub ( " %|n " , " \n " ) ;
local strings , tmp = { strsplit ( " \n " , txt ) } , { } ;
for i = 1 , # strings do
tinsert ( tmp , ns.strWrap ( strings [ i ] , limit , insetCount , insetChr , insetLastChr ) ) ;
end
return tconcat ( tmp , " \n " ) ;
end
if text : len ( ) <= limit then return text ; end
local tmp , result , inset = " " , { } , " " ;
if type ( insetCount ) == " number " then
inset = ( insetChr or " " ) : rep ( insetCount - ( insetLastChr or " " ) : len ( ) ) .. ( insetLastChr or " " ) ;
end
for str in text : gmatch ( " ([^ \n ]+) " ) do
local tmp2 = strtrim ( tmp .. " " .. str ) ;
if tmp2 : len ( ) >= limit then
tinsert ( result , tmp ) ;
tmp = strtrim ( str ) ;
else
tmp = tmp2 ;
end
end
if tmp ~= " " then
tinsert ( result , tmp ) ;
end
return tconcat ( result , " |n " .. inset )
end
---@param str string
---@param limit number
---@return string
function ns . strCut ( str , limit )
if str : len ( ) > limit - 3 then str = strsub ( str , 1 , limit - 3 ) .. " ... " end
return str
end
---@param str string
---@param pat string
---@param count number
---@param append boolean
---@return string
function ns . strFill ( str , pat , count , append )
local l = ( count or 1 ) - str : len ( ) ;
if l <= 0 then return str ; end
local p = ( pat or " " ) : rep ( l ) ;
if append then return str .. p ; end
return p .. str ;
end
-- ----------------------------------------
-- secure button as transparent overlay
-- http://wowpedia.org/SecureActionButtonTemplate
-- be careful...
--
-- @param self UI_ELEMENT
-- @param obj TABLE
-- obj = {
-- {
-- typeName STRING | see "Modified attributes"
-- typeValue STRING | see "Action types" "Type"-column
-- attrName STRING | see "Action types" "Used attributes"-column
-- attrValue ~mixed~ | see "Action types" "Behavior"-column.
-- | Note: if typeValue is click then attrValue must
-- be a ui element with :Click() function like
-- buttons. thats a good way to open frames
-- like spellbook without risk tainting it by
-- an addon.
-- },
-- { ... }
-- }
-- ----------------------------------------
do
local sbfObject , sbf = { } ;
function ns . secureButton ( self , obj )
if self == nil or InCombatLockdown ( ) then
return ;
end
if sbf ~= nil and self == false then
sbf : Hide ( ) ;
return ;
end
if type ( obj ) ~= " table " then
return ;
end
sbfObject = obj ;
if not sbf then
sbf = CreateFrame ( " Button " , addon .. " _SecureButton " , UIParent , " SecureActionButtonTemplate, SecureHandlerEnterLeaveTemplate, SecureHandlerShowHideTemplate " ) ;
sbf : SetHighlightTexture ( [[interface\friendsframe\ui-friendsframe-highlightbar-blue]] , " ADD " ) ;
sbf : HookScript ( " OnClick " , function ( _ , button ) if type ( sbfObject.OnClick ) == " function " then sbfObject.OnClick ( self , button , sbfObject ) ; end end ) ;
sbf : HookScript ( " OnEnter " , function ( ) if type ( sbfObject.OnEnter ) == " function " then sbfObject.OnEnter ( self , sbfObject ) ; end end ) ;
sbf : HookScript ( " OnLeave " , function ( ) if type ( sbfObject.OnLeave ) == " function " then sbfObject.OnLeave ( self , sbfObject ) ; end end ) ;
sbf : RegisterForClicks ( " AnyUp " , " AnyDown " ) ; -- TODO: testing
end
sbf : SetParent ( self ) ;
sbf : SetPoint ( " CENTER " ) ;
sbf : SetSize ( self : GetSize ( ) ) ;
for k , v in pairs ( obj.attributes ) do
if type ( k ) == " string " and v ~= nil then
sbf : SetAttribute ( k , v ) ;
end
end
sbf : SetAttribute ( " _onleave " , " self:Hide() " ) ;
sbf : SetAttribute ( " _onhide " , " self:SetParent(UIParent);self:ClearAllPoints(); " ) ;
sbf : Show ( ) ;
end
end
-- -------------------------------------------------------------- --
-- module independent bags and inventory scanner --
-- event driven with delayed execution --
-- -------------------------------------------------------------- --
do
local itemsByID , itemsBySlot , itemsBySpell , equip , ammo = { } , { } , { } , { } , { } ;
ns.items = { byID = itemsByID , bySlot = itemsBySlot , bySpell = itemsBySpell , equip = equip , ammo = ammo } ;
local hasChanged , updateBags , IsEnabledBags , IsEnabledInv = { bags = false , inv = false , equip = false , ammo = false , items = false , spells = false , item = { } , itemNum = 0 } , { } ;
local callbacks = { any = { } , inv = { } , bags = { } , item = { } , equip = { } , prepare = { } , toys = { } , ammo = { } } ;
local cbCounter = { any = 0 , inv = 0 , bags = 0 , item = 0 , equip = 0 , prepare = 0 , toys = 0 , ammo = 0 } ;
local eventFrame , inventoryDelayed = CreateFrame ( " Frame " ) ;
local LE_ITEM_CLASS_PROJECTILE = LE_ITEM_CLASS_PROJECTILE or Enum.ItemClass . Projectile or 6 ;
local function doCallbacks ( tbl , ... )
if callbacks [ tbl ] == nil or cbCounter [ tbl ] == 0 then
return ; -- no callbacks registered
end
for _ , fnc in pairs ( callbacks [ tbl ] ) do
fnc ( tbl , ... ) ;
end
end
local function callbackHandler ( )
-- execute callback functions
if hasChanged.bags or hasChanged.inv or hasChanged.equip or hasChanged.items or hasChanged.ammo then
-- 'prepare' callbacks
doCallbacks ( " prepare " , hasChanged ) ;
-- 'any' callbacks
doCallbacks ( " any " , hasChanged ) ;
end
-- 'item' callbacks
if hasChanged.items then
for id , locations in pairs ( hasChanged.item ) do
if callbacks.item [ id ] and # callbacks.item [ id ] then
doCallbacks ( " item " , id , locations ) ;
end
end
wipe ( hasChanged.item ) ;
hasChanged.items = false ;
end
-- callbacks by type
for _ , cbType in ipairs ( { " bags " , " inv " , " equip " , " ammo " } ) do
if hasChanged [ cbType ] then
doCallbacks ( cbType ) ;
hasChanged [ cbType ] = false ;
end
end
end
local function addItem ( info , scanner )
if itemsBySlot [ info.sharedSlot ] and itemsBySlot [ info.sharedSlot ] . diff == info.diff then
return false ; -- item has not changed; must not be added again.
end
if itemsByID [ info.id ] == nil then
itemsByID [ info.id ] = { } ;
end
-- add item info to ByID and BySlot tables
itemsByID [ info.id ] [ info.sharedSlot ] = info ;
itemsBySlot [ info.sharedSlot ] = info ;
-- add to extra table for equipment; inventory and bags. Needed for durability summary calculation.
if info.equip then
equip [ info.sharedSlot ] = true ;
hasChanged.equip = true ;
end
-- item has 'Use:' effect spell
local _ , itemSpellID = GetItemSpell ( info.link ) ;
if itemSpellID then
info.spell = itemSpellID ;
if itemsBySpell [ info.spell ] == nil then
itemsBySpell [ info.spell ] = { } ;
end
itemsBySpell [ info.spell ] [ info.sharedSlot ] = info.count ;
end
-- ns.ammo_classic defined in modules/ammo_classic.lua
if ns.ammo_classic then
if info.ammo then
ammo [ info.sharedSlot ] = true ;
hasChanged.ammo = true ;
end
end
if callbacks.item [ info.id ] then
hasChanged.item [ info.id ] [ info.sharedSlot ] = true ;
hasChanged.items = true ;
end
hasChanged [ scanner ] = true ;
return true ;
end
local function removeItem ( sharedSlotIndex , scanner )
local id = itemsBySlot [ sharedSlotIndex ] . id ;
itemsByID [ itemsBySlot [ sharedSlotIndex ] . id ] [ sharedSlotIndex ] = nil ;
itemsBySlot [ sharedSlotIndex ] = nil ;
if equip [ sharedSlotIndex ] then
equip [ sharedSlotIndex ] = nil ;
hasChanged.equip = true ;
end
if ns.ammo_classic and ammo [ sharedSlotIndex ] then
ammo [ sharedSlotIndex ] = nil ;
hasChanged.ammo = true ;
end
if callbacks.item [ id ] then
hasChanged.item [ id ] [ sharedSlotIndex ] = true ;
hasChanged.items = true ;
end
hasChanged [ scanner ] = true ;
end
local scanInventory
function scanInventory ( )
local retry = false ;
for slotIndex = 1 , 19 do
local sharedSlotIndex = - ( slotIndex / 100 ) ;
local id = tonumber ( ( GetInventoryItemID ( " player " , slotIndex ) ) ) ;
if id then
local link = GetInventoryItemLink ( " player " , slotIndex ) ;
-- back again; need durability for detect changes to trigger update of durability module broker display
local durability , durabilityMax = GetInventoryItemDurability ( slotIndex ) ;
addItem ( {
bag =- 1 ,
slot = slotIndex ,
sharedSlot = sharedSlotIndex ,
id = id ,
link = link ,
diff = table.concat ( { link , durability , durabilityMax } , " ^ " ) ,
equip = true
} , " inv " ) ;
if link and link : find ( " %[%] " ) then
retry = true ; -- Query heirloom item info looks like unstable. too often return invalid item links
end
elseif itemsBySlot [ sharedSlotIndex ] then
-- no item in inventory slot
removeItem ( sharedSlotIndex , " inv " ) ;
end
end
callbackHandler ( ) ;
if retry then
-- retry on heirloom link bug
C_Timer.After ( 1.2 , scanInventory ) ;
return ;
end
inventoryDelayed = false ;
end
local function scanBags ( )
for bagIndex , bool in pairs ( updateBags ) do
bagIndex = tonumber ( bagIndex )
if bagIndex and bool == true then
local numBagSlots = ( C_Container and C_Container.GetContainerNumSlots or GetContainerNumSlots ) ( bagIndex ) ;
local numFreeSlots = ( C_Container and C_Container.GetContainerNumFreeSlots or GetContainerNumFreeSlots ) ( bagIndex ) ;
local IndexTabardType = INVTYPE_TABARD or Enum.InventoryType . IndexTabardType ;
local IndexBodyType = INVTYPE_BODY or Enum.InventoryType . IndexBodyType ;
if numBagSlots ~= numFreeSlots then -- do not scan empty bag ;-)
--[[
local isSoul = false ; -- currently could not test. have no warlock on classic realms.
if ns.client_version < 2 then
local _ , _ , _ , _ , _ , itemClassID , itemSubClassID = GetItemInfoInstant ( GetInventoryItemLink ( " player " , bagIndex + 19 ) ) ;
if itemSubClassID == 1 then -- soul pouch
isSoul = true ;
end
end
--]]
for slotIndex = 1 , numBagSlots do
local sharedSlotIndex = bagIndex + ( slotIndex / 100 ) ;
local itemInfo , count , _ , _ , _ , _ , link , _ , _ , id = ( C_Container and C_Container.GetContainerItemInfo or GetContainerItemInfo ) ( bagIndex , slotIndex ) ;
if not count and type ( itemInfo ) == " table " then
count = itemInfo.stackCount ;
link = itemInfo.hyperlink ;
end
if link and not id then
id = tonumber ( ( link : match ( " item:(%d+) " ) ) ) or - 1 ;
end
if link and id then
local _ , _ , _ , itemEquipLocation , _ , itemClassID = GetItemInfoInstant ( link ) ; -- equipment in bags; merchant repair all function will be repair it too
local durability , durabilityMax = ( C_Container and C_Container.GetContainerItemDurability or GetContainerItemDurability ) ( bagIndex , slotIndex )
local isEquipment = false ;
if not ( itemEquipLocation == " " or itemEquipLocation == IndexTabardType or itemEquipLocation == IndexBodyType ) then
isEquipment = true ; -- ignore shirts and tabards
end
addItem ( {
bag = bagIndex ,
slot = slotIndex ,
count = count ,
sharedSlot = sharedSlotIndex ,
id = id ,
link = link ,
diff = table.concat ( { link , count , durability , durabilityMax } , " ^ " ) ,
equip = isEquipment ,
ammo = ( itemClassID == LE_ITEM_CLASS_PROJECTILE )
} , " bags " ) ;
elseif itemsBySlot [ sharedSlotIndex ] then
removeItem ( sharedSlotIndex , " bags " ) ;
end
end
else
-- bag is empty but previosly it had items
for slotIndex = 1 , numBagSlots do
local sharedSlotIndex = bagIndex + ( slotIndex / 100 ) ;
if itemsBySlot [ sharedSlotIndex ] then
removeItem ( sharedSlotIndex , " bags " ) ;
end
end
end
end
end
wipe ( updateBags ) ;
callbackHandler ( ) ;
end
local inventoryEvents = {
PLAYER_LOGIN = true ,
PLAYER_EQUIPMENT_CHANGED = true ,
UPDATE_INVENTORY_DURABILITY = true ,
ITEM_UPGRADE_MASTER_UPDATE = true ,
MERCHANT_CLOSED = true
} ;
local function OnEvent ( self , event , ... )
if event == " BAG_UPDATE " and tonumber ( ... ) and ( ... ) <= NUM_BAG_SLOTS then
updateBags [ tostring ( ... ) ] = true
elseif event == " BAG_UPDATE_DELAYED " and table.getn ( updateBags ) > 0 then
scanBags ( ) ;
elseif event == " PLAYER_LOGIN " then
updateBags [ " 0 " ] = true ; -- BAG_UPDATE fired with 1-12 as bag index (argument) before PLAYER_LOGIN; bag index 0 is missing
scanBags ( ) ;
elseif event == " GET_ITEM_INFO_RECEIVED " and ( ... ) ~= nil then
local id = ... ;
if itemsByID [ id ] then
local info = itemsByID [ id ] ;
local _ , spell = GetItemSpell ( info.link ) ;
if spell then
for sharedSlot , info in pairs ( itemsByID [ ... ] ) do
local _ , count , _ , _ , _ , _ , _ , _ , _ = ( C_Container and C_Container.GetContainerItemInfo or GetContainerItemInfo ) ( info.bag , info.slot ) ;
info.spell = spell ;
itemsBySpell [ spell ] [ sharedSlot ] = count ;
end
end
elseif callbacks.toys [ id ] and PlayerHasToy then
local toyName , _ , _ , _ , _ , _ , _ , _ , _ , toyIcon = GetItemInfo ( id ) ;
local hasToy = PlayerHasToy ( id ) ;
local canUse = C_ToyBox.IsToyUsable ( id ) ;
if toyName and hasToy and canUse then
callbacks.toys [ id ] ( id , toyIcon , toyName ) ;
end
end
elseif inventoryEvents [ event ] and not inventoryDelayed then
inventoryDelayed = true ;
C_Timer.After ( 0.5 , scanInventory ) ;
end
end
eventFrame : SetScript ( " OnEvent " , OnEvent ) ;
local function initBags ( )
if IsEnabledBags then return end
IsEnabledBags = true ;
-- bag events
eventFrame : RegisterEvent ( " BAG_UPDATE " ) ;
eventFrame : RegisterEvent ( " BAG_UPDATE_DELAYED " ) ;
if ns.eventPlayerEnteredWorld then
-- module registered after PLAYER_ENTERING_WORLD
updateBags = { [ " 0 " ] = true , [ " 1 " ] = true , [ " 2 " ] = true , [ " 3 " ] = true , [ " 4 " ] = true } ;
OnEvent ( eventFrame , " BAG_UPDATE_DELAYED " ) ;
end
end
local function initInventory ( )
if IsEnabledInv then return end
IsEnabledInv = true ;
-- inventory events
eventFrame : RegisterEvent ( " PLAYER_LOGIN " )
eventFrame : RegisterEvent ( " PLAYER_EQUIPMENT_CHANGED " ) ;
eventFrame : RegisterEvent ( " UPDATE_INVENTORY_DURABILITY " ) ;
eventFrame : RegisterEvent ( " GET_ITEM_INFO_RECEIVED " ) ;
eventFrame : RegisterEvent ( " MERCHANT_CLOSED " ) ;
if ns.eventPlayerEnteredWorld then
-- module registered after PLAYER_ENTERING_WORLD
OnEvent ( eventFrame , " PLAYER_EQUIPMENT_CHANGED " ) ;
end
end
function ns . items . Init ( initType )
assert ( initType , " Missing argument initType " ) ;
if initType ~= " inv " then
initBags ( ) ;
end
if initType ~= " bags " then
initInventory ( ) ;
end
end
function ns . items . RegisterCallback ( modName , func , mode , id )
mode = tostring ( mode ) : lower ( ) ;
assert ( type ( modName ) == " string " and ns.modules [ modName ] , " argument #1 (modName) must be a string, got " .. type ( modName ) ) ;
assert ( type ( func ) == " function " , " argument #2 (function) must be a function, got " .. type ( func ) ) ;
assert ( type ( callbacks [ mode ] ) == " table " , " argument #3 must be 'any', 'inv', 'bags', 'item', 'spell', 'equip', 'toys' or 'prepare'. " ) ;
if mode == " item " then
assert ( type ( id ) == " number " , " argument #4 must be number, got " .. type ( id ) ) ;
if callbacks.item [ id ] == nil then
callbacks.item [ id ] = { } ;
end
callbacks.item [ id ] [ modName ] = func ;
else
callbacks [ mode ] [ modName ] = func ;
end
cbCounter [ mode ] = cbCounter [ mode ] + 1 ;
ns.items . Init ( mode ) ;
end
function ns . items . GetBagSlot ( sharedSlotIndex )
if sharedSlotIndex < 0 then
return false , sharedSlotIndex * 100
end
local bagIndex , slotIndex = floor ( sharedSlotIndex ) ;
slotIndex = ( sharedSlotIndex - bagIndex ) * 100
return bagIndex , slotIndex ;
end
end
-- -------------------------------------------------------------- --
-- UseContainerItem hook
-- -------------------------------------------------------------- --
do
local callback = { } ;
local function UseContainerItemHook ( bag , slot )
if bag and slot then
local itemId = tonumber ( ( ( C_Container and C_Container.GetContainerItemLink or GetContainerItemLink ) ( bag , slot ) or " " ) : match ( " Hitem:([0-9]+) " ) ) ;
if itemId and callback [ itemId ] then
for _ , entry in pairs ( callback [ itemId ] ) do
if type ( entry.callback ) == " function " then
entry.callback ( bag , slot , itemId , entry.info ) ;
end
end
end
end
end
if C_Container and C_Container.UseContainerItem then
hooksecurefunc ( C_Container , " UseContainerItem " , UseContainerItemHook ) ;
elseif UseContainerItem then
hooksecurefunc ( " UseContainerItem " , UseContainerItemHook ) ;
end
ns.UseContainerItemHook = {
registerItemID = function ( modName , itemId , callbackFunc , info )
if callback [ itemId ] == nil then
callback [ itemId ] = { } ;
end
callback [ itemId ] [ modName ] = { callback = callbackFunc , info = info } ;
end
} ;
end
-- --------------------- --
-- scanTooltip functions --
-- --------------------- --
do
local QueueModeScanTT = CreateFrame ( " GameTooltip " , addon .. " ScanTooltip " , UIParent , " GameTooltipTemplate " ) ;
local InstantModeScanTT = CreateFrame ( " GameTooltip " , addon .. " ScanTooltip2 " , UIParent , " GameTooltipTemplate " ) ;
local _ITEM_LEVEL = ITEM_LEVEL : gsub ( " %%d " , " (%%d*) " ) ;
local ITEM_UPGRADE_TOOLTIP_1 = strsplit ( " : " , ITEM_UPGRADE_TOOLTIP_FORMAT ) .. CHAT_HEADER_SUFFIX ;
local ITEM_UPGRADE_TOOLTIP_2 = ITEM_UPGRADE_TOOLTIP_FORMAT_STRING and strsplit ( " : " , ITEM_UPGRADE_TOOLTIP_FORMAT_STRING ) .. CHAT_HEADER_SUFFIX or false ;
for f , v in pairs ( { SetScale = 0.0001 , SetAlpha = 0 , Hide = true , SetClampedToScreen = false , SetFrameStrata = " BACKGROUND " , ClearAllPoints = true } ) do
QueueModeScanTT [ f ] ( QueueModeScanTT , v ) ;
InstantModeScanTT [ f ] ( InstantModeScanTT , v ) ;
end
-- remove scripts from tooltip... prevents taint log spamming.
local badScripts = { " OnLoad " , " OnHide " , " OnTooltipSetDefaultAnchor " , " OnTooltipCleared " } ;
if ns.client_version <= 9 then
tinsert ( badScripts , " OnTooltipAddMoney " ) ;
end
for _ , v in ipairs ( badScripts ) do
QueueModeScanTT : SetScript ( v , nil ) ;
InstantModeScanTT : SetScript ( v , nil ) ;
end
ns.ScanTT = { } ;
local queries = { } ;
local ticker = nil ;
local duration = 0.05 ;
local try = 0 ;
local function GetLinkData ( link )
if not link then return end
local _ , _ , _ , link = link : match ( " |c(%x*)|H([^:]*):(%d+):(.+)|h%[([^%[%]]*)%]|h|r " ) ;
link = { strsplit ( _G [ " HEADER_COLON " ] , link or " " ) } ;
for i = 1 , # link do
link [ i ] = tonumber ( link [ i ] ) or 0 ;
end
return link ;
end
local function collect ( tt , Data )
local data , _ ;
if not Data then
if # queries == 0 then
if ( ticker ) then
ticker : Cancel ( ) ;
ticker = nil ;
end
tt : Hide ( ) ;
return ;
end
data = queries [ 1 ] ;
else
data = Data ;
end
tt : SetOwner ( UIParent , " ANCHOR_NONE " ) ;
tt : SetPoint ( " RIGHT " , UIParent , " LEFT " , 0 , 0 ) ;
if not data._type then
data._type = data.type ;
end
if data.try == nil then
data.try = 1 ;
else
data.try = data.try + 1 ;
end
if data._type == " bag " or data._type == " bags " then
if data.link == nil then
data.link = ( C_Container and C_Container.GetContainerItemLink or GetContainerItemLink ) ( data.bag , data.slot ) ;
end
data.linkData = GetLinkData ( data.link ) ;
data.itemName , data.itemLink , data.itemRarity , data.itemLevel , data.itemMinLevel , data.itemType , data.itemSubType , data.itemStackCount , data.itemEquipLoc , data.itemTexture , data.itemSellPrice = GetItemInfo ( data.link ) ;
data.startTime , data.duration , data.isEnabled = ( C_Container and C_Container.GetContainerItemCooldown or GetContainerItemCooldown ) ( data.bag , data.slot ) ;
data.hasCooldown , data.repairCost = tt : SetBagItem ( data.bag , data.slot ) ;
elseif data._type == " inventory " or data._type == " inv " then
if data.link == nil then
data.link = GetInventoryItemLink ( " player " , data.slot ) ;
end
data.linkData = GetLinkData ( data.link ) ;
_ , data.hasCooldown , data.repairCost = tt : SetInventoryItem ( " player " , data.slot ) ; -- repair costs
elseif data._type == " unit " then
-- https://wow.gamepedia.com/API_UnitGUID
data._type = " link " ;
if data.unit == " Creature " or data.unit == " Pet " or data.unit == " GameObject " or data.unit == " Vehicle " then
-- unit:<Creature|Pet|GameObject|Vehicle>-0-<server>-<instance>-<zone>-<id>-<spawn>
data.link = " unit: " .. data.unit .. " -0-0-0-0- " .. data.id .. " -0 " ;
elseif data.unit == " Player " then
-- unit:Player-<server>-<playerUniqueID>
elseif data.unit == " Vignette " then
-- unit:Vignette-0-<server>-<instance>-<zone>-0-<spawn>
end
elseif data._type == " item " then
data._type = " link " ;
if not data.link then
data.link = " item: " .. data.id ;
end
elseif data._type == " quest " then
data._type = " link " ;
if not data.link then
data.link = " quest: " .. data.id .. _G [ " HEADER_COLON " ] .. ( data.level or 0 ) ;
end
end
if data._type == " link " and data.link then
data.str = data.link ;
tt : SetHyperlink ( data.link ) ;
end
try = try + 1 ;
if try > 8 then try = 0 ; end
tt : Show ( ) ;
local regions = { tt : GetRegions ( ) } ;
data.lines = { } ;
for _ , v in ipairs ( regions ) do
if ( v ~= nil ) and ( v : GetObjectType ( ) == " FontString " ) then
local str = strtrim ( v : GetText ( ) or " " ) ;
if str : len ( ) > 0 then
tinsert ( data.lines , str ) ;
end
end
end
if data._type == " inventory " or data._type == " inv " or data._type == " bag " or data._type == " bags " then
for i = 2 , min ( # data.lines , 20 ) do
local lvl = tonumber ( data.lines [ i ] : match ( _ITEM_LEVEL ) ) ;
if lvl then
data.level = lvl ;
elseif data.lines [ i ] : find ( ITEM_UPGRADE_TOOLTIP_1 ) then
data.upgrades = data.lines [ i ] : gsub ( ITEM_UPGRADE_TOOLTIP_1 , " " )
elseif ITEM_UPGRADE_TOOLTIP_2 and data.lines [ i ] : find ( ITEM_UPGRADE_TOOLTIP_2 ) then
data.upgrades = data.lines [ i ] : gsub ( ITEM_UPGRADE_TOOLTIP_2 , " " )
elseif i > 4 and data.setname == nil and data.lines [ i ] : find ( " %(%d*/%d*%)$ " ) then
data.setname = strsplit ( " ( " , data.lines [ i ] ) ;
end
end
end
tt : Hide ( ) ;
if Data then
return data ;
end
if ( # data.lines > 0 ) then
data.callback ( data ) ;
tremove ( queries , 1 ) ;
elseif data.try > 5 then
tremove ( queries , 1 ) ;
end
end
--[[
ns.ScanTT . query ( {
type = " bag|link " ,
calllback = [ func ] ,
-- if type bag
bag = < number >
slot = < number >
-- if type item
id = < number >
-- if type link
link = < string >
-- if type unit
id = < number >
unit = < creature | player | ? >
} )
--]]
function ns . ScanTT . query ( data , instant )
if data.type == " bag " then
assert ( type ( data.bag ) == " number " , " bag must be a number, got " .. type ( data.bag ) ) ;
assert ( type ( data.slot ) == " number " , " slot must be a number, got " .. type ( data.slot ) ) ;
elseif data.type == " item " or data.type == " quest " or data.type == " unit " then
assert ( type ( data.id ) == " number " , " id must be a number, got " .. type ( data.id ) ) ;
elseif data.type == " link " then
assert ( type ( data.link ) == " string " , " link must be a string, got " .. type ( data.link ) ) ;
elseif data.type == " unit " then
assert ( type ( data.id ) == " number " , " id must be a number, got " .. type ( data.id ) ) ;
assert ( type ( data.unit ) , " unit (type) must be a string, got " .. type ( data.unit ) ) ;
end
if instant then
return collect ( InstantModeScanTT , data ) ;
else
assert ( type ( data.callback ) == " function " , " callback must be a function. got " .. type ( data.callback ) ) ;
tinsert ( queries , data ) ;
if ticker == nil then
ticker = C_Timer.NewTicker ( duration , function ( ) collect ( QueueModeScanTT ) ; end ) ;
end
end
end
end
-- ----------------------------------------------------- --
-- goldColor function to display amount of gold --
-- in colored strings or with coin textures depending on --
-- a per module and a addon wide toggle. --
-- ----------------------------------------------------- --
function ns . GetCoinColorOrTextureString ( modName , amount , opts )
local zz , tex , stop = " %02d " , " |TInterface \\ MoneyFrame \\ UI-%sIcon:14:14:2:0|t " , false ;
opts , amount = opts or { } , tonumber ( amount ) or 0 ;
-- color option
opts.color = ( opts.color or ns.profile . GeneralOptions.goldColor ) : lower ( ) ;
local colors = ( opts.color == " white " and { " white " , " white " , " white " } ) or ( opts.color == " color " and { " copper " , " silver " , " gold " } ) or false ;
-- goin icon option
opts.coins = opts.coins or ns.profile . GeneralOptions.goldCoins ;
-- hide option
local hideMoney = tonumber ( ns.profile . GeneralOptions.goldHide ) or 0 ;
opts.hideMoney = tonumber ( opts.hideMoney or 0 ) ;
if opts.hideMoney > 0 then
hideMoney = opts.hideMoney ; -- override general option by module option
end
local gold , silver , copper , t = floor ( amount / 10000 ) , mod ( floor ( amount / 100 ) , 100 ) , mod ( floor ( amount ) , 100 ) , { } ;
local showSilver , showCopper = ( gold > 0 or silver > 0 ) , true ;
if hideMoney == 1 then -- Hide Copper values
showCopper = false ;
elseif hideMoney == 2 then -- Hide Silver & copper
showSilver = false ;
showCopper = false ;
elseif hideMoney == 3 then -- Hide zeros values
showSilver = ( silver > 0 ) ;
showCopper = ( not showSilver or copper > 0 ) ;
elseif hideMoney == 4 then -- show highest value only
showSilver = ( gold == 0 and silver > 0 ) ;
showCopper = ( gold == 0 and silver == 0 and copper > 0 ) ;
end
if gold > 0 then
local str = tostring ( ns.FormatLargeNumber ( modName , gold , opts.inTooltip ) or " white " ) ;
tinsert ( t , ( colors and ns.LC . color ( colors [ 3 ] , str ) or str ) .. ( opts.coins and tex : format ( " Gold " ) or " " ) ) ;
if hideMoney == 4 then
stop = true ;
end
end
if showSilver and ( not stop ) then
local str = tostring ( gold > 0 and zz : format ( silver ) or silver ) ;
tinsert ( t , ( colors and ns.LC . color ( colors [ 2 ] , str ) or str ) .. ( opts.coins and tex : format ( " Silver " ) or " " ) ) ;
if hideMoney == 4 then
stop = true ;
end
end
if showCopper and ( not stop ) then
local str = tostring ( ( silver > 0 or gold > 0 ) and zz : format ( copper ) or copper ) ;
tinsert ( t , ( colors and ns.LC . color ( colors [ 1 ] , str ) or str ) .. ( opts.coins and tex : format ( " Copper " ) or " " ) ) ;
end
return tconcat ( t , opts.sep or " " ) ;
end
-- ----------------------------------------------------- --
-- screen capture mode - string replacement function --
-- ----------------------------------------------------- --
function ns . scm ( str , all , str2 )
if str == nil then return " " end
str2 , str = ( str2 or " * " ) , tostring ( str ) ;
local length = str : len ( ) ;
if length > 0 and ns.profile . GeneralOptions.scm == true then
str = all and str2 : rep ( length ) or strsub ( str , 1 , 1 ) .. str2 : rep ( length - 1 ) ;
end
return str ;
end
-- ------------------------ --
-- Hide blizzard elements --
-- ------------------------ --
do
local hideFrames = CreateFrame ( " Frame " , addon .. " _HideFrames " , UIParent ) ;
hideFrames.origParent = { } ;
hideFrames : Hide ( ) ;
function ns . hideFrames ( frameName , hideIt )
local frame = _G [ frameName ] ;
if frame and hideIt then
local parent = frame : GetParent ( ) ;
if parent == nil or parent == hideFrames then
return false
end
hideFrames.origParent [ frameName ] = parent ;
frame : SetParent ( hideFrames ) ;
elseif frame and hideFrames.origParent [ frameName ] then
frame : SetParent ( hideFrames.origParent [ frameName ] ) ;
hideFrames.origParent [ frameName ] = nil ;
end
end
end
-- ---------------- --
-- EasyMenu wrapper --
-- ---------------- --
do
local LDDM = LibStub ( " LibDropDownMenu " ) ;
local EasyMenu = LDDM.Create_DropDownMenu ( addon .. " _LibDropDownMenu " , UIParent ) ;
ns.EasyMenu = EasyMenu ;
EasyMenu.menu , EasyMenu.controlGroups , EasyMenu.IsPrevSeparator = { } , { } , false ;
local grpOrder = { " broker " , " tooltip " , " misc " , " ClickOpts " } ;
local cvarTypeFunc = {
bool = function ( D )
if ( type ( D.cvar ) == " table " ) then
--?
elseif ( type ( D.cvar ) == " string " ) then
function D . checked ( ) return ( GetCVar ( D.cvar ) == " 1 " ) end ;
function D . func ( ) SetCVar ( D.cvar , GetCVar ( D.cvar ) == " 1 " and " 0 " or " 1 " , D.cvarEvent ) ; end ;
end
end ,
slider = function ( ... )
end ,
num = function ( ... )
end ,
str = function ( ... )
end
} ;
local beTypeFunc = {
bool = function ( d )
local chk , fnc = nil , nil ;
if ( d.beModName ) then
function chk ( ) return ( ns.profile [ d.beModName ] [ d.beKeyName ] ) end ;
function fnc ( ) ns.profile [ d.beModName ] [ d.beKeyName ] = not ns.profile [ d.beModName ] [ d.beKeyName ] ; end ;
else
function chk ( ) return ( ns.profile . GeneralOptions [ d.beKeyName ] ) end ;
function fnc ( ) ns.profile . GeneralOptions [ d.beKeyName ] = not ns.profile . GeneralOptions [ d.beKeyName ] ; end ;
end
d.checked = chk ;
d.func = fnc ;
end ,
slider = function ( ... )
end ,
num = function ( D )
--[[if (D.cvarKey) then
elseif type ( D.cvars ) == " table " then
end ] ]
end ,
str = function ( ... )
end
} ;
local function pairsByAceOptions ( t )
local a , f = { } , " %06d;%s " ;
for k , v in pairs ( t ) do
tinsert ( a , f : format ( v.order or 100 , k ) ) ;
end
tsort ( a ) ;
local i = 0 ;
local function iter ( )
i = i + 1 ;
if a [ i ] == nil then
return nil ;
end
local _ , k = strsplit ( " ; " , a [ i ] , 2 ) ;
return k , t [ k ] ;
end
return iter ;
end
local function pairsByOptionGroup ( t )
local a = { }
for n in pairs ( t ) do
for i , v in ipairs ( grpOrder ) do
if n : find ( " ^ " .. v ) then
n = i .. " . " .. n ;
break ;
end
end
tinsert ( a , n ) ;
end
tsort ( a ) ;
local i , _ = 0 , nil ;
local function iter ( )
i = i + 1
if a [ i ] == nil then
return nil
end
_ , a [ i ] = strsplit ( " . " , a [ i ] , 2 ) ;
return a [ i ] , t [ a [ i ] ] ;
end
return iter
end
local function LibCloseDropDownMenus ( )
LDDM.CloseDropDownMenus ( ) ;
CloseMenus ( ) ;
end
---@param Data table
---@param Parent? frame
---@return frame|nil Parent
function EasyMenu : AddEntry ( Data , Parent )
local entry = { } ;
if ( type ( Data ) == " table " ) and ( # Data > 0 ) then -- numeric table = multible entries
self.IsPrevSeparator = false ;
for _ , childEntry in ipairs ( Data ) do
self : AddEntry ( childEntry , Parent ) ;
end
return ;
elseif ( Data.childs ) then -- child elements
self.IsPrevSeparator = false ;
local parent = self : AddEntry ( { label = Data.label , arrow = true , disabled = Data.disabled } , Parent ) ;
for _ , v in ipairs ( Data.childs ) do
self : AddEntry ( v , parent ) ;
end
return ;
elseif ( Data.groupName ) and ( Data.optionGroup ) then -- similar to childs but with group control
self.IsPrevSeparator = false ;
if ( self.controlGroups [ Data.groupName ] == nil ) then
self.controlGroups [ Data.groupName ] = { } ;
else
wipe ( self.controlGroups [ Data.groupName ] )
end
local parent = self : AddEntry ( { label = Data.label , arrow = true , disabled = Data.disabled } , Parent ) ;
parent.controlGroup = self.controlGroups [ Data.groupName ] ;
for _ , v in ipairs ( Data.optionGroup ) do
tinsert ( self.controlGroups [ Data.groupName ] , self : AddEntry ( v , parent ) ) ;
end
return ;
elseif ( Data.separator ) then -- separator line (decoration)
if self.IsPrevSeparator then
return ;
end
self.IsPrevSeparator = true ;
entry = { text = " " , dist = 0 , isTitle = true , notCheckable = true , isNotRadio = true , sUninteractable = true , iconOnly = true , icon = " Interface \\ Common \\ UI-TooltipDivider-Transparent " , tCoordLeft = 0 , tCoordRight = 1 , tCoordTop = 0 , tCoordBottom = 1 , tFitDropDownSizeX = true , tSizeX = 0 , tSizeY = 8 } ;
entry.iconInfo = entry ;
else
self.IsPrevSeparator = false ;
entry.isTitle = Data.title or false ;
entry.hasArrow = Data.arrow or false ;
entry.disabled = Data.disabled or false ;
entry.notClickable = not not Data.noclick ;
entry.isNotRadio = not Data.radio ;
entry.keepShownOnClick = true ;
entry.noClickSound = true ;
if ( Data.keepShown == false ) then
entry.keepShownOnClick = false ;
end
if ( Data.cvarType ) and ( Data.cvar ) and ( type ( Data.cvarType ) == " string " ) and ( cvarTypeFunc [ Data.cvarType ] ) then
cvarTypeFunc [ Data.cvarType ] ( Data ) ;
end
if ( Data.beType ) and ( Data.beKeyName ) and ( type ( Data.beType ) == " string " ) and ( beTypeFunc [ Data.beType ] ) then
beTypeFunc [ Data.beType ] ( Data ) ;
end
if ( Data.checked ~= nil ) then
entry.checked = Data.checked ;
if ( entry.keepShownOnClick == nil ) then
entry.keepShownOnClick = false ;
end
else
entry.notCheckable = true ;
end
entry.text = Data.label or " " ;
if ( Data.colorName ) then
entry.colorCode = " |c " .. ns.LC . color ( Data.colorName ) ;
elseif ( Data.colorCode ) then
entry.colorCode = entry.colorCode ;
end
if ( Data.tooltip ) and ( type ( Data.tooltip ) == " table " ) then
entry.tooltipTitle = ns.LC . color ( " dkyellow " , Data.tooltip [ 1 ] ) ;
if type ( Data.tooltip [ 2 ] ) == " string " and Data.tooltip [ 2 ] ~= " " then
entry.tooltipText = ns.LC . color ( " white " , Data.tooltip [ 2 ] ) ;
end
entry.tooltipOnButton = 1 ;
end
if ( Data.icon ) then
entry.text = entry.text .. " " ;
entry.icon = Data.icon ;
entry.tCoordLeft , entry.tCoordRight = 0.05 , 0.95 ;
entry.tCoordTop , entry.tCoordBottom = 0.05 , 0.95 ;
end
if ( Data.func ) then
entry.arg1 = Data.arg1 ;
entry.arg2 = Data.arg2 ;
function entry . func ( ... )
Data.func ( ... )
if ( type ( Data.event ) == " function " ) then
Data.event ( ) ;
end
if ( Parent ) and ( not entry.keepShownOnClick ) then
LibCloseDropDownMenus ( ) ;
end
end ;
end
if ( not Data.title ) and ( not Data.disabled ) and ( not Data.arrow ) and ( not Data.checked ) and ( not Data.func ) then
entry.disabled = true ;
end
end
if ( Parent ) and ( type ( Parent ) == " table " ) then
if ( not Parent.menuList ) then Parent.menuList = { } ; end
tinsert ( Parent.menuList , entry ) ;
return Parent.menuList [ # Parent.menuList ] ;
else
tinsert ( self.menu , entry ) ;
return self.menu [ # self.menu ] ;
end
end
---@param modName string module name
---@param key string
---@param value table
---@param Parent? table
function EasyMenu : AddConfigEntry ( modName , info , key , value , Parent )
local info = CopyTable ( info )
local value_name = value.name
if value_name and type ( value_name ) == " function " then
value_name = value_name ( { key } ) ;
end
if value.type == " separator " then
self : AddEntry ( { separator = true } , Parent ) ;
elseif value.type == " header " then
self : AddEntry ( { separator = true } , Parent ) ;
self : AddEntry ( { label = value_name , title = true } , Parent ) ;
elseif value.type == " toggle " and not value_name then
ns : debug ( " <EasyMenu> " , " <AddConfigEntry> " , " [missing name] " , key ) ;
elseif value.type == " toggle " then
local tooltip = nil ;
if value.desc then
tooltip = { value_name , value.desc } ;
if type ( tooltip [ 2 ] ) == " function " then
tooltip [ 2 ] = tooltip [ 2 ] ( ) ;
end
end
self : AddEntry ( {
label = value_name : gsub ( " |n " , " " ) ,
checked = function ( )
if key == " minimap " then
return not ns.profile [ modName ] [ key ] . hide ;
end
return ns.profile [ modName ] [ key ] ;
end ,
func = function ( )
local info = { modName , " " , key } ;
if key == " minimap " then
ns.option ( info , ns.profile [ modName ] . minimap.hide ) ;
else
ns.option ( info , not ns.profile [ modName ] [ key ] ) ;
end
end ,
tooltip = tooltip ,
} , Parent ) ;
elseif value.type == " select " then
local tooltip = { value_name , value.desc } ;
if type ( tooltip [ 2 ] ) == " function " then
tooltip [ 2 ] = tooltip [ 2 ] ( ) ;
end
local p = self : AddEntry ( {
label = value_name ,
tooltip = tooltip ,
arrow = true
} , Parent ) ;
local values = value.values ;
if type ( values ) == " function " then
values = values ( { modName , " " , key } ) ;
end
for valKey , valLabel in ns.pairsByKeys ( values ) do
self : AddEntry ( {
label = valLabel ,
radio = valKey ,
keepShown = false ,
checked = function ( )
return ( ns.profile [ modName ] [ key ] == valKey ) ;
end ,
func = function ( self )
ns.option ( { modName , " " , key } , valKey ) ;
self : GetParent ( ) : Hide ( ) ;
end
} , p ) ;
end
elseif value.type == " group " then
local tooltip = { value.name , value.desc } ;
if type ( tooltip [ 2 ] ) == " function " then
tooltip [ 2 ] = tooltip [ 2 ] ( ) ;
end
local parent = self : AddEntry ( {
label = value_name ,
tooltip = tooltip ,
arrow = true ,
} , Parent ) ;
for _key , _value in pairsByAceOptions ( value.args ) do
tinsert ( info , _key )
self : AddConfigEntry ( modName , info , _key , _value , parent ) ;
end
elseif value.type == " range " then
-- coming soon
end
end
---@param modName string module name
---@param noTitle? boolean
function EasyMenu : AddConfig ( modName , noTitle )
local options , separator = ns.getModOptionTable ( modName ) ;
if noTitle == nil then
noTitle = false ;
elseif noTitle == true then
separator = true
end
if options then
for _ , optGrp in pairsByOptionGroup ( options ) do
if optGrp and type ( optGrp.args ) == " table " then
-- add group header
if separator then
self : AddEntry ( { separator = true } ) ;
else
if not noTitle then
self : AddEntry ( { label = L [ modName ] , title = true } ) ;
self : AddEntry ( { separator = true } ) ;
noTitle = false ;
end
separator = true
end
self : AddEntry ( { label = optGrp.name , title = true } ) ;
-- replace shared option entries
for key , value in pairs ( optGrp ) do
if ns.sharedOptions [ key ] then
local order = tonumber ( value ) ;
optGrp [ key ] = CopyTable ( ns.sharedOptions [ key ] ) ;
optGrp [ key ] . order = order ;
end
end
-- sort group table
for key , value in pairsByAceOptions ( optGrp.args ) do
local info = { key }
local hide = ( value.hidden == true ) or ( value.disabled == true ) or false ;
if not hide and type ( value.hidden ) == " function " then
hide = value.hidden ( info , " EasyMenu " ) ;
if hide == true or hide == " EasyMenu " then
hide = true ;
end
end
if not hide and type ( value.disabled ) == " function " then
hide = value.disabled ( info ) ;
end
if not hide then
self : AddConfigEntry ( modName , info , key , value ) ;
end
end
end
end
end
end
---@param level number
function EasyMenu : Refresh ( level )
-- local self = ns.EasyMenu
if level then
LDDM.UIDropDownMenu_Refresh ( self , nil , level ) ;
end
LDDM.UIDropDownMenu_RefreshAll ( self ) ;
end
function EasyMenu : InitializeMenu ( )
wipe ( self.menu ) ;
end
---@param parent? frame|string
---@param parentX? number
---@param parentY? number
function EasyMenu : ShowMenu ( parent , parentX , parentY )
if openTooltip then
ns.hideTooltip ( openTooltip ) ;
end
self : AddEntry ( { separator = true } ) ;
self : AddEntry ( { label = L [ " Close menu " ] , func = LibCloseDropDownMenus } ) ;
LDDM.EasyMenu ( self.menu , self , parent or " cursor " , parentX or 0 , parentY or 0 , " MENU " ) ;
end
end
-- ----------------------- --
-- DurationOrExpireDate --
-- ----------------------- --
---@param timeLeft number
---@param lastTime number
---@param durationTitle string
---@param expireTitle string
---@return string|osdate
---@return string
---@return string
function ns . DurationOrExpireDate ( timeLeft , lastTime , durationTitle , expireTitle )
local mod = " shift " ;
timeLeft = timeLeft or 0 ;
if ( type ( lastTime ) == " number " ) then
timeLeft = timeLeft - ( time ( ) - lastTime ) ;
end
if ( IsShiftKeyDown ( ) ) then
return date ( " %Y-%m-%d %H:%M " , time ( ) + timeLeft ) , expireTitle , mod ;
end
return SecondsToTime ( timeLeft ) , durationTitle , mod ;
end
-- ------------------------ --
-- clickOptions System --
-- ------------------------ --
do
ns.ClickOpts = { prefix = " ClickOpt: " } ;
local shared , values = { } , {
[ " __NONE " ] = ADDON_DISABLED ,
[ " _CLICK " ] = L [ " MouseBtn " ] ,
[ " _LEFT " ] = L [ " MouseBtnL " ] ,
[ " _RIGHT " ] = L [ " MouseBtnR " ] ,
[ " ALTCLICK " ] = L [ " ModKeyA " ] .. " + " .. L [ " MouseBtn " ] ,
[ " ALTLEFT " ] = L [ " ModKeyA " ] .. " + " .. L [ " MouseBtnL " ] ,
[ " ALTRIGHT " ] = L [ " ModKeyA " ] .. " + " .. L [ " MouseBtnR " ] ,
[ " SHIFTCLICK " ] = L [ " ModKeyS " ] .. " + " .. L [ " MouseBtn " ] ,
[ " SHIFTLEFT " ] = L [ " ModKeyS " ] .. " + " .. L [ " MouseBtnL " ] ,
[ " SHIFTRIGHT " ] = L [ " ModKeyS " ] .. " + " .. L [ " MouseBtnR " ] ,
[ " CTRLCLICK " ] = L [ " ModKeyC " ] .. " + " .. L [ " MouseBtn " ] ,
[ " CTRLLEFT " ] = L [ " ModKeyC " ] .. " + " .. L [ " MouseBtnL " ] ,
[ " CTRLRIGHT " ] = L [ " ModKeyC " ] .. " + " .. L [ " MouseBtnR " ] ,
} ;
function shared . OptionMenu ( self , button , modName )
if ( openTooltip ~= nil ) and ( openTooltip : IsShown ( ) ) then ns.hideTooltip ( openTooltip ) ; end
ns.EasyMenu : InitializeMenu ( ) ;
ns.EasyMenu : AddConfig ( modName ) ;
ns.EasyMenu : ShowMenu ( self ) ;
end
local sharedClickOptions = {
OptionMenu = { " ClickOptMenu " , " shared " , " OptionMenu " } , -- L["ClickOptMenu"]
OptionMenuCustom = { " ClickOptMenu " , " module " , " OptionMenu " } ,
OptionPanel = { " ClickOptPanel " , " namespace " , " ToggleBlizzOptionPanel " } , -- L["ClickOptPanel"]
CharacterInfo = { CHARACTER_INFO , " call " , { " ToggleCharacter " , " PaperDollFrame " } } ,
GarrisonReport = { GARRISON_LANDING_PAGE_TITLE , " call " , " GarrisonLandingPage_Toggle " } , --"ClickOptGarrReport"
Guild = { GUILD , " call " , " ToggleGuildFrame " } , -- "ClickOptGuild"
Currency = { CURRENCY , " call " , { " ToggleCharacter " , " TokenFrame " } } , -- "ClickOptCurrency"
QuestLog = { QUEST_LOG , " call " , " ToggleQuestLog " } -- "ClickOptQuestLog"
} ;
local iLabel , iSource , iFunction , iPrefix = 1 , 2 , 3 , 4 ;
function ns . ClickOpts . func ( self , button , modName )
local mod = ns.modules [ modName ] ;
if not ( mod and mod.onclick ) then return ; end
-- click(plan)A = combine modifier if pressed with named button (left,right)
-- click(panl)B = combine modifier if pressed with left or right mouse button without expliced check.
local clickA , clickB , action , actName = " " , " " , nil , nil ;
-- check modifier
if ( IsAltKeyDown ( ) ) then clickA = clickA .. " ALT " ; clickB = clickB .. " ALT " ; end
if ( IsShiftKeyDown ( ) ) then clickA = clickA .. " SHIFT " ; clickB = clickB .. " SHIFT " ; end
if ( IsControlKeyDown ( ) ) then clickA = clickA .. " CTRL " ; clickB = clickB .. " CTRL " ; end
-- no modifier used... add an undercore (for dropdown menu entry sorting)
if ( clickA == " " ) then clickA = clickA .. " _ " ; end
if ( clickB == " " ) then clickB = clickB .. " _ " ; end
-- check which mouse button is pressed
if ( button == " LeftButton " ) then
clickA = clickA .. " LEFT " ;
elseif ( button == " RightButton " ) then
clickA = clickA .. " RIGHT " ;
--elseif () then
-- clickA=clickA.."";
-- more mouse buttons?
end
-- planB
clickB = clickB .. " CLICK " ;
if ( mod.onclick [ clickA ] ) then
actName = mod.onclick [ clickA ] ;
action = mod.clickOptions [ actName ] ;
elseif ( mod.onclick [ clickB ] ) then
actName = mod.onclick [ clickB ] ;
action = mod.clickOptions [ actName ] ;
end
if action then
local func
if action [ iSource ] == " direct " then
func = action [ iFunction ] ;
elseif action [ iSource ] == " module " then
func = mod [ action [ iFunction ] ] ;
elseif action [ iSource ] == " namespace " then
func = ns [ action [ iFunction ] ] ;
elseif action [ iSource ] == " shared " then
func = shared [ action [ iFunction ] ] ;
elseif action [ iSource ] == " call " then
if type ( action [ iFunction ] ) == " table " then
if action [ iFunction ] [ 1 ] == " ToggleFrame " then
securecall ( " ToggleFrame " , _G [ action [ iFunction ] [ 2 ] ] ) ;
return ;
end
securecall ( unpack ( action [ iFunction ] ) ) ;
else
securecall ( action [ iFunction ] ) ;
end
return ;
end
if func then
func ( self , button , modName , actName ) ;
end
end
end
function ns . ClickOpts . update ( modName ) -- executed on event BE_UPDATE_CFG from active modules
-- name, desc, default, func
local mod = ns.modules [ modName ] ;
if not ( mod and type ( mod.clickOptions ) == " table " ) then return end
local hasOptions = false ;
mod.onclick = { } ;
mod.clickHints = { } ;
local order = mod.clickOptionsOrder or { } ;
if # order == 0 then
for actName , actData in ns.pairsByKeys ( mod.clickOptions ) do
if actData then
tinsert ( order , actName ) ;
end
end
end
for _ , actName in ipairs ( order ) do
local action = mod.clickOptions [ actName ] ;
local cfgKey = ns.ClickOpts . prefix .. actName ;
if mod.clickOptionsRename and mod.clickOptionsRename [ cfgKey ] then
local altKey = mod.clickOptionsRename [ cfgKey ] ;
if ns.profile [ modName ] ( altKey ) ~= nil then
ns.profile [ modName ] [ cfgKey ] = ns.profile [ modName ] [ altKey ] ;
ns.profile [ modName ] [ altKey ] = nil ;
end
end
local key = ns.profile [ modName ] [ cfgKey ] ;
if key and key ~= " __NONE " then
local functionType , func = action [ iSource ] ;
if functionType == " direct " then
func = action [ iFunction ] ;
elseif functionType == " module " then
func = mod [ action [ iFunction ] ] ;
elseif functionType == " namespace " then
func = ns [ action [ iFunction ] ] ;
elseif functionType == " shared " then
func = shared [ action [ iFunction ] ] ;
elseif functionType == " call " then
func = _G [ type ( action [ iFunction ] ) == " table " and action [ iFunction ] [ 1 ] or action [ iFunction ] ] ;
end
if func and type ( func ) == " function " then
mod.onclick [ key ] = actName ;
tinsert ( mod.clickHints , ns.LC . color ( " copper " , values [ key ] ) .. " || " .. ns.LC . color ( " green " , L [ action [ iLabel ] ] ) ) ;
hasOptions = true ;
end
end
end
return hasOptions ;
end
function ns . ClickOpts . createOptions ( modName , modOptions ) -- executed by ns.Options_AddModuleOptions()
local mod = ns.modules [ modName ] ;
if not ( mod and type ( mod.clickOptions ) == " table " ) then return end
-- generate option panel entries
for cfgKey , clickOpts in ns.pairsByKeys ( mod.clickOptions ) do
if modOptions.ClickOpts == nil then
modOptions.ClickOpts = { } ;
end
if type ( clickOpts ) == " string " and sharedClickOptions [ clickOpts ] then
-- copy shared entry
mod.clickOptions [ cfgKey ] = sharedClickOptions [ clickOpts ] ;
clickOpts = mod.clickOptions [ cfgKey ] ;
end
if clickOpts then
local optKey = ns.ClickOpts . prefix .. cfgKey ;
-- ace option table entry
modOptions.ClickOpts [ optKey ] = {
type = " select " ,
name = L [ clickOpts [ iLabel ] ] ,
desc = L [ " ClickOptDesc " ] .. " " .. L [ clickOpts [ iLabel ] ] ,
values = values
} ;
end
end
end
function ns . ClickOpts . ttAddHints ( tt , name , ttColumns , entriesPerLine )
local _lines = { } ;
if ( type ( entriesPerLine ) ~= " number " ) then entriesPerLine = 1 ; end
if ( ns.modules [ name ] . clickHints ) then
for i = 1 , # ns.modules [ name ] . clickHints , entriesPerLine do
if ( ns.modules [ name ] . clickHints [ i ] ) then
tinsert ( _lines , { } ) ;
for I = 1 , entriesPerLine do
if ( ns.modules [ name ] . clickHints [ i + I - 1 ] ) then
tinsert ( _lines [ # _lines ] , ns.modules [ name ] . clickHints [ i + I - 1 ] ) ;
end
end
end
end
end
for i , v in ipairs ( _lines ) do
if ( v ) then
v = tconcat ( v , " - " ) ;
if ( type ( tt.SetCell ) == " function " ) then
local line = tt : AddLine ( ) ;
tt : SetCell ( line , 1 , v , nil , " LEFT " , ttColumns or 0 ) ;
else
tt : AddLine ( v ) ;
end
end
end
end
function ns . ClickOpts . getShared ( name )
return sharedClickOptions [ name ] ;
end
function ns . ClickOpts . addDefaults ( module , key , value )
assert ( module ) ;
local tKey = type ( key ) ;
if tKey == " table " then
for k , v in pairs ( key ) do
ns.ClickOpts . addDefaults ( module , k , v ) ;
end
elseif tKey == " string " then
module.config_defaults [ ns.ClickOpts . prefix .. key ] = value ;
end
end
end
-- --------------------------------------- --
-- shared data for questlog & world quests --
-- --------------------------------------- --
do
if ns.client_version >= 9 then
-- TODO: shadowlands update -- LE_QUEST_TAG_TYPE_ vs. Enum.QuestTag
local Enum = Enum
if not ( Enum and Enum.QuestTag ) then
if not Enum.QuestTag then
Enum.QuestTag = { }
end
Enum.QuestTag . Group = LE_QUEST_TAG_TYPE_GROUP or QUEST_TAG_GROUP or 1 -- "grp"
Enum.QuestTag . PvP = LE_QUEST_TAG_TYPE_PVP or QUEST_TAG_PVP or 41 -- "pvp"
Enum.QuestTag . Dungeon = LE_QUEST_TAG_TYPE_DUNGEON or QUEST_TAG_DUNGEON or 81 -- "d"
Enum.QuestTag . Heroic = LE_QUEST_TAG_TYPE_HEROIC or QUEST_TAG_HEROIC or 85 -- "hc" -- missing in bfa
Enum.QuestTag . Raid = LE_QUEST_TAG_TYPE_RAID or QUEST_TAG_RAID or 63 -- "r"
Enum.QuestTag . Raid10 = LE_QUEST_TAG_TYPE_RAID10 or QUEST_TAG_RAID10 or 88 -- "r10" -- missing in bfa
Enum.QuestTag . Raid25 = LE_QUEST_TAG_TYPE_RAID25 or QUEST_TAG_RAID25 or 89 -- "r25" -- missing in bfa
Enum.QuestTag . Scenario = LE_QUEST_TAG_TYPE_SCENARIO or QUEST_TAG_SCENARIO or 98 -- "s" -- missing in bfa
Enum.QuestTag . Account = LE_QUEST_TAG_TYPE_ACCOUNT or QUEST_TAG_ACCOUNT or 102 -- "a" -- missing in bfa
Enum.QuestTag . Legendary = LE_QUEST_TAG_TYPE_LEGENDARY or QUEST_TAG_LEGENDARY or 83 -- "leg" -- missing in bfa
end
if Enum.QuestTag . Pvp then -- pre shadowlands
Enum.QuestTag . PvP = Enum.QuestTag . Pvp ;
end
ns.questTags = {
[ Enum.QuestTag . Group ] = L [ " QuestTagGRP " ] ,
[ Enum.QuestTag . PvP or Enum.QuestTag . Pvp ] = { L [ " QuestTagPVP " ] , " violet " } ,
[ Enum.QuestTag . Dungeon ] = L [ " QuestTagND " ] ,
[ Enum.QuestTag . Heroic ] = L [ " QuestTagHD " ] ,
[ Enum.QuestTag . Raid ] = L [ " QuestTagR " ] ,
[ Enum.QuestTag . Raid10 ] = L [ " QuestTagR " ] .. 10 ,
[ Enum.QuestTag . Raid25 ] = L [ " QuestTagR " ] .. 25 ,
[ Enum.QuestTag . Scenario ] = L [ " QuestTagS " ] ,
[ Enum.QuestTag . Account ] = L [ " QuestTagACC " ] ,
[ Enum.QuestTag . Legendary ] = { L [ " QuestTagLEG " ] , " orange " } ,
TRADE_SKILLS = { L [ " QuestTagTS " ] , " green " } ,
WORLD_QUESTS = { L [ " QuestTagWQ " ] , " yellow " } ,
DUNGEON_MYTHIC = { L [ " QuestTagMD " ] , " ltred " }
} ;
ns.questTagsLong = {
[ Enum.QuestTag . Group ] = GROUP ,
[ Enum.QuestTag . PvP or Enum.QuestTag . Pvp ] = { PVP , " violet " } ,
[ Enum.QuestTag . Dungeon ] = LFG_TYPE_DUNGEON ,
[ Enum.QuestTag . Heroic ] = LFG_TYPE_HEROIC_DUNGEON ,
[ Enum.QuestTag . Raid ] = LFG_TYPE_RAID ,
[ Enum.QuestTag . Raid10 ] = LFG_TYPE_RAID .. " (10) " ,
[ Enum.QuestTag . Raid25 ] = LFG_TYPE_RAID .. " (25) " ,
[ Enum.QuestTag . Scenario ] = TRACKER_HEADER_SCENARIO ,
[ Enum.QuestTag . Account ] = ITEM_BIND_TO_ACCOUNT ,
[ Enum.QuestTag . Legendary ] = TRACKER_HEADER_CAMPAIGN_QUESTS ,
TRADE_SKILLS = { TRADE_SKILLS , " green " } ,
WORLD_QUESTS = { WORLD_QUEST_BANNER , " yellow " } ,
DUNGEON_MYTHIC = { LFG_TYPE_DUNGEON .. " ( " .. PLAYER_DIFFICULTY6 .. " ) " , " ltred " }
}
else
local QUEST_TAG_GROUP = LE_QUEST_TAG_TYPE_GROUP or QUEST_TAG_GROUP or " grp " -- missing in bfa
local QUEST_TAG_PVP = LE_QUEST_TAG_TYPE_PVP or QUEST_TAG_PVP or " pvp "
local QUEST_TAG_DUNGEON = LE_QUEST_TAG_TYPE_DUNGEON or QUEST_TAG_DUNGEON or " d "
local QUEST_TAG_HEROIC = LE_QUEST_TAG_TYPE_HEROIC or QUEST_TAG_HEROIC or " hc " -- missing in bfa
local QUEST_TAG_RAID = LE_QUEST_TAG_TYPE_RAID or QUEST_TAG_RAID or " r "
local QUEST_TAG_RAID10 = LE_QUEST_TAG_TYPE_RAID10 or QUEST_TAG_RAID10 or " r10 " -- missing in bfa
local QUEST_TAG_RAID25 = LE_QUEST_TAG_TYPE_RAID25 or QUEST_TAG_RAID25 or " r25 " -- missing in bfa
local QUEST_TAG_SCENARIO = LE_QUEST_TAG_TYPE_SCENARIO or QUEST_TAG_SCENARIO or " s " -- missing in bfa
local QUEST_TAG_ACCOUNT = LE_QUEST_TAG_TYPE_ACCOUNT or QUEST_TAG_ACCOUNT or " a " -- missing in bfa
local QUEST_TAG_LEGENDARY = LE_QUEST_TAG_TYPE_LEGENDARY or QUEST_TAG_LEGENDARY or " leg " -- missing in bfa
ns.questTags = {
[ QUEST_TAG_GROUP ] = L [ " QuestTagGRP " ] ,
[ QUEST_TAG_PVP ] = { L [ " QuestTagPVP " ] , " violet " } ,
[ QUEST_TAG_DUNGEON ] = L [ " QuestTagND " ] ,
[ QUEST_TAG_HEROIC ] = L [ " QuestTagHD " ] ,
[ QUEST_TAG_RAID ] = L [ " QuestTagR " ] ,
[ QUEST_TAG_RAID10 ] = L [ " QuestTagR " ] .. 10 ,
[ QUEST_TAG_RAID25 ] = L [ " QuestTagR " ] .. 25 ,
[ QUEST_TAG_SCENARIO ] = L [ " QuestTagS " ] ,
[ QUEST_TAG_ACCOUNT ] = L [ " QuestTagACC " ] ,
[ QUEST_TAG_LEGENDARY ] = { L [ " QuestTagLEG " ] , " orange " } ,
TRADE_SKILLS = { L [ " QuestTagTS " ] , " green " } ,
WORLD_QUESTS = { L [ " QuestTagWQ " ] , " yellow " } ,
DUNGEON_MYTHIC = { L [ " QuestTagMD " ] , " ltred " }
} ;
end
end
-- -----------------
-- text bar
-- ----------------
-- num, {<max>,<cur>[,<rest>]},{<max>,<cur>[,<rest>]}
---@param num number
---@param values table
---@param colors table
---@param Char string
---@return string
function ns . textBar ( num , values , colors , Char )
local iMax , iMin , iRest = 1 , 2 , 3 ;
values [ iRest ] = ( values [ iRest ] and values [ iRest ] > 0 ) and values [ iRest ] or 0 ;
if values [ iMax ] == 1 then
values [ iMax ] , values [ iMin ] , values [ iRest ] = values [ iMax ] * 100 , values [ iMin ] * 100 , values [ iRest ] * 100 ;
end
local Char , resting , ppc , earned , tonextlvl = Char or " = " , 0 ;
ppc = values [ iMax ] / num ; -- percent per character
earned = ns.round ( values [ iMin ] / ppc ) ; -- number of characters of earned experience
if values [ iMin ] < 100 then
resting = ns.round ( values [ iRest ] / ppc ) ; -- number of characters of resting bonus
end
tonextlvl = num - ( earned + resting ) ; -- number of characters of open experience to the next level
return ns.LC . color ( colors [ iMin ] or " white " , Char : rep ( earned ) )
.. ( resting > 0 and ns.LC . color ( colors [ iRest ] or " white " , Char : rep ( resting ) ) or " " )
.. ( tonextlvl > 0 and ns.LC . color ( colors [ iMax ] or " white " , Char : rep ( tonextlvl ) ) or " " ) ;
end
-- -------------------------------
-- Retail / Classic / BC Classic compatibility
-- -------------------------------
ns.C_QuestLog_GetInfo = ( C_QuestLog and C_QuestLog.GetInfo ) or function ( questLogIndex )
local title , level , suggestedGroup , isHeader , isCollapsed , isComplete , frequency , questID , startEvent , displayQuestID , isOnMap , hasLocalPOI , isTask , isStory , isHidden , isScaling = GetQuestLogTitle ( questLogIndex ) ;
if type ( suggestedGroup ) == " string " then
suggestedGroup = tonumber ( suggestedGroup ) or 0 ; -- problem on bc classic client?
end
return {
campaignID = 0 , -- dummy
difficultyLevel = 0 , -- dummy
frequency = frequency ,
hasLocalPOI = hasLocalPOI ,
isAutoComplete = false , -- dummy
isBounty = false , -- dummy
isCollapsed = isCollapsed ,
isComplete = isComplete , -- missing in C_QuestLog.GetInfo ?
isHeader = isHeader ,
isHidden = isHidden ,
isOnMap = isOnMap ,
isScaling = isScaling ,
isStory = isStory ,
isTask = isTask ,
level = level ,
overridesSortOrder = false , -- dummy
questID = questID ,
questLogIndex = questLogIndex ,
readyForTranslation = false , -- dummy
startEvent = startEvent ,
suggestedGroup = suggestedGroup ,
title = title ,
} ;
end
ns.C_QuestLog_GetQuestTagInfo = ( C_QuestLog and C_QuestLog.GetQuestTagInfo ) or function ( questID )
local tagID , tagName , worldQuestType , rarity , isElite , tradeskillLineIndex = GetQuestTagInfo ( questID ) ;
return {
tagID = tagID ,
tagName = tagName ,
-- not found in returned table from C_QuestLog.GetQuestTagInfo
worldQuestType = worldQuestType ,
rarity = rarity ,
isElite = isElite ,
tradeskillLineIndex = tradeskillLineIndex
} ;
end
ns.C_BattleNet_GetFriendAccountInfo = ( C_BattleNet and C_BattleNet.GetFriendAccountInfo ) or function ( friendIndex )
local accountInfo = { gameAccountInfo = { } } ;
accountInfo.bnetAccountID ,
accountInfo.accountName ,
accountInfo.battleTag ,
accountInfo.isBattleTagFriend ,
accountInfo.gameAccountInfo . characterName ,
accountInfo.gameAccountInfo . gameAccountID ,
accountInfo.gameAccountInfo . clientProgram ,
accountInfo.gameAccountInfo . isOnline ,
accountInfo.lastOnlineTime ,
accountInfo.isAFK ,
accountInfo.isDND ,
accountInfo.customMessage ,
accountInfo.note ,
accountInfo.isFriend ,
accountInfo.customMessageTime ,
accountInfo.gameAccountInfo . wowProjectID ,
accountInfo.rafLinkType ,
accountInfo.gameAccountInfo . canSummon ,
accountInfo.isFavorite ,
accountInfo.gameAccountInfo . isWowMobile
= BNGetFriendInfo ( friendIndex )
return accountInfo ;
end