Advertisement
Guest User

Untitled

a guest
Nov 28th, 2014
167
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 44.62 KB | None | 0 0
  1. --------------------------------------------------------------------------------
  2. --------------------------------------------------------------------------------
  3. --
  4. --  file:    central_build_AI.lua
  5. --  brief:   Replacement for Central Build Group AI
  6. --  author:  Troy H. Cheek
  7. --
  8. --  Copyright (C) 2009.
  9. --  Licensed under the terms of the GNU GPL, v2 or later.
  10. --
  11. --------------------------------------------------------------------------------
  12. --------------------------------------------------------------------------------
  13.  
  14. local version = "v1.352"
  15. function widget:GetInfo()
  16.   return {
  17.     name      = "Central Build AI",
  18.     desc      = version.. " Common non-hierarchical permanent build queue\n\nInstruction: add constructor(s) to group 0 (use \255\90\255\90Auto Group\255\255\255\255 widget or manually), then give any of them a build queue. As a result: the whole group (group 0) will see the same build queue and they will distribute work automatically among them. Type \255\255\90\90/cba\255\255\255\255 to forcefully delete all stored queue",
  19.     author    = "Troy H. Cheek, modified by msafwan",
  20.     date      = "July 20, 2009, 8 March 2014",
  21.     license   = "GNU GPL, v2 or later",
  22.     layer     = 10,
  23.     enabled   = false  --  loaded by default?
  24.   }
  25. end
  26.  
  27. --  Central Build AI creates a common build order queue for all units in the
  28. --  group.  Select this group (or any member of it) and issue build orders
  29. --  while holding down the shift key to add orders to the common queue.
  30. --  Idle builders in the group will automatically move to carry out orders
  31. --  or assist other builders.  Issue same build order again to cancel.
  32. --  Orders issued without shift will be carried out immediately.
  33.  
  34. -- optionnally to do:
  35. -- _ orders with shift+ctrl have higher priority ( insert in queue -> cons won't interrupt their current actions)
  36.  
  37. ---- CHANGELOG -----
  38. -- msafwan,         v1.21   (7oct2012)  :   fix some cases where unit become 'idle' but failed to be registered by CBA,
  39. --                                          make CBA assign all job at once rather than sending 1 by 1 after every some gameframe delay,
  40. -- msafwan,         v1.2    (4sept2012) :   made it work with ZK "cmd_mex_placement.lua" mex queue,
  41. --                                          reduce the tendency to make a huge blob of constructor (where all constructor do same job),
  42. --                                          reduce chance of some constructor not given job when player have alot of constructor,
  43. -- rafal,           v1.1    (2May2012)  :   Don't fetch full Spring.GetCommandQueue in cases when only the first command is needed - instead using
  44. --                                          GetCommandQueue(unitID, 1)
  45. -- KingRaptor,      v1.1    (24dec2011) :   Removed the "remove in 85.0" stuff
  46. -- versus666,       v1.1    (16dec2011) :   mostly changed the layer order to get a logical priority among widgets.
  47. -- KingRaptor,      v1.1    (8dec2011)  :   Fixed the remaining unitdef tags for 85.0
  48. -- versus666,       v1.1    (7jan2011)  :   Made CBA, cmd_retreat, gui_nuke_button, gui_team_platter.lua, unit_auto_group to obey F5 (gui hidden).
  49. -- KingRaptor,      v1.1    (2Nov2010)  :   Moved version number from name to description.
  50. -- lccquantum,      v1.1    (2Nov2010)  :   central_build_AI is disabled by default (people will wonder why their builders are acting wierd when in group 0)
  51. -- versus666,       v1.1    (1Nov2010)  :   introduced into ZK
  52.  
  53. --------------------------------------------------------------------------------
  54. --------------------------------------------------------------------------------
  55.  
  56. local myGroupId = 0 --//Constant: a group number (0 to 9) will be controlled by Central Build AI. NOTE: put "-1" to use a custom group instead (use the hotkey to add units;ie: ctrl+hotkey).
  57. local hotkey = string.byte( "g" )   --//Constant: a custom hotkey to add unit to custom group. NOTE: set myGroupId to "-1" to use this hotkey.
  58. local checkFeatures = false --//Constant: if true, Central Build will reject any build queue on top of allied features (eg: ally's wreck & dragon teeth).
  59.  
  60. local Echo                  = Spring.Echo
  61. local spGetUnitDefID        = Spring.GetUnitDefID
  62. local spGetGroupList        = Spring.GetGroupList
  63. local spGetGroupUnits       = Spring.GetGroupUnits
  64. local spGetSelectedUnits    = Spring.GetSelectedUnits
  65. local spIsUnitInView        = Spring.IsUnitInView
  66. local spIsAABBInView        = Spring.IsAABBInView
  67. local spGetUnitsInCylinder  = Spring.GetUnitsInCylinder
  68. local spGetUnitViewPosition = Spring.GetUnitViewPosition
  69. local spGetCommandQueue     = Spring.GetCommandQueue
  70. local spGetUnitPosition     = Spring.GetUnitPosition
  71. local spGetUnitHealth       = Spring.GetUnitHealth
  72. local spGiveOrderToUnit     = Spring.GiveOrderToUnit
  73. local spGetMyPlayerID       = Spring.GetMyPlayerID
  74. local spGetMyTeamID         = Spring.GetMyTeamID
  75. local spGetFeatureTeam      = Spring.GetFeatureTeam
  76. local spGetLocalPlayerID    = Spring.GetLocalPlayerID
  77. local spGetPlayerInfo       = Spring.GetPlayerInfo
  78. local spGetSpectatingState  = Spring.GetSpectatingState
  79. local spGetModKeyState      = Spring.GetModKeyState
  80. local spTestBuildOrder      = Spring.TestBuildOrder
  81. local spSelectUnitMap       = Spring.SelectUnitMap
  82. local spGetUnitsInCylinder  = Spring.GetUnitsInCylinder
  83. local spGetUnitsInRectangle = Spring.GetUnitsInRectangle
  84. local spGetUnitAllyTeam     = Spring.GetUnitAllyTeam
  85. local spGetUnitIsStunned    = Spring.GetUnitIsStunned
  86.  
  87. local glPushMatrix  = gl.PushMatrix
  88. local glPopMatrix   = gl.PopMatrix
  89. local glTranslate   = gl.Translate
  90. local glBillboard   = gl.Billboard
  91. local glColor       = gl.Color
  92. local glText        = gl.Text
  93. local glBeginEnd    = gl.BeginEnd
  94. local GL_LINE_STRIP = GL.LINE_STRIP
  95. local glDepthTest   = gl.DepthTest
  96. local glRotate      = gl.Rotate
  97. local glUnitShape   = gl.UnitShape
  98. local glVertex      = gl.Vertex
  99.  
  100. local CMD_WAIT      = CMD.WAIT
  101. local CMD_MOVE      = CMD.MOVE
  102. local CMD_PATROL    = CMD.PATROL
  103. local CMD_REPAIR    = CMD.REPAIR
  104. local CMD_INSERT    = CMD.INSERT
  105. local CMD_REMOVE    = CMD.REMOVE
  106. local CMD_RECLAIM   = CMD.RECLAIM
  107. local CMD_GUARD     = CMD.GUARD
  108. local CMD_STOP      = CMD.STOP
  109.  
  110. local abs   = math.abs
  111. local floor = math.floor
  112. local huge  = math.huge
  113. local sqrt  = math.sqrt
  114. local max   = math.max
  115.  
  116. local currentFrame = Spring.GetGameFrame()
  117. local nextFrame = currentFrame +30
  118. local nextPathCheck = currentFrame + 400 --is used to check whether constructor can go to construction site
  119. local myAllyID = Spring.GetMyAllyTeamID()
  120. local textColor = {0.7, 1.0, 0.7, 1.0}
  121. local textSize = 12.0
  122. local enemyRange = 600 --range (in elmo) around build site to check for enemy
  123. local enemyThreshold = 0.49--fraction of enemy around build site w.r.t. ally unit for it to be marked as unsafe
  124.  
  125. --  "global" for this widget.  This is probably not a recommended practice.
  126. local myUnits = {}  --  list of units in the Central Build group
  127. local myQueue = {}  --  list of commands for Central Build group
  128. local groupHasChanged   --  Flag if group members have changed.
  129. local myQueueUnreachable = {} -- list of queue which units can't reach
  130.  
  131. local cachedValue = {} --cached results for "EnemyControlBuildSite()" function to reduce cost for repeated call
  132. --------------------------------------------------------------------------------
  133. --------------------------------------------------------------------------------
  134.  
  135. function widget:Initialize()
  136. --  Borrowed from xyz's Action Finder and very_bad_soldier's Ghost Radar
  137.     if spGetSpectatingState() then
  138.         Echo( "<Central Build AI>: Spectator mode. Widget removed." )
  139.         widgetHandler:RemoveWidget()
  140.         return
  141.     end
  142.     widgetHandler:RegisterGlobal("CommandNotifyMex", CommandNotifyMex) --an event which is called everytime "cmd_mex_placement.lua" widget handle a mex command. Reference : http://springrts.com/phpbb/viewtopic.php?f=23&t=24781 "Gadget and Widget Cross Communication"
  143. end
  144.  
  145. --function widget:CommandNotify(cmdID, cmdParams, cmdOpts)
  146. -- Spring.Echo(#cmdOpts,#cmdParams)
  147. --end
  148.  
  149. --  Paint 'cb' tags on units, draw ghosts of items in central build queue.
  150. --  Text stuff mostly borrowed from gunblob's Group Label and trepan/JK's BuildETA.
  151. --  Ghost stuff borrowed from very_bad_soldier's Ghost Radar.
  152.  
  153. function widget:DrawWorld()
  154. if not Spring.IsGUIHidden() then
  155.     local alt, ctrl, meta, shift = spGetModKeyState()
  156.  
  157.     for unitID,myCmd in pairs(myUnits) do   -- show user which units are in CB
  158.         if spIsUnitInView(unitID) then
  159. --          local udid = spGetUnitDefID(unitID)
  160. --          local ud = UnitDefs[udid]
  161.             local ux, uy, uz = spGetUnitViewPosition(unitID)
  162.             glPushMatrix()
  163.             glTranslate(ux, uy, uz)
  164.             glBillboard()
  165.             glColor(textColor)
  166. --          glText("cb "..myCmd, -10.0, -15.0, textSize, "con")
  167.             if myCmd == "idle" then
  168.                 glText("idl", -10.0, -15.0, textSize, "con")
  169.             elseif myCmd == "busy" then
  170.                 glText("bsy", -10.0, -15.0, textSize, "con")
  171.             else
  172.                 glText("cb", -10.0, -15.0, textSize, "con")
  173.             end
  174.             glPopMatrix()
  175.             glColor(1, 1, 1, 1)
  176.         end -- if InView
  177.     end -- for unitID in group
  178.  
  179.     for key, myCmd in pairs(myQueue) do -- show items in build queue
  180.         local cmd = myCmd.id
  181.         cmd = abs( cmd )
  182.         local x, y, z, h = myCmd.x, myCmd.y, myCmd.z, myCmd.h
  183.         local degrees = h * 90
  184.         if spIsAABBInView(x-1,y-1,z-1,x+1,y+1,z+1) then
  185.             if ( shift ) then
  186.                 glColor(0.0, 1.0, 0.0, 1 )
  187.                 glBeginEnd(GL_LINE_STRIP, DrawOutline, cmd, x, y, z, h)
  188.             end
  189.             glColor(1.0, 1.0, 1.0, 0.35 )   -- ghost value 0.35
  190.             glDepthTest(true)
  191.             glPushMatrix()
  192.             glTranslate( x, y, z )
  193.             glRotate( degrees, 0, 1.0, 0 )
  194.             glUnitShape( cmd, spGetMyTeamID() )
  195.             glRotate( degrees, 0, -1.0, 0 )
  196.             glBillboard()                   -- also show some debug stuff
  197.             glColor(textColor)
  198. --          glText(key, -10.0, -15.0, textSize/2, "con")
  199.             glText(cmd, -10.0, -15.0, textSize, "con")
  200.             glPopMatrix()
  201.             glDepthTest(false)
  202.             glColor(1, 1, 1, 1)
  203.         end -- if inview
  204.     end
  205. end
  206. end
  207.  
  208. function DrawOutline(cmd,x,y,z,h)
  209.     local ud = UnitDefs[cmd]
  210.     local baseX = ud.xsize * 4 -- ud.buildingDecalSizeX
  211.     local baseZ = ud.zsize * 4 -- ud.buildingDecalSizeY
  212.     if (h == 1 or h==3) then
  213.         baseX,baseZ = baseZ,baseX
  214.     end
  215.     glVertex(x-baseX,y,z-baseZ)
  216.     glVertex(x-baseX,y,z+baseZ)
  217.     glVertex(x+baseX,y,z+baseZ)
  218.     glVertex(x+baseX,y,z-baseZ)
  219.     glVertex(x-baseX,y,z-baseZ)
  220. end
  221.  
  222. --  Stuff which needs to run regularly but isn't covered elsewhere.
  223. --  Was Update(), but Niobium says GameFrame() is more better.
  224.  
  225. function widget:GameFrame(thisFrame)
  226.     currentFrame = thisFrame
  227.     if ( thisFrame > nextPathCheck ) then
  228.         cachedValue = {}
  229.         UpdateUnitsPathability()
  230.         nextPathCheck = thisFrame + 300
  231.     end
  232.     if ( thisFrame < nextFrame ) then
  233.         return
  234.     end
  235.     if ( groupHasChanged == true ) then
  236.         UpdateOneGroupsDetails(myGroupId)
  237.     end
  238.     nextFrame = thisFrame + 60  -- try again in 2 second if nothing else triggers
  239.     FindIdleUnits(myUnits,thisFrame)        -- locate idle units not found otherwise
  240. end
  241.  
  242. --  This function detects that a new group has been defined or changed.  Use it to set a flag
  243. --  because it fires before all units it's going to put into group have actually been put in.
  244. --  Borrowed from gunblob's UnitGroups v5.1
  245.  
  246. function widget:GroupChanged(groupId)  
  247.     if groupId == myGroupId then
  248. --      local units = spGetGroupUnits(myGroupId)
  249. --      Echo( Spring.GetGameFrame() .. " Change detected in group." )
  250.         groupHasChanged = true
  251.         nextFrame = currentFrame + ping()
  252.     end
  253. end
  254.  
  255. --  This function actually updates the list of builders in the CB group (myGroup).
  256. --  Also borrowed from gunblob's UnitGroups v5.1
  257.  
  258. function UpdateOneGroupsDetails(myGroupId)
  259.     local units = spGetGroupUnits(myGroupId)
  260.     for _, unitID in ipairs(units) do   --  add the new units
  261.         if ( not myUnits[unitID] ) then
  262.             local udid = spGetUnitDefID(unitID)
  263.             local ud = UnitDefs[udid]
  264.             if (ud.isBuilder and ud.canMove) then
  265.                 myUnits[unitID] = "idle"
  266.                 myQueueUnreachable[unitID]= {}
  267.                 UpdateOneUnitPathability(unitID)
  268.             end
  269.         end
  270.     end
  271.     for unitID,_ in pairs(myUnits) do   --  remove any old units
  272.         local isInThere = false
  273.         for _,unit2 in ipairs(units) do
  274.             if ( unitID == unit2 ) then
  275.                 isInThere = true
  276.                 break
  277.             end
  278.         end
  279.         if ( not isInThere ) then
  280.             myUnits[unitID] = nil
  281.             myQueueUnreachable[unitID]=nil
  282.         end
  283.     end
  284.     groupHasChanged = nil
  285. end
  286.  
  287. --  A compatibility function: receive broadcasted event from "cmd_mex_placement.lua" (ZK specific) which notify us that it has its own mex queue
  288. function CommandNotifyMex(id,params,options, isAreaMex)
  289.     local groundHeight = Spring.GetGroundHeight(params[1],params[3])
  290.     params[2] = math.max(0, groundHeight)
  291.     local returnValue = widget:CommandNotify(id, params, options, true,isAreaMex)
  292.     return returnValue
  293. end
  294.  
  295. --  If the command is issued to something in our group, flag it.
  296. --  Thanks to Niobium for pointing out CommandNotify().
  297.  
  298. function widget:CommandNotify(id, params, options, isZkMex,isAreaMex)
  299.     if id < 0 and params[1]==nil and params[2]==nil and params[3]==nil then --CentralBuildAI can't handle unit-build command for factory for the moment (is buggy).
  300.         return
  301.     end
  302.     if options.meta then --skip special insert command (spacebar). Handled by CommandInsert() widget
  303.         return
  304.     end
  305.     local selectedUnits = spGetSelectedUnits()
  306.     for _, unitID in pairs(selectedUnits) do    -- check selected units...
  307.         if ( myUnits[unitID] ) then --  was issued to one of our units.
  308.             if ( options.shift ) then -- used shift for:.
  309.                 if ( id < 0 ) then --for: building
  310.                     local x, y, z, h = params[1], params[2], params[3], params[4]
  311.                     local myCmd = { id=id, x=x, y=y, z=z, h=h }
  312.                     local isOverlap = CleanOrders(myCmd) -- check if current queue overlap with existing queue, and clear up any invalid queue
  313.                     if not isOverlap then
  314.                         local hash = hash(myCmd)
  315.                         --[[ crude delete-duplicate-command code. Now is handled by CleanOrder():
  316.                         if ( myQueue[hash] ) then   -- if dupe of existing order
  317.                             myQueue[hash] = nil     -- must want to cancel
  318.                         else                        -- if not a dupe
  319.                             myQueue[hash] = myCmd   -- add to CB queue
  320.                             UpdateUnitsPathabilityForOneQueue(hash)
  321.                         end
  322.                         --]]
  323.                         myQueue[hash] = myCmd   -- add to CB queue
  324.                         UpdateUnitsPathabilityForOneQueue(hash,nil)  --take note of build site reachability
  325.                     end
  326.                     nextFrame = currentFrame + 30 --wait 1 more second before distribute work, so user can queue more stuff
  327.                     return true -- have to return true or Spring still handles command itself.
  328.                 else --for: moving/attacking/repairing, ect
  329.                     if myUnits[unitID] == "idle" then --unit is not doing anything
  330.                         myUnits[unitID] = "busy" --is doing irrelevant thing
  331.                     end
  332.                     -- do NOT return here because there may be more units.  Let Spring handle.
  333.                 end
  334.             else
  335.                 if ( id < 0 ) and (not isAreaMex ) then --is building stuff & is direct command/not an area mex command
  336.                     local x, y, z, h = params[1], params[2], params[3], params[4]
  337.                     local myCmd = { id=id, x=x, y=y, z=z, h=h }
  338.                     local hash = hash(myCmd)
  339.                     myUnits[unitID] = hash --remember what command this unit is having
  340.                     UpdateUnitsPathabilityForOneQueue(hash,myCmd) --take note of build site reachability
  341.                 else
  342.                     myUnits[unitID] = "busy"    -- direct command of something else.
  343.                 end
  344.                 -- do NOT return here because there may be more units.  Let Spring handle.
  345.             end
  346.         end
  347.     end
  348. end
  349.  
  350. --If one of our units finished a build order, cancel units guarding/assisting it.
  351. --This replace UnitCmdDone() because UnitCmdDone() is called even if command is not finished, such as when new command is inserted into existing queue
  352. --Credit to Niobium for pointing out UnitCmdDone() originally.
  353.  
  354. function widget:UnitFinished(unitID, unitDefID, unitTeam)
  355.     local ux, _, uz = spGetUnitPosition(unitID)
  356.     local myCmd = { id=-unitDefID, x=ux, z=uz, }
  357.     local hash = hash(myCmd)
  358.     for unit2,myCmd in pairs(myUnits) do
  359.         if ( myCmd == hash ) then --check if others is using same command as this unit (note: myCmd == "cmdID@x@z", if true:
  360.             local cmd2 = GetFirstCommand(unit2)
  361.             if ( cmd2 == nil ) then -- no orders?  Must be idle.
  362.                 myUnits[unit2]="idle"
  363.             else
  364.                 if (cmd2.params[1] == ux and cmd2.params[3] == uz) then
  365.                     spGiveOrderToUnit(unit2, CMD_REMOVE, {cmd2.tag}, {""} ) --remove
  366.                 end
  367.                 spGiveOrderToUnit(unit2, CMD_INSERT, {0,CMD_STOP}, {"alt"} ) --stop current motion
  368.                 myUnits[unit2]="busy" -- command done but still busy. Example scenario: unit attempt to assist a construction while user SHIFT queued a reclaim command, however the construction finishes first before this unit arrives, so this unit continue finishing the RECLAIM command with "busy" tag.
  369.             end
  370.             for unit3,myCmd2 in pairs(myUnits) do
  371.                 if ( myCmd2 == "asst "..unit2 ) then  --check if this unit is being GUARDed
  372.                     spGiveOrderToUnit(unit3, CMD_REMOVE, {CMD_GUARD}, {"alt"} ) --remove the GUARD command from those units
  373.                     spGiveOrderToUnit(unit3, CMD_INSERT, {0,CMD_STOP}, {"alt"} ) --stop current motion
  374.                     --myUnits[unit2] = "idle" --set as "idle"
  375.                     myUnits[unit2]="busy" --busy until really idle
  376.                 end
  377.             end
  378.         end
  379.     end
  380.     nextFrame = currentFrame + ping() --find new work
  381. end
  382.  
  383. --  If unit detected as idle and it's one of ours, time to find it some work.
  384.  
  385. function widget:UnitIdle(unitID, unitDefID, teamID)
  386.     if ( myUnits[unitID] ) then
  387.         for unit2,myCmd in pairs(myUnits) do
  388.             if ( myCmd == "asst "..unitID ) then  --check if this unit is being GUARDed
  389.                 spGiveOrderToUnit(unit2, CMD_REMOVE, {CMD_GUARD}, {"alt"} ) --remove the GUARD command from those units
  390.                 spGiveOrderToUnit(unit2, CMD_INSERT, {0,CMD_STOP}, {"alt"} ) --stop current motion
  391.                 --myUnits[unit2] = "idle" --set as "idle"
  392.                 myUnits[unit2]="busy" --busy until really idle
  393.             end
  394.         end
  395.         myUnits[unitID] = "idle"
  396.         nextFrame = currentFrame + ping() --find new work
  397.     end
  398. end
  399.  
  400. --  One at a time, assign idle builders new tasks.
  401.  
  402. function FindIdleUnits(myUnits, thisFrame)
  403.     local idle_myUnits = {}
  404.     local idle_assigned = {}
  405.     local numberOfIdle = 0
  406.     for unitID,myCmd in pairs(myUnits) do --check if unit really idle or is actually busy, because UnitIdle() can be triggered by other widget and is overriden with new command afterward, thus making unit look idle but is busy (ie: cmd_mex_placement.lua, area mex widget)
  407.         if myCmd == "idle" then --if unit is marked as idle, then double check it
  408.             local cmd1 = GetFirstCommand(unitID)
  409.             if ( cmd1 ) then
  410.                 if ( cmd1.id < 0 ) then --is build (any) stuff
  411.                     if ( cmd1.params[3] ) then --is not build unit command (build unit command has nil parameter (only for factory))
  412.                         local unitCmd = {id=cmd1.id,x=cmd1.params[1],y=cmd1.params[2],z=cmd1.params[3],h=cmd1.params[4]}
  413.                         local hash = hash(unitCmd)
  414.                         myUnits[unitID] = hash
  415.                     end
  416.                 else
  417.                     myUnits[unitID] = 'busy'
  418.                 end
  419.             else
  420.                 if numberOfIdle <= 10 then
  421.                     numberOfIdle = numberOfIdle +1
  422.                     --NOTE: only allow up to 10 idler to be processed to prevent super lag.
  423.                     --The amount of external loop will be (numberOfmyunit^2+numberOfQueue)*numberOfIdle^2.
  424.                     --ie: if all variable were 50, then the amount of loop in total is 6375000 loop (6 million).
  425.                     idle_myUnits[numberOfIdle] = unitID
  426.                 end
  427.             end
  428.         end
  429.     end
  430.  
  431.     --HOW THIS WORK:
  432.     --*loopA for all idle CBA unit,
  433.     --  *loopB for all idle CBA unit,
  434.     --      *for each idle CBA unit: find nearest job (forA)
  435.     --          >check nearest job for assisting other working constructor
  436.     --          >check nearest job for building-stuff that not yet done by any working constructor
  437.     --          >check whether assisting or construction is nearest
  438.     --      *forA return nearest job for each idle CBA unit
  439.     --  *loopB return all nearest jobs for all idle CBA units
  440.     --  >check how many constructor has job order
  441.     --  >check which constructor is most nearest to its job
  442.     --  >register that constructor as working constructor
  443.     --*loopA return command to be given to that constructor
  444.     -->send all command to all CBA units.
  445.     CleanOrders()   -- check build site(s) for blockage (also remove the build queue if construction have started).
  446.     local orderArray={}
  447.     local unitArray={}
  448.     for i=1,numberOfIdle do
  449.         local nearestOrders = {}
  450.         for i=1,numberOfIdle do
  451.             --[[
  452.             local cmd1 = GetFirstCommand(unitID)
  453.             if ( cmd1 == nil ) then     -- no orders?  Must be idle.
  454.                 myUnits[unitID] = "idle"
  455.             --]]
  456.             local unitID = idle_myUnits[i] --if unit is marked as idle, then use it.
  457.             if (not idle_assigned[unitID] ) then --unit not yet assigned in previous iteration? find work
  458.                 local tmp = GetWorkFor(unitID)
  459.                 if ( tmp ~= nil ) then
  460.                     table.insert( nearestOrders, tmp )  -- indexed okay here
  461.                 end
  462.             end
  463.         end
  464.         if ( # nearestOrders < 1 ) then
  465.             break -- no more job, escape the loop.
  466.         end -- nothing we can do
  467.         local closeDist = huge
  468.         local close = {}
  469.         for _, cmd in ipairs(nearestOrders) do --find unit with the closest distance to their project
  470.             if ( cmd[4] < closeDist ) then
  471.                 closeDist = cmd[4]
  472.                 close = cmd
  473.             end
  474.         end
  475.         --spGiveOrderToUnit( close[1], close[2], close[3], { "" } )
  476.         local assignUnitID = close[1]
  477.         if not EnemyAtBuildSiteCleanOrder(assignUnitID,close[3],close[5]) then --skip this job? is too dangerous?
  478.             unitArray[#unitArray+1]= assignUnitID
  479.             orderArray[#orderArray+1]={close[2], close[3], { "" }}
  480.             if ( close[5] == 0 ) then --if GUARD command: then,
  481.                 myUnits[assignUnitID] = "asst "..unpack(close[3])   --  unitID we're assisting
  482.                 --Echo(close[3])
  483.             else --if regular assisting: then,
  484.                 myUnits[assignUnitID] = close[5]    --  hash of command we're executing
  485.                 --Echo(close[5])
  486.             end
  487.             idle_assigned[assignUnitID] = true
  488.         end
  489.     end
  490.     if #orderArray > 0 then --we should not give empty command else it will delete all unit's existing queue
  491.         Spring.GiveOrderArrayToUnitArray (unitArray,orderArray, true) --send command to bulk of constructor
  492.     end
  493.     unitArray = nil
  494.     orderArray = nil
  495.     --nextFrame = thisFrame + ping()
  496. end
  497.  
  498. --  Borrowed distance calculation from Google Frog's Area Mex
  499.  
  500. local function Distance(x1,z1,x2,z2)
  501.   local dis = sqrt((x1-x2)*(x1-x2)+(z1-z2)*(z1-z2))
  502.   return dis
  503. end
  504.  
  505. --  Borrowed this from CarRepairer's Retreat.  Returns only first command in queue.
  506.  
  507. function GetFirstCommand(unitID)
  508.     local queue = spGetCommandQueue(unitID, 1)
  509.     return queue and queue[1]
  510. end
  511.  
  512. --  Remove duplicate orders, process cancel requests, delete bad builds.
  513.  
  514. function CleanOrders(newCmd)
  515.     local xSize = nil --variables for checking queue overlaping
  516.     local zSize = nil
  517.     local xSize_queue = nil
  518.     local zSize_queue = nil
  519.     local x_newCmd = nil
  520.     local z_newCmd = nil
  521.     local isOverlap = nil
  522.     if newCmd then --check the size of the new queue
  523.         local newCmdID = abs ( newCmd.id )
  524.         x_newCmd = newCmd.x
  525.         z_newCmd = newCmd.z
  526.         if x_newCmd then
  527.             if newCmd.h == 0 or newCmd.h == 2 then --get building facing. Reference: unit_prevent_lab_hax.lua by googlefrog
  528.                 xSize = UnitDefs[newCmdID].xsize*4
  529.                 zSize = UnitDefs[newCmdID].zsize*4
  530.             else
  531.                 xSize = UnitDefs[newCmdID].zsize*4
  532.                 zSize = UnitDefs[newCmdID].xsize*4
  533.             end
  534.         end
  535.     end
  536.     for key,myCmd in pairs(myQueue) do
  537.         local cmdID = abs( myCmd.id )
  538.         local x, y, z, facing = myCmd.x, myCmd.y, myCmd.z, myCmd.h
  539.         local canBuildThisThere,featureID = spTestBuildOrder(cmdID,x,y,z,facing) --check if build site is blocked by buildings & terrain
  540.        
  541.         if (canBuildThisThere == 1) or (newCmd and xSize and canBuildThisThere > 0) then --if unit blocking OR new build site was added
  542.             if facing == 0 or facing == 2 then --check the size of the queued building
  543.                 xSize_queue = UnitDefs[cmdID].xsize*4
  544.                 zSize_queue = UnitDefs[cmdID].zsize*4
  545.             else
  546.                 xSize_queue = UnitDefs[cmdID].zsize*4
  547.                 zSize_queue = UnitDefs[cmdID].xsize*4
  548.             end
  549.         end
  550.        
  551.         if newCmd and xSize and (canBuildThisThere > 0) then --check if build site overlap new queue
  552.             local minTolerance = xSize_queue + xSize --check minimum tolerance in x direction
  553.             local axisDist = abs (x - x_newCmd) --check actual separation in x direction
  554.             if axisDist < minTolerance then --if too close in x direction
  555.                 minTolerance = zSize_queue + zSize --check minimum tolerance in z direction
  556.                 axisDist = abs (z - z_newCmd) -- check actual separation in z direction
  557.                 if axisDist < minTolerance then --if too close in z direction
  558.                     canBuildThisThere = 0 --flag this queue for removal
  559.                     isOverlap = true --return true
  560.                    
  561.                     -- stop any constructor constructing this queue
  562.                     local unitArray = {}
  563.                     for unitID, queueKey in pairs(myUnits) do
  564.                         if queueKey == key then
  565.                             unitArray[#unitArray+1] = unitID
  566.                         end
  567.                     end
  568.                     Spring.GiveOrderToUnitArray (unitArray,CMD_STOP, {}, {}) --send STOP to units assigned to this queue. A scenario: user deleted this queue by overlapping old queue with new queue and it automatically stop any unit trying to build this queue
  569.                 end
  570.             end
  571.         end
  572.        
  573.         if ( canBuildThisThere==1 ) then --check if blocking unit can move away from our build site
  574.             local blockingUnits = spGetUnitsInRectangle(x-xSize_queue, z-zSize_queue, x+xSize_queue, z+zSize_queue)
  575.             for i=1, #blockingUnits do
  576.                 local blockerDefID = spGetUnitDefID(blockingUnits[i])
  577.                 if math.modf(UnitDefs[blockerDefID].speed*10) == 0 then -- ie: modf(0.01*10) == 00 (fractional speed is ignored)
  578.                     canBuildThisThere = 0 --blocking unit can't move away, cancel this queue
  579.                     break;
  580.                 end
  581.                 if blockerDefID == cmdID then
  582.                     local _,_,inBuild = spGetUnitIsStunned(blockingUnits[i])
  583.                     if inBuild then
  584.                         canBuildThisThere = 0 --unit that we wanted to build is already being build, cancel this queue
  585.                         break;
  586.                     end
  587.                 end
  588.             end
  589.         end
  590.        
  591.         --prevent build queue on ally feature
  592.         if ( checkFeatures ) and ( featureID ) then --check if build site is blocked by feature
  593.             if ( spGetFeatureTeam(featureID) == spGetMyTeamID() ) then --if feature belong to team, then don't reclaim or build there. (ie: dragon-teeth's wall)
  594.                 canBuildThisThere = 0
  595.             end
  596.         end
  597.         --end feature check
  598.        
  599.         if (canBuildThisThere == 0) then --if queue is flagged for removal
  600.             myQueue[key] = nil  --remove queue
  601.         end
  602.     end
  603.    
  604.     return isOverlap --return a value for "widget:CommandNotify()" to handle user's command.
  605. end
  606.  
  607. --  This function returns closest work for a particular builder.
  608.  
  609. function GetWorkFor(unitID)
  610.     local busyClosestID = 0     -- unitID of closest busy unit
  611.     local busyDist = huge   -- how far away it is.  (Thanks to Niobium.)
  612.     local queueClose = 0    -- command hash of closest project in the queue
  613.     local queueDist = huge  -- how far away it is.  (Thanks to Niobium.)
  614.     local ux, uy, uz = spGetUnitPosition(unitID)    -- unit location
  615.  
  616.     for busyUnitID,busyCmd1 in pairs(myUnits) do    -- see if any busy units need help.
  617.         if ( busyUnitID ~= unitID ) then --if not observing ourself
  618.             local cmd1 = GetFirstCommand(busyUnitID)
  619.             local myCmd = myQueue[busyCmd1]
  620.             if ( myCmd ) then --when busy unit has CentralBuild command (is using SHIFT)
  621.                 local cmd, x, y, z, h = myCmd.id, myCmd.x, myCmd.y, myCmd.z, myCmd.h
  622.                 if ( cmd  and cmd < 0 ) then
  623.                     local x2, y2, z2 = spGetUnitPosition( busyUnitID)   -- location of busy unit
  624.                     local numOfAssistant = 0.0
  625.                     for assistantUnitID,assistantCmd1 in pairs(myUnits) do --find how many unit is assisting this busy unit
  626.                         if ( busyUnitID ~= assistantUnitID ) then --if busyUnit not observing itself
  627.                             local prefix = assistantCmd1:sub(1,4)
  628.                             if prefix ~= "asst" then
  629.                                 if (assistantCmd1 == busyCmd1) then
  630.                                     numOfAssistant = numOfAssistant + 0.11
  631.                                 end
  632.                             else
  633.                                 -- assistantCmd1:sub(1,5) == "asst "
  634.                                 local assisting = tonumber(assistantCmd1:sub(6))
  635.                                 if (assisting == busyUnitID) then
  636.                                     numOfAssistant = numOfAssistant + 0.11
  637.                                 end
  638.                             end
  639.                         end
  640.                     end
  641.                     local dist = ( Distance(ux,uz,x,z) + Distance(ux,uz,x2,z2) + Distance(x,z,x2,z2) ) / 2 --distance btwn busy unit & self,and btwn structure & self, and btwn structure & busy unit. A distance that is no less than btwn structure & busy unit but is 1.5 when opposite to the structure & the busy unit OR when is further away than btwn structure & busy unit
  642.                     local notaccessible = myQueueUnreachable[unitID] and myQueueUnreachable[unitID][busyCmd1] or 0 --check if I can reach this location
  643.                     dist = dist + notaccessible*4500 --increase perceived distance arbitrarity if cannot reach
  644.                     --local dist = Distance(ux,uz,x,z) --distance btwn structure & self
  645.                     dist = dist + dist*numOfAssistant --make the distance look further away than it actually is if there's too many assistance
  646.                     if ( dist < busyDist ) then
  647.                         busyClosestID = busyUnitID  -- busy unit who needs help
  648.                         busyDist = dist     -- dist to said unit * 1.5 (divided by 2 instead of 3)
  649.                     end
  650.                 end
  651.             elseif ( cmd1 and cmd1.id < 0) then --when busy unit is currently building a structure, is "busy" and not using SHIFT
  652.                 local x2, y2, z2 = spGetUnitPosition(busyUnitID)    -- location of busy unit
  653.                 --local x, z = cmd1.params[1], cmd1.params[3]
  654.                 local numOfAssistant = 0.0
  655.                 for assistantUnitID,assistantCmd1 in pairs(myUnits) do --find how many unit is assisting this busy unit
  656.                     if ( busyUnitID ~= assistantUnitID ) then --if busyUnit not observing itself
  657.                         local prefix = assistantCmd1:sub(1,4)
  658.                         if prefix ~= "asst" then
  659.                             local cmd2 = GetFirstCommand(assistantUnitID) --check using unit's command queue when we dont have its command stored
  660.                             if (cmd2 and (cmd2 == cmd1)) then
  661.                                 numOfAssistant = numOfAssistant + 0.11
  662.                             end
  663.                         else
  664.                             -- assistantCmd1:sub(1,5) == "asst "
  665.                             local assisting = tonumber(assistantCmd1:sub(6))
  666.                             if (assisting == busyUnitID) then
  667.                                 numOfAssistant = numOfAssistant + 0.11
  668.                             end
  669.                         end
  670.                     end
  671.                 end
  672.                 local dist = Distance(ux,uz,x2,z2) * 1.5 --distance between busy unit & self
  673.                 local notaccessible = myQueueUnreachable[unitID] and myQueueUnreachable[unitID][busyCmd1] or 0 --check if I can reach this location
  674.                 dist = dist + notaccessible*4500 --increase perceived distance arbitrarity if cannot reach
  675.                 --local dist = Distance(ux,uz,x,z) --distance btwn structure & self
  676.                 dist = dist + dist*numOfAssistant
  677.                 if ( dist < busyDist ) then
  678.                     busyClosestID = busyUnitID  -- busy unit who needs help
  679.                     busyDist = dist     -- dist to said unit * 1.5 (divided by 2 instead of 3)
  680.                 end
  681.             end
  682.         end
  683.     end
  684.    
  685.     for index,myCmd in pairs(myQueue) do    -- any new projects to be started?
  686.         local cmd, x, y, z, h = myCmd.id, myCmd.x, myCmd.y, myCmd.z, myCmd.h
  687.         local alreadyWorkingOnIt = false    -- is some other unit already assigned this?
  688.         for unit2,cmd2 in pairs(myUnits) do
  689.             if ( index == cmd2 ) then
  690.                 alreadyWorkingOnIt = true
  691.                 break
  692.             end
  693.         end
  694.         if ( not alreadyWorkingOnIt ) then
  695.             local acmd = abs(cmd)
  696.             local udid = spGetUnitDefID(unitID)
  697.             local ud = UnitDefs[udid]
  698.             local canBuild = false  -- if this unit can't build it, skip it.
  699.             for _, options in ipairs(ud.buildOptions) do
  700.                 if ( options == acmd ) then canBuild = true break end   -- only one to find.
  701.             end
  702.             local dist = Distance(ux,uz,x,z) --distance btwn current unit & project to be started
  703.             local notaccessible = myQueueUnreachable[unitID] and myQueueUnreachable[unitID][index] or 0 --check if I can reach this location
  704.             dist = dist + notaccessible*4500 --increase perceived distance arbitrarity if cannot reach
  705.             if ( dist < queueDist and canBuild ) then
  706.                 queueClose = index  -- # of the project we'll be starting
  707.                 queueDist = dist
  708.             end
  709.         end
  710.        
  711.     end
  712.    
  713.     -- removed canHover tag since it's deprecated
  714.     -- also, @special handling: why?
  715.     if ( busyDist < huge or queueDist < huge ) then --there is work nearby
  716.         local udid = spGetUnitDefID(unitID)
  717.         local ud = UnitDefs[udid]
  718.         if ( busyDist < queueDist ) then    -- assist is closer
  719.             if ( ud.canFly ) then busyDist = busyDist * 0.50 end
  720.             --if ( ud.canHover ) then busyDist = busyDist * 0.75 end
  721.             local theCmd = myUnits[busyClosestID]
  722.             local myCmd = myQueue[theCmd] --get orders stored in CBA's queue
  723.             local canBuild = false  -- this flag determine if unit can assist by copying order instead of GUARD.
  724.             if myCmd then
  725.                 local acmd = abs(myCmd.id)
  726.                 for _, options in ipairs(ud.buildOptions) do --check buildoptions for buildable
  727.                     if ( options == acmd ) then canBuild = true break end   -- if found, escape loop and mark as "canBuild=true".
  728.                 end
  729.             end
  730.             if canBuild then --if myCmd is not empty and unit can build that building: use the same build queue from myCmd (CBA's queue)
  731.                 return { unitID, myCmd.id, { myCmd.x, myCmd.y, myCmd.z, myCmd.h }, busyDist, theCmd } --assist the busy unit by copying order.
  732.             else
  733.                 local cmd1 = GetFirstCommand(busyClosestID) --get orders stored in unit's queue
  734.                 if cmd1 then
  735.                     local acmd = abs(cmd1.id)
  736.                     for _, options in ipairs(ud.buildOptions) do
  737.                         if ( options == acmd ) then canBuild = true break end   -- if found, escape loop and mark as "canBuild=true".
  738.                     end
  739.                 end
  740.                 if canBuild then --see if unit can use the same exact queue from the unit to be assisted
  741.                     return { unitID, cmd1.id, { cmd1.params[1], cmd1.params[2], cmd1.params[3], cmd1.params[4] }, busyDist, theCmd } --assist the busy unit by copying order.
  742.                 else --simply GUARD the unit to be assisted when all fail
  743.                     return { unitID, CMD_GUARD, { busyClosestID }, busyDist, 0 } --assist the busy unit by GUARDING it.
  744.                 end
  745.             end
  746.         else    -- new project is closer
  747.             if ( ud.canFly ) then queueDist = queueDist * 0.50 end
  748.             --if ( ud.canHover ) then queueDist = queueDist * 0.75 end
  749.             myCmd = myQueue[queueClose]
  750.             local cmd, x, y, z, h = myCmd.id, myCmd.x, myCmd.y, myCmd.z, myCmd.h
  751.             return { unitID, cmd, { x, y, z, h }, queueDist, queueClose }
  752.         end
  753.     end
  754. end
  755.  
  756. --  Detect when player enters spectator mode (thanks to SeanHeron).
  757.  
  758. function widget:PlayerChanged(playerID)
  759.     if spGetSpectatingState() then
  760. --      Echo( "<Central Build> Spectator mode. Widget removed." )
  761.         widgetHandler:RemoveWidget()
  762.         return
  763.     end
  764. end
  765.  
  766. --  Concept borrowed from Dave Rodger (trepan) MetalMakers widget
  767.  
  768. function widget:UnitDestroyed(unitID, unitDefID, unitTeam)
  769.     UnitGoByeBye(unitID)
  770. end
  771.  
  772. function widget:UnitTaken(unitID, unitDefID, unitTeam, newTeam)
  773.     UnitGoByeBye(unitID)
  774. end
  775.  
  776. --  If unit in our group is destroyed, captured, dropped from group, cancel any
  777. --  GUARD order from rest of group.  Probably not necessary for destroyed units.
  778.  
  779. function UnitGoByeBye(unitID)
  780.     if ( myUnits[unitID] ) then
  781.         myUnits[unitID] = nil
  782.         for unit2,myCmd in pairs(myUnits) do
  783.             if ( myCmd == "asst "..unitID ) then
  784.                 spGiveOrderToUnit(unit2, CMD_REMOVE, {CMD_GUARD}, {"alt"} ) --remove the GUARD command
  785.                 myUnits[unit2] = "idle"
  786.             end
  787.         end
  788.         myQueueUnreachable[unitID] = nil --clear unit's accessibility list
  789.     end
  790.        
  791. end
  792.  
  793. --  Prevent CBAI from canceling orders that just haven't made it to host yet
  794. --  because of high ping. Donated by SkyStar.
  795.  
  796. function ping()
  797.     local playerID = spGetLocalPlayerID()
  798.     local tname, _, tspec, tteam, tallyteam, tping, tcpu = spGetPlayerInfo(playerID)  
  799.     tping = (tping*1000-((tping*1000)%1)) /100 * 4
  800.     return max( tping, 15 ) --wait minimum 0.5 sec delay
  801. end
  802.  
  803. --  Generate unique key value for each command using its parameters.
  804. --  Much easier than expected once I learned Lua can use *anything* for a key.
  805.  
  806. function hash(myCmd)
  807.     local hash = myCmd.id .. "@" .. myCmd.x .. "x" .. myCmd.z
  808.     return hash
  809. end
  810.  
  811. function widget:KeyPress(key, mods, isRepeat)
  812. --Spring.Echo("<central_build_AI.lua DEBUG>: (KeyPress): ".. key)
  813.     if ( myGroupId > -1 ) then return end
  814.     if ( key ~= hotkey ) then return end
  815.     if ( mods.ctrl ) then   -- ctrl means add selected units to group
  816.     --Spring.Echo("<central_build_AI.lua DEBUG>: (KeyPress): CTRL")
  817.         local units = spGetSelectedUnits()
  818.         for _, unitID in ipairs(units) do   --  add the new units
  819.             if ( not myUnits[unitID] ) then
  820.                 local udid = spGetUnitDefID(unitID)
  821.                 local ud = UnitDefs[udid]
  822.                 if (ud.isBuilder and ud.canMove) then
  823.                     myUnits[unitID] = "idle"
  824.                 end
  825.             end
  826.         end
  827.         for unitID,_ in pairs(myUnits) do   --  remove any old units
  828.             local isInThere = false
  829.             for _,unit2 in ipairs(units) do
  830.                 if ( unitID == unit2 ) then
  831.                     isInThere = true
  832.                     break
  833.                 end
  834.             end
  835.             if ( not isInThere ) then
  836.                 myUnits[unitID] = nil
  837.             end
  838.         end
  839.     elseif ( mods.shift ) then  -- add group to selected units
  840.     --Spring.Echo("<central_build_AI.lua DEBUG>: (KeyPress): shift")
  841.         spSelectUnitMap(myUnits,true)
  842.     else    -- select our group or center our group if already selected.
  843.         local myUnitsCount = 0
  844.         for unitID,_ in pairs(myUnits) do
  845.             myUnitsCount = myUnitsCount + 1
  846.         end
  847.         if ( myUnitsCount < 1 ) then return end
  848.         local isInThere = true
  849.         local units = spGetSelectedUnits()
  850.         if ( # units ~= myUnitsCount ) then
  851.             isInThere = false
  852.         end
  853.         for _, unitID in ipairs(units) do
  854.             if ( not myUnits[unitID] ) then
  855.                 isInThere = false
  856.                 break
  857.             end
  858.         end
  859.         if ( isInThere ) then   -- center screen on our group
  860.             local xc,yc,zc = 0,0,0  -- composite mappos
  861.             for unitID,_ in pairs(myUnits) do
  862.                 local x, y, z = spGetUnitPosition(unitID)
  863.                 xc,yc,zc = xc+x,yc+y,zc+z
  864.             end
  865.             xc,yc,zc = xc/myUnitsCount,yc/myUnitsCount,zc/myUnitsCount
  866.             Spring.SetCameraTarget(xc, yc, zc)
  867.         end
  868.         spSelectUnitMap(myUnits)
  869.     end
  870. end
  871.  
  872. -- Function to help clear ALL existing command instantenously
  873.  
  874. function widget:TextCommand(command)
  875.     if command == "cba" then
  876.         for key,myCmd in pairs(myQueue) do
  877.             myQueue[key]=nil
  878.         end
  879.         Spring.Echo("cba's command queue cleared")
  880.         return true
  881.     end
  882.     return false
  883. end
  884.  
  885. --------------Additional Functions---------------------------
  886. -- The following functions is grouped here for easy debugging
  887. -- It add behaviour like path checking and enemy checks
  888. -------------------------------------------------------------
  889.  
  890. -- This function check all build site whether it is accessible to all constructor OR blocked by enemy. Reference: http://springrts.com/phpbb/viewtopic.php?t&t=22953&start=2
  891. -- NOTE: path check only work for Spring's Standard pathing because Spring.RequestPath() always return nil for qtpfs as in Spring 93.2.1+
  892.  
  893. function UpdateUnitsPathability()
  894.     for unitID, _ in pairs(myUnits) do
  895.         myQueueUnreachable[unitID] = {} --CLEAN. note: unitID entry is also cleared when unit die or exit CBA group
  896.         UpdateOneUnitPathability(unitID)
  897.     end
  898. end
  899.  
  900. -- This function check ALL build site whether it is accessible to 1 constructor. Reference: http://springrts.com/phpbb/viewtopic.php?t&t=22953&start=2
  901. function UpdateOneUnitPathability(unitID)
  902.     local udid = spGetUnitDefID(unitID)
  903.     local moveID = UnitDefs[udid].moveDef.id
  904.     local ux, uy, uz = spGetUnitPosition(unitID)    -- unit location
  905.     --check for build site in queue list
  906.     for queueKey, location in pairs(myQueue) do
  907.         local x, y, z = location.x, location.y, location.z
  908.         SetQueueUnreachableValue(unitID,moveID,ux,uy,uz,x,y,z,queueKey)
  909.     end
  910.     --check for build site NOT in queue list (such as order given without SHIFT)
  911.     for unitID2, queueKey in pairs(myUnits) do
  912.         if not myQueue[queueKey] and queueKey~='idle' and queueKey~='busy' then
  913.             local cmd1 = GetFirstCommand(unitID2) --get orders stored in unit2's queue
  914.             if cmd1 and cmd1.params[3] then
  915.                 local x, y, z = cmd1.params[1],cmd1.params[2],cmd1.params[3]
  916.                 SetQueueUnreachableValue(unitID,moveID,ux,uy,uz,x,y,z,queueKey)
  917.             end
  918.         end
  919.     end
  920.     --send STOP to units en-route to build site surrounded by enemy
  921.     local unitArray = {}
  922.     for unitID2, queueKey in pairs(myUnits) do
  923.         local enemy = myQueueUnreachable[unitID2][queueKey]==1
  924.         if enemy and myQueue[queueKey] then --was checked to contain enemy, and is a queue (not build assist or build order without SHIFT)
  925.             unitArray[#unitArray+1] = unitID2
  926.         end
  927.     end
  928.     Spring.GiveOrderToUnitArray (unitArray,CMD_STOP, {}, {}) --send stop
  929. end
  930.  
  931. -- This function check 1 build site whether it is accessible to ALL constructor. Reference: http://springrts.com/phpbb/viewtopic.php?t&t=22953&start=2
  932. function UpdateUnitsPathabilityForOneQueue(queueKey,customCoord)
  933.     local location = myQueue[queueKey]
  934.     customCoord = customCoord or {x=location.x,y=location.y,z=location.z}
  935.     local x, y, z = customCoord.x, customCoord.y, customCoord.z --use provided coordinate or use from coordinate from myQueue
  936.     for unitID, _ in pairs(myUnits) do
  937.         local udid = spGetUnitDefID(unitID)
  938.         local moveID = UnitDefs[udid].moveDef.id
  939.         local ux, uy, uz = spGetUnitPosition(unitID)    -- unit location
  940.         SetQueueUnreachableValue(unitID,moveID,ux,uy,uz,x,y,z,queueKey)
  941.     end
  942. end
  943.  
  944. --This function determine what to fill into the "myQueueUnreachable" table based on input
  945. function SetQueueUnreachableValue(unitID,moveID,ux,uy,uz,x,y,z,queueKey)
  946.     local reach = true --Note: first assume unit is flying and/or target always reachable
  947.     if moveID then --Note: crane/air-constructor do not have moveID!
  948.         local result,finCoord = IsTargetReachable(moveID, ux,uy,uz,x,y,z,128)
  949.         if result == "outofreach" then --if result not reachable but we have the closest coordinate, then:
  950.             reach = false --target is unreachable
  951.         else -- Spring.PathRequest() must be non-functional. (unsynced blocked?)
  952.         end
  953.         --Technical note: Spring.PathRequest() will return NIL(noreturn) if either origin is too close to target or when pathing is not functional (this is valid for Spring91, may change in different version)
  954.     end
  955.     if not reach then
  956.         myQueueUnreachable[unitID][queueKey]=2
  957.     elseif EnemyControlBuildSite({x,y,z}) then
  958.         myQueueUnreachable[unitID][queueKey]=1
  959.     else
  960.         myQueueUnreachable[unitID][queueKey]=0
  961.     end
  962. end
  963.  
  964. --This function process result of Spring.PathRequest() to say whether target is reachable or not
  965. function IsTargetReachable (moveID, ox,oy,oz,tx,ty,tz,radius)
  966.     local result,lastcoordinate, waypoints
  967.     local path = Spring.RequestPath( moveID,ox,oy,oz,tx,ty,tz, radius)
  968.     if path then
  969.         local waypoint = path:GetPathWayPoints() --get crude waypoint (low chance to hit a 10x10 box). NOTE; if waypoint don't hit the 'dot' is make reachable build queue look like really far away to the GetWorkFor() function.
  970.         local finalCoord = waypoint[#waypoint]
  971.         if finalCoord then --unknown why sometimes NIL
  972.             local dx, dz = finalCoord[1]-tx, finalCoord[3]-tz
  973.             local dist = math.sqrt(dx*dx + dz*dz)
  974.             if dist <= radius+20 then --is within radius?
  975.                 result = "reach"
  976.                 lastcoordinate = finalCoord
  977.                 waypoints = waypoint
  978.             else
  979.                 result = "outofreach"
  980.                 lastcoordinate = finalCoord
  981.                 waypoints = waypoint
  982.             end
  983.         end
  984.     else
  985.         result = "noreturn"
  986.         lastcoordinate = nil
  987.         waypoints = nil
  988.     end
  989.     return result, lastcoordinate, waypoints
  990.    
  991.     --[[ --step-by-step detailed path check: WARNING!: lag prone if path too long and unreachable.
  992.     local dx,dy,dz = x-ux, y-uy, z-uz
  993.     local distance = math.sqrt(dx*dx + dy*dy + dz*dz)
  994.     local exageratedYetReasonableDistance = math.sqrt(distance*distance+distance*distance)
  995.     local walkedDistance = 0
  996.     local nx,ny,nz = ux, uy, uz
  997.     while (not ( walkedDistance>=exageratedYetReasonableDistance or reach )) do
  998.         local nx1,ny1,nz1 = path:Next(nx,ny,nz)
  999.         dx,dy,dz = abs(nx1-nx),abs(ny1-ny),abs(nz1-nz)
  1000.         walkedDistance =walkedDistance + dx + dy + dz --Note: is rough estimate,not accurate because not calculate hypotenus
  1001.         if nx1 and ny1 and nz1 then
  1002.             if abs(nx1-x) <200 and abs(ny1-y)<200 and abs(nz1-z)<200 then
  1003.                 reach = true
  1004.             end
  1005.         end
  1006.         nx,ny,nz = nx1,ny1,nz1
  1007.     end
  1008.     --]]   
  1009. end
  1010.  
  1011.  
  1012. -- Helper function (detect enemy at build site)
  1013. function EnemyControlBuildSite(order)
  1014.     local coordString = order[1].." " .. order[3]
  1015.     if cachedValue[coordString] and cachedValue[coordString].frame == currentFrame then
  1016.         return cachedValue[coordString].enemy
  1017.     end
  1018.    
  1019.     local unitList = spGetUnitsInCylinder(order[1], order[3], enemyRange)
  1020.     local enemyCount = 0
  1021.     local totalCount = #unitList
  1022.     local returnValue = false
  1023.     for i=1, totalCount do
  1024.         local unitID = unitList[i]
  1025.         local unitAlly = spGetUnitAllyTeam(unitID)
  1026.         if unitAlly~=myAllyID then
  1027.             enemyCount = enemyCount + 1
  1028.         end
  1029.     end
  1030.     if enemyCount/totalCount > enemyThreshold then
  1031.         returnValue = true
  1032.     end
  1033.    
  1034.     cachedValue[coordString] = cachedValue[coordString] or {}
  1035.     cachedValue[coordString].enemy = returnValue
  1036.     cachedValue[coordString].frame = currentFrame
  1037.     return returnValue
  1038. end
  1039.  
  1040. --  Detect if enemy is present at build site, and cancel queue if there is.
  1041. --  this make sure constructor don't suicide into enemy if unattended.
  1042.  
  1043. function EnemyAtBuildSiteCleanOrder(unitID, order, queueKey)
  1044.     if queueKey ~= 0 then --can't handle GUARD assist yet if queueKey==0
  1045.         local enemyPresent = myQueueUnreachable[unitID] and myQueueUnreachable[unitID][queueKey]==1
  1046.         if enemyPresent or EnemyControlBuildSite(order) then
  1047.             if myQueue[queueKey] then              
  1048.                 local unitArray = {}
  1049.                 for unitID2, queueKey2 in pairs(myUnits) do
  1050.                     if queueKey2 == queueKey then
  1051.                         unitArray[#unitArray+1] = unitID2
  1052.                     end
  1053.                 end
  1054.                 Spring.GiveOrderToUnitArray (unitArray,CMD_STOP, {}, {}) --send STOP to all units already assigned to this queue. A scenario: a newly built constructor decide to assist another old constructor (which is en-route toward an enemy infested build site), this stop the old constructor
  1055.                 myQueue[queueKey] = nil  --remove queue
  1056.                 return true --cancel order assignment
  1057.             else --can't handle build-assist without queue yet (myQueue[queueKey] == nil). A scenario: user directly order a build without SHIFT
  1058.                 return nil
  1059.             end
  1060.         end
  1061.     end
  1062.     return nil
  1063. end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement