local _, ns = ...
local B, C, L, DB = unpack(ns)
local M = B:GetModule("Misc")

local format, gsub, strsplit, strfind = string.format, string.gsub, string.split, string.find
local pairs, wipe = pairs, wipe
local GetInstanceInfo, PlaySound, print = GetInstanceInfo, PlaySound, print
local IsInRaid, IsInGroup, IsInInstance, IsInGuild = IsInRaid, IsInGroup, IsInInstance, IsInGuild
local UnitInRaid, UnitInParty = UnitInRaid, UnitInParty
local UnitName, Ambiguate, GetTime = UnitName, Ambiguate, GetTime
local GetSpellLink = C_Spell.GetSpellLink
local GetSpellName = C_Spell.GetSpellName
local GetActionInfo, GetMacroSpell, GetMacroItem = GetActionInfo, GetMacroSpell, GetMacroItem
local GetItemInfoFromHyperlink = GetItemInfoFromHyperlink
local C_Map_GetBestMapForUnit = C_Map.GetBestMapForUnit
local C_VignetteInfo_GetVignetteInfo = C_VignetteInfo.GetVignetteInfo
local C_VignetteInfo_GetVignettePosition = C_VignetteInfo.GetVignettePosition
local C_Texture_GetAtlasInfo = C_Texture.GetAtlasInfo
local C_ChatInfo_SendAddonMessage = C_ChatInfo.SendAddonMessage
local C_ChatInfo_RegisterAddonMessagePrefix = C_ChatInfo.RegisterAddonMessagePrefix

local function IsRandomGroup()
	return IsPartyLFG() or C_PartyInfo.IsPartyWalkIn()
end

--[[
	发现稀有/事件时的通报插件
]]
local cache = {}
local isIgnoredZone = {
	[1153] = true,	-- 部落要塞
	[1159] = true,	-- 联盟要塞
	[1803] = true,	-- 涌泉海滩
	[1876] = true,	-- 部落激流堡
	[1943] = true,	-- 联盟激流堡
	[2111] = true,	-- 黑海岸前线
}
local defaultList = {
	[6149] = true, -- 奥妮克希亚龙蛋
	[6699] = true, -- 错放的奇珍，地下堡
}
local isIgnoredIDs = {}

local function isUsefulAtlas(info)
	local atlas = info.atlasName
	if atlas then
		return strfind(atlas, "[Vv]ignette") or (atlas == "nazjatar-nagaevent")
	end
end

function M:RareAlert_UpdateIgnored()
	B.SplitList(isIgnoredIDs, OrzUISetDB["IgnoredRares"], true)

	for id in pairs(defaultList) do
		isIgnoredIDs[id] = true
	end
end

function M:RareAlert_Update(id)
	if id and not cache[id] then
		local info = C_VignetteInfo_GetVignetteInfo(id)
		if not info or not isUsefulAtlas(info) or isIgnoredIDs[info.vignetteID] then return end

		local atlasInfo = C_Texture_GetAtlasInfo(info.atlasName)
		if not atlasInfo then return end
		local tex = B:GetTextureStrByAtlas(atlasInfo)
		if not tex then return end

		--UIErrorsFrame:AddMessage(DB.InfoColor..L["Rare Found"]..tex..(info.name or ""))
		RaidNotice_AddMessage(RaidWarningFrame, "----------   "..tex..(info.name or "").."   ----------", ChatTypeInfo["RAID_WARNING"])
		if C.db["Misc"]["RarePrint"] then
			local currrentTime = OrzUISetDB["TimestampFormat"] == 1 and "|cff00ff00["..date("%H:%M:%S").."]|r" or ""
			local nameString
			local mapID = C_Map_GetBestMapForUnit("player")
			local position = mapID and C_VignetteInfo_GetVignettePosition(info.vignetteGUID, mapID)
			if position then
				local x, y = position:GetXY()
				nameString = format(M.RareString, mapID, x*10000, y*10000, info.name, x*100, y*100, "") --"ID:"..info.vignetteID
			end
			print(currrentTime.." -> "..DB.InfoColor..tex..(nameString or info.name or ""))  --.." → "
		end
		if not C.db["Misc"]["RareAlertInWild"] or M.RareInstType == "none" then
			PlaySoundFile("Interface\\Addons\\OrzUI\\Media\\Sounds\\Dadongda.ogg", "Master")
		end

		cache[id] = true
	end

	if #cache > 666 then wipe(cache) end
end

function M:RareAlert_CheckInstance()
	local _, instanceType, _, _, maxPlayers, _, _, instID = GetInstanceInfo()
	if (instID and isIgnoredZone[instID]) or (instanceType == "scenario" and (maxPlayers == 3 or maxPlayers == 6)) then
		B:UnregisterEvent("VIGNETTE_MINIMAP_UPDATED", M.RareAlert_Update)
	else
		B:RegisterEvent("VIGNETTE_MINIMAP_UPDATED", M.RareAlert_Update)
	end
	M.RareInstType = instanceType
end

function M:RareAlert()
	M.RareString = "|Hworldmap:%d+:%d+:%d+|h[%s] <%.1f, %.1f>%s|h|r"

	if C.db["Misc"]["RareAlerter"] then
		M:RareAlert_UpdateIgnored()
		M:RareAlert_CheckInstance()
		B:RegisterEvent("UPDATE_INSTANCE_INFO", M.RareAlert_CheckInstance)
	else
		wipe(cache)
		B:UnregisterEvent("VIGNETTE_MINIMAP_UPDATED", M.RareAlert_Update)
		B:UnregisterEvent("UPDATE_INSTANCE_INFO", M.RareAlert_CheckInstance)
	end
end

--[[
	闭上你的嘴！
	打断、偷取及驱散法术时的警报
]]
function M:GetMsgChannel()
	return IsRandomGroup() and "INSTANCE_CHAT" or IsInRaid() and "RAID" or "PARTY"
end

local function msgChannel()
	if IsRandomGroup() then
		return "INSTANCE_CHAT"
	elseif IsInRaid() then
		--if warning and (UnitIsGroupLeader("player") or UnitIsGroupAssistant("player") or IsEveryoneAssistant()) then
			--return "RAID_WARNING"
		--else
			return "RAID"
		--end
	elseif IsInGroup(LE_PARTY_CATEGORY_HOME) then
		return "PARTY"
	end
	return "SAY"
end

local infoType = {}

function M:InterruptAlert_Toggle()
	infoType["SPELL_STOLEN"] = C.db["Misc"]["DispellAlert"] and L["Steal"]
	infoType["SPELL_DISPEL"] = C.db["Misc"]["DispellAlert"] and L["Dispel"]
	infoType["SPELL_INTERRUPT"] = C.db["Misc"]["InterruptAlert"] and L["Interrupt"]
	infoType["SPELL_AURA_BROKEN_SPELL"] = C.db["Misc"]["BrokenAlert"] and L["BrokenSpell"]
end

function M:InterruptAlert_IsEnabled()
	for _, value in pairs(infoType) do
		if value then
			return true
		end
	end
end

local blackList = {
	[99] = true,		-- 夺魂咆哮
	[122] = true,		-- 冰霜新星
	[1776] = true,		-- 凿击
	[1784] = true,		-- 潜行
	[5246] = true,		-- 破胆怒吼
	[8122] = true,		-- 心灵尖啸
	[31661] = true,		-- 龙息术
	[33395] = true,		-- 冰冻术
	[64695] = true,		-- 陷地
	[82691] = true,		-- 冰霜之环
	[91807] = true,		-- 蹒跚冲锋
	[102359] = true,	-- 群体缠绕
	[105421] = true,	-- 盲目之光
	[115191] = true,	-- 潜行
	[157997] = true,	-- 寒冰新星
	[197214] = true,	-- 裂地术
	[198121] = true,	-- 冰霜撕咬
	[207167] = true,	-- 致盲冰雨
	[207685] = true,	-- 悲苦咒符
	[226943] = true,	-- 心灵炸弹
	[228600] = true,	-- 冰川尖刺
	[285515] = true,	-- 能量湍流
	[331866] = true,	-- 混沌代理人
	[354051] = true,	-- 轻盈步
	[355689] = true,	-- 山崩
	[386770] = true,	-- 极寒
	[378760] = true,	-- 冰霜撕咬
}

function M:IsAllyPet(sourceFlags)
	if DB:IsMyPet(sourceFlags) or sourceFlags == DB.PartyPetFlags or sourceFlags == DB.RaidPetFlags then
		return true
	end
end

function M:InterruptAlert_Update(...)
	if C.db["Misc"]["LeaderOnly"] and not (UnitIsGroupAssistant("player") or UnitIsGroupLeader("player")) then return end -- only alert for leader, needs review

	local _, eventType, _, sourceGUID, sourceName, sourceFlags, _, _, destName, _, _, spellID, _, _, extraskillID, _, _, auraType = ...
	if not sourceGUID or sourceName == destName then return end

	if UnitInRaid(sourceName) or UnitInParty(sourceName) or M:IsAllyPet(sourceFlags) then
		local infoText = infoType[eventType]
		if infoText then
			local sourceSpellID, destSpellID
			if infoText == L["BrokenSpell"] then
				if auraType and auraType == AURA_TYPE_BUFF or blackList[spellID] then return end
				sourceSpellID, destSpellID = extraskillID, spellID
			    if sourceSpellID and destSpellID then
				    B:SendChatMessage(format(infoText, sourceName..GetSpellLink(sourceSpellID), destName..GetSpellLink(destSpellID)), msgChannel())  --M:GetMsgChannel()
			    end
			elseif infoText == L["Interrupt"] then
				if C.db["Misc"]["OwnInterrupt"] and sourceName ~= DB.MyName and not DB:IsMyPet(sourceFlags) then return end
				sourceSpellID, destSpellID = spellID, extraskillID
				if sourceSpellID and destSpellID then
					B:SendChatMessage(format(infoText, GetSpellLink(destSpellID)), msgChannel())
				end
			else
				if C.db["Misc"]["OwnDispell"] and sourceName ~= DB.MyName and not DB:IsMyPet(sourceFlags) then return end
				sourceSpellID, destSpellID = spellID, extraskillID
				if sourceSpellID and destSpellID then
					B:SendChatMessage(format(infoText, GetSpellLink(destSpellID)), msgChannel())
				end
			end
			if C.db["Misc"]["InterruptSound"] then
				PlaySoundFile("Interface\\Addons\\OrzUI\\Media\\Sounds\\ShutupFool.ogg", "Master")
			end
		end
	end
end

function M:InterruptAlert_CheckGroup()
	if IsInGroup() and (not C.db["Misc"]["InstAlertOnly"] or (IsInInstance() and not IsRandomGroup())) then
		B:RegisterEvent("COMBAT_LOG_EVENT_UNFILTERED", M.InterruptAlert_Update)
	else
		B:UnregisterEvent("COMBAT_LOG_EVENT_UNFILTERED", M.InterruptAlert_Update)
	end
end

function M:InterruptAlert()
	M:InterruptAlert_Toggle()

	if M:InterruptAlert_IsEnabled() then
		M:InterruptAlert_CheckGroup()
		B:RegisterEvent("GROUP_LEFT", M.InterruptAlert_CheckGroup)
		B:RegisterEvent("GROUP_JOINED", M.InterruptAlert_CheckGroup)
		B:RegisterEvent("PLAYER_ENTERING_WORLD", M.InterruptAlert_CheckGroup)
	else
		B:UnregisterEvent("GROUP_LEFT", M.InterruptAlert_CheckGroup)
		B:UnregisterEvent("GROUP_JOINED", M.InterruptAlert_CheckGroup)
		B:UnregisterEvent("PLAYER_ENTERING_WORLD", M.InterruptAlert_CheckGroup)
		B:UnregisterEvent("COMBAT_LOG_EVENT_UNFILTERED", M.InterruptAlert_Update)
	end
end


--[[
	放大餐时叫一叫
]]
local myGUID = UnitGUID("player")
local groupUnits = {["player"] = true, ["pet"] = true}
for i = 1, 4 do
	groupUnits["party"..i] = true
	groupUnits["partypet"..i] = true
end
for i = 1, 40 do
	groupUnits["raid"..i] = true
	groupUnits["raidpet"..i] = true
end

local spellList = {
	[698] = true,		-- 拉人
	[29893] = true,		-- 糖
	[190336] = true,	-- 面包
	[54710] = true,		-- 随身邮箱
	[67826] = true,		-- 基维斯
	[226241] = true,	-- 宁神圣典
	[256230] = true,	-- 静心圣典
	[185709] = true,	-- 焦糖鱼宴
	[199109] = true,	-- 自动铁锤
	[259409] = true,	-- 海帆盛宴
	[259410] = true,	-- 船长盛宴
	[276972] = true,	-- 秘法药锅
	[286050] = true,	-- 鲜血大餐
	[265116] = true,	-- 8.0工程战复
	[308458] = true,	-- 惊异怡人大餐
	[308462] = true,	-- 纵情饕餮盛宴
	[345130] = true,	-- 9.0工程战复
	[307157] = true,	-- 永恒药锅
	[359336] = true,	-- 石头汤锅
	[261602] = true,	-- 凯蒂的印哨
	[376664] = true,	-- 欧胡纳栖枝

	[2825]   = true,	-- 嗜血
	[32182]  = true,	-- 英勇
	[80353]  = true,	-- 时间扭曲
	[90355] = true,		-- 远古狂乱，宠物
	[264667] = true,	-- 原始暴怒，宠物
	[272678] = true,	-- 原始暴怒，宠物掌控
	[178207] = true,	-- 狂怒战鼓
	[230935] = true,	-- 高山战鼓
	[256740] = true,	-- 漩涡战鼓
	[292686] = true,	-- 雷皮之槌
	[390386] = true,	-- 守护巨龙之怒
	[309658] = true,	-- 死亡凶蛮战鼓
	[444257] = true,	-- 掣雷之鼓
	[466904] = true, 	-- 鹞鹰尖啸

	[384893] = true,	-- 足以乱真的救急电缆11.0工程战复
	[453949] = true,	-- 不可抗拒的红色按钮11.0工程战复工具
	[453942] = true,	-- 阿加修理机器人11O   
	[432877] = true,	-- 阿加合剂大锅
	[433292] = true,	-- 阿加药水大锅
	[455960] = true,	-- 全味炖煮
	[457285] = true,	-- 午夜舞会盛宴
	[457302] = true,	-- 特色寿司
	[457487] = true,	-- 丰盛的全味炖煮(战团)
	[462211] = true,	-- 丰盛的特色寿司(战团)
	[462213] = true,	-- 丰盛的午夜舞会盛宴(战团)
}

function M:ItemAlert_Update(unit, castID, spellID)
	if B:IsSecretValue(spellID) then return end
	if C.db["Misc"]["LeaderOnly"] and not (UnitIsGroupAssistant("player") or UnitIsGroupLeader("player")) then return end -- only alert for leader, needs review

	if groupUnits[unit] and spellList[spellID] and (spellList[spellID] ~= castID) then
		B:SendChatMessage(format(L["SpellItemAlertStr"], UnitName(unit), GetSpellLink(spellID) or GetSpellName(spellID)), M:GetMsgChannel())
		spellList[spellID] = castID
	end
end

local bloodLustDebuffs = {
	[57723]  = true, -- 筋疲力尽
	[57724]  = true, -- 心满意足
	[80354]  = true, -- 时空错位
	[264689] = true, -- 疲倦
	[390435]  = true, -- 筋疲力尽，龙希尔
}

function M:CheckBloodlustStatus(...)
	if C.db["Misc"]["LeaderOnly"] and not (UnitIsGroupAssistant("player") or UnitIsGroupLeader("player")) then return end -- only alert for leader, needs review

	local _, eventType, _, sourceGUID, _, _, _, _, _, _, _, spellID = ...
	if eventType == "SPELL_AURA_REMOVED" and bloodLustDebuffs[spellID] and sourceGUID == myGUID then
		B:SendChatMessage(format(L["BloodlustStr"], GetSpellLink(spellID), M.factionSpell), M:GetMsgChannel())
	end
end

function M:ItemAlert_CheckGroup()
	if IsInGroup() then
		B:RegisterEvent("UNIT_SPELLCAST_SUCCEEDED", M.ItemAlert_Update)
	--	B:RegisterEvent("CLEU", M.CheckBloodlustStatus)
	else
		B:UnregisterEvent("UNIT_SPELLCAST_SUCCEEDED", M.ItemAlert_Update)
	--	B:UnregisterEvent("CLEU", M.CheckBloodlustStatus)
	end
end

function M:SpellItemAlert()
	M.factionSpell = DB.MyFaction == "Alliance" and 32182 or 2825
	M.factionSpell = GetSpellLink(M.factionSpell)

	if C.db["Misc"]["SpellItemAlert"] then
		M:ItemAlert_CheckGroup()
		B:RegisterEvent("GROUP_LEFT", M.ItemAlert_CheckGroup)
		B:RegisterEvent("GROUP_JOINED", M.ItemAlert_CheckGroup)
	else
		B:UnregisterEvent("GROUP_LEFT", M.ItemAlert_CheckGroup)
		B:UnregisterEvent("GROUP_JOINED", M.ItemAlert_CheckGroup)
		B:UnregisterEvent("UNIT_SPELLCAST_SUCCEEDED", M.ItemAlert_Update)
	--	B:UnregisterEvent("CLEU", M.CheckBloodlustStatus)
	end
end

-- Incompatible check
local IncompatibleAddOns = {
	["BigFoot"] = true,
	["NDui"] = true,
	["!!!163UI!!!"] = true,
	["DomiRank"] = true, -- NDui Core addon
	["MDGuildBest"] = true, -- NDui Core addon
	["Simulationcraft"] = true,
	["TeleportMenu"] = true,
	["BetterBlizzQueue"] = true,
	["DialogKey"] = true,
	["CanOpener"] = true,
	["LootSpecSwapper"] = true,
	["AutoPotion"] = true,
	["FriendGroups"] = true,
	["ParagonRewards"] = true,
	["RandomHearthToy"] = true,
	["IncentiveProgram"] = true,
	["Paragon"] = true,
	["ParagonReputation"] = true,
	["SpellActivationText"] = true,
	--["BattlePetBinds"] = true,
	["WorldMapQuestRewardIcon"] = true,
	["zMPlus"] = true,
	["AdventureGuideLockouts"] = true,
	["BossesKilled"] = true,
	["AngryKeystones"] = true,
	["LFR_of_the_past"] = true,
	["PremadeSort"] = true,
	["AlreadyKnown"] = true,
	["DragEmAll"] = true,
	["QuickQuest"] = true,
	["Quester"] = true,
	--["BarrelsOEasy"] = true,
	["ExtraQuestButton"] = true,
	["StatPriority"] = true,
	["BetterWorldQuests"] = true,
	["QuickAuctionBuyer"] = true,
	--["CancelPetBattle"] = true,
	--["WorldBossTimers"] = true,
	["DFCN_Status"] = true,
	["BlizzMove"] = true,
	["LifaMpLoop"] = true,
	["RoyMapGuide"] = true,
	["TankHappy"] = true,
	["AppearanceTooltip"] = true,
	["MogPartialSets"] = true,
	["KillingBlow_Enhanced"] = true,
	["MiniKillingBlow"] = true,
	["KeystoneGroupList"] = true,
}
local AddonDependency = {
	["BigFoot"] = "!!!Libs",
}
function M:CheckIncompatible()
	local IncompatibleList = {}
	for addon in pairs(IncompatibleAddOns) do
		if C_AddOns.IsAddOnLoaded(addon) then
			tinsert(IncompatibleList, addon)
		end
	end

	if #IncompatibleList > 0 then
		local frame = CreateFrame("Frame", nil, UIParent)
		frame:SetPoint("TOP", 0, -200)
		frame:SetFrameStrata("HIGH")
		B.CreateMF(frame)
		B.SetBD(frame)
		B.CreateFS(frame, 18, L["FoundIncompatibleAddon"], true, "TOPLEFT", 10, -10)
		B.CreateWatermark(frame)

		local offset = 0
		for _, addon in pairs(IncompatibleList) do
			B.CreateFS(frame, 14, addon, false, "TOPLEFT", 10, -(50 + offset))
			offset = offset + 24
		end
		frame:SetSize(300, 100 + offset)

		local close = B.CreateButton(frame, 16, 16, true, DB.closeTex)
		close:SetPoint("TOPRIGHT", -10, -10)
		close:SetScript("OnClick", function() frame:Hide() end)

		local disable = B.CreateButton(frame, 150, 25, L["DisableIncompatibleAddon"])
		disable:SetPoint("BOTTOM", 0, 10)
		disable.text:SetTextColor(1, .8, 0)
		disable:SetScript("OnClick", function()
			for _, addon in pairs(IncompatibleList) do
				C_AddOns.DisableAddOn(addon)
				if AddonDependency[addon] then
					C_AddOns.DisableAddOn(AddonDependency[addon])
				end
			end
			ReloadUI()
		end)
	end
