Quantcast
-------------------------------------------------------------------------------
-- Elementarist 2.0.2
--
-- Shows the advised spell for an elemental shaman for optimal DPS output.
-------------------------------------------------------------------------------

Elementarist = {Locals = {}}

local L = Elementarist.Locals

Elementarist.versionNumber = '2.0.2'
Elementarist.playerName = UnitName("player")
Elementarist.playerGUID = UnitGUID("player")
Elementarist.targetGUID = nil
Elementarist.spellHaste = GetCombatRatingBonus(20)
Elementarist.timeSinceLastUpdate = 0
Elementarist.debuffTrackerUpdate = 0
Elementarist.shieldTrackerUpdate = 0
Elementarist.spellPower = GetSpellBonusDamage(4);	-- nature spell bonus
Elementarist.lastBaseGCD = 1.5
Elementarist.lastShockCD = 5
Elementarist.person = {
	["foeCount"]	= 0,
	["friendCount"]	= 0,
	["friend"]  = {},
	["foe"]		= {}
}
Elementarist.debuffCooldowns = {
}
Elementarist.lastPersonTablePurged = 0.0;
Elementarist.configPanel = nil
Elementarist.prevDB = {}
Elementarist.DPSTable = {}
Elementarist.DebugMode = false
Elementarist.DebugChat = DEFAULT_CHAT_FRAME
Elementarist.inParty = 0
Elementarist.OmniCC = _G['OmniCC']
Elementarist.SpellFlash = _G['SpellFlashAddon']
Elementarist.SFHistory = {
	["spell"] = nil,
	["misc"] = nil,
	["int"] = nil
}
Elementarist.isEle = false
Elementarist.talentUnsure = true
Elementarist.lastSpell = nil
Elementarist.CustomIDs = {
	["Flask of Enhancement Item"] = 58149,
	["Flask of Enhancement Spell"] = 79640,
	["Moonkin Aura"] = 24907,
	["Mind Quickening"] = 49868
}
Elementarist.SpellList = {
	["Flame Shock"] = GetSpellInfo(8050),
	["Lightning Bolt"] = GetSpellInfo(403),
	["Lava Burst"] = GetSpellInfo(51505),
	["Chain Lightning"] = GetSpellInfo(421),
	["Thunderstorm"] = GetSpellInfo(59159),
	["Purge"]	= GetSpellInfo(8012),
	["Wind Shear"] = GetSpellInfo(57994),
	["Water Shield"] = GetSpellInfo(52127),
	["Flametongue Weapon"] = GetSpellInfo(8024),
	["Wrath of Air Totem"] = GetSpellInfo(3738),
	["Mana Spring Totem"] = GetSpellInfo(567),
	["Elemental Mastery"] = GetSpellInfo(16166),
	["Fire Nova"] = GetSpellInfo(1535),
	["Earth Shock"] = GetSpellInfo(8042),
	["Searing Totem"] = GetSpellInfo(3599),
	["Lightning Shield"] = GetSpellInfo(324),
	["Unleash Elements"] = GetSpellInfo(73680),
	["Earthquake"] = GetSpellInfo(61882),

	-- racials
	["Berserking"] = GetSpellInfo(26297),	-- Troll racial
	["Blood Fury"] = GetSpellInfo(33697),	-- Orc racial

	-- debuffs
	["Totem of Wrath Debuff"] = GetSpellInfo(30708),
	["Heart of the Crusader"] = GetSpellInfo(54499),

	-- other buffs
	["Demonic Pact"] = GetSpellInfo(48090),
	["Flask of Enhancement"] = GetSpellInfo(Elementarist.CustomIDs["Flask of Enhancement Spell"]),
	["Flask of the Draconic Mind"] = GetSpellInfo(79470),
	["Lifeblood"] = GetSpellInfo(55503)
}
Elementarist.debuffCooldownFrame = {
	["main"] = nil,
	["mini_1"] = nil,
	["mini_2"] = nil,
	["mini_3"] = nil,
	["mini_4"] = nil
}
Elementarist.textureList = {
	["next"] = nil,
	["next1"] = nil,
	["next2"] = nil,
	["misc"] = nil,
	["int"] = nil,
	["debuff"] = nil,
	["debuff_1"] = nil,
	["debuff_2"] = nil,
	["debuff_3"] = nil,
	["debuff_4"] = nil,
	["shield"] = nil
}

Elementarist.textList = {
	["dps"] = nil,
	["debuff"] = nil,
	["shield"] = nil
}
Elementarist.Behaviours = {
}
Elementarist.CLBehaviours = {
}
Elementarist.HostileFilter = {
  ["_DAMAGE"] = true,
  ["_LEECH"] = true,
  ["_DRAIN"] = true,
  ["_STOLEN"] = true,
  ["_INSTAKILL"] = true,
  ["_INTERRUPT"] = true,
  ["_MISSED"] = true
}

-- Our sneaky frame to watch for events ... checks Elementarist.events[] for the function.  Passes all args.
Elementarist.eventFrame = CreateFrame("Frame")
Elementarist.eventFrame:SetScript("OnEvent", function(this, event, ...)
  Elementarist.events[event](...)
end)

Elementarist.eventFrame:RegisterEvent("ADDON_LOADED")
Elementarist.eventFrame:RegisterEvent("PLAYER_LOGIN")
Elementarist.eventFrame:RegisterEvent("PLAYER_ALIVE")

-- Define our Event Handlers here
Elementarist.events = {}

function Elementarist:Debug(statictxt,msg)
	if (Elementarist.DebugMode) and (Elementarist.DebugChat) then
		if (msg) then
			Elementarist.DebugChat:AddMessage( statictxt  .. " : " .. msg)
		else
			Elementarist.DebugChat:AddMessage( statictxt  .. " : " .. "<nil>")
		end
	end
end

function Elementarist:GetDebugFrame()
	for i=1,NUM_CHAT_WINDOWS do
		local windowName = GetChatWindowInfo(i);
		if windowName == "EleDBG" then
			return getglobal("ChatFrame" .. i)
		end
	end
end

function Elementarist.events.PLAYER_TALENT_UPDATE()
	Elementarist:detectTalent()

	Elementarist:ApplySettings()
end

function Elementarist.events.PARTY_MEMBERS_CHANGED()
	Elementarist.inParty = Elementarist:PlayerInParty()
end

function Elementarist.events.PLAYER_ALIVE()
	-- check anything
	Elementarist:detectTalent()
	Elementarist:ApplySettings()

	-- Elementarist.eventFrame:UnregisterEvent("PLAYER_ALIVE")
end

function Elementarist.events.PLAYER_ENTERING_WORLD()
	Elementarist:detectTalent()
end

function Elementarist.events.PLAYER_LOGIN()
	Elementarist.playerName = UnitName("player");

	Elementarist.spellHaste = GetCombatRatingBonus(20)
	Elementarist.spellPower = GetSpellBonusDamage(4);
end

function Elementarist.events.ADDON_LOADED(addon)
	if addon == "OmniCC" then
		Elementarist.OmniCC = _G['OmniCC']
	end
	if addon == "SpellFlash" then
		Elementarist.SpellFlash = _G['SpellFlashAddon']
	end
	if addon ~= "Elementarist" then return end
	local _,playerClass = UnitClass("player")
	if playerClass ~= "SHAMAN" then
		Elementarist.eventFrame:UnregisterEvent("PLAYER_ALIVE")
		return
	end

	-- load defaults, if first start
	Elementarist:InitSettings()

	-- add slash command
	SlashCmdList["Elementarist"] = Elementarist.Options
	SLASH_Elementarist1 = "/Elementarist"
	SLASH_Elementarist2 = "/ele"

	-- check if talent is elemental
	Elementarist:detectTalent()

	Elementarist.playerLevel = UnitLevel("player")

	-- Setup behaviours
	Elementarist.Behaviours["1"] = L.BEHAVIOUR_KEEP_FS_UP
	Elementarist.Behaviours["2"] = L.BEHAVIOUR_FS_BEFORE_LVB
	Elementarist.CLBehaviours["0"] = L.CLSTBEHAVIOUR_NONE
	Elementarist.CLBehaviours["1"] = L.CLSTBEHAVIOUR_CL_AFTER_LVB
	Elementarist.CLBehaviours["2"] = L.CLSTBEHAVIOUR_CL_ON_CD

	-- Create GUI
	Elementarist:CreateGUI()
	Elementarist.displayFrame:SetScale(ElementaristDB.scale)
	Elementarist.OmniCC = _G['OmniCC']
	Elementarist.SpellFlash = _G['SpellFlashAddon']

	-- Create config page
	Elementarist:CreateConfig()

	-- Register for Function Events
	Elementarist.eventFrame:RegisterEvent("COMBAT_LOG_EVENT_UNFILTERED")
	Elementarist.eventFrame:RegisterEvent("COMBAT_RATING_UPDATE") -- Monitor the all-mighty haste
	Elementarist.eventFrame:RegisterEvent("PLAYER_TARGET_CHANGED")
	Elementarist.eventFrame:RegisterEvent("PLAYER_REGEN_ENABLED") -- Left combat, clean up all enemy GUIDs
	Elementarist.eventFrame:RegisterEvent("PLAYER_TALENT_UPDATE")
	Elementarist.eventFrame:RegisterEvent("PARTY_MEMBERS_CHANGED")

	-- get debug frame
	Elementarist.DebugChat = Elementarist:GetDebugFrame()
end

function Elementarist:InitSettings()
	if not ElementaristDB then
		ElementaristDB = {} -- fresh start
	end
	if not ElementaristDB.scale then ElementaristDB.scale = 1 end
	if not ElementaristDB.debuffscale then ElementaristDB.debuffscale = 1 end
	if not ElementaristDB.shieldscale then ElementaristDB.shieldscale = 1 end
	if ElementaristDB.locked == nil then ElementaristDB.locked = false end
	if ElementaristDB.enabled == nil then ElementaristDB.enabled = true end
	if ElementaristDB.disableIfNotEle == nil then ElementaristDB.disableIfNotEle = true end
	if ElementaristDB.debuffdisabled == nil then ElementaristDB.debuffdisabled = false end
	if ElementaristDB.shielddisabled == nil then ElementaristDB.shielddisabled = false end
	if ElementaristDB.alpha == nil then ElementaristDB.alpha = 0.8 end
	if ElementaristDB.debuffalpha == nil then ElementaristDB.debuffalpha = 1 end
	if ElementaristDB.shieldalpha == nil then ElementaristDB.shieldalpha = 1 end
	if ElementaristDB.FireNova == nil then ElementaristDB.FireNova = true end
	if ElementaristDB.Behaviour == nil then ElementaristDB.Behaviour = Elementarist.Behaviours["1"] end
	if ElementaristDB.CLBehaviour == nil then ElementaristDB.CLBehaviour = Elementarist.CLBehaviours["0"] end
	if ElementaristDB.EnableUE == nil then ElementaristDB.EnableUE = false end
	if ElementaristDB.ThreatWarning == nil then ElementaristDB.ThreatWarning = true end
	if ElementaristDB.EnableEQ == nil then ElementaristDB.EnableEQ = false end
	if not ElementaristDB.x then ElementaristDB.x = -200 end
	if not ElementaristDB.y then ElementaristDB.y = -200 end
	if not ElementaristDB.relativePoint then ElementaristDB.relativePoint = "CENTER" end
	if not ElementaristDB.debuffx then ElementaristDB.debuffx = -200 end
	if not ElementaristDB.debuffy then ElementaristDB.debuffy = -100 end
	if not ElementaristDB.debuffrelativePoint then ElementaristDB.debuffrelativePoint = "CENTER" end
	if not ElementaristDB.shieldrelativePoint then ElementaristDB.shieldrelativePoint = "CENTER" end
end

function Elementarist:detectTalent()
	local _,_,_,_,rest,_ = GetTalentTabInfo(3)
	local _,_,_,_,enha,_ = GetTalentTabInfo(2)
	local _,_,_,_,elem,_ = GetTalentTabInfo(1)

	if (elem+rest+enha>0) then
		Elementarist.isEle = ((elem>=enha) and (elem>=rest))
	end
	if (elem + enha + rest == 0) then
		Elementarist.talentUnsure = true
	else
		Elementarist.talentUnsure = false
	end
end

function Elementarist:PlayerInParty()
	if (GetNumRaidMembers()>0) then
		return 2
	elseif (GetNumPartyMembers()>0) then
		return 1
	else
		return 0
	end
end

