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.
2387 lines
79 KiB
2387 lines
79 KiB
|
|
-- ====================================== --
|
|
-- 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
|
|
|
|
|