local _ , addon = ...
local API = addon.API ;
local IS_TWW = addon.IsGame_11_0_0 ;
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
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 ;
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 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 ;
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 ;
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 [ 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 = 172800 ;
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 )
--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 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 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 " , math.floor ( seconds / 60 ) , math.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 ;
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 ;
end
do -- Tooltip Parser
local GetInfoByHyperlink = 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 ) ;
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 = floor ( x * 10000 + 0.5 ) / 10000 ;
y = floor ( y * 10000 + 0.5 ) / 10000 ;
PlumberDevData.POIPositions [ poiID ] = {
id = poiID ,
mapID = uiMapID ,
continent = continentMapID ,
cx = x ,
cy = y ,
} ;
end
C_Map.ClearUserWaypoint ( ) ;
else
print ( " No user waypoint found. " )
end
end ) ;
end
API.ConvertMapPositionToContinentPosition = ConvertMapPositionToContinentPosition ;
--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 )
if not Converter then
Converter = CreateFrame ( " Frame " ) ;
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 = x ,
y = y ,
poiID = poiID ,
} ;
onCoordReceivedFunc ( positionData )
C_Map.ClearUserWaypoint ( ) ;
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 ;
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
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 ;
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 : IsShown ( ) ;
end
API.IsInEditMode = IsInEditMode ;
end
do --Reputation
local GetFriendshipReputation = C_GossipInfo.GetFriendshipReputation ;
local GetFriendshipReputationRanks = C_GossipInfo.GetFriendshipReputationRanks ;
local function GetFriendshipProgress ( factionID )
local repInfo = factionID and GetFriendshipReputation ( factionID ) ;
if repInfo and repInfo.friendshipFactionID and repInfo.friendshipFactionID > 0 then
local currentValue , maxValue ;
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 ) ;
local level = rankInfo.currentLevel ;
local isFull = level >= rankInfo.maxLevel ;
return level , isFull , currentValue , maxValue
end
end
API.GetFriendshipProgress = GetFriendshipProgress ;
end
do --Spell
if IS_TWW then
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 ;
else
API.GetSpellInfo = GetSpellInfo ;
end
end
do --System
if IS_TWW then
local GetMouseFoci = GetMouseFoci ;
local function GetMouseFocus ( )
local objects = GetMouseFoci ( ) ;
return objects and objects [ 1 ]
end
API.GetMouseFocus = GetMouseFocus ;
else
API.GetMouseFocus = GetMouseFocus ;
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 ;
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
--[[
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 ) ;
--]]