local AceLocale = LibStub("AceLocale-3.0") local L = AceLocale:GetLocale( "Recount" ) local BossIDs = LibStub("LibBossIDs-1.0") local Recount = _G.Recount local revision = tonumber(string.sub("$Revision: 1120 $", 12, -3)) if Recount.Version < revision then Recount.Version = revision end local dbCombatants --Data for Recount is tracked within this file local Tracking={} local UnitName = UnitName local UnitHealth = UnitHealth local UnitHealthMax = UnitHealthMax local UnitIsFeignDeath = UnitIsFeignDeath local GetTime = GetTime local tonumber = tonumber local type = type local pairs = pairs local unpack = unpack local tinsert = table.insert local string_upper = string.upper local string_lower = string.lower local string_sub = string.sub local math_floor = math.floor local math_abs = math.abs local math_fmod = math.fmod -- Elsia: This is straight from GUIDRegistryLib-0.1 by ArrowMaster. local bit_bor = bit.bor local bit_band = bit.band local COMBATLOG_OBJECT_AFFILIATION_MINE = COMBATLOG_OBJECT_AFFILIATION_MINE or 0x00000001 local COMBATLOG_OBJECT_AFFILIATION_PARTY = COMBATLOG_OBJECT_AFFILIATION_PARTY or 0x00000002 local COMBATLOG_OBJECT_AFFILIATION_RAID = COMBATLOG_OBJECT_AFFILIATION_RAID or 0x00000004 local COMBATLOG_OBJECT_AFFILIATION_OUTSIDER = COMBATLOG_OBJECT_AFFILIATION_OUTSIDER or 0x00000008 local COMBATLOG_OBJECT_AFFILIATION_MASK = COMBATLOG_OBJECT_AFFILIATION_MASK or 0x0000000F -- Reaction local COMBATLOG_OBJECT_REACTION_FRIENDLY = COMBATLOG_OBJECT_REACTION_FRIENDLY or 0x00000010 local COMBATLOG_OBJECT_REACTION_NEUTRAL = COMBATLOG_OBJECT_REACTION_NEUTRAL or 0x00000020 local COMBATLOG_OBJECT_REACTION_HOSTILE = COMBATLOG_OBJECT_REACTION_HOSTILE or 0x00000040 local COMBATLOG_OBJECT_REACTION_MASK = COMBATLOG_OBJECT_REACTION_MASK or 0x000000F0 -- Ownership local COMBATLOG_OBJECT_CONTROL_PLAYER = COMBATLOG_OBJECT_CONTROL_PLAYER or 0x00000100 local COMBATLOG_OBJECT_CONTROL_NPC = COMBATLOG_OBJECT_CONTROL_NPC or 0x00000200 local COMBATLOG_OBJECT_CONTROL_MASK = COMBATLOG_OBJECT_CONTROL_MASK or 0x00000300 -- Unit type local COMBATLOG_OBJECT_TYPE_PLAYER = COMBATLOG_OBJECT_TYPE_PLAYER or 0x00000400 local COMBATLOG_OBJECT_TYPE_NPC = COMBATLOG_OBJECT_TYPE_NPC or 0x00000800 local COMBATLOG_OBJECT_TYPE_PET = COMBATLOG_OBJECT_TYPE_PET or 0x00001000 local COMBATLOG_OBJECT_TYPE_GUARDIAN = COMBATLOG_OBJECT_TYPE_GUARDIAN or 0x00002000 local COMBATLOG_OBJECT_TYPE_OBJECT = COMBATLOG_OBJECT_TYPE_OBJECT or 0x00004000 local COMBATLOG_OBJECT_TYPE_MASK = COMBATLOG_OBJECT_TYPE_MASK or 0x0000FC00 -- Special cases (non-exclusive) local COMBATLOG_OBJECT_TARGET = COMBATLOG_OBJECT_TARGET or 0x00010000 local COMBATLOG_OBJECT_FOCUS = COMBATLOG_OBJECT_FOCUS or 0x00020000 local COMBATLOG_OBJECT_MAINTANK = COMBATLOG_OBJECT_MAINTANK or 0x00040000 local COMBATLOG_OBJECT_MAINASSIST = COMBATLOG_OBJECT_MAINASSIST or 0x00080000 local COMBATLOG_OBJECT_RAIDTARGET1 = COMBATLOG_OBJECT_RAIDTARGET1 or 0x00100000 local COMBATLOG_OBJECT_RAIDTARGET2 = COMBATLOG_OBJECT_RAIDTARGET2 or 0x00200000 local COMBATLOG_OBJECT_RAIDTARGET3 = COMBATLOG_OBJECT_RAIDTARGET3 or 0x00400000 local COMBATLOG_OBJECT_RAIDTARGET4 = COMBATLOG_OBJECT_RAIDTARGET4 or 0x00800000 local COMBATLOG_OBJECT_RAIDTARGET5 = COMBATLOG_OBJECT_RAIDTARGET5 or 0x01000000 local COMBATLOG_OBJECT_RAIDTARGET6 = COMBATLOG_OBJECT_RAIDTARGET6 or 0x02000000 local COMBATLOG_OBJECT_RAIDTARGET7 = COMBATLOG_OBJECT_RAIDTARGET7 or 0x04000000 local COMBATLOG_OBJECT_RAIDTARGET8 = COMBATLOG_OBJECT_RAIDTARGET8 or 0x08000000 local COMBATLOG_OBJECT_NONE = COMBATLOG_OBJECT_NONE or 0x80000000 local COMBATLOG_OBJECT_SPECIAL_MASK = COMBATLOG_OBJECT_SPECIAL_MASK or 0xFFFF0000 local LIB_FILTER_RAIDTARGET = bit_bor( COMBATLOG_OBJECT_RAIDTARGET1, COMBATLOG_OBJECT_RAIDTARGET2, COMBATLOG_OBJECT_RAIDTARGET3, COMBATLOG_OBJECT_RAIDTARGET4, COMBATLOG_OBJECT_RAIDTARGET5, COMBATLOG_OBJECT_RAIDTARGET6, COMBATLOG_OBJECT_RAIDTARGET7, COMBATLOG_OBJECT_RAIDTARGET8 ) local LIB_FILTER_ME = bit_bor( COMBATLOG_OBJECT_AFFILIATION_MINE, COMBATLOG_OBJECT_CONTROL_PLAYER, COMBATLOG_OBJECT_TYPE_PLAYER ) local LIB_FILTER_MY_PET = bit_bor( COMBATLOG_OBJECT_AFFILIATION_MINE, COMBATLOG_OBJECT_CONTROL_PLAYER, COMBATLOG_OBJECT_TYPE_PET ) local LIB_FILTER_PARTY = bit_bor(COMBATLOG_OBJECT_TYPE_PLAYER, COMBATLOG_OBJECT_AFFILIATION_PARTY) local LIB_FILTER_RAID = bit_bor(COMBATLOG_OBJECT_TYPE_PLAYER, COMBATLOG_OBJECT_AFFILIATION_RAID) local LIB_FILTER_GROUP = bit_bor(LIB_FILTER_PARTY, LIB_FILTER_RAID) local HotTickTimeId={ [746]=1, -- First Aid (rank 1) [1159]=1, [3267]=1, [3268]=1, [7926]=1, [7927]=1, [23569]=1, [24412]=1, [10838]=1, [10839]=1, [23568]=1, [24413]=1, [18608]=1, [18610]=1, [23567]=1, [23696]=1, [24414]=1, [27030]=1, [27031]=1, -- First Aid (rank 12) [33763]=1, -- Lifebloom (rank 1) no other ranks } local DotTickTimeId={ -- Mage Ticks [133]=2, -- Fireball (rank 1) [143]=2, [145]=2, [3140]=2, [8400]=2, [8401]=2, [8402]=2, [10148]=2, [10149]=2, [10150]=2, [10151]=2, [25306]=2, [27070]=2, [38692]=2, -- Fireball (rank 14) [11119]=2, -- Ignite (rank 1) [11120]=2, [12846]=2, [12847]=2, [12848]=2, -- Ignite (rank 5) [15407]=1, -- Mind Flay (rank 1) [17311]=1, [17312]=1, [17313]=1, [17314]=1, [18807]=1, [25387]=1, -- Mind Flay (rank 7) [980]=2, -- Curse of Agony (rank 1) [1014]=2, [6217]=2, [11711]=2, [11712]=2, [11713]=2, [27218]=2, -- Curse of Agony (rank 7) [603]=60, -- Curse of Doom (rank 1) [30910]=60, -- Curse of Doom (rank 2) [689]=1, -- Drain Life (rank 1) Elsia: According to wowhead it's 1. Which makes sense compared to Mind Flay... [699]=1, [709]=1, [7651]=1, [11699]=1, [11700]=1, [27219]=1, [27220]=1, -- Drain Life (rank 8) [755]=1, -- Health Funnel (rank 1) [3698]=1, [3699]=1, [3700]=1, [11693]=1, [11694]=1, [11695]=1, [27259]=1, -- Health Funnel (rank 8) [1949]=1, -- Hellfire (rank 1) [11683]=1, [11684]=1, [27213]=1, -- Hellfire (rank 4) } local bossIDs = BossIDs.BossIDs function Recount.IsBoss(GUID) return GUID and bossIDs[tonumber(GUID:sub(9, 12), 16)] end -- Base Events: SWING – These events relate to melee swings, commonly called “White Damage”. RANGE – These events relate to hunters shooting their bow or a warlock shooting their wand. SPELL – These events relate to spells and abilities. SPELL_CAST – These events relate to spells starting and failing. SPELL_AURA – These events relate to buffs and debuffs. SPELL_PERIODIC – These events relate to HoT, DoTs and similar effects. DAMAGE_SHIELD – These events relate to damage shields, such as Thorns ENCHANT – These events relate to temporary and permanent item buffs. ENVIRONMENTAL – This is any damage done by the world. Fires, Lava, Falling, etc. -- Suffixes: _DAMAGE – If the event resulted in damage, here it is. _MISSED - If the event resulted in failure, such as missing, resisting or being blocked. _HEAL – If the event resulted in a heal. _ENERGIZE – If the event resulted in a power restoration. _LEECH – If the event transferred health or power. _DRAIN – If the event reduces power, but did not transfer it. -- Special Events: PARTY_KILL – Fired when you or a party member kills something. UNIT_DIED – Fired when any nearby unit dies. local SPELLSCHOOL_PHYSICAL = 1 local SPELLSCHOOL_HOLY = 2 local SPELLSCHOOL_FIRE = 4 local SPELLSCHOOL_NATURE = 8 local SPELLSCHOOL_NATUREFIRE = SPELLSCHOOL_FIRE + SPELLSCHOOL_NATURE local SPELLSCHOOL_FROST = 16 local SPELLSCHOOL_FROSTFIRE = SPELLSCHOOL_FIRE + SPELLSCHOOL_FROST local SPELLSCHOOL_SHADOW = 32 local SPELLSCHOOL_ARCANE = 64 Recount.SpellSchoolName = { [SPELLSCHOOL_PHYSICAL] = "Physical", -- Elsia: Do NOT localize this, it breaks functionality!!! If you need this localized contact me on WowAce or Curse. [SPELLSCHOOL_HOLY] = "Holy", -- Elsia: Do NOT localize this, it breaks functionality!!! If you need this localized contact me on WowAce or Curse. [SPELLSCHOOL_FIRE] = "Fire", -- Elsia: Do NOT localize this, it breaks functionality!!! If you need this localized contact me on WowAce or Curse. [SPELLSCHOOL_NATURE] = "Nature", -- Elsia: Do NOT localize this, it breaks functionality!!! If you need this localized contact me on WowAce or Curse. [SPELLSCHOOL_NATUREFIRE] = "Naturefire", -- Elsia: Do NOT localize this, it breaks functionality!!! If you need this localized contact me on WowAce or Curse. [SPELLSCHOOL_FROST] = "Frost", -- Elsia: Do NOT localize this, it breaks functionality!!! If you need this localized contact me on WowAce or Curse. [SPELLSCHOOL_FROSTFIRE] = "Frostfire", -- Elsia: Do NOT localize this, it breaks functionality!!! If you need this localized contact me on WowAce or Curse. [SPELLSCHOOL_SHADOW] = "Shadow", -- Elsia: Do NOT localize this, it breaks functionality!!! If you need this localized contact me on WowAce or Curse. [SPELLSCHOOL_ARCANE] = "Arcane", -- Elsia: Do NOT localize this, it breaks functionality!!! If you need this localized contact me on WowAce or Curse. } function Recount:MatchGUID(nName,nGUID,nFlags) if not Recount.PlayerName or not Recount.PlayerGUID then if bit_band(nFlags, LIB_FILTER_ME) == LIB_FILTER_ME then Recount.PlayerName = nName Recount.PlayerGUID = nGUID return end end --[[ if bit_band(nFlags,LIB_FILTER_MY_PET) == LIB_FILTER_MY_PET then if not Recount.PlayerPet or not Recount.PlayerPetGUID or nGUID~=Recount.PlayerPetGUID then --Recount:Print("NewPet detected: "..nName.." "..nGUID.."("..(Recount.PlayerPetGUID or "nil")..")") Recount.PlayerPetGUID = nGUID if Recount.PlayerPet ~= nName then Recount.PlayerPet = nName end return end end]] end function Recount:SwingDamage(timestamp, eventtype, srcGUID, srcName, srcFlags, dstGUID, dstName, dstFlags,amount, overkill,school, resisted, blocked, absorbed, critical, glancing, crushing) Recount:SpellDamage(timestamp, eventtype, srcGUID, srcName, srcFlags, dstGUID, dstName, dstFlags,0, L["Melee"], SPELLSCHOOL_PHYSICAL, amount, overkill,school, resisted, blocked, absorbed, critical, glancing, crushing) end function Recount:SpellBuildingDamage(timestamp, eventtype, srcGUID, srcName, srcFlags, dstGUID, dstName, dstFlags,spellId, spellName, spellSchool, amount, overkill, school, resisted, blocked, absorbed, critical, glancing, crushing) -- Ignoring these for now end function Recount:SpellBuildingHeal(timestamp, eventtype, srcGUID, srcName, srcFlags, dstGUID, dstName, dstFlags,spellId, spellName, spellSchool, amount, overheal,critical) -- Ignoring these for now end function Recount:SpellDamage(timestamp, eventtype, srcGUID, srcName, srcFlags, dstGUID, dstName, dstFlags,spellId, spellName, spellSchool, amount, overkill, school, resisted, blocked, absorbed, critical, glancing, crushing) -- amount = amount - overkill -- Taking out overdamage on killing blows local HitType="Hit" -- Elsia: Do NOT localize this, it breaks functionality!!! If you need this localized contact me on WowAce or Curse. local isDot if eventtype == "SPELL_PERIODIC_DAMAGE" then HitType="Tick" -- Elsia: Do NOT localize this, it breaks functionality!!! If you need this localized contact me on WowAce or Curse. spellName = spellName .." ("..L["DoT"]..")" isDot = true end if critical then HitType="Crit" -- Elsia: Do NOT localize this, it breaks functionality!!! If you need this localized contact me on WowAce or Curse. end if eventtype == "DAMAGE_SPLIT" then HitType="Split" -- Elsia: Do NOT localize this, it breaks functionality!!! If you need this localized contact me on WowAce or Curse. end if crushing then HitType="Crushing" -- Elsia: Do NOT localize this, it breaks functionality!!! If you need this localized contact me on WowAce or Curse. end if glancing then HitType="Glancing" -- Elsia: Do NOT localize this, it breaks functionality!!! If you need this localized contact me on WowAce or Curse. end --[[ if blocked then HitType="Block" end if absorbed then HitType="Absorbed" end--]] if eventtype == "RANGE_DAMAGE" then spellSchool = school end Recount:AddDamageData(srcName, dstName, spellName, Recount.SpellSchoolName[spellSchool], HitType, amount, resisted, srcGUID, srcFlags, dstGUID, dstFlags, spellId, blocked, absorbed, isDot) end function Recount:EnvironmentalDamage(timestamp, eventtype, srcGUID, srcName, srcFlags, dstGUID, dstName, dstFlags,enviromentalType, amount, overkill, school, resisted, blocked, absorbed, critical, glancing, crushing) local HitType = "Hit" -- Elsia: Do NOT localize this, it breaks functionality!!! If you need this localized contact me on WowAce or Curse. if critical then HitType="Crit" -- Elsia: Do NOT localize this, it breaks functionality!!! If you need this localized contact me on WowAce or Curse. end if crushing then HitType="Crushing" -- Elsia: Do NOT localize this, it breaks functionality!!! If you need this localized contact me on WowAce or Curse. end if glancing then HitType="Glancing" -- Elsia: Do NOT localize this, it breaks functionality!!! If you need this localized contact me on WowAce or Curse. end --[[if blocked then HitType="Block" end if absorbed then HitType="Absorbed" end--]] Recount:AddDamageData("Environment", dstName, Recount:FixCaps(enviromentalType), Recount.SpellSchoolName[school], HitType, amount, resisted, srcGUID, 0, dstGUID, dstFlags, nil, blocked, absorbed) end function Recount:FixCaps(capsstr) if type(capsstr)=="string" then return string_upper(string_sub(capsstr,1,1))..string_lower(string_sub(capsstr,2)) else return nil end end function Recount:SwingMissed(timestamp, eventtype, srcGUID, srcName, srcFlags, dstGUID, dstName, dstFlags, missType, missAmount) local blocked local absorbed local spellId = L["Melee"] if missType == "ABSORB" then absorbed = missAmount elseif missType == "BLOCK" then blocked = missAmount end Recount:AddDamageData(srcName, dstName, L["Melee"], nil, Recount:FixCaps(missType),nil,nil, srcGUID, srcFlags, dstGUID, dstFlags, spellId, blocked, absorbed) -- Elsia: Do NOT localize this, it breaks functionality!!! If you need this localized contact me on WowAce or Curse. end function Recount:SpellMissed(timestamp, eventtype, srcGUID, srcName, srcFlags, dstGUID, dstName, dstFlags,spellId, spellName, spellSchool, missType, missAmount) local blocked local absorbed if missType == "ABSORB" then absorbed = missAmount elseif missType == "BLOCK" then blocked = missAmount end Recount:AddDamageData(srcName, dstName, spellName, Recount.SpellSchoolName[spellSchool], Recount:FixCaps(missType),nil,nil, srcGUID, srcFlags, dstGUID, dstFlags, spellId, blocked, absorbed) end function Recount:SpellHeal(timestamp, eventtype, srcGUID, srcName, srcFlags, dstGUID, dstName, dstFlags,spellId, spellName, spellSchool, amount, overheal,absorbed, critical) local healtype="Hit" -- Elsia: Do NOT localize this, it breaks functionality!!! If you need this localized contact me on WowAce or Curse. local isHot if eventtype == "SPELL_PERIODIC_HEAL" then healtype="Tick" -- Elsia: Do NOT localize this, it breaks functionality!!! If you need this localized contact me on WowAce or Curse. isHot = true -- Not activated yet: spellName=spellName.." ("..L["HoT"]..")" end if absorbed==1 and not critical then -- 3.1 to 3.2 combatibility critical = absorbed absorbed = nil end if critical then healtype="Crit" -- Elsia: Do NOT localize this, it breaks functionality!!! If you need this localized contact me on WowAce or Curse. end Recount:AddHealData(srcName, dstName, spellName, healtype, amount,overheal, srcGUID,srcFlags,dstGUID,dstFlags,spellId,isHot, absorbed)-- Elsia: Overheal missing!!! end local extraattacks function Recount:SpellExtraAttacks(timestamp, eventtype, srcGUID, srcName, srcFlags, dstGUID, dstName, dstFlags,spellId, spellName, spellSchool, amount) --[[ source = Recount.curr_srcName victim = Recount.curr_dstName local healtype="Hit" Recount:Print(Recount.curr_type.." "..spellName.." "..amount) Recount:AddDamageData(source, victim, spellName, Recount.SpellSchoolName[spellSchool], HitType, amount)--]] -- Elsia: Don't have use for extra attacks currently, amount is number of extra attacks it seems from combat log traces. extraattacks = extraattacks or {} if extraattacks[srcName] then Recount:DPrint("Double proc: "..spellName.." "..extraattacks[srcName].spellName) else extraattacks[srcName] = {} extraattacks[srcName].spellName = spellName extraattacks[srcName].amount = amount extraattacks[srcName].proctime = GetTime() Recount:DPrint("Proc: "..spellName.." "..extraattacks[srcName].spellName) end end function Recount:SpellDrain(timestamp, eventtype, srcGUID, srcName, srcFlags, dstGUID, dstName, dstFlags,spellId, spellName, spellSchool, amount, powerType, extraAmount) -- Currently unused. end function Recount:SpellAuraAppliedRemoved(timestamp, eventtype, srcGUID, srcName, srcFlags, dstGUID, dstName, dstFlags,spellId, spellName, spellSchool, auraType) -- Spirit of Redemption and Shadow of Death handling if eventtype == "SPELL_AURA_REMOVED" and (spellId == 54223 or spellId == 27827) then Recount:HandleDoubleDeath(srcName, dstName, spellName,srcGUID,srcFlags,dstGUID,dstFlags,spellId) end end function Recount:SpellAuraAppliedRemovedDose(timestamp, eventtype, srcGUID, srcName, srcFlags, dstGUID, dstName, dstFlags,spellId, spellName, spellSchool, auraType, amount) -- Not sure yet how to handle this end function Recount:SpellCastStartSuccess(timestamp, eventtype, srcGUID, srcName, srcFlags, dstGUID, dstName, dstFlags,spellId, spellName, spellSchool) if eventtype == "SPELL_INSTAKILL" then --Recount:Print(Recount.curr_type .." "..source.." "..victim) Recount:AddDeathData(srcName, dstName, nil, srcGUID, srcFlags, dstGUID, dstFlags, spellId) end end -- Note: GetSpellLink(id) gets spell name from ID. -- GetSpellInfo(id) function Recount:SpellCastFailed(timestamp, eventtype, srcGUID, srcName, srcFlags, dstGUID, dstName, dstFlags,spellId, spellName, spellSchool, failedType) -- Not sure yet how to handle this, are these interrupts? end function Recount:EnchantAppliedRemoved(timestamp, eventtype, srcGUID, srcName, srcFlags, dstGUID, dstName, dstFlags,spellName, itemId, itemName) -- Not sure yet how to handle this, end function Recount:PartyKill(timestamp, eventtype, srcGUID, srcName, srcFlags, dstGUID, dstName, dstFlags) --Recount:AddDeathData(srcName , dstName, nil, srcGUID, srcFlags, dstGUID, dstFlags, nil) -- Could be killing blow tracker end function Recount:UnitDied(timestamp, eventtype, srcGUID, srcName, srcFlags, dstGUID, dstName, dstFlags) Recount:AddDeathData(nil , dstName, nil, srcGUID, srcFlags, dstGUID, dstFlags, nil) end function Recount:SpellSummon(timestamp, eventtype, srcGUID, srcName, srcFlags, dstGUID, dstName, dstFlags,spellId, spellName, spellSchool) Recount:AddPetCombatant(dstGUID,dstName,dstFlags,srcGUID,srcName,srcFlags) end function Recount:SpellCreate(timestamp, eventtype, srcGUID, srcName, srcFlags, dstGUID, dstName, dstFlags,spellId, spellName, spellSchool) -- Elsia: We do nothing for these yet. end local EventParse = { ["SWING_DAMAGE"] = Recount.SwingDamage, -- Elsia: Melee swing damage ["RANGE_DAMAGE"] = Recount.SpellDamage, -- Elsia: Ranged and spell damage types ["SPELL_DAMAGE"] = Recount.SpellDamage, ["SPELL_PERIODIC_DAMAGE"] = Recount.SpellDamage, ["DAMAGE_SHIELD"] = Recount.SpellDamage, ["DAMAGE_SPLIT"] = Recount.SpellDamage, ["ENVIRONMENTAL_DAMAGE"] = Recount.EnvironmentalDamage, -- Elsia: Environmental damage ["SWING_MISSED"] = Recount.SwingMissed, -- Elsia: Misses ["RANGE_MISSED"] = Recount.SpellMissed, ["SPELL_MISSED"] = Recount.SpellMissed, ["SPELL_PERIODIC_MISSED"] = Recount.SpellMissed, ["DAMAGE_SHIELD_MISSED"] = Recount.SpellMissed, ["SPELL_HEAL"] = Recount.SpellHeal, -- Elsia: heals ["SPELL_PERIODIC_HEAL"] = Recount.SpellHeal, ["SPELL_ENERGIZE"] = Recount.SpellEnergize, -- Elsia: Energize ["SPELL_PERIODIC_ENERGIZE"] = Recount.SpellEnergize, ["SPELL_EXTRA_ATTACKS"] = Recount.SpellExtraAttacks, -- Elsia: Extra attacks ["SPELL_INTERRUPT"] = Recount.SpellInterrupt, -- Elsia: Interrupts ["SPELL_DRAIN"] = Recount.SpellDrain, -- Elsia: Drains and leeches. ["SPELL_LEECH"] = Recount.SpellLeech, ["SPELL_PERIODIC_DRAIN"] = Recount.SpellDrain, ["SPELL_PERIODIC_LEECH"] = Recount.SpellLeech, ["SPELL_DISPEL_FAILED"] = Recount.SpellAuraDispelFailed, -- Elsia: Failed dispell -- ["SPELL_AURA_DISPELLED"] = Recount.SpellAuraDispelledStolen, -- Removed with 2.4.3 -- ["SPELL_AURA_STOLEN"] = Recount.SpellAuraDispelledStolen, -- Removed with 2.4.3 ["SPELL_AURA_APPLIED"] = Recount.SpellAuraAppliedRemoved, -- Elsia: Auras ["SPELL_AURA_REMOVED"] = Recount.SpellAuraAppliedRemoved, ["SPELL_AURA_APPLIED_DOSE"] = Recount.SpellAuraAppliedRemovedDose, -- Elsia: Aura doses ["SPELL_AURA_REMOVED_DOSE"] = Recount.SpellAuraAppliedRemovedDose, ["SPELL_CAST_START"] = Recount.SpellCastStartSuccess, -- Elsia: Spell casts ["SPELL_CAST_SUCCESS"] = Recount.SpellCastStartSuccess, ["SPELL_INSTAKILL"] = Recount.SpellCastStartSuccess, ["SPELL_DURABILITY_DAMAGE"] = Recount.SpellCastStartSuccess, ["SPELL_DURABILITY_DAMAGE_ALL"] = Recount.SpellCastStartSuccess, ["SPELL_CAST_FAILED"] = Recount.SpellCastFailed, -- Elsia: Spell aborts/fails ["ENCHANT_APPLIED"] = Recount.EnchantAppliedRemoved, -- Elsia: Enchants ["ENCHANT_REMOVED"] = Recount.EnchantAppliedRemoved, ["PARTY_KILL"] = Recount.PartyKill, -- Elsia: Party killing blow ["UNIT_DIED"] = Recount.UnitDied, -- Elsia: Unit died ["UNIT_DESTROYED"] = Recount.UnitDied, ["SPELL_SUMMON"] = Recount.SpellSummon, -- Elsia: Summons ["SPELL_CREATE"] = Recount.SpellCreate, -- Elsia: Creations ["SPELL_AURA_BROKEN"] = Recount.SpellAuraBroken, -- New with 2.4.3 ["SPELL_AURA_BROKEN_SPELL"] = Recount.SpellAuraBroken, -- New with 2.4.3 ["SPELL_AURA_REFRESH"] = Recount.SpellAuraAppliedRemoved, -- New with 2.4.3 ["SPELL_DISPEL"] = Recount.SpellAuraDispelledStolen, -- Post 2.4.3 ["SPELL_STOLEN"] = Recount.SpellAuraDispelledStolen, -- Post 2.4.3 ["SPELL_RESURRECT"] = Recount.SpellResurrect, -- Post WotLK ["SPELL_BUILDING_DAMAGE"] = Recount.SpellBuildingDamage, -- Post WotLK ["SPELL_BUILDING_HEAL"] = Recount.SpellBuildingHeal, ["UNIT_DISSIPATES"] = Recount.UnitDied, -- Post 3.2 } local QuickExitEvents = { ["SPELL_AURA_APPLIED"] = true, -- ["SPELL_AURA_REMOVED"] = true, ["SPELL_AURA_APPLIED_DOSE"] = true, ["SPELL_AURA_REMOVED_DOSE"] = true, ["SPELL_AURA_REFRESH"] = true, ["SPELL_CAST_START"] = true, ["SPELL_CAST_SUCCESS"] = true, ["SPELL_CAST_FAILED"] = true, ["SPELL_DRAIN"] = true, -- ["SPELL_LEECH"] = true, ["PARTY_KILL"] = true, ["SPELL_PERIODIC_DRAIN"] = true, -- ["SPELL_PERIODIC_LEECH"] = true, ["SPELL_DISPEL_FAILED"] = true, ["SPELL_DURABILITY_DAMAGE"] = true, ["SPELL_DURABILITY_DAMAGE_ALL"] = true, ["ENCHANT_APPLIED"] = true, ["ENCHANT_REMOVED"] = true, ["SPELL_CREATE"] = true, ["SPELL_BUILDING_DAMAGE"] = true, } -- This is to allow modularity of the tracker code. Functions that are not registered to be handled will be quickexited. if not Recount.SpellResurrect then QuickExitEvents["SPELL_RESURRECT"] = true end if not Recount.SpellAuraBroken then QuickExitEvents["SPELL_AURA_BROKEN"] = true QuickExitEvents["SPELL_AURA_BROKEN_SPELL"] = true end if not Recount.SpellAuraDispelledStolen then QuickExitEvents["SPELL_DISPEL"] = true QuickExitEvents["SPELL_STOLEN"] = true end if not Recount.SpellEnergize then QuickExitEvents["SPELL_ENERGIZE"] = true QuickExitEvents["SPELL_PERIODIC_ENERGIZE"] = true QuickExitEvents["SPELL_LEECH"] = true QuickExitEvents["SPELL_PERIODIC_LEECH"] = true end if not Recount.SpellInterrupt then QuickExitEvents["SPELL_INTERRUPT"] = true end local GROUPED_FILTER_BITMASK = COMBATLOG_OBJECT_AFFILIATION_MINE+COMBATLOG_OBJECT_AFFILIATION_PARTY+COMBATLOG_OBJECT_AFFILIATION_RAID local SELF_FILTER_BITMASK = COMBATLOG_OBJECT_AFFILIATION_MINE+COMBATLOG_OBJECT_TYPE_PLAYER local UNGROUPED_FILTER_BITMASK = COMBATLOG_OBJECT_TYPE_PLAYER+COMBATLOG_OBJECT_REACTION_FRIENDLY local PET_FILTER_BITMASK = COMBATLOG_OBJECT_TYPE_PET+COMBATLOG_OBJECT_TYPE_GUARDIAN local GROUPED_PET_FILTER_BITMASK = COMBATLOG_OBJECT_AFFILIATION_PARTY+COMBATLOG_OBJECT_AFFILIATION_RAID local UNGROUPED_PET_FILTER_BITMASK = COMBATLOG_OBJECT_AFFILIATION_OUTSIDER+COMBATLOG_OBJECT_REACTION_FRIENDLY function Recount:CheckRetentionFromFlags(nameFlags,name,nameGUID) local filters = Recount.db.profile.Filters.Data if filters["Grouped"] and bit_band(nameFlags, GROUPED_FILTER_BITMASK) ~= 0 then return true -- Grouped elseif filters["Self"] and bit_band(nameFlags, SELF_FILTER_BITMASK) == SELF_FILTER_BITMASK then return true -- Self elseif filters["Ungrouped"] and bit_band(nameFlags, UNGROUPED_FILTER_BITMASK) == UNGROUPED_FILTER_BITMASK then return true -- Ungrouped elseif filters["Hostile"] and bit_band(nameFlags, COMBATLOG_OBJECT_CONTROL_PLAYER) ~=0 then return true elseif filters["Pet"] and bit_band(nameFlags, PET_FILTER_BITMASK) ~=0 then if filters["Self"] and bit_band(nameFlags, COMBATLOG_OBJECT_AFFILIATION_MINE) ~=0 then return true elseif filters["Grouped"] and bit_band(nameFlags, GROUPED_PET_FILTER_BITMASK) ~=0 then return true elseif filters["Ungrouped"] and bit_band(nameFlags, UNGROUPED_PET_FILTER_BITMASK) == UNGROUPED_PET_FILTER_BITMASK then return true elseif bit_band(nameFlags, COMBATLOG_OBJECT_TYPE_GUARDIAN)~=0 and Recount:GetGuardianOwnerByGUID(nameGUID) then -- This is necessary because guardian combat log flags can be wrong in 3.0.9 return true end elseif bit_band(nameFlags, COMBATLOG_OBJECT_CONTROL_NPC) ~=0 then local isBoss = Recount.IsBoss(nameGUID) if not isBoss and (filters["Trivial"] or filters["Nontrivial"]) then return true elseif isBoss and filters["Boss"] then return true end end return false end Recount.srcRetention = false Recount.dstRetention = false local srcRetention = Recount.srcRetention local dstRetention = Recount.dstRetention local parsefunc function Recount:CombatLogEvent(_,timestamp, eventtype, srcGUID, srcName, srcFlags, dstGUID, dstName, dstFlags, ...) if not Recount.db.profile.GlobalDataCollect or not Recount.CurrentDataCollect then return end if QuickExitEvents[eventtype] then -- Counter bursty combat log events we don't care about. return end srcRetention = Recount:CheckRetentionFromFlags(srcFlags,srcName,srcGUID) dstRetention = Recount:CheckRetentionFromFlags(dstFlags,dstName,dstGUID) if not srcRetention and not dstRetention then return end Recount.srcRetention = srcRetention Recount.dstRetention = dstRetention if srcName == nil then srcName = "No One" else Recount:MatchGUID(srcName,srcGUID,srcFlags) end if dstName == nil then dstName = "No One" else Recount:MatchGUID(dstName,dstGUID,dstFlags) end parsefunc = EventParse[eventtype] if parsefunc then parsefunc(self, timestamp, eventtype, srcGUID, srcName, srcFlags, dstGUID, dstName, dstFlags, ...) else Recount:Print("Unknown combat log event type: "..eventtype) end end function Recount:SetActive(who) if not who then return end who.LastActive=Recount.CurTime end local eventtime local Adding function Recount:AddTimeEvent(who, onWho, ability, friendly) if not who then return end eventtime=GetTime() who.LastAbility = who.LastAbility or 0 Adding=eventtime-who.LastAbility who.LastAbility=eventtime if Adding>3.5 then Adding=3.5 end Adding=math_floor(100*Adding+0.5)/100 Recount:AddOwnerPetLazySyncAmount(who,"ActiveTime", Adding) --Recount:AddSyncAmount(who,"ActiveTime", Adding) Recount:AddAmount(who,"ActiveTime",Adding) Recount:AddTableDataSum(who,"TimeSpent",onWho,ability,Adding) if friendly then Recount:AddAmount(who,"TimeHeal",Adding) Recount:AddTableDataSum(who,"TimeHealing",onWho,ability,Adding) else Recount:AddAmount(who,"TimeDamage",Adding) Recount:AddTableDataSum(who,"TimeDamaging",onWho,ability,Adding) end end --Only care about event tracking for those we want to track deaths for function Recount:AddCurrentEvent(who, eventType, incoming, number, event) if not who then return end if Recount.db.profile.Filters.TrackDeaths[who.type] then who.LastEvents = who.LastEvents or {} who.LastEventTimes = who.LastEventTimes or {} who.LastEventType = who.LastEventType or {} who.LastEventIncoming = who.LastEventIncoming or {} who.NextEventNum = who.NextEventNum or 1 who.LastEventTimes[who.NextEventNum]=GetTime() who.LastEventType[who.NextEventNum]=eventType who.LastEventIncoming[who.NextEventNum]=incoming who.LastEvents[who.NextEventNum]=event --(eventType or "").." "..(abiliy or "").." "..(number or "") if (not who.unit) or (UnitName(who.unit)~=who.Name) and who.UnitLockoutRecount.db.profile.MessagesTracked then who.NextEventNum=who.NextEventNum-Recount.db.profile.MessagesTracked end end end --Functions for adding data function Recount:AddAmount(who,datatype,amount) if not who then return end if not Recount.db.profile.Filters.Data[who.type] or not Recount.db.profile.GlobalDataCollect or not Recount.CurrentDataCollect then return end Recount.NewData = true -- Inform MainWindow that we got new data stored. --We add the data to both overall & current fight data who.Fights = who.Fights or {} who.Fights.OverallData = who.Fights.OverallData or {} who.Fights.OverallData[datatype] = who.Fights.OverallData[datatype] or 0 who.Fights.OverallData[datatype]=who.Fights.OverallData[datatype]+amount who.Fights.CurrentFightData = who.Fights.CurrentFightData or {} who.Fights.CurrentFightData[datatype] = who.Fights.CurrentFightData[datatype] or 0 who.Fights.CurrentFightData[datatype]=who.Fights.CurrentFightData[datatype]+amount --Now add the time data -- if who.TimeWindows[datatype] then who.TimeWindows = who.TimeWindows or {} who.TimeWindows[datatype] = who.TimeWindows[datatype] or {} who.TimeWindows[datatype][Recount.TimeStep] = who.TimeWindows[datatype][Recount.TimeStep] or 0 who.TimeWindows[datatype][Recount.TimeStep]=who.TimeWindows[datatype][Recount.TimeStep]+amount who.TimeLast = who.TimeLast or {} who.TimeLast[datatype]=Recount.CurTime who.TimeLast["OVERALL"]=Recount.CurTime -- end end --Meant for like elemental data and this type isn't expected to be initialized function Recount:AddAmount2(who,datatype,secondary,amount) if not who then return end if not Recount.db.profile.Filters.Data[who.type] or not Recount.db.profile.GlobalDataCollect or not Recount.CurrentDataCollect then return end if not secondary then Recount:DPrint("Empty secondary: "..datatype) return end --We add the data to both overall & current fight data who.Fights = who.Fights or {} who.Fights.OverallData = who.Fights.OverallData or {} who.Fights.OverallData[datatype] = who.Fights.OverallData[datatype] or {} who.Fights.OverallData[datatype][secondary]=(who.Fights.OverallData[datatype][secondary] or 0)+amount who.Fights.CurrentFightData = who.Fights.CurrentFightData or {} who.Fights.CurrentFightData[datatype] = who.Fights.CurrentFightData[datatype] or {} who.Fights.CurrentFightData[datatype][secondary]=(who.Fights.CurrentFightData[datatype][secondary] or 0)+amount end --Two Different Types of table functions --First type tracks min/max & count while the other only counts the total sum in the count column local CurTable local Details function Recount:AddTableDataStats(who,datatype,secondary,detailtype,amount) if not who then return end if not Recount.db.profile.Filters.Data[who.type] or not Recount.db.profile.GlobalDataCollect or not Recount.CurrentDataCollect then return end who.Fights = who.Fights or {} who.Fights.OverallData = who.Fights.OverallData or {} who.Fights.OverallData[datatype] = who.Fights.OverallData[datatype] or {} CurTable=who.Fights.OverallData[datatype][secondary] if type(CurTable)~="table" then who.Fights.OverallData[datatype][secondary]=Recount:GetTable() CurTable=who.Fights.OverallData[datatype][secondary] CurTable.count=0 CurTable.amount=0 CurTable.Details=Recount:GetTable() end CurTable.count=CurTable.count+1 CurTable.amount=CurTable.amount+amount if type(CurTable.Details[detailtype])~="table" then CurTable.Details[detailtype]=Recount:GetTable() CurTable.Details[detailtype].count=0 CurTable.Details[detailtype].amount=0 end Details=CurTable.Details[detailtype] Details.count=Details.count+1 Details.amount=Details.amount+amount if Details.max then if amount>Details.max then Details.max=amount elseif amountDetails.max then Details.max=amount elseif amount") if not petName then petName = name else name = petName end if nFlags and bit_band(nFlags, COMBATLOG_OBJECT_TYPE_PET+COMBATLOG_OBJECT_TYPE_GUARDIAN)~=0 then if bit_band(nFlags, COMBATLOG_OBJECT_AFFILIATION_MINE) ~=0 then name = name.." <"..Recount.PlayerName..">" owner = Recount.PlayerName -- Elsia: Fix up so that owner properly gets set ownerID = Recount.PlayerGUID -- if bit_band(nFlags, COMBATLOG_OBJECT_TYPE_PET) ~=0 then -- Recount.PlayerPetGUID = nGUID -- else -- Guardians -- Recount.LatestGuardian = Recount.LatestGuardian + 1 -- Recount.GuardiansGUIDs[Recount.LatestGuardian]=nGUID -- if Recount.LatestGuardian > 20 then -- Elsia: Max guardians set to 20 for now -- Recount.LatestGuardian = 0 -- end -- end elseif bit_band(nFlags, COMBATLOG_OBJECT_AFFILIATION_PARTY+COMBATLOG_OBJECT_AFFILIATION_RAID)~=0 then -- if nFlags and bit_band(nFlags, COMBATLOG_OBJECT_TYPE_PET)~=0 then -- elseif nFlags and bit_band(nFlags, COMBATLOG_OBJECT_TYPE_GUARDIAN)~=0 then owner = Recount:GetGuardianOwnerByGUID(nGUID) ownerID = owner and dbCombatants[owner] and dbCombatants[owner].GUID if not owner then owner, ownerID = Recount:FindOwnerPetFromGUID(name,nGUID) if not owner then Recount:DPrint("NoOwner: "..name.." "..(nGUID or "nil")) end end if owner then name = name.." <"..owner..">" end -- Recount:DPrint("Party guardian: "..name.." "..(nGUID or "nil").." "..(owner or "nil").." "..(ownerID or "nil")) -- end else petName = Recount:GetGuardianOwnerByGUID(nameGUID) if petName then petName, owner = petName:match("(.-) <(.*)>") end end end return name, owner, ownerID end function Recount:BossFightWho(srcFlags,dstFlags,victimData, victim) if Recount:InGroup(srcFlags) and not Recount:IsFriend(dstFlags) then if not victimData.level then --Recount:Print(victimData.Name.." lacks level, please report") -- This happens for freezing traps intriguingly enough victimData.level = 1 end if (victimData.level==-1) or ((Recount.FightingLevel~=-1) and (victimData.level>Recount.FightingLevel)) then Recount.FightingWho=victim Recount.FightingLevel=victimData.level end end end function Recount:BossFightWhoFromFlags(srcFlags, dstFlags, victim, victimGUID) if Recount:InGroup(srcFlags) and not Recount:IsFriend(dstFlags) then if Recount.IsBoss(victimGUID) then Recount.FightingWho = victim Recount.FightingLevel = -1 elseif Recount.FightingWho=="" then Recount.FightingWho = victim Recount.FightingLevel = 1 end end end local dottime -- Duration of a dot local DPass -- nil or damage to record function Recount:AddDamageData(source, victim, ability, element, hittype, damage, resist, srcGUID, srcFlags, dstGUID, dstFlags, spellId, blocked, absorbed, isDot) --Is this friendly fire? local FriendlyFire = Recount:IsFriendlyFire(srcFlags,dstFlags) --Before any further processing need to check if we are going to be placed in combat or in combat if not Recount.InCombat and Recount.db.profile.RecordCombatOnly then if (not FriendlyFire) and (Recount:InGroup(srcFlags) or Recount:InGroup(dstFlags)) then Recount:PutInCombat() end end if spellId == 70890 then -- Elsia: This isn't elegant but alas... this disambiguates Scourge Strike by attaching the spell-school as label if it's the shadow portion. Blizz really should not have given both parts the exact same spell name. if not element then element = "Shadow" -- This is a band-aid, this should never be necessary but evidentally some scourge strike events arrive with broken spell school fields! end ability = ability.." ("..element..")" end -- Name and ID of pet owners local sourceowner local sourceownerID local victimowner local victimownerID source, sourceowner, sourceownerID = Recount:DetectPet(source, srcGUID, srcFlags) victim, victimowner, victimownerID = Recount:DetectPet(victim, dstGUID, dstFlags) -- Extra attack handling if extraattacks and extraattacks[source] and not extraattacks[source].ability then Recount:DPrint("Proc ability: "..ability) extraattacks[source].ability = ability elseif extraattacks and extraattacks[source] and extraattacks[source].ability and ability==L["Melee"] then if extraattacks[source].proctime < GetTime()-5 then -- This is an outdated proc of which we never saw damage contributions. Timeout at 5 seconds extraattacks[source] = nil else Recount:DPrint("Damage proc: "..ability.." "..extraattacks[source].spellName.." "..(damage or "0")) ability = extraattacks[source].ability .. " ("..extraattacks[source].spellName..")" extraattacks[source].amount = extraattacks[source].amount - 1 if extraattacks[source].amount == 0 then extraattacks[source] = nil end end end -- Death log entry text Recount.cleventtext = source.." "..ability.." "..victim.." "..hittype if damage then Recount.cleventtext = Recount.cleventtext.." -"..damage end if resist and resist > 0 then Recount.cleventtext = Recount.cleventtext .." ("..resist..L[" resisted"]..")" end if absorbed and absorbed > 0 then Recount.cleventtext = Recount.cleventtext .." ("..absorbed.." "..L["Absorbed"]..")" end if element then Recount.cleventtext = Recount.cleventtext.." ("..element..")" end if srcRetention then if not dbCombatants[source] then Recount:AddCombatant(source,sourceowner,srcGUID,srcFlags, sourceownerID) end -- Reference to combatant data local sourceData=dbCombatants[source] if sourceData then Recount:SetActive(sourceData) --Need to add events for potential deaths Recount:AddCurrentEvent(sourceData, "DAMAGE", false, nil,Recount.cleventtext) --Fight tracking purposes to speed up leaving combat sourceData.LastFightIn=Recount.db2.FightNum --Melee is always considered Melee since its handled differently from specials keep it seperate if ability==L["Melee"] then element="Melee" end --Need to set the source as active Recount:AddTimeEvent(sourceData,victim,ability,false) --Stats for keeping track of DOT Uptime if isDot or hittype=="Tick" then --3 is default time since most abilities have 3 seconds inbetween ticks dottime = DotTickTimeId[spellId] or 3 Recount:AddAmount(sourceData,"DOT_Time",dottime) Recount:AddTableDataSum(sourceData,"DOTs",ability,victim,dottime) end if damage then --Record the element type -- sourceData.AbilityType = sourceData.AbilityType or {} -- sourceData.AbilityType[ability]=element --Alright now if there was a friendly damage done or not decides where this data goes for the source if not FriendlyFire then Recount:AddOwnerPetLazySyncAmount(sourceData,"Damage", damage) --Recount:AddSyncAmount(sourceData, "Damage", damage) Recount:AddAmount(sourceData,"Damage",damage) local newhittype = hittype if blocked then newhittype = hittype .. " ("..L["Blocked"]..")" end Recount:AddTableDataStats(sourceData,"Attacks",ability,newhittype,damage) Recount:AddAmount2(sourceData,"ElementDone",element,damage) else --Recount:AddOwnerPetLazySyncAmount(sourceData,"FDamage", damage) -- We don't currently sync friendly damage --Recount:AddSyncAmount(sourceData, "FDamage", damage) Recount:AddAmount(sourceData,"FDamage",damage) Recount:AddTableDataStats(sourceData,"FAttacks",ability,hittype,damage) Recount:AddTableDataSum(sourceData,"FDamagedWho",victim,ability,damage) -- Fix to track friendly fire contributions in death log. if dstRetention then if not dbCombatants[victim] then Recount:AddCombatant(victim,victimowner,dstGUID,dstFlags, victimownerID) end -- Reference to combatant data local victimData=dbCombatants[victim] if victimData then --Need to add events for potential deaths DPass=damage if DPass==0 then DPass=nil end Recount:AddCurrentEvent(victimData, "DAMAGE", true, DPass, Recount.cleventtext) end end return end Recount:AddTableDataSum(sourceData,"DamagedWho",victim,ability,damage) else Recount:AddTableDataStatsNoAmount(sourceData,"Attacks",ability,hittype) end -- Elsia: Moved this out because we want this recorded regardless whether it was friendly damage or not -- Elsia: Also removed bug, victims resist/block/absorb! if resist then Recount:AddAmount2(sourceData,"ElementDoneResist",element,resist) end if blocked then Recount:AddAmount2(sourceData,"ElementDoneBlock",element,blocked) end if absorbed then Recount:AddAmount2(sourceData,"ElementDoneAbsorb",element,absorbed) end --Needs to be here for tracking so we don't add Friendly Damage as well if Tracking["DAMAGE"] then if Tracking["DAMAGE"][source] then for _, v in pairs(Tracking["DAMAGE"][source]) do v.func(v.pass,damage) end end if Recount:InGroup(srcFlags) and Tracking["DAMAGE"]["!RAID"] then for _, v in pairs(Tracking["DAMAGE"]["!RAID"]) do v.func(v.pass,damage) end end end Recount:BossFightWho(dstFlags,srcFlags,sourceData,source) if element then Recount:AddTableDataSum(sourceData,"ElementHitsDone",element,hittype,1) end end else Recount:BossFightWhoFromFlags(dstFlags,srcFlags,source,srcGUID) end if dstRetention then if not dbCombatants[victim] then Recount:AddCombatant(victim,victimowner,dstGUID,dstFlags, victimownerID) end -- Reference to combatant data local victimData=dbCombatants[victim] if victimData then Recount:SetActive(victimData) --Need to add events for potential deaths DPass=damage if DPass==0 then DPass=nil end Recount:AddCurrentEvent(victimData, "DAMAGE", true, DPass, Recount.cleventtext) --Fight tracking purposes to speed up leaving combat victimData.LastFightIn=Recount.db2.FightNum --Melee is always considered Melee since its handled differently from specials keep it seperate if ability==L["Melee"] then element="Melee" end if damage then --Victim always cares Recount:AddAmount(victimData,"DamageTaken",damage) Recount:AddTableDataSum(victimData,"WhoDamaged",source,ability,damage) --Sync Data Recount:AddOwnerPetLazySyncAmount(victimData,"DamageTaken", damage) --Recount:AddSyncAmount(victimData, "DamageTaken", damage) Recount:AddAmount2(victimData,"ElementTaken",element,damage) --For identifying who killed when no message is triggered victimData.LastAttackedBy=source victimData.LastDamageTaken=damage victimData.LastDamageAbility=ability end if resist then -- Elsia: Fixed bug, source has to "take" resists, blocks and absorbs. if hittype=="Crit" then resist=resist*2 end Recount:AddAmount2(victimData,"ElementTakenResist",element,resist) if resist<(damage/2.5) then --25% Resist Recount:AddTableDataStats(victimData,"PartialResist",ability,"25%"..L["Resist"],resist) elseif resist<(1.25*damage) then --50% Resist Recount:AddTableDataStats(victimData,"PartialResist",ability,"50%"..L["Resist"],resist) else --75% Resist Recount:AddTableDataStats(victimData,"PartialResist",ability,"75%"..L["Resist"],resist) end else Recount:AddTableDataStats(victimData,"PartialResist",ability,L["No Resist"],0) end if blocked or hittype=="Block" then Recount:AddAmount2(victimData,"ElementTakenBlock",element,blocked) if blocked then Recount:AddTableDataStats(victimData,"PartialBlock",ability,L["Blocked"],blocked) else Recount:AddTableDataStats(victimData,"PartialBlock",ability,L["No Block"],0) end end if absorbed then Recount:AddAmount2(victimData,"ElementTakenAbsorb",element,absorbed) Recount:AddTableDataStats(victimData,"PartialAbsorb",ability,L["Absorbed"],absorbed) else Recount:AddTableDataStats(victimData,"PartialAbsorb",ability,L["No Absorb"],0) end -- Elsia: Moved this out because we want this recorded regardless whether it was friendly damage or not -- Elsia: Also removed bug, victims resist/block/absorb! --Tracking for passing data to other functions if Tracking["DAMAGETAKEN"] then if Tracking["DAMAGETAKEN"][victim] then for _, v in pairs(Tracking["DAMAGETAKEN"][victim]) do v.func(v.pass,damage) end end if Recount:InGroup(dstFlags) and Tracking["DAMAGETAKEN"]["!RAID"] then for _, v in pairs(Tracking["DAMAGETAKEN"]["!RAID"]) do v.func(v.pass,damage) end end end Recount:BossFightWho(srcFlags,dstFlags,victimData, victim) if element then Recount:AddTableDataSum(victimData,"ElementHitsTaken",element,hittype,1) end end else Recount:BossFightWhoFromFlags(srcFlags,dstFlags,victim,dstGUID) end end function Recount:AddHealData(source, victim, ability, healtype, amount, overheal,srcGUID,srcFlags,dstGUID,dstFlags,spellId,isHot) --First lets figure if there was overhealing --Get the tables -- Name and ID of pet owners local sourceowner local sourceownerID local victimowner local victimownerID source, sourceowner, sourceownerID = Recount:DetectPet(source, srcGUID, srcFlags) victim, victimowner, victimownerID = Recount:DetectPet(victim, dstGUID, dstFlags) Recount.cleventtext = source.." "..ability.." "..victim if healtype then Recount.cleventtext = Recount.cleventtext.." "..healtype end if amount then Recount.cleventtext = Recount.cleventtext.." +"..amount end if overheal and overheal ~= 0 then Recount.cleventtext = Recount.cleventtext .." ("..overheal..L[" overheal"]..")" end local sourceData if srcRetention then if not dbCombatants[source] then Recount:AddCombatant(source,sourceowner,srcGUID,srcFlags,sourceownerID) end sourceData=dbCombatants[source] if sourceData then Recount:SetActive(sourceData) --Need to add events for potential deaths if source~=victim then Recount:AddCurrentEvent(sourceData, "HEAL", false, nil,Recount.cleventtext) end end end local victimData if dstRetention then if not dbCombatants[victim] then Recount:AddCombatant(victim,victimowner,dstGUID,dstFlags, victimownerID) -- Elsia: Bug, owner was missing here end victimData=dbCombatants[victim] if victimData then Recount:SetActive(victimData) --Need to add events for potential deaths Recount:AddCurrentEvent(victimData, "HEAL", true, amount,Recount.cleventtext) end --Before any further processing need to check if we are in combat if not Recount.InCombat and Recount.db.profile.RecordCombatOnly then return end end --Fight tracking purposes to speed up leaving combat --if not sourceData then Recount:DPrint("Source-less heal: "..(ability or "nil")..(source or "nil").." "..(victim or "nil").." Please report!") end if Recount:IsFriend(dstFlags) then -- Only record heals of friends as heals. if overheal == nil then overheal=0 elseif overheal > 0 then amount = amount - overheal end if srcRetention and sourceData then sourceData.LastFightIn=Recount.db2.FightNum Recount:AddOwnerPetLazySyncAmount(sourceData,"Healing", amount) Recount:AddOwnerPetLazySyncAmount(sourceData,"Overhealing", overheal) --Tracking for passing data to other functions if Tracking["HEALING"] then if Tracking["HEALING"][source] then for _, v in pairs(Tracking["HEALING"][source]) do v.func(v.pass,amount) end end if sourceData and Recount:InGroup(srcFlags) and Tracking["HEALING"]["!RAID"] then for _, v in pairs(Tracking["HEALING"]["!RAID"]) do v.func(v.pass,amount) end end end --Need to set the source as active if amount > 0 then -- Restore legacy behavior, i.e. pure overheals are not counted. Recount:AddTimeEvent(sourceData,victim,ability,true) end --Stats for keeping track of HOT Uptime if isHot or healtype=="Tick" then --3 is default time since most abilities have 3 seconds inbetween ticks local hottime=HotTickTimeId[spellId] or 3 Recount:AddAmount(sourceData,"HOT_Time",hottime) Recount:AddTableDataSum(sourceData,"HOTs",ability,victim,hottime) end --No reason to add information if everything was overhealing if amount>0 then Recount:AddAmount(sourceData,"Healing",amount) Recount:AddTableDataStats(sourceData,"Heals",ability,healtype,amount) Recount:AddTableDataSum(sourceData,"HealedWho",victim,ability,amount) end --Now if there was overhealing lets add that data in if overheal>0 then Recount:AddAmount(sourceData,"Overhealing",overheal) Recount:AddTableDataStats(sourceData,"OverHeals",ability,healtype,overheal) end end end if dstRetention and victimData then victimData.LastFightIn=Recount.db2.FightNum --[[ local VictimUnit=victimData.unit if (not VictimUnit or victim~=UnitName(VictimUnit)) and (victimData.UnitLockout>Recount.UnitLockout) then victimData.UnitLockout=Recount.CurTime VictimUnit=Recount:FindUnit(victim) victimData.unit=VictimUnit end ]] Recount:AddOwnerPetLazySyncAmount(victimData,"HealingTaken", amount) if Tracking["HEALINGTAKEN"] then if Tracking["HEALINGTAKEN"][victim] then for _, v in pairs(Tracking["HEALINGTAKEN"][victim]) do v.func(v.pass,amount) end end if Recount:InGroup(dstFlags) and Tracking["HEALINGTAKEN"]["!RAID"] then for _, v in pairs(Tracking["HEALINGTAKEN"]["!RAID"]) do v.func(v.pass,amount) end end end --No reason to add information if everything was overhealing if amount>0 then Recount:AddAmount(victimData,"HealingTaken",amount) Recount:AddTableDataSum(victimData,"WhoHealed",source,ability,amount) end end end function Recount:HandleDoubleDeath(source, victim, skill,srcGUID,srcFlags,dstGUID,dstFlags,spellId) if dstRetention then -- Name and ID of pet owners local victimowner local victimownerID victim, victimowner, victimownerID = Recount:DetectPet(victim, dstGUID, dstFlags) --Get the tables if not dbCombatants[victim] then Recount:AddCombatant(victim, victimowner,dstGUID,dstFlags, victimownerID) -- Elsia: Bug owner missing end local victimData=dbCombatants[victim] if not victimData then return end victimData.DoubleDeathSpellID = spellId victimData.DoubleDeathSpellName = skill victimData.DoubleDeathTime = GetTime() end end local deathargs={} local timeofdeath local doubleDeathDelay function Recount:AddDeathData(source, victim, skill,srcGUID,srcFlags,dstGUID,dstFlags,spellId) --Before any further processing need to check if we are in combat --Recount:DPrint("Add Death: "..victim) --[[ if not Recount.InCombat and Recount.db.profile.RecordCombatOnly then --Recount:Print("Death out of combat, not recorded") return end]] -- Record all deaths. -- Name and ID of pet owners local sourceowner local sourceownerID local victimowner local victimownerID if source and type(source) == "string" then -- Elsia: Fix bug when death doesn't have a killer source, sourceowner, sourceownerID = Recount:DetectPet(source, srcGUID, srcFlags) end victim, victimowner, victimownerID = Recount:DetectPet(victim, dstGUID, dstFlags) if srcRetention then --Need to add events for potential deaths if source and source~=victim then -- Elsia: May be worth removing the source~=victim check if not dbCombatants[source] then Recount:AddCombatant(source,sourceowner,srcGUID,srcFlags, sourceownerID) end local sourceData=dbCombatants[source] if sourceData then sourceData.LastFightIn=Recount.db2.FightNum Recount:AddCurrentEvent(sourceData, "MISC", false) end end end if dstRetention then --Get the tables if not dbCombatants[victim] then Recount:AddCombatant(victim, victimowner,dstGUID,dstFlags, victimownerID) -- Elsia: Bug owner missing end local victimData=dbCombatants[victim] if victimData then -- This is fix to prevent feign death to show as real deaths. if Recount:InGroup(dstFlags) and UnitIsFeignDeath(victim) then --Recount:Print("Yikes: Feign Death reported as real, please report!") return -- No need to record this! end --Fight tracking purposes to speed up leaving combat victimData.LastFightIn=Recount.db2.FightNum Recount.cleventtext = victim..L[" dies."] -- Check for Spirit of Redemption or Ghoul timeofdeath = GetTime() doubleDeathDelay = victimData.DoubleDeathTime and timeofdeath-victimData.DoubleDeathTime or 10 if doubleDeathDelay < 2 then Recount.cleventtext = Recount.cleventtext .. " ("..victimData.DoubleDeathSpellName..")" end Recount:AddCurrentEvent(victimData, "MISC", true,nil,Recount.cleventtext) --This saves who/what killed the victim if source then victimData.LastKilledBy=source victimData.LastKilledAt=timeofdeath elseif skill then victimData.LastKilledBy=skill victimData.LastKilledAt=timeofdeath elseif not victimData.DoubleDeathSpellID or doubleDeathDelay >= 2 then -- We don't count spirits and ghouls --The case where we actually add a deathcount Recount:AddAmount(victimData,"DeathCount",1) end -- Removed cached double event if it existed. victimData.DoubleDeathSpellID = nil victimData.DoubleDeathSpellName = nil victimData.DoubleDeathTime = nil --We delay the saving of the event logs just in case more messages come later if Recount.db.profile.Filters.TrackDeaths[victimData.type] then --Recount:ScheduleTimer(Recount.HandleDeath,2,Recount,victim,GetTime(),dstGUID,dstFlags) deathargs={} -- Elsia: Make sure we create new ones in case of overlapping deaths! deathargs[1]=victim deathargs[2]=timeofdeath deathargs[3]=dstGUID deathargs[4]=dstFlags Recount:ScheduleTimer("HandleDeath",2,deathargs) --Recount:HandleDeath(deathargs) end end end end function Recount:HandleDeath(arg) local victim,DeathTime,dstGUID,dstFlags = unpack(arg) -- Recount:DPrint("death: "..victim) if not dbCombatants[victim] then return end local who=dbCombatants[victim] local num=Recount.db.profile.MessagesTracked local DeathLog=Recount:GetTable() DeathLog.DeathAt=Recount.CurTime DeathLog.Messages=Recount:GetTable() DeathLog.MessageTimes=Recount:GetTable() DeathLog.MessageType=Recount:GetTable() DeathLog.MessageIncoming=Recount:GetTable() DeathLog.Health=Recount:GetTable() DeathLog.HealthNum=Recount:GetTable() DeathLog.EventNum=Recount:GetTable() if who.LastKilledBy and math_abs(who.LastKilledAt-DeathTime)<2 then DeathLog.KilledBy=who.LastKilledBy elseif who.LastAttackedBy then DeathLog.KilledBy=who.LastAttackedBy who.LastAttackedBy=nil end local offset for i=1,num do offset=math_fmod(who.NextEventNum+i+num-2,num)+1 if who.LastEvents[offset] and (who.LastEventTimes[offset]-DeathTime)>-15 then DeathLog.MessageTimes[#DeathLog.MessageTimes+1]=who.LastEventTimes[offset]-DeathTime DeathLog.Messages[#DeathLog.Messages+1]=who.LastEvents[offset] or "" DeathLog.MessageType[#DeathLog.MessageType+1]=who.LastEventType[offset] or "MISC" DeathLog.MessageIncoming[#DeathLog.MessageIncoming+1]=who.LastEventIncoming[offset] or false DeathLog.Health[#DeathLog.Health+1]=who.LastEventHealth[offset] or 0 DeathLog.HealthNum[#DeathLog.HealthNum+1]=who.LastEventHealthNum[offset] or 0 DeathLog.EventNum[#DeathLog.HealthNum]=who.LastEventNum and who.LastEventNum[offset] or 0 end end who.DeathLogs = who.DeathLogs or {} tinsert(who.DeathLogs,1,DeathLog) if RecountDeathTrack then --Recount:DPrint(who.LastDamageTaken) RecountDeathTrack:AddDeath(victim, DeathTime-(Recount.InCombatT2 or DeathTime), who.LastDamageTaken , who, who.DeathLogs)--[[who.LastDamageAbility.." "..who.LastDamageTaken]]-- end --who.DeathLogs[#who.DeathLogs+1]=DeathLog end function Recount:SpellAuraDispelFailed(timestamp, eventtype, srcGUID, srcName, srcFlags, dstGUID, dstName, dstFlags,spellId, spellName, spellSchool, extraSpellId, extraSpellName, extraSpellSchool) -- Ignoring this one for now end --Potential Tracking --"DAMAGE" --"DAMAGETAKEN" --"HEALING" --"HEALINGTAKEN" --function Recount:FPSUpdate(pass) --end function Recount:RegisterTracking(id, who, stat, func, pass) --Special trackers handled first local idtoken if stat=="FPS" then idtoken=Recount:ScheduleRepeatingTimer(function() func(pass,GetFramerate()*0.1) end,0.1) -- id.."_TRACKER", --return -- Elsia: Removed this so we store tokens elseif stat=="LAG" then idtoken=Recount:ScheduleRepeatingTimer(function() local _, _, lag = GetNetStats(); func(pass,lag*0.1) end,0.1) elseif stat=="UP_TRAFFIC" then idtoken=Recount:ScheduleRepeatingTimer(function() local _, up = GetNetStats(); func(pass,1024*up*0.1) end,0.1) elseif stat=="DOWN_TRAFFIC" then idtoken=Recount:ScheduleRepeatingTimer(function() local down = GetNetStats(); func(pass,1024*down*0.1) end,0.1) elseif stat=="AVAILABLE_BANDWIDTH" then idtoken=Recount:ScheduleRepeatingTimer(function() func(pass,ChatThrottleLib:UpdateAvail()*0.1) end,0.1) end if type(Tracking[stat])~="table" then Tracking[stat]=Recount:GetTable() end if type(Tracking[stat][who])~="table" then Tracking[stat][who]=Recount:GetTable() end if type(Tracking[stat][who][id])~="table" then Tracking[stat][who][id]=Recount:GetTable() end Tracking[stat][who][id].func=func Tracking[stat][who][id].pass=pass Tracking[stat][who][id].token=idtoken end function Recount:UnregisterTracking(id, who, stat) if stat=="FPS" or stat=="LAG" or stat=="UP_TRAFFIC" or stat=="DOWN_TRAFFIC" or stat=="AVAILABLE_BANDWIDTH" then Recount:CancelTimer(Tracking[stat][who][id].token) -- Was id.."_TRACKER" return end if type(Tracking[stat])~="table" or type(Tracking[stat][who])~="table" then return end Tracking[stat][who][id]=nil end local oldlocalizer = Recount.LocalizeCombatants function Recount.LocalizeCombatants() dbCombatants = Recount.db2.combatants oldlocalizer() end