Quantcast
local me,ns=...
ns.Configure()
local addon=addon --#addon
local _G=_G
local P=ns.party
--local holdEvents,releaseEvents=addon.holdEvents,addon.releaseEvents
--local new, del, copy =ns.new,ns.del,ns.copy
--upvalue
local GMFRewardSplash=GarrisonMissionFrameMissions.CompleteDialog
local pairs=pairs
local format=format
local tonumber=tonumber
local tinsert=tinsert
local tremove=tremove
local loadstring=loadstring
local assert=assert
local rawset=rawset
local strsplit=strsplit
local epicMountTrait=221
local extraTrainingTrait=80 --all followers +35
local fastLearnerTrait=29 -- only this follower +50
local hearthStoneProTrait=236 -- all followers +36
local scavengerTrait=79 -- More resources
local GARRISON_CURRENCY=GARRISON_CURRENCY
local GARRISON_SHIP_OIL_CURRENCY=GARRISON_SHIP_OIL_CURRENCY
local LE_FOLLOWER_TYPE_GARRISON_6_0=_G.LE_FOLLOWER_TYPE_GARRISON_6_0 -- 1
local LE_FOLLOWER_TYPE_SHIPYARD_6_2=_G.LE_FOLLOWER_TYPE_SHIPYARD_6_2 -- 2
local dbg
local useCap=false
local currentCap=100
local testID=0

local function formatScore(c,r,x,t,maxres,cap)
	c=tonumber(c) or 0
	r=tonumber(r) or 0
	x=tonumber(x) or 0
	t=tonumber(t) or 0
	cap=tonumber(cap) or 100
	if (not maxres) then cap=100 end
	return format("%03d %03d %03d %03d %01d",math.min(c,cap),r,c,x,t),c
end

-- Empty lines to keep line numbers in sync

function addon:MissionScore(mission)
	if (mission) then
		local totalTimeString, totalTimeSeconds, isMissionTimeImproved, successChance, partyBuffs, isEnvMechanicCountered, xpBonus, materialMultiplier,goldMultiplier = G.GetPartyMissionInfo(mission.missionID)
		if not totalTimeString then
			return formatScore(0,1,0,0,false,0)
		end
		local x = tonumber(mission.xp)
		if x and x >0 then
			x= xpBonus/mission.xp*100
		else
			x=0
		end
		local r=0
		if type(materialMultiplier)=='table' then
			for _,v in pairs(mission.rewards) do
				if v.currencyID then
					if v.currencyID==0 then
						r=r+goldMultiplier
					else
						r=r+(materialMultiplier[v.currencyID] or 0)
					end
				end
			end
		end
		local t=isMissionTimeImproved and 1 or 0
		return formatScore(successChance,r,x,t,mission.maxable and self:GetBoolean("MAXRES"),self:GetNumber("MAXRESCHANCE"))
	else
		return formatScore(0,1,0,0,false,0)
	end
end


function addon:FollowerScore(mission,followerID)
	local score,chance=self:MissionScore(mission)
	return format("%s %d %04d",score,self:GetAnyData(0,followerID,'durability',0),followerID and math.min(1000-self:GetAnyData(0,followerID,'rank',90),999)),chance
end
local filters={skipMaxed=false,skipBusy=false}
function filters.nop(followerID)
	return true
end
function filters.maxed(followerID,missionID)
	return filters.skipMaxed and addon:GetAnyData(0,followerID,'maxed') or false
end
function filters.busy(followerID,missionID)
	return not addon:IsFollowerAvailableForMission(followerID,filters.skipBusy)
end
function filters.ignored(followerID,missionID)
	return addon:IsIgnored(followerID,missionID)
end
function filters.other(followerID,missionID)
	return filters.busy(followerID,missionID) or filters.ignored(followerID,missionID)
end
function filters.xp(followerID,missionID)
	return filters.maxed(followerID,missionID) or filters.other(followerID,missionID)
end
--alias
--[[
local filters={skipMaxed=false,skipBusy=true,skipTroops=false,skipIgnored=true}
setmetatable(filters,{
	__call=function(t,followerID,missionID)
		local follower=addon:GetAnyData(0,followerID)
		if t.skipMaxed then return follower.maxed end
		if t.skipTroops then return follower.isTroop end
		if t.skipBusy then return addon:IsFollowerAvailableForMission(followerID,t.skipBusy) end
		if t.skipIgnored then return addon:IsIgnored(followerID,missionID) end
	end,
	__index=function(t,key) return t end
}
)
--]]
local nop={addRow=function() end}
local scroller=nop
local function CreateFilter(missionClass)
	local code = [[
	local filters,print,pairs = ...
	local function filterdata(followers,missionID)
		for followerID,_ in pairs(followers) do
			if TEST then
				print("Removing",C_Garrison.GetFollowerName(followerID),"due to TEST", TEST)
				followers[followerID] = nil
			else
				print("Keeping",C_Garrison.GetFollowerName(followerID),"due to TEST", TEST)
			end
		end
	end
	return filterdata
	]]
	code = code:gsub("TEST", " filters." ..missionClass .."(followerID,missionID)")

--@debug@
print("Compiling ",missionClass,"filterOut")
--@end-debug@
	return assert(loadstring(code, "filterOut for " .. missionClass))(filters,print,pairs)
end

local filterTypes = setmetatable({}, {__index=function(self, missionClass)
	local filterOut = CreateFilter(missionClass)
	rawset(self, missionClass, CreateFilter(missionClass))
	return filterOut
end})
local function AddMoreFollowers(self,mission,scores,justdo,max)
	if #scores==0 then return end
	local missionID=mission.missionID
	local filterOut=filters[mission.class] or filters.other
	local missionScore=self:MissionScore(mission)
