Quantcast

Improvement to farming discovery

Alar of Daggerspine [03-21-15 - 19:09]
Improvement to farming discovery
Provision to keep trying when blizzard doesnt answer to mission
completion requests
light refactoring of widgets

Signed-off-by: Alar of Daggerspine <alar@aspide.it>
Filename
BuildingPage.lua
CHANGELOG.txt
GarrisonCommander-Broker/GarrisonCommander-Broker.toc
GarrisonCommander-Broker/embeds.xml
GarrisonCommander-Broker/ldb.lua
GarrisonCommander.lua
GarrisonCommander.toc
Init.lua
MissionCompletion.lua
Widgets.lua
diff --git a/BuildingPage.lua b/BuildingPage.lua
index 06df884..3b6049b 100644
--- a/BuildingPage.lua
+++ b/BuildingPage.lua
@@ -60,7 +60,6 @@ function module:AddFollowerToPlot(plot)
 	if (addon:GetToggle("HF")) then
 		return frame:Hide()
 	end
-	ns.xprint(plot.followerTooltip,plot.plotID)
 	if plot.followerTooltip then
 		local followerName, level, quality, displayID, followerID, garrFollowerID, status, portraitIconID = G.GetFollowerInfoForBuilding(plot.plotID)
 		if followerName then
diff --git a/CHANGELOG.txt b/CHANGELOG.txt
index 943b007..2f4c2f9 100644
--- a/CHANGELOG.txt
+++ b/CHANGELOG.txt
@@ -3,4 +3,5 @@
 Fix: Using a full upgrade token on follower was warning about lost points
 Fix: Possible soloution for a couple of very rare lua errors
 Feature: Clicking on data broker opens missions report (same as clicking on minimap icon)
-Feature: In follower rectuiting page, information about which follower have traits and counter are show in dropdown
\ No newline at end of file
+Feature: In follower rectuiting page, information about which follower have traits and counter are show in dropdown
+Feature: Broker now reports status of producing lot.
\ No newline at end of file
diff --git a/GarrisonCommander-Broker/GarrisonCommander-Broker.toc b/GarrisonCommander-Broker/GarrisonCommander-Broker.toc
index f49f988..6f17d36 100644
--- a/GarrisonCommander-Broker/GarrisonCommander-Broker.toc
+++ b/GarrisonCommander-Broker/GarrisonCommander-Broker.toc
@@ -4,7 +4,7 @@
 ## Notes-itIT: Data-Broker per GarrisonCommander
 ## Notes-frFR: Data-Broker pour GarrisonCommander
 ## Author: Alar of Daggerspine
-## Version: @project-version@ @project-abbreviated-hash@
+## Version: @project-version@
 ## X-Version: 2.3.5
 ## X-Revision: @project-abbreviated-hash@
 ## eMail: alar@aspide.it
diff --git a/GarrisonCommander-Broker/embeds.xml b/GarrisonCommander-Broker/embeds.xml
index d99d5dc..635e36d 100644
--- a/GarrisonCommander-Broker/embeds.xml
+++ b/GarrisonCommander-Broker/embeds.xml
@@ -7,4 +7,5 @@
 	<Include file="..\GarrisonCommander\libs\Ace3\AceTimer-3.0\AceTimer-3.0.xml"/>
 	<Include file="..\GarrisonCommander\libs\Ace3\AceLocale-3.0\AceLocale-3.0.xml"/>
 	<Include file="..\GarrisonCommander\libs\Ace3\AceDB-3.0\AceDB-3.0.xml"/>
+	<Include file="..\GarrisonCommander\libs\LibInit\LibInit.xml"/>
 </Ui>
diff --git a/GarrisonCommander-Broker/ldb.lua b/GarrisonCommander-Broker/ldb.lua
index cae7bfd..7a48f62 100644
--- a/GarrisonCommander-Broker/ldb.lua
+++ b/GarrisonCommander-Broker/ldb.lua
@@ -7,8 +7,11 @@ if (not LibStub:GetLibrary("LibDataBroker-1.1",true)) then
 end
 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 addon=LibStub("AceAddon-3.0"):NewAddon(me,"AceTimer-3.0","AceEvent-3.0","AceConsole-3.0") --#addon
+local addon=LibStub("LibInit"):NewAddon(me,"AceTimer-3.0","AceEvent-3.0","AceConsole-3.0") --#addon
+local C=addon:GetColorTable()
+local dataobj --#Missions
+local farmobj --#Farms
 local SecondsToTime=SecondsToTime
 local type=type
 local strsplit=strsplit
@@ -16,12 +19,41 @@ local tonumber=tonumber
 local tremove=tremove
 local time=time
 local tinsert=tinsert
+local tContains=tContains
 local G=C_Garrison
-local NONE=NONE
-local DONE=DONE
 local format=format
 local table=table
 local math=math
+local GetQuestResetTime=GetQuestResetTime
+local CalendarGetDate=CalendarGetDate
+local CalendarGetAbsMonth=CalendarGetAbsMonth
+local GameTooltip=GameTooltip
+local pairs=pairs
+local select=select
+local READY=READY
+local NEXT=NEXT
+local NONE=C(NONE,"Red")
+local DONE=C(DONE,"Green")
+local NEED=C(NEED,"Red")
+local dbversion=2
+
+local spellids={
+	[158754]='herb',
+	[158745]='mine',
+	[170599]='mine',
+	[170691]='herb',
+}
+local buildids={
+	mine={61,62,63},
+	herb={29,136,137}
+}
+local names={
+	mine="Lunar Fall",
+	herb="Herb Garden"
+}
+local today=0
+local yesterday=0
+local lastreset=0
 function addon:ldbCleanup()
 	local now=time()
 	for i=1,#self.db.realm.missions do
@@ -37,26 +69,9 @@ function addon:ldbCleanup()
 	end
 end
 function addon:ldbUpdate()
-	local now=time()
-	local completed=0
-	local ready=NONE
-	local prox=NONE
-	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
-			prox=format("|cff20ff20%s|r in %s",pc,SecondsToTime(duration),completed)
-			break;
-		else
-			if ready==NONE then
-				ready=format("|cff20ff20%s|r",pc)
-			end
-		end
-		completed=completed+1
-	end
-	dataobj.text=format("%s: %s (Tot: |cff00ff00%d|r) %s: %s",READY,ready,completed,NEXT,prox)
+	self:CheckDailyReset()
+	dataobj:Update()
+	farmobj:Update()
 end
 function addon:GARRISON_MISSION_STARTED(event,missionID)
 	local duration=select(2,G.GetPartyMissionInfo(missionID)) or 0
@@ -65,20 +80,168 @@ function addon:GARRISON_MISSION_STARTED(event,missionID)
 	table.sort(self.db.realm.missions)
 	self:ldbUpdate()
 end
-function addon:OnInitialize()
+function addon:CheckEvents()
+	if (G.IsOnGarrisonMap()) then
+		self:RegisterEvent("UNIT_SPELLCAST_START")
+		--self:RegisterEvent("ITEM_PUSH")
+	else
+		self:UnregisterEvent("UNIT_SPELLCAST_START")
+		--self:UnregisterEvent("ITEM_PUSH")
+	end
+end
+function addon:ZONE_CHANGED_NEW_AREA()
+	self:ScheduleTimer("CheckEvents",1)
+	self:ScheduleTimer("DiscoverFarms",1)
+
+end
+function addon:UNIT_SPELLCAST_START(event,unit,name,rank,lineID,spellID)
+	if (unit=='player') then
+		if spellids[spellID] then
+			name=names[spellids[spellID]]
+			if not self.db.realm.farms[ns.me][name] then
+				self.db.realm.farms[ns.me][name]=true
+				farmobj:Update()
+			end
+		end
+	end
+end
+function addon:ITEM_PUSH(event,bag,icon)
+	--@debug@
+	print(event,bag,icon)
+	--@end-debug@
+end
+function addon:CalculateModifiedDate()
+	local weekday, month, day, year = CalendarGetDate()
+	today=format("%04d%02d%02d",year,month,day)
+	if month==1 and day==1 then
+		local m, y, numdays, firstday = CalendarGetAbsMonth( 12, year-1 )
+		yesterday=format("%04d%02d%02d",y,m,numdays)
+	elseif day==1 then
+		local m, y, numdays, firstday = CalendarGetAbsMonth( month-1, year)
+		yesterday=format("%04d%02d%02d",y,m,numdays)
+	else
+		yesterday=format("%04d%02d%02d",year,month,day-1)
+	end
+	if (GetQuestResetTime()<3600*3) then
+		today=yesterday
+	end
+end
+function addon:CheckDailyReset()
+	if lastreset < GetQuestResetTime() then
+		lastreset =GetQuestResetTime()
+		self:CleanFarms()
+	end
+
+end
+function addon:CleanFarms()
+	for p,j in pairs(self.db.realm.farms) do
+		for s,_ in pairs(j) do
+			j[s]=false
+		end
+	end
+end
+function addon:CountMissing()
+	local tot=0
+	local missing=0
+	for p,j in pairs(self.db.realm.farms) do
+		for s,_ in pairs(j) do
+			tot=tot+1
+			if not j[s] then missing=missing+1 end
+		end
+	end
+	return missing,tot
+end
+function addon:DiscoverFarms()
+	local shipmentIndex = 1;
+	local buildings = C_Garrison.GetBuildings();
+	for i = 1, #buildings do
+		local buildingID = buildings[i].buildingID;
+		if ( buildingID) then
+			local name, texture, shipmentCapacity, shipmentsReady, shipmentsTotal, creationTime, duration, timeleftString, itemName, itemIcon, itemQuality, itemID = C_Garrison.GetLandingPageShipmentInfo(buildingID);
+			if (tContains(buildids.mine,buildingID)) then
+				names.mine=name
+				if not self.db.realm.farms[ns.me][name] then
+					self.db.realm.farms[ns.me][name]=false
+				end
+			end
+			if (tContains(buildids.herb,buildingID)) then
+				names.herb=name
+				if not self.db.realm.farms[ns.me][name] then
+					self.db.realm.farms[ns.me][name]=false
+				end
+			end
+
+		end
+	end
+end
+function addon:SetDbDefaults(default)
+	default.realm={
+		missions={},
+		farms={["**"]={}},
+		lastday="0",
+		dbversion=1
+	}
+end
+function addon:OnInitialized()
+	lastreset=GetQuestResetTime()
 	ns.me=GetUnitName("player",false)
 	self:RegisterEvent("GARRISON_MISSION_STARTED")
 	self:RegisterEvent("GARRISON_MISSION_NPC_OPENED","ldbCleanup")
