Quantcast
--[[
##############################################################################
_____/\\\\\\\\\\\____/\\\________/\\\__/\\\________/\\\__/\\\\\\\\\\\_       #
 ___/\\\/////////\\\_\/\\\_______\/\\\_\/\\\_______\/\\\_\/////\\\///__      #
  __\//\\\______\///__\//\\\______/\\\__\/\\\_______\/\\\_____\/\\\_____     #
   ___\////\\\__________\//\\\____/\\\___\/\\\_______\/\\\_____\/\\\_____    #
    ______\////\\\________\//\\\__/\\\____\/\\\_______\/\\\_____\/\\\_____   #
     _________\////\\\______\//\\\/\\\_____\/\\\_______\/\\\_____\/\\\_____  #
      __/\\\______\//\\\______\//\\\\\______\//\\\______/\\\______\/\\\_____ #
       _\///\\\\\\\\\\\/________\//\\\________\///\\\\\\\\\/____/\\\\\\\\\\\_#
        ___\///////////___________\///___________\/////////_____\///////////_#
##############################################################################
S U P E R - V I L L A I N - U I   By: Munglunch                              #
##############################################################################
##########################################################
LOCALIZED LUA FUNCTIONS
##########################################################
]]--

--[[ GLOBALS ]]--

local _G = _G;
local unpack    = _G.unpack;
local select    = _G.select;
local pairs     = _G.pairs;
local ipairs    = _G.ipairs;
local type      = _G.type;
local error     = _G.error;
local pcall     = _G.pcall;
local assert    = _G.assert;
local tostring  = _G.tostring;
local tonumber  = _G.tonumber;
local tinsert 	= _G.tinsert;
local string 	= _G.string;
local math 		= _G.math;
local table 	= _G.table;
local bit       = _G.bit;
--[[ STRING METHODS ]]--
local format, sub = string.format, string.sub;
--[[ MATH METHODS ]]--
local abs, ceil, floor, round, random = math.abs, math.ceil, math.floor, math.round, math.random;
--[[ TABLE METHODS ]]--
local tremove, wipe = table.remove, table.wipe;
--[[ BINARY METHODS ]]--
local band, bor = bit.band, bit.bor;

--[[  CONSTANTS ]]--

_G.BINDING_HEADER_SVUIFIGHT = "Supervillain UI: Fight-O-Matic";
_G.BINDING_NAME_SVUIFIGHT_RADIO = "Call Out Incoming";
--[[
##########################################################
GET ADDON DATA
##########################################################
]]--
local PLUGIN = select(2, ...)
local Schema = PLUGIN.Schema;

local SV = _G["SVUI"];
local L = SV.L;

local NewHook = hooksecurefunc;
--[[
##########################################################
GLOBAL SLASH FUNCTIONS
##########################################################
]]--
function SVUISayIncoming()
	local subzoneText = GetSubZoneText()
	local msg = ("{rt8} Incoming %s {rt8}"):format(subzoneText)
	SendChatMessage(msg, "INSTANCE_CHAT")
	return
end
--[[
##########################################################
LOCAL FUNCTIONS
##########################################################
]]--
local function HeadsUpAlarm(...)
	if not CombatText_AddMessage then return end
	CombatText_AddMessage(...)
end
--[[
##########################################################
MUNGLUNCH's FAVORITE EMOTE GENERATOR
##########################################################
]]--
local SpecialEmotes = {
	"ROFL",
	"CACKLE",
	"GIGGLE",
	"GRIN",
	"SMIRK",
	"MOON",
	"LICK",
	"YAWN",
	"FLEX",
	"TICKLE",
	"TAUNT",
	"SHOO",
	"PRAY",
	"SPIT",
	"MOCK",
	"GLOAT",
	"PITY",
	"VIOLIN",
	"BYE",
}

local LowHealthPlayerEmotes = {
	"ROFL",
	"CACKLE",
	"GIGGLE",
	"GRIN",
	"SMIRK",
	"MOON",
	"LICK",
	"YAWN",
	"BITE",
	"NOSEPICK"
}

local LowHealthTargetEmotes = {
	"ROFL",
	"CACKLE",
	"FLEX",
	"TICKLE",
	"TAUNT",
	"SHOO",
	"PRAY",
	"SPIT",
	"MOCK",
	"GLOAT",
	"PITY",
	"VIOLIN",
	"BYE",
}

local KOSEmotes = {
	"THREATEN",
	"CRACK",
	"POINT",
	"GRIN",
	"SMIRK",
	"TAUNT",
	"CHICKEN"
}

local StealthEmotes = {
	"CURIOUS",
	"EYE",
	"GASP",
	"GAZE",
	"MOCK",
	"NOSEPICK",
	"PEER",
	"POINT",
	"READY",
	"STARE",
	"TAP",
}


function SVUIEmote()
	local index = random(1,#SpecialEmotes)
	DoEmote(SpecialEmotes[index])
end

local function LowHealth_PlayerEmote()
	local index = random(1,#LowHealthPlayerEmotes)
	DoEmote(LowHealthPlayerEmotes[index])
end

local function LowHealth_TargetEmote()
	local index = random(1,#LowHealthTargetEmotes)
	DoEmote(LowHealthTargetEmotes[index])
end

local function KOS_Emote()
	local index = random(1,#KOSEmotes)
	DoEmote(KOSEmotes[index])
end

local function Stealth_Emote(name)
	local index = random(1,#StealthEmotes)
	DoEmote(StealthEmotes[index], name)
end
--[[
##########################################################
CORE FUNCTIONS
##########################################################
]]--
local EnemyCache, AlertedCache = {},{}

local playerGUID = UnitGUID('player')
local playerFaction = UnitFactionGroup("player")
local classColor = RAID_CLASS_COLORS
local classColors = SVUI_CLASS_COLORS[SV.class]
local classR, classG, classB = classColors.r, classColors.g, classColors.b
local classA = 0.35
local fallbackColor = {r=1,g=1,b=1}
local ACTIVE_ZONE = ""
--[[ ICONS ]]--
local INFO_ICON = [[Interface\AddOns\SVUI_FightOMatic\artwork\PVP-INFO]]
local UTILITY_ICON = [[Interface\AddOns\SVUI_FightOMatic\artwork\PVP-UTILITIES]]
local RADIO_ICON = [[Interface\AddOns\SVUI_FightOMatic\artwork\PVP-RADIO]]
local SCANNER_ICON = [[Interface\AddOns\SVUI_FightOMatic\artwork\PVP-SCANNER]]
local ICON_FILE = [[Interface\AddOns\SVUI_FightOMatic\artwork\DOCK-PVP]]
local PVP_SAFE = [[Interface\AddOns\SVUI_FightOMatic\artwork\PVP-SAFE]]
local PVP_HELP = [[Interface\AddOns\SVUI_FightOMatic\artwork\PVP-INCOMING]]
local PVP_LOST = [[Interface\WorldMap\Skull_64Red]]
local linkString = "|Hplayer:%s:1|h%s|h"
--[[ BG MAP DATA ]]--
local PVP_NODES = {
	[461] = { --Arathi Basin (5)
		"Stables", "Lumber", "Blacksmith", "Mine", "Farm"
	},
	[935] = { --Deepwind Gorge (3)
		"Center Mine", "North Mine", "South Mine"
	},
	[482] = { --Eye of the Storm (4)
		"Fel Reaver", "Blood Elf", "Draenei", "Mage"
	},
	[736] = { --The Battle for Gilneas (3)
		"LightHouse", "WaterWorks", "Mines"
	},
}

-- local PVP_POI = {
-- 	[401] = { --Alterac Valley (15)
-- 		"Stormpike Aid Station", "Dun Baldar North Bunker", "Dun Baldar South Bunker",
-- 		"Stormpike Graveyard", "Icewing Bunker", "Stonehearth Graveyard",
-- 		"Stonehearth Bunker", "Snowfall Graveyard", "Iceblood Tower",
-- 		"Iceblood Graveyard", "Tower Point", "Frostwolf Graveyard",
-- 		"West Frostwolf Tower", "East Frostwolf Tower", "Frostwolf Relief Hut"
-- 	},
-- 	[935] = { --Deepwind Gorge (2)
-- 		"Horde Cart", "Alliance Cart"
-- 	},
-- 	[482] = { --Eye of the Storm (1)
-- 		"Flag"
-- 	},
-- 	[860] = { --Silvershard Mines (1)
-- 		"Cart"
-- 	},
-- 	[512] = { --Strand of the Ancients (5)
-- 		"Green Emerald", "Blue Sapphire", "Purple Amethyst", "Red Sun", "Yellow Moon"
-- 	},
-- 	[540] = { --Isle of Conquest (5)
-- 		"Quarry", "Hangar", "Workshop", "Docks", "Refinery"
-- 	},
-- 	[856] = { --Temple of Kotmogu (4)
-- 		"Red Orb", "Blue Orb", "Orange Orb", "Purple Orb"
-- 	},
-- 	[626] = { --Twin Peaks (2)
-- 		"Horde Flag", "Alliance Flag"
-- 	},
-- 	[443] = { --Warsong Gulch (2)
-- 		"Horde Flag", "Alliance Flag"
-- 	},
-- }

local Safe_OnEnter = function(self)
	if InCombatLockdown() then return end
	local zone = self.name
	if(zone and zone ~= "") then
		self:SetBackdropBorderColor(1,0.45,0)
		GameTooltip:SetOwner(self, "ANCHOR_TOPLEFT", 0, 4)
		GameTooltip:ClearLines()
		GameTooltip:AddLine(("%s Is Safe!"):format(zone), 1, 1, 1)
		GameTooltip:Show()
	end
end

local Safe_OnLeave = function(self)
	if InCombatLockdown() then return end
	self:SetBackdropBorderColor(0,0,0)
	if(GameTooltip:IsShown()) then GameTooltip:Hide() end
end

local Safe_OnClick = function(self)
	local zone = self.name
	if(zone and zone ~= "") then
		SendChatMessage(("{rt4} %s Is Safe {rt4}"):format(zone), "INSTANCE_CHAT")
	end
end

local Help_OnEnter = function(self)
	if InCombatLockdown() then return end
	local zone = self.name
	if(zone and zone ~= "") then
		self:SetBackdropBorderColor(1,0.45,0)
		GameTooltip:SetOwner(self, "ANCHOR_TOPLEFT", 0, 4)
		GameTooltip:ClearLines()
		GameTooltip:AddLine(("%s Needs Help!"):format(zone), 1, 1, 1)
		GameTooltip:Show()
	end
end

local Help_OnLeave = function(self)
	if InCombatLockdown() then return end
	self:SetBackdropBorderColor(0,0,0)
	if(GameTooltip:IsShown()) then GameTooltip:Hide() end
end

local Help_OnClick = function(self)
	if(self.name and self.name ~= "") then
		local msg = ("{rt8} Incoming %s {rt8}"):format(self.name)
		SendChatMessage(msg, "INSTANCE_CHAT")
	end
end

local function AddEnemyScan(guid, timestamp)
	if(EnemyCache[guid]) then
		return EnemyCache[guid]
	end
	local class, classToken, race, raceToken, sex, name, realm = GetPlayerInfoByGUID(guid)
	local colors = classColor[classToken] or fallbackColor
	EnemyCache[guid] = {
        ["name"] = name,
        ["realm"] = realm,
        ["class"] = class,
        ["race"] = race,
        ["sex"] = sex,
        ["colors"] = colors,
        ["time"] = timestamp
    }
    PLUGIN:ScannerLog(EnemyCache[guid])
    return EnemyCache[guid];
end

local function SaveEnemyScan(guid, timestamp)
	local enemy = EnemyCache[guid]
	if(not enemy) then enemy = AddEnemyScan(guid, timestamp) end
	PLUGIN.cache[guid] = {
        ["name"] = enemy.name,
        ["realm"] = enemy.realm,
        ["class"] = enemy.class,
        ["race"] = enemy.race,
        ["sex"] = enemy.sex,
        ["colors"] = enemy.colors,
        ["time"] = enemy.timestamp
    }
    local msg = ("Killed By: %s"):format(enemy.name);
    HeadsUpAlarm(msg, CombatText_StandardScroll, enemy.colors.r, enemy.colors.g, enemy.colors.b, "sticky");
    PLUGIN:UpdateSummary()
end

local function KilledEnemyHandler(guid)
	local enemy = PLUGIN.cache[guid]
	if(enemy and enemy.name) then
		HeadsUpAlarm(("Killed Mortal Enemy: %s"):format(enemy.name), CombatText_StandardScroll, 0.2, 1, 0.1, "sticky");
	end
	enemy = EnemyCache[guid]
	if(enemy) then
		HeadsUpAlarm(("Killed Enemy: %s"):format(enemy.name), CombatText_StandardScroll, 0.1, 0.8, 0);
	end
end

local function ClearCacheScans()
	wipe(EnemyCache)
	wipe(AlertedCache)
	if(PLUGIN.LOG and PLUGIN.LOG.Output) then PLUGIN.LOG.Output:Clear() end
end

local function ClearSavedScans()
	wipe(PLUGIN.cache)
end

local function EnemyAlarm(name, class, colors, kos)
	if not name then return end
	local inInstance, instanceType = IsInInstance()
	if(instanceType ~= "pvp" and not AlertedCache[name]) then
		local msg
		if(kos) then
			msg = ("Mortal Enemy Detected!: %s"):format(name);
			HeadsUpAlarm(msg, CombatText_StandardScroll, 1, 0, 0)
		elseif(class and colors) then
			msg = ("%s Detected"):format(class);
			HeadsUpAlarm(msg, CombatText_StandardScroll, colors.r, colors.g, colors.b)
	    end
	    AlertedCache[name] = true
	end
end

local function StealthAlarm(spell, name)
	local msg = ("%s Detected!"):format(spell);
    HeadsUpAlarm(msg, CombatText_StandardScroll, 1, 0.5, 0);
    print(("%s has %sed nearby!"):format(name, spell))
    if(self.db.annoyingEmotes) then
    	Stealth_Emote(name)
    end
end

function PLUGIN:UpdateSummary()
	self.Summary:Clear();
	local stored = self.cache;
	local amount = 0
	for _,data in pairs(stored) do
		if type(data) == "table" and data.name and data.class then
			amount = amount + 1;
		end
	end
	self.Summary:AddMessage(("You Have |cffff5500%s|r Mortal Enemies"):format(amount), 0.8, 0.8, 0.8);
end

function PLUGIN:ResetLogs()
	wipe(EnemyCache)
	self.Title:Clear();
	self.Summary:Clear();
	self.LOG.Output:Clear();
	self.Title:AddMessage(("Scanning %s"):format(ACTIVE_ZONE), 1, 1, 0);
	self.Switch:Show()
	local stored = self.cache;
	local amount = 0
	for _,data in pairs(stored) do
		if type(data) == "table" and data.name and data.class then
			amount = amount + 1;
		end
	end
	self.Summary:AddMessage(("You Have |cffff5500%s|r Mortal Enemies"):format(amount), 0.8, 0.8, 0.8)
	collectgarbage("collect")
end

function PLUGIN:PopulateKOS()
	self.Title:Clear();
	self.Summary:Clear();
	self.LOG.Output:Clear();
	self.Title:AddMessage(("Scanning %s"):format(ACTIVE_ZONE), 1, 1, 0);
	self.Switch:Show()
	local stored = self.cache;
	local amount = 0
	for _,data in pairs(stored) do
		if type(data) == "table" and data.name and data.class and data.race then
			amount = amount + 1;
			local nameLink = linkString:format(data.name, data.name)
			local hex = ("%s - %s %s"):format(nameLink, data.race, data.class)
			self.LOG.Output:AddMessage(hex, data.colors.r, data.colors.g, data.colors.b);
		end
	end
	self.Summary:AddMessage(("You Have |cffff5500%s|r Mortal Enemies"):format(amount), 0.8, 0.8, 0.8)
end

function PLUGIN:PopulateScans()
	self.COMM.Unavailable:Hide()
	self.Title:Clear();
	self.Summary:Clear();
	self.LOG.Output:Clear();
	self.Title:AddMessage(("Scanning %s"):format(ACTIVE_ZONE), 1, 1, 0);
	self.Switch:Show()
	local stored = self.cache;
	local amount = 0
	for _,data in pairs(stored) do
		if type(data) == "table" and data.name and data.class then
			amount = amount + 1;
		end
	end
	self.Summary:AddMessage(("You Have |cffff5500%s|r Mortal Enemies"):format(amount), 0.8, 0.8, 0.8);
	for _,data in pairs(EnemyCache) do
		if type(data) == "table" and data.name and data.class and data.race then
			local nameLink = linkString:format(data.name, data.name)
			local hex = ("%s - %s %s"):format(nameLink, data.race, data.class)
			self.LOG.Output:AddMessage(hex, data.colors.r, data.colors.g, data.colors.b);
		end
	end
end

function PLUGIN:PauseScanner()
	if(not self.InPVP) then
		self.Title:Clear();
		self.Summary:Clear();
		self.LOG.Output:Clear();
		self.Title:AddMessage("Scanning Paused", 1, 0.1, 0);
		self.Summary:AddMessage(ACTIVE_ZONE, 1, 0.75, 0);
		self.Switch:Hide()
		self.LOG.Output:AddMessage(" ", 1, 1, 1);
		self.LOG.Output:AddMessage(" ", 1, 1, 1);
		self.LOG.Output:AddMessage("The Enenmy Scanner Will Resume", 0.8, 0.8, 0.8);
		self.LOG.Output:AddMessage("When You Leave This BattleGround", 0.8, 0.8, 0.8);
	else
		self:PopulateScans()
	end
end

function PLUGIN:ScannerLog(enemy)
	if(not enemy.name or not enemy.race or not enemy.class) then return end
    local nameLink = linkString:format(enemy.name, enemy.name)
	local hex = ("%s - %s %s"):format(nameLink, enemy.race, enemy.class)
	self.LOG.Output:AddMessage(hex, enemy.colors.r, enemy.colors.g, enemy.colors.b);
	EnemyAlarm(enemy.name, enemy.class, enemy.colors)
end

function PLUGIN:UpdateCommunicator()
	if(not self.InPVP) then
		local mapID = GetCurrentMapAreaID()
		if(mapID) then
			local points = PVP_NODES[mapID]
			if(points) then
				self.COMM.Unavailable:Hide()
				for i = 1, 5 do
					local nodeName = ("SVUI_PVPNode%d"):format(i)
					local node = _G[nodeName]
					local safe = node.Safe
					local help = node.Help
					if(i <= #points) then
						local name = points[i]
						safe.name = name
						help.name = name
						node.Text:SetText(name)
						node:Show()
					else
						safe.name = ""
						help.name = ""
						node.Text:SetText("")
						node:Hide()
					end
				end
				self.InPVP = true
				self:UnregisterEvent("UPDATE_BATTLEFIELD_SCORE")
				self.Scanning = false
				self:PauseScanner()
			end
		end
	elseif(self.InPVP) then
		self.COMM.Unavailable:Show()
		for i = 1, 5 do
			local nodeName = ("SVUI_PVPNode%d"):format(i)
			local node = _G[nodeName]
			local safe = node.Safe
			local help = node.Help
			safe.name = ""
			help.name = ""
			node.Text:SetText("")
			node:Hide()
		end
		self.InPVP = nil
		self:RegisterEvent("UPDATE_BATTLEFIELD_SCORE")
		self.Scanning = true

		self:PopulateScans()
	end
end

function PLUGIN:UpdateZoneStatus()
	if PLUGIN.ZoneTimer then return end
	local zoneText = GetRealZoneText() or GetZoneText()
	if(not zoneText or zoneText == "") then
		PLUGIN.ZoneTimer = SV.Timers:ExecuteTimer(PLUGIN.UpdateZoneStatus, 5)
		return
	end
	if(zoneText ~= ACTIVE_ZONE) then
		ClearCacheScans()
		ACTIVE_ZONE = zoneText
		PLUGIN.Title:Clear();
		PLUGIN.Title:AddMessage(("Scanning %s"):format(ACTIVE_ZONE), 1, 1, 0);
	end
	local zonePvP = GetZonePVPInfo()
	if(zonePvP == "sanctuary" or zoneText == "") then
		PLUGIN.Scanning = false
	else
		PLUGIN.Scanning = true
		local inInstance, instanceType = IsInInstance()
		if(inInstance and ((instanceType == "party") or (instanceType == "raid"))) then
			PLUGIN.Scanning = false
		elseif (not zonePvP or (zonePvP == "friendly") or (not UnitIsPVP("player"))) then
			PLUGIN.Scanning = false
		elseif(instanceType == "pvp") then
			PLUGIN:PauseScanner()
			PLUGIN.Scanning = false
			if(not PLUGIN.InPVP) then
				PLUGIN:UpdateCommunicator()
			end
		end
	end
	PLUGIN.ZoneTimer = nil
end

local function ParseIncomingLog(timestamp, event, eGuid, eName, pGuid)
	local cached = PLUGIN.cache[eGuid]
	local kos, needsUpdate = false, false

	if(cached) then
		kos = true
	else
		cached = EnemyCache[eGuid]
	end

	if(cached and cached.time) then
		needsUpdate = (cached.time + 60) < timestamp;
	else
		cached = AddEnemyScan(eGuid, timestamp)
		needsUpdate = true
	end

	if(cached) then
		if(needsUpdate) then
			EnemyAlarm(eName, cached.class, cached.colors, kos)
			cached.time = timestamp
		end

		if(pGuid == playerGUID and not AlertedCache[eName]) then
			AlertedCache[eName] = true
			local incoming = ("%s Attacking You!"):format(eName);
			HeadsUpAlarm(incoming, CombatText_StandardScroll, 1, 0.05, 0, "crit")
		end
	end
end

local function GetSourceType(guid)
	if not guid then return end
	local subStr, binStr, bitVal, srcType
	subStr  = guid:sub(3, 5)
	binStr  = ("0x%s"):format(subStr)
	bitVal  = tonumber(binStr)
	if(not bitVal) then return end
	srcType = band(bitVal, 0x00F)
	return srcType
end

function PLUGIN:COMBAT_LOG_EVENT_UNFILTERED(event, timestamp, event, _, srcGUID, srcName, srcFlags, sourceRaidFlags, dstGUID, dstName, dstFlags, destRaidFlags, _, spellName)
	if not srcFlags then return end
	local flagParse = band(srcFlags, COMBATLOG_OBJECT_REACTION_HOSTILE)
	local flagged = flagParse == COMBATLOG_OBJECT_REACTION_HOSTILE

	if(flagged) then
		if(srcGUID and srcName) then
			local isHostile = CombatLog_Object_IsA(srcFlags, COMBATLOG_FILTER_HOSTILE_PLAYERS)
			local srcType = GetSourceType(srcGUID)
			if(srcType and (srcType == 0 or srcType == 8) and isHostile) then
				if(event == "SPELL_AURA_APPLIED" and (spellName == L["Stealth"] or spellName == L["Prowl"])) then
					StealthAlarm(spellName, srcName)
				end
				if(dstGUID == playerGUID) then
					PLUGIN.HitBy = srcGUID
				end
				if(not PLUGIN.Scanning) then return end
				ParseIncomingLog(timestamp, event, srcGUID, srcName, dstGUID)
			end
		end

		if(PLUGIN.Scanning and dstGUID and dstName) then
			local isHostile = CombatLog_Object_IsA(dstFlags, COMBATLOG_FILTER_HOSTILE_PLAYERS)
			local srcType = GetSourceType(dstGUID)
			if(srcType and (srcType == 0 or srcType == 8) and isHostile) then
				ParseIncomingLog(timestamp, event, dstGUID, dstName, srcGUID)
			end
		end
	end

	if(PLUGIN.Scanning and event == "PARTY_KILL") then
		if(srcGUID == playerGUID and dstName) then
			KilledEnemyHandler(dstGUID)
		end
	end
end

function PLUGIN:EventDistributor(event, ...)
	local inInstance, instanceType = IsInInstance()

	if(event == "PLAYER_REGEN_ENABLED") then
		self.HitBy = false;
		if(instanceType == "pvp") then self.Scanning = false end
	else
		if(instanceType ~= "pvp") then
			if(event == "PLAYER_TARGET_CHANGED") then
				if(UnitIsPlayer("target") and UnitIsEnemy("target", "player")) then
					local guid = UnitGUID("target")
					if(not EnemyCache[guid]) then
						local timestamp = time()
						AddEnemyScan(guid, timestamp)
					elseif(self.cache[guid] and self.cache[guid].name) then
						--HeadsUpAlarm("Kill On Sight!", CombatText_StandardScroll, 1, 0, 0, "crit")
						if(self.db.annoyingEmotes) then
							KOS_Emote()
						end
					end
				end
			elseif(event == "PLAYER_DEAD") then
				local guid = self.HitBy
				if(guid and guid ~= "") then
					local stamp = time()
					SaveEnemyScan(guid, stamp)
				end
			end
		else
			self.Scanning = false
		end
	end
end

local onMouseWheel = function(self, delta)
	if (delta > 0) then
		self:ScrollUp()
	elseif (delta < 0) then
		self:ScrollDown()
	end
end

local function MakeLogWindow()
	local frame = CreateFrame("Frame", nil, UIParent)

	frame:SetFrameStrata("MEDIUM")
	frame:SetPoint("TOPLEFT", PLUGIN.Summary, "BOTTOMLEFT",0,0)
	frame:SetPoint("BOTTOMRIGHT", PLUGIN.Docklet, "BOTTOMRIGHT",0,0)
	frame:SetParent(PLUGIN.Docklet)

	local output = CreateFrame("ScrollingMessageFrame", nil, frame)
	output:SetSpacing(4)
	output:SetClampedToScreen(false)
	output:SetFrameStrata("MEDIUM")
	output:SetAllPoints(frame)
	output:SetFont(SV.Media.font.system, 11, "OUTLINE")
	output:SetJustifyH("CENTER")
	output:SetJustifyV("MIDDLE")
	output:SetShadowColor(0, 0, 0, 0)
	output:SetMaxLines(120)
	output:EnableMouseWheel(true)
	output:SetHyperlinksEnabled(true)
	output:SetScript("OnMouseWheel", onMouseWheel)
	output:SetFading(false)
	output:SetInsertMode('TOP')

	output:SetScript("OnHyperlinkEnter", function(self, linkData, link, button)
		local t = link:explode(":")
		local name = t[2] or ""
	    SVUI_TargetScanButton:SetAttribute("macrotext", ("/tar %s"):format(name))
	    SVUI_TargetScanButton:EnableMouse(true)
	end)

	frame.Output = output

	PLUGIN.LOG = frame

	_G["SVUI_FightOMaticTool1"].Window = PLUGIN.LOG
end

local function MakeCommWindow()

	local frame = CreateFrame("Frame", nil, UIParent)

	frame:SetFrameStrata("MEDIUM")
	frame:SetPoint("TOPLEFT", PLUGIN.Summary, "BOTTOMLEFT",0,0)
	frame:SetPoint("BOTTOMRIGHT", PLUGIN.Docklet, "BOTTOMRIGHT",0,0)
	frame:SetParent(PLUGIN.Docklet)

	local fallback = CreateFrame("Frame", nil, frame)
	fallback:SetAllPoints(frame)

	local fbText = fallback:CreateFontString(nil, "OVERLAY")
	fbText:SetAllPoints(fallback)
	fbText:SetFont(SV.Media.font.roboto, 12, "NONE")
	fbText:SetText("Nothing To Broadcast Right Now")

	frame.Unavailable = fallback

	local DOCK_WIDTH = frame:GetWidth();
	local DOCK_HEIGHT = frame:GetHeight();
	local BUTTON_SIZE = (DOCK_HEIGHT * 0.25) - 4;
	local sectionWidth = (DOCK_WIDTH / 6) - 2
	local sectionHeight = (DOCK_HEIGHT / 5) - 2
	local iconSize = sectionHeight * 0.5

	for i = 1, 5 do
		local yOffset = (sectionHeight * (i - 1)) + 2

		local poiName = ("SVUI_PVPNode%d"):format(i)
		local poi = CreateFrame("Frame", poiName, frame)
		poi:SetSize((DOCK_WIDTH - 2), sectionHeight)
		poi:SetPoint("TOP", frame, "TOP", 0, -yOffset)
		poi:SetPanelTemplate("Transparent")

		local safe = CreateFrame("Button", nil, poi)
		safe:SetSize(sectionWidth, sectionHeight)
		safe:SetPoint("RIGHT", poi, "RIGHT", -2, 0)
		safe:SetButtonTemplate()
		safe:SetPanelColor("green")
		local sicon = safe:CreateTexture(nil, "OVERLAY")
		sicon:SetPoint("CENTER", safe, "CENTER", 0, 0)
		sicon:SetSize(iconSize,iconSize)
		sicon:SetTexCoord(0.1, 0.9, 0.1, 0.9)
		sicon:SetTexture(PVP_SAFE)
		safe:SetScript("OnEnter", Safe_OnEnter)
		safe:SetScript("OnLeave", Safe_OnLeave)
		safe:SetScript("OnClick", Safe_OnClick)

		poi.Safe = safe

		local help = CreateFrame("Button", nil, poi)
		help:SetSize(sectionWidth, sectionHeight)
		help:SetPoint("RIGHT", safe, "LEFT", -2, 0)
		help:SetButtonTemplate()
		help:SetPanelColor("red")
		local hicon = help:CreateTexture(nil, "OVERLAY")
		hicon:SetPoint("CENTER", help, "CENTER", 0, 0)
		hicon:SetSize(iconSize,iconSize)
		hicon:SetTexCoord(0.1, 0.9, 0.1, 0.9)
		hicon:SetTexture(PVP_HELP)
		help:SetScript("OnEnter", Help_OnEnter)
		help:SetScript("OnLeave", Help_OnLeave)
		help:SetScript("OnClick", Help_OnClick)

		poi.Help = help

		poi.Text = poi:CreateFontString(nil,"OVERLAY")
		poi.Text:SetFont(SV.Media.font.roboto, 12, "NONE")
		poi.Text:SetPoint("TOPLEFT", poi, "TOPLEFT", 2, 0)
		poi.Text:SetPoint("BOTTOMRIGHT", help, "BOTTOMLEFT", -2, 0)
		poi.Text:SetJustifyH("CENTER")
		poi.Text:SetText("")
		poi:Hide()
	end

	PLUGIN.COMM = frame

	_G["SVUI_FightOMaticTool2"].Window = PLUGIN.COMM

	PLUGIN.COMM:Hide()
end

local function MakeUtilityWindow()
	local frame = CreateFrame("Frame", nil, UIParent)

	frame:SetFrameStrata("MEDIUM")
	frame:SetPoint("TOPLEFT", PLUGIN.Summary, "BOTTOMLEFT",0,0)
	frame:SetPoint("BOTTOMRIGHT", PLUGIN.Docklet, "BOTTOMRIGHT",0,0)
	frame:SetParent(PLUGIN.Docklet)

	local fbText = frame:CreateFontString(nil, "OVERLAY")
	fbText:SetAllPoints(frame)
	fbText:SetFont(SV.Media.font.roboto, 12, "NONE")
	fbText:SetText("Utilities Coming Soon....")

	PLUGIN.TOOL = frame

	_G["SVUI_FightOMaticTool3"].Window = PLUGIN.TOOL

	PLUGIN.TOOL:Hide()
end

local function MakeInfoWindow()
	local frame = CreateFrame("Frame", nil, UIParent)

	frame:SetFrameStrata("MEDIUM")
	frame:SetPoint("TOPLEFT", PLUGIN.Summary, "BOTTOMLEFT",0,0)
	frame:SetPoint("BOTTOMRIGHT", PLUGIN.Docklet, "BOTTOMRIGHT",0,0)
	frame:SetParent(PLUGIN.Docklet)

	local DATA_WIDTH = (frame:GetWidth() * 0.5) - 2;
	local DATA_HEIGHT = frame:GetHeight() - 2;

	local leftColumn = CreateFrame("Frame", "SVUI_FightOMaticInfoLeft", frame)
	leftColumn:Size(DATA_WIDTH, DATA_HEIGHT)
	leftColumn:Point("LEFT", frame, "LEFT", 0, 0)
	leftColumn.lockedOpen = true
	SV.SVStats:NewAnchor(leftColumn, 3, "ANCHOR_CURSOR", nil, "Transparent", true)
	leftColumn:SetFrameLevel(0)

	local rightColumn = CreateFrame("Frame", "SVUI_FightOMaticInfoRight", frame)
	rightColumn:Size(DATA_WIDTH, DATA_HEIGHT)
	rightColumn:Point("LEFT", leftColumn, "RIGHT", 2, 0)
	rightColumn.lockedOpen = true
	SV.SVStats:NewAnchor(rightColumn, 3, "ANCHOR_CURSOR", nil, "Transparent", true)
	rightColumn:SetFrameLevel(0)

	PLUGIN.INFO = frame

	_G["SVUI_FightOMaticTool4"].Window = PLUGIN.INFO

	SV.SVStats.BGPanels = {
		["SVUI_FightOMaticInfoLeft"] = {top = "Honor", middle = "Kills", bottom = "Assists"},
		["SVUI_FightOMaticInfoRight"] = {top = "Damage", middle = "Healing", bottom = "Deaths"}
	}

	SV.SVStats:Generate()

	PLUGIN.INFO:Hide()
end
--[[
##########################################################
DOCK ELEMENT HANDLERS
##########################################################
]]--
local FightOMaticAlert_OnEnter = function(self)
	if InCombatLockdown() then return; end
	self:SetBackdropColor(0.9, 0.15, 0.1)
	GameTooltip:SetOwner(self, "ANCHOR_TOPLEFT", 0, 4)
	GameTooltip:ClearLines()
	GameTooltip:AddLine(self.TText, 1, 1, 0)
	GameTooltip:Show()
end

local FightOMaticAlert_OnLeave = function(self)
	GameTooltip:Hide()
	if InCombatLockdown() then return end
	self:SetBackdropColor(0.25, 0.52, 0.1)
end

local FightOMaticAlert_OnHide = function()
	if InCombatLockdown() then
		SV:AddonMessage(ERR_NOT_IN_COMBAT);
		return;
	end
	SV.Dock.BottomRight.Alert:Deactivate()
end

local FightOMaticAlert_OnShow = function(self)
	if InCombatLockdown() then
		SV:AddonMessage(ERR_NOT_IN_COMBAT);
		self:Hide()
		return;
	end
	SV:SecureFadeIn(self, 0.3, 0, 1)
	SV.Dock.BottomRight.Alert:Activate(self)
end

local FightOMaticAlert_OnMouseDown = function(self)
	-- DO STUFF
	SV:SecureFadeOut(self, 0.5, 1, 0, true)
end

local FightOMaticTool_OnEnter = function(self)
	if InCombatLockdown() then return; end
	self.icon:SetGradient(unpack(SV.Media.gradient.yellow))
	GameTooltip:SetOwner(self, "ANCHOR_TOPLEFT", 0, 4)
	GameTooltip:ClearLines()
	GameTooltip:AddLine(self.TText, 1, 1, 1)
	GameTooltip:Show()
end

local FightOMaticTool_OnLeave = function(self)
	if InCombatLockdown() then return; end
	self.icon:SetGradient("VERTICAL", 0.5, 0.53, 0.55, 0.8, 0.8, 1)
	GameTooltip:Hide()
end

local FightOMaticTool_OnMouseDown = function(self)
	SV:SecureFadeOut(PLUGIN.LOG, 0.5, 1, 0, true)
	SV:SecureFadeOut(PLUGIN.COMM, 0.5, 1, 0, true)
	SV:SecureFadeOut(PLUGIN.TOOL, 0.5, 1, 0, true)
	SV:SecureFadeOut(PLUGIN.INFO, 0.5, 1, 0, true)
	SV:SecureFadeIn(self.Window, 0.3, 0, 1)
	PLUGIN.Title:Clear();
	PLUGIN.Title:AddMessage(self.TTitle, 1, 1, 0);
end

local Scanner_OnMouseDown = function(self)
	SV:SecureFadeOut(PLUGIN.LOG, 0.5, 1, 0, true)
	SV:SecureFadeOut(PLUGIN.COMM, 0.5, 1, 0, true)
	SV:SecureFadeOut(PLUGIN.TOOL, 0.5, 1, 0, true)
	SV:SecureFadeOut(PLUGIN.INFO, 0.5, 1, 0, true)
	SV:SecureFadeIn(self.Window, 0.3, 0, 1)
	PLUGIN:PopulateScans()
end

local Switch_OnEnter = function(self)
	GameTooltip:SetOwner(self, "ANCHOR_TOPLEFT", 0, 4)
	GameTooltip:ClearLines()
	if(self.ShowingKOS) then
		GameTooltip:AddDoubleLine("Click", "Show Scan List", 0.1, 1, 0.2, 1, 1, 1)
	else
		GameTooltip:AddDoubleLine("Click", 'Show "Kill On Sight" List', 0.1, 1, 0.2, 1, 1, 1)
	end
	GameTooltip:AddDoubleLine("[SHIFT] Click", "Clear All Scans", 0.1, 1, 0.2, 1, 1, 1)
	GameTooltip:AddDoubleLine("[CTRL] Click", 'Clear All "Kill On Sight"', 0.1, 1, 0.2, 1, 1, 1)
	GameTooltip:Show()
end

local Switch_OnLeave = function(self)
	GameTooltip:Hide()
end

local Switch_OnClick = function(self, button)
	Switch_OnLeave(self)
	if(IsControlKeyDown()) then
		ClearSavedScans()
		PLUGIN:ResetLogs()
	elseif(IsShiftKeyDown()) then
		ClearCacheScans()
	else
		if(self.ShowingKOS) then
			PLUGIN:PopulateScans()
			self.ShowingKOS = false
		else
			PLUGIN:PopulateKOS()
			self.ShowingKOS = true
		end
	end
	Switch_OnEnter(self)
end
--[[
##########################################################
BUILD FUNCTION
##########################################################
]]--
function PLUGIN:Load()
	self.cache = self.cache or {}

	local ALERT_HEIGHT = 60;
	local DOCK_WIDTH = SV.Dock.BottomRight.Window:GetWidth();
	local DOCK_HEIGHT = SV.Dock.BottomRight.Window:GetHeight();
	local BUTTON_SIZE = (DOCK_HEIGHT * 0.25) - 4;

	self.HitBy = false;
	self.Scanning = false;
	self.InPVP = false

	self.Docklet = SV.Dock:NewDocklet("BottomRight", "SVUI_FightOMaticDock", self.TitleID, ICON_FILE)

	local toolBar = CreateFrame("Frame", "SVUI_FightOMaticToolBar", self.Docklet)
	toolBar:SetWidth(BUTTON_SIZE + 4);
	toolBar:SetHeight((BUTTON_SIZE + 4) * 4);
	toolBar:SetPoint("BOTTOMLEFT", self.Docklet, "BOTTOMLEFT", 0, 0);

	local tbDivider = toolBar:CreateTexture(nil,"OVERLAY")
    tbDivider:SetTexture(0,0,0,0.5)
    tbDivider:SetPoint("TOPRIGHT")
    tbDivider:SetPoint("BOTTOMRIGHT")
    tbDivider:SetWidth(1)

	local tool4 = CreateFrame("Frame", "SVUI_FightOMaticTool4", toolBar)
	tool4:SetPoint("BOTTOM",toolBar,"BOTTOM",0,0)
	tool4:SetSize(BUTTON_SIZE,BUTTON_SIZE)
	tool4.icon = tool4:CreateTexture(nil, 'OVERLAY')
	tool4.icon:SetTexture(INFO_ICON)
	tool4.icon:FillInner(tool4)
	tool4.icon:SetGradient("VERTICAL", 0.5, 0.53, 0.55, 0.8, 0.8, 1)
	tool4.TText = "Stats"
	tool4.TTitle = "Statistics and Information"
	tool4:SetScript('OnEnter', FightOMaticTool_OnEnter)
	tool4:SetScript('OnLeave', FightOMaticTool_OnLeave)
	tool4:SetScript('OnMouseDown', FightOMaticTool_OnMouseDown)

	local tool3 = CreateFrame("Frame", "SVUI_FightOMaticTool3", toolBar)
	tool3:SetPoint("BOTTOM",tool4,"TOP",0,2)
	tool3:SetSize(BUTTON_SIZE,BUTTON_SIZE)
	tool3.icon = tool3:CreateTexture(nil, 'OVERLAY')
	tool3.icon:SetTexture(UTILITY_ICON)
	tool3.icon:FillInner(tool3)
	tool3.icon:SetGradient("VERTICAL", 0.5, 0.53, 0.55, 0.8, 0.8, 1)
	tool3.TText = "Tools"
	tool3.TTitle = "Tools and Utilities"
	tool3:SetScript('OnEnter', FightOMaticTool_OnEnter)
	tool3:SetScript('OnLeave', FightOMaticTool_OnLeave)
	tool3:SetScript('OnMouseDown', FightOMaticTool_OnMouseDown)

	local tool2 = CreateFrame("Frame", "SVUI_FightOMaticTool2", toolBar)
	tool2:SetPoint("BOTTOM",tool3,"TOP",0,2)
	tool2:SetSize(BUTTON_SIZE,BUTTON_SIZE)
	tool2.icon = tool2:CreateTexture(nil, 'OVERLAY')
	tool2.icon:SetTexture(RADIO_ICON)
	tool2.icon:FillInner(tool2)
	tool2.icon:SetGradient("VERTICAL", 0.5, 0.53, 0.55, 0.8, 0.8, 1)
	tool2.TText = "Radio"
	tool2.TTitle = "Radio Communicator"
	tool2:SetScript('OnEnter', FightOMaticTool_OnEnter)
	tool2:SetScript('OnLeave', FightOMaticTool_OnLeave)
	tool2:SetScript('OnMouseDown', FightOMaticTool_OnMouseDown)

	local tool1 = CreateFrame("Frame", "SVUI_FightOMaticTool1", toolBar)
	tool1:SetPoint("BOTTOM",tool2,"TOP",0,2)
	tool1:SetSize(BUTTON_SIZE,BUTTON_SIZE)
	tool1.icon = tool1:CreateTexture(nil, 'OVERLAY')
	tool1.icon:SetTexture(SCANNER_ICON)
	tool1.icon:FillInner(tool1)
	tool1.icon:SetGradient("VERTICAL", 0.5, 0.53, 0.55, 0.8, 0.8, 1)
	tool1.TText = "Scanner"
	tool1.TTitle = "Enemy Scanner"
	tool1:SetScript('OnEnter', FightOMaticTool_OnEnter)
	tool1:SetScript('OnLeave', FightOMaticTool_OnLeave)
	tool1:SetScript('OnMouseDown', Scanner_OnMouseDown)

	local title = CreateFrame("ScrollingMessageFrame", nil, self.Docklet)
	title:SetSpacing(4)
	title:SetClampedToScreen(false)
	title:SetFrameStrata("MEDIUM")
	title:SetPoint("TOPLEFT", toolBar, "TOPRIGHT",0,0)
	title:SetPoint("BOTTOMRIGHT", self.Docklet, "TOPRIGHT",0,-16)
	title:FontManager(SV.Media.font.names, 16, "OUTLINE", "CENTER", "MIDDLE")
	title:SetMaxLines(1)
	title:EnableMouseWheel(false)
	title:SetFading(false)
	title:SetInsertMode('TOP')

	local divider1 = title:CreateTexture(nil,"OVERLAY")
    divider1:SetTexture(0,0,0,0.5)
    divider1:SetPoint("BOTTOMLEFT")
    divider1:SetPoint("BOTTOMRIGHT")
    divider1:SetHeight(1)

    self.Title = title

    local listbutton = CreateFrame("Button", nil, self.Docklet)
    listbutton:SetPoint("TOPLEFT", title, "BOTTOMLEFT",0,0)
	listbutton:SetPoint("BOTTOMRIGHT", title, "BOTTOMRIGHT",0,-14)
	listbutton:SetButtonTemplate(true)
	listbutton.ShowingKOS = false
	listbutton:SetScript("OnEnter", Switch_OnEnter)
	listbutton:SetScript("OnLeave", Switch_OnLeave)
	listbutton:SetScript("OnClick", Switch_OnClick)

	self.Switch = listbutton

    local summary = CreateFrame("ScrollingMessageFrame", nil, self.Docklet)
	summary:SetSpacing(4)
	summary:SetClampedToScreen(false)
	summary:SetFrameStrata("MEDIUM")
	summary:SetPoint("TOPLEFT", title, "BOTTOMLEFT",0,0)
	summary:SetPoint("BOTTOMRIGHT", title, "BOTTOMRIGHT",0,-14)
	summary:FontManager(SV.Media.font.system, 12, "OUTLINE", "CENTER", "MIDDLE")
	summary:SetMaxLines(1)
	summary:EnableMouse(false)
	summary:SetFading(false)
	summary:SetInsertMode('TOP')

	self.Summary = summary

	local divider2 = summary:CreateTexture(nil,"OVERLAY")
    divider2:SetTexture(0,0,0,0.5)
    divider2:SetPoint("BOTTOMLEFT")
    divider2:SetPoint("BOTTOMRIGHT")
    divider2:SetHeight(1)

	MakeLogWindow()
	MakeCommWindow()
	MakeUtilityWindow()
	MakeInfoWindow()

	self.Docklet:Hide()

	self:ResetLogs()

	local targetButton = CreateFrame("Button", "SVUI_TargetScanButton", UIParent, "SecureActionButtonTemplate")
	targetButton:SetAllPoints(self.LOG)
	targetButton:SetFrameLevel(99)
	targetButton:RegisterForClicks("AnyUp")
	targetButton:SetAttribute("type1", "macro")
	targetButton:SetAttribute("macrotext", "/tar")
	targetButton:EnableMouse(false)
	targetButton:HookScript("OnClick", function(this) this:EnableMouse(false) end)

	self:RegisterEvent("COMBAT_LOG_EVENT_UNFILTERED")

	self:RegisterEvent("PLAYER_TARGET_CHANGED", "EventDistributor")
	self:RegisterEvent("PLAYER_REGEN_ENABLED", "EventDistributor")
	self:RegisterEvent("PLAYER_DEAD", "EventDistributor")

	self:RegisterEvent("ZONE_CHANGED", "UpdateZoneStatus")
	self:RegisterEvent("ZONE_CHANGED_NEW_AREA", "UpdateZoneStatus")
	self:RegisterEvent("UNIT_FACTION", "UpdateZoneStatus")

	self:RegisterEvent("PLAYER_ENTERING_WORLD", "UpdateCommunicator")
	self:RegisterEvent("UPDATE_BATTLEFIELD_SCORE", "UpdateCommunicator")

	if(self.db.annoyingEmotes) then
		SVUI_Player.Health.LowAlertFunc = LowHealth_PlayerEmote
		SVUI_Target.Health.LowAlertFunc = LowHealth_TargetEmote
	end
end