﻿
local mod = klhtm
local me = { }
mod.my = me

--[[ 
KTM_My.lua

This file stores data that specifically relates to the current player, and methods that change these data sets.
Most of the data sets are updated by polling, that is frequently recalculating their values.

me.states		- 	a set of flags, e.g. "player in combat" / "afk" / "feigning death"
me.spellranks	-	list of ranks the player has for the important threat causing spells / abilities
me.mods			-	currently active modifiers to threat / abilities from talents, gear, buffs, etc
me.globalthreat	-	buffs that affect all threat the player generates
]]

----------------------------------------------------------------------------------------

me.feigndeathresisttime = 0 -- return value of GetTime()
me.lastfadevalue = 0

-- mod.my.class is the unlocalised lower case representation. e.g. "warrior", "rogue", no matter what locale you are in.
_, me.class = UnitClass("player")
me.class = string.lower(me.class)

-----------------------------------------
--    Special Methods from Core.lua    --
-----------------------------------------

me.parserset = { }

-- onupdate
me.onload = function()
	
	-- make our parser
	local parserdata
	
	for _, parserdata in me.parserconstructor do
		mod.regex.addparsestring(me.parserset, parserdata[1], parserdata[2], parserdata[3])
	end
	
end

me.parserconstructor = 
{
   {"buffstart", "AURAADDEDSELFHELPFUL", "CHAT_MSG_SPELL_PERIODIC_SELF_BUFFS"},	-- "You gain %s."
   {"debuffstart", "AURAADDEDSELFHARMFUL", "CHAT_MSG_SPELL_PERIODIC_SELF_DAMAGE"}, -- "You are afflicated by %s."
	{"weaponbuff", "ITEMENCHANTMENTADDSELFSELF", "CHAT_MSG_SPELL_ITEM_ENCHANTMENTS"}, -- "You cast %s on your %s."
	{"buffend", "AURAREMOVEDSELF", "CHAT_MSG_SPELL_AURA_GONE_SELF"}, -- "%s fades from you."
}

-- The "UI_ERROR_MESSAGE" event is used to detect Feign Death resists.
me.myevents = { "UI_ERROR_MESSAGE", "CHAT_MSG_SPELL_PERIODIC_SELF_DAMAGE", "CHAT_MSG_SPELL_AURA_GONE_SELF", 
	"CHAT_MSG_SPELL_ITEM_ENCHANTMENTS", "CHAT_MSG_SPELL_PERIODIC_SELF_BUFFS"}

me.onevent = function()

	local ability

	-- 1) Check for Feign Death resisted
	if event == "UI_ERROR_MESSAGE" then
		
		if arg1 == ERR_FEIGN_DEATH_RESISTED then
			if mod.out.checktrace("info", me, "feigndeath") then
				mod.out.printtrace("Feign Death Resist message intercepted.")
			end
			me.feigndeathresisttime = GetTime()
		end
		
		return
	end
	
	-- 2) Check for various buffs
	local output = mod.regex.parse(me.parserset, arg1, event)
	
	if output.hit == nil then
		return
	end
	
	me.parserstagetwo[output.parser.identifier](output.final[1], output.final[2], output.final[3], output.final[4])

end

--[[
This is the continuation of the combat log parsing from me.onevent above. From that method, we know the identifier of the event that has been parsed, and all the arguments that go with it. We put all the identifiers into a table whose value is a method to handle that event.
]]
me.parserstagetwo = 
{
	["debuffstart"] = function(buff)	
		if buff == mod.string.get("boss", "spell", "burningadrenaline") then
			me.setstate("burningadrenaline", true)
		end
		
	end,
	
	["buffend"] = function(buff)
		if buff == mod.string.get("boss", "spell", "burningadrenaline") then
			me.setstate("burningadrenaline", false)
			
		elseif buff == mod.string.get("spell", "arcaneshroud") then
			me.setstate("arcaneshroud", false) 
		
		elseif buff == mod.string.get("spell", "fade") then
			-- retract fade
			mod.table.raidthreatoffset = mod.table.raidthreatoffset + me.lastfadevalue
			me.setstate("fade", false)
		end 	
				
	end,
	
	["weaponbuff"] = function(buff, weapon)
		-- only care about rockbiter == shaman
		if me.class ~= "shaman" then
			return
		end
		
		if string.find(buff, mod.string.get("spell", "rockbiter")) then
			
			-- get rank
			local rank
			_, _, rank = string.find(buff, ".-(%d+).-")
			
			-- set rank in mods
			me.mods.shaman.rockbiter = tonumber(rank)
		
		else
		
			-- added an enchant that was not rockbiter
			me.mods.shaman.rockbiter = 0
		end	
		
	end,
	
	["buffstart"] = function(buff)
		if buff == mod.string.get("spell", "arcaneshroud") then -- fetish of the sand reaver activation
			mod.my.setstate("arcaneshroud", true)
		
		elseif buff == mod.string.get("spell", "vanish") then
			mod.table.resetraidthreat()
			
		elseif buff == mod.string.get("spell", "fade") then
		
			local threat = mod.my.ability("fade", "threat")
			mod.table.raidthreatoffset = mod.table.raidthreatoffset - threat
			mod.my.lastfadevalue = threat
			
			mod.my.setstate("fade", true)
		end
	end,
}