+	self:RegisterEvent("ZONE_CHANGED_NEW_AREA")
+	if dbversion>self.db.realm.dbversion then
+		self.db:ResetDB()
+		self.db.realm.dbversion=dbversion
+	end
+	self:CalculateModifiedDate()
+	if self.db.realm.lastday<today then addon:CleanFarms() end
+	self.db.realm.lastday=today
 	self:ScheduleRepeatingTimer("ldbUpdate",1)
-	self.db=LibStub("AceDB-3.0"):New("dbGACB",{realm={missions={}}})
+	self:ScheduleTimer("ZONE_CHANGED_NEW_AREA",1)
 end
-dataobj=LibStub:GetLibrary("LibDataBroker-1.1"):NewDataObject(me, {
+dataobj=LibStub:GetLibrary("LibDataBroker-1.1"):NewDataObject("GC-Missions", {
 	type = "data source",
-	label = "Missions ",
+	label = "GC Missions ",
 	text=NONE,
 	category = "Interface",
 	icon = "Interface\\ICONS\\ACHIEVEMENT_GUILDPERK_WORKINGOVERTIME"
 })
+farmobj=LibStub:GetLibrary("LibDataBroker-1.1"):NewDataObject("GC-Farms", {
+	type = "data source",
+	label = "GC Farms ",
+	text=L["Harvesting status"],
+	category = "Interface",
+	icon = "Interface\\Icons\\Trade_Engineering"
+})
+function farmobj:Update()
+	local n,t=addon:CountMissing()
+	if (t>0) then
+		local c1=C.Green.c
+		local c2=C.Green.c
+		if n>t/2 then
+			c1=C.Red.c
+		elseif n>0 then
+			c1=C.Orange.c
+		end
+		farmobj.text=format("%s |cff%s%d|r/|cff%s%d|r",L["Harvest"],c1,t-n,c2,t)
+	else
+		farmobj.text=NONE
+	end
+end
+function farmobj:OnTooltipShow()
+	self:AddDoubleLine(L["Time to next reset"],SecondsToTime(GetQuestResetTime()))
+	for k,v in pairs(addon.db.realm.farms) do
+		if (k==ns.me) then
+			self:AddLine(k,C.Green())
+		else
+			self:AddLine(k,C.Orange())
+		end
+		for s,d in pairs(v) do
+			self:AddDoubleLine(s,d and DONE or NEED)
+		end
+	end
+	self:AddLine(me,C.Silver())
+end
+
 function dataobj:OnTooltipShow()
 	self:AddLine(L["Mission awaiting"])
 	local db=addon.db.realm.missions
@@ -87,17 +250,18 @@ function dataobj:OnTooltipShow()
 		if db[i] then
 			local t,missionID,pc=strsplit('.',db[i])
 			t=tonumber(t) or 0
-			local name=C_Garrison.GetMissionName(missionID)
+			local name=G.GetMissionName(missionID)
 			if (name) then
+				local msg=format("|cff%s%s|r: %s",pc==ns.me and C.Green.c or C.Orange.c,pc,name)
 				if t > now then
-					self:AddDoubleLine(format("|cffff9900%s|r: %s",pc,name),SecondsToTime(t-now),nil,nil,nil,0,1,0)
+					self:AddDoubleLine(msg,SecondsToTime(t-now),nil,nil,nil,C.Red())
 				else
-					self:AddDoubleLine(format("|cffff9900%s|r: %s",pc,name),DONE,nil,nil,nil,1,0,0)
+					self:AddDoubleLine(msg,DONE)
 				end
 			end
 		end
 	end
-	self:AddLine(me,0,1,0)
+	self:AddLine(me,C.Silver())
 end

 function dataobj:OnEnter()
@@ -112,12 +276,46 @@ end
 function dataobj:OnLeave()
 	GameTooltip:Hide()
 end
+function farmobj:OnEnter()
+	GameTooltip:SetOwner(self, "ANCHOR_NONE")
+	GameTooltip:SetPoint("TOPLEFT", self, "BOTTOMLEFT")
+	GameTooltip:ClearLines()
+	farmobj.OnTooltipShow(GameTooltip)
+	GameTooltip:Show()
+end
+farmobj.OnLeave=dataobj.OnLeave
+
 function dataobj:OnClick(button)
 	if (button=="LeftButton") then
 		GarrisonLandingPage_Toggle()
 	end
 end
+function dataobj:Update()
+	local now=time()
+	local completed=0
+	local ready=NONE
+	local prox=NONE
+	for i=1,#addon.db.realm.missions do
+		local t,missionID,pc=strsplit('.',addon.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
+			prox=format("|cff20ff20%s|r in %s",pc,SecondsToTime(duration),completed)
+			break;
+		else
+			if ready==NONE then
+				ready=format("|cff20ff20%s|r",pc)
+			end
+		end
+		completed=completed+1
+	end
+	self.text=format("%s: %s (Tot: |cff00ff00%d|r) %s: %s",READY,ready,completed,NEXT,prox)
+end

 --@debug@
-_G.GACDB=addon
+function addon:Dump()
+	DevTools_Dump(self.db.realm)
+end
+_G.GACB=addon
 --@end-debug@
diff --git a/GarrisonCommander.lua b/GarrisonCommander.lua
index c0cffd0..b3be823 100644
--- a/GarrisonCommander.lua
+++ b/GarrisonCommander.lua
@@ -9,11 +9,6 @@ local xprint=ns.xprint
 local _G=_G
 local pp=print
 local HD=false
-local print=ns.print
-local pairs=pairs
-local select=select
-local next=next
-local tinsert=tinsert
 local tremove=tremove
 local setmetatable=setmetatable
 local getmetatable=getmetatable
@@ -34,7 +29,7 @@ local pin=false
 local baseHeight
 local minHeight
 --@debug@
-if LibDebug() then LibDebug() end
+if LibDebug then LibDebug() end
 --@end-debug@
 ns.bigscreen=true
 -- Blizzard functions override support
@@ -233,20 +228,7 @@ end
 local counters=setmetatable({},t2)
 local counterThreatIndex=setmetatable({},t2)
 local counterFollowerIndex=setmetatable({},t2)
---- Quick backdrop
---
-local backdrop = {
-	bgFile="Interface\\TutorialFrame\\TutorialFrameBackground",
-	edgeFile="Interface\\Tooltips\\UI-Tooltip-Border",
-	tile=true,
-	tileSize=16,
-	edgeSize=16,
-	insets={bottom=7,left=7,right=7,top=7}
-}
-local function addBackdrop(f,color)
-	f:SetBackdrop(backdrop)
-	f:SetBackdropBorderColor(C[color or 'Yellow']())
-end
+

 --generic table scan

@@ -1056,278 +1038,6 @@ function addon:Toggle(button)
 		button:SetChecked(f:IsShown())
 	end
 end
--- Unused, experimenting with acegui
-function addon:GenerateMissionsWidgets()
-	self:GenerateMissionContainer()
-	self:GenerateMissionButton()
-end
-do
-local generated
-function addon:GenerateContainer()
-	if generated then return end
-	generated=true
-	do
-		local Type="GCGUIContainer"
-		local Version=1
-		local m={}
-		function m:Close()
-			self.frame.CloseButton:Click()
-		end
-		function m:OnAcquire()
-			self.frame:EnableMouse(true)
-			self:SetTitleColor(C.Yellow())
-			self.frame:SetFrameStrata("HIGH")
-			self.frame:SetFrameLevel(999)
-		end
-		function m:SetContentWidth(x)
-			self.content:SetWidth(x)
-		end
-		local function Constructor()
-			local frame=CreateFrame("Frame",nil,nil,"GarrisonUITemplate")
-			for _,f in pairs({frame:GetRegions()}) do
-				if (f:GetObjectType()=="Texture" and f:GetAtlas()=="Garr_WoodFrameCorner") then f:Hide() end
-			end
-			local widget={frame=frame,missions={}}
-			widget.type=Type
-			widget.SetTitle=function(self,...) self.frame.TitleText:SetText(...) end
-			widget.SetTitleColor=function(self,...) self.frame.TitleText:SetTextColor(...) end
-			for k,v in pairs(m) do widget[k]=v end
-			frame:SetScript("OnHide",function(self) self.obj:Fire('OnClose') end)
-			frame.obj=widget
-			--Container Support
-			local content = CreateFrame("Frame",nil,frame)
-			widget.content = content
-			--addBackdrop(content,'Green')
-			content.obj = widget
-			content:SetPoint("TOPLEFT",25,-25)
-			content:SetPoint("BOTTOMRIGHT",-25,25)
-			AceGUI:RegisterAsContainer(widget)
-			return widget
-		end
-		AceGUI:RegisterWidgetType(Type,Constructor,Version)
-	end
-end
-end
-function addon:GenerateMissionContainer()
-	do
-		local Type="GMCLayer"
-		local Version=1
-		local function OnRelease(self)
-			wipe(self.childs)
-		end
-		local m={}
-		function m:OnAcquire()
-			self.frame:SetParent(UIParent)
-			self.frame:SetFrameStrata("HIGH")
-			self.frame:SetHeight(50)
-			self.frame:SetWidth(100)
-			self.frame:Show()
-			self.frame:SetPoint("LEFT")
-		end
-		function m:Show()
-			return self.frame:Show()
-		end
-		function m:Hide()
-			self.frame:Hide()
-			self:Release()
-		end
-		function m:SetScript(...)
-			return self.frame:SetScript(...)
-		end
-		function m:SetParent(...)
-			return self.frame:SetParent(...)
-		end
-		function m:PushChild(child,index)
-			self.childs[index]=child
-			self.scroll:AddChild(child)
-		end
-		function m:RemoveChild(index)
-			local child=self.childs[index]
-			if (child) then
-				self.childs[index]=nil
-				child:Hide()
-				self:DoLayout()
-			end
-		end
-		function m:ClearChildren()
-			wipe(self.childs)
-			self:AddScroll()
-		end
-		function m:AddScroll()
-			if (self.scroll) then
-				self:ReleaseChildren()
-				self.scroll=nil
-			end
-			self.scroll=AceGUI:Create("ScrollFrame")
-			local scroll=self.scroll
-			self:AddChild(scroll)
-			scroll:SetLayout("List") -- probably?
-			scroll:SetFullWidth(true)
-			scroll:SetFullHeight(true)
-			scroll:SetPoint("TOPLEFT",self.title,"BOTTOMLEFT",0,0)
-			scroll:SetPoint("TOPRIGHT",self.title,"BOTTOMRIGHT",0,0)
-			scroll:SetPoint("BOTTOM",self.content,"BOTTOM",0,0)
-		end
-		local function Constructor()
-			local frame=CreateFrame("Frame")
-			local title=frame:CreateFontString(nil, "BACKGROUND", "GameFontNormalHugeBlack")
-			title:SetJustifyH("CENTER")
-			title:SetJustifyV("CENTER")
-			title:SetPoint("TOPLEFT")
-			title:SetPoint("TOPRIGHT")
-			title:SetHeight(0)
-			title:SetWidth(0)
-			addBackdrop(frame)
-			local widget={childs={}}
-			widget.title=title
-			widget.type=Type
-			widget.SetTitle=function(self,...) self.title:SetText(...) end
-			widget.SetTitleColor=function(self,...) self.title:SetTextColor(...) end
-			widget.SetFormattedTitle=function(self,...) self.title:SetFormattedText(...) end
-			widget.SetTitleWidth=function(self,...) self.title:SetWidth(...) end
-			widget.SetTitleHeight=function(self,...) self.title:SetHeight(...) end
-			widget.frame=frame
-			frame.obj=widget
-			for k,v in pairs(m) do widget[k]=v end
-			frame:SetScript("OnHide",function(self) self.obj:Fire('OnClose') end)
-			--Container Support
-			local content = CreateFrame("Frame",nil,frame)
-			widget.content = content
-			content.obj = self
-			content:SetPoint("TOPLEFT",title,"BOTTOMLEFT")
-			content:SetPoint("BOTTOMRIGHT")
-			AceGUI:RegisterAsContainer(widget)
-			return widget
-		end
-		AceGUI:RegisterWidgetType(Type,Constructor,Version)
-	end
-end
-
-function addon:GenerateMissionButton()
-	do
-		local Type1="GMCMissionButton"
-		local Type2="GMCSlimMissionButton"
-		local Version=1
-		local unique=0
-		local m={}
-		function m:OnAcquire()
-			local frame=self.frame
-			frame.info=nil
-			frame:SetHeight(self.type==Type1 and 80 or 80)
-			frame:SetAlpha(1)
-			frame:Enable()
-			for i=1,#self.scripts do
-				frame:SetScript(self.scripts[i],nil)
-			end
-			for i=1,#frame.Rewards do
-				frame.Rewards[i].Icon:SetDesaturated(false)
-			end
-			wipe(self.scripts)
-			return self.frame:SetScale(1.0)
-		end
-		function m:Show()
-			return self.frame:Show()
-		end
-		function m:SetHeight(h)
-			return self.frame:SetHeight(h)
-		end
-		function m:Hide()
-			self.frame:SetHeight(1)
-			self.frame:SetAlpha(0)
-			return self.frame:Disable()
-		end
-		function m:SetScript(name,method)
-			tinsert(self.scripts,name)
-			return self.frame:SetScript(name,method)
-		end
-		function m:SetScale(s)
-			return self.frame:SetScale(s)
-		end
-		function m:SetMission(mission,party)
-			self.frame.info=mission
-			self.frame.fromFollowerPage=true
-			self.frame:EnableMouse(true)
-			self.frame.party=party
-			if self.type==Type1 then
-				addon:DrawSingleButton(false,self.frame,false,false)
-				self.frame:SetScript("OnEnter",GarrisonMissionButton_OnEnter)
-				self.frame:SetScript("OnLeave",ns.OnLeave)
-			else
-				addon:DrawSingleSlimButton(false,self.frame,false,false)
-				self.frame:SetScript("OnEnter",nil)
-				self.frame:SetScript("OnLeave",nil)
-			end
-			if self.type==Type2 then
-				self.frame.Percent:SetFormattedText("%d%%",party.perc)
-				self.frame.Percent:SetTextColor(addon:GetDifficultyColors(party.perc))
-				_G.AX=self.frame
-			end
-		end
-
-		local function Constructor()
-			unique=unique+1
-			local frame=CreateFrame("Button",nil,nil,"GarrisonMissionListButtonTemplate") --"GarrisonCommanderMissionListButtonTemplate")
-			frame.Title:SetFontObject("QuestFont_Shadow_Small")
-			frame.Summary:SetFontObject("QuestFont_Shadow_Small")
-			frame:SetScript("OnEnter",nil)
-			frame:SetScript("OnLeave",nil)
-			frame:SetScript("OnClick",function(self,button) return self.obj:Fire("OnClick",self,button) end)
-			frame.LocBG:SetPoint("LEFT")
-			frame.MissionType:SetPoint("TOPLEFT",5,-2)
-			--[[
-			frame.members={}
-			for i=1,3 do
-				local f=CreateFrame("Button",nil,frame,"GarrisonCommanderMissionPageFollowerTemplateSmall" )
-				frame.members[i]=f
-				f:SetPoint("BOTTOMRIGHT",-65 -65 *i,5)
-				f:SetScale(0.8)
-			end
-			--]]
-			local widget={}
-			setmetatable(widget,{__index=frame})
-			widget.frame=frame
-			widget.scripts={}
-			frame.obj=widget
-			for k,v in pairs(m) do widget[k]=v end
-			return widget
-		end
-		local function Constructor1()
-			local widget=Constructor()
-			widget.type=Type1
-			return AceGUI:RegisterAsWidget(widget)
-		end
-		local function Constructor2()
-			local widget=Constructor()
-			local frame=widget.frame
-			widget.type=Type2
-			local indicators=CreateFrame("Frame",nil,frame,"GarrisonCommanderIndicators")
-			indicators.Percent:SetJustifyH("LEFT")
-			indicators.Percent:SetJustifyV("CENTER")
-			indicators:SetPoint("LEFT",70,0)
-			indicators.Age:Hide()
-			frame.Indicators=indicators
-			frame.Percent=indicators.Percent
-			frame.Failure=frame:CreateFontString()
-			frame.Success=frame:CreateFontString()
-			frame.Failure:SetFontObject("GameFontRedLarge")
-			frame.Success:SetFontObject("GameFontGreenLarge")
-			frame.Failure:SetText(FAILED)
-			frame.Success:SetText(SUCCESS)
-			frame.Failure:Hide()
-			frame.Success:Hide()
-			frame.Title:SetPoint("TOPLEFT",frame.Indicators,"TOPRIGHT",0,0)
-			frame.Success:SetPoint("BOTTOMLEFT",frame.Indicators,"BOTTOMRIGHT",0,10)
-			frame.Failure:SetPoint("BOTTOMLEFT",frame.Indicators,"BOTTOMRIGHT",0,10)
-
-			--widget.frame.MissionType:Hide()
-			--widget.frame.IconBG:Hide()
-			return AceGUI:RegisterAsWidget(widget)
-		end
-		AceGUI:RegisterWidgetType(Type1,Constructor1,Version)
-		AceGUI:RegisterWidgetType(Type2,Constructor2,Version)
-
-	end
-end

 function addon:CreateOptionsLayer(...)
 	local o=AceGUI:Create("SimpleGroup") -- a transparent frame
@@ -1804,7 +1514,6 @@ function addon:SetUp(...)
 	self:CheckMP()
 	self:CheckGMM()
 	self:Options()
-	self:GenerateMissionsWidgets()
 	GMC=self:GMCBuildPanel(ns.bigscreen)
 	local tabMC=CreateFrame("CheckButton",nil,GMF,"SpellBookSkillLineTabTemplate")
 	GMF.tabMC=tabMC
@@ -1844,6 +1553,9 @@ function addon:SetUp(...)
 	--collectgarbage("step",10)
 --/Interface/FriendsFrame/UI-Toast-FriendOnlineIcon
 end
+function addon:MissionComplete()
+	return self:GetModule("MissionCompletion"):MissionComplete()
+end
 function addon:AddMenu()
 	local menu,size=self:CreateOptionsLayer(MP and 'CKMP' or nil,'BIGSCREEN','MOVEPANEL','IGM','IGP','NOFILL','MSORT')
 	--self:AddOptionToOptionsLayer(GCF.Menu,'MSORT')
@@ -2020,6 +1732,19 @@ function addon:SafeHookScript(frame,hook,method,postHook)
 		end
 	end
 end
+local converter=CreateFrame("Frame"):CreateTexture()
+function addon:GetFollowerTexture(followerID)
+	local rc,iconID=pcall(self.GetFollowerData,self,followerID,"portraitIconID")
+	if rc then
+		if iconID then
+			converter:SetToFileData(iconID)
+			return converter:GetTexture()
+		end
+		return "Interface\\Garrison\\Portraits\\FollowerPortrait_NoPortrait"
+	else
+		return "Interface\\Garrison\\Portraits\\FollowerPortrait_NoPortrait"
+	end
+end

 function addon:CleanUp()
 	wipe(ns.CompletedMissions)
@@ -2032,7 +1757,7 @@ function addon:CleanUp()
 		GarrisonFollowerTooltip.fs:Hide()
 	end
 	GMFMissions.CompleteDialog:Hide()
-	self:CloseReport()
+	self:GetModule("MissionCompletion"):CloseReport()
 	--collectgarbage("collect")
 end
 function addon:EventGARRISON_FOLLOWER_XP_CHANGED(event,followerID,iLevel,xp,level,quality)
diff --git a/GarrisonCommander.toc b/GarrisonCommander.toc
index 798a2ff..3462cce 100644
--- a/GarrisonCommander.toc
+++ b/GarrisonCommander.toc
@@ -26,6 +26,8 @@ embeds.xml
 localization.lua
 wowhead.lua
 Init.lua
+Widgets.lua
+GarrisonCommander.xml
 MissionCache.lua
 FollowerCache.lua
 PartyCache.lua
@@ -34,6 +36,5 @@ MissionCompletion.lua
 FollowerPage.lua
 MatchMaker.lua
 FollowerRecruiting.lua
-GarrisonCommander.xml
 BuildingPage.lua
 RelNotes.lua
diff --git a/Init.lua b/Init.lua
index 2c3184b..09e7015 100644
--- a/Init.lua
+++ b/Init.lua
@@ -27,6 +27,7 @@ ns.AceGUI=LibStub("AceGUI-3.0")
 ns.D=LibStub("LibDeformat-3.0")
 ns.C=ns.addon:GetColorTable()
 ns.L=ns.addon:GetLocale()
+ns.G=C_Garrison
 ns.print=ns.addon:Wrap("Print")
 ns.dprint=ns.print
 ns.trace=ns.addon:Wrap("Trace")
diff --git a/MissionCompletion.lua b/MissionCompletion.lua
index b5246a0..cee6bf6 100644
--- a/MissionCompletion.lua
+++ b/MissionCompletion.lua
@@ -21,400 +21,245 @@ local strsplit=strsplit
 local generated
 local salvages={
 114120,114119,114116}
-local converter=CreateFrame("Frame"):CreateTexture()
-function addon:GetFollowerTexture(followerID)
-	local rc,iconID=pcall(addon.GetFollowerData,addon,followerID,"portraitIconID")
-	if rc then
-		if iconID then
-			converter:SetToFileData(iconID)
-			return converter:GetTexture()
-		end
-		return "Interface\\Garrison\\Portraits\\FollowerPortrait_NoPortrait"
-	else
-		return "Interface\\Garrison\\Portraits\\FollowerPortrait_NoPortrait"
-	end
-end
-function addon:GenerateMissionCompleteList(title)
-	if not generated then
-		generated=true
-		self:GenerateContainer()
-		do
-			local Type="GCMCList"
-			local Version=1
-			local m={} --#Widget
-			function m:ScrollDown()
-				local obj=self.scroll
-				if (#self.missions >1 and obj.scrollbar and obj.scrollbar:IsShown()) then
-					obj:SetScroll(80)
-					obj.scrollbar.ScrollDownButton:Click()
-				end
-			end
-			function m:OnAcquire()
-				wipe(self.missions)
-			end
-			function m:Show()
-				self.frame:Show()
-			end
-			function m:Hide()
-				self.frame:Hide()
-				self:Release()
-			end
-			function m:AddButton(text,action)
-				local obj=self.scroll
-				local b=AceGUI:Create("Button")
-				b:SetFullWidth(true)
-				b:SetText(text)
-				b:SetCallback("OnClick",action)
-				obj:AddChild(b)
-			end
-			function m:AddMissionButton(mission)
-				local obj=self.scroll
-				local b=AceGUI:Create("GMCSlimMissionButton")
-				b:SetMission(mission,addon:GetParty(mission.missionID))
-				b:SetScale(0.7)
-				b:SetFullWidth(true)
-				self.missions[mission.missionID]=b
-				obj:AddChild(b)
-			end
-			function m:AddMissionResult(missionID,success)
-				local mission=self.missions[missionID]
-				if mission then
-					local frame=mission.frame
-					if success then
-						frame.Success:Show()
-						frame.Failure:Hide()
-						for i=1,#frame.Rewards do
-							frame.Rewards[i].Icon:SetDesaturated(false)
-						end
-					else
-						frame.Success:Hide()
-						frame.Failure:Show()
-						for i=1,#frame.Rewards do
-							frame.Rewards[i].Icon:SetDesaturated(true)
-							frame.Rewards[i].Quantity:Hide()
-						end
-					end
-				end
-			end
-			function m:AddRow(data,...)
-				local obj=self.scroll
-				local l=AceGUI:Create("InteractiveLabel")
-				l:SetFontObject(GameFontNormalSmall)
-				l:SetText(data)
-				l:SetColor(...)
-				l:SetFullWidth(true)
-				obj:AddChild(l)
-
-			end
-			function m:AddFollower(followerID,xp,levelup)
-				local follower=addon:GetFollowerData(followerID)
-				if follower.maxed and not levelup then
-					return self:AddIconText(addon:GetFollowerTexture(followerID),
-										format("%s is already at maximum xp",addon:GetFollowerData(followerID,'fullname')))
-				end
-				local quality=G.GetFollowerQuality(followerID) or follower.quality
-				local level=G.GetFollowerLevel(followerID) or follower.level
-				if levelup then
-					PlaySound("UI_Garrison_CommandTable_Follower_LevelUp");
-				end
-				return self:AddIconText(addon:GetFollowerTexture(followerID),
-				format("%s gained %d xp%s%s",addon:GetFollowerData(followerID,'fullname',true),xp,
-				levelup and " |cffffed1a*** Level Up ***|r ." or ".",
-				format(" %d to go.",addon:GetFollowerData(followerID,'levelXP')-addon:GetFollowerData(followerID,'xp'))))
-			end
-			function m:AddIconText(icon,text,qt)
-				local obj=self.scroll
-				local l=AceGUI:Create("Label")
-				l:SetFontObject(GameFontNormalSmall)
-				if (qt) then
-					l:SetText(format("%s x %s",text,qt))
-				else
-					l:SetText(text)
-				end
-				l:SetImage(icon)
-				l:SetImageSize(24,24)
-				l:SetFullWidth(true)
-				obj:AddChild(l)
-				if (obj.scrollbar and obj.scrollbar:IsShown()) then
-					obj:SetScroll(80)
-					obj.scrollbar.ScrollDownButton:Click()
-				end
-				return l
-			end
-			function m:AddItem(itemID,qt)
-				local obj=self.scroll
-				local _,itemlink,itemquality,_,_,_,_,_,_,itemtexture=GetItemInfo(itemID)
-				if not itemlink then
-					self:AddIconText(itemtexture,itemID,qt)
-				else
-					self:AddIconText(itemtexture,itemlink,qt)
-				end
-			end
-			local function Constructor()
-				local widget=AceGUI:Create("GCGUIContainer")
-				widget:SetLayout("Fill")
-				widget.missions={}
-				local scroll = AceGUI:Create("ScrollFrame")
-				scroll:SetLayout("List") -- probably?
-				scroll:SetFullWidth(true)
-				scroll:SetFullHeight(true)
-				widget:AddChild(scroll)
-				for k,v in pairs(m) do widget[k]=v end
-				widget:Show()
-				widget.scroll=scroll
-				return widget
-			end
-			AceGUI:RegisterWidgetType(Type,Constructor,Version)
-		end
-	end
+local module=addon:NewSubClass('MissionCompletion') --#Module
+function module:GenerateMissionCompleteList(title)
 	local w=AceGUI:Create("GCMCList")
 	w:SetTitle(title)
 	w:SetCallback("OnClose", function(self) self:Release() ns.missionautocompleting=nil end)
 	return w
 end
-
---TODO: Portare la stampa dei risultati in fondo, e togliere il delay fra le missioni
-do
-	local missions={}
-	local states={}
-	local currentMission
-	local rewards={
-		items={},
-		followerBase={},
-		followerXP=setmetatable({},{__index=function() return 0 end}),
-		currencies=setmetatable({},{__index=function(t,k) rawset(t,k,{icon="",qt=0}) return t[k] end}),
-	}
-	local scroller
-	local report
-	local timer
-	local function startTimer(delay,event)
-		delay=delay or 0.2
-		event=event or "LOOP"
-		addon:ScheduleTimer("MissionAutoComplete",delay,event)
-		--@alpha@
-		addon:Dprint("Timer rearmed for",event,delay)
-		--@end-alpha@
-	end
-	local function stopTimer()
+local missions={}
+local states={}
+local currentMission
+local rewards={
+	items={},
+	followerBase={},
+	followerXP=setmetatable({},{__index=function() return 0 end}),
+	currencies=setmetatable({},{__index=function(t,k) rawset(t,k,{icon="",qt=0}) return t[k] end}),
+}
+local scroller
+local report
+local timer
+local function stopTimer()
+	if (timer) then
+		module:CancelTimer(timer)
 		timer=nil
 	end
-	function addon:MissionsCleanup()
-		stopTimer()
-		self:MissionEvents(false)
-		GMF.MissionTab.MissionList.CompleteDialog:Hide()
-		GMF.MissionComplete:Hide()
-		GMF.MissionCompleteBackground:Hide()
-		GMF.MissionComplete.currentIndex = nil
-		GMF.MissionTab:Show()
-		GarrisonMissionList_UpdateMissions()
-		-- Re-enable "view" button
-		GMFMissions.CompleteDialog.BorderFrame.ViewButton:SetEnabled(true)
-		ns.missionautocompleting=nil
-		GarrisonMissionFrame_SelectTab(1)
-		GarrisonMissionFrame_CheckCompleteMissions()
-	end
-	function addon:MissionEvents(start)
-		self:UnregisterEvent("GARRISON_MISSION_BONUS_ROLL_COMPLETE")
-		self:UnregisterEvent("GARRISON_MISSION_BONUS_ROLL_LOOT")
-		self:UnregisterEvent("GARRISON_MISSION_COMPLETE_RESPONSE")
-		self:UnregisterEvent("GARRISON_FOLLOWER_XP_CHANGED")
-		if start then
-			self:RegisterEvent("GARRISON_MISSION_BONUS_ROLL_LOOT","MissionAutoComplete")
-			self:RegisterEvent("GARRISON_MISSION_BONUS_ROLL_COMPLETE","MissionAutoComplete")
-			self:RegisterEvent("GARRISON_MISSION_COMPLETE_RESPONSE","MissionAutoComplete")
-			self:RegisterEvent("GARRISON_FOLLOWER_XP_CHANGED","MissionAutoComplete")
-		else
-			self:SafeRegisterEvent("GARRISON_MISSION_BONUS_ROLL_LOOT")
-			self:SafeRegisterEvent("GARRISON_MISSION_BONUS_ROLL_COMPLETE")
-			self:SafeRegisterEvent("GARRISON_MISSION_COMPLETE_RESPONSE")
-			self:SafeRegisterEvent("GARRISON_FOLLOWER_XP_CHANGED")
-		end
-	end
-	function addon:CloseReport()
-		if report then pcall(report.Close,report) end
-	end
-	function addon:MissionComplete(this,button)
-		GMFMissions.CompleteDialog.BorderFrame.ViewButton:SetEnabled(false) -- Disabling standard Blizzard Completion
-		missions=G.GetCompleteMissions()
-		if (missions and #missions > 0) then
-			ns.missionautocompleting=true
-			report=self:GenerateMissionCompleteList("Missions' results")
-			--report:SetPoint("TOPLEFT",GMFMissions.CompleteDialog.BorderFrame)
-			--report:SetPoint("BOTTOMRIGHT",GMFMissions.CompleteDialog.BorderFrame)
-			report:SetParent(GMF)
-			report:SetPoint("TOP",GMF)
-			report:SetPoint("BOTTOM",GMF)
-			report:SetWidth(500)
-			report:SetCallback("OnClose",function() return addon:MissionsCleanup() end)
-			wipe(rewards.followerBase)
-			wipe(rewards.followerXP)
-			wipe(rewards.currencies)
-			wipe(rewards.items)
-			for i=1,#missions do
-				for k,v in pairs(missions[i].followers) do
-					rewards.followerBase[v]=self:GetFollowerData(v,'qLevel')
-				end
-				local m=missions[i]
-				local _
-				_,_,_,m.successChance,_,_,m.xpBonus,m.resourceMultiplier,m.goldMultiplier=G.GetPartyMissionInfo(m.missionID)
+end
+local function startTimer(delay,event,...)
+	delay=delay or 0.2
+	event=event or "LOOP"
+	stopTimer()
+	timer=module:ScheduleRepeatingTimer("MissionAutoComplete",delay,event,...)
+	--@alpha@
+	addon:Dprint("Timer rearmed for",event,delay)
+	--@end-alpha@
+end
+function module:MissionsCleanup()
+	stopTimer()
+	GMF.MissionTab.MissionList.CompleteDialog:Hide()
+	GMF.MissionComplete:Hide()
+	GMF.MissionCompleteBackground:Hide()
+	GMF.MissionComplete.currentIndex = nil
+	GMF.MissionTab:Show()
+	GarrisonMissionList_UpdateMissions()
+	-- Re-enable "view" button
+	GMFMissions.CompleteDialog.BorderFrame.ViewButton:SetEnabled(true)
+	ns.missionautocompleting=nil
+	GarrisonMissionFrame_SelectTab(1)
+	GarrisonMissionFrame_CheckCompleteMissions()
+end
+function module:OnInitialized(start)
+	self:RegisterEvent("GARRISON_MISSION_BONUS_ROLL_LOOT","MissionAutoComplete")
+	self:RegisterEvent("GARRISON_MISSION_BONUS_ROLL_COMPLETE","MissionAutoComplete")
+	self:RegisterEvent("GARRISON_MISSION_COMPLETE_RESPONSE","MissionAutoComplete")
+	self:RegisterEvent("GARRISON_FOLLOWER_XP_CHANGED","MissionAutoComplete")
+end
+function module:CloseReport()
+	if report then pcall(report.Close,report) end
+end
+function module:MissionComplete(this,button)
+	GMFMissions.CompleteDialog.BorderFrame.ViewButton:SetEnabled(false) -- Disabling standard Blizzard Completion
+	missions=G.GetCompleteMissions()
+	if (missions and #missions > 0) then
+		ns.missionautocompleting=true
+		report=self:GenerateMissionCompleteList("Missions' results")
+		--report:SetPoint("TOPLEFT",GMFMissions.CompleteDialog.BorderFrame)
+		--report:SetPoint("BOTTOMRIGHT",GMFMissions.CompleteDialog.BorderFrame)
+		report:SetParent(GMF)
+		report:SetPoint("TOP",GMF)
+		report:SetPoint("BOTTOM",GMF)
+		report:SetWidth(500)
+		report:SetCallback("OnClose",function() return module:MissionsCleanup() end)
+		wipe(rewards.followerBase)
+		wipe(rewards.followerXP)
+		wipe(rewards.currencies)
+		wipe(rewards.items)
+		for i=1,#missions do
+			for k,v in pairs(missions[i].followers) do
+				rewards.followerBase[v]=self:GetFollowerData(v,'qLevel')
 			end
-			currentMission=tremove(missions)
-			ns.CompletedMissions[currentMission.missionID]=currentMission
-			self:MissionAutoComplete("LOOP")
-			self:MissionEvents(true)
+			local m=missions[i]
+			local _
+			_,_,_,m.successChance,_,_,m.xpBonus,m.resourceMultiplier,m.goldMultiplier=G.GetPartyMissionInfo(m.missionID)
 		end
+		currentMission=tremove(missions)
+		ns.CompletedMissions[currentMission.missionID]=currentMission
+		self:MissionAutoComplete("INIT")
 	end
-	function addon:MissionAutoComplete(event,ID,arg1,arg2,arg3,arg4)
+end
+function module:MissionAutoComplete(event,ID,arg1,arg2,arg3,arg4)
 -- C_Garrison.MarkMissionComplete Mark mission as complete and prepare it for bonus roll, da chiamare solo in caso di successo
 -- C_Garrison.MissionBonusRoll
-	--@alpha@
-		self:Dprint("evt",event,ID,arg1,arg2,arg3)
-	--@end-alpha@
-		if event=="LOOT" then
-			return addon:MissionsPrintResults()
-		end
+--@alpha@
+	self:Dprint("evt",event,ID,arg1 or'',arg2 or '',arg3 or '')
+--@end-alpha@
+	if event=="LOOT" then
+		return self:MissionsPrintResults()
+	end

-		if (event =="LOOP" ) then
-			ID=currentMission and currentMission.missionID or "none"
-			arg1=currentMission and currentMission.state or "none"
+	if (event =="LOOP" or event=="INIT") then
+		ID=currentMission and currentMission.missionID or "none"
+		arg1=currentMission and currentMission.state or "none"
+	end
+	-- GARRISON_FOLLOWER_XP_CHANGED: followerID, xpGained, actualXp, newLevel, quality
+	if (event=="GARRISON_FOLLOWER_XP_CHANGED") then
+		if (arg1 > 0) then
+			--report:AddFollower(ID,arg1,arg2)
+			rewards.followerXP[ID]=rewards.followerXP[ID]+tonumber(arg1) or 0
 		end
-		-- GARRISON_FOLLOWER_XP_CHANGED: followerID, xpGained, actualXp, newLevel, quality
-		if (event=="GARRISON_FOLLOWER_XP_CHANGED") then
-			if (arg1 > 0) then
-				--report:AddFollower(ID,arg1,arg2)
-				rewards.followerXP[ID]=rewards.followerXP[ID]+tonumber(arg1) or 0
-			end
-			return
-		-- GARRISON_MISSION_BONUS_ROLL_LOOT: itemID
-		elseif (event=="GARRISON_MISSION_BONUS_ROLL_LOOT") then
-			if (currentMission) then
-				rewards.items[format("%d:%s",currentMission.missionID,ID)]=1
-			else
-				rewards.items[format("%d:%s",0,ID)]=1
-			end
-			return
-		-- GARRISON_MISSION_COMPLETE_RESPONSE: missionID, requestCompleted, succeeded
-		elseif (event=="GARRISON_MISSION_COMPLETE_RESPONSE") then
-			if (not arg1) then
-				-- We need to call server again
+		return
+	-- GARRISON_MISSION_BONUS_ROLL_LOOT: itemID
+	elseif (event=="GARRISON_MISSION_BONUS_ROLL_LOOT") then
+		if (currentMission) then
+			rewards.items[format("%d:%s",currentMission.missionID,ID)]=1
+		else
+			rewards.items[format("%d:%s",0,ID)]=1
+		end
+		return
+	-- GARRISON_MISSION_COMPLETE_RESPONSE: missionID, requestCompleted, succeeded
+	elseif (event=="GARRISON_MISSION_COMPLETE_RESPONSE") then
+		if (not arg1) then
+			-- We need to call server again
+			currentMission.state=0
+		elseif (arg2) then -- success, we need to roll
+			currentMission.state=1
+		else -- failure, just print results
+			currentMission.state=2
+		end
+		startTimer(0.1)
+		return
+	-- GARRISON_MISSION_BONUS_ROLL_COMPLETE: missionID, requestCompleted; happens after C_Garrison.MissionBonusRoll
+	elseif (event=="GARRISON_MISSION_BONUS_ROLL_COMPLETE") then
+		if (not arg1) then
+			-- We need to call server again
+			currentMission.state=1
+		else
+			currentMission.state=3
+		end
+		startTimer(0.1)
+		return
+	else -- event == LOOP
+		if (currentMission) then
+			local step=currentMission.state or -1
+			if (step<1) then
+				step=0
 				currentMission.state=0
-			elseif (arg2) then -- success, we need to roll
-				currentMission.state=1
-			else -- failure, just print results
-				currentMission.state=2
-			end
-			startTimer(0.1)
-			return
-		-- GARRISON_MISSION_BONUS_ROLL_COMPLETE: missionID, requestCompleted; happens after C_Garrison.MissionBonusRoll
-		elseif (event=="GARRISON_MISSION_BONUS_ROLL_COMPLETE") then
-			if (not arg1) then
-				-- We need to call server again
-				currentMission.state=1
-			else
-				currentMission.state=3
+				currentMission.goldMultiplier=currentMission.goldMultiplier or 1
+				currentMission.xp=select(2,G.GetMissionInfo(currentMission.missionID))
+				report:AddMissionButton(currentMission)
 			end
-			startTimer(0.1)
-			return
-		else -- event == LOOP
-			if (currentMission) then
-				local step=currentMission.state or -1
-				if (step<1) then
-					step=0
-					currentMission.state=0
-					currentMission.goldMultiplier=currentMission.goldMultiplier or 1
-					currentMission.xp=select(2,G.GetMissionInfo(currentMission.missionID))
-					report:AddMissionButton(currentMission)
+			if (step==0) then
+				--@alpha@
+				self:Dprint("Fired mission complete for",currentMission.missionID)
+				--@end-alpha@
+				G.MarkMissionComplete(currentMission.missionID)
+				startTimer(2)
+			elseif (step==1) then
+				--@alpha@
+				self:Dprint("Fired bonus roll complete for",currentMission.missionID)
+				--@end-alpha@
+				G.MissionBonusRoll(currentMission.missionID)
+				startTimer(2)
+			elseif (step>=2) then
+				self:GetMissionResults(step==3)
+				self:RefreshFollowerStatus()
+				currentMission=tremove(missions)
+				if currentMission then
+					ns.CompletedMissions[currentMission.missionID]=currentMission
 				end
-				if (step==0) then
-					--@alpha@
-					self:Dprint("Fired mission complete for",currentMission.missionID)
-					--@end-alpha@
-					G.MarkMissionComplete(currentMission.missionID)
-				elseif (step==1) then
-					--@alpha@
-					self:Dprint("Fired bonus roll complete for",currentMission.missionID)
-					--@end-alpha@
-					G.MissionBonusRoll(currentMission.missionID)
-				elseif (step>=2) then
-					self:GetMissionResults(step==3)
-					self:RefreshFollowerStatus()
-					currentMission=tremove(missions)
-					if currentMission then
-						ns.CompletedMissions[currentMission.missionID]=currentMission
-					end
-					startTimer()
-					return
-				end
-				currentMission.state=step
-			else
-				report:AddButton(L["Building Final report"],function() addon:MissionsPrintResult() end)
-				startTimer(1,"LOOT")
+				startTimer()
+				return
 			end
-		end
-	end
-	function addon:GetMissionResults(success)
-		stopTimer()
-		if (success) then
-			report:AddMissionResult(currentMission.missionID,true)
-			PlaySound("UI_Garrison_Mission_Complete_Mission_Success")
+			currentMission.state=step
 		else
-			report:AddMissionResult(currentMission.missionID,false)
-			PlaySound("UI_Garrison_Mission_Complete_Encounter_Fail")
+			report:AddButton(L["Building Final report"],function() addon:MissionsPrintResult() end)
+			startTimer(1,"LOOT")
 		end
-		if success then
-			local resourceMultiplier=currentMission.resourceMultiplier or 1
-			local goldMultiplier=currentMission.goldMultiplier or 1
-			for k,v in pairs(currentMission.rewards) do
-				v.quantity=v.quantity or 0
-				if v.currencyID then
-					rewards.currencies[v.currencyID].icon=v.icon
-					if v.currencyID == 0 then
-						rewards.currencies[v.currencyID].qt=rewards.currencies[v.currencyID].qt+v.quantity * goldMultiplier
-					elseif v.currencyID == GARRISON_CURRENCY then
-						rewards.currencies[v.currencyID].qt=rewards.currencies[v.currencyID].qt+v.quantity * resourceMultiplier
-					else
-						rewards.currencies[v.currencyID].qt=rewards.currencies[v.currencyID].qt+v.quantity
-					end
-				elseif v.itemID then
-					GetItemInfo(v.itemID) -- Triggering the cache
-					rewards.items[format("%d:%s",currentMission.missionID,v.itemID)]=1
+	end
+end
+function module:GetMissionResults(success)
+	stopTimer()
+	if (success) then
+		report:AddMissionResult(currentMission.missionID,true)
+		PlaySound("UI_Garrison_Mission_Complete_Mission_Success")
+	else
+		report:AddMissionResult(currentMission.missionID,false)
+		PlaySound("UI_Garrison_Mission_Complete_Encounter_Fail")
+	end
+	if success then
+		local resourceMultiplier=currentMission.resourceMultiplier or 1
+		local goldMultiplier=currentMission.goldMultiplier or 1
+		for k,v in pairs(currentMission.rewards) do
+			v.quantity=v.quantity or 0
+			if v.currencyID then
+				rewards.currencies[v.currencyID].icon=v.icon
+				if v.currencyID == 0 then
+					rewards.currencies[v.currencyID].qt=rewards.currencies[v.currencyID].qt+v.quantity * goldMultiplier
+				elseif v.currencyID == GARRISON_CURRENCY then
+					rewards.currencies[v.currencyID].qt=rewards.currencies[v.currencyID].qt+v.quantity * resourceMultiplier
+				else
+					rewards.currencies[v.currencyID].qt=rewards.currencies[v.currencyID].qt+v.quantity
 				end
+			elseif v.itemID then
+				GetItemInfo(v.itemID) -- Triggering the cache
+				rewards.items[format("%d:%s",currentMission.missionID,v.itemID)]=1
 			end
 		end
 	end
-	function addon:MissionsPrintResults(success)
-		stopTimer()
-		self:FollowerCacheInit()
+end
+function module:MissionsPrintResults(success)
+	stopTimer()
+	self:FollowerCacheInit()
 --@debug@
-		--self:Dump("Ended Mission",rewards)
+	--self:Dump("Ended Mission",rewards)
 --@end-debug@
-		for k,v in pairs(rewards.currencies) do
-			if k == 0 then
-				-- Money reward
-				report:AddIconText(v.icon,GetMoneyString(v.qt))
-			elseif k == GARRISON_CURRENCY then
-				-- Garrison currency reward
-				report:AddIconText(v.icon,GetCurrencyLink(k),v.qt)
-			else
-				-- Other currency reward
-				report:AddIconText(v.icon,GetCurrencyLink(k),v.qt)
-			end
-		end
-		local items=new()
-		for k,v in pairs(rewards.items) do
-			local missionid,itemid=strsplit(":",k)
-			if (not items[itemid]) then
-				items[itemid]=1
-			else
-				items[itemid]=items[itemid]+1
-			end
-		end
-		for itemid,qt in pairs(items) do
-			report:AddItem(itemid,qt)
+	for k,v in pairs(rewards.currencies) do
+		if k == 0 then
+			-- Money reward
+			report:AddIconText(v.icon,GetMoneyString(v.qt))
+		elseif k == GARRISON_CURRENCY then
+			-- Garrison currency reward
+			report:AddIconText(v.icon,GetCurrencyLink(k),v.qt)
+		else
+			-- Other currency reward
+			report:AddIconText(v.icon,GetCurrencyLink(k),v.qt)
 		end
-		del(items)
-		for k,v in pairs(rewards.followerXP) do
-			report:AddFollower(k,v,self:GetFollowerData(k,'qLevel') > rewards.followerBase[k])
+	end
+	local items=new()
+	for k,v in pairs(rewards.items) do
+		local missionid,itemid=strsplit(":",k)
+		if (not items[itemid]) then
+			items[itemid]=1
+		else
+			items[itemid]=items[itemid]+1
 		end
 	end
+	for itemid,qt in pairs(items) do
+		report:AddItem(itemid,qt)
+	end
+	del(items)
+	for k,v in pairs(rewards.followerXP) do
+		report:AddFollower(k,v,self:GetFollowerData(k,'qLevel') > rewards.followerBase[k])
+	end
 end
diff --git a/Widgets.lua b/Widgets.lua
new file mode 100644
index 0000000..e0b1135
--- /dev/null
+++ b/Widgets.lua
@@ -0,0 +1,428 @@
+local me, ns = ...
+local _G=_G
+local pp=print
+local addon=ns.addon
+local AceGUI=LibStub("AceGUI-3.0")
+local C=ns.C
+local G=ns.G
+local L=ns.L
+local module=addon:NewSubModule("Widgets") --#module
+--- Quick backdrop
+--
+local backdrop = {
+	bgFile="Interface\\TutorialFrame\\TutorialFrameBackground",
+	edgeFile="Interface\\Tooltips\\UI-Tooltip-Border",
+	tile=true,
+	tileSize=16,
+	edgeSize=16,
+	insets={bottom=7,left=7,right=7,top=7}
+}
+local function addBackdrop(f,color)
+	f:SetBackdrop(backdrop)
+	f:SetBackdropBorderColor(C[color or 'Yellow']())
+end
+local function GMCList()
+	local Type="GCMCList"
+	local Version=1
+	local m={} --#GCMList
+	function m:ScrollDown()
+		local obj=self.scroll
+		if (#self.missions >1 and obj.scrollbar and obj.scrollbar:IsShown()) then
+			obj:SetScroll(80)
+			obj.scrollbar.ScrollDownButton:Click()
+		end
+	end
+	function m:OnAcquire()
+		wipe(self.missions)
+	end
+	function m:Show()
+		self.frame:Show()
+	end
+	function m:Hide()
+		self.frame:Hide()
+		self:Release()
+	end
+	function m:AddButton(text,action)
+		local obj=self.scroll
+		local b=AceGUI:Create("Button")
+		b:SetFullWidth(true)
+		b:SetText(text)
+		b:SetCallback("OnClick",action)
+		obj:AddChild(b)
+	end
+	function m:AddMissionButton(mission)
+		local obj=self.scroll
+		local b=AceGUI:Create("GMCSlimMissionButton")
+		b:SetMission(mission,addon:GetParty(mission.missionID))
+		b:SetScale(0.7)
+		b:SetFullWidth(true)
+		self.missions[mission.missionID]=b
+		obj:AddChild(b)
+		b.frame.Spinner:Show()
+		b.frame.Spinner.Anim:Play()
+
+	end
+	function m:AddMissionResult(missionID,success)
+		local mission=self.missions[missionID]
+		if mission then
+			local frame=mission.frame
+			frame.Spinner.Anim:Stop()
+			frame.Spinner:Hide()
+			if success then
+				frame.Success:Show()
+				frame.Failure:Hide()
+				for i=1,#frame.Rewards do
+					frame.Rewards[i].Icon:SetDesaturated(false)
+				end
+			else
+				frame.Success:Hide()
+				frame.Failure:Show()
+				for i=1,#frame.Rewards do
+					frame.Rewards[i].Icon:SetDesaturated(true)
+					frame.Rewards[i].Quantity:Hide()
+				end
+			end
+		end
+	end
+	function m:AddRow(data,...)
+		local obj=self.scroll
+		local l=AceGUI:Create("InteractiveLabel")
+		l:SetFontObject(GameFontNormalSmall)
+		l:SetText(data)
+		l:SetColor(...)
+		l:SetFullWidth(true)
+		obj:AddChild(l)
+
+	end
+	function m:AddFollower(followerID,xp,levelup)
+		local follower=addon:GetFollowerData(followerID)
+		if follower.maxed and not levelup then
+			return self:AddIconText(addon:GetFollowerTexture(followerID),
+								format("%s is already at maximum xp",addon:GetFollowerData(followerID,'fullname')))
+		end
+		local quality=G.GetFollowerQuality(followerID) or follower.quality
+		local level=G.GetFollowerLevel(followerID) or follower.level
+		if levelup then
+			PlaySound("UI_Garrison_CommandTable_Follower_LevelUp");
+		end
+		return self:AddIconText(addon:GetFollowerTexture(followerID),
+		format("%s gained %d xp%s%s",addon:GetFollowerData(followerID,'fullname',true),xp,
+		levelup and " |cffffed1a*** Level Up ***|r ." or ".",
+		format(" %d to go.",addon:GetFollowerData(followerID,'levelXP')-addon:GetFollowerData(followerID,'xp'))))
+	end
+	function m:AddIconText(icon,text,qt)
+		local obj=self.scroll
+		local l=AceGUI:Create("Label")
+		l:SetFontObject(GameFontNormalSmall)
+		if (qt) then
+			l:SetText(format("%s x %s",text,qt))
+		else
+			l:SetText(text)
+		end
+		l:SetImage(icon)
+		l:SetImageSize(24,24)
+		l:SetFullWidth(true)
+		obj:AddChild(l)
+		if (obj.scrollbar and obj.scrollbar:IsShown()) then
+			obj:SetScroll(80)
+			obj.scrollbar.ScrollDownButton:Click()
+		end
+		return l
+	end
+	function m:AddItem(itemID,qt)
+		local obj=self.scroll
+		local _,itemlink,itemquality,_,_,_,_,_,_,itemtexture=GetItemInfo(itemID)
+		if not itemlink then
+			self:AddIconText(itemtexture,itemID,qt)
+		else
+			self:AddIconText(itemtexture,itemlink,qt)
+		end
+	end
+	local function Constructor()
+		local widget=AceGUI:Create("GMCGUIContainer")
+		widget:SetLayout("Fill")
+		widget.missions={}
+		local scroll = AceGUI:Create("ScrollFrame")
+		scroll:SetLayout("List") -- probably?
+		scroll:SetFullWidth(true)
+		scroll:SetFullHeight(true)
+		widget:AddChild(scroll)
+		for k,v in pairs(m) do widget[k]=v end
+		widget:Show()
+		widget.scroll=scroll
+		return widget
+	end
+	AceGUI:RegisterWidgetType(Type,Constructor,Version)
+end
+local function GMCGUIContainer()
+	local Type="GMCGUIContainer"
+	local Version=1
+	local m={} --#GMCGUIContainer
+	function m:Close()
+		self.frame.CloseButton:Click()
+	end
+	function m:OnAcquire()
+		self.frame:EnableMouse(true)
+		self:SetTitleColor(C.Yellow())
+		self.frame:SetFrameStrata("HIGH")
+		self.frame:SetFrameLevel(999)
+	end
+	function m:SetContentWidth(x)
+		self.content:SetWidth(x)
+	end
+	---@function [parent=#GMCGUIContainer]
+	local function Constructor()
+		local frame=CreateFrame("Frame",nil,nil,"GarrisonUITemplate")
+		for _,f in pairs({frame:GetRegions()}) do
+			if (f:GetObjectType()=="Texture" and f:GetAtlas()=="Garr_WoodFrameCorner") then f:Hide() end
+		end
+		local widget={frame=frame,missions={}}
+		widget.type=Type
+		widget.SetTitle=function(self,...) self.frame.TitleText:SetText(...) end
+		widget.SetTitleColor=function(self,...) self.frame.TitleText:SetTextColor(...) end
+		for k,v in pairs(m) do widget[k]=v end
+		frame:SetScript("OnHide",function(self) self.obj:Fire('OnClose') end)
+		frame.obj=widget
+		--Container Support
+		local content = CreateFrame("Frame",nil,frame)
+		widget.content = content
+		--addBackdrop(content,'Green')
+		content.obj = widget
+		content:SetPoint("TOPLEFT",25,-25)
+		content:SetPoint("BOTTOMRIGHT",-25,25)
+		AceGUI:RegisterAsContainer(widget)
+		return widget
+	end
+	AceGUI:RegisterWidgetType(Type,Constructor,Version)
+end
+local function GMCLayer()
+	local Type="GMCLayer"
+	local Version=1
+	local function OnRelease(self)
+		wipe(self.childs)
+	end
+	local m={} --#GMCLayer
+	function m:OnAcquire()
+		self.frame:SetParent(UIParent)
+		self.frame:SetFrameStrata("HIGH")
+		self.frame:SetHeight(50)
+		self.frame:SetWidth(100)
+		self.frame:Show()
+		self.frame:SetPoint("LEFT")
+	end
+	function m:Show()
+		return self.frame:Show()
+	end
+	function m:Hide()
+		self.frame:Hide()
+		self:Release()
+	end
+	function m:SetScript(...)
+		return self.frame:SetScript(...)
+	end
+	function m:SetParent(...)
+		return self.frame:SetParent(...)
+	end
+	function m:PushChild(child,index)
+		self.childs[index]=child
+		self.scroll:AddChild(child)
+	end
+	function m:RemoveChild(index)
+		local child=self.childs[index]
+		if (child) then
+			self.childs[index]=nil
+			child:Hide()
+			self:DoLayout()
+		end
+	end
+	function m:ClearChildren()
+		wipe(self.childs)
+		self:AddScroll()
+	end
+	function m:AddScroll()
+		if (self.scroll) then
+			self:ReleaseChildren()
+			self.scroll=nil
+		end
+		self.scroll=AceGUI:Create("ScrollFrame")
+		local scroll=self.scroll
+		self:AddChild(scroll)
+		scroll:SetLayout("List") -- probably?
+		scroll:SetFullWidth(true)
+		scroll:SetFullHeight(true)
+		scroll:SetPoint("TOPLEFT",self.title,"BOTTOMLEFT",0,0)
+		scroll:SetPoint("TOPRIGHT",self.title,"BOTTOMRIGHT",0,0)
+		scroll:SetPoint("BOTTOM",self.content,"BOTTOM",0,0)
+	end
+	---@function [parent=#GMCLayer]
+	local function Constructor()
+		local frame=CreateFrame("Frame")
+		local title=frame:CreateFontString(nil, "BACKGROUND", "GameFontNormalHugeBlack")
+		title:SetJustifyH("CENTER")
+		title:SetJustifyV("CENTER")
+		title:SetPoint("TOPLEFT")
+		title:SetPoint("TOPRIGHT")
+		title:SetHeight(0)
+		title:SetWidth(0)
+		addBackdrop(frame)
+		local widget={childs={}}
+		widget.title=title
+		widget.type=Type
+		widget.SetTitle=function(self,...) self.title:SetText(...) end
+		widget.SetTitleColor=function(self,...) self.title:SetTextColor(...) end
+		widget.SetFormattedTitle=function(self,...) self.title:SetFormattedText(...) end
+		widget.SetTitleWidth=function(self,...) self.title:SetWidth(...) end
+		widget.SetTitleHeight=function(self,...) self.title:SetHeight(...) end
+		widget.frame=frame
+		frame.obj=widget
+		for k,v in pairs(m) do widget[k]=v end
+		frame:SetScript("OnHide",function(self) self.obj:Fire('OnClose') end)
+		--Container Support
+		local content = CreateFrame("Frame",nil,frame)
+		widget.content = content
+		content.obj = self
+		content:SetPoint("TOPLEFT",title,"BOTTOMLEFT")
+		content:SetPoint("BOTTOMRIGHT")
+		AceGUI:RegisterAsContainer(widget)
+		return widget
+	end
+	AceGUI:RegisterWidgetType(Type,Constructor,Version)
+end
+
+local function GMCMissionButton()
+	do
+		local Type1="GMCMissionButton"
+		local Type2="GMCSlimMissionButton"
+		local Version=1
+		local unique=0
+		local m={} --#GMCMissionButton
+		function m:OnAcquire()
+			local frame=self.frame
+			frame.info=nil
+			frame:SetHeight(self.type==Type1 and 80 or 80)
+			frame:SetAlpha(1)
+			frame:Enable()
+			for i=1,#self.scripts do
+				frame:SetScript(self.scripts[i],nil)
+			end
+			for i=1,#frame.Rewards do
+				frame.Rewards[i].Icon:SetDesaturated(false)
+			end
+			wipe(self.scripts)
+			return self.frame:SetScale(1.0)
+		end
+		function m:Show()
+			return self.frame:Show()
+		end
+		function m:SetHeight(h)
+			return self.frame:SetHeight(h)
+		end
+		function m:Hide()
+			self.frame:SetHeight(1)
+			self.frame:SetAlpha(0)
+			return self.frame:Disable()
+		end
+		function m:SetScript(name,method)
+			tinsert(self.scripts,name)
+			return self.frame:SetScript(name,method)
+		end
+		function m:SetScale(s)
+			return self.frame:SetScale(s)
+		end
+		function m:SetMission(mission,party)
+			self.frame.info=mission
+			self.frame.fromFollowerPage=true
+			self.frame:EnableMouse(true)
+			self.frame.party=party
+			if self.type==Type1 then
+				addon:DrawSingleButton(false,self.frame,false,false)
+				self.frame:SetScript("OnEnter",GarrisonMissionButton_OnEnter)
+				self.frame:SetScript("OnLeave",ns.OnLeave)
+			else
+				addon:DrawSingleSlimButton(false,self.frame,false,false)
+				self.frame:SetScript("OnEnter",nil)
+				self.frame:SetScript("OnLeave",nil)
+			end
+			if self.type==Type2 then
+				self.frame.Percent:SetFormattedText("%d%%",party.perc)
+				self.frame.Percent:SetTextColor(addon:GetDifficultyColors(party.perc))
+				_G.AX=self.frame
+			end
+		end
+
+		local function Constructor()
+			unique=unique+1
+			local frame=CreateFrame("Button",nil,nil,"GarrisonMissionListButtonTemplate") --"GarrisonCommanderMissionListButtonTemplate")
+			frame.Title:SetFontObject("QuestFont_Shadow_Small")
+			frame.Summary:SetFontObject("QuestFont_Shadow_Small")
+			frame:SetScript("OnEnter",nil)
+			frame:SetScript("OnLeave",nil)
+			frame:SetScript("OnClick",function(self,button) return self.obj:Fire("OnClick",self,button) end)
+			frame.LocBG:SetPoint("LEFT")
+			frame.MissionType:SetPoint("TOPLEFT",5,-2)
+			--[[
+			frame.members={}
+			for i=1,3 do
+				local f=CreateFrame("Button",nil,frame,"GarrisonCommanderMissionPageFollowerTemplateSmall" )
+				frame.members[i]=f
+				f:SetPoint("BOTTOMRIGHT",-65 -65 *i,5)
+				f:SetScale(0.8)
+			end
+			--]]
+			local widget={}
+			setmetatable(widget,{__index=frame})
+			widget.frame=frame
+			widget.scripts={}
+			frame.obj=widget
+			for k,v in pairs(m) do widget[k]=v end
+			return widget
+		end
+		---@function [parent=#GMCMissionButton]
+		local function Constructor1()
+			local widget=Constructor()
+			widget.type=Type1
+			return AceGUI:RegisterAsWidget(widget)
+		end
+		---@function [parent=#GMCMissionButton]
+		local function Constructor2()
+			local widget=Constructor()
+			local frame=widget.frame
+			widget.type=Type2
+			local indicators=CreateFrame("Frame",nil,frame,"GarrisonCommanderIndicators")
+			indicators.Percent:SetJustifyH("LEFT")
+			indicators.Percent:SetJustifyV("CENTER")
+			indicators:SetPoint("LEFT",70,0)
+			indicators.Age:Hide()
+			local spinner=CreateFrame("Frame",nil,frame,"LoadingSpinnerTemplate")
+			frame.Spinner=spinner
+			frame.Indicators=indicators
+			frame.Percent=indicators.Percent
+			frame.Failure=frame:CreateFontString()
+			frame.Success=frame:CreateFontString()
+			frame.Failure:SetFontObject("GameFontRedLarge")
+			frame.Success:SetFontObject("GameFontGreenLarge")
+			frame.Failure:SetText(FAILED)
+			frame.Success:SetText(SUCCESS)
+			frame.Failure:Hide()
+			frame.Success:Hide()
+			frame.Title:SetPoint("TOPLEFT",frame.Indicators,"TOPRIGHT",0,0)
+			frame.Success:SetPoint("BOTTOMLEFT",frame.Indicators,"BOTTOMRIGHT",0,10)
+			frame.Failure:SetPoint("BOTTOMLEFT",frame.Indicators,"BOTTOMRIGHT",0,10)
+			frame.Spinner:SetPoint("BOTTOMLEFT",frame.Indicators,"BOTTOMRIGHT",0,-2)
+
+			--widget.frame.MissionType:Hide()
+			--widget.frame.IconBG:Hide()
+			return AceGUI:RegisterAsWidget(widget)
+		end
+		AceGUI:RegisterWidgetType(Type1,Constructor1,Version)
+		AceGUI:RegisterWidgetType(Type2,Constructor2,Version)
+
+	end
+end
+function module:OnInitialized()
+	print("Module widget called")
+	GMCGUIContainer()
+	GMCLayer()
+	GMCMissionButton()
+	GMCList()
+end