Quantcast
local L = LibStub("AceLocale-3.0"):GetLocale("Skada", false)

local Skada = Skada

local mod = Skada:NewModule(L["Damage"])
local dpsmod = Skada:NewModule(L["DPS"])
local playermod = Skada:NewModule(L["Damage spell list"])
local spellmod = Skada:NewModule(L["Damage spell details"])
local damagedmod = Skada:NewModule(L["Damaged mobs"])

local function getDPS(set, player)
	local totaltime = Skada:PlayerActiveTime(set, player)

	return player.damage / math.max(1,totaltime)
end

local function getRaidDPS(set)
	if set.time > 0 then
		return set.damage / math.max(1, set.time)
	else
		local endtime = set.endtime
		if not endtime then
			endtime = time()
		end
		return set.damage / math.max(1, endtime - set.starttime)
	end
end

local function log_damage(set, dmg)
	-- Get the player.
	local player = Skada:get_player(set, dmg.playerid, dmg.playername)
	if player then

		-- Subtract overkill
--		local amount = math.max(0,dmg.amount - dmg.overkill)
		-- Or don't. Seems to be the way other meters do it.
		local amount = dmg.amount
--		self:Print(player.name..": "..dmg.spellname.." for "..tostring(amount))

		-- Also add to set total damage.
		set.damage = set.damage + amount

		-- Add spell to player if it does not exist.
		if not player.damagespells[dmg.spellname] then
			player.damagespells[dmg.spellname] = {id = dmg.spellid, hit = 0, totalhits = 0, damage = 0, critical = 0, glancing = 0, crushing = 0, ABSORB = 0, BLOCK = 0, DEFLECT = 0, DODGE= 0, EVADE = 0, IMMUNE = 0, PARRY = 0, REFLECT = 0, RESIST = 0, MISS = 0}
		end

		-- Add to player total damage.
		player.damage = player.damage + amount

		-- Get the spell from player.
		local spell = player.damagespells[dmg.spellname]

		spell.totalhits = spell.totalhits + 1

		if spell.max == nil or amount > spell.max then
			spell.max = amount
		end

		if (spell.min == nil or amount < spell.min) and not dmg.missed then
			spell.min = amount
		end

		spell.damage = spell.damage + amount
		if dmg.critical then
			spell.critical = spell.critical + 1
		elseif dmg.missed ~= nil then
			if spell[dmg.missed] ~= nil then	-- Just in case.
				spell[dmg.missed] = spell[dmg.missed] + 1
			end
		elseif dmg.glancing then
			spell.glancing = spell.glancing + 1
		elseif dmg.crushing then
			spell.crushing = spell.crushing + 1
		else
			spell.hit = spell.hit + 1
		end

		-- For now, only save damaged info to current set.
		-- Saving this to Total may become a memory hog deluxe, and besides, it does not make much sense
		-- to see in Total. Why care which particular mob you damaged the most in a whole raid, for example?
		if set == Skada.current then
			-- Make sure destination exists in player.
			if not player.damaged[dmg.dstname] then
				player.damaged[dmg.dstname] = 0
			end

			-- Add to destination.
			player.damaged[dmg.dstname] = player.damaged[dmg.dstname] + dmg.amount
		end

	end
end

local dmg = {}

local function SpellDamage(timestamp, eventtype, srcGUID, srcName, srcFlags, dstGUID, dstName, dstFlags, ...)
	-- Spell damage.
	if srcGUID ~= dstGUID then
		local spellId, spellName, spellSchool, samount, soverkill, sschool, sresisted, sblocked, sabsorbed, scritical, sglancing, scrushing = ...

		dmg.playerid = srcGUID
		dmg.playerflags = srcFlags
		dmg.dstname = dstName
		dmg.playername = srcName
		dmg.spellid = spellId
		dmg.spellname = spellName
		dmg.amount = samount
		dmg.overkill = soverkill
		dmg.resisted = sresisted
		dmg.blocked = sblocked
		dmg.absorbed = sabsorbed
		dmg.critical = scritical
		dmg.glancing = sglancing
		dmg.crushing = scrushing
		dmg.missed = nil

		Skada:FixPets(dmg)
		log_damage(Skada.current, dmg)
		log_damage(Skada.total, dmg)
	end
end

