-- VgerCore by Vger-Azjol-Nerub -- www.vgermods.com -- © 2006-2022 Travis Spomer. This mod is released under the Creative Commons Attribution-NonCommercial-NoDerivs 3.0 license. -- -- Version 1.0.17 -- IsWrath should still work on Burning Crusade Classic local VgerCoreThisVersion = 1.17 -- -- VgerCore contains functionality that is shared by Vger's mods. -- It can be used as a standalone add-on, or embedded within other mods. -- ------------------------------------------------------------ local InitializeOrUpgrade = (not VgerCore) or (not VgerCore.Version) or (VgerCore.Version < VgerCoreThisVersion) -- If the currently loaded version of VgerCore isn't as good as this one, load the new one. if InitializeOrUpgrade then VgerCore = {} VgerCore.Version = VgerCoreThisVersion -- What version is this? local BuildNumber = select(4, GetBuildInfo()) VgerCore.IsClassic = (WOW_PROJECT_ID == WOW_PROJECT_CLASSIC) VgerCore.IsBurningCrusade = (WOW_PROJECT_ID == WOW_PROJECT_BURNING_CRUSADE_CLASSIC and LE_EXPANSION_LEVEL_CURRENT == LE_EXPANSION_BURNING_CRUSADE) -- includes pre-patch VgerCore.IsWrath = (WOW_PROJECT_ID == WOW_PROJECT_BURNING_CRUSADE_CLASSIC and LE_EXPANSION_WRATH_OF_THE_LICH_KING and LE_EXPANSION_LEVEL_CURRENT >= LE_EXPANSION_WRATH_OF_THE_LICH_KING) or (WOW_PROJECT_WRATH_CLASSIC and WOW_PROJECT_ID == WOW_PROJECT_WRATH_CLASSIC) -- includes pre-patch VgerCore.IsMainline = BuildNumber >= 90000 VgerCore.IsDragonflight = VgerCore.IsMainline and BuildNumber >= 100000 VgerCore.IsShadowlands = VgerCore.IsMainline and BuildNumber >= 90000 VgerCore.DeathKnightsExist = VgerCore.IsWrath or VgerCore.IsMainline VgerCore.MonksExist = VgerCore.IsMainline VgerCore.DemonHuntersExist = VgerCore.IsMainline VgerCore.EvokersExist = VgerCore.IsDragonflight VgerCore.SpecsExist = VgerCore.IsMainline VgerCore.RangedSlotExists = VgerCore.IsClassic or VgerCore.IsBurningCrusade or VgerCore.IsWrath -- or VgerCore.IsCataclysm VgerCore.ArtifactsExist = VgerCore.IsMainline VgerCore.EquipmentSetsExist = VgerCore.IsWrath or VgerCore.IsMainline -- Common colors VgerCore.Color = {} VgerCore.Color.Reset = "|r" VgerCore.Color.Blue = "|cff8ec3e6" VgerCore.Color.BlueR = 142 / 255 VgerCore.Color.BlueG = 195 / 255 VgerCore.Color.BlueB = 230 / 255 VgerCore.Color.DarkBlue = "|cff6a92ac" VgerCore.Color.DarkBlueR = 106 / 255 VgerCore.Color.DarkBlueG = 146 / 255 VgerCore.Color.DarkBlueB = 172 / 255 VgerCore.Color.Green = "|cffb4fe2c" VgerCore.Color.GreenR = 180 / 255 VgerCore.Color.GreenG = 255 / 255 VgerCore.Color.GreenB = 44 / 255 VgerCore.Color.Orange = "|cfffecf38" VgerCore.Color.OrangeR = 255 / 255 VgerCore.Color.OrangeG = 207 / 255 VgerCore.Color.OrangeB = 56 / 255 VgerCore.Color.Lemon = "|cfffffdd0" VgerCore.Color.LemonR = 255 / 255 VgerCore.Color.LemonG = 253 / 255 VgerCore.Color.LemonB = 208 / 255 VgerCore.Color.Salmon = "|cfffe8460" VgerCore.Color.SalmonR = 255 / 255 VgerCore.Color.SalmonG = 132 / 255 VgerCore.Color.SalmonB = 96 / 255 VgerCore.Color.Beige = "|cffe0dec8" VgerCore.Color.BeigeR = 224 / 255 VgerCore.Color.BeigeG = 222 / 255 VgerCore.Color.BeigeB = 200 / 255 VgerCore.Color.White = "|cffffffff" VgerCore.Color.WhiteR = 255 / 255 VgerCore.Color.WhiteG = 255 / 255 VgerCore.Color.WhiteB = 255 / 255 VgerCore.Color.Grey = "|cff909090" VgerCore.Color.GreyR = 144 / 255 VgerCore.Color.GreyG = 144 / 255 VgerCore.Color.GreyB = 144 / 255 VgerCore.Color.Silver = "|cffc0c0c0" VgerCore.Color.SilverR = 192 / 255 VgerCore.Color.SilverG = 192 / 255 VgerCore.Color.SilverB = 192 / 255 VgerCore.Color.Black= "|cff000000" VgerCore.Color.BlackR = 0 / 255 VgerCore.Color.BlackG = 0 / 255 VgerCore.Color.BlackB = 0 / 255 VgerCore.MoneyColor = {} VgerCore.MoneyColor.Gold = "|cffecda90" VgerCore.MoneyColor.Silver = "|cffd7d5d8" VgerCore.MoneyColor.Copper = "|cffe2ad8e" -- Displays a standard VgerCore message. function VgerCore.Message(Text) if DEFAULT_CHAT_FRAME then DEFAULT_CHAT_FRAME:AddMessage(VgerCore.Color.Orange .. tostring(Text)) else message(VgerCore.Color.Orange .. tostring(Text)) end end -- Displays a bunch of messages from one string, separated by newlines. -- Notes: -- * Colors specified at the beginning of Text will not propagate to subsequent lines of Text. -- Use the optional Color parameter instead. -- * Empty lines will be skipped. Add a space to the line if you want it to be printed. function VgerCore.MultilineMessage(Text, Color) local Line local ColorString = Color if not ColorString then ColorString = "" end for Line in string.gmatch(Text, "[^\r\n]+") do VgerCore.Message(ColorString .. Line) end end -- Displays a large VgerCore message. function VgerCore.BigMessage(Text) if UIErrorsFrame then UIErrorsFrame:AddMessage(tostring(Text), VgerCore.Color.GreenR, VgerCore.Color.GreenG, VgerCore.Color.GreenB, 1.0, 4.0) end if DEFAULT_CHAT_FRAME then DEFAULT_CHAT_FRAME:AddMessage(VgerCore.Color.Green .. tostring(Text)) end end -- Displays a VgerCore error message if the condition is false. function VgerCore.Assert(Condition, Message) -- Possibility: call the assert() function to get a callstack and integrate with mods such as Swatter. if not Condition then VgerCore.Fail(Message) end end -- Displays a VgerCore error message. function VgerCore.Fail(Message) VgerCore.Message(VgerCore.Color.Salmon .. "ERROR: " .. VgerCore.Color.White .. tostring(Message)) end -- Hooks an insecure function. Similar to the base WoW API's hooksecurefunc. The hook function will be run -- after the original function to be hooked, unless Pre is true, in which case the hook will be run first. -- Valid usage: -- VgerCore.HookInsecureFunction(Object, FunctionName, Hook, Pre) -- VgerCore.HookInsecureFunction(FunctionName, Hook, Pre) function VgerCore.HookInsecureFunction(arg1, arg2, arg3, arg4) local TypeOfObject = type(arg1) local OldFunction if TypeOfObject == "table" then -- Object, FunctionName, Hook, Pre OldFunction = arg1[arg2] if OldFunction then arg1[arg2] = VgerCore.CreateHookFunction(OldFunction, arg3, arg4) else VgerCore.Fail("VgerCore.HookInsecureFunction: could not find member function '" .. arg2 .. "'.") end elseif TypeOfObject == "string" then -- FunctionName, Hook, Pre OldFunction = getglobal(arg1) if OldFunction then local Environment = getfenv() Environment[arg1] = VgerCore.CreateHookFunction(OldFunction, arg2, arg3) else VgerCore.Fail("VgerCore.HookInsecureFunction: could not find function '" .. arg1 .. "'.") end else VgerCore.Fail("VgerCore.HookInsecureFunction argument 1 must be table or string, not " .. TypeOfObject .. ".") end end -- Hooks an insecure script handler. Works just like HookInsecureFunction(Object, FunctionName, Hook, Pre), except that -- instead of a function name, a script name is passed. function VgerCore.HookInsecureScript(Object, ScriptName, Hook, Pre) local OldFunction = Object:GetScript(ScriptName) if OldFunction then Object:SetScript(ScriptName, VgerCore.CreateHookFunction(OldFunction, Hook, Pre)) else Object:SetScript(ScriptName, Hook) end end -- Internal function used by HookInsecureFunction. function VgerCore.CreateHookFunction(OldFunction, Hook, Pre) if Pre then return function(...) Hook(...) return OldFunction(...) end else return function(...) local ReturnValue = OldFunction(...) Hook(...) return ReturnValue end end end -- Executes a chat command just as if it were typed in the chat window. -- Returns true if successful, or false if not (primarily if the command is a secure function, such as /cast). function VgerCore.ExecuteChatCommand(Command) local EditBox = DEFAULT_CHAT_FRAME.editBox if not EditBox then return false end -- First, make sure that this command is okay. local _, _, SlashCommand = strfind(Command, "^(/%w+) ") if SlashCommand then if IsSecureCmd(SlashCommand) then VgerCore.Fail(SlashCommand .. " is a secure command and cannot be run automatically.") return false end end -- Now, execute the chat command. local PreviousText = EditBox:GetText() EditBox:SetText(Command) ChatEdit_SendText(EditBox) EditBox:SetText(PreviousText) return true end -- Runs a macro. -- Returns true if successful, or false if not. function VgerCore.RunMacro(MacroName) -- First, get the text of the macro. local _, _, Script, _ = GetMacroInfo(MacroName) if not Script then return false end -- Then, execute each line individually. Ignore comments marked with # or -. local Line for Line in string.gmatch(Script, "[^\n]+") do local FirstChar = strsub(Line, 1, 1) if FirstChar ~= "#" and FirstChar ~= "-" then VgerCore.ExecuteChatCommand(Line) end end return true end -- Returns true if the user is in a Battleground, or false if not. function VgerCore.IsInBattleground() local Battleground for Battleground = 1, GetMaxBattlefieldID() do if GetBattlefieldStatus(Battleground) == "active" then return true end end return false end -- Comparer function for use in table.sort that sorts strings alphabetically, ignoring case. function VgerCore.CaseInsensitiveComparer(a, b) return strlower(a) < strlower(b) end -- Returns a six-digit hex string for three RGB values 0-1. function VgerCore.RGBToHex(r, g, b) r = r <= 1 and r >= 0 and r or 0 g = g <= 1 and g >= 0 and g or 0 b = b <= 1 and b >= 0 and b or 0 return format("%02x%02x%02x", r * 255, g * 255, b * 255) end -- Returns RGB values 0-1 for a six-digit hex string, or nil if unsuccessful. function VgerCore.HexToRGB(hex) if not hex or strlen(hex) ~= 6 then return end local r, g, b = strsub(hex, 1, 2), strsub(hex, 3, 4), strsub(hex, 5, 6) r, g, b = r or 0, g or 0, b or 0 return tonumber(r, 16) / 255, tonumber(g, 16) / 255, tonumber(b, 16) / 255 end -- Same as strfind, but finds the last occurrence of a substring. The substring to find must be -- no more than two characters. function VgerCore.StringFindReverse(str, find) local FindLen = strlen(find) VgerCore.Assert(FindLen == 1 or FindLen == 2, "The substring to find must be one or two characters in length.") local FindByte, FindByte2 = strbyte(find, 1, 2) local StringLength = strlen(str) local i for i = StringLength, 1, -1 do if strbyte(str, i) == FindByte and (FindByte2 == nil or strbyte(str, i + 1) == FindByte2) then return i end end return nil end -- Returns a string representation of a number to a maximum of one decimal place. If the number passed is nil, nil is returned. function VgerCore.FormatShortDecimal(Number) if Number == nil then return nil elseif Number >= 1000 then return BreakUpLargeNumbers(floor(Number + .5)) elseif abs(Number - floor(Number)) < .0001 then return tostring(Number) else return format("%.1f", Number) end end -- Returns a string representation of a number to a maximum of one decimal place. If the number is long, it is shortened using -- an abbreviation like "12.3k" so that it fits in a small space. If the number passed is nil, nil is returned. function VgerCore.FormatCompactDecimal(Number) if Number == nil then return nil elseif Number >= 1000000000 then return format("%.1fb", Number / 1000000000) elseif Number >= 1000000 then return format("%.1fm", Number / 1000000) elseif Number >= 10000 then return format("%.1fk", Number / 1000) elseif Number >= 1000 then return BreakUpLargeNumbers(floor(Number + .5)) elseif abs(Number - floor(Number)) < .0001 then return tostring(Number) else return format("%.1f", Number) end end -- Returns a string representation of a number with no decimal places. If the number passed is nil, nil is returned. function VgerCore.FormatInteger(Number) if Number == nil then return nil else return BreakUpLargeNumbers(floor(Number + .5)) end end function VgerCore.ThrottleDelayCore(Duration, Immediate, Func, arg1) if arg1 ~= nil then VgerCore.Fail("Throttle and Delay don't support functions with arguments!") end local ThrottleUntil, WasThrottled local TimerCallback = function() Func() ThrottleUntil = nil WasThrottled = nil end local Throttled = function() if (not Immediate) or (ThrottleUntil ~= nil and GetTime() < ThrottleUntil) then -- We were called too soon. -- Or, if Immediate is true, then delay the first call too. if not WasThrottled then if ThrottleUntil == nil then ThrottleUntil = GetTime() + Duration end C_Timer.After(Duration, TimerCallback) WasThrottled = true end else -- It's been long enough, or this is the first call of the sequence. ThrottleUntil = GetTime() + Duration Func() end end return Throttled end -- Returns a version of Function that is called no more often than Duration (in sec). function VgerCore.Throttle(Duration, Func, arg1) return VgerCore.ThrottleDelayCore(Duration, true, Func, arg1) end -- Returns a version of Function that delays its own execution by Duration (in sec), and also -- prevents execution more than once in the same duration. function VgerCore.Delay(Duration, Func, arg1) return VgerCore.ThrottleDelayCore(Duration, false, Func, arg1) end -- Returns the index of an item in a table, or nil if it's not in the table. -- (Keep in mind that this requires iteration!) function VgerCore.IndexOf(Table, Item) if not Item then VgerCore.Fail("Can't find the index of nothing.") end local ThisIndex, ThisItem for ThisIndex, ThisItem in pairs(Table) do if ThisItem == Item then return ThisIndex end end end ------------------------------------------------------------ end -- if InitializeOrUpgrade