--@debug@
	if missionID==testID then
		print("AddMoreFollowers called with ",#scores,justdo,justone,P:FreeSlots())
	end
	if dbg then
		scroller:AddRow("AddMore")
	end
--@end-debug@
	max=max or P:FreeSlots()
	for p=1,math.min(max,P:FreeSlots()) do
--@debug@
		if dbg then
			scroller:AddRow("--------------------- Slot " .. P:CurrentSlot() .. " ------------------")
		end
--@end-debug@
		local candidate=nil
		local candidateScore=missionScore
		for i=1,#scores do
			local score,followerID,name=strsplit('@',scores[i])
--@debug@
			if missionID==testID then
				print("Checking",i,followerID,name,score)
			end
--@end-debug@
			if (not filterOut(followerID,missionID) and not P:IsIn(followerID)) then
				P:AddFollower(followerID)
				local newScore=self:MissionScore(mission)
--@debug@
				if missionID==testID then
					print("Temp add",followerID,P:FreeSlots())
				end
				if dbg then
					local c1,c2="green","red"
					if newScore > candidateScore or justdo then
						c1="red"
						c2="green"
					end
					scroller:AddRow(addon:GetAnyData(0,followerID,'fullname') .." changes score from " .. C(candidateScore,c1).." to "..C(newScore,c2))
				end
--@end-debug@
				if (newScore > candidateScore or justdo) then
					candidate=followerID
					candidateScore=newScore
				end
				P:RemoveFollower(followerID)
--@debug@
				if missionID==testID then
					print("Remove:",followerID,P:FreeSlots())
				end
--@end-debug@
			end
		end
		if candidate then
			local slot=P:CurrentSlot()
			if P:AddFollower(candidate,scroller) and dbg then
--@debug@
				if missionID==testID then
					print("Added",candidate,addon:GetAnyData(0,candidate,'fullname'))
				end
--@end-debug@
			end
			candidate=nil
		end
	end
end
local function MatchMaker(self,mission,party,includeBusy,onlyBest)

	local class=mission.class
	local missionID=mission.missionID
	local filterOut=filters[class] or filters.other
	filters.skipMaxed=self:GetBoolean("IGP")
	local followerType=mission.followerTypeID
	if followerType==LE_FOLLOWER_TYPE_SHIPYARD_6_2 then
		filters.skipMaxed=false
	end

	if (includeBusy==nil) then
		filters.skipBusy=self:GetBoolean("IGM")
	else
		filters.skipBusy=not includeBusy
	end
	local scores=new()
	local troops=new()
	P:Open(missionID,mission.numFollowers)
	local missionTypeID=mission.followerTypeID
	for _,followerID in self:GetAnyIterator(missionTypeID) do
		if self:IsFollowerAvailableForMission(followerID,filters.skipBusy) then
			if P:AddFollower(followerID) then
				local score,chance=self:FollowerScore(mission,followerID)
				tinsert(scores,format("%s@%s@%s",score,followerID,self:GetAnyData(missionTypeID,followerID,'fullname')))
				P:RemoveFollower(followerID)
			end
		end
	end
--@debug@
	if dbg then
		scroller=self:GetScroller("Score for " .. mission.name .. " Class " .. mission.class)
	end
	if missionID == testID then
		print("Scores")
		for i=1,#scores do print(scores[i]) end
		print("Troops")
		for i=1,#troops do print(troops[i]) end
	end
--@end-debug@
	table.sort(scores)
	table.sort(troops)
	local firstmember
	if #scores > 0 then
--@debug@
		if (dbg) then
			scroller:addRow("Cap Res Cha Xp T Vra Ran")
			for i=1,#scores do
				local score,followerID=strsplit('@',scores[i])
				local t=score .. " " .. addon:GetAnyData(mission.followerTypeID,followerID,'fullname') .. " " .. tostring(G.GetFollowerStatus(followerID))
				scroller:addRow(t)
			end
		else
			scroller=nop
		end
--@end-debug@
		for i=#scores,1,-1 do
			local score,followerID=strsplit('@',scores[i])
			if not filterOut(followerID,missionID) then
				firstmember=followerID
				break
			end
		end
		if firstmember then
			if P:AddFollower(firstmember) and dbg then
--@debug@
				scroller:addRow(C("Slot 1:","Green").. " " .. addon:GetAnyData(0,firstmember,'fullname'))
--@end-debug@
			end
			if mission.numFollowers > 1 then
				AddMoreFollowers(self,mission,scores)
			end
		end
		if P:FreeSlots() > 0 then
			if not onlyBest then
				filters.skipMaxed=false
				AddMoreFollowers(self,mission,scores)
				AddMoreFollowers(self,mission,troops)
			end
		end
		if P:FreeSlots() > 0 then
			filters.skipMaxed=false
			AddMoreFollowers(self,mission,scores,true)
			AddMoreFollowers(self,mission,troops,true)
		end
--@debug@
		if dbg then
			scroller:addRow("Final score: " .. self:MissionScore(mission))
		end
--@end-debug@
	end
	if not party.class then
		party.class=class
		party.itemLevel=mission.itemLevel
		party.followerUpgrade=mission.followerUpgrade
		party.xpBonus=mission.xpBonus
		party.gold=mission.gold
		party.resources=mission.resources
		party.oil=mission.oil
		party.apexis=mission.apexis
		party.seal=mission.seal
		party.other=mission.other
		party.rush=mission.rush
	end
	P:StoreFollowers(party.members)
	P:Close(party)
	del(scores)
	del(troops)
end
function addon:MCMatchMaker(missionID,party,skipEpic,cap)
	local mission=type(missionID)=="table" and missionID or self:GetMissionData(missionID)
	missionID=mission.missionID
	if (not party) then party=addon:GetParty(missionID) end
--@debug@
	print("Using cap data:",cap)
--@end-debug@
	MatchMaker(self,mission,party,false,true,cap)
	if (skipEpic) then
		if (self:GetMissionData(missionID,'class')=='xp') then
			for i=1,#party.members do
				if not self:GetAnyData(0,party.members[i],'maxed') then
					return
				end
			end
			party.full=false
			wipe(party.members)
		end
	end
	return party.perc
end
function addon:MatchMaker(missionID,party,includeBusy,useCap,currentCap)
	self:SetTest(1052)
	local mission=type(missionID)=="table" and missionID or self:GetMissionData(missionID)
	if not mission then return 0 end
	missionID=mission.missionID
	if (not party) then party=addon:GetParty(missionID) end
	useCap=useCap or self:GetBoolean("MAXRES")
	currentCap=currentCap or self:GetNumber("MAXRESCHANCE")
--@debug@
	if missionID==testID then
		print("Matchmaker start",missionID,mission.name,mission.class,useCap,currentCap)
		pcall(party)
	end
--@end-debug@
	MatchMaker(self,mission,party,includeBusy)
	if (missionID==testID) then
		for i=1,#party.members do
			print(party.members[i],self:GetAnyData(0,party.members[i],'fullname'))
		end
	end
	return party.perc
end
function addon:SetTest(num)
	testID=num
end
function addon:TestMission(missionID,includeBusy)
	local mission=type(missionID)=="table" and missionID or self:GetMissionData(missionID)
	missionID=mission.missionID
	dbg=true
	local party=new()
	party.members=new()
	self:MatchMaker(mission,party,includeBusy)
	del(party.members)
	del(party)
	scroller=nop
	dbg=false
end
function addon:MCTestMission(missionID,includeBusy,chance)
	local mission=type(missionID)=="table" and missionID or self:GetMissionData(missionID)
	missionID=mission.missionID
	dbg=true
	local party=new()
	party.members=new()
	self:MCMatchMaker(mission,party,includeBusy,true,chance)
	del(party.members)
	del(party)
	scroller=nop
	dbg=false
end
function addon:MatchDebug(d)
	dbg=d
end


--@do-not-package@
--[[
Dump value=GetBuffedFollowersForMission(315)
{
	["0x0000000000079D62"]={
		[1]={
			counterIcon="Interface\\ICONS\\Ability_Rogue_FanofKnives.blp",
			name="Minion Swarms",
			counterName="Fan of Knives",
			icon="Interface\\ICONS\\Spell_DeathKnight_ArmyOfTheDead.blp",
			description="An enemy with many allies.  Susceptible to area-of-effect damage."
		}
	},
	["0x000000000002F5E1"]={
		[1]={
			counterIcon="Interface\\ICONS\\Spell_Nature_StrangleVines.blp",
			name="Deadly Minions",
			counterName="Entangling Roots",
			icon="Interface\\ICONS\\Achievement_Boss_TwinOrcBrutes.blp",
			description="An enemy with powerful allies that should be neutralized."
		}
	},
	["0x00000000000CBDF8"]={
		[1]={
			counterIcon="Interface\\ICONS\\ability_deathknight_boneshield.blp",
			name="Massive Strike",
			counterName="Bone Shield",
			icon="Interface\\ICONS\\Ability_Warrior_SavageBlow.blp",
			description="An ability that deals massive damage."
		}
	}
}
Dump: value=C_Garrison.GetFollowersTraitsForMission(109)
{
	["0x00000000001BE95D"]={
		[1]={
			traitID=236,
			icon="Interface\\ICONS\\Item_Hearthstone_Card.blp"
		},
		[2]={
			traitID=76,
			icon="Interface\\ICONS\\Spell_Holy_WordFortitude.blp"
		}
	}
}
Enemies
Dump: value=GAC:GetMissionData(315,"enemies")
{
	[1]={
		portraitFileDataID=1067293,
		displayID=54329,
		name="Imperator Mar'gok",
		mechanics={
			[9]={
				description="An enemy with powerful allies that should be neutralized.",
				name="Deadly Minions",
				icon="Interface\\ICONS\\Achievement_Boss_TwinOrcBrutes.blp"
			},
			[10]={
				description="An enemy that must be dealt with quickly.",
				name="Timed Battle",
				icon="Interface\\ICONS\\SPELL_HOLY_BORROWEDTIME.BLP"
			}
		}
	},
	[2]={
		portraitFileDataID=1067315,
		displayID=54825,
		name="Ko'ragh",
		mechanics={
			[7]={
				description="An enemy with many allies.  Susceptible to area-of-effect damage.",
				name="Minion Swarms",
				icon="Interface\\ICONS\\Spell_DeathKnight_ArmyOfTheDead.blp"
			},
			[4]={
				description="A dangerous harmful effect that should be dispelled.",
				name="Magic Debuff",
				icon="Interface\\ICONS\\Spell_Shadow_ShadowWordPain.blp"
			}
		}
	},
	[3]={
		portraitFileDataID=1067275,
		displayID=53855,
		name="The Butcher",
		mechanics={
			[10]={
				description="An enemy that must be dealt with quickly.",
				name="Timed Battle",
				icon="Interface\\ICONS\\SPELL_HOLY_BORROWEDTIME.BLP"
			},
			[2]={
				description="An ability that deals massive damage.",
				name="Massive Strike",
				icon="Interface\\ICONS\\Ability_Warrior_SavageBlow.blp"
			}
		}
	}
}
Dump: value=C_Garrison.GetMissionUncounteredMechanics(315)
{
	[1]={
		[1]=9,
		[2]=10
	},
	[2]={
		[1]=7,
		[2]=4
	},
	[3]={
		[1]=10,
		[2]=2
	}
}

--]]
--@end-do-not-package@