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.
378 lines
12 KiB
378 lines
12 KiB
-- VgerCore by Vger-Azjol-Nerub
|
|
-- www.vgermods.com
|
|
-- © 2006-2021 Travis Spomer. This mod is released under the Creative Commons Attribution-NonCommercial-NoDerivs 3.0 license.
|
|
--
|
|
-- Version 1.0.14 -- IndexOf
|
|
local VgerCoreThisVersion = 1.14
|
|
--
|
|
-- 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?
|
|
VgerCore.IsClassic = (WOW_PROJECT_ID == WOW_PROJECT_CLASSIC)
|
|
VgerCore.IsBurningCrusade = (WOW_PROJECT_ID == WOW_PROJECT_BURNING_CRUSADE_CLASSIC) -- includes pre-patch
|
|
VgerCore.IsShadowlands = (select(4, GetBuildInfo()) >= 90000)
|
|
|
|
VgerCore.DeathKnightsExist = VgerCore.IsShadowlands
|
|
VgerCore.MonksExist = VgerCore.IsShadowlands
|
|
VgerCore.DemonHuntersExist = VgerCore.IsShadowlands
|
|
VgerCore.SpecsExist = VgerCore.IsShadowlands
|
|
VgerCore.RangedSlotExists = VgerCore.IsClassic or VgerCore.IsBurningCrusade
|
|
VgerCore.ArtifactsExist = VgerCore.IsShadowlands
|
|
VgerCore.EquipmentSetsExist = VgerCore.IsShadowlands
|
|
|
|
-- 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
|
|
|