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

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);
--]]