Quantcast

Merge branch 'master' into mop

F16Gaming (Laptop) [05-15-12 - 08:46]
Merge branch 'master' into mop
Filename
AddonComm.lua
ChatManager.lua
Command.lua
Command.toc
CommandManager.lua
Events.lua
GroupTools.lua
PlayerManager.lua
String.lua
locales/enUS.lua
locales/svSE.lua
diff --git a/AddonComm.lua b/AddonComm.lua
index e22dff2..d8c5b33 100644
--- a/AddonComm.lua
+++ b/AddonComm.lua
@@ -1,18 +1,18 @@
 --[[
 	* Copyright (c) 2011-2012 by Adam Hellberg.
-	*
+	*
 	* This file is part of Command.
-	*
+	*
 	* Command is free software: you can redistribute it and/or modify
 	* it under the terms of the GNU General Public License as published by
 	* the Free Software Foundation, either version 3 of the License, or
 	* (at your option) any later version.
-	*
+	*
 	* Command is distributed in the hope that it will be useful,
 	* but WITHOUT ANY WARRANTY; without even the implied warranty of
 	* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 	* GNU General Public License for more details.
-	*
+	*
 	* You should have received a copy of the GNU General Public License
 	* along with Command. If not, see <http://www.gnu.org/licenses/>.
 --]]
