Quantcast

1. fix raid frame shows blizzard default raid frame

rawoil [06-28-21 - 10:04]
1. fix raid frame shows blizzard default raid frame
2. add healcomm support
Filename
oUF/elements/healthprediction.lua
oUF/elements/range.lua
oUF/libs/CallbackHandler-1.0/CallbackHandler-1.0.lua
oUF/libs/CallbackHandler-1.0/CallbackHandler-1.0.xml
oUF/libs/LibHealComm-4.0/ChatThrottleLib.lua
oUF/libs/LibHealComm-4.0/LibHealComm-4.0.lua
oUF/libs/LibHealComm-4.0/LibHealComm-4.0.xml
oUF/libs/LibStub/LibStub.lua
oUF/libs/LibStub/LibStub.toc
oUF/libs/LibStub/tests/test.lua
oUF/libs/LibStub/tests/test2.lua
oUF/libs/LibStub/tests/test3.lua
oUF/libs/LibStub/tests/test4.lua
oUF/oUF.toc
oUF_Simple/core/functions.lua
oUF_Simple/core/spawn.lua
oUF_Simple/core/style.lua
oUF_Simple/modules/oUF_Smooth.lua
oUF_Simple/oUF_Simple.toc
oUF_Simple/templates/raid.lua
oUF_SimpleConfig/global.lua
diff --git a/oUF/elements/healthprediction.lua b/oUF/elements/healthprediction.lua
index 19e61c4..ce19261 100644
--- a/oUF/elements/healthprediction.lua
+++ b/oUF/elements/healthprediction.lua
@@ -80,6 +80,25 @@ A default texture will be applied to the Texture widgets if they don't have a te

 local _, ns = ...
 local oUF = ns.oUF
+local myGUID = UnitGUID('player')
+local HealComm, ALL_PENDING_HEALS, ALL_OVERTIME_HEALS, HEAL_TICK_INTERVAL
+
+if oUF.BCC then
+	HealComm = LibStub("LibHealComm-4.0")
+
+	ALL_PENDING_HEALS = bit.bor(HealComm.DIRECT_HEALS, HealComm.BOMB_HEALS)
+	ALL_OVERTIME_HEALS = bit.bor(HealComm.CHANNEL_HEALS, HealComm.HOT_HEALS)
+	HEAL_TICK_INTERVAL = 3
+end
+
+local function GetHealAmount(targetGUID, currentTime, casterGUID)
+    local nextTickTime = currentTime + HEAL_TICK_INTERVAL
+
+    local pendingHeal = HealComm:GetHealAmount(targetGUID, ALL_PENDING_HEALS, nil, casterGUID) or 0
+    local overtimeHeal = HealComm:GetHealAmount(targetGUID, ALL_OVERTIME_HEALS, nextTickTime, casterGUID) or 0
+
+    return (pendingHeal + overtimeHeal) * HealComm:GetHealModifier(casterGUID)
+end

 local function Update(self, event, unit)
 	if(self.unit ~= unit) then return end
@@ -96,10 +115,13 @@ local function Update(self, event, unit)
 		element:PreUpdate(unit)
 	end

-	local myIncomingHeal = UnitGetIncomingHeals(unit, 'player') or 0
-	local allIncomingHeal = UnitGetIncomingHeals(unit) or 0
-	local absorb = UnitGetTotalAbsorbs(unit) or 0
-	local healAbsorb = UnitGetTotalHealAbsorbs(unit) or 0
+	local guid = UnitGUID(unit)
+	local currentTime = GetTime()
+	local isSmoothedEvent = event == "UNIT_MAXHEALTH" or event == "UNIT_HEALTH_FREQUENT" or event == "UNIT_HEALTH"
+	local myIncomingHeal = oUF.BCC and GetHealAmount(guid, currentTime, myGUID) or UnitGetIncomingHeals(unit, 'player') or 0
+	local allIncomingHeal = oUF.BCC and GetHealAmount(guid, currentTime, nil) or UnitGetIncomingHeals(unit) or 0
+	local absorb = oUF.Retail and UnitGetTotalAbsorbs(unit) or 0
+	local healAbsorb = oUF.Retail and UnitGetTotalHealAbsorbs(unit) or 0
 	local health, maxHealth = UnitHealth(unit), UnitHealthMax(unit)
 	local otherIncomingHeal = 0
 	local hasOverHealAbsorb = false
@@ -138,15 +160,31 @@ local function Update(self, event, unit)
 	end

 	if(element.myBar) then
-		element.myBar:SetMinMaxValues(0, maxHealth)
-		element.myBar:SetValue(myIncomingHeal)
-		element.myBar:Show()
+		if element.smoothing then
+            element.myBar:SetMinMaxSmoothedValue(0, maxHealth)
+            element.myBar:SetSmoothedValue(myIncomingHeal)
+        end
+
+		if not element.smoothing or not isSmoothedEvent then
+            element.myBar:SetMinMaxValues(0, maxHealth)
+            element.myBar:SetValue(myIncomingHeal)
+        end
+
+        element.myBar:Show()
 	end

 	if(element.otherBar) then
-		element.otherBar:SetMinMaxValues(0, maxHealth)
-		element.otherBar:SetValue(otherIncomingHeal)
-		element.otherBar:Show()
+		if element.smoothing then
+            element.otherBar:SetMinMaxSmoothedValue(0, maxHealth)
+            element.otherBar:SetSmoothedValue(otherIncomingHeal)
+        end
+
+		if not element.smoothing or not isSmoothedEvent then
+            element.otherBar:SetMinMaxValues(0, maxHealth)
+            element.otherBar:SetValue(otherIncomingHeal)
+        end
+
+        element.otherBar:Show()
 	end

 	if(element.absorbBar) then
@@ -194,7 +232,7 @@ local function Update(self, event, unit)
 	end
 end

-local function Path(self, ...)
+local function Path(self, event, ...)
 	--[[ Override: HealthPrediction.Override(self, event, unit)
 	Used to completely override the internal update function.

@@ -202,7 +240,16 @@ local function Path(self, ...)
 	* event - the event triggering the update (string)
 	* unit  - the unit accompanying the event
 	--]]
-	return (self.HealthPrediction.Override or Update) (self, ...)
+
+    if not oUF.Retail and self:IsVisible() then
+        for i = 1, select('#', ...) do
+            if self.unit and UnitGUID(self.unit) == (UnitGUID(select(i, ...)) or select(i, ...)) then
+                return (self.HealthPrediction.Override or Update) (self, event, self.unit)
+            end
+        end
+	else
+		return (self.HealthPrediction.Override or Update) (self, event, ...)
+    end
 end

 local function ForceUpdate(element)
@@ -214,24 +261,54 @@ local function Enable(self)
 	if(element) then
 		element.__owner = self
 		element.ForceUpdate = ForceUpdate
+
+		if oUF.Retail then
+			self:RegisterEvent('UNIT_HEALTH', Path)
+			self:RegisterEvent('UNIT_HEAL_PREDICTION', Path)
+			self:RegisterEvent('UNIT_ABSORB_AMOUNT_CHANGED', Path)
+			self:RegisterEvent('UNIT_HEAL_ABSORB_AMOUNT_CHANGED', Path)
+		else
+			self:RegisterEvent('UNIT_HEALTH_FREQUENT', Path)
+
+			local function UpdateHeal(event, casterGUID, spellID, healType, _, ...)
+				Path(self, event, ...)
+			end

-		self:RegisterEvent('UNIT_HEALTH', Path)
+			local function UpdateModifier(event, guid)
+				Path(self, event, guid)
+			end
+
+			HealComm.RegisterCallback(self, "HealComm_HealStarted", UpdateHeal)
+			HealComm.RegisterCallback(self, "HealComm_HealUpdated", UpdateHeal)
+			HealComm.RegisterCallback(self, "HealComm_HealDelayed", UpdateHeal)
+			HealComm.RegisterCallback(self, "HealComm_HealStopped", UpdateHeal)
+			HealComm.RegisterCallback(self, "HealComm_ModifierChanged", UpdateModifier)
+			HealComm.RegisterCallback(self, "HealComm_GUIDDisappeared", UpdateModifier)
+		end
+
 		self:RegisterEvent('UNIT_MAXHEALTH', Path)
-		self:RegisterEvent('UNIT_HEAL_PREDICTION', Path)
-		self:RegisterEvent('UNIT_ABSORB_AMOUNT_CHANGED', Path)
-		self:RegisterEvent('UNIT_HEAL_ABSORB_AMOUNT_CHANGED', Path)

 		if(not element.maxOverflow) then
 			element.maxOverflow = 1.05
 		end

 		if(element.myBar) then
+			if(element.smoothing) then
+                element.myBar.SetSmoothedValue = SmoothStatusBarMixin.SetSmoothedValue
+                element.myBar.SetMinMaxSmoothedValue = SmoothStatusBarMixin.SetMinMaxSmoothedValue
+            end
+
 			if(element.myBar:IsObjectType('StatusBar') and not element.myBar:GetStatusBarTexture()) then
 				element.myBar:SetStatusBarTexture([[Interface\TargetingFrame\UI-StatusBar]])
 			end
 		end

 		if(element.otherBar) then
+			if(element.smoothing) then
+                element.otherBar.SetSmoothedValue = SmoothStatusBarMixin.SetSmoothedValue
+                element.otherBar.SetMinMaxSmoothedValue = SmoothStatusBarMixin.SetMinMaxSmoothedValue
+            end
+
 			if(element.otherBar:IsObjectType('StatusBar') and not element.otherBar:GetStatusBarTexture()) then
 				element.otherBar:SetStatusBarTexture([[Interface\TargetingFrame\UI-StatusBar]])
 			end
diff --git a/oUF/elements/range.lua b/oUF/elements/range.lua
index 201cab5..f68e920 100644
--- a/oUF/elements/range.lua
+++ b/oUF/elements/range.lua
@@ -33,6 +33,15 @@ local OnRangeFrame

 local UnitInRange, UnitIsConnected = UnitInRange, UnitIsConnected

+local RangeSpellID = {
+	["PALADIN"] = 19750,
+	["SHAMAN"] = 25357,
+	["DRUID"] = 774,
+	["PRIEST"] = 2050,
+	["WARLOCK"] = 5697,
+	["MAGE"] = 475,
+}
+
 local function Update(self, event)
 	local element = self.Range
 	local unit = self.unit
@@ -52,8 +61,24 @@ local function Update(self, event)
 		if oUF.Retail then
 			inRange, checkedRange = UnitInRange(unit)
 		else
-			inRange = CheckInteractDistance(unit, 4)
-			checkedRange = true
+			local Spell = RangeSpellID[select(2, UnitClass("player"))]
+			local IsFriend = UnitIsFriend(unit, "player")
+
+			if IsFriend and Spell and IsSpellKnown(Spell) then
+				local name, rank, icon, castTime, minRange, maxRange, spellId = GetSpellInfo(Spell)
+				local IsSpellInRangeFromPlayer = IsSpellInRange(name, unit)
+
+				if IsSpellInRangeFromPlayer == 1 then
+					inRange = true
+					checkedRange = true
+				else
+					inRange = false
+					checkedRange = true
+				end
+			else
+				inRange = CheckInteractDistance(unit, 4)
+				checkedRange = true
+			end
 		end

 		if(checkedRange and not inRange) then
