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