local function SwingDamage(timestamp, eventtype, srcGUID, srcName, srcFlags, dstGUID, dstName, dstFlags, ...)
	-- White melee.
	if srcGUID ~= dstGUID then
		local samount, soverkill, sschool, sresisted, sblocked, sabsorbed, scritical, sglancing, scrushing = ...

		dmg.playerid = srcGUID
		dmg.playername = srcName
		dmg.playerflags = srcFlags
		dmg.dstname = dstName
		dmg.spellid = 6603
		dmg.spellname = L["Attack"]
		dmg.amount = samount
		dmg.overkill = soverkill
		dmg.resisted = sresisted
		dmg.blocked = sblocked
		dmg.absorbed = sabsorbed
		dmg.critical = scritical
		dmg.glancing = sglancing
		dmg.crushing = scrushing
		dmg.missed = nil

		Skada:FixPets(dmg)
		log_damage(Skada.current, dmg)
		log_damage(Skada.total, dmg)
	end
end

local function SwingMissed(timestamp, eventtype, srcGUID, srcName, srcFlags, dstGUID, dstName, dstFlags, ...)
	if srcGUID ~= dstGUID then
		-- Melee misses

		dmg.playerid = srcGUID
		dmg.playername = srcName
		dmg.playerflags = srcFlags
		dmg.dstname = dstName
		dmg.spellid = 6603
		dmg.spellname = L["Attack"]
		dmg.amount = 0
		dmg.overkill = 0
		dmg.resisted = nil
		dmg.blocked = nil
		dmg.absorbed = nil
		dmg.critical = nil
		dmg.glancing = nil
		dmg.crushing = nil
		dmg.missed = select(1, ...)

		Skada:FixPets(dmg)
		log_damage(Skada.current, dmg)
		log_damage(Skada.total, dmg)
	end
end

local function SpellMissed(timestamp, eventtype, srcGUID, srcName, srcFlags, dstGUID, dstName, dstFlags, ...)
	-- Misses
	if srcGUID ~= dstGUID then
		local spellId, spellName, spellSchool, missType, samount = ...
		dmg.playerid = srcGUID
		dmg.playername = srcName
		dmg.playerflags = srcFlags
		dmg.dstname = dstName
		dmg.spellid = spellId
		dmg.spellname = spellName
		dmg.amount = 0
		dmg.overkill = 0
		dmg.resisted = nil
		dmg.blocked = nil
		dmg.absorbed = nil
		dmg.critical = nil
		dmg.glancing = nil
		dmg.crushing = nil
		dmg.missed = missType

		Skada:FixPets(dmg)
		log_damage(Skada.current, dmg)
		log_damage(Skada.total, dmg)
	end
end

-- Damage overview.
function mod:Update(win, set)
	-- Max value.
	local max = 0

	local nr = 1
	for i, player in ipairs(set.players) do
		if player.damage > 0 then
			local dps = getDPS(set, player)

			local d = win.dataset[nr] or {}
			win.dataset[nr] = d
			d.label = player.name

			d.valuetext = Skada:FormatValueText(
											Skada:FormatNumber(player.damage), self.metadata.columns.Damage,
											string.format("%02.1f", dps), self.metadata.columns.DPS,
											string.format("%02.1f%%", player.damage / set.damage * 100), self.metadata.columns.Percent
										)

			d.value = player.damage
			d.id = player.id
			d.class = player.class
			if player.damage > max then
				max = player.damage
			end
			nr = nr + 1
		end
	end

	win.metadata.maxvalue = max
end

-- Tooltip for a specific player.
local function dps_tooltip(win, id, label, tooltip)
	local set = win:get_selected_set()
	local player = Skada:find_player(set, id)
	if player then

		local activetime = Skada:PlayerActiveTime(set, player)
		local totaltime = Skada:GetSetTime(set)
		tooltip:AddLine(player.name.." - "..L["DPS"])
		tooltip:AddDoubleLine(L["Segment time"], totaltime.."s", 255,255,255,255,255,255)
		tooltip:AddDoubleLine(L["Active time"], activetime.."s", 255,255,255,255,255,255)
		tooltip:AddDoubleLine(L["Damage done"], Skada:FormatNumber(player.damage), 255,255,255,255,255,255)
		tooltip:AddDoubleLine(Skada:FormatNumber(player.damage) .. " / " .. activetime .. ":", ("%02.1f"):format(player.damage / math.max(1,activetime)), 255,255,255,255,255,255)

	end
end

-- Tooltip for a specific player.
local function player_tooltip(win, id, label, tooltip)
	local player = Skada:find_player(win:get_selected_set(), playermod.playerid)
	if player then
		local spell = player.damagespells[label]
		if spell then
			tooltip:AddLine(player.name.." - "..label)
			if spell.max and spell.min then
				tooltip:AddDoubleLine(L["Minimum hit:"], Skada:FormatNumber(spell.min), 255,255,255,255,255,255)
				tooltip:AddDoubleLine(L["Maximum hit:"], Skada:FormatNumber(spell.max), 255,255,255,255,255,255)
			end
			tooltip:AddDoubleLine(L["Average hit:"], Skada:FormatNumber(spell.damage / spell.totalhits), 255,255,255,255,255,255)
		end
	end
