view ICU.lua @ 13:ed15bd476bbb beta

+ Added notification when checking is cancelled due to a disabled zone. Fixed a bug causing players to sometimes show as away/not ready more than once (event fire a third time if player is targeted and in your raid sub-group). Fixed chat filter for outgoing whispers sent by ICU.
author Xiiph
date Mon, 21 Feb 2011 23:58:36 +0100
parents 2d952bc9897a
children 21cefa363c73
line wrap: on
line source
local L = LibStub("AceLocale-3.0"):GetLocale("ICU")
if not L then return end

local icu = LibStub("AceAddon-3.0"):NewAddon("ICU", "AceEvent-3.0", "AceTimer-3.0");

-- Move global namespace to local
local _G = _G;

-- UTF8
local raidMap = {
	bwd = L["Blackwing Descent"],
	bot = L["The Bastion of Twilight"],
	tfw = L["Throne of the Four Winds"],
	bh = L["Baradin Hold"],
}

-- Accepted flasks
local flaskID = {
	79469, -- Flask of Steelskin
    79470, -- Flask of the Draconic Mind
    79471, -- Flask of the Winds
    79472, -- Flask of Titanic Strength
    94160, -- Flask of Flowing Water
	
	92725, -- Cauldron Agility
	92729, -- Cauldron Stamina
	92730, -- Cauldron Intellect
	92731, -- Cauldron Strength
	};

-- Accepted well-fed buffs
local foodID = {
	87545, -- 90 Strength
	87546, -- 90 Agility
	87547, -- 90 Intellect
	87548, -- 90 Spirit
	87549, -- 90 Mastery
	87550, -- 90 Hit
	87551, -- 90 Crit
	87552, -- 90 Haste
	87635, -- 90 Expertise
	87554, -- 90 Dodge
	87555, -- 90 Parry
	};


local minFlaskDuration = 10;
local noFlask, noFood, hasLowDuration = {},{},{};
local notReady, notReadyAFK, responders = {},{},{};
local disabledZone = false;

local defaults = {
	profile = {
		checkFlask = true,
		checkFood = true,
		checkLowFlaskDuration = true,
		remindRaider = true,
		reportResults = true,
		reportDestination = "OFFICER",
		manualCheckOnly = false,
		checkAfterFinish = true,
		advertiseICU = true,
		noFoodMessage = L["Well Fed reminder!"],
		noFlaskMessage = L["Flask reminder!"],
		lowFlaskDurationMessage = L["Your Flask runs out in less than 10 minutes!"],
		disabledZones = {},
		enableAllZones = true,
		announceReadyCheck = true,
	}
}

function icu:OnInitialize()
    self.db = LibStub("AceDB-3.0"):New("icuDB", defaults, true)
	
	self.db.RegisterCallback(self, "OnProfileChanged", "refreshConfig")
	self.db.RegisterCallback(self, "OnProfileCopied", "refreshConfig")
	self.db.RegisterCallback(self, "OnProfileReset", "refreshConfig")
	
	self.options = self:getOptions()
	
	local AceConfig = LibStub("AceConfig-3.0")
	local AceConfigDialog = LibStub("AceConfigDialog-3.0")
	
	AceConfig:RegisterOptionsTable("ICU", self.options, {"icu"})
	
	AceConfigDialog:AddToBlizOptions("ICU", nil, nil, "general")
	AceConfigDialog:AddToBlizOptions("ICU", "Whispers","ICU","messages")
	AceConfigDialog:AddToBlizOptions("ICU", "Disabled Zones","ICU","zones")
	AceConfigDialog:AddToBlizOptions("ICU", "Profiles","ICU","profile")
	
	-- Ready check confirm event registration
	if self.db.profile.announceReadyCheck then
		self:RegisterEvent("READY_CHECK_CONFIRM");
	end
	
	-- Ready check event registration
	if not self.db.profile.manualCheckOnly then
		icu:RegisterEvent("READY_CHECK");
	end
end

function icu:refreshConfig()
	--print("ICU: Config refreshing ...");
	
	if self.db.profile.manualCheckOnly then
		icu:UnregisterEvent("READY_CHECK");
	else
		icu:RegisterEvent("READY_CHECK");
	end
	
	if not self.db.profile.announceReadyCheck then
		icu:UnregisterEvent("READY_CHECK_CONFIRM");
	else
		icu:RegisterEvent("READY_CHECK_CONFIRM");
	end
	
	--print("ICU: Config refreshed!");