me.lastupdatetime = 0.0			-- return value of GetTime()
me.longupdateinterval = 1.0 	-- at least this time in seconds will pass between long updates

me.lastmegaupdatetime = 0.0
me.megaupdateinterval = 10.0

--[[  
Special onupdate() function, called from Core.lua
me.states		-	frequent update
me.spellranks	-	long time update
me.mods			-	long time update
me.globalthreat	-	frequent update
]]
me.onupdate = function()
	
	-- short updates
	me.redostates()
	
	-- check for long updates
	local timenow = GetTime()
	
	-- long updates
	if timenow > me.lastupdatetime + me.longupdateinterval then
		me.lastupdatetime = timenow
		me.redomods()
		me.redoglobalthreat()
	end
	
	-- mega updates
	if timenow > me.lastmegaupdatetime + me.megaupdateinterval then
		me.lastmegaupdatetime = timenow
		me.redospellranks()
	end
		
	-- check status of rockbiter enchant for Shaman, i.e. whether it has run out.
	if GetWeaponEnchantInfo() == nil then
		me.mods.shaman.rockbiter = 0
	end
end

----------------------------------------------------------------------------------------

----------------------------
--	  General Methods     --
----------------------------


--[[ 
mod.my.ability(name, value)
Returns the current value for some property of your spells and abilities.
	<name> is the mod's internal name for the ability.
	<value> is the name of the property, e.g. "threat", "multiplier", "rage". These are the indexes of values in
the mod.data.spells structure.
--> multiplier: return nil if nil
--> rage: return 0 if nil
--> threat / nextattack: throw error if nil

	This method will use me.spellranks to get the data that applies to your specific rank of the ability. Then
it will apply any modifiers from me.mods that are appropriate.
]]
me.ability = function(spellid, parameter)
	
	if (spellid == nil) or (mod.data.spells[spellid] == nil) then
		if mod.out.checktrace("error", me, "ability") then
			mod.out.printtrace(string.format("No ability |cffffff00%s|r found.", tostring(spellid)))
		end
		return 0
	end
	
	local value
	local data = mod.data.spells[spellid]
	
	-- "rage" and "multiplier" parameters are easy, since they 
	if parameter == "rage" then
		if data.rage == nil then
			return 0
		else
			return data.rage
		end
	
	elseif parameter == "multiplier" then
		return data.multiplier -- may be nil
	end
		
	-- to get here, <parameter> is either "threat" or "nextattack"
	
	-- Item abilities only have one value for threat, no ranks.
	if data.class == "item" then
		return data.threat
	end
	
	-- Now check the spell has a known rank
	local spellrank = me.spellranks[spellid]
	
	if spellrank == nil then
		if mod.out.checktrace("error", me, "ability") then
			mod.out.printtrace(string.format("No spell rank defined for |cffffff00%s.", tostring(spellid)))
		end

		return 0
	end
	
	-- Check there is a value in data for our parameter (should always be)
	value = data[spellrank][parameter]
	
	if value == nil then
		if mod.out.checktrace("error", me, "ability") then
			mod.out.printtrace(string.format("No value of |cffffff00%s for rank |cffffff00%s of |cffffff00%s.", tostring(parameter), tostring(spellrankg), tostring(spellid)))
		end
		
		return 0
	end

	-- to get here, there were no errors. we got a value. Now we have to look for class mods that would affect it.
	if spellid == "sunder" then
		if parameter == "rage" then
			value = value + me.mods.warrior.sundercost
		elseif parameter == "threat" then
			value = value * me.mods.warrior.sunderthreat
		end
		
	elseif spellid == "heroicstrike" then
		if parameter == "rage" then
			value = value + me.mods.warrior.heroicstrikecost
		end
		
	elseif spellid == "feint" then
		if parameter == "threat" then
			value = value * me.mods.rogue.feintthreat
		end
		
	elseif spellid == "maul" then
		if parameter == "rage" then
			value = value + me.mods.druid.ferocity
		end
		
	elseif spellid == "swipe" then
		if parameter == "rage" then
			value = value + me.mods.druid.ferocity
		end	
	end
	
	return value