end

-- Send cooldown status
local function GetRemainTime(second)
	if second > 60 then
		return format("%d:%.2d", second/60, second%60)
	else
		return format("%ds", second)
	end
end

local lastCDSend = 0
function M:SendCurrentSpell(thisTime, spellID)
	local spellLink = GetSpellLink(spellID)
	local chargeInfo = C_Spell.GetSpellCharges(spellID)
	local charges = chargeInfo and chargeInfo.currentCharges
	local maxCharges = chargeInfo and chargeInfo.maxCharges
	local chargeStart = chargeInfo and chargeInfo.cooldownStartTime
	local chargeDuration = chargeInfo and chargeInfo.cooldownDuration

	if charges and maxCharges then
		if B:NotSecretValue(charges) then
			if charges ~= maxCharges then
				local remain = chargeStart + chargeDuration - thisTime
				B:SendChatMessage(format(L["ChargesRemaining"], spellLink, charges, maxCharges, GetRemainTime(remain)), M:GetMsgChannel())
			else
				B:SendChatMessage(format(L["ChargesCompleted"], spellLink, charges, maxCharges), M:GetMsgChannel())
			end
		end
	else
		local cooldownInfo = C_Spell.GetSpellCooldown(spellID)
		local start = cooldownInfo and cooldownInfo.startTime
		local duration = cooldownInfo and cooldownInfo.duration

		if B:NotSecretValue(duration) then
			if start and duration > 0 then
				local remain = start + duration - thisTime
				B:SendChatMessage(format(L["CooldownRemaining"], spellLink, GetRemainTime(remain)), M:GetMsgChannel())
			else
				B:SendChatMessage(format(L["CooldownCompleted"], spellLink), M:GetMsgChannel())
			end
		end
	end
end

function M:SendCurrentItem(thisTime, itemID, itemLink, itemCount)
	local start, duration = C_Item.GetItemCooldown(itemID)
	if start and duration > 0 then
		local remain = start + duration - thisTime
		B:SendChatMessage(format(L["CooldownRemaining"], itemLink.." x"..itemCount, GetRemainTime(remain)), M:GetMsgChannel())
	else
		B:SendChatMessage(format(L["CooldownCompleted"], itemLink.." x"..itemCount), M:GetMsgChannel())
	end
end

function M:AnalyzeButtonCooldown()
	if not self._state_action then return end -- no action for pet actionbar
	if not C.db["Misc"]["SendActionCD"] then return end
	if not IsInGroup() then return end
	if InCombatLockdown() then return end

	local thisTime = GetTime()
	if thisTime - lastCDSend < 1.5 then return end
	lastCDSend = thisTime

	local spellType, id, subType = GetActionInfo(self._state_action)
	local itemCount = GetActionCount(self._state_action)
	if spellType == "spell" then
		M:SendCurrentSpell(thisTime, id)
	elseif spellType == "item" then
		local itemName, itemLink = C_Item.GetItemInfo(id)
		M:SendCurrentItem(thisTime, id, itemLink or itemName, itemCount)
	elseif spellType == "macro" then
		local spellID = subType == "spell" and id or GetMacroSpell(id)
		local _, itemLink = GetMacroItem(id)
		local itemID = itemLink and GetItemInfoFromHyperlink(itemLink)
		if spellID then
			M:SendCurrentSpell(thisTime, spellID)
		elseif itemID then
			M:SendCurrentItem(thisTime, itemID, itemLink, itemCount)
		end
	end
