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.
3745 lines
124 KiB
3745 lines
124 KiB
local _, addon = ...
|
|
local API = addon.API;
|
|
local L = addon.L;
|
|
local CallbackRegistry = addon.CallbackRegistry;
|
|
|
|
local tonumber = tonumber;
|
|
local match = string.match;
|
|
local format = string.format;
|
|
local gsub = string.gsub;
|
|
local tinsert = table.insert;
|
|
local tremove = table.remove;
|
|
local floor = math.floor;
|
|
local sqrt = math.sqrt;
|
|
local time = time;
|
|
local unpack = unpack;
|
|
local GetCVarBool = C_CVar.GetCVarBool;
|
|
local CreateFrame = CreateFrame;
|
|
local securecallfunction = securecallfunction;
|
|
|
|
local function Nop(...)
|
|
end
|
|
API.Nop = Nop;
|
|
|
|
do -- Table
|
|
local function Mixin(object, ...)
|
|
for i = 1, select("#", ...) do
|
|
local mixin = select(i, ...)
|
|
for k, v in pairs(mixin) do
|
|
object[k] = v;
|
|
end
|
|
end
|
|
return object
|
|
end
|
|
API.Mixin = Mixin;
|
|
|
|
local function CreateFromMixins(...)
|
|
return Mixin({}, ...)
|
|
end
|
|
API.CreateFromMixins = CreateFromMixins;
|
|
|
|
|
|
local function RemoveValueFromList(tbl, v)
|
|
for i = 1, #tbl do
|
|
if tbl[i] == v then
|
|
tremove(tbl, i);
|
|
return true
|
|
end
|
|
end
|
|
end
|
|
API.RemoveValueFromList = RemoveValueFromList;
|
|
|
|
|
|
local function ReverseList(list)
|
|
if not list then return end;
|
|
local tbl = {};
|
|
local n = 0;
|
|
for i = #list, 1, -1 do
|
|
n = n + 1;
|
|
tbl[n] = list[i];
|
|
end
|
|
return tbl
|
|
end
|
|
API.ReverseList = ReverseList;
|
|
|
|
local function CopyTable(tbl)
|
|
--Blizzard TableUtil.lua
|
|
if not tbl then return; end;
|
|
local copy = {};
|
|
for k, v in pairs(tbl) do
|
|
if type(v) == "table" then
|
|
copy[k] = CopyTable(v);
|
|
else
|
|
copy[k] = v;
|
|
end
|
|
end
|
|
return copy;
|
|
end
|
|
API.CopyTable = CopyTable;
|
|
end
|
|
|
|
do -- String
|
|
local function GetCreatureIDFromGUID(guid)
|
|
local id = match(guid, "Creature%-0%-%d*%-%d*%-%d*%-(%d*)");
|
|
if id then
|
|
return tonumber(id)
|
|
end
|
|
end
|
|
API.GetCreatureIDFromGUID = GetCreatureIDFromGUID;
|
|
|
|
local function GetVignetteIDFromGUID(guid)
|
|
local id = match(guid, "Vignette%-0%-%d*%-%d*%-%d*%-(%d*)");
|
|
if id then
|
|
return tonumber(id)
|
|
end
|
|
end
|
|
API.GetVignetteIDFromGUID = GetVignetteIDFromGUID;
|
|
|
|
local function GetWaypointFromText(text)
|
|
local uiMapID, x, y = match(text, "|Hworldmap:(%d*):(%d*):(%d*)|h");
|
|
if uiMapID and x and y then
|
|
return tonumber(uiMapID), tonumber(x), tonumber(y)
|
|
end
|
|
end
|
|
API.GetWaypointFromText = GetWaypointFromText;
|
|
|
|
|
|
local UnitGUID = UnitGUID;
|
|
|
|
local function GetUnitCreatureID(unit)
|
|
local guid = UnitGUID(unit);
|
|
if guid then
|
|
return GetCreatureIDFromGUID(guid)
|
|
end
|
|
end
|
|
API.GetUnitCreatureID = GetUnitCreatureID;
|
|
|
|
local ValidUnitTypes = {
|
|
Creature = true,
|
|
Pet = true,
|
|
GameObject = true,
|
|
Vehicle = true,
|
|
};
|
|
|
|
local function GetUnitIDGeneral(unit)
|
|
local guid = UnitGUID(unit);
|
|
if guid then
|
|
local unitType, id = match(guid, "(%a+)%-0%-%d*%-%d*%-%d*%-(%d*)");
|
|
if id and unitType and ValidUnitTypes[unitType] then
|
|
return tonumber(id)
|
|
end
|
|
end
|
|
end
|
|
API.GetUnitIDGeneral = GetUnitIDGeneral;
|
|
|
|
local function GetGlobalObject(objNameKey)
|
|
--Get object via string "FrameName.Key1.Key2"
|
|
local obj = _G;
|
|
|
|
for k in string.gmatch(objNameKey, "%w+") do
|
|
obj = obj[k];
|
|
if not obj then
|
|
return
|
|
end
|
|
end
|
|
|
|
return obj
|
|
end
|
|
API.GetGlobalObject = GetGlobalObject;
|
|
|
|
local function JoinText(delimiter, l, r)
|
|
if l and r then
|
|
return l..delimiter..r
|
|
else
|
|
return l or r
|
|
end
|
|
end
|
|
API.JoinText = JoinText;
|
|
|
|
|
|
function API.StringTrim(text)
|
|
if text then
|
|
text = gsub(text, "^(%s+)", "");
|
|
text = gsub(text, "(%s+)$", "");
|
|
if text ~= "" then
|
|
return text
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
do -- DEBUG
|
|
local function CreateSaveDB(key)
|
|
if not PlumberDevOutput then
|
|
PlumberDevOutput = {};
|
|
end
|
|
if not PlumberDevOutput[key] then
|
|
PlumberDevOutput[key] = {};
|
|
end
|
|
end
|
|
|
|
local function SaveLocalizedText(localizedText, englishText)
|
|
local locale = GetLocale();
|
|
CreateSaveDB(locale);
|
|
PlumberDevOutput[locale][localizedText] = englishText or true;
|
|
end
|
|
API.SaveLocalizedText = SaveLocalizedText;
|
|
|
|
local function SaveDataUnderKey(key, ...)
|
|
CreateSaveDB(key);
|
|
PlumberDevOutput[key] = {...}
|
|
end
|
|
API.SaveDataUnderKey = SaveDataUnderKey;
|
|
end
|
|
|
|
do -- Math
|
|
local function Clamp(value, min, max)
|
|
if value > max then
|
|
return max
|
|
elseif value < min then
|
|
return min
|
|
end
|
|
return value
|
|
end
|
|
API.Clamp = Clamp;
|
|
|
|
local function Lerp(startValue, endValue, amount)
|
|
return (1 - amount) * startValue + amount * endValue;
|
|
end
|
|
API.Lerp = Lerp;
|
|
|
|
local function GetPointsDistance2D(x1, y1, x2, y2)
|
|
return sqrt( (x1 - x2)*(x1 - x2) + (y1 - y2)*(y1 - y2))
|
|
end
|
|
API.GetPointsDistance2D = GetPointsDistance2D;
|
|
|
|
local function Round(n)
|
|
return floor(n + 0.5);
|
|
end
|
|
API.Round = Round;
|
|
|
|
local function RoundCoord(n)
|
|
return floor(n * 1000 + 0.5) * 0.001
|
|
end
|
|
API.RoundCoord = RoundCoord;
|
|
|
|
local function Saturate(value)
|
|
return Clamp(value, 0.0, 1.0);
|
|
end
|
|
|
|
local function DeltaLerp(startValue, endValue, amount, timeSec)
|
|
return Lerp(startValue, endValue, Saturate(amount * timeSec * 60.0));
|
|
end
|
|
API.DeltaLerp = DeltaLerp;
|
|
end
|
|
|
|
do -- Color
|
|
local ColorSwatches = {
|
|
SelectionBlue = {12, 105, 216},
|
|
SmoothGreen = {124, 197, 118},
|
|
WarningRed = {212, 100, 28}, --228, 13, 14 248, 81, 73
|
|
};
|
|
|
|
for _, swatch in pairs(ColorSwatches) do
|
|
swatch[1] = swatch[1]/255;
|
|
swatch[2] = swatch[2]/255;
|
|
swatch[3] = swatch[3]/255;
|
|
end
|
|
|
|
local function GetColorByName(colorName)
|
|
if ColorSwatches[colorName] then
|
|
return unpack(ColorSwatches[colorName])
|
|
else
|
|
return 1, 1, 1
|
|
end
|
|
end
|
|
API.GetColorByName = GetColorByName;
|
|
|
|
|
|
-- Make Rare and Epic brighter (use the color in Narcissus)
|
|
local ITEM_QUALITY_COLORS = ITEM_QUALITY_COLORS;
|
|
local QualityColors = {};
|
|
QualityColors[1] = CreateColor(0.92, 0.92, 0.92, 1);
|
|
QualityColors[3] = CreateColor(105/255, 158/255, 255/255, 1);
|
|
QualityColors[4] = CreateColor(185/255, 83/255, 255/255, 1);
|
|
|
|
local function GetItemQualityColor(quality)
|
|
if QualityColors[quality] then
|
|
return QualityColors[quality]
|
|
else
|
|
return ITEM_QUALITY_COLORS[quality].color
|
|
end
|
|
end
|
|
API.GetItemQualityColor = GetItemQualityColor;
|
|
|
|
|
|
local function IsWarningColor(r, g, b)
|
|
--Used to determine if the tooltip fontstring is red, which indicates there is a requirement you don't meet
|
|
return (r > 0.99 and r <= 1) and (g > 0.1254 and g < 0.1255) and (b > 0.1254 and b < 0.1255)
|
|
end
|
|
API.IsWarningColor = IsWarningColor;
|
|
|
|
|
|
local function SetTextColorByGlobal(fontString, colorMixin)
|
|
local r, g, b;
|
|
if colorMixin then
|
|
r, g, b = colorMixin:GetRGB();
|
|
else
|
|
r, g, b = 1, 1, 1;
|
|
end
|
|
fontString:SetTextColor(r, g, b);
|
|
end
|
|
API.SetTextColorByGlobal = SetTextColorByGlobal;
|
|
end
|
|
|
|
do -- Time
|
|
local D_DAYS = D_DAYS or "%d |4Day:Days;";
|
|
local D_HOURS = D_HOURS or "%d |4Hour:Hours;";
|
|
local D_MINUTES = D_MINUTES or "%d |4Minute:Minutes;";
|
|
local D_SECONDS = D_SECONDS or "%d |4Second:Seconds;";
|
|
|
|
local DAYS_ABBR = DAYS_ABBR or "%d |4Day:Days;"
|
|
local HOURS_ABBR = HOURS_ABBR or "%d |4Hr:Hr;";
|
|
local MINUTES_ABBR = MINUTES_ABBR or "%d |4Min:Min;";
|
|
local SECONDS_ABBR = SECONDS_ABBR or "%d |4Sec:Sec;";
|
|
|
|
local SHOW_HOUR_BELOW_DAYS = 3;
|
|
local SHOW_MINUTE_BELOW_HOURS = 12;
|
|
local SHOW_SECOND_BELOW_MINUTES = 10;
|
|
local COLOR_RED_BELOW_SECONDS = 43200;
|
|
|
|
local function BakePlural(number, singularPlural)
|
|
singularPlural = gsub(singularPlural, ";", "");
|
|
|
|
if number > 1 then
|
|
return format(gsub(singularPlural, "|4[^:]*:", ""), number);
|
|
else
|
|
singularPlural = gsub(singularPlural, ":.*", "");
|
|
singularPlural = gsub(singularPlural, "|4", "");
|
|
return format(singularPlural, number);
|
|
end
|
|
end
|
|
|
|
local function FormatTime(t, pattern, bakePluralEscapeSequence)
|
|
if bakePluralEscapeSequence then
|
|
return BakePlural(t, pattern);
|
|
else
|
|
return format(pattern, t);
|
|
end
|
|
end
|
|
|
|
local function SecondsToTime(seconds, abbreviated, partialTime, bakePluralEscapeSequence, colorized)
|
|
--partialTime: Stop processing if the remaining units don't really matter. e.g. to display the remaining time of an event when there are still days left
|
|
--bakePluralEscapeSequence: Convert EcsapeSequence like "|4Sec:Sec;" to its result so it can be sent to chat
|
|
local intialSeconds = seconds;
|
|
local timeString = "";
|
|
local isComplete = false;
|
|
local days = 0;
|
|
local hours = 0;
|
|
local minutes = 0;
|
|
|
|
if seconds >= 86400 then
|
|
days = floor(seconds / 86400);
|
|
seconds = seconds - days * 86400;
|
|
|
|
local dayText = FormatTime(days, (abbreviated and DAYS_ABBR) or D_DAYS, bakePluralEscapeSequence);
|
|
timeString = dayText;
|
|
|
|
if partialTime and days >= SHOW_HOUR_BELOW_DAYS then
|
|
isComplete = true;
|
|
end
|
|
end
|
|
|
|
if not isComplete then
|
|
hours = floor(seconds / 3600);
|
|
seconds = seconds - hours * 3600;
|
|
|
|
if hours > 0 then
|
|
local hourText = FormatTime(hours, (abbreviated and HOURS_ABBR) or D_HOURS, bakePluralEscapeSequence);
|
|
if timeString == "" then
|
|
timeString = hourText;
|
|
else
|
|
timeString = timeString.." "..hourText;
|
|
end
|
|
|
|
if partialTime and hours >= SHOW_MINUTE_BELOW_HOURS then
|
|
isComplete = true;
|
|
end
|
|
else
|
|
if timeString ~= "" and partialTime then
|
|
isComplete = true;
|
|
end
|
|
end
|
|
end
|
|
|
|
if partialTime and days > 0 then
|
|
isComplete = true;
|
|
end
|
|
|
|
if not isComplete then
|
|
minutes = floor(seconds / 60);
|
|
seconds = seconds - minutes * 60;
|
|
|
|
if minutes > 0 then
|
|
local minuteText = FormatTime(minutes, (abbreviated and MINUTES_ABBR) or D_MINUTES, bakePluralEscapeSequence);
|
|
if timeString == "" then
|
|
timeString = minuteText;
|
|
else
|
|
timeString = timeString.." "..minuteText;
|
|
end
|
|
if partialTime and (minutes >= SHOW_SECOND_BELOW_MINUTES or hours > 0) then
|
|
isComplete = true;
|
|
end
|
|
else
|
|
if timeString ~= "" and partialTime then
|
|
isComplete = true;
|
|
end
|
|
end
|
|
end
|
|
|
|
if (not isComplete) and seconds > 0 then
|
|
seconds = floor(seconds);
|
|
local secondText = FormatTime(seconds, (abbreviated and SECONDS_ABBR) or D_SECONDS, bakePluralEscapeSequence);
|
|
if timeString == "" then
|
|
timeString = secondText;
|
|
else
|
|
timeString = timeString.." "..secondText;
|
|
end
|
|
end
|
|
|
|
if colorized and partialTime and intialSeconds < COLOR_RED_BELOW_SECONDS and not bakePluralEscapeSequence then
|
|
--WARNING_FONT_COLOR
|
|
timeString = "|cffff4800"..timeString.."|r";
|
|
end
|
|
|
|
return timeString
|
|
end
|
|
API.SecondsToTime = SecondsToTime;
|
|
|
|
local function SecondsToClock(seconds)
|
|
--Clock: 00:00
|
|
return format("%s:%02d", floor(seconds / 60), floor(seconds % 60))
|
|
end
|
|
API.SecondsToClock = SecondsToClock;
|
|
|
|
--Unix Epoch is in UTC
|
|
local MonthDays = {
|
|
31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31,
|
|
};
|
|
|
|
local function IsLeapYear(year)
|
|
return year % 400 == 0 or (year % 4 == 0 and year % 100 ~= 0)
|
|
end
|
|
|
|
local function GetFebruaryDays(year)
|
|
if IsLeapYear(year) then
|
|
return 29
|
|
else
|
|
return 28
|
|
end
|
|
end
|
|
|
|
local function IsLeftTimeFuture(time1, time2, i)
|
|
if not time1[i] then return false end;
|
|
|
|
if time1[i] > time2[i] then
|
|
return true;
|
|
elseif time1[i] == time2[i] then
|
|
return IsLeftTimeFuture(time1, time2, i + 1);
|
|
else
|
|
return false
|
|
end
|
|
end
|
|
|
|
local function GetNumDaysToDate(year, month, day)
|
|
local numDays = day;
|
|
|
|
for yr = 1, (year -1) do
|
|
if IsLeapYear(yr) then
|
|
numDays = numDays + 366;
|
|
else
|
|
numDays = numDays + 365;
|
|
end
|
|
end
|
|
|
|
for m = 1, (month - 1) do
|
|
if m == 2 then
|
|
numDays = numDays + GetFebruaryDays(year);
|
|
else
|
|
numDays = numDays + MonthDays[m];
|
|
end
|
|
end
|
|
|
|
return numDays
|
|
end
|
|
|
|
local function GetNumSecondsToDate(year, month, day, hour, minute, second)
|
|
hour = hour or 0;
|
|
minute = minute or 0;
|
|
second = second or 0;
|
|
local numDays = GetNumDaysToDate(year, month, day);
|
|
local numSeconds = second;
|
|
numSeconds = numSeconds + numDays * 86400;
|
|
numSeconds = numSeconds + hour * 3600 + minute * 60;
|
|
return numSeconds
|
|
end
|
|
|
|
local function ConvertCalendarTime(calendarTime)
|
|
--WoW's CalendarTime See https://warcraft.wiki.gg/wiki/API_C_DateAndTime.GetCurrentCalendarTime
|
|
local year = calendarTime.year;
|
|
local month = calendarTime.month;
|
|
local day = calendarTime.monthDay;
|
|
local hour = calendarTime.hour;
|
|
local minute = calendarTime.minute;
|
|
local second = calendarTime.second or 0; --the original calendarTime does not contain second
|
|
|
|
return {year, month, day, hour, minute, second}
|
|
end
|
|
|
|
local function GetCalendarTimeDifference(lhsCalendarTime, rhsCalendarTime)
|
|
--time = {year, month, day, hour, minute, second}
|
|
local time1 = ConvertCalendarTime(lhsCalendarTime);
|
|
local time2 = ConvertCalendarTime(rhsCalendarTime);
|
|
local second1 = GetNumSecondsToDate(unpack(time1));
|
|
local second2 = GetNumSecondsToDate(unpack(time2));
|
|
return second2 - second1
|
|
end
|
|
API.GetCalendarTimeDifference = GetCalendarTimeDifference;
|
|
|
|
|
|
local function WrapNumberWithBrackets(text)
|
|
text = gsub(text, "%%d%+", "%%d");
|
|
text = gsub(text, "%%d", "%(%%d%+%)");
|
|
return text
|
|
end
|
|
|
|
local PATTERN_DAYS = WrapNumberWithBrackets(DAYS_ABBR);
|
|
local PATTERN_HOURS = WrapNumberWithBrackets(HOURS_ABBR);
|
|
local PATTERN_MINUTES = WrapNumberWithBrackets(MINUTES_ABBR);
|
|
local PATTERN_SECONDS = WrapNumberWithBrackets(SECONDS_ABBR);
|
|
|
|
local function ConvertTextToSeconds(durationText)
|
|
if not durationText then return 0 end;
|
|
if not match(durationText, "%d") then return 0 end;
|
|
|
|
local hours = tonumber(match(durationText, PATTERN_HOURS) or 0);
|
|
local minutes = tonumber(match(durationText, PATTERN_MINUTES) or 0);
|
|
local seconds = tonumber(match(durationText, PATTERN_SECONDS) or 0);
|
|
|
|
return 3600 * hours + 60 * minutes + seconds;
|
|
end
|
|
API.TimeLeftTextToSeconds = ConvertTextToSeconds;
|
|
end
|
|
|
|
do -- Item
|
|
local C_Item = C_Item;
|
|
local GetItemSpell = GetItemSpell;
|
|
|
|
|
|
local function ColorizeTextByQuality(text, quality, allowColorBlind)
|
|
if not (text and quality) then
|
|
return text
|
|
end
|
|
|
|
local color = API.GetItemQualityColor(quality);
|
|
text = color:WrapTextInColorCode(text);
|
|
if allowColorBlind and GetCVarBool("colorblindMode") then
|
|
text = text.." |cffffffff[".._G[format("ITEM_QUALITY%s_DESC", quality)].."]|r";
|
|
end
|
|
|
|
return text
|
|
end
|
|
API.ColorizeTextByQuality = ColorizeTextByQuality;
|
|
|
|
|
|
local function GetColorizedItemName(itemID)
|
|
local name = C_Item.GetItemNameByID(itemID);
|
|
local quality = C_Item.GetItemQualityByID(itemID);
|
|
|
|
return ColorizeTextByQuality(name, quality, true);
|
|
end
|
|
API.GetColorizedItemName = GetColorizedItemName;
|
|
|
|
|
|
local function GetItemSpellID(item)
|
|
local spellName, spellID = GetItemSpell(item);
|
|
return spellID
|
|
end
|
|
API.GetItemSpellID = GetItemSpellID;
|
|
|
|
|
|
function API.IsToyItem(item)
|
|
return C_ToyBox.GetToyInfo(item) ~= nil
|
|
end
|
|
|
|
|
|
function API.GetItemSellPrice(item)
|
|
if item then
|
|
local sellPrice = select(11, C_Item.GetItemInfo(item));
|
|
if sellPrice and sellPrice > 0 then
|
|
return sellPrice
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
function API.IsMountCollected(mountID)
|
|
local isCollected = select(11, C_MountJournal.GetMountInfoByID(mountID));
|
|
return isCollected
|
|
end
|
|
end
|
|
|
|
do -- Tooltip Parser
|
|
local GetInfoByHyperlink = C_TooltipInfo and C_TooltipInfo.GetHyperlink;
|
|
|
|
local function GetLineText(lines, index)
|
|
if lines[index] then
|
|
return lines[index].leftText;
|
|
end
|
|
end
|
|
|
|
local function GetCreatureName(creatureID)
|
|
if not creatureID then return end;
|
|
local tooltipData = GetInfoByHyperlink("unit:Creature-0-0-0-0-"..creatureID);
|
|
if tooltipData then
|
|
return GetLineText(tooltipData.lines, 1);
|
|
end
|
|
end
|
|
API.GetCreatureName = GetCreatureName;
|
|
end
|
|
|
|
do -- Holiday
|
|
local CalendarTextureXHolidayKey = {
|
|
--Only the important ones :)
|
|
[235469] = "lunarfestival",
|
|
[235470] = "lunarfestival",
|
|
[235471] = "lunarfestival",
|
|
|
|
[235466] = "loveintheair",
|
|
[235467] = "loveintheair",
|
|
[235468] = "loveintheair",
|
|
|
|
[235475] = "noblegarden",
|
|
[235476] = "noblegarden",
|
|
[235477] = "noblegarden",
|
|
|
|
[235443] = "childrensweek",
|
|
[235444] = "childrensweek",
|
|
[235445] = "childrensweek",
|
|
|
|
[235472] = "midsummer",
|
|
[235473] = "midsummer",
|
|
[235474] = "midsummer",
|
|
|
|
[235439] = "brewfest",
|
|
[235440] = "brewfest",
|
|
[235441] = "brewfest",
|
|
[235442] = "brewfest",
|
|
|
|
[235460] = "hallowsendend",
|
|
[235461] = "hallowsendend",
|
|
[235462] = "hallowsendend",
|
|
|
|
[235482] = "winterveil",
|
|
[235483] = "winterveil",
|
|
[235484] = "winterveil",
|
|
[235485] = "winterveil",
|
|
};
|
|
|
|
local HolidayInfoMixin = {};
|
|
|
|
function HolidayInfoMixin:GetRemainingSeconds()
|
|
if self.endTime then
|
|
local presentTime = time();
|
|
return self.endTime - presentTime
|
|
else
|
|
return 0
|
|
end
|
|
end
|
|
|
|
function HolidayInfoMixin:GetEndTimeString()
|
|
--MM/DD 00:00
|
|
return self.endTimeString
|
|
end
|
|
|
|
function HolidayInfoMixin:GetRemainingTimeString()
|
|
--DD/HH/MM/SS
|
|
local seconds = self:GetRemainingSeconds();
|
|
return API.SecondsToTime(seconds, false, true);
|
|
end
|
|
|
|
function HolidayInfoMixin:GetName()
|
|
return self.name
|
|
end
|
|
|
|
function HolidayInfoMixin:GetKey()
|
|
return self.key
|
|
end
|
|
|
|
local function GetActiveMajorHolidayInfo()
|
|
local currentCalendarTime = C_DateAndTime.GetCurrentCalendarTime();
|
|
local presentDay = currentCalendarTime.monthDay;
|
|
local monthOffset = 0;
|
|
local holidayInfo;
|
|
local holidayKey, holidayName;
|
|
local endTimeString;
|
|
local eventEndTimeMixin; --{}
|
|
local endTime; --number time()
|
|
local activeHolidayData; --{ {holiday1}, {holiday2} }
|
|
|
|
for i = 1, C_Calendar.GetNumDayEvents(monthOffset, presentDay) do --Need to request data first with C_Calendar.OpenCalendar()
|
|
holidayInfo = C_Calendar.GetHolidayInfo(monthOffset, presentDay, i);
|
|
--print(i, holidayInfo.name)
|
|
if holidayInfo and holidayInfo.texture and CalendarTextureXHolidayKey[holidayInfo.texture] then
|
|
holidayKey = CalendarTextureXHolidayKey[holidayInfo.texture];
|
|
holidayName = holidayInfo.name;
|
|
|
|
if holidayInfo.startTime and holidayInfo.endTime then
|
|
endTimeString = FormatShortDate(holidayInfo.endTime.monthDay, holidayInfo.endTime.month) .." ".. GameTime_GetFormattedTime(holidayInfo.endTime.hour, holidayInfo.endTime.minute, true);
|
|
eventEndTimeMixin = holidayInfo.endTime;
|
|
end
|
|
|
|
local isEventActive = true;
|
|
if eventEndTimeMixin then
|
|
local presentTime = time();
|
|
local remainingSeconds = API.GetCalendarTimeDifference(currentCalendarTime, eventEndTimeMixin);
|
|
endTime = presentTime + remainingSeconds;
|
|
if remainingSeconds <= 0 then
|
|
isEventActive = false;
|
|
end
|
|
end
|
|
|
|
if isEventActive and holidayName then
|
|
local mixin = API.CreateFromMixins(HolidayInfoMixin);
|
|
|
|
mixin.name = holidayName;
|
|
mixin.key = holidayKey;
|
|
mixin.endTimeString = endTimeString;
|
|
mixin.endTime = endTime;
|
|
|
|
if not activeHolidayData then
|
|
activeHolidayData = {};
|
|
end
|
|
|
|
tinsert(activeHolidayData, mixin);
|
|
end
|
|
end
|
|
end
|
|
|
|
return activeHolidayData
|
|
end
|
|
API.GetActiveMajorHolidayInfo = GetActiveMajorHolidayInfo;
|
|
end
|
|
|
|
do -- Fade Frame
|
|
local abs = math.abs;
|
|
local tinsert = table.insert;
|
|
local wipe = wipe;
|
|
|
|
local fadeInfo = {};
|
|
local fadingFrames = {};
|
|
|
|
local f = CreateFrame("Frame");
|
|
|
|
local function OnUpdate(self, elpased)
|
|
local i = 1;
|
|
local frame, info, timer, alpha;
|
|
local isComplete = true;
|
|
while fadingFrames[i] do
|
|
frame = fadingFrames[i];
|
|
info = fadeInfo[frame];
|
|
if info then
|
|
timer = info.timer + elpased;
|
|
if timer >= info.duration then
|
|
alpha = info.toAlpha;
|
|
fadeInfo[frame] = nil;
|
|
if info.alterShownState and alpha <= 0 then
|
|
frame:Hide();
|
|
end
|
|
else
|
|
alpha = info.fromAlpha + (info.toAlpha - info.fromAlpha) * timer/info.duration;
|
|
info.timer = timer;
|
|
end
|
|
frame:SetAlpha(alpha);
|
|
isComplete = false;
|
|
end
|
|
i = i + 1;
|
|
end
|
|
|
|
if isComplete then
|
|
f:Clear();
|
|
end
|
|
end
|
|
|
|
function f:Clear()
|
|
self:SetScript("OnUpdate", nil);
|
|
wipe(fadingFrames);
|
|
wipe(fadeInfo);
|
|
end
|
|
|
|
function f:Add(frame, fullDuration, fromAlpha, toAlpha, alterShownState, useConstantDuration)
|
|
local alpha = frame:GetAlpha();
|
|
if alterShownState then
|
|
if toAlpha > 0 then
|
|
frame:Show();
|
|
end
|
|
if toAlpha == 0 then
|
|
if not frame:IsShown() then
|
|
frame:SetAlpha(0);
|
|
alpha = 0;
|
|
end
|
|
if alpha == 0 then
|
|
frame:Hide();
|
|
end
|
|
end
|
|
end
|
|
if fromAlpha == toAlpha or alpha == toAlpha then
|
|
if fadeInfo[frame] then
|
|
fadeInfo[frame] = nil;
|
|
end
|
|
return;
|
|
end
|
|
local duration;
|
|
if useConstantDuration then
|
|
duration = fullDuration;
|
|
else
|
|
if fromAlpha then
|
|
duration = fullDuration * (alpha - toAlpha)/(fromAlpha - toAlpha);
|
|
else
|
|
duration = fullDuration * abs(alpha - toAlpha);
|
|
end
|
|
end
|
|
if duration <= 0 then
|
|
frame:SetAlpha(toAlpha);
|
|
if toAlpha == 0 then
|
|
frame:Hide();
|
|
end
|
|
return;
|
|
end
|
|
fadeInfo[frame] = {
|
|
fromAlpha = alpha,
|
|
toAlpha = toAlpha,
|
|
duration = duration,
|
|
timer = 0,
|
|
alterShownState = alterShownState,
|
|
};
|
|
for i = 1, #fadingFrames do
|
|
if fadingFrames[i] == frame then
|
|
return;
|
|
end
|
|
end
|
|
tinsert(fadingFrames, frame);
|
|
self:SetScript("OnUpdate", OnUpdate);
|
|
end
|
|
|
|
function f:SimpleFade(frame, toAlpha, alterShownState, speedMultiplier)
|
|
--Use a constant fading speed: 1.0 in 0.25s
|
|
--alterShownState: if true, run Frame:Hide() when alpha reaches zero / run Frame:Show() at the beginning
|
|
speedMultiplier = speedMultiplier or 1;
|
|
local alpha = frame:GetAlpha();
|
|
local duration = abs(alpha - toAlpha) * 0.25 * speedMultiplier;
|
|
if duration <= 0 then
|
|
return;
|
|
end
|
|
|
|
self:Add(frame, duration, alpha, toAlpha, alterShownState, true);
|
|
end
|
|
|
|
function f:Snap()
|
|
local i = 1;
|
|
local frame, info;
|
|
while fadingFrames[i] do
|
|
frame = fadingFrames[i];
|
|
info = fadeInfo[frame];
|
|
if info then
|
|
frame:SetAlpha(info.toAlpha);
|
|
end
|
|
i = i + 1;
|
|
end
|
|
self:Clear();
|
|
end
|
|
|
|
local function UIFrameFade(frame, duration, toAlpha, initialAlpha)
|
|
if initialAlpha then
|
|
frame:SetAlpha(initialAlpha);
|
|
f:Add(frame, duration, initialAlpha, toAlpha, true, false);
|
|
else
|
|
f:Add(frame, duration, nil, toAlpha, true, false);
|
|
end
|
|
end
|
|
|
|
local function UIFrameFadeIn(frame, duration)
|
|
frame:SetAlpha(0);
|
|
f:Add(frame, duration, 0, 1, true, false);
|
|
end
|
|
|
|
|
|
API.UIFrameFade = UIFrameFade; --from current alpha
|
|
API.UIFrameFadeIn = UIFrameFadeIn; --from 0 to 1
|
|
end
|
|
|
|
do -- Map
|
|
---- Create Module that will be activated in specific zones:
|
|
---- 1. Soridormi Auto-report
|
|
---- 2. Dreamseed
|
|
|
|
local C_Map = C_Map;
|
|
local GetMapInfo = C_Map.GetMapInfo;
|
|
local GetBestMapForUnit = C_Map.GetBestMapForUnit;
|
|
local CreateVector2D = CreateVector2D;
|
|
local controller;
|
|
local modules;
|
|
local lastMapID, total;
|
|
|
|
local ZoneTriggeredModuleMixin = {};
|
|
|
|
ZoneTriggeredModuleMixin.validMaps = {};
|
|
ZoneTriggeredModuleMixin.inZone = false;
|
|
ZoneTriggeredModuleMixin.enabled = true;
|
|
|
|
local function DoNothing(arg)
|
|
end
|
|
|
|
ZoneTriggeredModuleMixin.enterZoneCallback = DoNothing;
|
|
ZoneTriggeredModuleMixin.leaveZoneCallback = DoNothing;
|
|
|
|
function ZoneTriggeredModuleMixin:IsZoneValid(uiMapID)
|
|
return self.validMaps[uiMapID]
|
|
end
|
|
|
|
function ZoneTriggeredModuleMixin:SetValidZones(...)
|
|
self.validMaps = {};
|
|
local map;
|
|
for i = 1, select("#", ...) do
|
|
map = select(i, ...);
|
|
if type(map) == "table" then
|
|
for _, uiMapID in ipairs(map) do
|
|
self.validMaps[uiMapID] = true;
|
|
end
|
|
else
|
|
self.validMaps[map] = true;
|
|
end
|
|
end
|
|
end
|
|
|
|
function ZoneTriggeredModuleMixin:PlayerEnterZone(mapID)
|
|
if not self.inZone then
|
|
self.inZone = true;
|
|
self.enterZoneCallback(mapID);
|
|
end
|
|
|
|
if mapID ~= self.currentMapID then
|
|
self.currentMapID = mapID;
|
|
self:OnCurrentMapChanged(mapID);
|
|
end
|
|
end
|
|
|
|
function ZoneTriggeredModuleMixin:PlayerLeaveZone()
|
|
if self.inZone then
|
|
self.inZone = false;
|
|
self.currentMapID = nil;
|
|
self.leaveZoneCallback();
|
|
end
|
|
end
|
|
|
|
function ZoneTriggeredModuleMixin:SetEnterZoneCallback(callback)
|
|
self.enterZoneCallback = callback;
|
|
end
|
|
|
|
function ZoneTriggeredModuleMixin:SetLeaveZoneCallback(callback)
|
|
self.leaveZoneCallback = callback;
|
|
end
|
|
|
|
function ZoneTriggeredModuleMixin:OnCurrentMapChanged(newMapID)
|
|
end
|
|
|
|
function ZoneTriggeredModuleMixin:SetEnabled(state)
|
|
self.enabled = state or false;
|
|
if not self.enabled then
|
|
self:PlayerLeaveZone();
|
|
end
|
|
end
|
|
|
|
function ZoneTriggeredModuleMixin:Update()
|
|
if not self.enabled then return end;
|
|
|
|
local mapID = GetBestMapForUnit("player");
|
|
if self:IsZoneValid(mapID) then
|
|
self:PlayerEnterZone(mapID);
|
|
else
|
|
self:PlayerLeaveZone();
|
|
end
|
|
end
|
|
|
|
local function AddZoneModules(module)
|
|
if not controller then
|
|
controller = CreateFrame("Frame");
|
|
modules = {};
|
|
total = 0;
|
|
|
|
controller:SetScript("OnEvent", function(f, event, ...)
|
|
local mapID = GetBestMapForUnit("player");
|
|
|
|
if mapID and mapID ~= lastMapID then
|
|
lastMapID = mapID;
|
|
else
|
|
return
|
|
end
|
|
|
|
for i = 1, total do
|
|
if modules[i].enabled then
|
|
if modules[i]:IsZoneValid(mapID) then
|
|
modules[i]:PlayerEnterZone(mapID);
|
|
else
|
|
modules[i]:PlayerLeaveZone();
|
|
end
|
|
end
|
|
end
|
|
end);
|
|
|
|
--Note: if the player enters an unmapped area like The Great Sea then re-enter a regular zone
|
|
--GetBestMapForUnit will still return the continent mapID when ZONE_CHANGED_NEW_AREA triggers
|
|
controller:RegisterEvent("ZONE_CHANGED_NEW_AREA");
|
|
controller:RegisterEvent("PLAYER_ENTERING_WORLD");
|
|
end
|
|
|
|
table.insert(modules, module);
|
|
total = total + 1;
|
|
end
|
|
|
|
local function CreateZoneTriggeredModule(tag)
|
|
local module = {
|
|
tag = tag,
|
|
validMaps = {},
|
|
};
|
|
|
|
for k, v in pairs(ZoneTriggeredModuleMixin) do
|
|
module[k] = v;
|
|
end
|
|
|
|
AddZoneModules(module);
|
|
|
|
return module
|
|
end
|
|
|
|
API.CreateZoneTriggeredModule = CreateZoneTriggeredModule;
|
|
|
|
|
|
--Get Player Coord Less RAM cost
|
|
local UnitPosition = UnitPosition;
|
|
local GetPlayerMapPosition = C_Map.GetPlayerMapPosition;
|
|
local _posY, _posX, _data;
|
|
local lastUiMapID;
|
|
local MapData = {};
|
|
|
|
local function CacheMapData(uiMapID)
|
|
if MapData[uiMapID] then return end;
|
|
|
|
local instance, topLeft = C_Map.GetWorldPosFromMapPos(uiMapID, {x=0, y=0});
|
|
local width, height = C_Map.GetMapWorldSize(uiMapID);
|
|
|
|
if topLeft then
|
|
local top, left = topLeft:GetXY()
|
|
MapData[uiMapID] = {width, height, left, top};
|
|
end
|
|
end
|
|
|
|
local function GetPlayerMapCoord_Fallback(uiMapID)
|
|
local position = GetPlayerMapPosition(uiMapID, "player");
|
|
if position then
|
|
return position.x, position.Y
|
|
end
|
|
end
|
|
|
|
local function GetPlayerMapCoord(uiMapID)
|
|
_posY, _posX = UnitPosition("player");
|
|
if not (_posX and _posY) then return GetPlayerMapCoord_Fallback(uiMapID) end;
|
|
|
|
if uiMapID ~= lastUiMapID then
|
|
lastUiMapID = uiMapID;
|
|
CacheMapData(uiMapID);
|
|
end
|
|
|
|
_data = MapData[uiMapID]
|
|
if not _data or _data[1] == 0 or _data[2] == 0 then return GetPlayerMapCoord_Fallback(uiMapID) end;
|
|
|
|
return (_data[3] - _posX) / _data[1], (_data[4] - _posY) / _data[2]
|
|
end
|
|
API.GetPlayerMapCoord = GetPlayerMapCoord;
|
|
|
|
|
|
local function ConvertMapPositionToContinentPosition(uiMapID, x, y, poiID)
|
|
local info = GetMapInfo(uiMapID);
|
|
if not info then return end;
|
|
|
|
local continentMapID; --uiMapID
|
|
|
|
while info do
|
|
if info.mapType == Enum.UIMapType.Continent then
|
|
continentMapID = info.mapID;
|
|
break
|
|
elseif info.parentMapID then
|
|
info = GetMapInfo(info.parentMapID);
|
|
else
|
|
return
|
|
end
|
|
end
|
|
|
|
if not continentMapID then
|
|
print(string.format("Map %s doesn't belong to any continent.", uiMapID));
|
|
end
|
|
|
|
local point = {
|
|
uiMapID = uiMapID,
|
|
position = CreateVector2D(x, y);
|
|
};
|
|
|
|
C_Map.SetUserWaypoint(point);
|
|
|
|
C_Timer.After(0, function()
|
|
local posVector = C_Map.GetUserWaypointPositionForMap(continentMapID);
|
|
if posVector then
|
|
x, y = posVector:GetXY();
|
|
print(continentMapID, x, y);
|
|
|
|
if not PlumberDevData then
|
|
PlumberDevData = {};
|
|
end
|
|
|
|
if not PlumberDevData.POIPositions then
|
|
PlumberDevData.POIPositions = {};
|
|
end
|
|
|
|
if poiID then
|
|
x = API.RoundCoord(x);
|
|
y = API.RoundCoord(y);
|
|
PlumberDevData.POIPositions[poiID] = {
|
|
poiID = poiID,
|
|
uiMapID = uiMapID,
|
|
continent = continentMapID,
|
|
x = x,
|
|
y = y,
|
|
};
|
|
end
|
|
|
|
C_Map.ClearUserWaypoint();
|
|
else
|
|
print("No user waypoint found.")
|
|
end
|
|
end);
|
|
end
|
|
API.ConvertMapPositionToContinentPosition = ConvertMapPositionToContinentPosition;
|
|
|
|
|
|
function API.GetPlayerMap()
|
|
return GetBestMapForUnit("player");
|
|
end
|
|
|
|
|
|
--Calculate a list of map positions (cache data) and run callback
|
|
local Converter;
|
|
|
|
local function Converter_OnUpdate(self, elapsed)
|
|
self.t = self.t + elapsed;
|
|
if self.t > self.delay then
|
|
if self.t > 1 then --The delay is always much shorter than 1s, thie line is to prevent error looping
|
|
self.t = nil;
|
|
self:SetScript("OnUpdate", nil);
|
|
return
|
|
end
|
|
self.t = 0;
|
|
else
|
|
return
|
|
end
|
|
|
|
self.index = self.index + 1;
|
|
|
|
if self.calls[self.index] then
|
|
self.calls[self.index]();
|
|
else
|
|
self:SetScript("OnUpdate", nil);
|
|
self.t = nil;
|
|
self.calls = nil;
|
|
self.index = nil;
|
|
self.oldWaypoint = nil;
|
|
if self.onFinished then
|
|
self.onFinished();
|
|
self.onFinished = nil;
|
|
end
|
|
end
|
|
end
|
|
|
|
local function ConvertAndCacheMapPositions(positions, onCoordReceivedFunc, onFinishedFunc)
|
|
--Convert Zone position to Continent position
|
|
if not Converter then
|
|
Converter = CreateFrame("Frame");
|
|
print("Plumber Request ConvertAndCacheMapPositions");
|
|
end
|
|
|
|
local MAPTYPE_CONTINENT = Enum.UIMapType.Continent;
|
|
if not MAPTYPE_CONTINENT then
|
|
print("Plumber WoW API Changed");
|
|
return
|
|
end
|
|
|
|
local calls, n, oldWaypoint;
|
|
|
|
if Converter.t then
|
|
--still processing
|
|
calls = Converter.calls;
|
|
n = #calls;
|
|
oldWaypoint = Converter.oldWaypoint;
|
|
else
|
|
calls = {};
|
|
n = 0;
|
|
oldWaypoint = C_Map.GetUserWaypoint();
|
|
Converter.oldWaypoint = oldWaypoint;
|
|
Converter.index = 0;
|
|
end
|
|
|
|
for _, data in ipairs(positions) do
|
|
local info = GetMapInfo(data.uiMapID);
|
|
if info then
|
|
local continentMapID; --uiMapID
|
|
|
|
while info do
|
|
if info.mapType == MAPTYPE_CONTINENT then
|
|
continentMapID = info.mapID;
|
|
break
|
|
elseif info.parentMapID then
|
|
info = GetMapInfo(info.parentMapID);
|
|
else
|
|
info = nil;
|
|
end
|
|
end
|
|
|
|
if continentMapID then
|
|
local uiMapID = data.uiMapID;
|
|
local poiID = data.poiID;
|
|
|
|
local point = {
|
|
uiMapID = uiMapID,
|
|
position = CreateVector2D(data.x, data.y);
|
|
};
|
|
|
|
n = n + 1;
|
|
local function SetWaypoint()
|
|
C_Map.SetUserWaypoint(point);
|
|
Converter.t = 0;
|
|
end
|
|
calls[n] = SetWaypoint;
|
|
|
|
n = n + 1;
|
|
local function ProcessWaypoint()
|
|
local posVector = C_Map.GetUserWaypointPositionForMap(continentMapID);
|
|
if posVector then
|
|
local x, y = posVector:GetXY();
|
|
local positionData = {
|
|
uiMapID = uiMapID,
|
|
continent = continentMapID,
|
|
x = API.RoundCoord(x),
|
|
y = API.RoundCoord(y),
|
|
poiID = poiID,
|
|
};
|
|
|
|
onCoordReceivedFunc(positionData)
|
|
C_Map.ClearUserWaypoint();
|
|
|
|
--Debug Save Position
|
|
--[[
|
|
if not PlumberDevData then
|
|
PlumberDevData = {};
|
|
end
|
|
if not PlumberDevData.Waypoints then
|
|
PlumberDevData.Waypoints = {};
|
|
end
|
|
PlumberDevData.Waypoints[poiID] = positionData;
|
|
--]]
|
|
end
|
|
Converter.t = 0.033;
|
|
end
|
|
calls[n] = ProcessWaypoint;
|
|
end
|
|
end
|
|
end
|
|
|
|
Converter.onFinished = function()
|
|
if Converter.oldWaypoint then
|
|
C_Map.SetUserWaypoint(oldWaypoint);
|
|
Converter.oldWaypoint = nil;
|
|
end
|
|
|
|
if onFinishedFunc then
|
|
onFinishedFunc();
|
|
end
|
|
end
|
|
|
|
Converter.calls = calls;
|
|
Converter.t = 0;
|
|
Converter.delay = -0.1;
|
|
Converter:SetScript("OnUpdate", Converter_OnUpdate);
|
|
|
|
return true
|
|
end
|
|
API.ConvertAndCacheMapPositions = ConvertAndCacheMapPositions;
|
|
|
|
--[[
|
|
function YeetPos()
|
|
local uiMapID = C_Map.GetBestMapForUnit("player");
|
|
local x, y = GetPlayerMapCoord(uiMapID);
|
|
print(x, y);
|
|
|
|
local position = C_Map.GetPlayerMapPosition(uiMapID, "player");
|
|
local x0, y0 = position:GetXY();
|
|
|
|
print(x0, y0);
|
|
end
|
|
--]]
|
|
|
|
local MARGIN_X = 0.02;
|
|
local MARGIN_Y = MARGIN_X * 1.42;
|
|
|
|
local function AreWaypointsClose(userX, userY, preciseX, preciseY)
|
|
--Examine if the left coords (user set) is roughly the same as the precise position
|
|
--We don't calculate the exact distance (e.g. in yards)
|
|
--We assume the user waypoint falls into a square around around their target, cuz manually placed pin cannot reach that precision
|
|
--The margin of Y is larger than that of X, due to map ratio
|
|
return (userX > preciseX - MARGIN_X) and (userX < preciseX + MARGIN_X) and (userY > preciseY - MARGIN_Y) and (userY < preciseY + MARGIN_Y)
|
|
end
|
|
API.AreWaypointsClose = AreWaypointsClose;
|
|
|
|
|
|
local MAP_PIN_HYPERLINK = MAP_PIN_HYPERLINK or "|A:Waypoint-MapPin-ChatIcon:13:13:0:0|a Map Pin Location";
|
|
local FORMAT_USER_WAYPOINT = "|cffffff00|Hworldmap:%d:%.0f:%.0f|h["..MAP_PIN_HYPERLINK.."]|h|r"; --Message will be blocked by the server if you changing the map pin's name
|
|
|
|
local function CreateWaypointHyperlink(uiMapID, normalizedX, normalizedY)
|
|
if uiMapID and normalizedX and normalizedY then
|
|
return format(FORMAT_USER_WAYPOINT, uiMapID, 10000*normalizedX, 10000*normalizedY);
|
|
end
|
|
end
|
|
API.CreateWaypointHyperlink = CreateWaypointHyperlink;
|
|
|
|
|
|
local function GetZoneName(areaID)
|
|
return C_Map.GetAreaInfo(areaID) or ("Area:"..areaID)
|
|
end
|
|
API.GetZoneName = GetZoneName;
|
|
|
|
|
|
local HasActiveDelve = C_DelvesUI and C_DelvesUI.HasActiveDelve or Nop;
|
|
local function IsInDelves()
|
|
--See Blizzard InstanceDifficulty.lua
|
|
local _, _, _, mapID = UnitPosition("player");
|
|
return HasActiveDelve(mapID);
|
|
end
|
|
API.IsInDelves = IsInDelves;
|
|
|
|
|
|
function API.GetMapName(uiMapID)
|
|
local info = GetMapInfo(uiMapID);
|
|
if info then
|
|
return info.name
|
|
end
|
|
end
|
|
end
|
|
|
|
do -- Instance -- Map
|
|
local GetInstanceInfo = GetInstanceInfo;
|
|
|
|
local function GetMapID()
|
|
local instanceID = select(8, GetInstanceInfo());
|
|
return instanceID
|
|
end
|
|
API.GetMapID = GetMapID;
|
|
end
|
|
|
|
do -- Pixel
|
|
local GetPhysicalScreenSize = GetPhysicalScreenSize;
|
|
|
|
local function GetPixelForScale(scale, pixelSize)
|
|
local SCREEN_WIDTH, SCREEN_HEIGHT = GetPhysicalScreenSize();
|
|
if pixelSize then
|
|
return pixelSize * (768/SCREEN_HEIGHT)/scale
|
|
else
|
|
return (768/SCREEN_HEIGHT)/scale
|
|
end
|
|
end
|
|
API.GetPixelForScale = GetPixelForScale;
|
|
|
|
local function GetPixelForWidget(widget, pixelSize)
|
|
local scale = widget:GetEffectiveScale();
|
|
return GetPixelForScale(scale, pixelSize);
|
|
end
|
|
API.GetPixelForWidget = GetPixelForWidget;
|
|
end
|
|
|
|
do -- Easing
|
|
local EasingFunctions = {};
|
|
addon.EasingFunctions = EasingFunctions;
|
|
|
|
|
|
local sin = math.sin;
|
|
local cos = math.cos;
|
|
local pow = math.pow;
|
|
local pi = math.pi;
|
|
|
|
--t: total time elapsed
|
|
--b: beginning position
|
|
--e: ending position
|
|
--d: animation duration
|
|
|
|
function EasingFunctions.linear(t, b, e, d)
|
|
return (e - b) * t / d + b
|
|
end
|
|
|
|
function EasingFunctions.outSine(t, b, e, d)
|
|
return (e - b) * sin(t / d * (pi / 2)) + b
|
|
end
|
|
|
|
function EasingFunctions.inOutSine(t, b, e, d)
|
|
return -(e - b) / 2 * (cos(pi * t / d) - 1) + b
|
|
end
|
|
|
|
function EasingFunctions.outQuart(t, b, e, d)
|
|
t = t / d - 1;
|
|
return (b - e) * (pow(t, 4) - 1) + b
|
|
end
|
|
|
|
function EasingFunctions.outQuint(t, b, e, d)
|
|
t = t / d
|
|
return (b - e)* (pow(1 - t, 5) - 1) + b
|
|
end
|
|
|
|
function EasingFunctions.inQuad(t, b, e, d)
|
|
t = t / d
|
|
return (e - b) * pow(t, 2) + b
|
|
end
|
|
end
|
|
|
|
do -- Currency
|
|
local GetCurrencyInfo = C_CurrencyInfo.GetCurrencyInfo;
|
|
local CurrencyDataProvider = CreateFrame("Frame");
|
|
CurrencyDataProvider.cache = {};
|
|
CurrencyDataProvider.icons = {};
|
|
|
|
local RelevantKeys = {"name", "quantity", "iconFileID", "maxQuantity", "quality"};
|
|
|
|
CurrencyDataProvider:SetScript("OnEvent", function(self, event, currencyID, quantity, quantityChange)
|
|
if currencyID and self.cache[currencyID] then
|
|
self.cache[currencyID] = nil;
|
|
end
|
|
end);
|
|
|
|
function CurrencyDataProvider:CacheAndGetCurrencyInfo(currencyID)
|
|
if not self.cache[currencyID] then
|
|
local info = GetCurrencyInfo(currencyID);
|
|
if not info then return end;
|
|
local vital = {};
|
|
end
|
|
|
|
if not self.registered then
|
|
self.registered = true;
|
|
self:RegisterEvent("CURRENCY_DISPLAY_UPDATE");
|
|
end
|
|
|
|
return self.cache[currencyID]
|
|
end
|
|
|
|
function CurrencyDataProvider:GetIcon(currencyID)
|
|
if not self.icons[currencyID] then
|
|
self:CacheAndGetCurrencyInfo(currencyID);
|
|
end
|
|
end
|
|
|
|
local IGNORED_OVERFLOW_ID = {
|
|
[3068] = true, --Delver's Journey
|
|
[3143] = true, --Delver's Journey
|
|
};
|
|
|
|
local function WillCurrencyRewardOverflow(currencyID, rewardQuantity)
|
|
if IGNORED_OVERFLOW_ID[currencyID] then
|
|
return false, 0
|
|
end
|
|
local currencyInfo = GetCurrencyInfo(currencyID);
|
|
local quantity = currencyInfo and (currencyInfo.useTotalEarnedForMaxQty and currencyInfo.totalEarned or currencyInfo.quantity);
|
|
local overflow = quantity and currencyInfo.maxQuantity > 0 and rewardQuantity + quantity > currencyInfo.maxQuantity;
|
|
return overflow, quantity, currencyInfo.useTotalEarnedForMaxQty, currencyInfo.maxQuantity
|
|
end
|
|
API.WillCurrencyRewardOverflow = WillCurrencyRewardOverflow;
|
|
|
|
local CoinUtil = {};
|
|
addon.CoinUtil = CoinUtil;
|
|
|
|
CoinUtil.patternGold = L["Match Pattern Gold"];
|
|
CoinUtil.patternSilver = L["Match Pattern Silver"];
|
|
CoinUtil.patternCopper = L["Match Pattern Copper"];
|
|
|
|
function CoinUtil:GetCopperFromCoinText(coinText)
|
|
local rawCopper = 0;
|
|
local gold = match(coinText, self.patternGold);
|
|
local silver = match(coinText, self.patternSilver);
|
|
local copper = match(coinText, self.patternCopper);
|
|
|
|
if gold then
|
|
rawCopper = rawCopper + 10000 * (tonumber(gold) or 0);
|
|
end
|
|
|
|
if silver then
|
|
rawCopper = rawCopper + 100 * (tonumber(silver) or 0);
|
|
end
|
|
|
|
if copper then
|
|
rawCopper = rawCopper + (tonumber(copper) or 0);
|
|
end
|
|
|
|
return rawCopper
|
|
end
|
|
end
|
|
|
|
do -- Chat Message
|
|
--Check if the a rare spawn info has been announced by other players
|
|
local function SearchChatHistory(searchFunc)
|
|
local pool = ChatFrame1 and ChatFrame1.fontStringPool;
|
|
if pool then
|
|
local text, uiMapID, x, y;
|
|
for fontString in pool:EnumerateActive() do
|
|
text = fontString:GetText();
|
|
if text ~= nil then
|
|
if searchFunc(text) then
|
|
return true
|
|
end
|
|
end
|
|
end
|
|
end
|
|
return false
|
|
end
|
|
API.SearchChatHistory = SearchChatHistory;
|
|
end
|
|
|
|
do -- Cursor Position
|
|
local UI_SCALE_RATIO = 1;
|
|
local UIParent = UIParent;
|
|
local EL = CreateFrame("Frame");
|
|
local GetCursorPosition = GetCursorPosition;
|
|
|
|
EL:RegisterEvent("UI_SCALE_CHANGED");
|
|
EL:SetScript("OnEvent", function(self, event)
|
|
UI_SCALE_RATIO = 1 / UIParent:GetEffectiveScale();
|
|
end);
|
|
|
|
local function GetScaledCursorPosition()
|
|
local x, y = GetCursorPosition();
|
|
return x*UI_SCALE_RATIO, y*UI_SCALE_RATIO
|
|
end
|
|
API.GetScaledCursorPosition = GetScaledCursorPosition;
|
|
|
|
function API.GetScaledCursorPositionForFrame(frame)
|
|
local uiScale = frame:GetEffectiveScale();
|
|
local x, y = GetCursorPosition();
|
|
return x / uiScale, y / uiScale;
|
|
end
|
|
end
|
|
|
|
do -- TomTom Compatibility
|
|
local TomTomUtil = {};
|
|
addon.TomTomUtil = TomTomUtil;
|
|
|
|
TomTomUtil.waypointUIDs = {};
|
|
TomTomUtil.pauseCrazyArrowUpdate = false;
|
|
|
|
local TT;
|
|
|
|
function TomTomUtil:IsTomTomAvailable()
|
|
if self.available == nil then
|
|
self.available = (TomTom and TomTom.AddWaypoint and TomTom.RemoveWaypoint and TomTom.SetClosestWaypoint and TomTomCrazyArrow and true) or false
|
|
if self.available then
|
|
TT = TomTom;
|
|
end
|
|
end
|
|
return self.available
|
|
end
|
|
|
|
function TomTomUtil:AddWaypoint(uiMapID, x, y, desc, plumberTag, plumberArg1, plumberArg2)
|
|
--x, y: 0-1
|
|
if self:IsTomTomAvailable() then
|
|
plumberTag = plumberTag or "plumber";
|
|
|
|
local opts = {
|
|
title = desc or "TomTom Waypoint via Plumber",
|
|
from = "Plumber",
|
|
persistent = false, --waypoint will not be saved
|
|
crazy = true,
|
|
cleardistance = 8,
|
|
arrivaldistance = 15,
|
|
world = false, --don't show on WorldMap
|
|
minimap = false,
|
|
plumberTag = plumberTag,
|
|
plumberArg1 = plumberArg1,
|
|
plumberArg2 = plumberArg2,
|
|
};
|
|
|
|
local uid = securecallfunction(TT.AddWaypoint, TT, uiMapID, x, y, opts);
|
|
|
|
if uid then
|
|
if not self.waypointUIDs[uid] then
|
|
self.waypointUIDs[uid] = {plumberTag, plumberArg1, plumberArg2};
|
|
end
|
|
return uid
|
|
else
|
|
return
|
|
end
|
|
end
|
|
end
|
|
|
|
function TomTomUtil:SelectClosestWaypoint()
|
|
local announceInChat = false;
|
|
securecallfunction(TT.SetClosestWaypoint, TT, announceInChat);
|
|
end
|
|
|
|
function TomTomUtil:RemoveWaypoint(uid)
|
|
if self:IsTomTomAvailable() then
|
|
securecallfunction(TT.RemoveWaypoint, TT, uid);
|
|
end
|
|
end
|
|
|
|
function TomTomUtil:RemoveWaypointsByTag(tag)
|
|
for uid, data in pairs(self.waypointUIDs) do
|
|
if data[1] == tag then
|
|
self.waypointUIDs[uid] = nil;
|
|
self:RemoveWaypoint(uid);
|
|
end
|
|
end
|
|
end
|
|
|
|
function TomTomUtil:RemoveWaypointsByRule(rule)
|
|
for uid, data in pairs(self.waypointUIDs) do
|
|
if rule(unpack(data)) then
|
|
self.waypointUIDs[uid] = nil;
|
|
self:RemoveWaypoint(uid);
|
|
end
|
|
end
|
|
end
|
|
|
|
function TomTomUtil:RemoveAllPlumberWaypoints()
|
|
for uid, tag in pairs(self.waypointUIDs) do
|
|
self:RemoveWaypoint(uid);
|
|
end
|
|
self.waypointUIDs = {};
|
|
end
|
|
|
|
function TomTomUtil:GetDistanceToWaypoint(uid)
|
|
return securecallfunction(TT.GetDistanceToWaypoint, TT, uid)
|
|
end
|
|
end
|
|
|
|
do -- Game UI
|
|
local function IsInEditMode()
|
|
return EditModeManagerFrame and EditModeManagerFrame:IsEditModeActive();
|
|
end
|
|
API.IsInEditMode = IsInEditMode;
|
|
end
|
|
|
|
do -- Reputation
|
|
local C_Reputation = C_Reputation;
|
|
local C_MajorFactions = C_MajorFactions;
|
|
local GetFriendshipReputation = C_GossipInfo.GetFriendshipReputation;
|
|
local GetFriendshipReputationRanks = C_GossipInfo.GetFriendshipReputationRanks;
|
|
local GetFactionParagonInfo = C_Reputation.GetFactionParagonInfo or Nop;
|
|
local UnitSex = UnitSex;
|
|
local GetText = GetText;
|
|
|
|
local GetFactionDataByID;
|
|
if C_Reputation.GetFactionDataByID then
|
|
GetFactionDataByID = C_Reputation.GetFactionDataByID;
|
|
else --Classic
|
|
function GetFactionDataByID(factionID)
|
|
local name, description, standingID, barMin, barMax, barValue = GetFactionInfoByID(factionID);
|
|
if name then
|
|
local tbl = {
|
|
name = name,
|
|
factionID = factionID,
|
|
reaction = standingID,
|
|
currentStanding = barValue,
|
|
currentReactionThreshold = barMin,
|
|
nextReactionThreshold = barMax,
|
|
}
|
|
return tbl
|
|
end
|
|
end
|
|
end
|
|
|
|
local function GetReputationProgress(factionID)
|
|
if not factionID then return end;
|
|
|
|
local level, isFull, currentValue, maxValue, name, reputationType, isUnlocked, reaction;
|
|
|
|
local repInfo = GetFriendshipReputation(factionID);
|
|
local paragonRepEarned, paragonThreshold, rewardQuestID, hasRewardPending = GetFactionParagonInfo(factionID);
|
|
|
|
if repInfo and repInfo.friendshipFactionID and repInfo.friendshipFactionID > 0 then
|
|
reputationType = 2;
|
|
name = repInfo.name;
|
|
reaction = repInfo.reaction;
|
|
isUnlocked = true;
|
|
if repInfo.nextThreshold then
|
|
currentValue = repInfo.standing - repInfo.reactionThreshold;
|
|
maxValue = repInfo.nextThreshold - repInfo.reactionThreshold;
|
|
if maxValue == 0 then
|
|
currentValue = 1;
|
|
maxValue = 1;
|
|
end
|
|
else
|
|
currentValue = 1;
|
|
maxValue = 1;
|
|
end
|
|
|
|
local rankInfo = GetFriendshipReputationRanks(repInfo.friendshipFactionID);
|
|
level = rankInfo.currentLevel;
|
|
isFull = level >= rankInfo.maxLevel;
|
|
end
|
|
|
|
if C_Reputation.IsMajorFaction and C_Reputation.IsMajorFaction(factionID) then
|
|
local majorFactionData = C_MajorFactions.GetMajorFactionData(factionID);
|
|
if majorFactionData then
|
|
reputationType = 3;
|
|
maxValue = majorFactionData.renownLevelThreshold;
|
|
local isCapped = C_MajorFactions.HasMaximumRenown(factionID);
|
|
currentValue = isCapped and majorFactionData.renownLevelThreshold or majorFactionData.renownReputationEarned or 0;
|
|
level = majorFactionData.renownLevel;
|
|
name = majorFactionData.name;
|
|
isUnlocked = majorFactionData.isUnlocked;
|
|
end
|
|
end
|
|
|
|
if not reputationType then
|
|
repInfo = GetFactionDataByID(factionID);
|
|
if repInfo then
|
|
reputationType = 1;
|
|
name = repInfo.name;
|
|
isUnlocked = true;
|
|
|
|
if repInfo.currentReactionThreshold then
|
|
currentValue = repInfo.currentStanding - repInfo.currentReactionThreshold;
|
|
maxValue = repInfo.nextReactionThreshold - repInfo.currentReactionThreshold;
|
|
if maxValue == 0 then
|
|
currentValue = 1;
|
|
maxValue = 1;
|
|
end
|
|
else
|
|
currentValue = 1;
|
|
maxValue = 1;
|
|
end
|
|
|
|
local zeroLevel = 4; --Neutral
|
|
reaction = repInfo.reaction;
|
|
level = reaction - zeroLevel;
|
|
isFull = level >= 8; --TEMP DEBUG
|
|
end
|
|
end
|
|
|
|
if C_Reputation.IsFactionParagon and C_Reputation.IsFactionParagon(factionID) then
|
|
isFull = true;
|
|
if paragonRepEarned and paragonThreshold and paragonThreshold ~= 0 then
|
|
local paragonLevel = floor(paragonRepEarned / paragonThreshold);
|
|
currentValue = paragonRepEarned - paragonLevel * paragonThreshold;
|
|
maxValue = paragonThreshold;
|
|
level = paragonLevel;
|
|
end
|
|
end
|
|
|
|
if reputationType then
|
|
local tbl = {
|
|
level = level,
|
|
currentValue = currentValue,
|
|
maxValue = maxValue,
|
|
isFull = isFull,
|
|
name = name,
|
|
reputationType = reputationType, --1:Standard, 2:Friendship
|
|
rewardPending = hasRewardPending,
|
|
isUnlocked = isUnlocked,
|
|
reaction = reaction,
|
|
};
|
|
|
|
return tbl
|
|
end
|
|
end
|
|
API.GetReputationProgress = GetReputationProgress;
|
|
|
|
|
|
local function GetParagonValuesAndLevel(factionID)
|
|
local totalEarned, threshold = GetFactionParagonInfo(factionID);
|
|
if totalEarned and threshold and threshold ~= 0 then
|
|
local paragonLevel = floor(totalEarned / threshold); --How many times the player has reached paragon
|
|
local currentValue = totalEarned - paragonLevel * threshold;
|
|
return currentValue, threshold, paragonLevel
|
|
end
|
|
return 0, 1, 0
|
|
end
|
|
API.GetParagonValuesAndLevel = GetParagonValuesAndLevel;
|
|
|
|
|
|
local function GetReputationStandingText(reaction)
|
|
if type(reaction) == "string" then
|
|
--Friendship
|
|
return reaction
|
|
end
|
|
local gender = UnitSex("player");
|
|
local reputationStandingtext = GetText("FACTION_STANDING_LABEL"..reaction, gender); --GetText: Game API that returns localized texts
|
|
return reputationStandingtext
|
|
end
|
|
API.GetReputationStandingText = GetReputationStandingText;
|
|
|
|
|
|
local function GetFactionStatusText(factionID, simplified)
|
|
--Derived from Blizzard ReputationFrame_InitReputationRow in ReputationFrame.lua
|
|
if not factionID then return end;
|
|
local factionName;
|
|
local p1, description, standingID, barMin, barMax, barValue = GetFactionDataByID(factionID);
|
|
|
|
if type(p1) == "table" then
|
|
standingID = p1.reaction;
|
|
barMin = p1.currentReactionThreshold;
|
|
barMax = p1.nextReactionThreshold;
|
|
barValue = p1.currentStanding;
|
|
factionName = p1.name;
|
|
else
|
|
factionName = p1;
|
|
end
|
|
|
|
local isParagon = C_Reputation.IsFactionParagon and C_Reputation.IsFactionParagon(factionID);
|
|
local isMajorFaction = C_Reputation.IsMajorFaction and C_Reputation.IsMajorFaction(factionID);
|
|
local repInfo = GetFriendshipReputation(factionID);
|
|
|
|
local isCapped;
|
|
local factionStandingtext; --Revered/Junior/Renown 1
|
|
local cappedAlert;
|
|
local isFriendship;
|
|
|
|
if repInfo and repInfo.friendshipFactionID > 0 then --Friendship
|
|
isFriendship = true;
|
|
factionStandingtext = repInfo.reaction;
|
|
|
|
if repInfo.nextThreshold then
|
|
barMin, barMax, barValue = repInfo.reactionThreshold, repInfo.nextThreshold, repInfo.standing;
|
|
else
|
|
barMin, barMax, barValue = 0, 1, 1;
|
|
isCapped = true;
|
|
end
|
|
|
|
local rankInfo = GetFriendshipReputationRanks(repInfo.friendshipFactionID);
|
|
if rankInfo then
|
|
factionStandingtext = factionStandingtext .. format(" (Lv. %s/%s)", rankInfo.currentLevel, rankInfo.maxLevel);
|
|
end
|
|
|
|
elseif isMajorFaction then
|
|
local majorFactionData = C_MajorFactions.GetMajorFactionData(factionID);
|
|
if majorFactionData then
|
|
barMin, barMax = 0, majorFactionData.renownLevelThreshold;
|
|
isCapped = C_MajorFactions.HasMaximumRenown(factionID);
|
|
barValue = isCapped and majorFactionData.renownLevelThreshold or majorFactionData.renownReputationEarned or 0;
|
|
factionStandingtext = L["Renown Level Label"] .. majorFactionData.renownLevel;
|
|
|
|
if isParagon then
|
|
local totalEarned, threshold, rewardQuestID, hasRewardPending = GetFactionParagonInfo(factionID);
|
|
if totalEarned and threshold and threshold ~= 0 then
|
|
local paragonLevel = floor(totalEarned / threshold);
|
|
local currentValue = totalEarned - paragonLevel * threshold;
|
|
factionStandingtext = ("|cff00ccff"..L["Paragon Reputation"].."|r %d/%d"):format(currentValue, threshold);
|
|
end
|
|
|
|
if hasRewardPending then
|
|
cappedAlert = "|cffff4800"..L["Unclaimed Reward Alert"].."|r";
|
|
end
|
|
else
|
|
if isCapped then
|
|
factionStandingtext = factionStandingtext.." "..L["Level Maxed"];
|
|
end
|
|
end
|
|
end
|
|
elseif (standingID and standingID > 0) then
|
|
isCapped = standingID == 8; --MAX_REPUTATION_REACTION
|
|
factionStandingtext = GetReputationStandingText(standingID);
|
|
end
|
|
|
|
local rolloverText; --(0/24000)
|
|
if barMin and barValue and barMax and (not isCapped) then
|
|
rolloverText = format("(%s/%s)", barValue - barMin, barMax - barMin);
|
|
if simplified then
|
|
factionStandingtext = isFriendship and repInfo.reaction or factionStandingtext or "";
|
|
return (factionStandingtext.." "..rolloverText), factionName
|
|
end
|
|
end
|
|
|
|
local text;
|
|
|
|
if factionStandingtext then
|
|
if not text then text = L["Current Colon"] end;
|
|
factionStandingtext = " |cffffffff"..factionStandingtext.."|r";
|
|
text = text .. factionStandingtext;
|
|
end
|
|
|
|
if rolloverText then
|
|
if not text then text = L["Current Colon"] end;
|
|
rolloverText = " |cffffffff"..rolloverText.."|r";
|
|
text = text .. rolloverText;
|
|
end
|
|
|
|
if text then
|
|
text = " \n"..text;
|
|
|
|
if cappedAlert then
|
|
text = text.."\n"..cappedAlert;
|
|
end
|
|
end
|
|
|
|
return text, factionName
|
|
end
|
|
API.GetFactionStatusText = GetFactionStatusText;
|
|
|
|
|
|
local function GetReputationChangeFromText(text)
|
|
local name, amount;
|
|
name, amount = match(text, L["Match Pattern Rep 1"]);
|
|
if not name then
|
|
name, amount = match(text, L["Match Pattern Rep 2"]);
|
|
end
|
|
if name then
|
|
if amount then
|
|
amount = gsub(amount, ",", "");
|
|
amount = tonumber(amount);
|
|
end
|
|
return name, amount
|
|
end
|
|
end
|
|
API.GetReputationChangeFromText = GetReputationChangeFromText;
|
|
|
|
|
|
function API.GetMaxRenownLevel(factionID)
|
|
local renownLevelsInfo = C_MajorFactions.GetRenownLevels(factionID);
|
|
if renownLevelsInfo then
|
|
return renownLevelsInfo[#renownLevelsInfo].level
|
|
end
|
|
end
|
|
end
|
|
|
|
do -- Spell
|
|
local GetSpellInfo_Table = C_Spell.GetSpellInfo;
|
|
local SPELL_INFO_KEYS = {"name", "rank", "iconID", "castTime", "minRange", "maxRange", "spellID", "originalIconID"};
|
|
local function GetSpellInfo_Flat(spellID)
|
|
local info = spellID and GetSpellInfo_Table(spellID);
|
|
if info then
|
|
local tbl = {};
|
|
local n = 0;
|
|
for _, key in ipairs(SPELL_INFO_KEYS) do
|
|
n = n + 1;
|
|
tbl[n] = info[key];
|
|
end
|
|
return unpack(tbl)
|
|
end
|
|
end
|
|
API.GetSpellInfo = GetSpellInfo_Flat;
|
|
|
|
if C_Spell.GetSpellCooldown then
|
|
API.GetSpellCooldown = C_Spell.GetSpellCooldown;
|
|
else
|
|
local GetSpellCooldown = GetSpellCooldown;
|
|
function API.GetSpellCooldown(spell)
|
|
local startTime, duration, isEnabled, modRate = GetSpellCooldown(spell);
|
|
if startTime ~= nil then
|
|
local tbl = {
|
|
startTime = startTime,
|
|
duration = duration,
|
|
isEnabled = isEnabled,
|
|
modRate = modRate,
|
|
};
|
|
return tbl
|
|
end
|
|
end
|
|
end
|
|
|
|
if C_Spell.GetSpellCharges then
|
|
API.GetSpellCharges = C_Spell.GetSpellCharges;
|
|
else
|
|
local GetSpellCharges = GetSpellCharges;
|
|
function API.GetSpellCharges(spell)
|
|
local currentCharges, maxCharges, cooldownStartTime, cooldownDuration, chargeModRate = GetSpellCharges(spell);
|
|
if currentCharges then
|
|
local tbl = {
|
|
currentCharges = currentCharges,
|
|
maxCharges = maxCharges,
|
|
cooldownStartTime = cooldownStartTime,
|
|
cooldownDuration = cooldownDuration,
|
|
chargeModRate = chargeModRate,
|
|
};
|
|
return tbl
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
do -- System
|
|
if true then --IS_TWW
|
|
local GetMouseFoci = GetMouseFoci;
|
|
|
|
local function GetMouseFocus()
|
|
local objects = GetMouseFoci();
|
|
return objects and objects[1]
|
|
end
|
|
API.GetMouseFocus = GetMouseFocus;
|
|
else
|
|
API.GetMouseFocus = GetMouseFocus;
|
|
end
|
|
|
|
|
|
local ModifierKeyName = {
|
|
LSHIFT = "Shift",
|
|
LCTRL = "Ctrl",
|
|
LALT = "Alt",
|
|
};
|
|
|
|
if IsMacClient and IsMacClient() then
|
|
--Mac OS
|
|
ModifierKeyName.LCTRL = "Command";
|
|
ModifierKeyName.LALT = "Option";
|
|
end
|
|
|
|
ModifierKeyName.RSHIFT = ModifierKeyName.LSHIFT;
|
|
ModifierKeyName.RCTRL = ModifierKeyName.LCTRL;
|
|
ModifierKeyName.RALT = ModifierKeyName.LALT;
|
|
|
|
function API.GetModifierKeyName(key)
|
|
if key and ModifierKeyName[key] then
|
|
return ModifierKeyName[key]
|
|
end
|
|
end
|
|
|
|
|
|
function API.HandleModifiedItemClick(link, itemLocation)
|
|
if InCombatLockdown() then return false end;
|
|
|
|
if IsModifiedClick("CHATLINK") then
|
|
if ( ChatEdit_InsertLink(link) ) then
|
|
return true
|
|
elseif SocialPostFrame and Social_IsShown() then
|
|
Social_InsertLink(link);
|
|
return true
|
|
end
|
|
end
|
|
|
|
if IsModifiedClick("DRESSUP") then
|
|
if itemLocation then
|
|
return DressUpItemLocation(itemLocation)
|
|
end
|
|
return DressUpLink(link)
|
|
end
|
|
end
|
|
|
|
|
|
function API.ToggleBlizzardTokenUIIfWarbandCurrency(currencyID)
|
|
if InCombatLockdown() then return end;
|
|
|
|
local info = currencyID and C_CurrencyInfo.GetCurrencyInfo(currencyID);
|
|
if not (info and info.isAccountTransferable) then return end;
|
|
|
|
local onlyShow = false; --If true, don't hide the frame when shown
|
|
ToggleCharacter("TokenFrame", onlyShow);
|
|
end
|
|
|
|
|
|
function API.AddButtonToAddonCompartment(identifier, name, icon, onClickFunc, onEnterFunc, onLeaveFunc)
|
|
local f = AddonCompartmentFrame;
|
|
if not f then return end;
|
|
|
|
for index, addonData in ipairs(f.registeredAddons) do
|
|
if addonData.identifier == identifier then
|
|
return
|
|
end
|
|
end
|
|
|
|
local addonData = {
|
|
identifier = identifier,
|
|
text = name,
|
|
icon = icon,
|
|
func = onClickFunc,
|
|
funcOnEnter = onEnterFunc,
|
|
funcOnLeave = onLeaveFunc,
|
|
};
|
|
|
|
f:RegisterAddon(addonData)
|
|
end
|
|
|
|
|
|
function API.RemoveButtonFromAddonCompartment(identifier)
|
|
local f = AddonCompartmentFrame;
|
|
if not f then return end;
|
|
|
|
for index, addonData in ipairs(f.registeredAddons) do
|
|
if addonData.identifier == identifier then
|
|
table.remove(f.registeredAddons, index);
|
|
f:UpdateDisplay();
|
|
return
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
function API.TriggerExpansionMinimapButtonAlert(text)
|
|
if ExpansionLandingPageMinimapButton then
|
|
ExpansionLandingPageMinimapButton:TriggerAlert(text);
|
|
end
|
|
end
|
|
|
|
function API.CloseBossBanner()
|
|
local banner = BossBanner;
|
|
if not banner then return end;
|
|
|
|
banner:StopAnimating();
|
|
banner:Hide();
|
|
banner.lootShown = 0;
|
|
banner.pendingLoot = {};
|
|
|
|
if banner.baseHeight then
|
|
banner:SetHeight(banner.baseHeight);
|
|
end
|
|
|
|
if banner.LootFrames then
|
|
for _, f in ipairs(banner.LootFrames) do
|
|
f:Hide();
|
|
end
|
|
end
|
|
|
|
local textureKeys = {
|
|
"BannerTop", "BannerBottom", "BannerMiddle", "BottomFillagree", "SkullSpikes", "RightFillagree", "LeftFillagree",
|
|
"Title", "SubTitle", "FlashBurst", "FlashBurstLeft", "FlashBurstCenter", "RedFlash",
|
|
};
|
|
|
|
for _, key in ipairs(textureKeys) do
|
|
if banner[key] then
|
|
banner[key]:SetAlpha(0);
|
|
end
|
|
end
|
|
|
|
TopBannerManager_BannerFinished();
|
|
end
|
|
end
|
|
|
|
do -- Player
|
|
local function GetPlayerMaxLevel()
|
|
local serverExpansionLevel = GetServerExpansionLevel();
|
|
local maxLevel = GetMaxLevelForExpansionLevel(serverExpansionLevel);
|
|
return maxLevel or 80
|
|
end
|
|
API.GetPlayerMaxLevel = GetPlayerMaxLevel;
|
|
|
|
|
|
local function IsPlayerAtMaxLevel()
|
|
local maxLevel = GetPlayerMaxLevel();
|
|
local playerLevel = UnitLevel("player");
|
|
return playerLevel >= maxLevel
|
|
end
|
|
API.IsPlayerAtMaxLevel = IsPlayerAtMaxLevel;
|
|
|
|
|
|
function API.IsGreatVaultFeatureAvailable()
|
|
return IsPlayerAtMaxLevel() and C_WeeklyRewards ~= nil;
|
|
end
|
|
end
|
|
|
|
do -- Scenario
|
|
--[[
|
|
local SCENARIO_DELVES = addon.L["Scenario Delves"] or "Delves";
|
|
|
|
local GetScenarioInfo = C_ScenarioInfo.GetScenarioInfo;
|
|
|
|
local function IsInDelves()
|
|
local scenarioInfo = GetScenarioInfo();
|
|
return scenarioInfo and scenarioInfo.name == SCENARIO_DELVES
|
|
end
|
|
API.IsInDelves = IsInDelves;
|
|
--]]
|
|
end
|
|
|
|
do -- ObjectPool
|
|
local ObjectPoolMixin = {};
|
|
|
|
function ObjectPoolMixin:RemoveObject(obj)
|
|
obj:Hide();
|
|
obj:ClearAllPoints();
|
|
|
|
if obj.OnRemoved then
|
|
obj:OnRemoved();
|
|
end
|
|
end
|
|
|
|
function ObjectPoolMixin:RecycleObject(obj)
|
|
local isActive;
|
|
|
|
for i, activeObject in ipairs(self.activeObjects) do
|
|
if activeObject == obj then
|
|
tremove(self.activeObjects, i);
|
|
isActive = true;
|
|
break
|
|
end
|
|
end
|
|
|
|
if isActive then
|
|
self:RemoveObject(obj);
|
|
self.numUnused = self.numUnused + 1;
|
|
self.unusedObjects[self.numUnused] = obj;
|
|
end
|
|
end
|
|
|
|
function ObjectPoolMixin:CreateObject()
|
|
local obj = self.createObjectFunc();
|
|
tinsert(self.objects, obj);
|
|
obj.Release = self.Object_Release;
|
|
return obj
|
|
end
|
|
|
|
function ObjectPoolMixin:Acquire()
|
|
local obj;
|
|
|
|
if self.numUnused > 0 then
|
|
obj = tremove(self.unusedObjects, self.numUnused);
|
|
self.numUnused = self.numUnused - 1;
|
|
end
|
|
|
|
if not obj then
|
|
obj = self:CreateObject();
|
|
end
|
|
|
|
tinsert(self.activeObjects, obj);
|
|
obj:Show();
|
|
|
|
return obj
|
|
end
|
|
|
|
function ObjectPoolMixin:ReleaseAll()
|
|
if #self.activeObjects == 0 then return end;
|
|
|
|
for _, obj in ipairs(self.activeObjects) do
|
|
self:RemoveObject(obj);
|
|
end
|
|
|
|
self.activeObjects = {};
|
|
self.unusedObjects = {};
|
|
|
|
for index, obj in ipairs(self.objects) do
|
|
self.unusedObjects[index] = obj;
|
|
end
|
|
|
|
self.numUnused = #self.objects;
|
|
end
|
|
|
|
function ObjectPoolMixin:GetTotalObjects()
|
|
return #self.objects
|
|
end
|
|
|
|
function ObjectPoolMixin:CallAllObjects(method, ...)
|
|
for i, obj in ipairs(self.objects) do
|
|
obj[method](obj, ...);
|
|
end
|
|
end
|
|
|
|
function ObjectPoolMixin:Object_Release()
|
|
--Override
|
|
end
|
|
|
|
function ObjectPoolMixin:GetActiveObjects()
|
|
return self.activeObjects
|
|
end
|
|
|
|
local function CreateObjectPool(createObjectFunc)
|
|
local pool = {};
|
|
API.Mixin(pool, ObjectPoolMixin);
|
|
|
|
local function Object_Release(f)
|
|
pool:RecycleObject(f);
|
|
end
|
|
pool.Object_Release = Object_Release;
|
|
|
|
pool.objects = {};
|
|
pool.activeObjects = {};
|
|
pool.unusedObjects = {};
|
|
pool.numUnused = 0;
|
|
pool.createObjectFunc = createObjectFunc;
|
|
|
|
return pool
|
|
end
|
|
API.CreateObjectPool = CreateObjectPool;
|
|
end
|
|
|
|
do -- Transmog
|
|
local GetItemInfo = C_TransmogCollection.GetItemInfo;
|
|
local PlayerKnowsSource = C_TransmogCollection.PlayerHasTransmogItemModifiedAppearance;
|
|
|
|
local function IsUncollectedTransmogByItemInfo(itemInfo)
|
|
--C_TransmogCollection.PlayerHasTransmogByItemInfo isn't reliable
|
|
local visualID, sourceID =GetItemInfo(itemInfo);
|
|
if sourceID and sourceID ~= 0 and (not PlayerKnowsSource(sourceID)) then
|
|
return true
|
|
end
|
|
end
|
|
API.IsUncollectedTransmogByItemInfo = IsUncollectedTransmogByItemInfo
|
|
|
|
if not addon.IsToCVersionEqualOrNewerThan(40000) then
|
|
API.IsUncollectedTransmogByItemInfo = Nop;
|
|
end
|
|
end
|
|
|
|
do -- Quest
|
|
local GetRegularQuestTitle = C_QuestLog.GetTitleForQuestID or C_QuestLog.GetQuestInfo;
|
|
local RequestLoadQuest = C_QuestLog.RequestLoadQuestByID or Nop;
|
|
local GetLogIndexForQuestID = C_QuestLog.GetLogIndexForQuestID or GetQuestLogIndexByID or Nop;
|
|
|
|
local function GetQuestName(questID)
|
|
local questName = C_TaskQuest.GetQuestInfoByQuestID(questID);
|
|
if not questName then
|
|
questName = GetRegularQuestTitle(questID);
|
|
end
|
|
if questName and questName ~= "" then
|
|
return questName
|
|
else
|
|
RequestLoadQuest(questID);
|
|
end
|
|
end
|
|
API.GetQuestName = GetQuestName;
|
|
|
|
function API.IsQuestRewardCached(questID)
|
|
--We use this to query Faction Paragon rewards, so numQuestRewards should always > 0
|
|
--May be 0 during the first query
|
|
|
|
local numQuestRewards = GetNumQuestLogRewards(questID);
|
|
if numQuestRewards > 0 then
|
|
local getterFunc = GetQuestLogRewardInfo;
|
|
local itemName, itemTexture, quantity, quality, isUsable, itemID;
|
|
for i = 1, numQuestRewards do
|
|
itemName, itemTexture, quantity, quality, isUsable, itemID = getterFunc(i, questID);
|
|
if not itemName then
|
|
return false
|
|
end
|
|
end
|
|
return true
|
|
else
|
|
return false
|
|
end
|
|
end
|
|
|
|
if addon.IS_CLASSIC then
|
|
--Classic
|
|
function API.GetQuestProgressPercent(questID, asText)
|
|
local value, max = 0, 0;
|
|
local questLogIndex = questID and GetLogIndexForQuestID(questID);
|
|
|
|
if questLogIndex and questLogIndex ~= 0 then
|
|
local numObjectives = GetNumQuestLeaderBoards(questLogIndex);
|
|
local text, objectiveType, finished, fulfilled, required;
|
|
for objectiveIndex = 1, numObjectives do
|
|
text, objectiveType, finished = GetQuestLogLeaderBoard(objectiveIndex, questLogIndex);
|
|
--print(questID, GetQuestName(questID), numObjectives, finished, fulfilled, required)
|
|
if objectiveType ~= "spell" and objectiveType ~= "log" then
|
|
fulfilled, required = match(text, "(%d+)/(%d+)");
|
|
if not (fulfilled and required) then
|
|
fulfilled = 0;
|
|
required = 1;
|
|
end
|
|
if fulfilled > required then
|
|
fulfilled = required;
|
|
end
|
|
value = value + fulfilled;
|
|
max = max + required;
|
|
end
|
|
end
|
|
else
|
|
return
|
|
end
|
|
|
|
if max == 0 then
|
|
value = 0;
|
|
max = 1;
|
|
end
|
|
|
|
if asText then
|
|
return floor(100 * value / max).."%"
|
|
else
|
|
return value / max
|
|
end
|
|
end
|
|
|
|
function API.GetQuestProgressTexts(questID, hideFinishedObjectives)
|
|
local questLogIndex = questID and GetLogIndexForQuestID(questID);
|
|
|
|
if questLogIndex and questLogIndex ~= 0 then
|
|
local texts = {};
|
|
if IsQuestComplete(questID) then
|
|
texts[1] = QUEST_PROGRESS_TOOLTIP_QUEST_READY_FOR_TURN_IN or ("|cff20ff20"..L["Ready To Turn In Tooltip"].."|r");
|
|
return texts
|
|
end
|
|
|
|
local numObjectives = GetNumQuestLeaderBoards(questLogIndex);
|
|
local text, objectiveType, finished, fulfilled, required;
|
|
|
|
for objectiveIndex = 1, numObjectives do
|
|
text, objectiveType, finished = GetQuestLogLeaderBoard(objectiveIndex, questLogIndex);
|
|
text = text or "";
|
|
if (objectiveType ~= "spell" and objectiveType ~= "log") and ((not finished) or not hideFinishedObjectives) then
|
|
if finished then
|
|
tinsert(texts, format("|cff808080- %s|r", text));
|
|
else
|
|
tinsert(texts, format("- %s", text));
|
|
end
|
|
end
|
|
end
|
|
|
|
return texts
|
|
else
|
|
if not C_QuestLog.IsOnQuest(questID) then
|
|
local texts = {};
|
|
|
|
if C_QuestLog.IsQuestFlaggedCompleted(questID) then
|
|
texts[1] = format("|cff808080%s|r", QUEST_COMPLETE);
|
|
else
|
|
texts[1] = format("|cffff2020%s|r", L["Not On Quest"]);
|
|
local description = API.GetDescriptionFromTooltip(questID);
|
|
if description and description ~= QUEST_TOOLTIP_REQUIREMENTS then
|
|
tinsert(texts, " ");
|
|
tinsert(texts, description);
|
|
end
|
|
end
|
|
|
|
return texts;
|
|
end
|
|
end
|
|
end
|
|
else
|
|
--Retail
|
|
function API.GetQuestProgressPercent(questID, asText)
|
|
--Unify progression text and bar
|
|
--C_QuestLog.GetNumQuestObjectives
|
|
|
|
local value, max = 0, 0;
|
|
local questLogIndex = questID and GetLogIndexForQuestID(questID);
|
|
|
|
if questLogIndex and questLogIndex ~= 0 then
|
|
local numObjectives = GetNumQuestLeaderBoards(questLogIndex);
|
|
local text, objectiveType, finished, fulfilled, required;
|
|
for objectiveIndex = 1, numObjectives do
|
|
text, objectiveType, finished, fulfilled, required = GetQuestObjectiveInfo(questID, objectiveIndex, false);
|
|
--print(questID, GetQuestName(questID), numObjectives, finished, fulfilled, required)
|
|
if fulfilled > required then
|
|
fulfilled = required;
|
|
end
|
|
|
|
if objectiveType == "progressbar" then
|
|
fulfilled = 0.01 * GetQuestProgressBarPercent(questID);
|
|
required = 1;
|
|
else
|
|
if not finished then
|
|
if fulfilled == required then
|
|
--"Complete the scenario Nightfall" fulfilled = required = 1 when accepting the quest
|
|
fulfilled = 0;
|
|
end
|
|
end
|
|
end
|
|
value = value + fulfilled;
|
|
max = max + required;
|
|
end
|
|
else
|
|
return
|
|
end
|
|
|
|
if max == 0 then
|
|
value = 0;
|
|
max = 1;
|
|
end
|
|
|
|
if asText then
|
|
return floor(100 * value / max).."%"
|
|
else
|
|
return value / max
|
|
end
|
|
end
|
|
|
|
function API.GetQuestProgressTexts(questID, hideFinishedObjectives)
|
|
local questLogIndex = questID and GetLogIndexForQuestID(questID);
|
|
|
|
if questLogIndex and questLogIndex ~= 0 then
|
|
local texts = {};
|
|
if C_QuestLog.ReadyForTurnIn(questID) then
|
|
texts[1] = QUEST_PROGRESS_TOOLTIP_QUEST_READY_FOR_TURN_IN;
|
|
return texts
|
|
end
|
|
|
|
local numObjectives = GetNumQuestLeaderBoards(questLogIndex);
|
|
local text, objectiveType, finished, fulfilled, required;
|
|
|
|
for objectiveIndex = 1, numObjectives do
|
|
text, objectiveType, finished, fulfilled, required = GetQuestObjectiveInfo(questID, objectiveIndex, false);
|
|
text = text or "";
|
|
if (not finished) or not hideFinishedObjectives then
|
|
if objectiveType == "progressbar" then
|
|
fulfilled = GetQuestProgressBarPercent(questID);
|
|
fulfilled = floor(fulfilled);
|
|
if finished then
|
|
tinsert(texts, format("|cff808080- %s%% %s|r", fulfilled, text));
|
|
else
|
|
tinsert(texts, format("- %s", text));
|
|
end
|
|
else
|
|
if finished then
|
|
tinsert(texts, format("|cff808080- %s|r", text));
|
|
else
|
|
tinsert(texts, format("- %s", text));
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
return texts
|
|
else
|
|
if not C_QuestLog.IsOnQuest(questID) then
|
|
local texts = {};
|
|
|
|
if C_QuestLog.IsQuestFlaggedCompleted(questID) then
|
|
texts[1] = format("|cff808080%s|r", QUEST_COMPLETE);
|
|
else
|
|
texts[1] = format("|cffff2020%s|r", L["Not On Quest"]);
|
|
local description = API.GetDescriptionFromTooltip(questID);
|
|
if description and description ~= QUEST_TOOLTIP_REQUIREMENTS then
|
|
tinsert(texts, " ");
|
|
tinsert(texts, description);
|
|
end
|
|
end
|
|
|
|
return texts;
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
function API.GetQuestRewards(questID)
|
|
--Ignore XP, Money --GetQuestLogRewardXP()
|
|
|
|
local rewards;
|
|
local missingData = false;
|
|
|
|
local function SortFunc_QualityID(a, b)
|
|
if a.quality ~= b.quality then
|
|
return a.quality > b.quality
|
|
end
|
|
|
|
if a.id ~= b.id then
|
|
return a.id > b.id
|
|
end
|
|
|
|
if a.quantity ~= b.quantity then
|
|
return a.quantity > b.quantity
|
|
end
|
|
|
|
return true
|
|
end
|
|
|
|
if C_QuestLog.GetQuestRewardCurrencies and C_QuestInfoSystem.HasQuestRewardCurrencies(questID) then
|
|
local currencies = {};
|
|
local currencyRewards = C_QuestLog.GetQuestRewardCurrencies(questID);
|
|
local currencyID, quality;
|
|
local info;
|
|
|
|
for index, currencyReward in ipairs(currencyRewards) do
|
|
currencyID = currencyReward.currencyID;
|
|
quality = C_CurrencyInfo.GetCurrencyInfo(currencyID).quality;
|
|
info = {
|
|
name = currencyReward.name,
|
|
texture = currencyReward.texture,
|
|
quantity = currencyReward.totalRewardAmount,
|
|
id = currencyID,
|
|
questRewardContextFlags = currencyReward.questRewardContextFlags,
|
|
quality = quality,
|
|
};
|
|
tinsert(currencies, info);
|
|
end
|
|
|
|
table.sort(currencies, SortFunc_QualityID);
|
|
|
|
if not rewards then
|
|
rewards = {};
|
|
end
|
|
rewards.currencies = currencies;
|
|
|
|
if #currencyRewards == 0 then
|
|
missingData = true;
|
|
end
|
|
elseif GetQuestLogRewardCurrencyInfo then
|
|
local numCurrencies = GetNumQuestLogRewardCurrencies(questID) or 0;
|
|
local name, texture, quantity, currencyID, quality;
|
|
local currencies;
|
|
for i = 1, numCurrencies do
|
|
name, texture, quantity, currencyID, quality = GetQuestLogRewardCurrencyInfo(i, questID);
|
|
if name then
|
|
if not currencies then
|
|
currencies = {};
|
|
end
|
|
local info = {
|
|
name = name,
|
|
texture = texture,
|
|
quantity = quantity,
|
|
id = currencyID,
|
|
questRewardContextFlags = 0,
|
|
quality = quality,
|
|
};
|
|
tinsert(currencies, info);
|
|
else
|
|
missingData = true;
|
|
end
|
|
end
|
|
|
|
if currencies then
|
|
table.sort(currencies, SortFunc_QualityID);
|
|
if not rewards then
|
|
rewards = {};
|
|
end
|
|
rewards.currencies = currencies;
|
|
end
|
|
end
|
|
|
|
if C_QuestInfoSystem.GetQuestRewardSpells and C_QuestInfoSystem.HasQuestRewardSpells(questID) then
|
|
local spells = {};
|
|
local spellRewards = C_QuestInfoSystem.GetQuestRewardSpells(questID);
|
|
local info;
|
|
for index, spellID in ipairs(spellRewards) do
|
|
info = C_QuestInfoSystem.GetQuestRewardSpellInfo(questID, spellID);
|
|
info.id = spellID;
|
|
tinsert(spells, info);
|
|
end
|
|
|
|
table.sort(spells,
|
|
function(a, b)
|
|
if a.id ~= b.id then
|
|
return a.id > b.id
|
|
end
|
|
|
|
return true
|
|
end
|
|
);
|
|
|
|
if not rewards then
|
|
rewards = {};
|
|
end
|
|
rewards.spells = spells;
|
|
end
|
|
|
|
local numItems = GetNumQuestLogRewards(questID);
|
|
|
|
if numItems > 0 then
|
|
local items = {};
|
|
local name, texture, quantity, quality, isUsable, itemID, itemLevel;
|
|
local info;
|
|
for index = 1, numItems do
|
|
name, texture, quantity, quality, isUsable, itemID, itemLevel = GetQuestLogRewardInfo(index, questID);
|
|
if name and itemID then
|
|
info = {
|
|
name = name,
|
|
texture = texture,
|
|
quantity = quantity,
|
|
quality = quality,
|
|
id = itemID,
|
|
};
|
|
tinsert(items, info);
|
|
else
|
|
missingData = true;
|
|
end
|
|
end
|
|
|
|
table.sort(items, SortFunc_QualityID);
|
|
|
|
if not rewards then
|
|
rewards = {};
|
|
end
|
|
rewards.items = items;
|
|
end
|
|
|
|
local honor = GetQuestLogRewardHonor(questID);
|
|
if honor > 0 then
|
|
if not rewards then
|
|
rewards = {};
|
|
end
|
|
rewards.honor = honor;
|
|
end
|
|
|
|
return rewards, missingData
|
|
end
|
|
|
|
--[[
|
|
function YeetQuestForMap(uiMapID)
|
|
--Only contains quests with visible marker on the map
|
|
if not uiMapID then
|
|
uiMapID = C_Map.GetBestMapForUnit("player");
|
|
end
|
|
|
|
local function PrintQuests(quests)
|
|
if not quests then return end;
|
|
local questID, name;
|
|
for k, v in ipairs(quests) do
|
|
questID = v.questID;
|
|
name = GetQuestName(questID);
|
|
if name then
|
|
print(questID, name);
|
|
else
|
|
CallbackRegistry:LoadQuest(questID, function(_questID)
|
|
print(questID, GetQuestName(questID));
|
|
end);
|
|
end
|
|
end
|
|
end
|
|
|
|
PrintQuests(C_TaskQuest.GetQuestsOnMap(uiMapID));
|
|
print(" ");
|
|
PrintQuests(C_QuestLog.GetQuestsOnMap(uiMapID));
|
|
end
|
|
--]]
|
|
end
|
|
|
|
do -- Tooltip
|
|
if C_TooltipInfo then
|
|
addon.TooltipAPI = C_TooltipInfo;
|
|
else
|
|
--For Classic where C_TooltipInfo doesn't exist:
|
|
|
|
local TooltipAPI = {};
|
|
local CreateColor = CreateColor;
|
|
local TOOLTIP_NAME = "PlumberClassicVirtualTooltip";
|
|
local TP = CreateFrame("GameTooltip", TOOLTIP_NAME, nil, "GameTooltipTemplate");
|
|
local UIParent = UIParent;
|
|
|
|
TP:SetOwner(UIParent, 'ANCHOR_NONE');
|
|
TP:SetClampedToScreen(false);
|
|
TP:SetPoint("TOPLEFT", UIParent, "BOTTOMLEFT", 0, -128);
|
|
TP:Show();
|
|
TP:SetScript("OnUpdate", nil);
|
|
|
|
|
|
local UpdateFrame = CreateFrame("Frame");
|
|
|
|
local function UpdateTooltipInfo_OnUpdate(self, elapsed)
|
|
self.t = self.t + elapsed;
|
|
if self.t > 0.2 then
|
|
self.t = 0;
|
|
self:SetScript("OnUpdate", nil);
|
|
addon.CallbackRegistry:Trigger("SharedTooltip.TOOLTIP_DATA_UPDATE", 0);
|
|
end
|
|
end
|
|
|
|
function UpdateFrame:OnItemChanged(numLines)
|
|
self.t = 0;
|
|
self.numLines = numLines;
|
|
self:SetScript("OnUpdate", UpdateTooltipInfo_OnUpdate);
|
|
end
|
|
|
|
local function GetTooltipHyperlink()
|
|
local name, link = TP:GetItem();
|
|
if link then
|
|
return link
|
|
end
|
|
|
|
name, link = TP:GetSpell();
|
|
if link then
|
|
return "spell:"..link
|
|
end
|
|
end
|
|
|
|
local function GetTooltipTexts()
|
|
local numLines = TP:NumLines();
|
|
if numLines == 0 then return end;
|
|
|
|
local tooltipData = {};
|
|
tooltipData.dataInstanceID = 0;
|
|
|
|
local addItemLevel;
|
|
local itemLink = GetTooltipHyperlink();
|
|
|
|
if itemLink then
|
|
if itemLink ~= TP.hyperlink then
|
|
UpdateFrame:OnItemChanged(numLines);
|
|
end
|
|
|
|
if API.IsEquippableItem(itemLink) then
|
|
addItemLevel = API.GetItemLevel(itemLink);
|
|
end
|
|
end
|
|
|
|
TP.hyperlink = itemLink;
|
|
tooltipData.hyperlink = itemLink;
|
|
|
|
local lines = {};
|
|
local n = 0;
|
|
|
|
local fs, text;
|
|
for i = 1, numLines do
|
|
if i == 2 and addItemLevel then
|
|
n = n + 1;
|
|
lines[n] = {
|
|
leftText = L["Format Item Level"]:format(addItemLevel);
|
|
leftColor = CreateColor(1, 0.82, 0),
|
|
};
|
|
end
|
|
|
|
fs = _G[TOOLTIP_NAME.."TextLeft"..i];
|
|
if fs then
|
|
n = n + 1;
|
|
|
|
local r, g, b = fs:GetTextColor();
|
|
text = fs:GetText();
|
|
local lineData = {
|
|
leftText = text,
|
|
leftColor = CreateColor(r, g, b),
|
|
rightText = nil,
|
|
wrapText = true,
|
|
leftOffset = 0,
|
|
};
|
|
|
|
fs = _G[TOOLTIP_NAME.."TextRight"..i];
|
|
if fs then
|
|
text = fs:GetText();
|
|
if text and text ~= "" then
|
|
r, g, b = fs:GetTextColor();
|
|
lineData.rightText = text;
|
|
lineData.rightColor = CreateColor(r, g, b);
|
|
end
|
|
end
|
|
|
|
lines[n] = lineData;
|
|
end
|
|
end
|
|
|
|
local sellPrice = API.GetItemSellPrice(itemLink);
|
|
if sellPrice then
|
|
n = n + 1;
|
|
lines[n] = {
|
|
leftText = "", --this will be ignored by our tooltip
|
|
price = sellPrice,
|
|
};
|
|
end
|
|
|
|
tooltipData.lines = lines;
|
|
return tooltipData
|
|
end
|
|
|
|
do
|
|
local accessors = {
|
|
SetItemByID = "GetItemByID",
|
|
SetCurrencyByID = "GetCurrencyByID",
|
|
SetQuestItem = "GetQuestItem",
|
|
SetQuestCurrency = "GetQuestCurrency",
|
|
SetSpellByID = "GetSpellByID",
|
|
SetItemByGUID = "GetItemByGUID",
|
|
SetHyperlink = "GetHyperlink",
|
|
};
|
|
|
|
for accessor, getterName in pairs(accessors) do
|
|
if TP[accessor] then
|
|
local function GetterFunc(...)
|
|
TP:ClearLines();
|
|
TP:SetOwner(UIParent, "ANCHOR_PRESERVE");
|
|
TP[accessor](TP, ...);
|
|
return GetTooltipTexts();
|
|
end
|
|
|
|
TooltipAPI[getterName] = GetterFunc;
|
|
end
|
|
end
|
|
end
|
|
|
|
addon.TooltipAPI = TooltipAPI;
|
|
end
|
|
|
|
|
|
local function SetTooltipWithPostCall(tooltip, tooltipPostCall, getterName, ...)
|
|
local tooltipInfo = {
|
|
getterName = getterName,
|
|
getterArgs = { ... };
|
|
};
|
|
tooltipInfo.tooltipPostCall = tooltipPostCall;
|
|
tooltip:ProcessInfo(tooltipInfo);
|
|
end
|
|
API.SetTooltipWithPostCall = SetTooltipWithPostCall;
|
|
|
|
|
|
function API.GetDescriptionFromTooltip(questID)
|
|
if questID then
|
|
local hyperlink = "|Hquest:"..questID.."|h";
|
|
local data = addon.TooltipAPI.GetHyperlink(hyperlink);
|
|
if data then
|
|
return data.lines[3] and data.lines[3].leftText or nil
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
local PseudoTooltipInfoMixin = {};
|
|
do
|
|
function PseudoTooltipInfoMixin:AddBlankLine()
|
|
tinsert(self.tooltipData.lines, {
|
|
leftText = " ",
|
|
wrapText = true,
|
|
});
|
|
end
|
|
|
|
function PseudoTooltipInfoMixin:AddLine(text, r, g, b, wrapText)
|
|
local color;
|
|
if type(r) == "table" then
|
|
color = r;
|
|
else
|
|
color = CreateColor(r, g, b);
|
|
end
|
|
tinsert(self.tooltipData.lines, {
|
|
leftText = text,
|
|
leftColor = color,
|
|
wrapText = true,
|
|
});
|
|
end
|
|
|
|
function PseudoTooltipInfoMixin:AddDoubleLine(leftText, rightText, leftR, leftG, leftB, rightR, rightG, rightB)
|
|
local leftColor, rightColor;
|
|
if type(leftR) == "table" then
|
|
leftColor = leftR;
|
|
rightColor = leftG;
|
|
else
|
|
leftColor = CreateColor(leftR, leftG, leftB);
|
|
rightColor = CreateColor(rightR, rightG, rightB);
|
|
end
|
|
tinsert(self.tooltipData.lines, {
|
|
leftText = leftText,
|
|
leftColor = leftColor,
|
|
rightText = rightText,
|
|
rightColor = rightColor,
|
|
});
|
|
end
|
|
end
|
|
|
|
function API.CreateAppendTooltipInfo()
|
|
local info = {};
|
|
info.append = true;
|
|
info.tooltipData = {};
|
|
info.tooltipData.lines = {};
|
|
API.Mixin(info, PseudoTooltipInfoMixin);
|
|
info:AddBlankLine();
|
|
return info
|
|
end
|
|
|
|
|
|
local TextureInfoTable = {
|
|
width = 14,
|
|
height = 14,
|
|
margin = { left = 0, right = 4, top = 0, bottom = 0 },
|
|
texCoords = { left = 0.0625, right = 0.9375, top = 0.0625, bottom = 0.9375 },
|
|
};
|
|
|
|
function API.AddCraftingReagentToTooltip(tooltip, item, quantityRequired)
|
|
local name = C_Item.GetItemNameByID(item) or ("item:"..item);
|
|
local count = C_Item.GetItemCount(item, true, false, true, true);
|
|
local icon = C_Item.GetItemIconByID(item);
|
|
local rightText;
|
|
local isRed;
|
|
|
|
if quantityRequired then
|
|
rightText = count.."/"..quantityRequired;
|
|
isRed = count < quantityRequired;
|
|
else
|
|
rightText = count;
|
|
end
|
|
|
|
if isRed then
|
|
tooltip:AddDoubleLine(name, rightText, 1, 0.125, 0.125, 1, 0.125, 0.125);
|
|
else
|
|
tooltip:AddDoubleLine(name, rightText, 1, 1, 1, 1, 1, 1);
|
|
end
|
|
|
|
tooltip:AddTexture(icon, TextureInfoTable);
|
|
|
|
return true
|
|
end
|
|
end
|
|
|
|
do -- AsyncCallback
|
|
local AsyncCallback = CreateFrame("Frame");
|
|
|
|
--LoadQuestAPI is not available in 60 Classic
|
|
--In this case we will run all callbacks when the time is up
|
|
AsyncCallback.WoWAPI_LoadQuest = C_QuestLog.RequestLoadQuestByID;
|
|
AsyncCallback.WoWAPI_LoadItem = C_Item.RequestLoadItemDataByID;
|
|
AsyncCallback.WoWAPI_LoadSpell = C_Spell.RequestLoadSpellData;
|
|
|
|
local CreatureNameCache = {};
|
|
|
|
function AsyncCallback:RunAllCallbacks(list)
|
|
for id, callbacks in pairs(list) do
|
|
for _, callbackInfo in ipairs(callbacks) do
|
|
if (callbackInfo.oneTime and not callbackInfo.processed) or (callbackInfo.oneTime == false) then
|
|
callbackInfo.processed = true;
|
|
callbackInfo.func(id);
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
function AsyncCallback:OnEvent(event, ...)
|
|
local id, success = ...
|
|
local list;
|
|
|
|
if event == "QUEST_DATA_LOAD_RESULT" then
|
|
list = self.questCallbacks;
|
|
elseif event == "ITEM_DATA_LOAD_RESULT" then
|
|
list = self.itemCallbacks;
|
|
elseif event == "SPELL_DATA_LOAD_RESULT" then
|
|
list = self.spellCallbacks;
|
|
end
|
|
|
|
if list and id and success then
|
|
if list[id] then
|
|
for _, callbackInfo in ipairs(list[id]) do
|
|
if (callbackInfo.oneTime and not callbackInfo.processed) or (callbackInfo.oneTime == false) then
|
|
callbackInfo.processed = true;
|
|
callbackInfo.func(id);
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
self.t = 0;
|
|
end
|
|
AsyncCallback:SetScript("OnEvent", AsyncCallback.OnEvent);
|
|
|
|
function AsyncCallback:OnUpdate(elapsed)
|
|
self.t = self.t + elapsed;
|
|
if self.t > 0.5 then
|
|
self.t = nil;
|
|
self:SetScript("OnUpdate", nil);
|
|
|
|
if self.questCallbacks then
|
|
if self.LoadQuest then
|
|
self:UnregisterEvent("QUEST_DATA_LOAD_RESULT");
|
|
end
|
|
if self.runCallbackAfter then
|
|
self:RunAllCallbacks(self.questCallbacks);
|
|
end
|
|
self.questCallbacks = nil;
|
|
end
|
|
|
|
if self.itemCallbacks then
|
|
if self.LoadItem then
|
|
self:UnregisterEvent("ITEM_DATA_LOAD_RESULT");
|
|
end
|
|
self:RunAllCallbacks(self.itemCallbacks);
|
|
self.itemCallbacks = nil;
|
|
end
|
|
|
|
if self.spellCallbacks then
|
|
if self.LoadSpell then
|
|
self:UnregisterEvent("SPELL_DATA_LOAD_RESULT");
|
|
end
|
|
self:RunAllCallbacks(self.spellCallbacks);
|
|
self.spellCallbacks = nil;
|
|
end
|
|
|
|
if self.creatureCallbacks then
|
|
for id, callbacks in pairs(self.creatureCallbacks) do
|
|
local name = API.GetCreatureName(id);
|
|
if name and name ~= "" then
|
|
CreatureNameCache[id] = name;
|
|
else
|
|
name = nil;
|
|
end
|
|
if name then
|
|
for _, callbackInfo in ipairs(callbacks) do
|
|
if not callbackInfo.processed then
|
|
callbackInfo.processed = true;
|
|
callbackInfo.func(id, name);
|
|
end
|
|
end
|
|
end
|
|
end
|
|
self.creatureCallbacks = nil;
|
|
end
|
|
end
|
|
end
|
|
|
|
function AsyncCallback:AddCallback(key, id, callback, oneTime)
|
|
if not self[key] then
|
|
self[key] = {};
|
|
end
|
|
|
|
if not self[key][id] then
|
|
self[key][id] = {};
|
|
end
|
|
|
|
if oneTime == nil then
|
|
oneTime = true;
|
|
end
|
|
|
|
local callbackInfo = {
|
|
func = callback,
|
|
oneTime = oneTime,
|
|
processed = false,
|
|
};
|
|
|
|
tinsert(self[key][id], callbackInfo);
|
|
end
|
|
|
|
|
|
function CallbackRegistry:LoadQuest(id, callback, oneTime)
|
|
AsyncCallback:AddCallback("questCallbacks", id, callback, oneTime);
|
|
if AsyncCallback.WoWAPI_LoadQuest then
|
|
AsyncCallback:RegisterEvent("QUEST_DATA_LOAD_RESULT");
|
|
AsyncCallback.WoWAPI_LoadQuest(id);
|
|
else
|
|
AsyncCallback.runCallbackAfter = true;
|
|
end
|
|
AsyncCallback.t = 0;
|
|
AsyncCallback:SetScript("OnUpdate", AsyncCallback.OnUpdate);
|
|
end
|
|
|
|
function CallbackRegistry:LoadItem(id, callback, oneTime)
|
|
AsyncCallback:AddCallback("itemCallbacks", id, callback, oneTime);
|
|
if AsyncCallback.WoWAPI_LoadItem then
|
|
AsyncCallback:RegisterEvent("ITEM_DATA_LOAD_RESULT");
|
|
AsyncCallback.WoWAPI_LoadItem(id);
|
|
else
|
|
AsyncCallback.runCallbackAfter = true;
|
|
end
|
|
AsyncCallback.t = 0;
|
|
AsyncCallback:SetScript("OnUpdate", AsyncCallback.OnUpdate);
|
|
end
|
|
|
|
function CallbackRegistry:LoadSpell(id, callback, oneTime)
|
|
AsyncCallback:AddCallback("spellCallbacks", id, callback, oneTime);
|
|
if AsyncCallback.WoWAPI_LoadSpell then
|
|
AsyncCallback:RegisterEvent("SPELL_DATA_LOAD_RESULT");
|
|
AsyncCallback.WoWAPI_LoadSpell(id);
|
|
else
|
|
AsyncCallback.runCallbackAfter = true;
|
|
end
|
|
AsyncCallback.t = 0;
|
|
AsyncCallback:SetScript("OnUpdate", AsyncCallback.OnUpdate);
|
|
end
|
|
|
|
function CallbackRegistry:LoadCreature(id, callback)
|
|
--Usually used to get npc name
|
|
AsyncCallback:AddCallback("creatureCallbacks", id, callback);
|
|
AsyncCallback.t = 0;
|
|
AsyncCallback:SetScript("OnUpdate", AsyncCallback.OnUpdate);
|
|
end
|
|
|
|
|
|
function API.GetAndCacheCreatureName(creatureID)
|
|
if CreatureNameCache[creatureID] then
|
|
return CreatureNameCache[creatureID]
|
|
end
|
|
local name = API.GetCreatureName(creatureID);
|
|
if name and name ~= "" then
|
|
CreatureNameCache[creatureID] = name;
|
|
else
|
|
name = nil;
|
|
end
|
|
return name
|
|
end
|
|
end
|
|
|
|
do -- Container Item Processor
|
|
local GetItemCount = C_Item.GetItemCount;
|
|
local GetContainerNumSlots = C_Container.GetContainerNumSlots;
|
|
local GetContainerItemID = C_Container.GetContainerItemID;
|
|
local GetItemInfoInstant = C_Item.GetItemInfoInstant;
|
|
local GetBagItem = C_TooltipInfo and C_TooltipInfo.GetBagItem;
|
|
|
|
local function GetItemBagPosition(itemID)
|
|
local count = GetItemCount(itemID); --unused arg2: Include banks
|
|
if count and count > 0 then
|
|
for bagID = 0, 4 do
|
|
for slotID = 1, GetContainerNumSlots(bagID) do
|
|
if(GetContainerItemID(bagID, slotID) == itemID) then
|
|
return bagID, slotID
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
API.GetItemBagPosition = GetItemBagPosition;
|
|
|
|
local Processor = CreateFrame("Frame");
|
|
local ITEM_OPENABLE = ITEM_OPENABLE or "<Right Click to Open>";
|
|
local OPENABLE_ITEM = {};
|
|
|
|
function Processor:OnUpdate_Queue(elapsed)
|
|
self.t = self.t + elapsed;
|
|
if self.t > 0.1 then
|
|
self.t = 0;
|
|
self:SetScript("OnUpdate", nil);
|
|
local itemID;
|
|
local anyMatch;
|
|
|
|
for bagID = 0, 4 do
|
|
for slotID = 1, GetContainerNumSlots(bagID) do
|
|
itemID = GetContainerItemID(bagID, slotID);
|
|
if self.queue[itemID] ~= nil then
|
|
if self.queue[itemID].bagPosition == nil then
|
|
anyMatch = true;
|
|
self.queue[itemID].bagPosition = {bagID, slotID};
|
|
if OPENABLE_ITEM[itemID] == nil then
|
|
GetBagItem(bagID, slotID);
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
if anyMatch then
|
|
self:SetScript("OnUpdate", self.OnUpdate_Tooltip);
|
|
end
|
|
end
|
|
end
|
|
|
|
function Processor:OnUpdate_Tooltip(elapsed)
|
|
self.t = self.t + elapsed;
|
|
if self.t > 0.1 then
|
|
self.t = 0;
|
|
self:SetScript("OnUpdate", nil);
|
|
local tooltipData;
|
|
local lines;
|
|
local leftText;
|
|
local openable;
|
|
local bag, slot;
|
|
for itemID, v in pairs(self.queue) do
|
|
if v.bagPosition then
|
|
bag = v.bagPosition[1];
|
|
slot = v.bagPosition[2];
|
|
tooltipData = GetBagItem(bag, slot);
|
|
|
|
if OPENABLE_ITEM[itemID] then
|
|
openable = true;
|
|
else
|
|
openable = false;
|
|
if tooltipData then
|
|
lines = tooltipData.lines;
|
|
leftText = lines[#lines].leftText;
|
|
openable = leftText and leftText == ITEM_OPENABLE
|
|
OPENABLE_ITEM[itemID] = openable;
|
|
end
|
|
end
|
|
|
|
if openable then
|
|
for callback in pairs(v) do
|
|
if callback ~= "bagPosition" then
|
|
callback(bag, slot)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
self.queue = nil;
|
|
end
|
|
end
|
|
|
|
function API.InquiryOpenableItem(itemID, callback)
|
|
--Pre-exclude invalid item types
|
|
|
|
if OPENABLE_ITEM[itemID] == false then
|
|
return false
|
|
end
|
|
|
|
if not Processor.queue then
|
|
Processor.queue = {};
|
|
end
|
|
|
|
if not Processor.queue[itemID] then
|
|
Processor.queue[itemID] = {};
|
|
end
|
|
|
|
callback = callback or Nop;
|
|
|
|
Processor.queue[itemID][callback] = true;
|
|
|
|
Processor.t = 0;
|
|
Processor:SetScript("OnUpdate", Processor.OnUpdate_Queue);
|
|
end
|
|
|
|
function API.DoesItemReallyExist(item)
|
|
local a = item and GetItemInfoInstant(item);
|
|
return a ~= nil
|
|
end
|
|
|
|
function API.IsItemContextToken(item)
|
|
local _, _, _, _, _, classID, subClassID = GetItemInfoInstant(item);
|
|
return classID == 5 and subClassID == 2
|
|
end
|
|
end
|
|
|
|
do -- Chat Message
|
|
local ADDON_ICON = "|TInterface\\AddOns\\Plumber\\Art\\Logo\\PlumberLogo32:0:0|t";
|
|
local function PrintMessage(msg)
|
|
if not msg then
|
|
msg = "";
|
|
end
|
|
print(ADDON_ICON.." |cffb8c8d1Plumber:|r "..msg);
|
|
end
|
|
API.PrintMessage = PrintMessage;
|
|
|
|
function API.DisplayErrorMessage(msg)
|
|
if not msg then return end;
|
|
local messageType = 0;
|
|
UIErrorsFrame:TryDisplayMessage(messageType, (ADDON_ICON.." |cffb8c8d1Plumber:|r ")..msg, RED_FONT_COLOR:GetRGB());
|
|
end
|
|
|
|
function API.CheckAndDisplayErrorIfInCombat()
|
|
if InCombatLockdown() then
|
|
API.DisplayErrorMessage(L["Error Show UI In Combat"]);
|
|
return true
|
|
else
|
|
return false
|
|
end
|
|
end
|
|
end
|
|
|
|
do -- Custom Hyperlink ItemRef
|
|
|
|
--[[--Example
|
|
local CustomLink = {};
|
|
|
|
CustomLink.typeName = "Test";
|
|
CustomLink.colorCode = "66bbff"; --LINK_FONT_COLOR
|
|
|
|
function CustomLink.callback(arg1, arg2, arg3)
|
|
print(arg1, arg2, arg3);
|
|
end
|
|
|
|
API.AddCustomLinkType(CustomLink.typeName, CustomLink.callback, CustomLink.colorCode);
|
|
|
|
function CustomLink.GenerateLink(arg1, arg2, arg3)
|
|
return API.GenerateCustomLink(CustomLink.typeName, L["Click To See Details"], arg1, arg2, arg3);
|
|
end
|
|
--]]
|
|
|
|
local CustomLinkUtil = {};
|
|
|
|
function API.AddCustomLinkType(typeName, callback, colorCode)
|
|
CustomLinkUtil[typeName] = {
|
|
callback = callback,
|
|
colorCode = colorCode,
|
|
};
|
|
end
|
|
|
|
function API.GenerateCustomLink(typeName, displayedText, ...)
|
|
if CustomLinkUtil[typeName] then
|
|
if not CustomLinkUtil.registered then
|
|
CustomLinkUtil.registered = true;
|
|
EventRegistry:RegisterCallback("SetItemRef", function(_, link, text, button, chatFrame)
|
|
if link then
|
|
local _typeName, subText = match(link, "plumber:([^:]+):([^|]+)");
|
|
if _typeName and CustomLinkUtil[_typeName] then
|
|
local args = {};
|
|
for arg in string.gmatch(subText, "[^:]+") do
|
|
tinsert(args, arg);
|
|
end
|
|
CustomLinkUtil[_typeName].callback(unpack(args));
|
|
end
|
|
end
|
|
end);
|
|
end
|
|
|
|
--|cffxxxxxx|Htype:payload|h[text]|h|r
|
|
local args = {...};
|
|
local link = "|Haddon:plumber:"..typeName;
|
|
|
|
for i, v in ipairs(args) do
|
|
link = link..":"..v;
|
|
end
|
|
|
|
link = format("|cff%s%s|h[%s]|h|r", CustomLinkUtil[typeName].colorCode or "ffd100", link, displayedText);
|
|
|
|
return link
|
|
end
|
|
end
|
|
end
|
|
|
|
do -- 11.0 Menu Formatter
|
|
function API.ShowBlizzardMenu(ownerRegion, schematic, contextData)
|
|
contextData = contextData or {};
|
|
|
|
local menu = MenuUtil.CreateContextMenu(ownerRegion, function(ownerRegion, rootDescription)
|
|
rootDescription:SetTag(schematic.tag, contextData);
|
|
|
|
for _, info in ipairs(schematic.objects) do
|
|
local elementDescription;
|
|
if info.type == "Title" then
|
|
elementDescription = rootDescription:CreateTitle();
|
|
elementDescription:AddInitializer(function(f, description, menu)
|
|
f.fontString:SetText(info.name);
|
|
end);
|
|
elseif info.type == "Divider" then
|
|
elementDescription = rootDescription:CreateDivider();
|
|
elseif info.type == "Spacer" then
|
|
elementDescription = rootDescription:CreateSpacer();
|
|
elseif info.type == "Button" then
|
|
elementDescription = rootDescription:CreateButton(info.name, info.OnClick);
|
|
elseif info.type == "Checkbox" then
|
|
elementDescription = rootDescription:CreateCheckbox(info.name, info.IsSelected, info.ToggleSelected);
|
|
end
|
|
|
|
if info.IsEnabledFunc then
|
|
local enabled = info.IsEnabledFunc();
|
|
elementDescription:SetEnabled(enabled);
|
|
end
|
|
|
|
if info.tooltip then
|
|
elementDescription:SetTooltip(function(tooltip, elementDescription)
|
|
--GameTooltip_AddInstructionLine(tooltip, "Test Tooltip Instruction");
|
|
--GameTooltip_AddErrorLine(tooltip, "Test Tooltip Colored Line");
|
|
if info.DynamicTooltipFunc then
|
|
local text, r, g, b = info.DynamicTooltipFunc();
|
|
if text then
|
|
GameTooltip_SetTitle(tooltip, MenuUtil.GetElementText(elementDescription));
|
|
tooltip:AddLine(text, r, g, b, true);
|
|
end
|
|
else
|
|
GameTooltip_SetTitle(tooltip, MenuUtil.GetElementText(elementDescription));
|
|
GameTooltip_AddNormalLine(tooltip, info.tooltip);
|
|
end
|
|
end);
|
|
end
|
|
|
|
if info.rightText or info.rightTexture then
|
|
local rightText;
|
|
if type(info.rightText) == "function" then
|
|
rightText = info.rightText();
|
|
else
|
|
rightText = info.rightText;
|
|
end
|
|
elementDescription:AddInitializer(function(button, description, menu)
|
|
local rightWidth = 0;
|
|
|
|
if info.rightTexture then
|
|
local iconSize = 18;
|
|
local rightTexture = button:AttachTexture();
|
|
rightTexture:SetSize(iconSize, iconSize);
|
|
rightTexture:SetPoint("RIGHT");
|
|
rightTexture:SetTexture(info.rightTexture);
|
|
rightWidth = rightWidth + iconSize;
|
|
rightWidth = 20;
|
|
end
|
|
|
|
local fontString = button.fontString;
|
|
fontString:SetTextColor(NORMAL_FONT_COLOR:GetRGB());
|
|
|
|
local fontString2;
|
|
if info.rightText then
|
|
fontString2 = button:AttachFontString();
|
|
fontString2:SetHeight(20);
|
|
fontString2:SetPoint("RIGHT", button, "RIGHT", 0, 0);
|
|
fontString2:SetJustifyH("RIGHT");
|
|
fontString2:SetText(rightText);
|
|
fontString2:SetTextColor(0.5, 0.5, 0.5);
|
|
rightWidth = fontString2:GetWrappedWidth() + 20;
|
|
end
|
|
|
|
local width = fontString:GetWrappedWidth() + rightWidth;
|
|
local height = 20;
|
|
|
|
return width, height;
|
|
end);
|
|
end
|
|
end
|
|
end);
|
|
|
|
if schematic.onMenuClosedCallback then
|
|
menu:SetClosedCallback(schematic.onMenuClosedCallback);
|
|
end
|
|
|
|
return menu
|
|
end
|
|
end
|
|
|
|
do -- Slash Commands
|
|
local SlashCmdUtil = {};
|
|
SlashCmdUtil.functions = {};
|
|
SlashCmdUtil.alias = "plmr";
|
|
SlashCmdUtil.cmdID = {
|
|
DrawerMacro = 1,
|
|
};
|
|
|
|
function SlashCmdUtil.Process(input)
|
|
if input and type(input) == "string" then
|
|
input = " "..input;
|
|
local token;
|
|
local args = {};
|
|
for arg in string.gmatch(input, "%s+([%S]+)") do
|
|
if not token then
|
|
token = arg;
|
|
else
|
|
tinsert(args, arg);
|
|
end
|
|
end
|
|
|
|
if token and SlashCmdUtil.functions[token] then
|
|
SlashCmdUtil.functions[token](unpack(args));
|
|
end
|
|
end
|
|
end
|
|
|
|
function SlashCmdUtil.CreateSlashCommand(func, alias1, alias2)
|
|
local name = "PLUMBERCMD";
|
|
if alias1 then
|
|
_G["SLASH_"..name.."1"] = "/"..alias1;
|
|
end
|
|
if alias2 then
|
|
_G["SLASH_"..name.."2"] = "/"..alias2;
|
|
end
|
|
SlashCmdList[name] = func;
|
|
end
|
|
|
|
function API.AddSlashSubcommand(name, func)
|
|
if not SlashCmdUtil.cmdID[name] then return end;
|
|
|
|
if not SlashCmdUtil.cmdAdded then
|
|
SlashCmdUtil.CreateSlashCommand(SlashCmdUtil.Process, SlashCmdUtil.alias);
|
|
end
|
|
|
|
local token = tostring(SlashCmdUtil.cmdID[name]);
|
|
SlashCmdUtil.functions[token] = func;
|
|
end
|
|
|
|
function API.GetSlashSubcommand(name)
|
|
if SlashCmdUtil.cmdID[name] then
|
|
return string.format("/%s %s", SlashCmdUtil.alias, SlashCmdUtil.cmdID[name]);
|
|
end
|
|
end
|
|
end
|
|
|
|
do -- Macro Util
|
|
local WoWAPI = {
|
|
IsSpellKnown = C_SpellBook.IsSpellKnown or IsSpellKnownOrOverridesKnown or IsSpellKnown,
|
|
IsPlayerSpell = IsPlayerSpell,
|
|
PlayerHasToy = PlayerHasToy or Nop,
|
|
GetItemCount = C_Item.GetItemCount,
|
|
GetItemCraftedQualityByItemInfo = C_TradeSkillUI and C_TradeSkillUI.GetItemCraftedQualityByItemInfo or Nop,
|
|
GetItemReagentQualityByItemInfo = C_TradeSkillUI and C_TradeSkillUI.GetItemReagentQualityByItemInfo or Nop,
|
|
--IsConsumableItem = C_Item.IsConsumableItem or Nop, --This is not what we thought it is
|
|
GetItemInfoInstant = C_Item.GetItemInfoInstant,
|
|
FindPetIDByName = C_PetJournal and C_PetJournal.FindPetIDByName or Nop,
|
|
GetPetInfoBySpeciesID = C_PetJournal and C_PetJournal.GetPetInfoBySpeciesID or Nop,
|
|
};
|
|
|
|
function API.CanPlayerPerformAction(actionType, arg1, arg2)
|
|
if actionType == "spell" then
|
|
return WoWAPI.IsSpellKnown(arg1) or WoWAPI.IsPlayerSpell(arg1)
|
|
elseif actionType == "item" then
|
|
if API.IsToyItem(arg1) then
|
|
return WoWAPI.PlayerHasToy(arg1)
|
|
else
|
|
local _, _, _, _, _, classID, subClassID = WoWAPI.GetItemInfoInstant(arg1);
|
|
|
|
--always return true for conumable items in case player needs to restock
|
|
--if classID == 0 then
|
|
-- return true
|
|
--end
|
|
local isConsumable = classID == 0;
|
|
|
|
local count = WoWAPI.GetItemCount(arg1, true, true, true, true);
|
|
return count > 0, isConsumable
|
|
end
|
|
end
|
|
|
|
return true --always return true for unrecognized action
|
|
end
|
|
|
|
function API.GetItemCraftingQuality(item)
|
|
local quality = WoWAPI.GetItemCraftedQualityByItemInfo(item);
|
|
if not quality then
|
|
quality = WoWAPI.GetItemReagentQualityByItemInfo(item);
|
|
end
|
|
return quality
|
|
end
|
|
|
|
function API.GetPetNameAndUsability(speciesID, checkUsability)
|
|
local name = WoWAPI.GetPetInfoBySpeciesID(speciesID);
|
|
if checkUsability then
|
|
local _, petGUID = WoWAPI.FindPetIDByName(name);
|
|
return name, petGUID ~= nil
|
|
else
|
|
return name
|
|
end
|
|
end
|
|
end
|
|
|
|
do --Professions
|
|
--/dump ProfessionsBook_GetSpellBookItemSlot(GetMouseFoci()[1]) --Used on ProfessionsBookFrame SpellButton
|
|
|
|
local GetProfessions = GetProfessions;
|
|
local GetProfessionInfo = GetProfessionInfo;
|
|
local GetSpellBookItemType = (C_SpellBook and C_SpellBook.GetSpellBookItemType) or GetSpellBookItemInfo;
|
|
|
|
function API.GetProfessionSpellInfo(professionOrderIndex)
|
|
local prof1, prof2, archaeology, fishing, cooking = GetProfessions();
|
|
local index;
|
|
|
|
if professionOrderIndex == 2 then
|
|
index = prof2;
|
|
else
|
|
index = prof1;
|
|
end
|
|
|
|
if not index then return end;
|
|
|
|
local name, texture, rank, maxRank, numSpells, spellOffset, skillLine, rankModifier, specializationIndex, specializationOffset, skillLineName = GetProfessionInfo(index);
|
|
if not spellOffset then return end;
|
|
|
|
local buttonID = 1; --PrimaryProfessionSpellButtonBottom
|
|
local slotIndex = spellOffset + buttonID;
|
|
local activeSpellBank = 0; --Enum.SpellBookSpellBank.Player
|
|
local itemType, actionID, spellID = GetSpellBookItemType(slotIndex, activeSpellBank);
|
|
|
|
--Classic
|
|
if not spellID then
|
|
spellID = actionID;
|
|
end
|
|
|
|
local tbl = {
|
|
spellID = spellID,
|
|
texture = texture,
|
|
name = name,
|
|
slotIndex = slotIndex,
|
|
activeSpellBank = activeSpellBank,
|
|
skillLine = skillLine,
|
|
};
|
|
|
|
return tbl
|
|
end
|
|
|
|
if C_TradeSkillUI and C_TradeSkillUI.OpenTradeSkill then
|
|
--Retail
|
|
function API.OpenProfessionFrame(professionOrderIndex)
|
|
local info = API.GetProfessionSpellInfo(professionOrderIndex);
|
|
if info then
|
|
local currBaseProfessionInfo = C_TradeSkillUI.GetBaseProfessionInfo();
|
|
if (not currBaseProfessionInfo) or (currBaseProfessionInfo.professionID ~= info.skillLine) then
|
|
C_TradeSkillUI.OpenTradeSkill(info.skillLine);
|
|
--C_SpellBook.CastSpellBookItem(info.slotIndex, info.activeSpellBank);
|
|
else
|
|
C_TradeSkillUI.CloseTradeSkill();
|
|
end
|
|
end
|
|
end
|
|
else
|
|
--Classic
|
|
function API.OpenProfessionFrame(professionOrderIndex)
|
|
local info = API.GetProfessionSpellInfo(professionOrderIndex);
|
|
if info then
|
|
CastSpell(info.slotIndex, "professions");
|
|
end
|
|
end
|
|
end
|
|
PlumberGlobals.OpenProfessionFrame = API.OpenProfessionFrame;
|
|
end
|
|
|
|
do --Addon Skin
|
|
local AddOnSkinHandler = {
|
|
ElvUI = {
|
|
global = "ElvUI",
|
|
root = function() local E = ElvUI[1]; return E:GetModule("Skins") end,
|
|
handlerKey = {
|
|
editbox = "HandleEditBox";
|
|
};
|
|
},
|
|
};
|
|
|
|
function API.SetupSkinExternal(object)
|
|
local objectType = object:GetObjectType();
|
|
objectType = string.lower(objectType);
|
|
|
|
for addOnName, v in pairs(AddOnSkinHandler) do
|
|
if _G[v.global] then
|
|
local root = v.root();
|
|
if v.handlerKey[objectType] then
|
|
root[v.handlerKey[objectType]](root, object);
|
|
return true, addOnName
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
do --FrameUtil
|
|
function API.RegisterFrameForEvents(frame, events)
|
|
for i, event in ipairs(events) do
|
|
frame:RegisterEvent(event);
|
|
end
|
|
end
|
|
|
|
function API.UnregisterFrameForEvents(frame, events)
|
|
for i, event in ipairs(events) do
|
|
frame:UnregisterEvent(event);
|
|
end
|
|
end
|
|
end
|
|
|
|
do --Locale-dependent API
|
|
local locale = GetLocale();
|
|
if locale == "ruRU" then
|
|
function API.GetItemCountFromText(text)
|
|
--"r%s*[xх](%d+)" doesn't work
|
|
local count = match(text, "r%s*x(%d+)");
|
|
if not count then
|
|
count = match(text, "r%s*х(%d+)");
|
|
end
|
|
if count then
|
|
return tonumber(count)
|
|
else
|
|
return 1
|
|
end
|
|
end
|
|
elseif locale == "zhCN" or locale == "zhTW" then
|
|
function API.GetItemCountFromText(text)
|
|
local count = match(text, "r%s*x(%d+)");
|
|
if count then
|
|
return tonumber(count)
|
|
else
|
|
return 1
|
|
end
|
|
end
|
|
else
|
|
function API.GetItemCountFromText(text)
|
|
local count = match(text, "|r%s*x(%d+)");
|
|
if count then
|
|
return tonumber(count)
|
|
else
|
|
return 1
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
--[[
|
|
local DEBUG = CreateFrame("Frame");
|
|
DEBUG:RegisterUnitEvent("UNIT_SPELLCAST_CHANNEL_START", "player");
|
|
DEBUG:RegisterUnitEvent("UNIT_SPELLCAST_CHANNEL_STOP", "player");
|
|
DEBUG:RegisterUnitEvent("UNIT_SPELLCAST_SUCCEEDED", "player");
|
|
|
|
DEBUG:SetScript("OnEvent", function(self, event, ...)
|
|
print(event);
|
|
if event == "UNIT_SPELLCAST_SUCCEEDED" then
|
|
local name, text, texture, startTime, endTime, isTradeSkill = UnitChannelInfo("player");
|
|
self.endTime = endTime;
|
|
elseif event == "UNIT_SPELLCAST_CHANNEL_STOP" then
|
|
local t = GetTime();
|
|
t = t * 1000;
|
|
if self.endTime then
|
|
local diff = t - self.endTime;
|
|
if diff < 200 and diff > -200 then
|
|
print("Natural Complete")
|
|
else
|
|
print("Interrupted")
|
|
end
|
|
end
|
|
end
|
|
end);
|
|
--]]
|