Quantcast

Started refactoring

Alar of Daggerspine [02-13-15 - 00:18]
Started refactoring
Signed-off-by: Alar of Daggerspine <alar@aspide.it>
Filename
Debug.lua
FollowerCache.lua
FollowerPage.lua
FollowerRecruiting.lua
GarrisonCommander-Broker/ldb.lua
GarrisonCommander.lua
GarrisonCommander.toc
Init.lua
MatchMaker.lua
MissionCache.lua
MissionCompletion.lua
MissionControl.lua
PartyCache.lua
diff --git a/Debug.lua b/Debug.lua
new file mode 100644
index 0000000..4afd57c
--- /dev/null
+++ b/Debug.lua
@@ -0,0 +1,227 @@
+--@do-not-package@
+local me, ns = ...
+local addon=ns.addon --#addon
+local L=ns.L
+local D=ns.D
+local C=ns.C
+local AceGUI=ns.AceGUI
+local _G=_G
+local pp=print
+_G.GAC=addon
+
+--- Enable a trace for every function call. It's a VERY heavy debug
+--
+if ns.HD then
+local memorysinks={}
+local callstack={}
+local lib=LibStub("LibInit")
+for k,v in pairs(addon) do
+	if (type(v))=="function" and not lib[k] then
+		local wrapped
+		do
+			local original=addon[k]
+			wrapped=function(...)
+				pp(k)
+				tinsert(callstack,k)
+				local membefore=GetAddOnMemoryUsage("GarrisonCommander")
+				local a1,a2,a3,a4,a5,a6,a7,a8,a9=original(...)
+				local memafter=GetAddOnMemoryUsage("GarrisonCommander")
+				tremove(callstack)
+				memorysinks[k].mem=memorysinks[k].mem+memafter-membefore
+				memorysinks[k].calls=memorysinks[k].calls+1
+				if (#callstack) then
+					memorysinks[k].callers=strjoin("->",unpack(callstack))
+				else
+					memorysinks[k].callers="main"
+				end
+				if (memafter-membefore > 20) then
+					pp(C(k,'Red'),'used ',memafter-membefore)
+				end
+				return a1,a2,a3,a4,a5,a6,a7,a8,a9
+			end
+		end
+		addon[k]=wrapped
+		memorysinks[k]={mem=0,calls=0,callers=""}
+	end
+end
+function addon:ResetSinks()
+	for k,v in pairs(memorysinks) do
+		memorysinks[k].mem=0
+		memorysinks[k].calls=0
+	end
+end
+local sorted={}
+function addon:DumpSinks()
+	local scroll=self:GetScroller("Sinks",nil,400,1000)
+	wipe(sorted)
+	for k,v in pairs(memorysinks) do
+		if v.mem then
+			tinsert(sorted,format("Mem %06d (calls: %03d) Mem per call:%03.2f  Callstack:%s(%s)",v.mem,v.calls,v.mem/v.calls,C(k,"Orange"),v.callers))
+		end
+	end
+	table.sort(sorted,function(a,b) return a>b end)
+	self:cutePrint(scroll,sorted)
+end
+end
+function addon:GetScroller(title,type,h,w)
+	h=h or 800
+	w=w or 400
+	type=type or "Frame"
+	local scrollerWindow=AceGUI:Create("Frame")
+	scrollerWindow:SetTitle(title)
+	scrollerWindow:SetLayout("Fill")
+	--local scrollcontainer = AceGUI:Create("SimpleGroup") -- "InlineGroup" is also good
+	--scrollcontainer:SetFullWidth(true)
+	--scrollcontainer:SetFullHeight(true) -- probably?
+	--scrollcontainer:SetLayout("Fill") -- important!
+	--scrollerWindow:AddChild(scrollcontainer)
+	local scroll = AceGUI:Create("ScrollFrame")
+	scroll:SetLayout("Flow") -- probably?
+	scroll:SetFullWidth(true)
+	scroll:SetFullHeight(true)
+	scrollerWindow:AddChild(scroll)
+	scrollerWindow:SetCallback("OnClose","Release")
+	scrollerWindow:SetHeight(h)
+	scrollerWindow:SetWidth(w)
+	scrollerWindow:SetPoint("CENTER")
+	scrollerWindow:Show()
+	scroll.AddRow=function (self,...) return addon:AddRow(self,...) end
+	return scroll
+end
+function addon:AddRow(obj,text,...)
+--@debug@
+	assert(obj)
+--@end-debug@
+	if (obj) then
+		local l=AceGUI:Create("Label")
+		l:SetText(text)
+		l:SetColor(...)
+		l:SetFullWidth(true)
+		obj:AddChild(l)
+	end
+end
+function addon:cutePrint(scroll,level,k,v)
+	if (type(level)=="table") then
+		for k,v in pairs(level) do
+			self:cutePrint(scroll,"",k,v)
+		end
+		return
+	end
+	if (type(v)=="table") then
+		self:AddRow(scroll,level..C(k,"Azure")..":" ..C("Table","Orange"))
+		for kk,vv in pairs(v) do
+			self:cutePrint(scroll,level .. "  ",kk,vv)
+		end
+	else
+		if (type(v)=="string" and v:sub(1,2)=='0x') then
+			v=v.. " " ..tostring(self:GetFollowerData(v,'name'))
+		end
+		self:AddRow(scroll,level..C(k,"White")..":" ..C(v,"Yellow"))
+	end
+end
+function addon:DumpFollower(name)
+	local follower=self:GetFollowerData(name)
+	if (follower) then
+		local scroll=self:GetScroller(follower.name)
+		self:cutePrint(scroll,follower)
+	end
+
+end
+function addon:DumpStatus(title)
+	local scroll=self:GetScroller(title)
+	for i=1,#followersCache do
+		local followerID=followersCache[i].followerID
+		scroll:AddRow(format("%s (%s): %d",self:GetFollowerData(followerID,'fullname'),self:GetFollowerData(followerID,'followerID'),G.GetFollowerXP(followerID)))
+	end
+	scroll:AddRow("Garrison resources: " .. select(2,GetCurrencyInfo(GARRISON_CURRENCY)))
+	scroll:AddRow("Money: " .. GetMoneyString(GetMoney()))
+end
+function addon:DumpFollowers()
+	local scroll=self:GetScroller("Followers Cache (" .. #followersCache ..")"  )
+	self:cutePrint(scroll,followersCache)
+end
+function addon:DumpFollowerMissions(missionID)
+	local scroll=self:GetScroller("FollowerMissions " .. self:GetMissionData(missionID,'name'))
+	self:cutePrint(scroll,followerMissions.missions[missionID])
+end
+function addon:DumpIgnored()
+	local scroll=self:GetScroller("Ignored")
+	self:cutePrint(scroll,self.privatedb.profile.ignored)
+end
+function addon:DumpMission(missionID)
+	local scroll=self:GetScroller("MissionCache " .. self:GetMissionData(missionID,'name'))
+	self:cutePrint(scroll,cache.missions[missionID])
+end
+function addon:DumpMissions()
+	local scroll=self:GetScroller("MissionCache")
+	for id,data in pairs(cache.missions) do
+		self:cutePrint(scroll,id .. '.'..data.name)
+	end
+end
+---
+-- Debug function
+--@param missionID Identificativo missione
+function addon:DumpCounters(missionID)
+	local scroll=self:GetScroller("Counters " .. self:GetMissionData(missionID,'name'))
+	self:cutePrint(scroll,counters[missionID])
+	self:cutePrint(scroll,"Lista per follower","","")
+	self:cutePrint(scroll,counterFollowerIndex[missionID])
+	self:cutePrint(scroll,"Lista per threat","","")
+	self:cutePrint(scroll,counterThreatIndex[missionID])
+end
+function addon:Dump(title,data)
+	local scroll=self:GetScroller(title)
+	self:cutePrint(scroll,data)
+
+end
+function addon:DumpCounterers(missionID)
+	local scroll=self:GetScroller("Counterers " .. self:GetMissionData(missionID,'name'))
+	self:cutePrint(scroll,cache.missions[missionID].counterers)
+end
+function addon:DumpParty(missionID)
+	local scroll=self:GetScroller("Party " .. self:GetMissionData(missionID,'name'))
+	self:cutePrint(scroll,parties[missionID])
+end
+function addon:DumpAgeDb()
+	local t=new()
+	for i,v in pairs(dbcache.seen) do
+		tinsert(t,format("%80s %s %d",self:GetMissionData(i,'name'),date("%d/%m/%y %H:%M:%S",v),ns.wowhead[i]))
+	end
+	local scroll=self:GetScroller("Expire db")
+	self:cutePrint(scroll,t)
+	del(t)
+end
+_G.GCF=GCF
+_G.MW=173
+--[[
+PlaySound("UI_Garrison_CommandTable_Open");
+	PlaySound("UI_Garrison_CommandTable_Close");
+	PlaySound("UI_Garrison_Nav_Tabs");
+	PlaySound("UI_Garrison_Nav_Tabs");
+	PlaySound("UI_Garrison_CommandTable_SelectMission");
+			PlaySound("UI_Garrison_CommandTable_IncreaseSuccess");
+			PlaySound("UI_Garrison_CommandTable_100Success");
+			PlaySound("UI_Garrison_CommandTable_ReducedSuccessChance");
+				PlaySound("UI_Garrison_Mission_Threat_Countered");
+			PlaySoundKitID(43507);	-- 100% chance reached
+	PlaySound("UI_Garrison_CommandTable_AssignFollower");
+			PlaySound("UI_Garrison_CommandTable_UnassignFollower");
+		PlaySound("UI_Garrison_Mission_Threat_Countered");
+	PlaySound("UI_Garrison_CommandTable_MissionStart");
+	PlaySound("UI_Garrison_CommandTable_ViewMissionReport");
+		PlaySound("UI_Garrison_Mission_Complete_Encounter_Chance");
+	PlaySound("UI_Garrison_CommandTable_Nav_Next");
+		PlaySound("UI_Garrison_CommandTable_ChestUnlock_Gold_Success");
+		PlaySound("UI_Garrison_Mission_Threat_Countered");
+		PlaySound("UI_Garrison_MissionEncounter_Animation_Generic");
+			PlaySoundKitID(currentAnim.castSoundID);
+		PlaySoundKitID(currentAnim.impactSoundID);
+			PlaySound("UI_Garrison_Mission_Complete_Encounter_Fail");
+			PlaySound("UI_Garrison_Mission_Complete_Mission_Success");
+		PlaySound("UI_Garrison_CommandTable_MissionSuccess_Stinger");
+		PlaySound("UI_Garrison_Mission_Complete_MissionFail_Stinger");
+		PlaySound("UI_Garrison_CommandTable_ChestUnlock");
+				PlaySound("UI_Garrison_CommandTable_Follower_LevelUp");
+
+--]]
+--@end-do-not-package@
diff --git a/FollowerCache.lua b/FollowerCache.lua
new file mode 100644
index 0000000..e69de29
diff --git a/FollowerPage.lua b/FollowerPage.lua
new file mode 100644
index 0000000..e69de29
diff --git a/FollowerRecruiting.lua b/FollowerRecruiting.lua
new file mode 100644
index 0000000..e69de29
diff --git a/GarrisonCommander-Broker/ldb.lua b/GarrisonCommander-Broker/ldb.lua
index 964440d..46b94e1 100644
--- a/GarrisonCommander-Broker/ldb.lua
+++ b/GarrisonCommander-Broker/ldb.lua
@@ -9,7 +9,18 @@ if (LibDebug) then LibDebug() end
 local L=LibStub("AceLocale-3.0"):GetLocale(me,true)
 local addon=LibStub("AceAddon-3.0"):NewAddon(me,"AceTimer-3.0","AceEvent-3.0")
 local dataobj
+local SecondsToTime=SecondsToTime
+local type=type
+local strsplit=strsplit
+local tonumber=tonumber
+local tremove=tremove
+local time=time
+local tinsert=tinsert
 local G=C_Garrison
+local NONE=NONE
+local DONE=DONE
+local format=format
+local table=table
 function addon:ldbCleanup()
 	local now=time()
 	for i=1,#self.db.realm.missions do
@@ -26,15 +37,17 @@ function addon:ldbCleanup()
 end
 function addon:ldbUpdate()
 	local now=time()
+	local completed=0
 	for i=1,#self.db.realm.missions do
 		local t,missionID,pc=strsplit('.',self.db.realm.missions[i])
 		t=tonumber(t) or 0
 		if t>now then
 			local duration=t-now
 			local duration=duration < 60 and duration or math.floor(duration/60)*60
-			dataobj.text=format("Next mission on |cff20ff20%s|r in %s",pc,SecondsToTime(duration))
+			dataobj.text=format("Next mission on |cff20ff20%s|r in %s (%d completed)",pc,SecondsToTime(duration),completed)
 			return
 		end
+		completed=completed+1
 	end
 	dataobj.text=NONE
 end
diff --git a/GarrisonCommander.lua b/GarrisonCommander.lua
index 8c59f4d..34d14fc 100644
--- a/GarrisonCommander.lua
+++ b/GarrisonCommander.lua
@@ -1,27 +1,14 @@
 local me, ns = ...
-ns.me=GetUnitName("player",true)
+local addon=ns.addon --#addon
+local L=ns.L
+local D=ns.D
+local C=ns.C
+local P=ns.party
+local AceGUI=ns.AceGUI
 local _G=_G
 local pp=print
 local HD=false
---@debug@
-	LoadAddOn("Blizzard_DebugTools")
---@end-debug@
-local addon=LibStub("LibInit"):NewAddon(me,'AceHook-3.0','AceTimer-3.0','AceEvent-3.0','AceBucket-3.0') --#Addon
-local AceGUI=LibStub("AceGUI-3.0")
-local D=LibStub("LibDeformat-3.0")
-local C=addon:GetColorTable()
-local L=addon:GetLocale()
-local print=addon:Wrap("Print")
-local dprint=print
-local trace=addon:Wrap("Trace")
-local xprint=function() end
-local xdump=function() end
-local xtrace=function() end
---@debug@
---xprint=function(...) print("DBG",...) end
---xdump=function(d,t) print(t) DevTools_Dump(d) end
---xtrace=trace
---@end-debug@
+local print=ns.print
 local pairs=pairs
 local select=select
 local next=next
@@ -73,63 +60,13 @@ local function tcopy(obj, seen)
 	for k, v in pairs(obj) do res[tcopy(k, s)] = tcopy(v, s) end
 	return res
 end
------------------------------------------------------------------
--- Recycling function from ACE3
+
+local parties


 local missionautocompleting
 local lastTab=1
-local new, del, copy
-do
-	--@debug@
-	local newcount, delcount,createdcount,cached = 0,0,0
-	--@end-debug@
-	local pool = setmetatable({},{__mode="k"})
-	function new()
-	--@debug@
-		newcount = newcount + 1
-	--@end-debug@
-		local t = next(pool)
-		if t then
-			pool[t] = nil
-			return t
-		else
-	--@debug@
-			createdcount = createdcount + 1
-	--@end-debug@
-			return {}
-		end
-	end
-	function copy(t)
-		local c = new()
-		for k, v in pairs(t) do
-			c[k] = v
-		end
-		return c
-	end
-	function del(t)
-	--@debug@
-		delcount = delcount + 1
-	--@end-debug@
-		wipe(t)
-		pool[t] = true
-	end
-	--@debug@
-	function cached()
-		local n = 0
-		for k in pairs(pool) do
-			n = n + 1
-		end
-		return n
-	end
-	function addon:CacheStats()
-		print("Created:",createdcount)
-		print("Aquired:",newcount)
-		print("Released:",delcount)
-		print("Cached:",cached())
-	end
-	--@end-debug@
-end
+local new, del, copy =ns.new,ns.del,ns.copy

 local function capitalize(s)
 	s=tostring(s)
@@ -235,7 +172,6 @@ local BIGBUTTON=BIGSIZEW-GCSIZE
 local SMALLBUTTON=BIGSIZEW-GCSIZE
 local GCF
 local GMC
-local GMCUsedFollowers={}
 local GCFMissions
 local GCFBusyStatus
 local GameTooltip=GameTooltip
@@ -331,165 +267,7 @@ local function inTable(table, value)
 end


---- Parties storage
---
---
-local parties=setmetatable({},{
-	__index=function(t,k)  rawset(t,k,{members={},perc=0,full=false}) return t[k] end
-})
-local function inParty(missionID,followerID)
-	return inTable(parties[missionID].members,followerID)
-end
---- Follower Missions Info
---
-local followerMissions=setmetatable({},{
-	__index=function(t,k)  rawset(t,k,{}) return t[k] end
-})
-
-
-local holdEvents,releaseEvents
-do
-	local stacklevel=0
-	local frames
-	function holdEvents()
-		if stacklevel==0 then
-			frames={GetFramesRegisteredForEvent('GARRISON_FOLLOWER_LIST_UPDATE')}
-			for i=1,#frames do
-				frames[i]:UnregisterEvent("GARRISON_FOLLOWER_LIST_UPDATE")
-			end
-		end
-		stacklevel=stacklevel+1
-	end
-	function releaseEvents()
-		stacklevel=stacklevel-1
-		assert(stacklevel>=0)
-		if (stacklevel==0) then
-			for i=1,#frames do
-				frames[i]:RegisterEvent("GARRISON_FOLLOWER_LIST_UPDATE")
-			end
-			frames=nil
-		end
-	end
-end
-
---
--- Temporary party management
-local openParty,partyIgnore,isPartyIgnored,isInParty,pushFollower,removeFollower,fillParty,closeParty,roomInParty,storeFollowers,dumpParty,isPartyEmpty,addTrait,needTrait
-
-do
-	local ID,maxFollowers,members,ignored=0,1,{},{}
-	---@function [parent=#party] openParty
-	function openParty(missionID,followers)
-		wipe(members)
-		maxFollowers=followers
-		ID=missionID
-		holdEvents()
-	end
-
-	---@function [parent=#party] partyIgnore
-	function partyIgnore(followerID,soft)
-		ignored[followerID]=soft and 1 or 2
-	end
-	---@function [parent=#party] isPartyIgnored
-	function isPartyIgnored(followerID)
-		return ignored[followerID]
-	end
-
-	---@function [parent=#party] isInParty
-	function isInParty(followerID)
-		return inTable(members,followerID)
-	end
-
-	---@function [parent=#party] roomInParty
-	function roomInParty()
-		return maxFollowers-#members
-	end
-
-	---@function [parent=#party] isPartyEmpty
-	function isPartyEmpty()
-		return maxFollowers>0 and #members==0
-	end
-
-	---@function [parent=#party] dumpParty
-	function dumpParty()
-		for i=1,3 do
-			if (members[i]) then
-				xprint(i,addon:GetFollowerData(members[i],'fullname'))
-			end
-		end
-	end
-
-	---@function [parent=#party] pushFollower
-	function pushFollower(followerID)
-		if (followerID:sub(1,2) ~= '0x') then trace(followerID .. "is not an id") end
-		if (roomInParty()>0) then
-			local rc,code=pcall (C_Garrison.AddFollowerToMission,ID,followerID)
-			if (rc and code) then
-				tinsert(members,followerID)
-				return true
---@debug@
-			else
-				xprint("Unable to add", n[followerID],"to",ID,code)
---@end-debug@
-			end
-		end
-	end
-	---@function [parent=#party] removeFollowers
-	function removeFollower(followerID)
-		for i=1,maxFollowers do
-			if (followerID==members[i]) then
-				tremove(members,i)
-				local rc,code=pcall(C_Garrison.RemoveFollowerFromMission,ID,followerID)
---@debug@
-				if (not rc) then trace("Unable to remove", n[members[i]],"from",ID,code) end
---@end-debug@
-			return true end
-		end
-	end
-
-	---@function [parent=#party] storeFollowers
-	function storeFollowers(table)
-		wipe(table)
-		for i=1,#members do
-			tinsert(table,members[i])
-		end
-		return #table
-	end
-	---@function [parent=#party] fillParty
-	function fillParty()
-		if roomInParty() < 1 then return end
-		for followerID,soft in pairs(ignored) do
-			if soft==1 then
-				if not isInParty(followerID) then
-					pushFollower(followerID)
-				end
-				if (roomInParty()<=0) then
-					break
-				end
-			end
-		end
-	end
-
-	---@function [parent=#party] closeParty
-	function closeParty()
-		local perc=select(4,G.GetPartyMissionInfo(ID))
-		for i=1,3 do
-			if (members[i]) then
-				local rc,code=pcall(C_Garrison.RemoveFollowerFromMission,ID,members[i])
---@debug@
-				if (not rc) then trace("Unable to pop", members[i]," from ",ID,code) end
---@end-debug@

-			else
-				break
-			end
-		end
-		releaseEvents()
-		wipe(members)
-		wipe(ignored)
-		return perc or 0
-	end
-end
 --
 --@debug@
 local origGarrisonMissionButton_OnEnter = _G.GarrisonMissionButton_OnEnter
@@ -579,10 +357,11 @@ end

 function addon:OnInitialized()
 --@debug@
-	xprint("OnInitialized")
+	ns.xprint("OnInitialized")
 	self.evdebug=CreateFrame("Frame")
 	self.evdebug:SetScript("OnEvent",function(...) return print('|cffff2020event|r',...) end)
 --@end-debug@
+	parties=self:GetParty()
 	for _,b in ipairs(GMFMissionsListScrollFrame.buttons) do
 		local scale=0.8
 		local f,h,s=b.Title:GetFont()
@@ -835,7 +614,6 @@ function addon:FillCounters(missionID,mission)
 				-- l.icon
 				-- l.description
 				tinsert(missioncounters,{k=cleanicon(l.icon),mechanic=true,name=l.name,followerID=id,bias=bias,rank=rank,quality=quality,icon=l.icon})
-				followerMissions[id][missionID]=1+ (tonumber(followerMissions[id][missionID]) or 0)
 			end
 		end
 	end
@@ -848,7 +626,6 @@ function addon:FillCounters(missionID,mission)
 			for i,l in pairs(d) do
 				--l.traitID
 				--l.icon
-				followerMissions[id][missionID]=1+ (tonumber(followerMissions[id][missionID]) or 0)
 				tinsert(missioncounters,{k=cleanicon(l.icon),trait=true,name=l.traitID,followerID=id,bias=bias,rank=rank,quality=quality,icon=l.icon})
 			end
 		end
@@ -966,7 +743,7 @@ local function best(fid1,fid2,counters,mission)
 	if (dbg) then
 		print("Current",fid1,n[f1.followerID]," vs Candidate",fid2,n[f2.followerID])
 	end
-	if (isInParty(f1.followerID)) then return fid1 end
+	if (P:IsIn(f1.followerID)) then return fid1 end
 	if (f2.bias<0) then return fid1 end
 	if (f2.bias>f1.bias) then return fid2 end
 	if (f1.bias == f2.bias) then
@@ -982,14 +759,14 @@ end
 function addon:MatchMaker(missionID,mission,party,fromMissionControl)
 	if (GMFRewardSplash:IsShown()) then return end
 	if (not mission) then mission=self:GetMissionData(missionID) end
-	if (not party) then party=parties[missionID] end
+	if (not party) then party=self:GetParty(missionID) end
 	local skipBusy=self:GetBoolean("IGM")
 	local skipMaxed=self:GetBoolean("IGP")
 	dbg=missionID==(tonumber(_G.MW) or 0)
 	local slots=mission.slots
 	local missionCounters=counters[missionID]
 	local ct=counterThreatIndex[missionID]
-	openParty(missionID,mission.numFollowers)
+	P:Open(missionID,mission.numFollowers)
 	-- Preloading skipped ones in party table.
 	if (dbg) then print(C("Matchmaking mission","Red"),missionID,mission.name) end
 	if (missionCounters) then
@@ -1000,13 +777,13 @@ function addon:MatchMaker(missionID,mission,party,fromMissionControl)
 			else
 				if (self:IsIgnored(followerID,missionID)) then
 					if (dbg) then print("Skipped",n[followerID],"due to ignored" ) end
-					partyIgnore(followerID,true)
+					P:Ignore(followerID,true)
 				elseif not self:IsFollowerAvailableForMission(followerID,skipBusy) then
 					if (dbg) then print("Skipped",n[followerID],"due to busy" ) end
-					partyIgnore(followerID)
+					P:Ignore(followerID)
 				elseif (skipMaxed and mission.xpOnly and self:GetFollowerData(followerID,'maxed')) then
 					if (dbg) then print("Skipped",n[followerID],"due to maxed" ) end
-					partyIgnore(followerID,true)
+					P:Ignore(followerID,true)
 				end
 			end
 		end
@@ -1018,13 +795,13 @@ function addon:MatchMaker(missionID,mission,party,fromMissionControl)
 				if (dbg) then print("Checking ",threat) end
 				for i=1,#candidates do
 					local followerID=missionCounters[candidates[i]].followerID
-					if isInParty(followerID) then
+					if P:IsIn(followerID) then
 						if dbg then print("Countered by",n[followerID],"which is already in party") end
 						choosen=nil
 						break
 					end
 					if followerID then
-						if(not isPartyIgnored(followerID)) then
+						if(not P:IsIgnored(followerID)) then
 							choosen=best(choosen,candidates[i],missionCounters,mission)
 							if (dbg) then print("Taken",n[missionCounters[choosen].followerID]) end
 						else
@@ -1033,41 +810,38 @@ function addon:MatchMaker(missionID,mission,party,fromMissionControl)
 					end
 				end
 				if (choosen) then
-					if (type(missionCounters[choosen]) ~="table") then
-						trace(format("%s %s %d %d",mission.name,threat,missionID,tonumber(choosen) or 0))
-					end
-					if dbg then print("Adding to party",n[missionCounters[choosen].followerID]," still need ",roomInParty()) end
-					pushFollower(missionCounters[choosen].followerID)
+					if dbg then print("Adding to party",n[missionCounters[choosen].followerID]," still need ",P:FreeSlots()) end
+					P:AddFollower(missionCounters[choosen].followerID)
 				end
-				if (roomInParty()==0) then
+				if (P:FreeSlots()==0) then
 					break
 				end
 			end
 		else
-			xprint("Mission",missionID,"has no slots????")
+			ns.xprint("Mission",missionID,"has no slots????")
 		end
-		if roomInParty() > 0 then self:AddTraitsToParty(missionID,mission) end
+		if P:FreeSlots() > 0 then self:AddTraitsToParty(missionID,mission) end
 	end
-	if roomInParty() > 0 then self:CompleteParty(missionID,mission,skipBusy,skipMaxed) end
-	if (not fromMissionControl and not isPartyEmpty()) then
-		if roomInParty() > 0 then self:CompleteParty(missionID,mission,skipBusy,false) end
+	if P:FreeSlots() > 0 then self:CompleteParty(missionID,mission,skipBusy,skipMaxed) end
+	if (not fromMissionControl and not P:IsEmpty()) then
+		if P:FreeSlots() > 0 then self:CompleteParty(missionID,mission,skipBusy,false) end
 	end
-	storeFollowers(party.members)
-	party.full= roomInParty()==0
-	party.perc=closeParty()
+	P:StoreFollowers(party.members)
+	party.full= P:FreeSlots()==0
+	party.perc=P:Close()
 end
 function addon:AddTraitsToParty(missionID,mission,skipBusy,skipMaxed)
 	local t=counters[missionID]
 	if (t) then
 		for i=1,#t do
 			local follower=t[i]
-			if (follower.trait and not isPartyIgnored(follower.followerID) and not isInParty(follower.followerID)) then
+			if (follower.trait and not P:IsIgnored(follower.followerID) and not P:IsIn(follower.followerID)) then
 				if mission.resources > 0 and follower.name==scavengerTrait then
-					pushFollower(follower.followerID)
+					P:AddFollower(follower.followerID)
 				elseif mission.xpOnly  and (follower.name==extraTrainingTrait or follower.name==hearthStoneProTrait) then
-					pushFollower(follower.followerID)
+					P:AddFollower(follower.followerID)
 				elseif mission.durationSeconds > GARRISON_LONG_MISSION_TIME  and follower.name==epicMountTrait then
-					pushFollower(follower.followerID)
+					P:AddFollower(follower.followerID)
 				end
 			end
 		end
@@ -1080,24 +854,23 @@ function addon:CompleteParty(missionID,mission,skipBusy,skipMaxed)
 	local candidateQuality=9999
 	if (dbg) then
 		print("Attemptin to fill party, so far perc is ",perc, "and party is")
-		dumpParty()
+		P:Dump()
 	end
-	local stash={}
-	for x=1,roomInParty() do
+	for x=1,P:FreeSlots() do
 		local candidate
 		local candidatePerc=perc
-		if (dbg) then print("            Perc to beat",perc, "Going for spot ",roomInParty()) end
+		if (dbg) then print("            Perc to beat",perc, "Going for spot ",P:FreeSlots()) end
 		local totFollowers=#followersCache
 		for i=1,totFollowers do
 			local data=followersCache[i]
 			local followerID=data.followerID
 			if (dbg) then print("evaluating",data.fullname) end
 			repeat
-				if isPartyIgnored(followerID) then
+				if P:IsIgnored(followerID) then
 					if (dbg) then print("Skipped due to party ignored") end
 					break
 				end
-				if isInParty(followerID) then
+				if P:IsIn(followerID) then
 					if (dbg) then print("Skipped due to already in party") end
 					break
 				end
@@ -1113,51 +886,37 @@ function addon:CompleteParty(missionID,mission,skipBusy,skipMaxed)
 					if (dbg) then print("Skipped due to busy") end
 					break
 				end
-				local missions=#followerMissions[followerID]
 				local rank=data.rank
 				local quality=data.quality
 				perc=tonumber(perc) or 0
 				if ((perc) <100) then
-					pushFollower(followerID)
+					P:AddFollower(followerID)
 					local newperc=select(4,G.GetPartyMissionInfo(missionID))
 					newperc=tonumber(newperc) or 0
 					candidatePerc=tonumber(candidatePerc) or 0
-					removeFollower(followerID)
+					P:RemoveFollower(followerID)
 					if (newperc > candidatePerc) then
 						candidatePerc=newperc
 						candidate=followerID
-						candidateMissions=missions
 						candidateRank=rank
 						candidateQuality=quality
 						break -- continue
-					elseif (newperc <= candidatePerc) then
-						if (dbg) then print("Stashed ",n[followerID]) end
-						tinsert(stash,followerID)
-						break --continue
 					end
 				else
 					-- This candidate is not improving success chance or we are already at 100%, minimize
 					if (i < totFollowers  and data.maxed) then
 						break  -- Pointless using a maxed follower if we  have more follower to try
 					end
-					if (missions<candidateMissions) then
+					if(rank<candidateRank) then
 						candidate=followerID
-						candidateMissions=missions
 						candidateRank=rank
 						candidateQuality=quality
-					elseif(missions==candidateMissions and rank<candidateRank) then
+					elseif(rank==candidateRank and quality<candidateQuality) then
 						candidate=followerID
-						candidateMissions=missions
-						candidateRank=rank
-						candidateQuality=quality
-					elseif(missions==candidateMissions and rank==candidateRank and quality<candidateQuality) then
-						candidate=followerID
-						candidateMissions=missions
 						candidateRank=rank
 						candidateQuality=quality
 					elseif (not candidate) then
 						candidate=followerID
-						candidateMissions=missions
 						candidateRank=rank
 						candidateQuality=quality
 					end
@@ -1166,20 +925,15 @@ function addon:CompleteParty(missionID,mission,skipBusy,skipMaxed)
 		end
 		if (candidate) then
 			if (dbg) then print("Attempting to add to party") end
-			pushFollower(candidate)
+			P:AddFollower(candidate)
 			if (dbg) then
 				print("Added member to party")
-				dumpParty()
+				P:Dump()
 			end
 			perc=select(4,G.GetPartyMissionInfo(missionID))
 			if (dbg) then print("New perc is",perc) end
 		end
 	end
-	for i=1,#stash do
-		if roomInParty()==0 then break end
-		if (dbg) then print("Tring stashed ",n[stash[i]]) end
-		pushFollower(stash[i])
-	end
 end
 function addon:TestMission(missionID)
 	local party=new()
@@ -1215,7 +969,7 @@ function addon:GetCounterBias(missionID,threat)
 			for i=1,#index do
 				local follower=data[index[i]]
 				if ((tonumber(follower.bias) or -1) > bias) then
-					if (inParty(missionID,follower.followerID)) then
+					if (tContains(self:GetParty(missionID).members,follower.followerID)) then
 						if (dbg) then print("   Choosen",self:GetFollowerData(follower.followerID,'fullname')) end
 						bias=follower.bias
 						who=follower.name
@@ -1348,10 +1102,10 @@ function addon:BuildMissionsCache(fc,mm,OnEvent)
 --cache.missions
 --@debug@
 	local start=GetTime()
-	xprint("Start Full Cache Rebuild")
+	ns.xprint("Start Full Cache Rebuild")
 --@end-debug@
 	local t=new()
-	holdEvents()
+	self:holdEvents()
 	G.GetAvailableMissions(t)
 	for index=1,#t do
 		local missionID=t[index].missionID
@@ -1371,11 +1125,11 @@ function addon:BuildMissionsCache(fc,mm,OnEvent)
 			dbcache.seen[k]=nil
 		end
 	end
-	releaseEvents()
+	self:releaseEvents()
 	del(t)
 	collectgarbage("step",10)
 --@debug@
-	xprint("Done in",GetTime()-start)
+	ns.xprint("Done in",GetTime()-start)
 --@end-debug@
 end
 --[[
@@ -1479,7 +1233,7 @@ function addon:BuildMissionCache(id,data)
 	local mission=cache.missions[id]
 	if (not mission) then
 --@dedbug@
-		xprint("Generating new data for ",id)
+		ns.xprint("Generating new data for ",id)
 --@end-debug@
 		mission = data or G.GetBasicMissionInfo(id)
 		if (not mission) then return end
@@ -1521,7 +1275,7 @@ function addon:BuildMissionCache(id,data)
 	mission.locPrefix=locPrefix
 	if (not type) then
 --@debug@
-		xprint("No type",id,data.name)
+		ns.xprint("No type",id,data.name)
 --@end-debug@
 	else
 		if (not self.db.global.types[type]) then
@@ -1620,13 +1374,13 @@ end

 function addon:EventGARRISON_MISSION_NPC_OPENED(event,...)
 --@debug@
-	xprint(event,...)
+	ns.xprint(event,...)
 --@end-debug@
 	if (GCF) then GCF:Show() end
 end
 function addon:EventGARRISON_MISSION_NPC_CLOSED(event,...)
 --@debug@
-	xprint(event,...)
+	ns.xprint(event,...)
 --@end-debug@
 	if (GCF) then
 		self:RemoveMenu()
@@ -1636,7 +1390,7 @@ end
 function addon:EventGARRISON_MISSION_LIST_UPDATE(event)
 	local n=0
 	for _,k in pairs(event) do n=n+1 end
-	xprint("GARRISON_MISSION_LIST_UPDATE",n,date("%d/%m/%y %H:%M:%S"))
+	ns.xprint("GARRISON_MISSION_LIST_UPDATE",n,date("%d/%m/%y %H:%M:%S"))
 	local t=new()
 	G.GetAvailableMissions(t)
 	local justseen={}
@@ -1657,7 +1411,7 @@ end

 function addon:EventGARRISON_MISSION_STARTED(event,missionID,...)
 --@debug@
-	xprint(event,missionID,...)
+	ns.xprint(event,missionID,...)
 --@end-debug@
 --				running={
 --					["*"]={
@@ -1701,8 +1455,8 @@ end

 function addon:EventGARRISON_MISSION_FINISHED(event,missionID,...)
 --@debug@
-	xprint(event,missionID,...)
-	xdump(G.GetPartyMissionInfo(missionID))
+	ns.xprint(event,missionID,...)
+	ns.xdump(G.GetPartyMissionInfo(missionID))
 --@end-debug@
 end
 function addon:EventGARRISON_FOLLOWER_LIST_UPDATE(event)
@@ -1710,12 +1464,12 @@ function addon:EventGARRISON_FOLLOWER_LIST_UPDATE(event)
 	wipe(followersCache)
 	wipe(followersCacheIndex)
 	wipe(parties)
-	xprint("Follower cache cleaned")
+	ns.xprint("Follower cache cleaned")
 end

 function addon:EventGARRISON_MISSION_BONUS_ROLL_LOOT(event,missionID,completed,success)
 --@debug@
-	xprint('evt',event,missionID,completed,success)
+	ns.xprint('evt',event,missionID,completed,success)
 --@end-debug@
 	self:RefreshFollowerStatus()
 end
@@ -1732,7 +1486,7 @@ end
 --
 function addon:EventGARRISON_MISSION_COMPLETE_RESPONSE(event,missionID,completed,rewards)
 --@debug@
-	xprint('evt',event,missionID,completed,rewards)
+	ns.xprint('evt',event,missionID,completed,rewards)
 --@end-debug@
 	dbcache.history[missionID][time()]={result=100,success=rewards}
 	dbcache.seen[missionID]=nil
@@ -1778,25 +1532,29 @@ function addon:Clock()
 		dbgFrame=AceGUI:Create("Window")
 		dbgFrame:SetTitle("GC Performance")
 		dbgFrame:SetPoint("LEFT")
-		dbgFrame:SetHeight(80)
+		dbgFrame:SetHeight(120)
 		dbgFrame:SetWidth(350)
-		dbgFrame:SetLayout("fill")
+		dbgFrame:SetLayout("flow")
 		dbgFrame.Text=AceGUI:Create("Label")
-		dbgFrame.Text:SetColor(1,0,0)
+		dbgFrame.Text:SetColor(1,1,0)
+		dbgFrame.Text:SetFullWidth(true)
+		dbgFrame.Text1=AceGUI:Create("Label")
+		dbgFrame.Text1:SetColor(1,1,0)
+		dbgFrame.Text1:SetFullWidth(true)
 		dbgFrame:AddChild(dbgFrame.Text)
+		dbgFrame:AddChild(dbgFrame.Text1)
 	end
 	local h,m=GetGameTime()
 	if (m~=lastmin) then
 		lastmin=m
 	end
 	UpdateAddOnMemoryUsage()
-	local cpu=GetAddOnCPUUsage("GarrisonCommander")
-	dbgFrame.Text:SetText(format("GC Cpu %3.2f/%2.2f/%2.2f Mem %4.3fMB ",
-		cpu,
-		cpu-lastCPU,cpu/(GetTime()-startTime),
+	dbgFrame.Text:SetText(format("Garrison Commander Mem %4.3fMB ",
 		GetAddOnMemoryUsage("GarrisonCommander")/1024)
 	)
-	lastCPU=cpu
+	dbgFrame.Text1:SetText(format("Garrison Broker   Mem %4.3fMB ",
+		GetAddOnMemoryUsage("GarrisonCommander-Broker")/1024)
+	)
 --@end-debug@
 	dbcache.lastseen=time()
 	if (not MP or MPGoodGuy) then return  end
@@ -1900,10 +1658,10 @@ function addon:ShowMissionControl()
 		GMF.TitleText:SetText("Garrison Commander Mission Control")
 		GMC:Show()
 		GMC.startButton:Click()
-		GMC.tabMC:SetChecked(true)
+		GMF.tabMC:SetChecked(true)
 	else
 		GMC:Hide()
-		GMC.tabMC:SetChecked(false)
+		GMF.tabMC:SetChecked(false)
 		GarrisonMissionFrame_SelectTab(1)
 	end
 end
@@ -2069,7 +1827,7 @@ function addon:GenerateMissionCompleteList(title)
 				end
 				local quality=G.GetFollowerQuality(followerID) or data[4]
 				local level=G.GetFollowerLevel(followerID) or data[3]
-				xprint(followerID,quality,level,data[3],data[4])
+				ns.xprint(followerID,quality,level,data[3],data[4])
 				local levelup=((quality > data[4]) or (level > data[3]))
 				--local levelup=true
 				if levelup then
@@ -2555,7 +2313,7 @@ end

 function addon:ScriptTrace(hook,frame,...)
 --@debug@
-	xprint("Triggered " .. C(hook,"red").." script on",C(frame,"Azure"),...)
+	ns.xprint("Triggered " .. C(hook,"red").." script on",C(frame,"Azure"),...)
 --@end-debug@
 end
 function addon:IsProgressMissionPage()
@@ -2579,9 +2337,8 @@ end
 -- Switches between missions (1) and followers (others) panels
 function addon:HookedGarrisonMissionFrame_SelectTab(id)
 	GMC:Hide()
-	GMC.tabMC:SetChecked(false)
+	GMF.tabMC:SetChecked(false)
 	self:RefreshFollowerStatus()
-	wipe(GMCUsedFollowers)
 end
 ---
 -- Switchs between active and availabla missions depending on tab object
@@ -2898,7 +2655,7 @@ do
 		end
 	end
 	function addon:RenderFollowerPageMissionList(frame,followerID,force)
-		xprint("hook",followerID,force)
+		ns.xprint("hook",followerID,force)
 		if (followerID==lastFollowerID and not force) then return end
 		lastFollowerID=followerID
 		local i=0
@@ -2906,8 +2663,8 @@ do
 		if (not GCFMissions.Missions) then GCFMissions.Missions={} end
 		if (not Busystatusmessage) then Busystatusmessage=C(BUSY_MESSAGE,"Red)") end
 		-- frame has every info you can need on a follower, but here I dont really need them, maybe just counters
-		--xdump(table.Counters)
-		xprint("Passed",followerID)
+		--ns.xdump(table.Counters)
+		ns.xprint("Passed",followerID)
 		local followerName=self:GetFollowerData(followerID,'name',true)
 		repeat -- a poor man goto
 			if (type(frame.followerID)=="number") then
@@ -2960,7 +2717,7 @@ do
 				local missionID=partyIndex[z]
 				if not(tonumber(missionID)) then
 	--@debug@
-					xprint("missionid not a number",missionID)
+					ns.xprint("missionid not a number",missionID)
 					self:Dump("partyIndex table",partyIndex)
 	--@end-debug@
 					perc=-1 --(lowering perc  to ignore this mission
@@ -3027,7 +2784,7 @@ end
 --Initial one time setup
 function addon:SetUp(...)
 --@debug@
-	xprint("Setup")
+	ns.xprint("Setup")
 --@end-debug@
 --@alpha@
 	if (not db.alfa.v220) then
@@ -3041,23 +2798,25 @@ function addon:SetUp(...)
 	self:CheckGMM()
 	self:Options()
 	self:GenerateMissionsWidgets()
-	self:GMCBuildPanel()
+	GMC=self:GMCBuildPanel(bigscreen)
 	local tabMC=CreateFrame("CheckButton",nil,GMF,"SpellBookSkillLineTabTemplate")
+	GMF.tabMC=tabMC
 	tabMC.tooltip="Open Garrison Commander Mission Control"
 	--tab:SetNormalTexture("World\\Dungeon\\Challenge\\clockRunes.blp")
 	--tab:SetNormalTexture("Interface\\Timer\\Challenges-Logo.blp")
 	tabMC:SetNormalTexture("Interface\\ICONS\\ACHIEVEMENT_GUILDPERK_WORKINGOVERTIME.blp")
 	self:MarkAsNew(tabMC,'MissionControl','New in 2.2.0! Try automatic mission management!')
 	tabMC:Show()
-	GMC.tabMC=tabMC
 	GMF.FollowerStatusInfo=GMF:CreateFontString(nil, "BORDER", "GameFontNormal")
 	GMF.FollowerStatusInfo:SetPoint("TOPRIGHT",-30,-5)
 	local tabCF=CreateFrame("Button",nil,GMF,"SpellBookSkillLineTabTemplate")
+	GMF.tabCF=tabCF
 	tabCF.tooltip="Open Garrison Commander Configuration Screen"
 	tabCF:SetNormalTexture("Interface\\ICONS\\Trade_Engineering.blp")
 	tabCF:SetPushedTexture("Interface\\ICONS\\Trade_Engineering.blp")
 	tabCF:Show()
 	local tabHP=CreateFrame("Button",nil,GMF,"SpellBookSkillLineTabTemplate")
+	GMF.tabHP=tabHP
 	tabHP.tooltip="Open Garrison Commander Help"
 	tabHP:SetNormalTexture("Interface\\ICONS\\INV_Misc_QuestionMark.blp")
 	tabHP:SetPushedTexture("Interface\\ICONS\\INV_Misc_QuestionMark.blp")
@@ -3113,7 +2872,7 @@ end
 -- when it closes, I remove most of used hooks
 function addon:StartUp(...)
 --@debug@
-	xprint("Startup")
+	ns.xprint("Startup")
 --@end-debug@
 	self:GrowPanel()
 	self:Unhook(GMF,"OnShow")
@@ -3128,7 +2887,7 @@ function addon:StartUp(...)
 	self:SafeSecureHook("GarrisonMissionButton_SetRewards")
 	self:SafeSecureHook("GarrisonFollowerListButton_OnClick") -- used both to update follower mission list and itemlevel display
 	if (bigscreen) then
-		self:SafeSecureHook("GarrisonFollowerPage_ShowFollower")--,function(...) xprint("GarrisonFollowerPage_ShowFollower",...) end)
+		self:SafeSecureHook("GarrisonFollowerPage_ShowFollower")--,function(...) ns.xprint("GarrisonFollowerPage_ShowFollower",...) end)
 		self:SafeSecureHook("GarrisonFollowerTooltipTemplate_SetGarrisonFollower")
 	end
 	self:SafeSecureHook("GarrisonMissionFrame_HideCompleteMissions")	-- Mission reward completed
@@ -3184,12 +2943,12 @@ end
 function addon:checkMethod(method,hook)
 	if (type(self[method])=="function") then
 --@debug@
-		--xprint("Hooking ",hook,"to self:" .. method)
+		--ns.xprint("Hooking ",hook,"to self:" .. method)
 --@end-debug@
 		return true
 --@debug@
 	else
-		--xprint("Hooking ",hook,"to print")
+		--ns.xprint("Hooking ",hook,"to print")
 --@end-debug@
 	end
 end
@@ -3204,7 +2963,7 @@ function addon:SafeRegisterEvent(event)
 		return self:RegisterEvent(event,method)
 --@debug@
 	else
-		return self:RegisterEvent(event,xprint)
+		return self:RegisterEvent(event,ns.xprint)
 --@end-debug@
 	end
 end
@@ -3217,7 +2976,7 @@ function addon:SafeSecureHook(tobehooked,method)
 	else
 		do
 			local hooked=tobehooked
-			return self:SecureHook(tobehooked,function(...) xprint(hooked,...) end)
+			return self:SecureHook(tobehooked,function(...) ns.xprint(hooked,...) end)
 		end
 --@end-debug@
 	end
@@ -3261,7 +3020,7 @@ function addon:CleanUp()
 	end
 	--collectgarbage("collect")
 --@debug@
-	xprint("Cleaning up")
+	ns.xprint("Cleaning up")
 --@end-debug@
 end
 function addon:EventGARRISON_FOLLOWER_XP_CHANGED(event,followerID,iLevel,xp,level,quality)
@@ -3362,7 +3121,7 @@ function addon:GetMissionData(missionID,subkey)
 	local missionCache=cache.missions[missionID]
 	if (not missionCache) then
 --@debug@
-		xprint("Found a new mission",missionID,"Refreshing it")
+		ns.xprint("Found a new mission",missionID,"Refreshing it")
 --@end-debug@
 		self:BuildMissionCache(missionID)
 		self:FillCounters(missionID,cache.missions[missionID])
@@ -3375,7 +3134,7 @@ function addon:GetMissionData(missionID,subkey)
 	return missionCache
 end
 function addon:IsFollowerAvailableForMission(followerID,skipbusy)
-	if (GMCUsedFollowers[followerID]) then
+	if self:GMCBusy(followerID) then
 		return false
 	end
 	if (not skipbusy) then
@@ -3406,15 +3165,15 @@ end
 function addon:FillMissionPage(missionInfo)
 	if type(missionInfo)=="number" then missionInfo=self:GetMissionData(missionInfo) end
 	if not missionInfo then return end
-	if( IsControlKeyDown()) then xprint("Shift key, ignoring mission prefill") return end
+	if( IsControlKeyDown()) then ns.xprint("Shift key, ignoring mission prefill") return end
 	if (self:GetBoolean("NOFILL")) then return end
 	local missionID=missionInfo.missionID
 --@debug@
-	xprint("UpdateMissionPage for",missionID,missionInfo.name,missionInfo.numFollowers)
+	ns.xprint("UpdateMissionPage for",missionID,missionInfo.name,missionInfo.numFollowers)
 --@end-debug@
-	--xdump(missionInfo)
+	--ns.xdump(missionInfo)
 	--self:BuildMissionData(missionInfo.missionID.missionInfo)
-	holdEvents()
+	self:holdEvents()
 	GarrisonMissionPage_ClearParty()
 	local party=parties[missionID]
 	if (party) then
@@ -3426,7 +3185,7 @@ function addon:FillMissionPage(missionInfo)
 				if (false and status) then
 					if status == GARRISON_FOLLOWER_IN_PARTY then -- Left from a previous assignment?
 --@debug@
-						xprint(followerID,self:GetFollowerData(followerID,"name"),"was already on mission")
+						ns.xprint(followerID,self:GetFollowerData(followerID,"name"),"was already on mission")
 --@end-debug@
 						self:RemoveFromAllMissions(followerID)
 						GarrisonMissionPage_AddFollower(followerID)
@@ -3435,14 +3194,14 @@ function addon:FillMissionPage(missionInfo)
 					end
 				else
 					pcall(G.RemoveFollowerFromMission,missionID,followerID)
-					xprint("Adding",followerID,G.GetFollowerName(followerID))
+					ns.xprint("Adding",followerID,G.GetFollowerName(followerID))
 					GarrisonMissionPage_AddFollower(followerID)
 				end
 			end
 		end
 	end
 	GarrisonMissionPage_UpdateMissionForParty()
-	releaseEvents()
+	self:releaseEvents()
 end
 local firstcall=true

@@ -3742,7 +3501,7 @@ function addon:BuildExtraButton(button)
 end
 function addon:OnShow_FollowerPage(page)
 	if not GCFMissions then return end
-	xprint("Onshow")
+	ns.xprint("Onshow")
 	if type(GCFMissions.Header.info)=="table" then
 		self:HookedGarrisonFollowerPage_ShowFollower(page,GCFMissions.Header.info.followerID,true)
 --		local s =self:GetScroller("GFCMissions.Header")
@@ -3838,7 +3597,7 @@ function addon:OnClick_GarrisonMissionButton(tab,button)
 	end
 	if (type(tab.info)~="table") then return end
 --@debug@
-	xprint("Clicked GarrisonMissionButton")
+	ns.xprint("Clicked GarrisonMissionButton")
 --@end-debug@
 	if (tab.fromFollowerPage) then
 		if (#tab.info.followers>0) then
@@ -3859,7 +3618,12 @@ function addon:OnClick_GCMissionButton(frame,button)
 end

 function addon:HookedGarrisonMissionButton_SetRewards(button,rewards,numRewards)
-	if GMF.MissionTab.MissionList.showInProgress and button.info.missionID==button.lastMissionID then collectgarbage("step",50) return end
+	if GMF.MissionTab.MissionList.showInProgress and button.info.missionID==button.lastMissionID then
+--[===[@non-debug@
+		collectgarbage("step",50)
+--@end-non-debug@]===]
+		return
+	end
 	button.lastMissionID=button.info.missionID
 	return self:RenderButton(button,rewards,numRewards)
 end
@@ -3991,673 +3755,6 @@ function addon:RenderButton(button,rewards,numRewards)
 	return self:RenderExtraButton(button,numRewards)
 end

--- Courtesy of Motig
--- Concept and interface reused with permission
--- Mission building rewritten from scratch
-local GMC_G = {}
---GMC_G.frame = CreateFrame('FRAME')
-local aMissions={}
-
-function addon:GMCCreateMissionList(workList)
-	--First get rid of unwanted rewards and missions that are too long
-	local settings=self.privatedb.profile.missionControl
-	local ar=settings.allowedRewards
-	wipe(workList)
-	for missionID,mission in pairs(cache.missions) do
-		local discarded=false
-		repeat
-			if (mission.durationSeconds > settings.maxDuration * 3600 or mission.durationSeconds <  settings.minDuration * 3600) then
-				xprint(missionID,"discarded due to len",mission.durationSeconds /3600)
-				break
-			end -- Mission too long, out of here
-			if (mission.isRare and settings.skipRare) then
-				xprint(missionID,"discarded due to rarity")
-				break
-			end
-			for k,v in pairs(ar) do
-				if (not v) then
-					if (mission[k] and mission[k]~=0) then -- we have a forbidden reward
-						discarded=true
-						break
-					end
-				end
-			end
-			if (not discarded) then
-				tinsert(workList,missionID)
-			end
-		until true
-	end
-	local function msort(i1,i2)
-		local m1=addon:GetMissionData(i1)
-		local m2=addon:GetMissionData(i2)
-		for i=1,#GMC.settings.itemPrio do
-			local criterium=GMC.settings.itemPrio[i]
-			if (criterium) then
-				if (m1[criterium] ~= m2[criterium]) then
-					return m1[criterium] > m2[criterium]
-				end
-			end
-		end
-		if (parties[m1.missionID].perc and parties[m2.missionID].perc) then
-			return parties[m1.missionID].perc > parties[m2.missionID].perc
-		end
-		return m1.level > m2.level
-	end
-	table.sort(workList,msort)
-	--@debug@
-	xprint("Sorted list")
-	local x=new()
-	for i=1,#workList do
-		local mission=self:GetMissionData(workList[i])
-		local t=new()
-		for i=1,#GMC.settings.itemPrio do
-			local criterium=GMC.settings.itemPrio[i]
-			tinsert(t,format("%s: %d",criterium,mission[criterium]))
-		end
-		tinsert(x,mission.name .." "..  strjoin("\t",unpack(t)) .. " Success" ..  tostring(parties[mission.missionID].perc))
-		del(t)
-	end
-	local scroll=self:GetScroller("Sorted missions")
-	self:cutePrint(scroll,x)
-	del(x)
-	--@end-debug@
-
-end
-local factory={} --#factory
-do
-	local nonce=0
-	local GetTime=GetTime
-	function factory:Slider(father,min,max,current,message)
-		local name=tostring(self)..GetTime()*1000 ..nonce
-		nonce=nonce+1
-		local sl = CreateFrame('Slider',name, father, 'OptionsSliderTemplate')
-		sl:SetWidth(128)
-		sl:SetHeight(20)
-		sl:SetOrientation('HORIZONTAL')
-		sl:SetMinMaxValues(min, max)
-		sl:SetValue(current)
-		sl:SetValueStep(1)
-		sl.Low=_G[name ..'Low']
-		sl.Low:SetText(min)
-		sl.High=_G[name .. 'High']
-		sl.High:SetText(max)
-		sl.Text=_G[name.. 'Text']
-		sl.Text:SetText(message)
-		sl.OnValueChanged=function(this,value)
-			if (not this.unrounded) then
-				value = math.floor(value)
-			end
-			if (this.isPercent) then
-				this.Text:SetFormattedText('%d%%',value)
-			else
-				this.Text:SetText(value)
-			end
-			return value
-		end
-		sl:SetScript("OnValueChanged",sl.OnValueChanged)
-		return sl
-	end
-	function factory:Checkbox(father,current,message)
-		local name=tostring(self)..GetTime()*1000 ..nonce
-		nonce=nonce+1
-		local ck=CreateFrame("CheckButton",name,father,"ChatConfigCheckButtonTemplate")
-		ck.Text=_G[name..'Text']
-		ck.Text:SetText(message)
-		ck:SetChecked(current)
-		return ck
-	end
-end
---- This routine can be called both as coroutin and as a standard one
--- In standard version, delay between group building and submitting is done via a self schedule
---@param #integer missionID Optional, to run a single mission
---@param #bool start Optional, tells that follower already are on mission and that we need just to start it
-function addon:GMCRunMission(missionID,start)
-	xprint("Asked to start mission",missionID)
-	if (start) then
-		G.StartMission(missionID)
-		PlaySound("UI_Garrison_CommandTable_MissionStart")
-		return
-	end
-	for i=1,#GMC.ml.Parties do
-		local party=GMC.ml.Parties[i]
-		xprint("Checking",party.missionID)
-		if (missionID and party.missionID==missionID or not missionID) then
-			GMC.ml.widget:RemoveChild(party.missionID)
-			GMC.ml.widget:DoLayout()
-			if (party.full) then
-				for j=1,#party.members do
-					G.AddFollowerToMission(party.missionID, party.members[j])
-				end
-				if (not missionID) then
-					coroutine.yield(true)
-					G.StartMission(party.missionID)
-					PlaySound("UI_Garrison_CommandTable_MissionStart")
-					coroutine.yield(true)
-				else
-					self:ScheduleTimer("GMCRunMission",0.25,party.missionID,true)
-				end
-			end
-		end
-	end
-end
-do
-	local timeElapsed=0
-	local currentMission=0
-	local x=0
-	function addon:GMCCalculateMissions(this,elapsed)
-		db.news.MissionControl=true
-		timeElapsed = timeElapsed + elapsed
-		if (#aMissions == 0 ) then
-			if timeElapsed >= 1 then
-				currentMission=0
-				x=0
-				self:Unhook(this,"OnUpdate")
-				GMC.ml.widget:SetTitle(READY)
-				GMC.ml.widget:SetTitleColor(C.Green())
-				this:Enable()
-				if (#GMC.ml.Parties>0) then
-					GMC.runButton:Enable()
-				end
-			end
-			return
-		end
-		if (timeElapsed >=0.) then
-			currentMission=currentMission+1
-			if (currentMission > #aMissions) then
-				wipe(aMissions)
-				currentMission=0
-				x=0
-				timeElapsed=0.2
-			else
-				GMC.ml.widget:SetFormattedTitle("Processing mission %d of %d",currentMission,#aMissions)
-				local missionID=aMissions[currentMission]
-				if (dbg) then print(C("Processing ","Red"),missionID) end
-				local party={members={},perc=0}
-				local mission=self:GetMissionData(missionID)
-				if (not mission ) then
-					if dbg then print ("NO data for",missionID) end
-					return
-				end
-				self:MatchMaker(missionID,mission,party) -- I need my mission data
-				local minimumChance=0
-				if (GMC.settings.useOneChance) then
-					minimumChance=GMC.settings.minimumChance
-				end
-				for prio,enabled in pairs(GMC.settings.allowedRewards) do
-					if (dbg) then print("Chance from ",prio,"=",GMC.settings.rewardChance[prio],enabled) end
-					if (enabled and (tonumber(self:GetMissionData(missionID,prio)) or 0) >0) then
-						minimumChance=math.max(GMC.settings.rewardChance[prio],minimumChance)
-					end
-				end
-				if (dbg) then print ("Missionid",missionID,"Chance",minimumChance,"chance",party.perc) end
-				if ( party.full and party.perc >= minimumChance) then
-					if (dbg) then print("Preparing button for",missionID) end
-					local mb=AceGUI:Create("GMCMissionButton")
-					for i=1,#party.members do
-						GMCUsedFollowers[party.members[i]]=true
-					end
-					party.missionID=missionID
-					tinsert(GMC.ml.Parties,party)
-					GMC.ml.widget:PushChild(mb,missionID)
-					mb:SetFullWidth(true)
-					mb:SetMission(mission)
-					mb:SetCallback("OnClick",function(...)
-						addon:GMCRunMission(missionID)
-						GMC.ml.widget:RemoveChild(missionID)
-					end
-					)
-				end
-				timeElapsed=0
-			end
-		end
-	end
-end
-
-function addon:GMC_OnClick_Run(this,button)
-	this:Disable()
-	do
-	local elapsed=0
-	local co=coroutine.wrap(self.GMCRunMission)
-	self:RawHookScript(GMC.runButton,'OnUpdate',function(this,ts)
-		elapsed=elapsed+ts
-		if (elapsed>0.25) then
-			elapsed=0
-			local rc=co(self)
-			if (not rc) then
-				self:Unhook(GMC.runButton,'OnUpdate')
-			end
-		end
-	end
-	)
-	end
-end
-function addon:GMC_OnClick_Start(this,button)
-	xprint(C("-------------------------------------------------","Yellow"))
-	this:Disable()
-	GMC.ml.widget:ClearChildren()
-	if (self:GetTotFollowers(AVAILABLE) == 0) then
-		self:Popup("All followers are busy",10)
-		this:Enable()
-		return
-	end
-	addon:GMCCreateMissionList(aMissions)
-	wipe(GMCUsedFollowers)
-	wipe(GMC.ml.Parties)
-	self:RefreshFollowerStatus()
-	if (#aMissions>0) then
-		GMC.ml.widget:SetFormattedTitle(L["Processing mission %d of %d"],1,#aMissions)
-	else
-		GMC.ml.widget:SetTitle("No mission matches your criteria")
-		GMC.ml.widget:SetTitleColor(C.Red())
-	end
-	self:RawHookScript(GMC.startButton,'OnUpdate',"GMCCalculateMissions")
-
-end
-local chestTexture
-function addon:GMCBuildPanel()
-	--PanelTemplates_SetNumTabs(GarrisonMissionFrame, 4)
-	--PanelTemplates_UpdateTabs(GarrisonMissionFrame)
-	chestTexture='GarrMission-'..UnitFactionGroup('player').. 'Chest'
-	GMC = CreateFrame('FRAME', 'GMCOptions', GarrisonMissionFrame)
-	GMC.settings=dbcache.missionControl
-	GMC:SetPoint('CENTER')
-	GMC:SetSize(GarrisonMissionFrame:GetWidth(), GarrisonMissionFrame:GetHeight())
-	GMC:Hide()
-	local chance=self:GMCBuildChance()
-	local duration=self:GMCBuildDuration()
-	local rewards=self:GMCBuildRewards()
-	local priorities=self:GMCBuildPriorities()
-	local list=self:GMCBuildMissionList()
-	duration:SetPoint("TOPLEFT",0,-50)
-	chance:SetPoint("TOPLEFT",duration,"TOPRIGHT",bigscreen and 50 or 10,0)
-	priorities:SetPoint("TOPLEFT",duration,"BOTTOMLEFT",25,-40)
-	rewards:SetPoint("TOPLEFT",priorities,"TOPRIGHT",bigscreen and 50 or 15,0)
-	list:SetPoint("TOPLEFT",chance,"TOPRIGHT",10,0)
-	list:SetPoint("BOTTOMRIGHT",GMF,"BOTTOMRIGHT",-25,25)
-	GMC.startButton = CreateFrame('BUTTON',nil,  list.frame, 'GameMenuButtonTemplate')
-	GMC.startButton:SetText('Calculate')
-	GMC.startButton:SetWidth(148)
-	GMC.startButton:SetPoint('TOPLEFT',15,25)
-	GMC.startButton:SetScript('OnClick', function(this,button) self:GMC_OnClick_Start(this,button) end)
-	GMC.startButton:SetScript('OnEnter', function() GameTooltip:SetOwner(GMC.startButton, 'ANCHOR_TOPRIGHT') GameTooltip:AddLine('Assign your followers to missions.') GameTooltip:Show() end)
-	GMC.startButton:SetScript('OnLeave', function() GameTooltip:Hide() end)
-	GMC.runButton = CreateFrame('BUTTON', nil,list.frame, 'GameMenuButtonTemplate')
-	GMC.runButton:SetText('Send all mission at once')
-	GMC.runButton:SetScript('OnEnter', function()
-		GameTooltip:SetOwner(GMC.runButton, 'ANCHOR_TOPRIGHT')
-		GameTooltip:AddLine('Submit all yopur mission at once. No question asked.')
-		GameTooltip:AddLine('You can also send mission one by one clicking on each button.')
-		GameTooltip:Show()
-	end)
-	GMC.runButton:SetScript('OnLeave', function() GameTooltip:Hide() end)
-	GMC.runButton:SetWidth(148)
-	GMC.runButton:SetPoint('TOPRIGHT',-15,25)
-	GMC.runButton:SetScript('OnClick',function(this,button) self:GMC_OnClick_Run(this,button) end)
-	GMC.runButton:Disable()
-	GMC.skipRare=factory:Checkbox(GMC,GMC.settings.skipRare,L["Ignore rare missions"])
-	GMC.skipRare:SetPoint("TOPLEFT",priorities,"BOTTOMLEFT",0,-10)
-	GMC.skipRare:SetScript("OnClick",function(this)
-		GMC.settings.skipRare=this:GetChecked()
-		addon:GMC_OnClick_Start(GMC.startButton,"LeftUp")
-	end)
-	GMC.Credits=GMC:CreateFontString(nil,"ARTWORK","ReputationDetailFont")
-	GMC.Credits:SetWidth(0)
-	GMC.Credits:SetFormattedText("Original concept and interface by %s",C("Motig","Red") )
-	GMC.Credits:SetPoint("BOTTOMLEFT",25,25)
-end
-function addon:GMCRewardRefresh()
-	local single=GMC.settings.useOneChance
-	local ref
-	for i=1,#GMC.ignoreFrames do
-		local frame=GMC.ignoreFrames[i]
-		local allowed=GMC.settings.allowedRewards[frame.key]
-		frame.icon:SetDesaturated(not allowed)
-		local a1,o,a2,x,y=frame:GetPoint(1)
-		if (not single) then
-			frame.chest:Show()
-			frame.slider:Show()
-			frame:SetPoint(a1,o,a2,0,y)
-		else
-			frame.chest:Hide()
-			frame.slider:Hide()
-			frame:SetPoint(a1,o,a2,100,y)
-		end
-		ref=frame
-	end
-	if (single) then
-		GMC.itf2:SetPoint('TOPLEFT',ref,'BOTTOMLEFT', -110, -15)
-		GMC.cp:SetDesaturated(false)
-		GMC.ct:SetTextColor(C.Green())
-	else
-		GMC.itf2:SetPoint('TOPLEFT',ref,'BOTTOMLEFT', 10, -15)
-		GMC.cp:SetDesaturated(true)
-		GMC.ct:SetTextColor(C.Silver())
-	end
-end
-function addon:GMCBuildChance()
-	_G['GMC']=GMC
-	--Chance
-	GMC.cf = CreateFrame('FRAME', nil, GMC)
-	GMC.cf:SetSize(256, 150)
-
-	GMC.cp = GMC.cf:CreateTexture(nil, 'BACKGROUND')
-	GMC.cp:SetTexture('Interface\\Garrison\\GarrisonMissionUI2.blp')
-	GMC.cp:SetAtlas(chestTexture)
-	GMC.cp:SetSize((209-(209*0.25))*0.60, (155-(155*0.25))*0.60)
-	GMC.cp:SetPoint('CENTER', 0, 20)
-
-	GMC.cc = GMC.cf:CreateFontString()
-	GMC.cc:SetFontObject('GameFontNormalHuge')
-	GMC.cc:SetText('Success Chance')
-	GMC.cc:SetPoint('TOP', 0, 0)
-	GMC.cc:SetTextColor(1, 1, 1)
-
-	GMC.ct = GMC.cf:CreateFontString()
-	GMC.ct:SetFontObject('ZoneTextFont')
-	GMC.ct:SetFormattedText('%d%%',GMC.settings.minimumChance)
-	GMC.ct:SetPoint('TOP', 0, -40)
-	GMC.ct:SetTextColor(0, 1, 0)
-
-	GMC.cs = factory:Slider(GMC.cf,0,100,GMC.settings.minimumChance,'Minumum chance to start a mission')
-	GMC.cs:SetPoint('BOTTOM', 10, 0)
-	GMC.cs:SetScript('OnValueChanged', function(self, value)
-			local value = math.floor(value)
-			GMC.ct:SetText(value..'%')
-			GMC.settings.minimumChance = value
-	end)
-	GMC.cs:SetValue(GMC.settings.minimumChance)
-	GMC.ck=factory:Checkbox(GMC.cs,GMC.settings.useOneChance,"Use this percentage for all missions")
-	GMC.ck.tooltip="Unchecking this will allow you to set specific success chance for each reward type"
-	GMC.ck:SetPoint("TOPLEFT",GMC.cs,"BOTTOMLEFT",-60,-10)
-	GMC.ck:SetScript("OnClick",function(this)
-		GMC.settings.useOneChance=this:GetChecked()
-		addon:GMCRewardRefresh()
-	end)
-	return GMC.cf
-end
-local function timeslidechange(this,value)
-	local value = math.floor(value)
-	if (this.max) then
-		GMC.settings.maxDuration = max(value,GMC.settings.minDuration)
-		if (value~=GMC.settings.maxDuration) then this:SetValue(GMC.settings.maxDuration) end
-	else
-		GMC.settings.minDuration = min(value,GMC.settings.maxDuration)
-		if (value~=GMC.settings.minDuration) then this:SetValue(GMC.settings.minDuration) end
-	end
-	local c = 1-(value*(1/24))
-	if c < 0.3 then c = 0.3 end
-	GMC.mt:SetTextColor(1, c, c)
-	GMC.mt:SetFormattedText("%d-%dh",GMC.settings.minDuration,GMC.settings.maxDuration)
-end
-function addon:GMCBuildDuration()
-	-- Duration
-	GMC.tf = CreateFrame('FRAME', nil, GMC)
-	GMC.tf:SetSize(256, 180)
-	GMC.tf:SetPoint('LEFT', 80, 120)
-
-	GMC.bg = GMC.tf:CreateTexture(nil, 'BACKGROUND')
-	GMC.bg:SetTexture('Interface\\Timer\\Challenges-Logo.blp')
-	GMC.bg:SetSize(100, 100)
-	GMC.bg:SetPoint('CENTER', 0, 0)
-	GMC.bg:SetBlendMode('ADD')
-
-	GMC.tcf = GMC.tf:CreateTexture(nil, 'BACKGROUND')
-	--bb:SetTexture('Interface\\Timer\\Challenges-Logo.blp')
-	--bb:SetTexture('dungeons\\textures\\devices\\mm_clockface_01.blp')
-	GMC.tcf:SetTexture('World\\Dungeon\\Challenge\\clockRunes.blp')
-	GMC.tcf:SetSize(110, 110)
-	GMC.tcf:SetPoint('CENTER', 0, 0)
-	GMC.tcf:SetBlendMode('ADD')
-
-	GMC.mdt = GMC.tf:CreateFontString()
-	GMC.mdt:SetFontObject('GameFontNormalHuge')
-	GMC.mdt:SetText('Mission Duration')
-	GMC.mdt:SetPoint('TOP', 0, 0)
-	GMC.mdt:SetTextColor(1, 1, 1)
-
-	GMC.mt = GMC.tf:CreateFontString()
-	GMC.mt:SetFontObject('ZoneTextFont')
-	GMC.mt:SetFormattedText('%d-%dh',GMC.settings.minDuration,GMC.settings.maxDuration)
-	GMC.mt:SetPoint('CENTER', 0, 0)
-	GMC.mt:SetTextColor(1, 1, 1)
-
-	GMC.ms1 = factory:Slider(GMC.tf,0,24,GMC.settings.minDuration,'Minimum mission duration.')
-	GMC.ms2 = factory:Slider(GMC.tf,0,24,GMC.settings.maxDuration,'Maximum mission duration.')
-	GMC.ms1:SetPoint('BOTTOM', 0, 0)
-	GMC.ms2:SetPoint('TOP', GMC.ms1,"BOTTOM",0, -25)
-	GMC.ms2.max=true
-	GMC.ms1:SetScript('OnValueChanged', timeslidechange)
-	GMC.ms2:SetScript('OnValueChanged', timeslidechange)
-	timeslidechange(GMC.ms1,GMC.settings.minDuration)
-	timeslidechange(GMC.ms2,GMC.settings.maxDuration)
-	return GMC.tf
-end
-function addon:GMCBuildRewards()
-	--Allowed rewards
-	GMC.aif = CreateFrame('FRAME', nil, GMC)
-	GMC.aif:SetPoint('CENTER', 0, 120)
-
-	GMC.itf = GMC.aif:CreateFontString()
-	GMC.itf:SetFontObject('GameFontNormalHuge')
-	GMC.itf:SetText('Allowed Rewards')
-	GMC.itf:SetPoint('TOP', 0, -10)
-	GMC.itf:SetTextColor(1, 1, 1)
-
-	GMC.itf2 = GMC.aif:CreateFontString()
-	GMC.itf2:SetFontObject('GameFontHighlight')
-	GMC.itf2:SetText('Click to enable/disable a reward.')
-	GMC.itf2:SetTextColor(1, 1, 1)
-
-
-	local t = {
-		{t = 'Enable/Disable money rewards.', i = 'Interface\\Icons\\inv_misc_coin_01', key = 'gold'},
-		{t = 'Enable/Disable other currency awards. (Resources/Seals)', i= 'Interface\\Icons\\inv_garrison_resource', key = 'resources'},
-		{t = 'Enable/Disable Follower XP Bonus rewards.', i = 'Interface\\Icons\\XPBonus_Icon', key = 'xpBonus'},
-		{t = 'Enable/Disable follower equip enhancement.', i = 'Interface\\ICONS\\Garrison_ArmorUpgrade', key = 'followerUpgrade'},
-		{t = 'Enable/Disable item tokens.', i = "Interface\\ICONS\\INV_Bracer_Cloth_Reputation_C_01", key = 'itemLevel'}
-	}
-	local scale=1.1
-	GMC.ignoreFrames = {}
-	local ref
-	local h=37 -- itemButtonTemplate standard size
-	local gap=5
-	for i = 1, #t do
-			local frame = CreateFrame('BUTTON', nil, GMC.aif, 'ItemButtonTemplate')
-			frame:SetScale(scale)
-			frame:SetPoint('TOPLEFT', 0,(i) * (-h -gap) * scale)
-			frame.icon:SetTexture(t[i].i)
-			frame.key=t[i].key
-			frame.tooltip=t[i].t
-			local allowed=GMC.settings.allowedRewards[frame.key]
-			local chance=GMC.settings.rewardChance[frame.key]
-			-- Need to resave them asap in order to populate the array for future scans
-			GMC.settings.allowedRewards[frame.key]=allowed
-			GMC.settings.rewardChance[frame.key]=chance
-			frame.slider=factory:Slider(frame,0,100,chance or 100,chance or 100)
-			frame.slider:SetWidth(128)
-			frame.slider:SetPoint('BOTTOMLEFT',60,0)
-			frame.slider.Text:SetFontObject('NumberFont_Outline_Med')
-			frame.slider.Text:SetTextColor(C.Green())
-			frame.slider.isPercent=true
-			frame.slider:SetScript("OnValueChanged",function(this,value)
-				GMC.settings.rewardChance[this:GetParent().key]=this:OnValueChanged(value)
-				end
-			)
-			frame.chest = frame:CreateTexture(nil, 'BACKGROUND')
-			frame.chest:SetTexture('Interface\\Garrison\\GarrisonMissionUI2.blp')
-			frame.chest:SetAtlas(chestTexture)
-			frame.chest:SetSize((209-(209*0.25))*0.30, (155-(155*0.25)) * 0.30)
-			frame.chest:SetPoint('CENTER',frame.slider, 0, 25)
-			frame:SetScript('OnClick', function(this)
-				local allowed=  this.icon:IsDesaturated() -- ID it was desaturated, I want it allowed, now
-				GMC.settings.allowedRewards[this.key] = allowed
-				addon:GMCRewardRefresh()
-			end)
-			frame:SetScript('OnEnter', function(this)
-				GameTooltip:SetOwner(this, 'ANCHOR_BOTTOMRIGHT')
-				GameTooltip:AddLine(this.tooltip);
-				GameTooltip:Show()
-			end)
-
-			frame:SetScript('OnLeave', function() GameTooltip:Hide() end)
-			GMC.ignoreFrames[i] = frame
-			ref=frame
-	end
-	self:GMCRewardRefresh()
-	GMC.aif:SetSize(256, (scale*h+gap) * #t)
-	GMC.itf2:SetPoint('TOPLEFT',ref,'BOTTOMLEFT', 5, -15)
-	return GMC.aif
-end
-
-local addPriorityRule,prioRefresh,removePriorityRule,prioMenu,prioTitles,prioCheck,prioVoices
-do
--- 1 = item, 2 = folitem, 3 = exp, 4 = money, 5 = resource
-	prioTitles={
-		itemLevel="Gear Items",
-		followerUpgrade="Upgrade Items",
-		xpBonus="Follower XP Bonus",
-		gold="Gold Reward",
-		resources="Resource Rewards"
-	}
-	prioVoices=0
-	for _ in pairs(prioTitles) do prioVoices=prioVoices+1 end
-	prioMenu={}
-
-	---@function [parent=#GMC] prioRefresh
-	function prioRefresh()
-		for i=1,prioVoices do
-			local group=GMC.prioFrames[i]
-			local code=GMC.settings.itemPrio[i]
-			if (not code) then
-				group.text:Hide()
-				group.xbutton:Hide()
-				group.nr:Hide()
-			else
-				group.text:Show()
-				group.text:SetText(prioTitles[code])
-				group.xbutton:Show()
-				group.nr:Show()
-			end
-		end
-		GMC.abutton:Hide()
-		for i=1,prioVoices do
-			local group=GMC.prioFrames[i]
-			if (not group.text:IsShown()) then
-				group.nr:Show()
-				GMC.abutton:SetPoint("TOPLEFT",group.text)
-				GMC.abutton:Show()
-				break
-			end
-		end
-	end
-	---@function [parent=#GMC] addPriorityRule
-	function addPriorityRule(this,key)
-		tinsert(GMC.settings.itemPrio,key)
-		prioRefresh()
-	end
-	---@function [parent=#GMC] removePriorityRule
-	function removePriorityRule(index)
-		tremove(GMC.settings.itemPrio,index)
-		prioRefresh()
-	end
-
-end
-_G.XPRIO=prioRefresh
-function addon:GMCBuildPriorities()
-	--Prio
-	GMC.pf = CreateFrame('FRAME', nil, GMC)
-	GMC.pf:SetSize(256, 240)
-
-	GMC.pft = GMC.pf:CreateFontString()
-	GMC.pft:SetFontObject('GameFontNormalHuge')
-	GMC.pft:SetText('Item Priority')
-	GMC.pft:SetPoint('TOP', 0, -10)
-	GMC.pft:SetTextColor(1, 1, 1)
-
-	GMC.pft2 = GMC.pf:CreateFontString()
-	GMC.pft2:SetFontObject('GameFontNormal')
-	GMC.pft2:SetText('Prioritize missions with certain a reward.')
-	GMC.pft2:SetPoint('BOTTOM', 0, 16)
-	GMC.pft2:SetTextColor(1, 1, 1)
-	GMC.pmf = CreateFrame("FRAME", "GMC_PRIO_MENU", GMC.pf, "UIDropDownMenuTemplate")
-
-
-	GMC.prioFrames = {}
-	GMC.prioFrames.selected = 0
-	for i = 1, prioVoices do
-		GMC.prioFrames[i] = {}
-		local this = GMC.prioFrames[i]
-		this.f = CreateFrame('FRAME', nil, GMC.pf)
-		this.f:SetSize(255, 32)
-		this.f:SetPoint('TOP', 0, -38-((i-1)*32))
-
-		this.nr = this.f:CreateFontString()
-		this.nr:SetFontObject('GameFontNormalHuge')
-		this.nr:SetText(i..'.')
-		this.nr:SetPoint('LEFT', 8, 0)
-		this.nr:SetTextColor(1, 1, 1)
-
-		this.text = this.f:CreateFontString()
-		this.text:SetFontObject('GameFontNormalLarge')
-		this.text:SetText('Def')
-		this.text:SetPoint('LEFT', 32, 0)
-		--this.text:SetTextColor(1, 1, 0)
-		this.text:SetJustifyH('LEFT')
-		this.text:Hide()
-
-		this.xbutton = CreateFrame('BUTTON', nil, this.f, 'GameMenuButtonTemplate')
-		this.xbutton:SetPoint('RIGHT', 0, 0)
-		this.xbutton:SetText('X')
-		this.xbutton:SetWidth(28)
-		this.xbutton:SetScript('OnClick', function() removePriorityRule(i)  end)
-		this.xbutton:Hide()
-	end
-
-	GMC.abutton = CreateFrame('BUTTON', nil, GMC.pmf, 'GameMenuButtonTemplate')
-	GMC.abutton:SetText(L['Add priority rule'])
-	GMC.abutton:SetWidth(128)
-	GMC.abutton:Hide()
-	GMC.abutton:SetScript('OnClick', function()
-		wipe(prioMenu)
-		tinsert(prioMenu,{text = L["Select an item to add as priority."], isTitle = true, isNotRadio=true,disabled=true, notCheckable=true,notClickable=true})
-		for k,v in pairs(prioTitles) do
-			tinsert(prioMenu,{text = v, func = addPriorityRule, notCheckable=true, isNotRadio=true, arg1 = k , disabled=inTable(GMC.settings.itemPrio,k)})
-		end
-		EasyMenu(prioMenu, GMC.pmf, "cursor", 0 , 0, "MENU")
-		end
-	)
-	prioRefresh()
-	return GMC.pf
-end
-function addon:GMCBuildMissionList()
-		-- Mission list on follower panels
---		local ml=CreateFrame("Frame",nil,GMC)
---		addBackdrop(ml)
---		ml:Show()
---		ml.Missions={}
---		ml.Parties={}
---		GMC.ml=ml
---		local fs=ml:CreateFontString(nil, "BACKGROUND", "GameFontNormalHugeBlack")
---		fs:SetPoint("TOPLEFT",0,-5)
---		fs:SetPoint("TOPRIGHT",0,-5)
---		fs:SetText(READY)
---		fs:SetTextColor(C.Green())
---		fs:SetHeight(30)
---		fs:SetJustifyV("CENTER")
---		fs:Show()
---		GMC.progressText=fs
---		GMC.ml.Header=fs
---		return GMC.ml
-	local ml={widget=AceGUI:Create("GMCLayer"),Parties={}}
-	ml.widget:SetTitle(READY)
-	ml.widget:SetTitleColor(C.Green())
-	ml.widget:SetTitleHeight(40)
-	ml.widget:SetParent(GMC)
-	ml.widget:Show()
-	GMC.ml=ml
-	return ml.widget
-
-end
 --[[
 addon.oldSetUp=addon.SetUp
 function addon:ExperimentalSetUp()
@@ -4839,9 +3936,9 @@ do
 				v.quantity=v.quantity or 0
 				v.multiplier=v.multiplier or 1
 --@debug@
---				xprint(format("Reward type: = %s",k))
+--				ns.xprint(format("Reward type: = %s",k))
 --				for field,value in pairs(v) do
---					xprint(format("   %s = %s",field,value),C.Silver())
+--					ns.xprint(format("   %s = %s",field,value),C.Silver())
 --				end
 --@end-debug@
 				if v.currencyID then
@@ -4879,9 +3976,7 @@ function GarrisonMissionPage_Close(self)

 	GarrisonMissionFrame.MissionTab.MissionPage:Hide();
 	GarrisonMissionFrame.MissionTab.MissionList:Show();
-	holdEvents()
 	GarrisonMissionPage_ClearParty();
-	releaseEvents()
 	GarrisonMissionFrame.followerCounters = nil;
 	GarrisonMissionFrame.MissionTab.MissionPage.missionInfo = nil;
 	if (lastTab) then
@@ -4889,223 +3984,3 @@ function GarrisonMissionPage_Close(self)
 	end
 end
 GMF.MissionTab.MissionPage.CloseButton:SetScript("OnClick",GarrisonMissionPage_Close)
-
---@do-not-package@
-_G.GAC=addon
-
---- Enable a trace for every function call. It's a VERY heavy debug
---
-if HD then
-local memorysinks={}
-local callstack={}
-local lib=LibStub("LibInit")
-for k,v in pairs(addon) do
-	if (type(v))=="function" and not lib[k] then
-		local wrapped
-		do
-			local original=addon[k]
-			wrapped=function(...)
-				pp(k)
-				tinsert(callstack,k)
-				local membefore=GetAddOnMemoryUsage("GarrisonCommander")
-				local a1,a2,a3,a4,a5,a6,a7,a8,a9=original(...)
-				local memafter=GetAddOnMemoryUsage("GarrisonCommander")
-				tremove(callstack)
-				memorysinks[k].mem=memorysinks[k].mem+memafter-membefore
-				memorysinks[k].calls=memorysinks[k].calls+1
-				if (#callstack) then
-					memorysinks[k].callers=strjoin("->",unpack(callstack))
-				else
-					memorysinks[k].callers="main"
-				end
-				if (memafter-membefore > 20) then
-					pp(C(k,'Red'),'used ',memafter-membefore)
-				end
-				return a1,a2,a3,a4,a5,a6,a7,a8,a9
-			end
-		end
-		addon[k]=wrapped
-		memorysinks[k]={mem=0,calls=0,callers=""}
-	end
-end
-function addon:ResetSinks()
-	for k,v in pairs(memorysinks) do
-		memorysinks[k].mem=0
-		memorysinks[k].calls=0
-	end
-end
-local sorted={}
-function addon:DumpSinks()
-	local scroll=self:GetScroller("Sinks",nil,400,1000)
-	wipe(sorted)
-	for k,v in pairs(memorysinks) do
-		if v.mem then
-			tinsert(sorted,format("Mem %06d (calls: %03d) Mem per call:%03.2f  Callstack:%s(%s)",v.mem,v.calls,v.mem/v.calls,C(k,"Orange"),v.callers))
-		end
-	end
-	table.sort(sorted,function(a,b) return a>b end)
-	self:cutePrint(scroll,sorted)
-end
-end
-function addon:GetScroller(title,type,h,w)
-	h=h or 800
-	w=w or 400
-	type=type or "Frame"
-	local scrollerWindow=AceGUI:Create("Frame")
-	scrollerWindow:SetTitle(title)
-	scrollerWindow:SetLayout("Fill")
-	--local scrollcontainer = AceGUI:Create("SimpleGroup") -- "InlineGroup" is also good
-	--scrollcontainer:SetFullWidth(true)
-	--scrollcontainer:SetFullHeight(true) -- probably?
-	--scrollcontainer:SetLayout("Fill") -- important!
-	--scrollerWindow:AddChild(scrollcontainer)
-	local scroll = AceGUI:Create("ScrollFrame")
-	scroll:SetLayout("Flow") -- probably?
-	scroll:SetFullWidth(true)
-	scroll:SetFullHeight(true)
-	scrollerWindow:AddChild(scroll)
-	scrollerWindow:SetCallback("OnClose","Release")
-	scrollerWindow:SetHeight(h)
-	scrollerWindow:SetWidth(w)
-	scrollerWindow:SetPoint("CENTER")
-	scrollerWindow:Show()
-	scroll.AddRow=function (self,...) return addon:AddRow(self,...) end
-	return scroll
-end
-function addon:AddRow(obj,text,...)
---@debug@
-	assert(obj)
---@end-debug@
-	if (obj) then
-		local l=AceGUI:Create("Label")
-		l:SetText(text)
-		l:SetColor(...)
-		l:SetFullWidth(true)
-		obj:AddChild(l)
-	end
-end
-function addon:cutePrint(scroll,level,k,v)
-	if (type(level)=="table") then
-		for k,v in pairs(level) do
-			self:cutePrint(scroll,"",k,v)
-		end
-		return
-	end
-	if (type(v)=="table") then
-		self:AddRow(scroll,level..C(k,"Azure")..":" ..C("Table","Orange"))
-		for kk,vv in pairs(v) do
-			self:cutePrint(scroll,level .. "  ",kk,vv)
-		end
-	else
-		if (type(v)=="string" and v:sub(1,2)=='0x') then
-			v=v.. " " ..tostring(self:GetFollowerData(v,'name'))
-		end
-		self:AddRow(scroll,level..C(k,"White")..":" ..C(v,"Yellow"))
-	end
-end
-function addon:DumpFollower(name)
-	local follower=self:GetFollowerData(name)
-	if (follower) then
-		local scroll=self:GetScroller(follower.name)
-		self:cutePrint(scroll,follower)
-	end
-
-end
-function addon:DumpStatus(title)
-	local scroll=self:GetScroller(title)
-	for i=1,#followersCache do
-		local followerID=followersCache[i].followerID
-		scroll:AddRow(format("%s (%s): %d",self:GetFollowerData(followerID,'fullname'),self:GetFollowerData(followerID,'followerID'),G.GetFollowerXP(followerID)))
-	end
-	scroll:AddRow("Garrison resources: " .. select(2,GetCurrencyInfo(GARRISON_CURRENCY)))
-	scroll:AddRow("Money: " .. GetMoneyString(GetMoney()))
-end
-function addon:DumpFollowers()
-	local scroll=self:GetScroller("Followers Cache (" .. #followersCache ..")"  )
-	self:cutePrint(scroll,followersCache)
-end
-function addon:DumpFollowerMissions(missionID)
-	local scroll=self:GetScroller("FollowerMissions " .. self:GetMissionData(missionID,'name'))
-	self:cutePrint(scroll,followerMissions.missions[missionID])
-end
-function addon:DumpIgnored()
-	local scroll=self:GetScroller("Ignored")
-	self:cutePrint(scroll,self.privatedb.profile.ignored)
-end
-function addon:DumpMission(missionID)
-	local scroll=self:GetScroller("MissionCache " .. self:GetMissionData(missionID,'name'))
-	self:cutePrint(scroll,cache.missions[missionID])
-end
-function addon:DumpMissions()
-	local scroll=self:GetScroller("MissionCache")
-	for id,data in pairs(cache.missions) do
-		self:cutePrint(scroll,id .. '.'..data.name)
-	end
-end
----
--- Debug function
---@param missionID Identificativo missione
-function addon:DumpCounters(missionID)
-	local scroll=self:GetScroller("Counters " .. self:GetMissionData(missionID,'name'))
-	self:cutePrint(scroll,counters[missionID])
-	self:cutePrint(scroll,"Lista per follower","","")
-	self:cutePrint(scroll,counterFollowerIndex[missionID])
-	self:cutePrint(scroll,"Lista per threat","","")
-	self:cutePrint(scroll,counterThreatIndex[missionID])
-end
-function addon:Dump(title,data)
-	local scroll=self:GetScroller(title)
-	self:cutePrint(scroll,data)
-
-end
-function addon:DumpCounterers(missionID)
-	local scroll=self:GetScroller("Counterers " .. self:GetMissionData(missionID,'name'))
-	self:cutePrint(scroll,cache.missions[missionID].counterers)
-end
-function addon:DumpParty(missionID)
-	local scroll=self:GetScroller("Party " .. self:GetMissionData(missionID,'name'))
-	self:cutePrint(scroll,parties[missionID])
-end
-function addon:DumpAgeDb()
-	local t=new()
-	for i,v in pairs(dbcache.seen) do
-		tinsert(t,format("%80s %s %d",self:GetMissionData(i,'name'),date("%d/%m/%y %H:%M:%S",v),ns.wowhead[i]))
-	end
-	local scroll=self:GetScroller("Expire db")
-	self:cutePrint(scroll,t)
-	del(t)
-end
-_G.GCF=GCF
-_G.MW=173
---[[
-PlaySound("UI_Garrison_CommandTable_Open");
-	PlaySound("UI_Garrison_CommandTable_Close");
-	PlaySound("UI_Garrison_Nav_Tabs");
-	PlaySound("UI_Garrison_Nav_Tabs");
-	PlaySound("UI_Garrison_CommandTable_SelectMission");
-			PlaySound("UI_Garrison_CommandTable_IncreaseSuccess");
-			PlaySound("UI_Garrison_CommandTable_100Success");
-			PlaySound("UI_Garrison_CommandTable_ReducedSuccessChance");
-				PlaySound("UI_Garrison_Mission_Threat_Countered");
-			PlaySoundKitID(43507);	-- 100% chance reached
-	PlaySound("UI_Garrison_CommandTable_AssignFollower");
-			PlaySound("UI_Garrison_CommandTable_UnassignFollower");
-		PlaySound("UI_Garrison_Mission_Threat_Countered");
-	PlaySound("UI_Garrison_CommandTable_MissionStart");
-	PlaySound("UI_Garrison_CommandTable_ViewMissionReport");
-		PlaySound("UI_Garrison_Mission_Complete_Encounter_Chance");
-	PlaySound("UI_Garrison_CommandTable_Nav_Next");
-		PlaySound("UI_Garrison_CommandTable_ChestUnlock_Gold_Success");
-		PlaySound("UI_Garrison_Mission_Threat_Countered");
-		PlaySound("UI_Garrison_MissionEncounter_Animation_Generic");
-			PlaySoundKitID(currentAnim.castSoundID);
-		PlaySoundKitID(currentAnim.impactSoundID);
-			PlaySound("UI_Garrison_Mission_Complete_Encounter_Fail");
-			PlaySound("UI_Garrison_Mission_Complete_Mission_Success");
-		PlaySound("UI_Garrison_CommandTable_MissionSuccess_Stinger");
-		PlaySound("UI_Garrison_Mission_Complete_MissionFail_Stinger");
-		PlaySound("UI_Garrison_CommandTable_ChestUnlock");
-				PlaySound("UI_Garrison_CommandTable_Follower_LevelUp");
-
---]]
---@end-do-not-package@
diff --git a/GarrisonCommander.toc b/GarrisonCommander.toc
index 896ea08..f381854 100644
--- a/GarrisonCommander.toc
+++ b/GarrisonCommander.toc
@@ -25,5 +25,18 @@
 embeds.xml
 localization.lua
 wowhead.lua
+Init.lua
+MissionCache.lua
+FollowerCache.lua
+PartyCache.lua
+MissionControl.lua
+MissionCompletion.lua
+FollowerPage.lua
+MatchMaker.lua
+FollowerRecruiting.lua
 GarrisonCommander.xml
 RelNotes.lua
+#@do-not-package@
+Debug.lua
+#@end-do-not-package@
+
diff --git a/Init.lua b/Init.lua
new file mode 100644
index 0000000..b036a73
--- /dev/null
+++ b/Init.lua
@@ -0,0 +1,100 @@
+local me, ns = ...
+local _G=_G
+local pp=print
+--@debug@
+LoadAddOn("Blizzard_DebugTools")
+--@end-debug@
+ns.addon=LibStub("LibInit"):NewAddon(me,'AceHook-3.0','AceTimer-3.0','AceEvent-3.0','AceBucket-3.0')
+local addon=ns.addon --#addon
+ns.AceGUI=LibStub("AceGUI-3.0")
+ns.D=LibStub("LibDeformat-3.0")
+ns.C=ns.addon:GetColorTable()
+ns.L=ns.addon:GetLocale()
+ns.print=ns.addon:Wrap("Print")
+ns.dprint=ns.print
+ns.trace=ns.addon:Wrap("Trace")
+ns.xprint=function() end
+ns.xdump=function() end
+ns.xtrace=function() end
+--@debug@
+ns.xprint=function(...) pp("|cffff9900DBG|r",...) end
+ns.xdump=function(d,t) pp("|cffff9900DMP|r",t) DevTools_Dump(d) end
+ns.xtrace=ns.trace
+--@end-debug@
+local setmetatable=setmetatable
+local next=next
+local pairs=pairs
+local wipe=wipe
+do
+	--@debug@
+	local newcount, delcount,createdcount,cached = 0,0,0
+	--@end-debug@
+	local pool = setmetatable({},{__mode="k"})
+	function ns.new()
+	--@debug@
+		newcount = newcount + 1
+	--@end-debug@
+		local t = next(pool)
+		if t then
+			pool[t] = nil
+			return t
+		else
+	--@debug@
+			createdcount = createdcount + 1
+	--@end-debug@
+			return {}
+		end
+	end
+	function ns.copy(t)
+		local c = ns.new()
+		for k, v in pairs(t) do
+			c[k] = v
+		end
+		return c
+	end
+	function ns.del(t)
+	--@debug@
+		delcount = delcount + 1
+	--@end-debug@
+		wipe(t)
+		pool[t] = true
+	end
+	--@debug@
+	function cached()
+		local n = 0
+		for k in pairs(pool) do
+			n = n + 1
+		end
+		return n
+	end
+	function ns.addon:CacheStats()
+		ns.print("Created:",createdcount)
+		ns.print("Aquired:",newcount)
+		ns.print("Released:",delcount)
+		ns.print("Cached:",cached())
+	end
+	--@end-debug@
+end
+
+local stacklevel=0
+local frames
+function addon:holdEvents()
+	if stacklevel==0 then
+		frames={GetFramesRegisteredForEvent('GARRISON_FOLLOWER_LIST_UPDATE')}
+		for i=1,#frames do
+			frames[i]:UnregisterEvent("GARRISON_FOLLOWER_LIST_UPDATE")
+		end
+	end
+	stacklevel=stacklevel+1
+end
+function addon:releaseEvents()
+	stacklevel=stacklevel-1
+	assert(stacklevel>=0)
+	if (stacklevel==0) then
+		for i=1,#frames do
+			frames[i]:RegisterEvent("GARRISON_FOLLOWER_LIST_UPDATE")
+		end
+		frames=nil
+	end
+end
+local holdEvents,releaseEvents=addon.holdEvents,addon.releaseEvents
diff --git a/MatchMaker.lua b/MatchMaker.lua
new file mode 100644
index 0000000..e69de29
diff --git a/MissionCache.lua b/MissionCache.lua
new file mode 100644
index 0000000..e69de29
diff --git a/MissionCompletion.lua b/MissionCompletion.lua
new file mode 100644
index 0000000..e69de29
diff --git a/MissionControl.lua b/MissionControl.lua
new file mode 100644
index 0000000..ba3144c
--- /dev/null
+++ b/MissionControl.lua
@@ -0,0 +1,652 @@
+local me, ns = ...
+local addon=ns.addon --#addon
+local L=ns.L
+local D=ns.D
+local C=ns.C
+local AceGUI=ns.AceGUI
+local _G=_G
+local new, del, copy =ns.new,ns.del,ns.copy
+-- Courtesy of Motig
+-- Concept and interface reused with permission
+-- Mission building rewritten from scratch
+--local GMC_G = {}
+local factory=addon:GetFactory()
+--GMC_G.frame = CreateFrame('FRAME')
+local aMissions={}
+local dbcache
+local cache
+local db
+local parties
+local GMC
+local GMF=GarrisonMissionFrame
+local G=C_Garrison
+local GMCUsedFollowers={}
+local wipe=wipe
+local pairs=pairs
+local tinsert=tinsert
+--@debug@
+if LibDebug then LibDebug() end
+--@end-debug@
+local dbg
+function addon:GMCBusy(followerID)
+	return GMCUsedFollowers[followerID]
+end
+function addon:GMCCreateMissionList(workList)
+	--First get rid of unwanted rewards and missions that are too long
+	local settings=self.privatedb.profile.missionControl
+	local ar=settings.allowedRewards
+	wipe(workList)
+	for missionID,mission in pairs(cache.missions) do
+		local discarded=false
+		repeat
+			if (mission.durationSeconds > settings.maxDuration * 3600 or mission.durationSeconds <  settings.minDuration * 3600) then
+				ns.xprint(missionID,"discarded due to len",mission.durationSeconds /3600)
+				break
+			end -- Mission too long, out of here
+			if (mission.isRare and settings.skipRare) then
+				ns.xprint(missionID,"discarded due to rarity")
+				break
+			end
+			for k,v in pairs(ar) do
+				if (not v) then
+					if (mission[k] and mission[k]~=0) then -- we have a forbidden reward
+						discarded=true
+						break
+					end
+				end
+			end
+			if (not discarded) then
+				tinsert(workList,missionID)
+			end
+		until true
+	end
+	local function msort(i1,i2)
+		local m1=addon:GetMissionData(i1)
+		local m2=addon:GetMissionData(i2)
+		for i=1,#GMC.settings.itemPrio do
+			local criterium=GMC.settings.itemPrio[i]
+			if (criterium) then
+				if (m1[criterium] ~= m2[criterium]) then
+					return m1[criterium] > m2[criterium]
+				end
+			end
+		end
+		if (parties[m1.missionID].perc and parties[m2.missionID].perc) then
+			return parties[m1.missionID].perc > parties[m2.missionID].perc
+		end
+		return m1.level > m2.level
+	end
+	table.sort(workList,msort)
+	--@debug@
+	ns.xprint("Sorted list")
+	local x=new()
+	for i=1,#workList do
+		local mission=self:GetMissionData(workList[i])
+		local t=mission.name
+		for i=1,#GMC.settings.itemPrio do
+			local criterium=GMC.settings.itemPrio[i]
+			t=t..format(" %s: %d",criterium,mission[criterium])
+		end
+		tinsert(x,t .. " Success" ..  tostring(parties[mission.missionID].perc))
+	end
+	local scroll=self:GetScroller("Sorted missions",nil,600,600)
+	self:cutePrint(scroll,x)
+	del(x)
+	--@end-debug@
+
+end
+--- This routine can be called both as coroutin and as a standard one
+-- In standard version, delay between group building and submitting is done via a self schedule
+--@param #integer missionID Optional, to run a single mission
+--@param #bool start Optional, tells that follower already are on mission and that we need just to start it
+function addon:GMCRunMission(missionID,start)
+	ns.xprint("Asked to start mission",missionID)
+	if (start) then
+		G.StartMission(missionID)
+		PlaySound("UI_Garrison_CommandTable_MissionStart")
+		return
+	end
+	for i=1,#GMC.ml.Parties do
+		local party=GMC.ml.Parties[i]
+		ns.xprint("Checking",party.missionID)
+		if (missionID and party.missionID==missionID or not missionID) then
+			GMC.ml.widget:RemoveChild(party.missionID)
+			GMC.ml.widget:DoLayout()
+			if (party.full) then
+				for j=1,#party.members do
+					G.AddFollowerToMission(party.missionID, party.members[j])
+				end
+				if (not missionID) then
+					coroutine.yield(true)
+					G.StartMission(party.missionID)
+					PlaySound("UI_Garrison_CommandTable_MissionStart")
+					coroutine.yield(true)
+				else
+					self:ScheduleTimer("GMCRunMission",0.25,party.missionID,true)
+				end
+			end
+		end
+	end
+end
+do
+	local timeElapsed=0
+	local currentMission=0
+	local x=0
+	function addon:GMCCalculateMissions(this,elapsed)
+		db.news.MissionControl=true
+		timeElapsed = timeElapsed + elapsed
+		if (#aMissions == 0 ) then
+			if timeElapsed >= 1 then
+				currentMission=0
+				x=0
+				self:Unhook(this,"OnUpdate")
+				GMC.ml.widget:SetTitle(READY)
+				GMC.ml.widget:SetTitleColor(C.Green())
+				wipe(GMCUsedFollowers)
+				this:Enable()
+				if (#GMC.ml.Parties>0) then
+					GMC.runButton:Enable()
+				end
+			end
+			return
+		end
+		if (timeElapsed >=0.) then
+			currentMission=currentMission+1
+			if (currentMission > #aMissions) then
+				wipe(aMissions)
+				currentMission=0
+				x=0
+				timeElapsed=0.2
+			else
+				GMC.ml.widget:SetFormattedTitle("Processing mission %d of %d",currentMission,#aMissions)
+				local missionID=aMissions[currentMission]
+				if (dbg) then print(C("Processing ","Red"),missionID) end
+				local party={members={},perc=0}
+				local mission=self:GetMissionData(missionID)
+				if (not mission ) then
+					if dbg then print ("NO data for",missionID) end
+					return
+				end
+				self:MatchMaker(missionID,mission,party) -- I need my mission data
+				local minimumChance=0
+				if (GMC.settings.useOneChance) then
+					minimumChance=GMC.settings.minimumChance
+				end
+				for prio,enabled in pairs(GMC.settings.allowedRewards) do
+					if (dbg) then print("Chance from ",prio,"=",GMC.settings.rewardChance[prio],enabled) end
+					if (enabled and (tonumber(self:GetMissionData(missionID,prio)) or 0) >0) then
+						minimumChance=math.max(GMC.settings.rewardChance[prio],minimumChance)
+					end
+				end
+				if (dbg) then print ("Missionid",missionID,"Chance",minimumChance,"chance",party.perc) end
+				if ( party.full and party.perc >= minimumChance) then
+					if (dbg) then print("Preparing button for",missionID) end
+					local mb=AceGUI:Create("GMCMissionButton")
+					for i=1,#party.members do
+						GMCUsedFollowers[party.members[i]]=true
+					end
+					party.missionID=missionID
+					tinsert(GMC.ml.Parties,party)
+					GMC.ml.widget:PushChild(mb,missionID)
+					mb:SetFullWidth(true)
+					mb:SetMission(mission)
+					mb:SetCallback("OnClick",function(...)
+						addon:GMCRunMission(missionID)
+						GMC.ml.widget:RemoveChild(missionID)
+					end
+					)
+				end
+				timeElapsed=0
+			end
+		end
+	end
+end
+
+function addon:GMC_OnClick_Run(this,button)
+	this:Disable()
+	do
+	local elapsed=0
+	local co=coroutine.wrap(self.GMCRunMission)
+	self:RawHookScript(GMC.runButton,'OnUpdate',function(this,ts)
+		elapsed=elapsed+ts
+		if (elapsed>0.25) then
+			elapsed=0
+			local rc=co(self)
+			if (not rc) then
+				self:Unhook(GMC.runButton,'OnUpdate')
+			end
+		end
+	end
+	)
+	end
+end
+function addon:GMC_OnClick_Start(this,button)
+	ns.xprint(C("-------------------------------------------------","Yellow"))
+	this:Disable()
+	GMC.ml.widget:ClearChildren()
+	if (self:GetTotFollowers(AVAILABLE) == 0) then
+		self:Popup("All followers are busy",10)
+		this:Enable()
+		return
+	end
+	addon:GMCCreateMissionList(aMissions)
+	wipe(GMCUsedFollowers)
+	wipe(GMC.ml.Parties)
+	self:RefreshFollowerStatus()
+	if (#aMissions>0) then
+		GMC.ml.widget:SetFormattedTitle(L["Processing mission %d of %d"],1,#aMissions)
+	else
+		GMC.ml.widget:SetTitle("No mission matches your criteria")
+		GMC.ml.widget:SetTitleColor(C.Red())
+	end
+	self:RawHookScript(GMC.startButton,'OnUpdate',"GMCCalculateMissions")
+
+end
+local chestTexture
+function addon:GMCBuildPanel(bigscreen)
+	db=self.db.global
+	dbcache=self.privatedb.profile
+	cache=self.private.profile
+	parties=self:GetParty()
+	chestTexture='GarrMission-'..UnitFactionGroup('player').. 'Chest'
+	GMC = CreateFrame('FRAME', 'GMCOptions', GMF)
+	GMC.settings=dbcache.missionControl
+	GMC:SetPoint('CENTER')
+	GMC:SetSize(GMF:GetWidth(), GMF:GetHeight())
+	GMC:Hide()
+	local chance=self:GMCBuildChance()
+	local duration=self:GMCBuildDuration()
+	local rewards=self:GMCBuildRewards()
+	local priorities=self:GMCBuildPriorities()
+	local list=self:GMCBuildMissionList()
+	duration:SetPoint("TOPLEFT",0,-50)
+	chance:SetPoint("TOPLEFT",duration,"TOPRIGHT",bigscreen and 50 or 10,0)
+	priorities:SetPoint("TOPLEFT",duration,"BOTTOMLEFT",25,-40)
+	rewards:SetPoint("TOPLEFT",priorities,"TOPRIGHT",bigscreen and 50 or 15,0)
+	list:SetPoint("TOPLEFT",chance,"TOPRIGHT",10,0)
+	list:SetPoint("BOTTOMRIGHT",GMF,"BOTTOMRIGHT",-25,25)
+	GMC.startButton = CreateFrame('BUTTON',nil,  list.frame, 'GameMenuButtonTemplate')
+	GMC.startButton:SetText('Calculate')
+	GMC.startButton:SetWidth(148)
+	GMC.startButton:SetPoint('TOPLEFT',15,25)
+	GMC.startButton:SetScript('OnClick', function(this,button) self:GMC_OnClick_Start(this,button) end)
+	GMC.startButton:SetScript('OnEnter', function() GameTooltip:SetOwner(GMC.startButton, 'ANCHOR_TOPRIGHT') GameTooltip:AddLine('Assign your followers to missions.') GameTooltip:Show() end)
+	GMC.startButton:SetScript('OnLeave', function() GameTooltip:Hide() end)
+	GMC.runButton = CreateFrame('BUTTON', nil,list.frame, 'GameMenuButtonTemplate')
+	GMC.runButton:SetText('Send all mission at once')
+	GMC.runButton:SetScript('OnEnter', function()
+		GameTooltip:SetOwner(GMC.runButton, 'ANCHOR_TOPRIGHT')
+		GameTooltip:AddLine('Submit all yopur mission at once. No question asked.')
+		GameTooltip:AddLine('You can also send mission one by one clicking on each button.')
+		GameTooltip:Show()
+	end)
+	GMC.runButton:SetScript('OnLeave', function() GameTooltip:Hide() end)
+	GMC.runButton:SetWidth(148)
+	GMC.runButton:SetPoint('TOPRIGHT',-15,25)
+	GMC.runButton:SetScript('OnClick',function(this,button) self:GMC_OnClick_Run(this,button) end)
+	GMC.runButton:Disable()
+	GMC.skipRare=factory:Checkbox(GMC,GMC.settings.skipRare,L["Ignore rare missions"])
+	GMC.skipRare:SetPoint("TOPLEFT",priorities,"BOTTOMLEFT",0,-10)
+	GMC.skipRare:SetScript("OnClick",function(this)
+		GMC.settings.skipRare=this:GetChecked()
+		addon:GMC_OnClick_Start(GMC.startButton,"LeftUp")
+	end)
+	GMC.Credits=GMC:CreateFontString(nil,"ARTWORK","ReputationDetailFont")
+	GMC.Credits:SetWidth(0)
+	GMC.Credits:SetFormattedText("Original concept and interface by %s",C("Motig","Red") )
+	GMC.Credits:SetPoint("BOTTOMLEFT",25,25)
+	return GMC
+end
+function addon:GMCRewardRefresh()
+	local single=GMC.settings.useOneChance
+	local ref
+	for i=1,#GMC.ignoreFrames do
+		local frame=GMC.ignoreFrames[i]
+		local allowed=GMC.settings.allowedRewards[frame.key]
+		frame.icon:SetDesaturated(not allowed)
+		local a1,o,a2,x,y=frame:GetPoint(1)
+		if (not single) then
+			frame.chest:Show()
+			frame.slider:Show()
+			frame:SetPoint(a1,o,a2,0,y)
+		else
+			frame.chest:Hide()
+			frame.slider:Hide()
+			frame:SetPoint(a1,o,a2,100,y)
+		end
+		ref=frame
+	end
+	if (single) then
+		GMC.itf2:SetPoint('TOPLEFT',ref,'BOTTOMLEFT', -110, -15)
+		GMC.cp:SetDesaturated(false)
+		GMC.ct:SetTextColor(C.Green())
+	else
+		GMC.itf2:SetPoint('TOPLEFT',ref,'BOTTOMLEFT', 10, -15)
+		GMC.cp:SetDesaturated(true)
+		GMC.ct:SetTextColor(C.Silver())
+	end
+end
+function addon:GMCBuildChance()
+	_G['GMC']=GMC
+	--Chance
+	GMC.cf = CreateFrame('FRAME', nil, GMC)
+	GMC.cf:SetSize(256, 150)
+
+	GMC.cp = GMC.cf:CreateTexture(nil, 'BACKGROUND')
+	GMC.cp:SetTexture('Interface\\Garrison\\GarrisonMissionUI2.blp')
+	GMC.cp:SetAtlas(chestTexture)
+	GMC.cp:SetSize((209-(209*0.25))*0.60, (155-(155*0.25))*0.60)
+	GMC.cp:SetPoint('CENTER', 0, 20)
+
+	GMC.cc = GMC.cf:CreateFontString()
+	GMC.cc:SetFontObject('GameFontNormalHuge')
+	GMC.cc:SetText('Success Chance')
+	GMC.cc:SetPoint('TOP', 0, 0)
+	GMC.cc:SetTextColor(1, 1, 1)
+
+	GMC.ct = GMC.cf:CreateFontString()
+	GMC.ct:SetFontObject('ZoneTextFont')
+	GMC.ct:SetFormattedText('%d%%',GMC.settings.minimumChance)
+	GMC.ct:SetPoint('TOP', 0, -40)
+	GMC.ct:SetTextColor(0, 1, 0)
+
+	GMC.cs = factory:Slider(GMC.cf,0,100,GMC.settings.minimumChance,'Minumum chance to start a mission')
+	GMC.cs:SetPoint('BOTTOM', 10, 0)
+	GMC.cs:SetScript('OnValueChanged', function(self, value)
+			local value = math.floor(value)
+			GMC.ct:SetText(value..'%')
+			GMC.settings.minimumChance = value
+	end)
+	GMC.cs:SetValue(GMC.settings.minimumChance)
+	GMC.ck=factory:Checkbox(GMC.cs,GMC.settings.useOneChance,"Use this percentage for all missions")
+	GMC.ck.tooltip="Unchecking this will allow you to set specific success chance for each reward type"
+	GMC.ck:SetPoint("TOPLEFT",GMC.cs,"BOTTOMLEFT",-60,-10)
+	GMC.ck:SetScript("OnClick",function(this)
+		GMC.settings.useOneChance=this:GetChecked()
+		addon:GMCRewardRefresh()
+	end)
+	return GMC.cf
+end
+local function timeslidechange(this,value)
+	local value = math.floor(value)
+	if (this.max) then
+		GMC.settings.maxDuration = max(value,GMC.settings.minDuration)
+		if (value~=GMC.settings.maxDuration) then this:SetValue(GMC.settings.maxDuration) end
+	else
+		GMC.settings.minDuration = min(value,GMC.settings.maxDuration)
+		if (value~=GMC.settings.minDuration) then this:SetValue(GMC.settings.minDuration) end
+	end
+	local c = 1-(value*(1/24))
+	if c < 0.3 then c = 0.3 end
+	GMC.mt:SetTextColor(1, c, c)
+	GMC.mt:SetFormattedText("%d-%dh",GMC.settings.minDuration,GMC.settings.maxDuration)
+end
+function addon:GMCBuildDuration()
+	-- Duration
+	GMC.tf = CreateFrame('FRAME', nil, GMC)
+	GMC.tf:SetSize(256, 180)
+	GMC.tf:SetPoint('LEFT', 80, 120)
+
+	GMC.bg = GMC.tf:CreateTexture(nil, 'BACKGROUND')
+	GMC.bg:SetTexture('Interface\\Timer\\Challenges-Logo.blp')
+	GMC.bg:SetSize(100, 100)
+	GMC.bg:SetPoint('CENTER', 0, 0)
+	GMC.bg:SetBlendMode('ADD')
+
+	GMC.tcf = GMC.tf:CreateTexture(nil, 'BACKGROUND')
+	--bb:SetTexture('Interface\\Timer\\Challenges-Logo.blp')
+	--bb:SetTexture('dungeons\\textures\\devices\\mm_clockface_01.blp')
+	GMC.tcf:SetTexture('World\\Dungeon\\Challenge\\clockRunes.blp')
+	GMC.tcf:SetSize(110, 110)
+	GMC.tcf:SetPoint('CENTER', 0, 0)
+	GMC.tcf:SetBlendMode('ADD')
+
+	GMC.mdt = GMC.tf:CreateFontString()
+	GMC.mdt:SetFontObject('GameFontNormalHuge')
+	GMC.mdt:SetText('Mission Duration')
+	GMC.mdt:SetPoint('TOP', 0, 0)
+	GMC.mdt:SetTextColor(1, 1, 1)
+
+	GMC.mt = GMC.tf:CreateFontString()
+	GMC.mt:SetFontObject('ZoneTextFont')
+	GMC.mt:SetFormattedText('%d-%dh',GMC.settings.minDuration,GMC.settings.maxDuration)
+	GMC.mt:SetPoint('CENTER', 0, 0)
+	GMC.mt:SetTextColor(1, 1, 1)
+
+	GMC.ms1 = factory:Slider(GMC.tf,0,24,GMC.settings.minDuration,'Minimum mission duration.')
+	GMC.ms2 = factory:Slider(GMC.tf,0,24,GMC.settings.maxDuration,'Maximum mission duration.')
+	GMC.ms1:SetPoint('BOTTOM', 0, 0)
+	GMC.ms2:SetPoint('TOP', GMC.ms1,"BOTTOM",0, -25)
+	GMC.ms2.max=true
+	GMC.ms1:SetScript('OnValueChanged', timeslidechange)
+	GMC.ms2:SetScript('OnValueChanged', timeslidechange)
+	timeslidechange(GMC.ms1,GMC.settings.minDuration)
+	timeslidechange(GMC.ms2,GMC.settings.maxDuration)
+	return GMC.tf
+end
+function addon:GMCBuildRewards()
+	--Allowed rewards
+	GMC.aif = CreateFrame('FRAME', nil, GMC)
+	GMC.aif:SetPoint('CENTER', 0, 120)
+
+	GMC.itf = GMC.aif:CreateFontString()
+	GMC.itf:SetFontObject('GameFontNormalHuge')
+	GMC.itf:SetText('Allowed Rewards')
+	GMC.itf:SetPoint('TOP', 0, -10)
+	GMC.itf:SetTextColor(1, 1, 1)
+
+	GMC.itf2 = GMC.aif:CreateFontString()
+	GMC.itf2:SetFontObject('GameFontHighlight')
+	GMC.itf2:SetText('Click to enable/disable a reward.')
+	GMC.itf2:SetTextColor(1, 1, 1)
+
+
+	local t = {
+		{t = 'Enable/Disable money rewards.', i = 'Interface\\Icons\\inv_misc_coin_01', key = 'gold'},
+		{t = 'Enable/Disable other currency awards. (Resources/Seals)', i= 'Interface\\Icons\\inv_garrison_resource', key = 'resources'},
+		{t = 'Enable/Disable Follower XP Bonus rewards.', i = 'Interface\\Icons\\XPBonus_Icon', key = 'xpBonus'},
+		{t = 'Enable/Disable follower equip enhancement.', i = 'Interface\\ICONS\\Garrison_ArmorUpgrade', key = 'followerUpgrade'},
+		{t = 'Enable/Disable item tokens.', i = "Interface\\ICONS\\INV_Bracer_Cloth_Reputation_C_01", key = 'itemLevel'}
+	}
+	local scale=1.1
+	GMC.ignoreFrames = {}
+	local ref
+	local h=37 -- itemButtonTemplate standard size
+	local gap=5
+	for i = 1, #t do
+			local frame = CreateFrame('BUTTON', nil, GMC.aif, 'ItemButtonTemplate')
+			frame:SetScale(scale)
+			frame:SetPoint('TOPLEFT', 0,(i) * (-h -gap) * scale)
+			frame.icon:SetTexture(t[i].i)
+			frame.key=t[i].key
+			frame.tooltip=t[i].t
+			local allowed=GMC.settings.allowedRewards[frame.key]
+			local chance=GMC.settings.rewardChance[frame.key]
+			-- Need to resave them asap in order to populate the array for future scans
+			GMC.settings.allowedRewards[frame.key]=allowed
+			GMC.settings.rewardChance[frame.key]=chance
+			frame.slider=factory:Slider(frame,0,100,chance or 100,chance or 100)
+			frame.slider:SetWidth(128)
+			frame.slider:SetPoint('BOTTOMLEFT',60,0)
+			frame.slider.Text:SetFontObject('NumberFont_Outline_Med')
+			frame.slider.Text:SetTextColor(C.Green())
+			frame.slider.isPercent=true
+			frame.slider:SetScript("OnValueChanged",function(this,value)
+				GMC.settings.rewardChance[this:GetParent().key]=this:OnValueChanged(value)
+				end
+			)
+			frame.chest = frame:CreateTexture(nil, 'BACKGROUND')
+			frame.chest:SetTexture('Interface\\Garrison\\GarrisonMissionUI2.blp')
+			frame.chest:SetAtlas(chestTexture)
+			frame.chest:SetSize((209-(209*0.25))*0.30, (155-(155*0.25)) * 0.30)
+			frame.chest:SetPoint('CENTER',frame.slider, 0, 25)
+			frame:SetScript('OnClick', function(this)
+				local allowed=  this.icon:IsDesaturated() -- ID it was desaturated, I want it allowed, now
+				GMC.settings.allowedRewards[this.key] = allowed
+				addon:GMCRewardRefresh()
+			end)
+			frame:SetScript('OnEnter', function(this)
+				GameTooltip:SetOwner(this, 'ANCHOR_BOTTOMRIGHT')
+				GameTooltip:AddLine(this.tooltip);
+				GameTooltip:Show()
+			end)
+
+			frame:SetScript('OnLeave', function() GameTooltip:Hide() end)
+			GMC.ignoreFrames[i] = frame
+			ref=frame
+	end
+	self:GMCRewardRefresh()
+	GMC.aif:SetSize(256, (scale*h+gap) * #t)
+	GMC.itf2:SetPoint('TOPLEFT',ref,'BOTTOMLEFT', 5, -15)
+	return GMC.aif
+end
+
+local addPriorityRule,prioRefresh,removePriorityRule,prioMenu,prioTitles,prioCheck,prioVoices
+do
+-- 1 = item, 2 = folitem, 3 = exp, 4 = money, 5 = resource
+	prioTitles={
+		itemLevel="Gear Items",
+		followerUpgrade="Upgrade Items",
+		xpBonus="Follower XP Bonus",
+		gold="Gold Reward",
+		resources="Resource Rewards"
+	}
+	prioVoices=0
+	for _ in pairs(prioTitles) do prioVoices=prioVoices+1 end
+	prioMenu={}
+
+	---@function [parent=#GMC] prioRefresh
+	function prioRefresh()
+		for i=1,prioVoices do
+			local group=GMC.prioFrames[i]
+			local code=GMC.settings.itemPrio[i]
+			if (not code) then
+				group.text:Hide()
+				group.xbutton:Hide()
+				group.nr:Hide()
+			else
+				group.text:Show()
+				group.text:SetText(prioTitles[code])
+				group.xbutton:Show()
+				group.nr:Show()
+			end
+		end
+		GMC.abutton:Hide()
+		for i=1,prioVoices do
+			local group=GMC.prioFrames[i]
+			if (not group.text:IsShown()) then
+				group.nr:Show()
+				GMC.abutton:SetPoint("TOPLEFT",group.text)
+				GMC.abutton:Show()
+				break
+			end
+		end
+	end
+	---@function [parent=#GMC] addPriorityRule
+	function addPriorityRule(this,key)
+		tinsert(GMC.settings.itemPrio,key)
+		prioRefresh()
+	end
+	---@function [parent=#GMC] removePriorityRule
+	function removePriorityRule(index)
+		tremove(GMC.settings.itemPrio,index)
+		prioRefresh()
+	end
+
+end
+_G.XPRIO=prioRefresh
+function addon:GMCBuildPriorities()
+	--Prio
+	GMC.pf = CreateFrame('FRAME', nil, GMC)
+	GMC.pf:SetSize(256, 240)
+
+	GMC.pft = GMC.pf:CreateFontString()
+	GMC.pft:SetFontObject('GameFontNormalHuge')
+	GMC.pft:SetText('Item Priority')
+	GMC.pft:SetPoint('TOP', 0, -10)
+	GMC.pft:SetTextColor(1, 1, 1)
+
+	GMC.pft2 = GMC.pf:CreateFontString()
+	GMC.pft2:SetFontObject('GameFontNormal')
+	GMC.pft2:SetText('Prioritize missions with certain a reward.')
+	GMC.pft2:SetPoint('BOTTOM', 0, 16)
+	GMC.pft2:SetTextColor(1, 1, 1)
+	GMC.pmf = CreateFrame("FRAME", "GMC_PRIO_MENU", GMC.pf, "UIDropDownMenuTemplate")
+
+
+	GMC.prioFrames = {}
+	GMC.prioFrames.selected = 0
+	for i = 1, prioVoices do
+		GMC.prioFrames[i] = {}
+		local this = GMC.prioFrames[i]
+		this.f = CreateFrame('FRAME', nil, GMC.pf)
+		this.f:SetSize(255, 32)
+		this.f:SetPoint('TOP', 0, -38-((i-1)*32))
+
+		this.nr = this.f:CreateFontString()
+		this.nr:SetFontObject('GameFontNormalHuge')
+		this.nr:SetText(i..'.')
+		this.nr:SetPoint('LEFT', 8, 0)
+		this.nr:SetTextColor(1, 1, 1)
+
+		this.text = this.f:CreateFontString()
+		this.text:SetFontObject('GameFontNormalLarge')
+		this.text:SetText('Def')
+		this.text:SetPoint('LEFT', 32, 0)
+		--this.text:SetTextColor(1, 1, 0)
+		this.text:SetJustifyH('LEFT')
+		this.text:Hide()
+
+		this.xbutton = CreateFrame('BUTTON', nil, this.f, 'GameMenuButtonTemplate')
+		this.xbutton:SetPoint('RIGHT', 0, 0)
+		this.xbutton:SetText('X')
+		this.xbutton:SetWidth(28)
+		this.xbutton:SetScript('OnClick', function() removePriorityRule(i)  end)
+		this.xbutton:Hide()
+	end
+
+	GMC.abutton = CreateFrame('BUTTON', nil, GMC.pmf, 'GameMenuButtonTemplate')
+	GMC.abutton:SetText(L['Add priority rule'])
+	GMC.abutton:SetWidth(128)
+	GMC.abutton:Hide()
+	GMC.abutton:SetScript('OnClick', function()
+		wipe(prioMenu)
+		tinsert(prioMenu,{text = L["Select an item to add as priority."], isTitle = true, isNotRadio=true,disabled=true, notCheckable=true,notClickable=true})
+		for k,v in pairs(prioTitles) do
+			tinsert(prioMenu,{text = v, func = addPriorityRule, notCheckable=true, isNotRadio=true, arg1 = k , disabled=inTable(GMC.settings.itemPrio,k)})
+		end
+		EasyMenu(prioMenu, GMC.pmf, "cursor", 0 , 0, "MENU")
+		end
+	)
+	prioRefresh()
+	return GMC.pf
+end
+function addon:GMCBuildMissionList()
+		-- Mission list on follower panels
+--		local ml=CreateFrame("Frame",nil,GMC)
+--		addBackdrop(ml)
+--		ml:Show()
+--		ml.Missions={}
+--		ml.Parties={}
+--		GMC.ml=ml
+--		local fs=ml:CreateFontString(nil, "BACKGROUND", "GameFontNormalHugeBlack")
+--		fs:SetPoint("TOPLEFT",0,-5)
+--		fs:SetPoint("TOPRIGHT",0,-5)
+--		fs:SetText(READY)
+--		fs:SetTextColor(C.Green())
+--		fs:SetHeight(30)
+--		fs:SetJustifyV("CENTER")
+--		fs:Show()
+--		GMC.progressText=fs
+--		GMC.ml.Header=fs
+--		return GMC.ml
+	local ml={widget=AceGUI:Create("GMCLayer"),Parties={}}
+	ml.widget:SetTitle(READY)
+	ml.widget:SetTitleColor(C.Green())
+	ml.widget:SetTitleHeight(40)
+	ml.widget:SetParent(GMC)
+	ml.widget:Show()
+	GMC.ml=ml
+	return ml.widget
+
+end
diff --git a/PartyCache.lua b/PartyCache.lua
new file mode 100644
index 0000000..c47b693
--- /dev/null
+++ b/PartyCache.lua
@@ -0,0 +1,117 @@
+local me,ns=...
+local addon=ns.addon --#addon
+local holdEvents,releaseEvents=addon.holdEvents,addon.releaseEvents
+--upvalue
+local G=C_Garrison
+local setmetatable=setmetatable
+local rawset=rawset
+local tContains=tContains
+local wipe=wipe
+local tremove=tremove
+local tinsert=tinsert
+local pcall=pcall
+
+--
+-- Temporary party management
+local parties=setmetatable({},{
+	__index=function(t,k)  rawset(t,k,{members={},perc=0,full=false}) return t[k] end
+})
+function ns.inParty(missionID,followerID)
+	return tContains(ns.parties[missionID].members,followerID)
+end
+--- Follower Missions Info
+--
+local followerMissions=setmetatable({},{
+	__index=function(t,k)  rawset(t,k,{}) return t[k] end
+})
+ns.party={}
+local party=ns.party --#party
+local ID,maxFollowers,members,ignored=0,1,{},{}
+function party:Open(missionID,followers)
+	maxFollowers=followers
+	ID=missionID
+	holdEvents()
+end
+function party:Ignore(followerID)
+	ignored[followerID]=true
+end
+function party:IsIgnored(followerID)
+	return ignored[followerID]
+end
+
+function party:IsIn(followerID)
+	return tContains(members,followerID)
+end
+function party:FreeSlots()
+	return maxFollowers-#members
+end
+function party:IsEmpty()
+	return maxFollowers>0 and #members==0
+end
+
+function party:Dump()
+	for i=1,3 do
+		if (members[i]) then
+			ns.xprint(i,addon:GetFollowerData(members[i],'fullname'))
+		end
+	end
+end
+
+function party:AddFollower(followerID)
+	if (followerID:sub(1,2) ~= '0x') then ns.xtrace(followerID .. "is not an id") end
+	if (self:FreeSlots()>0) then
+		local rc,code=pcall (G.AddFollowerToMission,ID,followerID)
+		if (rc and code) then
+			tinsert(members,followerID)
+			return true
+--@debug@
+		else
+			ns.xprint("Unable to add", G.GetFollowerName(followerID),"to",ID,code)
+--@end-debug@
+		end
+	end
+end
+function party:RemoveFollower(followerID)
+	for i=1,maxFollowers do
+		if (followerID==members[i]) then
+			tremove(members,i)
+			local rc,code=pcall(G.RemoveFollowerFromMission,ID,followerID)
+--@debug@
+			if (not rc) then trace("Unable to remove", G.GetFollowerName(members[i]),"from",ID,code) end
+--@end-debug@
+		return true end
+	end
+end
+
+function party:StoreFollowers(table)
+	wipe(table)
+	for i=1,#members do
+		tinsert(table,members[i])
+	end
+	return #table
+end
+function party:Close()
+	local perc=select(4,G.GetPartyMissionInfo(ID))
+	for i=1,3 do
+		if (members[i]) then
+			local rc,code=pcall(G.RemoveFollowerFromMission,ID,members[i])
+--@debug@
+			if (not rc) then ns.xtrace("Unable to pop", G.GetFollowerName(members[i])," from ",ID,code) end
+--@end-debug@
+
+		else
+			break
+		end
+	end
+	releaseEvents()
+	wipe(members)
+	wipe(ignored)
+	return perc or 0
+end
+function addon:GetParty(missionID)
+	if (missionID) then
+		return parties[missionID]
+	else
+		return parties
+	end
+end
\ No newline at end of file