diff CombatLog.lua @ 57:01b63b8ed811 v21

total rewrite to version 21
author yellowfive
date Fri, 05 Jun 2015 11:05:15 -0700
parents
children cf2b6b9a8337
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/CombatLog.lua	Fri Jun 05 11:05:15 2015 -0700
@@ -0,0 +1,564 @@
+local Amr = LibStub("AceAddon-3.0"):GetAddon("AskMrRobot")
+local L = LibStub("AceLocale-3.0"):GetLocale("AskMrRobot", true)
+local AceGUI = LibStub("AceGUI-3.0")
+
+local _btnToggle = nil
+local _panelUndoWipe = nil
+local _chkAutoAll = nil
+local _autoChecks = nil
+
+local function createDifficultyCheckBox(instanceId, difficultyId)
+	local chk = AceGUI:Create("AmrUiCheckBox")
+	chk:SetText(L.DifficultyNames[difficultyId])
+	chk:SetCallback("OnClick", function(widget)
+		Amr:ToggleAutoLog(instanceId, difficultyId)
+	end)
+	
+	_autoChecks[instanceId][difficultyId] = chk
+	return chk
+end
+
+-- render a group of controls for auto-logging of a raid zone
+local function renderAutoLogSection(instanceId, container)
+	_autoChecks[instanceId] = {}
+	
+	local lbl = AceGUI:Create("AmrUiLabel")
+	lbl:SetWidth(200)
+	lbl:SetText(L.InstanceNames[instanceId])
+	lbl:SetFont(Amr.CreateFont("Regular", 20, Amr.Colors.White))
+	container:AddChild(lbl)
+	
+	local line = AceGUI:Create("AmrUiPanel")
+	line:SetHeight(1)
+	line:SetBackgroundColor(Amr.Colors.White)
+	line:SetPoint("TOPLEFT", lbl.frame, "BOTTOMLEFT", 1, -7)
+	line:SetPoint("TOPRIGHT", lbl.frame, "BOTTOMRIGHT", 0, -7)
+	container:AddChild(line)
+	
+	local chkMythic = createDifficultyCheckBox(instanceId, Amr.Difficulties.Mythic)
+	chkMythic:SetPoint("TOPLEFT", line.frame, "BOTTOMLEFT", 0, -8)
+	container:AddChild(chkMythic)
+	
+	local chkNormal = createDifficultyCheckBox(instanceId, Amr.Difficulties.Normal)
+	chkNormal:SetPoint("TOPLEFT", line.frame, "BOTTOMLEFT", 0, -30)
+	container:AddChild(chkNormal)
+	
+	-- find the widest of mythic/normal
+	local w = math.max(chkMythic:GetWidth(), chkNormal:GetWidth())
+	
+	local chkHeroic = createDifficultyCheckBox(instanceId, Amr.Difficulties.Heroic)
+	chkHeroic:SetPoint("TOPLEFT", line.frame, "BOTTOMLEFT", w + 20, -8)
+	container:AddChild(chkHeroic)
+	
+	local chkLfr = createDifficultyCheckBox(instanceId, Amr.Difficulties.Lfr)
+	chkLfr:SetPoint("TOPLEFT", line.frame, "BOTTOMLEFT", w + 20, -30)
+	container:AddChild(chkLfr)
+	
+	return lbl
+end
+
+-- renders the main UI for the Combat Log tab
+function Amr:RenderTabLog(container)
+
+	-- main commands
+	_btnToggle = AceGUI:Create("AmrUiButton")
+	_btnToggle:SetText(L.LogButtonStartText)
+	_btnToggle:SetBackgroundColor(Amr.Colors.Green)
+	_btnToggle:SetFont(Amr.CreateFont("Bold", 16, Amr.Colors.White))
+	_btnToggle:SetWidth(200)
+	_btnToggle:SetHeight(26)
+	_btnToggle:SetPoint("TOPLEFT", container.content, "TOPLEFT", 0, -40)
+	_btnToggle:SetCallback("OnClick", function() Amr:ToggleLogging() end)
+	container:AddChild(_btnToggle)
+	
+	_lblLogging = AceGUI:Create("AmrUiLabel")
+	_lblLogging:SetText(L.LogNote)
+	_lblLogging:SetWidth(200)	
+	_lblLogging:SetFont(Amr.CreateFont("Italic", 14, Amr.Colors.BrightGreen))
+	_lblLogging:SetJustifyH("MIDDLE")
+	_lblLogging:SetPoint("TOP", _btnToggle.frame, "BOTTOM", 0, -5)
+	container:AddChild(_lblLogging)
+	
+	local btnReload = AceGUI:Create("AmrUiButton")
+	btnReload:SetText(L.LogButtonReloadText)
+	btnReload:SetBackgroundColor(Amr.Colors.Blue)
+	btnReload:SetFont(Amr.CreateFont("Bold", 16, Amr.Colors.White))
+	btnReload:SetWidth(200)
+	btnReload:SetHeight(26)
+	btnReload:SetPoint("TOPLEFT", _btnToggle.frame, "TOPRIGHT", 40, 0)
+	btnReload:SetCallback("OnClick", ReloadUI)
+	container:AddChild(btnReload)
+	
+	local lbl = AceGUI:Create("AmrUiLabel")
+	lbl:SetText(L.LogReloadNote)
+	lbl:SetWidth(200)	
+	lbl:SetFont(Amr.CreateFont("Italic", 14, Amr.Colors.TextTan))
+	lbl:SetJustifyH("MIDDLE")
+	lbl:SetPoint("TOP", btnReload.frame, "BOTTOM", 0, -5)
+	container:AddChild(lbl)
+	
+	-- container for undo wipe so we can hide/show it all
+	_panelUndoWipe = AceGUI:Create("AmrUiPanel")
+	_panelUndoWipe:SetLayout("None")
+	_panelUndoWipe:SetBackgroundColor(Amr.Colors.Black, 0)
+	_panelUndoWipe:SetPoint("TOPLEFT", lbl.frame, "BOTTOMLEFT", 0, -40)
+	container:AddChild(_panelUndoWipe)
+	
+	local btnUndoWipe = AceGUI:Create("AmrUiButton")
+	btnUndoWipe:SetText(L.LogButtonUndoWipeText)
+	btnUndoWipe:SetBackgroundColor(Amr.Colors.Orange)
+	btnUndoWipe:SetFont(Amr.CreateFont("Bold", 16, Amr.Colors.White))
+	btnUndoWipe:SetWidth(200)
+	btnUndoWipe:SetHeight(26)
+	btnUndoWipe:SetPoint("TOPLEFT", lbl.frame, "BOTTOMLEFT", 0, -40)
+	btnUndoWipe:SetCallback("OnClick", function() Amr:UndoWipe() end)
+	_panelUndoWipe:AddChild(btnUndoWipe)
+	
+	lbl = AceGUI:Create("AmrUiLabel")
+	lbl:SetText(L.LogUndoWipeNote)
+	lbl:SetWidth(200)	
+	lbl:SetFont(Amr.CreateFont("Italic", 14, Amr.Colors.TextTan))
+	lbl:SetJustifyH("MIDDLE")
+	lbl:SetPoint("TOP", btnUndoWipe.frame, "BOTTOM", 0, -5)
+	_panelUndoWipe:AddChild(lbl)
+	
+	local lbl2 = AceGUI:Create("AmrUiLabel")
+	lbl2:SetText(L.LogUndoWipeDate(date("%B %d", time()), date("%I:%M %p", time())))
+	lbl2:SetWidth(200)	
+	lbl2:SetFont(Amr.CreateFont("Italic", 14, Amr.Colors.TextTan))
+	lbl2:SetJustifyH("MIDDLE")
+	lbl2:SetPoint("TOP", lbl.frame, "BOTTOM", 0, -2)
+	_panelUndoWipe:AddChild(lbl2)
+	
+	local btnWipe = AceGUI:Create("AmrUiButton")
+	btnWipe:SetText(L.LogButtonWipeText)
+	btnWipe:SetBackgroundColor(Amr.Colors.Orange)
+	btnWipe:SetFont(Amr.CreateFont("Bold", 16, Amr.Colors.White))
+	btnWipe:SetWidth(200)
+	btnWipe:SetHeight(26)
+	btnWipe:SetPoint("TOPRIGHT", btnUndoWipe.frame, "TOPLEFT", -40, 0)
+	btnWipe:SetCallback("OnClick", function() Amr:Wipe() end)
+	container:AddChild(btnWipe)
+
+	lbl = AceGUI:Create("AmrUiLabel")
+	lbl:SetText(L.LogWipeNote)
+	lbl:SetWidth(200)	
+	lbl:SetFont(Amr.CreateFont("Italic", 14, Amr.Colors.TextTan))
+	lbl:SetJustifyH("MIDDLE")
+	lbl:SetPoint("TOP", btnWipe.frame, "BOTTOM", 0, -5)
+	container:AddChild(lbl)
+	
+	lbl2 = AceGUI:Create("AmrUiLabel")
+	lbl2:SetText(L.LogWipeNote2("/amr wipe"))
+	lbl2:SetWidth(200)	
+	lbl2:SetFont(Amr.CreateFont("Italic", 14, Amr.Colors.TextTan))
+	lbl2:SetJustifyH("MIDDLE")
+	lbl2:SetPoint("TOP", lbl.frame, "BOTTOM", 0, -2)
+	container:AddChild(lbl2)
+	
+	-- auto-logging controls
+	lbl = AceGUI:Create("AmrUiLabel")
+	lbl:SetWidth(600)
+	lbl:SetText(L.LogAutoTitle)
+	lbl:SetFont(Amr.CreateFont("Bold", 24, Amr.Colors.TextHeaderActive))
+	lbl:SetPoint("TOPLEFT", lbl2.frame, "BOTTOMLEFT", 0, -40)
+	container:AddChild(lbl)
+	
+	_chkAutoAll = AceGUI:Create("AmrUiCheckBox")
+	_chkAutoAll:SetText(L.LogAutoAllText)
+	_chkAutoAll:SetPoint("TOPLEFT", lbl.frame, "BOTTOMLEFT", 1, -15)
+	_chkAutoAll:SetCallback("OnClick", function(widget) Amr:ToggleAllAutoLog() end)
+	container:AddChild(_chkAutoAll)
+	
+	_autoChecks = {}
+	
+	-- go through all supported instances, rendering in a left->right pattern, 2 per row
+	local autoSections = {}
+	for i, instanceId in ipairs(Amr.InstanceIdsOrdered) do
+		local autoSection = renderAutoLogSection(instanceId, container)
+		if i == 1 then
+			autoSection:SetPoint("TOPLEFT", _chkAutoAll.frame, "BOTTOMLEFT", -1, -15)
+		elseif i % 2 == 0 then
+			autoSection:SetPoint("TOPLEFT", autoSections[i - 1].frame, "TOPRIGHT", 40, 0)
+		else
+			autoSection:SetPoint("TOPLEFT", autoSections[i - 2].frame, "BOTTOMLEFT", 0, -15)
+		end
+		
+		table.insert(autoSections, autoSection)
+	end
+	autoSections = nil
+	
+	-- instructions
+	lbl = AceGUI:Create("AmrUiLabel")
+	lbl:SetText(L.LogInstructionsTitle)
+	lbl:SetWidth(480)	
+	lbl:SetFont(Amr.CreateFont("Italic", 24, Amr.Colors.Text))
+	lbl:SetPoint("TOPRIGHT", container.content, "TOPRIGHT", 0, -40)
+	container:AddChild(lbl)
+	
+	lbl2 = AceGUI:Create("AmrUiLabel")
+	lbl2:SetText(L.LogInstructions)
+	lbl2:SetWidth(480)	
+	lbl2:SetFont(Amr.CreateFont("Italic", 14, Amr.Colors.Text))
+	lbl2:SetPoint("TOPLEFT", lbl.frame, "BOTTOMLEFT", 0, -10)
+	container:AddChild(lbl2)
+	
+	-- initialize state of controls
+	Amr:RefreshLogUi()
+end
+
+function Amr:ReleaseTabLog()
+	_btnToggle = nil
+	_panelUndoWipe = nil
+	_chkAutoAll = nil
+	_autoChecks = nil
+end
+
+local function isAllAutoLoggingEnabled()
+	-- see if all auto-logging options are enabled
+	local allChecked = true
+	for i, instanceId in ipairs(Amr.InstanceIdsOrdered) do
+		for k, difficultyId in pairs(Amr.Difficulties) do
+			if not Amr.db.profile.Logging.Auto[instanceId][difficultyId] then
+				allChecked = false
+				break
+			end
+		end
+		if not allChecked then break end
+	end
+	
+	return allChecked
+end
+
+-- check current zone and auto-logging settings, and enable logging if appropriate
+local function updateAutoLogging(force)
+	
+	-- get the info about the instance
+	local zone, _, difficultyId, _, _, _, _, instanceId = GetInstanceInfo()
+
+	if not force and zone == Amr.db.char.Logging.LastZone and difficultyId == Amr.db.char.Logging.LastDiff then
+	  -- do nothing if the zone hasn't actually changed, otherwise we may override the user's manual enable/disable
+		return
+	end
+
+	Amr.db.char.Logging.LastZone = zone
+	Amr.db.char.Logging.LastDiff = difficultyId
+
+	if Amr.IsSupportedInstanceId(instanceId) and Amr.db.profile.Logging.Auto[tonumber(instanceId)][tonumber(difficultyId)] then
+		-- we are in a supported zone that we want to auto-log, turn logging on 
+		-- (supported check is probably redundant, but just in case someone has old settings lying around)
+		if not Amr:IsLogging() then
+			Amr:StartLogging()
+		end
+	else
+		-- not in a zone that we want to auto-log, turn logging off
+		if Amr:IsLogging() then
+			Amr:StopLogging()
+		end
+	end
+end
+
+-- refresh the state of the tab based on current settings
+function Amr:RefreshLogUi()
+	if not _btnToggle then return end
+	
+	-- set state of logging button based on whether it is on or off
+	if self:IsLogging() then
+		_btnToggle:SetBackgroundColor(Amr.Colors.Red)
+		_btnToggle:SetText(L.LogButtonStopText)
+	else
+		_btnToggle:SetBackgroundColor(Amr.Colors.Green)
+		_btnToggle:SetText(L.LogButtonStartText)
+	end
+	
+	_lblLogging:SetVisible(self:IsLogging())
+	
+	-- hide/show undo wipe button based on whether a wipe has been called recently
+	_panelUndoWipe:SetVisible(Amr.db.char.Logging.LastWipe and true or false)
+	
+	local all = isAllAutoLoggingEnabled()
+	_chkAutoAll:SetChecked(all)
+	
+	for i, instanceId in ipairs(Amr.InstanceIdsOrdered) do
+		for k, difficultyId in pairs(Amr.Difficulties) do
+			_autoChecks[instanceId][difficultyId]:SetChecked(Amr.db.profile.Logging.Auto[instanceId][difficultyId])
+		end
+	end
+end
+
+function Amr:IsLogging()
+	return Amr.db.char.Logging.Enabled
+end
+
+function Amr:ToggleLogging()
+	if not Amr.db.char.Logging.Enabled then
+		Amr:StartLogging()
+	else
+		Amr:StopLogging()
+	end
+end
+
+function Amr:StartLogging()
+
+	local now = time()
+	local oldDuration = 60 * 60 * 24 * 10
+	
+	-- prune out entries in log data that are more than 10 days old
+
+	-- player data
+	local playerData = Amr.db.global.Logging.PlayerData
+	if playerData then
+		for name, timeList in pairs(playerData) do
+			for timestamp, dataString in pairs(timeList) do
+				if difftime(now, tonumber(timestamp)) > oldDuration then
+					timeList[timestamp] = nil
+				end
+			end
+			
+			if next(timeList) == nil then
+				playerData[name] = nil
+			end
+		end
+	end
+	
+	-- same idea with extra info (auras, pets, whatever we end up adding to it)
+	local extraData = Amr.db.global.Logging.PlayerExtras
+	if extraData then
+		for name, timeList in pairs(extraData) do
+			for timestamp, dataString in pairs(timeList) do
+				if difftime(now, tonumber(timestamp)) > oldDuration then
+					timeList[timestamp] = nil
+				end
+			end
+			
+			if next(timeList) == nil then
+				extraData[name] = nil
+			end
+		end
+	end
+
+	-- delete wipes that are more than 10 days old
+	if Amr.db.global.Logging.Wipes then
+		local wipes = Amr.db.global.Logging.Wipes
+		local i = 1
+		while i <= #wipes do
+			local t = wipes[i]
+			if difftime(now, t) > oldDuration then
+		        table.remove(wipes, i)
+		    else
+		        i = i + 1
+		    end
+		end
+	end
+
+	-- delete the last wipe date if it is more than 10 days old
+	if Amr.db.char.Logging.LastWipe and difftime(now, Amr.db.char.Logging.LastWipe) > oldDuration then
+		Amr.db.char.Logging.LastWipe = nil
+	end
+
+	-- always enable advanced combat logging via our addon, gathers more detailed data for better analysis
+	SetCVar("advancedCombatLogging", 1)
+	LoggingCombat(true)
+	Amr.db.char.Logging.Enabled = true
+	
+	self:Print(L.LogChatStart)
+	
+	self:UpdateMinimap()
+	self:RefreshLogUi()
+end
+
+function Amr:StopLogging()
+	
+	LoggingCombat(false)
+	Amr.db.char.Logging.Enabled = false
+	
+	self:Print(L.LogChatStop)
+	
+	self:UpdateMinimap()
+	self:RefreshLogUi()
+end
+
+function Amr:Wipe()
+	local t = time()
+	table.insert(Amr.db.global.Logging.Wipes, t)
+	Amr.db.char.Logging.LastWipe = t
+
+	self:Print(L.LogChatWipe(date('%I:%M %p', t)))
+
+	self:RefreshLogUi()
+end
+
+function Amr:UndoWipe()
+
+	local t = Amr.db.char.Logging.LastWipe
+	local wipes = Amr.db.global.Logging.Wipes
+	
+	if not t then
+		self:Print(L.LogChatNoWipes)
+	else
+		-- find this wipe and remove it, may not be the last one if this person is raiding on multiple characters
+		for i = #wipes, 1, -1 do
+			if wipes[i] == t then
+				table.remove(wipes, i)
+				break
+			end			
+		end
+		
+		Amr.db.char.Logging.LastWipe = nil
+		self:Print(L.LogChatUndoWipe(date('%I:%M %p', t)))
+	end
+	
+	self:RefreshLogUi()
+end
+
+function Amr:ToggleAutoLog(instanceId, difficultyId)
+
+	local byDiff = Amr.db.profile.Logging.Auto[instanceId]	
+	byDiff[difficultyId] = not byDiff[difficultyId]
+	
+	self:RefreshLogUi()
+	
+	-- see if we should turn logging on right now	
+	updateAutoLogging(true)
+end
+
+function Amr:ToggleAllAutoLog()
+	
+	local val = not isAllAutoLoggingEnabled()
+	
+	for i, instanceId in ipairs(Amr.InstanceIdsOrdered) do
+		for k, difficultyId in pairs(Amr.Difficulties) do
+			Amr.db.profile.Logging.Auto[instanceId][difficultyId] = val
+		end
+	end
+	
+	self:RefreshLogUi()
+	
+	-- see if we should turn logging on right now	
+	updateAutoLogging(true)
+end
+
+function Amr:ProcessPlayerSnapshot(msg)
+	if not self:IsLogging() then return end
+	
+    -- message will be of format: timestamp\nregion\nrealm\nname\n[stuff]
+    local parts = {}
+	for part in string.gmatch(msg, "([^\n]+)") do
+		table.insert(parts, part)
+	end
+    
+    local timestamp = tonumber(parts[1])
+    local name = parts[2] .. ":" .. parts[3] .. ":" .. parts[4]
+    local setup = parts[5]
+
+	-- initialize the player's table
+	local playerList = Amr.db.global.Logging.PlayerData[name]
+	if not playerList then
+		playerList = {}
+		Amr.db.global.Logging.PlayerData[name] = playerList
+	end
+
+	-- find the most recent setup already recorded for this player
+	local previousSetup = nil
+	local previousTime = 0
+	for t, v in pairs(playerList) do
+		if t > previousTime then
+			previousSetup = v
+			previousTime = t
+		end
+	end
+	
+	-- if the previous setup is more than 12 hours old, don't consider it
+	if previousSetup and difftime(timestamp, previousTime) > 60 * 60 * 12 then
+		previousSetup = nil
+	end
+	
+	-- we only need to keep this setup if it is different than the previous
+	if setup ~= previousSetup then
+		playerList[timestamp] = setup
+	end
+	
+end
+
+-- read auras and pet mapping info (pet may not be necessary anymore... but doesn't hurt)
+local function getPlayerExtraData(data, unitId, petId)
+
+	local guid = UnitGUID(unitId)
+	if guid == nil then return end
+	
+	local fields = {}
+
+	local buffs = {}
+    for i=1,40 do
+    	local _,_,_,count,_,_,_,_,_,_,spellId = UnitAura(unitId, i, "HELPFUL")
+    	table.insert(buffs, spellId)
+    end
+	if not buffs or #buffs == 0 then
+		table.insert(fields, "_")
+	else
+		table.insert(fields, Amr.Serializer:ToCompressedNumberList(buffs))
+	end
+
+	local petGuid = UnitGUID(petId)
+	if petGuid then
+		table.insert(fields, guid .. "," .. petGuid)
+    else
+		table.insert(fields, '_')
+	end
+
+	local name = GetUnitName(unitId, true) -- GetRaidRosterInfo(rosterIndex)
+    local realm = GetRealmName()
+    local region = Amr.RegionNames[GetCurrentRegion()]
+    local splitPos = string.find(name, "-")
+    if splitPos ~= nil then
+        realm = string.sub(name, splitPos + 1)
+        name = string.sub(name, 1, splitPos - 1)
+    end
+
+	data[region .. ":" .. realm .. ":" .. name] = table.concat(fields, ";")
+end
+
+local function logPlayerExtraData()
+	if not Amr:IsLogging() or not Amr:IsSupportedInstance() then return end
+
+	local timestamp = time()
+    local units = {}
+    local petUnits = {}
+    
+    if IsInRaid() then
+        for i = 1,40 do
+            table.insert(units, "raid" .. i)
+            table.insert(petUnits, "raidpet" .. i)
+        end
+    elseif IsInGroup() then
+        table.insert(units, "player")
+        table.insert(petUnits, "pet")
+        for i = 1,4 do
+            table.insert(units, "party" .. i)
+            table.insert(petUnits, "partypet" .. i)
+        end
+    else
+        return
+    end
+
+	local data = {}
+    for i = 1,#units do
+        getPlayerExtraData(data, units[i], petUnits[i])
+    end
+    
+	for name, val in pairs(data) do
+		-- record aura stuff, we never check for duplicates, need to know it at each point in time
+		if Amr.db.global.Logging.PlayerExtras[name] == nil then
+			Amr.db.global.Logging.PlayerExtras[name] = {}
+		end
+		Amr.db.global.Logging.PlayerExtras[name][timestamp] = val
+	end
+end
+
+function Amr:InitializeCombatLog()
+	updateAutoLogging()
+end
+
+Amr:AddEventHandler("UPDATE_INSTANCE_INFO", updateAutoLogging)
+Amr:AddEventHandler("PLAYER_DIFFICULTY_CHANGED", updateAutoLogging)
+Amr:AddEventHandler("PLAYER_REGEN_DISABLED", logPlayerExtraData)