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.
257 lines
12 KiB
257 lines
12 KiB
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 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 found = false
|
|
for _, uId in ipairs(bossTargetuIds) do
|
|
if mod:GetUnitCreatureId(uId) == cidOrGuid then
|
|
found = true
|
|
bossuIdCache[cidOrGuid] = uId
|
|
bossuIdCache[UnitGUID(uId)] = uId
|
|
name, uid, bossuid = getBossTarget(UnitGUID(uId), scanOnlyBoss)
|
|
break
|
|
end
|
|
end
|
|
if not found and not scanOnlyBoss then
|
|
if IsInRaid() then
|
|
for i = 1, GetNumGroupMembers() do
|
|
if mod:GetUnitCreatureId("raid"..i.."target") == cidOrGuid then
|
|
bossuIdCache[cidOrGuid] = "raid"..i.."target"
|
|
bossuIdCache[UnitGUID("raid"..i.."target")] = "raid"..i.."target"
|
|
name, uid, bossuid = getBossTarget(UnitGUID("raid"..i.."target"))
|
|
break
|
|
end
|
|
end
|
|
elseif IsInGroup() then
|
|
for i = 1, GetNumSubgroupMembers() do
|
|
if mod:GetUnitCreatureId("party"..i.."target") == cidOrGuid then
|
|
bossuIdCache[cidOrGuid] = "party"..i.."target"
|
|
bossuIdCache[UnitGUID("party"..i.."target")] = "party"..i.."target"
|
|
name, uid, bossuid = getBossTarget(UnitGUID("party"..i.."target"))
|
|
break
|
|
end
|
|
end
|
|
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
|
|
|