InfamousX

Skada.lua

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