--[[-------------------------------------------------------------------- Copyright (C) 2012 Sidoine De Wispelaere. Copyright (C) 2012, 2013, 2014 Johnny C. Lam. See the file LICENSE.txt for copying permission. --]]-------------------------------------------------------------------- -- Gather information about ennemies local OVALE, Ovale = ... local OvaleEnemies = Ovale:NewModule("OvaleEnemies", "AceEvent-3.0", "AceTimer-3.0") Ovale.OvaleEnemies = OvaleEnemies --<private-static-properties> local L = Ovale.L local OvaleDebug = Ovale.OvaleDebug local OvaleProfiler = Ovale.OvaleProfiler -- Forward declarations for module dependencies. local OvaleState = nil local bit_band = bit.band local ipairs = ipairs local pairs = pairs local strfind = string.find local tostring = tostring local wipe = wipe local API_GetTime = GetTime local API_UnitGUID = UnitGUID local COMBATLOG_OBJECT_AFFILIATION_OUTSIDER = COMBATLOG_OBJECT_AFFILIATION_OUTSIDER local COMBATLOG_OBJECT_REACTION_HOSTILE = COMBATLOG_OBJECT_REACTION_HOSTILE -- Register for debugging messages. OvaleDebug:RegisterDebugging(OvaleEnemies) -- Register for profiling. OvaleProfiler:RegisterProfiling(OvaleEnemies) -- List of CLEU event suffixes that can correspond to the player damaging or try to damage (tag) an enemy. local CLEU_TAG_SUFFIXES = { "_CAST_START", "_DAMAGE", "_MISSED", "_DRAIN", "_LEECH", "_INTERRUPT", "_DISPEL", "_DISPEL_FAILED", "_STOLEN", "_AURA_APPLIED", "_AURA_APPLIED_DOSE", "_AURA_REFRESH", } -- Player's GUID. local self_guid = nil -- enemyName[guid] = name local self_enemyName = {} -- enemyLastSeen[guid] = timestamp local self_enemyLastSeen = {} -- taggedEnemyLastSeen[guid] = timestamp -- GUIDs used as keys for this table are a subset of the GUIDs used for enemyLastSeen. local self_taggedEnemyLastSeen = {} -- Timer for reaper function to remove inactive enemies. local self_reaperTimer = nil local REAP_INTERVAL = 3 --</private-static-properties> --<public-static-properties> -- Total number of active enemies. OvaleEnemies.activeEnemies = 0 -- Total number of tagged enemies. OvaleEnemies.taggedEnemies = 0 --</public-static-properties> --<private-static-methods> local function IsTagEvent(cleuEvent) for _, suffix in ipairs(CLEU_TAG_SUFFIXES) do if strfind(cleuEvent, suffix .. "$") then return true end end return false end --</private-static-methods> --<public-static-methods> function OvaleEnemies:OnInitialize() -- Resolve module dependencies. OvaleState = Ovale.OvaleState end function OvaleEnemies:OnEnable() self_guid = API_UnitGUID("player") if not self_reaperTimer then self_reaperTimer = self:ScheduleRepeatingTimer("RemoveInactiveEnemies", REAP_INTERVAL) end self:RegisterEvent("COMBAT_LOG_EVENT_UNFILTERED") self:RegisterEvent("PLAYER_REGEN_DISABLED") OvaleState:RegisterState(self, self.statePrototype) end function OvaleEnemies:OnDisable() OvaleState:UnregisterState(self) if not self_reaperTimer then self:CancelTimer(self_reaperTimer) self_reaperTimer = nil end self:UnregisterEvent("COMBAT_LOG_EVENT_UNFILTERED") self:UnregisterEvent("PLAYER_REGEN_DISABLED") end function OvaleEnemies:COMBAT_LOG_EVENT_UNFILTERED(event, timestamp, cleuEvent, hideCaster, sourceGUID, sourceName, sourceFlags, sourceRaidFlags, destGUID, destName, destFlags, destRaidFlags, ...) if cleuEvent == "UNIT_DIED" then local now = API_GetTime() self:RemoveEnemy(destGUID, now, true) elseif sourceFlags and bit_band(sourceFlags, COMBATLOG_OBJECT_REACTION_HOSTILE) > 0 and bit_band(sourceFlags, COMBATLOG_OBJECT_AFFILIATION_OUTSIDER) > 0 and destFlags and bit_band(destFlags, COMBATLOG_OBJECT_AFFILIATION_OUTSIDER) == 0 then local now = API_GetTime() self:AddEnemy(sourceGUID, sourceName, now) elseif destGUID and bit_band(destFlags, COMBATLOG_OBJECT_REACTION_HOSTILE) > 0 and bit_band(destFlags, COMBATLOG_OBJECT_AFFILIATION_OUTSIDER) > 0 and sourceFlags and bit_band(sourceFlags, COMBATLOG_OBJECT_AFFILIATION_OUTSIDER) == 0 then local now = API_GetTime() local isTagged = (sourceGUID == self_guid and IsTagEvent(cleuEvent)) self:AddEnemy(destGUID, destName, now, isTagged) end end function OvaleEnemies:PLAYER_REGEN_DISABLED() -- Reset enemy tracking when combat starts. wipe(self_enemyName) wipe(self_enemyLastSeen) wipe(self_taggedEnemyLastSeen) self.activeEnemies = 0 self.taggedEnemies = 0 end -- Remove enemies that have been inactive for at least REAP_INTERVAL seconds. -- These enemies are not in combat with your group, out of range, or -- incapacitated and shouldn't count toward the number of active enemies. function OvaleEnemies:RemoveInactiveEnemies() self:StartProfiling("OvaleEnemies_RemoveInactiveEnemies") local now = API_GetTime() for guid, timestamp in pairs(self_enemyLastSeen) do if now - timestamp > REAP_INTERVAL then self:RemoveEnemy(guid, now) end end self:StopProfiling("OvaleEnemies_RemoveInactiveEnemies") end function OvaleEnemies:AddEnemy(guid, name, timestamp, isTagged) self:StartProfiling("OvaleEnemies_AddEnemy") if guid then self_enemyName[guid] = name local tagged = self_taggedEnemyLastSeen[guid] if isTagged then local tagged = self_taggedEnemyLastSeen[guid] self_taggedEnemyLastSeen[guid] = timestamp if not tagged then self.taggedEnemies = self.taggedEnemies + 1 end end local seen = self_enemyLastSeen[guid] self_enemyLastSeen[guid] = timestamp if not seen then self.activeEnemies = self.activeEnemies + 1 end if isTagged and not tagged then self:Debug(true, "New tagged enemy seen (%d total, %d tagged): %s (%s)", self.activeEnemies, self.taggedEnemies, guid, name) Ovale.refreshNeeded["player"] = true elseif not seen then self:Debug(true, "New enemy seen (%d total): %s (%s)", self.activeEnemies, guid, name) Ovale.refreshNeeded["player"] = true end end self:StopProfiling("OvaleEnemies_AddEnemy") end function OvaleEnemies:RemoveEnemy(guid, timestamp, isDead) self:StartProfiling("OvaleEnemies_RemoveEnemy") if guid then local name = self_enemyName[guid] local seen = self_enemyLastSeen[guid] local tagged = self_taggedEnemyLastSeen[guid] if tagged then self_taggedEnemyLastSeen[guid] = nil if self.taggedEnemies > 0 then self.taggedEnemies = self.taggedEnemies - 1 end end if seen then self_enemyLastSeen[guid] = nil if self.activeEnemies > 0 then self.activeEnemies = self.activeEnemies - 1 end end if tagged then if isDead then self:Debug(true, "Tagged enemy died (%d total, %d tagged): %s (%s)", self.activeEnemies, self.taggedEnemies, guid, name) else self:Debug(true, "Tagged enemy removed( %d total, %d tagged): %s (%s), last seen at %f", self.activeEnemies, self.taggedEnemies, guid, name, tagged) end elseif seen then if isDead then self:Debug(true, "Enemy died (%d total): %s (%s)", self.activeEnemies, guid, name) else self:Debug(true, "Enemy removed (%d total): %s (%s), last seen at %f", self.activeEnemies, guid, name, seen) end end if tagged or seen then Ovale.refreshNeeded["player"] = true self:SendMessage("Ovale_InactiveUnit", guid) end end self:StopProfiling("OvaleEnemies_RemoveEnemy") end function OvaleEnemies:DebugEnemies() for guid, seen in pairs(self_enemyLastSeen) do local name = self_enemyName[guid] local tagged = self_taggedEnemyLastSeen[guid] if tagged then self:Print("Tagged enemy %s (%s) last seen at %f", guid, name, tagged) else self:Print("Enemy %s (%s) last seen at %f", guid, name, seen) end end self:Print("Total enemies: %d", self.activeEnemies) self:Print("Total tagged enemies: %d", self.taggedEnemies) end --</public-static-methods> --[[---------------------------------------------------------------------------- State machine for simulator. --]]---------------------------------------------------------------------------- --<public-static-properties> OvaleEnemies.statePrototype = {} --</public-static-properties> --<private-static-properties> local statePrototype = OvaleEnemies.statePrototype --</private-static-properties> --<state-properties> -- Total number of active enemies. statePrototype.activeEnemies = nil -- Total number of tagged enemies. statePrototype.taggedEnemies = nil -- Requested number of enemies. statePrototype.enemies = nil --</state-properties> --<public-static-methods> -- Initialize the state. function OvaleEnemies:InitializeState(state) state.enemies = nil end -- Reset the state to the current conditions. function OvaleEnemies:ResetState(state) self:StartProfiling("OvaleEnemies_ResetState") state.activeEnemies = self.activeEnemies state.taggedEnemies = self.taggedEnemies self:StopProfiling("OvaleEnemies_ResetState") end -- Release state resources prior to removing from the simulator. function OvaleEnemies:CleanState(state) state.activeEnemies = nil state.taggedEnemies = nil state.enemies = nil end --</public-static-methods>