Advertisement
Guest User

Untitled

a guest
Jul 22nd, 2017
57
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 120.66 KB | None | 0 0
  1.  
  2. -- In-game, type "/luarules caiscout [x]" in the console to toggle scoutmap drawing for team [x]
  3.  
  4. function gadget:GetInfo()
  5. return {
  6. name = "CAI",
  7. desc = "AI that plays normal ZK",
  8. author = "Google Frog",
  9. date = "June 8 2010",
  10. license = "GNU GPL, v2 or later",
  11. layer = 0,
  12. enabled = true -- loaded by default?
  13. }
  14. end
  15.  
  16. local spGetAllUnits = Spring.GetAllUnits
  17. local spGetTeamInfo = Spring.GetTeamInfo
  18. local spGetTeamLuaAI = Spring.GetTeamLuaAI
  19. local spGetTeamList = Spring.GetTeamList
  20. local spGetAllyTeamList = Spring.GetAllyTeamList
  21. local spGetUnitAllyTeam = Spring.GetUnitAllyTeam
  22. local spGiveOrderToUnit = Spring.GiveOrderToUnit
  23. local spGetUnitPosition = Spring.GetUnitPosition
  24. local spGetTeamResources = Spring.GetTeamResources
  25. local spGetUnitRulesParam = Spring.GetUnitRulesParam
  26. local spGetCommandQueue = Spring.GetCommandQueue
  27. local spGetUnitHealth = Spring.GetUnitHealth
  28. local spTestBuildOrder = Spring.TestBuildOrder
  29. local spGetUnitBuildFacing = Spring.GetUnitBuildFacing
  30. local spGetUnitRadius = Spring.GetUnitRadius
  31. local spGetFactoryCommands = Spring.GetFactoryCommands
  32. local spGetUnitSeparation = Spring.GetUnitSeparation
  33. local spIsPosInLos = Spring.IsPosInLos
  34. local spGetGroundHeight = Spring.GetGroundHeight
  35. local spGetUnitDefID = Spring.GetUnitDefID
  36. local spGetUnitsInRectangle = Spring.GetUnitsInRectangle
  37. local spGetUnitNearestEnemy = Spring.GetUnitNearestEnemy
  38. local spGetGameFrame = Spring.GetGameFrame
  39. local spValidUnitID = Spring.ValidUnitID
  40. local spGetUnitTeam = Spring.GetUnitTeam
  41. --local spSetUnitSensorRadius = Spring.SetUnitSensorRadius
  42. local spIsPosInRadar = Spring.IsPosInRadar
  43. local spGetTeamUnits = Spring.GetTeamUnits
  44.  
  45. local jumpDefNames = VFS.Include"LuaRules/Configs/jump_defs.lua"
  46. local jumpDefs = {}
  47. for name, data in pairs(jumpDefNames) do
  48. jumpDefs[UnitDefNames[name].id] = data
  49. end
  50.  
  51.  
  52. -- commands
  53. include("LuaRules/Configs/customcmds.h.lua")
  54.  
  55. local CMD_MOVE_STATE = CMD.MOVE_STATE
  56. local CMD_FIRE_STATE = CMD.FIRE_STATE
  57. local CMD_RECLAIM = CMD.RECLAIM
  58. local CMD_REPAIR = CMD.REPAIR
  59. local CMD_MOVE = CMD.MOVE
  60. local CMD_FIGHT = CMD.FIGHT
  61. local CMD_ATTACK = CMD.ATTACK
  62. local CMD_PATROL = CMD.PATROL
  63. local CMD_STOP = CMD.STOP
  64. local CMD_GUARD = CMD.GUARD
  65. local CMD_OPT_SHIFT = CMD.OPT_SHIFT
  66. local CMD_OPT_INTERNAL = CMD.OPT_INTERNAL
  67. local CMD_INSERT = CMD.INSERT
  68. local CMD_REMOVE = CMD.REMOVE
  69.  
  70. local twoPi = math.pi*2
  71.  
  72. if (not gadgetHandler:IsSyncedCode()) then
  73. return
  74. end
  75.  
  76. local function CopyTable(original) -- Warning: circular table references lead to
  77. local copy = {} -- an infinite loop.
  78. for k, v in pairs(original) do
  79. if (type(v) == "table") then
  80. copy[k] = CopyTable(v)
  81. else
  82. copy[k] = v
  83. end
  84. end
  85. return copy
  86. end
  87.  
  88. --unused
  89. local function ModifyTable(original, modify) -- Warning: circular table references lead to an infinite loop.
  90. for k, v in pairs(modify) do
  91. --Spring.Echo("Original entry: "..original[k])
  92. --Spring.Echo("Modifier entry: "..k)
  93. --if not (original and modify) then return end
  94. if (type(v) == "table") then
  95. ModifyTable(original[k], v)
  96. else
  97. original[k] = v
  98. end
  99. end
  100. end
  101.  
  102. -- *** Config
  103.  
  104. include "LuaRules/Configs/cai/general.lua"
  105. include "LuaRules/Gadgets/mex_spot_finder.lua"
  106.  
  107. -- *** 'Globals'
  108.  
  109. local usingAI -- whether there is AI in the game
  110.  
  111. -- array of allyTeams to draw heatmap data for
  112. local debugData = {
  113. drawScoutmap = {},
  114. drawOffensemap = {},
  115. drawEconmap = {},
  116. drawDefencemap = {},
  117. showEnemyForceCompostion = {},
  118. showConJobList = {},
  119. showFacJobList = {},
  120. }
  121.  
  122. local aiTeamData = {} -- all the information a single AI stores
  123. local allyTeamData = {} -- all the information that each ally team is required to store
  124. -- some information is stored by all. Other info is stored only be allyTeams with AI
  125.  
  126. -- spots that mexes can be built on
  127. mexSpot = {count = 0}
  128.  
  129. local econAverageMemory = 3 -- how many econ update steps economy is averaged over
  130.  
  131. -- size of heatmap arrays and squares
  132. mapWidth = Game.mapSizeX
  133. mapHeight = Game.mapSizeZ
  134.  
  135. -- elements in array
  136. heatArrayWidth = math.ceil(mapWidth/heatSquareMinSize)
  137. heatArrayHeight = math.ceil(mapHeight/heatSquareMinSize)
  138. -- size of a single square in elmos
  139. heatSquareWidth = mapWidth/heatArrayWidth
  140. heatSquareHeight = mapHeight/heatArrayHeight
  141. -- how many elements there are in total
  142. heatSquares = heatArrayWidth*heatArrayHeight
  143.  
  144. -- Initialise heatmap position data
  145. heatmapPosition = {}
  146. for i = 1,heatArrayWidth do -- init array
  147. heatmapPosition[i] = {}
  148. for j = 1, heatArrayHeight do
  149. heatmapPosition[i][j] = {
  150. x = heatSquareWidth*(i-0.5), z = heatSquareHeight*(j-0.5),
  151. left = heatSquareWidth*(i-1), right = heatSquareWidth*i,
  152. top = heatSquareHeight*(j-1), bottom = heatSquareHeight*j,
  153. }
  154. heatmapPosition[i][j].y = spGetGroundHeight(heatmapPosition[i][j].x,heatmapPosition[i][j].z)
  155. end
  156. end
  157.  
  158. -- area of a command placed in the centre of the map
  159. local areaCommandRadius = math.sqrt( (mapWidth/2)^2 + (mapHeight/2)^2 )
  160.  
  161. -- *** Little Helper Functions
  162. -- places text on map at unit position
  163. local function mapEcho(unitID,text)
  164. local x,y,z = spGetUnitPosition(unitID)
  165. Spring.MarkerAddPoint(x,y,z,text)
  166. end
  167.  
  168. -- returns 2D distance^2 between 2 points
  169. local function disSQ(x1,y1,x2,y2)
  170. return (x1 - x2)^2 + (y1 - y2)^2
  171. end
  172.  
  173. -- removes the index i from the array
  174. local function removeIndexFromArray(array,index)
  175. array[index] = array[array.count]
  176. array[array.count] = nil
  177. array.count = array.count - 1
  178. end
  179.  
  180. -- chooses a unitDef at random from the chance of each being chosen
  181. local function chooseUnitDefID(array)
  182. local count = array.count
  183. if count == 0 then return end
  184. local rand = math.random()
  185.  
  186. local total = 0
  187. for i = 1, count do
  188. total = total + array[i].chance
  189. if rand < total then
  190. return array[i].ID
  191. end
  192. end
  193. Spring.Echo(" ******* Chance Wrong ******* ")
  194. end
  195.  
  196. -- chooses a unitDef at random from the chance of each being chosen and prints useful debug
  197. local function chooseUnitDefIDWithDebug(array, unitID, ud, choice)
  198. local count = array.count
  199. if count == 0 then return end
  200. local rand = math.random()
  201.  
  202. local total = 0
  203. for i = 1, count do
  204. if not array then
  205. Spring.Echo("bad array for " .. ud.humanName .. " with choice " .. choice)
  206. end
  207. if not array[i] then
  208. Spring.Echo("bad array index for " .. ud.humanName .. " with choice " .. choice)
  209. end
  210. total = total + array[i].chance
  211. if rand < total then
  212. return array[i].ID
  213. end
  214. end
  215. Spring.Echo("Chance Wrong for " .. ud.humanName .. " with choice " .. choice)
  216. end
  217.  
  218. -- normalises the importance factors in an importance array
  219. local function normaliseImportance(array)
  220.  
  221. local totalImportance = 0
  222. for _,data in pairs(array) do
  223. totalImportance = totalImportance + data.importance
  224. end
  225.  
  226. if totalImportance > 0 then
  227. local scaleFactor = 1/totalImportance
  228. for _,data in pairs(array) do
  229. data.importance = data.importance*scaleFactor
  230. end
  231. end
  232. end
  233.  
  234. -- is the enemy unitID position knowable to an allyteam
  235. local function isUnitVisible(unitID, allyTeam)
  236. if spValidUnitID(unitID) then
  237. local state = Spring.GetUnitLosState(unitID,allyTeam)
  238. return state.los or state.radar --(state.radar and state.typed) -- typed for has unit icon
  239. else
  240. return false
  241. end
  242. end
  243.  
  244. -- updates the economy information of the team. Averaged over a few queries
  245. local function updateTeamResourcing(team)
  246.  
  247. local a = aiTeamData[team]
  248. local averagedEcon = a.averagedEcon
  249.  
  250. -- get resourcing
  251. local eCur, eMax, ePull, eInc, eExp, eShare, eSent, eRec = spGetTeamResources(team, "energy")
  252. local mCur, mMax, mPull, mInc, mExp, mShare, mSent, mRec = spGetTeamResources(team, "metal")
  253.  
  254. averagedEcon.mStor = mMax -- m storage
  255.  
  256. --// average the resourcing over the past few updates to reduce sharp spikes and dips
  257. -- update previous frame info
  258. for i = econAverageMemory, 2, -1 do
  259. averagedEcon.prevEcon[i] = averagedEcon.prevEcon[i-1]
  260. end
  261. averagedEcon.prevEcon[1] = {eInc = eInc, mInc = mInc, activeBp = mPull}
  262. -- calculate average
  263. averagedEcon.aveEInc = 0
  264. averagedEcon.aveMInc = 0
  265. averagedEcon.aveActiveBP = 0
  266. for i = 1,econAverageMemory do
  267. averagedEcon.aveEInc = averagedEcon.aveEInc + averagedEcon.prevEcon[i].eInc
  268. averagedEcon.aveMInc = averagedEcon.aveMInc + averagedEcon.prevEcon[i].mInc
  269. averagedEcon.aveActiveBP = averagedEcon.aveActiveBP + averagedEcon.prevEcon[i].activeBp
  270. end
  271. averagedEcon.aveEInc = averagedEcon.aveEInc/econAverageMemory
  272. averagedEcon.aveMInc = averagedEcon.aveMInc/econAverageMemory
  273. averagedEcon.aveActiveBP = averagedEcon.aveActiveBP/econAverageMemory
  274. averagedEcon.mCur = mCur
  275. averagedEcon.eCur = eCur
  276.  
  277. if averagedEcon.aveMInc > 0 then
  278. averagedEcon.energyToMetalRatio = averagedEcon.aveEInc/averagedEcon.aveMInc
  279. averagedEcon.activeBpToMetalRatio = averagedEcon.aveActiveBP/averagedEcon.aveMInc
  280. else
  281. averagedEcon.energyToMetalRatio = false
  282. averagedEcon.activeBpToMetalRatio = false
  283. end
  284. end
  285.  
  286. -- changes the weighting on con and factory jobs based on brain, can do other things too
  287. local function executeControlFunction(team, frame)
  288.  
  289. local a = aiTeamData[team]
  290. local at = allyTeamData[a.allyTeam]
  291.  
  292. local averagedEcon = a.averagedEcon
  293.  
  294. if averagedEcon.energyToMetalRatio then
  295.  
  296. a.controlFunction(a, at, frame)
  297.  
  298. end -- no metal income no con
  299.  
  300. normaliseImportance(a.facJob)
  301. normaliseImportance(a.conJob)
  302.  
  303. end
  304.  
  305. -- allocates the con so the BP factors of each job matches the allocated importance of each job
  306. local function conJobAllocator(team)
  307. --Spring.Echo{"totalBP: " .. totalBP}
  308. local a = aiTeamData[team]
  309. local conJob = a.conJob
  310. local conJobByIndex = a.conJobByIndex
  311. local unassignedCons = a.unassignedCons
  312. local controlledUnit = a.controlledUnit
  313.  
  314. local lackingCons = {} -- jobs that need more cons
  315. local lackingConCount = 0 -- number of jobs that need more cons
  316.  
  317. -- remove con from jobs with too much BP
  318. for _,data in pairs(conJob) do
  319. data.bpChange = data.importance*a.totalBP - data.assignedBP
  320. local changed = true
  321. while (changed and data.bpChange <= -4.8) do
  322. changed = false
  323. for unitID,_ in pairs(data.con) do
  324. --if controlledUnit.conByID[unitID].bp <= -data.bpChange then
  325. data.bpChange = data.bpChange + controlledUnit.conByID[unitID].bp
  326. data.con[unitID] = nil
  327. data.assignedBP = data.assignedBP - controlledUnit.conByID[unitID].bp
  328. unassignedCons.count = unassignedCons.count + 1
  329. unassignedCons[unassignedCons.count] = unitID
  330. changed = true
  331. break
  332. --end
  333. end
  334. end
  335.  
  336. if data.bpChange > 0 then
  337. lackingConCount = lackingConCount + 1
  338. lackingCons[lackingConCount] = data
  339. end
  340. end
  341.  
  342. -- add con to jobs with not enough BP
  343. while unassignedCons.count > 0 do
  344. local largestChange = 0
  345. local largestID = -1
  346.  
  347. -- find the job with the largest bp need
  348. for i = 1,lackingConCount do
  349. if lackingCons[i].bpChange > largestChange then
  350. largestID = i
  351. largestChange = lackingCons[i].bpChange
  352. end
  353. end
  354.  
  355. -- add a con to the job with the largest bp need
  356. if largestID ~= -1 then
  357. local data = lackingCons[largestID]
  358. local i = unassignedCons.count
  359. local unitID = unassignedCons[i]
  360. local oldConJob = conJobByIndex[controlledUnit.conByID[unitID].oldJob]
  361.  
  362. -- find the closest free con
  363. while oldConJob and oldConJob.location ~= data.location and i > 1 do
  364. i = i-1
  365. unitID = unassignedCons[i]
  366. oldConJob = conJobByIndex[controlledUnit.conByID[testUnitID].oldJob]
  367. end
  368.  
  369. data.bpChange = data.bpChange - controlledUnit.conByID[unitID].bp
  370. data.con[unitID] = true
  371. data.assignedBP = data.assignedBP + controlledUnit.conByID[unitID].bp
  372.  
  373. if controlledUnit.conByID[unitID].oldJob > 0 and controlledUnit.conByID[unitID].oldJob ~= data.index then
  374. if conJobByIndex[controlledUnit.conByID[unitID].oldJob].interruptable then
  375. controlledUnit.conByID[unitID].idle = true
  376. end
  377. end
  378. controlledUnit.conByID[unitID].currentJob = data.index
  379.  
  380. removeIndexFromArray(unassignedCons,i)
  381.  
  382. --mapEcho(unitID,"con added to " .. data.name)
  383. else
  384. Spring.Echo("broke 'add con to jobs with not enough BP'") -- should not happen
  385. break
  386. end
  387. end
  388.  
  389. end
  390.  
  391. -- faces the building towards the centre of the map - facing values { S = 0, E = 1, N = 2, W = 3 }
  392. local function getBuildFacing(left,top)
  393.  
  394. local right = mapWidth - left
  395. local bottom = mapHeight - top
  396.  
  397. if right < top then
  398. if right < left then
  399. if right < bottom then
  400. return 3
  401. else
  402. return 2
  403. end
  404. else
  405. if left < bottom then
  406. return 1
  407. else
  408. return 2
  409. end
  410. end
  411. else
  412. if top < left then
  413. if top < bottom then
  414. return 0
  415. else
  416. return 2
  417. end
  418. else
  419. if left < bottom then
  420. return 1
  421. else
  422. return 2
  423. end
  424. end
  425. end
  426. end
  427.  
  428. -- returns if position is within distance of a unit in unit array
  429. local function nearRadar(team,tx,tz,distance)
  430.  
  431. local unitArray = allyTeamData[aiTeamData[team].allyTeam].units.radarByID
  432.  
  433. for unitID,_ in pairs(unitArray) do
  434. local x,_,z = spGetUnitPosition(unitID)
  435. if disSQ(x,z,tx,tz) < distance^2 then
  436. return true
  437. end
  438. end
  439. return false
  440. end
  441.  
  442. -- returns if position is within distance of a unit in unit array
  443. local function nearFactory(team,tx,tz,distance)
  444.  
  445. local unitArray = allyTeamData[aiTeamData[team].allyTeam].units.factoryByID
  446.  
  447. for unitID, data in pairs(unitArray) do
  448. --mapEcho(unitID,"factroyChecked")
  449. local x,_,z = spGetUnitPosition(unitID)
  450. if disSQ(x,z,tx,tz) < distance^2 then
  451. return true
  452. end
  453. if disSQ(data.wayX,data.wayZ,tx,tz) < (distance*1.5)^2 then
  454. return true
  455. end
  456. end
  457. return false
  458. end
  459.  
  460. -- returns if position is within distance of a unit in unit array
  461. local function nearEcon(team,tx,tz,distance)
  462.  
  463. local unitArray = allyTeamData[aiTeamData[team].allyTeam].units.econByID
  464. for unitID,_ in pairs(unitArray) do
  465. local x,_,z = spGetUnitPosition(unitID)
  466. if disSQ(x,z,tx,tz) < distance^2 then
  467. return true
  468. end
  469. end
  470. return false
  471. end
  472.  
  473. -- returns if position is within distance of a mex spot
  474. local function nearMexSpot(tx,tz,distance)
  475.  
  476. for i = 1, mexSpot.count do
  477. local x = mexSpot[i].x
  478. local z = mexSpot[i].z
  479. if disSQ(x,z,tx,tz) < distance^2 then
  480. return true
  481. end
  482. end
  483. return false
  484. end
  485.  
  486. -- returns if position is within distance of defence structure or wanted defence location
  487. local function nearDefence(team,tx,tz,distance)
  488.  
  489. local a = aiTeamData[team]
  490. local unitArray = allyTeamData[aiTeamData[team].allyTeam].units.turretByID
  491.  
  492. for unitID,_ in pairs(unitArray) do
  493. local x,_,z = spGetUnitPosition(unitID)
  494. if disSQ(x,z,tx,tz) < distance^2 then
  495. return true
  496. end
  497. end
  498.  
  499. for i = 1, a.wantedDefence.count do
  500. if disSQ(a.wantedDefence[i].x,a.wantedDefence[i].z,tx,tz) < distance^2 then
  501. return true
  502. end
  503. end
  504.  
  505. return false
  506. end
  507.  
  508. -- checks if the location is within distance of map edge
  509. local function nearMapEdge(tx,tz,distance)
  510. if tx <= distance or tz <= distance or mapWidth - tx <= distance or mapHeight - tz <= distance then
  511. return true
  512. end
  513. return false
  514. end
  515.  
  516. -- makes defence in response to an enemy
  517. local function makeReponsiveDefence(team,unitID,eid,eUnitDefID,aidSearchRange)
  518.  
  519. local a = aiTeamData[team]
  520. local turretByID = a.controlledUnit.turretByID
  521. local conByID = a.controlledUnit.conByID
  522. local buildDefs = a.buildDefs
  523.  
  524. local eud = UnitDefs[eUnitDefID]
  525. local ux,uy,uz = spGetUnitPosition(unitID)
  526.  
  527. -- check for nearby nanoframes
  528. for tid,data in pairs(turretByID) do
  529. local tx,_,tz = spGetUnitPosition(tid)
  530. if (not data.finished) and disSQ(ux,uz,tx,tz) < aidSearchRange^2 and not data.air then
  531. spGiveOrderToUnit(unitID, CMD_REPAIR, {tid}, {})
  532. conByID[unitID].makingDefence = true
  533. return
  534. end
  535. end
  536.  
  537. local ex,ey,ez = spGetUnitPosition(eid)
  538. local defenceDef = buildDefs.defenceIds[1][1].ID
  539.  
  540. if eud.speed > 0 then
  541.  
  542. local range = eud.maxWeaponRange
  543. if range > 500 then
  544. return
  545. end
  546.  
  547. local searchRange = 40
  548.  
  549. local x = ux + math.random(-searchRange,searchRange)
  550. local z = uz + math.random(-searchRange,searchRange)
  551.  
  552. while spTestBuildOrder(defenceDef, x, 0 ,z, 1) == 0 or nearMexSpot(x,z,60) or nearFactory(team,x,z,200) do
  553. x = ux + math.random(-searchRange,searchRange)
  554. z = uz + math.random(-searchRange,searchRange)
  555. searchRange = searchRange + 10
  556. if searchRange > 400 then
  557. return
  558. end
  559. end
  560.  
  561. conByID[unitID].makingDefence = true
  562. spGiveOrderToUnit(unitID, -defenceDef, {x,0,z}, {})
  563. else
  564.  
  565. local vectorX = ex - ux
  566. local vectorZ = ez - uz
  567. local vectorMag = math.sqrt(disSQ(0,0,vectorX,vectorZ))
  568. if vectorMag == 0 then
  569. return
  570. end
  571. vectorX = vectorX/vectorMag
  572. vectorZ = vectorZ/vectorMag
  573.  
  574. local range = eud.maxWeaponRange
  575. if range < 520 then
  576. if math.random() < 0.8 then
  577. defenceDef = buildDefs.defenceIds[1][2].ID -- build MT
  578. end
  579. end
  580.  
  581. spGiveOrderToUnit(unitID, CMD_MOVE, { ex - vectorX*(range+200), 0, ez - vectorZ*(range+200)}, {})
  582.  
  583. local bx = ex - vectorX*(range+70)
  584. local bz = ez - vectorZ*(range+70)
  585.  
  586. local searchRange = 30
  587. while spTestBuildOrder(defenceDef, bx, 0 ,bz, 1) == 0 or nearMexSpot(bx,bz,60) or nearFactory(team,bx,bz,200) do
  588. bx = ex + vectorZ*math.random(-searchRange,searchRange)
  589. bz = ez + vectorX*math.random(-searchRange,searchRange)
  590. searchRange = searchRange + 5
  591. if searchRange > 100 then
  592. return
  593. end
  594. end
  595.  
  596. conByID[unitID].makingDefence = true
  597. spGiveOrderToUnit(unitID, -defenceDef, { bx, 0, bz}, CMD_OPT_SHIFT)
  598. end
  599. end
  600.  
  601. local function runAway(unitID, enemyID, range)
  602.  
  603. local ux,uy,uz = spGetUnitPosition(unitID)
  604. local ex,ey,ez = spGetUnitPosition(enemyID)
  605.  
  606. local vectorX = ex - ux
  607. local vectorZ = ez - uz
  608. local vectorMag = math.sqrt(disSQ(0,0,vectorX,vectorZ))
  609. if vectorMag == 0 then
  610. return
  611. end
  612.  
  613. vectorX = vectorX/vectorMag
  614. vectorZ = vectorZ/vectorMag
  615.  
  616. spGiveOrderToUnit(unitID, CMD_MOVE, { ex - vectorX*range, 0, ez - vectorZ*range}, {})
  617. end
  618.  
  619. -- makes defence using wantedDefence position
  620. local function makeWantedDefence(team,unitID,searchRange, maxDistance, priorityDistance)
  621. local a = aiTeamData[team]
  622. local wantedDefence = a.wantedDefence
  623. local turretByID = a.controlledUnit.turretByID
  624.  
  625. local x,y,z = spGetUnitPosition(unitID)
  626.  
  627. -- check for nearby nanoframes
  628. for tid,data in pairs(turretByID) do
  629. local tx,_,tz = spGetUnitPosition(tid)
  630. if (not data.finished) and disSQ(x,z,tx,tz) < searchRange^2 and not data.air then
  631. spGiveOrderToUnit(unitID, CMD_REPAIR, {tid}, {})
  632. return true
  633. end
  634. end
  635.  
  636.  
  637. local minDefDisSQ = false
  638. local minPriority = 0
  639. local minDeftID = 0
  640.  
  641. x = x + math.random(-searchRange,searchRange)
  642. z = z + math.random(-searchRange,searchRange)
  643.  
  644. for i = 1, wantedDefence.count do
  645. if spTestBuildOrder(wantedDefence[i].ID, wantedDefence[i].x, 0 ,wantedDefence[i].z, 1) ~= 0 then
  646. local dis = disSQ(wantedDefence[i].x,wantedDefence[i].z,x,z)
  647. if ((not maxDistance) or maxDistance^2 < dis) and minPriority == wantedDefence[i].priority then
  648. if ((not minDefDisSQ) or dis < minDefDisSQ) then
  649. minDefDisSQ = dis
  650. minDeftID = i
  651. end
  652. elseif ((not priorityDistance) or dis < priorityDistance^2) and minPriority < wantedDefence[i].priority then
  653. minDefDisSQ = dis
  654. minDeftID = i
  655. minPriority = wantedDefence[i].priority
  656. end
  657. else
  658. removeIndexFromArray(wantedDefence,i)
  659. break
  660. end
  661. end
  662.  
  663. if minDeftID ~= 0 then
  664. spGiveOrderToUnit(unitID, -wantedDefence[minDeftID].ID, {wantedDefence[minDeftID].x,0,wantedDefence[minDeftID].z}, {})
  665. return true
  666. else
  667. return false
  668. --Spring.Echo("No defence to make")
  669. end
  670. end
  671.  
  672. -- makes air defence directly using the air defence heatmap
  673. local function makeAirDefence(team,unitID, searchRange,maxDistance)
  674.  
  675. local a = aiTeamData[team]
  676. local turretByID = a.controlledUnit.turretByID
  677. local selfDefenceAirTask = a.selfDefenceAirTask
  678. local selfDefenceHeatmap = a.selfDefenceHeatmap
  679. local buildDefs = a.buildDefs
  680.  
  681. local x,y,z = spGetUnitPosition(unitID)
  682. -- check for nearby nanoframes
  683. for tid,data in pairs(turretByID) do
  684. local tx,_,tz = spGetUnitPosition(tid)
  685. if (not data.finished) and disSQ(x,z,tx,tz) < searchRange^2 and data.air then
  686. spGiveOrderToUnit(unitID, CMD_REPAIR, {tid}, {})
  687. return true
  688. end
  689. end
  690.  
  691. local minDisSQ = false
  692. local minID = 0
  693.  
  694. --x = x + math.random(-searchRange,searchRange)
  695. --z = z + math.random(-searchRange,searchRange)
  696. for i = 1, selfDefenceAirTask.count do
  697. local dis = disSQ(selfDefenceAirTask[i].x,selfDefenceAirTask[i].z,x,z)
  698. if ((not minDisSQ) or dis < minDisSQ) and ((not maxDistance) or maxDistance^2 < dis) then
  699. minDisSQ = dis
  700. minID = i
  701. end
  702. end
  703.  
  704. if minID ~= 0 then
  705. local aX = selfDefenceAirTask[minID].aX
  706. local aZ = selfDefenceAirTask[minID].aZ
  707. local data = selfDefenceHeatmap[aX][aZ]
  708. local defIndex = 0
  709.  
  710. for i = buildDefs.airDefenceIdCount, 1, -1 do
  711. if data[i].air >= 1 then
  712. defIndex = i
  713. break
  714. end
  715. end
  716.  
  717. if defIndex == 0 then
  718. removeIndexFromArray(selfDefenceAirTask,minID)
  719. return false
  720. end
  721.  
  722. local deID = chooseUnitDefID(buildDefs.airDefenceIds[defIndex])
  723.  
  724. local r = buildDefs.airDefenceRange[defIndex]
  725. local theta = math.random(twoPi)
  726.  
  727. local ox = selfDefenceAirTask[minID].x
  728. local oz = selfDefenceAirTask[minID].z
  729.  
  730. local x,y,z = spGetUnitPosition(unitID)
  731.  
  732. local vectorX = mapWidth*0.5 - ox
  733. local vectorZ = mapHeight*0.5 - oz
  734. local vectorMag = math.sqrt(disSQ(0,0,vectorX,vectorZ))
  735. if vectorMag == 0 then
  736. vectorMag = 1
  737. end
  738. local bx = ox + vectorX*r/vectorMag
  739. local bz = oz + vectorZ*r/vectorMag
  740. --Spring.MarkerAddLine(bx,0,bz,ox,0,oz)
  741.  
  742. local searches = 0
  743.  
  744. while spTestBuildOrder(deID, bx, 0 ,bz, 1) == 0 or nearFactory(team,bx,bz,250) or nearMexSpot(bx,bz,60) or nearMapEdge(bx,bz,300) do
  745. theta = math.random(twoPi)
  746. bx = ox + r*math.sin(theta)
  747. bz = oz + r*math.cos(theta)
  748. searches = searches + 1
  749. if searches > 15 then
  750. return false
  751. end
  752. end
  753.  
  754. data[defIndex].air = data[defIndex].air - 1
  755. spGiveOrderToUnit(unitID, -deID, {bx,0,bz}, {})
  756.  
  757. local empty = true
  758. for i = 1, buildDefs.airDefenceIdCount do
  759. if data[i].air >= 0 then
  760. empty = false
  761. break
  762. end
  763. end
  764.  
  765. if empty then
  766. removeIndexFromArray(selfDefenceAirTask,minID)
  767. end
  768.  
  769. return true
  770. else
  771. return false
  772. --Spring.Echo("No defence to make")
  773. end
  774. end
  775.  
  776. local function makeMiscBuilding(team, unitID, defId, searchRange, maxRange)
  777.  
  778. local a = aiTeamData[team]
  779. local at = allyTeamData[a.allyTeam]
  780. local anyByID = a.controlledUnit.anyByID
  781.  
  782. local ux,uy,uz = spGetUnitPosition(unitID)
  783. -- check for nearby nanoframes
  784. for id,data in pairs(anyByID) do
  785. if data.ud.id == defId and (not data.finished) then
  786. local x,_,z = spGetUnitPosition(id)
  787. if disSQ(ux,uz,x,z) < maxRange^2 then
  788. spGiveOrderToUnit(unitID, CMD_REPAIR, {id}, {})
  789. return true
  790. end
  791. end
  792. end
  793.  
  794. x = ux + math.random(-searchRange,searchRange)
  795. z = uz + math.random(-searchRange,searchRange)
  796.  
  797. while spTestBuildOrder(defId, x, 0 ,z, 1) == 0 or nearFactory(team,x,z,250) or nearMexSpot(x,z,100) do
  798. x = ux + math.random(-searchRange,searchRange)
  799. z = uz + math.random(-searchRange,searchRange)
  800. searchRange = searchRange + 10
  801. if searchRange > maxRange then
  802. return false
  803. end
  804. end
  805.  
  806. spGiveOrderToUnit(unitID, -defId, {x,0,z}, {})
  807. return true
  808. end
  809.  
  810. local function makeRadar(team, unitID, searchRange, minDistance)
  811.  
  812. local a = aiTeamData[team]
  813. local at = allyTeamData[a.allyTeam]
  814. local radar = a.controlledUnit.radar
  815. local radarByID = a.controlledUnit.radarByID
  816. local buildDefs = a.buildDefs
  817.  
  818. local ux,uy,uz = spGetUnitPosition(unitID)
  819. -- check for nearby nanoframes - helps with ally radar too!
  820. for rid,_ in pairs(at.units.radarByID) do
  821. local x,_,z = spGetUnitPosition(rid)
  822. if disSQ(ux,uz,x,z) < minDistance^2 then
  823. spGiveOrderToUnit(unitID, CMD_REPAIR, {rid}, {})
  824. return true
  825. end
  826. end
  827.  
  828. -- start my on construction
  829. local radarDefID = buildDefs.radarIds[1].ID
  830.  
  831. x = ux + math.random(-searchRange,searchRange)
  832. z = uz + math.random(-searchRange,searchRange)
  833.  
  834. while spTestBuildOrder(radarDefID, x, 0 ,z, 1) == 0 or nearFactory(team,x,z,250) or nearMexSpot(x,z,60) do
  835. x = ux + math.random(-searchRange,searchRange)
  836. z = uz + math.random(-searchRange,searchRange)
  837. searchRange = searchRange + 10
  838. if searchRange > minDistance then
  839. return false
  840. end
  841. end
  842.  
  843. spGiveOrderToUnit(unitID, -radarDefID, {x,0,z}, {})
  844. return true
  845. end
  846.  
  847. -- queues nearest mex, checks for already under construction mexes
  848. local function makeMex(team, unitID)
  849.  
  850. local a = aiTeamData[team]
  851. local mex = a.controlledUnit.mex
  852. local mexByID = a.controlledUnit.mexByID
  853. local buildDefs = a.buildDefs
  854.  
  855. local x,y,z = spGetUnitPosition(unitID)
  856.  
  857. -- check for nearby nanoframes
  858. for i = 1, mex.count do
  859. local mid = mex[i]
  860. local ux = mexByID[mid].x
  861. local uz = mexByID[mid].z
  862. if (not mexByID[mid].finished) and disSQ(x,z,ux,uz) < 1000^2 then
  863. spGiveOrderToUnit(unitID, CMD_REPAIR, {mid}, {})
  864. return
  865. end
  866. end
  867.  
  868. x = x + math.random(-200,200)
  869. z = z + math.random(-200,200)
  870.  
  871. local minMexSpotDisSQ = false
  872. local minMexSpotID = 0
  873.  
  874. for i = 1, mexSpot.count do
  875. if CallAsTeam(team, function () return spTestBuildOrder(buildDefs.mexIds[1].ID, mexSpot[i].x, 0 ,mexSpot[i].z, 1) ~= 0 end) then
  876. local dis = disSQ(mexSpot[i].x,mexSpot[i].z,x,z)
  877. if (not minMexSpotDisSQ) or dis < minMexSpotDisSQ then
  878. minMexSpotDisSQ = dis
  879. minMexSpotID = i
  880. end
  881. end
  882. end
  883.  
  884. if minMexSpotID ~= 0 then
  885. spGiveOrderToUnit(unitID, -buildDefs.mexIds[1].ID, {mexSpot[minMexSpotID].x,0,mexSpot[minMexSpotID].z}, {})
  886. else
  887. --Spring.Echo("No free mex spots")
  888. end
  889.  
  890. end
  891.  
  892.  
  893. -- makes a nano turret for nearest factory
  894. local function makeNano(team,unitID)
  895.  
  896. local a = aiTeamData[team]
  897. local factory = a.controlledUnit.factory
  898. local nano = a.controlledUnit.nano
  899. local nanoDefID = a.buildDefs.nanoDefID
  900. local nanoRangeSQ = (UnitDefs[nanoDefID].buildDistance-60)^2
  901.  
  902. local ux,uy,uz = spGetUnitPosition(unitID)
  903.  
  904. -- check for nearby nano frames
  905. for i = 1, nano.count do
  906. local nid = nano[i]
  907. local data = a.controlledUnit.nanoByID[nid]
  908. if (not a.controlledUnit.nanoByID[nid].finished) and disSQ(data.x,data.z,ux,uz) < 1000^2 then
  909. spGiveOrderToUnit(unitID, CMD_REPAIR, {nid}, {})
  910. return
  911. end
  912. end
  913.  
  914. closestFactory = false
  915. minDis = false
  916. minNanoCount = false
  917.  
  918. for i = 1, factory.count do
  919. local fid = factory[i]
  920. local data = a.controlledUnit.factoryByID[fid]
  921. local dis = disSQ(data.x, data.z, ux, uz)
  922. if (not minDis) or dis < minDis then
  923. closestFactory = fid
  924. minDis = dis
  925. end
  926. if (not minNanoCount) or data.nanoCount < minNanoCount then
  927. minNanoCount = data.nanoCount
  928. end
  929. end
  930.  
  931. if not minNanoCount then
  932. return
  933. end
  934.  
  935. local data = a.controlledUnit.factoryByID[closestFactory]
  936.  
  937. if minNanoCount ~= data.nanoCount then
  938. return
  939. end
  940.  
  941. local searchRange = 0
  942.  
  943. x = data.nanoX + math.random(-searchRange,searchRange)
  944. z = data.nanoZ + math.random(-searchRange,searchRange)
  945.  
  946. while spTestBuildOrder(nanoDefID, x, 0 ,z, 1) == 0 or disSQ(data.x, data.z, x ,z) > nanoRangeSQ do
  947. x = data.nanoX + math.random(-searchRange,searchRange)
  948. z = data.nanoZ + math.random(-searchRange,searchRange)
  949. searchRange = searchRange + 20
  950. if searchRange > 250 then
  951. return
  952. end
  953. end
  954.  
  955. spGiveOrderToUnit(unitID, -nanoDefID, {x,0,z}, {})
  956. end
  957.  
  958. -- queues energy order or helps nearby construction
  959. local function makeEnergy(team,unitID)
  960.  
  961. local a = aiTeamData[team]
  962. local econ = a.controlledUnit.econ
  963. local econByID = a.controlledUnit.econByID
  964. local conByID = a.controlledUnit.conByID
  965. local averagedEcon = a.averagedEcon
  966. local conJob = a.conJob
  967. local buildDefs = a.buildDefs
  968.  
  969. local ux,uy,uz = spGetUnitPosition(unitID)
  970.  
  971. -- check for nearby nanoframes
  972. for i = 1, econ.count do
  973. local eid = econ[i]
  974. local x = econByID[eid].x
  975. local z = econByID[eid].z
  976. if (not econByID[eid].finished) and disSQ(x,z,ux,uz) < 1000^2 then
  977. spGiveOrderToUnit(unitID, CMD_REPAIR, {eid}, {})
  978. return
  979. end
  980. end
  981.  
  982. -- check for nearby con
  983. for cid,_ in pairs(conJob.energy.con) do
  984. local cQueue = spGetCommandQueue(cid)
  985. local cx,cy,cz = spGetUnitPosition(cid)
  986. if #cQueue > 0 and disSQ(cx,cz,ux,uz) < 800^2 then
  987. for i = 1, buildDefs.energyIds.count do
  988. if cQueue[1].id == buildDefs.energyIds[i].ID then
  989. spGiveOrderToUnit(unitID, CMD_GUARD, {cid}, {})
  990. conByID[unitID].idle = true
  991. return
  992. end
  993. end
  994. end
  995. end
  996.  
  997. -- start my own construction
  998. local energyDefID = buildDefs.energyIds[buildDefs.energyIds.count].ID
  999.  
  1000. for i = 1, buildDefs.energyIds.count do
  1001. local udID = buildDefs.energyIds[i].ID
  1002. if averagedEcon.aveEInc >= buildDefs.econByDefId[udID].energyGreaterThan and
  1003. ((not buildDefs.econByDefId[udID].makeNearFactory) or nearFactory(team,ux,uz,buildDefs.econByDefId[udID].makeNearFactory)) and
  1004. (averagedEcon.eCur > 200 or buildDefs.econByDefId[udID].whileStall) and buildDefs.econByDefId[udID].chance > math.random() then
  1005. energyDefID = udID
  1006. break
  1007. end
  1008. end
  1009.  
  1010. local searchRange = 120
  1011. local minDistance = 100
  1012.  
  1013. x = ux + math.random(minDistance,searchRange)*math.ceil(math.random(-1,1))
  1014. z = uz + math.random(minDistance,searchRange)*math.ceil(math.random(-1,1))
  1015.  
  1016. while spTestBuildOrder(energyDefID, x, 0 ,z, 1) == 0 or nearFactory(team,x,z,250) or nearMexSpot(x,z,60) or nearEcon(team,x,z, buildDefs.econByDefId[energyDefID].energySpacing) do
  1017. x = ux + math.random(minDistance,searchRange)*math.ceil(math.random(-1,1))
  1018. z = uz + math.random(minDistance,searchRange)*math.ceil(math.random(-1,1))
  1019. searchRange = searchRange + 10
  1020. if searchRange > 500 then
  1021. x = ux + math.random(-700,700)
  1022. z = uz + math.random(-700,700)
  1023. spGiveOrderToUnit(unitID, CMD_MOVE, { x , 0, z },{})
  1024. return
  1025. end
  1026. end
  1027.  
  1028. spGiveOrderToUnit(unitID, -energyDefID, {x,0,z}, {})
  1029.  
  1030. end
  1031.  
  1032. -- assigns con to a factory or builds a new one
  1033. local function assignFactory(team,unitID,cQueue)
  1034.  
  1035. local a = aiTeamData[team]
  1036. local controlledUnit = a.controlledUnit
  1037. local conJob = a.conJob
  1038. local buildDefs = a.buildDefs
  1039.  
  1040. if #cQueue == 0 or not buildDefs.factoryByDefId[-cQueue[1].id] then
  1041. if (a.totalBP < a.totalFactoryBPQuota or a.uncompletedFactory ~= false) and controlledUnit.factory.count > 0 then
  1042.  
  1043. --local assistAir = (math.random() < conJob.factory.airFactor)
  1044.  
  1045. local randIndex = math.floor(math.random(1,controlledUnit.factory.count))
  1046. local facID = controlledUnit.factory[randIndex]
  1047. if a.uncompletedFactory ~= true and a.uncompletedFactory ~= false then
  1048. facID = a.uncompletedFactory
  1049. end
  1050.  
  1051. local x, y, z = spGetUnitPosition(facID)
  1052. local fd = controlledUnit.factoryByID[facID].ud
  1053.  
  1054. if (not x) then
  1055. return
  1056. end
  1057.  
  1058. local radius = spGetUnitRadius(facID)
  1059. if (not radius) then
  1060. return
  1061. end
  1062. local dist = radius * 2
  1063.  
  1064. local frontDis = fd.xsize*4+32 -- 4 to edge
  1065. local sideDis = ((fd.zsize or fd.ysize)*4+32)
  1066.  
  1067. if frontDis > dist then
  1068. dist = frontDis
  1069. end
  1070.  
  1071. if sideDis > dist then
  1072. dist = sideDis
  1073. end
  1074.  
  1075. local facing = spGetUnitBuildFacing(facID)
  1076. if (not facing) then
  1077. return
  1078. end
  1079. -- facing values { S = 0, E = 1, N = 2, W = 3 }
  1080.  
  1081. dist = dist * (math.floor(math.random(0,1))*2-1)
  1082. if (facing == 0) or (facing == 2) then
  1083. spGiveOrderToUnit(unitID, CMD_MOVE, { x + dist, y, z },{})
  1084. else
  1085. spGiveOrderToUnit(unitID, CMD_MOVE, { x , y, z + dist},{})
  1086. end
  1087.  
  1088.  
  1089. spGiveOrderToUnit(unitID, CMD_GUARD, {facID},CMD_OPT_SHIFT)
  1090. else
  1091.  
  1092. local buildableFactoryCount = 0
  1093. local buildableFactory = {}
  1094. local totalImportance = 0
  1095.  
  1096. if math.random() < conJob.factory.airFactor then
  1097. for id,data in pairs(buildDefs.factoryByDefId) do
  1098. if data.airFactory and data.minFacCount <= controlledUnit.factory.count and ((not a.factoryCountByDefID[id]) or a.factoryCountByDefID[id] == 0) then
  1099. buildableFactoryCount = buildableFactoryCount + 1
  1100. buildableFactory[buildableFactoryCount] = {ID = id, importance = data.importance}
  1101. totalImportance = totalImportance + data.importance
  1102. end
  1103. end
  1104. else
  1105. for id,data in pairs(buildDefs.factoryByDefId) do
  1106. if (not data.airFactory) and data.minFacCount <= controlledUnit.factory.count and ((not a.factoryCountByDefID[id]) or a.factoryCountByDefID[id] == 0) then
  1107. buildableFactoryCount = buildableFactoryCount + 1
  1108. buildableFactory[buildableFactoryCount] = {ID = id, importance = data.importance}
  1109. totalImportance = totalImportance + data.importance
  1110. end
  1111. end
  1112. end
  1113.  
  1114. if buildableFactoryCount == 0 then
  1115. for id,data in pairs(buildDefs.factoryByDefId) do
  1116. if data.minFacCount <= controlledUnit.factory.count and ((not a.factoryCountByDefID[id]) or a.factoryCountByDefID[id] == 0) then
  1117. buildableFactoryCount = buildableFactoryCount + 1
  1118. buildableFactory[buildableFactoryCount] = {ID = id, importance = data.importance}
  1119. totalImportance = totalImportance + data.importance
  1120. end
  1121. end
  1122. end
  1123.  
  1124. if buildableFactoryCount == 0 then
  1125. for id,data in pairs(buildDefs.factoryByDefId) do
  1126. buildableFactoryCount = buildableFactoryCount + 1
  1127. buildableFactory[buildableFactoryCount] = {ID = id, importance = data.importance}
  1128. totalImportance = totalImportance + data.importance
  1129. end
  1130. end
  1131.  
  1132. local choice = 1
  1133. local rand = math.random()*totalImportance
  1134. local total = 0
  1135. for i = 1, buildableFactoryCount do
  1136. total = total + buildableFactory[i].importance
  1137. if rand < total then
  1138. choice = i
  1139. break
  1140. end
  1141. end
  1142.  
  1143. local ux,uy,uz = spGetUnitPosition(unitID)
  1144. local searchRange = 200
  1145. x = ux + math.random(-searchRange,searchRange)
  1146. z = uz + math.random(-searchRange,searchRange)
  1147. while spTestBuildOrder(buildableFactory[choice].ID, x, 0 ,z, 1) == 0 or nearMexSpot(x,z,160) or nearEcon(team,x,z,200) or nearMapEdge(x,z,600) or nearFactory(team,x,z,1000) or nearDefence(team,x,z,120) do
  1148.  
  1149. x = ux + math.random(-searchRange,searchRange)
  1150. z = uz + math.random(-searchRange,searchRange)
  1151. searchRange = searchRange + 10
  1152. if searchRange > 2000 then
  1153. return
  1154. end
  1155. end
  1156. a.uncompletedFactory = true
  1157. spGiveOrderToUnit(unitID, -buildableFactory[choice].ID, {x,0,z,getBuildFacing(x,z)}, {})
  1158. end
  1159. end
  1160.  
  1161. end
  1162.  
  1163.  
  1164. -- updates the con state based on their current job
  1165. local function conJobHandler(team)
  1166.  
  1167. local a = aiTeamData[team]
  1168. local conJob = a.conJob
  1169. local controlledUnit = a.controlledUnit
  1170. local buildDefs = a.buildDefs
  1171. --[[
  1172. for id, data in pairs(conJob) do
  1173. Spring.Echo(data.name .. " importance " .. data.importance)
  1174. end
  1175. --]]
  1176. -- reclaim
  1177. for unitID,_ in pairs(conJob.reclaim.con) do
  1178. local cQueue = spGetCommandQueue(unitID)
  1179. if #cQueue == 0 or controlledUnit.conByID[unitID].idle then
  1180. controlledUnit.conByID[unitID].idle = false
  1181. controlledUnit.conByID[unitID].makingDefence = false
  1182. controlledUnit.conByID[unitID].oldJob = conJob.reclaim.index
  1183. spGiveOrderToUnit(unitID, CMD_RECLAIM, {mapWidth/2,0,mapHeight/2,areaCommandRadius}, {})
  1184. end
  1185. end
  1186.  
  1187. -- defence
  1188. for unitID,_ in pairs(conJob.defence.con) do
  1189. local cQueue = spGetCommandQueue(unitID)
  1190. if #cQueue == 0 or controlledUnit.conByID[unitID].idle then
  1191. local x,y,z = spGetUnitPosition(unitID)
  1192. controlledUnit.conByID[unitID].oldJob = conJob.defence.index
  1193. controlledUnit.conByID[unitID].idle = false
  1194. if not
  1195. (
  1196. (
  1197. (
  1198. (not nearRadar(team,x,z,1600))
  1199. or
  1200. (not spIsPosInRadar(x,y,z,a.allyTeam))
  1201. )
  1202. and
  1203. math.random() < conJob.defence.radarChance
  1204. and
  1205. makeRadar(team, unitID, 400, 400)
  1206. )
  1207. or
  1208. (
  1209. math.random() < conJob.defence.airChance
  1210. and
  1211. makeAirDefence(team,unitID,1000,false)
  1212. )
  1213. or
  1214. (
  1215. math.random() < conJob.defence.airpadChance
  1216. and
  1217. makeMiscBuilding(team,unitID,buildDefs.airpadDefID,200,1000)
  1218. )
  1219. or
  1220. (
  1221. math.random() < conJob.defence.metalStorageChance
  1222. and
  1223. makeMiscBuilding(team,unitID,buildDefs.metalStoreDefID,200,1000)
  1224. )
  1225. )
  1226. then
  1227. makeWantedDefence(team,unitID,1000,false, 1500)
  1228. end
  1229. end
  1230. end
  1231.  
  1232. -- mex
  1233. for unitID,_ in pairs(conJob.mex.con) do
  1234. local cQueue = spGetCommandQueue(unitID)
  1235. if #cQueue == 0 or controlledUnit.conByID[unitID].idle then
  1236. controlledUnit.conByID[unitID].idle = false
  1237. controlledUnit.conByID[unitID].oldJob = conJob.mex.index
  1238. if math.random() < conJob.mex.defenceChance and makeWantedDefence(team,unitID,500,500,1000) then
  1239. controlledUnit.conByID[unitID].makingDefence = true
  1240. else
  1241. controlledUnit.conByID[unitID].makingDefence = false
  1242. makeMex(team,unitID)
  1243. end
  1244. end
  1245. end
  1246.  
  1247. -- check for queued factory
  1248. if a.uncompletedFactory == true then
  1249. a.uncompletedFactory = false
  1250. for unitID,data in pairs(conJob.factory.con) do
  1251. local cQueue = spGetCommandQueue(unitID)
  1252. if #cQueue ~= 0 and buildDefs.factoryByDefId[-cQueue[1].id] then
  1253. a.uncompletedFactory = true
  1254. end
  1255. end
  1256. end
  1257.  
  1258. -- factory assist/construction
  1259. for unitID,data in pairs(conJob.factory.con) do
  1260. local cQueue = spGetCommandQueue(unitID)
  1261.  
  1262. if #cQueue == 0 or controlledUnit.conByID[unitID].idle then
  1263. controlledUnit.conByID[unitID].idle = false
  1264. controlledUnit.conByID[unitID].makingDefence = false
  1265. controlledUnit.conByID[unitID].oldJob = conJob.factory.index
  1266. assignFactory(team,unitID,cQueue)
  1267. elseif a.wantedNanoCount > controlledUnit.nano.count and math.random() < 0.05 then
  1268. makeNano(team, unitID)
  1269. end
  1270. end
  1271.  
  1272. -- energy
  1273. for unitID,_ in pairs(conJob.energy.con) do
  1274. local cQueue = spGetCommandQueue(unitID)
  1275. if #cQueue == 0 or controlledUnit.conByID[unitID].idle then
  1276. controlledUnit.conByID[unitID].idle = false
  1277. controlledUnit.conByID[unitID].makingDefence = false
  1278. controlledUnit.conByID[unitID].oldJob = conJob.energy.index
  1279. makeEnergy(team,unitID)
  1280. end
  1281. end
  1282.  
  1283. end
  1284.  
  1285. local function setUnitPosting(team, unitID)
  1286.  
  1287. local a = aiTeamData[team]
  1288.  
  1289. if a.conJob.mex.assignedBP == 0 then
  1290. return
  1291. end
  1292.  
  1293. local rand = math.random(0,a.conJob.mex.assignedBP)
  1294. local total = 0
  1295. for cid,_ in pairs(a.conJob.mex.con) do
  1296. total = total + a.controlledUnit.conByID[cid].bp
  1297. if total > rand then
  1298. local x,y,z = spGetUnitPosition(cid)
  1299. spGiveOrderToUnit(unitID, CMD_FIGHT , {x+math.random(-100,100),y,z+math.random(-100,100)}, {})
  1300. break
  1301. end
  1302. end
  1303.  
  1304. end
  1305.  
  1306. local function getPositionTowardsMiddle(unitID, range)
  1307.  
  1308. local x,y,z = spGetUnitPosition(unitID)
  1309. local vectorX = mapWidth*0.5 - x
  1310. local vectorZ = mapHeight*0.5 - z
  1311.  
  1312. local vectorMag = math.sqrt(disSQ(0,0,vectorX,vectorZ))
  1313. if vectorMag == 0 then
  1314. vectorMag = 1
  1315. end
  1316. vectorMag = 1/vectorMag
  1317. while spTestBuildOrder(waypointTester, x + vectorX*range*vectorMag, 0, z + vectorZ*range*vectorMag, 1) == 0 do
  1318. range = range + 25
  1319. end
  1320.  
  1321. return x + vectorX*range*vectorMag, y, z + vectorZ*range*vectorMag
  1322. end
  1323.  
  1324. -- updates factory that has finished it's queue with a new order
  1325. local function factoryJobHandler(team)
  1326.  
  1327. local a = aiTeamData[team]
  1328. local factoryByID = a.controlledUnit.factoryByID
  1329. local facJob = a.facJob
  1330. local buildDefs = a.buildDefs
  1331.  
  1332. for unitID,data in pairs(factoryByID) do
  1333.  
  1334. local scouting = false
  1335. local raiding = false
  1336.  
  1337. local cQueue = spGetFactoryCommands(unitID)
  1338. if #cQueue == 0 then
  1339. local defData = buildDefs.factoryByDefId[data.ud.id]
  1340. local choice = 1
  1341. if defData.airFactory then
  1342. local facJobAir = a.facJobAir
  1343. local totalImportance = 0
  1344. for i = 1, 5 do
  1345. totalImportance = totalImportance + facJobAir[i].importance*defData[i].importanceMult
  1346. end
  1347.  
  1348. local total = 0
  1349. local rand = math.random()*totalImportance
  1350.  
  1351. for i = 1, 5 do
  1352. total = total + facJobAir[i].importance*defData[i].importanceMult
  1353. if rand < total then
  1354. choice = i
  1355. scouting = (i == 2)
  1356. break
  1357. end
  1358. end
  1359. else
  1360. if math.random() < a.unitHording then
  1361. setUnitPosting(team, unitID)
  1362. else
  1363. spGiveOrderToUnit(unitID, CMD_FIGHT, { data.wayX+math.random(-200,200), data.wayY, data.wayZ+math.random(-200,200)}, {})
  1364. end
  1365. local facJob = a.facJob
  1366. local totalImportance = 0
  1367. for i = 1, 8 do
  1368. totalImportance = totalImportance + facJob[i].importance*defData[i].importanceMult
  1369. end
  1370.  
  1371. local total = 0
  1372. local rand = math.random()*totalImportance
  1373.  
  1374. for i = 1, 8 do
  1375. total = total + facJob[i].importance*defData[i].importanceMult
  1376. if rand < total then
  1377. choice = i
  1378. scouting = (i == 2)
  1379. raiding = (i == 3)
  1380. break
  1381. end
  1382. end
  1383. end
  1384.  
  1385. local bud = chooseUnitDefIDWithDebug(defData[choice], unitID, data.ud, choice)
  1386. data.producingScout = scouting
  1387. data.producingRaider = raiding
  1388. spGiveOrderToUnit(unitID, -bud , {}, {})
  1389. end
  1390. end
  1391.  
  1392. end
  1393.  
  1394. local function removeValueFromHeatmap(indexer, heatmap, aX, aZ)
  1395.  
  1396. if heatmap[aX][aZ].cost > 0 then
  1397. indexer.totalCost = indexer.totalCost - heatmap[aX][aZ].cost
  1398. heatmap[aX][aZ].cost = 0
  1399. -- remove index
  1400. local index = heatmap[aX][aZ].index
  1401. indexer[index] = indexer[indexer.count]
  1402. indexer[indexer.count] = nil
  1403. if index ~= indexer.count then
  1404. heatmap[indexer[index].aX][indexer[index].aZ].index = index
  1405. end
  1406. indexer.count = indexer.count - 1
  1407. end
  1408.  
  1409. end
  1410.  
  1411. local function wipeSquareData(allyTeam,aX,aZ)
  1412.  
  1413. local at = allyTeamData[allyTeam]
  1414.  
  1415. removeValueFromHeatmap(at.enemyOffense, at.enemyOffenseHeatmap, aX, aZ)
  1416. removeValueFromHeatmap(at.enemyDefence, at.enemyDefenceHeatmap, aX, aZ)
  1417. removeValueFromHeatmap(at.enemyEconomy, at.enemyEconomyHeatmap, aX, aZ)
  1418.  
  1419. end
  1420.  
  1421. local function getEnemyAntiAirInRange(allyTeam, x, z)
  1422.  
  1423. local at = allyTeamData[allyTeam]
  1424. local static = 0
  1425. local mobile = 0
  1426.  
  1427. for id, data in pairs(at.enemyStaticAA) do
  1428. if (x-data.x)^2 + (z-data.z)^2 < data.rangeSQ then
  1429. static = static + data.cost
  1430. end
  1431. end
  1432.  
  1433. for id, data in pairs(at.enemyMobileAA) do
  1434. if (x-data.x)^2 + (z-data.z)^2 < data.rangeSQ then
  1435. mobile = mobile + data.cost
  1436. end
  1437. end
  1438.  
  1439. return static, mobile
  1440.  
  1441. end
  1442.  
  1443. local function gatherBattlegroupNeededAA(team, index)
  1444.  
  1445. local a = aiTeamData[team]
  1446. local at = allyTeamData[a.allyTeam]
  1447.  
  1448. local aa = a.controlledUnit.aa
  1449. local aaByID = a.controlledUnit.aaByID
  1450. local unitInBattleGroupByID = a.unitInBattleGroupByID
  1451.  
  1452. local bg = a.battleGroup[index]
  1453.  
  1454. local cost = 0
  1455.  
  1456. for unitID,_ in pairs(bg.aa) do
  1457. if spValidUnitID(unitID) then
  1458. cost = cost + aaByID[unitID].cost
  1459. else
  1460. bg.unit[unitID] = nil
  1461. bg.aa[unitID] = nil
  1462. unitInBattleGroupByID[unitID] = nil
  1463. end
  1464. end
  1465.  
  1466. for unitID,data in pairs(aaByID) do
  1467. if not unitInBattleGroupByID[unitID] then
  1468. cost = cost + data.cost
  1469. bg.unit[unitID] = true
  1470. bg.aa[unitID] = true
  1471. unitInBattleGroupByID[unitID] = true
  1472. if cost > bg.neededAA then
  1473. break
  1474. end
  1475. end
  1476. end
  1477.  
  1478. end
  1479.  
  1480. local function battleGroupHandler(team, frame, slowUpdate)
  1481.  
  1482. local a = aiTeamData[team]
  1483. local at = allyTeamData[a.allyTeam]
  1484. local battleGroup = a.battleGroup
  1485. local unitInBattleGroupByID = a.unitInBattleGroupByID
  1486.  
  1487. for i = 1, battleGroup.count do
  1488.  
  1489. local data = battleGroup[i]
  1490.  
  1491. for unitID,_ in pairs(data.unit) do
  1492. if spValidUnitID(unitID) then
  1493. if not unitInBattleGroupByID[unitID] then
  1494. --mapEcho(unitID, "unit not in group")
  1495. end
  1496. end
  1497. end
  1498.  
  1499. local averageX = 0
  1500. local averageZ = 0
  1501. local averageCount = 0
  1502.  
  1503. local maxX = false
  1504. local minX = false
  1505. local maxZ = false
  1506. local minZ = false
  1507.  
  1508. for unitID,_ in pairs(data.unit) do
  1509.  
  1510. if spValidUnitID(unitID) then
  1511. local x, y, z = spGetUnitPosition(unitID)
  1512. averageX = averageX + x
  1513. averageZ = averageZ + z
  1514. averageCount = averageCount + 1
  1515.  
  1516. if not maxX or maxX < x then
  1517. maxX = x
  1518. end
  1519. if not minX or minX > x then
  1520. minX = x
  1521. end
  1522. if not maxZ or maxZ < z then
  1523. maxZ = z
  1524. end
  1525. if not minZ or minZ > z then
  1526. minZ = z
  1527. end
  1528.  
  1529. local cQueue = spGetCommandQueue(unitID)
  1530. if #cQueue > 0 and cQueue[1].id == CMD_ATTACK and #cQueue[1].params == 1 then
  1531. local udid = spGetUnitDefID(cQueue[1].params[1])
  1532. if (not udid) or (not UnitDefs[udid].canFly) then
  1533. data.tempTarget = cQueue[1].params[1]
  1534. end
  1535. end
  1536. else
  1537. data.unit[unitID] = nil
  1538. data.aa[unitID] = nil
  1539. unitInBattleGroupByID[unitID] = nil
  1540. end
  1541. end
  1542.  
  1543. if averageCount == 0 then
  1544. removeIndexFromArray(battleGroup,i)
  1545. break
  1546. end
  1547.  
  1548. averageX = averageX/averageCount
  1549. averageZ = averageZ/averageCount
  1550.  
  1551. data.currentX = averageX
  1552. data.currentZ = averageZ
  1553.  
  1554. local gy = spGetGroundHeight(averageX,averageZ)
  1555. for unitID,_ in pairs(data.aa) do
  1556. spGiveOrderToUnit(unitID, CMD_MOVE , {averageX,gy,averageZ}, {})
  1557. end
  1558.  
  1559. if data.tempTarget then
  1560. if spValidUnitID(data.tempTarget) and isUnitVisible(data.tempTarget,at.aTeamOnThisTeam) then
  1561. local x, y, z = spGetUnitPosition(data.tempTarget)
  1562. for unitID,_ in pairs(data.unit) do
  1563. if not data.aa[unitID] then
  1564. spGiveOrderToUnit(unitID, CMD_FIGHT , {x,y,z}, {})
  1565. end
  1566. end
  1567. else
  1568. data.tempTarget = false
  1569. end
  1570. else
  1571.  
  1572. local aX = math.ceil(averageX/heatSquareWidth)
  1573. local aZ = math.ceil(averageZ/heatSquareHeight)
  1574.  
  1575. if aX == data.aX and aZ == data.aZ then
  1576. wipeSquareData(a.allyTeam, aX, aZ)
  1577. if data.disbandable then
  1578. for unitID,_ in pairs(data.unit) do
  1579. unitInBattleGroupByID[unitID] = nil
  1580. end
  1581. removeIndexFromArray(battleGroup,i)
  1582. break
  1583. else
  1584. local minTargetDistance = false
  1585. local changed = false
  1586. local x = data.aimX
  1587. local z = data.aimZ
  1588. for i = 1, at.enemyEconomy.count do
  1589. local dis = disSQ(x, z, at.enemyEconomy[i].x, at.enemyEconomy[i].z)
  1590. if (not minTargetDistance) or minTargetDistance > dis and
  1591. not (data.aX == at.enemyEconomy[i].aX and data.aZ == at.enemyEconomy[i].aZ) then
  1592. data.aX = at.enemyEconomy[i].aX
  1593. data.aZ = at.enemyEconomy[i].aZ
  1594. minTargetDistance = dis
  1595. changed = true
  1596. end
  1597. end
  1598.  
  1599. if changed then
  1600. data.aimX = heatmapPosition[data.aX][data.aZ].x
  1601. data.aimY = heatmapPosition[data.aX][data.aZ].y
  1602. data.aimZ = heatmapPosition[data.aX][data.aZ].z
  1603. --Spring.MarkerAddPoint(data.aimX,0,data.aimZ, "New Target " .. at.enemyEconomyHeatmap[data.aX][data.aZ].cost)
  1604. data.regroup = false
  1605. local groupRange = 450 + averageCount*30
  1606. local moveGroupRange = groupRange*0.4
  1607. for unitID,_ in pairs(data.unit) do
  1608. if not data.aa[unitID] then
  1609. local x, y, z = spGetUnitPosition(unitID)
  1610. --spGiveOrderToUnit(unitID, CMD_FIGHT , {data.aimX ,data.aimY,data.aimZ,}, {})
  1611. spGiveOrderToUnit(unitID, CMD_FIGHT , {data.aimX + math.random(-moveGroupRange,moveGroupRange),
  1612. data.aimY,data.aimZ + math.random(-moveGroupRange,moveGroupRange),}, {})
  1613. end
  1614. end
  1615. else
  1616. for unitID,_ in pairs(data.unit) do
  1617. unitInBattleGroupByID[unitID] = nil
  1618. end
  1619. removeIndexFromArray(battleGroup,i)
  1620. break
  1621. end
  1622. end
  1623.  
  1624. else
  1625. local groupRange = 450 + averageCount*30
  1626. local moveGroupRange = groupRange*0.4
  1627.  
  1628. if data.regroup == true then
  1629. groupRange = 400 + averageCount*20
  1630. end
  1631.  
  1632. if maxX - minX > groupRange or maxZ - minZ > groupRange then
  1633. if (not data.regroup) or slowUpdate then
  1634.  
  1635. local aDivX = 0
  1636. local aDivZ = 0
  1637.  
  1638. for unitID,_ in pairs(data.unit) do
  1639. local x, y, z = spGetUnitPosition(unitID)
  1640. aDivX = aDivX + math.abs(x - averageX)
  1641. aDivZ = aDivZ + math.abs(z - averageZ)
  1642. end
  1643. aDivX = aDivX/averageCount
  1644. aDivZ = aDivZ/averageCount
  1645.  
  1646. if aDivX < 300 and aDivZ < 300 then
  1647. for unitID,_ in pairs(data.unit) do
  1648. local x, y, z = spGetUnitPosition(unitID)
  1649. if math.abs(x-averageX) > aDivX*1.7 and math.abs(z-averageZ) > aDivX*1.7 then
  1650. data.unit[unitID] = nil
  1651. data.aa[unitID] = nil
  1652. unitInBattleGroupByID[unitID] = nil
  1653. --Spring.MarkerAddPoint(x,y,z, "Left Behind")
  1654. end
  1655. end
  1656. end
  1657.  
  1658. --[[Spring.MarkerAddPoint(averageX,0,averageZ, "Div")
  1659. Spring.MarkerAddLine(averageX-aDivX,Spring.GetGroundHeight(averageX-aDivX,averageZ-aDivZ),averageZ-aDivZ,
  1660. averageX+aDivX,Spring.GetGroundHeight(averageX+aDivX,averageZ-aDivZ),averageZ-aDivZ)
  1661. Spring.MarkerAddLine(averageX-aDivX,Spring.GetGroundHeight(averageX-aDivX,averageZ+aDivZ),averageZ+aDivZ,
  1662. averageX+aDivX,Spring.GetGroundHeight(averageX+aDivX,averageZ+aDivZ),averageZ+aDivZ)
  1663. Spring.MarkerAddLine(averageX+aDivX,Spring.GetGroundHeight(averageX+aDivX,averageZ-aDivZ),averageZ-aDivZ,
  1664. averageX+aDivX,Spring.GetGroundHeight(averageX+aDivX,averageZ+aDivZ),averageZ+aDivZ)
  1665. Spring.MarkerAddLine(averageX-aDivX,Spring.GetGroundHeight(averageX-aDivX,averageZ-aDivZ),averageZ-aDivZ,
  1666. averageX-aDivX,Spring.GetGroundHeight(averageX-aDivX,averageZ+aDivZ),averageZ+aDivZ)--]]
  1667. end
  1668. data.regroup = true
  1669. for unitID,_ in pairs(data.unit) do
  1670. if not data.aa[unitID] then
  1671. spGiveOrderToUnit(unitID, CMD_FIGHT , {averageX,gy,averageZ}, {})
  1672. end
  1673. end
  1674. else
  1675. if data.regroup == true then
  1676. data.regroup = false
  1677. for unitID,_ in pairs(data.unit) do
  1678. if not data.aa[unitID] then
  1679. --spGiveOrderToUnit(unitID, CMD_FIGHT , {data.aimX ,data.aimY,data.aimZ,}, {})
  1680. spGiveOrderToUnit(unitID, CMD_FIGHT , {data.aimX + math.random(-moveGroupRange,moveGroupRange),
  1681. data.aimY,data.aimZ + math.random(-moveGroupRange,moveGroupRange),}, {})
  1682. end
  1683.  
  1684. end
  1685. end
  1686. end
  1687. end
  1688. end
  1689.  
  1690. end
  1691.  
  1692. end
  1693.  
  1694. local function raiderJobHandler(team)
  1695.  
  1696. local a = aiTeamData[team]
  1697. local at = allyTeamData[a.allyTeam]
  1698.  
  1699. local raider = a.controlledUnit.raider
  1700. local raiderByID = a.controlledUnit.raiderByID
  1701.  
  1702. local battleGroup = a.battleGroup
  1703.  
  1704. local enemyOffenseHeatmap = at.enemyOffenseHeatmap
  1705. local enemyOffense = at.enemyOffense
  1706. local enemyDefenceHeatmap = at.enemyDefenceHeatmap
  1707. local enemyDefence = at.enemyDefence
  1708. local enemyEconomyHeatmap = at.enemyEconomyHeatmap
  1709. local enemyEconomy = at.enemyEconomy
  1710.  
  1711. local unitInBattleGroupByID = a.unitInBattleGroupByID
  1712.  
  1713. if raider.count == 0 then
  1714. return
  1715. end
  1716.  
  1717. local tX = false
  1718. local tY = false
  1719. local tZ = false
  1720. local idleCost = 0
  1721.  
  1722. local averageX = 0
  1723. local averageZ = 0
  1724. local averageCount = 0
  1725.  
  1726. for unitID,data in pairs(raiderByID) do
  1727. local cQueue = spGetCommandQueue(unitID)
  1728. if (#cQueue == 0 or (cQueue == 2 and cQueue[1].id == CMD_MOVE)) and data.finished and not unitInBattleGroupByID[unitID] then
  1729. local x, y, z = spGetUnitPosition(unitID)
  1730. idleCost = idleCost + data.cost
  1731. averageX = averageX + x
  1732. averageZ = averageZ + z
  1733. averageCount = averageCount + 1
  1734. end
  1735. end
  1736.  
  1737. if averageCount > 0 then
  1738.  
  1739. averageX = averageX/averageCount
  1740. averageZ = averageZ/averageCount
  1741. local aX,aZ
  1742. local idleFactor = idleCost/raider.cost
  1743.  
  1744. if a.raiderBattlegroupCondition(idleFactor, idleCost) then
  1745. local minCost = false
  1746.  
  1747. for i = 1, enemyEconomy.count do
  1748. aX = enemyEconomy[i].aX
  1749. aZ = enemyEconomy[i].aZ
  1750. if enemyOffenseHeatmap[aX][aZ].cost*2 < raider.cost and enemyDefenceHeatmap[aX][aZ].cost*2 < raider.cost then
  1751. if (not minCost) or minCost < enemyEconomyHeatmap[aX][aZ].cost then
  1752. tX = enemyEconomy[i].x
  1753. tY = enemyEconomy[i].y
  1754. tZ = enemyEconomy[i].z
  1755. end
  1756. end
  1757. end
  1758. end
  1759.  
  1760. if tX then
  1761. battleGroup.count = battleGroup.count+1
  1762. battleGroup[battleGroup.count] = {
  1763. aimX = tX, aimY = tY, aimZ = tZ,
  1764. aX = aX, aZ = aZ,
  1765. currentX = false, currentZ = false,
  1766. respondToSOSpriority = 1,
  1767. regroup = true,
  1768. unit = {},
  1769. tempTarget = false,
  1770. disbandable = false,
  1771. aa = {},
  1772. neededAA = 0,
  1773. }
  1774. --wipeSquareData(team, aX, aZ)
  1775. end
  1776. end
  1777.  
  1778. for unitID,data in pairs(raiderByID) do
  1779. local cQueue = spGetCommandQueue(unitID)
  1780. if (#cQueue == 0 or (cQueue == 2 and cQueue[1].id == CMD_MOVE)) and data.finished then
  1781. local eID = spGetUnitNearestEnemy(unitID,1200)
  1782. if eID then
  1783. spGiveOrderToUnit(unitID, CMD_ATTACK , {eID}, {})
  1784. elseif tX and not unitInBattleGroupByID[unitID] then
  1785. --spGiveOrderToUnit(unitID, CMD_FIGHT , {tX + math.random(-200,200),tY,tZ + math.random(-200,200),}, {})
  1786. for i = 1, battleGroup.count do
  1787. if battleGroup[i].unit[unitID] then
  1788. Spring.Echo("Unit already in battle group")
  1789. end
  1790. end
  1791. battleGroup[battleGroup.count].unit[unitID] = true
  1792. unitInBattleGroupByID[unitID] = true
  1793. end
  1794. end
  1795. end
  1796.  
  1797. end
  1798.  
  1799. local function artyJobHandler(team)
  1800.  
  1801. local a = aiTeamData[team]
  1802. local at = allyTeamData[a.allyTeam]
  1803.  
  1804. local arty = a.controlledUnit.arty
  1805. local artyByID = a.controlledUnit.artyByID
  1806.  
  1807. local enemyDefenceHeatmap = at.enemyDefenceHeatmap
  1808. local enemyDefence = at.enemyDefence
  1809.  
  1810. if arty.count == 0 then
  1811. return
  1812. end
  1813.  
  1814. if enemyDefence.count > 0 then
  1815. for unitID,data in pairs(artyByID) do
  1816. local cQueue = spGetCommandQueue(unitID)
  1817. if #cQueue == 0 then
  1818. local randIndex = math.floor(math.random(1,enemyDefence.count))
  1819. spGiveOrderToUnit(unitID, CMD_FIGHT , {enemyDefence[randIndex].x,enemyDefence[randIndex].y, enemyDefence[randIndex].z,}, {})
  1820. end
  1821. end
  1822. end
  1823.  
  1824. end
  1825.  
  1826. local function bomberJobHandler(team)
  1827.  
  1828. local a = aiTeamData[team]
  1829. local at = allyTeamData[a.allyTeam]
  1830.  
  1831. local bomber = a.controlledUnit.bomber
  1832. local bomberByID = a.controlledUnit.bomberByID
  1833.  
  1834. local enemyOffenseHeatmap = at.enemyOffenseHeatmap
  1835. local enemyOffense = at.enemyOffense
  1836.  
  1837. if bomber.count == 0 then
  1838. return
  1839. end
  1840.  
  1841. if enemyOffense.count > 0 then
  1842. for unitID,data in pairs(bomberByID) do
  1843. local cQueue = spGetCommandQueue(unitID)
  1844. if #cQueue == 0 then
  1845. local randIndex = math.floor(math.random(1,enemyOffense.count))
  1846. local static, mobile = getEnemyAntiAirInRange(a.allyTeam, enemyOffense[randIndex].x, enemyOffense[randIndex].z)
  1847. if static*2 + mobile < 600 then
  1848. spGiveOrderToUnit(unitID, CMD_FIGHT , {enemyOffense[randIndex].x,enemyOffense[randIndex].y, enemyOffense[randIndex].z,}, {})
  1849. end
  1850. end
  1851. end
  1852. end
  1853.  
  1854. end
  1855.  
  1856. local function gunshipJobHandler(team)
  1857.  
  1858. local a = aiTeamData[team]
  1859. local at = allyTeamData[a.allyTeam]
  1860.  
  1861. local gunship = a.controlledUnit.gunship
  1862. local gunshipByID = a.controlledUnit.gunshipByID
  1863.  
  1864. local battleGroup = a.battleGroup
  1865.  
  1866. local enemyOffenseHeatmap = at.enemyOffenseHeatmap
  1867. local enemyOffense = at.enemyOffense
  1868. local enemyDefenceHeatmap = at.enemyDefenceHeatmap
  1869. local enemyDefence = at.enemyDefence
  1870. local enemyEconomyHeatmap = at.enemyEconomyHeatmap
  1871. local enemyEconomy = at.enemyEconomy
  1872.  
  1873. local unitInBattleGroupByID = a.unitInBattleGroupByID
  1874.  
  1875. if gunship.count == 0 then
  1876. return
  1877. end
  1878.  
  1879. local tX = false
  1880. local tY = false
  1881. local tZ = false
  1882. local idleCost = 0
  1883.  
  1884. local averageX = 0
  1885. local averageZ = 0
  1886. local averageCount = 0
  1887.  
  1888. for unitID,data in pairs(gunshipByID) do
  1889. local cQueue = spGetCommandQueue(unitID)
  1890. if (#cQueue == 0 or (cQueue == 2 and cQueue[1].id == CMD_MOVE)) and data.finished and not unitInBattleGroupByID[unitID] then
  1891. local x, y, z = spGetUnitPosition(unitID)
  1892. idleCost = idleCost + data.cost
  1893. averageX = averageX + x
  1894. averageZ = averageZ + z
  1895. averageCount = averageCount + 1
  1896. end
  1897. end
  1898.  
  1899. if averageCount > 0 then
  1900.  
  1901. averageX = averageX/averageCount
  1902. averageZ = averageZ/averageCount
  1903. local aX,aZ
  1904. local idleFactor = idleCost/gunship.cost
  1905.  
  1906. if a.gunshipBattlegroupCondition(idleFactor, idleCost) then
  1907. local minCost = false
  1908.  
  1909. for i = 1, enemyEconomy.count do
  1910. aX = enemyEconomy[i].aX
  1911. aZ = enemyEconomy[i].aZ
  1912.  
  1913. local static, mobile = getEnemyAntiAirInRange(a.allyTeam, enemyEconomy[i].x, enemyEconomy[i].z)
  1914.  
  1915. if enemyOffenseHeatmap[aX][aZ].cost*2 < gunship.cost and enemyDefenceHeatmap[aX][aZ].cost*2 < gunship.cost and static*3 + mobile*2 < gunship.cost then
  1916. if (not minCost) or minCost < enemyEconomyHeatmap[aX][aZ].cost then
  1917. tX = enemyEconomy[i].x
  1918. tY = enemyEconomy[i].y
  1919. tZ = enemyEconomy[i].z
  1920. end
  1921. end
  1922. end
  1923. end
  1924.  
  1925. if tX then
  1926. battleGroup.count = battleGroup.count+1
  1927. battleGroup[battleGroup.count] = {
  1928. aimX = tX, aimY = tY, aimZ = tZ,
  1929. aX = aX, aZ = aZ,
  1930. currentX = false, currentZ = false,
  1931. respondToSOSpriority = 1,
  1932. regroup = true,
  1933. unit = {},
  1934. tempTarget = false,
  1935. disbandable = true,
  1936. aa = {},
  1937. neededAA = 0,
  1938. }
  1939. --wipeSquareData(team, aX, aZ)
  1940. end
  1941. end
  1942.  
  1943. for unitID,data in pairs(gunshipByID) do
  1944. local cQueue = spGetCommandQueue(unitID)
  1945. if (#cQueue == 0 or (cQueue == 2 and cQueue[1].id == CMD_MOVE)) and data.finished then
  1946. local eID = spGetUnitNearestEnemy(unitID,1200)
  1947. if eID then
  1948. spGiveOrderToUnit(unitID, CMD_ATTACK , {eID}, {})
  1949. elseif tX and not unitInBattleGroupByID[unitID] then
  1950. --spGiveOrderToUnit(unitID, CMD_FIGHT , {tX + math.random(-200,200),tY,tZ + math.random(-200,200),}, {})
  1951. for i = 1, battleGroup.count do
  1952. if battleGroup[i].unit[unitID] then
  1953. Spring.Echo("Unit already in battle group")
  1954. end
  1955. end
  1956. battleGroup[battleGroup.count].unit[unitID] = true
  1957. unitInBattleGroupByID[unitID] = true
  1958. end
  1959. end
  1960. end
  1961.  
  1962.  
  1963.  
  1964. end
  1965.  
  1966. local function fighterJobHandler(team)
  1967.  
  1968. local a = aiTeamData[team]
  1969. local at = allyTeamData[a.allyTeam]
  1970. local target = at.fighterTarget
  1971.  
  1972. local fighter = a.controlledUnit.fighter
  1973. local fighterByID = a.controlledUnit.fighterByID
  1974.  
  1975. if fighter.count == 0 or not at.fighterTarget then
  1976. return
  1977. end
  1978.  
  1979. if spValidUnitID( at.fighterTarget) then
  1980. for unitID,data in pairs(fighterByID) do
  1981. local cQueue = spGetCommandQueue(unitID)
  1982. if #cQueue == 0 then
  1983. spGiveOrderToUnit(unitID, CMD_ATTACK , { at.fighterTarget}, {})
  1984. end
  1985. end
  1986. else
  1987. at.fighterTarget = nil
  1988. end
  1989.  
  1990. end
  1991.  
  1992. local function combatJobHandler(team)
  1993.  
  1994. local a = aiTeamData[team]
  1995. local at = allyTeamData[a.allyTeam]
  1996.  
  1997. local combat = a.controlledUnit.combat
  1998. local combatByID = a.controlledUnit.combatByID
  1999.  
  2000. local battleGroup = a.battleGroup
  2001.  
  2002. local enemyOffenseHeatmap = at.enemyOffenseHeatmap
  2003. local enemyOffense = at.enemyOffense
  2004. local enemyDefenceHeatmap = at.enemyDefenceHeatmap
  2005. local enemyDefence = at.enemyDefence
  2006. local enemyEconomyHeatmap = at.enemyEconomyHeatmap
  2007. local enemyEconomy = at.enemyEconomy
  2008.  
  2009. local unitInBattleGroupByID = a.unitInBattleGroupByID
  2010.  
  2011. if combat.count == 0 then
  2012. return
  2013. end
  2014.  
  2015. local tX = false
  2016. local tY = false
  2017. local tZ = false
  2018. local idleCost = 0
  2019.  
  2020. local averageX = 0
  2021. local averageZ = 0
  2022. local averageCount = 0
  2023.  
  2024. for unitID,data in pairs(combatByID) do
  2025. local cQueue = spGetCommandQueue(unitID)
  2026. if (#cQueue == 0 or (cQueue == 2 and cQueue[1].id == CMD_MOVE)) and data.finished and not unitInBattleGroupByID[unitID] then
  2027. local x, y, z = spGetUnitPosition(unitID)
  2028. idleCost = idleCost + data.cost
  2029. averageX = averageX + x
  2030. averageZ = averageZ + z
  2031. averageCount = averageCount + 1
  2032. end
  2033. end
  2034.  
  2035. if averageCount > 0 then
  2036.  
  2037. averageX = averageX/averageCount
  2038. averageZ = averageZ/averageCount
  2039. local aX,aZ
  2040. local idleFactor = idleCost/combat.cost
  2041.  
  2042. if a.combatBattlegroupCondition(idleFactor, idleCost) then
  2043. local minTargetDistance = false
  2044.  
  2045. for i = 1, enemyOffense.count do
  2046. aX = enemyOffense[i].aX
  2047. aZ = enemyOffense[i].aZ
  2048.  
  2049. if enemyOffenseHeatmap[aX][aZ].cost*2 < combat.cost and enemyDefenceHeatmap[aX][aZ].cost < combat.cost then
  2050. if (not minTargetDistance) or minTargetDistance > disSQ(averageX,averageZ,enemyOffense[i].x,enemyOffense[i].z) then
  2051. tX = enemyOffense[i].x
  2052. tY = enemyOffense[i].y
  2053. tZ = enemyOffense[i].z
  2054. end
  2055. end
  2056. end
  2057.  
  2058. for i = 1, enemyEconomy.count do
  2059. aX = enemyEconomy[i].aX
  2060. aZ = enemyEconomy[i].aZ
  2061.  
  2062. if enemyOffenseHeatmap[aX][aZ].cost*2 < combat.cost and enemyDefenceHeatmap[aX][aZ].cost < combat.cost then
  2063. if (not minTargetDistance) or minTargetDistance > disSQ(averageX,averageZ,enemyEconomy[i].x,enemyEconomy[i].z) then
  2064. tX = enemyEconomy[i].x
  2065. tY = enemyEconomy[i].y
  2066. tZ = enemyEconomy[i].z
  2067. end
  2068. end
  2069. end
  2070.  
  2071. if not tX then
  2072. for i = 1, enemyDefence.count do
  2073. aX = enemyDefence[i].aX
  2074. aZ = enemyDefence[i].aZ
  2075.  
  2076. if enemyOffenseHeatmap[aX][aZ].cost < combat.cost and enemyDefenceHeatmap[aX][aZ].cost*2 < combat.cost then
  2077. if (not minTargetDistance) or minTargetDistance > disSQ(averageX,averageZ,enemyDefence[i].x,enemyDefence[i].z) then
  2078. tX = enemyDefence[i].x
  2079. tY = enemyDefence[i].y
  2080. tZ = enemyDefence[i].z
  2081. end
  2082. end
  2083. end
  2084. end
  2085. end
  2086.  
  2087. if tX then
  2088. battleGroup.count = battleGroup.count+1
  2089. battleGroup[battleGroup.count] = {
  2090. aimX = tX, aimY = tY, aimZ = tZ,
  2091. aX = aX, aZ = aZ,
  2092. currentX = false, currentZ = false,
  2093. respondToSOSpriority = 0,
  2094. regroup = true,
  2095. unit = {},
  2096. tempTarget = false,
  2097. disbandable = false,
  2098. aa = {},
  2099. neededAA = idleCost/5,
  2100. }
  2101. gatherBattlegroupNeededAA(team,battleGroup.count)
  2102. --wipeSquareData(team,aX, aZ)
  2103. end
  2104. end
  2105.  
  2106. for unitID,data in pairs(combatByID) do
  2107. local cQueue = spGetCommandQueue(unitID)
  2108. if (#cQueue == 0 or (cQueue == 2 and cQueue[1].id == CMD_MOVE)) and data.finished then
  2109. local eID = spGetUnitNearestEnemy(unitID,1200)
  2110. if eID then
  2111. spGiveOrderToUnit(unitID, CMD_ATTACK , {eID}, {})
  2112. elseif tX and not unitInBattleGroupByID[unitID] then
  2113. --spGiveOrderToUnit(unitID, CMD_FIGHT , {tX + math.random(-300,300),tY,tZ + math.random(-300,300),}, {})
  2114. for i = 1, battleGroup.count do
  2115. if battleGroup[i].unit[unitID] then
  2116. Spring.Echo("Unit already in battle group")
  2117. end
  2118. end
  2119. battleGroup[battleGroup.count].unit[unitID] = true
  2120. unitInBattleGroupByID[unitID] = true
  2121. end
  2122. end
  2123. end
  2124.  
  2125. end
  2126.  
  2127. local function scoutJobHandler(team)
  2128.  
  2129. local a = aiTeamData[team]
  2130. local at = allyTeamData[a.allyTeam]
  2131.  
  2132. local scoutByID = a.controlledUnit.scoutByID
  2133.  
  2134. local unScoutedPoint = at.unScoutedPoint
  2135. if unScoutedPoint.count > 0 then
  2136. for unitID,data in pairs(scoutByID) do
  2137. local cQueue = spGetCommandQueue(unitID)
  2138. if #cQueue == 0 then
  2139. local randIndex = math.floor(math.random(1,unScoutedPoint.count))
  2140. spGiveOrderToUnit(unitID, CMD_FIGHT , {unScoutedPoint[randIndex].x,unScoutedPoint[randIndex].y,unScoutedPoint[randIndex].z}, {})
  2141. end
  2142.  
  2143. end
  2144. end
  2145.  
  2146. end
  2147.  
  2148. -- updates the scouted state of the map
  2149. local function updateScoutingHeatmap(allyTeam,frame,removeEmpty)
  2150.  
  2151. local at = allyTeamData[allyTeam]
  2152.  
  2153. local scoutingHeatmap = at.scoutingHeatmap
  2154. local unScoutedPoint = at.unScoutedPoint
  2155.  
  2156. unScoutedPoint.count = 0
  2157.  
  2158. for i = 1, heatArrayWidth do -- init array
  2159. for j = 1, heatArrayHeight do
  2160. local data = scoutingHeatmap[i][j]
  2161. if spIsPosInLos(heatmapPosition[i][j].x,0,heatmapPosition[i][j].z,allyTeam) then
  2162. if debugData.drawScoutmap[allyTeam] and not data.scouted then
  2163. Spring.MarkerAddPoint(heatmapPosition[i][j].x,0,heatmapPosition[i][j].z,"now scouted")
  2164. end
  2165. if removeEmpty then -- called infrequently
  2166. local units = CallAsTeam(at.aTeamOnThisTeam,
  2167. function ()
  2168. return spGetUnitsInRectangle(
  2169. heatmapPosition[i][j].left,heatmapPosition[i][j].top,
  2170. heatmapPosition[i][j].right, heatmapPosition[i][j].bottom)
  2171. end
  2172. )
  2173. local empty = true
  2174. for u = i, #units do
  2175. if spGetUnitAllyTeam(units[u]) ~= allyTeam then
  2176. empty = false
  2177. break
  2178. end
  2179. end
  2180. if empty then
  2181. --Spring.MarkerAddPoint(heatmapPosition[i][j].x,0,heatmapPosition[i][j].z,"econ removed")
  2182. if scoutingHeatmap[i][j].previouslyEmpty then
  2183. wipeSquareData(allyTeam, i, j)
  2184. else
  2185. scoutingHeatmap[i][j].previouslyEmpty = true
  2186. end
  2187. else
  2188. scoutingHeatmap[i][j].previouslyEmpty = false
  2189. end
  2190. end
  2191.  
  2192. data.scouted = true
  2193. data.lastScouted = frame
  2194. else
  2195. if frame - data.lastScouted > 2000 then
  2196. unScoutedPoint.count = unScoutedPoint.count + 1
  2197. unScoutedPoint[unScoutedPoint.count] = {x = heatmapPosition[i][j].x, y = heatmapPosition[i][j].y, z = heatmapPosition[i][j].z}
  2198. if j <= 2 or heatArrayHeight-j <= 2 then -- weight scouting towards the edges
  2199. unScoutedPoint.count = unScoutedPoint.count + 1
  2200. unScoutedPoint[unScoutedPoint.count] = {x = heatmapPosition[i][j].x, y = heatmapPosition[i][j].y, z = heatmapPosition[i][j].z}
  2201. end
  2202. if i <= 2 or heatArrayWidth-i <= 2 then -- twice really weights towards the corners
  2203. unScoutedPoint.count = unScoutedPoint.count + 1
  2204. unScoutedPoint[unScoutedPoint.count] = {x = heatmapPosition[i][j].x, y = heatmapPosition[i][j].y, z = heatmapPosition[i][j].z}
  2205. end
  2206. data.scouted = false
  2207. if debugData.drawScoutmap[allyTeam] then
  2208. Spring.MarkerErasePosition (heatmapPosition[i][j].x,0,heatmapPosition[i][j].z)
  2209. end
  2210. end
  2211.  
  2212. end
  2213. end
  2214. end
  2215. end
  2216.  
  2217.  
  2218. -- decays offensive heatmap as it will likely move
  2219. local function decayEnemyHeatmaps(allyTeam,frame)
  2220.  
  2221. local at = allyTeamData[allyTeam]
  2222.  
  2223. local enemyOffense = at.enemyOffense
  2224. local enemyOffenseHeatmap = at.enemyOffenseHeatmap
  2225. local scoutingHeatmap = at.scoutingHeatmap
  2226.  
  2227. for i = 1,enemyOffense.count do
  2228. local aX = enemyOffense[i].aX
  2229. local aZ = enemyOffense[i].aZ
  2230. local data = scoutingHeatmap[aX][aZ]
  2231. if frame - data.lastScouted > 2000 then
  2232. removeValueFromHeatmap(enemyOffense, enemyOffenseHeatmap, aX, aZ)
  2233. break
  2234. end
  2235. end
  2236.  
  2237. end
  2238.  
  2239. local function decayEnemyMobileAA(allyTeam,frame)
  2240.  
  2241. local at = allyTeamData[allyTeam]
  2242. local enemyMobileAA = at.enemyMobileAA
  2243.  
  2244. for unitID,data in pairs(enemyMobileAA) do
  2245. if frame - data.spottedFrame > 2000 then
  2246. enemyMobileAA[unitID] = nil
  2247. end
  2248. end
  2249.  
  2250. end
  2251.  
  2252. local function diluteEnemyForceComposition(allyTeam)
  2253.  
  2254. -- adds it's own mIncome to enemy force composition
  2255. local at = allyTeamData[allyTeam]
  2256. local mInc = 0
  2257. local aiCount = 0
  2258.  
  2259. for team,_ in pairs(at.teams) do
  2260. if aiTeamData[team] then
  2261. mInc = mInc + aiTeamData[team].averagedEcon.aveMInc
  2262. aiCount = aiCount + 1
  2263. end
  2264. end
  2265.  
  2266. mInc = mInc/aiCount * 0.3
  2267.  
  2268. local totalCost = 0
  2269.  
  2270. -- strange algorithm!! put actual thought into one
  2271. for index,_ in pairs(at.enemyForceComposition.unit) do
  2272. at.enemyForceComposition.unit[index] = at.enemyForceComposition.unit[index] + mInc
  2273. totalCost = totalCost + at.enemyForceComposition.unit[index]
  2274. --value = (value-1000)*0.95 + 1000
  2275. end
  2276.  
  2277. for index,value in pairs(at.relativeEnemyForceComposition.unit) do
  2278. at.relativeEnemyForceComposition.unit[index] = at.enemyForceComposition.unit[index]/totalCost*9
  2279. end
  2280. end
  2281.  
  2282. local function addValueToHeatmap(indexer,heatmap, value, aX, aZ)
  2283. if value > 0 then
  2284. if heatmap[aX][aZ].cost == 0 then
  2285. indexer.count = indexer.count + 1
  2286. indexer[indexer.count] = {x = heatmapPosition[aX][aZ].x, y = heatmapPosition[aX][aZ].y, z = heatmapPosition[aX][aZ].z, aX = aX, aZ = aZ}
  2287. heatmap[aX][aZ].index = indexer.count
  2288. end
  2289. indexer.totalCost = indexer.totalCost + value
  2290. heatmap[aX][aZ].cost = heatmap[aX][aZ].cost + value
  2291. end
  2292. end
  2293.  
  2294. local function addValueToHeatmapInArea(indexer,heatmap, value, x, z)
  2295.  
  2296. local aX = math.ceil(x/heatSquareWidth)
  2297. local aZ = math.ceil(z/heatSquareHeight)
  2298.  
  2299. local aX2, aZ2
  2300.  
  2301. local sXfactor = 1
  2302. local sZfactor = 1
  2303.  
  2304. if (aX-0.5)*heatSquareWidth > x then
  2305. if aX == 1 then
  2306. aX2 = 1
  2307. else
  2308. aX2 = aX - 1
  2309. sXfactor = (aX-0.5) - x/heatSquareWidth
  2310. end
  2311. else
  2312. if aX == heatArrayWidth then
  2313. aX2 = heatArrayWidth
  2314. else
  2315. aX2 = aX + 1
  2316. sXfactor = x/heatSquareWidth - (aX-0.5)
  2317. end
  2318. end
  2319.  
  2320. if (aZ-0.5)*heatSquareHeight > z then
  2321. if aZ == 1 then
  2322. aZ2 = 1
  2323. else
  2324. aZ2 = aZ - 1
  2325. sZfactor = (aZ-0.5) - z/heatSquareHeight
  2326. end
  2327. else
  2328. if aZ == heatArrayHeight then
  2329. aZ2 = heatArrayHeight
  2330. else
  2331. aZ2 = aZ + 1
  2332. sZfactor = z/heatSquareHeight - (aZ-0.5)
  2333. end
  2334. end
  2335.  
  2336. addValueToHeatmap(indexer, heatmap, value*(sXfactor + sZfactor)*0.5, aX, aZ)
  2337. addValueToHeatmap(indexer, heatmap, value*((1-sXfactor) + sZfactor)*0.5, aX2, aZ)
  2338. addValueToHeatmap(indexer, heatmap, value*(sXfactor + (1-sZfactor))*0.5, aX, aZ2)
  2339. addValueToHeatmap(indexer, heatmap, value*((1-sXfactor) + (1-sZfactor))*0.5, aX2, aZ2)
  2340.  
  2341. end
  2342.  
  2343. local function editDefenceHeatmap(team,unitID,groundArray,airArray,range,sign,priority)
  2344.  
  2345. local a = aiTeamData[team]
  2346. local ux,uy,uz = spGetUnitPosition(unitID)
  2347. local buildDefs = a.buildDefs
  2348.  
  2349. local aX = math.ceil(ux/heatSquareWidth)
  2350. local aZ = math.ceil(uz/heatSquareHeight)
  2351. local selfDefenceHeatmap = a.selfDefenceHeatmap
  2352. local selfDefenceAirTask = a.selfDefenceAirTask
  2353. local wantedDefence = a.wantedDefence
  2354. local defenceChoice = buildDefs.defenceIds
  2355.  
  2356.  
  2357. for i = 1, buildDefs.airDefenceIdCount do
  2358. local oldValue = selfDefenceHeatmap[aX][aZ][i].air
  2359. selfDefenceHeatmap[aX][aZ][i].air = selfDefenceHeatmap[aX][aZ][i].air + airArray[i]*sign
  2360. if sign > 0 then
  2361. if oldValue < 1 and selfDefenceHeatmap[aX][aZ][i].air >= 1 and selfDefenceHeatmap[aX][aZ][i].airTask == 0 then
  2362. selfDefenceAirTask.count = selfDefenceAirTask.count + 1
  2363. selfDefenceAirTask[selfDefenceAirTask.count] = {aX = aX, aZ = aZ, x = ux, z = uz}
  2364. selfDefenceHeatmap[aX][aZ][i].airTask = selfDefenceAirTask.count
  2365. end
  2366. else
  2367. if oldValue >= 1 and selfDefenceHeatmap[aX][aZ][i].air < 1 and selfDefenceHeatmap[aX][aZ][i].airTask ~= 0 then
  2368. removeIndexFromArray(selfDefenceAirTask,selfDefenceHeatmap[aX][aZ][i].airTask)
  2369. selfDefenceHeatmap[aX][aZ][i].airTask = 0
  2370. end
  2371. end
  2372.  
  2373. end
  2374.  
  2375. for i = 1, buildDefs.defenceIdCount do
  2376. selfDefenceHeatmap[aX][aZ][i].total = selfDefenceHeatmap[aX][aZ][i].total + groundArray[i]*sign
  2377.  
  2378. if sign > 0 then
  2379. selfDefenceHeatmap[aX][aZ][i].toBuild = selfDefenceHeatmap[aX][aZ][i].toBuild + groundArray[i]*sign
  2380.  
  2381. local oldX = false
  2382. local oldZ = false
  2383. local x, z, ox, oz,searchRange
  2384.  
  2385. while selfDefenceHeatmap[aX][aZ][i].toBuild >= 1 do
  2386.  
  2387. local deID = chooseUnitDefID(defenceChoice[i])
  2388. local success = true
  2389.  
  2390. if oldX then
  2391. x = 2*ux - oldX
  2392. z = 2*uz - oldZ
  2393. ox = x
  2394. oz = z
  2395. searchRange = 0
  2396. else
  2397. searchRange = range
  2398. x = ux + math.random(-searchRange,searchRange)
  2399. z = uz + math.random(-searchRange,searchRange)
  2400. ox = ux
  2401. oz = uz
  2402. end
  2403.  
  2404. while spTestBuildOrder(deID, x, 0 ,z, 1) == 0 or nearEcon(team,x,z,50) or nearFactory(team,x,z,200) or nearMexSpot(x,z,60) or nearDefence(team,x,z,140) do
  2405. x = ox + math.random(-searchRange,searchRange)
  2406. z = oz + math.random(-searchRange,searchRange)
  2407. searchRange = searchRange + 10
  2408. if searchRange > range+300 then
  2409. success = false
  2410. break
  2411. end
  2412. end
  2413.  
  2414. if success then
  2415. wantedDefence.count = wantedDefence.count + 1
  2416. wantedDefence[wantedDefence.count] = {ID = deID, x = x, z = z, priority = priority}
  2417. oldX = x
  2418. oldZ = z
  2419. --if priority ~= 0 then
  2420. --Spring.MarkerAddPoint(x,0,z,"Defence Added")
  2421. --end
  2422. end
  2423.  
  2424. selfDefenceHeatmap[aX][aZ][i].toBuild = selfDefenceHeatmap[aX][aZ][i].toBuild - 1
  2425. end
  2426. end
  2427. end
  2428.  
  2429. end
  2430.  
  2431. local function callForMobileDefence(team ,unitID, attackerID, callRange, priority)
  2432.  
  2433. local a = aiTeamData[team]
  2434. local at = allyTeamData[a.allyTeam]
  2435.  
  2436. --SOS code
  2437. if not a.sosTimeout[unitID] or a.sosTimeout[unitID] < spGetGameFrame() then
  2438. if UnitDefs[Spring.GetUnitDefID(unitID)].commander then callRange = callRange * 2 end
  2439. a.sosTimeout[unitID] = spGetGameFrame() + sosTime
  2440. local dx, dy, dz = spGetUnitPosition(attackerID)
  2441. local friendlies = Spring.GetUnitsInCylinder(dx, dz, callRange, team)
  2442. if friendlies then
  2443. for i=1, #friendlies do
  2444. fid = friendlies[i]
  2445. if (a.controlledUnit.combatByID[fid] or a.controlledUnit.raiderByID[fid]) and (not a.unitInBattleGroupByID[fid]) then
  2446. spGiveOrderToUnit(fid, CMD_FIGHT, { dx, 0, dz }, {})
  2447. end
  2448. end
  2449. end
  2450. local battleGroup = a.battleGroup
  2451. for i = 1, battleGroup.count do
  2452. if battleGroup[i].respondToSOSpriority <= priority and disSQ(dx, battleGroup[i].aX, dz, battleGroup[i].aZ) < callRange^2 then
  2453. battleGroup[i].tempTarget = attackerID
  2454. end
  2455. end
  2456. end
  2457.  
  2458. end
  2459.  
  2460. local function spotEnemyUnit(allyTeam, unitID, unitDefID,readd)
  2461.  
  2462. local at = allyTeamData[allyTeam]
  2463.  
  2464. local enemyOffenseHeatmap = at.enemyOffenseHeatmap
  2465. local enemyOffense = at.enemyOffense
  2466. local enemyDefenceHeatmap = at.enemyDefenceHeatmap
  2467. local enemyDefence = at.enemyDefence
  2468. local enemyEconomyHeatmap = at.enemyEconomyHeatmap
  2469. local enemyEconomy = at.enemyEconomy
  2470.  
  2471. local ud = UnitDefs[unitDefID]
  2472.  
  2473. if readd then
  2474. --mapEcho(unitID, "added heatmap")
  2475. end
  2476.  
  2477. if readd or (not at.unitInHeatmap[unitID]) then
  2478. --mapEcho(unitID, "added heatmap")
  2479. local x, y, z = spGetUnitPosition(unitID)
  2480. local aX = math.ceil(x/heatSquareWidth)
  2481. local aZ = math.ceil(z/heatSquareHeight)
  2482.  
  2483. if ud.maxWeaponRange > 0 then -- combat
  2484. at.enemyForceComposition.totalCost = at.enemyForceComposition.totalCost + ud.metalCost
  2485. if ud.weapons[1].onlyTargets.land then
  2486. if ud.speed > 0 then -- offense
  2487. addValueToHeatmap(enemyOffense,enemyOffenseHeatmap, ud.metalCost, aX, aZ)
  2488. else -- defence
  2489. addValueToHeatmapInArea(enemyDefence,enemyDefenceHeatmap, ud.metalCost, x, z)
  2490. end
  2491. end
  2492. elseif ud.buildSpeed > 0 or ud.isFactory or (ud.energyMake > 0 or ud.energyUpkeep < 0) or ud.extractsMetal ~= 0 then -- econ
  2493. addValueToHeatmap(enemyEconomy, enemyEconomyHeatmap, ud.metalCost, aX, aZ)
  2494. end
  2495.  
  2496. end
  2497.  
  2498. if not at.unitInHeatmap[unitID] then
  2499. --mapEcho(unitID, "added composition")
  2500. at.unitInHeatmap[unitID] = true
  2501.  
  2502. local x, y, z = spGetUnitPosition(unitID)
  2503. local aX = math.ceil(x/heatSquareWidth)
  2504. local aZ = math.ceil(z/heatSquareHeight)
  2505.  
  2506. if ud.maxWeaponRange > 0 then -- combat
  2507. at.enemyForceComposition.totalCost = at.enemyForceComposition.totalCost + ud.metalCost
  2508. if ud.speed > 0 then -- offense
  2509. if ud.canFly then
  2510. at.enemyForceComposition.unit.air = at.enemyForceComposition.unit.air + ud.metalCost
  2511. at.enemyHasAir = true
  2512. else
  2513. if ud.weapons[1].onlyTargets.land then
  2514. -- Add to enemy force composition by checking against a table.
  2515. if assaultArray[unitDefID] then
  2516. at.enemyForceComposition.unit.assault = at.enemyForceComposition.unit.assault + ud.metalCost
  2517. elseif skirmArray[unitDefID] then
  2518. at.enemyForceComposition.unit.skirm = at.enemyForceComposition.unit.skirm + ud.metalCost
  2519. elseif riotArray[unitDefID] then
  2520. at.enemyForceComposition.unit.riot = at.enemyForceComposition.unit.riot + ud.metalCost
  2521. elseif raiderArray[unitDefID] then
  2522. at.enemyForceComposition.unit.raider = at.enemyForceComposition.unit.raider + ud.metalCost
  2523. elseif artyArray[unitDefID] then
  2524. at.enemyForceComposition.unit.arty = at.enemyForceComposition.unit.arty + ud.metalCost
  2525. end
  2526. --Spring.MarkerAddPoint(heatmapPosition[aX][aZ].x,0,heatmapPosition[aX][aZ].z,data.cost)
  2527. else
  2528. at.enemyForceComposition.unit.antiAir = at.enemyForceComposition.unit.antiAir + ud.metalCost
  2529. end
  2530. end
  2531. else -- defence
  2532. if ud.weapons[1].onlyTargets.land then
  2533. at.enemyForceComposition.unit.groundDefence = at.enemyForceComposition.unit.groundDefence + ud.metalCost
  2534. else
  2535. at.enemyStaticAA[unitID] = {x = x, z = z, rangeSQ = ud.maxWeaponRange^2, range = ud.maxWeaponRange, cost = ud.metalCost}
  2536. at.enemyForceComposition.unit.airDefence = at.enemyForceComposition.unit.airDefence + ud.metalCost
  2537. end
  2538. end
  2539. elseif ud.buildSpeed > 0 or ud.isFactory or (ud.energyMake > 0 or ud.energyUpkeep < 0) or ud.extractsMetal ~= 0 then -- econ
  2540.  
  2541. end
  2542. end
  2543.  
  2544. if ud.canFly and not ud.isFighter then
  2545. at.fighterTarget = unitID
  2546. end
  2547.  
  2548. if ud.maxWeaponRange > 0 and (not ud.weapons[1].onlyTargets.land) and ud.speed > 0 then
  2549. at.enemyMobileAA[unitID] = {x = x, z = z, rangeSQ = ud.maxWeaponRange^2, range = ud.maxWeaponRange, cost = ud.metalCost, spottedFrame = spGetGameFrame()}
  2550. end
  2551.  
  2552. end
  2553.  
  2554. function gadget:UnitDamaged(unitID, unitDefID, unitTeam, damage, paralyzer, weaponID, attackerID, attackerDefID, attackerTeam)
  2555.  
  2556. if (not aiTeamData[unitTeam]) or allyTeamData[aiTeamData[unitTeam].allyTeam].teams[attackerTeam] then
  2557. return
  2558. end
  2559.  
  2560. local a = aiTeamData[unitTeam]
  2561.  
  2562. if attackerID then
  2563. if jumperArray[unitDefID] then
  2564. local ud = UnitDefs[attackerDefID]
  2565. if not ud.canFly then
  2566. local jump = spGetUnitRulesParam(unitID, "jumpReload")
  2567. if ((not jump) or jump == 1) and spGetUnitSeparation(unitID, attackerID, true) < jumpDefs[unitDefID].range + 100 then
  2568. local cQueue = spGetCommandQueue(unitID)
  2569. if #cQueue == 0 or cQueue[1].id ~= CMD_JUMP then
  2570. local x,y,z = spGetUnitPosition(attackerID)
  2571. spGiveOrderToUnit(unitID, CMD_JUMP, {x+math.random(-30,30),y,z+math.random(-30,30)}, {"alt"} )
  2572. end
  2573. end
  2574. end
  2575. end
  2576.  
  2577. spotEnemyUnit(a.allyTeam, attackerID, attackerDefID, false)
  2578.  
  2579. if prioritySosArray[unitDefID] then
  2580. callForMobileDefence(unitTeam, unitID, attackerID, prioritySosRadius, 1)
  2581. else
  2582. callForMobileDefence(unitTeam, unitID, attackerID, sosRadius, 0)
  2583. end
  2584.  
  2585.  
  2586. if a.controlledUnit.conByID[unitID] and not a.controlledUnit.conByID[unitID].makingDefence then
  2587. local ud = UnitDefs[attackerDefID]
  2588. if ud.speed == 0 then
  2589. makeReponsiveDefence(unitTeam,unitID,attackerID,attackerDefID,200)
  2590. else
  2591. runAway(unitID,attackerID,500)
  2592. end
  2593. end
  2594. end
  2595.  
  2596. end
  2597.  
  2598. function gadget:UnitEnteredLos(unitID, unitTeam, allyTeam, unitDefID)
  2599.  
  2600. if not allyTeamData[allyTeam].ai then
  2601. return
  2602. end
  2603. local at = allyTeamData[allyTeam]
  2604.  
  2605. local scoutingHeatmap = at.scoutingHeatmap
  2606.  
  2607. local x, y, z = spGetUnitPosition(unitID)
  2608. local aX = math.ceil(x/heatSquareWidth)
  2609. local aZ = math.ceil(z/heatSquareHeight)
  2610.  
  2611. --[[if (scoutingHeatmap[aX] and scoutingHeatmap[aX][aZ]) then -- unit outside map
  2612. scoutingHeatmap[aX][aZ].scouted = true
  2613. scoutingHeatmap[aX][aZ].lastScouted = frame
  2614. end--]]
  2615.  
  2616. if (scoutingHeatmap[aX] and scoutingHeatmap[aX][aZ]) then
  2617. spotEnemyUnit(allyTeam,unitID,unitDefID,true)
  2618. end
  2619. end
  2620.  
  2621. local function drawHeatmap(heatmap)
  2622.  
  2623. for i = 1,heatArrayWidth do -- init array
  2624. for j = 1, heatArrayHeight do
  2625. local cost = heatmap[i][j].cost
  2626. if cost > 0 then
  2627. Spring.MarkerAddPoint(heatmapPosition[i][j].x,0,heatmapPosition[i][j].z,cost)
  2628. else
  2629. Spring.MarkerErasePosition(heatmapPosition[i][j].x,0,heatmapPosition[i][j].z)
  2630. end
  2631. end
  2632. end
  2633.  
  2634. end
  2635.  
  2636. local function checkHeatmap(heatmap, indexer, name, team)
  2637. for i = 1, indexer.count do -- init array
  2638. local cost = heatmap[indexer[i].aX][indexer[i].aZ].cost
  2639. if cost == 0 then
  2640. Spring.MarkerAddPoint(indexer[i].x,0,indexer[i].z, "0 cost! " .. name .. ", allyTeam " .. team)
  2641. else
  2642. if heatmap[indexer[i].aX][indexer[i].aZ].index ~= i then
  2643. Spring.MarkerAddPoint(indexer[i].x,0,indexer[i].z, "Index Mismatch from indexer")
  2644. end
  2645. end
  2646. end
  2647.  
  2648. for i = 1,heatArrayWidth do -- init array
  2649. for j = 1, heatArrayHeight do
  2650. if heatmap[i][j].cost ~= 0 then
  2651. if heatmap[i][j].index == 0 then
  2652. Spring.MarkerAddPoint(heatmapPosition[i][j].x,0,heatmapPosition[i][j].z, "Zero Index")
  2653. end
  2654. if not indexer[heatmap[i][j].index] == 0 then
  2655. Spring.MarkerAddPoint(heatmapPosition[i][j].x,0,heatmapPosition[i][j].z, "Null Indexer")
  2656. elseif indexer[heatmap[i][j].index].aX ~= i or indexer[heatmap[i][j].index].aZ ~= j then
  2657. Spring.MarkerAddPoint(heatmapPosition[i][j].x,0,heatmapPosition[i][j].z, "Index Mismatch from heatmap")
  2658. end
  2659. end
  2660. end
  2661. end
  2662.  
  2663. end
  2664.  
  2665. local function drawHeatmapIndex(heatmap, indexer)
  2666. for i = 1, indexer.count do -- init array
  2667. Spring.MarkerAddPoint(indexer[i].x,0,indexer[i].z, heatmap[indexer[i].aX][indexer[i].aZ].cost)
  2668. end
  2669. end
  2670.  
  2671. local function initialiseFaction(team)
  2672.  
  2673. local a = aiTeamData[team]
  2674. if a.buildDefs then
  2675. return true
  2676. end
  2677.  
  2678. local shortname = Game.modShortName
  2679. if shortname == "ZK" then
  2680. a.buildDefs = a.buildConfig.robots
  2681. return true
  2682. else
  2683. local units = spGetTeamUnits(team)
  2684. for i = 1, #units do
  2685. local ud = UnitDefs[spGetUnitDefID(units[i])]
  2686. if ud.customParams then
  2687. local faction = ud.customParams.factionname
  2688. if faction == "arm" then
  2689. a.buildDefs = a.buildConfig.arm
  2690. return true
  2691. elseif faction == "core" then
  2692. a.buildDefs = a.buildConfig.core
  2693. return true
  2694. end
  2695. end
  2696. end
  2697. end
  2698.  
  2699. return false
  2700. end
  2701.  
  2702. local function echoEnemyForceComposition(allyTeam)
  2703. local at = allyTeamData[allyTeam]
  2704. Spring.Echo("")
  2705. for index,value in pairs(at.relativeEnemyForceComposition.unit) do
  2706. Spring.Echo(index .. " \t\t" .. value .. "\t\t" .. at.enemyForceComposition.unit[index])
  2707. end
  2708. end
  2709.  
  2710. local function echoConJobList(team)
  2711. local data = aiTeamData[team]
  2712. Spring.Echo("")
  2713. for index,value in pairs(data.conJob) do
  2714. Spring.Echo(conJobNames[index] .. " " .. math.floor(value.importance*1000) .. " " .. value.assignedBP)
  2715. end
  2716. end
  2717.  
  2718. local function echoFacJobList(team)
  2719. local data = aiTeamData[team]
  2720. Spring.Echo("")
  2721. for index,value in ipairs(data.facJob) do
  2722. Spring.Echo(factoryJobNames[index] .. " " .. math.floor(value.importance*1000))
  2723. end
  2724. Spring.Echo("")
  2725. for index,value in ipairs(data.facJobAir) do
  2726. Spring.Echo(airFactoryJobNames[index] .. " " .. math.floor(value.importance*1000))
  2727. end
  2728. end
  2729.  
  2730. function gadget:GameFrame(n)
  2731.  
  2732. for team,_ in pairs(aiTeamData) do
  2733.  
  2734. initialiseFaction(team)
  2735.  
  2736. if n%60 == 0 + team then
  2737. updateTeamResourcing(team)
  2738. executeControlFunction(team, n)
  2739. conJobAllocator(team)
  2740. end
  2741.  
  2742. if n%40 == 15 + team then
  2743. battleGroupHandler(team, n, n%200 == 15)
  2744. end
  2745.  
  2746. if n%40 == 35 + team then
  2747. raiderJobHandler(team)
  2748. combatJobHandler(team)
  2749. artyJobHandler(team)
  2750. bomberJobHandler(team)
  2751. fighterJobHandler(team)
  2752. gunshipJobHandler(team)
  2753. end
  2754.  
  2755. if n%30 == 0 + team then
  2756. conJobHandler(team)
  2757. factoryJobHandler(team)
  2758. scoutJobHandler(team)
  2759. end
  2760.  
  2761. if n%200 == 0 + team then
  2762. if debugData.showConJobList[team] then
  2763. echoConJobList(team)
  2764. end
  2765. if debugData.showFacJobList[team] then
  2766. echoFacJobList(team)
  2767. end
  2768. end
  2769.  
  2770. end
  2771.  
  2772. for allyTeam,data in pairs(allyTeamData) do
  2773.  
  2774. if n%50 == 25 + allyTeam then
  2775. if data.ai then
  2776. updateScoutingHeatmap(allyTeam,n, n%500 == 25)
  2777. end
  2778. end
  2779.  
  2780. if n%200 == 0 + allyTeam then
  2781. if debugData.showEnemyForceCompostion[team] then
  2782. echoEnemyForceComposition(allyTeam)
  2783. end
  2784. end
  2785.  
  2786. if n%120 == 30 + allyTeam then
  2787. if data.ai then
  2788.  
  2789. decayEnemyHeatmaps(allyTeam,n)
  2790. decayEnemyMobileAA(allyTeam,n)
  2791. diluteEnemyForceComposition(allyTeam)
  2792.  
  2793. -- DEBUG functions, check entire heatmap index consistency. They look slow
  2794. --checkHeatmap(allyTeamData[allyTeam].enemyOffenseHeatmap, allyTeamData[allyTeam].enemyOffense,allyTeam,"Offense")
  2795. --checkHeatmap(allyTeamData[allyTeam].enemyEconomyHeatmap, allyTeamData[allyTeam].enemyEconomy,allyTeam,"Economy")
  2796. --checkHeatmap(allyTeamData[allyTeam].enemyDefenceHeatmap, allyTeamData[allyTeam].enemyDefence,allyTeam,"Defence")
  2797.  
  2798. if debugData.drawOffensemap[allyTeam] then
  2799. drawHeatmapIndex(allyTeamData[allyTeam].enemyOffenseHeatmap, allyTeamData[allyTeam].enemyOffense)
  2800. end
  2801. if debugData.drawEconmap[allyTeam] then
  2802. drawHeatmapIndex(allyTeamData[allyTeam].enemyEconomyHeatmap, allyTeamData[allyTeam].enemyEconomy)
  2803. end
  2804. if debugData.drawDefencemap[allyTeam] then
  2805. drawHeatmapIndex(allyTeamData[allyTeam].enemyDefenceHeatmap, allyTeamData[allyTeam].enemyDefence)
  2806. end
  2807. end
  2808. end
  2809.  
  2810. end
  2811. end
  2812.  
  2813. function gadget:UnitGiven(unitID, unitDefID, teamID, oldTeamID)
  2814. gadget:UnitDestroyed(unitID, unitDefID, oldTeamID)
  2815. gadget:UnitCreated(unitID, unitDefID, teamID)
  2816. local _,_,_,_,build = spGetUnitHealth(unitID)
  2817. if build == 1 then -- will catch reverse build, UnitFinished will not ever be called for this unit
  2818. gadget:UnitFinished(unitID, unitDefID, team)
  2819. end
  2820. end
  2821.  
  2822. function gadget:UnitDestroyed(unitID, unitDefID, unitTeam)
  2823.  
  2824. local allyTeam = spGetUnitAllyTeam(unitID)
  2825. local ud = UnitDefs[unitDefID]
  2826.  
  2827.  
  2828. if (aiTeamData[unitTeam]) then
  2829.  
  2830. local a = aiTeamData[unitTeam]
  2831. local controlledUnit = a.controlledUnit
  2832.  
  2833. if (ud ~= nil) and initialiseFaction(unitTeam) and controlledUnit.anyByID[unitID] then
  2834. local buildDefs = a.buildDefs
  2835. if unitDefID == buildDefs.airpadDefID then
  2836. controlledUnit.airpad.cost = controlledUnit.airpad.cost - ud.metalCost
  2837. controlledUnit.airpad.count = controlledUnit.airpad.count - 1
  2838. controlledUnit.airpadByID[unitID] = nil
  2839. elseif ud.extractsMetal > 0 then
  2840. if controlledUnit.mexByID[unitID].onDefenceHeatmap then
  2841. editDefenceHeatmap(unitTeam,unitID,buildDefs.econByDefId[unitDefID].defenceQuota,buildDefs.econByDefId[unitDefID].airDefenceQuota,buildDefs.econByDefId[unitDefID].defenceRange,-1,0)
  2842. end
  2843. controlledUnit.mex.cost = controlledUnit.mex.cost - ud.metalCost
  2844. local index = controlledUnit.mexByID[unitID].index
  2845. controlledUnit.mexByID[controlledUnit.mex[controlledUnit.mex.count]].index = index
  2846. controlledUnit.mexByID[unitID] = nil
  2847. removeIndexFromArray(controlledUnit.mex,index)
  2848. elseif ud.isFactory then -- factory
  2849. if controlledUnit.factoryByID[unitID].onDefenceHeatmap then
  2850. editDefenceHeatmap(unitTeam,unitID,buildDefs.factoryByDefId[unitDefID].defenceQuota,buildDefs.factoryByDefId[unitDefID].airDefenceQuota,buildDefs.factoryByDefId[unitDefID].defenceRange,-1,0)
  2851. end
  2852. controlledUnit.factory.cost = controlledUnit.factory.cost - ud.metalCost
  2853. if controlledUnit.factoryByID[unitID].finished then
  2854. a.totalBP = a.totalBP - controlledUnit.factoryByID[unitID].bp
  2855. a.conJob.factory.assignedBP = a.conJob.factory.assignedBP - controlledUnit.factoryByID[unitID].bp
  2856. else
  2857. a.uncompletedFactory = false
  2858. end
  2859. a.totalFactoryBPQuota = a.totalFactoryBPQuota - buildDefs.factoryByDefId[unitDefID].BPQuota
  2860. a.factoryCountByDefID[unitDefID] = a.factoryCountByDefID[unitDefID] - 1
  2861. local index = controlledUnit.factoryByID[unitID].index
  2862. controlledUnit.factoryByID[controlledUnit.factory[controlledUnit.factory.count]].index = index
  2863. controlledUnit.factoryByID[unitID] = nil
  2864. removeIndexFromArray(controlledUnit.factory,index)
  2865. elseif ud.buildSpeed > 0 then
  2866. if ud.speed > 0 then -- constructor
  2867. if controlledUnit.conByID[unitID].finished then
  2868. a.totalBP = a.totalBP - controlledUnit.conByID[unitID].bp
  2869. local jobIndex = controlledUnit.conByID[unitID].currentJob
  2870. if jobIndex > 0 then
  2871. a.conJobByIndex[jobIndex].assignedBP = a.conJobByIndex[jobIndex].assignedBP - controlledUnit.conByID[unitID].bp
  2872. a.conJobByIndex[jobIndex].con[unitID] = nil
  2873. end
  2874. for i = 1, a.unassignedCons.count do
  2875. if a.unassignedCons[i] == unitID then
  2876. removeIndexFromArray(a.unassignedCons,i)
  2877. break
  2878. end
  2879. end
  2880. end
  2881. controlledUnit.con.cost = controlledUnit.con.cost - ud.metalCost
  2882. controlledUnit.con.count = controlledUnit.con.count - 1
  2883. controlledUnit.conByID[unitID] = nil
  2884. else -- nano turret
  2885. controlledUnit.nano.cost = controlledUnit.nano.cost - ud.metalCost
  2886. controlledUnit.nano.count = controlledUnit.nano.count - 1
  2887. local index = controlledUnit.nanoByID[unitID].index
  2888. closestFactory = controlledUnit.nanoByID[unitID].closestFactory
  2889. if a.controlledUnit.factoryByID[closestFactory] then
  2890. a.controlledUnit.factoryByID[closestFactory].nanoCount = a.controlledUnit.factoryByID[closestFactory].nanoCount - 1
  2891. end
  2892. controlledUnit.nanoByID[unitID] = nil
  2893. removeIndexFromArray(controlledUnit.nano,index)
  2894. end
  2895. elseif controlledUnit.anyByID[unitID].isScout then
  2896. controlledUnit.scout.cost = controlledUnit.scout.cost - ud.metalCost
  2897. controlledUnit.scout.count = controlledUnit.scout.count - 1
  2898. controlledUnit.scoutByID[unitID] = nil
  2899. elseif controlledUnit.anyByID[unitID].isRaider then
  2900. controlledUnit.raider.cost = controlledUnit.raider.cost - ud.metalCost
  2901. controlledUnit.raider.count = controlledUnit.raider.count - 1
  2902. controlledUnit.raiderByID[unitID] = nil
  2903. elseif ud.canFly then -- aircraft
  2904. if ud.maxWeaponRange > 0 then
  2905. if ud.isFighter then -- fighter
  2906. controlledUnit.fighter.cost = controlledUnit.fighter.cost - ud.metalCost
  2907. controlledUnit.fighter.count = controlledUnit.fighter.count - 1
  2908. controlledUnit.fighterByID[unitID] = nil
  2909. elseif ud.isBomber then -- bomber
  2910. controlledUnit.bomber.cost = controlledUnit.bomber.cost - ud.metalCost
  2911. controlledUnit.bomber.count = controlledUnit.bomber.count - 1
  2912. controlledUnit.bomberByID[unitID] = nil
  2913. else -- gunship
  2914. controlledUnit.gunship.cost = controlledUnit.gunship.cost - ud.metalCost
  2915. controlledUnit.gunship.count = controlledUnit.gunship.count - 1
  2916. controlledUnit.gunshipByID[unitID] = nil
  2917. end
  2918. else -- scout plane
  2919. controlledUnit.scout.cost = controlledUnit.scout.cost - ud.metalCost
  2920. controlledUnit.scout.count = controlledUnit.scout.count - 1
  2921. controlledUnit.scoutByID[unitID] = nil
  2922. end
  2923. elseif ud.maxWeaponRange > 0 and ud.speed > 0 then -- land combat unit
  2924.  
  2925. if ud.weapons[1].onlyTargets.land then -- land firing combat
  2926. if ud.speed >= 3*30 then -- raider
  2927. controlledUnit.raider.cost = controlledUnit.raider.cost - ud.metalCost
  2928. controlledUnit.raider.count = controlledUnit.raider.count - 1
  2929. controlledUnit.raiderByID[unitID] = nil
  2930. elseif ud.maxWeaponRange > 650 then -- arty
  2931. controlledUnit.arty.cost = controlledUnit.arty.cost - ud.metalCost
  2932. controlledUnit.arty.count = controlledUnit.arty.count - 1
  2933. controlledUnit.artyByID[unitID] = nil
  2934. else -- other combat
  2935. controlledUnit.combat.cost = controlledUnit.combat.cost - ud.metalCost
  2936. controlledUnit.combat.count = controlledUnit.combat.count - 1
  2937. controlledUnit.combatByID[unitID] = nil
  2938. end
  2939. else -- mobile anti air
  2940. controlledUnit.aa.cost = controlledUnit.aa.cost - ud.metalCost
  2941. controlledUnit.aa.count = controlledUnit.aa.count - 1
  2942. controlledUnit.aaByID[unitID] = nil
  2943. end
  2944.  
  2945. elseif ud.isBuilding or ud.speed == 0 then -- building
  2946. if ud.maxWeaponRange > 0 then -- turret
  2947. a.wantedDefence.count = a.wantedDefence.count + 1
  2948. a.wantedDefence[a.wantedDefence.count] = {ID = ud.id,
  2949. x = controlledUnit.turretByID[unitID].x, z = controlledUnit.turretByID[unitID].z, priority = 0}
  2950. controlledUnit.turret.cost = controlledUnit.turret.cost - ud.metalCost
  2951. controlledUnit.turret.count = controlledUnit.turret.count - 1
  2952. controlledUnit.turretByID[unitID] = nil
  2953. elseif (ud.energyMake > 0 or ud.energyUpkeep < 0) then
  2954. controlledUnit.econ.cost = controlledUnit.econ.cost - ud.metalCost
  2955. if controlledUnit.econByID[unitID].onDefenceHeatmap then
  2956. editDefenceHeatmap(unitTeam,unitID,buildDefs.econByDefId[unitDefID].defenceQuota,buildDefs.econByDefId[unitDefID].airDefenceQuota,buildDefs.econByDefId[unitDefID].defenceRange,-1,0)
  2957. end
  2958. local index = controlledUnit.econByID[unitID].index
  2959. controlledUnit.econByID[controlledUnit.econ[controlledUnit.econ.count]].index = index
  2960. controlledUnit.econByID[unitID] = nil
  2961. removeIndexFromArray(controlledUnit.econ,index)
  2962. end
  2963. end
  2964.  
  2965. controlledUnit.any.cost = controlledUnit.any.cost - ud.metalCost
  2966. controlledUnit.any.count = controlledUnit.any.count - 1
  2967. controlledUnit.anyByID[unitID] = nil
  2968. end
  2969. end
  2970.  
  2971. if (ud ~= nil) then
  2972. local units = allyTeamData[allyTeam].units
  2973.  
  2974. units.cost = units.cost - ud.metalCost
  2975. units.cost = units.cost + ud.metalCost
  2976.  
  2977. if ud.extractsMetal > 0 then
  2978. units.mex.count = units.mex.count - 1
  2979. end
  2980. units.factoryByID[unitID] = nil
  2981. units.econByID[unitID] = nil
  2982. units.radarByID[unitID] = nil
  2983. units.mexByID[unitID] = nil
  2984. units.turretByID[unitID] = nil
  2985.  
  2986. for i,at in pairs(allyTeamData) do
  2987. if at.ai then
  2988. at.enemyMobileAA[unitID] = nil
  2989. at.enemyStaticAA[unitID] = nil
  2990. end
  2991. --[[if at.ai and at.unitInHeatmap[unitID] then
  2992.  
  2993. if assaultArray[unitDefID] then
  2994. at.enemyForceComposition.unit.assault = at.enemyForceComposition.unit.assault - ud.metalCost
  2995. elseif skirmArray[unitDefID] then
  2996. at.enemyForceComposition.unit.skirm = at.enemyForceComposition.unit.skirm - ud.metalCost
  2997. elseif riotArray[unitDefID] then
  2998. at.enemyForceComposition.unit.riot = at.enemyForceComposition.unit.riot - ud.metalCost
  2999. elseif raiderArray[unitDefID] then
  3000. at.enemyForceComposition.unit.raider = at.enemyForceComposition.unit.raider - ud.metalCost
  3001. elseif artyArray[unitDefID] then
  3002. at.enemyForceComposition.unit.arty = at.enemyForceComposition.unit.arty - ud.metalCost
  3003. end
  3004.  
  3005. if ud.maxWeaponRange > 0 and (not ud.weapons[1].onlyTargets.land) and ud.speed > 0 then
  3006. at.enemyMobileAA[unitID] = nil
  3007. at.enemyForceComposition.unit.antiAir = at.enemyForceComposition.unit.antiAir - ud.metalCost
  3008. end
  3009.  
  3010. if ud.maxWeaponRange > 0 and ud.canFly then
  3011. at.enemyForceComposition.unit.air = at.enemyForceComposition.unit.air - ud.metalCost
  3012. end
  3013.  
  3014. if ud.maxWeaponRange > 0 and ud.speed == 0 then
  3015. if ud.weapons[1].onlyTargets.land then
  3016. at.enemyForceComposition.unit.groundDefence = at.enemyForceComposition.unit.groundDefence - ud.metalCost
  3017. else
  3018. at.enemyStaticAA[unitID] = nil
  3019. at.enemyForceComposition.unit.airDefence = at.enemyForceComposition.unit.airDefence - ud.metalCost
  3020. end
  3021. end
  3022.  
  3023. at.unitInHeatmap[unitID] = nil
  3024. end--]]
  3025. end
  3026. end
  3027.  
  3028. end
  3029.  
  3030. function gadget:UnitCreated(unitID, unitDefID, unitTeam, builderID)
  3031.  
  3032. local allyTeam = spGetUnitAllyTeam(unitID)
  3033. local ud = UnitDefs[unitDefID]
  3034. if (aiTeamData[unitTeam]) then
  3035.  
  3036. local a = aiTeamData[unitTeam]
  3037. local controlledUnit = a.controlledUnit
  3038.  
  3039. local _,_,_,_,build = spGetUnitHealth(unitID)
  3040. local built = (build == 1)
  3041.  
  3042. if (ud ~= nil) and initialiseFaction(unitTeam) then
  3043. local buildDefs = a.buildDefs
  3044.  
  3045. controlledUnit.any.cost = controlledUnit.any.cost + ud.metalCost
  3046. controlledUnit.any.count = controlledUnit.any.count + 1
  3047. controlledUnit.anyByID[unitID] = {ud = ud, cost = ud.metalCost, finished = false,
  3048. isScout = (builderID and controlledUnit.factoryByID[builderID] and controlledUnit.factoryByID[builderID].producingScout),
  3049. isRaider = (builderID and controlledUnit.factoryByID[builderID] and controlledUnit.factoryByID[builderID].producingRaider)}
  3050.  
  3051. if unitDefID == buildDefs.airpadDefID then
  3052. controlledUnit.airpad.cost = controlledUnit.airpad.cost + ud.metalCost
  3053. controlledUnit.airpad.count = controlledUnit.airpad.count + 1
  3054. controlledUnit.airpadByID[unitID] = { ud = ud, cost = ud.metalCost, finished = false}
  3055. elseif ud.extractsMetal > 0 then
  3056. local x,y,z = spGetUnitPosition(unitID)
  3057. if not built then
  3058. editDefenceHeatmap(unitTeam,unitID,buildDefs.econByDefId[unitDefID].defenceQuota,buildDefs.econByDefId[unitDefID].airDefenceQuota,buildDefs.econByDefId[unitDefID].defenceRange,1,0)
  3059. end
  3060. controlledUnit.mex.count = controlledUnit.mex.count + 1
  3061. controlledUnit.mex[controlledUnit.mex.count] = unitID
  3062. controlledUnit.mexByID[unitID] = {finished = false,index = controlledUnit.mex.count,
  3063. ud = ud, x = x, y = y, z = z, cost = ud.metalCost, onDefenceHeatmap = not built}
  3064. elseif ud.isFactory then -- factory
  3065. local x,y,z = spGetUnitPosition(unitID)
  3066. local mx,my,mz = getPositionTowardsMiddle(unitID, 500)
  3067. local amx,amy,amz = getPositionTowardsMiddle(unitID, -250)
  3068. local amx,amy,amz = getPositionTowardsMiddle(unitID, -250)
  3069. if not built then
  3070. editDefenceHeatmap(unitTeam,unitID,buildDefs.factoryByDefId[unitDefID].defenceQuota,buildDefs.factoryByDefId[unitDefID].airDefenceQuota,buildDefs.factoryByDefId[unitDefID].defenceRange,1,1)
  3071. end
  3072. a.totalFactoryBPQuota = a.totalFactoryBPQuota + buildDefs.factoryByDefId[unitDefID].BPQuota
  3073. a.uncompletedFactory = unitID
  3074. if a.factoryCountByDefID[unitDefID] then
  3075. a.factoryCountByDefID[unitDefID] = a.factoryCountByDefID[unitDefID] + 1
  3076. else
  3077. a.factoryCountByDefID[unitDefID] = 1
  3078. end
  3079. controlledUnit.factory.count = controlledUnit.factory.count + 1
  3080. controlledUnit.factory.cost = controlledUnit.factory.cost + ud.metalCost
  3081. controlledUnit.factory[controlledUnit.factory.count] = unitID
  3082. controlledUnit.factoryByID[unitID] = {finished = false,index = controlledUnit.factory.count, nanoCount = 0,
  3083. ud = ud,bp = ud.buildSpeed, x = x, y = y, z = z, cost = ud.metalCost, producingScout = false, producingRaider = false,
  3084. wayX = mx, wayY = my, wayZ = mz, nanoX = amx, nanoY = amy, nanoZ = amz, onDefenceHeatmap = not built}
  3085. elseif ud.buildSpeed > 0 then
  3086. if ud.speed > 0 then -- constructor
  3087. controlledUnit.con.count = controlledUnit.con.count + 1
  3088. controlledUnit.con.cost = controlledUnit.con.cost + ud.metalCost
  3089. controlledUnit.conByID[unitID] = {ud = ud,bp = ud.buildSpeed, finished = false, index = controlledUnit.con.count, idle = true, currentJob = 0, oldJob = 0, makingDefence = false}
  3090. else -- nano turret
  3091. local x,y,z = spGetUnitPosition(unitID)
  3092. controlledUnit.nano.count = controlledUnit.nano.count + 1
  3093. controlledUnit.nano[controlledUnit.nano.count] = unitID
  3094. closestFactory = false
  3095. minDis = false
  3096. for i = 1, a.controlledUnit.factory.count do
  3097. local fid = a.controlledUnit.factory[i]
  3098. local data = a.controlledUnit.factoryByID[fid]
  3099. local dis = disSQ(data.x, data.z, x, z)
  3100. if (not minDis) or dis < minDis then
  3101. closestFactory = fid
  3102. minDis = dis
  3103. end
  3104. end
  3105. if closestFactory then
  3106. a.controlledUnit.factoryByID[closestFactory].nanoCount = a.controlledUnit.factoryByID[closestFactory].nanoCount + 1
  3107. end
  3108. controlledUnit.nanoByID[unitID] = {index = controlledUnit.nano.count, finished = false, ud = ud,
  3109. bp = ud.buildSpeed, x = x, y = y, z = z, cost = ud.metalCost, closestFactory = closestFactory}
  3110. end
  3111. elseif controlledUnit.anyByID[unitID].isScout then
  3112. controlledUnit.scout.cost = controlledUnit.scout.cost + ud.metalCost
  3113. controlledUnit.scout.count = controlledUnit.scout.count + 1
  3114. controlledUnit.scoutByID[unitID] = { ud = ud, cost = ud.metalCost, finished = false}
  3115. elseif controlledUnit.anyByID[unitID].isRaider then
  3116. controlledUnit.raider.cost = controlledUnit.raider.cost + ud.metalCost
  3117. controlledUnit.raider.count = controlledUnit.raider.count + 1
  3118. controlledUnit.raiderByID[unitID] = { ud = ud, cost = ud.metalCost, finished = false}
  3119. if ud.canFly then
  3120. Spring.Echo("flying raider")
  3121. end
  3122. elseif ud.canFly then -- aircraft
  3123. if ud.maxWeaponRange > 0 then
  3124. if ud.isFighter then -- fighter
  3125. controlledUnit.fighter.cost = controlledUnit.fighter.cost + ud.metalCost
  3126. controlledUnit.fighter.count = controlledUnit.fighter.count + 1
  3127. controlledUnit.fighterByID[unitID] = { ud = ud, cost = ud.metalCost, finished = false}
  3128. elseif ud.isBomber then -- bomber
  3129. controlledUnit.bomber.cost = controlledUnit.bomber.cost + ud.metalCost
  3130. controlledUnit.bomber.count = controlledUnit.bomber.count + 1
  3131. controlledUnit.bomberByID[unitID] = { ud = ud, cost = ud.metalCost, finished = false}
  3132. else -- gunship
  3133. controlledUnit.gunship.cost = controlledUnit.gunship.cost + ud.metalCost
  3134. controlledUnit.gunship.count = controlledUnit.gunship.count + 1
  3135. controlledUnit.gunshipByID[unitID] = { ud = ud, cost = ud.metalCost, finished = false}
  3136. end
  3137. else -- scout plane
  3138. controlledUnit.scout.cost = controlledUnit.scout.cost + ud.metalCost
  3139. controlledUnit.scout.count = controlledUnit.scout.count + 1
  3140. controlledUnit.scoutByID[unitID] = { ud = ud, cost = ud.metalCost, finished = false}
  3141. end
  3142. elseif ud.maxWeaponRange > 0 and ud.speed > 0 then -- land combat unit
  3143.  
  3144. if ud.weapons[1].onlyTargets.land then -- land firing combat
  3145. if ud.speed >= 3*30 then -- raider
  3146. controlledUnit.raider.cost = controlledUnit.raider.cost + ud.metalCost
  3147. controlledUnit.raider.count = controlledUnit.raider.count + 1
  3148. controlledUnit.raiderByID[unitID] = { ud = ud, cost = ud.metalCost, finished = false}
  3149. elseif ud.maxWeaponRange > 650 then -- arty
  3150. --spSetUnitSensorRadius(unitID,"los",ud.maxWeaponRange + 20)
  3151. controlledUnit.arty.cost = controlledUnit.arty.cost + ud.metalCost
  3152. controlledUnit.arty.count = controlledUnit.arty.count + 1
  3153. controlledUnit.artyByID[unitID] = { ud = ud, cost = ud.metalCost, finished = false}
  3154. else -- other combat
  3155. controlledUnit.combat.cost = controlledUnit.combat.cost + ud.metalCost
  3156. controlledUnit.combat.count = controlledUnit.combat.count + 1
  3157. controlledUnit.combatByID[unitID] = { ud = ud, cost = ud.metalCost, finished = false}
  3158. end
  3159. else -- mobile anti air
  3160. controlledUnit.aa.cost = controlledUnit.aa.cost + ud.metalCost
  3161. controlledUnit.aa.count = controlledUnit.aa.count + 1
  3162. controlledUnit.aaByID[unitID] = { ud = ud, cost = ud.metalCost, finished = false}
  3163. end
  3164.  
  3165. elseif ud.isBuilding or ud.speed == 0 then -- building
  3166. if ud.maxWeaponRange > 0 then -- turret
  3167. local x,y,z = spGetUnitPosition(unitID)
  3168. for i = 1, a.wantedDefence.count do
  3169. if math.abs(a.wantedDefence[i].x - x) < 16 and math.abs(a.wantedDefence[i].z - z) < 16 then
  3170. removeIndexFromArray(a.wantedDefence,i)
  3171. break
  3172. end
  3173. end
  3174. controlledUnit.turret.cost = controlledUnit.turret.cost + ud.metalCost
  3175. controlledUnit.turret.count = controlledUnit.turret.count + 1
  3176. controlledUnit.turretByID[unitID] = {index = controlledUnit.turret.count, ud = ud,x = x, y = y, z = z, cost = ud.metalCost, finished = false, air = not ud.weapons[1].onlyTargets.land }
  3177. elseif (ud.energyMake > 0 or ud.energyUpkeep < 0) then
  3178. local x,y,z = spGetUnitPosition(unitID)
  3179. if not built then
  3180. editDefenceHeatmap(unitTeam,unitID,buildDefs.econByDefId[unitDefID].defenceQuota,buildDefs.econByDefId[unitDefID].airDefenceQuota,buildDefs.econByDefId[unitDefID].defenceRange,1,0)
  3181. end
  3182. controlledUnit.econ.cost = controlledUnit.econ.cost + ud.metalCost
  3183. controlledUnit.econ.count = controlledUnit.econ.count + 1
  3184. controlledUnit.econ[controlledUnit.econ.count] = unitID
  3185. controlledUnit.econByID[unitID] = {index = controlledUnit.econ.count,finished = false,
  3186. ud = ud,x = x, y = y, z = z, nearbyTurrets = 0, cost = ud.metalCost, onDefenceHeatmap = not built}
  3187. elseif ud.radarRadius > 0 then -- radar
  3188. controlledUnit.radar.cost = controlledUnit.econ.cost + ud.metalCost
  3189. controlledUnit.radar.count = controlledUnit.econ.count + 1
  3190. controlledUnit.radarByID[unitID] = {finished = false, ud = ud,x = x, y = y, z = z, cost = ud.metalCost}
  3191. end
  3192. end
  3193. end
  3194. end
  3195.  
  3196. if (ud ~= nil) then
  3197. local units = allyTeamData[allyTeam].units
  3198.  
  3199. units.cost = units.cost + ud.metalCost
  3200.  
  3201. if ud.extractsMetal > 0 then
  3202. units.mex.count = units.mex.count + 1
  3203. units.mexByID[unitID] = true
  3204. elseif ud.isFactory then -- factory
  3205. local mx,my,mz = getPositionTowardsMiddle(unitID, 450)
  3206. units.factoryByID[unitID] = {wayX = mx, wayY = my, wayZ = mz}
  3207. --mapEcho(unitID,"factory added")
  3208. elseif ud.buildSpeed > 0 then
  3209.  
  3210. elseif ud.canFly then -- aircraft
  3211.  
  3212. elseif ud.maxWeaponRange > 0 and ud.speed > 0 then -- land combat unit
  3213.  
  3214. elseif ud.isBuilding or ud.speed == 0 then -- building
  3215. if ud.maxWeaponRange > 0 then
  3216. units.turretByID[unitID] = true
  3217. elseif (ud.energyMake > 0 or ud.energyUpkeep < 0) then
  3218. units.econByID[unitID] = true
  3219. elseif ud.radarRadius > 0 then -- radar
  3220. units.radarByID[unitID] = true
  3221. end
  3222. end
  3223. end
  3224.  
  3225. end
  3226.  
  3227. -- adds some things that can only be done on unit completion
  3228. function gadget:UnitFinished(unitID, unitDefID, unitTeam)
  3229.  
  3230. local allyTeam = spGetUnitAllyTeam(unitID)
  3231. local ud = UnitDefs[unitDefID]
  3232.  
  3233. if (aiTeamData[unitTeam]) then
  3234.  
  3235. local a = aiTeamData[unitTeam]
  3236. local controlledUnit = a.controlledUnit
  3237.  
  3238. if (ud ~= nil) and initialiseFaction(unitTeam) and controlledUnit.anyByID[unitID] then
  3239. local buildDefs = a.buildDefs
  3240.  
  3241. controlledUnit.anyByID[unitID].finished = true
  3242. if unitDefID == buildDefs.airpadDefID then
  3243. controlledUnit.airpadByID[unitID].finished = true
  3244. elseif ud.extractsMetal > 0 then
  3245. controlledUnit.mexByID[unitID].finished = true
  3246. elseif ud.isFactory then -- factory
  3247. a.conJob.factory.assignedBP = a.conJob.factory.assignedBP + ud.buildSpeed
  3248. a.totalBP = a.totalBP + controlledUnit.factoryByID[unitID].bp
  3249. a.uncompletedFactory = false
  3250. controlledUnit.factoryByID[unitID].finished = true
  3251. elseif ud.buildSpeed > 0 then
  3252. if ud.speed > 0 then -- constructor
  3253. a.totalBP = a.totalBP + controlledUnit.conByID[unitID].bp
  3254. a.unassignedCons.count = a.unassignedCons.count + 1
  3255. a.unassignedCons[a.unassignedCons.count] = unitID
  3256. controlledUnit.conByID[unitID].finished = true
  3257. else -- nano turret
  3258. local x,y,z = spGetUnitPosition(unitID)
  3259. spGiveOrderToUnit(unitID, CMD_MOVE_STATE, { 2 }, {})
  3260. spGiveOrderToUnit(unitID, CMD_PATROL, { x + 25, y, z - 25 }, {})
  3261. controlledUnit.nanoByID[unitID].finished = true
  3262. end
  3263. elseif controlledUnit.anyByID[unitID].isScout then
  3264. controlledUnit.scoutByID[unitID].finished = true
  3265. elseif controlledUnit.anyByID[unitID].isRaider then
  3266. controlledUnit.raiderByID[unitID].finished = true
  3267. elseif ud.canFly then -- aircraft
  3268. if ud.maxWeaponRange > 0 then
  3269. spGiveOrderToUnit(unitID, CMD_MOVE_STATE, { 1 }, {})
  3270. spGiveOrderToUnit(unitID, CMD_FIRE_STATE, { 2 }, {})
  3271. if ud.isFighter then -- fighter
  3272. controlledUnit.fighterByID[unitID].finished = true
  3273. elseif ud.isBomber then -- bomber
  3274. controlledUnit.bomberByID[unitID].finished = true
  3275. else -- gunship
  3276. controlledUnit.gunshipByID[unitID].finished = true
  3277. end
  3278. else -- scout plane
  3279. controlledUnit.scoutByID[unitID].finished = true
  3280. end
  3281. elseif ud.maxWeaponRange > 0 and ud.speed > 0 then -- land combat unit
  3282. spGiveOrderToUnit(unitID, CMD_MOVE_STATE, { 1 }, {})
  3283. spGiveOrderToUnit(unitID, CMD_FIRE_STATE, { 2 }, {})
  3284. if ud.weapons[1].onlyTargets.land then -- land firing combat
  3285. if ud.speed >= 3*30 then -- raider
  3286. controlledUnit.raiderByID[unitID].finished = true
  3287. elseif ud.maxWeaponRange > 650 then -- arty
  3288. controlledUnit.artyByID[unitID].finished = true
  3289. else -- other combat
  3290. controlledUnit.combatByID[unitID].finished = true
  3291. end
  3292. else -- mobile anti air
  3293. controlledUnit.aaByID[unitID].finished = true
  3294. end
  3295.  
  3296. elseif ud.isBuilding or ud.speed == 0 then -- building
  3297. if ud.maxWeaponRange > 0 then -- turret
  3298. controlledUnit.turretByID[unitID].finished = true
  3299. elseif (ud.energyMake > 0 or ud.energyUpkeep < 0) then
  3300. controlledUnit.econByID[unitID].finished = true
  3301. elseif ud.radarRadius > 0 then -- radar
  3302. controlledUnit.radarByID[unitID].finished = true
  3303. end
  3304. end
  3305. end
  3306. end
  3307.  
  3308. end
  3309.  
  3310. local function initialiseAiTeam(team, allyteam, aiConfig)
  3311.  
  3312. Spring.Echo("AI taken control of team " .. team .. " on allyTeam " .. allyteam)
  3313.  
  3314. aiTeamData[team] = {
  3315.  
  3316. allyTeam = allyteam,
  3317.  
  3318. controlFunction = aiConfig.controlFunction,
  3319. --buildConfig = aiConfig.buildConfig,
  3320. buildConfig = CopyTable(aiConfig.buildConfig),
  3321. raiderBattlegroupCondition = aiConfig.raiderBattlegroupCondition,
  3322. combatBattlegroupCondition = aiConfig.combatBattlegroupCondition,
  3323. gunshipBattlegroupCondition = aiConfig.gunshipBattlegroupCondition,
  3324.  
  3325. averagedEcon = {
  3326. prevEcon = {},
  3327. aveMInc = 3,
  3328. aveEInc = 3,
  3329. aveActiveBp = 0,
  3330. eCur = 0,
  3331. mCur = 0,
  3332. mStor = 500,
  3333. energyToMetalRatio = 1,
  3334. activeBpToMetalRatio = 0,
  3335. },
  3336.  
  3337. buildDefs = false,
  3338.  
  3339. selfDefenceHeatmap = {},
  3340.  
  3341. selfDefenceAirTask = {count = 0},
  3342.  
  3343. battleGroup = {count = 0},
  3344. unitInBattleGroupByID = {},
  3345.  
  3346. sosTimeout = {},
  3347. factoryCountByDefID = {},
  3348.  
  3349. wantedDefence = {count = 0},
  3350.  
  3351. unitHording = 0, -- factor from 0 to 1 of percentage of units horded at main base
  3352. wantedNanoCount = 0,
  3353.  
  3354. totalBP = 0, -- total controlled build power
  3355. totalFactoryBPQuota = 0, -- build more factories when over this
  3356. unassignedCons = {count = 0}, -- con that do not have a job
  3357.  
  3358. conJob = {
  3359. reclaim = {importance = 0, con = {}, assignedBP = 0, name = "reclaim", interruptable = true, index = 1, grouping = 1},
  3360. defence = {importance = 0, con = {}, assignedBP = 0, name = "defence", interruptable = false, index = 2, grouping = 2,
  3361. radarChance = 0, airChance = 0, airpadChance = 0, metalStorageChance = 0},
  3362. mex = {importance = 0, con = {}, assignedBP = 0, name = "mex", interruptable = false, defenceChance = 0, index = 3, grouping = 1},
  3363. factory = {importance = 0, con = {}, assignedBP = 0, name = "factory", airFactor = 0,interruptable = true, index = 4, grouping = 2},
  3364. energy = {importance = 0, con = {}, assignedBP = 0, name = "energy", interruptable = false, index = 5, grouping = 2},
  3365. },
  3366.  
  3367. -- jobs that a factory can have and the weighted importance on each
  3368. facJob = {
  3369. [1] = {importance = 0}, -- con
  3370. [2] = {importance = 0}, -- scout
  3371. [3] = {importance = 0}, -- raider
  3372. [4] = {importance = 0}, -- arty
  3373. [5] = {importance = 0}, -- assault
  3374. [6] = {importance = 0}, -- skirm
  3375. [7] = {importance = 0}, -- riot
  3376. [8] = {importance = 0}, -- aa
  3377. },
  3378.  
  3379. facJobAir = {
  3380. [1] = {importance = 0}, -- con
  3381. [2] = {importance = 0}, -- scout
  3382. [3] = {importance = 0}, -- fighter
  3383. [4] = {importance = 0}, -- bomber
  3384. [5] = {importance = 0}, -- gunship
  3385. },
  3386.  
  3387. uncompletedFactory = false,
  3388.  
  3389. controlledUnit = { -- only factory, mex and econ hold an ordered array. the rest have only count and cost
  3390. any = {cost = 0, count = 0,},
  3391. anyByID = {},
  3392. mex = {cost = 0, count = 0,},
  3393. mexByID = {},
  3394. factory = {cost = 0, count = 0,},
  3395. factoryByID = {},
  3396. nano = {cost = 0, count = 0,},
  3397. nanoByID = {},
  3398. con = {cost = 0, count = 0,},
  3399. conByID = {},
  3400. scout = {cost = 0, count = 0,},
  3401. scoutByID = {},
  3402. raider = {cost = 0, count = 0,},
  3403. raiderByID = {},
  3404. arty = {cost = 0, count = 0,},
  3405. artyByID = {},
  3406. combat = {cost = 0, count = 0,},
  3407. combatByID = {},
  3408. aa = {cost = 0, count = 0,},
  3409. aaByID = {},
  3410. fighter = {cost = 0, count = 0,},
  3411. fighterByID = {},
  3412. bomber = {cost = 0, count = 0,},
  3413. bomberByID = {},
  3414. gunship = {cost = 0, count = 0,},
  3415. gunshipByID = {},
  3416. econ = {cost = 0, count = 0,},
  3417. econByID = {},
  3418. turret = {cost = 0, count = 0,},
  3419. turretByID = {},
  3420. radar = {cost = 0,count = 0,},
  3421. radarByID = {},
  3422. airpad = {cost = 0,count = 0,},
  3423. airpadByID = {},
  3424. },
  3425.  
  3426. }
  3427.  
  3428. local a = aiTeamData[team]
  3429.  
  3430. a.conJobByIndex = { -- con job by index
  3431. [1] = a.conJob.reclaim,
  3432. [2] = a.conJob.defence,
  3433. [3] = a.conJob.mex,
  3434. [4] = a.conJob.factory,
  3435. [5] = a.conJob.energy,
  3436. }
  3437.  
  3438. for i = 1, econAverageMemory do
  3439. a.averagedEcon.prevEcon[i] = {eInc = 3, mInc = 3, activeBp = 0}
  3440. end
  3441.  
  3442. for i = 1,heatArrayWidth do -- init array
  3443. a.selfDefenceHeatmap[i] = {}
  3444. for j = 1, heatArrayHeight do
  3445. a.selfDefenceHeatmap[i][j] = {
  3446. [1] = {
  3447. total = 0,
  3448. toBuild = 0,
  3449. air = 0,
  3450. airTask = 0,
  3451. },
  3452. [2] = {
  3453. total = 0,
  3454. toBuild = 0,
  3455. air = 0,
  3456. airTask = 0,
  3457. },
  3458. [3] = {
  3459. total = 0,
  3460. toBuild = 0,
  3461. air = 0,
  3462. airTask = 0,
  3463. },
  3464. }
  3465. end
  3466. end
  3467.  
  3468. local player = select(2, Spring.GetTeamInfo(team))
  3469. local stratIndex = SelectRandomStrat(player, team)
  3470. --Spring.Echo(a.buildConfig)
  3471. --ModifyTable(a.buildConfig, buildTasksMods)
  3472. strategies[stratIndex].buildTasksMods(aiTeamData[team].buildConfig)
  3473. end
  3474.  
  3475. local function initialiseAllyTeam(allyTeam, aiOnTeam)
  3476.  
  3477. Spring.Echo("Ally Team " .. allyTeam .. " intialised")
  3478.  
  3479. allyTeamData[allyTeam] = {
  3480.  
  3481. teams = {},
  3482. aTeamOnThisTeam = 0,
  3483. ai = aiOnTeam,
  3484.  
  3485. units = { -- most of these are unused - would be used in cheating AI
  3486. cost = 0,
  3487. mex = {count = 0,},
  3488. mexByID = {},
  3489. --factory = {count = 0,},
  3490. factoryByID = {},
  3491. --[[con = {count = 0,},
  3492. conByID = {},
  3493. scout = {cost = 0, count = 0,},
  3494. scoutByID = {},
  3495. raider = {cost = 0, count = 0,},
  3496. raiderByID = {},
  3497. arty = {cost = 0, count = 0,},
  3498. artyByID = {},
  3499. combat = {cost = 0, count = 0,},
  3500. combatByID = {},
  3501. econ = {cost = 0, count = 0,},--]]
  3502. econByID = {},
  3503. radarByID = {},
  3504. --turret = {cost = 0, count = 0,},
  3505. turretByID = {},
  3506. --[[nano = {count = 0,},
  3507. nanoByID = {},--]]
  3508. }
  3509. }
  3510.  
  3511. for _,t in pairs(spGetTeamList()) do
  3512. local _,_,_,_,_,at = spGetTeamInfo(t)
  3513. if at == allyTeam then
  3514. Spring.Echo("Team " .. t .. " on allyTeam " .. allyTeam)
  3515. allyTeamData[allyTeam].teams[t] = true
  3516. allyTeamData[allyTeam].aTeamOnThisTeam = t
  3517. end
  3518. end
  3519.  
  3520. if aiOnTeam then
  3521. Spring.Echo("AI on ally team " .. allyTeam)
  3522.  
  3523. local at = allyTeamData[allyTeam]
  3524.  
  3525. at.fighterTarget = false
  3526.  
  3527. at.unitInHeatmap = {}
  3528.  
  3529. at.enemyForceComposition = {
  3530. totalCost = 1,
  3531. unit = {
  3532. raider = 500,
  3533. skirm = 500,
  3534. assault = 500,
  3535. riot = 500,
  3536. arty = 500,
  3537. antiAir = 500,
  3538. air = 500,
  3539. airDefence = 500,
  3540. groundDefence = 500,
  3541. },
  3542. }
  3543.  
  3544. at.relativeEnemyForceComposition = { -- adds to 9
  3545. unit = {
  3546. raider = 1,
  3547. skirm = 1,
  3548. assault = 1,
  3549. riot = 1,
  3550. arty = 1,
  3551. antiAir = 1,
  3552. air = 1,
  3553. airDefence = 1,
  3554. groundDefence = 1,
  3555. },
  3556. }
  3557.  
  3558. at.enemyHasAir = false
  3559.  
  3560. at.enemyStaticAA = {}
  3561. at.enemyMobileAA = {}
  3562.  
  3563. at.enemyOffense = {totalCost = 0, count = 0}
  3564. at.enemyOffenseHeatmap = {}
  3565.  
  3566. at.enemyEconomy = {totalCost = 0, count = 0}
  3567. at.enemyEconomyHeatmap = {}
  3568.  
  3569. at.enemyDefence = {totalCost = 0, count = 0}
  3570. at.enemyDefenceHeatmap = {}
  3571.  
  3572. at.unScoutedPoint = {count = 0}
  3573. at.scoutingHeatmap = {}
  3574.  
  3575. for i = 1,heatArrayWidth do -- init array
  3576. at.enemyOffenseHeatmap[i] = {}
  3577. for j = 1, heatArrayHeight do
  3578. at.enemyOffenseHeatmap[i][j] = {cost = 0, index = 0}
  3579. end
  3580. end
  3581.  
  3582. for i = 1,heatArrayWidth do -- init array
  3583. at.enemyEconomyHeatmap[i] = {}
  3584. for j = 1, heatArrayHeight do
  3585. at.enemyEconomyHeatmap[i][j] = {cost = 0, index = 0}
  3586. end
  3587. end
  3588.  
  3589. for i = 1,heatArrayWidth do -- init array
  3590. at.enemyDefenceHeatmap[i] = {}
  3591. for j = 1, heatArrayHeight do
  3592. at.enemyDefenceHeatmap[i][j] = {cost = 0, index = 0}
  3593. end
  3594. end
  3595.  
  3596. for i = 1,heatArrayWidth do
  3597. at.scoutingHeatmap[i] = {}
  3598. for j = 1, heatArrayHeight do
  3599. at.scoutingHeatmap[i][j] = {scouted = false, lastScouted = -10000, previouslyEmpty = false}
  3600. end
  3601. end
  3602.  
  3603. end
  3604. end
  3605.  
  3606. ----------------------------
  3607. -- Debug (based on KPAI)
  3608.  
  3609.  
  3610. local function changeAIscoutmapDrawing(cmd,line,words,player)
  3611. local allyTeam=tonumber(words[1])
  3612. if allyTeam and allyTeamData[allyTeam] and allyTeamData[allyTeam].ai then
  3613. if debugData.drawScoutmap[allyTeam] then
  3614. debugData.drawScoutmap[allyTeam] = false
  3615. Spring.Echo("CAI scoutmap drawing for allyTeam " .. allyTeam .. " OFF")
  3616. else
  3617. debugData.drawScoutmap[allyTeam] = true
  3618. Spring.Echo("CAI scoutmap drawing for allyTeam " .. allyTeam .. " ON")
  3619. end
  3620. else
  3621. Spring.Echo("Incorrect allyTeam for CAI")
  3622. end
  3623. return true
  3624. end
  3625.  
  3626. local function changeAIoffenseDrawing(cmd,line,words,player)
  3627. local allyTeam=tonumber(words[1])
  3628. if allyTeam and allyTeamData[allyTeam] and allyTeamData[allyTeam].ai then
  3629. if debugData.drawOffensemap[allyTeam] then
  3630. debugData.drawOffensemap[allyTeam] = false
  3631. Spring.Echo("CAI offense drawing for allyTeam " .. allyTeam .. " OFF")
  3632. else
  3633. debugData.drawOffensemap[allyTeam] = true
  3634. Spring.Echo("CAI offense drawing for allyTeam " .. allyTeam .. " ON")
  3635. end
  3636. else
  3637. Spring.Echo("Incorrect allyTeam CAI")
  3638. end
  3639. return true
  3640. end
  3641.  
  3642. local function changeAIeconDrawing(cmd,line,words,player)
  3643. local allyTeam=tonumber(words[1])
  3644. if allyTeam and allyTeamData[allyTeam] and allyTeamData[allyTeam].ai then
  3645. if debugData.drawEconmap[allyTeam] then
  3646. debugData.drawEconmap[allyTeam] = false
  3647. Spring.Echo("CAI economy drawing for allyTeam " .. allyTeam .. " OFF")
  3648. else
  3649. debugData.drawEconmap[allyTeam] = true
  3650. Spring.Echo("CAI economy drawing for allyTeam " .. allyTeam .. " ON")
  3651. end
  3652. else
  3653. Spring.Echo("Incorrect allyTeam CAI")
  3654. end
  3655. return true
  3656. end
  3657.  
  3658. local function changeAIdefenceDrawing(cmd,line,words,player)
  3659. local allyTeam=tonumber(words[1])
  3660. if allyTeam and allyTeamData[allyTeam] and allyTeamData[allyTeam].ai then
  3661. if debugData.drawDefencemap[allyTeam] then
  3662. debugData.drawDefencemap[allyTeam] = false
  3663. Spring.Echo("CAI defence drawing for allyTeam " .. allyTeam .. " OFF")
  3664. else
  3665. debugData.drawDefencemap[allyTeam] = true
  3666. Spring.Echo("CAI defence drawing for allyTeam " .. allyTeam .. " ON")
  3667. end
  3668. else
  3669. Spring.Echo("Incorrect allyTeam CAI")
  3670. end
  3671. return true
  3672. end
  3673.  
  3674. local function changeAIshowEnemyForceComposition(cmd,line,words,player)
  3675. local team=tonumber(words[1])
  3676. if team and aiTeamData[team] then
  3677. if debugData.showEnemyForceCompostion[team] then
  3678. debugData.showEnemyForceCompostion[team] = false
  3679. Spring.Echo("CAI enemy force composition " .. team .. " OFF")
  3680. else
  3681. debugData.showEnemyForceCompostion[team] = true
  3682. Spring.Echo("CAI enemy force composition " .. team .. " ON")
  3683. end
  3684. else
  3685. Spring.Echo("Incorrect CAI team")
  3686. end
  3687. return true
  3688. end
  3689.  
  3690. local function changeAIshowConJob(cmd,line,words,player)
  3691. local team=tonumber(words[1])
  3692. if team and aiTeamData[team] then
  3693. if debugData.showConJobList[team] then
  3694. debugData.showConJobList[team] = false
  3695. Spring.Echo("CAI con job " .. team .. " OFF")
  3696. else
  3697. debugData.showConJobList[team] = true
  3698. Spring.Echo("CAI con job " .. team .. " ON")
  3699. end
  3700. else
  3701. Spring.Echo("Incorrect CAI team")
  3702. end
  3703. return true
  3704. end
  3705.  
  3706. local function changeAIshowFacJob(cmd,line,words,player)
  3707. local team=tonumber(words[1])
  3708. if team and aiTeamData[team] then
  3709. if debugData.showFacJobList[team] then
  3710. debugData.showFacJobList[team] = false
  3711. Spring.Echo("CAI factory job " .. team .. " OFF")
  3712. else
  3713. debugData.showFacJobList[team] = true
  3714. Spring.Echo("CAI factory job " .. team .. " ON")
  3715. end
  3716. else
  3717. Spring.Echo("Incorrect CAI team")
  3718. end
  3719. return true
  3720. end
  3721.  
  3722. local function SetupCmdChangeAIDebug()
  3723. gadgetHandler:AddChatAction("caiscout",changeAIscoutmapDrawing,"toggles CAI scoutmap drawing")
  3724. Script.AddActionFallback("caiscout"..' ',"toggles CAI scoutmap drawing")
  3725.  
  3726. gadgetHandler:AddChatAction("caioffense",changeAIoffenseDrawing,"toggles CAI offense drawing")
  3727. Script.AddActionFallback("caioffense"..' ',"toggles CAI offense drawing")
  3728.  
  3729. gadgetHandler:AddChatAction("caiecon",changeAIeconDrawing,"toggles CAI economy drawing")
  3730. Script.AddActionFallback("caiecon"..' ',"toggles CAI economy drawing")
  3731.  
  3732. gadgetHandler:AddChatAction("caidefence",changeAIdefenceDrawing,"toggles CAI defence drawing")
  3733. Script.AddActionFallback("caidefence"..' ',"toggles CAI defence drawing")
  3734.  
  3735. gadgetHandler:AddChatAction("caicomp",changeAIshowEnemyForceComposition,"toggles CAI enemy composition output")
  3736. Script.AddActionFallback("caicomp"..' ',"toggles CAI enemy composition output")
  3737.  
  3738. gadgetHandler:AddChatAction("caicon",changeAIshowConJob,"toggles CAI con job output")
  3739. Script.AddActionFallback("caicon"..' ',"toggles CAI con job output")
  3740.  
  3741. gadgetHandler:AddChatAction("caifac",changeAIshowFacJob,"toggles CAI factory job output")
  3742. Script.AddActionFallback("caifac"..' ',"toggles CAI factory job output")
  3743. end
  3744.  
  3745. function gadget:Initialize()
  3746. -- Initialise AI for all team that are set to use it
  3747. local aiOnTeam = {}
  3748. usingAI = false
  3749.  
  3750. for _,team in ipairs(spGetTeamList()) do
  3751. --local _,_,_,isAI,side = spGetTeamInfo(team)
  3752. if aiConfigByName[spGetTeamLuaAI(team)] then
  3753. local _,_,_,_,_,_,CustomTeamOptions = spGetTeamInfo(team)
  3754. if (not CustomTeamOptions) or (not CustomTeamOptions["aioverride"]) then -- what is this for?
  3755. local _,_,_,_,_,allyTeam = spGetTeamInfo(team)
  3756. initialiseAiTeam(team, allyTeam, aiConfigByName[spGetTeamLuaAI(team)])
  3757. aiOnTeam[allyTeam] = true
  3758. usingAI = true
  3759. end
  3760. end
  3761. end
  3762.  
  3763. if usingAI then
  3764. for _,allyTeam in ipairs(spGetAllyTeamList()) do
  3765. initialiseAllyTeam(allyTeam, aiOnTeam[allyTeam])
  3766. end
  3767. else
  3768. gadgetHandler:RemoveGadget()
  3769. return
  3770. end
  3771.  
  3772. SetupCmdChangeAIDebug()
  3773.  
  3774. --// mex spot detection
  3775. mexSpot = GetMetalMap()
  3776. if not mexSpot then
  3777. Spring.Echo("Mex spot detection failed, AI failed to initalise")
  3778. gadgetHandler:RemoveGadget()
  3779. return
  3780. end
  3781.  
  3782. --local x,z
  3783. -- get team units
  3784. for _, unitID in ipairs(spGetAllUnits()) do
  3785. local unitDefID = spGetUnitDefID(unitID)
  3786. local team = spGetUnitTeam(unitID)
  3787. gadget:UnitCreated(unitID, unitDefID, team)
  3788. local _,_,_,_,build = spGetUnitHealth(unitID)
  3789. if build == 1 then
  3790. gadget:UnitFinished(unitID, unitDefID, team)
  3791. end
  3792. --x,_,z = spGetUnitPosition(unitID)
  3793. end
  3794.  
  3795. end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement