Advertisement
Guest User

Global Build Command yayborked

a guest
Apr 16th, 2015
311
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 31.29 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. --              CAUTION! CAUTION! CAUTION!
  14. -- Please avoid accidental removal of desireable behaviour! so
  15. -- only regular user of CBAI should make changes/clean-up.
  16. -- (due to undocumented use-case & apparent complexity of this widget)
  17.  
  18.  
  19. local version = "v1.356"
  20. function widget:GetInfo()
  21.   return {
  22.     name      = "Central Build AI",
  23.     desc      = version.. " Common non-hierarchical permanent build queue\n\nInstruction: add constructor(s) to group 0 "..
  24. "(\255\200\200\200Ctrl+0\255\255\255\255, or \255\200\200\200Alt+0\255\255\255\255 if using \255\90\255\90Auto Group\255\255\255\255 widget), "..
  25. "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",
  26.     author    = "Troy H. Cheek, modified by msafwan",
  27.     date      = "July 20, 2009, 8 March 2014",
  28.     license   = "GNU GPL, v2 or later",
  29.     layer     = 10,
  30.     enabled   = false  --  loaded by default?
  31.   }
  32. end
  33.  
  34. --  Central Build AI creates a common build order queue for all units in the
  35. --  group.  Select this group (or any member of it) and issue build orders
  36. --  while holding down the shift key to add orders to the common queue.
  37. --  Idle builders in the group will automatically move to carry out orders
  38. --  or assist other builders.  Issue same build order again to cancel.
  39. --  Orders issued without shift will be carried out immediately.
  40.  
  41. -- optional ToDo:
  42. -- _ orders with shift+ctrl have higher priority ( insert in queue -> cons won't interrupt their current actions)
  43.  
  44. ---- CHANGELOG -----
  45. -- msafwan(xponen)  v1.355  (26Jan2015) :   1) all builder re-assign job every 4 second (even if already assigned a job)
  46. --                                          2) keep queue for unfinished building
  47. --                                          3) lower priority (and/or removal) for queue at enemy infested area
  48. --
  49. -- msafwan,         v1.21   (7oct2012)  :   fix some cases where unit become 'idle' but failed to be registered by CBA,
  50. --                                          make CBA assign all job at once rather than sending 1 by 1 after every some gameframe delay,
  51. -- msafwan,         v1.2    (4sept2012) :   made it work with ZK "cmd_mex_placement.lua" mex queue,
  52. --                                          reduce the tendency to make a huge blob of constructor (where all constructor do same job),
  53. --                                          reduce chance of some constructor not given job when player have alot of constructor,
  54. -- rafal,           v1.1    (2May2012)  :   Don't fetch full Spring.GetCommandQueue in cases when only the first command is needed - instead using
  55. --                                          GetCommandQueue(unitID, 1)
  56. -- KingRaptor,      v1.1    (24dec2011) :   Removed the "remove in 85.0" stuff
  57. -- versus666,       v1.1    (16dec2011) :   mostly changed the layer order to get a logical priority among widgets.
  58. -- KingRaptor,      v1.1    (8dec2011)  :   Fixed the remaining unitdef tags for 85.0
  59. -- versus666,       v1.1    (7jan2011)  :   Made CBA, cmd_retreat, gui_nuke_button, gui_team_platter.lua, unit_auto_group to obey F5 (gui hidden).
  60. -- KingRaptor,      v1.1    (2Nov2010)  :   Moved version number from name to description.
  61. -- lccquantum,      v1.1    (2Nov2010)  :   central_build_AI is disabled by default (people will wonder why their builders are acting wierd when in group 0)
  62. -- versus666,       v1.1    (1Nov2010)  :   introduced into ZK
  63.  
  64. --------------------------------------------------------------------------------
  65. --------------------------------------------------------------------------------
  66. -- Declarations ----------------------------------------------------------------
  67.  
  68. 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).
  69. local hotkey = string.byte( "g" )   --//Constant: a custom hotkey to add unit to custom group. NOTE: set myGroupId to "-1" to use this hotkey.
  70.  
  71. local Echo                  = Spring.Echo
  72. local spGetUnitDefID        = Spring.GetUnitDefID
  73. local spGetGroupList        = Spring.GetGroupList
  74. local spGetGroupUnits       = Spring.GetGroupUnits
  75. local spGetSelectedUnits    = Spring.GetSelectedUnits
  76. local spIsUnitInView        = Spring.IsUnitInView
  77. local spIsAABBInView        = Spring.IsAABBInView
  78. local spGetUnitsInCylinder  = Spring.GetUnitsInCylinder
  79. local spGetUnitViewPosition = Spring.GetUnitViewPosition
  80. local spGetCommandQueue     = Spring.GetCommandQueue
  81. local spGetUnitPosition     = Spring.GetUnitPosition
  82. local spGetUnitHealth       = Spring.GetUnitHealth
  83. local spGiveOrderToUnit     = Spring.GiveOrderToUnit
  84. local spGetMyPlayerID       = Spring.GetMyPlayerID
  85. local spGetMyTeamID         = Spring.GetMyTeamID
  86. local spGetFeatureTeam      = Spring.GetFeatureTeam
  87. local spGetLocalPlayerID    = Spring.GetLocalPlayerID
  88. local spGetPlayerInfo       = Spring.GetPlayerInfo
  89. local spGetSpectatingState  = Spring.GetSpectatingState
  90. local spGetModKeyState      = Spring.GetModKeyState
  91. local spTestBuildOrder      = Spring.TestBuildOrder
  92. local spSelectUnitMap       = Spring.SelectUnitMap
  93. local spGetUnitsInCylinder  = Spring.GetUnitsInCylinder
  94. local spGetUnitsInRectangle = Spring.GetUnitsInRectangle
  95. local spGetUnitAllyTeam     = Spring.GetUnitAllyTeam
  96. local spGetUnitIsStunned    = Spring.GetUnitIsStunned
  97.  
  98. local glPushMatrix  = gl.PushMatrix
  99. local glPopMatrix   = gl.PopMatrix
  100. local glTranslate   = gl.Translate
  101. local glBillboard   = gl.Billboard
  102. local glColor       = gl.Color
  103. local glText        = gl.Text
  104. local glBeginEnd    = gl.BeginEnd
  105. local GL_LINE_STRIP = GL.LINE_STRIP
  106. local glDepthTest   = gl.DepthTest
  107. local glRotate      = gl.Rotate
  108. local glUnitShape   = gl.UnitShape
  109. local glVertex      = gl.Vertex
  110.  
  111. local CMD_WAIT      = CMD.WAIT
  112. local CMD_MOVE      = CMD.MOVE
  113. local CMD_PATROL    = CMD.PATROL
  114. local CMD_REPAIR    = CMD.REPAIR
  115. local CMD_INSERT    = CMD.INSERT
  116. local CMD_REMOVE    = CMD.REMOVE
  117. local CMD_RECLAIM   = CMD.RECLAIM
  118. local CMD_GUARD     = CMD.GUARD
  119. local CMD_STOP      = CMD.STOP
  120.  
  121. local abs   = math.abs
  122. local floor = math.floor
  123. local huge  = math.huge
  124. local sqrt  = math.sqrt
  125. local max   = math.max
  126.  
  127. local currentFrame = Spring.GetGameFrame()
  128. local nextFrame = currentFrame +30
  129. local nextPathCheck = currentFrame + 400 --is used to check whether constructor can go to construction site
  130. local myAllyID = Spring.GetMyAllyTeamID()
  131. local textColor = {0.7, 1.0, 0.7, 1.0}
  132. local textSize = 12.0
  133.  
  134. --  "global" for this widget.  This is probably not a recommended practice.
  135. local myUnits = {}  --  list of units in the Central Build group
  136. local myQueue = {}  --  list of commands for Central Build group
  137. local groupHasChanged   --  Flag if group members have changed.
  138.  
  139. local myQueueUnreachable = {} -- list of queue which units can't reach
  140. local myQueueDanger = {} --list of queue which lead to dead constructors
  141.  
  142. local cachedValue = {} --cached results for "EnemyControlBuildSite()" function to reduce cost for repeated call
  143. local cachedMetalCost = {} --cached metalcost for "GetWorkFor()" function
  144. local cachedCommand = {} --cached first command for "GetWorkFor{}" function
  145. local reassignedUnits = {} --list of units that had its task re-checked (to be re-tasked or remained with current task).
  146.  
  147. --------------------------------------------
  148. --List of prefix used as value for myUnits[]
  149. local queueType = {
  150.     drec = 'drec', --appended with: cmdId .. "@" .. x .. "x" .. z. Indicate direct orders from the user
  151.     buildQueue = 'queu',--appended with: cmdId .. "@" .. x .. "x" .. z. Indicate unit have task. Its also a Key to myQueue[], if no longer a Key to myQueue[] then it meant the construction has already begun  
  152.     idle = 'idle',
  153. }
  154.  
  155. --------------------------------------------------------------------------------
  156. --------------------------------------------------------------------------------
  157. -- Event Handlers --------------------------------------------------------------
  158.  
  159. --  Borrowed from xyz's Action Finder and very_bad_soldier's Ghost Radar
  160. function widget:Initialize()
  161.     if spGetSpectatingState() then
  162.         Echo( "<Central Build AI>: Spectator mode. Widget removed." )
  163.         widgetHandler:RemoveWidget()
  164.         return
  165.     end
  166.     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"
  167. end
  168.  
  169. --  Paint 'cb' tags on units, draw ghosts of items in central build queue.
  170. --  Text stuff mostly borrowed from gunblob's Group Label and trepan/JK's BuildETA.
  171. --  Ghost stuff borrowed from very_bad_soldier's Ghost Radar.
  172. function widget:DrawWorld()
  173.     if not Spring.IsGUIHidden() then
  174.         local alt, ctrl, meta, shift = spGetModKeyState()
  175.  
  176.         for unitID,myCmd in pairs(myUnits) do   -- show user which units are in CB
  177.             if spIsUnitInView(unitID) then
  178.                 local ux, uy, uz = spGetUnitViewPosition(unitID)
  179.                 glPushMatrix()
  180.                 glTranslate(ux, uy, uz)
  181.                 glBillboard()
  182.                 glColor(textColor)
  183.                 glText(myCmd:sub(1,4), -10.0, -15.0, textSize, "con")
  184.                 glPopMatrix()
  185.                 glColor(1, 1, 1, 1)
  186.             end -- if InView
  187.         end -- for unitID in group
  188.  
  189.         for key, myCmd in pairs(myQueue) do -- show items in build queue
  190.             local cmd = myCmd.id
  191.             cmd = abs( cmd )
  192.             local x, y, z, h = myCmd.x, myCmd.y, myCmd.z, myCmd.h
  193.             local degrees = h * 90
  194.             if spIsAABBInView(x-1,y-1,z-1,x+1,y+1,z+1) then
  195.                 if ( shift ) then
  196.                     glColor(0.0, 1.0, 0.0, 1 )
  197.                     glBeginEnd(GL_LINE_STRIP, DrawOutline, cmd, x, y, z, h)
  198.                 end
  199.             glColor(1.0, 1.0, 1.0, 0.35 )   -- ghost value 0.35
  200.             glDepthTest(true)
  201.             glPushMatrix()
  202.             glTranslate( x, y, z )
  203.             glRotate( degrees, 0, 1.0, 0 )
  204.             glUnitShape( cmd, spGetMyTeamID() )
  205.             glRotate( degrees, 0, -1.0, 0 )
  206.             glBillboard()                   -- also show some debug stuff
  207.             glColor(textColor)
  208.             glText(cmd, -10.0, -15.0, textSize, "con")
  209.             glPopMatrix()
  210.             glDepthTest(false)
  211.             glColor(1, 1, 1, 1)
  212.             end -- if inview
  213.         end
  214.     end
  215. end
  216.  
  217. function DrawOutline(cmd,x,y,z,h)
  218.     local ud = UnitDefs[cmd]
  219.     local baseX = ud.xsize * 4 -- ud.buildingDecalSizeX
  220.     local baseZ = ud.zsize * 4 -- ud.buildingDecalSizeY
  221.     if (h == 1 or h==3) then
  222.         baseX,baseZ = baseZ,baseX
  223.     end
  224.     glVertex(x-baseX,y,z-baseZ)
  225.     glVertex(x-baseX,y,z+baseZ)
  226.     glVertex(x+baseX,y,z+baseZ)
  227.     glVertex(x+baseX,y,z-baseZ)
  228.     glVertex(x-baseX,y,z-baseZ)
  229. end
  230.  
  231. --  Stuff which needs to run regularly but isn't covered elsewhere.
  232. --  Was Update(), but Niobium says GameFrame() is more better.
  233. function widget:GameFrame(thisFrame)
  234.     currentFrame = thisFrame
  235.     if ( thisFrame < nextFrame ) then
  236.         return
  237.     end
  238.     if ( groupHasChanged == true ) then
  239.         UpdateOneGroupsDetails(myGroupId)
  240.     end
  241.     FindEligibleWorker()    -- compile list of eligible units and assign them jobs.
  242.     nextFrame = thisFrame + 60  -- try again in 2 second if nothing else triggers
  243. end
  244.  
  245. --  This function detects that a new group has been defined or changed.  Use it to set a flag
  246. --  because it fires before all units it's going to put into group have actually been put in.
  247. --  Borrowed from gunblob's UnitGroups v5.1
  248. function widget:GroupChanged(groupId)  
  249.     if groupId == myGroupId then
  250.         groupHasChanged = true
  251.         nextFrame = currentFrame + ping()
  252.     end
  253. end
  254.  
  255. --  A compatibility function: receive broadcasted event from "cmd_mex_placement.lua" (ZK specific) which notify us that it has its own mex queue
  256. function CommandNotifyMex(id,params,options, isAreaMex)
  257.     local groundHeight = Spring.GetGroundHeight(params[1],params[3])
  258.     params[2] = math.max(0, groundHeight)
  259.     local returnValue = widget:CommandNotify(id, params, options, true,isAreaMex)
  260.     return returnValue
  261. end
  262.  
  263. --  If the command is issued to something in our group, flag it.
  264. --  Thanks to Niobium for pointing out CommandNotify().
  265. function widget:CommandNotify(id, params, options, isZkMex,isAreaMex)
  266.     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).
  267.         return
  268.     end
  269.     if options.meta then --skip special insert command (spacebar). Handled by CommandInsert() widget
  270.         return
  271.     end
  272.    
  273.     local selectedUnits = spGetSelectedUnits()
  274.     for _, unitID in pairs(selectedUnits) do    -- check selected units...
  275.         if ( myUnits[unitID] ) then --  was issued to one of our units.
  276.             if ( id < 0 ) then --if the order is for building something
  277.                 local x, y, z, h = params[1], params[2], params[3], params[4]
  278.                 local myCmd = { id=id, x=x, y=y, z=z, h=h }
  279.                 local isOverlap = CleanOrders(myCmd) -- check if current queue overlap with existing queue, and clear up any invalid queue
  280.                 if not isOverlap then
  281.                     local hash = queueType.buildQueue .. BuildHash(myCmd)
  282.                         myQueue[hash] = myCmd   -- add to CB queue
  283.                 end
  284.                 nextFrame = currentFrame + 30 --wait 1 more second before distribute work, so user can queue more stuff
  285.                 if ( options.shift ) then -- if the command was given with shift
  286.                 return true -- we return true to take ownership of the command from Spring.
  287.                 else myUnits[unitID] = queueType.drec -- for direct build orders given without shift we simply mark the constructor as under user direction and let Spring handle the command normally
  288.                 -- note we still add the command to the queue so that other units can see the job and assist independently
  289.                 end
  290.             else -- if the order is not for building something
  291.                 myUnits[unitID] = queueType.drec -- then the unit is marked as under user direction and we let spring handle it.
  292.             end
  293.         end
  294.         -- do NOT return here because there may be more units.
  295.     end
  296. end
  297.  
  298. --If one of our units finished a build order, cancel units guarding/assisting it.
  299. --This replace UnitCmdDone() because UnitCmdDone() is called even if command is not finished, such as when new command is inserted into existing queue
  300. --Credit to Niobium for pointing out UnitCmdDone() originally.
  301. function widget:UnitFinished(unitID, unitDefID, unitTeam)
  302.     local ux, _, uz = spGetUnitPosition(unitID)
  303.     local myCmd = { id=-unitDefID, x=ux, z=uz, }
  304.     local hash = queueType.buildQueue .. BuildHash(myCmd)
  305.     StopAnyWorker(hash) --stop any other workers working on the job that finished
  306.     myQueue[hash] = nil --remove the job from the build queue
  307.     nextFrame = currentFrame + ping() --find new work
  308. end
  309.  
  310. --  If unit detected as idle (probably finished work) and it's one of ours, time to find it some work.
  311. function widget:UnitIdle(unitID, unitDefID, teamID)
  312.     if ( myUnits[unitID] ) then
  313.         myUnits[unitID] = queueType.idle
  314.         nextFrame = currentFrame + ping() --find new work
  315.     end
  316. end
  317.  
  318. --  Detect when player enters spectator mode (thanks to SeanHeron).
  319. function widget:PlayerChanged(playerID)
  320.     if spGetSpectatingState() then
  321. --      Echo( "<Central Build> Spectator mode. Widget removed." )
  322.         widgetHandler:RemoveWidget()
  323.         return
  324.     end
  325. end
  326.  
  327. function widget:KeyPress(key, mods, isRepeat)
  328. --Spring.Echo("<central_build_AI.lua DEBUG>: (KeyPress): ".. key)
  329.     if ( myGroupId > -1 ) then return end
  330.     if ( key ~= hotkey ) then return end
  331.     if ( mods.ctrl ) then   -- ctrl means add selected units to group
  332.     --Spring.Echo("<central_build_AI.lua DEBUG>: (KeyPress): CTRL")
  333.         local units = spGetSelectedUnits()
  334.         for _, unitID in ipairs(units) do   --  add the new units
  335.             if ( not myUnits[unitID] ) then
  336.                 local udid = spGetUnitDefID(unitID)
  337.                 local ud = UnitDefs[udid]
  338.                 if (ud.isBuilder and ud.canMove) then
  339.                     myUnits[unitID] = queueType.idle
  340.                 end
  341.             end
  342.         end
  343.         for unitID,_ in pairs(myUnits) do   --  remove any old units
  344.             local isInThere = false
  345.             for _,unit2 in ipairs(units) do
  346.                 if ( unitID == unit2 ) then
  347.                     isInThere = true
  348.                     break
  349.                 end
  350.             end
  351.             if ( not isInThere ) then
  352.                 myUnits[unitID] = nil
  353.             end
  354.         end
  355.     elseif ( mods.shift ) then  -- add group to selected units
  356.     --Spring.Echo("<central_build_AI.lua DEBUG>: (KeyPress): shift")
  357.         spSelectUnitMap(myUnits,true)
  358.     else    -- select our group or center our group if already selected.
  359.         local myUnitsCount = 0
  360.         for unitID,_ in pairs(myUnits) do
  361.             myUnitsCount = myUnitsCount + 1
  362.         end
  363.         if ( myUnitsCount < 1 ) then return end
  364.         local isInThere = true
  365.         local units = spGetSelectedUnits()
  366.         if ( # units ~= myUnitsCount ) then
  367.             isInThere = false
  368.         end
  369.         for _, unitID in ipairs(units) do
  370.             if ( not myUnits[unitID] ) then
  371.                 isInThere = false
  372.                 break
  373.             end
  374.         end
  375.         if ( isInThere ) then   -- center screen on our group
  376.             local xc,yc,zc = 0,0,0  -- composite mappos
  377.             for unitID,_ in pairs(myUnits) do
  378.                 local x, y, z = spGetUnitPosition(unitID)
  379.                 xc,yc,zc = xc+x,yc+y,zc+z
  380.             end
  381.             xc,yc,zc = xc/myUnitsCount,yc/myUnitsCount,zc/myUnitsCount
  382.             Spring.SetCameraTarget(xc, yc, zc)
  383.         end
  384.         spSelectUnitMap(myUnits)
  385.     end
  386. end
  387.  
  388. -- Function to help clear ALL existing command instantenously
  389. function widget:TextCommand(command)
  390.     if command == "cba" then
  391.         for key,myCmd in pairs(myQueue) do
  392.             myQueue[key]=nil
  393.         end
  394.         Spring.Echo("cba's command queue cleared")
  395.         return true
  396.     end
  397.     return false
  398. end
  399.  
  400. --------------------------------------------------------------------------------
  401. --------------------------------------------------------------------------------
  402. -- Core Logic ------------------------------------------------------------------
  403.  
  404. --select 10 worker from a pool of idle constructor or reuse some of the non-idle constructor as new worker
  405. function FindEligibleWorker()
  406.     local unitToWork = {}
  407.     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)
  408.         if myCmd == queueType.idle then --if unit is marked as idle, then double check it
  409.             local cmd1 = GetFirstCommand(unitID,true)
  410.             if ( cmd1 and cmd1.id) then
  411.                 myUnits[unitID] = queueType.drec
  412.             else
  413.                 if #unitToWork < 10 then
  414.                     --NOTE: only allow up to 10 idler to be processed to prevent super lag.
  415.                     --The amount of external loop will be (numberOfmyunit^2+numberOfQueue)*numberOfIdle^2.
  416.                     --ie: if all variable were 50, then the amount of loop in total is 6375000 loop (6 million).
  417.                     unitToWork[#unitToWork+1] = unitID
  418.                 end
  419.             end
  420.         end
  421.     end
  422.     --if we still have room after assigning idle workers then we can mark queue-assigned workers to be reassigned in case lower cost jobs become available.
  423.     if #unitToWork < 10 then
  424.         local gaveWork = false
  425.         for unitID,myCmd in pairs(myUnits) do
  426.             if (not reassignedUnits[unitID]) then
  427.                 local workType = myCmd:sub(1,4)
  428.                 if workType == queueType.buildQueue then
  429.                     unitToWork[#unitToWork+1] = unitID
  430.                     gaveWork = true
  431.                     reassignedUnits[unitID] = true
  432.                     if (#unitToWork == 10) then
  433.                         break
  434.                     end
  435.                 end
  436.             end
  437.         end
  438.         if not gaveWork then --no more unit to be reassigned? then reset list
  439.             reassignedUnits = {}
  440.         end
  441.     end
  442.     CleanOrders()   -- check build site(s) for blockage and handle accordingly.
  443.  
  444.     if (#unitToWork > 0) then
  445.         GiveWorkToUnits(unitToWork)
  446.     end
  447.     cachedCommand = {}
  448. end
  449.  
  450. -- This function finds work for all the workers compiled in our eligible worker list and issues the orders.
  451. function GiveWorkToUnits(unitToWork)
  452.     if myQueue then -- if there is work on the queue
  453.         for unitID,_ in pairs(unitToWork) do
  454.             local myJob = FindCheapestJob(unitID) -- find the cheapest job
  455.             if myJob then -- if myJob returns a job rather than nil
  456.                 spGiveOrderToUnit(unitID, myJob, true) -- issue the cheapest job as an order to the unit
  457.                 myUnits[unitID] = queueType.buildQueue -- and mark the unit as under CB control
  458.                 Spring.Echo("Assigned a job.")
  459.             else
  460.                 myUnits[unitID] = queueType.idle -- otherwise if no valid job is found mark it as idle
  461.                 Spring.Echo("No valid jobs.")
  462.             end
  463.         end
  464.     else
  465.         myUnits[unitID] = queueType.idle -- or if there are no jobs on the queue, mark as idle
  466.         Spring.Echo("Empty Queue.")
  467.     end
  468. end
  469.  
  470. -- This function returns the cheapest job for a given worker, given the cost model implemented in CostOfJob().
  471. function FindCheapestJob(unitID)
  472.     local cachedJob = nil -- the cheapest job that we've seen
  473.     local cachedCost = 0 -- the cost of the currently cached cheapest job
  474.     local ux, uy, uz = spGetUnitPosition(unitID)    -- unit location
  475.     local udid = spGetUnitDefID(unitID)
  476.     local moveID = UnitDefs[udid].moveDef.id -- unit pathing type
  477.    
  478.     for index,tmpJob in pairs(myQueue) do -- here we compare our unit to each job in the queue
  479.         local cmd, jx, jy, jz = tmpJob.id, tmpJob.x, tmpJob.y, tmpJob.z --the location of the current job
  480.         local udef = UnitDefs[udid]
  481.        
  482.         if CanBuildThis(cmd, udef) then -- if our worker can perform the job
  483.             Spring.Echo("Job is buildable.")
  484.             if IsTargetReachable(moveID, ux, uy, uz, jx, jy, jz) then -- and can also reach the job site
  485.                 Spring.Echo("Job is reachable.")
  486.                 local tmpCost = CostOfJob(unitID, tmpJob, index) -- calculate the job cost
  487.                 if cachedJob == nil or tmpCost < cachedCost then -- then if there is no cached job or our current job is cheaper, replace the cached job with the current one and update the cost
  488.                     cachedJob = tmpJob
  489.                     cachedCost = tmpCost
  490.                     Spring.Echo("Cached a job.")
  491.                 end
  492.             end
  493.         end
  494.     end
  495.     return cachedJob -- after iterating over the entire queue, the resulting cached job will be the cheapest, return it.
  496. end      
  497.                    
  498. -- This function implements the cost model by which jobs are assigned.
  499. function CostOfJob(unitID, job, qIndex)
  500.     local ux, uy, uz = spGetUnitPosition(unitID)    -- the location of our unit
  501.     local jx, jy, jz = tmpJob.x, tmpJob.y, tmpJob.z -- the location of the job
  502.     local distance = Distance(ux, uz, jx, jz) -- the distance between our worker and job
  503.    
  504.     local costMod = 1 -- our cost modifier
  505.    
  506.     for unit,cmd in pairs(myUnits) do -- find the number of other workers besides our current worker assigned to the job already
  507.         if ( unitID ~= unit and job == cmd) then
  508.             costMod = costMod + 1 -- and add that number to costMod
  509.         end
  510.     end
  511.    
  512.     costModEcho = tostring(costMod)
  513.     Spring.Echo("costMod= " .. costModEcho)
  514.    
  515.     local cost = 0
  516.    
  517.     if costMod == 1 then
  518.         cost = distance -- if no other workers are assigned to the job, cost equals distance
  519.     else
  520.         cost = max(300, distance) * costMod -- otherwise the distance is rounded up to 300 elmos if it is less than this, and then multiplied by costMod.
  521.         -- this is to minimize constructors mobbing on jobs when they could build faster by splitting up; ie to reduce walking time.
  522.     end
  523.     return cost
  524. end                
  525.  
  526. -- This function removes duplicate orders, processes cancel requests, and handles blocked builds.
  527. function CleanOrders(newCmd)
  528.     local xSize = nil --variables for checking queue overlaping
  529.     local zSize = nil
  530.     local xSize_queue = nil
  531.     local zSize_queue = nil
  532.     local x_newCmd = nil
  533.     local z_newCmd = nil
  534.     local isOverlap = nil
  535.     if newCmd then --check the size of the new queue
  536.         local newCmdID = abs ( newCmd.id )
  537.         x_newCmd = newCmd.x
  538.         z_newCmd = newCmd.z
  539.         if x_newCmd then
  540.             if newCmd.h == 0 or newCmd.h == 2 then --get building facing. Reference: unit_prevent_lab_hax.lua by googlefrog
  541.                 xSize = UnitDefs[newCmdID].xsize*4
  542.                 zSize = UnitDefs[newCmdID].zsize*4
  543.             else
  544.                 xSize = UnitDefs[newCmdID].zsize*4
  545.                 zSize = UnitDefs[newCmdID].xsize*4
  546.             end
  547.         end
  548.     end
  549.    
  550.     local blockageType = {
  551.         obstructed = 0, --also applies to blocked by another structure
  552.         mobiles = 1,
  553.         free = 2,
  554.     }
  555.  
  556.     for key,myCmd in pairs(myQueue) do
  557.         local cmdID = abs( myCmd.id )
  558.         local x, y, z, facing = myCmd.x, myCmd.y, myCmd.z, myCmd.h
  559.         local canBuildThisThere,featureID = spTestBuildOrder(cmdID,x,y,z,facing) --check if build site is blocked by buildings & terrain
  560.  
  561.         local newFree = (newCmd and xSize and canBuildThisThere ~= blockageType.obstructed)
  562.         local blocked = (canBuildThisThere == blockageType.mobiles or canBuildThisThere == blockageType.obstructed)
  563.         local structureBlockage = false
  564.         local overlappingQueue = false
  565.         local featureBlockage = false
  566.         local isNanoframe = false
  567.        
  568.         if blocked or newFree then
  569.             if facing == 0 or facing == 2 then --check the size of the queued building
  570.                 xSize_queue = UnitDefs[cmdID].xsize*4
  571.                 zSize_queue = UnitDefs[cmdID].zsize*4
  572.             else
  573.                 xSize_queue = UnitDefs[cmdID].zsize*4
  574.                 zSize_queue = UnitDefs[cmdID].xsize*4
  575.             end
  576.         end
  577.        
  578.         if blocked then
  579.             local blockingUnits = spGetUnitsInRectangle(x-xSize_queue, z-zSize_queue, x+xSize_queue, z+zSize_queue)
  580.             for i=1, #blockingUnits do
  581.                 local blockerDefID = spGetUnitDefID(blockingUnits[i])
  582.                 if blockerDefID == cmdID then
  583.                     local _,_,nanoframe = spGetUnitIsStunned(blockingUnits[i])
  584.                     if not nanoframe then
  585.                         structureBlockage= true --unit that we wanted to build is already being build, cancel this queue
  586.                     else
  587.                         isNanoframe = true
  588.                     end
  589.                     break;
  590.                 elseif math.modf(UnitDefs[blockerDefID].speed*10) == 0 then -- immobile unit is blocking. ie: modf(0.01*10) == 00 (fractional speed is ignored)
  591.                     structureBlockage= true --blocking unit can't move away, cancel this queue
  592.                     break;
  593.                 end
  594.             end
  595.         end
  596.        
  597.         if newFree then --check if build site overlap new queue
  598.             local minTolerance = xSize_queue + xSize --check minimum tolerance in x direction
  599.             local axisDist = abs (x - x_newCmd) --check actual separation in x direction
  600.             if axisDist < minTolerance then --if too close in x direction
  601.                 minTolerance = zSize_queue + zSize --check minimum tolerance in z direction
  602.                 axisDist = abs (z - z_newCmd) -- check actual separation in z direction
  603.                 if axisDist < minTolerance then --if too close in z direction
  604.                     overlappingQueue = true --flag this queue for removal
  605.                     isOverlap = true --return true
  606.                    
  607.                     StopAnyWorker(key) --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
  608.                 end
  609.             end
  610.         end
  611.        
  612.         if (canBuildThere==blockageType.obstructed and not structureBlockage) --terrain issue
  613.         or (structureBlockage) --queue on existing building (not nanoframe of intended building)
  614.         or (overlappingQueue) -- queue on another queue
  615.         then --if queue is flagged for removal
  616.             myQueue[key] = nil  --remove queue
  617.         elseif isNanoframe then
  618.             myQueue[key].isStarted = true
  619.         end
  620.     end
  621.    
  622.     return isOverlap --return a value for "widget:CommandNotify()" to handle user's command.
  623. end
  624.  
  625. --------------------------------------------------------------------------------
  626. --------------------------------------------------------------------------------
  627. -- Helper Functions ------------------------------------------------------------
  628.  
  629. --  This function actually updates the list of builders in the CB group (myGroup).
  630. --  Also borrowed from gunblob's UnitGroups v5.1
  631. function UpdateOneGroupsDetails(myGroupId)
  632.     local units = spGetGroupUnits(myGroupId)
  633.     for _, unitID in ipairs(units) do   --  add the new units
  634.         if ( not myUnits[unitID] ) then
  635.             local udid = spGetUnitDefID(unitID)
  636.             local ud = UnitDefs[udid]
  637.             if (ud.isBuilder and ud.canMove) then
  638.                 myUnits[unitID] = queueType.idle
  639.             end
  640.         end
  641.     end
  642.    
  643.     for unitID,_ in pairs(myUnits) do   --  remove any old units
  644.         local isInThere = false
  645.         for _,unit2 in ipairs(units) do
  646.             if ( unitID == unit2 ) then
  647.                 isInThere = true
  648.                 break
  649.             end
  650.         end
  651.         if ( not isInThere ) then
  652.             myUnits[unitID] = nil
  653.         end
  654.     end
  655.     groupHasChanged = nil
  656. end
  657.  
  658. --This function tells us if a unit can perform the job in question.
  659. function CanBuildThis(cmdID, unitDef)
  660.     local acmd = abs(cmdID)
  661.     Spring.Echo("CanBuildThis was called.")
  662.     for _, options in ipairs(unitDef.buildOptions) do
  663.         if ( options == acmd ) then
  664.             return true
  665.         end
  666.     end
  667.     return false
  668. end
  669.  
  670. --This function process result of Spring.PathRequest() to say whether target is reachable or not
  671. function IsTargetReachable(moveID, ox,oy,oz,tx,ty,tz)
  672.     if moveID then --air units have no moveID, and we don't need to calculate pathing for them.
  673.         local result,lastcoordinate, waypoints
  674.         local path = Spring.RequestPath( moveID,ox,oy,oz,tx,ty,tz, 128)
  675.         if path then
  676.             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.
  677.             local finalCoord = waypoint[#waypoint]
  678.             if finalCoord then --unknown why sometimes NIL
  679.                 local dx, dz = finalCoord[1]-tx, finalCoord[3]-tz
  680.                 local dist = math.sqrt(dx*dx + dz*dz)
  681.                 if dist <= radius+20 then --is within radius?
  682.                     return true --within reach
  683.                 else
  684.                     return false --not within reach
  685.                 end
  686.             else
  687.                 return false --if finalCoord is nil for some reason, return false
  688.             end
  689.         else
  690.             return false --if path is nil for some reason, return false
  691.         end
  692.     else
  693.         return true --for air units; always reachable
  694.     end
  695. end
  696.  
  697. --  Borrowed distance calculation from Google Frog's Area Mex
  698. local function Distance(x1,z1,x2,z2)
  699.   local dis = sqrt((x1-x2)*(x1-x2)+(z1-z2)*(z1-z2))
  700.   return dis
  701. end
  702.  
  703. --  Borrowed this from CarRepairer's Retreat.  Returns only first command in queue.
  704. function GetFirstCommand(unitID, useCache)
  705.     if (useCache) then
  706.         if (not cachedCommand[unitID]) then
  707.             local value = spGetCommandQueue(unitID, 1)
  708.             cachedCommand[unitID] = value and value[1]
  709.         end
  710.         return cachedCommand[unitID]
  711.     end
  712.  
  713.     local queue = spGetCommandQueue(unitID, 1)
  714.     return queue and queue[1]
  715. end
  716.  
  717. --  Prevent CBAI from canceling orders that just haven't made it to host yet
  718. --  because of high ping. Donated by SkyStar.
  719. function ping()
  720.     local playerID = spGetLocalPlayerID()
  721.     local tname, _, tspec, tteam, tallyteam, tping, tcpu = spGetPlayerInfo(playerID)  
  722.     tping = (tping*1000-((tping*1000)%1)) /100 * 4
  723.     return max( tping, 15 ) --wait minimum 0.5 sec delay
  724. end
  725.  
  726. --  Generate unique key value for each command using its parameters.
  727. --  Much easier than expected once I learned Lua can use *anything* for a key.
  728. function BuildHash(myCmd)
  729.     return myCmd.id .. "@" .. myCmd.x .. "x" .. myCmd.z
  730. end
  731.  
  732. -- Tell any worker for construction of "myQueue[key]" to stop the job immediately
  733. function StopAnyWorker(key,onlyOne)
  734.     if key:sub(1,4) ~= queueType.buildQueue then --only order with SHIFT order should be stopped
  735.         return
  736.     end
  737.  
  738.     -- stop any constructor constructing this queue
  739.     local builderArray = {}
  740.     for unitID, queueKey in pairs(myUnits) do
  741.         if queueKey == key then
  742.             builderArray[#builderArray+1] = unitID
  743.             myUnits[unitID]= queueType.drec --busy until really idle.
  744.             if onlyOne then
  745.                 break
  746.             end
  747.         end
  748.     end
  749.     if #builderArray>0 then
  750.         Spring.GiveOrderArrayToUnitArray (builderArray,{{CMD_REMOVE, {myQueue[key].id}, {"alt"}},{CMD_INSERT, {0,CMD_STOP},{"alt"}}})
  751.     end
  752. end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement