local _ , private = ...
local twipe = table.wipe
local UnitExists , UnitPlayerOrPetInRaid , UnitGUID =
UnitExists , UnitPlayerOrPetInRaid , UnitGUID
local module = private : NewModule ( " TargetScanning " )
--Traditional loop scanning method tables
local targetScanCount = { }
local bossuIdCache = { }
--UNIT_TARGET scanning method table
local unitScanCount = 0
local unitMonitor = { }
function module : OnModuleEnd ( )
twipe ( targetScanCount )
twipe ( bossuIdCache )
unitScanCount = 0
twipe ( unitMonitor )
end
do
local CL = DBM_COMMON_L
local bossTargetuIds = {
" boss1 " , " boss2 " , " boss3 " , " boss4 " , " boss5 " , " boss6 " , " boss7 " , " boss8 " , " boss9 " , " boss10 " , " focus " , " target "
}
local fullUids = {
" boss1 " , " boss2 " , " boss3 " , " boss4 " , " boss5 " , " boss6 " , " boss7 " , " boss8 " , " boss9 " , " boss10 " ,
" mouseover " , " target " , " focus " , " focustarget " , " targettarget " , " mouseovertarget " ,
" party1target " , " party2target " , " party3target " , " party4target " ,
" raid1target " , " raid2target " , " raid3target " , " raid4target " , " raid5target " , " raid6target " , " raid7target " , " raid8target " , " raid9target " , " raid10target " ,
" raid11target " , " raid12target " , " raid13target " , " raid14target " , " raid15target " , " raid16target " , " raid17target " , " raid18target " , " raid19target " , " raid20target " ,
" raid21target " , " raid22target " , " raid23target " , " raid24target " , " raid25target " , " raid26target " , " raid27target " , " raid28target " , " raid29target " , " raid30target " ,
" raid31target " , " raid32target " , " raid33target " , " raid34target " , " raid35target " , " raid36target " , " raid37target " , " raid38target " , " raid39target " , " raid40target " ,
" nameplate1 " , " nameplate2 " , " nameplate3 " , " nameplate4 " , " nameplate5 " , " nameplate6 " , " nameplate7 " , " nameplate8 " , " nameplate9 " , " nameplate10 " ,
" nameplate11 " , " nameplate12 " , " nameplate13 " , " nameplate14 " , " nameplate15 " , " nameplate16 " , " nameplate17 " , " nameplate18 " , " nameplate19 " , " nameplate20 " ,
" nameplate21 " , " nameplate22 " , " nameplate23 " , " nameplate24 " , " nameplate25 " , " nameplate26 " , " nameplate27 " , " nameplate28 " , " nameplate29 " , " nameplate30 " ,
" nameplate31 " , " nameplate32 " , " nameplate33 " , " nameplate34 " , " nameplate35 " , " nameplate36 " , " nameplate37 " , " nameplate38 " , " nameplate39 " , " nameplate40 "
}
local function getBossTarget ( guid , scanOnlyBoss )
local name , uid , bossuid
local cacheuid = bossuIdCache [ guid ] or " boss1 "
--Try to check last used unit token cache before iterating again.
if UnitGUID ( cacheuid ) == guid then
bossuid = cacheuid
name = DBM : GetUnitFullName ( cacheuid .. " target " )
uid = cacheuid .. " target "
bossuIdCache [ guid ] = bossuid
end
if name then return name , uid , bossuid end
--Else, perform iteration again
local unitID = DBM : GetUnitIdFromGUID ( guid , scanOnlyBoss )
if unitID then
bossuid = unitID
name = DBM : GetUnitFullName ( unitID .. " target " )
uid = unitID .. " target "
bossuIdCache [ guid ] = bossuid
end
return name , uid , bossuid
end
function module : GetBossTarget ( mod , cidOrGuid , scanOnlyBoss )
local name , uid , bossuid
DBM : Debug ( " GetBossTarget firing for : " .. tostring ( mod ) .. " " .. tostring ( cidOrGuid ) .. " " .. tostring ( scanOnlyBoss ) , 3 )
if type ( cidOrGuid ) == " number " then --CID passed, slower and slighty more hacky scan
cidOrGuid = cidOrGuid or mod.creatureId
local cacheuid = bossuIdCache [ cidOrGuid ] or " boss1 "
if mod : GetUnitCreatureId ( cacheuid ) == cidOrGuid then
bossuIdCache [ cidOrGuid ] = cacheuid
bossuIdCache [ UnitGUID ( cacheuid ) ] = cacheuid
name , uid , bossuid = getBossTarget ( UnitGUID ( cacheuid ) , scanOnlyBoss )
else
local usedTable = scanOnlyBoss and bossTargetuIds or fullUids
for _ , uId in ipairs ( usedTable ) do
if mod : GetUnitCreatureId ( uId ) == cidOrGuid then
bossuIdCache [ cidOrGuid ] = uId
bossuIdCache [ UnitGUID ( uId ) ] = uId
name , uid , bossuid = getBossTarget ( UnitGUID ( uId ) , scanOnlyBoss )
break
end
end
end
else
name , uid , bossuid = getBossTarget ( cidOrGuid , scanOnlyBoss )
end
if uid then
local cid = mod : GetUnitCreatureId ( uid )
if cid == 24207 or cid == 80258 or cid == 87519 then --Filter useless units, like "Army of the Dead", that would otherwise throw off the target scan
return
end
end
return name , uid , bossuid
end
function module : BossTargetScannerAbort ( mod , cidOrGuid , returnFunc )
targetScanCount [ cidOrGuid ] = nil --Reset count for later use.
mod : UnscheduleMethod ( " BossTargetScanner " , cidOrGuid , returnFunc )
DBM : Debug ( " Boss target scan for " .. cidOrGuid .. " should be aborting. " , 2 )
end
function module : BossTargetScanner ( mod , cidOrGuid , returnFunc , scanInterval , scanTimes , scanOnlyBoss , isEnemyScan , isFinalScan , targetFilter , tankFilter , onlyPlayers )
--Increase scan count
cidOrGuid = cidOrGuid or mod.creatureId
if not cidOrGuid then return end
if not targetScanCount [ cidOrGuid ] then
targetScanCount [ cidOrGuid ] = 0
DBM : Debug ( " Boss target scan started for " .. cidOrGuid , 2 )
end
targetScanCount [ cidOrGuid ] = targetScanCount [ cidOrGuid ] + 1
--Set default values
scanInterval = scanInterval or 0.05
scanTimes = scanTimes or 16
local targetname , targetuid , bossuid = self : GetBossTarget ( mod , cidOrGuid , scanOnlyBoss )
DBM : Debug ( " Boss target scan " .. targetScanCount [ cidOrGuid ] .. " of " .. scanTimes .. " , found target " .. ( targetname or " nil " ) .. " using " .. ( bossuid or " nil " ) , 3 ) --Doesn't hurt to keep this, as level 3
--Do scan
if targetname and targetname ~= CL.UNKNOWN and ( not targetFilter or ( targetFilter and targetFilter ~= targetname ) ) then
if not IsInGroup ( ) then scanTimes = 1 end --Solo, no reason to keep scanning, give faster warning. But only if first scan is actually a valid target, which is why i have this check HERE
if ( isEnemyScan and UnitIsFriend ( " player " , targetuid ) or ( onlyPlayers and not UnitIsPlayer ( " player " , targetuid ) ) or mod : IsTanking ( targetuid , bossuid ) ) and not isFinalScan then --On player scan, ignore tanks. On enemy scan, ignore friendly player. On Only player, ignore npcs and pets
if targetScanCount [ cidOrGuid ] < scanTimes then --Make sure no infinite loop.
mod : ScheduleMethod ( scanInterval , " BossTargetScanner " , cidOrGuid , returnFunc , scanInterval , scanTimes , scanOnlyBoss , isEnemyScan , nil , targetFilter , tankFilter , onlyPlayers ) --Scan multiple times to be sure it's not on something other then tank (or friend on enemy scan, or npc/pet on only person)
else --Go final scan.
self : BossTargetScanner ( mod , cidOrGuid , returnFunc , scanInterval , scanTimes , scanOnlyBoss , isEnemyScan , true , targetFilter , tankFilter , onlyPlayers )
end
else --Scan success. (or failed to detect right target.) But some spells can be used on tanks, anyway warns tank if player scan. (enemy scan block it)
targetScanCount [ cidOrGuid ] = nil --Reset count for later use.
mod : UnscheduleMethod ( " BossTargetScanner " , cidOrGuid , returnFunc ) --Unschedule all checks just to be sure none are running, we are done.
if ( tankFilter and mod : IsTanking ( targetuid , bossuid ) ) or ( isFinalScan and isEnemyScan ) or onlyPlayers and not UnitIsPlayer ( " player " , targetuid ) then return end --If enemyScan and playerDetected, return nothing
local scanningTime = ( targetScanCount [ cidOrGuid ] or 1 ) * scanInterval
mod [ returnFunc ] ( mod , targetname , targetuid , bossuid , scanningTime ) --Return results to warning function with all variables.
DBM : Debug ( " BossTargetScanner has ended for " .. cidOrGuid , 2 )
end
else --target was nil, lets schedule a rescan here too.
if targetScanCount [ cidOrGuid ] < scanTimes then --Make sure not to infinite loop here as well.
mod : ScheduleMethod ( scanInterval , " BossTargetScanner " , cidOrGuid , returnFunc , scanInterval , scanTimes , scanOnlyBoss , isEnemyScan , nil , targetFilter , tankFilter , onlyPlayers )
else
targetScanCount [ cidOrGuid ] = nil --Reset count for later use.
mod : UnscheduleMethod ( " BossTargetScanner " , cidOrGuid , returnFunc ) --Unschedule all checks just to be sure none are running, we are done.
end
end
end
end
do
--UNIT_TARGET Target scanning method
local eventsRegistered = false
--Validate target is in group (I'm not sure why i actually required this since I didn't comment code when I added requirement, so I leave it for now)
--If I determine this check isn't needed and it continues to be a problem i'll kill it off.
--For now, I'll have check be smart and switch between raid and party and just disable when solo
local function validateGroupTarget ( unit )
if IsInGroup ( ) then
if UnitPlayerOrPetInRaid ( unit ) or UnitPlayerOrPetInParty ( unit ) then
return true
end
else --Solo
return true
end
end
function module : UNIT_TARGET_UNFILTERED ( uId )
--Active BossUnitTargetScanner
DBM : Debug ( " UNIT_TARGET_UNFILTERED fired for : " .. uId , 3 )
if unitMonitor [ uId ] and UnitExists ( uId .. " target " ) and validateGroupTarget ( uId .. " target " ) then
DBM : Debug ( " unitMonitor for this unit exists, target exists in group " , 2 )
local modId , returnFunc = unitMonitor [ uId ] . modid , unitMonitor [ uId ] . returnFunc
DBM : Debug ( " unitMonitor: " .. modId .. " , " .. uId .. " , " .. returnFunc , 2 )
if not unitMonitor [ uId ] . allowTank then
local tanking , status = UnitDetailedThreatSituation ( uId , uId .. " target " ) --Tanking may return 0 if npc is temporarily looking at an NPC (IE fracture) but status will still be 3 on true tank
if tanking or ( status == 3 ) then
DBM : Debug ( " unitMonitor ending for unit without 'allowTank', ignoring target " , 2 )
return
end
end
local mod = DBM : GetModByName ( modId ) --The whole reason we store modId in unitMonitor,
DBM : Debug ( " unitMonitor success for this unit, a valid target for returnFunc " , 2 )
mod [ returnFunc ] ( mod , DBM : GetUnitFullName ( uId .. " target " ) , uId .. " target " , uId ) --Return results to warning function with all variables.
unitMonitor [ uId ] = nil
unitScanCount = unitScanCount - 1
end
if unitScanCount == 0 then --Out of scans
eventsRegistered = false
self : UnregisterShortTermEvents ( )
DBM : Debug ( " All target scans complete, unregistering events " , 2 )
end
end
function module : BossUnitTargetScannerAbort ( mod , uId )
if not uId then --Not called with unit, means mod requested to clear all used units
DBM : Debug ( " BossUnitTargetScannerAbort called without unit, clearing all unitMonitor units " , 2 )
twipe ( unitMonitor )
unitScanCount = 0
return
end
--If tank is allowed, return current target when scan ends no matter what.
if unitMonitor [ uId ] and ( unitMonitor [ uId ] . allowTank or not IsInGroup ( ) ) and validateGroupTarget ( uId .. " target " ) then
DBM : Debug ( " unitMonitor unit exists, allowTank target exists " , 2 )
local modId , returnFunc = unitMonitor [ uId ] . modid , unitMonitor [ uId ] . returnFunc
DBM : Debug ( " unitMonitor: " .. modId .. " , " .. uId .. " , " .. returnFunc , 2 )
DBM : Debug ( " unitMonitor found a target that probably is a tank " , 2 )
mod [ returnFunc ] ( mod , DBM : GetUnitFullName ( uId .. " target " ) , uId .. " target " , uId ) --Return results to warning function with all variables.
end
unitMonitor [ uId ] = nil
unitScanCount = unitScanCount - 1
DBM : Debug ( " Boss unit target scan should be aborting for " .. uId , 2 )
end
function module : BossUnitTargetScanner ( mod , uId , returnFunc , scanTime , allowTank )
--UNIT_TARGET event monitor target scanner. Will instantly detect a target change of a registered Unit
--If target change occurs before this method is called (or if boss doesn't change target because cast ends up actually being on the tank, target scan will fail completely
--If allowTank is passed, it basically tells this scanner to return current target of unitId at time of failure/abort when scanTime is complete
unitMonitor [ uId ] = { }
unitScanCount = unitScanCount + 1
unitMonitor [ uId ] . modid , unitMonitor [ uId ] . returnFunc , unitMonitor [ uId ] . allowTank = mod.id , returnFunc , allowTank
mod : ScheduleMethod ( scanTime or 1.5 , " BossUnitTargetScannerAbort " , uId ) --In case of BossUnitTargetScanner firing too late, and boss already having changed target before monitor started, it needs to abort after x seconds
if not eventsRegistered then
eventsRegistered = true
self : RegisterShortTermEvents ( " UNIT_TARGET_UNFILTERED " )
DBM : Debug ( " Registering UNIT_TARGET event for BossUnitTargetScanner " , 2 )
end
end
end
do
local repeatedScanEnabled = { }
--infinite scanner. so use this carefully.
local function repeatedScanner ( mod , cidOrGuid , returnFunc , scanInterval , scanOnlyBoss , includeTank )
if repeatedScanEnabled [ returnFunc ] then
cidOrGuid = cidOrGuid or mod.creatureId
scanInterval = scanInterval or 0.1
local targetname , targetuid , bossuid = module : GetBossTarget ( mod , cidOrGuid , scanOnlyBoss )
if targetname and ( includeTank or not mod : IsTanking ( targetuid , bossuid ) ) then
mod [ returnFunc ] ( mod , targetname , targetuid , bossuid )
end
mod : Schedule ( scanInterval , repeatedScanner , mod , cidOrGuid , returnFunc , scanInterval , scanOnlyBoss , includeTank )
end
end
function module : StartRepeatedScan ( mod , cidOrGuid , returnFunc , scanInterval , scanOnlyBoss , includeTank )
repeatedScanEnabled [ returnFunc ] = true
repeatedScanner ( mod , cidOrGuid , returnFunc , scanInterval , scanOnlyBoss , includeTank )
end
function module : StopRepeatedScan ( returnFunc )
repeatedScanEnabled [ returnFunc ] = nil
end
end