Advertisement
Guest User

Skada.lua

a guest
Oct 30th, 2014
52
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 71.24 KB | None | 0 0
  1.  
  2. local Skada = LibStub("AceAddon-3.0"):NewAddon("Skada", "AceTimer-3.0")
  3. _G.Skada = Skada
  4.  
  5. local L = LibStub("AceLocale-3.0"):GetLocale("Skada", false)
  6. local ldb = LibStub:GetLibrary("LibDataBroker-1.1")
  7. local icon = LibStub("LibDBIcon-1.0", true)
  8. local media = LibStub("LibSharedMedia-3.0")
  9. local boss = LibStub("LibBossIDs-1.0")
  10. local lds = LibStub:GetLibrary("LibDualSpec-1.0", 1)
  11. local dataobj = ldb:NewDataObject("Skada", {label = "Skada", type = "data source", icon = "Interface\\Icons\\Spell_Lightning_LightningBolt01", text = "n/a"})
  12. local popup, cleuFrame
  13.  
  14. -- Returns the group type (i.e., "party" or "raid") and the size of the group.
  15. function Skada:GetGroupTypeAndCount()
  16. local type
  17. local count = GetNumGroupMembers()
  18. if IsInRaid() then
  19. type = "raid"
  20. elseif IsInGroup() then
  21. type = "party"
  22. -- To make the counts similar between 4.3 and 5.0, we need
  23. -- to subtract one because GetNumPartyMembers() does not
  24. -- include the player while GetNumGroupMembers() does.
  25. count = count - 1
  26. end
  27.  
  28. return type, count
  29. end
  30.  
  31. do
  32. popup = CreateFrame("Frame", nil, UIParent) -- Recycle the popup frame as an event handler.
  33. popup:SetScript("OnEvent", function(frame, event, ...)
  34. Skada[event](Skada, ...)
  35. end)
  36.  
  37. popup:SetBackdrop({bgFile = "Interface\\DialogFrame\\UI-DialogBox-Background-Dark",
  38. edgeFile = "Interface\\DialogFrame\\UI-DialogBox-Border",
  39. tile = true, tileSize = 16, edgeSize = 16,
  40. insets = {left = 1, right = 1, top = 1, bottom = 1}}
  41. )
  42. popup:SetSize(250, 70)
  43. popup:SetPoint("CENTER", UIParent, "CENTER")
  44. popup:SetFrameStrata("DIALOG")
  45. popup:Hide()
  46.  
  47. local text = popup:CreateFontString(nil, "ARTWORK", "ChatFontNormal")
  48. text:SetPoint("TOP", popup, "TOP", 0, -10)
  49. text:SetText(L["Do you want to reset Skada?"])
  50.  
  51. local accept = CreateFrame("Button", nil, popup)
  52. accept:SetNormalTexture("Interface\\Buttons\\UI-CheckBox-Check")
  53. accept:SetHighlightTexture("Interface\\Buttons\\UI-CheckBox-Highlight", "ADD")
  54. accept:SetSize(50, 50)
  55. accept:SetPoint("BOTTOM", popup, "BOTTOM", -50, 0)
  56. accept:SetScript("OnClick", function(f) Skada:Reset() f:GetParent():Hide() end)
  57.  
  58. local close = CreateFrame("Button", nil, popup)
  59. close:SetNormalTexture("Interface\\Buttons\\UI-Panel-MinimizeButton-Up")
  60. close:SetHighlightTexture("Interface\\Buttons\\UI-Panel-MinimizeButton-Highlight", "ADD")
  61. close:SetSize(50, 50)
  62. close:SetPoint("BOTTOM", popup, "BOTTOM", 50, 0)
  63. close:SetScript("OnClick", function(f) f:GetParent():Hide() end)
  64. function Skada:ShowPopup()
  65. popup:Show()
  66. end
  67. end
  68.  
  69. -- Keybindings
  70. BINDING_HEADER_Skada = "Skada"
  71. BINDING_NAME_SKADA_TOGGLE = L["Toggle window"]
  72. BINDING_NAME_SKADA_RESET = L["Reset"]
  73. BINDING_NAME_SKADA_NEWSEGMENT = L["Start new segment"]
  74.  
  75. -- The current set
  76. Skada.current = nil
  77.  
  78. -- The total set
  79. Skada.total = nil
  80.  
  81. -- The last set
  82. Skada.last = nil
  83.  
  84. -- Modes - these are modules, really. Modeules?
  85. local modes = {}
  86.  
  87. -- Pets; an array of pets and their owners.
  88. local pets, players = {}, {}
  89.  
  90. -- Flag marking if we need an update.
  91. local changed = true
  92.  
  93. -- Flag for if we were in a party/raid.
  94. local wasinparty = nil
  95.  
  96. -- By default we just use RAID_CLASS_COLORS as class colors.
  97. Skada.classcolors = RAID_CLASS_COLORS
  98.  
  99. -- The selected data feed.
  100. local selectedfeed = nil
  101.  
  102. -- A list of data feeds available. Modules add to it.
  103. local feeds = {}
  104.  
  105. -- Disabled flag.
  106. local disabled = false
  107.  
  108. -- Our windows.
  109. local windows = {}
  110.  
  111. -- Our display providers.
  112. Skada.displays = {}
  113.  
  114. -- Timer for updating windows.
  115. local update_timer = nil
  116.  
  117. -- Timer for checking for combat end.
  118. local tick_timer = nil
  119.  
  120. function Skada:GetWindows()
  121. return windows
  122. end
  123.  
  124. local function find_mode(name)
  125. for i, mode in ipairs(modes) do
  126. if mode:GetName() == name then
  127. return mode
  128. end
  129. end
  130. end
  131.  
  132. -- Our window type.
  133. local Window = {}
  134.  
  135. local mt = {__index = Window}
  136.  
  137. function Window:new()
  138. return setmetatable(
  139. {
  140. -- The selected mode and set
  141. selectedmode = nil,
  142. selectedset = nil,
  143.  
  144. -- Mode and set to return to after combat.
  145. restore_mode = nil,
  146. restore_set = nil,
  147.  
  148. usealt = true,
  149.  
  150. -- Our dataset.
  151. dataset = {},
  152.  
  153. -- Metadata about our dataset.
  154. metadata = {},
  155.  
  156. -- Our display provider.
  157. display = nil,
  158.  
  159. -- Our mode traversing history.
  160. history = {},
  161.  
  162. -- Flag for window-specific changes.
  163. changed = false,
  164.  
  165. }, mt)
  166. end
  167.  
  168. function Window:AddOptions()
  169. local db = self.db
  170.  
  171. local options = {
  172. type="group",
  173. name=function() return db.name end,
  174. args={
  175.  
  176. rename = {
  177. type="input",
  178. name=L["Rename window"],
  179. desc=L["Enter the name for the window."],
  180. get=function() return db.name end,
  181. set=function(win, val)
  182. if val ~= db.name and val ~= "" then
  183. local oldname = db.name
  184. db.name = val
  185. Skada.options.args.windows.args[val] = Skada.options.args.windows.args[oldname]
  186. Skada.options.args.windows.args[oldname] = nil
  187. end
  188. end,
  189. order=1,
  190. },
  191.  
  192. locked = {
  193. type="toggle",
  194. name=L["Lock window"],
  195. desc=L["Locks the bar window in place."],
  196. order=2,
  197. get=function() return db.barslocked end,
  198. set=function()
  199. db.barslocked = not db.barslocked
  200. Skada:ApplySettings()
  201. end,
  202. },
  203.  
  204. delete = {
  205. type="execute",
  206. name=L["Delete window"],
  207. desc=L["Deletes the chosen window."],
  208. order=20,
  209. width="full",
  210. confirm=function() return "Are you sure you want to delete this window?" end,
  211. func=function(self) Skada:DeleteWindow(db.name) end,
  212. },
  213.  
  214. }
  215. }
  216.  
  217. options.args.switchoptions = {
  218. type = "group",
  219. name = L["Mode switching"],
  220. order=4,
  221. args = {
  222.  
  223. modeincombat = {
  224. type="select",
  225. name=L["Combat mode"],
  226. desc=L["Automatically switch to set 'Current' and this mode when entering combat."],
  227. values=function()
  228. local modes = {}
  229. modes[""] = L["None"]
  230. for i, mode in ipairs(Skada:GetModes()) do
  231. modes[mode:GetName()] = mode:GetName()
  232. end
  233. return modes
  234. end,
  235. get=function() return db.modeincombat end,
  236. set=function(win, mode) db.modeincombat = mode end,
  237. order=21,
  238. },
  239.  
  240. wipemode = {
  241. type="select",
  242. name=L["Wipe mode"],
  243. desc=L["Automatically switch to set 'Current' and this mode after a wipe."],
  244. values=function()
  245. local modes = {}
  246. modes[""] = L["None"]
  247. for i, mode in ipairs(Skada:GetModes()) do
  248. modes[mode:GetName()] = mode:GetName()
  249. end
  250. return modes
  251. end,
  252. get=function() return db.wipemode end,
  253. set=function(win, mode) db.wipemode = mode end,
  254. order=21,
  255. },
  256. returnaftercombat = {
  257. type="toggle",
  258. name=L["Return after combat"],
  259. desc=L["Return to the previous set and mode after combat ends."],
  260. order=23,
  261. get=function() return db.returnaftercombat end,
  262. set=function() db.returnaftercombat = not db.returnaftercombat end,
  263. disabled=function() return db.returnaftercombat == nil end,
  264. },
  265. }
  266. }
  267.  
  268. self.display:AddDisplayOptions(self, options.args)
  269.  
  270. Skada.options.args.windows.args[self.db.name] = options
  271. end
  272.  
  273. -- Sets a slave window for this window. This window will also be updated on view updates.
  274. function Window:SetChild(window)
  275. self.child = window
  276. end
  277.  
  278. function Window:destroy()
  279. self.dataset = nil
  280.  
  281. self.display:Destroy(self)
  282.  
  283. local name = self.db.name or Skada.windowdefaults.name
  284. Skada.options.args.windows.args[name] = nil -- remove from options
  285. end
  286.  
  287. function Window:SetDisplay(name)
  288. -- Don't do anything if nothing actually changed.
  289. if name ~= self.db.display or self.display == nil then
  290. if self.display then
  291. -- Destroy old display.
  292. self.display:Destroy(self)
  293. end
  294.  
  295. -- Set new display.
  296. self.db.display = name
  297. self.display = Skada.displays[self.db.display]
  298.  
  299. -- Add options. Replaces old options.
  300. self:AddOptions()
  301. end
  302. end
  303.  
  304. -- Tells window to update the display of its dataset, using its display provider.
  305. function Window:UpdateDisplay()
  306. -- Fetch max value if our mode has not done this itself.
  307. if not self.metadata.maxvalue then
  308. self.metadata.maxvalue = 0
  309. for i, data in ipairs(self.dataset) do
  310. if data.id and data.value > self.metadata.maxvalue then
  311. self.metadata.maxvalue = data.value
  312. end
  313. end
  314. end
  315.  
  316. -- Display it.
  317. self.display:Update(self)
  318. self:set_mode_title()
  319. end
  320.  
  321. -- Called before dataset is updated.
  322. function Window:UpdateInProgress()
  323. for i, data in ipairs(self.dataset) do
  324. if data.ignore then -- ensure total bar icon is cleared before bar is recycled
  325. data.icon = nil
  326. end
  327. data.id = nil
  328. data.ignore = nil
  329. end
  330. end
  331.  
  332. function Window:Show()
  333. self.display:Show(self)
  334. end
  335.  
  336. function Window:Hide()
  337. self.display:Hide(self)
  338. end
  339.  
  340. function Window:IsShown()
  341. return self.display:IsShown(self)
  342. end
  343.  
  344. function Window:Reset()
  345. for i, data in ipairs(self.dataset) do
  346. wipe(data)
  347. end
  348. end
  349.  
  350. function Window:Wipe()
  351. -- Clear dataset.
  352. self:Reset()
  353.  
  354. -- Clear display.
  355. self.display:Wipe(self)
  356.  
  357. if self.child then
  358. self.child:Wipe()
  359. end
  360. end
  361.  
  362. -- If selectedset is "current", returns current set if we are in combat, otherwise returns the last set.
  363. function Window:get_selected_set()
  364. return Skada:find_set(self.selectedset)
  365. end
  366.  
  367. -- Sets up the mode view.
  368. function Window:DisplayMode(mode)
  369. self:Wipe()
  370.  
  371. self.selectedplayer = nil
  372. self.selectedspell = nil
  373. self.selectedmode = mode
  374.  
  375. self.metadata = wipe(self.metadata or {})
  376.  
  377. -- Apply mode's metadata.
  378. if mode.metadata then
  379. for key, value in pairs(mode.metadata) do
  380. self.metadata[key] = value
  381. end
  382. end
  383.  
  384. self.changed = true
  385. self:set_mode_title() -- in case data sets are empty
  386.  
  387. if self.child then
  388. self.child:DisplayMode(mode)
  389. end
  390.  
  391. Skada:UpdateDisplay(false)
  392. end
  393.  
  394. local numsetfmts = 8
  395. local function SetLabelFormat(name,starttime,endtime,fmt)
  396. fmt = fmt or Skada.db.profile.setformat
  397. local namelabel = name
  398. if fmt < 1 or fmt > numsetfmts then fmt = 3 end
  399. local timelabel = ""
  400. if starttime and endtime and fmt > 1 then
  401. local duration = SecondsToTime(endtime-starttime, false, false, 2)
  402. -- translate locale time abbreviations, whose escape sequences are not legal in chat
  403. Skada.getsetlabel_fs = Skada.getsetlabel_fs or UIParent:CreateFontString(nil, "ARTWORK", "ChatFontNormal")
  404. Skada.getsetlabel_fs:SetText(duration)
  405. duration = "("..Skada.getsetlabel_fs:GetText()..")"
  406.  
  407. if fmt == 2 then
  408. timelabel = duration
  409. elseif fmt == 3 then
  410. timelabel = date("%H:%M",starttime).." "..duration
  411. elseif fmt == 4 then
  412. timelabel = date("%I:%M",starttime).." "..duration
  413. elseif fmt == 5 then
  414. timelabel = date("%H:%M",starttime).." - "..date("%H:%M",endtime)
  415. elseif fmt == 6 then
  416. timelabel = date("%I:%M",starttime).." - "..date("%I:%M",endtime)
  417. elseif fmt == 7 then
  418. timelabel = date("%H:%M:%S",starttime).." - "..date("%H:%M:%S",endtime)
  419. elseif fmt == 8 then
  420. timelabel = date("%H:%M",starttime).." - "..date("%H:%M",endtime).." "..duration
  421. end
  422. end
  423.  
  424. local comb
  425. if #namelabel == 0 or #timelabel == 0 then
  426. comb = namelabel..timelabel
  427. elseif timelabel:match("^%p") then
  428. comb = namelabel.." "..timelabel
  429. else
  430. comb = namelabel..": "..timelabel
  431. end
  432. -- provide both the combined label and the separated name/time labels
  433. return comb, namelabel, timelabel
  434. end
  435.  
  436. function Skada:SetLabelFormats() -- for config option display
  437. local ret = {}
  438. local start = 1000007900
  439. for i=1,numsetfmts do
  440. ret[i] = SetLabelFormat("Hogger", start, start+380, i)
  441. end
  442. return ret
  443. end
  444.  
  445. function Skada:GetSetLabel(set) -- return a nicely-formatted label for a set
  446. if not set then return "" end
  447. return SetLabelFormat(set.name or "Unknown", set.starttime, set.endtime or time())
  448. end
  449.  
  450. function Window:set_mode_title()
  451. if not self.selectedmode or not self.selectedset then return end
  452. local name = self.selectedmode.title or self.selectedmode:GetName()
  453.  
  454. -- save window settings for RestoreView after reload
  455. self.db.set = self.selectedset
  456. local savemode = name
  457. if self.history[1] then -- can't currently preserve a nested mode, use topmost one
  458. savemode = self.history[1].title or self.history[1]:GetName()
  459. end
  460. self.db.mode = savemode
  461.  
  462. if self.db.titleset then
  463. local setname
  464. if self.selectedset == "current" then
  465. setname = L["Current"]
  466. elseif self.selectedset == "total" then
  467. setname = L["Total"]
  468. else
  469. local set = self:get_selected_set()
  470. if set then
  471. setname = Skada:GetSetLabel(set)
  472. end
  473. end
  474. if setname then
  475. name = name..": "..setname
  476. end
  477. end
  478. if disabled and (self.selectedset == "current" or self.selectedset == "total") then
  479. -- indicate when data collection is disabled
  480. name = name.." |cFFFF0000"..L["DISABLED"].."|r"
  481. end
  482. self.metadata.title = name
  483. self.display:SetTitle(self, name)
  484. end
  485.  
  486. local function click_on_mode(win, id, label, button)
  487. if button == "LeftButton" then
  488. local mode = find_mode(id)
  489. if mode then
  490. win:DisplayMode(mode)
  491. end
  492. elseif button == "RightButton" then
  493. win:RightClick()
  494. end
  495. end
  496.  
  497. -- Sets up the mode list.
  498. function Window:DisplayModes(settime)
  499. self.history = wipe(self.history or {})
  500. self:Wipe()
  501.  
  502. self.selectedplayer = nil
  503. self.selectedmode = nil
  504.  
  505. self.metadata = wipe(self.metadata or {})
  506.  
  507. self.metadata.title = L["Skada: Modes"]
  508.  
  509. -- Find the selected set
  510. if settime == "current" or settime == "total" then
  511. self.selectedset = settime
  512. else
  513. for i, set in ipairs(Skada.char.sets) do
  514. if tostring(set.starttime) == settime then
  515. if set.name == L["Current"] then
  516. self.selectedset = "current"
  517. elseif set.name == L["Total"] then
  518. self.selectedset = "total"
  519. else
  520. self.selectedset = i
  521. end
  522. end
  523. end
  524. end
  525.  
  526. self.metadata.click = click_on_mode
  527. self.metadata.maxvalue = 1
  528. self.metadata.sortfunc = function(a,b) return a.name < b.name end
  529.  
  530. self.display:SetTitle(self, self.metadata.title)
  531. self.changed = true
  532.  
  533. if self.child then
  534. self.child:DisplayModes(settime)
  535. end
  536.  
  537. Skada:UpdateDisplay(false)
  538. end
  539.  
  540. local function click_on_set(win, id, label, button)
  541. if button == "LeftButton" then
  542. win:DisplayModes(id)
  543. elseif button == "RightButton" then
  544. win:RightClick()
  545. end
  546. end
  547.  
  548. -- Sets up the set list.
  549. function Window:DisplaySets()
  550. self.history = wipe(self.history or {})
  551. self:Wipe()
  552.  
  553. self.metadata = wipe(self.metadata or {})
  554.  
  555. self.selectedplayer = nil
  556. self.selectedmode = nil
  557. self.selectedset = nil
  558.  
  559. self.metadata.title = L["Skada: Fights"]
  560.  
  561. self.metadata.click = click_on_set
  562. self.metadata.maxvalue = 1
  563. -- self.metadata.sortfunc = function(a,b) return a.name < b.name end
  564. self.changed = true
  565.  
  566. if self.child then
  567. self.child:DisplaySets()
  568. end
  569.  
  570. Skada:UpdateDisplay(false)
  571. end
  572.  
  573. -- Default "right-click" behaviour in case no special click function is defined:
  574. -- 1) If there is a mode traversal history entry, go to the last mode.
  575. -- 2) Go to modes list if we are in a mode.
  576. -- 3) Go to set list.
  577. function Window:RightClick(group, button)
  578. if self.selectedmode then
  579. -- If mode traversal history exists, go to last entry, else mode list.
  580. if #self.history > 0 then
  581. self:DisplayMode(tremove(self.history))
  582. else
  583. self:DisplayModes(self.selectedset)
  584. end
  585. elseif self.selectedset then
  586. self:DisplaySets()
  587. end
  588. end
  589.  
  590. function Skada:tcopy(to, from)
  591. for k,v in pairs(from) do
  592. if(type(v)=="table") then
  593. to[k] = {}
  594. Skada:tcopy(to[k], v);
  595. else
  596. to[k] = v;
  597. end
  598. end
  599. end
  600.  
  601. function Skada:CreateWindow(name, db, display)
  602. if not db then
  603. db = {}
  604. self:tcopy(db, Skada.windowdefaults)
  605. table.insert(self.db.profile.windows, db)
  606. end
  607. if display then
  608. db.display = display
  609. end
  610.  
  611. -- Migrate old settings.
  612. if not db.barbgcolor then
  613. db.barbgcolor = {r = 0.3, g = 0.3, b = 0.3, a = 0.6}
  614. end
  615. if not db.buttons then
  616. db.buttons = {menu = true, reset = true, report = true, mode = true, segment = true}
  617. end
  618. if not db.scale then
  619. db.scale = 1
  620. end
  621.  
  622. local window = Window:new()
  623. window.db = db
  624. window.db.name = name
  625.  
  626. if self.displays[window.db.display] then
  627. -- Set the window's display and call it's Create function.
  628. window:SetDisplay(window.db.display or "bar")
  629.  
  630. window.display:Create(window)
  631.  
  632. table.insert(windows, window)
  633.  
  634. if window.db.set or window.db.mode then
  635. -- Restore view.
  636. window:DisplaySets()
  637. self:RestoreView(window, window.db.set, window.db.mode)
  638. else
  639. -- Set initial view, set list.
  640. window:DisplaySets()
  641. end
  642. else
  643. -- This window's display is missing.
  644. self:Print("Window '"..name.."' was not loaded because its display module, '"..window.db.display.."' was not found.")
  645. end
  646.  
  647. self:ApplySettings()
  648. return window
  649. end
  650.  
  651. -- Deleted named window from our windows table, and also from db.
  652. function Skada:DeleteWindow(name)
  653. for i, win in ipairs(windows) do
  654. if win.db.name == name then
  655. win:destroy()
  656. wipe(table.remove(windows, i))
  657. end
  658. end
  659. for i, win in ipairs(self.db.profile.windows) do
  660. if win.name == name then
  661. table.remove(self.db.profile.windows, i)
  662. end
  663. end
  664. end
  665.  
  666. function Skada:Print(msg)
  667. print("|cFF33FF99Skada|r: "..msg)
  668. end
  669.  
  670. function Skada:Debug(...)
  671. if not Skada.db.profile.debug then return end
  672. local msg = ""
  673. for i=1, select("#",...) do
  674. local v = tostring(select(i,...))
  675. if #msg > 0 then
  676. msg = msg .. ", "
  677. end
  678. msg = msg..v
  679. end
  680. print("|cFF33FF99Skada Debug|r: "..msg)
  681. end
  682.  
  683. local function slashHandler(param)
  684. local reportusage = "/skada report [raid|party|instance|guild|officer|say] [current||total|set_num] [mode] [max_lines]"
  685. if param == "pets" then
  686. Skada:PetDebug()
  687. elseif param == "test" then
  688. Skada:OpenMenu()
  689. elseif param == "reset" then
  690. Skada:Reset()
  691. elseif param == "newsegment" then
  692. Skada:NewSegment()
  693. elseif param == "toggle" then
  694. Skada:ToggleWindow()
  695. elseif param == "debug" then
  696. Skada.db.profile.debug = not Skada.db.profile.debug
  697. Skada:Print("Debug mode "..(Skada.db.profile.debug and ("|cFF00FF00"..L["ENABLED"].."|r") or ("|cFFFF0000"..L["DISABLED"].."|r")))
  698. elseif param == "config" then
  699. InterfaceOptionsFrame_OpenToCategory(Skada.optionsFrame)
  700. InterfaceOptionsFrame_OpenToCategory(Skada.optionsFrame)
  701. elseif param:sub(1,6) == "report" then
  702. local chan = (IsInGroup(LE_PARTY_CATEGORY_INSTANCE) and "instance") or
  703. (IsInRaid() and "raid") or
  704. (IsInGroup() and "party") or
  705. "say"
  706. local set = "current"
  707. local report_mode_name = L["Damage"]
  708. local w1, w2, w3, w4 = param:match("^%s*(%w*)%s*(%w*)%s*([^%d]-)%s*(%d*)%s*$",7)
  709. if w1 and #w1 > 0 then
  710. chan = string.lower(w1)
  711. end
  712. if w2 and #w2 > 0 then
  713. w2 = tonumber(w2) or w2:lower()
  714. if Skada:find_set(w2) then
  715. set = w2
  716. end
  717. end
  718. if w3 and #w3 > 0 then
  719. w3 = strtrim(w3)
  720. w3 = strtrim(w3,"'\"[]()") -- strip optional quoting
  721. if find_mode(w3) then
  722. report_mode_name = w3
  723. end
  724. end
  725. local max = tonumber(w4) or 10
  726.  
  727. if chan == "instance" then chan = "instance_chat" end
  728. if (chan == "say" or chan == "guild" or chan == "raid" or chan == "party" or chan == "officer" or chan == "instance_chat") then
  729. Skada:Report(chan, "preset", report_mode_name, set, max)
  730. else
  731. Skada:Print("Usage:")
  732. Skada:Print(("%-20s"):format(reportusage))
  733. end
  734. else
  735. Skada:Print("Usage:")
  736. Skada:Print(("%-20s"):format(reportusage))
  737. Skada:Print(("%-20s"):format("/skada reset"))
  738. Skada:Print(("%-20s"):format("/skada toggle"))
  739. Skada:Print(("%-20s"):format("/skada debug"))
  740. Skada:Print(("%-20s"):format("/skada newsegment"))
  741. Skada:Print(("%-20s"):format("/skada config"))
  742. end
  743. end
  744.  
  745. local function sendchat(msg, chan, chantype)
  746. if chantype == "self" then
  747. -- To self.
  748. Skada:Print(msg)
  749. elseif chantype == "channel" then
  750. -- To channel.
  751. SendChatMessage(msg, "CHANNEL", nil, chan)
  752. elseif chantype == "preset" then
  753. -- To a preset channel id (say, guild, etc).
  754. SendChatMessage(msg, string.upper(chan))
  755. elseif chantype == "whisper" then
  756. -- To player.
  757. SendChatMessage(msg, "WHISPER", nil, chan)
  758. elseif chantype == "RealID" then
  759. BNSendWhisper(chan,msg)
  760. end
  761. end
  762.  
  763. function Skada:Report(channel, chantype, report_mode_name, report_set_name, max, window)
  764.  
  765. if(chantype == "channel") then
  766. local list = {GetChannelList()}
  767. for i=1,table.getn(list)/2 do
  768. if(Skada.db.profile.report.channel == list[i*2]) then
  769. channel = list[i*2-1]
  770. break
  771. end
  772. end
  773. end
  774.  
  775. local report_table
  776. local report_set
  777. local report_mode
  778. if not window then
  779. report_mode = find_mode(report_mode_name)
  780. report_set = Skada:find_set(report_set_name)
  781. if report_set == nil then
  782. return
  783. end
  784. -- Create a temporary fake window.
  785. report_table = Window:new()
  786.  
  787. -- Tell our mode to populate our dataset.
  788. report_mode:Update(report_table, report_set)
  789. else
  790. report_table = window
  791. report_set = window:get_selected_set()
  792. report_mode = window.selectedmode
  793. end
  794.  
  795. if not report_set then
  796. Skada:Print(L["There is nothing to report."])
  797. return
  798. end
  799.  
  800. -- Sort our temporary table according to value unless ordersort is set.
  801. if not report_table.metadata.ordersort then
  802. table.sort(report_table.dataset, Skada.valueid_sort)
  803. end
  804.  
  805. -- Title
  806. sendchat(string.format(L["Skada: %s for %s:"], report_mode.title or report_mode:GetName(), Skada:GetSetLabel(report_set)),
  807. channel, chantype)
  808.  
  809. -- For each item in dataset, print label and valuetext.
  810. local nr = 1
  811. for i, data in ipairs(report_table.dataset) do
  812. if data.id then
  813. local label = data.reportlabel or (data.spellid and GetSpellLink(data.spellid)) or data.label
  814. if report_mode.metadata and report_mode.metadata.showspots then
  815. sendchat(("%2u. %s %s"):format(nr, label, data.valuetext), channel, chantype)
  816. else
  817. sendchat(("%s %s"):format(label, data.valuetext), channel, chantype)
  818. end
  819. nr = nr + 1
  820. end
  821. if nr > max then
  822. break
  823. end
  824. end
  825.  
  826. end
  827.  
  828. function Skada:RefreshMMButton()
  829. if icon then
  830. icon:Refresh("Skada", self.db.profile.icon)
  831. if self.db.profile.icon.hide then
  832. icon:Hide("Skada")
  833. else
  834. icon:Show("Skada")
  835. end
  836. end
  837. end
  838.  
  839. function Skada:PetDebug()
  840. self:CheckGroup()
  841. self:Print("pets:")
  842. for pet, owner in pairs(pets) do
  843. self:Print("pet "..pet.." belongs to ".. owner.id..", "..owner.name)
  844. end
  845. end
  846.  
  847. function Skada:SetActive(enable)
  848. if enable then
  849. for i, win in ipairs(windows) do
  850. win:Show()
  851. end
  852. else
  853. for i, win in ipairs(windows) do
  854. win:Hide()
  855. end
  856. end
  857. if not enable and self.db.profile.hidedisables then
  858. if not disabled then -- print a message when we change state
  859. self:Debug(L["Data Collection"].." ".."|cFFFF0000"..L["DISABLED"].."|r")
  860. end
  861. disabled = true
  862. cleuFrame:UnregisterEvent("COMBAT_LOG_EVENT_UNFILTERED")
  863. else
  864. if disabled then -- print a message when we change state
  865. self:Debug(L["Data Collection"].." ".."|cFF00FF00"..L["ENABLED"].."|r")
  866. end
  867. disabled = false
  868. cleuFrame:RegisterEvent("COMBAT_LOG_EVENT_UNFILTERED")
  869. end
  870.  
  871. Skada:UpdateDisplay(true) -- update title indicator
  872. end
  873.  
  874. function Skada:CheckGroup()
  875. local type, count = self:GetGroupTypeAndCount()
  876. if count > 0 then
  877. for i = 1, count do
  878. local unit = ("%s%d"):format(type, i)
  879. local playerGUID = UnitGUID(unit)
  880. if playerGUID then
  881. players[playerGUID] = true
  882. local unitPet = unit.."pet"
  883. local petGUID = UnitGUID(unitPet)
  884. if petGUID and not pets[petGUID] then
  885. local name, server = UnitName(unit)
  886. if server and server ~= "" then name = name.."-"..server end
  887. pets[petGUID] = {id = playerGUID, name = name}
  888. end
  889. end
  890. end
  891. end
  892.  
  893. -- Solo, always check.
  894. local playerGUID = UnitGUID("player")
  895. if playerGUID then
  896. players[playerGUID] = true
  897. local petGUID = UnitGUID("playerpet")
  898. if petGUID and not pets[petGUID] then
  899. local name = UnitName("player")
  900. pets[petGUID] = {id = playerGUID, name = name}
  901. end
  902. end
  903. end
  904.  
  905. -- Ask a mode to verify the contents of a set.
  906. local function verify_set(mode, set)
  907. if mode.AddSetAttributes ~= nil then
  908. mode:AddSetAttributes(set)
  909. end
  910. for j, player in ipairs(set.players) do
  911. if mode.AddPlayerAttributes ~= nil then
  912. mode:AddPlayerAttributes(player, set)
  913. end
  914. end
  915. end
  916.  
  917. local wasininstance
  918. local wasinpvp
  919.  
  920. -- Are we in a PVP zone?
  921. local pvp_zones = {}
  922. local function IsInPVP()
  923. local pvpType, isFFA = GetZonePVPInfo()
  924. local _, instanceType = IsInInstance()
  925. return instanceType == "pvp" or instanceType == "arena" or pvpType == "arena" or pvpType == "combat" or isFFA
  926. end
  927.  
  928. function Skada:ZoneCheck()
  929. -- Check if we are entering an instance.
  930. local inInstance, instanceType = IsInInstance()
  931. local isininstance = inInstance and (instanceType == "party" or instanceType == "raid")
  932. local isinpvp = IsInPVP()
  933.  
  934. -- If we are entering an instance, and we were not previously in an instance, and we got this event before... and we have some data...
  935. if isininstance and wasininstance ~= nil and not wasininstance and self.db.profile.reset.instance ~= 1 and self.total ~= nil then
  936. if self.db.profile.reset.instance == 3 then
  937. Skada:ShowPopup()
  938. else
  939. self:Reset()
  940. end
  941. end
  942.  
  943. -- Hide in PvP. Hide if entering a PvP instance, show if we are leaving one.
  944. if self.db.profile.hidepvp then
  945. if IsInPVP() then
  946. Skada:SetActive(false)
  947. elseif wasinpvp then
  948. Skada:SetActive(true)
  949. end
  950. end
  951.  
  952. -- Save a flag marking our previous (current) instance status.
  953. if isininstance then
  954. wasininstance = true
  955. else
  956. wasininstance = false
  957. end
  958.  
  959. -- Save a flag marking out previous (current) pvp status.
  960. if isinpvp then
  961. wasinpvp = true
  962. else
  963. wasinpvp = false
  964. end
  965. end
  966.  
  967. -- Fired on entering a zone.
  968. function Skada:ZONE_CHANGED_NEW_AREA()
  969. Skada:ZoneCheck()
  970. end
  971.  
  972. -- Fired on blue bar screen
  973. function Skada:PLAYER_ENTERING_WORLD()
  974.  
  975. Skada:ZoneCheck() -- catch reloadui within a zone, which does not fire ZONE_CHANGED_NEW_AREA
  976. -- If this event fired in response to a login or teleport, zone info is usually not yet available
  977. -- and will be caught by a sunsequent ZONE_CHANGED_NEW_AREA
  978.  
  979. -- make sure we update once on reload
  980. -- delay it because group is unavailable during first PLAYER_ENTERING_WORLD on login
  981. if wasinparty == nil then Skada:ScheduleTimer("GROUP_ROSTER_UPDATE",1) end
  982. end
  983.  
  984. -- Check if we join a party/raid.
  985. local function check_for_join_and_leave()
  986. if not IsInGroup() and wasinparty then
  987. -- We left a party.
  988.  
  989. if Skada.db.profile.reset.leave == 3 then
  990. Skada:ShowPopup()
  991. elseif Skada.db.profile.reset.leave == 2 then
  992. Skada:Reset()
  993. end
  994.  
  995. -- Hide window if we have enabled the "Hide when solo" option.
  996. if Skada.db.profile.hidesolo then
  997. Skada:SetActive(false)
  998. end
  999. end
  1000.  
  1001. if IsInGroup() and wasinparty == false then -- if nil this is first check after reload/relog
  1002. -- We joined a raid.
  1003.  
  1004. if Skada.db.profile.reset.join == 3 then
  1005. Skada:ShowPopup()
  1006. elseif Skada.db.profile.reset.join == 2 then
  1007. Skada:Reset()
  1008. end
  1009.  
  1010. -- Show window if we have enabled the "Hide when solo" option.
  1011. -- But only when NOT in pvp if it's set to hide in pvp.
  1012. if Skada.db.profile.hidesolo and not (Skada.db.profile.hidepvp and IsInPVP()) then
  1013. Skada:SetActive(true)
  1014. end
  1015. end
  1016.  
  1017. -- Mark our last party status.
  1018. wasinparty = not not IsInGroup()
  1019. end
  1020.  
  1021. function Skada:GROUP_ROSTER_UPDATE()
  1022. check_for_join_and_leave()
  1023.  
  1024. -- Check for new pets.
  1025. self:CheckGroup()
  1026. end
  1027.  
  1028. function Skada:UNIT_PET()
  1029. -- Check for new pets.
  1030. self:CheckGroup()
  1031. end
  1032.  
  1033. function Skada:PET_BATTLE_OPENING_START()
  1034. -- Hide during pet battles
  1035. for i, win in ipairs(windows) do
  1036. if win:IsShown() then
  1037. win:Hide()
  1038. end
  1039. end
  1040. end
  1041.  
  1042. function Skada:PET_BATTLE_CLOSE()
  1043. -- Restore after pet battles
  1044. for i, win in ipairs(windows) do
  1045. if not win.db.hidden and not win:IsShown() then
  1046. win:Show()
  1047. end
  1048. end
  1049. end
  1050.  
  1051. -- Toggles all windows.
  1052. function Skada:ToggleWindow()
  1053. for i, win in ipairs(windows) do
  1054. if win:IsShown() then
  1055. win.db.hidden = true
  1056. win:Hide()
  1057. else
  1058. win.db.hidden = false
  1059. win:Show()
  1060. end
  1061. end
  1062. end
  1063.  
  1064. local function createSet(setname)
  1065. local set = {players = {}, name = setname, starttime = time(), ["time"] = 0, last_action = time()}
  1066.  
  1067. -- Tell each mode to apply its needed attributes.
  1068. for i, mode in ipairs(modes) do verify_set(mode, set) end
  1069.  
  1070. return set
  1071. end
  1072.  
  1073. function Skada:Reset()
  1074. self:Wipe()
  1075.  
  1076. pets, players = {}, {}
  1077. self:CheckGroup()
  1078.  
  1079. if self.current ~= nil then
  1080. wipe(self.current)
  1081. self.current = createSet(L["Current"])
  1082. end
  1083. if self.total ~= nil then
  1084. wipe(self.total)
  1085. self.total = createSet(L["Total"])
  1086. self.char.total = self.total
  1087. end
  1088. self.last = nil
  1089.  
  1090. -- Delete sets that are not marked as persistent.
  1091. for i=table.maxn(self.char.sets), 1, -1 do
  1092. if not self.char.sets[i].keep then
  1093. wipe(table.remove(self.char.sets, i))
  1094. end
  1095. end
  1096.  
  1097. -- Don't leave windows pointing to deleted sets
  1098. for _, win in ipairs(windows) do
  1099. if win.selectedset ~= "total" then
  1100. win.selectedset = "current"
  1101. win.changed = true
  1102. end
  1103. end
  1104.  
  1105. self:UpdateDisplay(true)
  1106. self:Print(L["All data has been reset."])
  1107. if not InCombatLockdown() then -- ticket 377: avoid timeout errors in combat because GC can run too long
  1108. collectgarbage("collect")
  1109. end
  1110. end
  1111.  
  1112. -- Delete a set.
  1113. function Skada:DeleteSet(set)
  1114. if not set then return end
  1115.  
  1116.  
  1117. for i, s in ipairs(self.char.sets) do
  1118. if s == set then
  1119. wipe(table.remove(self.char.sets, i))
  1120.  
  1121. if set == self.last then
  1122. self.last = nil
  1123. end
  1124.  
  1125. -- Don't leave windows pointing to deleted sets
  1126. for _, win in ipairs(windows) do
  1127. if win.selectedset == i or win:get_selected_set() == set then
  1128. win.selectedset = "current"
  1129. win.changed = true
  1130. elseif (tonumber(win.selectedset) or 0) > i then
  1131. win.selectedset = win.selectedset - 1
  1132. win.changed = true
  1133. end
  1134. end
  1135. break
  1136. end
  1137. end
  1138. self:Wipe()
  1139. self:UpdateDisplay(true)
  1140. end
  1141.  
  1142. function Skada:ReloadSettings()
  1143. -- Delete all existing windows in case of a profile change.
  1144. for i, win in ipairs(windows) do
  1145. win:destroy()
  1146. end
  1147. windows = {}
  1148.  
  1149. -- Re-create windows
  1150. -- As this can be called from a profile change as well as login, re-use windows when possible.
  1151. for i, win in ipairs(self.db.profile.windows) do
  1152. self:CreateWindow(win.name, win)
  1153. end
  1154.  
  1155. self.total = self.char.total
  1156.  
  1157. Skada:ClearAllIndexes()
  1158.  
  1159. -- Minimap button.
  1160. if icon and not icon:IsRegistered("Skada") then
  1161. icon:Register("Skada", dataobj, self.db.profile.icon)
  1162. end
  1163.  
  1164. self:RefreshMMButton()
  1165.  
  1166. self:ApplySettings()
  1167. end
  1168.  
  1169. -- Applies settings to things like the bar window.
  1170. function Skada:ApplySettings()
  1171. for i, win in ipairs(windows) do
  1172. win.display:ApplySettings(win)
  1173. end
  1174.  
  1175. -- Don't show window if we are solo, option.
  1176. -- Don't show window in a PvP instance, option.
  1177. if (self.db.profile.hidesolo and not IsInGroup()) or (self.db.profile.hidepvp and IsInPVP())then
  1178. self:SetActive(false)
  1179. else
  1180. self:SetActive(true)
  1181.  
  1182. -- Hide specific windows if window is marked as hidden (ie, if user manually hid the window, keep hiding it).
  1183. for i, win in ipairs(windows) do
  1184. if win.db.hidden and win:IsShown() then
  1185. win:Hide()
  1186. end
  1187. end
  1188. end
  1189.  
  1190. self:UpdateDisplay(true)
  1191. end
  1192.  
  1193. -- Set a data feed as selectedfeed.
  1194. function Skada:SetFeed(feed)
  1195. selectedfeed = feed
  1196. self:UpdateDisplay()
  1197. end
  1198.  
  1199. -- Iterates over all players in a set and adds to the "time" variable
  1200. -- the time between first and last action.
  1201. local function setPlayerActiveTimes(set)
  1202. for i, player in ipairs(set.players) do
  1203. if player.last then
  1204. player.time = player.time + (player.last - player.first)
  1205. end
  1206. end
  1207. end
  1208.  
  1209. -- Starts a new segment, saving the current one first.
  1210. -- Does nothing if we are out of combat.
  1211. -- Useful for multi-part fights where you want individual segments for each part.
  1212. function Skada:NewSegment()
  1213. if self.current then
  1214. self:EndSegment()
  1215. self:StartCombat()
  1216. end
  1217. end
  1218.  
  1219. local function IsRaidInCombat()
  1220. local type, count = Skada:GetGroupTypeAndCount()
  1221. if count > 0 then
  1222. for i = 1, count, 1 do
  1223. if UnitExists(type..i) and UnitAffectingCombat(type..i) then
  1224. return true
  1225. end
  1226. end
  1227. elseif UnitAffectingCombat("player") then
  1228. return true
  1229. end
  1230. end
  1231.  
  1232. -- Returns true if the party/raid/us are dead/ghost.
  1233. local function IsRaidDead()
  1234. local type, count = Skada:GetGroupTypeAndCount()
  1235. if count > 0 then
  1236. for i = 1, count, 1 do
  1237. if UnitExists(type..i) and not UnitIsDeadOrGhost(type..i) then
  1238. return false
  1239. end
  1240. end
  1241. elseif not UnitIsDeadOrGhost("player") then
  1242. return false
  1243. end
  1244. return true
  1245. end
  1246.  
  1247. -- Our scheme for segmenting fights:
  1248. -- Each second, if player is not in combat and is not dead and we have an active set (current),
  1249. -- check if anyone in raid is in combat; if so, close up shop.
  1250. -- We can not simply rely on PLAYER_REGEN_ENABLED since it is fired if we die and the fight continues.
  1251. function Skada:Tick()
  1252. if not disabled and self.current and not InCombatLockdown() and not IsRaidInCombat() then
  1253. self:Debug("EndSegment: Tick")
  1254. self:EndSegment()
  1255. end
  1256. end
  1257.  
  1258. function Skada:EndSegment()
  1259. -- Save current set unless this a trivial set, or if we have the Only keep boss fights options on, and no boss in fight.
  1260. -- A set is trivial if we have no mob name saved, or if total time for set is not more than 5 seconds.
  1261. if not self.db.profile.onlykeepbosses or self.current.gotboss then
  1262. if self.current.mobname ~= nil and time() - self.current.starttime > 5 then
  1263. -- End current set.
  1264. self.current.endtime = time()
  1265. self.current.time = self.current.endtime - self.current.starttime
  1266. setPlayerActiveTimes(self.current)
  1267.  
  1268. -- compute a count suffix for the set name
  1269. local setname = self.current.mobname
  1270. if self.db.profile.setnumber then
  1271. local max = 0
  1272. for _, set in ipairs(self.char.sets) do
  1273. if set.name == setname and max == 0 then
  1274. max = 1
  1275. else
  1276. local n,c = set.name:match("^(.-)%s*%((%d+)%)$")
  1277. if n == setname then max = math.max(max,tonumber(c) or 0) end
  1278. end
  1279. end
  1280. if max > 0 then
  1281. setname = setname .. " ("..(max+1)..")"
  1282. end
  1283. end
  1284. self.current.name = setname
  1285.  
  1286. -- Tell each mode that set has finished and do whatever it wants to do about it.
  1287. for i, mode in ipairs(modes) do
  1288. if mode.SetComplete ~= nil then
  1289. mode:SetComplete(self.current)
  1290. end
  1291. end
  1292.  
  1293. -- Add set to sets.
  1294. table.insert(self.char.sets, 1, self.current)
  1295. end
  1296. end
  1297.  
  1298. -- Make set last set.
  1299. self.last = self.current
  1300.  
  1301. -- Add time spent to total set as well.
  1302. self.total.time = self.total.time + self.current.time
  1303. setPlayerActiveTimes(self.total)
  1304.  
  1305. -- Set player.first and player.last to nil in total set.
  1306. -- Neccessary since first and last has no relevance over an entire raid.
  1307. -- Modes should look at the "time" value if available.
  1308. for i, player in ipairs(self.total.players) do
  1309. player.first = nil
  1310. player.last = nil
  1311. end
  1312.  
  1313. -- Reset current set.
  1314. self.current = nil
  1315.  
  1316. -- Find out number of non-persistent sets.
  1317. local numsets = 0
  1318. for i, set in ipairs(self.char.sets) do if not set.keep then numsets = numsets + 1 end end
  1319.  
  1320. -- Trim segments; don't touch persistent sets.
  1321. for i=table.maxn(self.char.sets), 1, -1 do
  1322. if numsets > self.db.profile.setstokeep and not self.char.sets[i].keep then
  1323. table.remove(self.char.sets, i)
  1324. numsets = numsets - 1
  1325. end
  1326. end
  1327.  
  1328. for i, win in ipairs(windows) do
  1329. -- win:Wipe()
  1330. -- changed = true
  1331.  
  1332. -- Wipe mode - switch to current set and specific mode if no party/raid members are alive.
  1333. -- Restore mode is not changed.
  1334. if win.db.wipemode ~= "" and IsRaidDead() then
  1335. self:RestoreView(win, "current", win.db.wipemode)
  1336. elseif win.db.returnaftercombat and win.restore_mode and win.restore_set then
  1337. -- Auto-switch back to previous set/mode.
  1338. if win.restore_set ~= win.selectedset or win.restore_mode ~= win.selectedmode then
  1339.  
  1340. self:RestoreView(win, win.restore_set, win.restore_mode)
  1341.  
  1342. win.restore_mode = nil
  1343. win.restore_set = nil
  1344. end
  1345. end
  1346.  
  1347. -- Hide in combat option.
  1348. if not win.db.hidden and self.db.profile.hidecombat then
  1349. win:Show()
  1350. end
  1351. end
  1352.  
  1353. self:UpdateDisplay(true) -- force required to update displays looking at older sets after insertion
  1354. if update_timer then self:CancelTimer(update_timer) end
  1355. if tick_timer then self:CancelTimer(tick_timer) end
  1356. update_timer, tick_timer = nil, nil
  1357. end
  1358.  
  1359. function Skada:PLAYER_REGEN_DISABLED()
  1360. -- Start a new set if we are not in one already.
  1361. if not disabled and not self.current then
  1362. self:Debug("StartCombat: PLAYER_REGEN_DISABLED")
  1363. self:StartCombat()
  1364. end
  1365. end
  1366.  
  1367. -- This flag is used to mark a possible combat start.
  1368. -- It is a count of captured events.
  1369. -- When we hit our treshold (let's say 5), combat starts.
  1370. -- If we have not hit our treshold after a certain time (let's say 3 seconds) combat start failed.
  1371. local tentative = nil
  1372.  
  1373. -- AceTimer handle for reverting combat start.
  1374. local tentativehandle= nil
  1375.  
  1376. function Skada:StartCombat()
  1377. -- Cancel cancelling combat if needed.
  1378. if tentativehandle ~= nil then
  1379. self:CancelTimer(tentativehandle)
  1380. tentativehandle = nil
  1381. end
  1382.  
  1383. if update_timer then
  1384. self:Debug("EndSegment: StartCombat")
  1385. self:EndSegment()
  1386. end
  1387.  
  1388. -- Remove old bars.
  1389. self:Wipe()
  1390.  
  1391. -- Create a new current set unless we are already have one (combat detection kicked in).
  1392. if not self.current then
  1393. self.current = createSet(L["Current"])
  1394. end
  1395.  
  1396. if self.encounterName and
  1397. GetTime() < (self.encounterTime or 0) + 15 then -- a recent ENCOUNTER_START named our segment
  1398. self:Debug("StartCombat setting encounterName from ENCOUNTER_START",self.encounterName)
  1399. self.current.mobname = self.encounterName
  1400. self.current.gotboss = true
  1401.  
  1402. self.encounterName = nil
  1403. self.encounterTime = nil
  1404. end
  1405.  
  1406. -- Also start the total set if it is nil.
  1407. if self.total == nil then
  1408. self.total = createSet(L["Total"])
  1409. self.char.total = self.total
  1410. end
  1411.  
  1412. -- Auto-switch set/mode if configured.
  1413. for i, win in ipairs(windows) do
  1414. if win.db.modeincombat ~= "" then
  1415. -- First, get the mode. The mode may not actually be available.
  1416. local mymode = find_mode(win.db.modeincombat)
  1417.  
  1418. -- If the mode exists, switch to current set and this mode. Save current set/mode so we can return after combat if configured.
  1419. if mymode ~= nil then
  1420. -- self:Print("Switching to "..mymode.name.." mode.")
  1421.  
  1422. if win.db.returnaftercombat then
  1423. if win.selectedset then
  1424. win.restore_set = win.selectedset
  1425. end
  1426. if win.selectedmode then
  1427. win.restore_mode = win.selectedmode:GetName()
  1428. end
  1429. end
  1430.  
  1431. win.selectedset = "current"
  1432. win:DisplayMode(mymode)
  1433. end
  1434. end
  1435.  
  1436. -- Hide in combat option.
  1437. if not win.db.hidden and self.db.profile.hidecombat then
  1438. win:Hide()
  1439. end
  1440. end
  1441.  
  1442. -- Force immediate update.
  1443. self:UpdateDisplay(true)
  1444.  
  1445. -- Schedule timers for updating windows and detecting combat end.
  1446. update_timer = self:ScheduleRepeatingTimer("UpdateDisplay", 0.5)
  1447. -- ticket 363: It is NOT safe to use ENCOUNTER_END to replace combat detection
  1448. tick_timer = self:ScheduleRepeatingTimer("Tick", 1)
  1449. end
  1450.  
  1451. -- Simply calls the same function on all windows.
  1452. function Skada:Wipe()
  1453. for i, win in ipairs(windows) do
  1454. win:Wipe()
  1455. end
  1456. end
  1457.  
  1458. -- Attempts to restore a view (set and mode).
  1459. -- Set is either the set name ("total", "current"), or an index.
  1460. -- Mode is the name of a mode.
  1461. function Skada:RestoreView(win, theset, themode)
  1462. -- Set the... set. If no such set exists, set to current.
  1463. if theset and type(theset) == "string" and (theset == "current" or theset == "total" or theset == "last") then
  1464. win.selectedset = theset
  1465. elseif theset and type(theset) == "number" and theset <= table.maxn(self.char.sets) then
  1466. win.selectedset = theset
  1467. else
  1468. win.selectedset = "current"
  1469. end
  1470.  
  1471. -- Force an update.
  1472. changed = true
  1473.  
  1474. -- Find the mode. The mode may not actually be available.
  1475. if themode then
  1476. local mymode = find_mode(themode)
  1477.  
  1478. -- If the mode exists, switch to this mode.
  1479. -- If not, show modes.
  1480. if mymode then
  1481. win:DisplayMode(mymode)
  1482. else
  1483. win:DisplayModes(win.selectedset)
  1484. end
  1485. else
  1486. win:DisplayModes(win.selectedset)
  1487. end
  1488. end
  1489.  
  1490. -- If set is "current", returns current set if we are in combat, otherwise returns the last set.
  1491. function Skada:find_set(s)
  1492. if s == "current" then
  1493. if Skada.current ~= nil then
  1494. return Skada.current
  1495. elseif Skada.last ~= nil then
  1496. return Skada.last
  1497. else
  1498. return self.char.sets[1]
  1499. end
  1500. elseif s == "total" then
  1501. return Skada.total
  1502. else
  1503. return self.char.sets[s]
  1504. end
  1505. end
  1506.  
  1507. function Skada:ClearIndexes(set)
  1508. if set then
  1509. set._playeridx = nil
  1510. end
  1511. end
  1512.  
  1513. function Skada:ClearAllIndexes()
  1514. -- clear indexes used for accelerating set lookups
  1515. -- this is done on login/logout to prevent the in-memory aliasing from becoming redundant tables on reload
  1516. Skada:ClearIndexes(self.current)
  1517. Skada:ClearIndexes(self.char.total)
  1518. for _,set in pairs(self.char.sets) do
  1519. Skada:ClearIndexes(set)
  1520. end
  1521. end
  1522.  
  1523. -- Returns a player from the current. Safe to use to simply view a player without creating an entry.
  1524. function Skada:find_player(set, playerid)
  1525. if set then
  1526. -- use a private index here for more efficient lookup
  1527. -- may eventually want to re-key .players by id but that would break external mods
  1528. set._playeridx = set._playeridx or {}
  1529. local player = set._playeridx[playerid]
  1530. if player then return player end
  1531. for i, p in ipairs(set.players) do
  1532. if p.id == playerid then
  1533. set._playeridx[playerid] = p
  1534. return p
  1535. end
  1536. end
  1537. end
  1538. end
  1539.  
  1540. -- Returns or creates a player in the current.
  1541. function Skada:get_player(set, playerid, playername)
  1542. -- Add player to set if it does not exist.
  1543. local player = Skada:find_player(set, playerid)
  1544.  
  1545. if not player then
  1546. -- If we do not supply a playername (often the case in submodes), we can not create an entry.
  1547. if not playername then
  1548. return
  1549. end
  1550.  
  1551. local _, playerClass = UnitClass(playername)
  1552. player = {id = playerid, class = playerClass, name = playername, first = time(), ["time"] = 0}
  1553.  
  1554. -- Tell each mode to apply its needed attributes.
  1555. for i, mode in ipairs(modes) do
  1556. if mode.AddPlayerAttributes ~= nil then
  1557. mode:AddPlayerAttributes(player, set)
  1558. end
  1559. end
  1560.  
  1561. -- Strip realm name
  1562. -- This is done after module processing due to cross-realm names messing with modules (death log for example, which needs to do UnitHealthMax on the playername).
  1563. local player_name, realm = string.split("-", playername, 2)
  1564. player.name = player_name or playername
  1565.  
  1566. table.insert(set.players, player)
  1567. end
  1568.  
  1569. if player.name == UNKNOWN and playername ~= UNKNOWN then -- fixup players created before we had their info
  1570. local player_name, realm = string.split("-", playername, 2)
  1571. player.name = player_name or playername
  1572. local _, playerClass = UnitClass(playername)
  1573. player.class = playerClass
  1574. end
  1575.  
  1576.  
  1577. -- The total set clears out first and last timestamps.
  1578. if not player.first then
  1579. player.first = time()
  1580. end
  1581.  
  1582. -- Mark now as the last time player did something worthwhile.
  1583. player.last = time()
  1584. changed = true
  1585. return player
  1586. end
  1587.  
  1588. local combatlogevents = {}
  1589. function Skada:RegisterForCL(func, event, flags)
  1590. if not combatlogevents[event] then
  1591. combatlogevents[event] = {}
  1592. end
  1593. tinsert(combatlogevents[event], {["func"] = func, ["flags"] = flags})
  1594. end
  1595.  
  1596. local band = bit.band
  1597. local PET_FLAGS = bit.bor(COMBATLOG_OBJECT_TYPE_PET, COMBATLOG_OBJECT_TYPE_GUARDIAN)
  1598. local RAID_FLAGS = bit.bor(COMBATLOG_OBJECT_AFFILIATION_MINE, COMBATLOG_OBJECT_AFFILIATION_PARTY, COMBATLOG_OBJECT_AFFILIATION_RAID)
  1599. -- The basic idea for CL processing:
  1600. -- Modules register for interest in a certain event, along with the function to call and the flags determining if the particular event is interesting.
  1601. -- On a new event, loop through the interested parties.
  1602. -- The flags are checked, and the flag value (say, that the SRC must be interesting, ie, one of the raid) is only checked once, regardless
  1603. -- of how many modules are interested in the event. The check is also only done on the first flag that requires it.
  1604. cleuFrame = CreateFrame("Frame") -- Dedicated event handler for a small performance improvement.
  1605. cleuFrame:SetScript("OnEvent", function(frame, event, timestamp, eventtype, hideCaster, srcGUID, srcName, srcFlags, srcRaidFlags, dstGUID, dstName, dstFlags, dstRaidFlags, ...)
  1606. local src_is_interesting = nil
  1607. local dst_is_interesting = nil
  1608.  
  1609. -- Optional tentative combat detection.
  1610. -- Instead of simply checking when we enter combat, combat start is also detected based on needing a certain
  1611. -- amount of interesting (as defined by our modules) CL events.
  1612. if not Skada.current and Skada.db.profile.tentativecombatstart and srcName and dstName and srcGUID ~= dstGUID and (eventtype == 'SPELL_DAMAGE' or eventtype == 'SPELL_BUILDING_DAMAGE' or eventtype == 'RANGE_DAMAGE' or eventtype == 'SWING_DAMAGE' or eventtype == 'SPELL_PERIODIC_DAMAGE') then
  1613. src_is_interesting = band(srcFlags, RAID_FLAGS) ~= 0 or (band(srcFlags, PET_FLAGS) ~= 0 and pets[srcGUID]) or players[srcGUID]
  1614. -- AWS: To avoid incoming periodic damage (e.g. from a debuff) triggering combat, we simply do not initialize
  1615. -- dst_is_interesting for periodic damage...
  1616. if eventtype ~= 'SPELL_PERIODIC_DAMAGE' then
  1617. dst_is_interesting = band(dstFlags, RAID_FLAGS) ~= 0 or (band(dstFlags, PET_FLAGS) ~= 0 and pets[dstGUID]) or players[dstGUID]
  1618. end
  1619. if src_is_interesting or dst_is_interesting then
  1620. -- Create a current set and set our "tentative" flag to true.
  1621. Skada.current = createSet(L["Current"])
  1622.  
  1623. -- Also create total set if needed.
  1624. if not Skada.total then
  1625. Skada.total = createSet(L["Total"])
  1626. end
  1627.  
  1628. -- Schedule an end to this tentative combat situation in 3 seconds.
  1629. tentativehandle = Skada:ScheduleTimer(
  1630. function()
  1631. tentative = nil
  1632. tentativehandle = nil
  1633. Skada.current = nil
  1634. --self:Print("tentative combat start FAILED!")
  1635. end, 1)
  1636.  
  1637. tentative = 0
  1638. --self:Print("tentative combat start INIT!")
  1639. end
  1640. end
  1641.  
  1642. if Skada.current and combatlogevents[eventtype] then
  1643. for i, mod in ipairs(combatlogevents[eventtype]) do
  1644. local fail = false
  1645.  
  1646. if mod.flags.src_is_interesting_nopets then
  1647. local src_is_interesting_nopets = (band(srcFlags, RAID_FLAGS) ~= 0 and band(srcFlags, PET_FLAGS) == 0) or players[srcGUID]
  1648. if src_is_interesting_nopets then
  1649. src_is_interesting = true
  1650. else
  1651. --self:Print("fail on src_is_interesting_nopets")
  1652. fail = true
  1653. end
  1654. end
  1655. if not fail and mod.flags.dst_is_interesting_nopets then
  1656. local dst_is_interesting_nopets = (band(dstFlags, RAID_FLAGS) ~= 0 and band(dstFlags, PET_FLAGS) == 0) or players[dstGUID]
  1657. if dst_is_interesting_nopets then
  1658. dst_is_interesting = true
  1659. else
  1660. --self:Print("fail on dst_is_interesting_nopets")
  1661. fail = true
  1662. end
  1663. end
  1664. if not fail and mod.flags.src_is_interesting or mod.flags.src_is_not_interesting then
  1665. if not src_is_interesting then
  1666. src_is_interesting = band(srcFlags, RAID_FLAGS) ~= 0 or (band(srcFlags, PET_FLAGS) ~= 0 and pets[srcGUID]) or players[srcGUID]
  1667. end
  1668. if mod.flags.src_is_interesting and not src_is_interesting then
  1669. --self:Print("fail on src_is_interesting")
  1670. fail = true
  1671. end
  1672. if mod.flags.src_is_not_interesting and src_is_interesting then
  1673. fail = true
  1674. end
  1675. end
  1676. if not fail and mod.flags.dst_is_interesting or mod.flags.dst_is_not_interesting then
  1677. if not dst_is_interesting then
  1678. dst_is_interesting = band(dstFlags, RAID_FLAGS) ~= 0 or (band(dstFlags, PET_FLAGS) ~= 0 and pets[dstGUID]) or players[dstGUID]
  1679. end
  1680. if mod.flags.dst_is_interesting and not dst_is_interesting then
  1681. --self:Print("fail on dst_is_interesting")
  1682. fail = true
  1683. end
  1684. if mod.flags.dst_is_not_interesting and dst_is_interesting then
  1685. fail = true
  1686. end
  1687. end
  1688.  
  1689. -- Pass along event if it did not fail our tests.
  1690. if not fail then
  1691. mod.func(timestamp, eventtype, srcGUID, srcName, srcFlags, dstGUID, dstName, dstFlags, ...)
  1692.  
  1693. -- If our "tentative" flag is set and reached the treshold, this means combat really did start.
  1694. if tentative ~= nil then
  1695. tentative = tentative + 1
  1696. if tentative == 5 then
  1697. Skada:CancelTimer(tentativehandle)
  1698. tentativehandle = nil
  1699. Skada:Debug("StartCombat: tentative combat")
  1700. Skada:StartCombat()
  1701. end
  1702. end
  1703. end
  1704.  
  1705. end
  1706. end
  1707.  
  1708. -- Note: relies on src_is_interesting having been checked.
  1709. if Skada.current and src_is_interesting and not Skada.current.gotboss then
  1710. -- Store mob name for set name. For now, just save first unfriendly name available, or first boss available.
  1711. if bit.band(dstFlags, COMBATLOG_OBJECT_REACTION_FRIENDLY) == 0 then
  1712. if not Skada.current.gotboss and boss.BossIDs[tonumber(dstGUID:sub(6, 10), 16)] then
  1713. Skada.current.mobname = dstName
  1714. Skada.current.gotboss = true
  1715. elseif not Skada.current.mobname then
  1716. Skada.current.mobname = dstName
  1717. end
  1718. end
  1719. end
  1720.  
  1721. -- Pet summons.
  1722. -- Pet scheme: save the GUID in a table along with the GUID of the owner.
  1723. -- Note to self: this needs 1) to be made self-cleaning so it can't grow too much, and 2) saved persistently.
  1724. -- Now also done on raid roster/party changes.
  1725. if eventtype == 'SPELL_SUMMON' and ( (band(srcFlags, RAID_FLAGS) ~= 0) or ( (band(srcFlags, PET_FLAGS)) ~= 0 ) or ((band(dstFlags, PET_FLAGS) ~= 0) and pets[dstGUID])) then
  1726. -- assign pet normally
  1727. pets[dstGUID] = {id = srcGUID, name = srcName}
  1728. if pets[srcGUID] then
  1729. -- the pets owner is a pet -> change it to the owner of the pet
  1730. -- this check may no longer be necessary?
  1731. pets[dstGUID].id = pets[srcGUID].id
  1732. pets[dstGUID].name = pets[srcGUID].name
  1733.  
  1734. end
  1735. end
  1736. end)
  1737.  
  1738. function Skada:AssignPet(ownerguid, ownername, petguid)
  1739. pets[petguid] = {id = ownerguid, name = ownername}
  1740. end
  1741.  
  1742. function Skada:ENCOUNTER_START(encounterId, encounterName)
  1743. self:Debug("ENCOUNTER_START", encounterId, encounterName)
  1744. if not disabled then
  1745. if self.current then -- already in combat, update the segment name
  1746. self.current.mobname = encounterName
  1747. self.current.gotboss = true
  1748. else -- we are not in combat yet
  1749. -- if we StartCombat here, the segment will immediately end by Tick
  1750. -- just save the encounter name for use when we enter combat
  1751. self.encounterName = encounterName
  1752. self.encounterTime = GetTime()
  1753. end
  1754. end
  1755. end
  1756.  
  1757. function Skada:ENCOUNTER_END(encounterId, encounterName)
  1758. self:Debug("ENCOUNTER_END", encounterId, encounterName)
  1759. if not disabled and self.current then
  1760. -- ticket 363: it is NOT safe to EndSegment here
  1761. if not self.current.gotboss then -- might have missed the bossname (eg d/c in combat)
  1762. self.current.mobname = encounterName
  1763. self.current.gotboss = true
  1764. end
  1765. end
  1766. end
  1767.  
  1768. --
  1769. -- Data broker
  1770. --
  1771.  
  1772. function dataobj:OnEnter()
  1773. GameTooltip:SetOwner(self, "ANCHOR_NONE")
  1774. GameTooltip:SetPoint("TOPLEFT", self, "BOTTOMLEFT")
  1775. GameTooltip:ClearLines()
  1776.  
  1777. local set
  1778. if Skada.current then
  1779. set = Skada.current
  1780. else
  1781. set = Skada.char.sets[1]
  1782. end
  1783. if set then
  1784. GameTooltip:AddLine(L["Skada summary"], 0, 1, 0)
  1785. for i, mode in ipairs(modes) do
  1786. if mode.AddToTooltip ~= nil then
  1787. mode:AddToTooltip(set, GameTooltip)
  1788. end
  1789. end
  1790. end
  1791.  
  1792. GameTooltip:AddLine(L["Hint: Left-Click to toggle Skada window."], 0, 1, 0)
  1793. GameTooltip:AddLine(L["Shift + Left-Click to reset."], 0, 1, 0)
  1794. GameTooltip:AddLine(L["Right-click to open menu"], 0, 1, 0)
  1795.  
  1796. GameTooltip:Show()
  1797. end
  1798.  
  1799. function dataobj:OnLeave()
  1800. GameTooltip:Hide()
  1801. end
  1802.  
  1803. function dataobj:OnClick(button)
  1804. if button == "LeftButton" and IsShiftKeyDown() then
  1805. Skada:Reset()
  1806. elseif button == "LeftButton" then
  1807. Skada:ToggleWindow()
  1808. elseif button == "RightButton" then
  1809. Skada:OpenMenu()
  1810. end
  1811. end
  1812.  
  1813. local totalbarcolor = {r = 0.2, g = 0.2, b = 0.5, a = 1}
  1814.  
  1815. function Skada:UpdateDisplay(force)
  1816. -- Force an update by setting our "changed" flag to true.
  1817. if force then
  1818. changed = true
  1819. end
  1820.  
  1821. -- Update data feed.
  1822. -- This is done even if our set has not changed, since for example DPS changes even though the data does not.
  1823. -- Does not update feed text if nil.
  1824. if selectedfeed ~= nil then
  1825. local feedtext = selectedfeed()
  1826. if feedtext then
  1827. dataobj.text = feedtext
  1828. end
  1829. end
  1830.  
  1831. for i, win in ipairs(windows) do
  1832. if (changed or win.changed or self.current) then
  1833. win.changed = false
  1834. if win.selectedmode then -- Force mode display for display systems which do not handle navigation.
  1835.  
  1836. local set = win:get_selected_set()
  1837.  
  1838. if set then
  1839. -- Inform window that a data update will take place.
  1840. win:UpdateInProgress()
  1841.  
  1842. -- Let mode update data.
  1843. if win.selectedmode.Update then
  1844. win.selectedmode:Update(win, set)
  1845. else
  1846. self:Print("Mode "..win.selectedmode:GetName().." does not have an Update function!")
  1847. end
  1848.  
  1849. -- Add a total bar using the mode summaries optionally.
  1850. if self.db.profile.showtotals and win.selectedmode.GetSetSummary then
  1851. local total = 0
  1852. local existing = nil
  1853. for i, data in ipairs(win.dataset) do
  1854. if data.id then
  1855. total = total + data.value
  1856. end
  1857. if not existing and not data.id then
  1858. existing = data
  1859. end
  1860. end
  1861. total = total + 1
  1862.  
  1863. local d = existing or {}
  1864. d.valuetext = win.selectedmode:GetSetSummary(set)
  1865. d.value = total
  1866. d.label = L["Total"]
  1867. d.icon = dataobj.icon
  1868. d.id = "total"
  1869. d.ignore = true
  1870. if not existing then
  1871. table.insert(win.dataset, 1, d)
  1872. end
  1873. end
  1874.  
  1875. end
  1876.  
  1877. -- Let window display the data.
  1878. win:UpdateDisplay()
  1879.  
  1880. elseif win.selectedset then
  1881. local set = win:get_selected_set()
  1882.  
  1883. -- View available modes.
  1884. for i, mode in ipairs(modes) do
  1885.  
  1886. local d = win.dataset[i] or {}
  1887. win.dataset[i] = d
  1888.  
  1889. d.id = mode:GetName()
  1890. d.label = mode:GetName()
  1891. d.value = 1
  1892. if set and mode.GetSetSummary ~= nil then
  1893. d.valuetext = mode:GetSetSummary(set)
  1894. end
  1895. end
  1896.  
  1897. -- Tell window to sort by our data order. Our modes are in alphabetical order already.
  1898. win.metadata.ordersort = true
  1899.  
  1900. -- Let window display the data.
  1901. win:UpdateDisplay()
  1902. else
  1903. -- View available sets.
  1904. local nr = 1
  1905. local d = win.dataset[nr] or {}
  1906. win.dataset[nr] = d
  1907.  
  1908. d.id = "total"
  1909. d.label = L["Total"]
  1910. d.value = 1
  1911.  
  1912. nr = nr + 1
  1913. local d = win.dataset[nr] or {}
  1914. win.dataset[nr] = d
  1915.  
  1916. d.id = "current"
  1917. d.label = L["Current"]
  1918. d.value = 1
  1919.  
  1920. for i, set in ipairs(self.char.sets) do
  1921. nr = nr + 1
  1922. local d = win.dataset[nr] or {}
  1923. win.dataset[nr] = d
  1924.  
  1925. d.id = tostring(set.starttime)
  1926. d.label, d.valuetext = select(2,Skada:GetSetLabel(set))
  1927. d.value = 1
  1928. if set.keep then
  1929. d.emphathize = true
  1930. end
  1931. end
  1932.  
  1933. win.metadata.ordersort = true
  1934.  
  1935. -- Let window display the data.
  1936. win:UpdateDisplay()
  1937. end
  1938.  
  1939. end
  1940. end
  1941.  
  1942. -- Mark as unchanged.
  1943. changed = false
  1944. end
  1945.  
  1946. --[[
  1947.  
  1948. API
  1949. Everything below this is OK to use in modes.
  1950.  
  1951. --]]
  1952.  
  1953. function Skada:GetSets()
  1954. return self.char.sets
  1955. end
  1956.  
  1957. function Skada:GetModes()
  1958. return modes
  1959. end
  1960.  
  1961. -- Formats a number into human readable form.
  1962. function Skada:FormatNumber(number)
  1963. if number then
  1964. if self.db.profile.numberformat == 1 then
  1965. if number > 1000000 then
  1966. return ("%02.2fM"):format(number / 1000000)
  1967. else
  1968. return ("%02.1fK"):format(number / 1000)
  1969. end
  1970. else
  1971. return math.floor(number)
  1972. end
  1973. end
  1974. end
  1975.  
  1976. local function scan_for_columns(mode)
  1977. -- Only process if not already scanned.
  1978. if not mode.scanned then
  1979. mode.scanned = true
  1980.  
  1981. -- Add options for this mode if available.
  1982. if mode.metadata and mode.metadata.columns then
  1983. Skada:AddColumnOptions(mode)
  1984. end
  1985.  
  1986. -- Scan any linked modes.
  1987. if mode.metadata then
  1988. if mode.metadata.click1 then
  1989. scan_for_columns(mode.metadata.click1)
  1990. end
  1991. if mode.metadata.click2 then
  1992. scan_for_columns(mode.metadata.click2)
  1993. end
  1994. if mode.metadata.click3 then
  1995. scan_for_columns(mode.metadata.click3)
  1996. end
  1997. end
  1998. end
  1999. end
  2000.  
  2001. -- Register a mode.
  2002. function Skada:AddMode(mode)
  2003. -- Ask mode to verify our sets.
  2004. -- Needed in case we enable a mode and we have old data.
  2005. if self.total then
  2006. verify_set(mode, self.total)
  2007. end
  2008. if self.current then
  2009. verify_set(mode, self.current)
  2010. end
  2011. for i, set in ipairs(self.char.sets) do
  2012. verify_set(mode, set)
  2013. end
  2014.  
  2015. table.insert(modes, mode)
  2016.  
  2017. -- Set this mode as the active mode if it matches the saved one.
  2018. -- Bit of a hack.
  2019. for i, win in ipairs(windows) do
  2020. if mode:GetName() == win.db.mode then
  2021. self:RestoreView(win, win.db.set, mode:GetName())
  2022. end
  2023. end
  2024.  
  2025. -- Find if we now have our chosen feed.
  2026. -- Also a bit ugly.
  2027. if selectedfeed == nil and self.db.profile.feed ~= "" then
  2028. for name, feed in pairs(feeds) do
  2029. if name == self.db.profile.feed then
  2030. self:SetFeed(feed)
  2031. end
  2032. end
  2033. end
  2034.  
  2035. -- Add column configuration if available.
  2036. if mode.metadata then
  2037. scan_for_columns(mode)
  2038. end
  2039.  
  2040. -- Sort modes.
  2041. table.sort(modes, function(a, b) return a.name < b.name end)
  2042.  
  2043. -- Remove all bars and start over to get ordering right.
  2044. -- Yes, this all sucks - the problem with this and the above is that I don't know when
  2045. -- all modules are loaded. :/
  2046. for i, win in ipairs(windows) do
  2047. win:Wipe()
  2048. end
  2049. changed = true
  2050. end
  2051.  
  2052. -- Unregister a mode.
  2053. function Skada:RemoveMode(mode)
  2054. table.remove(modes, mode)
  2055. end
  2056.  
  2057. function Skada:GetFeeds()
  2058. return feeds
  2059. end
  2060.  
  2061. -- Register a data feed.
  2062. function Skada:AddFeed(name, func)
  2063. feeds[name] = func
  2064. end
  2065.  
  2066. -- Unregister a data feed.
  2067. function Skada:RemoveFeed(name, func)
  2068. for i, feed in ipairs(feeds) do
  2069. if feed.name == name then
  2070. table.remove(feeds, i)
  2071. end
  2072. end
  2073. end
  2074.  
  2075. --[[
  2076.  
  2077. Sets
  2078.  
  2079. --]]
  2080.  
  2081. function Skada:GetSetTime(set)
  2082. if set.time then
  2083. return set.time
  2084. else
  2085. return (time() - set.starttime)
  2086. end
  2087. end
  2088.  
  2089. -- Returns the time (in seconds) a player has been active for a set.
  2090. function Skada:PlayerActiveTime(set, player)
  2091. local maxtime = 0
  2092.  
  2093. -- Add recorded time (for total set)
  2094. if player.time > 0 then
  2095. maxtime = player.time
  2096. end
  2097.  
  2098. -- Add in-progress time if set is not ended.
  2099. if not set.endtime and player.first then
  2100. maxtime = maxtime + player.last - player.first
  2101. end
  2102. return maxtime
  2103. end
  2104.  
  2105. -- Modify objects if they are pets.
  2106. -- Expects to find "playerid", "playername", and optionally "spellname" in the object.
  2107. -- Playerid and playername are exchanged for the pet owner's, and spellname is modified to include pet name.
  2108. function Skada:FixPets(action)
  2109. if action and action.playername then
  2110. local pet = pets[action.playerid]
  2111. if pet then
  2112.  
  2113. if (self.db.profile.mergepets) then
  2114. if action.spellname then
  2115. action.spellname = action.playername..": "..action.spellname
  2116. end
  2117. action.playername = pet.name
  2118. action.playerid = pet.id
  2119. else
  2120. action.playername = pet.name..": "..action.playername
  2121. -- create a unique ID for each player for each type of pet
  2122. local petMobID=action.playerid:sub(6,10); -- Get Pet creature ID
  2123. action.playerid = pet.id .. petMobID; -- just append it to the pets owner id
  2124. end
  2125.  
  2126. else
  2127.  
  2128. -- Fix for guardians; requires "playerflags" to be set from CL.
  2129. -- This only works for one self. Other player's guardians are all lumped into one.
  2130. if action.playerflags and bit.band(action.playerflags, COMBATLOG_OBJECT_TYPE_GUARDIAN) ~= 0 then
  2131. if bit.band(action.playerflags, COMBATLOG_OBJECT_AFFILIATION_MINE) ~=0 then
  2132. if action.spellname then
  2133. action.spellname = action.playername..": "..action.spellname
  2134. end
  2135. action.playername = UnitName("player")
  2136. action.playerid = UnitGUID("player")
  2137. else
  2138. -- Nothing decent in place here yet. Modify guid so that there will only be 1 similar entry at least.
  2139. action.playerid = action.playername
  2140. end
  2141. end
  2142.  
  2143. end
  2144. end
  2145. end
  2146.  
  2147. function Skada:SetTooltipPosition(tooltip, frame)
  2148. local p = self.db.profile.tooltippos
  2149. if p == "default" then
  2150. tooltip:SetOwner(UIParent, "ANCHOR_NONE")
  2151. tooltip:SetPoint("BOTTOMRIGHT", "UIParent", "BOTTOMRIGHT", -40, 40);
  2152. elseif p == "topleft" then
  2153. tooltip:SetOwner(frame, "ANCHOR_NONE")
  2154. tooltip:SetPoint("TOPRIGHT", frame, "TOPLEFT")
  2155. elseif p == "topright" then
  2156. tooltip:SetOwner(frame, "ANCHOR_NONE")
  2157. tooltip:SetPoint("TOPLEFT", frame, "TOPRIGHT")
  2158. end
  2159. end
  2160.  
  2161. -- Same thing, only takes two arguments and returns two arguments.
  2162. function Skada:FixMyPets(playerGUID, playerName)
  2163. local pet = pets[playerGUID]
  2164. if pet then
  2165. return pet.id, pet.name
  2166. end
  2167. -- No pet match - return the player.
  2168. return playerGUID, playerName
  2169. end
  2170.  
  2171. -- Format value text in a standardized way. Up to 3 value and boolean (show/don't show) combinations are accepted.
  2172. -- Values are rendered from left to right.
  2173. -- Idea: "compile" a function on the fly instead and store in mode for re-use.
  2174. function Skada:FormatValueText(...)
  2175. local value1, bool1, value2, bool2, value3, bool3 = ...
  2176.  
  2177. -- This construction is a little silly.
  2178. if bool1 and bool2 and bool3 then
  2179. return value1.." ("..value2..", "..value3..")"
  2180. elseif bool1 and bool2 then
  2181. return value1.." ("..value2..")"
  2182. elseif bool1 and bool3 then
  2183. return value1.." ("..value3..")"
  2184. elseif bool2 and bool3 then
  2185. return value2.." ("..value3..")"
  2186. elseif bool2 then
  2187. return value2
  2188. elseif bool1 then
  2189. return value1
  2190. elseif bool3 then
  2191. return value3
  2192. end
  2193. end
  2194.  
  2195. local function value_sort(a,b)
  2196. if not a or a.value == nil then
  2197. return false
  2198. elseif not b or b.value == nil then
  2199. return true
  2200. else
  2201. return a.value > b.value
  2202. end
  2203. end
  2204.  
  2205. function Skada.valueid_sort(a,b)
  2206. if not a or a.value == nil or a.id == nil then
  2207. return false
  2208. elseif not b or b.value == nil or b.id == nil then
  2209. return true
  2210. else
  2211. return a.value > b.value
  2212. end
  2213. end
  2214.  
  2215. -- Tooltip display. Shows subview data for a specific row.
  2216. -- Using a fake window, the subviews are asked to populate the window's dataset normally.
  2217. local ttwin = Window:new()
  2218. local white = {r = 1, g = 1, b = 1}
  2219. function Skada:AddSubviewToTooltip(tooltip, win, mode, id, label)
  2220. -- Clean dataset.
  2221. wipe(ttwin.dataset)
  2222.  
  2223. -- Tell mode we are entering our real window.
  2224. mode:Enter(win, id, label)
  2225.  
  2226. -- Ask mode to populate dataset in our fake window.
  2227. mode:Update(ttwin, win:get_selected_set())
  2228.  
  2229. -- Sort dataset unless we are using ordersort.
  2230. if not mode.metadata or not mode.metadata.ordersort then
  2231. table.sort(ttwin.dataset, value_sort)
  2232. end
  2233.  
  2234. -- Show title and data if we have data.
  2235. if #ttwin.dataset > 0 then
  2236. tooltip:AddLine(mode.title or mode:GetName(), 1,1,1)
  2237.  
  2238. -- Display the top X, default 3, rows.
  2239. local nr = 0
  2240. for i, data in ipairs(ttwin.dataset) do
  2241. if data.id and nr < Skada.db.profile.tooltiprows then
  2242. nr = nr + 1
  2243.  
  2244. local color = white
  2245. if data.color then
  2246. -- Explicit color from dataset.
  2247. color = data.color
  2248. elseif data.class then
  2249. -- Class color.
  2250. local color = Skada.classcolors[data.class]
  2251. end
  2252.  
  2253. local label = data.label
  2254. if mode.metadata and mode.metadata.showspots then
  2255. label = nr..". "..label
  2256. end
  2257. tooltip:AddDoubleLine(label, data.valuetext, color.r, color.g, color.b)
  2258. end
  2259. end
  2260.  
  2261. -- Add an empty line.
  2262. tooltip:AddLine(" ")
  2263. end
  2264. end
  2265.  
  2266. do
  2267. --[[ XXX TEMP UPGRADE POPUP ]]
  2268. local tempPopup = function()
  2269. local tbl = {
  2270. SkadaCC = true,
  2271. SkadaDamage = true,
  2272. SkadaDamageTaken = true,
  2273. SkadaDeaths = true,
  2274. SkadaDebuffs = true,
  2275. SkadaDispels = true,
  2276. SkadaEnemies = true,
  2277. SkadaHealing = true,
  2278. SkadaPower = true,
  2279. SkadaThreat = true,
  2280. }
  2281.  
  2282. local create
  2283. local concat = "\n"
  2284. for i = 1, GetNumAddOns() do
  2285. local name = GetAddOnInfo(i)
  2286. if tbl[name] then
  2287. create = true
  2288. concat = concat .. name .. "\n"
  2289. DisableAddOn(i)
  2290. end
  2291. end
  2292.  
  2293. if create or not SkadaDB.hasUpgraded then
  2294. local frame = CreateFrame("Frame", "SkadaWarn", UIParent)
  2295.  
  2296. frame:SetBackdrop({bgFile = "Interface\\DialogFrame\\UI-DialogBox-Background-Dark",
  2297. edgeFile = "Interface\\DialogFrame\\UI-DialogBox-Border",
  2298. tile = true, tileSize = 16, edgeSize = 16,
  2299. insets = {left = 1, right = 1, top = 1, bottom = 1}}
  2300. )
  2301. frame:SetSize(550, 420)
  2302. frame:SetPoint("CENTER", UIParent, "CENTER")
  2303. frame:SetFrameStrata("DIALOG")
  2304. frame:Show()
  2305.  
  2306. local title = frame:CreateFontString(nil, "ARTWORK", "GameFontNormalHuge")
  2307. title:SetPoint("TOP", frame, "TOP", 0, -12)
  2308. title:SetText(L["Skada has changed!"])
  2309.  
  2310. local text = frame:CreateFontString(nil, "ARTWORK", "ChatFontNormal")
  2311. text:SetPoint("CENTER", frame, "CENTER")
  2312. text:SetText(L["All Skada functionality is now in 1 addon folder."] .. (create and "\n\n" .. L["Skada will |cFFFF0000NOT|r function properly until you delete the following AddOns:"] ..concat or ""))
  2313.  
  2314. local btn = CreateFrame("Button", nil, frame, "UIPanelButtonTemplate")
  2315. btn:SetWidth(110)
  2316. btn:SetHeight(20)
  2317. btn:SetPoint("BOTTOM", frame, "BOTTOM", 0, 8)
  2318. btn:SetText(OKAY)
  2319. btn:SetScript("OnClick", function(f)
  2320. f:GetParent():Hide()
  2321. if not create then
  2322. InterfaceOptionsFrame_OpenToCategory(Skada.optionsFrame) InterfaceOptionsFrame_OpenToCategory(Skada.optionsFrame)
  2323. end
  2324. end)
  2325.  
  2326. local ending = frame:CreateFontString(nil, "ARTWORK", "ChatFontNormal")
  2327. ending:SetPoint("TOP", btn, "TOP", 0, 30)
  2328. ending:SetText(create and "" or L["Click below and configure your '|cFFFF0000Disabled Modules|r'."])
  2329. if not create then
  2330. SkadaDB.hasUpgraded = true
  2331. end
  2332. end
  2333. end
  2334.  
  2335. function Skada:OnInitialize()
  2336. -- XXX temp
  2337. self:ScheduleTimer(tempPopup, 1)
  2338.  
  2339. -- Register some SharedMedia goodies.
  2340. media:Register("font", "Adventure", [[Interface\Addons\Skada\fonts\Adventure.ttf]])
  2341. media:Register("font", "ABF", [[Interface\Addons\Skada\fonts\ABF.ttf]])
  2342. media:Register("font", "Vera Serif", [[Interface\Addons\Skada\fonts\VeraSe.ttf]])
  2343. media:Register("font", "Diablo", [[Interface\Addons\Skada\fonts\Avqest.ttf]])
  2344. media:Register("font", "Accidental Presidency", [[Interface\Addons\Skada\fonts\Accidental Presidency.ttf]])
  2345. media:Register("statusbar", "Aluminium", [[Interface\Addons\Skada\statusbar\Aluminium]])
  2346. media:Register("statusbar", "Armory", [[Interface\Addons\Skada\statusbar\Armory]])
  2347. media:Register("statusbar", "BantoBar", [[Interface\Addons\Skada\statusbar\BantoBar]])
  2348. media:Register("statusbar", "Glaze2", [[Interface\Addons\Skada\statusbar\Glaze2]])
  2349. media:Register("statusbar", "Gloss", [[Interface\Addons\Skada\statusbar\Gloss]])
  2350. media:Register("statusbar", "Graphite", [[Interface\Addons\Skada\statusbar\Graphite]])
  2351. media:Register("statusbar", "Grid", [[Interface\Addons\Skada\statusbar\Grid]])
  2352. media:Register("statusbar", "Healbot", [[Interface\Addons\Skada\statusbar\Healbot]])
  2353. media:Register("statusbar", "LiteStep", [[Interface\Addons\Skada\statusbar\LiteStep]])
  2354. media:Register("statusbar", "Minimalist", [[Interface\Addons\Skada\statusbar\Minimalist]])
  2355. media:Register("statusbar", "Otravi", [[Interface\Addons\Skada\statusbar\Otravi]])
  2356. media:Register("statusbar", "Outline", [[Interface\Addons\Skada\statusbar\Outline]])
  2357. media:Register("statusbar", "Perl", [[Interface\Addons\Skada\statusbar\Perl]])
  2358. media:Register("statusbar", "Smooth", [[Interface\Addons\Skada\statusbar\Smooth]])
  2359. media:Register("statusbar", "Round", [[Interface\Addons\Skada\statusbar\Round]])
  2360. media:Register("statusbar", "TukTex", [[Interface\Addons\Skada\statusbar\normTex]])
  2361.  
  2362. -- Some sounds (copied from Omen).
  2363. media:Register("sound", "Rubber Ducky", [[Sound\Doodad\Goblin_Lottery_Open01.wav]])
  2364. media:Register("sound", "Cartoon FX", [[Sound\Doodad\Goblin_Lottery_Open03.wav]])
  2365. media:Register("sound", "Explosion", [[Sound\Doodad\Hellfire_Raid_FX_Explosion05.wav]])
  2366. media:Register("sound", "Shing!", [[Sound\Doodad\PortcullisActive_Closed.wav]])
  2367. media:Register("sound", "Wham!", [[Sound\Doodad\PVP_Lordaeron_Door_Open.wav]])
  2368. media:Register("sound", "Simon Chime", [[Sound\Doodad\SimonGame_LargeBlueTree.wav]])
  2369. media:Register("sound", "War Drums", [[Sound\Event Sounds\Event_wardrum_ogre.wav]])
  2370. media:Register("sound", "Cheer", [[Sound\Event Sounds\OgreEventCheerUnique.wav]])
  2371. media:Register("sound", "Humm", [[Sound\Spells\SimonGame_Visual_GameStart.wav]])
  2372. media:Register("sound", "Short Circuit", [[Sound\Spells\SimonGame_Visual_BadPress.wav]])
  2373. media:Register("sound", "Fel Portal", [[Sound\Spells\Sunwell_Fel_PortalStand.wav]])
  2374. media:Register("sound", "Fel Nova", [[Sound\Spells\SeepingGaseous_Fel_Nova.wav]])
  2375. media:Register("sound", "You Will Die!", [[Sound\Creature\CThun\CThunYouWillDie.wav]])
  2376.  
  2377. -- DB
  2378. self.db = LibStub("AceDB-3.0"):New("SkadaDB", self.defaults, "Default")
  2379. if type(SkadaPerCharDB) ~= "table" then SkadaPerCharDB = {} end
  2380. self.char = SkadaPerCharDB
  2381. self.char.sets = self.char.sets or {}
  2382. LibStub("AceConfigRegistry-3.0"):RegisterOptionsTable("Skada", self.options, true)
  2383. self.optionsFrame = LibStub("AceConfigDialog-3.0"):AddToBlizOptions("Skada", "Skada")
  2384.  
  2385. -- Profiles
  2386. LibStub("AceConfigRegistry-3.0"):RegisterOptionsTable("Skada-Profiles", LibStub("AceDBOptions-3.0"):GetOptionsTable(self.db), true)
  2387. self.profilesFrame = LibStub("AceConfigDialog-3.0"):AddToBlizOptions("Skada-Profiles", "Profiles", "Skada")
  2388.  
  2389. -- Dual spec profiles
  2390. if lds then
  2391. lds:EnhanceDatabase(self.db, "SkadaDB")
  2392. lds:EnhanceOptions(LibStub("AceDBOptions-3.0"):GetOptionsTable(self.db), self.db)
  2393. end
  2394.  
  2395. -- Slash Handler
  2396. SLASH_SKADA1 = "/skada"
  2397. SlashCmdList.SKADA = slashHandler
  2398.  
  2399. self.db.RegisterCallback(self, "OnProfileChanged", "ReloadSettings")
  2400. self.db.RegisterCallback(self, "OnProfileCopied", "ReloadSettings")
  2401. self.db.RegisterCallback(self, "OnProfileReset", "ReloadSettings")
  2402. self.db.RegisterCallback(self, "OnDatabaseShutdown", "ClearAllIndexes")
  2403.  
  2404. -- Migrate old settings.
  2405. if self.db.profile.barmax then
  2406. self:Print("Migrating old settings somewhat gracefully. This should only happen once.")
  2407. self.db.profile.barmax = nil
  2408. self.db.profile.background.height = 200
  2409. end
  2410. if self.db.profile.total then
  2411. self.db.profile.current = nil
  2412. self.db.profile.total = nil
  2413. self.db.profile.sets = nil
  2414. end
  2415.  
  2416. -- XXX temp
  2417. self.db.profile.modulesToSkip = nil
  2418. end
  2419. end
  2420.  
  2421. function Skada:OnEnable()
  2422. self:ReloadSettings()
  2423.  
  2424. cleuFrame:RegisterEvent("COMBAT_LOG_EVENT_UNFILTERED")
  2425.  
  2426. popup:RegisterEvent("PLAYER_ENTERING_WORLD")
  2427. popup:RegisterEvent("ZONE_CHANGED_NEW_AREA")
  2428. popup:RegisterEvent("GROUP_ROSTER_UPDATE")
  2429. popup:RegisterEvent("UNIT_PET")
  2430. popup:RegisterEvent("PLAYER_REGEN_DISABLED")
  2431.  
  2432. popup:RegisterEvent("PET_BATTLE_OPENING_START")
  2433. popup:RegisterEvent("PET_BATTLE_CLOSE")
  2434.  
  2435. popup:RegisterEvent("ENCOUNTER_START")
  2436. popup:RegisterEvent("ENCOUNTER_END")
  2437.  
  2438. if type(CUSTOM_CLASS_COLORS) == "table" then
  2439. Skada.classcolors = CUSTOM_CLASS_COLORS
  2440. end
  2441.  
  2442. if self.moduleList then
  2443. for i = 1, #self.moduleList do
  2444. self.moduleList[i](self, L)
  2445. end
  2446. self.moduleList = nil
  2447. end
  2448.  
  2449. -- Instead of listening for callbacks on SharedMedia we simply wait a few seconds and then re-apply settings
  2450. -- to catch any missing media. Lame? Yes.
  2451. self:ScheduleTimer("ApplySettings", 2)
  2452. end
  2453.  
  2454. function Skada:AddLoadableModule(name, func)
  2455. if not self.moduleList then self.moduleList = {} end
  2456. self.moduleList[#self.moduleList+1] = func
  2457. self:AddLoadableModuleCheckbox(name, L[name])
  2458. end
  2459.  
  2460.  
  2461. -- A minimal mode showing test data. Used by the config.
  2462. --[[
  2463. local testmod = {
  2464. name = "Test",
  2465. Update = function(self, win, set)
  2466. for i=1,i<10,1 do
  2467. local d = win.dataset[nr] or {}
  2468. win.dataset[nr] = d
  2469. d.value = math.random(100)
  2470. d.label = "Test"
  2471. d.class = math
  2472. d.id = player.id
  2473. d.valuetext = tostring(player.dispells)
  2474. end
  2475. end
  2476. }
  2477. --]]
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement