--------------------------------------- --- Author: Ketho (EU-Boulderfist) --- --- Created: 2011.02.25 --- --- License: Public Domain --- --- Version: v0.27 --- --------------------------------------- -- http://wow.curse.com/downloads/wow-addons/details/simpleding.aspx -- http://www.wowinterface.com/downloads/info19479-SimpleDing.html local VERSION = 0.27 local FILETYPE = "Release" SimpleDing = LibStub("AceAddon-3.0"):NewAddon("SimpleDing", "AceEvent-3.0", "AceTimer-3.0", "AceConsole-3.0") local SD = SimpleDing local ACR = LibStub("AceConfigRegistry-3.0") local LDB = LibStub("LibDataBroker-1.1") local _G = _G local gsub, time = gsub, time local GetGuildRosterInfo = GetGuildRosterInfo local profile, char local playerLevel = UnitLevel("player") local playerDinged local TPM_total, TPM_current = 0, 0 -- event vars local TPM_total2, TPM_current2 -- backup event vars local levelTime -- accurate time on Levelup local currentTime, totalTime = 0, 0 -- estimated Time local filterPlayed -- used for filtering SimpleDing's /played requests local isStopwatch -- eligible for using the Blizzard Stopwatch local lastPlayed = time() -- timestamp of last /played request local function AddedTime() return time() - lastPlayed end --------------- --- Options --- --------------- local defaults = {profile = {dingMessage = "Ding! Level [LEVEL] in [TIME]"}} local options = { type = "group", name = "SimpleDing |cffB6CA00v"..VERSION.."|r", args = { inlineGroup = { type = "group", name = " ", order = 1, inline = true, args = { GuildAnnounce = { type = "toggle", order = 1, descStyle = "", name = "|TInterface\\Icons\\Ability_Warrior_RallyingCry:16:16:1:0:64:64:4:60:4:60|t |cff40FF40"..GUILD.."|r "..CHAT_ANNOUNCE, get = function() return profile.GuildAnnounce end, set = function(i, v) profile.GuildAnnounce = v end, }, Screenshot = { type = "toggle", order = 2, descStyle = "", name = "|TInterface\\Icons\\inv_misc_spyglass_03:16:16:1:0:64:64:4:60:4:60|t "..BINDING_NAME_SCREENSHOT, get = function() return profile.Screenshot end, set = function(i, v) profile.Screenshot = v end, }, newline = {type = "description", order = 3, name = ""}, GuildMemberDings = { type = "toggle", order = 4, descStyle = "", name = "|TInterface\\GuildFrame\\GuildLogo-NoLogo:16:16:1:0:64:64:14:51:14:51|t |cff40FF40"..GUILD.."|r Dings", get = function(i) return profile.GuildMemberDings end, set = function(i, v) profile.GuildMemberDings = v end, }, Stopwatch = { type = "toggle", order = 5, desc = TIMEMANAGER_SHOW_STOPWATCH, name = "|TInterface\\Icons\\Spell_Holy_BorrowedTime:16:16:2:0:64:64:4:60:4:60|t "..STOPWATCH_TITLE, get = function(i) return profile.Stopwatch end, set = function(i, v) profile.Stopwatch = v if v then if isStopwatch then StopwatchFrame:Show() StopwatchTicker.timer = currentTime Stopwatch_Play() end else Stopwatch_Clear() StopwatchFrame:Hide() end end, }, }, }, DingMessage = { type = "input", order = 2, width = "full", name = " ", usage = "\n|cffADFF2F[LEVEL]|r |cffFFFFFF= New Level|r\n|cff71D5FF[TIME]|r |cffFFFFFF= Level Time|r\n|cff71D5FF[TOTAL]|r |cffFFFFFF= Total Time|r", get = function(i) return profile.dingMessage end, set = function(i, v) profile.dingMessage = v if #strtrim(v) == 0 then profile.dingMessage = defaults.profile.dingMessage end end, }, Example = { type = "description", order = 3, name = function() return " "..SD:ReplaceText(profile.dingMessage, true) end, }, }, } function SD:OnInitialize() self.db = LibStub("AceDB-3.0"):New("SimpleDingDB", defaults, true) profile, char = self.db.profile, self.db.char ACR:RegisterOptionsTable("SimpleDing", options) SD.optionsFrame = LibStub("AceConfigDialog-3.0"):AddToBlizOptions("SimpleDing", "SimpleDing") self:RegisterChatCommand("sd", "SlashCmd") self:RegisterChatCommand("simpleding", "SlashCmd") self.db.global.version = VERSION self.db.global.fileType = FILETYPE char.levelTime = char.levelTime or {} char.totalTime = char.totalTime or {} end function SD:OnEnable() self:RegisterEvent("PLAYER_LEVEL_UP") self:RegisterEvent("TIME_PLAYED_MSG") self:RegisterEvent("GUILD_ROSTER_UPDATE") if profile.Stopwatch and playerLevel < 85 then StopwatchFrame:Show() StopwatchTicker.timer = TPM_current + AddedTime() Stopwatch_Play() end self:ScheduleTimer(function() if TPM_total == 0 then filterPlayed = true RequestTimePlayed() end end, 5) self:ScheduleRepeatingTimer(function() GuildRoster() end, 11) self:ScheduleRepeatingTimer(function() currentTime = TPM_current + AddedTime() totalTime = TPM_total + AddedTime() isStopwatch = playerLevel < 85 and currentTime < MAX_TIMER_SEC end, 1) end function SD:SlashCmd() InterfaceOptionsFrame_OpenToCategory(self.optionsFrame) end function SD:PLAYER_LEVEL_UP(event, level) playerLevel = level playerDinged, filterPlayed = true, true RequestTimePlayed() end function SD:TIME_PLAYED_MSG(event, ...) TPM_total, TPM_current = ... lastPlayed = time() if playerDinged then playerDinged = false local prevTime = char.totalTime[playerLevel-1] if prevTime then levelTime = TPM_total - prevTime else -- fall back to less accurate data levelTime = TPM_current2 + (TPM_total - TPM_total2) end char.levelTime[playerLevel] = levelTime char.totalTime[playerLevel] = TPM_total -- announce stuff local text = SD:ReplaceText(profile.dingMessage) SendChatMessage(text, GetNumPartyMembers() > 0 and "PARTY" or "SAY") if profile.GuildAnnounce and IsInGuild() then SendChatMessage(text, "GUILD") end if profile.Screenshot then self:ScheduleTimer(function() Screenshot() end, 1) end -- temporarily pause Stopwatch if profile.Stopwatch and playerLevel < 85 and TPM_current < MAX_TIMER_SEC then Stopwatch_Pause() self:ScheduleTimer(function() StopwatchTicker.timer = TPM_current + AddedTime() Stopwatch_Play() end, 30) end else if profile.Stopwatch then if playerLevel < 85 and TPM_current < MAX_TIMER_SEC then -- currentTime var isn't updated yet StopwatchTicker.timer = TPM_current else Stopwatch_Clear() StopwatchFrame:Hide() end end end -- update stuff TPM_total2, TPM_current2 = TPM_total, TPM_current if InterfaceOptionsFrame:IsShown() then currentTime = TPM_current + AddedTime() totalTime = TPM_total + AddedTime() ACR:NotifyChange("SimpleDing") end end ----------------------- --- Time Formatting --- ----------------------- -- not capitalized local D_SECONDS = strlower(D_SECONDS) local D_MINUTES = strlower(D_MINUTES) local D_HOURS = strlower(D_HOURS) local D_DAYS = strlower(D_DAYS) -- except for German locale if GetLocale() == "deDE" then D_SECONDS = _G.D_SECONDS D_MINUTES = _G.D_MINUTES D_HOURS = _G.D_HOURS D_DAYS = _G.D_DAYS end local b = CreateFrame("Button") local function TimetoString(value) local seconds = mod(floor(value), 60) local minutes = mod(floor(value/60), 60) local hours = mod(floor(value/3600), 24) local days = floor(value/86400) local fseconds = b:GetText(b:SetFormattedText(D_SECONDS, seconds)) local fminutes = b:GetText(b:SetFormattedText(D_MINUTES, minutes)) local fhours = b:GetText(b:SetFormattedText(D_HOURS, hours)) local fdays = b:GetText(b:SetFormattedText(D_DAYS, days)) if value >= 86400 then return hours > 0 and format("%s, %s", fdays, fhours) or fdays elseif value >= 3600 then return minutes > 0 and format("%s, %s", fhours, fminutes) or fhours elseif value >= 60 then return seconds > 0 and format("%s, %s", fminutes, fseconds) or fminutes elseif value >= 0 then return fseconds end end ----------------------- --- Substitute Text --- ----------------------- function SD:ReplaceText(msg, example) if not msg then return "[ERROR] No Message" end if example then msg = msg:gsub("%[[Ll][Ee][Vv][Ee][Ll]%]", "|cffADFF2F"..(playerLevel == GetMaxPlayerLevel() and "[Max Level]" or playerLevel + 1).."|r") msg = msg:gsub("%[[Tt][Ii][Mm][Ee]%]", "|cff71D5FF"..TimetoString(currentTime).."|r") msg = msg:gsub("%[[Tt][Oo][Tt][Aa][Ll]%]", "|cff71D5FF"..TimetoString(totalTime).."|r") else msg = msg:gsub("%[[Ll][Ee][Vv][Ee][Ll]%]", playerLevel) msg = msg:gsub("%[[Tt][Ii][Mm][Ee]%]", TimetoString(levelTime)) msg = msg:gsub("%[[Tt][Oo][Tt][Aa][Ll]%]", TimetoString(TPM_total)) end return msg end -------------------------- --- Guild Member Dings --- -------------------------- local cache = {} local function GetClassColor(class) if cache[class] then return cache[class] else local classColorTable = RAID_CLASS_COLORS[class] cache[class] = format("%02X%02X%02X", classColorTable.r*255, classColorTable.g*255, classColorTable.b*255) return cache[class] end end local cd = 0 local guild = {} local msgcolor = {r=1, g=1, b=1} local playerName = UnitName("player") function SD:GUILD_ROSTER_UPDATE() if time() > cd then -- throttle cd = time() + 2 if IsInGuild() and profile.GuildMemberDings then for i = 1, GetNumGuildMembers() do local name, _, _, level, _, _, _, _, _, _, englishClass = GetGuildRosterInfo(i) -- sanity checks if name and guild[name] and level > guild[name] and name ~= playerName then RaidNotice_AddMessage(RaidWarningFrame, format("|cff%s%s|r dinged %s |cffADFF2F%s|r", GetClassColor(englishClass), name, LEVEL, level), msgcolor) end guild[name] = level end end end end -------------------------------- --- Military Time Formatting --- -------------------------------- local function TimetoMilitaryTime(value) local seconds = mod(floor(value), 60) local minutes = mod(floor(value/60), 60) local hours = mod(floor(value/3600), 24) local days = floor(value/86400) if days > 0 then return format("%s:%02.f:%02.f:%02.f", days, hours, minutes, seconds) elseif hours > 0 then return format("%s:%02.f:%02.f", hours, minutes, seconds) else return format("%02.f:%02.f ", minutes, seconds) end end ---------------------- --- Filter /played --- ---------------------- local oldChatFrame_DisplayTimePlayed = ChatFrame_DisplayTimePlayed function ChatFrame_DisplayTimePlayed(...) -- using /played manually should still work if not filterPlayed then oldChatFrame_DisplayTimePlayed(...) end filterPlayed = false end --------------------- --- LibDataBroker --- --------------------- local TIME_PLAYED_TOTAL_TEXT = gsub(TIME_PLAYED_TOTAL, "%%s", "") local TIME_PLAYED_LEVEL_TEXT = gsub(TIME_PLAYED_LEVEL, "%%s", "") local function TooltipXPline() local curxp = UnitXP("player") local maxxp = UnitXPMax("player") return format("|cffADFF2F%d|r / |cff71D5FF%d|r = |cffFFFFFF%d%%|r", curxp, maxxp, (curxp/maxxp)*100) end local dataobject = { type = playerLevel < 85 and "data source" or "launcher", icon = "Interface\\Icons\\Spell_Holy_BorrowedTime", OnClick = function(clickedframe, button) if InterfaceOptionsFrame:IsShown() and strfind(InterfaceOptionsFramePanelContainer.displayedPanel.name, "SimpleDing") then InterfaceOptionsFrame:Hide() else InterfaceOptionsFrame_OpenToCategory(SD.optionsFrame) end end, OnTooltipShow = function(tt) tt:AddLine("|cffADFF2FSimpleDing|r") tt:AddDoubleLine(EXPERIENCE_COLON, TooltipXPline()) tt:AddDoubleLine(TIME_PLAYED_LEVEL_TEXT, format("|cffFFFFFF"..TIME_DAYHOURMINUTESECOND.."|r", unpack( {ChatFrame_TimeBreakDown(currentTime)} ))) tt:AddDoubleLine(TIME_PLAYED_TOTAL_TEXT, format("|cffFFFFFF"..TIME_DAYHOURMINUTESECOND.."|r", unpack( {ChatFrame_TimeBreakDown(TPM_total + AddedTime())} ))) tt:AddLine("|cffFFFFFFClick|r to open the options menu") end, } if playerLevel < 85 then SD:ScheduleRepeatingTimer(function() dataobject.text = TimetoMilitaryTime(currentTime) end, 1) else dataobject.text = "SimpleDing" end LDB:NewDataObject("SimpleDing", dataobject)