/trunk
## Interface: 40300 |
## Author: Animor |
## Title: Animor's AutoReply |
## Version: 0.9.04 |
## Version: 1.0 |
## Notes: Auto reply whispers in specific combat related situations. |
## OptionalDeps: Ace3, Recount, LibDataBroker-1.1 |
## X-Embeds: Ace3, LibDataBroker-1.1 |
---------------------------------------- |
-- Local variables |
---------------------------------------- |
-- Debug messages |
local debug = nil |
-- Boss IDs (for encounters without INSTANCE_ENCOUNTER_ENGAGE_UNIT) |
local bossIDList = { |
54431, -- Echo of Baine |
46254, -- Hogger |
} |
-- Extract encounter name from mobs names that appear in boss frame |
local bossMobs = { |
-- Warmaster Blackhorn |
[56598] = L["Warmaster Blackhorn"], -- The Skyfire |
-- Spine of Deathwing |
[53879] = L["Spine of Deathwing"], -- Deathwing |
-- Madness of Deathwing |
[56846] = L["Madness of Deathwing"], -- Arm Tentacle |
[56167] = L["Madness of Deathwing"], -- Arm Tentacle |
[56471] = L["Madness of Deathwing"], -- Mutated Corruption |
[56168] = L["Madness of Deathwing"], -- Wing Tentacle |
} |
--====================================== |
--====================================== |
-- BOSS FIGHT FUNCTIONS |
-- Unit died or distroyed |
---------------------------------------- |
function AutoReply:COMBAT_LOG_EVENT_UNFILTERED(_, _, subevent, ...) |
-- self:Print(subevent) |
if subevent == "UNIT_DIED" or subevent == "UNIT_DESTROYED" then |
self:CheckBossKill(subevent) |
end |
-- Check if boss fight started |
---------------------------------------- |
function AutoReply:CheckBossFightStart() |
local bossAlive, targetExist = self:BossAlive() |
if bossAlive then |
-- Boss fight start |
-- self:Print("boss fight start") |
self.bossEngaged = true |
self:RegisterEvent("COMBAT_LOG_EVENT_UNFILTERED") |
self:RegisterEvent("CHAT_MSG_MONSTER_YELL", "CheckBossKill") |
self:RegisterEvent("CHAT_MSG_MONSTER_EMOTE", "CheckBossKill") |
self:RegisterEvent("RAID_BOSS_EMOTE", "CheckBossKill") |
self:RegisterEvent("CHAT_MSG_MONSTER_SAY", "CheckBossKill") |
-- self:Print("ScheduleTimer CheckBossWipe - first") |
self:ScheduleTimer("CheckBossWipe", 3) |
elseif (not targetExist) and UnitAffectingCombat("player") then |
-- If boss encounter starts before the boss was targetted, schedule boss fight check again. |
-- self:Print("ScheduleTimer CheckBossFightStart") |
self:ScheduleTimer("CheckBossFightStart", 3) |
if not self.bossEngaged then |
local bossEngaged, targetInCombat = self:BossEngaged() |
if bossEngaged then |
-- Boss fight start |
if debug then self:Print("boss fight start") end |
self.bossEngaged = true |
self:RegisterEvent("COMBAT_LOG_EVENT_UNFILTERED") |
self:RegisterEvent("CHAT_MSG_MONSTER_YELL", "CheckBossKill") |
self:RegisterEvent("CHAT_MSG_MONSTER_EMOTE", "CheckBossKill") |
self:RegisterEvent("RAID_BOSS_EMOTE", "CheckBossKill") |
self:RegisterEvent("CHAT_MSG_MONSTER_SAY", "CheckBossKill") |
-- self:Print("ScheduleTimer CheckBossWipe - first") |
self:ScheduleTimer("CheckBossWipe", 3) |
elseif (not targetInCombat) and UnitAffectingCombat("player") then |
-- If boss encounter starts before the boss was targetted or before the boss is in combat, schedule boss fight check again. |
-- self:Print("ScheduleTimer CheckBossFightStart") |
self:ScheduleTimer("CheckBossFightStart", 3) |
end |
end |
end |
---------------------------------------- |
-- Check for boss fight wipe |
---------------------------------------- |
function AutoReply:CheckBossWipe(confirm) |
function AutoReply:CheckBossWipe(confirm) |
if self.bossEngaged then |
if self:GroupInCombat() then |
-- Still in combat, schedule check again |
local allBossesDead, bossFound = self:AllBossesDead() |
if self:GroupInCombat() or (bossFound and not allBossesDead) then |
-- No wipe (schedule wipe check again) if: |
-- 1) Still in combat. |
-- 2) Encountered started and Boss appeared, but out of combat yet. |
-- self:Print("ScheduleTimer CheckBossWipe") |
self:ScheduleTimer("CheckBossWipe", 3) |
elseif confirm then |
-- Boss fight wipe |
-- self:Print("boss fight: wipe") |
if debug then self:Print("boss fight: wipe") end |
self:ReplyAllMissedBoss("wipe") |
self:HandleBossFightEnd() |
else |
-- Perform when boss fight ends (wipe/kill) |
-------------------------------------------- |
function AutoReply:HandleBossFightEnd() |
self.bossEngaged = false |
self.bossID = nil |
self.bossName = "Unknown" |
self.bossEngaged = false |
self.bossNum = nil |
self.bossName = L["Unknown"] |
self:CancelAllTimers() |
self.checkBossKillTimer = nil |
self:UnregisterEvent("COMBAT_LOG_EVENT_UNFILTERED") |
self:UnregisterEvent("CHAT_MSG_MONSTER_YELL") |
self:UnregisterEvent("CHAT_MSG_MONSTER_EMOTE") |
---------------------------------------- |
-- Check if boss is alive |
---------------------------------------- |
function AutoReply:BossAlive() |
local bossAlive = false |
local targetExist = false |
function AutoReply:BossEngaged() |
local bossEngaged = false |
local targetInCombat = false |
local bossNum = "" |
local bossID = "" |
-- Check for boss frame |
for i = 1, MAX_BOSS_FRAMES do |
if (UnitExists("boss"..i) and not UnitIsDead("boss"..i)) then |
bossAlive = true |
targetExist = true |
self.bossID = "boss"..i |
self.bossName = UnitName("boss"..i) |
bossNum = "boss"..i |
if UnitExists(bossNum) and not UnitIsDead(bossNum) then -- no need to check UnitAffectingCombat for boss frames (some of them may be friendly). |
bossEngaged = true |
if UnitAffectingCombat(bossNum) then |
targetInCombat = true |
end |
self.bossNum = bossNum |
-- Try to match mob name to real boss name |
bossID = self:GetUnitId(bossNum) |
for mobID, bossName in pairs(bossMobs) do |
if mobID == bossID then |
self.bossName = bossName |
break |
end |
end |
-- If mob name wasn't matched, use unit name. |
if self.bossName == L["Unknown"] then self.bossName = UnitName(bossNum) end |
break |
end |
end |
-- Manual check targets, for bosses without boss frame |
if not bossAlive then |
bossAlive, targetExist = self:CheckTargetBoss() |
if not bossEngaged then |
bossEngaged, targetInCombat = self:CheckTargetBoss() |
end |
return bossAlive, targetExist |
return bossEngaged, targetInCombat |
end |
---------------------------------------- |
local allBossesDead = true |
local bossFound = false |
local bossAlive = false |
local targetExist = false |
-- Check for boss frame |
for i = 1, MAX_BOSS_FRAMES do |
-- Manual check targets, for bosses without boss frame |
if not bossFound then |
bossAlive, targetExist, bossFound = self:CheckTargetBoss() |
if bossAlive or (not bossFound) then |
_, _, bossFound, bossAlive = self:CheckTargetBoss() |
if bossAlive or not bossFound then |
allBossesDead = false |
end |
end |
return allBossesDead, bossFound |
end |
--------------------------------------------- |
-- Manual check targets for boss |
-- Returns bossEngaged, targetInCombat, bossFound, bossAlive |
--------------------------------------------- |
function AutoReply:CheckTargetBoss() |
local targetInCombat = false |
local bossFound = false |
local bossAlive = false |
local unitId = ((GetNumRaidMembers() == 0) and "party") or "raid" |
local target = nil |
local targetUnitID = nil |
for i = 0, math.max(GetNumRaidMembers(), GetNumPartyMembers()) do |
target = (i == 0 and "playertarget") or unitId..i.."target" |
if UnitExists(target) and not UnitIsPlayer(target) then |
if UnitAffectingCombat(target) then |
targetInCombat = true |
end |
if UnitClassification(target) == "worldboss" then |
-- worldboss |
bossFound = true -- boss found (dead/alive/in combat) |
if not UnitIsDead(target) then |
bossAlive = true -- boss is alive |
end |
if UnitAffectingCombat(target) then -- boss is in combat |
if self.bossName == L["Unknown"] then self.bossName = UnitName(target) end |
return true, targetInCombat, bossFound, bossAlive |
end |
else |
-- check against boss ID list |
targetUnitID = self:GetUnitId(target) |
-- for _, bossID in pairs(bossIDList) do |
for i = 1, #bossIDList do |
if targetUnitID == bossIDList[i] then |
bossFound = true -- boss found (dead/alive/in combat) |
if not UnitIsDead(target) then |
bossAlive = true -- boss is alive |
end |
if UnitAffectingCombat(target) then -- boss is in combat |
if self.bossName == L["Unknown"] then self.bossName = UnitName(target) end |
return true, targetInCombat, bossFound, bossAlive |
end |
end |
end |
end |
end |
end |
return false, targetInCombat, bossFound, bossAlive |
end |
---------------------------------------- |
-- Check for boss fight end |
---------------------------------------- |
function AutoReply:CheckBossKill(eventname) |
-- self:Print(eventname) |
if debug then self:Print(eventname) end |
if self.bossEngaged and (not UnitAffectingCombat("player")) and (self.db.profile.raidEn or self.db.profile.dungeonEn) then |
-- self:Print("CheckBossKill") |
if debug then self:Print("CheckBossKill") end -- debug |
local allBossesDead, bossFound = self:AllBossesDead() |
if allBossesDead or (self:GroupSurvived() and not bossFound) then |
-- self:Print("boss fight: kill") |
if debug then self:Print(allBossesDead) end -- debug |
if allBossesDead or ((not bossFound) and self:GroupSurvived()) then |
if debug then self:Print("boss fight: kill") end -- debug |
self.numKilledBosses = self.numKilledBosses + 1 |
self:ReplyAllMissedBoss("kill") |
self:ReplyAllMissedBoss("kill") |
self:HandleBossFightEnd() |
-- Reschedule kill check in case: |
-- 1) Player is dead (so no PLAYER_REGEN_DISABLED will trigger when out of combat). |
-- 2) Boss is down and its frame/body vanish |
-- 3) Other players are still in combat for some time until the encounter ends. |
elseif (not bossFound) and self:GroupInCombat() and |
((not self:TimeLeft(self.checkBossKillTimer)) or self:TimeLeft(self.checkBossKillTimer) <= 0.1) then -- Do not schedule more than one at a time. |
self.checkBossKillTimer = self:ScheduleTimer("CheckBossKill", 0.5) |
end |
end |
end |
end |
-------------------------------------- |
local unitId = ((GetNumRaidMembers() == 0) and "party") or "raid" |
for i = 0, math.max(GetNumRaidMembers(), GetNumPartyMembers()) do |
local id = (i == 0 and "player") or unitId..i |
if UnitAffectingCombat(id) and not UnitIsDeadOrGhost(id) then |
if UnitAffectingCombat(id) and UnitIsVisible(id) and not UnitIsDeadOrGhost(id) then |
return true |
end |
end |
-- Check if player is out of combat and didn't use any feign death ability to reset the boss. |
-- Check in addition if player is in 100 yard range. |
if UnitIsVisible(id) and not (UnitAffectingCombat(id) or UnitIsDeadOrGhost(id) or UnitIsFeignDeath(id) or |
UnitAura(id, "Vanish") or UnitAura(id, "Invisibility") or UnitAura(id, "Shadowmeld")) then |
-- self:Print(UnitName(id)) |
UnitAura(id, "Vanish") or UnitAura(id, "Invisibility") or UnitAura(id, "Shadowmeld") or UnitAura(id, "Spirit of Redemption")) then |
if debug then self:Print(GetUnitName(id)) end |
return true |
end |
end |
return false |
end |
--------------------------------------------- |
-- Manual check targets for boss |
-- Returns bossAlive, targetExist, bossFound |
--------------------------------------------- |
function AutoReply:CheckTargetBoss() |
local targetExist = false |
local bossFound = false |
local unitId = ((GetNumRaidMembers() == 0) and "party") or "raid" |
local target = nil |
local targetUnitID = nil |
for i = 0, math.max(GetNumRaidMembers(), GetNumPartyMembers()) do |
target = (i == 0 and "playertarget") or unitId..i.."target" |
if UnitExists(target) and not UnitIsPlayer(target) then |
-- if UnitExists(target) and not UnitIsDead(target) and not UnitIsPlayer(target) then |
targetExist = true |
if UnitClassification(target) == "worldboss" then |
-- worldboss |
bossFound = true |
if not UnitIsDead(target) then |
if self.bossName == "Unknown" then self.bossName = UnitName(target) end |
return true, targetExist, bossFound |
end |
else |
-- check against boss ID list |
targetUnitID = self:GetUnitId(target) |
for _, unitID in pairs(bossIDList) do |
if targetUnitID == unitID then |
bossFound = true |
if not UnitIsDead(target) then |
if self.bossName == "Unknown" then self.bossName = UnitName(target) end |
return true, targetExist, bossFound |
end |
end |
end |
end |
end |
end |
return false, targetExist, bossFound |
end |
---------------------------------------- |
-- Get unit ID |
---------------------------------------- |
local locationName = "" |
local instanceType = "" |
-- Check that message doesn't start with addons prefix. |
if not (msg:find(self.msgPrefix) or msg:find("^<DBM>") or msg:find("^<Deadly Boss Mods>") or msg:find("^<BW>")) then |
-- Check that message doesn't start with addons prefix. |
if not (msg:find("^<.*>") or msg:find("^%[.*%]")) then |
locationName, instanceType = GetInstanceInfo() |
-- Open world location |
if instanceType == "none" then |
---------------------------------------- |
-- Recieve whisper event |
---------------------------------------- |
function AutoReply:CHAT_MSG_WHISPER(_, msg, sender) |
self:HandleWhisper(msg, sender) |
function AutoReply:CHAT_MSG_WHISPER(_, msg, sender, _, _, _, status) |
-- Do nothing with whispers from GM |
if status ~= "GM" then |
self:HandleWhisper(msg, sender) |
end |
end |
---------------------------------------- |
end |
---------------------------------------- |
-- Reply to all away missed whisperes |
-- Reply to all away missed whisperers |
---------------------------------------- |
function AutoReply:PLAYER_FLAGS_CHANGED() |
-- Reply whispers to all missed away senders |
msg = msg:gsub("%%TN", GetUnitName("target", true)) |
msg = msg:gsub("%%THP", math.floor(UnitHealth("target")/UnitHealthMax("target")*100).."%%") |
else |
msg = msg:gsub("%%TN", "unknown") |
msg = msg:gsub("%%TN", L["Unknown"]) |
msg = msg:gsub("%s*%(?%%BHP%)?", "") |
end |
-- Boss |
if self.bossEngaged then |
msg = msg:gsub("%%BN", self.bossName) |
if self.bossID then |
msg = msg:gsub("%%BHP", math.floor(UnitHealth(self.bossID)/UnitHealthMax(self.bossID)*100).."%%") |
if self.bossNum and (UnitName(self.bossNum) == self.bossName) then |
-- Set boss health only if boss frame shows the correct boss name |
msg = msg:gsub("%%BHP", math.floor(UnitHealth(self.bossNum)/UnitHealthMax(self.bossNum)*100).."%%") |
else |
msg = msg:gsub("%s*%(?%%BHP%)?", "") |
end |
end |
msg = self.msgPrefix..msg |
--self:Print(msg) |
return msg |
end |
-- Send reply |
---------------------------------------- |
function AutoReply:SendReply(replyMsg, target) |
--self:Print(replyMsg) |
if type(target) == "number" and select(7, BNGetFriendInfoByID(target)) then -- check if BN friend is online |
BNSendWhisper(target, replyMsg) |
elseif type(target) == "string" then |
if AutoReplyHistory:IsShown() then |
-- If already shown, then hide |
AutoReplyHistory:Hide() |
AutoReplyHistory.messageFrame:Clear() |
else |
-- Add messages from DB in history window |
AutoReplyHistory.messageFrame:Clear() |
-- Add messages from DB in history window |
for idx, value in pairs(dbChar.msgHistory) do |
if (value.sender == "_") then |
AutoReplyHistory.messageFrame:AddMessage(string.rep("_", 30)) |
---------------------------------------- |
function AutoReply:ClearHistory() |
wipe (dbChar.msgHistory) |
AutoReplyHistory.messageFrame:Clear() |
self:Print(L["Missed whispers history was cleared."]) |
end |
local numEnemyRemain = tonumber((team2Remain or ""):match("(%d+)")) or 0 |
if myBGFaction == 1 then |
myArenaFaction = "Gold" |
myArenaFaction = L["Gold"] |
else |
myArenaFaction = "Green" |
myArenaFaction = L["Green"] |
end |
if team2Remain:match(myArenaFaction) then |
local _, id, _, _, instanceName, _, _, _, _, completedEncounters = GetLFGProposal() |
self.numKilledBosses = completedEncounters |
if id == 416 or id == 417 then -- Siege of Wyrmrest Temple or Fall of Deathwing |
self.locationName = "Dragon Soul" |
self.locationName = L["Dragon Soul"] -- preset self.locationName in order to maintain self.numKilledBosses when changing zone. |
else |
self.locationName = instanceName |
end |
---------------------------------------- |
-- Out of combat event: |
-- 1) Reply to all missed whisperes |
-- 1) Reply to all missed whisperers |
-- 2) Check for boss kill |
---------------------------------------- |
function AutoReply:PLAYER_REGEN_ENABLED() |
---------------------------------------- |
-- Zone Change event: |
-- 1) Reset numKilledBosses on new instance |
-- 2) Reply to all missed whisperes |
-- 2) Reply to all missed whisperers |
---------------------------------------- |
function AutoReply:ZONE_CHANGED_NEW_AREA() |
-- If new instance, reset numKilledBosses |
if db.raidEn or db.dungeonEn then |
-- If new instance, reset numKilledBosses |
local locationName, instanceType = GetInstanceInfo() |
if instanceType ~= "none" and self.locationName ~= locationName then |
self.numKilledBosses = 0 |
end |
end |
-- Reply whispers to all missed senders |
if not self:CheckPlayerState ("", "") and not UnitIsDeadOrGhost("player") then |
self:ReplyAllMissed() |
end |
-- Reply whispers to all missed senders |
if not self:CheckPlayerState ("", "") and not UnitIsDeadOrGhost("player") then |
self:ReplyAllMissed() |
end |
-- Clear PvP status for next time |
myBGFaction = nil |
myBGName = "" |
BGWinner = nil |
end |
---------------------------------------- |
-- Send to missed senders list |
for sender in pairs(self.missedSenders) do |
if type(sender) == "number" or IsInInstance() or not sender:find("-") then -- Do not reply cross-realm whisperes when out of instance |
if type(sender) == "number" or IsInInstance() or not sender:find("-") then -- Do not reply cross-realm whisperers when out of instance |
self:SendReply(replyMsg, sender) |
end |
end |
-- Init boss fight trakcing |
self.bossEngaged = false |
self.bossName = "Unknown" |
self.bossID = nil |
self.bossName = L["Unknown"] |
self.bossNum = nil |
self.locationName = "" |
self.checkBossKillTimer = nil |
-- Init PvP status for reply message |
myBGFaction = nil |
self:UnregisterEvent("UPDATE_BATTLEFIELD_STATUS") |
self:UnregisterEvent("PLAYER_ALIVE") |
self:UnregisterEvent("PLAYER_UNGHOST") |
self:HandleBossFightEnd() -- unregister boss fight events |
self:CancelAllTimers() |
self:HandleBossFightEnd() -- unregister boss fight events, cancel all timers |
end |
replyWhisperersEn = { |
order = 3, |
type = "toggle", |
name = L["Notify missed whisperes when available"], |
desc = L["Notify missed whisperes when becoming available (except for AFK)."], |
name = L["Notify missed whisperers when available"], |
desc = L["Notify missed whisperers when becoming available (except for AFK)."], |
width = "double", |
disabled = function(info) return not AutoReply.db.profile.globalEn end, |
}, |
replyAwayWhisperersEn = { |
order = 7.4, |
type = "toggle", |
name = L["Notify missed whisperes when no longer AFK"], |
desc = L["Notify missed whisperes when no longer AFK"], |
name = L["Notify missed whisperers when no longer AFK"], |
desc = L["Notify missed whisperers when no longer AFK"], |
width = "double", |
disabled = function(info) return not (AutoReply.db.profile.awayHistoryEn and AutoReply.db.profile.globalEn) end, |
}, |
---------------------------------------- |
-- Libs |
---------------------------------------- |
local L = LibStub("AceLocale-3.0"):GetLocale("AutoReply") |
local AutoReply = LibStub("AceAddon-3.0"):GetAddon("AutoReply") |
local L = LibStub("AceLocale-3.0"):GetLocale("AutoReply") |
---------------------------------------- |
-- Backdrop templates |
frame.closeButton = closeButton |
---------------------------------------- |
-- Clear history button |
---------------------------------------- |
local clearButton = CreateFrame("Button", nil, frame, "UIPanelButtonTemplate") |
clearButton.buttonUpTexture = clearButton:CreateTexture(nil, nil, "UIPanelButtonUpTexture") |
clearButton.buttonUpTexture:SetAlpha(0.7) |
clearButton:SetNormalTexture(clearButton.buttonUpTexture) |
clearButton.buttonDownTexture = clearButton:CreateTexture(nil, nil, "UIPanelButtonDownTexture") |
clearButton.buttonDownTexture:SetAlpha(0.7) |
clearButton:SetPushedTexture(clearButton.buttonDownTexture) |
clearButton:SetNormalFontObject("GameFontDisable") |
-- clearButton:SetHighlightFontObject("GameFontHighlight") |
clearButton:SetPoint("BOTTOMLEFT", 15, 15) |
clearButton:SetHeight(22) |
clearButton:SetWidth(95) |
clearButton:SetText(L["Clear History"]) |
clearButton:SetScript("OnClick", function(self) |
StaticPopupDialogs["AutoReply clear history"] = { |
text = L["Are you sure you want to clear the history?"], |
button1 = ACCEPT, |
button2 = CANCEL, |
timeout = 0, |
whileDead = true, |
hideOnEscape= true, |
OnAccept = function() |
AutoReply:ClearHistory() |
end, |
} |
AutoReplyHistoryDialog = StaticPopup_Show("AutoReply clear history") |
AutoReplyHistoryDialog:SetFrameStrata("TOOLTIP") |
end) |
frame.clearButton = clearButton |
---------------------------------------- |
-- Inner frame |
---------------------------------------- |
local innerFRame = CreateFrame("Frame", nil, frame) |
L["Auto reply same whisperer only once per busy session"] = true |
L["Also to party/raid members"] = true |
L["Auto reply also to party/raid members"] = true |
L["Notify missed whisperes when available"] = true |
L["Notify missed whisperes when becoming available (except for AFK)."]= true |
L["Notify missed whisperers when available"] = true |
L["Notify missed whisperers when becoming available (except for AFK)."]= true |
L["Hide minimap icon"] = true |
L["Hide auto reply from chat"] = true |
L["Hide auto reply from the player's chat frame"] = true |
L["AFK"] = true |
L["Save missed whispers also when AFK"] = true |
L["Save missed whispers also when away/AFK"] = true |
L["Notify missed whisperes when no longer AFK"] = true |
L["Notify missed whisperers when no longer AFK"] = true |
L[" is no longer AFK."] = true |
-- Death |
L["Uncheck this if you run Boss mods and want to avoid double auto replies"]= true |
L["Reply message during raid boss fights:"] = true |
L["%PN (%PHP) is busy raiding %LOC, now fighting boss %BN (%BHP)."]= true |
L["Unknown"] = true |
L["Dragon Soul"] = true -- raid name |
L["Warmaster Blackhorn"] = true |
L["Spine of Deathwing"] = true |
L["Madness of Deathwing"] = true |
-- 5men Dungeons |
L["Dungeons"] = true |
L["Status: on preparation"] = true |
L["Status: team: %d/%d, enemy: %d/%d"] = true |
L["%s%s %s the Arena match!"] = true |
L["Gold"] = true -- arend team color |
L["Green"] = true -- arena team color |
-- Done messages |
L[" has been defeated by "] = true |