Eggerd

World of Warcraft, Addon "Decliner" Fix, Shadowlands 9.0.1

Oct 26th, 2020 (edited)
539
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 60.00 KB | None | 0 0
  1. --[[----
  2.  
  3.     DeclinerAllowed is the main player check function, the bouncer, which decides if a player is a stranger or not.
  4.     Usage, such as in another addon or a WeakAura:
  5.  
  6.             DeclinerAllowed("name-realm")
  7.  
  8.         Returns true if _you_ recently whispered "name", they're in your group, or online in your guild, friends, or bnet.
  9.         Returns false if they are a complete stranger.
  10.         Return is subject to settings. If Decliner is disabled, returns false. If "name" is in a list that is declined, returns false.
  11.  
  12.             DeclinerAllowed("name-realm","action")
  13.  
  14.         Returns true or false for "name" based on your settings for that action.
  15.         Actions are "chan", "duel", "pet", "group", "guild", "chart", or "trade".
  16.  
  17.     For example, DeclinerAllowed("Guildie") will return true because Guildie is online in your guild, but also returns true if in your group.
  18.  
  19.     An example of the second usage, let's say your options will decline duel requests from guild members and realm friends, but not group members.
  20.     Mcstabyrouge is a guildie, but not a friend, DeclinerAllowed("Mcstabyrouge","duel") will return false.
  21.     You add them as a friend, DeclinerAllowed("Mcstabyrouge","duel") still returns false.
  22.     You invite them to a group, DeclinerAllowed("Mcstabyrouge","duel") now returns true.
  23.  
  24.     ----    If no realm is provided for "name", your realm is assumed.    ----
  25.  
  26. --------
  27.  
  28.     DeclinerQuery queries Decliner's list of players that would be allowed, similar to the above function but unaffected by settings.
  29.     Usage, such as in another addon or a WeakAura:
  30.  
  31.             DeclinerQuery("name-realm")
  32.  
  33.         Returns true if _you_ recently whispered "name", they're in your group, or online in your guild, friends, or bnet.
  34.         Returns false if they are a complete stranger.
  35.  
  36.             DeclinerQuery("name-realm","section")
  37.  
  38.         Returns true if "name" is in the section of the list you're querying.
  39.         Sections are "whisper", "group", "guild", "friend", or "bnet".
  40.  
  41.             DeclinerQuery()
  42.  
  43.         Returns a table of all allowed players sorted by sections.
  44.  
  45.     Keep in mind Decliner only tracks online players. Offline group members are tracked, since they are still in the group according to the UI.
  46.  
  47.     ----    If no realm is provided for "name", your realm is assumed.    ----
  48.  
  49. --------
  50.  
  51.     DeclinerTrack returns the amount of times you recently whispered players or they were declined.
  52.     Players are cleared as Decliner forgets whispers or they don't spam you after some time.
  53.     Usage, such as in another addon or a WeakAura:
  54.  
  55.             DeclinerTrack("action","name-realm")
  56.  
  57.         Returns the number of times that action occurred recently from "name". Details below.
  58.  
  59.             DeclinerTrack("action")
  60.  
  61.         Returns how many players are currently listed for that action. Details below.
  62.  
  63.             DeclinerTrack("players")
  64.  
  65.         Returns how many players are currently listed for all actions.
  66.         If a single player spammed duel and trade many times, this returns 1.
  67.  
  68.             DeclinerTrack("all","name-realm")
  69.  
  70.         Returns total amount of interactions currently tracked for "name".
  71.         If "name" spammed duel twice and tried to invite three times, this returns 5.
  72.  
  73.             DeclinerTrack("all")
  74.  
  75.         Returns total amount of all interactions currently tracked.
  76.         If one player tried to duel twice, another player tried to trade three times, and a third player tried to invite once, this returns 6.
  77.  
  78.             DeclinerTrack()
  79.  
  80.         Returns the entire current tracking table.
  81.  
  82.     Actions are "whisper", "chan", "duel", "pet", "group", "guild", "chart", or "trade".
  83.     "whisper" is the amount of times _you_ whispered "name" while the others are the times they were blocked for that action.
  84.  
  85.     An example of the first usage, Suprawsom spammed a duel request 5 times, so DeclinerTrack("duel","Suprawsom") will return 5, then 4, and so on as time goes by.
  86.     Once the last action is cleared, DeclinerTrack("duel","Suprawsom") will be nil, allowing Decliner to once again notify you of any further duels from them.
  87.     Another example, you whispered Chanter 9 times due to a conversation, so DeclinerTrack("whisper","Chanter") will be 9.
  88.     They will be authorized for whatever your options are set to for whispers, such as trading, until that number counts down to nil.
  89.  
  90.     An example of the second usage, let's say you are standing in front of Orgrimmar on the Area 52 server.
  91.     14 players just spammed you with duel invites one right after another. DeclinerTrack("duel") will return 14.
  92.     Depending on how many times each player spammed you, that number will count down erratically.
  93.     It may jump to nil if all 14 only sent one request or hang around at a lower number for a while when the single requests are cleared.
  94.  
  95.     ----    If no realm is provided for "name", your realm is assumed.    ----
  96.  
  97. --------
  98.  
  99.     declinerhistory is an organized table of interactions converted from declinerlog
  100.  
  101.     The most recent 30 interactions, allowed and declined, can be viewed with /dump declinerhistory
  102.  
  103.     Table is indexed with 1 being the most recent entry and last index the oldest entry
  104.     Keys in each entry are event, ostamp, fstamp, name, realm, channel, guild, result, and reason
  105.  
  106.     event is the UI event triggered for that interaction, such as PARTY_INVITE_REQUEST for group invites
  107.     ostamp is what time() returned on that event and fstamp is formatted as hh:mm:ss am/pm - mmm dd yyyy (day)
  108.     channel, guild, and reason only appear for channel invites, guild invites, and allowed interactions respectively
  109.  
  110.     Interactions before Decliner version 7.0.3.160829.1 (Aug 29 2016) are incompatible with this conversion
  111.  
  112. ----]]--
  113.  
  114.  -- localize Lua functions
  115.  
  116. local date=date
  117. local error=error
  118. local format=string.format
  119. local gmatch=string.gmatch
  120. local gsub=string.gsub
  121. local insert=table.insert
  122. local ipairs=ipairs
  123. local lower=string.lower
  124. local match=string.match
  125. local max=math.max
  126. local next=next
  127. local random=math.random
  128. local remove=table.remove
  129. local select=select
  130. local sort=table.sort
  131. local sub=string.sub
  132. local time=time
  133. local tonumber=tonumber
  134. local tostring=tostring
  135. local type=type
  136. local unpack=unpack
  137. local upper=string.upper
  138.  
  139.  -- localize Blizzard functions
  140.  
  141. local After=C_Timer.After
  142. local BNGetNumFriends=BNGetNumFriends
  143. local CancelDuel=CancelDuel
  144. local CancelPetDuel=C_PetBattles.CancelPVPDuel
  145. local ClosePetition=ClosePetition
  146. local CloseTrade=CloseTrade
  147. local CreateFrame=CreateFrame
  148. local ct=CopyTable
  149. local DeclineChannelInvite=DeclineChannelInvite
  150. local DeclineGroup=DeclineGroup
  151. local DeclineGuild=DeclineGuild
  152. local GetFriendInfo=GetFriendInfo
  153. local GetGuildRosterInfo=GetGuildRosterInfo
  154. local GuildRoster=C_GuildInfo.GuildRoster
  155. local GetNumGroupMembers=GetNumGroupMembers
  156. local GetNumGuildMembers=GetNumGuildMembers
  157. local GetPetitionInfo=GetPetitionInfo
  158. local HideUIPanel=HideUIPanel
  159. local IsInRaid=IsInRaid
  160. local split=strsplit
  161. local StaticPopupSpecial_Hide=StaticPopupSpecial_Hide
  162. local StaticPopup_Hide=StaticPopup_Hide
  163. local StopSound=StopSound
  164. local Ticker=C_Timer.NewTicker
  165. local UnitName=UnitName
  166. local ValueToBoolean=ValueToBoolean
  167. local wipe=wipe
  168. local ShowFriends=C_FriendList.ShowFriends
  169.  
  170.  -- initialize
  171.  
  172. local _
  173. local a,L=...
  174. local d={} -- data
  175. local e={} -- events
  176. local f={} -- functions
  177. local p={} -- panel objects
  178. local g=GREEN_FONT_COLOR_CODE
  179. local r=RED_FONT_COLOR_CODE
  180. local DCL=CreateFrame('frame',a)
  181. local DCLPanel=CreateFrame('frame',a..'Panel')
  182. DCL:RegisterEvent('ADDON_LOADED')
  183. DCL:RegisterEvent('PLAYER_LOGIN')
  184. DCL:RegisterEvent('PLAYER_LOGOUT')
  185. DCL:RegisterEvent('LOADING_SCREEN_DISABLED')
  186. d.file={
  187.     ch='Interface\\RaidFrame\\ReadyCheck-Ready',
  188.     cr='Interface\\RaidFrame\\ReadyCheck-NotReady',
  189.     ha='Interface\\Icons\\Spell_Misc_EmotionHappy',
  190.     sa='Interface\\Icons\\Spell_Misc_EmotionSad',
  191. }
  192. d.def={
  193.     enable={chan=true,duel=true,pet=true,group=true,guild=true,chart=true,trade=true,tell=false,},
  194.     tell={chan=false,duel=false,pet=false,group=false,guild=false,chart=false,trade=false,tell=false,},
  195.     group={chan=false,duel=false,pet=false,group=false,guild=false,chart=false,trade=false,tell=false,},
  196.     guild={chan=false,duel=false,pet=false,group=false,guild=false,chart=false,trade=false,tell=false,},
  197.     friend={chan=false,duel=false,pet=false,group=false,guild=false,chart=false,trade=false,tell=false,},
  198.     bnet={chan=false,duel=false,pet=false,group=false,guild=false,chart=false,trade=false,tell=false,},
  199.     msg={chan=false,duel=false,pet=false,group=false,guild=false,chart=false,trade=false,tell=false,},
  200.     chan={emote=true,say=true,yell=true,[1]=true,[2]=true,[26]=true,},
  201.     gen={openinv=false,msgs=false,disable=false,spec=false,},
  202.     open={'1','invite','portal',},
  203. }
  204. declinerhistory,declinerlog={},{}
  205. local list={group={},guild={},friend={},bnet={},}
  206. local dhist,dlog,info,linktip,pool,track={},{},{},{},{},{whisper={}}
  207. for k in next,d.def.enable do track[k]={} end
  208.  
  209.  -- localization string reformatting
  210.  -- eases the work of importing/exporting localization since Curse's localization UI sucks
  211.  
  212. d.L={
  213.     ['BATTLENET']=BATTLENET_OPTIONS_LABEL,
  214.     ['BLOCK_CHAT_CHANNEL_INVITE']="“"..BLOCK_CHAT_CHANNEL_INVITE.."”",
  215.     ['BLOCK_GUILD_INVITES']="“"..BLOCK_GUILD_INVITES.."”",
  216.     ['BLOCK_TRADES']="“"..BLOCK_TRADES.."”",
  217.     ['CHANNEL_NAME']="“%%2$s”",
  218.     ['ENABLE_CHAN']="“"..L["ENABLE_CHAN"].."”",
  219.     ['ENABLE_GUILD']="“"..L["ENABLE_GUILD"].."”",
  220.     ['ENABLE_TRADE']="“"..L["ENABLE_TRADE"].."”",
  221.     ['GUILD_NAME']="<%%2$s>",
  222.     ['PLAYER_NAME']="%%1$s",
  223.     ['SLASH_DCL']=g.."/dcl|r",
  224. }
  225.  
  226. for k1,v1 in next,L do
  227.     for k2,v2 in next,d.L do
  228.         if match(v1,'"') then v1=v1:gsub('"','“',1):gsub('"','”',1) end
  229.         if match(v1,k2) then v1=v1:gsub(k2,v2) end
  230.     end
  231.     L[k1]=v1
  232. end
  233.  
  234.  -- global string strip/replace
  235.  -- idea from Phanx
  236.  
  237. function f.pattern(str,sy)
  238.     str=str:gsub('%%%d?$?%a',sy and '.*' or '')
  239.     str=str:gsub(sy and '[%[%]]' or '[:%(%)]',sy and '.' or '')
  240.     str=str:match('^%s*(.-)%s*$')
  241.     return str
  242. end
  243.  
  244.  -- Blizzard global strings used
  245.  -- everything in caps can be found in FrameXML/GlobalStrings.lua
  246.  -- this table is only for keeping strings in one place
  247.  
  248. d.S={
  249.     ADD=ADD.."/"..REMOVE,
  250.     BAT=BATTLENET_OPTIONS_LABEL,
  251.     CAL=gsub(BN_INLINE_TOAST_ALERT,BATTLENET_OPTIONS_LABEL,CHAT),
  252.     CHA=CHANNEL,
  253.     CHS=CHANNELS,
  254.     CSS=CHARACTER.." "..SETTINGS,
  255.     DIO=DISABLE.." "..OPENING.." "..GROUP.." ",
  256.     DIS=DISABLE.." ",
  257.     DUE=DUEL,
  258.     EMO=EMOTE,
  259.     ENA=ENABLE.." "..DECLINE,
  260.     GEN=GENERAL,
  261.     GRM=GROUP.." "..MEMBERS,
  262.     GRO=GROUP,
  263.     GUI=GUILD,
  264.     GUM=GUILD.." "..MEMBERS,
  265.     INV=f.pattern(GUILDEVENT_TYPE_INVITE):gsub('^%a',upper),
  266.     LOO=LFG_TITLE,
  267.     NOS=NO.." "..SOUND,
  268.     PET=PET_BATTLE_PVP_QUEUE,
  269.     PLA=PLAYER,
  270.     PTI=f.pattern(PETITION_TITLE),
  271.     REA=lower(READY),
  272.     REF=f.pattern(FRIENDS_LIST_REALM).." "..FRIENDS,
  273.     REQ=GUILDINFOTAB_APPLICANTS_NONE,
  274.     SAY=SAY,
  275.     TRA=TRADE,
  276.     VER=lower(GAME_VERSION_LABEL),
  277.     WHD=f.pattern(AUTOCOMPLETE_LABEL_INTERACTED),
  278.     WHS=f.pattern(CHAT_WHISPER_GET):gsub('^%a',upper),
  279.     WOW=BNET_CLIENT_WOW,
  280.     YEL=YELL,
  281. }
  282.  
  283.  -- matching tables for events, sounds, and text filters
  284.  
  285. d.IN={
  286.     CHANNEL_INVITE_REQUEST='chan',
  287.     CHAT_MSG_WHISPER='tell',
  288.     DUEL_REQUESTED='duel',
  289.     GUILD_INVITE_REQUEST='guild',
  290.     PARTY_INVITE_REQUEST='group',
  291.     PET_BATTLE_PVP_DUEL_REQUESTED='pet',
  292.     PETITION_SHOW='chart',
  293.     TRADE_SHOW='trade',
  294. }
  295. d.SO={
  296.     [839]=true, -- igCharacterInfoOpen
  297.     [840]=true, -- igCharacterInfoClose
  298.     [850]=true, -- igMainMenuOpen
  299.     [851]=true, -- igMainMenuClose
  300.     [875]=true, -- igQuestListOpen
  301.     [876]=true, -- igQuestListClose
  302.     [880]=true, -- igPlayerInvite
  303. }
  304. d.SY={
  305.     filter={
  306.         duel=gsub(DUEL_REQUESTED,'%%%d?$?%a','(.*)'),
  307.         pet=gsub(PET_BATTLE_PVP_DUEL_REQUESTED,'%%%d?$?%a','(.*)'),
  308.         guild=gsub(f.pattern(ERR_INVITED_TO_GUILD_SSS,1),':%.%*|',':(.*)|'),
  309.         group1=gsub(f.pattern(ERR_INVITED_TO_GROUP_SS,1),':%.%*|',':(.*)|'),
  310.         group2=gsub(f.pattern(ERR_INVITED_ALREADY_IN_GROUP_SS,1),':%.%*|',':(.*)|'),
  311.     },
  312.     trade=gsub(ERR_INITIATE_TRADE_S,'%%%d?$?%a','.*'),
  313. }
  314. d.UI={[ERR_TRADE_CANCELLED]=225,[ERR_DUEL_CANCELLED]=363,}
  315.  
  316.  -- internal bouncer, what DeclinerAllowed uses
  317.  
  318. function f.allowed(name,option,sup,missingrealm,bypass)
  319.     if type(name)~='string' then f.debug('ERROR','['..tostring(name)..'] not a string') return true end
  320.     name=f.n(name) option=option and lower(option) missingrealm=f.n(name,'')
  321.     if option then
  322.         if d.var.gen.disable or not d.var.enable[option] then
  323.             if not sup then f.debug(name..': allowed - ['..option..'] disabled') end return true
  324.         else if not sup then f.debug(name..': ['..option..'] enabled') end end
  325.     else bypass=true if not sup then f.debug(name..': no option, bypassed') end end
  326.     if bypass or not d.var.tell[option] then
  327.         if track.whisper[name] or track.whisper[missingrealm] then
  328.             if not sup then f.debug(name..': allowed - whispered') end return true
  329.         elseif #track.whisper<1 then if not sup then f.debug(name..': no one recently whispered') end
  330.         else if not sup then f.debug(name..': did not recently whisper') end end
  331.     else if not sup then f.debug(name..': tell disabled for '..option) end end
  332.     if bypass or option~='group' then if bypass or not d.var.group[option] then
  333.         if d.ngroup and d.ngroup>0 then
  334.             if list.group[name] or list.group[missingrealm] then
  335.                 if not sup then f.debug(name..': allowed - group member') end return true
  336.             else if not sup then f.debug(name..': not a group member') end end
  337.         else if not sup then f.debug(name..': not in a group') end end
  338.     else if not sup then f.debug(name..': group disabled for '..option) end end end
  339.     if bypass or option~='guild' then if bypass or not d.var.guild[option] then
  340.         if d.tguild and d.tguild>0 then
  341.             if d.oguild and d.oguild>0 then
  342.                 if list.guild[name] or list.guild[missingrealm] then
  343.                     if not sup then f.debug(name..': allowed - guild member') end return true
  344.                 else if not sup then f.debug(name..': not a guild member') end end
  345.             else if not sup then f.debug(name..': no online guild members') end end
  346.         else if not sup then f.debug(name..': not in a guild') end end
  347.     else if not sup then f.debug(name..': guild disabled for '..option) end end end
  348.     if bypass or not d.var.friend[option] then
  349.         if d.tfriend and d.tfriend>0 then
  350.             if d.ofriend and d.ofriend>0 then
  351.                 if list.friend[name] or list.friend[missingrealm] then
  352.                     if not sup then f.debug(name..': allowed - realm friend') end return true
  353.                 else if not sup then f.debug(name..': not a realm friend') end end
  354.             else if not sup then f.debug(name..': no online realm friends') end end
  355.         else if not sup then f.debug(name..': no realm friends') end end
  356.     else if not sup then f.debug(name..': friend disabled for '..option) end end
  357.     if bypass or not d.var.bnet[option] then
  358.         if d.tbnet and d.tbnet>0 then
  359.             if d.obnet and d.obnet>0 then
  360.                 if list.bnet[name] or list.bnet[missingrealm] then
  361.                     if not sup then f.debug(name..': allowed - bnet friend') end return true
  362.                 else if not sup then f.debug(name..': not a bnet friend') end end
  363.             else if not sup then f.debug(name..': no online bnet friends') end end
  364.         else if not sup then f.debug(name..': no bnet friends') end end
  365.     else if not sup then f.debug(name..': bnet disabled for '..option) end end
  366.     if not sup then f.debug(name..': declined') end return false
  367. end
  368.  
  369.  -- debug handler
  370.  
  371. function f.debug(msg,...)
  372.     msg=match(msg,':') and '    '..msg or match(msg,'^%u') and msg..' '..date('%y%m%d%H%M%S',time()) or msg
  373.     local args,argstr={n=select('#',...),...}
  374.     if args.n>0 then for i=1,args.n do
  375.         argstr=argstr and argstr..', '..tostring(args[i]) or ' - args: '..tostring(args[i])
  376.     end else argstr='' end
  377.     insert(dlog,msg..argstr)
  378. end
  379.  
  380.  -- error handler
  381.  
  382. function f.error(msg,arg1,arg2,nl)
  383.     arg1,arg2,nl=tostring(arg1),tostring(arg2),'\n        '
  384.     error(msg..nl..'See top of dclcore.lua for description'..nl..'args: '..arg1..', '..arg2,3)
  385. end
  386.  
  387.  -- converts declinerlog into an organized history
  388.  -- more info at the top of this file
  389.  
  390. function f.history(index,offset,found,event,stamp,ye,mo,da,ho,mi,se,ostamp,fstamp,channel,guild,name,realm,result,reason)
  391.     index,offset,found=0,0,nil
  392.     if next(dlog) then
  393.         for k in next,dlog do
  394.             if type(k)~='number' then dlog[k]=nil end index=index+1
  395.             if dlog[index]==nil then
  396.                 found=true dlog[index]='nil'
  397.                 if dhist[0] and index<dhist[0] then offset=offset+1 end
  398.             end
  399.         end
  400.         if found then for i=1,#dlog do while dlog[i]=='nil' do remove(dlog,i) end end end
  401.         if offset>0 then dhist[0]=dhist[0]-offset end
  402.     end
  403.     index,found=0,nil
  404.     if next(dhist) then
  405.         for k in next,dhist do
  406.             if type(k)~='number' then dhist[k]=nil end index=index+1
  407.             if dhist[index]==nil then found=true dhist[index]='nil' end
  408.         end
  409.         if found then for i=1,#dhist do while dhist[i]=='nil' do remove(dhist,i) end end end
  410.     end
  411.     for i=dhist[0] or 1,#dlog do
  412.         if dlog[i] and dlog[i]~='nil' then
  413.             for k,v in next,d.IN do
  414.                 event=((match(dlog[i],"^"..upper(v).."%s")) or (match(dlog[i],"^"..k.."%s"))) and k or event
  415.             end
  416.             if event then
  417.                 stamp=tonumber((match(dlog[i],"%d%d%d%d%d%d%d%d%d%d%d%d"))) or stamp
  418.                 if stamp then ye,mo,da,ho,mi,se=match(stamp,"(%d%d)(%d%d)(%d%d)(%d%d)(%d%d)(%d%d)")
  419.                 ostamp=time({year=ye+2000,month=mo,day=da,hour=ho,min=mi,sec=se})
  420.                 fstamp=date("%I:%M:%S %p - %b %d %Y (%a)",ostamp) end
  421.                 channel=(match(dlog[i],"^"..event.."%s%d+%s%-%sargs:%s(.-),%s.-")) or channel
  422.                 guild=(match(dlog[i],"^"..event.."%s%d+%s%-%sargs:%s.-,%s(.-),%s.-")) or guild
  423.                 name=(match(dlog[i],"^%s%s%s%s(.-)%-.-:")) and lower((gsub((match(dlog[i],"^%s%s%s%s(.-)%-.-:")),'[%p%s]',''))) or name
  424.                 realm=(match(dlog[i],"^%s%s%s%s.-%-(.-):")) and lower((gsub((match(dlog[i],"^%s%s%s%s.-%-(.-):")),'[%p%s]',''))) or realm
  425.                 result=(match(dlog[i],"^%s%s%s%s.*:%s(allowed)%s%-%s.-$")) or (match(dlog[i],"^%s%s%s%s.*:%s(declined)")) or result
  426.                 reason=(match(dlog[i],"^%s%s%s%s.*:%sallowed%s%-%s(.-)$")) or reason
  427.                 if ostamp and fstamp and name and realm and result then
  428.                     insert(dhist,1,{event=event,ostamp=ostamp,fstamp=fstamp,name=name,realm=realm,result=result,reason=reason,
  429.                             channel=match(event,'CHANNEL') and channel or nil,guild=match(event,'GUILD') and guild or nil})
  430.                     event,stamp,ostamp,fstamp,name,realm,channel,guild,result,reason=nil,nil,nil,nil,nil,nil,nil,nil,nil,nil
  431.                 end
  432.             end
  433.         end
  434.     end
  435.     while #dlog>999 do remove(dlog,1) end
  436.     while #dhist>999 do remove(dhist) end
  437.     dhist[0]=#dlog
  438. end
  439.  
  440.  -- player link data
  441.  
  442. function f.id(action,name,data,id)
  443.     name=f.n(name) if not linktip[name] then linktip[name]={} end
  444.     if track[action][name] then id=linktip[name][1] or action..":"..time()
  445.     else id=action..":"..time() insert(linktip[name],1,id) end
  446.     if action=='tell' then
  447.         linktip[name][id]=linktip[name][id] and linktip[name][id].."\n"..data or data
  448.     else linktip[name][id]=track[action][name] or 1 end
  449.     return id
  450. end
  451.  
  452.  -- normalizes name-realm usage
  453.  
  454. function f.n(name,realm)
  455.     realm=lower((gsub(realm or (match(name,"%-(.*)")) or 'nil','[%p%s]','')))
  456.     name=lower((gsub((match(name,"(.-)%-")) or name,'[%p%s]','')))
  457.     return realm~='nil' and name..'-'..realm or name..'-'..d.fnrealm
  458. end
  459.  
  460.  -- allows all group invites after player last said certain words
  461.  -- changing zones or joining a group will cancel this effect
  462.  -- effect ends after 5 minutes since last trigger
  463.  -- being in a group will not trigger this effect
  464.  -- if a number is used as a trigger word, it only works by itself
  465.  -- (numbers have too many false positives in normal conversation and item links)
  466.  
  467. function f.openinvite(msg,sender)
  468.     if f.n(sender)~=d.fnplayer or d.var.gen.openinv or (d.ngroup and d.ngroup>0) then return end
  469.     for _,v in next,d.var.open do
  470.         if type(tonumber(v))=='number' then
  471.             if msg==v then
  472.                 f.timer('openinv') f.debug('OPENINV',v,'['..msg..']') return
  473.             end
  474.         else
  475.             for m in gmatch(msg,'%w*'..v..'%w*') do if m==tostring(v) then
  476.                 f.timer('openinv') f.debug('OPENINV',m,'['..msg..']') return
  477.             end end
  478.         end
  479.     end
  480. end
  481.  
  482.  -- reusable framepool
  483.  
  484. function f.pool(t,i)
  485.     if i then pool[i]:SetScript('OnUpdate',nil) pool[i].t=nil return end
  486.     for i=1,#pool do
  487.         if pool[i] and not pool[i].t then pool[i].t={i=i,t=t} return i end
  488.     end
  489.     insert(pool,CreateFrame('frame')) i=#pool pool[i].t={i=i,t=t} return i
  490. end
  491.  
  492.  -- chat output
  493.  -- credit to Jaliborc's Scrap for giving me the idea of using ChatFrame_MessageEventHandler
  494.  
  495. function f.print(msg,action,name,other,id,fname,frame,success)
  496.     if d.var.gen.disable or d.var.gen.msgs then return end
  497.     if action then
  498.         fname=f.n(name) if f.timer('player',action,fname)>1 then
  499.         f.debug(fname..': '..track[action][fname]..' times recently') return end
  500.         f.debug(fname..': first recent occurrence')
  501.         if d.var.msg[action] then return end
  502.         name=match(fname,'%-(.*)')==d.fnrealm and (match(name,'(.-)%-') or name) or name
  503.         msg=r..a..":|r "..format(msg,"|Hplayer:"..name..(id and ":"..id or '').."|h["..name.."]|h",other)
  504.     end
  505.     for i=1,NUM_CHAT_WINDOWS do
  506.         frame=_G['ChatFrame'..i]:IsEventRegistered('CHAT_MSG_SYSTEM') and _G['ChatFrame'..i]
  507.         if frame or (i==NUM_CHAT_WINDOWS and not success) then success=true
  508.             ChatFrame_MessageEventHandler(frame or DEFAULT_CHAT_FRAME,'CHAT_MSG_SYSTEM',msg,'','','','','',0,0,'',0,0,nil,0)
  509.         end
  510.     end
  511. end
  512.  
  513.  -- filters interface sounds
  514.  
  515. function f.silence(id,channel,nodupe,handle,_,_,_,own)
  516.     if not own then if d.SO[id] then
  517.         _,handle=PlaySound(64,'Master',false,nil,nil,nil,nil,true)
  518.         if handle then
  519.             StopSound(handle) StopSound(handle-1)
  520.             pool[f.pool({id,channel,nodupe})]:SetScript('OnUpdate',function(self)
  521.                 if not d.block then PlaySound(self.t.t[1],self.t.t[2],self.t.t[3],nil,nil,nil,nil,true) end f.pool('',self.t.i)
  522.             end)
  523.         end
  524.     else
  525.         pool[f.pool(id)]:SetScript('OnUpdate',function(self)
  526.             if d.block then f.debug('PLAYSOUND',self.t.t,SOUNDKITNAME[self.t.t] or "???") end f.pool('',self.t.i)
  527.         end)
  528.     end end
  529. end
  530.  
  531.  -- filters associated system messages if that action is declined
  532.  
  533. function f.system(_,name,msg)
  534.     if not d.var.gen.disable then for k,v in next,d.SY.filter do
  535.         name=match(msg,v)
  536.         if name and not f.allowed(name,match(k,'%a*'),true) then
  537.             return true
  538.         end
  539.     end end
  540. end
  541.  
  542.  -- timer to track openinvites/players
  543.  
  544. function f.timer(which,reason,name)
  545.     if which=='openinv' then
  546.         d.OIcount=d.OIcount and d.OIcount+1 or 1
  547.         d.OIenable=d.OIcount>0 or nil
  548.         After(300,function()
  549.             d.OIcount=d.OIcount>0 and d.OIcount-1 or 0
  550.             if d.OIcount==0 and d.OIenable then f.debug('OPENINV','closed - 5min') end
  551.             d.OIenable=d.OIenable and d.OIcount>0 or nil
  552.         end)
  553.     elseif which=='player' then
  554.         if not track[reason][name] then
  555.             track[reason][name]=1
  556.             f.timer('playerloop',reason,name)
  557.         else
  558.             track[reason][name]=track[reason][name]+1
  559.         end
  560.         return track[reason][name]
  561.     elseif which=='playerloop' then
  562.         After(60,function()
  563.             if track[reason][name]==1 then
  564.                 track[reason][name]=nil
  565.             else
  566.                 track[reason][name]=track[reason][name]-1
  567.                 f.timer('playerloop',reason,name)
  568.             end
  569.         end)
  570.     end
  571. end
  572.  
  573.  -- filters whispers according to f.allowed
  574.  
  575. function f.whisper(_,_,_,name)
  576.     if d.var.enable.tell and not d.var.gen.disable then
  577.         if not f.allowed(name,'tell',true) then
  578.             return true
  579.         end
  580.     end
  581. end
  582.  
  583. --[[
  584.     WIM workaround to prevent LibChatHandler from blocking custom CHAT_MSG_SYSTEM messages
  585.     this lib adds a filterFunc to the UI that seems to inadvertently (or purposefully)
  586.     block non-event messages, since the filterFunc blocks the original message (through CF_MEH) while
  587.     the lib processes the UI event directly to post the message itself.
  588.     the filterFunc replacement below allows IDs of 0 to be ignored, only if LibChatHandler is loaded
  589.     note: in the lib, why is arg15 used twice with no arg14? author error?
  590.     14 marks mobile chat (ChatFrame.lua:3324) but 15 isn't used? 15 and 16 aren't even passed (3009)
  591.     use vararg with unpack, much less prone to errors
  592.     i also had to account for a weird double message if system messages were accepted by more than one chatframe
  593. ]]
  594.  
  595. local missingIdIndex=10000
  596. local messagesReceived={}
  597. function f.wimhack(self,_,...)
  598.     local arg,chatframe={...},''
  599.     if self and type(self.GetName)=='function' then chatframe=self:GetName() end
  600.     if arg[11]~=0 and match(chatframe,'^ChatFrame%d+$') then
  601.         if not arg[11] then
  602.             arg[11]=missingIdIndex*-1
  603.             missingIdIndex=missingIdIndex+1
  604.         end
  605.         pool[f.pool({false,arg[11]})]:SetScript('OnUpdate',function(self)
  606.             if self.t.t[1] then messagesReceived[self.t.t[2]]=nil f.pool('',self.t.i) else self.t.t[1]=true end
  607.         end) -- delete key two frames later
  608.         messagesReceived[arg[11]]=messagesReceived[arg[11]] or {}
  609.         if messagesReceived[arg[11]][chatframe]==nil then messagesReceived[arg[11]][chatframe]=true else messagesReceived[arg[11]][chatframe]=false end
  610.         return messagesReceived[arg[11]][chatframe],unpack(arg,1,select('#',...))
  611.     end
  612.     return false,unpack(arg,1,select('#',...))
  613. end
  614.  
  615.  -- custom debug functions for supported interactions
  616.  
  617. local dbgfunc=function(...) f.debug(...) end -- default, logs all event args
  618. d.DBG={
  619.     CHANNEL_INVITE_REQUEST=dbgfunc,
  620.     DUEL_REQUESTED=dbgfunc,
  621.     GUILD_INVITE_REQUEST=dbgfunc,
  622.     PARTY_INVITE_REQUEST=dbgfunc,
  623.     PET_BATTLE_PVP_DUEL_REQUESTED=dbgfunc,
  624.     CHAT_MSG_WHISPER=function(event,msg,...)
  625.         f.debug(event,...)
  626.     end, -- drop whisper message content, log everything else
  627.     PETITION_SHOW=function(event,...)
  628.         f.debug(event,'event',...)
  629.         f.debug(event,'info',GetPetitionInfo())
  630.     end, -- petition info comes from function, not event
  631.     TRADE_SHOW=function(event,...)
  632.         f.debug(event,'event',...)
  633.         f.debug(event,'info','self-initiated: '..tostring(d.trade or false),UnitName('npc'))
  634.     end, -- trade event is empty, need player name
  635. }
  636.  
  637.  -- performs independent checks on whether a player would be allowed or declined
  638.  -- more info at the top of this file
  639.  
  640. function DeclinerAllowed(name,action,sup)
  641.     name=name and tostring(name) action=action and tostring(action)
  642.     if name and (d.def.enable[action]~=nil or not action) then
  643.         if not sup then f.debug('REQUEST',name,action) end
  644.         return f.n(name)==d.fnplayer or f.allowed(name,action,sup)
  645.     end
  646.     f.error('Usage: DeclinerAllowed( character-realm[, action] )',name,action)
  647. end
  648.  
  649.  -- queries Decliner's memory of players that would be allowed by default
  650.  -- more info at the top of this file
  651.  
  652. function DeclinerQuery(name,section)
  653.     name=name and f.n(tostring(name)) section=section and tostring(section)
  654.     if (name and (section=='whisper' or list[section])) or not section then
  655.         if section then
  656.             return section=='whisper' and track.whisper[name] or list[section][name]
  657.         elseif name then
  658.             return name==d.fnplayer or track.whisper[name] or list.group[name] or list.guild[name] or list.friend[name] or list.bnet[name]
  659.         else
  660.             local mergedlist=list mergedlist.whisper=track.whisper
  661.             return mergedlist
  662.         end
  663.     end
  664.     f.error('Usage: DeclinerQuery( [character-realm, section] )',name,section)
  665. end
  666.  
  667.  -- queries recent declined interactions either generally or per player
  668.  -- more info at the top of this file
  669.  
  670. function DeclinerTrack(action,name,players,all)
  671.     action=action and tostring(action) name=name and f.n(tostring(name))
  672.     if action=='players' or action=='all' or d.def.enable[action] or (not action and not name) then
  673.         players,all={},0
  674.         for k1,v1 in next,track do for k2,v2 in next,v1 do
  675.             players[k2]=players[k2] and players[k2]+v2 or v2 all=all+v2
  676.         end end
  677.         if action=='players' then return #players
  678.         elseif action=='all' then return name and players[name] or all
  679.         elseif name then return track[action][name]
  680.         elseif action then return #track[action]
  681.         else return track end
  682.     end
  683.     f.error('Usage: DeclinerTrack( [action, character-realm] )',action,name)
  684. end
  685.  
  686.  -- event functions
  687.  
  688. function e.ADDON_LOADED(addon)
  689.     if addon==a then dhist=declinerhistory dlog=declinerlog end
  690. end
  691.  
  692. function e.PLAYER_LOGIN()
  693.     d.player,d.realm=UnitFullName('player')
  694.     d.fnplayer=f.n(d.player,d.realm)
  695.     d.fnrealm=lower((gsub(d.realm,'[%p%s]','')))
  696.     if Decliner_SV and (Decliner_SV[d.fnplayer] or Decliner_SV.a) then
  697.         if Decliner_SV[d.fnplayer] and Decliner_SV[d.fnplayer].gen.spec then
  698.         d.var=ct(Decliner_SV[d.fnplayer]) else d.var=ct(Decliner_SV.a) end
  699.         for k1,v1 in next,d.def do for k2,v2 in next,v1 do if d.var[k1]==nil then d.var[k1]=d.def[k1]
  700.         elseif type(d.var[k1][k2])~=type(v2) and k1~='open' then d.var[k1][k2]=d.def[k1][k2] end end end
  701.         for k1,v in next,d.var do if d.def[k1]==nil then d.var[k1]=nil elseif type(v)=='table' then
  702.         for k2 in next,v do if d.def[k1][k2]==nil and k1~='open' then d.var[k1][k2]=nil end end end end
  703.     else Decliner_SV={} d.var=ct(d.def) end
  704.     f.panel() f.register()
  705.     C_ChatInfo.RegisterAddonMessagePrefix(L.pf)
  706.     DCL:RegisterEvent('BN_FRIEND_INFO_CHANGED')
  707.     DCL:RegisterEvent('CHAT_MSG_ADDON')
  708.     DCL:RegisterEvent('CHAT_MSG_SYSTEM')
  709.     DCL:RegisterEvent('CHAT_MSG_WHISPER_INFORM')
  710.     DCL:RegisterEvent('FRIENDLIST_UPDATE')
  711.     DCL:RegisterEvent('GROUP_ROSTER_UPDATE')
  712.     DCL:RegisterEvent('GUILD_ROSTER_UPDATE')
  713.     DCL:RegisterEvent('UI_INFO_MESSAGE')
  714.     DCL:UnregisterEvent('PLAYER_LOGIN')
  715.     hooksecurefunc('PlaySound',f.silence)
  716.     ChatFrame_AddMessageEventFilter('CHAT_MSG_SYSTEM',f.system)
  717.     ChatFrame_AddMessageEventFilter('CHAT_MSG_WHISPER',f.whisper)
  718.     InterfaceOptionsFrame:HookScript('OnHide',function()
  719.         p.OIbutton2:UnlockHighlight() p.OIdrop:Hide()
  720.         p.OIbutton2:SetNormalFontObject('GameFontNormalSmall')
  721.         if d.cancel then f.options('c') f.register() end -- stupid taints
  722.     end)
  723.     for k1,v1 in next,_G do if type(v1)=='table' then for k2,v2 in next,v1 do
  724.     if (k2=='Pour' or k2=='ShouldDisplayMessageType') and type(v2)=='function' then
  725.     info[k1]=v2 _G[k1][k2]=function(...) local _,d1,d2=... if (d.UI[d1] or d.UI[d2]) and d.uiblock then
  726.     return false else return info[k1](...) end end end end end end -- directly filter uiinfomsgs
  727.     for i=1,NUM_CHAT_WINDOWS do local frame=_G['ChatFrame'..i] frame:HookScript('OnHyperlinkEnter',function(self,refstr,...)
  728.     local type,name,action,id=split(':',refstr) if type=='player' and name and action and id then name=f.n(name)
  729.     if linktip[name] and linktip[name][action..":"..id] then GameTooltip_SetDefaultAnchor(GameTooltip,self)
  730.     GameTooltip:SetText(linktip[name][action..":"..id]) end end end)
  731.     frame:HookScript('OnHyperlinkLeave',function() GameTooltip:Hide() end) end -- player link tooltip magic
  732.     local sysfilters=ChatFrame_GetMessageEventFilters('CHAT_MSG_SYSTEM') for _,v in next,sysfilters do
  733.     local _,_,_,_,_,_,_,_,_,_,_,arg11=v(DEFAULT_CHAT_FRAME,'CHAT_MSG_SYSTEM','','','','','','',0,0,'',0)
  734.     if type(arg11)=='number' and arg11<-9999 then ChatFrame_RemoveMessageEventFilter('CHAT_MSG_SYSTEM',v)
  735.     ChatFrame_AddMessageEventFilter('CHAT_MSG_SYSTEM',f.wimhack) end end -- WIM hack
  736.     DEFAULT_CHAT_FRAME:AddMessage(r..a..":|r "..d.S.VER.." "..GetAddOnMetadata(a,'Version').." "..d.S.REA,1,1,0)
  737. end
  738.  
  739. function e.PLAYER_LOGOUT()
  740.     if d.var.gen.spec then Decliner_SV[d.fnplayer]=ct(d.var) else Decliner_SV.a=ct(d.var) end
  741.     if not Decliner_SV.a then Decliner_SV.a=ct(d.def) end Decliner_SV[d.player.."-"..d.realm]=nil
  742. end
  743.  
  744. function e.LOADING_SCREEN_DISABLED()
  745.     GuildRoster() ShowFriends() e.BN_FRIEND_INFO_CHANGED() e.GROUP_ROSTER_UPDATE()
  746. end -- force refresh guild, friends, bnet, and group on login and load screens
  747.  
  748. function e.BN_FRIEND_INFO_CHANGED(id,online,name,client,realm)
  749.     d.tbnet,online=BNGetNumFriends()
  750.     if id then
  751.         for j=1,(C_BattleNet.GetFriendNumGameAccounts(id)) do
  752.             _,name,client,realm=C_BattleNet.GetFriendGameAccountInfo(id,j)
  753.             if client==d.S.WOW and name then list.bnet[f.n(name,realm)]=true end
  754.         end
  755.     elseif d.obnet~=online then
  756.         d.obnet=online wipe(list.bnet)
  757.         for i=1,d.tbnet do for j=1,(C_BattleNet.GetFriendNumGameAccounts(i)) do
  758.             _,name,client,realm=C_BattleNet.GetFriendGameAccountInfo(i,j)
  759.             if client==d.S.WOW and name then list.bnet[f.n(name,realm)]=true end
  760.         end end
  761.     end
  762. end
  763.  
  764. function e.CHANNEL_INVITE_REQUEST(chan,name)
  765.     if not f.allowed(name,'chan') then
  766.         d.block=true
  767.         DeclineChannelInvite(chan)
  768.         StaticPopup_Hide('CHAT_CHANNEL_INVITE')
  769.         f.print(L.DECLINED_CHAN,'chan',name,chan,f.id('chan',name))
  770.     end
  771. end
  772.  
  773. --[[ begin CHAT_MSG_ADDON
  774.  
  775.     transparency note: Due to scammers using certain scripts, I
  776.     feel the need to explain the following piece of code. For
  777.     debugging purposes, this CHAT_MSG_ADDON usage allows me to
  778.     "download" a user's log ingame if I can whisper them. My
  779.     standard debugging method is for a user to upload their
  780.     Decliner.lua in a ticket on CurseForge, however this code
  781.     makes it extremely convenient to view that log. So far, I
  782.     have only used it with a willing tester on working versions
  783.     of Decliner, but it may be useful in the future for a
  784.     guildie, another friend, or anyone that I may meet. This can
  785.     only give me what is shown in declinerlog and nothing else.
  786.     You can view the first 30 lines using /dump declinerlog
  787.     or all of it in the Decliner.lua saved file found at
  788.     WTF\Account\<account name or id>\SavedVariables\Decliner.lua
  789.  
  790. ]]
  791.  
  792. function e.CHAT_MSG_ADDON(prefix,msg,channel,sender,msg1,msg2,lines)
  793.     if not (prefix==L.pf and channel==L.ch and match(msg,'^'..a..'%d+:%d+$')) then return end
  794.     msg=gsub(msg,a,'') msg1,msg2=split(':',msg)
  795.     msg1=tonumber(msg1)>#dlog and #dlog or tonumber(msg1)
  796.     msg2=tonumber(msg2)>#dlog and #dlog or tonumber(msg2)
  797.     lines=msg2-msg1+1 C_ChatInfo.SendAddonMessage(prefix,lines..':'..#dlog,channel,sender)
  798.     if #dlog<1 then return
  799.     elseif ChatThrottleLib then
  800.         for i=msg1,msg2 do
  801.             ChatThrottleLib:SendAddonMessage('BULK',prefix,sub(dlog[i],1,255),channel,sender)
  802.         end
  803.     else
  804.         Ticker(0.05,function()
  805.             C_ChatInfo.SendAddonMessage(prefix,sub(dlog[msg1],1,255),channel,sender) msg1=msg1+1
  806.         end,lines)
  807.     end
  808. end
  809.  
  810. --[[ end CHAT_MSG_ADDON ]]
  811.  
  812. function e.CHAT_MSG_CHANNEL(msg,sender,_,_,_,_,id)
  813.     if d.var.chan[id] then f.openinvite(msg,sender) end
  814. end
  815.  
  816. function e.CHAT_MSG_EMOTE(msg,sender)
  817.     if d.var.chan.emote then f.openinvite(msg,sender) end
  818. end
  819.  
  820. function e.CHAT_MSG_SAY(msg,sender)
  821.     if d.var.chan.say then f.openinvite(msg,sender) end
  822. end
  823.  
  824. function e.CHAT_MSG_SYSTEM(msg)
  825.     if match(msg,d.SY.trade) then
  826.         d.trade=(d.var.enable.trade and not d.var.gen.disable) and true or nil return
  827.     else for k,v in next,d.SY.filter do
  828.         local name=match(msg,v)
  829.         if name then if k=='group2' and not f.allowed(name,'group',true) then
  830.             f.print(L.DECLINED_GROUP,'group',name)
  831.         end return end
  832.     end end
  833.     pool[f.pool(msg)]:SetScript('OnUpdate',function(self)
  834.         if d.block then f.debug('CHAT_MSG_SYSTEM',self.t.t) end f.pool('',self.t.i)
  835.     end)
  836. end
  837.  
  838. function e.CHAT_MSG_WHISPER(msg,name)
  839.     if not f.allowed(name,'tell') then
  840.         d.block=true
  841.         f.print(L.DECLINED_TELL,'tell',name,nil,f.id('tell',name,msg))
  842.     end
  843. end
  844.  
  845. function e.CHAT_MSG_WHISPER_INFORM(_,target)
  846.     f.timer('player','whisper',f.n(target))
  847. end
  848.  
  849. function e.CHAT_MSG_YELL(msg,sender)
  850.     if d.var.chan.yell then f.openinvite(msg,sender) end
  851. end
  852.  
  853. function e.CVAR_UPDATE(cvar,value)
  854.     value=ValueToBoolean(value)
  855.     if cvar=='BLOCK_TRADES' then
  856.         if value and d.var.enable.trade then
  857.             d.var.enable.trade=false
  858.             f.print(L.X_BLOCK_TRADES_ON)
  859.         elseif not value and not d.var.enable.trade then
  860.             d.var.enable.trade=true
  861.             f.print(L.X_BLOCK_TRADES_OFF)
  862.         end
  863.         f.register() f.blizzardoptions()
  864.         p.enabletrade:SetChecked(d.var.enable.trade)
  865.     elseif cvar=='BLOCK_CHAT_CHANNEL_INVITE' then
  866.         if value and d.var.enable.chan then
  867.             d.var.enable.chan=false
  868.             f.print(L.X_BLOCK_CHANNELS_ON)
  869.         elseif not value and not d.var.enable.chan then
  870.             d.var.enable.chan=true
  871.             f.print(L.X_BLOCK_CHANNELS_OFF)
  872.         end
  873.         f.register() f.blizzardoptions()
  874.         p.enablechan:SetChecked(d.var.enable.chan)
  875.     end
  876. end
  877.  
  878. function e.DISABLE_DECLINE_GUILD_INVITE()
  879.     if d.var.enable.guild then return end
  880.     d.var.enable.guild=true
  881.     f.print(L.X_BLOCK_GUILDS_OFF)
  882.     f.register() f.blizzardoptions()
  883.     p.enableguild:SetChecked(d.var.enable.guild)
  884. end
  885.  
  886. function e.DUEL_REQUESTED(name)
  887.     if not f.allowed(name,'duel') then
  888.         d.block=true d.uiblock=true
  889.         CancelDuel()
  890.         StaticPopup_Hide('DUEL_REQUESTED')
  891.         f.print(L.DECLINED_DUEL,'duel',name,nil,f.id('duel',name))
  892.     end
  893. end
  894.  
  895. function e.ENABLE_DECLINE_GUILD_INVITE()
  896.     if not d.var.enable.guild then return end
  897.     d.var.enable.guild=false
  898.     f.print(L.X_BLOCK_GUILDS_ON)
  899.     f.register() f.blizzardoptions()
  900.     p.enableguild:SetChecked(d.var.enable.guild)
  901. end
  902.  
  903. function e.FRIENDLIST_UPDATE(online,name)
  904.     d.tfriend,online=C_FriendList.GetNumFriends()
  905.     if d.ofriend~=online then
  906.         d.ofriend=online wipe(list.friend)
  907.         for i=1,d.tfriend do
  908.             name,_,_,_,online=GetFriendInfo(i)
  909.             if name and online then list.friend[f.n(name)]=true end
  910.             if #list.friend==d.ofriend then return end
  911.         end
  912.     end
  913. end
  914.  
  915. function e.GROUP_ROSTER_UPDATE(num,which,name,realm)
  916.     d.ngroup=GetNumGroupMembers()
  917.     if d.ngroup==0 then wipe(list.group) return end
  918.     if d.OIenable then d.OIenable=nil f.debug('OPENINV','closed - joined group') end
  919.     if #list.group~=d.ngroup then
  920.         wipe(list.group)
  921.         if IsInRaid() then num,which=40,'raid' else num,which=4,'party' end
  922.         for i=1,num do
  923.             name,realm=UnitName(which..i)
  924.             if name then list.group[f.n(name,realm)]=true end
  925.             if #list.group==d.ngroup then return end
  926.         end
  927.     end
  928. end
  929.  
  930. function e.GUILD_INVITE_REQUEST(name,guild)
  931.     if not f.allowed(name,'guild') then
  932.         d.block=true
  933.         pool[f.pool((GetCVar('Sound_EnableSFX')))]:SetScript('OnUpdate',function(self)
  934.             SetCVar('Sound_EnableSFX',self.t.t) f.pool('',self.t.i)
  935.         end)
  936.         SetCVar('Sound_EnableSFX',0)
  937.         DeclineGuild()
  938.         StaticPopupSpecial_Hide(GuildInviteFrame)
  939.         f.print(L.DECLINED_GUILD,'guild',name,guild,f.id('guild',name))
  940.     end
  941. end
  942.  
  943. function e.GUILD_ROSTER_UPDATE(online,name)
  944.     d.tguild,online=GetNumGuildMembers()
  945.     if d.oguild~=online then
  946.         d.oguild=online wipe(list.guild)
  947.         for i=1,d.tguild do
  948.             name,_,_,_,_,_,_,_,online=GetGuildRosterInfo(i)
  949.             if name and online then list.guild[f.n(name)]=true end
  950.             if #list.guild==d.oguild then return end
  951.         end
  952.     end
  953. end
  954.  
  955. function e.PARTY_INVITE_REQUEST(name)
  956.     if d.OIenable then f.debug(name..': allowed - open invites') return end
  957.     if not f.allowed(name,'group') then
  958.         d.block=true
  959.         DeclineGroup()
  960.         StaticPopup_Hide('PARTY_INVITE')
  961.         StaticPopupSpecial_Hide(LFGInvitePopup)
  962.         f.print(L.DECLINED_GROUP,'group',name,nil,f.id('group',name))
  963.     end
  964. end
  965.  
  966. function e.PET_BATTLE_PVP_DUEL_REQUESTED(name)
  967.     if not f.allowed(name,'pet') then
  968.         d.block=true
  969.         CancelPetDuel()
  970.         StaticPopup_Hide('PET_BATTLE_PVP_DUEL_REQUESTED')
  971.         f.print(L.DECLINED_PET,'pet',name,nil,f.id('pet',name))
  972.     end
  973. end
  974.  
  975. function e.PETITION_SHOW(ptype,name,originator)
  976.     ptype,_,_,_,name,originator=GetPetitionInfo()
  977.     if originator then return end
  978.     if not f.allowed(name,'chart') then
  979.         d.block=true
  980.         ClosePetition()
  981.         HideUIPanel(PetitionFrame)
  982.         if ptype=='guild' then f.print(L.DECLINED_CHART_GUILD,'chart',name)
  983.         else f.print(L.DECLINED_CHART_NIL,'chart',name,nil,f.id('chart',name)) end
  984.     end
  985. end
  986.  
  987. function e.TRADE_SHOW(name,realm)
  988.      -- thank you to http://wowprogramming.com/docs/api_types#unitID for that vague Trade UI note next to "npc"
  989.     if d.trade then d.trade=nil return end name,realm=UnitName('npc')
  990.     name=realm and name.."-"..realm or name.."-"..d.realm
  991.     if not f.allowed(name,'trade') then
  992.         d.block=true d.uiblock=true
  993.         CloseTrade()
  994.         HideUIPanel(TradeFrame)
  995.         f.print(L.DECLINED_TRADE,'trade',name,nil,f.id('trade',name))
  996.     end
  997. end
  998.  
  999. function e.UI_INFO_MESSAGE(id,msg,...)
  1000.     if d.uiblock and not d.UI[msg] then f.debug('UI_INFO_MESSAGE',id,msg,...) end d.uiblock=nil
  1001. end
  1002.  
  1003. function e.ZONE_CHANGED_NEW_AREA()
  1004.     if d.OIenable then d.OIenable=nil f.debug('OPENINV','closed - new zone') end
  1005. end
  1006.  
  1007.  -- calls an event function when that event triggers
  1008.  
  1009. DCL:SetScript('OnEvent',function(_,event,...)
  1010.     if d.IN[event] then
  1011.         d.DBG[event](event,...) d.ngroup=GetNumGroupMembers() if d.ngroup==0 then wipe(list.group) end
  1012.         pool[f.pool()]:SetScript('OnUpdate',function(self)
  1013.             if self.t.t then d.block=nil f.pool('',self.t.i) else self.t.t=true f.history() end
  1014.         end)
  1015.     end
  1016.     e[event](...)
  1017. end)
  1018.  
  1019.  -- disables the block trades, block guild invites, and block channel invites options
  1020.  -- while editing their display text to reflect the presence of Decliner
  1021.  -- Decliner won't be able to let authorized players through if these options are enabled
  1022.  
  1023. function f.blizzardoptions()
  1024.     if d.var.gen.disable or not d.var.enable.trade then
  1025.         InterfaceOptionsSocialPanelBlockTradesText:SetFontObject('GameFontHighlightLeft')
  1026.         InterfaceOptionsSocialPanelBlockTradesText:SetText(BLOCK_TRADES)
  1027.     else
  1028.         InterfaceOptionsSocialPanelBlockTradesText:SetFontObject('GameFontDisableLeft')
  1029.         InterfaceOptionsSocialPanelBlockTradesText:SetText(BLOCK_TRADES.." ("..a..")")
  1030.         if InterfaceOptionsSocialPanelBlockTrades:GetChecked() then InterfaceOptionsSocialPanelBlockTrades:Click() end
  1031.     end
  1032.     if d.var.gen.disable or not d.var.enable.guild then
  1033.         InterfaceOptionsSocialPanelBlockGuildInvitesText:SetFontObject('GameFontHighlightLeft')
  1034.         InterfaceOptionsSocialPanelBlockGuildInvitesText:SetText(BLOCK_GUILD_INVITES)
  1035.     else
  1036.         InterfaceOptionsSocialPanelBlockGuildInvitesText:SetFontObject('GameFontDisableLeft')
  1037.         InterfaceOptionsSocialPanelBlockGuildInvitesText:SetText(BLOCK_GUILD_INVITES.." ("..a..")")
  1038.         if InterfaceOptionsSocialPanelBlockGuildInvites:GetChecked() then InterfaceOptionsSocialPanelBlockGuildInvites:Click() end
  1039.     end
  1040.     if d.var.gen.disable or not d.var.enable.chan then
  1041.         InterfaceOptionsSocialPanelBlockChatChannelInvitesText:SetFontObject('GameFontHighlightLeft')
  1042.         InterfaceOptionsSocialPanelBlockChatChannelInvitesText:SetText(BLOCK_CHAT_CHANNEL_INVITE)
  1043.     else
  1044.         InterfaceOptionsSocialPanelBlockChatChannelInvitesText:SetFontObject('GameFontDisableLeft')
  1045.         InterfaceOptionsSocialPanelBlockChatChannelInvitesText:SetText(BLOCK_CHAT_CHANNEL_INVITE.." ("..a..")")
  1046.         if InterfaceOptionsSocialPanelBlockChatChannelInvites:GetChecked() then InterfaceOptionsSocialPanelBlockChatChannelInvites:Click() end
  1047.     end
  1048. end
  1049.  
  1050.  -- event registration and option panel changes
  1051.  
  1052. function f.register()
  1053.     if d.var.gen.disable then
  1054.         DCL:UnregisterEvent('CVAR_UPDATE')
  1055.         DCL:UnregisterEvent('DISABLE_DECLINE_GUILD_INVITE')
  1056.         DCL:UnregisterEvent('ENABLE_DECLINE_GUILD_INVITE')
  1057.         for k in next,d.IN do DCL:UnregisterEvent(k) end
  1058.         p.header01:SetFontObject('GameFontDisableLarge')
  1059.         for k1,v1 in next,d.def do
  1060.             if k1~='chan' and k1~='open' then
  1061.                 if k1~='gen' then p[k1..'text']:SetFontObject('GameFontDisableTiny') end
  1062.                 for k2 in next,v1 do
  1063.                     if k2~='disable' and (k1~=k2 or k2=='tell') then p[k1..k2]:Disable() end
  1064.                     if k1=='enable' then
  1065.                         p[k2..'text01']:SetFontObject('GameFontDisableTiny')
  1066.                         p[k2..'text02']:SetFontObject('GameFontDisableTiny')
  1067.                     end
  1068.                 end
  1069.             end
  1070.         end
  1071.         _G[p.genspec.text]:SetFontObject('GameFontDisableSmall')
  1072.         _G[p.genopeninv.text]:SetFontObject('GameFontDisableSmall')
  1073.         _G[p.genmsgs.text]:SetFontObject('GameFontDisableSmall')
  1074.         _G[p.gendisable.text]:SetFontObject('GameFontNormalLarge')
  1075.     else
  1076.         DCL:RegisterEvent('CVAR_UPDATE')
  1077.         DCL:RegisterEvent('DISABLE_DECLINE_GUILD_INVITE')
  1078.         DCL:RegisterEvent('ENABLE_DECLINE_GUILD_INVITE')
  1079.         for k,v in next,d.IN do if d.var.enable[v] then DCL:RegisterEvent(k) else DCL:UnregisterEvent(k) end end
  1080.         p.header01:SetFontObject('GameFontNormalLarge')
  1081.         for k1,v1 in next,d.def do
  1082.             if k1~='chan' and k1~='open' then
  1083.                 if k1~='gen' then
  1084.                     p[k1..'text']:SetFontObject((k1=='msg' and d.var.gen.msgs) and 'GameFontDisableTiny' or 'GameFontWhiteTiny')
  1085.                 end
  1086.                 for k2 in next,v1 do
  1087.                     if ((k1~='enable' and d.var.enable[k2]==false) or (k1=='msg' and d.var.gen.msgs)) and (k1~=k2 or k2=='tell') then
  1088.                     p[k1..k2]:Disable() else p[k1..k2]:Enable() end
  1089.                     if k1=='enable' then
  1090.                         p[k2..'text01']:SetFontObject(d.var[k1][k2] and 'GameFontWhiteTiny' or 'GameFontDisableTiny')
  1091.                         p[k2..'text02']:SetFontObject(d.var[k1][k2] and 'GameFontWhiteTiny' or 'GameFontDisableTiny')
  1092.                     end
  1093.                 end
  1094.             end
  1095.         end
  1096.         _G[p.genspec.text]:SetFontObject('GameFontHighlightSmall')
  1097.         _G[p.genopeninv.text]:SetFontObject('GameFontHighlightSmall')
  1098.         _G[p.genmsgs.text]:SetFontObject('GameFontHighlightSmall')
  1099.         _G[p.gendisable.text]:SetFontObject('GameFontHighlightSmall')
  1100.     end
  1101.     if d.var.gen.disable or d.var.gen.openinv then
  1102.         d.OIenable=nil
  1103.         p.OIdrop:Hide()
  1104.         p.OIlist:Disable()
  1105.         p.OIinput:Disable()
  1106.         p.OIbutton1:Disable()
  1107.         p.OIbutton2:Disable()
  1108.         p.OIbutton2:UnlockHighlight()
  1109.         DCL:UnregisterEvent('CHAT_MSG_CHANNEL')
  1110.         DCL:UnregisterEvent('CHAT_MSG_EMOTE')
  1111.         DCL:UnregisterEvent('CHAT_MSG_SAY')
  1112.         DCL:UnregisterEvent('CHAT_MSG_YELL')
  1113.         DCL:UnregisterEvent('ZONE_CHANGED_NEW_AREA')
  1114.         p.OIlist:SetFontObject('GameFontDisableSmall')
  1115.         p.OIinput:SetFontObject('GameFontDisableSmall')
  1116.         p.OIbutton2:SetNormalFontObject('GameFontNormalSmall')
  1117.     else
  1118.         d.OIenable=d.OIcount and d.OIcount>0 or nil
  1119.         p.OIlist:Enable()
  1120.         p.OIinput:Enable()
  1121.         p.OIbutton1:Enable()
  1122.         p.OIbutton2:Enable()
  1123.         DCL:RegisterEvent('CHAT_MSG_CHANNEL')
  1124.         DCL:RegisterEvent('CHAT_MSG_EMOTE')
  1125.         DCL:RegisterEvent('CHAT_MSG_SAY')
  1126.         DCL:RegisterEvent('CHAT_MSG_YELL')
  1127.         DCL:RegisterEvent('ZONE_CHANGED_NEW_AREA')
  1128.         p.OIlist:SetFontObject('GameFontHighlightSmall')
  1129.         p.OIinput:SetFontObject('GameFontHighlightSmall')
  1130.     end
  1131. end
  1132.  
  1133.  -- Interface Options panel
  1134.  -- I would like to thank FlexYourHead of the addon Work_Complete for a ton of comments in their code
  1135.  -- It made understanding buttons, text, and the options frame a whole lot easier
  1136.  
  1137. function f.create(obj,arg1,arg2,arg3,arg4,arg5,arg6,arg7,n,supported)
  1138.     if obj=='button' then
  1139.         d.btn=d.btn and d.btn+1 or 1
  1140.         n=a..'Button'..format('%02i',d.btn)
  1141.         if arg7 then
  1142.             obj=CreateFrame('CheckButton',n,p.OIdrop,'UIRadioButtonTemplate')
  1143.             obj:SetPoint('TOPLEFT',p.OIdrop,'TOPLEFT',arg1,arg2)
  1144.         else
  1145.             obj=CreateFrame('CheckButton',n,DCLPanel,'OptionsCheckButtonTemplate')
  1146.             obj.tooltipText=arg3 arg7=arg6 and -2 or -8
  1147.             obj:SetCheckedTexture(d.file[arg4])
  1148.             obj:SetDisabledCheckedTexture(d.file[arg4])
  1149.             obj:GetCheckedTexture():SetVertexColor(0.8,0.8,0.8)
  1150.             obj:SetPoint('CENTER',DCLPanel,'TOPRIGHT',arg1,arg2)
  1151.             obj:GetDisabledCheckedTexture():SetVertexColor(0.4,0.4,0.4)
  1152.             supported=obj:GetDisabledCheckedTexture():SetDesaturated(true)
  1153.             if not supported then obj:GetDisabledCheckedTexture():SetAlpha(0.4) end
  1154.         end
  1155.         obj.GetValue=function()end obj.SetValue=function()end -- weird BfA error (why don't these templates have these methods anymore?)
  1156.         obj.text=n..'Text' _G[obj.text]:SetText(arg5) obj.width=_G[obj.text]:GetWidth()
  1157.         obj:SetHitRectInsets(arg7,arg6 and (-1*obj.width)-8 or arg7,arg7,arg7)
  1158.     else
  1159.         d.obj=d.obj and d.obj+1 or 1
  1160.         n=a..'Object'..format('%02i',d.obj)
  1161.         if obj=='line' then
  1162.             obj=DCLPanel:CreateTexture(n)
  1163.             obj:SetSize(600,1)
  1164.             obj:SetColorTexture(arg3,arg3,arg3)
  1165.             obj:SetPoint('LEFT',DCLPanel,'TOPLEFT',arg1,arg2)
  1166.         elseif obj=='text' then
  1167.             obj=DCLPanel:CreateFontString(n,'ARTWORK','GameFontNormal')
  1168.             obj:SetPoint(arg4,DCLPanel,arg5 and 'TOPLEFT' or 'TOPRIGHT',arg1,arg2)
  1169.             obj:SetText(arg3)
  1170.         end
  1171.     end
  1172.     return obj
  1173. end
  1174.  
  1175. function f.options(a)
  1176.     if a=='c' then d.var=ct(d.tmp) elseif a=='d' then d.var=ct(d.def) end
  1177.     if a=='r' then
  1178.         for k1,v in next,d.def do for k2 in next,v do
  1179.             if k1~='open' then p[k1..k2]:SetChecked(d.var[k1][k2]) end
  1180.         end end
  1181.         p.groupgroup.soundname=nil p.guildguild.soundname=nil
  1182.         f.setOIlist() f.blizzardoptions() if not d.tmp then d.tmp=ct(d.var) end
  1183.     elseif a~='d' then d.tmp=nil end
  1184. end
  1185.  
  1186. function f.randomsound(self,k,x)
  1187.     if self[k..'s'] then StopSound(self[k..'s']) end
  1188.     x=random(1,999999)
  1189.     self[k..'p'],self[k..'s']=PlaySound(x,'Master',false,nil,nil,nil,nil,true)
  1190.     if self[k..'p'] then
  1191.         self:SetCheckedTexture(d.file.ha)
  1192.         self.soundname=SOUNDKITNAME[x] or "???"
  1193.         self.soundid="  ID: "..x
  1194.         GameTooltip:SetText(self.soundname,nil,nil,nil,nil,false)
  1195.         GameTooltip:AddLine(self.soundid,nil,nil,nil,false)
  1196.         GameTooltip:Show()
  1197.     elseif self[k..'l']<999 then
  1198.         self[k..'l']=self[k..'l']+1
  1199.         f.randomsound(self,k)
  1200.     else
  1201.         self:SetCheckedTexture(d.file.sa)
  1202.         self.soundname=d.S.NOS
  1203.         self.soundid=nil
  1204.         GameTooltip:SetText(self.soundname,nil,nil,nil,nil,false)
  1205.     end
  1206. end
  1207.  
  1208. function f.setOIlist(text)
  1209.     sort(d.var.open)
  1210.     if d.var.open[1] then for _,v in next,d.var.open do
  1211.         text=text and text.."\n"..tostring(v) or tostring(v)
  1212.     end end
  1213.     p.OIlist:SetText(text or '')
  1214. end
  1215.  
  1216. function f.panel()
  1217.     DCLPanel.name=a
  1218.     InterfaceOptions_AddCategory(DCLPanel,a)
  1219.     p.header01=f.create('text',16,-16,a,'TOPLEFT',1)
  1220.     p.genspec=f.create('button',-265,-22,format(L.X_CHAR_SETTINGS,d.player.."-"..d.realm),'ch',d.S.CSS,1)
  1221.     p.line01=f.create('line',10,-40,.25)
  1222.     p.chantext01=f.create('text',-482,-64,d.S.CHA,'CENTER')
  1223.     p.chantext02=f.create('text',-482,-80,d.S.INV,'CENTER')
  1224.     p.dueltext01=f.create('text',-420,-64,d.S.DUE,'CENTER')
  1225.     p.dueltext02=f.create('text',-420,-80,d.S.REQ,'CENTER')
  1226.     p.pettext01=f.create('text',-358,-64,d.S.PET,'CENTER')
  1227.     p.pettext02=f.create('text',-358,-80,d.S.REQ,'CENTER')
  1228.     p.grouptext01=f.create('text',-296,-64,d.S.GRO,'CENTER')
  1229.     p.grouptext02=f.create('text',-296,-80,d.S.INV,'CENTER')
  1230.     p.guildtext01=f.create('text',-234,-64,d.S.GUI,'CENTER')
  1231.     p.guildtext02=f.create('text',-234,-80,d.S.INV,'CENTER')
  1232.     p.charttext01=f.create('text',-172,-64,d.S.GUI,'CENTER')
  1233.     p.charttext02=f.create('text',-172,-80,d.S.PTI,'CENTER')
  1234.     p.telltext01=f.create('text',-110,-64,d.S.PLA,'CENTER')
  1235.     p.telltext02=f.create('text',-110,-80,d.S.WHS,'CENTER')
  1236.     p.tradetext01=f.create('text',-48,-64,d.S.TRA,'CENTER')
  1237.     p.tradetext02=f.create('text',-48,-80,d.S.REQ,'CENTER')
  1238.     p.enabletext=f.create('text',-512,-112,d.S.ENA,'RIGHT')
  1239.     p.telltext=f.create('text',-512,-160,d.S.WHD,'RIGHT')
  1240.     p.grouptext=f.create('text',-512,-208,d.S.GRM,'RIGHT')
  1241.     p.guildtext=f.create('text',-512,-256,d.S.GUM,'RIGHT')
  1242.     p.friendtext=f.create('text',-512,-304,d.S.REF,'RIGHT')
  1243.     p.bnettext=f.create('text',-512,-352,d.S.BAT,'RIGHT')
  1244.     p.msgtext=f.create('text',-512,-400,d.S.CAL,'RIGHT')
  1245.     p.enablechan=f.create('button',-482,-112,g..L.ENABLE_CHAN.."|r",'ch')
  1246.     p.enableduel=f.create('button',-420,-112,g..L.ENABLE_DUEL.."|r",'ch')
  1247.     p.enablepet=f.create('button',-358,-112,g..L.ENABLE_PET.."|r",'ch')
  1248.     p.enablegroup=f.create('button',-296,-112,g..L.ENABLE_GROUP.."|r",'ch')
  1249.     p.enableguild=f.create('button',-234,-112,g..L.ENABLE_GUILD.."|r",'ch')
  1250.     p.enablechart=f.create('button',-172,-112,g..L.ENABLE_CHART.."|r",'ch')
  1251.     p.enabletell=f.create('button',-110,-112,g..L.ENABLE_TELL.."|r",'ch')
  1252.     p.enabletrade=f.create('button',-48,-112,g..L.ENABLE_TRADE.."|r",'ch')
  1253.     p.tellchan=f.create('button',-482,-160,r..L.TELL_CHAN.."|r",'cr')
  1254.     p.tellduel=f.create('button',-420,-160,r..L.TELL_DUEL.."|r",'cr')
  1255.     p.tellpet=f.create('button',-358,-160,r..L.TELL_PET.."|r",'cr')
  1256.     p.tellgroup=f.create('button',-296,-160,r..L.TELL_GROUP.."|r",'cr')
  1257.     p.tellguild=f.create('button',-234,-160,r..L.TELL_GUILD.."|r",'cr')
  1258.     p.tellchart=f.create('button',-172,-160,r..L.TELL_CHART.."|r",'cr')
  1259.     p.telltell=f.create('button',-110,-160,r..L.TELL_TELL.."|r",'cr')
  1260.     p.telltrade=f.create('button',-48,-160,r..L.TELL_TRADE.."|r",'cr')
  1261.     p.groupchan=f.create('button',-482,-208,r..L.GROUP_CHAN.."|r",'cr')
  1262.     p.groupduel=f.create('button',-420,-208,r..L.GROUP_DUEL.."|r",'cr')
  1263.     p.grouppet=f.create('button',-358,-208,r..L.GROUP_PET.."|r",'cr')
  1264.     p.groupgroup=f.create('button',-296,-208,r..L.GROUP_GROUP.."|r",'cr')
  1265.     p.groupguild=f.create('button',-234,-208,r..L.GROUP_GUILD.."|r",'cr')
  1266.     p.groupchart=f.create('button',-172,-208,r..L.GROUP_CHART.."|r",'cr')
  1267.     p.grouptell=f.create('button',-110,-208,r..L.GROUP_TELL.."|r",'cr')
  1268.     p.grouptrade=f.create('button',-48,-208,r..L.GROUP_TRADE.."|r",'cr')
  1269.     p.guildchan=f.create('button',-482,-256,r..L.GUILD_CHAN.."|r",'cr')
  1270.     p.guildduel=f.create('button',-420,-256,r..L.GUILD_DUEL.."|r",'cr')
  1271.     p.guildpet=f.create('button',-358,-256,r..L.GUILD_PET.."|r",'cr')
  1272.     p.guildgroup=f.create('button',-296,-256,r..L.GUILD_GROUP.."|r",'cr')
  1273.     p.guildguild=f.create('button',-234,-256,r..L.GUILD_GUILD.."|r",'cr')
  1274.     p.guildchart=f.create('button',-172,-256,r..L.GUILD_CHART.."|r",'cr')
  1275.     p.guildtell=f.create('button',-110,-256,r..L.GUILD_TELL.."|r",'cr')
  1276.     p.guildtrade=f.create('button',-48,-256,r..L.GUILD_TRADE.."|r",'cr')
  1277.     p.friendchan=f.create('button',-482,-304,r..L.FRIEND_CHAN.."|r",'cr')
  1278.     p.friendduel=f.create('button',-420,-304,r..L.FRIEND_DUEL.."|r",'cr')
  1279.     p.friendpet=f.create('button',-358,-304,r..L.FRIEND_PET.."|r",'cr')
  1280.     p.friendgroup=f.create('button',-296,-304,r..L.FRIEND_GROUP.."|r",'cr')
  1281.     p.friendguild=f.create('button',-234,-304,r..L.FRIEND_GUILD.."|r",'cr')
  1282.     p.friendchart=f.create('button',-172,-304,r..L.FRIEND_CHART.."|r",'cr')
  1283.     p.friendtell=f.create('button',-110,-304,r..L.FRIEND_TELL.."|r",'cr')
  1284.     p.friendtrade=f.create('button',-48,-304,r..L.FRIEND_TRADE.."|r",'cr')
  1285.     p.bnetchan=f.create('button',-482,-352,r..L.BNET_CHAN.."|r",'cr')
  1286.     p.bnetduel=f.create('button',-420,-352,r..L.BNET_DUEL.."|r",'cr')
  1287.     p.bnetpet=f.create('button',-358,-352,r..L.BNET_PET.."|r",'cr')
  1288.     p.bnetgroup=f.create('button',-296,-352,r..L.BNET_GROUP.."|r",'cr')
  1289.     p.bnetguild=f.create('button',-234,-352,r..L.BNET_GUILD.."|r",'cr')
  1290.     p.bnetchart=f.create('button',-172,-352,r..L.BNET_CHART.."|r",'cr')
  1291.     p.bnettell=f.create('button',-110,-352,r..L.BNET_TELL.."|r",'cr')
  1292.     p.bnettrade=f.create('button',-48,-352,r..L.BNET_TRADE.."|r",'cr')
  1293.     p.msgchan=f.create('button',-482,-400,r..L.MSG_CHAN.."|r",'cr')
  1294.     p.msgduel=f.create('button',-420,-400,r..L.MSG_DUEL.."|r",'cr')
  1295.     p.msgpet=f.create('button',-358,-400,r..L.MSG_PET.."|r",'cr')
  1296.     p.msggroup=f.create('button',-296,-400,r..L.MSG_GROUP.."|r",'cr')
  1297.     p.msgguild=f.create('button',-234,-400,r..L.MSG_GUILD.."|r",'cr')
  1298.     p.msgchart=f.create('button',-172,-400,r..L.MSG_CHART.."|r",'cr')
  1299.     p.msgtell=f.create('button',-110,-400,r..L.MSG_TELL.."|r",'cr')
  1300.     p.msgtrade=f.create('button',-48,-400,r..L.MSG_TRADE.."|r",'cr')
  1301.     p.line02=f.create('line',10,-428,.25)
  1302.     p.genopeninv=f.create('button',-265,-464,r..L.X_DISABLE_OPENINV.."|r",'cr',d.S.DIO..d.S.INV,1)
  1303.     p.genmsgs=f.create('button',-265,-496,r..L.X_DISABLE_MESSAGES.."|r",'cr',d.S.DIS..d.S.CAL,1)
  1304.     p.gendisable=f.create('button',-265,-528,r..L.X_DISABLE_DECLINER.."|r",'cr',d.S.DIS..a,1)
  1305.  
  1306.     for k1,v1 in next,d.def do for k2 in next,v1 do
  1307.         if k1==k2 and k2~='tell' then
  1308.             p[k1..k2]:SetScript('OnEnter',function(self)
  1309.                 GameTooltip:SetOwner(self,self.tooltipOwnerPoint or 'ANCHOR_RIGHT')
  1310.                 if self.soundname then
  1311.                     GameTooltip:SetText(self.soundname,nil,nil,nil,nil,false)
  1312.                     if self.soundid then
  1313.                         GameTooltip:AddLine(self.soundid,nil,nil,nil,false)
  1314.                         GameTooltip:Show()
  1315.                     end
  1316.                 else
  1317.                     GameTooltip:SetText(self.tooltipText or '',nil,nil,nil,nil,true)
  1318.                 end
  1319.             end)
  1320.             p[k1..k2]:SetScript('PostClick',function(self)
  1321.                 self:SetChecked(true) d.var[k1][k2]=false
  1322.                 self[k1..'l']=0 f.randomsound(self,k1)
  1323.             end)
  1324.         elseif k1~='chan' and k1~='open' then
  1325.             p[k1..k2]:SetScript('PostClick',function(self)
  1326.                 d.var[k1][k2]=self:GetChecked()
  1327.                 f.register() f.blizzardoptions()
  1328.             end)
  1329.         end
  1330.     end end
  1331.  
  1332.     -- the following inputbox, button, and scrolling list inspired by funkydude's BadBoy_CCleaner chat filter options
  1333.  
  1334.     p.OIinput=CreateFrame('EditBox',a..'OpenInvInput',DCLPanel,'InputBoxTemplate')
  1335.     p.OIinput:SetPoint('TOPRIGHT',-325,-464)
  1336.     p.OIinput:SetAutoFocus(false)
  1337.     p.OIinput:EnableMouse(true)
  1338.     p.OIinput:SetSize(112,20)
  1339.     p.OIinput:SetScript('OnEnterPressed',function() p.OIbutton1:Click() end)
  1340.     p.OIinput:SetScript('OnEscapePressed',function(frame) frame:ClearFocus() end)
  1341.     p.OIinput:SetScript('OnEnter',function(self)
  1342.         if d.var.gen.disable or d.var.gen.openinv then
  1343.             GameTooltip:Hide()
  1344.         else
  1345.             GameTooltip:SetOwner(self,self.tooltipOwnerPoint or 'ANCHOR_RIGHT')
  1346.             GameTooltip:SetText(L.X_OPENINV_INPUT,nil,nil,nil,nil,true)
  1347.         end
  1348.     end)
  1349.     p.OIinput:SetScript('OnLeave',function()
  1350.         GameTooltip:Hide()
  1351.     end)
  1352.  
  1353.     p.OIbutton1=CreateFrame('Button',a..'OpenInvButton',DCLPanel,'UIPanelButtonTemplate')
  1354.     p.OIbutton1:SetPoint('TOPRIGHT',p.OIinput,'BOTTOMRIGHT',1,0)
  1355.     p.OIbutton1:SetPoint('TOPLEFT',p.OIinput,'BOTTOMLEFT',-6,0)
  1356.     p.OIbutton1:SetNormalFontObject('GameFontNormalSmall')
  1357.     p.OIbutton1:SetHighlightFontObject('GameFontHighlightSmall')
  1358.     p.OIbutton1:SetDisabledFontObject('GameFontDisableSmall')
  1359.     p.OIbutton1:SetText(d.S.ADD)
  1360.     p.OIbutton1:SetScript('OnClick',function(text,found)
  1361.         p.OIinput:ClearFocus() text=p.OIinput:GetText() found=nil
  1362.         if text:find('^%s*$') then p.OIinput:SetText('') return end
  1363.         for k,v in next,d.var.open do if text==v then found=k end end
  1364.         if found then remove(d.var.open,found) else insert(d.var.open,text) end
  1365.         f.setOIlist() p.OIinput:SetText('')
  1366.     end)
  1367.     p.OIbutton1:SetScript('OnEnter',function(self)
  1368.         if d.var.gen.disable or d.var.gen.openinv then
  1369.             GameTooltip:Hide()
  1370.         else
  1371.             GameTooltip:SetOwner(self,self.tooltipOwnerPoint or 'ANCHOR_RIGHT',-1)
  1372.             GameTooltip:SetText(L.X_OPENINV_BUTTON,nil,nil,nil,nil,true)
  1373.         end
  1374.     end)
  1375.     p.OIbutton1:SetScript('OnLeave',function()
  1376.         GameTooltip:Hide()
  1377.     end)
  1378.  
  1379.     p.OIbutton2=CreateFrame('Button',a..'OpenInvButton2',DCLPanel,'UIPanelButtonTemplate')
  1380.     p.OIbutton2:SetPoint('TOPRIGHT',p.OIbutton1,'BOTTOMRIGHT')
  1381.     p.OIbutton2:SetPoint('TOPLEFT',p.OIbutton1,'BOTTOMLEFT')
  1382.     p.OIbutton2:SetNormalFontObject('GameFontNormalSmall')
  1383.     p.OIbutton2:SetHighlightFontObject('GameFontHighlightSmall')
  1384.     p.OIbutton2:SetDisabledFontObject('GameFontDisableSmall')
  1385.     p.OIbutton2:SetText(d.S.CHS)
  1386.     p.OIbutton2:SetScript('OnClick',function()
  1387.         if p.OIdrop:IsVisible() then
  1388.             p.OIbutton2:UnlockHighlight() p.OIdrop:Hide()
  1389.             p.OIbutton2:SetNormalFontObject('GameFontNormalSmall')
  1390.         else
  1391.             p.OIbutton2:LockHighlight() p.OIdrop:Show()
  1392.             p.OIdrop:EnableMouse(true) p.OIdrop:SetFrameStrata('DIALOG')
  1393.             p.OIbutton2:SetNormalFontObject('GameFontHighlightSmall')
  1394.         end
  1395.     end)
  1396.     p.OIbutton2:SetScript('OnEnter',function(self)
  1397.         if d.var.gen.disable or d.var.gen.openinv then
  1398.             GameTooltip:Hide()
  1399.         else
  1400.             GameTooltip:SetOwner(self,self.tooltipOwnerPoint or 'ANCHOR_RIGHT',-1)
  1401.             GameTooltip:SetText(L.X_OPENINV_MENU,nil,nil,nil,nil,true)
  1402.         end
  1403.     end)
  1404.     p.OIbutton2:SetScript('OnLeave',function()
  1405.         GameTooltip:Hide()
  1406.     end)
  1407.  
  1408.     p.OIdrop=CreateFrame('Frame',a..'OpenInvDropDown',DCLPanel, BackdropTemplateMixin and "BackdropTemplate")
  1409.     p.OIdrop:SetBackdrop({
  1410.         bgFile='Interface\\Tooltips\\UI-Tooltip-Background',
  1411.         edgeFile='Interface\\Tooltips\\UI-Tooltip-Border',
  1412.         insets={left=2,right=2,top=2,bottom=2},
  1413.         tile=true,tileSize=8,edgeSize=8,})
  1414.     p.OIdrop:SetBackdropColor(0,0,0,1)
  1415.     p.OIdrop:SetBackdropBorderColor(.25,.25,.25)
  1416.     p.OIdrop:SetPoint('LEFT',p.OIbutton1,'RIGHT')
  1417.     p.chanemote=f.create('button',4,-4,nil,nil,d.S.EMO,1,1)
  1418.     p.chansay=f.create('button',4,-20,nil,nil,d.S.SAY,1,1)
  1419.     p.chanyell=f.create('button',4,-36,nil,nil,d.S.YEL,1,1)
  1420.     p.chan1=f.create('button',4,-52,nil,nil,d.S.GEN,1,1)
  1421.     p.chan2=f.create('button',4,-68,nil,nil,d.S.TRA,1,1)
  1422.     p.chan26=f.create('button',4,-84,nil,nil,d.S.LOO,1,1)
  1423.     for k in next,d.def.chan do
  1424.         d.mcw=d.mcw and max(d.mcw,p['chan'..k].width) or p['chan'..k].width
  1425.         p['chan'..k]:SetScript('PostClick',function(self) d.var.chan[k]=self:GetChecked() end)
  1426.     end
  1427.     p.OIdrop:SetSize(d.mcw+40,104) p.OIdrop:Hide()
  1428.  
  1429.     p.OIback=CreateFrame('Frame',a..'OpenInvBackdrop',DCLPanel, BackdropTemplateMixin and "BackdropTemplate")
  1430.     p.OIback:SetPoint('TOPRIGHT',p.OIinput,'TOPLEFT',-24,0)
  1431.     p.OIback:SetPoint('BOTTOMLEFT',p.OIbutton2,'BOTTOMLEFT',-119,0)
  1432.     p.OIback:SetBackdrop({
  1433.         bgFile='Interface\\Tooltips\\UI-Tooltip-Background',
  1434.         edgeFile='Interface\\Tooltips\\UI-Tooltip-Border',
  1435.         insets={left=2,right=2,top=2,bottom=2},
  1436.         tile=true,tileSize=8,edgeSize=8,})
  1437.     p.OIback:SetBackdropColor(0,0,0,.75)
  1438.     p.OIback:SetBackdropBorderColor(.25,.25,.25)
  1439.     p.OIback:SetScript('OnEnter',function(self)
  1440.         if d.var.gen.disable or d.var.gen.openinv then
  1441.             GameTooltip:Hide()
  1442.         else
  1443.             GameTooltip:SetOwner(self,self.tooltipOwnerPoint or 'ANCHOR_RIGHT')
  1444.             GameTooltip:SetText(L.X_OPENINV_LIST,nil,nil,nil,nil,true)
  1445.         end
  1446.     end)
  1447.     p.OIback:SetScript('OnLeave',function()
  1448.         GameTooltip:Hide()
  1449.     end)
  1450.  
  1451.     p.OIscroll=CreateFrame('ScrollFrame',a..'OpenInvScroll',DCLPanel,'UIPanelScrollFrameTemplate')
  1452.     p.OIscroll:SetPoint('LEFT',p.OIback,'LEFT',4.5,0)
  1453.     p.OIscroll:SetPoint('RIGHT',p.OIback,'RIGHT',-4.5,0)
  1454.     p.OIscroll:SetPoint('TOP',p.OIback,'TOP',0,-4)
  1455.     p.OIscroll:SetPoint('BOTTOM',p.OIback,'BOTTOM',0,4)
  1456.  
  1457.     p.OIlist=CreateFrame('EditBox',a..'OpenInvList',DCLPanel)
  1458.     p.OIlist:SetAutoFocus(false)
  1459.     p.OIlist:EnableMouse(false)
  1460.     p.OIlist:SetMultiLine(true)
  1461.     p.OIlist:SetMaxLetters(0)
  1462.     p.OIlist:SetWidth(120)
  1463.     p.OIlist:Show()
  1464.  
  1465.     p.OIscroll:SetScrollChild(p.OIlist)
  1466.  
  1467.     DCLPanel.okay=function() f.options() d.cancel=nil end
  1468.  -- DCLPanel.cancel=function() f.options('c') f.register() end -- stupid taints
  1469.     DCLPanel.default=function() f.options('d') f.register() end
  1470.     DCLPanel.refresh=function() f.options('r') d.cancel=true end
  1471.     SLASH_DECLINER1='/decliner'
  1472.     SLASH_DECLINER2='/decline'
  1473.     SLASH_DECLINER3='/dcl'
  1474.     SlashCmdList.DECLINER=function() InterfaceOptionsFrame_OpenToCategory(a) end
  1475. end
Add Comment
Please, Sign In to add comment