end

function icu:config()
	InterfaceOptionsFrame_OpenToCategory("ICU")
end

function icu:getOptions()
	local options = {
		handler = icu,
		type = 'group',
		args = {
					config = {
						name = L["Options"],
						desc = L["Shows the blizzard addon configuration window"],
						type = 'execute',
						func = "config",
						hidden = true,
						cmdHidden = false,
					},
					check = {
						name = L["Raid Check"],
						desc = L["Checks the raid for flask/food buffs"],
						type = 'execute',
						func = function() self:inspectRaid(false) end,
						hidden = true,
						cmdHidden = false,
					},
					quiet = {
						name = L["Raid Check (Silent)"],
						desc = L["Checks the raid for flask/food buffs, but does not remind raiders, regardless of other settings."],
						type = 'execute',
						func = function() self:inspectRaid(true) end,
						hidden = true,
						cmdHidden = false,
					},
			general = {
				name = L["General Settings"],
				type = 'group',
				args = {
					advertiseICU = {
						type = 'toggle',
						order = 1,
						width = 'full',
						name = L["Advertise ICU"],
						desc = L["Let everyone know you are using ICU! Prefixes whispers and reports"],
						set = function(o,v,...) self.db.profile.advertiseICU = v end,
						get = function() return self.db.profile.advertiseICU end,
					},
					checkFlask = {
						type = 'toggle',
						order = 2,
						name = L["Check for Flask"],
						desc = L["Checks the raid for valid flasks and reports results"],
						set = function(o,v,...) self.db.profile.checkFlask = v end,
						get = function() return self.db.profile.checkFlask end,
					},
					checkLowFlaskDuration = {
						type = 'toggle',
						order = 3,
						name = L["Flask expiration"],
						desc = L["Check for soon to expire flask buffs"],
						set = function(o,v,...) self.db.profile.checkLowFlaskDuration = v end,
						get = function() return self.db.profile.checkLowFlaskDuration end,
					},
					checkFood = {
						type = 'toggle',
						order = 4,
						name = L["Check for Food"],
						desc = L["Checks the raid for valid food buffs and reports results"],
						set = function(o,v,...) self.db.profile.checkFood = v end,
						get = function() return self.db.profile.checkFood end,
					},
					remindRaider = {
						type = 'toggle',
						order = 5,
						width = 'full',
						name = L["Raid reminder"],
						desc = L["Whisper the raider lacking (or soon to expire, if enabled) food/flask buff a reminder"],
						set = function(o,v,...) self.db.profile.remindRaider = v end,
						get = function() return self.db.profile.remindRaider end,
					},
					breakOne = {
						type = 'description',
						fontSize = 'large',
						name = ' ',
						order = 7,
						width = 'full',
					},
					manualCheckOnly = {
						type = 'toggle',
						order = 8,
						name = L["Manual checks only"],
						desc = L["Only perform buff checks if initiated manually (via /icu check)"],
						set = function(o,v,...) self.db.profile.manualCheckOnly = v; self:refreshConfig() end,
						get = function() return self.db.profile.manualCheckOnly end,
					},
					checkAfterFinish = {
						type = 'toggle',
						order = 9,
						width = 'full',
						name = L["Inspect AFTER ready check finishes"],
						desc = L["Don't check buffs until the ready check has finished (or timed out)"],
						set = function(o,v,...) self.db.profile.checkAfterFinish = v end,
						get = function() return self.db.profile.checkAfterFinish end,
					},
					breakTwo = {
						type = 'description',
						fontSize = 'large',
						name = ' ',
						order = 10,
						width = 'full',
					},
					announceReadyCheck = {
						type = 'toggle',
						order = 11,
						name = L["Report ready checks"],
						desc = L["Report the results of ready checks"],
						width = 'double',
						set = function(o,v,...) self.db.profile.announceReadyCheck = v; self:refreshConfig() end,
						get = function() return self.db.profile.announceReadyCheck end,
					},
					reportResults = {
						type = 'toggle',
						order = 12,
						width = 'full',
						name = L["Report buff checks"],
						desc = L["Report flask/food check results"],
						set = function(o,v,...) self.db.profile.reportResults = v end,
						get = function() return self.db.profile.reportResults end,
					},
					reportDestination = {
						type = 'select',
						order = 13,
						style = "dropdown",
						name = L["Report Destination"],
						desc = L["Report the results to the following channel"],
						values = 	{
										["OFFICER"] = L["Officer"],
										["SAY"]		= L["Say"],
										["RAID"]	= L["Raid"],
										["GUILD"]	= L["Guild"],
										["SELF"]	= L["Self"],
									},
						set = function(o,v,...) self.db.profile.reportDestination = v end,
						get = function() return self.db.profile.reportDestination end,
					},
				},
			},
			messages = {
				name = L["Messages"],
				type = 'group',
				args = {
						noFlaskMessage = {
							type = 'input',
							width = 'double',
							name = L["Flask reminder"],
							desc = L["Message whispered to raiders missing a cataclysm flask"],
							set = function(o,v,...) self.db.profile.noFlaskMessage = v end,
							get = function() return self.db.profile.noFlaskMessage end,
						},
						lowFlaskDurationMessage = {
							type = 'input',
							width = 'double',
							name = L["Flask expiration"],
							desc = L["Message whispered to raiders with less than 10 minutes left on their flask"],
							set = function(o,v,...) self.db.profile.lowFlaskDurationMessage = v end,
							get = function() return self.db.profile.lowFlaskDurationMessage end,
						},
						noFoodMessage = {
							type = 'input',
							width = 'double',
							name = L["Food reminder"],
							desc = L["Message whispered to raiders missing the well-fed buff"],
							set = function(o,v,...) self.db.profile.noFoodMessage = v end,
							get = function() return self.db.profile.noFoodMessage end,
						},
				},
			},
			zones = {
				name = L["Disabled Zones"],
				type = 'group',
				set = 	function(i,v)
							self.db.profile.disabledZones[i[#i]] = v;
						end,
				get = 	function(i)
							return self.db.profile.disabledZones[i[#i]];
						end,
				args = {
					help = {
						type = 'description',
						name = L['Toggle ICU automatic checking |cffcc0000off|r in the selected zones.'],
						width = 'full',
						order = 0,
					},
					breakOne = {
						type = 'description',
						fontSize = 'large',
						name = ' ',
						order = 1,
						width = 'full',
					},
					enableAllZones = {
						type = 'toggle',
						name = L["Enable ICU in all zones"],
						order = 2,
						width = 'full',
						set = 	function (i,v) 
									self.db.profile.enableAllZones = v;
									self.options.args.zones.args.bwd.disabled = v;
									self.options.args.zones.args.bot.disabled = v;
									self.options.args.zones.args.tfw.disabled = v;
									self.options.args.zones.args.bh.disabled = v;
								end,
						get = function (i) return self.db.profile.enableAllZones end,
					},
					breakTwo = {
						type = 'description',
						fontSize = 'large',
						name = ' ',
						order = 3,
						width = 'full',
					},
					bwd = {
						type = 'toggle',
						name = L["Blackwing Descent"],
						width = 'double',
						disabled = self.db.profile.enableAllZones,
					},
					bot = {
						type = 'toggle',
						name = L["The Bastion of Twilight"],
						width = 'double',
						disabled = self.db.profile.enableAllZones,
					},
					tfw = {
						type = 'toggle',
						name = L["Throne of the Four Winds"],
						width = 'double',
						disabled = self.db.profile.enableAllZones,
					},
					bh = {
						type = 'toggle',
						name = L["Baradin Hold"],
						width = 'double',
						disabled = self.db.profile.enableAllZones,
					},

				}
			},
			profile = LibStub("AceDBOptions-3.0"):GetOptionsTable(self.db),
		},
	}
	return options
end

function icu:inspectRaid(silent,automatic)

	-- Check if any zones have been disabled
	if automatic and not self.db.profile.enableAllZones then
		local currentZone = GetRealZoneText();
		for k,v in pairs(self.db.profile.disabledZones) do
			if currentZone == raidMap[k] and v then
				disabledZone = true;
				return true;
			end
		end
	end
	
	local icuAd = "";
	
	-- Not in a raid group
	if GetNumRaidMembers() == 0 then
		return true
	end
	
	if self.db.profile.advertiseICU then
		icuAd = L["ICU"] .. ": ";
	else
		icuAd = "";
	end
	
	for i = 1, GetNumRaidMembers() do
		local raider = GetRaidRosterInfo(i);
		if raider then
			
			local hasFood, hasFlask, hasLowDuration = icu:validateBuffs(i);

			if not hasFood and self.db.profile.checkFood then
				noFood[#noFood+1] = raider;
				-- Tell player
				if self.db.profile.remindRaider and not silent then
					SendChatMessage(icuAd .. self.db.profile.noFoodMessage,"WHISPER",nil,raider);
				end
			end
			
			if not hasFlask and self.db.profile.checkFlask then
				noFlask[#noFlask+1] = raider;
				-- Tell player
				if self.db.profile.remindRaider and not silent then
					SendChatMessage(icuAd .. self.db.profile.noFlaskMessage,"WHISPER",nil,raider);
				end
			elseif hasLowDuration and not silent then
				if self.db.profile.remindRaider and self.db.profile.checkLowFlaskDuration then
					SendChatMessage(icuAd .. self.db.profile.lowFlaskDurationMessage,"WHISPER",nil,raider);
				end
			end
		end
		
	end
	
	-- Announce the report if inspectRaid was by a slash command instead of ready check event
	if not automatic then
		self:announceReport(false);
	end
end

function icu:announceReport(automatic)
	SendChatMessage("---- "..L["ICU"].." "..L["Report"].." ----",self.db.profile.reportDestination,nil,nil);
	
	-- Check if inspection got aborted due to the current zone being disabled
	if disabledZone then
		if self.db.profile.reportDestination == "SELF" then
			DEFAULT_CHAT_FRAME:AddMessage(L["No checking performed, zone disabled."]);
		else
			SendChatMessage(L["No checking performed, zone disabled."],self.db.profile.reportDestination,nil,nil);
		end
	else
		-- Check flask
		if #noFlask > 0 and self.db.profile.checkFlask then
			local reportFlaskMessage = L["Missing Flask"]..": " .. table.concat(noFlask, ", ");
			
			if self.db.profile.reportDestination == "SELF" then
				DEFAULT_CHAT_FRAME:AddMessage(reportFlaskMessage);
			else
				SendChatMessage(reportFlaskMessage,self.db.profile.reportDestination,nil,nil);
			end
		elseif self.db.profile.checkFlask then	-- Nobody is missing a flask
			if self.db.profile.reportDestination == "SELF" then
				DEFAULT_CHAT_FRAME:AddMessage(L["Nobody is missing a proper flask."]);
			else
				SendChatMessage(L["Nobody is missing a proper flask."],self.db.profile.reportDestination,nil,nil);
			end
		end
		
		-- Check food
		if #noFood > 0 and self.db.profile.checkFood then
			local reportFoodMessage = L["Missing Food"]..": " .. table.concat(noFood, ", ");
			
			if self.db.profile.reportDestination == "SELF" then
				DEFAULT_CHAT_FRAME:AddMessage(reportFoodMessage);
			else
				SendChatMessage(reportFoodMessage,self.db.profile.reportDestination,nil,nil);
			end
		elseif self.db.profile.checkFood then	-- Nobody is missing a food buff
			if self.db.profile.reportDestination == "SELF" then
				DEFAULT_CHAT_FRAME:AddMessage(L["Nobody is missing a proper food buff."]);
			else
				SendChatMessage(L["Nobody is missing a proper food buff."],self.db.profile.reportDestination,nil,nil);
			end
		end
	end
	-- Check ready
	if automatic and self.db.profile.announceReadyCheck then -- Check was initialized by ready check event
		if (#notReady > 0) or (#notReadyAFK > 0) then
			local reportNotReady = L["Not Ready"]..": " .. table.concat(notReady, ", ") .. (#notReadyAFK > 0 and #notReady > 0 and ", " or "") .. table.concat(notReadyAFK, ", ");
			
			if self.db.profile.reportDestination == "SELF" then
				DEFAULT_CHAT_FRAME:AddMessage(reportNotReady);
			else
				SendChatMessage(reportNotReady,self.db.profile.reportDestination,nil,nil);
			end
		else	-- Everyone is ready
			if self.db.profile.reportDestination == "SELF" then
				DEFAULT_CHAT_FRAME:AddMessage(L["Everyone is ready."]);
			else
				SendChatMessage(L["Everyone is ready."],self.db.profile.reportDestination,nil,nil);
			end
		end
	end
	
	self:wipeTables();
end

function icu:validateBuffs(playerIndex)
	local i = 1;
	local hasFood, hasFlask, hasLowDuration = false, false, false;
	local name, _, _, _, _, _, expirationTime, _, _, _, spellId = UnitBuff("raid"..playerIndex,i);

	while name do
		-- Check for food
		if not hasFood then
			for k,v in ipairs(foodID) do
				if v == spellId then
					hasFood = true;
				end
			end
		end
		-- Check for flask
		if not hasFlask then
			for k,v in ipairs(flaskID) do
				if v == spellId then
					hasFlask = true;
					-- Check if low duration
					if expirationTime and ((expirationTime-GetTime())/60) < minFlaskDuration then
						hasLowDuration = true;
					end
				end
			end
		end
		i = i + 1;
		name, _, _, _, _, _, expirationTime, _, _, _, spellId = UnitBuff("raid"..playerIndex,i);
	end

	return hasFood, hasFlask, hasLowDuration;
end

function icu:READY_CHECK(event,requester)
	--print("Ready check init!");
	if self.db.profile.manualCheckOnly then
		icu:UnregisterEvent("READY_CHECK");
		return true;
	end
	
	responders[#responders+1] = requester;
	
	if not self.db.profile.checkAfterFinish and GetNumRaidMembers() then
		self:inspectRaid(nil,true);
	else
		self:ScheduleTimer("READY_CHECK_FINISHED", 30);
		self:RegisterEvent("READY_CHECK_FINISHED");
	end
end

function icu:wipeTables()
	-- Wipe tables
	wipe(noFlask);
	wipe(noFood);
	wipe(hasLowDuration);
	wipe(notReady);
	wipe(notReadyAFK);
	wipe(responders);
	disabledZone = false;
end

function icu:READY_CHECK_FINISHED()
	--print("Ready check finish!");
	self:UnregisterEvent("READY_CHECK_FINISHED");
	self:CancelAllTimers();
	
	-- Not in a raid group
	if GetNumRaidMembers() then
		self:inspectRaid(nil,true);
		
		if self.db.profile.announceReadyCheck then
			self:confirmReady();
		end
		
		self:announceReport(true);
	end
end

function icu:READY_CHECK_CONFIRM(event,unit,status)
	local raider = UnitName(unit);

	--@debug@
	print(unit);
	--@end-debug@
	
	-- Check if the latest entires contain the raider... 
	-- The event fires twice for units in your party, 
	-- and may fire a THIRD time if you have the person targetted ...
	if (responders[#responders] == raider or responders[#responders-1] == raider) then return end
	
	-- Raider is NOT afk, but might not be ready
	responders[#responders+1] = raider;
	
	-- 1 ready, 0 not ready
	if not status then
		notReady[#notReady+1] = raider;
	end
end

function icu:confirmReady()
	local numRaiders, matchFound = GetNumRaidMembers(), false;
	
	--@debug@
	print(#responders);
	--@end-debug@
	
	if #responders < numRaiders then
		for i = 1, numRaiders do
			matchFound = false;
		
			-- Get raider name
			local raider = GetRaidRosterInfo(i);
			
			--@debug@
			print(raider);
			--@end-debug@
					
			for i = 1, #responders do
				print("Iterating through responders ...");
				print(responders[i],raider);
				if responders[i] == raider then
					--@debug@
					print(responders[i],raider);
					--@end-debug@
					matchFound = true;
					break;
				end
			end
			
			if not matchFound and raider then
				--@debug@
				print("Tag following raider as AFK: ",raider,matchFound);
				--@end-debug@
				notReadyAFK[#notReadyAFK+1] = raider .. " (AFK)";
			elseif not raider then -- GetRaidRosterInfo did not return a proper name, out of bounds??
				print("Something is wrong ...");
			else
				--@debug@
				print(i,raider);
				--@end-debug@
			end
		end
	end
end

function icu:getDB()
	return self.db;
end

local function filterChat(self, event, msg)
	local db = icu:getDB();
	if msg == db.profile.noFoodMessage or msg == db.profile.noFlaskMessage or msg == db.profile.lowFlaskDurationMessage or msg == (L["ICU"]..": "..db.profile.noFoodMessage) or msg == (L["ICU"]..": "..db.profile.noFlaskMessage) or msg == (L["ICU"]..": "..db.profile.lowFlaskDurationMessage) then return true end
end
ChatFrame_AddMessageEventFilter("CHAT_MSG_WHISPER_INFORM", filterChat)