end

--[[
mod.my.testthreat()
Print out your threat properties for debug purposes.
]]
me.testthreat = function()
		
	-- 1) Print out spell ranks
	local key
	local value
	
	for key, value in me.spellranks do
		mod.out.print(string.format(mod.string.get("print", "data", "abilityrank"), key, value))
	end
	
	-- 2) Print out global threat
	mod.out.print(string.format(mod.string.get("print", "data", "globalthreat"), me.globalthreat.value))
	for key, value in me.globalthreat.modifiers do
		if value.isactive == false then
			break
		end
		
		mod.out.print(string.format(mod.string.get("print", "data", "globalthreatmod"), value.reason, value.value))
	end

	-- 3) Print out threat for specific abilities
	if me.class == "priest" then
		mod.out.print(string.format(mod.string.get("print", "data", "multiplier"), UnitClass("player"), mod.string.get("talent", "silentresolve"), 1.0 + me.mods.priest.silentresolve))
		mod.out.print(string.format(mod.string.get("print", "data", "multiplier"), UnitClass("player"), SPELL_SCHOOL5_CAP, me.mods.priest.shadowaffinity))
	
	elseif me.class == "warlock" then
		mod.out.print(string.format(mod.string.get("print", "data", "setactive"), mod.string.get("sets", "nemesis"), 8, mod.out.booltostring(me.mods.warlock.nemesis)))
	
	elseif me.class == "mage" then
		mod.out.print(string.format(mod.string.get("print", "data", "setactive"), mod.string.get("sets", "netherwind"), 3, mod.out.booltostring(me.mods.mage.netherwind)))
		mod.out.print(string.format(mod.string.get("print", "data", "multiplier"), UnitClass("player"), SPELL_SCHOOL6_CAP, 1.0 + me.mods.mage.arcanethreat))
		mod.out.print(string.format(mod.string.get("print", "data", "multiplier"), UnitClass("player"), SPELL_SCHOOL4_CAP, 1.0 + me.mods.mage.frostthreat))
		mod.out.print(string.format(mod.string.get("print", "data", "multiplier"), UnitClass("player"), SPELL_SCHOOL2_CAP, 1.0 + me.mods.mage.firethreat))
		
	elseif me.class == "paladin" then
		mod.out.print(string.format(mod.string.get("print", "data", "multiplier"), UnitClass("player"), mod.string.get("print", "data", "holyspell"), me.mods.paladin.righteousfury))
		mod.out.print(string.format(mod.string.get("print", "data", "healing"), mod.data.threatconstants.healing * me.mods.paladin.healing))
		
	elseif me.class == "warrior" then
		mod.out.print(string.format(mod.string.get("print", "data", "multiplier"), UnitClass("player"), mod.string.get("spell", "sunder"), me.mods.warrior.sunderthreat))
		
	elseif me.class == "druid" then
		mod.out.print(string.format(mod.string.get("print", "data", "multiplier"), UnitClass("player"), mod.string.get("talent", "tranquility"), me.mods.druid.tranquilitythreat))
		
	elseif me.class == "shaman" then
		mod.out.print(string.format(mod.string.get("print", "data", "healing"), mod.data.threatconstants.healing * me.mods.shaman.healing))
		
		if me.mods.shaman.rockbiter > 0 then
			mod.out.print(string.format(mod.string.get("print", "data", "rockbiter"), me.mods.shaman.rockbiter, UnitAttackSpeed("player") * mod.data.rockbiter[me.mods.shaman.rockbiter]))
		end
	end
end


----------------------------------------------------------------------------------------

----------------------------
--	  	  States          --
----------------------------

--[[      
each state is represented by a key-value pair. The key is a string, which is a description of the state,
e.g. "incombat", "afk". The value is a list with properties 
	["value"], boolean, whether the state is on or off
	["lastchange"], return value of GetTime() (seconds + decimal), when the value last changed
	["duration"], OPTIONAL, known duration of the state. This is used to turn the state off just in case
				  the normal mechanism to detect it does not work.
				
To set a state's value, call the method mod.my.setstate(<string name>, <boolean value>). If this causes the
state to change, the method will return non-nil, and an event KLHTM_STATECHANGE_<NAME> will be raised.
]]  
me.states = 
{
	["feigndeath"] = 
	{
		["value"] = false,
		["lastchange"] = 0,
	},
	["arcaneshroud"] = -- from Fetish of the Sandreaver
	{
		["value"] = false,
		["lastchange"] = 0,
		["duration"] = 20.0
	},
	["incombat"] = 
	{
		["value"] = false,
		["lastchange"] = 0,
	},
	["playercharmed"] = -- whether you have been mind controlled
	{
		["value"] = false,
		["lastchange"] = 0,
	},
	["burningadrenaline"] = 
	{
		["value"] = false,
		["lastchange"] = 0,
		["duration"] = 20.0,
	},
	["afk"] = 
	{
		["value"] = false,
		["lastchange"] = 0,
	},
	["fade"] = 
	{
		["value"] = false,
		["lastchange"] = 0,
		["duration"] = 10.0,
	},
}


--[[ 
mod.my.setstate(state, value)
Sets the value of one of the state variables above.
<state> is a string, a key to the me.states list, e.g. "afk", "incombat", etc.
<value> is a boolean, true or false.
Return: true if the set represents a change, otherwise nil
]]
me.setstate = function(state, value)
	
	-- verify <state>
	if me.states[state] == nil then
		if mod.out.checktrace("error", me, "state") then
			mod.out.printtrace(string.format("There is no state |cffffff00%s|r.", tostring(state)))
		end
		return
	end
	
	-- update
	if me.states[state].value ~= value then
		me.states[state].value = value
		me.states[state].lastchange = GetTime()
		
		return true
	end

end

--[[
me.redostates()
Checks for changes to state variables that we poll for (e.g. "incombat" / "ischarmed").
Also checks for states that have run out, but whose end events were not detected (e.g. burning adrenaline). This
last should not be relied upon though.
]]
me.redostates = function()
	
	-- hunter: check for FD
	if mod.my.class == "hunter" then
		
		-- Check for feign death debuff
		local currentfd = mod.data.isbuffpresent("Interface\\Icons\\Ability_Rogue_FeignDeath")
		
		if me.setstate("feigndeath", currentfd) and (currentfd == true) then
			
			-- first check for resist.
			if math.abs(me.feigndeathresisttime - GetTime()) < 1.0 then
				--resisted. Nothing happens
				
				if mod.out.checktrace("info", me, "feigndeath") then
					mod.out.printtrace("feigndeathdebug", "Feign Death was resisted!")
				end
				
			else -- wipe threat
			
				mod.table.resetraidthreat()
				
			end
			
		end
	end
 
	-- update charmed state
	if UnitIsCharmed("player") == 1 then
		me.setstate("playercharmed", true)
	else
		me.setstate("playercharmed", false)
	end

	-- update combat state
	if UnitAffectingCombat("player") == 1 then
		
		if me.setstate("incombat", true) then
			
			-- player has just joined combat
			if mod.out.checktrace("info", me, "incombat") then
				mod.out.printtrace("You entered combat.")
			end
			
			mod.table.resetraidthreat()
			
			local key
			local value
		
			for key, value in mod.combat.recentattacks do
				mod.table.raidthreatoffset = mod.table.raidthreatoffset + value[2]
			end	
		end
	
	else
		if (me.states.playercharmed.value == true) or (GetTime() - me.states.playercharmed.lastchange < 2.0) then
			me.setstate("incombat", true) -- should not be a change
		else
			if me.setstate("incombat", false) then
				if mod.out.checktrace("info", me, "incombat") then
					mod.out.printtrace("You left combat.")
				end
				me.lastfadevalue = 0 -- stop fade
			end 
		end
	end
	
	-- update states with a "duration" parameter. They are all designed to deactivate on their own, but e.g.
	-- burning adrenaline isn't working, so we put in this mechanism as a backup
	local state
	local data
	
	for state, data in me.states do
		if data.duration and (data.value == true) and (GetTime() > data.lastchange + data.duration + 1.0) then
			me.setstate(state, false)
			
			if mod.out.checktrace("info", me, "state") then
				mod.out.printtrace(string.format("Deactivated the state %s because it had passed its duration.", state))
			end
		end
	end 
end

----------------------------------------------------------------------------------------

----------------------------
--      Spell Ranks       --
----------------------------

--[[
	A collection of key-value pairs. The key is a string, the name of a spell / ability, e.g. "Sunder Armor". It
is localised. The value is an integer, the rank of that spell you have.
	The only spells recorded are those which are cause or remove threat by known amounts, i.e. Sunder Armor, 
Revenge, Maul, Feint, etc.
	We want to know the what rank of the spell the player has, because most abilities have different numbers for 
different spell ranks. e.g. Heroic Strike does more damage and threat for each new rank. Some players will have the
extra rank from AQ20, some will not.
	These ranks will probably not change over the course of a session, but we poll just in case.
]]
me.spellranks = { } 

--[[
me.redospellranks()
Redetermines the ranks of all special threat abilities you have.
]]
me.redospellranks = function()
	
	local index = 0
	local name, rankstring, rank, spellid
	local rankpattern = mod.string.get("misc", "spellrank") -- e.g. "Rank %d" in english.
		
	while true do
		index = index + 1
		name, rankstring = GetSpellName(index, "spell")
		
		if name == nil then
			break
		end
		
		-- get the internal spell ID
		spellid = mod.string.unlocalise("spell", name)
		
		if spellid and mod.data.spells[spellid] then
			rank = 0
			
			_, _, rank = string.find(rankstring, rankpattern)
			if rank then
				me.spellranks[spellid] = rank
			end
		end
	end
	
end


----------------------------------------------------------------------------------------

---------------------------------------
--    Threat And Ability Modifers    --
---------------------------------------

me.mods = 
{
	["warrior"] = 
	{
		["defiance"] = 0.0, 			-- modifier to global threat. e.g. "+0.15" for 5 points.
		["sunderthreat"] = 1.0, 	-- multiplier. e.g. "1.15" for 8/8 Might.
		["sundercost"] = 0.0,		-- modifier of rage cost. e.g. "-3" for 3/3 improved sunder talent.
		["heroicstrikecost"] = 0.0,-- modifier of rage cost. e.g. "-3" for 3/3 improved heroic strike talent.
		["impale"] = 0.0 				-- critical strike bonus damage for abilities. e.g. "0.2" for 2/2 impale talent.
	},
	["rogue"] = 
	{
		["feintthreat"] = 1.0,		-- multiplier. e.g. "1.25" for 5/8 Bloodfang.
	},
	["druid"] = 
	{
		["feralinstinct"] = 0.0, 	-- defiance for druids.
		["savagefury"] = 1.0, 		-- multiplier to damage for druid abilities
		["subtlety"] = 1.0, 			-- multiplier to healing threat. e.g. "0.8" for all talents.
		["ferocity"] = 0.0, 			-- modifier to rage cost of maul and swipe. e.g. "-5" for all talents.
		["tranquilitythreat"] = 1.0-- multiplier, from Improved Tranquility Talent
	},
	["mage"] = 
	{
		["arcanist"] = false, 		-- 8 piece bonus
		["netherwind"] = false, 	-- 3 piece
		["arcanethreat"] = 0.0,		-- almost a modifier to global threat (spells only) e.g. "-0.4" for 2/2 talent points.
		["frostthreat"] = 0.0, 		-- almost a modifier to global threat (spells only) e.g. "-0.3" for 3/3 talent points.
		["firethreat"] = 0.0, 		-- almost a modifier to global threat (spells only) e.g. "-0.3" for 2/2 talent points.
	},
	["warlock"] = 
	{
		["masterdemo"] = 0.0, 		-- modifier to global threat. e.g. "-0.2" for imp out, 5/5.
		["nemesis"] = false,			-- 8 piece bonus
	},
	["priest"] =
	{
		["silentresolve"] = 0.0, 	-- almost a modifier to global threat (spells only) e.g. "-0.2" for fully talented.
		["shadowaffinity"] = 1.0, 	-- multiplier for shadow damage. e.g. "0.8".
	},
	["paladin"] = 
	{
		["righteousfury"] = 1.0, 	-- multiplier for threat from holy damage / abilities.
		["healing"] = 0.5, 			-- this is actually constant, but really fits best in this variable.
	},
	["shaman"] = 
	{
		["rockbiter"] = 0, 			-- current rank of rockbiter that is active, 0 for none.
		["healing"] = 1.0, 			-- multiplier. e.g. 0.85 for 3/3 Healing Grace.
	}
}

me.redomods = function()
	
	-- talent / gear searching
	local info
	local rank
	local key
	local value
	
	-- warrior
	if mod.my.class == "warrior" then

		-- might 8/8
		if mod.data.getsetpieces("might") == 8 then
			me.mods.warrior.sunderthreat = 1.15
		else
			me.mods.warrior.sunderthreat = 1.0
		end

		-- improved sunder armor
		rank = mod.data.gettalentrank("sunder")
		me.mods.warrior.sundercost = -rank

		-- improved heroic strike
		rank = mod.data.gettalentrank("heroicstrike")
		me.mods.warrior.heroicstrikecost = -rank

		-- defiance
		rank = mod.data.gettalentrank("defiance")
		me.mods.warrior.defiance = 0.03 * rank

		-- impale
		rank = mod.data.gettalentrank("impale")
		me.mods.warrior.impale = 0.05 * rank

	-- rogue
	elseif mod.my.class == "rogue" then
		
		if mod.data.getsetpieces("bloodfang") > 4 then
			me.mods.rogue.feint = 1.25
		else
			me.mods.rogue.feint = 1.0
		end
	
	-- priest
	elseif mod.my.class == "priest" then
		
		-- silent resolve
		rank = mod.data.gettalentrank("silentresolve") 
		me.mods.priest.silentresolve = -0.04 * rank
				
		-- shadow affinity
		rank = mod.data.gettalentrank("shadowaffinity")
		me.mods.priest.shadowaffinity = 1 - rank * 0.25 / 3
	
	-- druid
	elseif mod.my.class == "druid" then
		
		-- subtlety
		rank = mod.data.gettalentrank("druidsubtlety")
		me.mods.druid.subtlety = 1 - 0.04 * rank
			
		-- feral instinct
		rank = mod.data.gettalentrank("feralinstinct")
		me.mods.druid.feralinstinct = 0.03 * rank
		
		-- ferocity
		rank = mod.data.gettalentrank("ferocity")
		me.mods.druid.ferocity = -rank
		
		-- savage fury
		rank = mod.data.gettalentrank("savagefury")
		me.mods.druid.savagefury = rank * 0.1
		
		-- improved tranquility
		rank = mod.data.gettalentrank("tranquility")
		me.mods.druid.tranquilitythreat = 1.0 - rank * 0.4
		
	-- mage
	elseif mod.my.class == "mage" then
		
		-- arcanist 8 piece
		if mod.data.getsetpieces("arcanist") == 8 then
			me.mods.mage.arcanist = true
		else
			me.mods.mage.arcanist = false
		end
		
		-- arcane subtelty
		rank = mod.data.gettalentrank("arcanesubtlety")
		me.mods.mage.arcanethreat = 0.0 - 0.2 * rank

		-- frost channeling
		rank = mod.data.gettalentrank("frostchanneling")
		me.mods.mage.frostthreat = 0.0 - 0.1 * rank
		
		-- burning soul
		rank = mod.data.gettalentrank("burningsoul")
		me.mods.mage.firethreat = 0.0 - 0.15 * rank
		
		-- netherwind 3 piece
		if mod.data.getsetpieces("netherwind") > 2 then
			me.mods.mage.netherwind = true
		else
			me.mods.mage.netherwind = false
		end
	
	-- warlock
	elseif mod.my.class == "warlock" then
		-- master demonologist
		rank = mod.data.gettalentrank("masterdemonologist")
		
		-- check for imp
		if UnitCreatureFamily("pet") == mod.string.get("misc", "imp") then
			me.mods.warlock.masterdemo = -0.04 * rank
		else
			me.mods.warlock.masterdemo = 0
		end
		
		-- nemesis 8 piece
		if mod.data.getsetpieces("nemesis") == 8 then
			me.mods.warlock.nemesis = true
		else
			me.mods.warlock.nemesis = false
		end
		
	-- shaman
	elseif mod.my.class == "shaman" then
		
		-- healing grace
		rank = mod.data.gettalentrank("healinggrace")
		me.mods.shaman.healing = 1.0 - 0.05 * rank
		
	-- paladin
	elseif mod.my.class == "paladin" then
		
		local multi = 1.0
		
		-- righteous fury buff:
		if mod.data.isbuffpresent("Interface\\Icons\\Spell_Holy_SealOfFury") then
			multi = multi + 0.6
		
			-- talents
			rank = mod.data.gettalentrank("righteousfury")
			multi = multi + (0.5 * rank / 3)
		end
		
		me.mods.paladin.righteousfury = multi
	end
	
end


----------------------------------------------------------------------------------------

----------------------------
--  Global Threat Mods    --
----------------------------

--[[
	There are certain threat modifiers that are applied to all threat done, and that stack additively.
For instance with the "Blessing of Salvation" buff, all your threat is reduced by 30%. Wit the Arcanist
8 piece bonus, threat is reduced by 15%, but these bonuses add directly, so that with both bonuses, your 
threat is reduced by 45%.
	mod.my.globalthreat provides a list of all your global bonuses that are currently active. The data is
designed to be easily printed to the user.
	The data is updated frequently, because the modifiers can come on and off at any time. This means once
every OnUpdate, roughly 40 times per second. As a result we take pains to avoid the creation of new lists,
which would otherwise generate excess heap memory.
	me.globalthreat.modifiers is an ARRAY, i.e. a list keyed by [1], [2], [3], etc. Each item of the array the value
is a list with properties
	["value"] - the fractional reduction in threat. -30% from Blessing of Salvation would be "-0.3".
	["reason"] - a description of the effect, for the user's benefit. e.g. "Blessing of Salvation".
	["isactive"] - if false, this value and all further values should be ignored. Since we don't want to keep
	             recreating lists in a frequently called procedure, when a threat modifier is no longer active,
				 <isactive> is set to false, and that value is ignored. A newly added buff will overwrite the
				other data fields, but set <isactive> to true.
	There is one final part me.globalthreat, a key-value pair. The key is "value" and the value is your total
modifier, i.e. the sum of all the modifiers. For a normal character with no buffs / talents / items, this
value would be 1.0, e.g.
	me.globalthreat = 
{
	["value"] = 1.0,
	["modifiers"] = 
	{
		[1] = 
		{
			["value"] = 1.0,
			["reason"] = "Base Value",
			["isactive"] = true,
		}
	}
}
]]
me.globalthreat = 
{
	["value"] = 0.0,
	["modifiers"] = { }
}

--[[ 
me.modifyglobalthreat(value, reason)
Adds a global threat modifier to me.globalthreat.
<value> and <reason> are as defined in me.globalthreat (see comments above).
This method will first look for a value in the me.globalthreat array that is labeled inactive (unused memory) 
and write the values there. Otherwise it will create a new value and append it to the array.
Heap memory creation occurs a maximum of n times, where n is the largest number of +- threat buffs you get.
This is on the order of 5, and for the whole running time, so negligable.
]]
me.modifyglobalthreat = function(value, reason)
	
	-- update the total / sum
	
	-- 1.12: multiplicative threat
	if mod.isnewwowversion then	
		me.globalthreat.value = me.globalthreat.value * (1.0 + value)
		value = 1.0 + value
	else
		me.globalthreat.value = me.globalthreat.value + value
	end
	
	-- look for an unused array position
	local x
	local count = table.getn(me.globalthreat.modifiers)
	
	for x = 1, count do
		
		if me.globalthreat.modifiers[x].isactive == false then
			
			-- found an inactive array index. Activate it and write values
			me.globalthreat.modifiers[x].value = value
			me.globalthreat.modifiers[x].reason = reason
			me.globalthreat.modifiers[x].isactive = true 
			return
		end
	end
	
	-- all the slots in the array are being used. Add a new value to the end
	table.insert(me.globalthreat.modifiers, {["value"] = value, ["reason"] = reason, ["isactive"] = true})
	
end