end

function M:SendCDStatus()
	if not C.db["Actionbar"]["Enable"] then return end

	local Bar = B:GetModule("Actionbar")
	if not Bar then return end
	for _, button in pairs(Bar.buttons) do
		button:HookScript("OnMouseWheel", M.AnalyzeButtonCooldown)
	end
end

-- Init
function M:AddAlerts()
	M:RareAlert()
	--M:InterruptAlert()
	M:SpellItemAlert()
	M:CheckIncompatible()
	M:SendCDStatus()
end
M:RegisterMisc("Notifications", M.AddAlerts)


--  CtrlIndicator    Author: 图图   --检测Ctrl是否卡住,Ctrl按下4.5秒之后就会提示
local ctrlCnt, AltCnt, ShiftCnt = 0, 0, 0;
C_Timer.NewTicker(0.1, function()
    if not C.db["Misc"]["CtrlIndicator"] then return end
    if IsControlKeyDown() then ctrlCnt=ctrlCnt+1 else ctrlCnt = 0 end
    if ctrlCnt==45 then
        PlaySoundFile("Interface\\AddOns\\OrzUI\\Media\\Sounds\\Legendary.ogg", "Master")
        UIErrorsFrame:AddMessage("|cFFFF0000".."你的Ctrl可能卡啦!")
        --RaidNotice_AddMessage(RaidWarningFrame, "|cffff0000你的Ctrl可能卡啦!|r", ChatTypeInfo["RAID_WARNING"])
    end
    if IsAltKeyDown() then AltCnt=AltCnt+1 else AltCnt = 0 end
    if AltCnt==45 then
        PlaySoundFile("Interface\\AddOns\\OrzUI\\Media\\Sounds\\Legendary.ogg", "Master")
        UIErrorsFrame:AddMessage("|cFFFF0000".."你的Alt可能卡啦!")
        --RaidNotice_AddMessage(RaidWarningFrame, "|cffff0000你的Alt可能卡啦!|r", ChatTypeInfo["RAID_WARNING"])
    end
    if IsShiftKeyDown() then ShiftCnt=ShiftCnt+1 else ShiftCnt = 0 end
    if ShiftCnt==45 then
        PlaySoundFile("Interface\\AddOns\\OrzUI\\Media\\Sounds\\Legendary.ogg", "Master")
        UIErrorsFrame:AddMessage("|cFFFF0000".."你的Shift可能卡啦!")
        --RaidNotice_AddMessage(RaidWarningFrame, "|cffff0000你的Shift可能卡啦!|r", ChatTypeInfo["RAID_WARNING"])
    end
end)

--------------------------------------------------------------------------- CrazyCatLady
local CrazyCatLady = CreateFrame("Frame")  
CrazyCatLady:RegisterEvent("PLAYER_DEAD")
CrazyCatLady:RegisterEvent("PLAYER_UNGHOST")
CrazyCatLady:SetScript("OnEvent", function(self, event, ...) 
  if not C.db["Misc"]["CrazyCatLady"] then self:UnregisterAllEvents() return end
	if event == "PLAYER_DEAD" then PlaySound(15476) elseif event == "PLAYER_UNGHOST" then StopMusic() end
end)

--[[## Author: Nick Melancon  ## Version: 0.1
local JaniFailedBonusRoll = CreateFrame("FRAME");
JaniFailedBonusRoll:RegisterEvent("BONUS_ROLL_RESULT");
JaniFailedBonusRoll:SetScript("OnEvent", function(self, event, ...)
  if (self == "BONUS_ROLL_RESULT") then
    local rewardType = event;
    if rewardType == "currency" then
      PlaySoundFile("Sound\\Creature\\Jani\\VO_801_Jani_02_M.ogg", "Master")
    end
  end
end);]]