end

function playermod:Enter(win, id, label)
	local player = Skada:find_player(win:get_selected_set(), id)
	playermod.playerid = id
	playermod.title = player.name..L["'s Damage"]
end

-- Detail view of a player.
function playermod:Update(win, set)
	-- View spells for this player.

	local player = Skada:find_player(set, self.playerid)
	local max = 0

	-- If we reset we have no data.
	if player then

		local nr = 1
		if player then
			for spellname, spell in pairs(player.damagespells) do

				local d = win.dataset[nr] or {}
				win.dataset[nr] = d
				d.label = spellname
				d.id = spellname
				d.icon = select(3, GetSpellInfo(spell.id))
				d.value = spell.damage
				d.valuetext = Skada:FormatValueText(
												Skada:FormatNumber(spell.damage), self.metadata.columns.Damage,
												string.format("%02.1f%%", spell.damage / player.damage * 100), self.metadata.columns.Percent
											)
				if spell.damage > max then
					max = spell.damage
				end
				nr = nr + 1
			end
		end
	end

	win.metadata.maxvalue = max
end

function damagedmod:Enter(win, id, label)
	local player = Skada:find_player(win:get_selected_set(), id)
	damagedmod.playerid = id
	damagedmod.title = player.name..L["'s "]..L["Damaged mobs"]
end

-- Player view showing damaged mobs.
function damagedmod:Update(win, set)
	local player = Skada:find_player(set, self.playerid)
	local max = 0

	-- If we reset we have no data.
	if player then

		local nr = 1
		if player then
			for mob, amount in pairs(player.damaged) do

				local d = win.dataset[nr] or {}
				win.dataset[nr] = d
				d.label = mob
				d.id = mob
				d.value = amount
				d.valuetext = Skada:FormatValueText(
												Skada:FormatNumber(amount), self.metadata.columns.Damage,
												string.format("%02.1f%%", amount / player.damage * 100), self.metadata.columns.Percent
											)
				if amount > max then
					max = amount
				end
				nr = nr + 1
			end
		end
	end

	win.metadata.maxvalue = max
end

local function add_detail_bar(win, nr, title, value)
	local d = win.dataset[nr] or {}
	win.dataset[nr] = d

	d.value = value
	d.label = title
	d.id = title
	d.valuetext = Skada:FormatValueText(
									value, mod.metadata.columns.Damage,
									string.format("%02.1f%%", value / win.metadata.maxvalue * 100), mod.metadata.columns.Percent
								)
end

function spellmod:Enter(win, id, label)
	local player = Skada:find_player(win:get_selected_set(), playermod.playerid)
	spellmod.spellname = label
	spellmod.title = player.name..L["'s "]..label
end

function spellmod:Update(win, set)
	local player = Skada:find_player(set,playermod.playerid)

	if player then
		local spell = player.damagespells[self.spellname]

		if spell then
			win.metadata.maxvalue = spell.totalhits

			if spell.hit > 0 then
				add_detail_bar(win, 1, L["Hit"], spell.hit)
			end
			if spell.critical > 0 then
				add_detail_bar(win, 2, L["Critical"], spell.critical)
			end
			if spell.glancing > 0 then
				add_detail_bar(win, 3, L["Glancing"], spell.glancing)
			end
			if spell.crushing > 0 then
				add_detail_bar(win, 4, L["Crushing"], spell.crushing)
			end
			if spell.ABSORB and spell.ABSORB > 0 then
				add_detail_bar(win, 5, L["Absorb"], spell.ABSORB)
			end
			if spell.BLOCK and spell.BLOCK > 0 then
				add_detail_bar(win, 6, L["Block"], spell.BLOCK)
			end
			if spell.DEFLECT and spell.DEFLECT > 0 then
				add_detail_bar(win, 7, L["Deflect"], spell.DEFLECT)
			end
			if spell.DODGE and spell.DODGE > 0 then
				add_detail_bar(win, 8, L["Dodge"], spell.DODGE)
			end
			if spell.EVADE and spell.EVADE > 0 then
				add_detail_bar(win, 9, L["Evade"], spell.EVADE)
			end
			if spell.IMMUNE and spell.IMMUNE > 0 then
				add_detail_bar(win, 10, L["Immune"], spell.IMMUNE)
			end
			if spell.MISS and spell.MISS > 0 then
				add_detail_bar(win, 11, L["Missed"], spell.MISS)
			end
			if spell.PARRY and spell.PARRY > 0 then
				add_detail_bar(win, 12, L["Parry"], spell.PARRY)
			end
			if spell.REFLECT and spell.REFLECT > 0 then
				add_detail_bar(win, 13, L["Reflect"], spell.REFLECT)
			end
			if spell.RESIST and spell.RESIST > 0 then
				add_detail_bar(win, 14, L["Resist"], spell.RESIST)
			end

		end
	end