--[[ 
me.redoglobalthreat()
Recalculates all your global threat modifiers.
]]
me.redoglobalthreat = function()

	-- 1) reset
	local x
	for x = 1, table.getn(me.globalthreat.modifiers) do
		me.globalthreat.modifiers[x].isactive = false
	end
	
	-- 1.12 change: all threat is multiplicative
	if mod.isnewwowversion then
		me.globalthreat.value = 1.0
		me.modifyglobalthreat(0.0, mod.string.get("threatmod", "basevalue"))
		
	else
		me.globalthreat.value = 0.0
		me.modifyglobalthreat(1.0, mod.string.get("threatmod", "basevalue"))
	end
	
	-- 2) rebuild
	
	-- Tranquil Air
	if mod.data.isbuffpresent("Interface\\Icons\\Spell_Nature_Brilliance") == true then
		me.modifyglobalthreat(-0.2, mod.string.get("threatmod", "tranquilair"))
	end
	
	-- Blessing of Salvation
	if mod.data.isbuffpresent("Interface\\Icons\\Spell_Holy_SealOfSalvation") == true then
		me.modifyglobalthreat(-0.3, mod.string.get("threatmod", "salvation"))
		
	elseif mod.data.isbuffpresent("Interface\\Icons\\Spell_Holy_GreaterBlessingofSalvation") == true then
		me.modifyglobalthreat(-0.3, mod.string.get("threatmod", "salvation"))
	end
	
	-- Burning Adrenaline
	if me.states["burningadrenaline"].value == true then
		me.modifyglobalthreat(-0.75, mod.string.get("boss", "spell", "burningadrenaline"))
	end
	
	-- Fetish of the Sand Reaver
	if me.states["arcaneshroud"].value == true then
		me.modifyglobalthreat(-0.7, mod.string.get("spell", "arcaneshroud"))
	end
		
	local linkstring
	local enchant
	
	-- +2% threat enchant to gloves
	linkstring = GetInventoryItemLink("player", 10) or "" 
	_, _, enchant = string.find(linkstring, ".-item:%d+:(%d+).*")
	
	if enchant == "2613" then
		me.modifyglobalthreat(0.02, mod.string.get("threatmod", "glovethreatenchant"))
	end
	
	-- -2% threat enchant to back
	linkstring = GetInventoryItemLink("player", 15) or ""
	_, _, enchant = string.find(linkstring, ".-item:%d+:(%d+).*")
	
	if enchant == "2621" then
		me.modifyglobalthreat(-0.02, mod.string.get("threatmod", "backthreatenchant"))
	end
	
	local stance
	
	-- Warrior
	if mod.my.class == "warrior" then
		stance = me.getstanceindex()
		
		if stance == 1 then
			me.modifyglobalthreat(-0.2, mod.string.get("threatmod", "battlestance"))
			
		elseif stance == 2 then
			me.modifyglobalthreat(0.3, mod.string.get("threatmod", "defensivestance"))
			me.modifyglobalthreat(me.mods.warrior.defiance, mod.string.get("talent", "defiance"))
	
		elseif stance == 3 then
			me.modifyglobalthreat(-0.2, mod.string.get("threatmod", "berserkerstance"))
		end
		
	-- Druid Stances
	elseif mod.my.class == "druid" then
		stance = me.getstanceindex()
		
		if stance == 1 then
			me.modifyglobalthreat(0.3, mod.string.get("threatmod", "bearform"))
			me.modifyglobalthreat(me.mods.druid.feralinstinct, mod.string.get("talent", "feralinstinct"))
			
		elseif stance == 3 then
			me.modifyglobalthreat(-0.2, mod.string.get("threatmod", "catform"))
			
		end
	
	-- Rogue
	elseif mod.my.class == "rogue" then
		me.modifyglobalthreat(-0.2, UnitClass("player"))
		
	-- Arcanist
	elseif (mod.my.class == "mage") and (me.mods.mage.arcanist == true) then
		me.modifyglobalthreat(-0.15, mod.string.get("sets", "arcanist") .. " 8/8")
		
	-- Warlock
	elseif (mod.my.class == "warlock") and (me.mods.warlock.masterdemo ~= 0) then
		me.modifyglobalthreat(me.mods.warlock.masterdemo, mod.string.get("talent", "masterdemonologist"))
	end	
end


--[[ 
me.getstanceindex()
For Druids and Warriors, returns an integer saying which stance you are in.
]]
me.getstanceindex = function()
	
	local x = 0
	local isactive
	local texture 
	
	while true do
		x = x + 1
		texture, _, isactive = GetShapeshiftFormInfo(x)
		
		if texture == nil then
			return 0
		end
		
		if isactive == 1 then
			return x
		end
	end
	
end