-- ////////////////////////////////////////////////////////////////////////////////////// -- // -- // BuffWatch -- // by Pup -- // -- // TODO: (possibly) -- // -- // Base resize on num players visible/buffs vis (store and count when scanning) -- // -- // Ignore dead / disconn in threshold count -- // Window doesnt always properly resize -- // Update ResizeWindow to run no more than once every 2 secs -- // Option to split into columns (previous changes might suffice) -- // Localisation for various bits of text -- // Keybinding to scan list and recast any expired buffs -- // Show poisons and weapon buffs for player -- // Lower spell rank support -- // Allow cleansing of debuffs -- // UI Scaling -- // -- // CHANGES: -- // -- // Added option to restrict buff updates -- // Fixed a couple of bugs as a result of 1.11 -- // -- ////////////////////////////////////////////////////////////////////////////////////// -- ////////////////////////////////////////////////////////////////////////////////////// -- // -- // Variables -- // -- ////////////////////////////////////////////////////////////////////////////////////// BINDING_HEADER_BUFFWATCHHEADER = "BuffWatch" BW_VERSION = "1.15" BW_RELEASE_DATE = "June 25, 2006" BW_SORTORDER_DROPDOWN_LIST = { "Raid Order", "Class", "Name" } BuffWatchConfig = { alpha, rightMouseSpell, show_on_startup, ShowPets, ShowCastableBuffs, ShowDebuffs, ShowDispellableDebuffs, DebuffsAlwaysVisible, AlignBuffs, ExpiredWarning, ExpiredSound, SortOrder, BuffThreshold, HighlightPvP, PreventPvPBuff, UpdPerSec, ShowUpdPerSec } local lastspellcast local lastgrouptype local buttonalignposition = 0 local buttonalignid local buffexpired local minimized = false local HideUnmonitored = false local HideMonitored = false local Player_Info = { } local Player_Left = { } local UNIT_IDs = { } local GroupBuffs = { } local BuffUpdateTime local LastBuffStatusUpdated local BuffUpdatePending = false local UpdPerSec = { } local CurrSec = 0 local SAMPLE_SIZE = 10 -- ////////////////////////////////////////////////////////////////////////////////////// -- // -- // Events -- // -- ////////////////////////////////////////////////////////////////////////////////////// function BW_OnLoad() this:RegisterEvent("PLAYER_LOGIN") this:RegisterEvent("PARTY_MEMBERS_CHANGED") this:RegisterEvent("RAID_ROSTER_UPDATE") this:RegisterEvent("SPELLCAST_START") this:RegisterEvent("UNIT_AURA") this:RegisterEvent("UNIT_PET") this:RegisterEvent("UNIT_PVP_UPDATE") this:RegisterEvent("VARIABLES_LOADED") SlashCmdList["BUFFWATCH"] = BW_SlashHandler SLASH_BUFFWATCH1 = "/buffwatch" SLASH_BUFFWATCH2 = "/bw" end function BW_OnEvent() if event == "VARIABLES_LOADED" then ----------------------- -- support for myAddons ----------------------- if(myAddOnsFrame_Register) then BuffWatchDetails = { name = "BuffWatch", description = "Keeps track of party/raid buffs", version = BW_VERSION, releaseDate = BW_RELEASE_DATE, author = "Pup", category = MYADDONS_CATEGORY_OTHERS, frame = "BW", optionsframe = "BW_Options" } BuffWatchHelp = { " - BuffWatch Usage - v " .. BW_VERSION .. "-\n\n" .. " Show/Hide the BuffWatch window:\n - Bind a keyboard button to show/hide the window\n" .. " - You can also close it by right clicking the \"BuffWatch\" label (appears on mouseover)\n\n" .. " Showing Buffs:\n - Left click the BuffWatch label\n - Also occurs automatically whenever your gain/lose a party or raid member\n\n" .. " Locking a player's watched buffs:\n - If the checkbox to the left is unchecked, buffs will be added automatically whenever they gain a buff\n" .. " - If checked, buffs will not be added\n\n", " Rebuffing:\n - Left click an icon (will auto-target)\n\n" .. " Right click spell:\n" .. " - Cast any spell with a cast time (not instant).\n Then type \"/bw set\"\n - To cast it, right click any icon (will auto-target)\n\n" .. " Deleting buffs:\n - Lock the player's buffs (check the box).\n Then [ CTRL + Right Click ] on the buff\n" .. " - Optionally, [ ALT + Right Click ] to delete all but the selected one.\n\n", " Slash Commands ( Use /buffwatch or /bw )\n" .. " - /bw toggle : shows/hides the window\n" .. " - /bw options : shows/hides the options window\n" .. " - /bw : shows this help menu :)\n\n" .. " Verbosity:\n" .. " - Hold [ Shift ] while left or right-clicking a buff icon to send a cast message to your party\n" } myAddOnsFrame_Register(BuffWatchDetails, BuffWatchHelp) end --------------------- -- support for Cosmos --------------------- if EarthFeature_AddButton then EarthFeature_AddButton({ id = "BuffWatch"; name = "BuffWatch"; subtext = "Buff monitoring"; tooltip = "Monitor Party or Raid buffs"; icon = "Interface\\Icons\\INV_Misc_Spyglass_03"; callback = BW_OptionsToggle; test = nil }) elseif Cosmos_RegisterButton then Cosmos_RegisterButton( "BuffWatch", "Buff monitoring", "Monitor Party or Raid buffs", "Interface\\Icons\\INV_Misc_Spyglass_03", BW_OptionsToggle ) end -------------------- -- support for CTMod -------------------- if CT_RegisterMod then CT_RegisterMod( "BuffWatch", "Buff monitoring", 5, "Interface\\Icons\\INV_Misc_Spyglass_03", "Monitor Party or Raid buffs", "switch", "", BW_OptionsToggle ) end BW_Print("BuffWatch loaded. Please type \"/buffwatch\" or \"/bw\" for usage. Use \"/bw toggle\" to show window.", 0.2, 0.9, 0.9 ) BW_HeaderText:SetText("BuffWatch") BW_HeaderText:SetTextColor(0.2, 0.9, 0.9) BW_Background:SetBackdropBorderColor(0, 0, 0) if BuffWatchConfig.alpha == nil then BuffWatchConfig.alpha = 0.5 end BW_Background:SetAlpha(BuffWatchConfig.alpha) if BuffWatchConfig.show_on_startup == nil then BuffWatchConfig.show_on_startup = true BW:Show() elseif BuffWatchConfig.show_on_startup == true then BW:Show() elseif BuffWatchConfig.show_on_startup == false then BW:Hide() end if BuffWatchConfig.ShowPets == nil then BuffWatchConfig.ShowPets = true end if BuffWatchConfig.ShowCastableBuffs == nil then BuffWatchConfig.ShowCastableBuffs = false end if BuffWatchConfig.ShowDebuffs == nil then BuffWatchConfig.ShowDebuffs = true end if BuffWatchConfig.ShowDispellableDebuffs == nil then BuffWatchConfig.ShowDispellableDebuffs = false end if BuffWatchConfig.DebuffsAlwaysVisible == nil then BuffWatchConfig.DebuffsAlwaysVisible = true end if BuffWatchConfig.AlignBuffs == nil then BuffWatchConfig.AlignBuffs = true end if BuffWatchConfig.ExpiredWarning == nil then BuffWatchConfig.ExpiredWarning = true end if BuffWatchConfig.ExpiredSound == nil then BuffWatchConfig.ExpiredSound = false end if BuffWatchConfig.SortOrder == nil then BuffWatchConfig.SortOrder = BW_SORTORDER_DROPDOWN_LIST[1] end if BuffWatchConfig.BuffThreshold == nil then BuffWatchConfig.BuffThreshold = 0 end if BuffWatchConfig.HighlightPvP == nil then BuffWatchConfig.HighlightPvP = false end if BuffWatchConfig.PreventPvPBuff == nil then BuffWatchConfig.PreventPvPBuff = false end if BuffWatchConfig.UpdPerSec == nil then BuffWatchConfig.UpdPerSec = 0 end if BuffWatchConfig.ShowUpdPerSec == nil then BuffWatchConfig.ShowUpdPerSec = false end -- Mark of the Wild (Same icon as Gift of the Wild) GroupBuffs["Interface\\Icons\\Spell_Nature_Regeneration"] = { ["Greater"] = "Gift of the Wild", ["Type"] = "SubGroup", ["ByName"] = 1 } -- Power Word Fortitude GroupBuffs["Interface\\Icons\\Spell_Holy_WordFortitude"] = { ["Greater"] = "Interface\\Icons\\Spell_Holy_PrayerOfFortitude", ["Type"] = "SubGroup" } -- Prayer of Fortitude GroupBuffs["Interface\\Icons\\Spell_Holy_PrayerOfFortitude"] = { ["Lesser"] = "Interface\\Icons\\Spell_Holy_WordFortitude", ["Type"] = "SubGroup" } -- Divine Spirit GroupBuffs["Interface\\Icons\\Spell_Holy_DivineSpirit"] = { ["Greater"] = "Interface\\Icons\\Spell_Holy_PrayerofSpirit", ["Type"] = "SubGroup" } -- Prayer of Spirit GroupBuffs["Interface\\Icons\\Spell_Holy_PrayerofSpirit"] = { ["Lesser"] = "Interface\\Icons\\Spell_Holy_DivineSpirit", ["Type"] = "SubGroup" } -- Shadow Protection GroupBuffs["Interface\\Icons\\Spell_Shadow_AntiShadow"] = { ["Greater"] = "Interface\\Icons\\Spell_Holy_PrayerofShadowProtection", ["Type"] = "SubGroup" } -- Prayer of Shadow Protection GroupBuffs["Interface\\Icons\\Spell_Holy_PrayerofShadowProtection"] = { ["Lesser"] = "Interface\\Icons\\Spell_Shadow_AntiShadow", ["Type"] = "SubGroup" } -- Arcane Intellect GroupBuffs["Interface\\Icons\\Spell_Holy_MagicalSentry"] = { ["Greater"] = "Interface\\Icons\\Spell_Holy_ArcaneIntellect", ["Type"] = "SubGroup" } -- Arcane Brilliance GroupBuffs["Interface\\Icons\\Spell_Holy_ArcaneIntellect"] = { ["Lesser"] = "Interface\\Icons\\Spell_Holy_MagicalSentry", ["Type"] = "SubGroup" } -- Blessing of Might GroupBuffs["Interface\\Icons\\Spell_Holy_FistOfJustice"] = { ["Greater"] = "Interface\\Icons\\Spell_Holy_GreaterBlessingofKings", ["Type"] = "Class" } -- Greater Blessing of Might GroupBuffs["Interface\\Icons\\Spell_Holy_GreaterBlessingofKings"] = { ["Lesser"] = "Interface\\Icons\\Spell_Holy_FistOfJustice", ["Type"] = "Class" } -- Blessing of Wisdom GroupBuffs["Interface\\Icons\\Spell_Holy_SealOfWisdom"] = { ["Greater"] = "Interface\\Icons\\Spell_Holy_GreaterBlessingofWisdom", ["Type"] = "Class" } -- Greater Blessing of Wisdom GroupBuffs["Interface\\Icons\\Spell_Holy_GreaterBlessingofWisdom"] = { ["Lesser"] = "Interface\\Icons\\Spell_Holy_SealOfWisdom", ["Type"] = "Class" } -- Blessing of Salvation GroupBuffs["Interface\\Icons\\Spell_Holy_SealOfSalvation"] = { ["Greater"] = "Interface\\Icons\\Spell_Holy_GreaterBlessingofSalvation", ["Type"] = "Class" } -- Greater Blessing of Salvation GroupBuffs["Interface\\Icons\\Spell_Holy_GreaterBlessingofSalvation"] = { ["Lesser"] = "Interface\\Icons\\Spell_Holy_SealOfSalvation", ["Type"] = "Class" } -- Blessing of Kings GroupBuffs["Interface\\Icons\\Spell_Magic_MageArmor"] = { ["Greater"] = "Interface\\Icons\\Spell_Magic_GreaterBlessingofKings", ["Type"] = "Class" } -- Greater Blessing of Kings GroupBuffs["Interface\\Icons\\Spell_Magic_GreaterBlessingofKings"] = { ["Lesser"] = "Interface\\Icons\\Spell_Magic_MageArmor", ["Type"] = "Class" } -- Blessing of Light GroupBuffs["Interface\\Icons\\Spell_Holy_PrayerOfHealing02"] = { ["Greater"] = "Interface\\Icons\\Spell_Holy_GreaterBlessingofLight", ["Type"] = "Class" } -- Greater Blessing of Light GroupBuffs["Interface\\Icons\\Spell_Holy_GreaterBlessingofLight"] = { ["Lesser"] = "Interface\\Icons\\Spell_Holy_PrayerOfHealing02", ["Type"] = "Class" } -- Blessing of Sanctuary GroupBuffs["Interface\\Icons\\Spell_Nature_LightningShield"] = { ["Greater"] = "Interface\\Icons\\Spell_Holy_GreaterBlessingofSanctuary", ["Type"] = "Class" } -- Greater Blessing of Sanctuary GroupBuffs["Interface\\Icons\\Spell_Holy_GreaterBlessingofSanctuary"] = { ["Lesser"] = "Interface\\Icons\\Spell_Nature_LightningShield", ["Type"] = "Class" } if LastBuffStatusUpdate == nil then LastBuffStatusUpdate = math.floor(GetTime()) BuffUpdateTime = GetTime() end UpdPerSec = { } for i = 0, SAMPLE_SIZE - 1 do UpdPerSec[i] = 0 end BW_Options_Init() BW_PlayersFrame:Show() end if BW:IsVisible() then if event == "PLAYER_LOGIN" then BW_Set_UNIT_IDs() BW_GetAllBuffs() BW_ResizeWindow() end if event == "SPELLCAST_START" then lastspellcast = arg1 end if event == "UNIT_AURA" then --BW_Print("UNIT_AURA for " .. arg1) for k, v in Player_Info do if arg1 == v.UNIT_ID then --BW_Print(arg1 .. " found as " .. v.Name) BW_Player_GetBuffs(v) BW_Player_AdjustBuffs(v) BW_ResizeWindow() break end end BW_UpdateBuffStatus() end if event == "PARTY_MEMBERS_CHANGED" or event == "RAID_ROSTER_UPDATE" or (event == "UNIT_PET" and BuffWatchConfig.ShowPets == true) then BW_Set_UNIT_IDs() BW_GetAllBuffs() BW_ResizeWindow() end if event == "UNIT_PVP_UPDATE" then local unitpet = BW_GetPetUnitID(arg1) for k, v in Player_Info do if arg1 == v.UNIT_ID or unitpet == v.UNIT_ID then BW_Player_ColourName(v) end end end end end -- ////////////////////////////////////////////////////////////////////////////////////// -- // -- // Main Functions -- // -- ////////////////////////////////////////////////////////////////////////////////////// -- Setup basic list of possible UNIT_IDs function BW_Set_UNIT_IDs(forced) local numRaidMembers = GetNumRaidMembers() if numRaidMembers > 0 then if lastgrouptype ~= "raid" or forced == true then UNIT_IDs = { } for i = 1, 40 do UNIT_IDs[i] = "raid" .. i end if BuffWatchConfig.ShowPets == true then for i = 1, 40 do UNIT_IDs[i+40] = "raidpet" .. i end end lastgrouptype = "raid" end elseif lastgrouptype ~= "party" or forced == true then UNIT_IDs = { } UNIT_IDs[1] = "player" UNIT_IDs[2] = "party1" UNIT_IDs[3] = "party2" UNIT_IDs[4] = "party3" UNIT_IDs[5] = "party4" if BuffWatchConfig.ShowPets == true then UNIT_IDs[6] = "pet" UNIT_IDs[7] = "partypet1" UNIT_IDs[8] = "partypet2" UNIT_IDs[9] = "partypet3" UNIT_IDs[10] = "partypet4" end lastgrouptype = "party" end BW_GetPlayerInfo() end --[[ Get details of each player we find in the UNIT_IDs list Player_Info[Name] props : ID - Unique number for this player, determines which playerframe to use UNIT_ID - UNIT_ID of this player Name - Players name (same as key, but useful if we are just looping array) Class - Players Class (for colouring name) IsPet - true if a pet (pets are sorted last, and can be hidden) SubGroup - 1 if in party, or 1-8 if in raid (Used for sorting) Checked - Used only in this function to determine players that are no longer present ]] -- function BW_GetPlayerInfo() local getnewalignpos = false for i = 1, table.getn(UNIT_IDs) do local unitname = UnitName(UNIT_IDs[i]) if unitname ~= nil and unitname ~= "Unknown Entity" then -- Check if we know about this person already, if not capture basic details if not Player_Info[unitname] then local id = BW_GetNextID(unitname) local namebutton = getglobal("BW_Player" .. id .. "_Name") local nametext = getglobal("BW_Player" .. id .. "_NameText") Player_Info[unitname] = { } Player_Info[unitname]["ID"] = id Player_Info[unitname]["Name"] = unitname local _, classname = UnitClass(UNIT_IDs[i]) if classname then Player_Info[unitname]["Class"] = classname else Player_Info[unitname]["Class"] = "" end if (lastgrouptype == "party" and i > 5) or (lastgrouptype == "raid" and i > 40) then Player_Info[unitname]["IsPet"] = 1 Player_Info[unitname]["Class"] = "" else Player_Info[unitname]["IsPet"] = 0 end nametext:SetText(unitname) namebutton:SetWidth(nametext:GetStringWidth()) if buttonalignposition < nametext:GetStringWidth() then buttonalignposition = nametext:GetStringWidth() buttonalignid = id end Player_Info[unitname]["UNIT_ID"] = UNIT_IDs[i] BW_Player_ColourName(Player_Info[unitname]) end -- Update any information that may have changed about this person, -- if we captured before, or take for first time Player_Info[unitname]["UNIT_ID"] = UNIT_IDs[i] if lastgrouptype == "raid" then local j = math.mod(i, 40) if j == 0 then j = 40 end _, _, Player_Info[unitname]["SubGroup"] = GetRaidRosterInfo(j) else Player_Info[unitname]["SubGroup"] = 1 end Player_Info[unitname]["Checked"] = 1 end end -- Remove players that are no longer in the group for k, v in Player_Info do if v.Checked == 1 then v.Checked = 0 else getglobal("BW_Player" .. v.ID):Hide() getglobal("BW_Player" .. v.ID .. "_NameText"):SetText(nil) if v.ID == buttonalignid then getnewalignpos = true end -- Add ID to temp array in case they come back -- (useful for dismissed or dead pets, or if a player leaves group briefly) Player_Left[v.Name] = v.ID Player_Info[k] = nil end end if getnewalignpos == true then buttonalignposition = 0 for k, v in Player_Info do local nametext = getglobal("BW_Player" .. v.ID .. "_NameText") if buttonalignposition < nametext:GetStringWidth() then buttonalignposition = nametext:GetStringWidth() buttonalignid = v.ID end end end end function BW_GetAllBuffs() -- Setup temp array for sorting, and get buffs for all players for k, v in Player_Info do BW_Player_GetBuffs(v) end -- ***** Does this need to be called here? BW_Player_AdjustFrames() -- Adjust buff icon positions for k, v in Player_Info do BW_Player_AdjustBuffs(v) end end function BW_Player_AdjustFrames() -- Only bother to sort player frames if we can see them if not minimized then local firstplayer = true local previousplayer local Player_Copy = { } for k, v in Player_Info do table.insert(Player_Copy, v) end -- Sort the player list in temp array if BuffWatchConfig.SortOrder == "Class" then table.sort(Player_Copy, function(a,b) if a.IsPet == b.IsPet then if a.Class == b.Class then return a.Name < b.Name else return a.Class < b.Class end else return a.IsPet < b.IsPet end end) elseif BuffWatchConfig.SortOrder == "Name" then table.sort(Player_Copy, function(a,b) if a.IsPet == b.IsPet then return a.Name < b.Name else return a.IsPet < b.IsPet end end) else -- Default table.sort(Player_Copy, function(a,b) if a.IsPet == b.IsPet then if a.SubGroup == b.SubGroup then return a.UNIT_ID < b.UNIT_ID else return a.SubGroup < b.SubGroup end else return a.IsPet < b.IsPet end end) end -- Position the player frames for k, v in Player_Copy do local playerframe = getglobal("BW_Player" .. v.ID) playerframe:ClearAllPoints() if playerframe:IsVisible() then if firstplayer then playerframe:SetPoint("TOPLEFT", "BW_Background", "TOPLEFT", 1, -16) previousplayer = v.ID firstplayer = false else playerframe:SetPoint("TOPLEFT", "BW_Player" .. previousplayer, "BOTTOMLEFT", 0, 0) previousplayer = v.ID end end end end end function BW_Player_GetBuffs(v) local curr_lock = getglobal("BW_Player" .. v.ID .. "_Lock") if not curr_lock:GetChecked() then for j = 1, 16 do local texture = UnitBuff(v.UNIT_ID, j, BuffWatchConfig.ShowCastableBuffs) local curr_buff = getglobal("BW_Player" .. v.ID .. "_Buff" .. j) local curr_buff_icon = getglobal("BW_Player" .. v.ID .. "_Buff" .. j .. "Icon") if texture == nil then curr_buff:Hide() curr_buff_icon:SetTexture(nil) elseif texture then curr_buff:Show() curr_buff_icon:SetTexture(texture) end end end for j = 1, 8 do local curr_buff = getglobal("BW_Player" .. v.ID .. "_Debuff" .. j) if BuffWatchConfig.ShowDebuffs == false then curr_buff:Hide() else local texture = UnitDebuff(v.UNIT_ID, j, BuffWatchConfig.ShowDispellableDebuffs) local curr_buff_icon = getglobal("BW_Player" .. v.ID .. "_Debuff" .. j .. "Icon") if texture == nil then curr_buff:Hide() elseif texture then curr_buff:Show() curr_buff_icon:SetTexture(texture) end end end end function BW_Player_AdjustBuffs(v) local firstbutton = true local previousbutton for j = 1, 16 do local curr_buff = getglobal("BW_Player" .. v.ID .. "_Buff" .. j) if curr_buff:IsVisible() then -- ***** Add code to sort this when buffs get hidden/shown, rather than refreshing all each time? curr_buff:ClearAllPoints() if firstbutton then if BuffWatchConfig.AlignBuffs == false then curr_buff:SetPoint("TOPLEFT", "BW_Player" .. v.ID .. "_NameText", "TOPRIGHT", 5, 2) else curr_buff:SetPoint("TOPLEFT", "BW_Player" .. v.ID .. "_NameText", "TOPLEFT", buttonalignposition + 5, 2) end firstbutton = false previousbutton = j else curr_buff:SetPoint("TOPLEFT", "BW_Player" .. v.ID .. "_Buff" .. previousbutton, "TOPRIGHT", 0, 0) previousbutton = j end end end local firstdebuff = true local previousdebuff for j = 1, 8 do local curr_debuff = getglobal("BW_Player" .. v.ID .. "_Debuff" .. j) if curr_debuff:IsVisible() then -- ***** Add code to sort this when buffs get hidden/shown, rather than refreshing all each time? curr_debuff:ClearAllPoints() if firstbutton then if BuffWatchConfig.AlignBuffs == false then curr_debuff:SetPoint("TOPLEFT", "BW_Player" .. v.ID .. "_NameText", "TOPRIGHT", 5, 2) else curr_debuff:SetPoint("TOPLEFT", "BW_Player" .. v.ID .. "_NameText", "TOPLEFT", buttonalignposition + 5, 2) end firstbutton = false previousbutton = j firstdebuff = false previousdebuff = j elseif firstdebuff then curr_debuff:SetPoint("TOPLEFT", "BW_Player" .. v.ID .. "_Buff" .. previousbutton, "TOPRIGHT", 0, 0) firstdebuff = false previousdebuff = j else curr_debuff:SetPoint("TOPLEFT", "BW_Player" .. v.ID .. "_Debuff" .. previousdebuff, "TOPRIGHT", 0, 0) previousdebuff = j end end end end function BW_ColourAllNames() for k, v in Player_Info do BW_Player_ColourName(v) end end function BW_Player_ColourName(v) local nametext = getglobal("BW_Player" .. v.ID .. "_NameText") if BuffWatchConfig.HighlightPvP and UnitIsPVP(v.UNIT_ID) then nametext:SetTextColor(0.0, 1.0, 0.0) else if v.Class ~= "" then local color = RAID_CLASS_COLORS[v.Class] if color then nametext:SetTextColor(color.r, color.g, color.b) else nametext:SetTextColor(1.0, 0.9, 0.8) end else nametext:SetTextColor(1.0, 0.9, 0.8) end end end function BW_ResizeWindow() local bottomcoord = 0 local height = 0 local rightcoord = 0 local width = 0 for k, v in Player_Info do if not minimized then local playerframe = getglobal("BW_Player" .. v.ID) if playerframe:GetBottom() ~= nil and playerframe:IsVisible() then if bottomcoord > playerframe:GetBottom() or bottomcoord == 0 then bottomcoord = playerframe:GetBottom() end end end for j = 1, 16 do local curr_buff = getglobal("BW_Player" .. v.ID .. "_Buff" .. j) if curr_buff:IsShown() and curr_buff:GetRight() then if curr_buff:GetRight() > rightcoord then rightcoord = curr_buff:GetRight() end end end for j = 1, 8 do local curr_buff = getglobal("BW_Player" .. v.ID .. "_Debuff" .. j) if curr_buff:IsShown() and curr_buff:GetRight() then if curr_buff:GetRight() > rightcoord then rightcoord = curr_buff:GetRight() end end end end if minimized then height = 20 else if bottomcoord and bottomcoord ~= 0 then if BuffWatchConfig.ShowUpdPerSec == true then height = BW_Background:GetTop() - bottomcoord + 25 else height = BW_Background:GetTop() - bottomcoord + 15 end if height < 65 then height = 65 end else height = 65 end end if rightcoord and rightcoord ~= 0 then width = rightcoord - BW_Background:GetLeft() + 20 if width < 100 then width = 100 end else width = 100 end BW_Background:SetHeight(height) BW:SetHeight(height) BW_Background:SetWidth(width) BW:SetWidth(width) end function BW_UpdateUPS() local TimeDiff = math.floor(GetTime() - LastBuffStatusUpdate) if TimeDiff == 0 then UpdPerSec[CurrSec] = UpdPerSec[CurrSec] + 1 elseif TimeDiff < SAMPLE_SIZE then for i = 0, TimeDiff - 2 do CurrSec = math.mod(CurrSec + 1, SAMPLE_SIZE) UpdPerSec[CurrSec] = 0 end CurrSec = math.mod(CurrSec + 1, SAMPLE_SIZE) UpdPerSec[CurrSec] = 1 else for i = 0, SAMPLE_SIZE - 1 do UpdPerSec[i] = 0 end CurrSec = math.mod(CurrSec + TimeDiff, SAMPLE_SIZE) UpdPerSec[CurrSec] = 1 end LastBuffStatusUpdate = math.floor(GetTime()) if BuffWatchConfig.ShowUpdPerSec == true then local UPS = 0 for i = 0, SAMPLE_SIZE - 1 do UPS = UPS + UpdPerSec[i] end UPS = UPS / SAMPLE_SIZE BW_UPS:SetText("UPS = " .. format("%4.1f", UPS)) end end function BW_UpdateBuffStatus(forced) if BuffWatchConfig.UpdPerSec > 0 then if (BuffUpdateTime + (1 / BuffWatchConfig.UpdPerSec) < GetTime()) or forced == true then BuffUpdateTime = GetTime() BuffUpdatePending = false else BuffUpdatePending = true return end end local hasbuffexpired = false local playerframechanges = false BW_UpdateUPS() for k, v in Player_Info do local player = "BW_Player" .. v.ID local playerframe = getglobal(player) local playerbuffexpired = false local playerhasbuffs = false local playerhasdebuffs = false local showframe = true if getglobal(player .. "_Lock"):GetChecked() then for j = 1, 16 do -- Check if there is a buff in this slot if getglobal(player .. "_Buff" .. j):IsShown() then playerhasbuffs = true -- If player is linkdead or dead in game, grey buffs out so user doesnt try to buff them if UnitIsDeadOrGhost(v.UNIT_ID) or UnitIsConnected(v.UNIT_ID) == nil then getglobal(player .. "_Buff" .. j .. "Icon"):SetVertexColor(0.4,0.4,0.4) else local Flag_BuffFound = false local texture = getglobal(player .. "_Buff" .. j .. "Icon"):GetTexture() -- See if we can find this buff on the player still for j_2 = 1, 16 do if UnitBuff(v.UNIT_ID, j_2) == texture then Flag_BuffFound = true break end end -- If not found, check if lesser or greater exists instead if Flag_BuffFound == false then if GroupBuffs[texture] and not GroupBuffs[texture].ByName then if GroupBuffs[texture].Lesser then texture = GroupBuffs[texture].Lesser elseif GroupBuffs[texture].Greater then texture = GroupBuffs[texture].Greater end for j_2 = 1, 16 do if UnitBuff(v.UNIT_ID, j_2) == texture then Flag_BuffFound = true getglobal(player .. "_Buff" .. j .. "Icon"):SetTexture(texture) break end end end end -- If buff has expired, highlight it red if Flag_BuffFound then getglobal(player .. "_Buff" .. j .. "Icon"):SetVertexColor(1,1,1) else getglobal(player .. "_Buff" .. j .. "Icon"):SetVertexColor(1,0,0) if BuffWatchConfig.ExpiredWarning and buffexpired ~= true then UIErrorsFrame:AddMessage("A buffwatch monitored buff has expired!", 0.2, 0.9, 0.9, 1.0, 2.0) buffexpired = true if BuffWatchConfig.ExpiredSound then PlaySound("igQuestFailed") end if minimized then BW_HeaderText:SetTextColor(1, 0, 0) end end hasbuffexpired = true playerbuffexpired = true end end end end if BuffWatchConfig.DebuffsAlwaysVisible == true then for j = 1, 8 do -- Check if there is a debuff in this slot if getglobal(player .. "_Debuff" .. j):IsShown() then playerhasdebuffs = true break end end end -- Determine whether we should show or hide this player if playerhasdebuffs == true then showframe = true else if HideMonitored == true then if HideUnmonitored == false then if playerhasbuffs == true then showframe = playerbuffexpired else showframe = true end else showframe = playerbuffexpired end else if HideUnmonitored == false then -- We are not hiding monitored or unmonitored, so show the playerframe showframe = true else -- Just hiding Unmonitored, so determine if any buffs are locked if playerhasbuffs == true then showframe = true else showframe = false end end end end else -- Player is unchecked, so show showframe = true -- Make sure buffs are not highlighted for j = 1, 16 do -- Check if there is a buff in this slot if getglobal(player .. "_Buff" .. j):IsShown() then getglobal(player .. "_Buff" .. j .. "Icon"):SetVertexColor(1,1,1) end end end if showframe == true then if not playerframe:IsShown() then playerframe:Show() playerframechanges = true end else if playerframe:IsShown() then playerframe:Hide() playerframechanges = true end end end buffexpired = hasbuffexpired if buffexpired == false then BW_HeaderText:SetTextColor(0.2, 0.9, 0.9) end if playerframechanges == true then BW_Player_AdjustFrames() end end -- ////////////////////////////////////////////////////////////////////////////////////// -- // -- // Slash Commands -- // -- ////////////////////////////////////////////////////////////////////////////////////// function BW_SlashHandler(msg) msg = string.lower(msg) if msg == "toggle" then BW_Toggle() elseif msg == "set" then BW_SetRightMouse() elseif msg == "options" then BW_OptionsToggle() elseif msg == "" or msg == "help" then BW_ShowHelp() else BW_Print("BuffWatch: Invalid Command or Parameter") end end -- ////////////////////////////////////////////////////////////////////////////////////// -- // -- // Mouse Events -- // -- ////////////////////////////////////////////////////////////////////////////////////// function BW_OnMouseDown(arg1) if arg1 == "LeftButton" then BW:StartMoving() end end function BW_OnMouseUp(arg1) if arg1 == "LeftButton" then BW:StopMovingOrSizing() end end function BW_MouseIsOverFrame() if MouseIsOver(BW) then BW_MinimizeButton:Show() if not minimized then BW_OptionsButton:Show() BW_HideUnmonitoredButton:Show() BW_HideMonitoredButton:Show() BW_Lock_All:Show() for k, v in Player_Info do getglobal("BW_Player" .. v.ID .. "_Lock"):Show() end else BW_Lock_All:Hide() end else if not minimized then BW_MinimizeButton:Hide() BW_OptionsButton:Hide() BW_HideUnmonitoredButton:Hide() BW_HideMonitoredButton:Hide() for k, v in Player_Info do getglobal("BW_Player" .. v.ID .. "_Lock"):Hide() end end BW_Lock_All:Hide() end end function BW_Check_Clicked() local checked = this:GetChecked() if checked then for k, v in Player_Info do local curr_lock = getglobal("BW_Player" .. v.ID .. "_Lock") -- if curr_lock:IsVisible() then if not curr_lock:GetChecked() then checked = nil break end -- end end if checked then BW_Lock_All:SetChecked(true) end else BW_Lock_All:SetChecked(false) end end function BW_Name_Clicked(button) local id = this:GetParent():GetID() local playername = getglobal("BW_Player" .. id .. "_NameText"):GetText() if button == "LeftButton" then TargetByName(playername) elseif button == "RightButton" then AssistByName(playername) end end function BW_Buff_Clicked(button) local buffid = this:GetID() local playerid = this:GetParent():GetID() local playername = getglobal("BW_Player" .. playerid .. "_NameText"):GetText() local playerframe = "BW_Player" .. playerid if button == "LeftButton" then local spellid = nil local spelltexture = getglobal(this:GetName() .. "Icon"):GetTexture() for i = 1, 300 do if GetSpellTexture(i, 1) == spelltexture then spellid = i end end if spellid then if BuffWatchConfig.PreventPvPBuff and UnitIsPVP(Player_Info[playername].UNIT_ID) then BW_Print(playername .. " is PvP Flagged, aborted casting.") elseif UnitIsVisible(Player_Info[playername].UNIT_ID) then if UnitName("target") and not UnitIsEnemy("target","player") then if UnitName("target") ~= playername then TargetByName(playername) end end if IsShiftKeyDown() then SendChatMessage(format("BW: Casting %s on %s", GetSpellName(spellid,1), playername), "PARTY") end -- Check if it's a group buff or a lesser version. if GroupBuffs[spelltexture] then local castgreater = false local newspelltexture = nil -- Check Buff Threshold for whether to cast single or group if BuffWatchConfig.BuffThreshold > 0 and (GetNumPartyMembers() >= (BuffWatchConfig.BuffThreshold - 1) or GetNumRaidMembers() >= BuffWatchConfig.BuffThreshold) then local castcount = 0 local otherspelltexture local pvpflagfound if GroupBuffs[spelltexture].Greater then otherspelltexture = GroupBuffs[spelltexture].Greater else otherspelltexture = GroupBuffs[spelltexture].Lesser end -- Check if we have other spell, and save OtherSpellID if not GroupBuffs[spelltexture].OtherSpellID then if GroupBuffs[spelltexture].ByName then for i = 1, 300 do local spellName, _ = GetSpellName(i, 1) if spellName == otherspelltexture then GroupBuffs[spelltexture].OtherSpellID = i end end else for i = 1, 300 do if GetSpellTexture(i, 1) == otherspelltexture then GroupBuffs[spelltexture].OtherSpellID = i end end end if not GroupBuffs[spelltexture].OtherSpellID then -- Player doesnt have greater spell GroupBuffs[spelltexture].OtherSpellID = 0 end end -- Check whether to cast greater or lesser. If we dont have greater, then skip. if GroupBuffs[spelltexture].OtherSpellID ~= 0 then for k, v in Player_Info do local Flag_BuffFound = false -- Find players with same SubGroup/Class if Player_Info[v.Name].IsPet == 0 and v[GroupBuffs[spelltexture].Type] == Player_Info[playername][GroupBuffs[spelltexture].Type] then if BuffWatchConfig.PreventPvPBuff then if UnitIsPVP(v.UNIT_ID) then pvpflagfound = v.Name -- Enforce a single buff to be cast end end -- Check for missing buff for j = 1, 16 do if UnitBuff(v.UNIT_ID, j) == spelltexture or UnitBuff(v.UNIT_ID, j) == otherspelltexture then Flag_BuffFound = true break end end if Flag_BuffFound == false then castcount = castcount + 1 end end end if castcount >= BuffWatchConfig.BuffThreshold then if pvpflagfound then BW_Print(pvpflagfound .. " is PvP Flagged, casting single version buff.") else castgreater = true end end end end if castgreater then newspelltexture = GroupBuffs[spelltexture].Greater else newspelltexture = GroupBuffs[spelltexture].Lesser end if newspelltexture then if GroupBuffs[spelltexture].OtherSpellID then spellid = GroupBuffs[spelltexture].OtherSpellID else for i = 1, 300 do if GetSpellTexture(i, 1) == newspelltexture then spellid = i GroupBuffs[spelltexture].OtherSpellID = i end end end end end CastSpell(spellid, 1) if SpellIsTargeting() then TargetByName(playername) end else BW_Print(playername .. " is out of range or not visible.") end end elseif button == "RightButton" then if getglobal(playerframe .. "_Lock"):GetChecked() and IsControlKeyDown() then this:Hide() BW_Player_AdjustBuffs(Player_Info[playername]) BW_ResizeWindow() elseif getglobal(playerframe .. "_Lock"):GetChecked() and IsAltKeyDown() then for i = 1, 16 do if i ~= buffid then getglobal(playerframe .. "_Buff" .. i):Hide() end end BW_Player_AdjustBuffs(Player_Info[playername]) BW_ResizeWindow() else if BuffWatchConfig.rightMouseSpell then if UnitName("target") and not UnitIsEnemy("target","player") then if UnitName("target") ~= playername then TargetByName(playername) end end if IsShiftKeyDown() then SendChatMessage(format("BW: Casting %s on %s", GetSpellName(BuffWatchConfig.rightMouseSpell,1), playername), "PARTY") end CastSpell(BuffWatchConfig.rightMouseSpell,1) if SpellIsTargeting() then TargetByName(playername) end else BW_Print(" BuffWatch: Right mouse button spell has not yet been set.") BW_Print(" Cast any spell with a duration. Then type \"/bw set\"") end end end end function BW_Buff_Tooltip() local playername = getglobal("BW_Player" .. this:GetParent():GetID() .. "_NameText"):GetText() local buffbuttonid = nil local debuffbuttonid = nil local texture = getglobal(this:GetName() .. "Icon"):GetTexture() for i = 1, 16 do if UnitBuff(Player_Info[playername]["UNIT_ID"], i) == texture then buffbuttonid = i break end end if buffbuttonid == nil then for i_2 = 1, 8 do if UnitDebuff(Player_Info[playername]["UNIT_ID"], i_2) == texture then debuffbuttonid = i_2 break end end end if buffbuttonid then GameTooltip:SetOwner(this, "ANCHOR_BOTTOMLEFT") GameTooltip:SetUnitBuff(Player_Info[playername]["UNIT_ID"], buffbuttonid) elseif debuffbuttonid then GameTooltip:SetOwner(this, "ANCHOR_BOTTOMLEFT") GameTooltip:SetUnitDebuff(Player_Info[playername]["UNIT_ID"], debuffbuttonid) end end function BW_Set_AllChecks(checked) for k, v in Player_Info do getglobal("BW_Player" .. v.ID .. "_Lock"):SetChecked(checked) end end function BW_Header_Clicked(button) if button == "LeftButton" then BW_Set_UNIT_IDs() BW_GetAllBuffs() BW_UpdateBuffStatus() BW_ResizeWindow() end if button == "RightButton" then BW_Toggle() end end function BW_MinimizeButton_Clicked() minimized = not minimized if minimized == true then BW_PlayersFrame:Hide() else BW_PlayersFrame:Show() end BW_ResizeWindow() BW_MouseIsOverFrame() end function BW_HideUnmonitored_Clicked() HideUnmonitored = not HideUnmonitored BW_Player_AdjustFrames() BW_ResizeWindow() BW_HideUnmonitored_OnEnter() end function BW_HideUnmonitored_OnEnter() GameTooltip:SetOwner(this, "ANCHOR_BOTTOM") if HideUnmonitored == true then GameTooltip:SetText("Hiding Unmonitored") else GameTooltip:SetText("Showing Unmonitored") end end function BW_HideMonitored_Clicked() HideMonitored = not HideMonitored BW_Player_AdjustFrames() BW_ResizeWindow() BW_HideMonitored_OnEnter() end function BW_HideMonitored_OnEnter() GameTooltip:SetOwner(this, "ANCHOR_BOTTOM") if HideMonitored == true then GameTooltip:SetText("Hiding Monitored") else GameTooltip:SetText("Showing Monitored") end end -- ////////////////////////////////////////////////////////////////////////////////////// -- // -- // Miscellaneous -- // -- ////////////////////////////////////////////////////////////////////////////////////// function BW_Toggle() if BW:IsVisible() then HideUIPanel(BW) else ShowUIPanel(BW) end end function BW_OptionsToggle() if not BW_Options:IsVisible() then ShowUIPanel(BW_Options) else HideUIPanel(BW_Options) end end function BW_SetRightMouse() if lastspellcast == nil then BW_Print(" BuffWatch: You have not cast any timed spells yet:") BW_Print(" Cast one, and then try \"/bw set\" again") else for i = 1, 300 do if GetSpellName(i,1) == lastspellcast then BuffWatchConfig.rightMouseSpell = i -- break end end if BuffWatchConfig.rightMouseSpell then BW_Print( format("BuffWatch: Right mouse button set to %s (%s)", GetSpellName(BuffWatchConfig.rightMouseSpell,1) ), 0.2, 0.9, 0.9 ) end end end function GetLen(arr) local len = 0 if arr ~= nil then for k, v in arr do len = len + 1 end end return len end function BW_GetNextID(unitname) local i = 1 if GetLen(Player_Info) == 0 then return i else local oldID = Player_Left[unitname] if oldID then local found = false for k, v in Player_Info do if v.ID == oldID then found = true break end end Player_Left[unitname] = nil if found == false then -- getglobal("BW_Player" .. oldID):Show() return oldID end end local Player_Copy = { } for k, v in Player_Info do table.insert(Player_Copy, v) end table.sort(Player_Copy, function(a,b) return a.ID < b.ID end) for k, v in Player_Copy do if i ~= v.ID then break end i = i + 1 end Player_Copy = nil end getglobal("BW_Player" .. i .. "_Lock"):SetChecked(false) return i end function BW_GetPetUnitID(unitid) -- if lastgrouptype == "party" then if unitid == "player" then return "pet", 1 end return string.gsub(unitid, "([a-z]+)([0-9]+)", "%1pet%2") --[[ return "partypet" .. string.sub(unitid, -1) else return "raidpet" .. string.sub(unitid, 5) end ]]-- end -- ////////////////////////////////////////////////////////////////////////////////////// -- // -- // BuffWatch Print Function -- // -- ////////////////////////////////////////////////////////////////////////////////////// function BW_Print(msg, R, G, B) DEFAULT_CHAT_FRAME:AddMessage(msg, R, G, B); end -- ////////////////////////////////////////////////////////////////////////////////////// -- // -- // BuffWatch Help -- // -- ////////////////////////////////////////////////////////////////////////////////////// function BW_ShowHelp() BW_HelpFrame:Show() BW_HelpFrame:ClearAllPoints() BW_HelpFrame:SetPoint("CENTER","UIParent","CENTER",0,-32) BW_HelpFrameText:SetText( " - BuffWatch Usage - v " .. BW_VERSION .. " - " .. [[ Show/Hide the BuffWatch window: - Bind a keyboard button to show/hide the window - You can also close it by right clicking the "BuffWatch" label (appears on mouseover) Showing Buffs: - Left click the BuffWatch label - Also occurs automatically whenever your gain/lose a party or raid member Locking a player's watched buffs: - If the checkbox to the left is unchecked, buffs will be added automatically whenever they gain a buff - If checked, buffs will not be added, or removed Rebuffing: - Left click an icon (will auto-target) Right click spell: - Cast any spell with a cast time (not instant). Then type "/bw set" - To cast it, right click any icon (will auto-target) Deleting buffs: - Lock the player's buffs (check the box). Then [ CTRL + Right Click ] on the buff - Optionally, [ ALT + Right Click ] to delete all but the selected one. Slash Commands ( Use /buffwatch or /bw ) - /bw toggle : shows/hides the window - /bw options : shows/hides the options window - /bw : shows this help menu :) Verbosity: - Hold [ Shift ] while left or right-clicking a buff icon to send a cast message to your party ]] ) end -- //////////////////////////////////////////////////////////////////////////////////////