end

-- DPS-only view
function dpsmod:GetSetSummary(set)
	return Skada:FormatNumber(getRaidDPS(set))
end

function dpsmod:Update(win, set)
	local max = 0
	local nr = 1

	for i, player in ipairs(set.players) do
		local dps = getDPS(set, player)

		if dps > 0 then
			local d = win.dataset[nr] or {}
			win.dataset[nr] = d
			d.label = player.name
			d.id = player.id
			d.value = dps
			d.class = player.class
			d.valuetext = ("%02.1f"):format(dps)
			if dps > max then
				max = dps
			end

			nr = nr + 1
		end
	end

	win.metadata.maxvalue = max
end

function mod:OnEnable()
	dpsmod.metadata = 		{showspots = true, tooltip = dps_tooltip}
	playermod.metadata = 	{tooltip = player_tooltip, click1 = spellmod, columns = {Damage = true, Percent = true}}
	mod.metadata = 			{showspots = true, click1 = playermod, click2 = damagedmod, columns = {Damage = true, DPS = true, Percent = true}}
	damagedmod.metadata = 	{columns = {Damage = true, Percent = true}}
	spellmod.metadata =		{columns = {Damage = true, Percent = true}}

	Skada:RegisterForCL(SpellDamage, 'DAMAGE_SHIELD', {src_is_interesting = true, dst_is_not_interesting = true})
	Skada:RegisterForCL(SpellDamage, 'SPELL_DAMAGE', {src_is_interesting = true, dst_is_not_interesting = true})
	Skada:RegisterForCL(SpellDamage, 'SPELL_PERIODIC_DAMAGE', {src_is_interesting = true, dst_is_not_interesting = true})
	Skada:RegisterForCL(SpellDamage, 'SPELL_BUILDING_DAMAGE', {src_is_interesting = true, dst_is_not_interesting = true})
	Skada:RegisterForCL(SpellDamage, 'RANGE_DAMAGE', {src_is_interesting = true, dst_is_not_interesting = true})

	Skada:RegisterForCL(SwingDamage, 'SWING_DAMAGE', {src_is_interesting = true, dst_is_not_interesting = true})
	Skada:RegisterForCL(SwingMissed, 'SWING_MISSED', {src_is_interesting = true, dst_is_not_interesting = true})

	Skada:RegisterForCL(SpellMissed, 'SPELL_MISSED', {src_is_interesting = true, dst_is_not_interesting = true})
	Skada:RegisterForCL(SpellMissed, 'SPELL_PERIODIC_MISSED', {src_is_interesting = true, dst_is_not_interesting = true})
	Skada:RegisterForCL(SpellMissed, 'RANGE_MISSED', {src_is_interesting = true, dst_is_not_interesting = true})
	Skada:RegisterForCL(SpellMissed, 'SPELL_BUILDING_MISSED', {src_is_interesting = true, dst_is_not_interesting = true})

	Skada:AddFeed(L["Damage: Personal DPS"], function()
								if Skada.current then
									local player = Skada:find_player(Skada.current, UnitGUID("player"))
									if player then
										return ("%02.1f"):format(getDPS(Skada.current, player)).." "..L["DPS"]
									end
								end
							end)
	Skada:AddFeed(L["Damage: Raid DPS"], function()
								if Skada.current then
									return ("%02.1f"):format(getRaidDPS(Skada.current)).." "..L["RDPS"]
								end
							end)
	Skada:AddMode(self)
end

function mod:OnDisable()
	Skada:RemoveMode(self)

	Skada:RemoveFeed(L["Damage: Personal DPS"])
	Skada:RemoveFeed(L["Damage: Raid DPS"])
end

function dpsmod:OnEnable()
	Skada:AddMode(self)
end

function dpsmod:OnDisable()
	Skada:RemoveMode(self)
end

function mod:AddToTooltip(set, tooltip)
 	GameTooltip:AddDoubleLine(L["DPS"], ("%02.1f"):format(getRaidDPS(set)), 1,1,1)
end

function mod:GetSetSummary(set)
	return Skada:FormatNumber(set.damage)
end

-- Called by Skada when a new player is added to a set.
function mod:AddPlayerAttributes(player)
	if not player.damage then
		player.damage = 0
		player.damagespells = {}
	end
	if not player.damaged then
		player.damaged = {}
	end
end

-- Called by Skada when a new set is created.
function mod:AddSetAttributes(set)
	if not set.damage then
		set.damage = 0
	end
end