diff --git a/oUF/libs/CallbackHandler-1.0/CallbackHandler-1.0.lua b/oUF/libs/CallbackHandler-1.0/CallbackHandler-1.0.lua
new file mode 100644
index 0000000..39e265d
--- /dev/null
+++ b/oUF/libs/CallbackHandler-1.0/CallbackHandler-1.0.lua
@@ -0,0 +1,212 @@
+--[[ $Id: CallbackHandler-1.0.lua 22 2018-07-21 14:17:22Z nevcairiel $ ]]
+local MAJOR, MINOR = "CallbackHandler-1.0", 7
+local CallbackHandler = LibStub:NewLibrary(MAJOR, MINOR)
+
+if not CallbackHandler then return end -- No upgrade needed
+
+local meta = {__index = function(tbl, key) tbl[key] = {} return tbl[key] end}
+
+-- Lua APIs
+local tconcat = table.concat
+local assert, error, loadstring = assert, error, loadstring
+local setmetatable, rawset, rawget = setmetatable, rawset, rawget
+local next, select, pairs, type, tostring = next, select, pairs, type, tostring
+
+-- Global vars/functions that we don't upvalue since they might get hooked, or upgraded
+-- List them here for Mikk's FindGlobals script
+-- GLOBALS: geterrorhandler
+
+local xpcall = xpcall
+
+local function errorhandler(err)
+	return geterrorhandler()(err)
+end
+
+local function Dispatch(handlers, ...)
+	local index, method = next(handlers)
+	if not method then return end
+	repeat
+		xpcall(method, errorhandler, ...)
+		index, method = next(handlers, index)
+	until not method
+end
+
+--------------------------------------------------------------------------
+-- CallbackHandler:New
+--
+--   target            - target object to embed public APIs in
+--   RegisterName      - name of the callback registration API, default "RegisterCallback"
+--   UnregisterName    - name of the callback unregistration API, default "UnregisterCallback"
+--   UnregisterAllName - name of the API to unregister all callbacks, default "UnregisterAllCallbacks". false == don't publish this API.
+
+function CallbackHandler:New(target, RegisterName, UnregisterName, UnregisterAllName)
+
+	RegisterName = RegisterName or "RegisterCallback"
+	UnregisterName = UnregisterName or "UnregisterCallback"
+	if UnregisterAllName==nil then	-- false is used to indicate "don't want this method"
+		UnregisterAllName = "UnregisterAllCallbacks"
+	end
+
+	-- we declare all objects and exported APIs inside this closure to quickly gain access
+	-- to e.g. function names, the "target" parameter, etc
+
+
+	-- Create the registry object
+	local events = setmetatable({}, meta)
+	local registry = { recurse=0, events=events }
+
+	-- registry:Fire() - fires the given event/message into the registry
+	function registry:Fire(eventname, ...)
+		if not rawget(events, eventname) or not next(events[eventname]) then return end
+		local oldrecurse = registry.recurse
+		registry.recurse = oldrecurse + 1
+
+		Dispatch(events[eventname], eventname, ...)
+
+		registry.recurse = oldrecurse
+
+		if registry.insertQueue and oldrecurse==0 then
+			-- Something in one of our callbacks wanted to register more callbacks; they got queued
+			for eventname,callbacks in pairs(registry.insertQueue) do
+				local first = not rawget(events, eventname) or not next(events[eventname])	-- test for empty before. not test for one member after. that one member may have been overwritten.
+				for self,func in pairs(callbacks) do
+					events[eventname][self] = func
+					-- fire OnUsed callback?
+					if first and registry.OnUsed then
+						registry.OnUsed(registry, target, eventname)
+						first = nil
+					end
+				end
+			end
+			registry.insertQueue = nil
+		end
+	end
+
+	-- Registration of a callback, handles:
+	--   self["method"], leads to self["method"](self, ...)
+	--   self with function ref, leads to functionref(...)
+	--   "addonId" (instead of self) with function ref, leads to functionref(...)
+	-- all with an optional arg, which, if present, gets passed as first argument (after self if present)
+	target[RegisterName] = function(self, eventname, method, ... --[[actually just a single arg]])
+		if type(eventname) ~= "string" then
+			error("Usage: "..RegisterName.."(eventname, method[, arg]): 'eventname' - string expected.", 2)
+		end
+
+		method = method or eventname
+
+		local first = not rawget(events, eventname) or not next(events[eventname])	-- test for empty before. not test for one member after. that one member may have been overwritten.
+
+		if type(method) ~= "string" and type(method) ~= "function" then
+			error("Usage: "..RegisterName.."(\"eventname\", \"methodname\"): 'methodname' - string or function expected.", 2)
+		end
+
+		local regfunc
+
+		if type(method) == "string" then
+			-- self["method"] calling style
+			if type(self) ~= "table" then
+				error("Usage: "..RegisterName.."(\"eventname\", \"methodname\"): self was not a table?", 2)
+			elseif self==target then
+				error("Usage: "..RegisterName.."(\"eventname\", \"methodname\"): do not use Library:"..RegisterName.."(), use your own 'self'", 2)
+			elseif type(self[method]) ~= "function" then
+				error("Usage: "..RegisterName.."(\"eventname\", \"methodname\"): 'methodname' - method '"..tostring(method).."' not found on self.", 2)
+			end
+
+			if select("#",...)>=1 then	-- this is not the same as testing for arg==nil!
+				local arg=select(1,...)
+				regfunc = function(...) self[method](self,arg,...) end
+			else
+				regfunc = function(...) self[method](self,...) end
+			end
+		else
+			-- function ref with self=object or self="addonId" or self=thread
+			if type(self)~="table" and type(self)~="string" and type(self)~="thread" then
+				error("Usage: "..RegisterName.."(self or \"addonId\", eventname, method): 'self or addonId': table or string or thread expected.", 2)
+			end
+
+			if select("#",...)>=1 then	-- this is not the same as testing for arg==nil!
+				local arg=select(1,...)
+				regfunc = function(...) method(arg,...) end
+			else
+				regfunc = method
+			end
+		end
+
+
+		if events[eventname][self] or registry.recurse<1 then
+		-- if registry.recurse<1 then
+			-- we're overwriting an existing entry, or not currently recursing. just set it.
+			events[eventname][self] = regfunc
+			-- fire OnUsed callback?
+			if registry.OnUsed and first then
+				registry.OnUsed(registry, target, eventname)
+			end
+		else
+			-- we're currently processing a callback in this registry, so delay the registration of this new entry!
+			-- yes, we're a bit wasteful on garbage, but this is a fringe case, so we're picking low implementation overhead over garbage efficiency
+			registry.insertQueue = registry.insertQueue or setmetatable({},meta)
+			registry.insertQueue[eventname][self] = regfunc
+		end
+	end
+
+	-- Unregister a callback
+	target[UnregisterName] = function(self, eventname)
+		if not self or self==target then
+			error("Usage: "..UnregisterName.."(eventname): bad 'self'", 2)
+		end
+		if type(eventname) ~= "string" then
+			error("Usage: "..UnregisterName.."(eventname): 'eventname' - string expected.", 2)
+		end
+		if rawget(events, eventname) and events[eventname][self] then
+			events[eventname][self] = nil
+			-- Fire OnUnused callback?
+			if registry.OnUnused and not next(events[eventname]) then
+				registry.OnUnused(registry, target, eventname)
+			end
+		end
+		if registry.insertQueue and rawget(registry.insertQueue, eventname) and registry.insertQueue[eventname][self] then
+			registry.insertQueue[eventname][self] = nil
+		end
+	end
+
+	-- OPTIONAL: Unregister all callbacks for given selfs/addonIds
+	if UnregisterAllName then
+		target[UnregisterAllName] = function(...)
+			if select("#",...)<1 then
+				error("Usage: "..UnregisterAllName.."([whatFor]): missing 'self' or \"addonId\" to unregister events for.", 2)
+			end
+			if select("#",...)==1 and ...==target then
+				error("Usage: "..UnregisterAllName.."([whatFor]): supply a meaningful 'self' or \"addonId\"", 2)
+			end
+
+
+			for i=1,select("#",...) do
+				local self = select(i,...)
+				if registry.insertQueue then
+					for eventname, callbacks in pairs(registry.insertQueue) do
+						if callbacks[self] then
+							callbacks[self] = nil
+						end
+					end
+				end
+				for eventname, callbacks in pairs(events) do
+					if callbacks[self] then
+						callbacks[self] = nil
+						-- Fire OnUnused callback?
+						if registry.OnUnused and not next(callbacks) then
+							registry.OnUnused(registry, target, eventname)
+						end
+					end
+				end
+			end
+		end
+	end
+
+	return registry
+end
+
+
+-- CallbackHandler purposefully does NOT do explicit embedding. Nor does it
+-- try to upgrade old implicit embeds since the system is selfcontained and
+-- relies on closures to work.
+
diff --git a/oUF/libs/CallbackHandler-1.0/CallbackHandler-1.0.xml b/oUF/libs/CallbackHandler-1.0/CallbackHandler-1.0.xml
new file mode 100644
index 0000000..876df83
--- /dev/null
+++ b/oUF/libs/CallbackHandler-1.0/CallbackHandler-1.0.xml
@@ -0,0 +1,4 @@
+<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/
+..\FrameXML\UI.xsd">
+	<Script file="CallbackHandler-1.0.lua"/>
+</Ui>
\ No newline at end of file
diff --git a/oUF/libs/LibHealComm-4.0/ChatThrottleLib.lua b/oUF/libs/LibHealComm-4.0/ChatThrottleLib.lua
new file mode 100644
index 0000000..ad21af4
--- /dev/null
+++ b/oUF/libs/LibHealComm-4.0/ChatThrottleLib.lua
@@ -0,0 +1,534 @@
+--
+-- ChatThrottleLib by Mikk
+--
+-- Manages AddOn chat output to keep player from getting kicked off.
+--
+-- ChatThrottleLib:SendChatMessage/:SendAddonMessage functions that accept
+-- a Priority ("BULK", "NORMAL", "ALERT") as well as prefix for SendChatMessage.
+--
+-- Priorities get an equal share of available bandwidth when fully loaded.
+-- Communication channels are separated on extension+chattype+destination and
+-- get round-robinned. (Destination only matters for whispers and channels,
+-- obviously)
+--
+-- Will install hooks for SendChatMessage and SendAddonMessage to measure
+-- bandwidth bypassing the library and use less bandwidth itself.
+--
+--
+-- Fully embeddable library. Just copy this file into your addon directory,
+-- add it to the .toc, and it's done.
+--
+-- Can run as a standalone addon also, but, really, just embed it! :-)
+--
+-- LICENSE: ChatThrottleLib is released into the Public Domain
+--
+
+local CTL_VERSION = 24
+
+local _G = _G
+
+if _G.ChatThrottleLib then
+	if _G.ChatThrottleLib.version >= CTL_VERSION then
+		-- There's already a newer (or same) version loaded. Buh-bye.
+		return
+	elseif not _G.ChatThrottleLib.securelyHooked then
+		print("ChatThrottleLib: Warning: There's an ANCIENT ChatThrottleLib.lua (pre-wow 2.0, <v16) in an addon somewhere. Get the addon updated or copy in a newer ChatThrottleLib.lua (>=v16) in it!")
+		-- ATTEMPT to unhook; this'll behave badly if someone else has hooked...
+		-- ... and if someone has securehooked, they can kiss that goodbye too... >.<
+		_G.SendChatMessage = _G.ChatThrottleLib.ORIG_SendChatMessage
+		if _G.ChatThrottleLib.ORIG_SendAddonMessage then
+			_G.SendAddonMessage = _G.ChatThrottleLib.ORIG_SendAddonMessage
+		end
+	end
+	_G.ChatThrottleLib.ORIG_SendChatMessage = nil
+	_G.ChatThrottleLib.ORIG_SendAddonMessage = nil
+end
+
+if not _G.ChatThrottleLib then
+	_G.ChatThrottleLib = {}
+end
+
+ChatThrottleLib = _G.ChatThrottleLib  -- in case some addon does "local ChatThrottleLib" above us and we're copypasted (AceComm-2, sigh)
+local ChatThrottleLib = _G.ChatThrottleLib
+
+ChatThrottleLib.version = CTL_VERSION
+
+
+
+------------------ TWEAKABLES -----------------
+
+ChatThrottleLib.MAX_CPS = 800			  -- 2000 seems to be safe if NOTHING ELSE is happening. let's call it 800.
+ChatThrottleLib.MSG_OVERHEAD = 40		-- Guesstimate overhead for sending a message; source+dest+chattype+protocolstuff
+
+ChatThrottleLib.BURST = 4000				-- WoW's server buffer seems to be about 32KB. 8KB should be safe, but seen disconnects on _some_ servers. Using 4KB now.
+
+ChatThrottleLib.MIN_FPS = 20				-- Reduce output CPS to half (and don't burst) if FPS drops below this value
+
+
+local setmetatable = setmetatable
+local table_remove = table.remove
+local tostring = tostring
+local GetTime = GetTime
+local math_min = math.min
+local math_max = math.max
+local next = next
+local strlen = string.len
+local GetFramerate = GetFramerate
+local strlower = string.lower
+local unpack,type,pairs,wipe = unpack,type,pairs,wipe
+local UnitInRaid,UnitInParty = UnitInRaid,UnitInParty
+
+
+-----------------------------------------------------------------------
+-- Double-linked ring implementation
+
+local Ring = {}
+local RingMeta = { __index = Ring }
+
+function Ring:New()
+	local ret = {}
+	setmetatable(ret, RingMeta)
+	return ret
+end
+
+function Ring:Add(obj)	-- Append at the "far end" of the ring (aka just before the current position)
+	if self.pos then
+		obj.prev = self.pos.prev
+		obj.prev.next = obj
+		obj.next = self.pos
+		obj.next.prev = obj
+	else
+		obj.next = obj
+		obj.prev = obj
+		self.pos = obj
+	end
+end
+
+function Ring:Remove(obj)
+	obj.next.prev = obj.prev
+	obj.prev.next = obj.next
+	if self.pos == obj then
+		self.pos = obj.next
+		if self.pos == obj then
+			self.pos = nil
+		end
+	end
+end
+
+
+
+-----------------------------------------------------------------------
+-- Recycling bin for pipes
+-- A pipe is a plain integer-indexed queue of messages
+-- Pipes normally live in Rings of pipes  (3 rings total, one per priority)
+
+ChatThrottleLib.PipeBin = nil -- pre-v19, drastically different
+local PipeBin = setmetatable({}, {__mode="k"})
+
+local function DelPipe(pipe)
+	PipeBin[pipe] = true
+end
+
+local function NewPipe()
+	local pipe = next(PipeBin)
+	if pipe then
+		wipe(pipe)
+		PipeBin[pipe] = nil
+		return pipe
+	end
+	return {}
+end
+
+
+
+
+-----------------------------------------------------------------------
+-- Recycling bin for messages
+
+ChatThrottleLib.MsgBin = nil -- pre-v19, drastically different
+local MsgBin = setmetatable({}, {__mode="k"})
+
+local function DelMsg(msg)
+	msg[1] = nil
+	-- there's more parameters, but they're very repetetive so the string pool doesn't suffer really, and it's faster to just not delete them.
+	MsgBin[msg] = true
+end
+
+local function NewMsg()
+	local msg = next(MsgBin)
+	if msg then
+		MsgBin[msg] = nil
+		return msg
+	end
+	return {}
+end
+
+
+-----------------------------------------------------------------------
+-- ChatThrottleLib:Init
+-- Initialize queues, set up frame for OnUpdate, etc
+
+
+function ChatThrottleLib:Init()
+
+	-- Set up queues
+	if not self.Prio then
+		self.Prio = {}
+		self.Prio["ALERT"] = { ByName = {}, Ring = Ring:New(), avail = 0 }
+		self.Prio["NORMAL"] = { ByName = {}, Ring = Ring:New(), avail = 0 }
+		self.Prio["BULK"] = { ByName = {}, Ring = Ring:New(), avail = 0 }
+	end
+
+	-- v4: total send counters per priority
+	for _, Prio in pairs(self.Prio) do
+		Prio.nTotalSent = Prio.nTotalSent or 0
+	end
+
+	if not self.avail then
+		self.avail = 0 -- v5
+	end
+	if not self.nTotalSent then
+		self.nTotalSent = 0 -- v5
+	end
+
+
+	-- Set up a frame to get OnUpdate events
+	if not self.Frame then
+		self.Frame = CreateFrame("Frame")
+		self.Frame:Hide()
+	end
+	self.Frame:SetScript("OnUpdate", self.OnUpdate)
+	self.Frame:SetScript("OnEvent", self.OnEvent)	-- v11: Monitor P_E_W so we can throttle hard for a few seconds
+	self.Frame:RegisterEvent("PLAYER_ENTERING_WORLD")
+	self.OnUpdateDelay = 0
+	self.LastAvailUpdate = GetTime()
+	self.HardThrottlingBeginTime = GetTime()	-- v11: Throttle hard for a few seconds after startup
+
+	-- Hook SendChatMessage and SendAddonMessage so we can measure unpiped traffic and avoid overloads (v7)
+	if not self.securelyHooked then
+		-- Use secure hooks as of v16. Old regular hook support yanked out in v21.
+		self.securelyHooked = true
+		--SendChatMessage
+		hooksecurefunc("SendChatMessage", function(...)
+			return ChatThrottleLib.Hook_SendChatMessage(...)
+		end)
+		--SendAddonMessage
+		if _G.C_ChatInfo then
+			hooksecurefunc(_G.C_ChatInfo, "SendAddonMessage", function(...)
+				return ChatThrottleLib.Hook_SendAddonMessage(...)
+			end)
+		else
+			hooksecurefunc("SendAddonMessage", function(...)
+				return ChatThrottleLib.Hook_SendAddonMessage(...)
+			end)
+		end
+	end
+	self.nBypass = 0
+end
+
+
+-----------------------------------------------------------------------
+-- ChatThrottleLib.Hook_SendChatMessage / .Hook_SendAddonMessage
+
+local bMyTraffic = false
+
+function ChatThrottleLib.Hook_SendChatMessage(text, chattype, language, destination, ...)
+	if bMyTraffic then
+		return
+	end
+	local self = ChatThrottleLib
+	local size = strlen(tostring(text or "")) + strlen(tostring(destination or "")) + self.MSG_OVERHEAD
+	self.avail = self.avail - size
+	self.nBypass = self.nBypass + size	-- just a statistic
+end
+function ChatThrottleLib.Hook_SendAddonMessage(prefix, text, chattype, destination, ...)
+	if bMyTraffic then
+		return
+	end
+	local self = ChatThrottleLib
+	local size = tostring(text or ""):len() + tostring(prefix or ""):len();
+	size = size + tostring(destination or ""):len() + self.MSG_OVERHEAD
+	self.avail = self.avail - size
+	self.nBypass = self.nBypass + size	-- just a statistic
+end
+
+
+
+-----------------------------------------------------------------------
+-- ChatThrottleLib:UpdateAvail
+-- Update self.avail with how much bandwidth is currently available
+
+function ChatThrottleLib:UpdateAvail()
+	local now = GetTime()
+	local MAX_CPS = self.MAX_CPS;
+	local newavail = MAX_CPS * (now - self.LastAvailUpdate)
+	local avail = self.avail
+
+	if now - self.HardThrottlingBeginTime < 5 then
+		-- First 5 seconds after startup/zoning: VERY hard clamping to avoid irritating the server rate limiter, it seems very cranky then
+		avail = math_min(avail + (newavail*0.1), MAX_CPS*0.5)
+		self.bChoking = true
+	elseif GetFramerate() < self.MIN_FPS then		-- GetFrameRate call takes ~0.002 secs
+		avail = math_min(MAX_CPS, avail + newavail*0.5)
+		self.bChoking = true		-- just a statistic
+	else
+		avail = math_min(self.BURST, avail + newavail)
+		self.bChoking = false
+	end
+
+	avail = math_max(avail, 0-(MAX_CPS*2))	-- Can go negative when someone is eating bandwidth past the lib. but we refuse to stay silent for more than 2 seconds; if they can do it, we can.
+
+	self.avail = avail
+	self.LastAvailUpdate = now
+
+	return avail
+end
+
+
+-----------------------------------------------------------------------
+-- Despooling logic
+-- Reminder:
+-- - We have 3 Priorities, each containing a "Ring" construct ...
+-- - ... made up of N "Pipe"s (1 for each destination/pipename)
+-- - and each pipe contains messages
+
+function ChatThrottleLib:Despool(Prio)
+	local ring = Prio.Ring
+	while ring.pos and Prio.avail > ring.pos[1].nSize do
+		local msg = table_remove(ring.pos, 1)
+		if not ring.pos[1] then  -- did we remove last msg in this pipe?
+			local pipe = Prio.Ring.pos
+			Prio.Ring:Remove(pipe)
+			Prio.ByName[pipe.name] = nil
+			DelPipe(pipe)
+		else
+			Prio.Ring.pos = Prio.Ring.pos.next
+		end
+		local didSend=false
+		local lowerDest = strlower(msg[3] or "")
+		if lowerDest == "raid" and not UnitInRaid("player") then
+			-- do nothing
+		elseif lowerDest == "party" and not UnitInParty("player") then
+			-- do nothing
+		else
+			Prio.avail = Prio.avail - msg.nSize
+			bMyTraffic = true
+			msg.f(unpack(msg, 1, msg.n))
+			bMyTraffic = false
+			Prio.nTotalSent = Prio.nTotalSent + msg.nSize
+			DelMsg(msg)
+			didSend = true
+		end
+		-- notify caller of delivery (even if we didn't send it)
+		if msg.callbackFn then
+			msg.callbackFn (msg.callbackArg, didSend)
+		end
+		-- USER CALLBACK MAY ERROR
+	end
+end
+
+
+function ChatThrottleLib.OnEvent(this,event)
+	-- v11: We know that the rate limiter is touchy after login. Assume that it's touchy after zoning, too.
+	local self = ChatThrottleLib
+	if event == "PLAYER_ENTERING_WORLD" then
+		self.HardThrottlingBeginTime = GetTime()	-- Throttle hard for a few seconds after zoning
+		self.avail = 0
+	end
+end
+
+
+function ChatThrottleLib.OnUpdate(this,delay)
+	local self = ChatThrottleLib
+
+	self.OnUpdateDelay = self.OnUpdateDelay + delay
+	if self.OnUpdateDelay < 0.08 then
+		return
+	end
+	self.OnUpdateDelay = 0
+
+	self:UpdateAvail()
+
+	if self.avail < 0  then
+		return -- argh. some bastard is spewing stuff past the lib. just bail early to save cpu.
+	end
+
+	-- See how many of our priorities have queued messages (we only have 3, don't worry about the loop)
+	local n = 0
+	for prioname,Prio in pairs(self.Prio) do
+		if Prio.Ring.pos or Prio.avail < 0 then
+			n = n + 1
+		end
+	end
+
+	-- Anything queued still?
+	if n<1 then
+		-- Nope. Move spillover bandwidth to global availability gauge and clear self.bQueueing
+		for prioname, Prio in pairs(self.Prio) do
+			self.avail = self.avail + Prio.avail
+			Prio.avail = 0
+		end
+		self.bQueueing = false
+		self.Frame:Hide()
+		return
+	end
+
+	-- There's stuff queued. Hand out available bandwidth to priorities as needed and despool their queues
+	local avail = self.avail/n
+	self.avail = 0
+
+	for prioname, Prio in pairs(self.Prio) do
+		if Prio.Ring.pos or Prio.avail < 0 then
+			Prio.avail = Prio.avail + avail
+			if Prio.Ring.pos and Prio.avail > Prio.Ring.pos[1].nSize then
+				self:Despool(Prio)
+				-- Note: We might not get here if the user-supplied callback function errors out! Take care!
+			end
+		end
+	end
+
+end
+
+
+
+
+-----------------------------------------------------------------------
+-- Spooling logic
+
+function ChatThrottleLib:Enqueue(prioname, pipename, msg)
+	local Prio = self.Prio[prioname]
+	local pipe = Prio.ByName[pipename]
+	if not pipe then
+		self.Frame:Show()
+		pipe = NewPipe()
+		pipe.name = pipename
+		Prio.ByName[pipename] = pipe
+		Prio.Ring:Add(pipe)
+	end
+
+	pipe[#pipe + 1] = msg
+
+	self.bQueueing = true
+end
+
+function ChatThrottleLib:SendChatMessage(prio, prefix,   text, chattype, language, destination, queueName, callbackFn, callbackArg)
+	if not self or not prio or not prefix or not text or not self.Prio[prio] then
+		error('Usage: ChatThrottleLib:SendChatMessage("{BULK||NORMAL||ALERT}", "prefix", "text"[, "chattype"[, "language"[, "destination"]]]', 2)
+	end
+	if callbackFn and type(callbackFn)~="function" then
+		error('ChatThrottleLib:ChatMessage(): callbackFn: expected function, got '..type(callbackFn), 2)
+	end
+
+	local nSize = text:len()
+
+	if nSize>255 then
+		error("ChatThrottleLib:SendChatMessage(): message length cannot exceed 255 bytes", 2)
+	end
+
+	nSize = nSize + self.MSG_OVERHEAD
+
+	-- Check if there's room in the global available bandwidth gauge to send directly
+	if not self.bQueueing and nSize < self:UpdateAvail() then
+		self.avail = self.avail - nSize
+		bMyTraffic = true
+		_G.SendChatMessage(text, chattype, language, destination)
+		bMyTraffic = false
+		self.Prio[prio].nTotalSent = self.Prio[prio].nTotalSent + nSize
+		if callbackFn then
+			callbackFn (callbackArg, true)
+		end
+		-- USER CALLBACK MAY ERROR
+		return
+	end
+
+	-- Message needs to be queued
+	local msg = NewMsg()
+	msg.f = _G.SendChatMessage
+	msg[1] = text
+	msg[2] = chattype or "SAY"
+	msg[3] = language
+	msg[4] = destination
+	msg.n = 4
+	msg.nSize = nSize
+	msg.callbackFn = callbackFn
+	msg.callbackArg = callbackArg
+
+	self:Enqueue(prio, queueName or (prefix..(chattype or "SAY")..(destination or "")), msg)
+end
+
+
+function ChatThrottleLib:SendAddonMessage(prio, prefix, text, chattype, target, queueName, callbackFn, callbackArg)
+	if not self or not prio or not prefix or not text or not chattype or not self.Prio[prio] then
+		error('Usage: ChatThrottleLib:SendAddonMessage("{BULK||NORMAL||ALERT}", "prefix", "text", "chattype"[, "target"])', 2)
+	end
+	if callbackFn and type(callbackFn)~="function" then
+		error('ChatThrottleLib:SendAddonMessage(): callbackFn: expected function, got '..type(callbackFn), 2)
+	end
+
+	local nSize = text:len();
+
+	if C_ChatInfo or RegisterAddonMessagePrefix then
+		if nSize>255 then
+			error("ChatThrottleLib:SendAddonMessage(): message length cannot exceed 255 bytes", 2)
+		end
+	else
+		nSize = nSize + prefix:len() + 1
+		if nSize>255 then
+			error("ChatThrottleLib:SendAddonMessage(): prefix + message length cannot exceed 254 bytes", 2)
+		end
+	end
+
+	nSize = nSize + self.MSG_OVERHEAD;
+
+	-- Check if there's room in the global available bandwidth gauge to send directly
+	if not self.bQueueing and nSize < self:UpdateAvail() then
+		self.avail = self.avail - nSize
+		bMyTraffic = true
+		if _G.C_ChatInfo then
+			_G.C_ChatInfo.SendAddonMessage(prefix, text, chattype, target)
+		else
+			_G.SendAddonMessage(prefix, text, chattype, target)
+		end
+		bMyTraffic = false
+		self.Prio[prio].nTotalSent = self.Prio[prio].nTotalSent + nSize
+		if callbackFn then
+			callbackFn (callbackArg, true)
+		end
+		-- USER CALLBACK MAY ERROR
+		return
+	end
+
+	-- Message needs to be queued
+	local msg = NewMsg()
+	msg.f = _G.C_ChatInfo and _G.C_ChatInfo.SendAddonMessage or _G.SendAddonMessage
+	msg[1] = prefix
+	msg[2] = text
+	msg[3] = chattype
+	msg[4] = target
+	msg.n = (target~=nil) and 4 or 3;
+	msg.nSize = nSize
+	msg.callbackFn = callbackFn
+	msg.callbackArg = callbackArg
+
+	self:Enqueue(prio, queueName or (prefix..chattype..(target or "")), msg)
+end
+
+
+
+
+-----------------------------------------------------------------------
+-- Get the ball rolling!
+
+ChatThrottleLib:Init()
+
+--[[ WoWBench debugging snippet
+if(WOWB_VER) then
+	local function SayTimer()
+		print("SAY: "..GetTime().." "..arg1)
+	end
+	ChatThrottleLib.Frame:SetScript("OnEvent", SayTimer)
+	ChatThrottleLib.Frame:RegisterEvent("CHAT_MSG_SAY")
+end
+]]
+
+
diff --git a/oUF/libs/LibHealComm-4.0/LibHealComm-4.0.lua b/oUF/libs/LibHealComm-4.0/LibHealComm-4.0.lua
new file mode 100644
index 0000000..ff9a67f
--- /dev/null
+++ b/oUF/libs/LibHealComm-4.0/LibHealComm-4.0.lua
@@ -0,0 +1,2739 @@
+if WOW_PROJECT_ID == WOW_PROJECT_MAINLINE then return end
+
+local major = "LibHealComm-4.0"
+local minor = 92
+assert(LibStub, format("%s requires LibStub.", major))
+
+local HealComm = LibStub:NewLibrary(major, minor)
+if( not HealComm ) then return end
+
+local COMM_PREFIX = "LHC40"
+C_ChatInfo.RegisterAddonMessagePrefix(COMM_PREFIX)
+
+local bit = bit
+local ceil = ceil
+local error = error
+local floor = floor
+local format = format
+local gsub = gsub
+local max = max
+local min = min
+local pairs = pairs
+local rawset = rawset
+local select = select
+local setmetatable = setmetatable
+local strlen = strlen
+local strmatch = strmatch
+local strsplit = strsplit
+local strsub = strsub
+local tinsert = tinsert
+local tonumber = tonumber
+local tremove = tremove
+local type = type
+local unpack = unpack
+local wipe = wipe
+
+local Ambiguate = Ambiguate
+local CastingInfo = CastingInfo
+local ChannelInfo = ChannelInfo
+local CreateFrame = CreateFrame
+local GetInventoryItemLink = GetInventoryItemLink
+local GetInventorySlotInfo = GetInventorySlotInfo
+local GetNumGroupMembers = GetNumGroupMembers
+local GetNumTalents = GetNumTalents
+local GetNumTalentTabs = GetNumTalentTabs
+local GetRaidRosterInfo = GetRaidRosterInfo
+local GetSpellBonusHealing = GetSpellBonusHealing
+local GetSpellCritChance = GetSpellCritChance
+local GetSpellInfo = GetSpellInfo
+local GetTalentInfo = GetTalentInfo
+local GetTime = GetTime
+local GetZonePVPInfo = GetZonePVPInfo
+local hooksecurefunc = hooksecurefunc
+local InCombatLockdown = InCombatLockdown
+local IsEquippedItem = IsEquippedItem
+local IsInGroup = IsInGroup
+local IsInInstance = IsInInstance
+local IsInRaid = IsInRaid
+local IsLoggedIn = IsLoggedIn
+local IsSpellInRange = IsSpellInRange
+local SpellIsTargeting = SpellIsTargeting
+local UnitAura = UnitAura
+local UnitCanAssist = UnitCanAssist
+local UnitExists = UnitExists
+local UnitGUID = UnitGUID
+local UnitIsCharmed = UnitIsCharmed
+local UnitIsVisible = UnitIsVisible
+local UnitInRaid = UnitInRaid
+local UnitLevel = UnitLevel
+local UnitName = UnitName
+local UnitPlayerControlled = UnitPlayerControlled
+local CheckInteractDistance = CheckInteractDistance
+
+local COMBATLOG_OBJECT_AFFILIATION_MINE = COMBATLOG_OBJECT_AFFILIATION_MINE
+
+local isTBC = WOW_PROJECT_ID == WOW_PROJECT_BURNING_CRUSADE_CLASSIC
+
+local spellRankTableData = {
+	[1] = { 774, 8936, 5185, 740, 635, 19750, 139, 2060, 596, 2061, 2054, 2050, 1064, 331, 8004, 136, 755, 689, 746, 33763, 32546 },
+	[2] = { 1058, 8938, 5186, 8918, 639, 19939, 6074, 10963, 996, 9472, 2055, 2052, 10622, 332, 8008, 3111, 3698, 699, 1159 },
+	[3] = { 1430, 8939, 5187, 9862, 647, 19940, 6075, 10964, 10960, 9473, 6063, 2053, 10623, 547, 8010, 3661, 3699, 709, 3267 },
+	[4] = { 2090, 8940, 5188, 9863, 1026, 19941, 6076, 10965, 10961, 9474, 6064, 913, 10466, 3662, 3700, 7651, 3268, 25422 },
+	[5] = { 2091, 8941, 5189, 1042, 19942, 6077, 22009, 25314, 25316, 10915, 939, 10467, 13542, 11693, 11699, 7926, 25423, 26983 },
+	[6] = { 3627, 9750, 6778, 3472, 19943, 6078, 10916, 959, 10468, 13543, 11694, 11700, 7927, 23569, 24412, 25210, 25308 },
+	[7] = { 8910, 9856, 8903, 10328, 10927, 10917, 8005, 13544, 11695, 10838, 27137, 25213, 25420, 27219 },
+	[8] = { 9839, 9857, 9758, 10329, 10928, 10395, 10839, 23568, 24413, 25233, 27259, 27220 },
+	[9] = { 9840, 9858, 9888, 25292, 10929, 10396, 18608, 25235 },
+	[10] = { 9841, 9889, 25315, 25357, 18610, 23567, 24414, 26980, 27135 },
+	[11] = { 25299, 25297, 30020, 27136, 25221, 25391, 27030 },
+	[12] = { 26981, 26978, 25222, 25396, 27031 },
+	[13] = { 26982, 26979 },
+}
+
+local SpellIDToRank = {}
+for rankIndex, spellIDTable in pairs(spellRankTableData) do
+	for _, spellID in pairs(spellIDTable) do
+		SpellIDToRank[spellID] = rankIndex
+	end
+end
+
+-- API CONSTANTS
+local ALL_DATA = 0x0f
+local DIRECT_HEALS = 0x01
+local CHANNEL_HEALS = 0x02
+local HOT_HEALS = 0x04
+local ABSORB_SHIELDS = 0x08
+local BOMB_HEALS = 0x10
+local ALL_HEALS = bit.bor(DIRECT_HEALS, CHANNEL_HEALS, HOT_HEALS, BOMB_HEALS)
+local CASTED_HEALS = bit.bor(DIRECT_HEALS, CHANNEL_HEALS)
+local OVERTIME_HEALS = bit.bor(HOT_HEALS, CHANNEL_HEALS)
+
+HealComm.ALL_HEALS, HealComm.CHANNEL_HEALS, HealComm.DIRECT_HEALS, HealComm.HOT_HEALS, HealComm.CASTED_HEALS, HealComm.ABSORB_SHIELDS, HealComm.ALL_DATA, HealComm.BOMB_HEALS = ALL_HEALS, CHANNEL_HEALS, DIRECT_HEALS, HOT_HEALS, CASTED_HEALS, ABSORB_SHIELDS, ALL_DATA, BOMB_HEALS
+
+local playerGUID, playerName, playerLevel
+local playerHealModifier = 1
+
+HealComm.callbacks = HealComm.callbacks or LibStub:GetLibrary("CallbackHandler-1.0"):New(HealComm)
+HealComm.activeHots = HealComm.activeHots or {}
+HealComm.activePets = HealComm.activePets or {}
+HealComm.equippedSetCache = HealComm.equippedSetCache or {}
+HealComm.guidToGroup = HealComm.guidToGroup or {}
+HealComm.guidToUnit = HealComm.guidToUnit or {}
+HealComm.hotData = HealComm.hotData or {}
+HealComm.itemSetsData = HealComm.itemSetsData or {}
+HealComm.pendingHeals = HealComm.pendingHeals or {}
+HealComm.pendingHots = HealComm.pendingHots or {}
+HealComm.spellData = HealComm.spellData or {}
+HealComm.talentData = HealComm.talentData or {}
+HealComm.tempPlayerList = HealComm.tempPlayerList or {}
+
+if( not HealComm.unitToPet ) then
+	HealComm.unitToPet = {["player"] = "pet"}
+	for i = 1, MAX_PARTY_MEMBERS do HealComm.unitToPet["party" .. i] = "partypet" .. i end
+	for i = 1, MAX_RAID_MEMBERS do HealComm.unitToPet["raid" .. i] = "raidpet" .. i end
+end
+
+local spellData, hotData, tempPlayerList, pendingHeals, pendingHots = HealComm.spellData, HealComm.hotData, HealComm.tempPlayerList, HealComm.pendingHeals, HealComm.pendingHots
+local equippedSetCache, itemSetsData, talentData = HealComm.equippedSetCache, HealComm.itemSetsData, HealComm.talentData
+local activeHots, activePets = HealComm.activeHots, HealComm.activePets
+
+-- Figure out what they are now since a few things change based off of this
+local playerClass = select(2, UnitClass("player"))
+
+if( not HealComm.compressGUID  ) then
+	HealComm.compressGUID = setmetatable({}, {
+		__index = function(tbl, guid)
+			local str
+			if strsub(guid,1,6) ~= "Player" then
+				for unit,pguid in pairs(activePets) do
+					if pguid == guid and UnitExists(unit) then
+						str = "p-" .. strmatch(UnitGUID(unit), "^%w*-([-%w]*)$")
+					end
+				end
+				if not str then
+					--assert(str, "Could not encode: "..guid)
+					return nil
+				end
+			else
+				str = strmatch(guid, "^%w*-([-%w]*)$")
+			end
+			rawset(tbl, guid, str)
+			return str
+	end})
+
+	HealComm.decompressGUID = setmetatable({}, {
+		__index = function(tbl, str)
+			if( not str ) then return nil end
+			local guid
+			if strsub(str,1,2) == "p-" then
+				local unit = HealComm.guidToUnit["Player-"..strsub(str,3)]
+				if not unit then
+					return nil
+				end
+				guid = activePets[unit]
+			else
+				guid = "Player-"..str
+			end
+
+			rawset(tbl, str, guid)
+			return guid
+	end})
+end
+
+local compressGUID, decompressGUID = HealComm.compressGUID, HealComm.decompressGUID
+
+-- Handles caching of tables for variable tick spells, like Wild Growth
+if( not HealComm.tableCache ) then
+	HealComm.tableCache = setmetatable({}, {__mode = "k"})
+	function HealComm:RetrieveTable()
+		return tremove(HealComm.tableCache, 1) or {}
+	end
+
+	function HealComm:DeleteTable(tbl)
+		wipe(tbl)
+		tinsert(HealComm.tableCache, tbl)
+	end
+end
+
+-- Validation for passed arguments
+if( not HealComm.tooltip ) then
+	local tooltip = CreateFrame("GameTooltip")
+	tooltip:SetOwner(UIParent, "ANCHOR_NONE")
+	tooltip.TextLeft1 = tooltip:CreateFontString()
+	tooltip.TextRight1 = tooltip:CreateFontString()
+	tooltip:AddFontStrings(tooltip.TextLeft1, tooltip.TextRight1)
+
+	HealComm.tooltip = tooltip
+end
+
+-- Record management, because this is getting more complicted to deal with
+local function updateRecord(pending, guid, amount, stack, endTime, ticksLeft)
+	if( pending[guid] ) then
+		local id = pending[guid]
+
+		pending[id] = guid
+		pending[id + 1] = amount
+		pending[id + 2] = stack
+		pending[id + 3] = endTime or 0
+		pending[id + 4] = ticksLeft or 0
+	else
+		pending[guid] = #(pending) + 1
+		tinsert(pending, guid)
+		tinsert(pending, amount)
+		tinsert(pending, stack)
+		tinsert(pending, endTime or 0)
+		tinsert(pending, ticksLeft or 0)
+
+		if( pending.bitType == HOT_HEALS ) then
+			activeHots[guid] = (activeHots[guid] or 0) + 1
+			HealComm.hotMonitor:Show()
+		end
+	end
+end
+
+local function getRecord(pending, guid)
+	local id = pending[guid]
+	if( not id ) then return nil end
+
+	-- amount, stack, endTime, ticksLeft
+	return pending[id + 1], pending[id + 2], pending[id + 3], pending[id + 4]
+end
+
+local function removeRecord(pending, guid)
+	local id = pending[guid]
+	if( not id ) then return nil end
+
+	-- ticksLeft, endTime, stack, amount, guid
+	tremove(pending, id + 4)
+	tremove(pending, id + 3)
+	tremove(pending, id + 2)
+	local amount = tremove(pending, id + 1)
+	tremove(pending, id)
+	pending[guid] = nil
+
+	-- Release the table
+	if( type(amount) == "table" ) then HealComm:DeleteTable(amount) end
+
+	if( pending.bitType == HOT_HEALS and activeHots[guid] ) then
+		activeHots[guid] = activeHots[guid] - 1
+		activeHots[guid] = activeHots[guid] > 0 and activeHots[guid] or nil
+	end
+
+	-- Shift any records after this ones index down 5 to account for the removal
+	for i=1, #(pending), 5 do
+		local guid = pending[i]
+		if( pending[guid] > id ) then
+			pending[guid] = pending[guid] - 5
+		end
+	end
+end
+
+local function removeRecordList(pending, inc, comp, ...)
+	for i=1, select("#", ...), inc do
+		local guid = select(i, ...)
+		guid = comp and decompressGUID[guid] or guid
+
+		if guid then
+			local id = pending[guid]
+			-- ticksLeft, endTime, stack, amount, guid
+			tremove(pending, id + 4)
+			tremove(pending, id + 3)
+			tremove(pending, id + 2)
+			local amount = tremove(pending, id + 1)
+			tremove(pending, id)
+			pending[guid] = nil
+
+			-- Release the table
+			if( type(amount) == "table" ) then HealComm:DeleteTable(amount) end
+		end
+	end
+
+	-- Redo all the id maps
+	for i=1, #(pending), 5 do
+		pending[pending[i]] = i
+	end
+end
+
+-- Removes every mention to the given GUID
+local function removeAllRecords(guid)
+	local changed
+
+	for _, tbl in pairs({pendingHeals, pendingHots}) do
+		for _, spells in pairs(tbl) do
+			for _, pending in pairs(spells) do
+				if( pending.bitType and pending[guid] ) then
+					local id = pending[guid]
+
+					-- ticksLeft, endTime, stack, amount, guid
+					tremove(pending, id + 4)
+					tremove(pending, id + 3)
+					tremove(pending, id + 2)
+					local amount = tremove(pending, id + 1)
+					tremove(pending, id)
+					pending[guid] = nil
+
+					-- Release the table
+					if( type(amount) == "table" ) then HealComm:DeleteTable(amount) end
+
+					-- Shift everything back
+					if( #(pending) > 0 ) then
+						for i=1, #(pending), 5 do
+							local guid = pending[i]
+							if( pending[guid] > id ) then
+								pending[guid] = pending[guid] - 5
+							end
+						end
+					else
+						wipe(pending)
+					end
+
+					changed = true
+				end
+			end
+		end
+	end
+
+	activeHots[guid] = nil
+
+	if( changed ) then
+		HealComm.callbacks:Fire("HealComm_GUIDDisappeared", guid)
+	end
+end
+
+-- These are not public APIs and are purely for the wrapper to use
+HealComm.removeRecordList = removeRecordList
+HealComm.removeRecord = removeRecord
+HealComm.getRecord = getRecord
+HealComm.updateRecord = updateRecord
+
+-- Removes all pending heals, if it's a group that is causing the clear then we won't remove the players heals on themselves
+local function clearPendingHeals()
+	for _, tbl in pairs({pendingHeals, pendingHots}) do
+		for casterGUID, spells in pairs(tbl) do
+			for _, pending in pairs(spells) do
+				if( pending.bitType ) then
+					wipe(tempPlayerList)
+					for i=#(pending), 1, -5 do tinsert(tempPlayerList, pending[i - 4]) end
+
+					if( #(tempPlayerList) > 0 ) then
+						local spellID, bitType = pending.spellID, pending.bitType
+						wipe(pending)
+
+						HealComm.callbacks:Fire("HealComm_HealStopped", casterGUID, spellID, bitType, true, unpack(tempPlayerList))
+					end
+				end
+			end
+		end
+	end
+end
+
+-- APIs
+-- Returns the players current heaing modifier
+function HealComm:GetPlayerHealingMod()
+	return playerHealModifier or 1
+end
+
+-- Returns the current healing modifier for the GUID
+function HealComm:GetHealModifier(guid)
+	return HealComm.currentModifiers[guid] or 1
+end
+
+-- Returns whether or not the GUID has casted a heal
+function HealComm:GUIDHasHealed(guid)
+	return (pendingHeals[guid] or pendingHots[guid]) and true or nil
+end
+
+-- Returns the guid to unit table
+function HealComm:GetGUIDUnitMapTable()
+	if( not HealComm.protectedMap ) then
+		HealComm.protectedMap = setmetatable({}, {
+			__index = function(tbl, key) return HealComm.guidToUnit[key] end,
+			__newindex = function() error("This is a read only table and cannot be modified.", 2) end,
+			__metatable = false
+		})
+	end
+
+	return HealComm.protectedMap
+end
+
+-- Gets the next heal landing on someone using the passed filters
+function HealComm:GetNextHealAmount(guid, bitFlag, time, ignoreGUID, srcGUID)
+	local healTime, healAmount, healFrom
+	local currentTime = GetTime()
+
+	for _, tbl in pairs({pendingHeals, pendingHots}) do
+		for casterGUID, spells in pairs(tbl) do
+			if( not ignoreGUID or ignoreGUID ~= casterGUID ) and (not srcGUID or srcGUID == casterGUID) then
+				for _, pending in pairs(spells) do
+					if( pending.bitType and bit.band(pending.bitType, bitFlag) > 0 ) then
+						for i=1, #(pending), 5 do
+							local targetGUID = pending[i]
+							if(not guid or targetGUID == guid) then
+								local amount = pending[i + 1]
+								local stack = pending[i + 2]
+								local endTime = pending[i + 3]
+								endTime = endTime > 0 and endTime or pending.endTime
+
+								-- Direct heals are easy, if they match the filter then return them
+								if( ( pending.bitType == DIRECT_HEALS or pending.bitType == BOMB_HEALS ) and ( not time or endTime <= time ) ) then
+									if( not healTime or endTime < healTime ) then
+										healTime = endTime
+										healAmount = amount * stack
+										healFrom = casterGUID
+									end
+
+								-- Channeled heals and hots, have to figure out how many times it'll tick within the given time band
+								elseif( ( pending.bitType == CHANNEL_HEALS or pending.bitType == HOT_HEALS ) ) then
+									local secondsLeft = time and time - currentTime or endTime - currentTime
+									local nextTick = currentTime + (secondsLeft % pending.tickInterval)
+									if( not healTime or nextTick < healTime ) then
+										healTime = nextTick
+										healAmount = amount * stack
+										healFrom = casterGUID
+									end
+								end
+							end
+						end
+					end
+				end
+			end
+		end
+	end
+
+	return healTime, healFrom, healAmount
+end
+
+-- Get the healing amount that matches the passed filters
+local function filterData(spells, filterGUID, bitFlag, time, ignoreGUID)
+	local healAmount = 0
+	local currentTime = GetTime()
+
+	if spells then
+		for _, pending in pairs(spells) do
+			if( pending.bitType and bit.band(pending.bitType, bitFlag) > 0 ) then
+				for i = 1, #(pending), 5 do
+					local guid = pending[i]
+					if( guid == filterGUID or ignoreGUID ) then
+						local amount = pending[i + 1]
+						local stack = pending[i + 2]
+						local endTime = pending[i + 3]
+						endTime = endTime > 0 and endTime or pending.endTime
+
+						if( ( pending.bitType == DIRECT_HEALS or pending.bitType == BOMB_HEALS ) and ( not time or endTime <= time ) ) then
+							healAmount = healAmount + amount * stack
+						elseif( ( pending.bitType == CHANNEL_HEALS or pending.bitType == HOT_HEALS ) and endTime > currentTime ) then
+							local ticksLeft = pending[i + 4]
+							if( not time or time >= endTime ) then
+								healAmount = healAmount + (amount * stack) * ticksLeft
+							else
+								local secondsLeft = endTime - currentTime
+								local bandSeconds = time - currentTime
+								local ticks = floor(min(bandSeconds, secondsLeft) / pending.tickInterval)
+								local nextTickIn = secondsLeft % pending.tickInterval
+								local fractionalBand = bandSeconds % pending.tickInterval
+								if( nextTickIn > 0 and nextTickIn < fractionalBand ) then
+									ticks = ticks + 1
+								end
+
+								healAmount = healAmount + (amount * stack) * min(ticks, ticksLeft)
+							end
+						end
+					end
+				end
+			end
+		end
+	end
+
+	return healAmount
+end
+
+-- Gets healing amount using the passed filters
+function HealComm:GetHealAmount(guid, bitFlag, time, casterGUID)
+	local amount = 0
+	if( casterGUID and (pendingHeals[casterGUID] or pendingHots[casterGUID]) ) then
+		amount = filterData(pendingHeals[casterGUID], guid, bitFlag, time) + filterData(pendingHots[casterGUID], guid, bitFlag, time)
+	elseif( not casterGUID ) then
+		for _, tbl in pairs({pendingHeals, pendingHots}) do
+			for _, spells in pairs(tbl) do
+				amount = amount + filterData(spells, guid, bitFlag, time)
+			end
+		end
+	end
+
+	return amount > 0 and amount or nil
+end
+
+-- Gets healing amounts for everyone except the player using the passed filters
+function HealComm:GetOthersHealAmount(guid, bitFlag, time)
+	local amount = 0
+	for _, tbl in pairs({pendingHeals, pendingHots}) do
+		for casterGUID, spells in pairs(tbl) do
+			if( casterGUID ~= playerGUID ) then
+				amount = amount + filterData(spells, guid, bitFlag, time)
+			end
+		end
+	end
+
+	return amount > 0 and amount or nil
+end
+
+function HealComm:GetCasterHealAmount(guid, bitFlag, time)
+	local amount = pendingHeals[guid] and filterData(pendingHeals[guid], nil, bitFlag, time, true) or 0
+	amount = amount + (pendingHots[guid] and filterData(pendingHots[guid], nil, bitFlag, time, true) or 0)
+	return amount > 0 and amount or nil
+end
+
+function HealComm:GetHealAmountEx(dstGUID, dstBitFlag, dstTime, srcGUID, srcBitFlag, srcTime)
+	local dstAmount1 = 0
+	local dstAmount2 = 0
+	local srcAmount1 = 0
+	local srcAmount2 = 0
+
+	local currTime = GetTime()
+
+	dstBitFlag = dstBitFlag or ALL_HEALS
+	srcBitFlag = srcBitFlag or ALL_HEALS
+
+	for _, tbl in ipairs({pendingHeals, pendingHots}) do
+		for casterGUID, spells in pairs(tbl) do
+			local time
+
+			if casterGUID ~= srcGUID then
+				time = dstTime
+			else
+				time = srcTime
+			end
+
+			if spells then
+				for _, pending in pairs(spells) do
+					local bitType = pending.bitType or 0
+
+					if casterGUID ~= srcGUID then
+						bitType = bit.band(bitType, dstBitFlag)
+					else
+						bitType = bit.band(bitType, srcBitFlag)
+					end
+
+					if bitType > 0 then
+						for i = 1, #pending, 5 do
+							local targetGUID = pending[i]
+
+							if targetGUID == dstGUID then
+								local amount = pending[i + 1]
+								local stack = pending[i + 2]
+								local endTime = pending[i + 3]
+
+								endTime = endTime > 0 and endTime or pending.endTime
+
+								if endTime > currTime then
+									amount = amount * stack
+
+									local amount1 = 0
+									local amount2 = 0
+
+									if bitType == DIRECT_HEALS or bitType == BOMB_HEALS then
+										if not time or endTime <= time then
+											amount1 = amount
+										end
+
+										amount2 = amount
+									elseif bitType == HOT_HEALS or bitType == CHANNEL_HEALS then
+										local ticksLeft = pending[i + 4]
+										local ticks
+
+										if not time then
+											ticks = 1
+										elseif time >= endTime then
+											ticks = ticksLeft
+										else
+											local tickInterval = pending.tickInterval
+											local secondsLeft = endTime - currTime
+											local bandSeconds = max(time - currTime, 0)
+
+											ticks = floor(min(bandSeconds, secondsLeft) / tickInterval)
+
+											local nextTickIn = secondsLeft % tickInterval
+											local fractionalBand = bandSeconds % tickInterval
+
+											if nextTickIn > 0 and nextTickIn < fractionalBand then
+												ticks = ticks + 1
+											end
+										end
+
+										if ticks > ticksLeft then
+											ticks = ticksLeft
+										end
+
+										amount1 = amount * ticks
+										amount2 = amount * ticksLeft
+									end
+
+									if casterGUID ~= srcGUID then
+										dstAmount1 = dstAmount1 + amount1
+										dstAmount2 = dstAmount2 + amount2
+									else
+										srcAmount1 = srcAmount1 + amount1
+										srcAmount2 = srcAmount2 + amount2
+									end
+								end
+							end
+						end
+					end
+				end
+			end
+		end
+	end
+
+	dstAmount2 = dstAmount2 - dstAmount1
+	srcAmount2 = srcAmount2 - srcAmount1
+
+	dstAmount1 = dstAmount1 > 0 and dstAmount1 or nil
+	dstAmount2 = dstAmount2 > 0 and dstAmount2 or nil
+	srcAmount1 = srcAmount1 > 0 and srcAmount1 or nil
+	srcAmount2 = srcAmount2 > 0 and srcAmount2 or nil
+
+	return dstAmount1, dstAmount2, srcAmount1, srcAmount2
+end
+
+-- Get the number of direct heals on a target
+function HealComm:GetNumHeals(filterGUID, time)
+	local numHeals = 0
+
+	for _, spells in pairs(pendingHeals) do
+		if spells then
+			for _, pending in pairs(spells) do
+				for i = 1, #(pending), 5 do
+					local guid = pending[i]
+					if( guid == filterGUID ) then
+						local endTime = pending[i + 3]
+						endTime = endTime > 0 and endTime or pending.endTime
+
+						if( pending.bitType == DIRECT_HEALS and ( not time or endTime <= time ) ) then
+							numHeals = numHeals + 1
+						end
+					end
+				end
+			end
+		end
+	end
+
+	return numHeals
+end
+
+-- Healing class data
+-- Thanks to Gagorian (DrDamage) for letting me steal his formulas and such
+local playerCurrentRelic
+local guidToUnit, guidToGroup = HealComm.guidToUnit, HealComm.guidToGroup
+
+local unitHasAura
+
+do
+	local findAura = AuraUtil.FindAura
+	local findAuraByName = AuraUtil.FindAuraByName
+
+	local function spellIdPredicate(spellIdToFind, _, _, _, _, _, _, _, _, _, _, _, spellId)
+		return spellIdToFind == spellId
+	end
+
+	local function findAuraBySpellId(spellId, unit, filter)
+		return findAura(spellIdPredicate, unit, filter, spellId)
+	end
+
+	function unitHasAura(unit, name)
+		if type(name) == "number" then
+			return findAuraBySpellId(name, unit)
+		else
+			return findAuraByName(name, unit)
+		end
+	end
+end
+
+-- Note because I always forget on the order:
+-- Talents that effective the coeffiency of spell power to healing are first and are tacked directly onto the coeffiency (Empowered Rejuvenation)
+-- Penalty modifiers (downranking/spell level too low) are applied directly to the spell power
+-- Spell power modifiers are then applied to the spell power
+-- Heal modifiers are applied after all of that
+-- Crit modifiers are applied after
+-- Any other modifiers such as Mortal Strike or Avenging Wrath are applied after everything else
+
+local function calculateGeneralAmount(level, amount, spellPower, spModifier, healModifier)
+	local penalty = level > 20 and 1 or (1 - ((20 - level) * 0.0375))
+	if isTBC then
+		-- TBC added another downrank penalty
+		penalty = penalty * min(1, (level + 11) / playerLevel)
+	end
+
+	spellPower = spellPower * penalty
+
+	return healModifier * (amount + (spellPower * spModifier))
+end
+
+local function DirectCoefficient(castTime)
+	return castTime / 3.5
+end
+
+local function HotCoefficient(duration)
+	return duration / 15
+end
+
+local function avg(a, b)
+	return (a + b) / 2
+end
+
+--[[
+	What the different callbacks do:
+
+	AuraHandler: Specific aura tracking needed for this class, who has Beacon up on them and such
+
+	ResetChargeData: Due to spell "queuing" you can't always rely on aura data for buffs that last one or two casts, for example Divine Favor (+100% crit, one spell)
+	if you cast Holy Light and queue Flash of Light the library would still see they have Divine Favor and give them crits on both spells. The reset means that the flag that indicates
+	they have the aura can be killed and if they interrupt the cast then it will call this and let you reset the flags.
+
+	What happens in terms of what the client thinks and what actually is, is something like this:
+
+	UNIT_SPELLCAST_START, Holy Light -> Divine Favor up
+	UNIT_SPELLCAST_SUCCEEDED, Holy Light -> Divine Favor up (But it was really used)
+	UNIT_SPELLCAST_START, Flash of Light -> Divine Favor up (It's not actually up but auras didn't update)
+	UNIT_AURA -> Divine Favor up (Split second where it still thinks it's up)
+	UNIT_AURA -> Divine Favor faded (Client catches up and realizes it's down)
+
+	CalculateHealing: Calculates the healing value, does all the formula calculations talent modifiers and such
+
+	CalculateHotHealing: Used specifically for calculating the heals of hots
+
+	GetHealTargets: Who the heal is going to hit, used for setting extra targets for Beacon of Light + Paladin heal or Prayer of Healing.
+	The returns should either be:
+
+	"compressedGUID1,compressedGUID2,compressedGUID3,compressedGUID4", healthAmount
+	Or if you need to set specific healing values for one GUID it should be
+	"compressedGUID1,healthAmount1,compressedGUID2,healAmount2,compressedGUID3,healAmount3", -1
+
+	The latter is for cases like Glyph of Healing Wave where you need a heal for 1,000 on A and a heal for 200 on the player for B without sending 2 events.
+	The -1 tells the library to look in the GUId list for the heal amounts
+
+	**NOTE** Any GUID returned from GetHealTargets must be compressed through a call to compressGUID[guid]
+]]
+
+local CalculateHealing, GetHealTargets, AuraHandler, CalculateHotHealing, ResetChargeData, LoadClassData
+
+local function getBaseHealAmount(spellData, spellName, spellRank)
+	spellData = spellData[spellName]
+	local average = spellData.averages[spellRank]
+	if type(average) == "number" then
+		return average
+	end
+	local requiresLevel = spellData.levels[spellRank]
+	return average[min(playerLevel - requiresLevel + 1, #average)]
+end
+
+if( playerClass == "DRUID" ) then
+	LoadClassData = function()
+		local GiftofNature = GetSpellInfo(17104)
+		local HealingTouch = GetSpellInfo(5185)
+		local ImprovedRejuv = GetSpellInfo(17111)
+		local MarkoftheWild = GetSpellInfo(1126)
+		local Regrowth = GetSpellInfo(8936)
+		local Rejuvenation = GetSpellInfo(774)
+		local Tranquility = GetSpellInfo(740)
+		local Lifebloom = GetSpellInfo(33763) or "Lifebloom"
+		local EmpoweredRejuv = GetSpellInfo(33886) or "EmpoweredRejuv"
+		local EmpoweredTouch = GetSpellInfo(33879) or "EmpoweredTouch"
+
+		hotData[Regrowth] = { interval = 3, ticks = 7, coeff = isTBC and 0.7 or 0.5, levels = { 12, 18, 24, 30, 36, 42, 48, 54, 60, 65 }, averages = { 98, 175, 259, 343, 427, 546, 686, 861, 1064, 1274 }}
+		hotData[Rejuvenation] = { interval = 3, levels = { 4, 10, 16, 22, 28, 34, 40, 46, 52, 58, 60, 63, 69 }, averages = { 32, 56, 116, 180, 244, 304, 388, 488, 608, 756, 888, 932, 1060 }}
+		hotData[Lifebloom] = {interval = 1, ticks = 7, coeff = 0.52, dhCoeff = 0.34335, levels = {64}, averages = {273}, bomb = {600}}
+
+		spellData[HealingTouch] = { levels = {1, 8, 14, 20, 26, 32, 38, 44, 50, 56, 60, 62, 69}, averages = {
+			{avg(37, 51), avg(37, 52), avg(38, 53), avg(39, 54), avg(40, 55)},
+			{avg(88, 112), avg(89, 114), avg(90, 115), avg(91, 116), avg(93, 118), avg(94, 119)},
+			{avg(195, 243), avg(196, 245), avg(198, 247), avg(200, 249), avg(202, 251), avg(204, 253)},
+			{avg(363, 445), avg(365, 448), avg(368, 451), avg(371, 454), avg(373, 456), avg(376, 459)},
+			{avg(572, 694), avg(575, 698), avg(579, 701), avg(582, 705), avg(586, 708), avg(589, 712)},
+			{avg(742, 894), avg(746, 898), avg(750, 902), avg(754, 906), avg(758, 910), avg(762, 914)},
+			{avg(936, 1120), avg(940, 1125), avg(945, 1129), avg(949, 1134), avg(954, 1138), avg(958, 1143)},
+			{avg(1199, 1427), avg(1204, 1433), avg(1209, 1438), avg(1214, 1443), avg(1219, 1448), avg(1225, 1453)},
+			{avg(1516, 1796), avg(1521, 1802), avg(1527, 1808), avg(1533, 1814), avg(1539, 1820), avg(1545, 1826)},
+			{avg(1890, 2230), avg(1896, 2237), avg(1903, 2244), avg(1909, 2250), avg(1916, 2257), avg(1923, 2263)},
+			{avg(2267, 2677), avg(2274, 2685), avg(2281, 2692), avg(2288, 2699), avg(2296, 2707), avg(2303, 2714)},
+			{avg(2364, 2790), avg(2371, 2798), avg(2378, 2805), avg(2386, 2813), avg(2393, 2820), avg(2401, 2827)},
+			{avg(2707, 3197), avg(2715, 3206)} }}
+		spellData[Regrowth] = {coeff = 0.5 * (2 / 3.5) , levels = hotData[Regrowth].levels, averages = {
+			{avg(84, 98), avg(85, 100), avg(87, 102), avg(89, 104), avg(91, 106), avg(93, 107)},
+			{avg(164, 188), avg(166, 191), avg(169, 193), avg(171, 196), avg(174, 198), avg(176, 201)},
+			{avg(240, 274), avg(243, 278), avg(246, 281), avg(249, 284), avg(252, 287), avg(255, 290)},
+			{avg(318, 360), avg(321, 364), avg(325, 368), avg(328, 371), avg(332, 375), avg(336, 378)},
+			{avg(405, 457), avg(409, 462), avg(413, 466), avg(417, 470), avg(421, 474), avg(425, 478)},
+			{avg(511, 575), avg(515, 580), avg(520, 585), avg(525, 590), avg(529, 594), avg(534, 599)},
+			{avg(646, 724), avg(651, 730), avg(656, 735), avg(661, 740), avg(667, 746), avg(672, 751)},
+			{avg(809, 905), avg(815, 911), avg(821, 917), avg(827, 923), avg(833, 929), avg(839, 935)},
+			{avg(1003, 1119), avg(1009, 1126), avg(1016, 1133), avg(1023, 1140), avg(1030, 1147), avg(1037, 1153)},
+			{avg(1215, 1355), avg(1222, 1363), avg(1230, 1371), avg(1238, 1379), avg(1245, 1386), avg(1253, 1394)} }}
+		if isTBC then
+			spellData[Tranquility] = {coeff = 1.145, ticks = 4, interval = 2, levels = {30, 40, 50, 60, 70}, averages = {
+				{351 * 4, 354 * 4, 356 * 4, 358 * 4, 360 * 4, 362 * 4, 365 * 4},
+				{515 * 4, 518 * 4, 521 * 4, 523 * 4, 526 * 4, 528 * 4, 531 * 4},
+				{765 * 4, 769 * 4, 772 * 4, 776 * 4, 779 * 4, 782 * 4, 786 * 4},
+				{1097 * 4, 1101 * 4, 1105 * 4, 1109 * 4, 1112 * 4, 1116 * 4, 1120 * 4},
+				{1518 * 4} }}
+		else
+			spellData[Tranquility] = {coeff = 1/3, ticks = 5, interval = 2, levels = {30, 40, 50, 60}, averages = {
+				{94 * 5, 95 * 5, 96 * 5, 96 * 5, 97 * 5, 97 * 5, 98 * 5},
+				{138 * 5, 139 * 5, 140 * 5, 141 * 5, 141 * 5, 142 * 5, 143 * 5},
+				{205 * 5, 206 * 5, 207 * 5, 208 * 5, 209 * 5, 210 * 5, 211 * 5},
+				{294 * 5} }}
+		end
+
+		talentData[GiftofNature] = {mod = 0.02, current = 0}
+		talentData[ImprovedRejuv] = {mod = 0.05, current = 0}
+		talentData[EmpoweredRejuv] = {mod = 0.04, current = 0}
+		talentData[EmpoweredTouch] = {mod = 0.1, current = 0}
+
+		itemSetsData["Stormrage"] = {16903, 16898, 16904, 16897, 16900, 16899, 16901, 16902}
+		itemSetsData["Nordrassil"] = {30216, 30217, 30219, 30220, 30221}
+		itemSetsData["Thunderheart"] = {31041, 31032, 31037, 31045, 31047, 34571, 34445, 34554}
+		local bloomBombIdols = {[28355] = 87, [33076] = 105, [33841] = 116, [35021] = 131}
+		local rejuIdols = {[186054] = 15, [22398] = 50, [25643] = 86}
+
+		GetHealTargets = function(bitType, guid, healAmount, spellID)
+			-- Tranquility pulses on everyone within 30 yards, if they are in range of Mark of the Wild they'll get Tranquility
+			local spellName = GetSpellInfo(spellID)
+			if( spellName == Tranquility ) then
+				local targets = compressGUID[playerGUID]
+				local playerGroup = guidToGroup[playerGUID]
+
+				for groupGUID, id in pairs(guidToGroup) do
+					if( id == playerGroup and playerGUID ~= groupGUID and not IsSpellInRange(MarkoftheWild, guidToUnit[groupGUID]) == 1 ) then
+						targets = targets .. "," .. compressGUID[groupGUID]
+					end
+				end
+
+				return targets, healAmount
+			end
+
+			return compressGUID[guid], healAmount
+		end
+
+		-- Calculate hot heals
+		CalculateHotHealing = function(guid, spellID)
+			local spellName, spellRank = GetSpellInfo(spellID), SpellIDToRank[spellID]
+			local healAmount = getBaseHealAmount(hotData, spellName, spellRank)
+			local spellPower = GetSpellBonusHealing()
+			local healModifier = HealComm:GetHealModifier(guid) * playerHealModifier
+			local spModifier = 1
+			local bombAmount, totalTicks
+
+			-- Gift of Nature
+			if isTBC then
+				healModifier = healModifier * (1 + talentData[GiftofNature].current)
+			else
+				-- Gift of Nature does only apply to base values in classic
+				healAmount = healAmount * (1 + talentData[GiftofNature].current)
+			end
+
+			-- Rejuvenation
+			if( spellName == Rejuvenation ) then
+				if isTBC then
+					healModifier = healModifier * (1 + talentData[ImprovedRejuv].current)
+				else
+					-- Improved Rejuvenation only applies to base values in classic
+					healAmount = healAmount * (1 + talentData[ImprovedRejuv].current)
+				end
+
+				if( playerCurrentRelic and rejuIdols[playerCurrentRelic] ) then
+					spellPower = spellPower + rejuIdols[playerCurrentRelic]
+				end
+
+				local duration = 12
+				local ticks = duration / hotData[spellName].interval
+
+				if( equippedSetCache["Stormrage"] >= 8 ) then
+					healAmount = healAmount + (healAmount / ticks) -- Add Tick Amount Gained by Set.
+					duration = 15
+					ticks = ticks + 1
+				end
+
+				totalTicks = ticks
+
+				spellPower = spellPower * (duration / 15) * (1 + talentData[EmpoweredRejuv].current)
+				spellPower = spellPower / ticks
+				healAmount = healAmount / ticks
+				if( playerCurrentRelic == 186054 ) then
+					healAmount = healAmount + 15
+				end
+			elseif( spellName == Regrowth ) then
+				spellPower = spellPower * hotData[spellName].coeff * (1 + talentData[EmpoweredRejuv].current)
+				spellPower = spellPower / hotData[spellName].ticks
+				healAmount = healAmount / hotData[spellName].ticks
+
+				totalTicks = 7
+
+				if( equippedSetCache["Nordrassil"] >= 2 ) then totalTicks = totalTicks + 2 end
+
+			elseif( spellName == Lifebloom ) then
+				-- Figure out the bomb heal, apparently Gift of Nature double dips and will heal 10% for the HOT + 10% again for the direct heal
+				local bombSpellPower = spellPower
+				if( playerCurrentRelic and bloomBombIdols[playerCurrentRelic] ) then
+					bombSpellPower = bombSpellPower + bloomBombIdols[playerCurrentRelic]
+				end
+
+				local bombSpell = bombSpellPower * hotData[spellName].dhCoeff * (1 + talentData[EmpoweredRejuv].current)
+				bombAmount = math.ceil(calculateGeneralAmount(hotData[spellName].levels[spellRank], hotData[spellName].bomb[spellRank], bombSpell, spModifier, healModifier))
+
+				-- Figure out the hot tick healing
+				spellPower = spellPower * (hotData[spellName].coeff * (1 + talentData[EmpoweredRejuv].current))
+
+				-- Idol of the Emerald Queen, +47 SP per tick
+				if( playerCurrentRelic == 27886 ) then
+					spellPower = spellPower + 47
+				end
+
+				spellPower = spellPower / hotData[spellName].ticks
+				healAmount = healAmount / hotData[spellName].ticks
+				-- Figure out total ticks
+				totalTicks = 7
+
+			end
+
+			healAmount = calculateGeneralAmount(hotData[spellName].levels[spellRank], healAmount, spellPower, spModifier, healModifier)
+
+			return HOT_HEALS, ceil(healAmount), totalTicks, hotData[spellName].interval, bombAmount
+		end
+
+		-- Calcualte direct and channeled heals
+		CalculateHealing = function(guid, spellID)
+			local spellName, spellRank = GetSpellInfo(spellID), SpellIDToRank[spellID]
+			local healAmount = getBaseHealAmount(spellData, spellName, spellRank)
+			local spellPower = GetSpellBonusHealing()
+			local healModifier = HealComm:GetHealModifier(guid) * playerHealModifier
+			local spModifier = 1
+
+			-- Gift of Nature
+			if isTBC then
+				healModifier = healModifier * (1 + talentData[GiftofNature].current)
+			else
+				-- Gift of Nature does only apply to base values in classic
+				healAmount = healAmount * (1 + talentData[GiftofNature].current)
+			end
+
+			-- Regrowth
+			if( spellName == Regrowth ) then
+				spellPower = spellPower * spellData[spellName].coeff
+			-- Healing Touch
+			elseif( spellName == HealingTouch ) then
+
+				healAmount = healAmount + (spellPower * talentData[EmpoweredTouch].current)
+
+				local castTime = spellRank >= 5 and 3.5 or (spellRank == 4 and 3 or (spellRank == 3 and 2.5 or (spellRank == 2 and 2 or 1.5)))
+				spellPower = spellPower * (castTime / 3.5)
+
+				if( playerCurrentRelic == 22399 ) then
+					healAmount = healAmount + 100
+				elseif( playerCurrentRelic == 28568 ) then
+					healAmount = healAmount + 136
+				end
+
+				if equippedSetCache["Thunderheart"] >= 4 then
+					healModifier = healModifier + 0.05
+				end
+
+			-- Tranquility
+			elseif( spellName == Tranquility ) then
+				spellPower = spellPower * spellData[spellName].coeff * (1 + talentData[EmpoweredRejuv].current)
+				spellPower = spellPower / spellData[spellName].ticks
+				healAmount = healAmount / spellData[spellName].ticks
+			end
+
+			healAmount = calculateGeneralAmount(spellData[spellName].levels[spellRank], healAmount, spellPower, spModifier, healModifier)
+
+			-- 100% chance to crit with Nature, this mostly just covers fights like Loatheb where you will basically have 100% crit
+			if( GetSpellCritChance(4) >= 100 ) then
+				healAmount = healAmount * 1.50
+			end
+
+			if( spellData[spellName].ticks ) then
+				return CHANNEL_HEALS, ceil(healAmount), spellData[spellName].ticks, spellData[spellName].interval
+			end
+
+			return DIRECT_HEALS, ceil(healAmount)
+		end
+	end
+end
+
+local hasDivineFavor
+
+if( playerClass == "PALADIN" ) then
+	LoadClassData = function()
+		local DivineFavor = GetSpellInfo(20216)
+		local FlashofLight = GetSpellInfo(19750)
+		local HealingLight = GetSpellInfo(20237)
+		local HolyLight = GetSpellInfo(635)
+
+		spellData[HolyLight] = { coeff = 2.5 / 3.5, levels = {1, 6, 14, 22, 30, 38, 46, 54, 60, 62, 70}, averages = {
+			{avg(39, 47), avg(39, 48), avg(40, 49), avg(41, 50), avg(42, 51)},
+			{avg(76, 90), avg(77, 92), avg(78, 93), avg(79, 94), avg(80, 95), avg(81, 96)},
+			{avg(159, 187), avg(160, 189), avg(162, 191), avg(164, 193), avg(165, 194), avg(167, 196)},
+			{avg(310, 356), avg(312, 359), avg(314, 361), avg(317, 364), avg(319, 366), avg(322, 368)},
+			{avg(491, 553), avg(494, 557), avg(497, 560), avg(500, 563), avg(503, 566), avg(506, 569)},
+			{avg(698, 780), avg(701, 784), avg(705, 788), avg(709, 792), avg(713, 796), avg(717, 799)},
+			{avg(945, 1053), avg(949, 1058), avg(954, 1063), avg(958, 1067), avg(963, 1072), avg(968, 1076)},
+			{avg(1246, 1388), avg(1251, 1394), avg(1256, 1399), avg(1261, 1404), avg(1266, 1409), avg(1272, 1414)},
+			{avg(1590, 1770), avg(1595, 1775), avg(1601, 1781), avg(1607, 1787), avg(1613, 1793), avg(1619, 1799)},
+			{avg(1741, 1939), avg(1747, 1946), avg(1753, 1952), avg(1760, 1959), avg(1766, 1965), avg(1773, 1971)},
+			{avg(2196, 2446)} }}
+		spellData[FlashofLight] = { coeff = 1.5 / 3.5, levels = {20, 26, 34, 42, 50, 58, 66}, averages = {
+			{avg(62, 72), avg(63, 73), avg(64, 74), avg(65, 75), avg(66, 76), avg(67, 77)},
+			{avg(96, 110), avg(97, 112), avg(98, 113), avg(99, 114), avg(101, 116), avg(102, 117)},
+			{avg(145, 163), avg(146, 165), avg(148, 167), avg(149, 168), avg(151, 170), avg(153, 171)},
+			{avg(197, 221), avg(198, 223), avg(200, 225), avg(202, 227), avg(204, 229), avg(206, 231)},
+			{avg(267, 299), avg(269, 302), avg(271, 304), avg(273, 306), avg(275, 308), avg(278, 310)},
+			{avg(343, 383), avg(345, 386), avg(348, 389), avg(350, 391), avg(353, 394), avg(356, 396)},
+			{avg(448, 502), avg(450, 505), avg(453, 508), avg(455, 510), avg(458, 513)} }}
+
+		talentData[HealingLight] = { mod = 0.04, current = 0 }
+
+		itemSetsData["Lightbringer"] = {30992, 30983, 30988, 30994, 30996, 34432, 34487, 34559}
+
+		local flashLibrams = {[23006] = 83, [23201] = 53, [186065] = 10, [25644] = 79}
+
+		local blessings = {
+			[19977] = {
+				[HolyLight] = 210,
+				[FlashofLight] = 60,
+			},
+			[19978] = {
+				[HolyLight] = 300,
+				[FlashofLight] = 85,
+			},
+			[19979] = {
+				[HolyLight] = 400,
+				[FlashofLight] = 115,
+			},
+			[25890] = {
+				[HolyLight] = 400,
+				[FlashofLight] = 115,
+			},
+			[27144] = {
+				[HolyLight] = 580,
+				[FlashofLight] = 185,
+			},
+			[27145] = {
+				[HolyLight] = 580,
+				[FlashofLight] = 185,
+			},
+		}
+
+		AuraHandler = function(unit, guid)
+			if( unit == "player" ) then
+				hasDivineFavor = unitHasAura("player", DivineFavor)
+			end
+		end
+
+		GetHealTargets = function(bitType, guid, healAmount, spellID)
+			return compressGUID[guid], healAmount
+		end
+
+		CalculateHealing = function(guid, spellID, unit)
+			local spellName, spellRank = GetSpellInfo(spellID), SpellIDToRank[spellID]
+			local healAmount = getBaseHealAmount(spellData, spellName, spellRank)
+			local spellPower = GetSpellBonusHealing()
+			local healModifier = HealComm:GetHealModifier(guid) * playerHealModifier
+			local spModifier = 1
+
+			if isTBC then
+				healModifier = healModifier * (1 + talentData[HealingLight].current)
+			else
+				healAmount = healAmount * (1 + talentData[HealingLight].current)
+			end
+
+			if playerCurrentRelic then
+				if spellName == FlashofLight and flashLibrams[playerCurrentRelic] then
+					spellPower = spellPower + flashLibrams[playerCurrentRelic]
+				elseif spellName == HolyLight and playerCurrentRelic == 28296 then
+					spellPower = spellPower + 87
+				end
+			end
+
+			if( equippedSetCache["Lightbringer"] >= 4 and spellName == FlashofLight ) then healModifier = healModifier + 0.05 end
+
+			spellPower = spellPower * spellData[spellName].coeff
+			healAmount = calculateGeneralAmount(spellData[spellName].levels[spellRank], healAmount, spellPower, spModifier, healModifier)
+
+			for auraID, values in pairs(blessings) do
+				if unitHasAura(unit, auraID) then
+					if playerCurrentRelic == 28592 then
+						if spellName == FlashofLight then
+							healAmount = calculateGeneralAmount(spellData[spellName].levels[spellRank], healAmount, values[spellName] + 60, healModifier, 1)
+						else
+							healAmount = calculateGeneralAmount(spellData[spellName].levels[spellRank], healAmount, values[spellName] + 120, healModifier, 1)
+						end
+					else
+						healAmount = calculateGeneralAmount(spellData[spellName].levels[spellRank], healAmount, values[spellName], healModifier, 1)
+					end
+					break
+				end
+			end
+
+			if( hasDivineFavor or GetSpellCritChance(2) >= 100 ) then
+				healAmount = healAmount * 1.50
+			end
+
+			return DIRECT_HEALS, ceil(healAmount)
+		end
+	end
+end
+
+if( playerClass == "PRIEST" ) then
+	LoadClassData = function()
+		local Renew = GetSpellInfo(139)
+		local GreaterHeal = GetSpellInfo(2060)
+		local PrayerofHealing = GetSpellInfo(596)
+		local FlashHeal = GetSpellInfo(2061)
+		local Heal = GetSpellInfo(2054)
+		local LesserHeal = GetSpellInfo(2050)
+		local SpiritualHealing = GetSpellInfo(14898)
+		local ImprovedRenew = GetSpellInfo(14908)
+		local GreaterHealHot = GetSpellInfo(22009)
+		local CureDisease = GetSpellInfo(528)
+		local BindingHeal = GetSpellInfo(32546) or "Binding Heal"
+		local EmpoweredHealing = GetSpellInfo(33158) or "Empowered Healing"
+		local Renewal = GetSpellInfo(37563) or "Renewal" -- T4 bonus
+
+		hotData[Renew] = {coeff = 1, interval = 3, ticks = 5, levels = {8, 14, 20, 26, 32, 38, 44, 50, 56, 60, 65, 70}, averages = {
+			45, 100, 175, 245, 315, 400, 510, 650, 810, 970, 1010, 1110 }}
+		hotData[GreaterHealHot] = hotData[Renew]
+		hotData[Renewal] = {coeff = 0, interval = 3, ticks = 3, levels = {70}, averages = {150}}
+
+		spellData[FlashHeal] = {coeff = 1.5 / 3.5, levels = {20, 26, 32, 38, 44, 50, 56, 61, 67}, averages = {
+			{avg(193, 237), avg(194, 239), avg(196, 241), avg(198, 243),  avg(200, 245), avg(202, 247)},
+			{avg(258, 314), avg(260, 317), avg(262, 319), avg(264, 321), avg(266, 323), avg(269, 325)},
+			{avg(327, 393), avg(329, 396), avg(332, 398), avg(334, 401), avg(337, 403), avg(339, 406)},
+			{avg(400, 478), avg(402, 481), avg(405, 484), avg(408, 487), avg(411, 490), avg(414, 492)},
+			{avg(518, 616), avg(521, 620), avg(524, 623), avg(527, 626), avg(531, 630), avg(534, 633)},
+			{avg(644, 764), avg(647, 768), avg(651, 772), avg(655, 776), avg(658, 779), avg(662, 783)},
+			{avg(812, 958), avg(816, 963), avg(820, 967), avg(824, 971), avg(828, 975), avg(833, 979)},
+			{avg(913, 1059), avg(917, 1064), avg(922, 1069), avg(927, 1074), avg(931, 1078)},
+			{avg(1101, 1279), avg(1106, 1285), avg(1111, 1290), avg(1116, 1295)} }}
+		spellData[GreaterHeal] = {coeff = 3 / 3.5, levels = {40, 46, 52, 58, 60, 63, 68}, averages = {
+			{avg(899, 1013), avg(904, 1019), avg(909, 1024), avg(914, 1029), avg(919, 1034), avg(924, 1039)},
+			{avg(1149, 1289), avg(1154, 1295), avg(1160, 1301), avg(1166, 1307), avg(1172, 1313), avg(1178, 1318)},
+			{avg(1437, 1609), avg(1443, 1616), avg(1450, 1623), avg(1456, 1629), avg(1463, 1636), avg(1470, 1642)},
+			{avg(1798, 2006), avg(1805, 2014), avg(1813, 2021), avg(1820, 2029), avg(1828, 2036), avg(1835, 2044)},
+			{avg(1966, 2194), avg(1974, 2203), avg(1982, 2211), avg(1990, 2219), avg(1998, 2227), avg(2006, 2235)},
+			{avg(2074, 2410), avg(2082, 2419), avg(2090, 2427), avg(2099, 2436), avg(2107, 2444)},
+			{avg(2396, 2784), avg(2405, 2794), avg(2414, 2803)} }}
+		spellData[Heal] = {coeff = 3 / 3.5, levels = {16, 22, 28, 34}, averages = {
+			{avg(295, 341), avg(297, 344), avg(299, 346), avg(302, 349), avg(304, 351), avg(307, 353)},
+			{avg(429, 491), avg(432, 495), avg(435, 498), avg(438, 501), avg(441, 504), avg(445, 507)},
+			{avg(566, 642), avg(570, 646), avg(574, 650), avg(578, 654), avg(582, 658), avg(586, 662)},
+			{avg(712, 804), avg(716, 809), avg(721, 813), avg(725, 818), avg(730, 822), avg(734, 827)} }}
+		spellData[LesserHeal] = {levels = {1, 4, 10}, averages = {
+			{avg(46, 56), avg(46, 57), avg(47, 58)},
+			{avg(71, 85), avg(72, 87), avg(73, 88), avg(74, 89), avg(75, 90), avg(76, 91)},
+			{avg(135, 157), avg(136, 159), avg(138, 161), avg(139, 162), avg(141, 164), avg(143, 165)} }}
+		spellData[PrayerofHealing] = {coeff = isTBC and 0.431596 or (3/3.5/3), levels = {30, 40, 50, 60, 60, 68}, averages = {
+			{avg(301, 321), avg(302, 323), avg(303, 324), avg(304, 325), avg(306, 327), avg(307, 328), avg(308, 329), avg(310, 331), avg(311, 332), avg(312, 333)},
+			{avg(444, 472), avg(445, 474), avg(447, 476), avg(448, 477), avg(450, 479), avg(452, 480), avg(453, 482), avg(455, 484), avg(456, 485), avg(458, 487)},
+			{avg(657, 695), avg(659, 697), avg(661, 699), avg(663, 701), avg(665, 703), avg(667, 705), avg(669, 707), avg(671, 709), avg(673, 711), avg(675, 713)},
+			{avg(939, 991), avg(941, 994), avg(943, 996), avg(946, 999), avg(948, 1001), avg(951, 1003), avg(953, 1006), avg(955, 1008), avg(958, 1011), avg(960, 1013)},
+			{avg(997, 1053), avg(999, 1056), avg(1002, 1058), avg(1004, 1061), avg(1007, 1063), avg(1009, 1066), avg(1012, 1068), avg(1014, 1071), avg(1017, 1073), avg(1019, 1076)},
+			{avg(1246, 1316), avg(1248, 1319), avg(1251, 1322)} }}
+		spellData[BindingHeal] = {coeff = 1.5 / 3.5, levels = {64}, averages = {
+			{avg(1042, 1338), avg(1043, 1340), avg(1045, 1342), avg(1047, 1344), avg(1049, 1346), avg(1051, 1348), avg(1053, 1350)} }}
+
+		talentData[ImprovedRenew] = {mod = 0.05, current = 0}
+		talentData[SpiritualHealing] = {mod = 0.02, current = 0}
+		talentData[EmpoweredHealing] = {mod = 0.02, current = 0}
+
+		itemSetsData["Oracle"] = {21351, 21349, 21350, 21348, 21352}
+		itemSetsData["Absolution"] = {31068, 31063, 31060, 31069, 31066, 34562, 34527, 34435}
+		itemSetsData["Avatar"] = {30153, 30152, 30151, 30154, 30150}
+
+		GetHealTargets = function(bitType, guid, healAmount, spellID)
+			local spellName = GetSpellInfo(spellID)
+			if( spellName == BindingHeal ) then
+				if guid == playerGUID then
+					return string.format("%s", compressGUID[playerGUID]), healAmount
+				else
+					return string.format("%s,%s", compressGUID[guid], compressGUID[playerGUID]), healAmount
+				end
+			elseif( spellName == PrayerofHealing ) then
+				guid = UnitGUID("player")
+				local targets = compressGUID[guid]
+				local group = guidToGroup[guid]
+
+				for groupGUID, id in pairs(guidToGroup) do
+					local unit = guidToUnit[groupGUID]
+					if( id == group and guid ~= groupGUID and (IsSpellInRange(CureDisease, unit) == 1 or CheckInteractDistance(unit, 4)) ) then
+						targets = targets .. "," .. compressGUID[groupGUID]
+					end
+				end
+
+				return targets, healAmount
+			end
+
+			return compressGUID[guid], healAmount
+		end
+
+		CalculateHotHealing = function(guid, spellID)
+			local spellName, spellRank = GetSpellInfo(spellID), SpellIDToRank[spellID]
+			local healAmount = getBaseHealAmount(hotData, spellName, spellRank)
+			local spellPower = GetSpellBonusHealing()
+			local healModifier = HealComm:GetHealModifier(guid) * playerHealModifier
+			local spModifier = 1
+			local totalTicks
+
+			if isTBC then
+				healModifier = healModifier * (1 + talentData[SpiritualHealing].current)
+			else
+				-- Spiritual Healing only applies to base values
+				healAmount = healAmount * (1 + talentData[SpiritualHealing].current)
+			end
+
+			if( spellName == Renew or spellName == GreaterHealHot ) then
+				if isTBC then
+					healModifier = healModifier * (1 + talentData[ImprovedRenew].current)
+				else
+					-- Improved Renew only applies to the base value in classic
+					healAmount = healAmount * (1 + talentData[ImprovedRenew].current)
+				end
+
+				local duration = 15
+				local ticks = hotData[spellName].ticks
+
+				if( equippedSetCache["Oracle"] >= 5 or equippedSetCache["Avatar"] >= 4 ) then
+					healAmount = healAmount + (healAmount / ticks) -- Add Tick Amount Gained by Set.
+					duration = 18
+					ticks = ticks + 1
+				end
+
+				totalTicks = ticks
+
+				spellPower = spellPower / ticks
+				healAmount = healAmount / ticks
+			end
+
+			healAmount = calculateGeneralAmount(hotData[spellName].levels[spellRank], healAmount, spellPower, spModifier, healModifier)
+			return HOT_HEALS, ceil(healAmount), totalTicks, hotData[spellName].interval
+		end
+
+		-- If only every other class was as easy as Paladins
+		CalculateHealing = function(guid, spellID)
+			local spellName, spellRank = GetSpellInfo(spellID), SpellIDToRank[spellID]
+			local healAmount = getBaseHealAmount(spellData, spellName, spellRank)
+			local spellPower = GetSpellBonusHealing()
+			local healModifier = HealComm:GetHealModifier(guid) * playerHealModifier
+			local spModifier = 1
+
+			if isTBC then
+				healModifier = healModifier * (1 + talentData[SpiritualHealing].current)
+			else
+				healAmount = healAmount * (1 + talentData[SpiritualHealing].current)
+			end
+
+			-- Greater Heal
+			if( spellName == GreaterHeal ) then
+				if( equippedSetCache["Absolution"] >= 4 ) then healModifier = healModifier * 1.05 end
+				healAmount = healAmount + (spellPower * (talentData[EmpoweredHealing].current * 2))
+				spellPower = spellPower * spellData[spellName].coeff
+			-- Flash Heal
+			elseif( spellName == FlashHeal ) then
+				healAmount = healAmount + (spellPower * talentData[EmpoweredHealing].current)
+				spellPower = spellPower * spellData[spellName].coeff
+			-- Binding Heal
+			elseif( spellName == BindingHeal ) then
+				healAmount = healAmount + (spellPower * talentData[EmpoweredHealing].current)
+				spellPower = spellPower * spellData[spellName].coeff
+			-- Prayer of Healing
+			elseif( spellName == PrayerofHealing ) then
+				spellPower = spellPower * spellData[spellName].coeff
+			-- Heal
+			elseif( spellName == Heal ) then
+				spellPower = spellPower * spellData[spellName].coeff
+			-- Lesser Heal
+			elseif( spellName == LesserHeal ) then
+				local castTime = spellRank >= 3 and 2.5 or spellRank == 2 and 2 or 1.5
+				spellPower = spellPower * (castTime / 3.5)
+			end
+
+			healAmount = calculateGeneralAmount(spellData[spellName].levels[spellRank], healAmount, spellPower, spModifier, healModifier)
+
+			-- Player has over a 100% chance to crit with Holy spells
+			if( GetSpellCritChance(2) >= 100 ) then
+				healAmount = healAmount * 1.50
+			end
+
+			return DIRECT_HEALS, ceil(healAmount)
+		end
+	end
+end
+
+if( playerClass == "SHAMAN" ) then
+	LoadClassData = function()
+		local ChainHeal = GetSpellInfo(1064)
+		local HealingWave = GetSpellInfo(331)
+		local LesserHealingWave = GetSpellInfo(8004)
+		local ImpChainHeal = GetSpellInfo(30872) or "Improved Chain Heal"
+		local HealingWay = GetSpellInfo(29206)
+		local Purification = GetSpellInfo(16178)
+
+		spellData[ChainHeal] = {coeff = 2.5 / 3.5, levels = {40, 46, 54, 61, 68}, averages = {
+			{avg(320, 368), avg(322, 371), avg(325, 373), avg(327, 376), avg(330, 378), avg(332, 381)},
+			{avg(405, 465), avg(407, 468), avg(410, 471), avg(413, 474), avg(416, 477), avg(419, 479)},
+			{avg(551, 629), avg(554, 633), avg(557, 636), avg(560, 639), avg(564, 643), avg(567, 646)},
+			{avg(605, 691), avg(608, 695), avg(612, 699), avg(616, 703), avg(620, 707), avg(624, 710)},
+			{avg(826, 942), avg(829, 946), avg(833, 950)} }}
+		spellData[HealingWave] = {levels = {1, 6, 12, 18, 24, 32, 40, 48, 56, 60, 63, 70}, averages = {
+			{avg(34, 44), avg(34, 45), avg(35, 46), avg(36, 47)},
+			{avg(64, 78), avg(65, 79), avg(66, 80), avg(67, 81), avg(68, 82), avg(69, 83)},
+			{avg(129, 155), avg(130, 157), avg(132, 158), avg(133, 160), avg(135, 161), avg(136, 163)},
+			{avg(268, 316), avg(270, 319), avg(272, 321), avg(274, 323), avg(277, 326), avg(279, 328)},
+			{avg(376, 440), avg(378, 443), avg(381, 446), avg(384, 449), avg(386, 451), avg(389, 454)},
+			{avg(536, 622), avg(539, 626), avg(542, 629), avg(545, 632), avg(549, 636), avg(552, 639)},
+			{avg(740, 854), avg(743, 858), avg(747, 862), avg(751, 866), avg(755, 870), avg(759, 874)},
+			{avg(1017, 1167), avg(1021, 1172), avg(1026, 1177), avg(1031, 1182), avg(1035, 1186), avg(1040, 1191)},
+			{avg(1367, 1561), avg(1372, 1567), avg(1378, 1572), avg(1383, 1578), avg(1389, 1583), avg(1394, 1589)},
+			{avg(1620, 1850), avg(1625, 1856), avg(1631, 1861), avg(1636, 1867), avg(1642, 1872), avg(1647, 1878)},
+			{avg(1725, 1969), avg(1731, 1976), avg(1737, 1982), avg(1743, 1988), avg(1750, 1995), avg(1756, 2001)},
+			{avg(2134, 2436)} }}
+		spellData[LesserHealingWave] = {coeff = 1.5 / 3.5, levels = {20, 28, 36, 44, 52, 60, 66}, averages = {
+			{avg(162, 186), avg(163, 188), avg(165, 190), avg(167, 192), avg(168, 193), avg(170, 195)},
+			{avg(247, 281), avg(249, 284), avg(251, 286), avg(253, 288), avg(255, 290), avg(257, 292)},
+			{avg(337, 381), avg(339, 384), avg(342, 386), avg(344, 389), avg(347, 391), avg(349, 394)},
+			{avg(458, 514), avg(461, 517), avg(464, 520), avg(467, 523), avg(470, 526), avg(473, 529)},
+			{avg(631, 705), avg(634, 709), avg(638, 713), avg(641, 716), avg(645, 720), avg(649, 723)},
+			{avg(832, 928), avg(836, 933), avg(840, 937), avg(844, 941), avg(848, 945), avg(853, 949)},
+			{avg(1039, 1185), avg(1043, 1190), avg(1047, 1194), avg(1051, 1198)} }}
+
+		talentData[HealingWay] = {mod = 0, current = 0}
+		talentData[ImpChainHeal] = {mod = 0.10, current = 0}
+		talentData[Purification] = {mod = 0.02, current = 0}
+
+		itemSetsData["Skyshatter"] = {31016, 31007, 31012, 31019, 31022, 34543, 34438, 34565}
+
+		local lhwTotems = {[22396] = 80, [23200] = 53, [186072] = 10, [25645] = 79}
+
+		-- Lets a specific override on how many people this will hit
+		GetHealTargets = function(bitType, guid, healAmount)
+			return compressGUID[guid], healAmount
+		end
+
+		-- If only every other class was as easy as Paladins
+		CalculateHealing = function(guid, spellID, unit)
+			local spellName, spellRank = GetSpellInfo(spellID), SpellIDToRank[spellID]
+			local healAmount = getBaseHealAmount(spellData, spellName, spellRank)
+			local spellPower = GetSpellBonusHealing()
+			local healModifier = HealComm:GetHealModifier(guid) * playerHealModifier
+			local spModifier = 1
+
+			if isTBC then
+				healModifier = healModifier * (1 + talentData[Purification].current)
+			else
+				-- Purification only applies to base values in classic
+				healAmount = healAmount * (1 + talentData[Purification].current)
+			end
+
+			-- Chain Heal
+			if( spellName == ChainHeal ) then
+				spellPower = spellPower * spellData[spellName].coeff
+
+				if( equippedSetCache["Skyshatter"] >= 4 ) then
+					healModifier = healModifier * 1.05
+				end
+
+				healModifier = healModifier * (1 + talentData[ImpChainHeal].current)
+
+				if playerCurrentRelic == 28523 then healAmount = healAmount + 87 end
+			-- Heaing Wave
+			elseif( spellName == HealingWave ) then
+				local hwStacks = select(3, unitHasAura(unit, 29203))
+				if( hwStacks ) then
+					healAmount = healAmount * ((hwStacks * 0.06) + 1)
+				end
+				--healModifier = healModifier * (talentData[HealingWay].spent == 3 and 1.25 or talentData[HealingWay].spent == 2 and 1.16 or talentData[HealingWay].spent == 1 and 1.08 or 1)
+
+				local castTime = spellRank > 3 and 3 or spellRank == 3 and 2.5 or spellRank == 2 and 2 or 1.5
+
+				if playerCurrentRelic == 27544 then spellPower = spellPower + 88 end
+
+				spellPower = spellPower * (castTime / 3.5)
+
+			-- Lesser Healing Wave
+			elseif( spellName == LesserHealingWave ) then
+				spellPower = spellPower + (playerCurrentRelic and lhwTotems[playerCurrentRelic] or 0)
+				spellPower = spellPower * spellData[spellName].coeff
+			end
+
+			healAmount = calculateGeneralAmount(spellData[spellName].levels[spellRank], healAmount, spellPower, spModifier, healModifier)
+
+			-- Player has over a 100% chance to crit with Nature spells
+			if( GetSpellCritChance(4) >= 100 ) then
+				healAmount = healAmount * 1.50
+			end
+
+			-- Apply the final modifier of any MS or self heal increasing effects
+			return DIRECT_HEALS, ceil(healAmount)
+		end
+	end
+end
+
+if( playerClass == "HUNTER" ) then
+	LoadClassData = function()
+		local MendPet = GetSpellInfo(136)
+
+		spellData[MendPet] = { interval = 1, levels = { 12, 20, 28, 36, 44, 52, 60 }, ticks = 5, averages = {100, 190, 340, 515, 710, 945, 1225 } }
+
+		itemSetsData["Giantstalker"] = {16851, 16849, 16850, 16845, 16848, 16852, 16846, 16847}
+
+		GetHealTargets = function(bitType, guid, healAmount, spellID)
+			return compressGUID[UnitGUID("pet")], healAmount
+		end
+
+		CalculateHealing = function(guid, spellID)
+			local spellName, spellRank = GetSpellInfo(spellID), SpellIDToRank[spellID]
+			local amount = getBaseHealAmount(spellData, spellName, spellRank)
+
+			if( equippedSetCache["Giantstalker"] >= 3 ) then amount = amount * 1.1 end
+
+			return CHANNEL_HEALS, ceil(amount / spellData[spellName].ticks), spellData[spellName].ticks, spellData[spellName].interval
+		end
+	end
+end
+
+if( playerClass == "WARLOCK" ) then
+	LoadClassData = function()
+		local HealthFunnel = GetSpellInfo(755)
+		local DrainLife = GetSpellInfo(689)
+		local ImpHealthFunnel = GetSpellInfo(18703)
+
+		spellData[HealthFunnel] = { interval = 1, levels = { 12, 20, 28, 36, 44, 52, 60, 68 }, ticks = 10, averages = { 120, 240, 430, 640, 890, 1190, 1530, 1880 } }
+		spellData[DrainLife] = { interval = 1, levels = { 14, 22, 30, 38, 46, 54, 62, 69 }, ticks = 5, averages = { 10 * 5, 17 * 5, 29 * 5, 41 * 5, 55 * 5, 71 * 5, 87 * 5, 108 * 5 } }
+
+		talentData[ImpHealthFunnel] = { mod = 0.1, current = 0 }
+
+		GetHealTargets = function(bitType, guid, healAmount, spellID)
+			return compressGUID[UnitGUID("pet")], healAmount
+		end
+
+		CalculateHealing = function(guid, spellID)
+			local spellName, spellRank = GetSpellInfo(spellID), SpellIDToRank[spellID]
+			local amount = getBaseHealAmount(spellData, spellName, spellRank)
+
+			amount = amount * (1 + talentData[ImpHealthFunnel].current)
+
+			return CHANNEL_HEALS, ceil(amount / spellData[spellName].ticks), spellData[spellName].ticks, spellData[spellName].interval
+		end
+	end
+end
+
+-- Healing modifiers
+if( not HealComm.aurasUpdated ) then
+	HealComm.aurasUpdated = true
+	HealComm.healingModifiers = nil
+end
+
+HealComm.currentModifiers = HealComm.currentModifiers or {}
+
+-- The only spell in the game with a name conflict is Ray of Pain from the Nagrand Void Walkers
+HealComm.healingModifiers = HealComm.healingModifiers or {
+	[28776] = 0.10, -- Necrotic Poison
+	[19716] = 0.25, -- Gehennas' Curse
+	[13737] = 0.50, -- Mortal Strike
+	[15708] = 0.50, -- Mortal Strike
+	[16856] = 0.50, -- Mortal Strike
+	[17547] = 0.50, -- Mortal Strike
+	[19643] = 0.50, -- Mortal Strike
+	[24573] = 0.50, -- Mortal Strike
+	[12294] = 0.50, -- Mortal Strike (Rank 1)
+	[21551] = 0.50, -- Mortal Strike (Rank 2)
+	[21552] = 0.50, -- Mortal Strike (Rank 3)
+	[21553] = 0.50, -- Mortal Strike (Rank 4)
+	[23169] = 0.50, -- Brood Affliction: Green
+	[22859] = 0.50, -- Mortal Cleave
+	[7068] = 0.25, -- Veil of Shadow
+	[17820] = 0.25, -- Veil of Shadow
+	[22687] = 0.25, -- Veil of Shadow
+	[23224] = 0.25, -- Veil of Shadow
+	[24674] = 0.25, -- Veil of Shadow
+	[28440] = 0.25, -- Veil of Shadow
+	[13583] = 0.50, -- Curse of the Deadwood
+	[23230] = 0.50, -- Blood Fury
+}
+
+HealComm.healingStackMods = HealComm.healingStackMods or {
+	-- Mortal Wound
+	[25646] = function(stacks) return 1 - stacks * 0.10 end,
+	[28467] = function(stacks) return 1 - stacks * 0.10 end,
+}
+
+local healingStackMods = HealComm.healingStackMods
+local healingModifiers, currentModifiers = HealComm.healingModifiers, HealComm.currentModifiers
+
+local distribution
+local CTL = _G.ChatThrottleLib
+local function sendMessage(msg)
+	if( distribution and strlen(msg) <= 240 ) then
+		if CTL then
+			CTL:SendAddonMessage("BULK", COMM_PREFIX, msg, distribution or 'GUILD')
+		end
+	end
+end
+
+-- Keep track of where all the data should be going
+local instanceType
+local function updateDistributionChannel()
+	if( instanceType == "pvp" or instanceType == "arena" ) then
+		distribution = "INSTANCE_CHAT"
+	elseif( IsInRaid() ) then
+		distribution = "RAID"
+	elseif( IsInGroup() ) then
+		distribution = "PARTY"
+	else
+		distribution = nil
+	end
+end
+
+-- Figure out where we should be sending messages and wipe some caches
+function HealComm:PLAYER_ENTERING_WORLD()
+	HealComm.eventFrame:UnregisterEvent("PLAYER_ENTERING_WORLD")
+	HealComm:ZONE_CHANGED_NEW_AREA()
+end
+
+function HealComm:ZONE_CHANGED_NEW_AREA()
+	local pvpType = GetZonePVPInfo()
+	local instance = select(2, IsInInstance())
+
+	HealComm.zoneHealModifier = 1
+	if( pvpType == "combat" or instance == "arena" or instance == "pvp" ) then
+		HealComm.zoneHealModifier = 0.90
+	end
+
+	if( instance ~= instanceType ) then
+		instanceType = instance
+
+		updateDistributionChannel()
+		clearPendingHeals()
+		wipe(activeHots)
+	end
+
+	instanceType = instance
+end
+
+local alreadyAdded = {}
+function HealComm:UNIT_AURA(unit)
+	local guid = UnitGUID(unit)
+	if( not guidToUnit[guid] ) then return end
+	local increase, decrease, playerIncrease, playerDecrease = 1, 1, 1, 1
+
+	-- Scan buffs
+	local id = 1
+	while( true ) do
+		local name, _, stack, _, _, _, _, _, _, spellID = UnitAura(unit, id, "HELPFUL")
+		if( not name ) then break end
+		-- Prevent buffs like Tree of Life that have the same name for the shapeshift/healing increase from being calculated twice
+		if( not alreadyAdded[name] ) then
+			alreadyAdded[name] = true
+
+			if( healingModifiers[spellID] ) then
+				increase = increase * healingModifiers[spellID]
+			elseif( healingStackMods[spellID] ) then
+				increase = increase * healingStackMods[spellID](stack)
+			end
+		end
+
+		id = id + 1
+	end
+
+	-- Scan debuffs
+	id = 1
+	while( true ) do
+		local name, _, stack, _, _, _, _, _, _, spellID = UnitAura(unit, id, "HARMFUL")
+		if( not name ) then break end
+
+		if( healingModifiers[spellID] ) then
+			decrease = min(decrease, healingModifiers[spellID])
+		elseif( healingStackMods[spellID] ) then
+			decrease = min(decrease, healingStackMods[spellID](stack))
+		end
+
+		id = id + 1
+	end
+
+	-- Check if modifier changed
+	local modifier = increase * decrease
+	if( modifier ~= currentModifiers[guid] ) then
+		if( currentModifiers[guid] or modifier ~= 1 ) then
+			currentModifiers[guid] = modifier
+			self.callbacks:Fire("HealComm_ModifierChanged", guid, modifier)
+		else
+			currentModifiers[guid] = modifier
+		end
+	end
+
+	wipe(alreadyAdded)
+
+	if( unit == "player" ) then
+		if unitHasAura("player", 10060) then -- Power Infusion
+			playerIncrease = playerIncrease * 1.20
+		end
+
+		playerHealModifier = playerIncrease * playerDecrease
+	end
+
+	-- Class has a specific monitor it needs for auras
+	if( AuraHandler ) then
+		AuraHandler(unit, guid)
+	end
+end
+
+function HealComm:PLAYER_LEVEL_UP(level)
+	playerLevel = tonumber(level) or UnitLevel("player")
+end
+
+-- Cache player talent data for spells we need
+function HealComm:CHARACTER_POINTS_CHANGED()
+	for tabIndex=1, GetNumTalentTabs() do
+		for i=1, GetNumTalents(tabIndex) do
+			local name, _, _, _, spent = GetTalentInfo(tabIndex, i)
+			if( name and talentData[name] ) then
+				talentData[name].current = talentData[name].mod * spent
+				talentData[name].spent = spent
+			end
+		end
+	end
+end
+
+-- Save the currently equipped range weapon
+local RANGED_SLOT = GetInventorySlotInfo("RangedSlot")
+function HealComm:PLAYER_EQUIPMENT_CHANGED()
+	-- Caches set bonus info, as you can't reequip set bonus gear in combat no sense in checking it
+	if( not InCombatLockdown() ) then
+		for name, items in pairs(itemSetsData) do
+			equippedSetCache[name] = 0
+			for _, itemID in pairs(items) do
+				if( IsEquippedItem(itemID) ) then
+					equippedSetCache[name] = equippedSetCache[name] + 1
+				end
+			end
+		end
+	end
+
+	-- Check relic
+	local relic = GetInventoryItemLink("player", RANGED_SLOT)
+	playerCurrentRelic = relic and tonumber(strmatch(relic, "item:(%d+):")) or nil
+end
+
+-- COMM CODE
+local function loadHealAmount(...)
+	local tbl = HealComm:RetrieveTable()
+	for i=1, select("#", ...) do
+		tbl[i] = tonumber((select(i, ...)))
+	end
+
+	return tbl
+end
+
+-- Direct heal started
+local function loadHealList(pending, amount, stack, endTime, ticksLeft, ...)
+	wipe(tempPlayerList)
+
+	-- For the sake of consistency, even a heal doesn't have multiple end times like a hot, it'll be treated as such in the DB
+	if( amount ~= -1 and amount ~= "-1" ) then
+		amount = not pending.hasVariableTicks and amount or loadHealAmount(strsplit("@", amount))
+
+		for i=1, select("#", ...) do
+			local guid = select(i, ...)
+			local decompGUID = guid and decompressGUID[guid]
+			if( decompGUID ) then
+				updateRecord(pending, decompGUID, amount, stack, endTime, ticksLeft)
+				tinsert(tempPlayerList, decompGUID)
+			end
+		end
+	else
+		for i = 1, select("#", ...), 2 do
+			local guid = select(i, ...)
+			local decompGUID = guid and decompressGUID[guid]
+			amount = tonumber((select(i + 1, ...)))
+			if( decompGUID and amount ) then
+				updateRecord(pending, decompGUID, amount, stack, endTime, ticksLeft)
+				tinsert(tempPlayerList, decompGUID)
+			end
+		end
+	end
+end
+
+local function parseDirectHeal(casterGUID, spellID, amount, castTime, ...)
+	local spellName = GetSpellInfo(spellID)
+	local unit = guidToUnit[casterGUID]
+
+	if( not unit or not spellName or not amount or select("#", ...) == 0 ) then return end
+
+	local endTime
+	if unit == "player" then
+		endTime = select(5, CastingInfo())
+		if not endTime then return end
+		endTime = endTime / 1000
+	else
+		endTime = GetTime() + (castTime or 1.5)
+	end
+
+	pendingHeals[casterGUID] = pendingHeals[casterGUID] or {}
+	pendingHeals[casterGUID][spellName] = pendingHeals[casterGUID][spellName] or {}
+
+	local pending = pendingHeals[casterGUID][spellName]
+	wipe(pending)
+	pending.endTime = endTime
+	pending.spellID = spellID
+	pending.bitType = DIRECT_HEALS
+
+	loadHealList(pending, amount, 1, pending.endTime, nil, ...)
+
+	HealComm.callbacks:Fire("HealComm_HealStarted", casterGUID, spellID, pending.bitType, pending.endTime, unpack(tempPlayerList))
+end
+
+HealComm.parseDirectHeal = parseDirectHeal
+
+-- Channeled heal started
+local function parseChannelHeal(casterGUID, spellID, amount, totalTicks, ...)
+	local spellName = GetSpellInfo(spellID)
+	local unit = guidToUnit[casterGUID]
+	if( not unit or not spellName or not totalTicks or not amount or select("#", ...) == 0 ) then return end
+
+	local tickInterval = spellName == GetSpellInfo(740) and 2 or 1
+
+	local startTime, endTime
+	if unit == "player" then
+		startTime, endTime = select(4, ChannelInfo())
+		if not startTime then return end
+		startTime = startTime / 1000
+		endTime = endTime / 1000
+	else
+		startTime = GetTime()
+		endTime = startTime + totalTicks * tickInterval
+	end
+
+	pendingHots[casterGUID] = pendingHots[casterGUID] or {}
+	pendingHots[casterGUID][spellName] = pendingHots[casterGUID][spellName] or {}
+
+	local inc = amount == -1 and 2 or 1
+	local pending = pendingHots[casterGUID][spellName]
+	wipe(pending)
+	pending.startTime = startTime
+	pending.endTime = endTime
+	pending.duration = endTime - startTime
+	pending.totalTicks = totalTicks
+	pending.tickInterval = pending.duration / totalTicks
+	pending.spellID = spellID
+	pending.isMultiTarget = (select("#", ...) / inc) > 1
+	pending.bitType = CHANNEL_HEALS
+
+	local ticksLeft = ceil((endTime - GetTime()) / pending.tickInterval)
+	loadHealList(pending, amount, 1, endTime, ticksLeft, ...)
+
+	HealComm.callbacks:Fire("HealComm_HealStarted", casterGUID, spellID, pending.bitType, pending.endTime, unpack(tempPlayerList))
+end
+
+-- Hot heal started
+-- When the person is within visible range of us, the aura is available by the time the message reaches the target
+-- as such, we can rely that at least one person is going to have the aura data on them (and that it won't be different, at least for this cast)
+local function findAura(casterGUID, spellID, ...)
+	for i = 1, select("#", ...) do
+		local guid = decompressGUID[select(i, ...)]
+		local unit = guid and guidToUnit[guid]
+		if( unit and UnitIsVisible(unit) ) then
+			local id = 1
+			while true do
+				local name, _, stack, _, duration, endTime, caster, _, _, spell = UnitAura(unit, id, 'HELPFUL')
+				if( not spell ) then break end
+
+				if( spell == spellID and caster and UnitGUID(caster) == casterGUID ) then
+					return (stack and stack > 0 and stack or 1), duration or 0, endTime or 0
+				end
+
+				id = id + 1
+			end
+		end
+	end
+end
+
+local function parseHotHeal(casterGUID, wasUpdated, spellID, tickAmount, totalTicks, tickInterval, ...)
+	local spellName = GetSpellInfo(spellID)
+	-- If the user is on 3.3, then anything without a total ticks attached to it is rejected
+	if( not tickAmount or not spellName or select("#", ...) == 0 ) then return end
+
+	-- Retrieve the hot information
+	local inc = 2
+	local stack, duration, endTime = findAura(casterGUID, spellID, ...)
+
+	if not ( tickAmount == -1 or tickAmount == "-1" ) then
+		inc = 1
+		duration = totalTicks * tickInterval
+		endTime = GetTime() + duration
+	end
+
+	if( not stack or stack == 0 or duration == 0 or endTime == 0 ) then return end
+
+	pendingHots[casterGUID] = pendingHots[casterGUID] or {}
+	pendingHots[casterGUID][spellName] = pendingHots[casterGUID][spellName] or {}
+
+	local pending = pendingHots[casterGUID][spellName]
+	pending.duration = duration
+	pending.endTime = endTime
+	pending.stack = stack
+	pending.totalTicks = totalTicks or duration / tickInterval
+	pending.tickInterval = totalTicks and duration / totalTicks or tickInterval
+	pending.spellID = spellID
+	pending.hasVariableTicks = type(tickAmount) == "string"
+	pending.isMutliTarget = (select("#", ...) / inc) > 1
+	pending.bitType = HOT_HEALS
+
+	-- As you can't rely on a hot being the absolutely only one up, have to apply the total amount now :<
+	local ticksLeft = ceil((endTime - GetTime()) / pending.tickInterval)
+	loadHealList(pending, tickAmount, stack, endTime, ticksLeft, ...)
+
+	if( not wasUpdated ) then
+		HealComm.callbacks:Fire("HealComm_HealStarted", casterGUID, spellID, pending.bitType, endTime, unpack(tempPlayerList))
+	else
+		HealComm.callbacks:Fire("HealComm_HealUpdated", casterGUID, spellID, pending.bitType, endTime, unpack(tempPlayerList))
+	end
+end
+
+local function parseHotBomb(casterGUID, wasUpdated, spellID, amount, ...)
+	local spellName, spellRank = GetSpellInfo(spellID)
+	if( not amount or not spellName or select("#", ...) == 0 ) then return end
+
+	-- If we don't have a pending hot then there is no bomb as far as were concerned
+	local hotPending = pendingHots[casterGUID] and pendingHots[casterGUID][spellName]
+	if( not hotPending or not hotPending.bitType ) then return end
+	hotPending.hasBomb = true
+
+	pendingHeals[casterGUID] = pendingHeals[casterGUID] or {}
+	pendingHeals[casterGUID][spellName] = pendingHeals[casterGUID][spellName] or {}
+
+	local pending = pendingHeals[casterGUID][spellName]
+	pending.endTime = hotPending.endTime
+	pending.spellID = spellID
+	pending.bitType = BOMB_HEALS
+	pending.stack = 1 -- TBC Lifebloom bomb heal does not stack
+
+	loadHealList(pending, amount, pending.stack, pending.endTime, nil, ...)
+
+	if( not wasUpdated ) then
+		HealComm.callbacks:Fire("HealComm_HealStarted", casterGUID, spellID, pending.bitType, pending.endTime, unpack(tempPlayerList))
+	else
+		HealComm.callbacks:Fire("HealComm_HealUpdated", casterGUID, spellID, pending.bitType, pending.endTime, unpack(tempPlayerList))
+	end
+end
+
+-- Heal finished
+local function parseHealEnd(casterGUID, pending, checkField, spellID, interrupted, ...)
+	local spellName = GetSpellInfo(spellID)
+	if( not spellName or not casterGUID ) then return end
+
+	if( not pending ) then
+		if pendingHeals[casterGUID] then
+			pending = pendingHeals[casterGUID][spellName]
+		end
+		if (not pending) and pendingHots[casterGUID] then
+			pending = pendingHots[casterGUID][spellName]
+		end
+	end
+	if( not pending or not pending.bitType ) then return end
+
+	wipe(tempPlayerList)
+
+	if( select("#", ...) == 0 ) then
+		for i=#(pending), 1, -5 do
+			tinsert(tempPlayerList, pending[i - 4])
+			removeRecord(pending, pending[i - 4])
+		end
+	else
+		for i=1, select("#", ...) do
+			local guid = decompressGUID[select(i, ...)]
+
+			if guid then
+				tinsert(tempPlayerList, guid)
+				removeRecord(pending, guid)
+			end
+		end
+	end
+
+	-- Double check and make sure we actually removed at least one person
+	if( #(tempPlayerList) == 0 ) then return end
+
+	-- Heals that also have a bomb associated to them have to end at this point, they will fire there own callback too
+	local bombPending = pending.hasBomb and pendingHeals[casterGUID][spellName]
+	if( bombPending and bombPending.bitType ) then
+		parseHealEnd(casterGUID, bombPending, "name", spellID, interrupted, ...)
+	end
+
+	local bitType = pending.bitType
+	-- Clear data if we're done
+	if( #(pending) == 0 ) then wipe(pending) end
+
+	HealComm.callbacks:Fire("HealComm_HealStopped", casterGUID, spellID, bitType, interrupted, unpack(tempPlayerList))
+end
+
+HealComm.parseHealEnd = parseHealEnd
+
+-- Heal delayed
+local function parseHealDelayed(casterGUID, startTimeRelative, endTimeRelative, spellID)
+	local spellName = GetSpellInfo(spellID)
+	local startTime = startTimeRelative + GetTime()
+	local endTime = endTimeRelative + GetTime()
+	if not casterGUID then return end
+	local pending = (pendingHeals[casterGUID][spellName] or pendingHots[casterGUID][spellName])
+	-- It's possible to get duplicate interrupted due to raid1 = party1, player = raid# etc etc, just block it here
+	if( pending.endTime == endTime and pending.startTime == startTime ) then return end
+
+	-- Casted heal
+	if( pending.bitType == DIRECT_HEALS ) then
+		pending.startTime = startTime
+		pending.endTime = endTime
+	-- Channel heal
+	elseif( pending.bitType == CHANNEL_HEALS ) then
+		pending.startTime = startTime
+		pending.endTime = endTime
+		pending.duration = endTime - startTime
+		pending.tickInterval = pending.duration / pending.totalTicks
+	else
+		return
+	end
+
+	wipe(tempPlayerList)
+	for i=1, #(pending), 5 do
+		pending[i + 3] = endTime
+		tinsert(tempPlayerList, pending[i])
+	end
+
+	HealComm.callbacks:Fire("HealComm_HealDelayed", casterGUID, pending.spellID, pending.bitType, pending.endTime, unpack(tempPlayerList))
+end
+
+-- After checking around 150-200 messages in battlegrounds, server seems to always be passed (if they are from another server)
+-- Channels use tick total because the tick interval varies by haste
+-- Hots use tick interval because the total duration varies but the tick interval stays the same
+function HealComm:CHAT_MSG_ADDON(prefix, message, channel, sender)
+	if( prefix ~= COMM_PREFIX or channel ~= distribution ) then return end
+
+	local commType, extraArg, spellID, arg1, arg2, arg3, arg4 = strsplit(":", message)
+	local casterGUID = UnitGUID(Ambiguate(sender, "none"))
+	spellID = tonumber(spellID)
+
+	if( not commType or not spellID or not casterGUID or casterGUID == playerGUID) then return end
+
+	-- New direct heal - D:<extra>:<spellID>:<amount>:target1,target2...
+	if( commType == "D" and arg1 and arg2 ) then
+		parseDirectHeal(casterGUID, spellID, tonumber(arg1), tonumber(extraArg), strsplit(",", arg2))
+	-- New channel heal - C:<extra>:<spellID>:<amount>:<totalTicks>:target1,target2...
+	elseif( commType == "C" and arg1 and arg3 ) then
+		parseChannelHeal(casterGUID, spellID, tonumber(arg1), tonumber(arg2), strsplit(",", arg3))
+	-- New hot with a "bomb" component - B:<totalTicks>:<spellID>:<bombAmount>:target1,target2:<amount>:<isMulti>:<tickInterval>:target1,target2...
+	elseif( commType == "B" and arg1 and arg6 ) then
+		parseHotHeal(casterGUID, false, spellID, tonumber(arg3), tonumber(extraArg), tonumber(arg5), string.split(",", arg6))
+		parseHotBomb(casterGUID, false, spellID, tonumber(arg1), string.split(",", arg2))
+	-- New hot - H:<totalTicks>:<spellID>:<amount>:<isMulti>:<tickInterval>:target1,target2...
+	elseif( commType == "H" and arg1 and arg4 ) then
+		parseHotHeal(casterGUID, false, spellID, tonumber(arg1), tonumber(extraArg), tonumber(arg3), strsplit(",", arg4))
+	-- New updated heal somehow before ending - U:<totalTicks>:<spellID>:<amount>:<tickInterval>:target1,target2...
+	elseif( commType == "U" and arg1 and arg3 ) then
+		parseHotHeal(casterGUID, true, spellID, tonumber(arg1), tonumber(extraArg), tonumber(arg2), strsplit(",", arg3))
+	-- New updated bomb hot - UB:<totalTicks>:<spellID>:<bombAmount>:target1,target2:<amount>:<tickInterval>:target1,target2...
+	elseif( commtype == "UB" and arg1 and arg5 ) then
+		parseHotHeal(casterGUID, true, spellID, tonumber(arg3), tonumber(extraArg), tonumber(arg4), string.split(",", arg5))
+		parseHotBomb(casterGUID, true, spellID, tonumber(arg1), string.split(",", arg2))
+	-- Heal stopped - S:<extra>:<spellID>:<ended early: 0/1>:target1,target2...
+	elseif( commType == "S" or commType == "HS" ) then
+		local interrupted = arg1 == "1" and true or false
+		local checkType = commType == "HS" and "id" or "name"
+		local pending = commType == "HS" and pendingHots[casterGUID] and pendingHots[casterGUID][GetSpellInfo(spellID)]
+
+		if( arg2 and arg2 ~= "" ) then
+			parseHealEnd(casterGUID, pending, checkType, spellID, interrupted, strsplit(",", arg2))
+		else
+			parseHealEnd(casterGUID, pending, checkType, spellID, interrupted)
+		end
+	elseif commType == "F" then
+		parseHealDelayed(casterGUID, tonumber(arg1), tonumber(arg2), spellID)
+	end
+end
+
+-- Bucketing reduces the number of events triggered for heals such as Tranquility that hit multiple targets
+-- instead of firing 5 events * ticks it will fire 1 (maybe 2 depending on lag) events
+HealComm.bucketHeals = HealComm.bucketHeals or {}
+local bucketHeals = HealComm.bucketHeals
+local BUCKET_FILLED = 0.30
+
+HealComm.bucketFrame = HealComm.bucketFrame or CreateFrame("Frame")
+HealComm.bucketFrame:Hide()
+
+HealComm.bucketFrame:SetScript("OnUpdate", function(self, elapsed)
+	local totalLeft = 0
+	for casterGUID, spells in pairs(bucketHeals) do
+		for _, data in pairs(spells) do
+			if( data.timeout ) then
+				data.timeout = data.timeout - elapsed
+				if( data.timeout <= 0 ) then
+					-- This shouldn't happen, on the offhand chance it does then don't bother sending an event
+					if( #(data) == 0 or not data.spellID or not data.spellName ) then
+						wipe(data)
+					-- We're doing a bucket for a tick heal like Tranquility or Wild Growth
+					elseif( data.type == "tick" ) then
+						local pending = pendingHots[casterGUID] and pendingHots[casterGUID][data.spellName]
+						if( pending and pending.bitType ) then
+							local endTime = select(3, getRecord(pending, data[1]))
+							HealComm.callbacks:Fire("HealComm_HealUpdated", casterGUID, pending.spellID, pending.bitType, endTime, unpack(data))
+						end
+
+						wipe(data)
+					-- We're doing a bucket for a cast thats a multi-target heal like Wild Growth or Prayer of Healing
+					elseif( data.type == "heal" ) then
+						local bitType, amount, totalTicks, tickInterval, _ = CalculateHotHealing(data[1], data.spellID)
+						if( bitType ) then
+							local targets, amt = GetHealTargets(bitType, data[1], max(amount, 0), data.spellID, data)
+							parseHotHeal(playerGUID, false, data.spellID, amt, totalTicks, tickInterval, strsplit(",", targets))
+							sendMessage(format("H:%d:%d:%d::%d:%s", totalTicks, data.spellID, amt, tickInterval, targets))
+						end
+
+						wipe(data)
+					end
+				else
+					totalLeft = totalLeft + 1
+				end
+			end
+		end
+	end
+
+	if( totalLeft <= 0 ) then
+		self:Hide()
+	end
+end)
+
+-- Monitor aura changes as well as new hots being cast
+local eventRegistered = {
+	SPELL_HEAL = true,
+	SPELL_PERIODIC_HEAL = true,
+	SPELL_AURA_REMOVED = true,
+	SPELL_AURA_APPLIED = true,
+	SPELL_AURA_REFRESH = true,
+	SPELL_AURA_APPLIED_DOSE = true,
+	SPELL_AURA_REMOVED_DOSE = true,
+}
+
+function HealComm:COMBAT_LOG_EVENT_UNFILTERED(...)
+	local timestamp, eventType, hideCaster, sourceGUID, sourceName, sourceFlags, sourceRaidFlags, destGUID, destName, destFlags, destRaidFlags = ...
+	if( not eventRegistered[eventType] ) then return end
+
+	local _, spellName = select(12, ...)
+	local destUnit = guidToUnit[destGUID]
+	local spellID = destUnit and select(10, unitHasAura(destUnit, spellName)) or select(7, GetSpellInfo(spellName))
+
+	-- Heal or hot ticked that the library is tracking
+	-- It's more efficient/accurate to have the library keep track of this locally, spamming the comm channel would not be a very good thing especially when a single player can have 4 - 8 hots/channels going on them.
+	if( eventType == "SPELL_HEAL" or eventType == "SPELL_PERIODIC_HEAL" ) then
+		local pending = sourceGUID and pendingHots[sourceGUID] and pendingHots[sourceGUID][spellName]
+		if( pending and pending[destGUID] and pending.bitType and bit.band(pending.bitType, OVERTIME_HEALS) > 0 ) then
+			local amount, stack, _, ticksLeft = getRecord(pending, destGUID)
+			ticksLeft = ticksLeft - 1
+			local endTime = GetTime() + pending.tickInterval * ticksLeft
+
+			updateRecord(pending, destGUID, amount, stack, endTime, ticksLeft)
+
+			if( pending.isMultiTarget ) then
+				bucketHeals[sourceGUID] = bucketHeals[sourceGUID] or {}
+				bucketHeals[sourceGUID][spellID] = bucketHeals[sourceGUID][spellID] or {}
+
+				local spellBucket = bucketHeals[sourceGUID][spellID]
+				if( not spellBucket[destGUID] ) then
+					spellBucket.timeout = BUCKET_FILLED
+					spellBucket.type = "tick"
+					spellBucket.spellName = spellName
+					spellBucket.spellID = spellID
+					spellBucket[destGUID] = true
+					tinsert(spellBucket, destGUID)
+
+					self.bucketFrame:Show()
+				end
+			else
+				HealComm.callbacks:Fire("HealComm_HealUpdated", sourceGUID, spellID, pending.bitType, endTime, destGUID)
+			end
+		end
+
+	-- New hot was applied
+	elseif( ( eventType == "SPELL_AURA_APPLIED" or eventType == "SPELL_AURA_REFRESH" or eventType == "SPELL_AURA_APPLIED_DOSE" ) and bit.band(sourceFlags, COMBATLOG_OBJECT_AFFILIATION_MINE) == COMBATLOG_OBJECT_AFFILIATION_MINE ) then
+		if( hotData[spellName] ) then
+			-- Single target so we can just send it off now thankfully
+			local bitType, amount, totalTicks, tickInterval, bombAmount = CalculateHotHealing(destGUID, spellID)
+			if( bitType ) then
+				local targets, amt = GetHealTargets(type, destGUID, max(amount, 0), spellID)
+				if targets then
+					parseHotHeal(sourceGUID, false, spellID, amt, totalTicks, tickInterval, strsplit(",", targets))
+
+					-- Hot with a bomb!
+					if( bombAmount ) then
+						local bombTargets, bombAmount = GetHealTargets(BOMB_HEALS, destGUID, math.max(bombAmount, 0), spellName)
+						parseHotBomb(sourceGUID, false, spellID, bombAmount, string.split(",", bombTargets))
+						sendMessage(string.format("B:%d:%d:%d:%s:%d::%d:%s", totalTicks, spellID, bombAmount, bombTargets, amount, tickInterval, targets))
+					else
+						sendMessage(string.format("H:%d:%d:%d::%d:%s", totalTicks, spellID, amount, tickInterval, targets))
+					end
+				end
+			end
+		elseif spellData[spellName] and spellData[spellName]._isChanneled then
+			local bitType, amount, totalTicks, tickInterval = CalculateHealing(destGUID, spellID, destUnit)
+			if bitType == CHANNEL_HEALS then
+				local targets, amt = compressGUID[destGUID], max(amount, 0)
+				if targets then
+					local endTime = select(5, ChannelInfo())
+					if endTime then
+						endTime = endTime / 1000
+					else
+						endTime = GetTime() + totalTicks * tickInterval
+					end
+					local ticksLeft = min(floor((endTime - GetTime() + 0.001) / tickInterval), totalTicks)
+					if ticksLeft > 0 then
+						parseChannelHeal(sourceGUID, spellID, amt, ticksLeft, strsplit(",", targets))
+						sendMessage(format("C::%d:%d:%s:%s", spellID, amt, ticksLeft, targets))
+					end
+				end
+			end
+		end
+	-- Single stack of a hot was removed, this only applies when going from 2 -> 1, when it goes from 1 -> 0 it fires SPELL_AURA_REMOVED
+	elseif( eventType == "SPELL_AURA_REMOVED_DOSE" and bit.band(sourceFlags, COMBATLOG_OBJECT_AFFILIATION_MINE) == COMBATLOG_OBJECT_AFFILIATION_MINE ) then
+		local pending = sourceGUID and pendingHeals[sourceGUID] and pendingHeals[sourceGUID][spellID]
+		if( pending and pending.bitType ) then
+			local amount = getRecord(pending, destGUID)
+			if( amount ) then
+				parseHotHeal(sourceGUID, true, spellID, amount, pending.totalTicks, pending.tickInterval, compressGUID[destGUID])
+
+				-- Plant the bomb
+				local bombPending = pending.hasBomb and pendingHeals[sourceGUID][spellName]
+				if( bombPending and bombPending.bitType ) then
+					local bombAmount = getRecord(bombPending, destGUID)
+					if( bombAmount ) then
+						parseHotBomb(sourceGUID, true, spellID, bombAmount, compressGUID[destGUID])
+
+						sendMessage(string.format("UB:%s:%d:%d:%s:%d:%d:%s", pending.totalTicks, spellID, bombAmount, compressGUID[destGUID], amount, pending.tickInterval, compressGUID[destGUID]))
+						return
+					end
+				end
+
+				sendMessage(string.format("U:%s:%d:%d:%d:%s", spellID, amount, pending.totalTicks, pending.tickInterval, compressGUID[destGUID]))
+			end
+		end
+	-- Aura faded
+	elseif( eventType == "SPELL_AURA_REMOVED" and bit.band(sourceFlags, COMBATLOG_OBJECT_AFFILIATION_MINE) == COMBATLOG_OBJECT_AFFILIATION_MINE ) then
+		if compressGUID[destGUID] then
+			-- Hot faded that we cast
+			local pending = pendingHots[playerGUID] and pendingHots[playerGUID][spellName]
+			if hotData[spellName] then
+				parseHealEnd(sourceGUID, pending, "id", spellID, false, compressGUID[destGUID])
+				sendMessage(format("HS::%d::%s", spellID, compressGUID[destGUID]))
+			elseif spellData[spellName] and spellData[spellName]._isChanneled then
+				parseHealEnd(sourceGUID, pending, "id", spellID, false, compressGUID[destGUID])
+				sendMessage(format("S::%d:0:%s", spellID, compressGUID[destGUID]))
+			end
+		end
+	end
+end
+
+-- Spell cast magic
+-- When auto self cast is on, the UNIT_SPELLCAST_SENT event will always come first followed by the funciton calls
+-- Otherwise either SENT comes first then function calls, or some function calls then SENT then more function calls
+local castTarget, mouseoverGUID, mouseoverName, hadTargetingCursor, lastSentID, lastTargetGUID, lastTargetName
+local lastFriendlyGUID, lastFriendlyName, lastGUID, lastName, lastIsFriend
+local castGUIDs, guidPriorities = {}, {}
+
+-- Deals with the fact that functions are called differently
+-- Why a table when you can only cast one spell at a time you ask? When you factor in lag and mash clicking it's possible to:
+-- cast A, interrupt it, cast B and have A fire SUCEEDED before B does, the tables keeps it from bugging out
+local function setCastData(priority, name, guid)
+	if( not guid or not lastSentID ) then return end
+	if( guidPriorities[lastSentID] and guidPriorities[lastSentID] >= priority ) then return end
+
+	-- This is meant as a way of locking a cast in because which function has accurate data can be called into question at times, one of them always does though
+	-- this means that as soon as it finds a name match it locks the GUID in until another SENT is fired. Technically it's possible to get a bad GUID but it first requires
+	-- the functions to return different data and it requires the messed up call to be for another name conflict.
+	if( castTarget and castTarget == name ) then priority = 99 end
+
+	castGUIDs[lastSentID] = guid
+	guidPriorities[lastSentID] = priority
+end
+
+-- When the game tries to figure out the UnitID from the name it will prioritize players over non-players
+-- if there are conflicts in names it will pull the one with the least amount of current health
+function HealComm:UNIT_SPELLCAST_SENT(unit, targetName, castGUID, spellID)
+	local spellName = GetSpellInfo(spellID)
+	if(unit ~= "player") then return end
+
+	if hotData[spellName] or spellData[spellName] then
+		targetName = targetName or UnitName("player")
+
+		castTarget = gsub(targetName, "(.-)%-(.*)$", "%1")
+		lastSentID = spellID
+
+		-- Self cast is off which means it's possible to have a spell waiting for a target.
+		-- It's possible that it's the mouseover unit, but if a Target, TargetLast or AssistUnit call comes right after it means it's casting on that instead instead.
+		if( hadTargetingCursor ) then
+			hadTargetingCursor = nil
+			self.resetFrame:Show()
+
+			guidPriorities[lastSentID] = nil
+			setCastData(5, mouseoverName, mouseoverGUID)
+		else
+			-- If the player is ungrouped and healing, you can't take advantage of the name -> "unit" map, look in the UnitIDs that would most likely contain the information that's needed.
+			local guid = UnitGUID(targetName)
+			if( not guid ) then
+				guid = UnitName("target") == castTarget and UnitGUID("target") or UnitName("focus") == castTarget and UnitGUID("focus") or UnitName("mouseover") == castTarget and UnitGUID("mouseover") or UnitName("targettarget") == castTarget and UnitGUID("target") or UnitName("focustarget") == castTarget and UnitGUID("focustarget")
+			end
+
+			guidPriorities[lastSentID] = nil
+			setCastData(0, nil, guid)
+		end
+	end
+end
+
+function HealComm:UNIT_SPELLCAST_START(unit, cast, spellID)
+	if( unit ~= "player") then return end
+
+	local spellName = GetSpellInfo(spellID)
+
+	if (not spellData[spellName] or UnitIsCharmed("player") or not UnitPlayerControlled("player") ) then return end
+
+	local castGUID = castGUIDs[spellID]
+	local castUnit = guidToUnit[castGUID]
+	if isTBC and not castUnit and spellID == 32546 and castGUID == UnitGUID("target") then
+		castGUID = UnitGUID("player")
+		castUnit = "player"
+	end
+	if( not castGUID or not castUnit ) then
+		return
+	end
+
+	-- Figure out who we are healing and for how much
+	local bitType, amount, ticks, tickInterval = CalculateHealing(castGUID, spellID, castUnit)
+	if not amount then return end
+
+	local targets, amt = GetHealTargets(bitType, castGUID, max(amount, 0), spellID)
+	if not targets then return end -- only here until I compress/decompress npcs
+
+	if( bitType == DIRECT_HEALS ) then
+		local startTime, endTime = select(4, CastingInfo())
+		parseDirectHeal(playerGUID, spellID, amt, (endTime - startTime) / 1000, strsplit(",", targets))
+		sendMessage(format("D:%.3f:%d:%d:%s", (endTime - startTime) / 1000, spellID or 0, amt or "", targets))
+	elseif( bitType == CHANNEL_HEALS ) then
+		spellData[spellName]._isChanneled = true
+	end
+end
+
+HealComm.UNIT_SPELLCAST_CHANNEL_START = HealComm.UNIT_SPELLCAST_START
+
+local spellCastSucceeded = {}
+
+function HealComm:UNIT_SPELLCAST_SUCCEEDED(unit, cast, spellID)
+	if( unit ~= "player") then return end
+	local spellName = GetSpellInfo(spellID)
+
+	if spellID == 20216 then
+		hasDivineFavor = true
+	end
+
+	if spellData[spellName] and not spellData[spellName]._isChanneled then
+		hasDivineFavor = nil
+		parseHealEnd(playerGUID, nil, "name", spellID, false)
+		sendMessage(format("S::%d:0", spellID or 0))
+		spellCastSucceeded[spellID] = true
+	elseif spellID == 20473 or spellID == 20929 or spellID == 20930 then -- Holy Shock
+		hasDivineFavor = nil
+	end
+end
+
+function HealComm:UNIT_SPELLCAST_STOP(unit, castGUID, spellID)
+	local spellName = GetSpellInfo(spellID)
+	if( unit ~= "player" or not spellData[spellName] or spellData[spellName]._isChanneled ) then return end
+
+	if not spellCastSucceeded[spellID] then
+		parseHealEnd(playerGUID, nil, "name", spellID, true)
+		sendMessage(format("S::%d:1", spellID or 0))
+	end
+
+	spellCastSucceeded[spellID] = nil
+end
+
+-- Cast didn't go through, recheck any charge data if necessary
+function HealComm:UNIT_SPELLCAST_INTERRUPTED(unit, castGUID, spellID)
+	local spellName = GetSpellInfo(spellID)
+	if( unit ~= "player" or not spellData[spellName] ) then return end
+
+	local guid = castGUIDs[spellID]
+	if( guid ) then
+		ResetChargeData(guid, spellID)
+	end
+end
+
+function HealComm:UNIT_SPELLCAST_DELAYED(unit, castGUID, spellID)
+	local spellName = GetSpellInfo(spellID)
+	local casterGUID = UnitGUID(unit)
+	if( unit ~= "player" or not pendingHeals[casterGUID] or not pendingHeals[casterGUID][spellName] ) then return end
+
+	-- Direct heal delayed
+	if( pendingHeals[casterGUID][spellName].bitType == DIRECT_HEALS ) then
+		local startTime, endTime = select(4, CastingInfo())
+		if( startTime and endTime ) then
+			local startTimeRelative = startTime / 1000 - GetTime()
+			local endTimeRelative = endTime / 1000 - GetTime()
+			parseHealDelayed(casterGUID, startTimeRelative, endTimeRelative, spellID)
+			sendMessage(format("F::%d:%.3f:%.3f", spellID, startTimeRelative, endTimeRelative))
+		end
+	-- Channel heal delayed
+	elseif( pendingHeals[casterGUID][spellName].bitType == CHANNEL_HEALS ) then
+		local startTime, endTime = select(4, ChannelInfo())
+		if( startTime and endTime ) then
+			local startTimeRelative = startTime / 1000 - GetTime()
+			local endTimeRelative = endTime / 1000 - GetTime()
+			parseHealDelayed(casterGUID, startTimeRelative, endTimeRelative, spellID)
+			sendMessage(format("F::%d:%.3f:%.3f", spellID, startTimeRelative, endTimeRelative))
+		end
+	end
+end
+
+HealComm.UNIT_SPELLCAST_CHANNEL_UPDATE = HealComm.UNIT_SPELLCAST_DELAYED
+
+-- Need to keep track of mouseover as it can change in the split second after/before casts
+function HealComm:UPDATE_MOUSEOVER_UNIT()
+	mouseoverGUID = UnitCanAssist("player", "mouseover") and UnitGUID("mouseover")
+	mouseoverName = UnitCanAssist("player", "mouseover") and UnitName("mouseover")
+end
+
+-- Keep track of our last target/friendly target for the sake of /targetlast and /targetlastfriend
+function HealComm:PLAYER_TARGET_CHANGED()
+	if( lastGUID and lastName ) then
+		if( lastIsFriend ) then
+			lastFriendlyGUID, lastFriendlyName = lastGUID, lastName
+		end
+
+		lastTargetGUID, lastTargetName = lastGUID, lastName
+	end
+
+	-- Despite the fact that it's called target last friend, UnitIsFriend won't actually work
+	lastGUID = UnitGUID("target")
+	lastName = UnitName("target")
+	lastIsFriend = UnitCanAssist("player", "target")
+end
+
+-- Unit was targeted through a function
+function HealComm:Target(unit)
+	if( self.resetFrame:IsShown() and UnitCanAssist("player", unit) ) then
+		setCastData(6, UnitName(unit), UnitGUID(unit))
+	end
+
+	self.resetFrame:Hide()
+	hadTargetingCursor = nil
+end
+
+-- This is only needed when auto self cast is off, in which case this is called right after UNIT_SPELLCAST_SENT
+-- because the player got a waiting-for-cast icon up and they pressed a key binding to target someone
+HealComm.TargetUnit = HealComm.Target
+
+-- Works the same as the above except it's called when you have a cursor icon and you click on a secure frame with a target attribute set
+HealComm.SpellTargetUnit = HealComm.Target
+
+-- Used in /assist macros
+function HealComm:AssistUnit(unit)
+	if( self.resetFrame:IsShown() and UnitCanAssist("player", unit .. "target") ) then
+		setCastData(6, UnitName(unit .. "target"), UnitGUID(unit .. "target"))
+	end
+
+	self.resetFrame:Hide()
+	hadTargetingCursor = nil
+end
+
+-- Target last was used, the only reason this is called with reset frame being shown is we're casting on a valid unit
+-- don't have to worry about the GUID no longer being invalid etc
+function HealComm:TargetLast(guid, name)
+	if( name and guid and self.resetFrame:IsShown() ) then
+		setCastData(6, name, guid)
+	end
+
+	self.resetFrame:Hide()
+	hadTargetingCursor = nil
+end
+
+function HealComm:TargetLastFriend()
+	self:TargetLast(lastFriendlyGUID, lastFriendlyName)
+end
+
+function HealComm:TargetLastTarget()
+	self:TargetLast(lastTargetGUID, lastTargetName)
+end
+
+-- Spell was cast somehow
+function HealComm:CastSpell(arg, unit)
+	-- If the spell is waiting for a target and it's a spell action button then we know that the GUID has to be mouseover or a key binding cast.
+	if( unit and UnitCanAssist("player", unit)  ) then
+		setCastData(4, UnitName(unit), UnitGUID(unit))
+	-- No unit, or it's a unit we can't assist
+	elseif( not SpellIsTargeting() ) then
+		if( UnitCanAssist("player", "target") ) then
+			setCastData(4, UnitName("target"), UnitGUID("target"))
+		else
+			setCastData(4, playerName, playerGUID)
+		end
+
+		hadTargetingCursor = nil
+	else
+		hadTargetingCursor = true
+	end
+end
+
+HealComm.CastSpellByName = HealComm.CastSpell
+HealComm.CastSpellByID = HealComm.CastSpell
+HealComm.UseAction = HealComm.CastSpell
+
+-- Make sure we don't have invalid units in this
+local function sanityCheckMapping()
+	for guid, unit in pairs(guidToUnit) do
+		-- Unit no longer exists, remove all healing for them
+		if guid ~= UnitGUID(unit) then
+			-- Check for (and remove) any active heals
+			for _, tbl in pairs({ pendingHeals, pendingHots }) do
+				if tbl[guid] then
+					for _, pending in pairs(tbl[guid]) do
+						if( pending.bitType ) then
+							parseHealEnd(guid, pending, nil, pending.spellID, true)
+						end
+					end
+				end
+			end
+
+			pendingHeals[guid] = nil
+			pendingHots[guid] = nil
+
+			-- Remove any heals that are on them
+			removeAllRecords(guid)
+
+			guidToUnit[guid] = nil
+			guidToGroup[guid] = nil
+		end
+	end
+end
+
+-- 5s poll that tries to solve the problem of X running out of range while a HoT is ticking
+-- this is not really perfect far from it in fact. If I can find a better solution I will switch to that.
+if( not HealComm.hotMonitor ) then
+	HealComm.hotMonitor = CreateFrame("Frame")
+	HealComm.hotMonitor:Hide()
+	HealComm.hotMonitor.timeElapsed = 0
+	HealComm.hotMonitor:SetScript("OnUpdate", function(self, elapsed)
+		self.timeElapsed = self.timeElapsed + elapsed
+		if( self.timeElapsed < 5 ) then return end
+		self.timeElapsed = self.timeElapsed - 5
+
+		-- For the time being, it will only remove them if they don't exist and it found a valid unit
+		-- units that leave the raid are automatically removed
+		local found
+		for guid in pairs(activeHots) do
+			if( guidToUnit[guid] and not UnitIsVisible(guidToUnit[guid]) ) then
+				removeAllRecords(guid)
+			else
+				found = true
+			end
+		end
+
+		if( not found ) then
+			self:Hide()
+		end
+	end)
+end
+
+-- After the player leaves a group, tables are wiped out or released for GC
+local function clearGUIDData()
+	clearPendingHeals()
+
+	wipe(compressGUID)
+	wipe(decompressGUID)
+	wipe(activePets)
+
+	playerGUID = playerGUID or UnitGUID("player")
+	HealComm.guidToUnit = {[playerGUID] = "player"}
+	guidToUnit = HealComm.guidToUnit
+
+	HealComm.guidToGroup = {}
+	guidToGroup = HealComm.guidToGroup
+
+	HealComm.activeHots = {}
+	activeHots = HealComm.activeHots
+
+	HealComm.pendingHeals = {}
+	pendingHeals = HealComm.pendingHeals
+
+	HealComm.pendingHots = {}
+	pendingHots = HealComm.pendingHots
+
+	HealComm.bucketHeals = {}
+	bucketHeals = HealComm.bucketHeals
+end
+
+-- Keeps track of pet GUIDs, as pets are considered vehicles this will also map vehicle GUIDs to unit
+function HealComm:UNIT_PET(unit)
+	local guid = UnitGUID(unit)
+	unit = guidToUnit[guid]
+
+	if not unit then return end
+
+	local pet = self.unitToPet[unit]
+	local petGUID = pet and UnitGUID(pet)
+
+	-- We have an active pet guid from this user and it's different, kill it
+	local activeGUID = activePets[unit]
+	if activeGUID and activeGUID ~= petGUID then
+		removeAllRecords(activeGUID)
+
+		guidToUnit[activeGUID] = nil
+		guidToGroup[activeGUID] = nil
+		activePets[unit] = nil
+	end
+
+	-- Add the new record
+	if petGUID then
+		guidToUnit[petGUID] = pet
+		guidToGroup[petGUID] = guidToGroup[guid]
+		activePets[unit] = petGUID
+	end
+end
+
+-- Keep track of raid GUIDs
+function HealComm:GROUP_ROSTER_UPDATE()
+	updateDistributionChannel()
+
+	wipe(activePets)
+
+	local function update(unit)
+		local guid = UnitGUID(unit)
+
+		if guid then
+			local raidID = UnitInRaid(unit)
+			local group = raidID and select(3, GetRaidRosterInfo(raidID)) or 1
+
+			guidToUnit[guid] = unit
+			guidToGroup[guid] = group
+
+			local pet = self.unitToPet[unit]
+			local petGUID = pet and UnitGUID(pet)
+
+			activePets[unit] = petGUID
+
+			if petGUID then
+				guidToUnit[petGUID] = pet
+				guidToGroup[petGUID] = group
+			end
+		end
+	end
+
+	if GetNumGroupMembers() == 0 then
+		clearGUIDData()
+		update("player")
+	elseif not IsInRaid() then
+		update("player")
+
+		for i = 1, MAX_PARTY_MEMBERS do
+			update(format("party%d", i))
+		end
+	else
+		for i = 1, MAX_RAID_MEMBERS do
+			update(format("raid%d", i))
+		end
+	end
+
+	sanityCheckMapping()
+end
+
+-- PLAYER_ALIVE = got talent data
+function HealComm:PLAYER_ALIVE()
+	self:CHARACTER_POINTS_CHANGED()
+	self.eventFrame:UnregisterEvent("PLAYER_ALIVE")
+end
+
+-- Initialize the library
+function HealComm:OnInitialize()
+	-- If another instance already loaded then the tables should be wiped to prevent old data from persisting
+	-- in case of a spell being removed later on, only can happen if a newer LoD version is loaded
+	wipe(spellData)
+	wipe(hotData)
+	wipe(itemSetsData)
+	wipe(talentData)
+
+	-- Load all of the classes formulas and such
+	if LoadClassData then
+		LoadClassData()
+	end
+
+	do
+		local FirstAid = GetSpellInfo(746)
+
+		spellData[FirstAid] = {
+			ticks = {6, 6, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8},
+			interval = 1,
+			averages = {66, 114, 161, 301, 400, 640, 800, 1104, 1360, 2000, 2800, 3400}
+		}
+
+		local _GetHealTargets = GetHealTargets
+
+		GetHealTargets = function(bitType, guid, healAmount, spellID, data)
+			local spellName = GetSpellInfo(spellID)
+
+			if spellName == FirstAid then
+				return compressGUID[guid], healAmount
+			end
+
+			if _GetHealTargets then
+				return _GetHealTargets(bitType, guid, healAmount, spellID, data)
+			end
+		end
+
+		local _CalculateHealing = CalculateHealing
+
+		CalculateHealing = function(guid, spellID, unit)
+			local spellName, spellRank = GetSpellInfo(spellID), SpellIDToRank[spellID]
+
+			if spellName == FirstAid then
+				local healAmount =  spellData[spellName].averages[spellRank]
+				if not healAmount then return end
+
+				local ticks = spellData[spellName].ticks[spellRank]
+
+				return CHANNEL_HEALS, ceil(healAmount / ticks), ticks, spellData[spellName].interval
+			end
+
+			if _CalculateHealing then
+				return _CalculateHealing(guid, spellID, unit)
+			end
+		end
+	end
+
+	self:PLAYER_EQUIPMENT_CHANGED()
+
+	-- When first logging in talent data isn't available until at least PLAYER_ALIVE, so if we don't have data
+	-- will wait for that event otherwise will just cache it right now
+	if( GetNumTalentTabs() == 0 ) then
+		self.eventFrame:RegisterEvent("PLAYER_ALIVE")
+	else
+		self:CHARACTER_POINTS_CHANGED()
+	end
+
+	if( ResetChargeData ) then
+		HealComm.eventFrame:RegisterEvent("UNIT_SPELLCAST_INTERRUPTED")
+	end
+
+	-- Finally, register it all
+	self.eventFrame:RegisterEvent("CHAT_MSG_ADDON")
+	self.eventFrame:RegisterEvent("UNIT_SPELLCAST_SENT")
+	self.eventFrame:RegisterEvent("UNIT_SPELLCAST_START")
+	self.eventFrame:RegisterEvent("UNIT_SPELLCAST_STOP")
+	self.eventFrame:RegisterEvent("UNIT_SPELLCAST_CHANNEL_START")
+	self.eventFrame:RegisterEvent("UNIT_SPELLCAST_DELAYED")
+	self.eventFrame:RegisterEvent("UNIT_SPELLCAST_CHANNEL_UPDATE")
+	self.eventFrame:RegisterEvent("UNIT_SPELLCAST_SUCCEEDED")
+	self.eventFrame:RegisterEvent("COMBAT_LOG_EVENT_UNFILTERED")
+	self.eventFrame:RegisterEvent("PLAYER_EQUIPMENT_CHANGED")
+	self.eventFrame:RegisterEvent("PLAYER_TARGET_CHANGED")
+	self.eventFrame:RegisterEvent("UPDATE_MOUSEOVER_UNIT")
+	self.eventFrame:RegisterEvent("PLAYER_LEVEL_UP")
+	self.eventFrame:RegisterEvent("CHARACTER_POINTS_CHANGED")
+	self.eventFrame:RegisterEvent("UNIT_AURA")
+
+	if( self.initialized ) then return end
+	self.initialized = true
+
+	self.resetFrame = CreateFrame("Frame")
+	self.resetFrame:Hide()
+	self.resetFrame:SetScript("OnUpdate", function(self) self:Hide() end)
+
+	-- You can't unhook secure hooks after they are done, so will hook once and the HealComm table will update with the latest functions
+	-- automagically. If a new function is ever used it'll need a specific variable to indicate those set of hooks.
+	-- By default most of these are mapped to a more generic function, but I call separate ones so I don't have to rehook
+	-- if it turns out I need to know something specific
+	hooksecurefunc("TargetUnit", function(...) HealComm:TargetUnit(...) end)
+	hooksecurefunc("SpellTargetUnit", function(...) HealComm:SpellTargetUnit(...) end)
+	hooksecurefunc("AssistUnit", function(...) HealComm:AssistUnit(...) end)
+	hooksecurefunc("UseAction", function(...) HealComm:UseAction(...) end)
+	hooksecurefunc("TargetLastFriend", function(...) HealComm:TargetLastFriend(...) end)
+	hooksecurefunc("TargetLastTarget", function(...) HealComm:TargetLastTarget(...) end)
+	hooksecurefunc("CastSpellByName", function(...) HealComm:CastSpellByName(...) end)
+	hooksecurefunc("CastSpellByID", function(...) HealComm:CastSpellByID(...) end)
+end
+
+-- General event handler
+local function OnEvent(self, event, ...)
+	if event == 'COMBAT_LOG_EVENT_UNFILTERED' then
+		HealComm[event](HealComm, CombatLogGetCurrentEventInfo())
+	else
+		HealComm[event](HealComm, ...)
+	end
+end
+
+-- Event handler
+HealComm.eventFrame = HealComm.frame or HealComm.eventFrame or CreateFrame("Frame")
+HealComm.eventFrame:UnregisterAllEvents()
+HealComm.eventFrame:RegisterEvent("UNIT_PET")
+HealComm.eventFrame:SetScript("OnEvent", OnEvent)
+HealComm.frame = nil
+
+-- At PLAYER_LEAVING_WORLD (Actually more like MIRROR_TIMER_STOP but anyway) UnitGUID("player") returns nil, delay registering
+-- events and set a playerGUID/playerName combo for all players on PLAYER_LOGIN not just the healers.
+function HealComm:PLAYER_LOGIN()
+	playerGUID = UnitGUID("player")
+	playerName = UnitName("player")
+	playerLevel = UnitLevel("player")
+
+	-- Oddly enough player GUID is not available on file load, so keep the map of player GUID to themselves too
+	guidToUnit[playerGUID] = "player"
+
+	self:OnInitialize()
+
+	self.eventFrame:UnregisterEvent("PLAYER_LOGIN")
+	self.eventFrame:RegisterEvent("ZONE_CHANGED_NEW_AREA")
+	self.eventFrame:RegisterEvent("PLAYER_ENTERING_WORLD")
+	self.eventFrame:RegisterEvent("GROUP_ROSTER_UPDATE")
+
+	self:ZONE_CHANGED_NEW_AREA()
+	self:GROUP_ROSTER_UPDATE()
+end
\ No newline at end of file
diff --git a/oUF/libs/LibHealComm-4.0/LibHealComm-4.0.xml b/oUF/libs/LibHealComm-4.0/LibHealComm-4.0.xml
new file mode 100644
index 0000000..730fcb3
--- /dev/null
+++ b/oUF/libs/LibHealComm-4.0/LibHealComm-4.0.xml
@@ -0,0 +1,5 @@
+<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/
+..\FrameXML\UI.xsd">
+	<Script file="ChatThrottleLib.lua"/>
+	<Script file="LibHealComm-4.0.lua"/>
+</Ui>
\ No newline at end of file
diff --git a/oUF/libs/LibStub/LibStub.lua b/oUF/libs/LibStub/LibStub.lua
new file mode 100644
index 0000000..cf5c842
--- /dev/null
+++ b/oUF/libs/LibStub/LibStub.lua
@@ -0,0 +1,51 @@
+-- $Id: LibStub.lua 103 2014-10-16 03:02:50Z mikk $
+-- LibStub is a simple versioning stub meant for use in Libraries.  http://www.wowace.com/addons/libstub/ for more info
+-- LibStub is hereby placed in the Public Domain
+-- Credits: Kaelten, Cladhaire, ckknight, Mikk, Ammo, Nevcairiel, joshborke
+local LIBSTUB_MAJOR, LIBSTUB_MINOR = "LibStub", 2  -- NEVER MAKE THIS AN SVN REVISION! IT NEEDS TO BE USABLE IN ALL REPOS!
+local LibStub = _G[LIBSTUB_MAJOR]
+
+-- Check to see is this version of the stub is obsolete
+if not LibStub or LibStub.minor < LIBSTUB_MINOR then
+	LibStub = LibStub or {libs = {}, minors = {} }
+	_G[LIBSTUB_MAJOR] = LibStub
+	LibStub.minor = LIBSTUB_MINOR
+
+	-- LibStub:NewLibrary(major, minor)
+	-- major (string) - the major version of the library
+	-- minor (string or number ) - the minor version of the library
+	--
+	-- returns nil if a newer or same version of the lib is already present
+	-- returns empty library object or old library object if upgrade is needed
+	function LibStub:NewLibrary(major, minor)
+		assert(type(major) == "string", "Bad argument #2 to `NewLibrary' (string expected)")
+		minor = assert(tonumber(strmatch(minor, "%d+")), "Minor version must either be a number or contain a number.")
+
+		local oldminor = self.minors[major]
+		if oldminor and oldminor >= minor then return nil end
+		self.minors[major], self.libs[major] = minor, self.libs[major] or {}
+		return self.libs[major], oldminor
+	end
+
+	-- LibStub:GetLibrary(major, [silent])
+	-- major (string) - the major version of the library
+	-- silent (boolean) - if true, library is optional, silently return nil if its not found
+	--
+	-- throws an error if the library can not be found (except silent is set)
+	-- returns the library object if found
+	function LibStub:GetLibrary(major, silent)
+		if not self.libs[major] and not silent then
+			error(("Cannot find a library instance of %q."):format(tostring(major)), 2)
+		end
+		return self.libs[major], self.minors[major]
+	end
+
+	-- LibStub:IterateLibraries()
+	--
+	-- Returns an iterator for the currently registered libraries
+	function LibStub:IterateLibraries()
+		return pairs(self.libs)
+	end
+
+	setmetatable(LibStub, { __call = LibStub.GetLibrary })
+end
diff --git a/oUF/libs/LibStub/LibStub.toc b/oUF/libs/LibStub/LibStub.toc
new file mode 100644
index 0000000..036915c
--- /dev/null
+++ b/oUF/libs/LibStub/LibStub.toc
@@ -0,0 +1,9 @@
+## Interface: 80000
+## Title: Lib: LibStub
+## Notes: Universal Library Stub
+## Credits: Kaelten, Cladhaire, ckknight, Mikk, Ammo, Nevcairiel
+## X-Website: http://www.wowace.com/addons/libstub/
+## X-Category: Library
+## X-License: Public Domain
+
+LibStub.lua
diff --git a/oUF/libs/LibStub/tests/test.lua b/oUF/libs/LibStub/tests/test.lua
new file mode 100644
index 0000000..338bcf4
--- /dev/null
+++ b/oUF/libs/LibStub/tests/test.lua
@@ -0,0 +1,41 @@
+debugstack = debug.traceback
+strmatch = string.match
+
+loadfile("../LibStub.lua")()
+
+local lib, oldMinor = LibStub:NewLibrary("Pants", 1) -- make a new thingy
+assert(lib) -- should return the library table
+assert(not oldMinor) -- should not return the old minor, since it didn't exist
+
+-- the following is to create data and then be able to check if the same data exists after the fact
+function lib:MyMethod()
+end
+local MyMethod = lib.MyMethod
+lib.MyTable = {}
+local MyTable = lib.MyTable
+
+local newLib, newOldMinor = LibStub:NewLibrary("Pants", 1) -- try to register a library with the same version, should silently fail
+assert(not newLib) -- should not return since out of date
+
+local newLib, newOldMinor = LibStub:NewLibrary("Pants", 0) -- try to register a library with a previous, should silently fail
+assert(not newLib) -- should not return since out of date
+
+local newLib, newOldMinor = LibStub:NewLibrary("Pants", 2) -- register a new version
+assert(newLib) -- library table
+assert(rawequal(newLib, lib)) -- should be the same reference as the previous
+assert(newOldMinor == 1) -- should return the minor version of the previous version
+
+assert(rawequal(lib.MyMethod, MyMethod)) -- verify that values were saved
+assert(rawequal(lib.MyTable, MyTable)) -- verify that values were saved
+
+local newLib, newOldMinor = LibStub:NewLibrary("Pants", "Blah 3 Blah") -- register a new version with a string minor version (instead of a number)
+assert(newLib) -- library table
+assert(newOldMinor == 2) -- previous version was 2
+
+local newLib, newOldMinor = LibStub:NewLibrary("Pants", "Blah 4 and please ignore 15 Blah") -- register a new version with a string minor version (instead of a number)
+assert(newLib)
+assert(newOldMinor == 3) -- previous version was 3 (even though it gave a string)
+
+local newLib, newOldMinor = LibStub:NewLibrary("Pants", 5) -- register a new library, using a normal number instead of a string
+assert(newLib)
+assert(newOldMinor == 4) -- previous version was 4 (even though it gave a string)
diff --git a/oUF/libs/LibStub/tests/test2.lua b/oUF/libs/LibStub/tests/test2.lua
new file mode 100644
index 0000000..eae7172
--- /dev/null
+++ b/oUF/libs/LibStub/tests/test2.lua
@@ -0,0 +1,27 @@
+debugstack = debug.traceback
+strmatch = string.match
+
+loadfile("../LibStub.lua")()
+
+for major, library in LibStub:IterateLibraries() do
+	-- check that MyLib doesn't exist yet, by iterating through all the libraries
+	assert(major ~= "MyLib")
+end
+
+assert(not LibStub:GetLibrary("MyLib", true)) -- check that MyLib doesn't exist yet by direct checking
+assert(not pcall(LibStub.GetLibrary, LibStub, "MyLib")) -- don't silently fail, thus it should raise an error.
+local lib = LibStub:NewLibrary("MyLib", 1) -- create the lib
+assert(lib) -- check it exists
+assert(rawequal(LibStub:GetLibrary("MyLib"), lib)) -- verify that :GetLibrary("MyLib") properly equals the lib reference
+
+assert(LibStub:NewLibrary("MyLib", 2))	-- create a new version
+
+local count=0
+for major, library in LibStub:IterateLibraries() do
+	-- check that MyLib exists somewhere in the libraries, by iterating through all the libraries
+	if major == "MyLib" then -- we found it!
+		count = count +1
+		assert(rawequal(library, lib)) -- verify that the references are equal
+	end
+end
+assert(count == 1) -- verify that we actually found it, and only once
diff --git a/oUF/libs/LibStub/tests/test3.lua b/oUF/libs/LibStub/tests/test3.lua
new file mode 100644
index 0000000..21bda2b
--- /dev/null
+++ b/oUF/libs/LibStub/tests/test3.lua
@@ -0,0 +1,14 @@
+debugstack = debug.traceback
+strmatch = string.match
+
+loadfile("../LibStub.lua")()
+
+local proxy = newproxy() -- non-string
+
+assert(not pcall(LibStub.NewLibrary, LibStub, proxy, 1)) -- should error, proxy is not a string, it's userdata
+local success, ret = pcall(LibStub.GetLibrary, proxy, true)
+assert(not success or not ret) -- either error because proxy is not a string or because it's not actually registered.
+
+assert(not pcall(LibStub.NewLibrary, LibStub, "Something", "No number in here")) -- should error, minor has no string in it.
+
+assert(not LibStub:GetLibrary("Something", true)) -- shouldn't've created it from the above statement
diff --git a/oUF/libs/LibStub/tests/test4.lua b/oUF/libs/LibStub/tests/test4.lua
new file mode 100644
index 0000000..4735246
--- /dev/null
+++ b/oUF/libs/LibStub/tests/test4.lua
@@ -0,0 +1,41 @@
+debugstack = debug.traceback
+strmatch = string.match
+
+loadfile("../LibStub.lua")()
+
+
+-- Pretend like loaded libstub is old and doesn't have :IterateLibraries
+assert(LibStub.minor)
+LibStub.minor = LibStub.minor - 0.0001
+LibStub.IterateLibraries = nil
+
+loadfile("../LibStub.lua")()
+
+assert(type(LibStub.IterateLibraries)=="function")
+
+
+-- Now pretend that we're the same version -- :IterateLibraries should NOT be re-created
+LibStub.IterateLibraries = 123
+
+loadfile("../LibStub.lua")()
+
+assert(LibStub.IterateLibraries == 123)
+
+
+-- Now pretend that a newer version is loaded -- :IterateLibraries should NOT be re-created
+LibStub.minor = LibStub.minor + 0.0001
+
+loadfile("../LibStub.lua")()
+
+assert(LibStub.IterateLibraries == 123)
+
+
+-- Again with a huge number
+LibStub.minor = LibStub.minor + 1234567890
+
+loadfile("../LibStub.lua")()
+
+assert(LibStub.IterateLibraries == 123)
+
+
+print("OK")
diff --git a/oUF/oUF.toc b/oUF/oUF.toc
index 98bdb7a..1cef314 100644
--- a/oUF/oUF.toc
+++ b/oUF/oUF.toc
@@ -9,6 +9,12 @@
 ## X-Curse-Project-ID: 21187
 ## X-WoWI-ID: 9994

+# make healthprediction works
+libs\LibStub\LibStub.lua
+libs\CallbackHandler-1.0\CallbackHandler-1.0.lua
+libs\LibHealComm-4.0\ChatThrottleLib.lua
+libs\LibHealComm-4.0\LibHealComm-4.0.lua
+
 init.lua
 private.lua
 ouf.lua
@@ -24,6 +30,7 @@ elements\classpower.lua
 elements\combatindicator.lua
 elements\grouproleindicator.lua
 elements\health.lua
+elements\healthprediction.lua
 elements\leaderindicator.lua
 elements\phaseindicator.lua
 elements\portrait.lua
@@ -41,4 +48,4 @@ elements\stagger.lua
 elements\tags.lua
 elements\threatindicator.lua
 elements\totems.lua
-finalize.lua
+finalize.lua
\ No newline at end of file
diff --git a/oUF_Simple/core/functions.lua b/oUF_Simple/core/functions.lua
index fe6e502..ffe5af5 100644
--- a/oUF_Simple/core/functions.lua
+++ b/oUF_Simple/core/functions.lua
@@ -219,6 +219,32 @@ local function CreateAbsorbBar(self)
 end
 L.F.CreateAbsorbBar = CreateAbsorbBar

+--CreateHealCommBar
+local function CreateHealCommBar(self, type)
+  local s = CreateFrame("StatusBar", nil, self.Health)
+  s:SetAllPoints()
+  s:SetOrientation(self.cfg.healthbar.orientation or "HORIZONTAL")
+  s:SetStatusBarTexture(L.C.textures[type] or L.C.textures.absorb)
+  s:SetStatusBarColor(unpack(L.C.colors.healthbar[type] or L.C.colors.healthbar.absorb))
+  s:SetReverseFill(true)
+  s:SetMinMaxValues(0, 1)
+  s:SetValue(0)
+  return s
+end
+
+--CreateHealComm
+local function CreateHealComm(self)
+  --like health the absorb bar cannot be disabled
+  --statusbar
+  return {
+    myBar = CreateHealCommBar(self, 'selfcomm'),
+    otherBar = CreateHealCommBar(self, 'othercomm'),
+    absorbBar = CreateHealCommBar(self, 'absorb'),
+    maxOverflow = 1,
+  }
+end
+L.F.CreateHealComm = CreateHealComm
+
 --CreateClassBar
 local function CreateClassBar(self)
   if not self.cfg.classbar or not self.cfg.classbar.enabled then return end
@@ -543,7 +569,8 @@ L.F.CreateDragFrame = CreateDragFrame

 --ToggleSmooth
 local function ToggleSmooth(self, enabled)
-  self.Health.Smooth = enabled
-  if self.Power then self.Power.Smooth = enabled end
+  self.Health.smoothing = enabled
+  if self.Power then self.Power.smoothing = enabled end
+  if self.HealthPrediction then self.HealthPrediction.smoothing = enabled end
 end
 L.F.ToggleSmooth = ToggleSmooth
\ No newline at end of file
diff --git a/oUF_Simple/core/spawn.lua b/oUF_Simple/core/spawn.lua
index feaf56a..7e736a0 100644
--- a/oUF_Simple/core/spawn.lua
+++ b/oUF_Simple/core/spawn.lua
@@ -10,6 +10,11 @@ local A, L = ...
 local oUF = L.oUF or oUF

 -----------------------------
+-- Load HealComm
+-----------------------------
+LibStub("LibHealComm-4.0"):PLAYER_LOGIN()
+
+-----------------------------
 -- Register Style
 -----------------------------

diff --git a/oUF_Simple/core/style.lua b/oUF_Simple/core/style.lua
index 08a44be..2157ad2 100644
--- a/oUF_Simple/core/style.lua
+++ b/oUF_Simple/core/style.lua
@@ -24,6 +24,7 @@ local function CreateStyle(self)
   L.F.CreateDragFrame(self)
   self.Health = L.F.CreateHealthBar(self)
   -- self.rAbsorbBar = L.F.CreateAbsorbBar(self)
+  self.HealthPrediction = L.F.CreateHealComm(self)
   L.F.CreateNameText(self)
   L.F.CreateHealthText(self)
   self.Power = L.F.CreatePowerBar(self)
diff --git a/oUF_Simple/modules/oUF_Smooth.lua b/oUF_Simple/modules/oUF_Smooth.lua
deleted file mode 100644
index b5dc3d8..0000000
--- a/oUF_Simple/modules/oUF_Smooth.lua
+++ /dev/null
@@ -1,53 +0,0 @@
-local _, ns = ...
-local oUF = ns.oUF or oUF
-assert(oUF, "<name> was unable to locate oUF install.")
-
-local smoothing = {}
-local function Smooth(self, value)
-	local _, max = self:GetMinMaxValues()
-	if value == self:GetValue() or (self._max and self._max ~= max) then
-		smoothing[self] = nil
-		self:SetValue_(value)
-	else
-		smoothing[self] = value
-	end
-	self._max = max
-end
-
-local function SmoothBar(self, bar)
-	bar.SetValue_ = bar.SetValue
-	bar.SetValue = Smooth
-end
-
-local function hook(frame)
-	frame.SmoothBar = SmoothBar
-	if frame.Health and frame.Health.Smooth then
-		frame:SmoothBar(frame.Health)
-	end
-	if frame.Power and frame.Power.Smooth then
-		frame:SmoothBar(frame.Power)
-	end
-end
-
-
-for i, frame in ipairs(oUF.objects) do hook(frame) end
-oUF:RegisterInitCallback(hook)
-
-
-local f, min, max = CreateFrame('Frame'), math.min, math.max
-f:SetScript('OnUpdate', function()
-	local limit = 30/GetFramerate()
-	for bar, value in pairs(smoothing) do
-		local cur = bar:GetValue()
-		local new = cur + min((value-cur)/3, max(value-cur, limit))
-		if new ~= new then
-			-- Mad hax to prevent QNAN.
-			new = value
-		end
-		bar:SetValue_(new)
-		if cur == value or abs(new - value) < 2 then
-			bar:SetValue_(value)
-			smoothing[bar] = nil
-		end
-	end
-end)
diff --git a/oUF_Simple/oUF_Simple.toc b/oUF_Simple/oUF_Simple.toc
index 9692924..9ea8a53 100644
--- a/oUF_Simple/oUF_Simple.toc
+++ b/oUF_Simple/oUF_Simple.toc
@@ -8,7 +8,6 @@
 modules\rClassBar.lua
 modules\oUF_DebuffHighlight.lua
 modules\oUF_Swing.lua
-modules\oUF_Smooth.lua

 core\init.lua
 core\functions.lua
diff --git a/oUF_Simple/templates/raid.lua b/oUF_Simple/templates/raid.lua
index eedc9ca..c321d19 100644
--- a/oUF_Simple/templates/raid.lua
+++ b/oUF_Simple/templates/raid.lua
@@ -14,7 +14,29 @@ local A, L = ...

 if not L.C.raid or not L.C.raid.enabled then return end

+local Hider = CreateFrame("Frame", nil, UIParent)
+Hider:Hide()
+
+local function DisableBlizzard()
+	if CompactRaidFrameManager then
+		-- Disable Blizzard Raid Frames.
+		CompactRaidFrameManager:SetParent(Hider)
+		CompactRaidFrameManager:UnregisterAllEvents()
+		CompactRaidFrameManager:Hide()
+
+		CompactRaidFrameContainer:SetParent(Hider)
+		CompactRaidFrameContainer:UnregisterAllEvents()
+		CompactRaidFrameContainer:Hide()
+
+		-- Hide Raid Interface Options.
+		InterfaceOptionsFrameCategoriesButton10:SetHeight(0.00001)
+		InterfaceOptionsFrameCategoriesButton10:SetAlpha(0)
+	end
+end
+
 local function CreateRaidStyle(self)
+  --disable blizzard
+  DisableBlizzard()
   --config
   self.cfg = L.C.raid
   --settings
diff --git a/oUF_SimpleConfig/global.lua b/oUF_SimpleConfig/global.lua
index 1101578..bcb8a6b 100644
--- a/oUF_SimpleConfig/global.lua
+++ b/oUF_SimpleConfig/global.lua
@@ -65,7 +65,9 @@ L.C.colors.healthbar = {
   threatBG = {1*L.C.colors.bgMultiplier,0,0},
   threatInvers = {0,1,0},
   threatInversBG = {0,1*L.C.colors.bgMultiplier,0},
-  absorb = {0.1,1,1,0.7}
+  absorb = {0.1,1,1,0.7},
+  selfcomm = {0.29,1,0.30,0.7},
+	othercomm = {1,.72,0.30,0.7},
 }
 --fix way to dark mana color
 L.C.colors.power = {