@@ -43,16 +43,21 @@ C.AddonComm = {
 	GuildChecked = false,
 	GroupRunning = false,
 	GuildRunning = false,
+	Prefix = "COMMAND",
 	Type = {
-		VersionUpdate = "COMM_VU",
-		GroupUpdate = "COMM_GU",
-		GroupAdd = "COMM_GA",
-		GroupRequest = "COMM_GR",
-		GuildUpdate = "COMM_UG",
-		GuildAdd = "COMM_AG",
-		GuildRequest = "COMM_RG"
+		VersionUpdate	= "VU",
+		GroupUpdate		= "GU",
+		GroupAdd		= "GA",
+		GroupRequest	= "GR",
+		GuildUpdate		= "UG",
+		GuildAdd		= "AG",
+		GuildRequest	= "RG"
+	},
+	Pattern = {
+		Message = "{(%w+)}(.*)"
 	},
 	Format = {
+		Message = "{%s}%s",
 		VersionUpdate = "%s"
 	},
 	GroupMembers = {},
@@ -92,20 +97,28 @@ end

 function AC:Init()
 	--self:LoadSavedVars()
+	--[[
 	for _,v in pairs(self.Type) do
 		if not RegisterAddonMessagePrefix(v) then
 			log:Error(L("AC_ERR_PREFIX"):format(tostring(v)))
 			error(L("AC_ERR_PREFIX"):format(tostring(v)))
 		end
 	end
+	]]
+	if not RegisterAddonMessagePrefix(self.Prefix) then
+		log:Error(L("AC_ERR_PREFIX"):format(tostring(self.Prefix)))
+		error(L("AC_ERR_PREFIX"):format(tostring(self.Prefix)))
+	end
 end

 function AC:LoadSavedVars()
-
+
 end

 function AC:Receive(msgType, msg, channel, sender)
 	if sender == UnitName("player") or not msg then return end
+	if msgType ~= self.Prefix then return end
+	msgType, msg = msg:match(self.Pattern.Message)
 	if msgType == self.Type.VersionUpdate then
 		local ver = tonumber(msg)
 		if type(ver) ~= "number" then return end
@@ -204,13 +217,14 @@ function AC:Send(msgType, msg, channel, target)
 			return
 		end
 	end
-	SendAddonMessage(msgType, msg, channel, target)
+	SendAddonMessage(self.Prefix, self.Format.Message:format(msgType, msg), channel, target)
 	if msgType ~= self.Type.VersionUpdate and channel ~= "WHISPER" then
-		SendAddonMessage(self.Type.VersionUpdate, self.Format.VersionUpdate:format(C.VersionNum), channel)
+		SendAddonMessage(self.Prefix, self.Format.Message:format(self.Type.VersionUpdate, self.Format.VersionUpdate:format(C.VersionNum)), channel)
 	end
 end

 function AC:UpdateGroup()
+	if self.GroupRunning then return end
 	if not GT:IsGroup() then
 		if self.InGroup then
 			log:Normal(L("AC_GROUP_LEFT"))
@@ -253,6 +267,7 @@ function AC:UpdateGroup()
 end

 function AC:UpdateGuild()
+	if self.GuildRunning then return end
 	if not IsInGuild() then
 		self.InGuild = false
 		self.GuildChecked = false
@@ -303,7 +318,7 @@ end

 function AC:CheckGroupRoster()
 	for i,v in ipairs(self.GroupMembers) do
-		if not GT:IsInGroup(v) then
+		if not GT:IsInGroup(v) or not GT:IsOnline(v) then
 			log:Normal(L("AC_GROUP_REMOVE"):format(v))
 			table.remove(self.GroupMembers, i)
 			if self.GroupMembers[1] == UnitName("player") then
diff --git a/ChatManager.lua b/ChatManager.lua
index 357857e..f95cb85 100644
--- a/ChatManager.lua
+++ b/ChatManager.lua
@@ -1,18 +1,18 @@
 --[[
 	* Copyright (c) 2011-2012 by Adam Hellberg.
-	*
+	*
 	* This file is part of Command.
-	*
+	*
 	* Command is free software: you can redistribute it and/or modify
 	* it under the terms of the GNU General Public License as published by
 	* the Free Software Foundation, either version 3 of the License, or
 	* (at your option) any later version.
-	*
+	*
 	* Command is distributed in the hope that it will be useful,
 	* but WITHOUT ANY WARRANTY; without even the implied warranty of
 	* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 	* GNU General Public License for more details.
-	*
+	*
 	* You should have received a copy of the GNU General Public License
 	* along with Command. If not, see <http://www.gnu.org/licenses/>.
 --]]
@@ -230,7 +230,7 @@ function CM:HandleMessage(msg, sender, channel, target, sourceChannel, isBN, pID
 			end
 			self:SendMessage(s, channel, target, isBN)
 		end
-	else
+	elseif arg then
 		local s = l[arg]
 		if type(errArg) == "table" then
 			s = s:format(unpack(errArg))
diff --git a/Command.lua b/Command.lua
index fb8e164..9aa9a93 100644
--- a/Command.lua
+++ b/Command.lua
@@ -1,18 +1,18 @@
 --[[
 	* Copyright (c) 2011-2012 by Adam Hellberg.
-	*
+	*
 	* This file is part of Command.
-	*
+	*
 	* Command is free software: you can redistribute it and/or modify
 	* it under the terms of the GNU General Public License as published by
 	* the Free Software Foundation, either version 3 of the License, or
 	* (at your option) any later version.
-	*
+	*
 	* Command is distributed in the hope that it will be useful,
 	* but WITHOUT ANY WARRANTY; without even the implied warranty of
 	* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 	* GNU General Public License for more details.
-	*
+	*
 	* You should have received a copy of the GNU General Public License
 	* along with Command. If not, see <http://www.gnu.org/licenses/>.
 --]]
diff --git a/Command.toc b/Command.toc
index 0e39f47..d5a028f 100644
--- a/Command.toc
+++ b/Command.toc
@@ -8,7 +8,4 @@
 # No libraries at the moment
 #embed.xml

-# !!LOCALE SUPPORT NOT ADDED!! #
-#locales\load.xml
-
 load.xml
diff --git a/CommandManager.lua b/CommandManager.lua
index 0e8f421..69e578a 100644
--- a/CommandManager.lua
+++ b/CommandManager.lua
@@ -1,18 +1,18 @@
 --[[
 	* Copyright (c) 2011-2012 by Adam Hellberg.
-	*
+	*
 	* This file is part of Command.
-	*
+	*
 	* Command is free software: you can redistribute it and/or modify
 	* it under the terms of the GNU General Public License as published by
 	* the Free Software Foundation, either version 3 of the License, or
 	* (at your option) any later version.
-	*
+	*
 	* Command is distributed in the hope that it will be useful,
 	* but WITHOUT ANY WARRANTY; without even the implied warranty of
 	* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 	* GNU General Public License for more details.
-	*
+	*
 	* You should have received a copy of the GNU General Public License
 	* along with Command. If not, see <http://www.gnu.org/licenses/>.
 --]]
@@ -73,7 +73,14 @@ function CM:Register(names, access, func, help)
 	if names[1] ~= "__DEFAULT__" then
 		names[1] = names[1]:lower()
 	end
-	local entry = {Name=names[1], Access=access, Call=func, Help=help, Alias={}}
+	local entry =
+	{
+		Name = names[1],
+		Access = access,
+		Call = func,
+		Help = help or "CM_NO_HELP",
+		Alias = {}
+	}
 	if #names > 1 then
 		for i=2,#names do
 			table.insert(entry.Alias, names[i]:lower())
@@ -160,6 +167,7 @@ function CM:HandleCommand(command, args, isChat, player)
 end

 --- Prints all command names together with their help messages.
+--
 function CM:AutoHelp()
 	local l = L:GetActive()
 	for k,v in pairs(self.Commands) do
@@ -168,14 +176,33 @@ function CM:AutoHelp()
 	end
 end

-CM:Register({"__DEFAULT__", "help", "h"}, PM.Access.Local, function(args, sender, isChat)
+function CM:GetHelp(cmd)
+	cmd = tostring(cmd):lower()
+	if not self:HasCommand(cmd) then return false, "CM_ERR_NOTREGGED" end
+	local command = self:GetCommand(cmd)
+	return command.Help or "CM_NO_HELP"
+end
+
+
+CM:Register({"__DEFAULT__"}, PM.Access.Local, function(args, sender, isChat)
 	if isChat then
 		return "CM_DEFAULT_CHAT"
 	end
 	CM:AutoHelp()
+	C.Logger:Normal(L("CM_DEFAULT_HELPCOMMAND"))
 	return "CM_DEFAULT_END"
 end, "CM_DEFAULT_HELP")

+CM:Register({"help", "h"}, PM.Access.Groups.User.Level, function(args, sender, isChat)
+	if #args <= 0 then
+		if isChat then
+			return "CM_DEFAULT_CHAT"
+		end
+		return false, "CM_HELP_USAGE"
+	end
+	return CM:GetHelp(tostring(args[1]):lower())
+end, "CM_HELP_HELP")
+
 CM:Register({"commands", "cmds", "cmdlist", "listcmds", "listcommands", "commandlist"}, PM.Access.Groups.User.Level, function(args, sender, isChat)
 	local all
 	if #args > 0 then
@@ -213,34 +240,36 @@ CM:Register({"set", "s"}, PM.Access.Groups.Admin.Level, function(args, sender, i
 			return C:DisableGroupInvite()
 		end
 		return false, "CM_SET_GROUPINVITE_USAGE"
-	elseif args[1]:match("^s.*l") then -- Set locale
+	end
+	return false, "CM_SET_USAGE"
+end, "CM_SET_HELP")
+
+CM:Register({"locale", "loc"}, PM.Access.Local, function(args, sender, isChat)
+	if isChat then return false, "CM_ERR_NOCHAT" end
+	if #args <= 0 then
+		return "CM_LOCALE_CURRENT", {L.Settings.LOCALE}
+	end
+	local arg = tostring(args[1]):lower()
+	if arg:match("^s") then -- Set
 		if #args < 2 then
-			return false, "CM_SET_SETLOCALE_USAGE"
+			return false, "CM_LOCALE_SET_USAGE"
 		end
-		local locale = tostring(args[2]):lower()
-		return L:SetLocale(locale)
-	elseif args[1]:match("^l") then -- Other locale settings
-		if #args < 2 then
-			return false, "CM_SET_LOCALE_USAGE"
+		return L:SetLocale(tostring(args[2]):lower())
+	elseif arg:match("^r") or arg:match("^u.*a") then -- Reset / Use Active
+		return L:ResetLocale()
+	elseif arg:match("^u.*m") then -- Use Master
+		return L:UseMasterLocale()
+	elseif arg:match("^p.*i") then -- Player Independent
+		local enabled = tostring(args[3]):lower()
+		if enabled:match("^[eay]") then
+			return L:EnablePlayerIndependent()
+		elseif enabled:match("^[dn]") then
+			return L:DisablePlayerIndependent()
 		end
-		local sub = tostring(args[2]):lower()
-		if sub:match("^r") or sub:match("^u.*a") then -- Reset / Use active
-			return L:ResetLocale()
-		elseif sub:match("^u.*m") then -- Use master
-			return L:UseMasterLocale()
-		elseif sub:match("^p.*i") then -- Player Independent
-			local enabled = tostring(args[3]):lower()
-			if enabled:match("^[eay]") then
-				return L:EnablePlayerIndependent()
-			elseif enabled:match("^[dn]") then
-				return L:DisablePlayerIndependent()
-			end
-			return L:TogglePlayerIndependent()
-		end
-		return false, "CM_SET_LOCALE_USAGE"
+		return L:TogglePlayerIndependent()
 	end
-	return false, "CM_SET_USAGE"
-end, "CM_SET_HELP")
+	return false, "CM_LOCALE_USAGE"
+end, "CM_LOCALE_HELP")

 CM:Register({"mylocale", "ml"}, PM.Access.Groups.User.Level, function(args, sender, isChat)
 	if not isChat then
@@ -343,7 +372,7 @@ CM:Register({"ban"}, PM.Access.Groups.Admin.Level, function(args, sender, isChat
 	return PM:BanUser(player)
 end, "CM_BAN_HELP")

-CM:Register({"acceptinvite", "acceptinv", "join", "joingroup"}, PM.Access.Groups.User.Level, function(args, sender, isChat)
+CM:Register({"accept", "acceptinvite", "acceptinv", "join", "joingroup"}, PM.Access.Groups.User.Level, function(args, sender, isChat)
 	if not StaticPopup_Visible("PARTY_INVITE") then
 		return false, "CM_ACCEPTINVITE_NOTACTIVE"
 	elseif GT:IsInGroup() then
@@ -388,7 +417,13 @@ CM:Register({"kick"}, PM.Access.Groups.Op.Level, function(args, sender, isChat)
 		return false, "CM_KICK_USAGE"
 	end
 	local player = PM:GetOrCreatePlayer(args[1])
-	return PM:Kick(player, sender, args[2])
+	local reason = args[2]
+	local override = args[3] ~= nil
+	if ((reason or ""):lower() == "override" or (reason or ""):lower() == "true") and #args == 2 then
+		reason = nil
+		override = true
+	end
+	return PM:Kick(player, sender, reason, override)
 end, "CM_KICK_HELP")

 CM:Register({"kingme", "givelead"}, PM.Access.Groups.Op.Level, function(args, sender, isChat)
@@ -464,9 +499,17 @@ CM:Register({"leavelfg", "cancellfg", "cancel", "leavelfd", "cancellfd"}, PM.Acc
 	return QM:Cancel()
 end, "CM_LEAVELFG_HELP")

-CM:Register({"acceptlfg", "accept", "acceptlfd", "joinlfg", "joinlfd"}, PM.Access.Groups.User.Level, function(args, sender, isChat)
+-- So apparently Blizzard does not allow accepting invites without HW event... Making this command useless...
+-- I'm keeping this command here for the future, if there will ever be a way to make this work.
+CM:Register({"acceptlfg", "acceptlfd", "joinlfg", "joinlfd"}, PM.Access.Groups.User.Level, function(args, sender, isChat)
+	if not C.Settings.DEBUG then
+		return false, "CM_ERR_DISABLED"
+	end
+	local exists = (select(1, GetLFGProposal()))
 	if not QM.QueuedByCommand then
 		return false, "CM_ACCEPTLFG_FAIL"
+	elseif not exists then
+		return false, "CM_ACCEPTLFG_NOEXIST"
 	end
 	return QM:Accept()
 end, "CM_ACCEPTLFG_HELP")
@@ -579,9 +622,11 @@ CM:Register({"toggledebug", "td", "debug", "d"}, PM.Access.Local, function(args,
 	return C:ToggleDebug()
 end, "CM_TOGGLEDEBUG_HELP")

-CM:Register({"readycheck", "rc"}, PM.Access.Groups.Op.Level, function(args, sender, isChat)
+CM:Register({"readycheck", "rc"}, PM.Access.Groups.User.Level, function(args, sender, isChat)
 	if #args <= 0 then
-		if GT:IsGroupLeader() or GT:IsRaidLeaderOrAssistant() then
+		if PM:GetAccess(sender) > PM.Access.Groups.Op.Level then
+			return "CM_ERR_NOACCESS", {sender.Info.Name, PM.Access.Groups.Op.Level, PM:GetAccess(sender)}
+		elseif GT:IsGroupLeader() or GT:IsRaidLeaderOrAssistant() then
 			C.Data.ReadyCheckRunning = true
 			local name = tostring(sender.Info.Name)
 			DoReadyCheck()
@@ -737,9 +782,50 @@ CM:Register({"raidwarning", "rw", "raid_warning"}, PM.Access.Groups.User.Level,
 		end
 	end
 	Chat:SendMessage(msg, "RAID_WARNING")
+	if isChat then
+		return nil -- "CM_RAIDWARNING_SENT"
+	end
 	return "CM_RAIDWARNING_SENT"
 end, "CM_RAIDWARNING_HELP")

+CM:Register({"dungeondifficulty", "dungeondiff", "dd", "dungeonmode", "dm"}, PM.Access.Groups.User.Level, function(args, sender, isChat)
+	if #args < 1 then
+		return GT:GetDungeonDifficultyString()
+	end
+	local diff = args[1]:lower()
+	if diff:match("^n") then
+		diff = GT.Difficulty.Dungeon.Normal
+	elseif diff:match("^h") then
+		diff = GT.Difficulty.Dungeon.Heroic
+	elseif tonumber(diff) then
+		diff = tonumber(diff)
+	else
+		return false, "CM_DUNGEONMODE_USAGE"
+	end
+	return GT:SetDungeonDifficulty(diff)
+end, "CM_DUNGEONMODE_HELP")
+
+CM:Register({"raiddifficulty", "raiddiff", "rd", "raidmode", "rm"}, PM.Access.Groups.User.Level, function(args, sender, isChat)
+	if #args < 1 then
+		return GT:GetRaidDifficultyString()
+	end
+	local diff = args[1]:lower()
+	if diff:match("^n.*1") then
+		diff = GT.Difficulty.Raid.Normal10
+	elseif diff:match("^n.*2") then
+		diff = GT.Difficulty.Raid.Normal25
+	elseif diff:match("^h.*1") then
+		diff = GT.Difficulty.Raid.Heroic10
+	elseif diff:match("^h.*2") then
+		diff = GT.Difficulty.Raid.Heroic25
+	elseif tonumber(diff) then
+		diff = tonumber(diff)
+	else
+		return false, "CM_RAIDMODE_USAGE"
+	end
+	return GT:SetRaidDifficulty(diff)
+end, "CM_RAIDMODE_HELP")
+
 for i,v in ipairs(CM.Slash) do
 	_G["SLASH_" .. C.Name:upper() .. i] = "/" .. v
 end
@@ -778,7 +864,11 @@ SlashCmdList[C.Name:upper()] = function(msg, editBox)
 			end
 			C.Logger:Normal(s)
 		end
-	else
-		C.Logger:Error(tostring(err))
+	elseif arg then
+		local s = l[arg]
+		if type(errArg) == "table" then
+			s = s:format(unpack(errArg))
+		end
+		C.Logger:Error(s)
 	end
 end
diff --git a/Events.lua b/Events.lua
index ccf65b1..3fd4c0e 100644
--- a/Events.lua
+++ b/Events.lua
@@ -1,18 +1,18 @@
 --[[
 	* Copyright (c) 2011-2012 by Adam Hellberg.
-	*
+	*
 	* This file is part of Command.
-	*
+	*
 	* Command is free software: you can redistribute it and/or modify
 	* it under the terms of the GNU General Public License as published by
 	* the Free Software Foundation, either version 3 of the License, or
 	* (at your option) any later version.
-	*
+	*
 	* Command is distributed in the hope that it will be useful,
 	* but WITHOUT ANY WARRANTY; without even the implied warranty of
 	* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 	* GNU General Public License for more details.
-	*
+	*
 	* You should have received a copy of the GNU General Public License
 	* along with Command. If not, see <http://www.gnu.org/licenses/>.
 --]]
@@ -108,7 +108,12 @@ function C.Events.PARTY_LEADER_CHANGED(self, ...)
 	AC:UpdateGroup()
 end

+local numGuildMembers = 0
 function C.Events.GUILD_ROSTER_UPDATE(self, ...)
 	if AC.GuildRunning then return end
-	AC:UpdateGuild()
+	local _, tmpGuildMembers = GetNumGuildMembers()
+	if numGuildMembers ~= tmpGuildMembers then
+		numGuildMembers = tmpGuildMembers
+		AC:UpdateGuild()
+	end
 end
diff --git a/GroupTools.lua b/GroupTools.lua
index 4493214..c46b2f0 100644
--- a/GroupTools.lua
+++ b/GroupTools.lua
@@ -1,18 +1,18 @@
 --[[
 	* Copyright (c) 2011-2012 by Adam Hellberg.
-	*
+	*
 	* This file is part of Command.
-	*
+	*
 	* Command is free software: you can redistribute it and/or modify
 	* it under the terms of the GNU General Public License as published by
 	* the Free Software Foundation, either version 3 of the License, or
 	* (at your option) any later version.
-	*
+	*
 	* Command is distributed in the hope that it will be useful,
 	* but WITHOUT ANY WARRANTY; without even the implied warranty of
 	* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 	* GNU General Public License for more details.
-	*
+	*
 	* You should have received a copy of the GNU General Public License
 	* along with Command. If not, see <http://www.gnu.org/licenses/>.
 --]]
@@ -21,6 +21,7 @@
 local select = select

 local C = Command
+local L = C.LocaleManager

 --- Table containing all GroupTools methods.
 -- This is referenced "GT" in GroupTools.lua.
@@ -31,10 +32,29 @@ local C = Command
 --
 C.GroupTools = {
 	PartyMax = 5,
-	RaidMax = 40
+	RaidMax = 40,
+	Difficulty = {
+		Dungeon = {
+			[1] = "GT_DUNGEON_NORMAL",
+			[2] = "GT_DUNGEON_HEROIC",
+			Normal = 1,
+			Heroic = 2
+		},
+		Raid = {
+			[1] = "GT_RAID_N10",
+			[2] = "GT_RAID_N25",
+			[3] = "GT_RAID_H10",
+			[4] = "GT_RAID_H25",
+			Normal10 = 1,
+			Normal25 = 2,
+			Heroic10 = 3,
+			Heroic25 = 4
+		}
+	}
 }

 local GT = C.GroupTools
+local CET = C.Extensions.Table

 --- Check if player is in a group.
 -- @return True if player is in group, false otherwise.
@@ -136,3 +156,65 @@ function GT:IsInGroup(name)
 	end
 	return false
 end
+
+--- Check if unit is online.
+-- @param unit Uint/Player Name to check.
+-- @return 1 if online, nil if not.
+--
+function GT:IsOnline(unit)
+	return UnitIsConnected(unit)
+end
+
+--- Set the dungeon difficulty.
+-- @param diff (number) Difficulty to change to.
+-- @return String representing outcome.
+--
+function GT:SetDungeonDifficulty(diff)
+	diff = tonumber(diff)
+	if not diff then return false, "GT_DIFF_INVALID", {tostring(diff)} end
+	if not CET:HasValue(self.Difficulty.Dungeon, diff) then return false, "GT_DIFF_INVALID", {tostring(diff)} end
+	if diff == GetDungeonDifficulty() then return false, "GT_DD_DUPE", {self:GetFriendlyDungeonDifficulty(diff)} end
+	SetDungeonDifficulty(diff)
+	return "GT_DD_SUCCESS", {self:GetFriendlyDungeonDifficulty(diff)}
+end
+
+function GT:GetDungeonDifficultyString(diff)
+	return self.Difficulty.Dungeon[tonumber(diff) or GetDungeonDifficulty()]
+end
+
+--- Get a string representation of the dungeon difficulty.
+-- @param diff (number) Difficulty to parse, defaults to current difficulty.
+-- @return String representation of dungeon difficulty.
+--
+function GT:GetFriendlyDungeonDifficulty(diff)
+	return L(self.Difficulty.Dungeon[tonumber(diff) or GetDungeonDifficulty()])
+end
+
+--- Set the raid difficulty.
+-- @param diff (number) Difficulty to change to.
+-- @return String representing outcome.
+--
+function GT:SetRaidDifficulty(diff)
+	diff = tonumber(diff)
+	if not diff then return false, "GT_DIFF_INVALID", {tostring(diff)} end
+	if not CET:HasValue(self.Difficulty.Raid, diff) then return false, "GT_DIFF_INVALID", {tostring(diff)} end
+	if diff == GetRaidDifficulty() then return false, "GT_RD_DUPE", {self:GetFriendlyRaidDifficulty(diff)} end
+	SetRaidDifficulty(diff)
+	return "GT_RD_SUCCESS", {self:GetFriendlyRaidDifficulty(diff)}
+end
+
+--- Get a string representation of the raid difficulty.
+-- @param diff (number) Difficulty to parse, defaults to current difficulty.
+-- @return String representation of raid difficulty.
+--
+function GT:GetRaidDifficultyString(diff)
+	return self.Difficulty.Raid[tonumber(diff) or GetRaidDifficulty()]
+end
+
+--- Get a string representation of the raid difficulty.
+-- @param diff (number) Difficulty to parse, defaults to current difficulty.
+-- @return String representation of raid difficulty.
+--
+function GT:GetFriendlyRaidDifficulty(diff)
+	return L(self.Difficulty.Raid[tonumber(diff) or GetRaidDifficulty()])
+end
diff --git a/PlayerManager.lua b/PlayerManager.lua
index 9c831bf..e2ef7ae 100644
--- a/PlayerManager.lua
+++ b/PlayerManager.lua
@@ -621,18 +621,23 @@ end
 --- Kick a player from the group.
 -- @param player Player object of the player to kick.
 -- @param sender Player object of the player who requested the kick.
+-- @param reason Optional reason for the kick.
+-- @param override True to kick even if target is friend.
 -- @return String stating the result of the kick, false if error.
 -- @return Error message if unsuccessful, nil otherwise.
 --
-function PM:Kick(player, sender, reason)
+function PM:Kick(player, sender, reason, override)
 	if player.Info.Name == UnitName("player") then
 		return false, "PM_KICK_SELF"
-	elseif self:IsFriend(player) or self:IsBNFriend(player) then
+	elseif (self:IsFriend(player) or self:IsBNFriend(player)) and not override then
 		return false, "PM_KICK_FRIEND"
 	elseif not GT:IsInGroup(player.Info.Name) then
 		return false, "PM_ERR_NOTINGROUP", {player.Info.Name}
 	end
 	if GT:IsGroupLeader() or GT:IsRaidLeaderOrAssistant() then
+		if GT:IsRaidAssistant() and GT:IsRaidAssistant(player.Info.Name) then
+			return false, "PM_KICK_TARGETASSIST", {player.Info.Name}
+		end
 		KickName = player.Info.Name
 		KickSender = sender.Info.Name
 		KickReason = reason or L("PM_KICK_DEFAULTREASON"):format(KickSender)
diff --git a/String.lua b/String.lua
index a5374ca..477c154 100644
--- a/String.lua
+++ b/String.lua
@@ -144,3 +144,7 @@ function CES:Fit(s, l, d)
 	end
 	return parts
 end
+
+function CES:IsNullOrEmpty(s)
+	return s == nil or s == ""
+end
diff --git a/locales/enUS.lua b/locales/enUS.lua
index 7c414f2..cadd148 100644
--- a/locales/enUS.lua
+++ b/locales/enUS.lua
@@ -1,18 +1,18 @@
 --[[
 	* Copyright (c) 2011-2012 by Adam Hellberg.
-	*
+	*
 	* This file is part of Command.
-	*
+	*
 	* Command is free software: you can redistribute it and/or modify
 	* it under the terms of the GNU General Public License as published by
 	* the Free Software Foundation, either version 3 of the License, or
 	* (at your option) any later version.
-	*
+	*
 	* Command is distributed in the hope that it will be useful,
 	* but WITHOUT ANY WARRANTY; without even the implied warranty of
 	* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 	* GNU General Public License for more details.
-	*
+	*
 	* You should have received a copy of the GNU General Public License
 	* along with Command. If not, see <http://www.gnu.org/licenses/>.
 --]]
@@ -26,18 +26,18 @@ local L = {
 	LOCALE_UPDATE = "Set new locale to: %s",
 	LOCALE_PI_ACTIVE = "Player independent locale settings is now active.",
 	LOCALE_PI_INACTIVE = "Player independent locale settings is now inactive.",
-
+
 	-------------
 	-- General --
 	-------------
-
+
 	YES = "Yes",
 	NO = "No",
-
+
 	----------
 	-- Core --
 	----------
-
+
 	ADDON_LOAD = "AddOn loaded! Use /cmd help or !help for help.",
 	SVARS_OUTDATED = "Saved Variables out of date, resetting...",
 	NEWVERSION_NOTICE = "\124cffFF0000A new version of \124cff00FFFF%s\124cffFF0000 is available! \124cffFFFF00Check the site you downloaded from for the updated version.",
@@ -45,128 +45,137 @@ local L = {
 	DISABLED = "AddOn \124cffFF0000disabled\124r.",
 	DEBUGENABLED = "Debugging \124cff00FF00enabled\124r.",
 	DEBUGDISABLED = "Debugging \124cffFF0000disabled\124r.",
-
+
 	---------------
 	-- AddonComm --
 	---------------
-
+
 	AC_ERR_PREFIX = "[FATAL] Failed to register AddOn prefix %q. Maximum number of prefixes reached on client.",
 	AC_ERR_MSGTYPE = "Invalid message type specified: %s",
 	AC_ERR_MALFORMED_DATA = "Malformed data received from %s. Their AddOn is probably outdated.",
 	AC_ERR_MALFORMED_DATA_SEND = "[AddonComm] Malformed data detected (\"%s\"). Aborting Send...",
-
+
 	AC_GROUP_NORESP = "No response from group, running updater...",
 	AC_GROUP_R_UPDATE = "Updated group members, controller: %s",
 	AC_GROUP_LEFT = "Left group, resetting group variables...",
 	AC_GROUP_WAIT = "Waiting for group response...",
 	AC_GROUP_REMOVE = "Detected that %s is no longer in the group, removing and updating group members...",
 	AC_GROUP_SYNC = "Detected group handlers out of date! Sending sync message...",
-
+
 	AC_GUILD_NORESP = "No response from guild, running updater...",
 	AC_GUILD_R_UPDATE = "Updated guild members, controller: %s",
 	AC_GUILD_WAIT = "Waiting for guild response...",
-
+
 	-----------------
 	-- ChatManager --
 	-----------------
-
+
 	CHAT_ERR_CMDCHAR = "Command char has to be of type string.",
 	CHAT_CMDCHAR_SUCCESS = "Successfully set the command char to: %s",
 	CHAT_HANDLE_NOTCONTROLLER = "Not controller instance for \124cff00FFFF%s\124r, aborting.",
-
+
 	--------------------
 	-- CommandManager --
 	--------------------
-
+
 	CM_ERR_NOTALLOWED = "%s is not allowed to be used, %s.",
 	CM_ERR_NOACCESS = "You do not have permission to use that command, %s. Required access level: %d. Your access level: %d.",
 	CM_ERR_NOTREGGED = "%q is not a registered command.",
 	CM_ERR_NOCMDCHAR = "No command character specified.",
 	CM_ERR_NOCHAT = "This command is not allowed to be used from the chat.",
 	CM_ERR_CHATONLY = "This command can only be used from the chat.",
-
+	CM_ERR_DISABLED = "This command has been permanently disabled.",
+
 	CM_NO_HELP = "No help available.",
-
+
 	CM_DEFAULT_HELP = "Prints this help message.",
-	CM_DEFAULT_CHAT = "Type !commands for a listing of commands available.",
+	CM_DEFAULT_HELPCOMMAND = "Use \"help <command>\" to get help on a specific command.",
+	CM_DEFAULT_CHAT = "Type !commands for a listing of commands available. Type !help <command> for help on a specific command.",
 	CM_DEFAULT_END = "End of help message.",
-
+
+	CM_HELP_HELP = "Gets help about the addon or a specific command.",
+	CM_HELP_USAGE = "Usage: help <command>",
+
 	CM_COMMANDS_HELP = "Print all registered commands.",
-
+
 	CM_VERSION_HELP = "Print the version of Command.",
 	CM_VERSION = "%s",
-
+
 	CM_SET_HELP = "Control the settings of Command.",
-	CM_SET_USAGE = "Usage: set cmdchar|groupinvite|setlocale|locale",
+	CM_SET_USAGE = "Usage: set cmdchar|groupinvite",
 	CM_SET_GROUPINVITE_USAGE = "Usage: set groupinvite enable|disable|<time>",
-	CM_SET_SETLOCALE_USAGE = "Usage: set setlocale <locale>",
-	CM_SET_LOCALE_USAGE = "Usage: set locale reset|usemaster",
-
+
+	CM_LOCALE_HELP = "Change locale settings.",
+	CM_LOCALE_USAGE ="Usage: locale [set|reset|usemaster|playerindependent]",
+	CM_LOCALE_CURRENT = "Current locale: %s.",
+	CM_LOCALE_SET_USAGE = "Usage: locale set <locale>",
+
 	CM_MYLOCALE_HELP = "Let's users set their own locale.",
 	CM_MYLOCALE_SET = "Successfully set your locale to %s.",
-
+
 	CM_LOCK_HELP = "Lock a player.",
 	CM_UNLOCK_HELP = "Unlock a player.",
-
+
 	CM_GETACCESS_HELP = "Get the access level of a user.",
 	CM_GETACCESS_STRING = "%s's access is %d (%s)",
-
+
 	CM_SETACCESS_HELP = "Set the access level of a user.",
 	CM_SETACCESS_USAGE = "Usage: setaccess [player] <group>",
-
+
 	CM_OWNER_HELP = "Promote a player to owner rank.",
-
+
 	CM_ADMIN_HELP = "Promote a player to admin rank.",
 	CM_ADMIN_USAGE = "Usage: admin <name>",
-
+
 	CM_OP_HELP = "Promote a player to op rank.",
-
+
 	CM_USER_HELP = "Promote a player to user rank.",
-
+
 	CM_BAN_HELP = "Ban a player.",
 	CM_BAN_USAGE = "Usage: ban <name>",
-
+
 	CM_ACCEPTINVITE_HELP = "Accepts a pending group invite.",
 	CM_ACCEPTINVITE_NOTACTIVE = "No pending invites active.",
 	CM_ACCEPTINVITE_EXISTS = "I am already in a group.",
 	CM_ACCEPTINVITE_SUCCESS = "Accepted group invite!",
-
+
 	CM_INVITE_HELP = "Invite a player to group.",
-
+
 	CM_INVITEME_HELP = "Player who issued the command will be invited to group.",
-
+
 	CM_DENYINVITE_HELP = "Player issuing this command will no longer be sent invites from this AddOn.",
-
+
 	CM_ALLOWINVITE_HELP = "Player issuing this command will receive invites sent from this AddOn.",
-
+
 	CM_KICK_HELP = "Kick a player from group with optional reason (Requires confirmation).",
 	CM_KICK_USAGE = "Usage: kick <player> [reason]",
-
+
 	CM_KINGME_HELP = "Player issuing this command will be promoted to group leader.",
-
+
 	CM_OPME_HELP = "Player issuing this command will be promoted to raid assistant.",
-
+
 	CM_DEOPME_HELP = "Player issuing this command will be demoted from assistant status.",
-
+
 	CM_LEADER_HELP = "Promote a player to group leader.",
 	CM_LEADER_USAGE = "Usage: leader <name>",
-
+
 	CM_PROMOTE_HELP = "Promote a player to raid assistant.",
 	CM_PROMOTE_USAGE = "Usage: promote <name>",
-
+
 	CM_DEMOTE_HELP = "Demote a player from assistant status.",
 	CM_DEMOTE_USAGE = "Usage: demote <name>",
-
+
 	CM_QUEUE_HELP = "Enter the LFG queue for the specified category.",
 	CM_QUEUE_USAGE = "Usage: queue <type>",
 	CM_QUEUE_INVALID = "No such dungeon type: %q.",
-
+
 	CM_LEAVELFG_HELP = "Leave the LFG queue.",
 	CM_LEAVELFG_FAIL = "Not queued by command, unable to cancel.",
-
+
 	CM_ACCEPTLFG_HELP = "Causes you to accept the LFG invite.",
 	CM_ACCEPTLFG_FAIL = "Not currently queued by command.",
-
+	CM_ACCEPTLFG_NOEXIST = "There is currently no LFG proposal to accept.",
+
 	CM_CONVERT_HELP = "Convert group to party or raid.",
 	CM_CONVERT_USAGE = "Usage: convert party||raid",
 	CM_CONVERT_LFG = "LFG groups cannot be converted.",
@@ -177,34 +186,34 @@ local L = {
 	CM_CONVERT_RAID = "Converted party to raid.",
 	CM_CONVERT_RAIDFAIL = "Group is already a raid.",
 	CM_CONVERT_INVALID = "Invalid group type, only \"party\" or \"raid\" allowed.",
-
+
 	CM_LIST_HELP = "Toggle status of a command on the blacklist/whitelist.",
 	CM_LIST_USAGE = "Usage: list <command>",
-
+
 	CM_LISTMODE_HELP = "Toggle list between being a blacklist and being a whitelist.",
-
+
 	CM_GROUPALLOW_HELP = "Allow a group to use a specific command.",
 	CM_GROUPALLOW_USAGE = "Usage: groupallow <group> <command>",
-
+
 	CM_GROUPDENY_HELP = "Deny a group to use a specific command.",
 	CM_GROUPDENY_USAGE = "Usage: groupdeny <group> <command>",
-
+
 	CM_RESETGROUPACCESS_HELP = "Reset the group's access to a specific command.",
 	CM_RESETGROUPACCESS_USAGE = "Usage: resetgroupaccess <group> <command>",
-
+
 	CM_USERALLOW_HELP = "Allow a user to use a specific command.",
 	CM_USERALLOW_USAGE = "Usage: userallow <player> <command>",
-
+
 	CM_USERDENY_HELP = "Deny a user to use a specific command.",
 	CM_USERDENY_USAGE = "Usage: userdeny <player> <command>",
-
+
 	CM_RESETUSERACCESS_HELP = "Reset the user's access to a specific command.",
 	CM_RESETUSERACCESS_USAGE = "Usage: resetuseraccess <player> <command>",
-
+
 	CM_TOGGLE_HELP = "Toggle AddOn on and off.",
-
+
 	CM_TOGGLEDEBUG_HELP = "Toggle debugging mode on and off.",
-
+
 	CM_READYCHECK_HELP = "Respond to ready check or initiate a new one.",
 	CM_READYCHECK_ISSUED = "%s issued a ready check!",
 	CM_READYCHECK_NOPRIV = "Cannot initiate ready check when not leader or assistant.",
@@ -213,44 +222,50 @@ local L = {
 	CM_READYCHECK_DECLINED = "Declined ready check.",
 	CM_READYCHECK_INVALID = "Invalid argument: %s",
 	CM_READYCHECK_FAIL = "Failed to accept or decline ready check.",
-
+
 	CM_LOOT_HELP = "Provides various loot functions.",
 	CM_LOOT_USAGE = "Usage: loot type||threshold||master||pass",
 	CM_LOOT_LFG = "Cannot use loot command in LFG group.",
 	CM_LOOT_NOMETHOD = "No loot method specified.",
 	CM_LOOT_NOTHRESHOLD = "No loot threshold specified.",
 	CM_LOOT_NOMASTER = "No master looter specified.",
-
+
 	CM_ROLL_HELP = "Provides tools for managing or starting/stopping rolls.",
 	CM_ROLL_USAGE = "Usage: roll [start||stop||pass||time||do||set]",
 	CM_ROLL_START_USAGE = "Usage: roll start <[time] [item]>",
 	CM_ROLL_SET_USAGE = "Usage: roll set min||max||time <amount>",
-
+
 	CM_RAIDWARNING_HELP = "Sends a raid warning.",
 	CM_RAIDWARNING_USAGE = "Usage: raidwarning <message>",
 	CM_RAIDWARNING_NORAID = "Cannot send raid warning when not in a raid group.",
 	CM_RAIDWARNING_NOPRIV = "Cannot send raid warning: Not raid leader or assistant.",
 	CM_RAIDWARNING_SENT = "Sent raid warning.",
-
+
+	CM_DUNGEONMODE_HELP = "Set the dungeon difficulty.",
+	CM_DUNGEONMODE_USAGE = "Usage: dungeondifficulty <difficulty>",
+
+	CM_RAIDMODE_HELP = "Set the raid difficulty.",
+	CM_RAIDMODE_USAGE = "Usage: raiddifficulty <difficulty>",
+
 	------------
 	-- Events --
 	------------
-
+
 	E_LFGPROPOSAL = "Group has been found, type !accept to make me accept the invite.",
 	E_LFGFAIL = "LFG failed, use !queue <type> to requeue.",
 	E_READYCHECK = "%s issued a ready check, type !rc accept to make me accept it or !rc deny to deny it.",
 	E_GROUPINVITE = "Type !acceptinvite to make me accept the group invite.",
-
+
 	------------------
 	-- EventHandler --
 	------------------
-
+
 	EH_REGISTERED = "%q registered.",
-
+
 	----------------------------
 	-- GroupInvite (Core-Sub) --
 	----------------------------
-
+
 	GI_ENABLED = "Group Invite (Announce) enabled.",
 	GI_DISABLED = "Group Invite (Announce) disabled.",
 	GI_DELAY_NUM = "Delay has to be a number.",
@@ -261,24 +276,24 @@ local L = {
 	------------
 	-- Logger --
 	------------
-
+
 	LOGGER_ERR_UNDEFINED = "Undefined logger level passed (%q)",
 	LOGGER_PREFIX_MAIN = "\124cff00FF00[%s]\124r",
 	LOGGER_PREFIX_DEBUG = " \124cffBBBBFFDebug\124r",
 	LOGGER_PREFIX_NORMAL = "",
 	LOGGER_PREFIX_WARNING = " \124cffFFFF00Warning\124r",
 	LOGGER_PREFIX_ERROR = " \124cffFF0000ERROR\124r",
-
+
 	-----------------
 	-- LootManager --
 	-----------------
-
+
 	LOOT_METHOD_GROUP = "Group Loot",
 	LOOT_METHOD_FFA = "Free For All",
 	LOOT_METHOD_MASTER = "Master Looter",
 	LOOT_METHOD_NEEDGREED = "Need Before Greed",
 	LOOT_METHOD_ROUNDROBIN = "Round Robin",
-
+
 	LOOT_THRESHOLD_UNCOMMON = "Uncommon",
 	LOOT_THRESHOLD_RARE = "Rare",
 	LOOT_THRESHOLD_EPIC = "Epic",
@@ -286,37 +301,37 @@ local L = {
 	LOOT_THRESHOLD_ARTIFACT = "Artifact",
 	LOOT_THRESHOLD_HEIRLOOM = "Heirloom",
 	LOOT_THRESHOLD_UNKNOWN = "Unknown",
-
+
 	LOOT_MASTER_NOEXIST = "%q is not in the group and cannot be set as the master looter.",
-
+
 	LOOT_SM_NOLEAD = "Unable to change loot method, not group leader.",
 	LOOT_SM_DUPE = "The loot method is already set to %s!",
 	LOOT_SM_SUCCESS = "Successfully set the loot method to %s!",
 	LOOT_SM_SUCCESSMASTER = "Successfully set the loot method to %s (%s)!",
-
+
 	LOOT_SLM_NOLOEAD = "Unable to change master looter, not group leader.",
 	LOOT_SLM_METHOD = "Cannot set master looter when loot method is set to %s.",
 	LOOT_SLM_SPECIFY = "Master looter not specified.",
 	LOOT_SLM_SUCCESS = "Successfully set %s as the master looter!",
-
+
 	LOOT_ST_NOLEAD = "Unable to change loot threshold, not group leader.",
 	LOOT_ST_INVALID = "Invalid loot threshold specified, please specify a loot threshold between 2 and 7 (inclusive).",
 	LOOT_ST_SUCCESS = "Successfully set the loot threshold to %s!",
-
+
 	LOOT_SP_PASS = "%s is now passing on loot.",
 	LOOT_SP_ROLL = "%s is not passing on loot.",
-
+
 	-------------------
 	-- PlayerManager --
 	-------------------
-
+
 	PM_ERR_NOCOMMAND = "No command specified.",
 	PM_ERR_LOCKED = "Target player is locked and cannot be modified.",
 	PM_ERR_NOTINGROUP = "%s is not in the group.",
-
+
 	PM_ACCESS_ALLOWED = "%q is now allowed for %s.",
 	PM_ACCESS_DENIED = "%q is now denied for %s.",
-
+
 	PM_KICK_REASON = "%s has been kicked on %s's request. (Reason: %s)",
 	PM_KICK = "%s has been kicked on %s's request.",
 	PM_KICK_NOTIFY = "%s was kicked on your request.",
@@ -328,25 +343,26 @@ local L = {
 	PM_KICK_DEFAULTREASON = "%s used !kick command.",
 	PM_KICK_WAIT = "Awaiting confirmation to kick %s...",
 	PM_KICK_NOPRIV = "Unable to kick %s from group. Not group leader or assistant.",
-
+	PM_KICK_TARGETASSIST = "Unable to kick %s, assistants cannot kick other assistants from group.",
+
 	PM_PLAYER_CREATE = "Created player %q with default settings.",
 	PM_PLAYER_UPDATE = "Updated player %q.",
-
+
 	PM_GA_REMOVED = "%q removed from group %s.",
 	PM_GA_EXISTSALLOW = "%q already has that command on the allow list.",
 	PM_GA_EXISTSDENY = "%q already has that command on the deny list.",
-
+
 	PM_PA_REMOVED = "%q removed from %s.",
 	PM_PA_EXISTSALLOW = "%s already has that command on the allow list.",
 	PM_PA_EXISTSDENY = "%s already has that command on the deny list.",
-
+
 	PM_LOCKED = "Player %s has been locked.",
 	PM_UNLOCKED = "Player %s has been unlocked",
-
+
 	PM_SAG_SELF = "Cannot modify my own access level.",
 	PM_SAG_NOEXIST = "No such access group: %q",
 	PM_SAG_SET = "Set the access level of %q to %d (%s).",
-
+
 	PM_INVITE_SELF = "Cannot invite myself to group.",
 	PM_INVITE_INGROUP = "%s is already in the group.",
 	PM_INVITE_FULL = "The group is already full.",
@@ -355,86 +371,105 @@ local L = {
 	PM_INVITE_SUCCESS = "Invited %s to group.",
 	PM_INVITE_BLOCKED = "%s does not wish to be invited.",
 	PM_INVITE_NOPRIV = "Unable to invite %s to group. Not group leader or assistant.",
-
+
 	PM_DI_BLOCKING = "You are now blocking invites, whisper !allowinvites to receive them again.",
 	PM_DI_SUCCESS = "%s is no longer receiving invites.",
 	PM_DI_FAIL = "You are already blocking invites.",
-
+
 	PM_AI_ALLOWING = "You are now allowing invites, whisper !blockinvites to block them.",
 	PM_AI_SUCCESS = "%s is now receiving invites.",
 	PM_AI_FAIL = "You are already allowing invites.",
-
+
 	PM_LEADER_SELF = "Cannot promote myself to leader.",
 	PM_LEADER_DUPE = "%s is already leader.",
 	PM_LEADER_SUCCESS = "Promoted %s to group leader.",
 	PM_LEADER_NOPRIV = "Cannot promote %s to group leader, insufficient permissions.",
-
+
 	PM_ASSIST_SELF = "Cannot promote myself to assistant.",
 	PM_ASSIST_DUPE = "%s is already assistant.",
 	PM_ASSIST_NORAID = "Cannot promote to assistant when not in a raid.",
 	PM_ASSIST_SUCCESS = "Promoted %s to assistant.",
 	PM_ASSIST_NOPRIV = "Cannot promote %s to assistant, insufficient permissions.",
-
+
 	PM_DEMOTE_SELF = "Cannot demote myself.",
 	PM_DEMOTE_INVALID = "%s is not an assistant, can only demote assistants.",
 	PM_DEMOTE_NORAID = "Cannot demote when not in a raid.",
 	PM_DEMOTE_SUCCESS = "Demoted %s.",
 	PM_DEMOTE_NOPRIV = "Cannot demote %s, insufficient permissions.",
-
+
 	PM_LIST_ADDWHITE = "Added %s to whitelist.",
 	PM_LIST_ADDBLACK = "Added %s to blacklist.",
 	PM_LIST_REMOVEWHITE = "Removed %s from whitelist.",
 	PM_LIST_REMOVEBLACK = "Removed %s from blacklist.",
 	PM_LIST_SETWHITE = "Now using list as whitelist.",
 	PM_LIST_SETBLACK = "Now using list as blacklist.",
-
+
+	----------------
+	-- GroupTools --
+	----------------
+
+	GT_DUNGEON_NORMAL = "Normal",
+	GT_DUNGEON_HEROIC = "Heroic",
+	GT_RAID_N10 = "Normal (10)",
+	GT_RAID_N25 = "Normal (25)",
+	GT_RAID_H10 = "Heroic (10)",
+	GT_RAID_H25 = "Heroic (25)",
+
+	GT_DIFF_INVALID = "%q is not a valid difficulty.",
+
+	GT_DD_DUPE = "Dungeon difficulty is already set to %s.",
+	GT_DD_SUCCESS = "Successfully set the dungeon difficulty to %s!",
+
+	GT_RD_DUPE = "Raid difficulty is already set to %s.",
+	GT_RD_SUCCESS = "Successfully set raid difficulty to %s!",
+
 	------------------
 	-- QueueManager --
 	------------------
-
+
 	QM_QUEUE_START = "Starting queue for %s, please select your role(s)... Type !cancel to cancel.",
 	QM_CANCEL = "Left the LFG queue.",
 	QM_ACCEPT = "Accepted LFG invite.",
 	QM_ANNOUNCE_QUEUEING = "Now queueing for %s, type !cancel to cancel.",
 	QM_ANNOUNCE_ROLECANCEL = "Role Check cancelled.",
 	QM_ANNOUNCE_LFGCANCEL = "LFG cancelled.",
-
+
 	-----------------
 	-- RollManager --
 	-----------------
-
+
 	RM_ERR_INVALIDAMOUNT = "Invalid amount passed: %s.",
 	RM_ERR_NOTRUNNING = "No roll is currently in progress.",
 	RM_ERR_INVALIDROLL = "%s specified too high or too low roll region, not including their roll.",
-
+
 	RM_MATCH = "(%w+) rolls (%d+) %((%d+)-(%d+)%)",
-
+
 	RM_ROLLEXISTS = "%s has already rolled! (%d)",
 	RM_ROLLPROGRESS = "%d/%d players have rolled!",
-
+
 	RM_UPDATE_TIMELEFT = "%d seconds left to roll!",
-
+
 	RM_SET_MINFAIL = "Minimum roll number cannot be higher than maximum roll number!",
 	RM_SET_MINSUCCESS = "Successfully set minimum roll number to %d!",
 	RM_SET_MAXFAIL = "Maximum roll number cannot be higher than minimum roll number!",
 	RM_SET_MAXSUCCESS = "Successfully set maximum roll number to %d!",
 	RM_SET_TIMEFAIL = "Amount must be larger than zero (0).",
 	RM_SET_TIMESUCCESS = "Successfully set default roll time to %d!",
-
+
 	RM_START_RUNNING = "A roll is already in progress, wait for it to complete or use roll stop.",
 	RM_START_SENDER = "Could not identify sender: %s. Aborting roll...",
 	RM_START_MEMBERS = "Could not start roll, not enough group members!",
 	RM_START_SUCCESS = "%s started a roll, ends in %d seconds! Type /roll %d %d. Type !roll pass to pass.",
 	RM_START_SUCCESSITEM = "%s started a roll for %s, ends in %d seconds! Type /roll %d %d. Type !roll pass to pass.",
-
+
 	RM_STOP_SUCCESS = "Roll has been stopped.",
-
+
 	RM_DO_SUCCESS = "Done! Executed RandomRoll(%d, %d)",
-
+
 	RM_PASS_SUCCESS = "%s has passed on the roll.",
-
+
 	RM_TIME_LEFT = "%d seconds remaining!",
-
+
 	RM_ANNOUNCE_EXPIRE = "Roll time expired! Results...",
 	RM_ANNOUNCE_FINISH = "Everyone has rolled! Results...",
 	RM_ANNOUNCE_EMPTY = "Noone rolled, there is no winner!",
diff --git a/locales/svSE.lua b/locales/svSE.lua
index 369ccd2..daee112 100644
--- a/locales/svSE.lua
+++ b/locales/svSE.lua
@@ -1,18 +1,18 @@
 --[[
 	* Copyright (c) 2011-2012 by Adam Hellberg.
-	*
+	*
 	* This file is part of Command.
-	*
+	*
 	* Command is free software: you can redistribute it and/or modify
 	* it under the terms of the GNU General Public License as published by
 	* the Free Software Foundation, either version 3 of the License, or
 	* (at your option) any later version.
-	*
+	*
 	* Command is distributed in the hope that it will be useful,
 	* but WITHOUT ANY WARRANTY; without even the implied warranty of
 	* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 	* GNU General Public License for more details.
-	*
+	*
 	* You should have received a copy of the GNU General Public License
 	* along with Command. If not, see <http://www.gnu.org/licenses/>.
 --]]
@@ -24,20 +24,20 @@ local L = {

 	LOCALE_NOT_LOADED = "Det specifierade språket är inte initialiserat.",
 	LOCALE_UPDATE = "Nytt språk inställt till: %s",
-	LOCALE_PI_ACTIVE = "Språkinställningar per-användare är nu aktivt.",
-	LOCALE_PI_INACTIVE = "Språkinställningar per-användare är nu inaktivt.",
-
+	LOCALE_PI_ACTIVE = "Språkinställning per-användare är nu aktivt.",
+	LOCALE_PI_INACTIVE = "Språkinställning per-användare är nu inaktivt.",
+
 	-------------
 	-- General --
 	-------------
-
+
 	YES = "Ja",
 	NO = "Nej",
-
+
 	----------
 	-- Core --
 	----------
-
+
 	ADDON_LOAD = "AddOn initialiserad! Använd /cmd help eller !help för hjälp.",
 	SVARS_OUTDATED = "Sparade variabler inaktuella, återställer...",
 	NEWVERSION_NOTICE = "\124cffFF0000En ny version av \124cff00FFFF%s\124cffFF0000 är tillgänglig! \124cffFFFF00Gå till sidan där du laddade ned Command för att installera den.",
@@ -45,130 +45,139 @@ local L = {
 	DISABLED = "AddOn \124cffFF0000avaktiverad\124r.",
 	DEBUGENABLED = "Debugging \124cff00FF00aktiverat\124r.",
 	DEBUGDISABLED = "Debugging \124cffFF0000avaktiverat\124r.",
-
+
 	---------------
 	-- AddonComm --
 	---------------
-
+
 	AC_ERR_PREFIX = "[KRITISK] Misslyckades med att registrera AddOn prefix %q. Maximalt antal prefix har nåtts på klienten.",
 	AC_ERR_MSGTYPE = "Ogiltig meddelandetyp: %s",
 	AC_ERR_MALFORMED_DATA = "Ogiltig data från %s. Deras AddOn är antagligen inaktuell.",
 	AC_ERR_MALFORMED_DATA_SEND = "[AddonComm] Malformed data detected (\"%s\"). Aborting Send...",
-
+
 	AC_GROUP_NORESP = "Inget svar från grupp, uppdaterar...",
 	AC_GROUP_R_UPDATE = "Gruppmedlemmar uppdaterade, kontrollerare: %s",
 	AC_GROUP_LEFT = "Grupp lämnad, återställer gruppvariabler...",
 	AC_GROUP_WAIT = "Väntar på gruppsvar...",
 	AC_GROUP_REMOVE = "Upptäckte att %s inte längre är i gruppen, tar bort och uppdaterar gruppmedlemmar...",
 	AC_GROUP_SYNC = "Grupphanterare inaktuella! Skickar synkmeddelande...",
-
+
 	AC_GUILD_NORESP = "Inget svar från guild, uppdaterar...",
 	AC_GUILD_R_UPDATE = "Guildmedlemmar uppdaterade, kontrollerare: %s",
 	AC_GUILD_WAIT = "Väntar på guildsvar...",
-
+
 	-----------------
 	-- ChatManager --
 	-----------------
-
+
 	CHAT_ERR_CMDCHAR = "Kommandotecken måste vara ett tecken.",
 	CHAT_CMDCHAR_SUCCESS = "Nytt kommandotecken inställt till: %s",
 	CHAT_HANDLE_NOTCONTROLLER = "Ej kontroller för \124cff00FFFF%s\124r, avbryter.",
-
+
 	--------------------
 	-- CommandManager --
 	--------------------
-
+
 	CM_ERR_NOTALLOWED = "%s är inte tillåtet att användas, %s.",
 	CM_ERR_NOACCESS = "Du har inte tillåtelse att använda det kommandot, %s. Behörighet som krävs: %d. Din behörighet: %d.",
 	CM_ERR_NOTREGGED = "%q är inte ett registrerat kommando.",
 	CM_ERR_NOCMDCHAR = "Inget kommandotecken angivet.",
 	CM_ERR_NOCHAT = "Det här kommandot kan inte användas från chatten.",
 	CM_ERR_CHATONLY = "Det här kommandot kan endast användas från chatten.",
-
+	CM_ERR_DISABLED = "Det här kommandot har blivit permanent avstängt.",
+
 	CM_NO_HELP = "Ingen hjälp tillgänglig.",
-
+
 	CM_DEFAULT_HELP = "Visar det här hjälpmeddelandet.",
-	CM_DEFAULT_CHAT = "Skriv !commands för en lista över kommandon.",
+	CM_DEFAULT_HELPCOMMAND = "Use \"help <command>\" to get help on a specific command.",
+	CM_DEFAULT_CHAT = "Skriv !commands för en lista över kommandon. Skriv !help <command> för hjälp med ett specifikt kommando.",
 	CM_DEFAULT_END = "Slut på hjälpmeddelandet.",
-
+
+	CM_HELP_HELP = "Gets help about the addon or a specific command.",
+	CM_HELP_USAGE = "Användning: help <command>",
+
 	CM_COMMANDS_HELP = "Visa alla registrerade kommandon.",
-
+
 	CM_VERSION_HELP = "Visa versionen av Command",
 	CM_VERSION = "%s",
-
+
 	CM_SET_HELP = "Ändra inställningarna i Command.",
-	CM_SET_USAGE = "Användning: set cmdchar|groupinvite|setlocale|locale",
+	CM_SET_USAGE = "Användning: set cmdchar|groupinvite",
 	CM_SET_GROUPINVITE_USAGE = "Användning: set groupinvite enable|disable|<tid>",
-	CM_SET_SETLOCALE_USAGE = "Användning: set setlocale <språk>",
-	CM_SET_LOCALE_USAGE = "Användning: set locale reset|usemaster",
-
+
+	CM_LOCALE_HELP = "Change locale settings.",
+	CM_LOCALE_USAGE ="Användning: locale [set|reset|usemaster|playerindependent]",
+	CM_LOCALE_CURRENT = "Current locale: %s.",
+	CM_LOCALE_SET_USAGE = "Användning: locale set <locale>",
+
 	CM_MYLOCALE_HELP = "Låter användare ställa in sitt eget språk.",
 	CM_MYLOCALE_SET = "Ditt språk är nu inställt till %s.",
-
+
 	CM_LOCK_HELP = "Lås en användare.",
 	CM_UNLOCK_HELP = "Lås upp en användare.",
-
+
 	CM_GETACCESS_HELP = "Visa en användares behörighetsnivå.",
 	CM_GETACCESS_STRING = "%ss behörighet är %d (%s)",
-
+
 	CM_SETACCESS_HELP = "Ändra en användares behörighetsnivå/grupp.",
 	CM_SETACCESS_USAGE = "Användning: setaccess [namn] <grupp>",
-
+
 	CM_OWNER_HELP = "Befordra en användare till ägarnivå.",
-
+
 	CM_ADMIN_HELP = "Befordra en användare till adminnivå.",
 	CM_ADMIN_USAGE = "Användning: admin <namn>",
-
+
 	CM_OP_HELP = "Befordra en användare till operatörnivå.",
-
+
 	CM_USER_HELP = "Befordra en användare till användarnivå.",
-
+
 	CM_BAN_HELP = "Bannlys en användare.",
 	CM_BAN_USAGE = "Användning: ban <namn>",
-
+
 	CM_ACCEPTINVITE_HELP = "Accepterar en pågående gruppinbjudan.",
 	CM_ACCEPTINVITE_NOTACTIVE = "Inga aktiva inbjudningar just nu.",
 	CM_ACCEPTINVITE_EXISTS = "Jag är redan i en grupp.",
 	CM_ACCEPTINVITE_SUCCESS = "Accepterade gruppinbjudningen!",
-
+
 	CM_INVITE_HELP = "Bjuder in en användare till gruppen.",
-
+
 	CM_INVITEME_HELP = "Användaren som skickar kommandot bjuds in till gruppen.",
-
+
 	CM_DENYINVITE_HELP = "Användare som använder det här kommandot kommer inte längre att få gruppinbjudningar.",
-
+
 	CM_ALLOWINVITE_HELP = "Användare som använder det här kommandot kommer att få gruppinbjudningar.",
-
+
 	CM_KICK_HELP = "Sparka ut en spelare från gruppen med valfri anledning (Kräver konfirmation).",
 	CM_KICK_USAGE = "Användning: kick <spelare> [anledning]",
-
+
 	CM_KINGME_HELP = "Player issuing this command will be promoted to group leader.",
-
+
 	CM_OPME_HELP = "Player issuing this command will be promoted to raid assistant.",
-
+
 	CM_DEOPME_HELP = "Player issuing this command will be demoted from assistant status.",
-
+
 	CM_LEADER_HELP = "Promote a player to group leader.",
-	CM_LEADER_USAGE = "Usage: leader <name>",
-
+	CM_LEADER_USAGE = "Användning: leader <name>",
+
 	CM_PROMOTE_HELP = "Promote a player to raid assistant.",
-	CM_PROMOTE_USAGE = "Usage: promote <name>",
-
+	CM_PROMOTE_USAGE = "Användning: promote <name>",
+
 	CM_DEMOTE_HELP = "Demote a player from assistant status.",
-	CM_DEMOTE_USAGE = "Usage: demote <name>",
-
+	CM_DEMOTE_USAGE = "Användning: demote <name>",
+
 	CM_QUEUE_HELP = "Enter the LFG queue for the specified category.",
-	CM_QUEUE_USAGE = "Usage: queue <type>",
+	CM_QUEUE_USAGE = "Användning: queue <type>",
 	CM_QUEUE_INVALID = "No such dungeon type: %q.",
-
+
 	CM_LEAVELFG_HELP = "Lämna LFG-kön.",
 	CM_LEAVELFG_FAIL = "Not queued by command, unable to cancel.",
-
+
 	CM_ACCEPTLFG_HELP = "Causes you to accept the LFG invite.",
 	CM_ACCEPTLFG_FAIL = "Not currently queued by command.",
-
+	CM_ACCEPTLFG_NOEXIST = "There is currently no LFG proposal to accept.",
+
 	CM_CONVERT_HELP = "Convert group to party or raid.",
-	CM_CONVERT_USAGE = "Usage: convert party||raid",
+	CM_CONVERT_USAGE = "Användning: convert party||raid",
 	CM_CONVERT_LFG = "LFG groups cannot be converted.",
 	CM_CONVERT_NOGROUP = "Cannot convert if not in a group.",
 	CM_CONVERT_NOLEAD = "Cannot convert group, not leader.",
@@ -177,34 +186,34 @@ local L = {
 	CM_CONVERT_RAID = "Converted party to raid.",
 	CM_CONVERT_RAIDFAIL = "Group is already a raid.",
 	CM_CONVERT_INVALID = "Invalid group type, only \"party\" or \"raid\" allowed.",
-
+
 	CM_LIST_HELP = "Toggle status of a command on the blacklist/whitelist.",
-	CM_LIST_USAGE = "Usage: list <command>",
-
+	CM_LIST_USAGE = "Användning: list <command>",
+
 	CM_LISTMODE_HELP = "Toggle list between being a blacklist and being a whitelist.",
-
+
 	CM_GROUPALLOW_HELP = "Allow a group to use a specific command.",
-	CM_GROUPALLOW_USAGE = "Usage: groupallow <group> <command>",
-
+	CM_GROUPALLOW_USAGE = "Användning: groupallow <group> <command>",
+
 	CM_GROUPDENY_HELP = "Deny a group to use a specific command.",
-	CM_GROUPDENY_USAGE = "Usage: groupdeny <group> <command>",
-
+	CM_GROUPDENY_USAGE = "Användning: groupdeny <group> <command>",
+
 	CM_RESETGROUPACCESS_HELP = "Reset the group's access to a specific command.",
-	CM_RESETGROUPACCESS_USAGE = "Usage: resetgroupaccess <group> <command>",
-
+	CM_RESETGROUPACCESS_USAGE = "Användning: resetgroupaccess <group> <command>",
+
 	CM_USERALLOW_HELP = "Allow a user to use a specific command.",
-	CM_USERALLOW_USAGE = "Usage: userallow <player> <command>",
-
+	CM_USERALLOW_USAGE = "Användning: userallow <player> <command>",
+
 	CM_USERDENY_HELP = "Deny a user to use a specific command.",
-	CM_USERDENY_USAGE = "Usage: userdeny <player> <command>",
-
+	CM_USERDENY_USAGE = "Användning: userdeny <player> <command>",
+
 	CM_RESETUSERACCESS_HELP = "Reset the user's access to a specific command.",
-	CM_RESETUSERACCESS_USAGE = "Usage: resetuseraccess <player> <command>",
-
+	CM_RESETUSERACCESS_USAGE = "Användning: resetuseraccess <player> <command>",
+
 	CM_TOGGLE_HELP = "Toggle AddOn on and off.",
-
+
 	CM_TOGGLEDEBUG_HELP = "Toggle debugging mode on and off.",
-
+
 	CM_READYCHECK_HELP = "Respond to ready check or initiate a new one.",
 	CM_READYCHECK_ISSUED = "%s issued a ready check!",
 	CM_READYCHECK_NOPRIV = "Cannot initiate ready check when not leader or assistant.",
@@ -213,44 +222,50 @@ local L = {
 	CM_READYCHECK_DECLINED = "Declined ready check.",
 	CM_READYCHECK_INVALID = "Invalid argument: %s",
 	CM_READYCHECK_FAIL = "Failed to accept or decline ready check.",
-
+
 	CM_LOOT_HELP = "Provides various loot functions.",
-	CM_LOOT_USAGE = "Usage: loot type||threshold||master||pass",
+	CM_LOOT_USAGE = "Användning: loot type||threshold||master||pass",
 	CM_LOOT_LFG = "Cannot use loot command in LFG group.",
 	CM_LOOT_NOMETHOD = "No loot method specified.",
 	CM_LOOT_NOTHRESHOLD = "No loot threshold specified.",
 	CM_LOOT_NOMASTER = "No master looter specified.",
-
+
 	CM_ROLL_HELP = "Provides tools for managing or starting/stopping rolls.",
-	CM_ROLL_USAGE = "Usage: roll [start||stop||pass||time||do||set]",
-	CM_ROLL_START_USAGE = "Usage: roll start <[time] [item]>",
-	CM_ROLL_SET_USAGE = "Usage: roll set min||max||time <amount>",
-
+	CM_ROLL_USAGE = "Användning: roll [start||stop||pass||time||do||set]",
+	CM_ROLL_START_USAGE = "Användning: roll start <[time] [item]>",
+	CM_ROLL_SET_USAGE = "Användning: roll set min||max||time <amount>",
+
 	CM_RAIDWARNING_HELP = "Sends a raid warning.",
-	CM_RAIDWARNING_USAGE = "Usage: raidwarning <message>",
+	CM_RAIDWARNING_USAGE = "Användning: raidwarning <message>",
 	CM_RAIDWARNING_NORAID = "Cannot send raid warning when not in a raid group.",
 	CM_RAIDWARNING_NOPRIV = "Cannot send raid warning: Not raid leader or assistant.",
 	CM_RAIDWARNING_SENT = "Sent raid warning.",
-
+
+	CM_DUNGEONMODE_HELP = "Set the dungeon difficulty.",
+	CM_DUNGEONMODE_USAGE = "Användning: dungeondifficulty <difficulty>",
+
+	CM_RAIDMODE_HELP = "Set the raid difficulty.",
+	CM_RAIDMODE_USAGE = "Användning: raiddifficulty <difficulty>",
+
 	------------
 	-- Events --
 	------------
-
+
 	E_LFGPROPOSAL = "Group has been found, type !accept to make me accept the invite.",
 	E_LFGFAIL = "LFG failed, use !queue <type> to requeue.",
 	E_READYCHECK = "%s issued a ready check, type !rc accept to make me accept it or !rc deny to deny it.",
 	E_GROUPINVITE = "Type !acceptinvite to make me accept the group invite.",
-
+
 	------------------
 	-- EventHandler --
 	------------------
-
+
 	EH_REGISTERED = "%q registered.",
-
+
 	----------------------------
 	-- GroupInvite (Core-Sub) --
 	----------------------------
-
+
 	GI_ENABLED = "Group Invite (Announce) enabled.",
 	GI_DISABLED = "Group Invite (Announce) disabled.",
 	GI_DELAY_NUM = "Delay has to be a number.",
@@ -261,24 +276,24 @@ local L = {
 	------------
 	-- Logger --
 	------------
-
+
 	LOGGER_ERR_UNDEFINED = "Undefined logger level passed (%q)",
 	LOGGER_PREFIX_MAIN = "\124cff00FF00[%s]\124r",
 	LOGGER_PREFIX_DEBUG = " \124cffBBBBFFDebug\124r",
 	LOGGER_PREFIX_NORMAL = "",
 	LOGGER_PREFIX_WARNING = " \124cffFFFF00Warning\124r",
 	LOGGER_PREFIX_ERROR = " \124cffFF0000ERROR\124r",
-
+
 	-----------------
 	-- LootManager --
 	-----------------
-
+
 	LOOT_METHOD_GROUP = "Group Loot",
 	LOOT_METHOD_FFA = "Free For All",
 	LOOT_METHOD_MASTER = "Master Looter",
 	LOOT_METHOD_NEEDGREED = "Need Before Greed",
 	LOOT_METHOD_ROUNDROBIN = "Round Robin",
-
+
 	LOOT_THRESHOLD_UNCOMMON = "Uncommon",
 	LOOT_THRESHOLD_RARE = "Rare",
 	LOOT_THRESHOLD_EPIC = "Epic",
@@ -286,37 +301,37 @@ local L = {
 	LOOT_THRESHOLD_ARTIFACT = "Artifact",
 	LOOT_THRESHOLD_HEIRLOOM = "Heirloom",
 	LOOT_THRESHOLD_UNKNOWN = "Unknown",
-
+
 	LOOT_MASTER_NOEXIST = "%q is not in the group and cannot be set as the master looter.",
-
+
 	LOOT_SM_NOLEAD = "Unable to change loot method, not group leader.",
 	LOOT_SM_DUPE = "The loot method is already set to %s!",
 	LOOT_SM_SUCCESS = "Successfully set the loot method to %s!",
 	LOOT_SM_SUCCESSMASTER = "Successfully set the loot method to %s (%s)!",
-
+
 	LOOT_SLM_NOLOEAD = "Unable to change master looter, not group leader.",
 	LOOT_SLM_METHOD = "Cannot set master looter when loot method is set to %s.",
 	LOOT_SLM_SPECIFY = "Master looter not specified.",
 	LOOT_SLM_SUCCESS = "Successfully set %s as the master looter!",
-
+
 	LOOT_ST_NOLEAD = "Unable to change loot threshold, not group leader.",
 	LOOT_ST_INVALID = "Invalid loot threshold specified, please specify a loot threshold between 2 and 7 (inclusive).",
 	LOOT_ST_SUCCESS = "Successfully set the loot threshold to %s!",
-
+
 	LOOT_SP_PASS = "%s is now passing on loot.",
 	LOOT_SP_ROLL = "%s is not passing on loot.",
-
+
 	-------------------
 	-- PlayerManager --
 	-------------------
-
+
 	PM_ERR_NOCOMMAND = "No command specified.",
 	PM_ERR_LOCKED = "Target player is locked and cannot be modified.",
 	PM_ERR_NOTINGROUP = "%s is not in the group.",
-
+
 	PM_ACCESS_ALLOWED = "%q is now allowed for %s.",
 	PM_ACCESS_DENIED = "%q is now denied for %s.",
-
+
 	PM_KICK_REASON = "%s has been kicked on %s's request. (Reason: %s)",
 	PM_KICK = "%s has been kicked on %s's request.",
 	PM_KICK_NOTIFY = "%s was kicked on your request.",
@@ -328,25 +343,26 @@ local L = {
 	PM_KICK_DEFAULTREASON = "%s used !kick command.",
 	PM_KICK_WAIT = "Awaiting confirmation to kick %s...",
 	PM_KICK_NOPRIV = "Unable to kick %s from group. Not group leader or assistant.",
-
+	PM_KICK_TARGETASSIST = "Unable to kick %s, assistants cannot kick other assistants from group.",
+
 	PM_PLAYER_CREATE = "Created player %q with default settings.",
 	PM_PLAYER_UPDATE = "Updated player %q.",
-
+
 	PM_GA_REMOVED = "%q removed from group %s.",
 	PM_GA_EXISTSALLOW = "%q already has that command on the allow list.",
 	PM_GA_EXISTSDENY = "%q already has that command on the deny list.",
-
+
 	PM_PA_REMOVED = "%q removed from %s.",
 	PM_PA_EXISTSALLOW = "%s already has that command on the allow list.",
 	PM_PA_EXISTSDENY = "%s already has that command on the deny list.",
-
+
 	PM_LOCKED = "Player %s has been locked.",
 	PM_UNLOCKED = "Player %s has been unlocked",
-
+
 	PM_SAG_SELF = "Cannot modify my own access level.",
 	PM_SAG_NOEXIST = "No such access group: %q",
 	PM_SAG_SET = "Set the access level of %q to %d (%s).",
-
+
 	PM_INVITE_SELF = "Cannot invite myself to group.",
 	PM_INVITE_INGROUP = "%s is already in the group.",
 	PM_INVITE_FULL = "The group is already full.",
@@ -355,86 +371,105 @@ local L = {
 	PM_INVITE_SUCCESS = "Invited %s to group.",
 	PM_INVITE_BLOCKED = "%s does not wish to be invited.",
 	PM_INVITE_NOPRIV = "Unable to invite %s to group. Not group leader or assistant.",
-
+
 	PM_DI_BLOCKING = "You are now blocking invites, whisper !allowinvites to receive them again.",
 	PM_DI_SUCCESS = "%s is no longer receiving invites.",
 	PM_DI_FAIL = "You are already blocking invites.",
-
+
 	PM_AI_ALLOWING = "You are now allowing invites, whisper !blockinvites to block them.",
 	PM_AI_SUCCESS = "%s is now receiving invites.",
 	PM_AI_FAIL = "You are already allowing invites.",
-
+
 	PM_LEADER_SELF = "Cannot promote myself to leader.",
 	PM_LEADER_DUPE = "%s is already leader.",
 	PM_LEADER_SUCCESS = "Promoted %s to group leader.",
 	PM_LEADER_NOPRIV = "Cannot promote %s to group leader, insufficient permissions.",
-
+
 	PM_ASSIST_SELF = "Cannot promote myself to assistant.",
 	PM_ASSIST_DUPE = "%s is already assistant.",
 	PM_ASSIST_NORAID = "Cannot promote to assistant when not in a raid.",
 	PM_ASSIST_SUCCESS = "Promoted %s to assistant.",
 	PM_ASSIST_NOPRIV = "Cannot promote %s to assistant, insufficient permissions.",
-
+
 	PM_DEMOTE_SELF = "Cannot demote myself.",
 	PM_DEMOTE_INVALID = "%s is not an assistant, can only demote assistants.",
 	PM_DEMOTE_NORAID = "Cannot demote when not in a raid.",
 	PM_DEMOTE_SUCCESS = "Demoted %s.",
 	PM_DEMOTE_NOPRIV = "Cannot demote %s, insufficient permissions.",
-
+
 	PM_LIST_ADDWHITE = "Added %s to whitelist.",
 	PM_LIST_ADDBLACK = "Added %s to blacklist.",
 	PM_LIST_REMOVEWHITE = "Removed %s from whitelist.",
 	PM_LIST_REMOVEBLACK = "Removed %s from blacklist.",
 	PM_LIST_SETWHITE = "Now using list as whitelist.",
 	PM_LIST_SETBLACK = "Now using list as blacklist.",
-
+
+	----------------
+	-- GroupTools --
+	----------------
+
+	GT_DUNGEON_NORMAL = "Normal",
+	GT_DUNGEON_HEROIC = "Heroic",
+	GT_RAID_N10 = "Normal (10)",
+	GT_RAID_N25 = "Normal (25)",
+	GT_RAID_H10 = "Heroic (10)",
+	GT_RAID_H25 = "Heroic (25)",
+
+	GT_DIFF_INVALID = "%q is not a valid difficulty.",
+
+	GT_DD_DUPE = "Dungeon difficulty is already set to %s.",
+	GT_DD_SUCCESS = "Successfully set the dungeon difficulty to %s!",
+
+	GT_RD_DUPE = "Raid difficulty is already set to %s.",
+	GT_RD_SUCCESS = "Successfully set raid difficulty to %s!",
+
 	------------------
 	-- QueueManager --
 	------------------
-
+
 	QM_QUEUE_START = "Starting queue for %s, please select your role(s)... Type !cancel to cancel.",
 	QM_CANCEL = "Left the LFG queue.",
 	QM_ACCEPT = "Accepted LFG invite.",
 	QM_ANNOUNCE_QUEUEING = "Now queueing for %s, type !cancel to cancel.",
 	QM_ANNOUNCE_ROLECANCEL = "Role Check cancelled.",
 	QM_ANNOUNCE_LFGCANCEL = "LFG cancelled.",
-
+
 	-----------------
 	-- RollManager --
 	-----------------
-
+
 	RM_ERR_INVALIDAMOUNT = "Invalid amount passed: %s.",
 	RM_ERR_NOTRUNNING = "No roll is currently in progress.",
 	RM_ERR_INVALIDROLL = "%s specified too high or too low roll region, not including their roll.",
-
+
 	RM_MATCH = "(%w+) rolls (%d+) %((%d+)-(%d+)%)", -- Same as enUS since svSE has no official client support.
-
+
 	RM_ROLLEXISTS = "%s has already rolled! (%d)",
 	RM_ROLLPROGRESS = "%d/%d spelare har rolled!",
-
+
 	RM_UPDATE_TIMELEFT = "%d seconds left to roll!",
-
+
 	RM_SET_MINFAIL = "Minimum roll number cannot be higher than maximum roll number!",
 	RM_SET_MINSUCCESS = "Successfully set minimum roll number to %d!",
 	RM_SET_MAXFAIL = "Maximum roll number cannot be higher than minimum roll number!",
 	RM_SET_MAXSUCCESS = "Successfully set maximum roll number to %d!",
 	RM_SET_TIMEFAIL = "Amount must be larger than zero (0).",
 	RM_SET_TIMESUCCESS = "Successfully set default roll time to %d!",
-
+
 	RM_START_RUNNING = "A roll is already in progress, wait for it to complete or use roll stop.",
 	RM_START_SENDER = "Could not identify sender: %s. Aborting roll...",
 	RM_START_MEMBERS = "Could not start roll, not enough group members!",
 	RM_START_SUCCESS = "%s started a roll, ends in %d seconds! Type /roll %d %d. Type !roll pass to pass.",
 	RM_START_SUCCESSITEM = "%s started a roll for %s, ends in %d seconds! Type /roll %d %d. Type !roll pass to pass.",
-
+
 	RM_STOP_SUCCESS = "Roll has been stopped.",
-
+
 	RM_DO_SUCCESS = "Done! Executed RandomRoll(%d, %d)",
-
+
 	RM_PASS_SUCCESS = "%s has passed on the roll.",
-
+
 	RM_TIME_LEFT = "%d seconds remaining!",
-
+
 	RM_ANNOUNCE_EXPIRE = "Roll time expired! Results...",
 	RM_ANNOUNCE_FINISH = "Everyone has rolled! Results...",
 	RM_ANNOUNCE_EMPTY = "Noone rolled, there is no winner!",