function Elementarist:RemoveFromTables(guid)
	if (Elementarist.person["friend"][guid]) and (Elementarist.person["friend"][guid] ~= 0) then
		Elementarist.person["friend"][guid] = 0
		Elementarist.person["friendCount"] = Elementarist.person["friendCount"] - 1
	end
	if (Elementarist.person["foe"][guid]) and (Elementarist.person["foe"][guid] ~= 0) then
		Elementarist.person["foe"][guid] = 0
		Elementarist.person["foeCount"] = Elementarist.person["foeCount"] - 1
		Elementarist:Debug('Enemy died:', Elementarist.person["foeCount"] .. " " .. guid)
	end
	if (Elementarist.debuffCooldowns[guid]) then
		table.remove(Elementarist.debuffCooldowns, guid)
		Elementarist:UpdateDebuffTracker()
	end
end

function Elementarist:PurgeDebuffTable()
	Elementarist.debuffCooldowns = {}
	Elementarist:UpdateDebuffTracker()
end

function Elementarist:PurgePersonTable()
	for i,v in pairs(Elementarist.person["foe"]) do
		if ( ( GetTime() - Elementarist.person["foe"][i] ) > 3) then
			-- no activity from that unit in last 2 seconds, remove it
			if ( Elementarist.person["foe"][i] ~= 0) then
				Elementarist.person["foe"][i] = 0	-- mark as inactive
				Elementarist.person["foeCount"] = Elementarist.person["foeCount"] - 1
				Elementarist:Debug('Enemy removed:', Elementarist.person["foeCount"])
			end
		end
	end
	for i,v in pairs(Elementarist.person["friend"]) do
		if ( ( GetTime() - Elementarist.person["friend"][i] ) > 3) then
			-- no activity from that unit in last 2 seconds, remove it
			if ( Elementarist.person["friend"][i] ~= 0 ) then
				Elementarist.person["friend"][i] = 0	-- mark as inactive
				Elementarist.person["friendCount"] = Elementarist.person["friendCount"] - 1
				Elementarist:Debug('Friend removed:', Elementarist.person["friendCount"])
			end
		end
	end
	Elementarist.lastPersonTablePurged = GetTime()
end

function Elementarist:CountPerson(time, event, sguid, sname, sflags, dguid, dname, dflags)
	local suffix = event:match(".+(_.-)$")
	local stype = (tonumber(sguid:sub(5,5), 16)) % 8
	local dtype = (tonumber(dguid:sub(5,5), 16)) % 8
	if Elementarist.HostileFilter[suffix] then
		if (bit.band(dflags, COMBATLOG_OBJECT_REACTION_HOSTILE) == COMBATLOG_OBJECT_REACTION_HOSTILE) and (bit.band(dflags, COMBATLOG_OBJECT_AFFILIATION_OUTSIDER) == COMBATLOG_OBJECT_AFFILIATION_OUTSIDER) and ((dtype==0) or (dtype==3)) then
			if ((not Elementarist.person["foe"][dguid]) or (Elementarist.person["foe"][dguid]==0)) then
				Elementarist.person["foeCount"] = Elementarist.person["foeCount"] + 1
				Elementarist:Debug('Enemy added', Elementarist.person["foeCount"] .. " " .. dguid .. " " .. dflags)
			end
			Elementarist.person["foe"][dguid] = GetTime()
    	elseif (bit.band(sflags, COMBATLOG_OBJECT_REACTION_HOSTILE) == COMBATLOG_OBJECT_REACTION_HOSTILE) and (bit.band(sflags, COMBATLOG_OBJECT_AFFILIATION_OUTSIDER) == COMBATLOG_OBJECT_AFFILIATION_OUTSIDER) and ((stype==0) or (stype==3)) then
			if ((not Elementarist.person["foe"][sguid]) or (Elementarist.person["foe"][sguid]==0)) then
				Elementarist.person["foeCount"] = Elementarist.person["foeCount"] + 1
				Elementarist:Debug('Enemy added', Elementarist.person["foeCount"] .. " " .. sguid)
			end
			Elementarist.person["foe"][sguid] = GetTime()
		end
		if (bit.band(dflags, COMBATLOG_OBJECT_REACTION_FRIENDLY) == COMBATLOG_OBJECT_REACTION_FRIENDLY) and ((dtype==0) or (dtype==3)) then
			if ((not Elementarist.person["friend"][dguid]) or (Elementarist.person["friend"][dguid]==0)) then
				Elementarist.person["friendCount"] = Elementarist.person["friendCount"] + 1
			end
			Elementarist.person["friend"][dguid] = GetTime()
    	elseif (bit.band(sflags, COMBATLOG_OBJECT_REACTION_FRIENDLY) == COMBATLOG_OBJECT_REACTION_FRIENDLY) and ((stype==0) or (stype==3)) then
			if ((not Elementarist.person["friend"][sguid]) or (Elementarist.person["friend"][sguid]==0)) then
				Elementarist.person["friendCount"] = Elementarist.person["friendCount"] + 1
			end
			Elementarist.person["friend"][sguid] = GetTime()
		end
	end
	if (Elementarist.lastPersonTablePurged < (GetTime() - 3)) and (Elementarist.person["foeCount"]>0) then
		Elementarist:PurgePersonTable()
	end
	--
	-- Elementarist:Debug('Enemy count:', Elementarist.person["foeCount"])
	-- Elementarist:Debug('Friend count:', Elementarist.person["friendCount"])
end

function Elementarist.HighDMGFormat(dmg_amount)
	if (dmg_amount >= 10000) then
		return(format('%.1f',dmg_amount/1000) .. "K")
	else
		return(format('%.f',dmg_amount))
	end
end

-- for 4.2:
-- function Elementarist.events.COMBAT_LOG_EVENT_UNFILTERED(timestamp, event, hideCaster, srcGUID, srcName, srcFlags, srcRaidFlags, dstGUID, dstName, dstFlags, dstRaidFlags, spellId, spellName, spellSchool, damage, ...)
function Elementarist.events.COMBAT_LOG_EVENT_UNFILTERED(timestamp, event, hideCaster, srcGUID, srcName, srcFlags, dstGUID, dstName, dstFlags, spellId, spellName, spellSchool, damage, ...)
	if Elementarist.isEnabled() then
		if srcName == Elementarist.playerName then
			if (event=="SPELL_PERIODIC_DAMAGE") and (spellName==Elementarist.SpellList["Flame Shock"]) and (Elementarist.debuffCooldowns[dstGUID]) then
				Elementarist.debuffCooldowns[dstGUID]["action"] = GetTime()
			end
			if (event=="SPELL_CAST_START") then
				Elementarist.SFHistory.spell = nil
				Elementarist.SFHistory.misc = nil
				Elementarist.SFHistory.int = nil
			end
			Elementarist:DecideSpells()
			-- calculate DPS
			if (event=="SPELL_CAST_SUCCESS") then
				Elementarist.lastSpell = spellName
			end
			if ((event=="SPELL_DAMAGE") or (event=="SPELL_PERIODIC_DAMAGE")) then
				if (not Elementarist.DPSTable[dstGUID]) then
					Elementarist.DPSTable[dstGUID] = {
						["time"] = GetTime(),
						["amount"] = 0,
					}
				end

				Elementarist.DPSTable[dstGUID]["amount"] = Elementarist.DPSTable[dstGUID]["amount"] + damage
				local dps_txt = ""
				if (dstGUID == Elementarist.targetGUID) and (Elementarist.DPSTable[Elementarist.targetGUID]) then
					local dps_sec = GetTime() - Elementarist.DPSTable[dstGUID]["time"]
					if (dps_sec > 5) then
						dps_txt = format('%.f',(Elementarist.DPSTable[dstGUID]["amount"] / dps_sec))
					end
					local threat_txt = ""
					local _, status, threatpct, _, _ = UnitDetailedThreatSituation("player", "target")
					if (status) then
						if (threatpct<80) then
							threat_txt = format("%.f",threatpct) .. " %"
							Elementarist.cooldownFrame:SetReverse(false)
							if (ElementaristDB.ThreatWarning) and (Elementarist.inParty>0) and (threatpct>70) then
								RaidNotice_AddMessage(RaidBossEmoteFrame, L.THREAT_WARNING_PREFIX .. format("%.f",threatpct) .. L.THREAT_WARNING_SUFFIX, ChatTypeInfo["RAID_WARNING"])
							end
						else
							threat_txt = "|cffff0000" .. format("%.f",threatpct) .. " %|r"
							if (ElementaristDB.ThreatWarning) and (Elementarist.inParty>0) then
								RaidNotice_AddMessage(RaidBossEmoteFrame, "|cffff0000" .. L.THREAT_WARNING_PREFIX .. format("%.f",threatpct) ..  L.THREAT_WARNING_SUFFIX .. "|r", ChatTypeInfo["RAID_WARNING"])
							end
							Elementarist.cooldownFrame:SetReverse((Elementarist.person["friendCount"]>1) and (Elementarist.inParty>0))
						end
					end
					dps_txt = dps_txt .. "|n" .. threat_txt
					Elementarist.textList["dps"]:SetText(dps_txt)
				end
			end
		else
			-- if unit died, remove if from friend and foe tables
			if (event=="UNIT_DIED") or (event=="UNIT_DESTROYED") then
				Elementarist:RemoveFromTables(dstGUID);
			end
			-- count enemies if player in combat
			if (UnitAffectingCombat("player")) then
				-- enemy count for CL advise and multiple player in combat (for aggro warning)
				Elementarist:CountPerson(timestamp, event, srcGUID, srcName, srcFlags, dstGUID, dstName, dstFlags)
			end
		end
	end
end

function Elementarist.events.COMBAT_RATING_UPDATE(unit)
	if unit == "player" then
    	Elementarist.spellHaste = GetCombatRatingBonus(20) -- update spell haste
		Elementarist.spellPower = GetSpellBonusDamage(4);
	end
end

function Elementarist.events.PLAYER_TARGET_CHANGED(...)
	Elementarist.targetGUID = UnitGUID("target")
	Elementarist.inParty = Elementarist:PlayerInParty()

	if (ElementaristDB.disableIfNotEle) and (not Elementarist:isEnabled()) then
		Elementarist:detectTalent()
		if Elementarist:isEnabled() then
			Elementarist:ApplySettings()
		end
	end
	if (not Elementarist.targetGUID) then
		local threat_txt = ""
		local _, status, threatpct, _, _ = UnitDetailedThreatSituation("player", "target")
		if (status) then
			if (threatpct<80) then
				threat_txt = format("%.f",threatpct) .. " %"
				Elementarist.cooldownFrame:SetReverse(false)
			else
				threat_txt = "|cffff0000" .. format("%.f",threatpct) .. " %|r"
				Elementarist.cooldownFrame:SetReverse((Elementarist.person["friendCount"]>1) and (Elementarist.inParty>0))
			end
		end
		Elementarist.textList["dps"]:SetText(threat_txt)
	end
	Elementarist:DecideSpells()
end

function Elementarist.events.PLAYER_REGEN_ENABLED(...)
	-- left combat
	Elementarist.person["friend"] = {}
	Elementarist.person["friendCount"] = 0
	Elementarist.person["foe"] = {}
	Elementarist.person["foeCount"] = 0
	Elementarist.DPSTable = {}
	Elementarist.textList["dps"]:SetText("")
	Elementarist.textList["debuff"]:SetText("")
	Elementarist.cooldownFrame:SetReverse(false)
	Elementarist:PurgePersonTable()
	Elementarist:PurgeDebuffTable()
end

function Elementarist:isEnabled()
	if (Elementarist.talentUnsure) then
		Elementarist:detectTalent()
	end
	return (
		ElementaristDB.enabled and (
			(not ElementaristDB.disableIfNotEle) or (Elementarist.isEle)
		)
	)
end

function Elementarist:UpdateShieldTracker()
	local name, _, icon, count, _, d, e = Elementarist:hasBuff("player",Elementarist.SpellList["Lightning Shield"])

	Elementarist.shieldTrackerUpdate = GetTime()
	if (name) then
		Elementarist.textureList["shield"]:SetTexture(icon)
		Elementarist.textList["shield"]:SetText(format('%.0f', count))
		Elementarist.shieldCooldownFrame:SetCooldown( e-d, d)
	else
		Elementarist.textureList["shield"]:SetTexture("")
		Elementarist.textList["shield"]:SetText("")
		Elementarist.shieldCooldownFrame:SetCooldown(0, 0)
	end
end

function Elementarist:UpdateDebuffTracker()
	local name, _, icon, _, _, d, e = Elementarist:hasDeBuff("target",Elementarist.SpellList["Flame Shock"],"player")
	local tguid = UnitGUID("target")

	Elementarist.debuffTrackerUpdate = GetTime()
	if (name) then
		if (tguid) and (not Elementarist.debuffCooldowns[tguid]) then
			Elementarist.debuffCooldowns[tguid] = {}
		end
		Elementarist.debuffCooldowns[tguid]["start"] = e-d
		Elementarist.debuffCooldowns[tguid]["duration"] = d
		Elementarist.debuffCooldowns[tguid]["action"] = GetTime()
	else
		_, _, icon = GetSpellInfo(Elementarist.SpellList["Flame Shock"])
	end

	-- update mini frames
	local m = 1
	for i,v in pairs(Elementarist.debuffCooldowns) do
		if ( (v["start"] + v["duration"]) > GetTime() ) and (i ~= tguid) and (m <= 4) and (v["action"]>GetTime() - 4) then
			Elementarist.textureList["debuff_" .. tostring(m)]:SetTexture(icon)
			Elementarist.debuffCooldownFrame["mini_" .. tostring(m)]:SetCooldown( v["start"], v["duration"])
			m = m + 1
		end
	end
	for i=m,4,1 do
		Elementarist.textureList["debuff_" .. tostring(m)]:SetTexture("")
		Elementarist.debuffCooldownFrame["mini_" .. tostring(m)]:SetCooldown( 0, 0)
	end

	-- update main frame
	if (name) then
		Elementarist.textureList["debuff"]:SetTexture(icon)
		if (not Elementarist.OmniCC) then
			Elementarist.textList["debuff"]:SetText(format('%.1f', (e - GetTime())))
		end
		Elementarist.debuffCooldownFrame["main"]:SetCooldown( e-d, d)
	else
		Elementarist.textureList["debuff"]:SetTexture("")
		Elementarist.textList["debuff"]:SetText("")
		Elementarist.debuffCooldownFrame["main"]:SetCooldown(0, 0)
	end
end

function Elementarist:GetSpellCooldownRemaining(spell)
	local s, d, _ = GetSpellCooldown(spell)
	if (d) and (d>0) then
		d = s - GetTime() + d
	end

	return d
end

function Elementarist:hasDeBuff(unit, spellName, casterUnit)
	local i = 1;
	while true do
		local name, rank, icon, count, debuffType, duration, expirationTime, unitCaster, isStealable = UnitDebuff(unit, i);
		if not name then
			break;
		end
		if (name) and (spellName) then
			if string.match(name, spellName) and ((unitCaster == casterUnit) or (casterUnit == nil)) then
				return name, rank, icon, count, debuffType, duration, expirationTime, unitCaster, isStealable;
			end
		end
		i = i + 1;
	end
end

function Elementarist:hasBuff(unit, spellName, stealableOnly, getByID)
	local i = 1;
	while true do
		local name, rank, icon, count, buffType, duration, expirationTime, source, isStealable, _, spellId = UnitBuff(unit, i);
		if not name then
			break;
		end
		if (not getByID) and (name) and (spellName) then
			if string.match(name, spellName) then
				if (not stealableOnly) or (isStealable) then
					return name, rank, icon, count, buffType, duration, expirationTime, unitCaster, isStealable;
				end
			end
		else
			if (getByID == spellId) then
				return name, rank, icon, count, buffType, duration, expirationTime, unitCaster, isStealable;
			end
		end
		i = i + 1;
	end
end

function Elementarist:hasTotem(unit, spellName)
	local i = 1;
	while true do
		local name, rank, icon, count, buffType, duration, expirationTime, source, isStealable = UnitBuff(unit, i);
		if not name then
			break;
		end
		if (string.match(name, spellName) or (string.match(icon, spellName))) and (expirationTime==0) then
	   		return name, rank, icon, count, buffType, duration, expirationTime, unitCaster, isStealable;
		end
		i = i + 1;
	end
end

function Elementarist:SpellAvailable(spell)
	if (not spell) then
		return false
	end
	if (IsUsableSpell(spell)) then
		return true
	else
		return false
	end
end

function Elementarist:NextSpell(timeshift,exspell1,exspell2)
	local guid = UnitGUID("target")
	local currentTime = GetTime()
	local s,d,e
	local name, fsExpiration, unitCaster
	local lastSpell

	if (exspell1) then
		if (exspell2) then
			lastSpell = exspell2
		else
			lastSpell = exspell1
		end
	else
		lastSpell = Elementarist.lastSpell
	end

	Elementarist.lastBaseGCD = 1.5 - (1.5 * Elementarist.spellHaste * .01)

	local flameshockavail = false
	local LvBct = 2 - (2 * Elementarist.spellHaste * .01)

	-- check Shock CD
	local s, d = GetSpellCooldown(Elementarist.SpellList["Flame Shock"])
	if (d) and (d>0) then
		Elementarist.lastShockCD = d
	end
	local s, d = GetSpellCooldown(Elementarist.SpellList["Earth Shock"])
	if (d) and (d>0) then
		Elementarist.lastShockCD = d
	end

	-- if target is dead, return ""
	if (UnitHealth("target")<=0) then
		return ""
	end

	if (not timeshift) then
		timeshift = 0
	end

	-- check current spell
	local spellInCast, _, _, _, sICstartTime, sICendTime = UnitCastingInfo("player")
	if (sepllInCast) then
		if ( (sICendTime - sICstartTime) / 1000 ) < Elementarist.lastBaseGCD then
			sICendTime = sICstartTime + (Elementarist.lastBaseGCD * 1000)
		end
		timeshift = timeshift + (sICendTime / 1000) - GetTime()
	else
		-- no spell in cast, check global cd via Flametongue Weapon
		if (Elementarist.SpellList["Flametongue Weapon"]) then
			local ftcd = Elementarist:GetSpellCooldownRemaining(Elementarist.SpellList["Flametongue Weapon"])
			if (ftcd) then
				timeshift = timeshift + Elementarist:GetSpellCooldownRemaining(Elementarist.SpellList["Flametongue Weapon"])
			else
				timeshift = timeshift + Elementarist.lastBaseGCD
			end
		else
			timeshift = timeshift + Elementarist.lastBaseGCD
		end
	end

	-- check if Flame shock applied on target first
	name, _, _, _, _, _, fsExpiration, unitCaster = Elementarist:hasDeBuff("target", Elementarist.SpellList["Flame Shock"], "player");
	if (exspell1 ~= Elementarist.SpellList["Flame Shock"]) and (exspell2 ~= Elementarist.SpellList["Flame Shock"]) then
		if IsSpellInRange(Elementarist.SpellList["Flame Shock"], "target") == 1 then
			if (not fsExpiration) then
				fsExpiration = 0
			end
			d = Elementarist:GetSpellCooldownRemaining(Elementarist.SpellList["Flame Shock"])
			if ((d - timeshift) <= 0) then
				flameshockavail = true
				local doFS = false
				if (unitCaster ~= "player") then	-- fs debuff is not casted by the player
					name = false
					fsExpiration = 0
				end
				if (not name) then 	-- no fs debuff on target
					fsExpiration = 0
				end
				if (ElementaristDB.Behaviour == Elementarist.Behaviours["1"]) then
					doFS = true
				else	-- if fs before lvb, check lvb cd
					d = Elementarist:GetSpellCooldownRemaining(Elementarist.SpellList["Lava Burst"])
					if (d <= (timeshift + Elementarist.lastBaseGCD)) then
						doFS = true
					end
				end
				if (doFS) and ((fsExpiration - GetTime() - timeshift) < 0) then
					return Elementarist.SpellList["Flame Shock"]
				end
			end
		end
	end

	-- Unleash Elements if LvB will be available after it, and target has FS debuff, and it will not expire before UE, and no LvB cast before UE
	-- Weapon enchant has to be Flamtounge Weapon, and Unleash Elements are not on CD
	if (ElementaristDB.EnableUE) then
		if (exspell1 ~= Elementarist.SpellList["Unleash Elements"]) and (exspell2 ~= Elementarist.SpellList["Unleash Elements"]) then
			if (
				(IsSpellInRange(Elementarist.SpellList["Unleash Elements"], "target") == 1) and
				(
					( (Elementarist.SpellList["Lava Burst"]) ~= spellInCast) and
					( (Elementarist.SpellList["Lava Burst"]) ~= exspell1) and
					( (Elementarist.SpellList["Lava Burst"]) ~= exspell2)
				) and
				(
					(exspell1 == Elementarist.SpellList["Flame Shock"] ) or
					(exspell2 == Elementarist.SpellList["Flame Shock"] ) or
					(fsExpiration > timeshift + (2 * Elementarist.lastBaseGCD ) )
				)
			) then
				local hasMainHandEnchant, _, _, _, _, _ = GetWeaponEnchantInfo()
				if (hasMainHandEnchant) then
					d = Elementarist:GetSpellCooldownRemaining(Elementarist.SpellList["Lava Burst"])
					e = Elementarist:GetSpellCooldownRemaining(Elementarist.SpellList["Unleash Elements"])
					if (d <= (timeshift + Elementarist.lastBaseGCD)) and ((e-timeshift) <= 0) then
						return Elementarist.SpellList["Unleash Elements"]
					end
				end
			end
		end
	end

	if (not fsExpiration) then
		fsExpiration = 0
	end
	if (exspell1 ~= Elementarist.SpellList["Lava Burst"]) and (exspell2 ~= Elementarist.SpellList["Lava Burst"]) then
		if (IsSpellInRange(Elementarist.SpellList["Lava Burst"], "target") == 1) and
		(
			((fsExpiration~=0) and ((fsExpiration-GetTime()-timeshift) > LvBct)) or
			(exspell1 == Elementarist.SpellList["Flame Shock"]) or
			(exspell2 == Elementarist.SpellList["Flame Shock"])
		) then
			if ((Elementarist.SpellList["Lava Burst"]) ~= spellInCast) then
				d = Elementarist:GetSpellCooldownRemaining(Elementarist.SpellList["Lava Burst"])
				if ((d-timeshift) <= 0) then
					return Elementarist.SpellList["Lava Burst"]
				end
			end
		end
	end

	-- if >=4 foes are available, and Earthquake not on cd
	if (ElementaristDB.EnableEQ) then
		if (exspell1 ~= Elementarist.SpellList["Earthquake"]) and (exspell2 ~= Elementarist.SpellList["Earthquake"]) then
			if (Elementarist.person["foeCount"]>=4) then
				d = Elementarist:GetSpellCooldownRemaining(Elementarist.SpellList["Earthquake"])
				if ((d-timeshift) <= 0) then
					return Elementarist.SpellList["Earthquake"]
				end
			end
		end
	end

	-- if >=4 foes are available, and Fire Nova enabled in settings, and not in cd (and has Flame Shock applied on target)
	if (exspell1 ~= Elementarist.SpellList["Fire Nova"]) and (exspell2 ~= Elementarist.SpellList["Fire Nova"]) then
		if (ElementaristDB.FireNova) and (Elementarist.person["foeCount"]>=4) then
			-- is Flame Shock on target
			local f = Elementarist:hasDeBuff("target",Elementarist.SpellList["Flame Shock"],"player")
			if f then
	            d = Elementarist:GetSpellCooldownRemaining(Elementarist.SpellList["Fire Nova"])
				if ((d-timeshift) <= 0) then
					return Elementarist.SpellList["Fire Nova"]
				end
			end
		end
	end

	-- Earth shock if Lightning Shield count >=9 and not on cd or FS debuff remaining between FS and FS cd + 2sec and LS count>=7
	if ( (exspell1 ~= Elementarist.SpellList["Earth Shock"]) and (exspell2 ~= Elementarist.SpellList["Earth Shock"]) ) then
		d = Elementarist:GetSpellCooldownRemaining(Elementarist.SpellList["Earth Shock"])
		local _, _, _, lscount = Elementarist:hasBuff("player",Elementarist.SpellList["Lightning Shield"])
		if (
				( (d) and ((d-timeshift) <= 0) and (lscount) ) and (
					(lscount>=9) or
					( (lscount>=7) and
						( (fsExpiration - GetTime() - timeshift) > Elementarist.lastShockCD ) and
						( (fsExpiration - GetTime() - timeshift) < Elementarist.lastShockCD + 2 )
					)
				)
			) then
			return Elementarist.SpellList["Earth Shock"]
		end
	end

	-- CL if there are multiple targets, (count in a dirty way from combat log, not to accurate!!!)
	if (exspell1 ~= Elementarist.SpellList["Chain Lightning"]) and (exspell2 ~= Elementarist.SpellList["Chain Lightning"]) then
		if (Elementarist.person["foeCount"]>2) then
			if IsSpellInRange(Elementarist.SpellList["Chain Lightning"], "target") == 1 then
				if ((Elementarist.SpellList["Chain Lightning"]) ~= spellInCast) then
					d = Elementarist:GetSpellCooldownRemaining(Elementarist.SpellList["Chain Lightning"])
					if ((d-timeshift) <= 0) then
						return Elementarist.SpellList["Chain Lightning"]
					end
				end
			end
		end
	end

	-- otherwise lightning bolt
	if IsSpellInRange(Elementarist.SpellList["Lightning Bolt"], "target") == 1 then
		return Elementarist.SpellList["Lightning Bolt"]
	end

	-- if nothing works, try flameshock again
	if flameshockavail then
		return Elementarist.SpellList["Flame Shock"]
	end

	return ""
end

function Elementarist:MiscSpell()
	-- Miscelaneous spell order:
	-- Flametongoue weapon
	-- Lightning Shield
	-- Searing Totem
	-- Totem of Wrath
	-- Wrath of Air totem
	-- Elemental Mastery
	-- Berserking troll racial (if available)
	-- Blood Fury orc racial
	-- Lifeblood Herbalism spell

	local d, e
	local name, expirationTime

	-- Alchemy Flask of Enhancement
	if GetItemCount(Elementarist.CustomIDs["Flask of Enhancement Item"]) ~= 0 then
		name = Elementarist:hasBuff("player", Elementarist.SpellList["Flask of the Draconic Mind"]);
		if (name == nil) then
			name, _, _, _, _, _, expirationTime = Elementarist:hasBuff("player", Elementarist.SpellList["Flask of Enhancement"], false, Elementarist.CustomIDs["Flask of Enhancement Spell"]);
			if (name == nil) or (expirationTime < 2) then
				if (icon == nil) then
					icon = GetItemIcon(Elementarist.CustomIDs["Flask of Enhancement Item"])
				end
				return nil,icon
			end
		end
	end

	-- Flametongue weapon
	if Elementarist:SpellAvailable(Elementarist.SpellList["Flametongue Weapon"]) then
		local hasMainHandEnchant, mainHandExpiration, _, _, _, _ = GetWeaponEnchantInfo()
		if (hasMainHandEnchant == nil) or ((mainHandExpiration / 60000) < 1) then
			return Elementarist.SpellList["Flametongue Weapon"]
		end
	end

	-- Lightning Shield
	if Elementarist:SpellAvailable(Elementarist.SpellList["Lightning Shield"]) then
		name, _, _, _, _, _, expirationTime = Elementarist:hasBuff("player", Elementarist.SpellList["Lightning Shield"]);
		if (name == nil) or (expirationTime < 1) then
			return Elementarist.SpellList["Lightning Shield"]
		end
	end

	-- Searing Totem
	if Elementarist:SpellAvailable(Elementarist.SpellList["Searing Totem"]) then
		local haveFireTotem,fireTotemName,_,_ = GetTotemInfo(1)
		if (fireTotemName == "") then
			-- no fire totem
			return Elementarist.SpellList["Searing Totem"]
		end
	end

	-- Wrath of Air totem
	if Elementarist:SpellAvailable(Elementarist.SpellList["Wrath of Air Totem"]) then
		if (
			(Elementarist:hasTotem("player", Elementarist.SpellList["Wrath of Air Totem"]) == nil) and
			(Elementarist:hasBuff("player", GetSpellInfo(Elementarist.CustomIDs["Moonkin Aura"]), false, Elementarist.CustomIDs["Moonkin Aura"]) == nil) and
			(Elementarist:hasBuff("player", GetSpellInfo(Elementarist.CustomIDs["Mind Quickening"]), false, Elementarist.CustomIDs["Mind Quickening"]) == nil)
		) then
			return Elementarist.SpellList["Wrath of Air Totem"]
		end
	end

	-- Elemental Mastery
	if Elementarist:SpellAvailable(Elementarist.SpellList["Elemental Mastery"]) then
		d = Elementarist:GetSpellCooldownRemaining(Elementarist.SpellList["Elemental Mastery"])
		if d <= 0.5 then
			return Elementarist.SpellList["Elemental Mastery"]
		end
	end

	-- Berserking
	if Elementarist:SpellAvailable(Elementarist.SpellList["Berserking"]) then
		d = Elementarist:GetSpellCooldownRemaining(Elementarist.SpellList["Berserking"])
		if d <= 0.5 then
			return Elementarist.SpellList["Berserking"]
		end
	end

	-- Blood Fury
	if Elementarist:SpellAvailable(Elementarist.SpellList["Blood Fury"]) then
		d = Elementarist:GetSpellCooldownRemaining(Elementarist.SpellList["Blood Fury"])
		if d <= 0.5 then
			return Elementarist.SpellList["Blood Fury"]
		end
	end

	-- Lifeblood
	if Elementarist:SpellAvailable(Elementarist.SpellList["Lifeblood"]) then
		d = Elementarist:GetSpellCooldownRemaining(Elementarist.SpellList["Lifeblood"])
		if d <= 0.5 then
			return Elementarist.SpellList["Lifeblood"]
		end
	end

	return ""

end

function Elementarist:IntSpell()
	-- interruptions, mana recharge wia thunderstorm, and purge on target
	if Elementarist:SpellAvailable(Elementarist.SpellList["Wind Shear"]) then
		if IsSpellInRange(Elementarist.SpellList["Wind Shear"], "target") == 1 then
			local _, status, threatpct, _, _ = UnitDetailedThreatSituation("player", "target")
			if ((UnitCastingInfo("target")) or (UnitChannelInfo("target"))) or ((status) and (threatpct>80) and (Elementarist.person["friendCount"]>1) and (Elementarist.inParty>0)) then
				local d = Elementarist:GetSpellCooldownRemaining(Elementarist.SpellList["Wind Shear"])
				if (d) and (d<0.5) and (IsSpellInRange(Elementarist.SpellList["Wind Shear"], "target") ) then
					return Elementarist.SpellList["Wind Shear"]
				end
			end

			if (UnitChannelInfo("target")) then
				local d = Elementarist:GetSpellCooldownRemaining(Elementarist.SpellList["Wind Shear"])
				if (d<0.5) and (interruptable) and (IsSpellInRange(Elementarist.SpellList["Wind Shear"], "target") ) then
					return Elementarist.SpellList["Wind Shear"]
				end
			end
		end
	end

	-- check if mana is below 70% and Thunderstorm available
	if Elementarist:SpellAvailable(Elementarist.SpellList["Thunderstorm"]) then
		local d = GetSpellCooldown(Elementarist.SpellList["Thunderstorm"])
		if (d) and ((UnitPower("player",0) / UnitPowerMax("player",0)) < 0.7) and (d < 0.5) then
			return Elementarist.SpellList["Thunderstorm"]
		end
	end

	-- check if purgeable buff is on target (not sure if this is ok)
	if Elementarist:SpellAvailable(Elementarist.SpellList["Purge"]) then
		if IsSpellInRange(Elementarist.SpellList["Purge"], "target") == 1 then
			if (Elementarist:hasBuff("target", ".", 1)) then
				local d = Elementarist:GetSpellCooldownRemaining(Elementarist.SpellList["Purge"])
				if (d<0.5) then
					return Elementarist.SpellList["Purge"]
				end
			end
		end
	end

	return ""
end

function Elementarist:FlashSpell(spell,spelltype)
	local color = "White"
	if (Elementarist.SpellFlash) and (Elementarist.SFHistory[spelltype] ~= spell) and (Elementarist.SpellFlash.Flashable(spell)) then
		if (spelltype == "int") then
			color = "Aqua"
		end
		if (spelltype == "misc") then
			color = "Green"
		end
		Elementarist.SpellFlash.Flash(spell,color)
		Elementarist.SFHistory.spell=spell
	end
end

function Elementarist:DecideSpells()

	Elementarist.timeSinceLastUpdate = 0;
	local currentTime = GetTime()

	local guid = UnitGUID("target")
	local guid = UnitGUID("target")
	if  UnitName("target") == nil or UnitIsFriend("player","target") ~= nil or UnitHealth("target") == 0 then
		guid = nil
	end

	if UnitInVehicle("player") then
		-- player is in a "vehicle" don't suggest spell
		Elementarist.textureList["next"]:SetTexture("")
		Elementarist.textureList["next1"]:SetTexture("")
		Elementarist.textureList["next2"]:SetTexture("")
		Elementarist.textureList["misc"]:SetTexture("")
		Elementarist.textureList["int"]:SetTexture("")

		return
	end

	if guid == nil then
		Elementarist.textureList["next"]:SetTexture("")
		Elementarist.textureList["next1"]:SetTexture("")
		Elementarist.textureList["next2"]:SetTexture("")
		Elementarist.textureList["misc"]:SetTexture("")
		Elementarist.textureList["int"]:SetTexture("")

		return
	end
  	if (UnitHealth("target") == 0) then
		Elementarist.textureList["next"]:SetTexture("")
		Elementarist.textureList["next1"]:SetTexture("")
		Elementarist.textureList["next2"]:SetTexture("")

		return
  	end

	local spell = ""
	spell = Elementarist:NextSpell()
	Elementarist:FlashSpell(spell,"spell")
	local d = Elementarist:GetSpellCooldownRemaining(spell)
	if (d) and (d>0) then
		local cdStart = currentTime - Elementarist.lastBaseGCD + d  -- should be less then the base gcd if we are suggesting it
		if (cdStart) and (Elementarist.lastBaseGCD) then
			Elementarist.cooldownFrame:SetCooldown(cdStart, Elementarist.lastBaseGCD)
		end
	end
	Elementarist.textureList["next"]:SetTexture(GetSpellTexture(spell))

	local _,_,_,_,_,_,ct1=GetSpellInfo(spell)
	if (not ct1) then
		ct1 = 0
	else
		ct1 = (ct1 / 1000)
	end
	local spell1 = Elementarist:NextSpell(ct1,spell)
	Elementarist.textureList["next1"]:SetTexture(GetSpellTexture(spell1))

	local _,_,_,_,_,_,ct2=GetSpellInfo(spell1)
	if (not ct2) then
		ct2 = 0
	else
		ct2 = (ct2 / 1000)
	end
	if (not ct2) or (ct2 < Elementarist.lastBaseGCD) then
		ct2 = Elementarist.lastBaseGCD
	end
	local spell2 = Elementarist:NextSpell(ct1+ct2,spell,spell1)
	Elementarist.textureList["next2"]:SetTexture(GetSpellTexture(spell2))

	local icon

	spell,icon = Elementarist:MiscSpell()
	Elementarist:FlashSpell(spell,"misc")

	if (icon) then
		Elementarist.textureList["misc"]:SetTexture(icon)
	else
		if (spell) then
			Elementarist.textureList["misc"]:SetTexture(GetSpellTexture(spell))
		end
	end

	spell = Elementarist:IntSpell()
	Elementarist:FlashSpell(spell,"int")
	Elementarist.textureList["int"]:SetTexture(GetSpellTexture(spell))


end

function Elementarist:OnUpdate(elapsed)
	if (Elementarist:isEnabled()) then
		Elementarist.timeSinceLastUpdate = Elementarist.timeSinceLastUpdate + elapsed

		if (Elementarist.timeSinceLastUpdate > (1.5 - (1.5 * Elementarist.spellHaste * .01)) * 0.3) then
			Elementarist:DecideSpells()
		end
		if (not ElementaristDB.debuffdisabled) then
			Elementarist.debuffTrackerUpdate = Elementarist.debuffTrackerUpdate + elapsed
			if (
				((Elementarist.OmniCC) and (Elementarist.debuffTrackerUpdate >= 1)) or
				((not Elementarist.OmniCC) and (Elementarist.debuffTrackerUpdate >= 1))
			) then
				Elementarist:UpdateDebuffTracker()
			end
		end
		if (not ElementaristDB.shielddisabled) then
			Elementarist.shieldTrackerUpdate = Elementarist.shieldTrackerUpdate + elapsed
			if (
				((Elementarist.OmniCC) and (Elementarist.shieldTrackerUpdate >= 1)) or
				((not Elementarist.OmniCC) and (Elementarist.shieldTrackerUpdate >= 1))
			) then
				Elementarist:UpdateShieldTracker()
			end
		end
	end
end