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

Elementarist = {Locals = {}}

local L = Elementarist.Locals

Elementarist.versionNumber = '2.9.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 = 6
Elementarist.fsCount = 0
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.tier6Talent = nil
Elementarist.lastSpell = nil
Elementarist.CustomIDs = {
	["Alchemist's Flask Item"] = 75525,
	["Alchemist's Flask Spell"] = 79640
}
Elementarist.SpellList = {
	["Flame Shock"] = GetSpellInfo(8050),
	["Lightning Bolt"] = GetSpellInfo(403),
	["Lava Burst"] = GetSpellInfo(51505),
	["Chain Lightning"] = GetSpellInfo(421),
	["Thunderstorm"] = GetSpellInfo(51490),
	["Purge"]	= GetSpellInfo(370),
	["Wind Shear"] = GetSpellInfo(57994),
	["Water Shield"] = GetSpellInfo(52127),
	["Flametongue Weapon"] = GetSpellInfo(8024),
	["Elemental Mastery"] = GetSpellInfo(16166),
	["Earth Shock"] = GetSpellInfo(8042),
	["Searing Totem"] = GetSpellInfo(3599),
	["Magma Totem"] = GetSpellInfo(8190),
	["Lightning Shield"] = GetSpellInfo(324),
	["Unleash Elements"] = GetSpellInfo(73680),
	["Earthquake"] = GetSpellInfo(61882),
	["Elemental Blast"] = GetSpellInfo(117014),

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

	-- other buffs
	["Demonic Pact"] = GetSpellInfo(48090),
	["Alchemist's Flask"] = GetSpellInfo(Elementarist.CustomIDs["Alchemist's Flask Spell"]),
	["Flask of the Warm Sun"] = GetSpellInfo(105691),
	["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
	return DEFAULT_CHAT_FRAME
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.disableMini == nil then ElementaristDB.disableMini = false 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 spec = GetSpecialization()
	local _

	Elementarist.isEle = (spec == 1)
	_, Elementarist.tier6Talent = GetTalentRowSelectionInfo(6)

	if (spec == nil) then
		Elementarist.talentUnsure = true
	else
		Elementarist.talentUnsure = false
	end
end

function Elementarist:PlayerInParty()
	if (IsInRaid()) then
		return 2
	elseif (GetNumGroupMembers()>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
		Elementarist.debuffCooldowns[guid]=nil
		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
				Elementarist:Debug('Friend added', Elementarist.person["friendCount"] .. " " .. dguid .. " " .. dflags)
			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
				Elementarist:Debug('Friend added', Elementarist.person["friendCount"] .. " " .. sguid .. " " .. sflags)
			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

function Elementarist.events.COMBAT_LOG_EVENT_UNFILTERED(timestamp, event, hideCaster, srcGUID, srcName, srcFlags, srcRaidFlags, dstGUID, dstName, dstFlags, dstRaidFlags, 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:SetTexture(Elementarist.textureList["shield"],icon)
		Elementarist.textList["shield"]:SetText(format('%.0f', count))
		Elementarist.shieldCooldownFrame:SetCooldown( e-d, d)
	else
		Elementarist:SetTexture(Elementarist.textureList["shield"],"")
		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:SetTexture(Elementarist.textureList["debuff_" .. tostring(m)],icon)
			Elementarist.debuffCooldownFrame["mini_" .. tostring(m)]:SetCooldown( v["start"], v["duration"])
			m = m + 1
		end
	end
	for i=m,4,1 do
		Elementarist:SetTexture(Elementarist.textureList["debuff_" .. tostring(m)],"")
		Elementarist.debuffCooldownFrame["mini_" .. tostring(m)]:SetCooldown( 0, 0)
	end

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

	Elementarist.fsCount = m
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
	local _

	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) and (s == 0) then
		Elementarist.lastShockCD = d
	end
	local s, d = GetSpellCooldown(Elementarist.SpellList["Earth Shock"])
	if (d) and (d>0) and (s == 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 (spellInCast) 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

	-- if Tier6 talent is Unleashed Fury Unleash Elements
	if (Elementarist.tier6Talent == 16) then
		if (GetWeaponEnchantInfo()) and (exspell1 ~= Elementarist.SpellList["Unleash Elements"]) and (exspell2 ~= Elementarist.SpellList["Unleash Elements"]) then
			return Elementarist.SpellList["Unleash Elements"];
		end
	-- if Tier6 talent is Elemental Blast use it
	elseif (Elementarist.tier6Talent == 18) then
		if (exspell1 ~= Elementarist.SpellList["Elemental Blast"]) and (exspell2 ~= Elementarist.SpellList["Elemental Blast"]) then
			d = Elementarist:GetSpellCooldownRemaining(Elementarist.SpellList["Flame Shock"])
			if ((d - timeshift) <= 0) then
				return Elementarist.SpellList["Elemental Blast"]
			end
		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"]) and (exspell1 ~= Elementarist.SpellList["Earth Shock"]) and (exspell2 ~= Elementarist.SpellList["Earth 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

	-- Earth shock if Lightning Shield count >=7 and not on cd, and FS debuff remaining > FS cooldown, or FS debuff remaining between FS and FS cd + 2sec and LS count>=5
	if ( (exspell1 ~= Elementarist.SpellList["Earth Shock"]) and (exspell2 ~= Elementarist.SpellList["Earth Shock"]) and (exspell1 ~= Elementarist.SpellList["Flame Shock"]) and (exspell2 ~= Elementarist.SpellList["Flame Shock"]) ) then
		if ( (fsExpiration - GetTime() - timeshift) > Elementarist.lastShockCD ) 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 (	-- Earth Shock is available, and has lightning shield
					(lscount>=7) or 														-- Lightning shield has 7 charge
					( (lscount>=5) and ( (fsExpiration - GetTime() - timeshift) < Elementarist.lastShockCD + 2 ) )	-- or, has 5 or more charges but FS expiration within 2 secs to FS CD
				)
			) then
				return Elementarist.SpellList["Earth Shock"]
			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
	-- 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["Alchemist's Flask Item"]) ~= 0 then
		name = Elementarist:hasBuff("player", Elementarist.SpellList["Flask of the Warm Sun"]);
		if (name == nil) then
			name, _, _, _, _, _, expirationTime = Elementarist:hasBuff("player", Elementarist.SpellList["Alchemist's Flask"], false, Elementarist.CustomIDs["Alchemist's Flask Spell"]);
			if (name == nil) or (expirationTime < 2) then
				if (icon == nil) then
					icon = GetItemIcon(Elementarist.CustomIDs["Alchemist's Flask 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

	-- 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, purge, and AoE on target
	local d

	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
				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
				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

	-- 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<0.5) then
					return Elementarist.SpellList["Earthquake"]
				end
			end
		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
				d = Elementarist:GetSpellCooldownRemaining(Elementarist.SpellList["Purge"])
				if (d<0.5) then
					return Elementarist.SpellList["Purge"]
				end
			end
		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<0.5) then
						return Elementarist.SpellList["Chain Lightning"]
					end
				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:SetTexture(Elementarist.textureList["next"],"")
		Elementarist:SetTexture(Elementarist.textureList["next1"],"")
		Elementarist:SetTexture(Elementarist.textureList["next2"],"")
		Elementarist:SetTexture(Elementarist.textureList["misc"],"")
		Elementarist:SetTexture(Elementarist.textureList["int"],"")

		return
	end

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

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

		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:SetTexture(Elementarist.textureList["next"],GetSpellTexture(spell))

	local _,_,_,_,_,_,ct1=GetSpellInfo(spell)
	if (not ct1) then
		ct1 = 0
	else
		ct1 = (ct1 / 1000)
	end
	local spell1 = Elementarist:NextSpell(ct1,spell)
	Elementarist:SetTexture(Elementarist.textureList["next1"],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:SetTexture(Elementarist.textureList["next2"],GetSpellTexture(spell2))

	if (not ElementaristDB.disableMini) then
		local icon

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

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

		spell = Elementarist:IntSpell()
		Elementarist:FlashSpell(spell,"int")
		Elementarist:SetTexture(Elementarist.textureList["int"],GetSpellTexture(spell))
	end
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