1require "common"
2
3
4local DebugEnabled = false
5
6local function EchoDebug(inStr)
7	if DebugEnabled then
8		game:SendToConsole("TaskQueueBehaviour: " .. inStr)
9	end
10end
11
12local CMD_GUARD = 25
13
14local extraEnergy, extraMetal, energyTooLow, energyOkay, metalTooLow, metalOkay, metalBelowHalf, metalAboveHalf, notEnoughCombats, farTooFewCombats
15
16local function GetEcon()
17	extraEnergy = ai.Energy.income - ai.Energy.usage
18	extraMetal = ai.Metal.income - ai.Metal.usage
19	local enoughMetalReserves = math.min(ai.Metal.income, ai.Metal.capacity * 0.1)
20	local lotsMetalReserves = math.min(ai.Metal.income * 10, ai.Metal.capacity * 0.5)
21	local enoughEnergyReserves = math.min(ai.Energy.income * 2, ai.Energy.capacity * 0.25)
22	-- local lotsEnergyReserves = math.min(ai.Energy.income * 3, ai.Energy.capacity * 0.5)
23	energyTooLow = ai.Energy.reserves < enoughEnergyReserves or ai.Energy.income < 40
24	energyOkay = ai.Energy.reserves >= enoughEnergyReserves and ai.Energy.income >= 40
25	metalTooLow = ai.Metal.reserves < enoughMetalReserves
26	metalOkay = ai.Metal.reserves >= enoughMetalReserves
27	metalBelowHalf = ai.Metal.reserves < lotsMetalReserves
28	metalAboveHalf = ai.Metal.reserves >= lotsMetalReserves
29	local attackCounter = ai.attackhandler:GetCounter()
30	notEnoughCombats = ai.combatCount < attackCounter * 0.6
31	farTooFewCombats = ai.combatCount < attackCounter * 0.2
32end
33
34TaskQueueBehaviour = class(Behaviour)
35
36function TaskQueueBehaviour:CategoryEconFilter(value)
37	if value == nil then return DummyUnitName end
38	if value == DummyUnitName then return DummyUnitName end
39	EchoDebug(value .. " (before econ filter)")
40	-- EchoDebug("ai.Energy: " .. ai.Energy.reserves .. " " .. ai.Energy.capacity .. " " .. ai.Energy.income .. " " .. ai.Energy.usage)
41	-- EchoDebug("ai.Metal: " .. ai.Metal.reserves .. " " .. ai.Metal.capacity .. " " .. ai.Metal.income .. " " .. ai.Metal.usage)
42	if nanoTurretList[value] then
43		-- nano turret
44		EchoDebug(" nano turret")
45		if metalBelowHalf or energyTooLow or farTooFewCombats then
46			value = DummyUnitName
47		end
48	elseif reclaimerList[value] then
49		-- dedicated reclaimer
50		EchoDebug(" dedicated reclaimer")
51		if metalAboveHalf or energyTooLow or farTooFewCombats then
52			value = DummyUnitName
53		end
54	elseif unitTable[value].isBuilding then
55		-- buildings
56		EchoDebug(" building")
57		if unitTable[value].extractsMetal > 0 then
58			-- metal extractor
59			EchoDebug("  mex")
60			if energyTooLow and ai.Metal.income > 3 then
61				value = DummyUnitName
62			end
63		elseif value == "corwin" or value == "armwin" or value == "cortide" or value == "armtide" or (unitTable[value].totalEnergyOut > 0 and not unitTable[value].buildOptions) then
64			-- energy plant
65			EchoDebug("  energy plant")
66			if bigEnergyList[uname] then
67				-- big energy plant
68				EchoDebug("   big energy plant")
69				-- don't build big energy plants until we have the resources to do so
70				if energyOkay or metalTooLow or ai.Energy.income < 400 or ai.Metal.income < 35 then
71					value = DummyUnitName
72				end
73				if self.name == "coracv" and value == "corfus" and ai.Energy.income > 4000 then
74					-- build advanced fusion
75					value = "cafus"
76				elseif self.name == "armacv" and value == "armfus" and ai.Energy.income > 4000 then
77					-- build advanced fusion
78					value = "aafus"
79				end
80				-- don't build big energy plants less than fifteen seconds from one another
81				if ai.lastNameFinished[value] ~= nil then
82					if game:Frame() < ai.lastNameFinished[value] + 450 then
83						value = DummyUnitName
84					end
85				end
86			else
87				if energyOkay or metalTooLow then
88					value = DummyUnitName
89				end
90			end
91		elseif unitTable[value].buildOptions ~= nil then
92			-- factory
93			EchoDebug("  factory")
94			EchoDebug(ai.factories)
95			if ai.factories - ai.outmodedFactories <= 0 and metalOkay and energyOkay and ai.Metal.income > 3 and ai.Metal.reserves > unitTable[value].metalCost * 0.7 then
96				EchoDebug("   first factory")
97				-- build the first factory
98			elseif advFactories[value] and metalOkay and energyOkay then
99				-- build advanced factory
100			elseif expFactories[value] and metalOkay and energyOkay then
101				-- build experimental factory
102			else
103				if ai.couldAttack >= 1 or ai.couldBomb >= 1 then
104					-- other factory after attack
105					if metalTooLow or ai.Metal.income < (ai.factories - ai.outmodedFactories) * 8 or energyTooLow or (ai.needAdvanced and not ai.haveAdvFactory) then
106						value = DummyUnitName
107					end
108				else
109					-- other factory before attack more stringent
110					if metalBelowHalf or ai.Metal.income < (ai.factories - ai.outmodedFactories) * 12 or energyTooLow or (ai.needAdvanced and not ai.haveAdvFactory) then
111						value = DummyUnitName
112					end
113				end
114			end
115		elseif unitTable[value].isWeapon then
116			-- defense
117			EchoDebug("  defense")
118			if bigPlasmaList[value] or nukeList[value] then
119				-- long-range plasma and nukes aren't really defense
120				if metalTooLow or energyTooLow or ai.Metal.income < 35 or ai.factories == 0 or notEnoughCombats then
121					value = DummyUnitName
122				end
123			elseif littlePlasmaList[value] then
124				-- plasma turrets need units to back them up
125				if metalTooLow or energyTooLow or ai.Metal.income < 10 or ai.factories == 0 or notEnoughCombats then
126					value = DummyUnitName
127				end
128			else
129				if metalTooLow or ai.Metal.income < (unitTable[value].metalCost / 35) + 2 or energyTooLow or ai.factories == 0 then
130					value = DummyUnitName
131				end
132			end
133		elseif unitTable[value].radarRadius > 0 then
134			-- radar
135			EchoDebug("  radar")
136			if metalTooLow or energyTooLow or ai.factories == 0 then
137				value = DummyUnitName
138			end
139		else
140			-- other building
141			EchoDebug("  other building")
142			if notEnoughCombats or metalTooLow or energyTooLow or ai.Energy.income < 200 or ai.Metal.income < 8 or ai.factories == 0 then
143				value = DummyUnitName
144			end
145		end
146	else
147		-- moving units
148		EchoDebug(" moving unit")
149		if unitTable[value].buildOptions ~= nil then
150			-- construction unit
151			EchoDebug("  construction unit")
152			if advConList[value] then
153				-- advanced construction unit
154				if (ai.nameCount[value] == nil or ai.nameCount[value] == 0) then
155					-- build at least one of each advanced con (a mex upgrader)
156					if metalTooLow or energyTooLow or (farTooFewCombats and not self.outmodedFactory) then
157						value = DummyUnitName
158					end
159				elseif ai.nameCount[value] == 1 then
160					-- build another fairly easily
161					if metalTooLow or energyTooLow or ai.Metal.income < 18 or (farTooFewCombats and not self.outmodedFactory) then
162						value = DummyUnitName
163					end
164				else
165					if metalBelowHalf or energyTooLow or ai.nameCount[value] > ai.conCount + ai.assistCount / 3 or notEnoughCombats then
166						value = DummyUnitName
167					end
168				end
169			elseif (ai.nameCount[value] == nil or ai.nameCount[value] == 0) and metalOkay and energyOkay and (self.outmodedFactory or not farTooFewCombats) then
170				-- build at least one of each type
171			elseif assistList[value] then
172				-- build enough assistants
173				if metalBelowHalf or energyTooLow or ai.assistCount > ai.Metal.income * 0.125 then
174					value = DummyUnitName
175				end
176			elseif value == "corcv" and ai.nameCount["coracv"] ~= 0 and ai.nameCount["coracv"] ~= nil and (ai.nameCount["coralab"] == 0 or ai.nameCount["coralab"] == nil) then
177				-- core doesn't have consuls, so treat lvl1 con vehicles like assistants, if there are no other alternatives
178				if metalBelowHalf or energyTooLow or ai.conCount > ai.Metal.income * 0.15 then
179					value = DummyUnitName
180				end
181			else
182				EchoDebug(ai.combatCount .. " " .. ai.conCount .. " " .. tostring(metalBelowHalf) .. " " .. tostring(energyTooLow))
183				if metalBelowHalf or energyTooLow or (ai.combatCount < ai.conCount * 4 and not self.outmodedFactory and not self.isAirFactory and not self.isShipyard) then
184					value = DummyUnitName
185				end
186			end
187		elseif unitTable[value].isWeapon then
188			-- combat unit
189			EchoDebug("  combat unit")
190			if metalTooLow or energyTooLow then
191				value = DummyUnitName
192			end
193		elseif value == "armpeep" or value == "corfink" then
194			-- scout planes have no weapons
195			if metalTooLow or energyTooLow then
196				value = DummyUnitName
197			end
198		else
199			-- other unit
200			EchoDebug("  other unit")
201			if notEnoughCombats or metalBelowHalf or energyTooLow then
202				value = DummyUnitName
203			end
204		end
205	end
206	return value
207end
208
209function TaskQueueBehaviour:Init()
210	if ai.outmodedFactories == nil then ai.outmodedFactories = 0 end
211
212	GetEcon()
213	self.active = false
214	self.currentProject = nil
215	self.lastWatchdogCheck = game:Frame()
216	self.watchdogTimeout = 1800
217	local u = self.unit:Internal()
218	local mtype, network = ai.maphandler:MobilityOfUnit(u)
219	self.mtype = mtype
220	self.name = u:Name()
221	if commanderList[self.name] then self.isCommander = true end
222	self.id = u:ID()
223
224	-- register if factory is going to use outmoded queue
225	if factoryMobilities[self.name] ~= nil then
226		self.isFactory = true
227		local upos = u:GetPosition()
228		self.position = upos
229		local outmoded = true
230		for i, mtype in pairs(factoryMobilities[self.name]) do
231			if not ai.maphandler:OutmodedFactoryHere(mtype, upos) then
232				-- just one non-outmoded mtype will cause the factory to act normally
233				outmoded = false
234			end
235			if mtype == "air" then self.isAirFactory = true end
236		end
237		if outmoded then
238			EchoDebug("outmoded " .. self.name)
239			self.outmodedFactory = true
240			ai.outmodedFactoryID[self.id] = true
241			ai.outmodedFactories = ai.outmodedFactories + 1
242			ai.outmodedFactories = 1
243		end
244	end
245
246	-- reset attack count
247	if self.isFactory and not self.outmodedFactory then
248		if self.isAirFactory then
249			ai.couldBomb = 0
250			ai.hasBombed = 0
251		else
252			ai.couldAttack = 0
253			ai.hasAttacked = 0
254		end
255	end
256
257	if self:HasQueues() then
258		self.queue = self:GetQueue()
259	end
260
261end
262
263function TaskQueueBehaviour:HasQueues()
264	return (taskqueues[self.name] ~= nil)
265end
266
267function TaskQueueBehaviour:UnitCreated(unit)
268	if unit.engineID == self.unit.engineID then
269
270	end
271end
272
273function TaskQueueBehaviour:UnitBuilt(unit)
274	if self.unit == nil then return end
275	if unit.engineID == self.unit.engineID then
276		if self:IsActive() then self.progress = true end
277	end
278end
279
280function TaskQueueBehaviour:UnitIdle(unit)
281	if not self:IsActive() then
282		return
283	end
284	if self.unit == nil then return end
285	if unit.engineID == self.unit.engineID then
286		self.progress = true
287		self.currentProject = nil
288		ai.buildsitehandler:ClearMyPlans(self)
289		self.unit:ElectBehaviour()
290	end
291end
292
293function TaskQueueBehaviour:UnitMoveFailed(unit)
294	-- sometimes builders get stuck
295	self:UnitIdle(unit)
296end
297
298function TaskQueueBehaviour:UnitDead(unit)
299	if self.unit ~= nil then
300		if unit.engineID == self.unit.engineID then
301			-- game:SendToConsole("taskqueue-er " .. self.name .. " died")
302			if self.outmodedFactory then ai.outmodedFactories = ai.outmodedFactories - 1 end
303			-- self.unit = nil
304			if self.target then ai.targethandler:AddBadPosition(self.target, self.mtype) end
305			ai.assisthandler:Release(nil, self.id, true)
306			ai.buildsitehandler:ClearMyPlans(self)
307			ai.buildsitehandler:ClearMyConstruction(self)
308		end
309	end
310end
311
312function TaskQueueBehaviour:GetHelp(value, position)
313	if value == nil then return DummyUnitName end
314	if value == DummyUnitName then return DummyUnitName end
315	EchoDebug(value .. " before getting help")
316	local builder = self.unit:Internal()
317	if helpList[value] then
318		local hashelp = ai.assisthandler:PersistantSummon(builder, position, helpList[value], 1)
319		if hashelp then
320			return value
321		end
322	elseif unitTable[value].isBuilding and unitTable[value].buildOptions then
323		if ai.factories - ai.outmodedFactories <= 0 or advFactories[value] then
324			EchoDebug("can get help to build factory but don't need it")
325			ai.assisthandler:Summon(builder, position)
326			ai.assisthandler:Magnetize(builder, position)
327			return value
328		else
329			local hashelp = ai.assisthandler:Summon(builder, position, ai.factories)
330			if hashelp then
331				ai.assisthandler:Magnetize(builder, position)
332				return value
333			end
334		end
335	else
336		local number
337		if self.isFactory then
338			-- factories have more nano output
339			number = math.floor((unitTable[value].metalCost + 1000) / 1500)
340		else
341			number = math.floor((unitTable[value].metalCost + 750) / 1000)
342		end
343		if number == 0 then return value end
344		local hashelp = ai.assisthandler:Summon(builder, position, number)
345		if hashelp or self.isFactory then return value end
346	end
347	return DummyUnitName
348end
349
350function TaskQueueBehaviour:LocationFilter(utype, value)
351	if self.isFactory then return utype, value end -- factories don't need to look for build locations
352	local p
353	local builder = self.unit:Internal()
354	if unitTable[value].extractsMetal > 0 then
355		-- metal extractor
356		local uw
357		p, uw, reclaimEnemyMex = ai.maphandler:ClosestFreeSpot(utype, builder)
358		if p ~= nil then
359			if reclaimEnemyMex then
360				value = {"ReclaimEnemyMex", reclaimEnemyMex}
361			else
362				EchoDebug("extractor spot: " .. p.x .. ", " .. p.z)
363				if uw then
364					EchoDebug("underwater extractor " .. uw:Name())
365					utype = uw
366					value = uw:Name()
367				end
368			end
369		else
370			utype = nil
371		end
372	elseif geothermalPlant[value] then
373		-- geothermal
374		local builderPos = builder:GetPosition()
375		p = map:FindClosestBuildSite(utype, builderPos, 5000, 0)
376		if p ~= nil then
377			-- don't build on geo spots that units can't get to
378			if ai.maphandler:UnitCanGoHere(builder, p) then
379				if value == "cmgeo" or value == "amgeo" then
380					-- don't build moho geos next to factories
381					if ai.buildsitehandler:ClosestHighestLevelFactory(builder, 500) ~= nil then
382						if value == "cmgeo" then
383							if ai.targethandler:IsBombardPosition(p, "corbhmth") then
384								-- instead build geothermal plasma battery if it's a good spot for it
385								value = "corbhmth"
386								utype = game:GetTypeByName(value)
387							end
388						else
389							-- instead build a safe geothermal
390							value = "armgmm"
391							utype = game:GetTypeByName(value)
392						end
393					end
394				end
395			else
396				utype = nil
397			end
398		else
399			utype = nil
400		end
401	elseif nanoTurretList[value] then
402		-- build nano turrets next to a factory near you
403		EchoDebug("looking for factory for nano")
404		local factoryPos = ai.buildsitehandler:ClosestHighestLevelFactory(builder, 5000)
405		if factoryPos then
406			EchoDebug("found factory")
407			p = ai.buildsitehandler:ClosestBuildSpot(builder, factoryPos, utype)
408			if p == nil then
409				EchoDebug("no spot near factory found")
410				utype = nil
411			end
412		else
413			EchoDebug("no factory found")
414			utype = nil
415		end
416	elseif nukeList[value] or bigPlasmaList[value] or littlePlasmaList[value] then
417		-- bombarders
418		EchoDebug("seeking bombard build spot")
419		local turtlePosList = ai.turtlehandler:MostTurtled(builder, value, value)
420		if turtlePosList then
421			EchoDebug("got sorted turtle list")
422			if #turtlePosList ~= 0 then
423				EchoDebug("turtle list has turtles")
424				for i, turtlePos in ipairs(turtlePosList) do
425					p = ai.buildsitehandler:ClosestBuildSpot(builder, turtlePos, utype)
426					if p ~= nil then break end
427				end
428			end
429		end
430		if p == nil then
431			utype = nil
432			EchoDebug("could not find bombard build spot")
433		else
434			EchoDebug("found bombard build spot")
435		end
436	elseif shieldList[value] or antinukeList[value] or unitTable[value].jammerRadius ~= 0 or unitTable[value].radarRadius ~= 0 or unitTable[value].sonarRadius ~= 0 or (unitTable[value].isWeapon and unitTable[value].isBuilding and not nukeList[value] and not bigPlasmaList[value] and not littlePlasmaList[value]) then
437		-- shields, defense, antinukes, jammer towers, radar, and sonar
438		EchoDebug("looking for least turtled positions")
439		local turtlePosList = ai.turtlehandler:LeastTurtled(builder, value)
440		if turtlePosList then
441			if #turtlePosList ~= 0 then
442				EchoDebug("found turtle positions")
443				for i, turtlePos in ipairs(turtlePosList) do
444					p = ai.buildsitehandler:ClosestBuildSpot(builder, turtlePos, utype)
445					if p ~= nil then break end
446				end
447			end
448		end
449		if p == nil then
450			EchoDebug("did NOT find build spot near turtle position")
451			utype = nil
452		end
453	elseif unitTable[value].isBuilding then
454		-- buildings in defended positions
455		local turtlePosList = ai.turtlehandler:MostTurtled(builder, value)
456		if turtlePosList then
457			if #turtlePosList ~= 0 then
458				for i, turtlePos in ipairs(turtlePosList) do
459					p = ai.buildsitehandler:ClosestBuildSpot(builder, turtlePos, utype)
460					if p ~= nil then break end
461				end
462			end
463		end
464	end
465	-- last ditch placement
466	if utype ~= nil and p == nil then
467		local builderPos = builder:GetPosition()
468		p = ai.buildsitehandler:ClosestBuildSpot(builder, builderPos, utype)
469		if p == nil then
470			p = map:FindClosestBuildSite(utype, builderPos, 500, 15)
471		end
472	end
473	return utype, value, p
474end
475
476function TaskQueueBehaviour:BestFactory()
477	local bestScore = -99999
478	local bestName, bestPos
479	local builder = self.unit:Internal()
480	local factoryNames = unitTable[self.name].factoriesCanBuild
481	if factoryNames ~= nil then
482		for i, factoryName in pairs(factoryNames) do
483			local buildMe = true
484			local isAdvanced = advFactories[factoryName]
485			local isExperimental = expFactories[factoryName] or leadsToExpFactories[factoryName]
486			if ai.needAdvanced and not ai.haveAdvFactory then
487				if not isAdvanced then buildMe = false end
488			end
489			if not ai.needAdvanced then
490				if isAdvanced then buildMe = false end
491			end
492			if ai.needExperimental and not ai.haveExpFactory then
493				if not isExperimental then buildMe = false end
494			end
495			if not ai.needExperimental then
496				if expFactories[factoryName] then buildMe = false end
497			end
498			--[[
499			-- this probably isn't a good idea, there are better ways to use up excess metal
500			if ai.Metal.income > 10 and ai.Metal.extra > 5 and ai.Metal.full > 0.9 then
501				-- don't include built factories if we've got tons of metal
502				-- if we include factories we already have, this algo will tend to spit out subpar factories
503				if ai.nameCount[factoryName] > 0 then buildMe = false end
504			end
505			]]--
506			if buildMe then
507				local utype = game:GetTypeByName(factoryName)
508				local builderPos = builder:GetPosition()
509				local p
510				EchoDebug("looking for most turtled position for " .. factoryName)
511				local turtlePosList = ai.turtlehandler:MostTurtled(builder, factoryName)
512				if turtlePosList then
513					if #turtlePosList ~= 0 then
514						for i, turtlePos in ipairs(turtlePosList) do
515							p = ai.buildsitehandler:ClosestBuildSpot(builder, turtlePos, utype)
516							if p ~= nil then break end
517						end
518					end
519				end
520				if p == nil then
521					EchoDebug("no turtle position found, trying next to factory")
522					local factoryPos = ai.buildsitehandler:ClosestHighestLevelFactory(builder, 10000)
523					if factoryPos then
524						p = ai.buildsitehandler:ClosestBuildSpot(builder, factoryPos, utype)
525					end
526				end
527				if p == nil then
528					EchoDebug("no turtle position found for " .. factoryName .. ", trying near builder")
529					p = ai.buildsitehandler:ClosestBuildSpot(builder, builderPos, utype)
530				end
531				if p ~= nil then
532					EchoDebug("found spot for " .. factoryName)
533					for mi, mtype in pairs(factoryMobilities[factoryName]) do
534						if mtype == "air" or ai.mobRating[mtype] > ai.mobilityRatingFloor then
535							local network = ai.maphandler:MobilityNetworkHere(mtype, p)
536							if ai.scoutSpots[mtype][network] then
537								local numberOfSpots
538								if mtype == "air" then
539									if factoryName == "armplat" or factoryName == "corplat" then
540										-- seaplanes can only build on UW metal
541										numberOfSpots = #ai.UWMetalSpots
542									else
543										-- other aircraft can only build land metal spots and geospots
544										numberOfSpots = #ai.landMetalSpots + #ai.geoSpots
545									end
546								else
547									numberOfSpots = #ai.scoutSpots[mtype][network]
548								end
549								if numberOfSpots > 5 then
550									local dist = Distance(builderPos, p)
551									local spotPercentage = numberOfSpots / #ai.scoutSpots["air"][1]
552									local score = (spotPercentage * ai.maxElmosDiag) - (dist * mobilitySlowMultiplier[mtype])
553									score = score * mobilityEffeciencyMultiplier[mtype]
554									EchoDebug(factoryName .. " " .. mtype .. " has enough spots (" .. numberOfSpots .. ") and a score of " .. score .. " (" .. spotPercentage .. " " .. dist .. ")")
555									if score > bestScore then
556										local okay = true
557										if okay then
558											if mtype == "veh" then
559												if ai.maphandler:OutmodedFactoryHere("veh", builderPos) and not ai.maphandler:OutmodedFactoryHere("bot", builderPos) then
560													-- don't build a not very useful vehicle plant if a bot factory can be built instead
561													okay = false
562												end
563											end
564										end
565										if okay then
566											if mtype == "bot" and not ai.needExperimental then
567												-- don't built a bot lab senselessly to slow us down
568												if not ai.maphandler:OutmodedFactoryHere("veh", builderPos) and (ai.nameCount["armvp"] >= 1 or ai.nameCount["corvp"] >= 1) then
569													okay = false
570												end
571											end
572										end
573										if okay then
574											bestScore = score
575											bestName = factoryName
576											bestPos = p
577										end
578									end
579								end
580							end
581						end
582					end
583				end
584			end
585			-- DebugEnabled = false
586		end
587	end
588	if bestName ~= nil then
589		if ai.nameCount[bestName] > 0 then return nil, nil end
590		EchoDebug("best factory: " .. bestName)
591	end
592	return bestPos, bestName
593end
594
595function TaskQueueBehaviour:GetQueue()
596	self.unit:ElectBehaviour()
597	-- fall back to only making enough construction units if a level 2 factory exists
598	local got = false
599	if wateryTaskqueues[self.name] ~= nil then
600		if ai.mobRating["shp"] * 0.5 > ai.mobRating["veh"] then
601			q = wateryTaskqueues[self.name]
602			got = true
603		end
604	end
605	self.outmodedTechLevel = false
606	if outmodedTaskqueues[self.name] ~= nil and not got then
607		if self.isFactory and unitTable[self.name].techLevel < ai.maxFactoryLevel and ai.Metal.reserves < ai.Metal.capacity * 0.95 then
608			-- stop buidling lvl1 attackers if we have a lvl2, unless we're about to waste metal, in which case use it up
609			q = outmodedTaskqueues[self.name]
610			got = true
611			self.outmodedTechLevel = true
612		elseif self.outmodedFactory then
613			q = outmodedTaskqueues[self.name]
614			got = true
615		end
616	end
617	if not got then
618		q = taskqueues[self.name]
619	end
620	if type(q) == "function" then
621		--game:SendToConsole("function table found!")
622		q = q(self)
623	end
624	return q
625end
626
627function TaskQueueBehaviour:ConstructionBegun(unitID, unitName, position)
628	self.constructing = { unitID = unitID, unitName = unitName, position = position }
629end
630
631function TaskQueueBehaviour:ConstructionComplete()
632	self.constructing = nil
633end
634
635function TaskQueueBehaviour:Update()
636	if not self:IsActive() then
637		return
638	end
639	local f = game:Frame()
640	-- econ check
641	if f % 22 == 0 then
642		GetEcon()
643	end
644	-- watchdog check
645	if not self.constructing and not self.isFactory then
646		if (self.lastWatchdogCheck + self.watchdogTimeout < f) or (self.currentProject == nil and (self.lastWatchdogCheck + 1 < f)) then
647			-- we're probably stuck doing nothing
648			local tmpOwnName = self.unit:Internal():Name() or "no-unit"
649			local tmpProjectName = self.currentProject or "empty project"
650			if self.currentProject ~= nil then
651				EchoDebug("Watchdog: "..tmpOwnName.." abandoning "..tmpProjectName)
652			end
653			self:ProgressQueue()
654			return
655		end
656	end
657	if self.progress == true then
658		self:ProgressQueue()
659	end
660end
661
662function TaskQueueBehaviour:ProgressQueue()
663	self.lastWatchdogCheck = game:Frame()
664	self.constructing = false
665	self.progress = false
666	local builder = self.unit:Internal()
667	if not self.released then
668		ai.assisthandler:Release(builder)
669		ai.buildsitehandler:ClearMyPlans(self)
670		if not self.isCommander and not self.isFactory then
671			if ai.IDByName[self.id] ~= nil then
672				if ai.IDByName[self.id] > ai.nonAssistantsPerName then
673					ai.nonAssistant[self.id] = nil
674				end
675			end
676		end
677		self.released = true
678	end
679	if self.queue ~= nil then
680		local idx, val = next(self.queue,self.idx)
681		self.idx = idx
682		if idx == nil then
683			self.queue = self:GetQueue(name)
684			self.progress = true
685			return
686		end
687
688		local utype = nil
689		local value = val
690
691		-- evaluate any functions here, they may return tables
692		while type(value) == "function" do
693			value = value(self)
694		end
695
696		if type(value) == "table" then
697			-- not using this
698		else
699			-- if bigPlasmaList[value] or littlePlasmaList[value] then DebugEnabled = true end -- debugging plasma
700			local p
701			if value == FactoryUnitName then
702				-- build the best factory this builder can build
703				p, value = self:BestFactory()
704			end
705			local success = false
706			if value ~= DummyUnitName and value ~= nil then
707				EchoDebug(self.name .. " filtering...")
708				value = self:CategoryEconFilter(value)
709				if value ~= DummyUnitName then
710					EchoDebug("before duplicate filter " .. value)
711					local duplicate = ai.buildsitehandler:CheckForDuplicates(value)
712					if duplicate then value = DummyUnitName end
713				end
714				EchoDebug(value .. " after filters")
715			else
716				value = DummyUnitName
717			end
718			if value ~= DummyUnitName then
719				if value ~= nil then
720					utype = game:GetTypeByName(value)
721				else
722					utype = nil
723					value = "nil"
724				end
725				if utype ~= nil then
726					if self.unit:Internal():CanBuild(utype) then
727						if self.isFactory then
728							local helpValue = self:GetHelp(value, self.position)
729							if helpValue ~= nil and helpValue ~= DummyUnitName then
730								success = self.unit:Internal():Build(utype)
731							end
732						else
733							if p == nil then utype, value, p = self:LocationFilter(utype, value) end
734							if utype ~= nil and p ~= nil then
735								if type(value) == "table" and value[1] == "ReclaimEnemyMex" then
736									EchoDebug("reclaiming enemy mex...")
737									--  success = self.unit:Internal():Reclaim(value[2])
738									success = CustomCommand(self.unit:Internal(), CMD_RECLAIM, {value[2].unitID})
739									value = value[1]
740								else
741									local helpValue = self:GetHelp(value, p)
742									if helpValue ~= nil and helpValue ~= DummyUnitName then
743										EchoDebug(utype:Name() .. " has help")
744										success = self.unit:Internal():Build(utype, p)
745									end
746								end
747							end
748						end
749					else
750						game:SendToConsole("WARNING: bad taskque: "..self.name.." cannot build "..value)
751					end
752				else
753					game:SendToConsole(self.name .. " cannot build:"..value..", couldnt grab the unit type from the engine")
754				end
755			end
756			-- DebugEnabled = false -- debugging plasma
757			if success then
758				if self.isFactory then
759					if not self.outmodedTechLevel then
760						-- factories take up idle assistants
761						ai.assisthandler:TakeUpSlack(builder)
762					end
763				else
764					self.target = p
765					self.watchdogTimeout = math.max(Distance(self.unit:Internal():GetPosition(), p) * 1.5, 360)
766					self.currentProject = value
767					if value == "ReclaimEnemyMex" then
768						self.watchdogTimeout = self.watchdogTimeout + 450 -- give it 15 more seconds to reclaim it
769					else
770						ai.buildsitehandler:NewPlan(value, p, self)
771					end
772				end
773				self.released = false
774				self.progress = false
775			else
776				self.target = nil
777				self.currentProject = nil
778				self.progress = true
779			end
780		end
781	end
782end
783
784function TaskQueueBehaviour:Activate()
785	self.active = true
786	if self.constructing then
787		EchoDebug(self.name .. " " .. self.id .. " resuming construction of " .. self.constructing.unitName .. " " .. self.constructing.unitID)
788		-- resume construction if we were interrupted
789		local floats = api.vectorFloat()
790		floats:push_back(self.constructing.unitID)
791		self.unit:Internal():ExecuteCustomCommand(CMD_GUARD, floats)
792		self:GetHelp(self.constructing.unitName, self.constructing.position)
793		-- self.target = self.constructing.position
794		-- self.currentProject = self.constructing.unitName
795		self.released = false
796		self.progress = false
797	else
798		self:UnitIdle(self.unit:Internal())
799	end
800end
801
802function TaskQueueBehaviour:Deactivate()
803	self.active = false
804	ai.buildsitehandler:ClearMyPlans()
805end
806
807function TaskQueueBehaviour:Priority()
808	if self.currentProject == nil then
809		return 50
810	else
811		return 75
812	end
813end