Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- --------------------------------------------------------------------------------
- --------------------------------------------------------------------------------
- --
- -- file: central_build_AI.lua
- -- brief: Replacement for Central Build Group AI
- -- author: Troy H. Cheek
- --
- -- Copyright (C) 2009.
- -- Licensed under the terms of the GNU GPL, v2 or later.
- --
- --------------------------------------------------------------------------------
- --------------------------------------------------------------------------------
- -- CAUTION! CAUTION! CAUTION!
- -- Please avoid accidental removal of desireable behaviour! so
- -- only regular user of CBAI should make changes/clean-up.
- -- (due to undocumented use-case & apparent complexity of this widget)
- local version = "v1.356"
- function widget:GetInfo()
- return {
- name = "Central Build AI",
- desc = version.. " Common non-hierarchical permanent build queue\n\nInstruction: add constructor(s) to group 0 "..
- "(\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), "..
- "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",
- author = "Troy H. Cheek, modified by msafwan",
- date = "July 20, 2009, 8 March 2014",
- license = "GNU GPL, v2 or later",
- layer = 10,
- enabled = false -- loaded by default?
- }
- end
- -- Central Build AI creates a common build order queue for all units in the
- -- group. Select this group (or any member of it) and issue build orders
- -- while holding down the shift key to add orders to the common queue.
- -- Idle builders in the group will automatically move to carry out orders
- -- or assist other builders. Issue same build order again to cancel.
- -- Orders issued without shift will be carried out immediately.
- -- optional ToDo:
- -- _ orders with shift+ctrl have higher priority ( insert in queue -> cons won't interrupt their current actions)
- ---- CHANGELOG -----
- -- msafwan(xponen) v1.355 (26Jan2015) : 1) all builder re-assign job every 4 second (even if already assigned a job)
- -- 2) keep queue for unfinished building
- -- 3) lower priority (and/or removal) for queue at enemy infested area
- --
- -- msafwan, v1.21 (7oct2012) : fix some cases where unit become 'idle' but failed to be registered by CBA,
- -- make CBA assign all job at once rather than sending 1 by 1 after every some gameframe delay,
- -- msafwan, v1.2 (4sept2012) : made it work with ZK "cmd_mex_placement.lua" mex queue,
- -- reduce the tendency to make a huge blob of constructor (where all constructor do same job),
- -- reduce chance of some constructor not given job when player have alot of constructor,
- -- rafal, v1.1 (2May2012) : Don't fetch full Spring.GetCommandQueue in cases when only the first command is needed - instead using
- -- GetCommandQueue(unitID, 1)
- -- KingRaptor, v1.1 (24dec2011) : Removed the "remove in 85.0" stuff
- -- versus666, v1.1 (16dec2011) : mostly changed the layer order to get a logical priority among widgets.
- -- KingRaptor, v1.1 (8dec2011) : Fixed the remaining unitdef tags for 85.0
- -- versus666, v1.1 (7jan2011) : Made CBA, cmd_retreat, gui_nuke_button, gui_team_platter.lua, unit_auto_group to obey F5 (gui hidden).
- -- KingRaptor, v1.1 (2Nov2010) : Moved version number from name to description.
- -- lccquantum, v1.1 (2Nov2010) : central_build_AI is disabled by default (people will wonder why their builders are acting wierd when in group 0)
- -- versus666, v1.1 (1Nov2010) : introduced into ZK
- --------------------------------------------------------------------------------
- --------------------------------------------------------------------------------
- -- Declarations ----------------------------------------------------------------
- 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).
- local hotkey = string.byte( "g" ) --//Constant: a custom hotkey to add unit to custom group. NOTE: set myGroupId to "-1" to use this hotkey.
- local Echo = Spring.Echo
- local spGetUnitDefID = Spring.GetUnitDefID
- local spGetGroupList = Spring.GetGroupList
- local spGetGroupUnits = Spring.GetGroupUnits
- local spGetSelectedUnits = Spring.GetSelectedUnits
- local spIsUnitInView = Spring.IsUnitInView
- local spIsAABBInView = Spring.IsAABBInView
- local spGetUnitsInCylinder = Spring.GetUnitsInCylinder
- local spGetUnitViewPosition = Spring.GetUnitViewPosition
- local spGetCommandQueue = Spring.GetCommandQueue
- local spGetUnitPosition = Spring.GetUnitPosition
- local spGetUnitHealth = Spring.GetUnitHealth
- local spGiveOrderToUnit = Spring.GiveOrderToUnit
- local spGetMyPlayerID = Spring.GetMyPlayerID
- local spGetMyTeamID = Spring.GetMyTeamID
- local spGetFeatureTeam = Spring.GetFeatureTeam
- local spGetLocalPlayerID = Spring.GetLocalPlayerID
- local spGetPlayerInfo = Spring.GetPlayerInfo
- local spGetSpectatingState = Spring.GetSpectatingState
- local spGetModKeyState = Spring.GetModKeyState
- local spTestBuildOrder = Spring.TestBuildOrder
- local spSelectUnitMap = Spring.SelectUnitMap
- local spGetUnitsInCylinder = Spring.GetUnitsInCylinder
- local spGetUnitsInRectangle = Spring.GetUnitsInRectangle
- local spGetUnitAllyTeam = Spring.GetUnitAllyTeam
- local spGetUnitIsStunned = Spring.GetUnitIsStunned
- local glPushMatrix = gl.PushMatrix
- local glPopMatrix = gl.PopMatrix
- local glTranslate = gl.Translate
- local glBillboard = gl.Billboard
- local glColor = gl.Color
- local glText = gl.Text
- local glBeginEnd = gl.BeginEnd
- local GL_LINE_STRIP = GL.LINE_STRIP
- local glDepthTest = gl.DepthTest
- local glRotate = gl.Rotate
- local glUnitShape = gl.UnitShape
- local glVertex = gl.Vertex
- local CMD_WAIT = CMD.WAIT
- local CMD_MOVE = CMD.MOVE
- local CMD_PATROL = CMD.PATROL
- local CMD_REPAIR = CMD.REPAIR
- local CMD_INSERT = CMD.INSERT
- local CMD_REMOVE = CMD.REMOVE
- local CMD_RECLAIM = CMD.RECLAIM
- local CMD_GUARD = CMD.GUARD
- local CMD_STOP = CMD.STOP
- local abs = math.abs
- local floor = math.floor
- local huge = math.huge
- local sqrt = math.sqrt
- local max = math.max
- local currentFrame = Spring.GetGameFrame()
- local nextFrame = currentFrame +30
- local nextPathCheck = currentFrame + 400 --is used to check whether constructor can go to construction site
- local myAllyID = Spring.GetMyAllyTeamID()
- local textColor = {0.7, 1.0, 0.7, 1.0}
- local textSize = 12.0
- -- "global" for this widget. This is probably not a recommended practice.
- local myUnits = {} -- list of units in the Central Build group
- local myQueue = {} -- list of commands for Central Build group
- local groupHasChanged -- Flag if group members have changed.
- local myQueueUnreachable = {} -- list of queue which units can't reach
- local myQueueDanger = {} --list of queue which lead to dead constructors
- local cachedValue = {} --cached results for "EnemyControlBuildSite()" function to reduce cost for repeated call
- local cachedMetalCost = {} --cached metalcost for "GetWorkFor()" function
- local cachedCommand = {} --cached first command for "GetWorkFor{}" function
- local reassignedUnits = {} --list of units that had its task re-checked (to be re-tasked or remained with current task).
- --------------------------------------------
- --List of prefix used as value for myUnits[]
- local queueType = {
- drec = 'drec', --appended with: cmdId .. "@" .. x .. "x" .. z. Indicate direct orders from the user
- 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
- idle = 'idle',
- }
- --------------------------------------------------------------------------------
- --------------------------------------------------------------------------------
- -- Event Handlers --------------------------------------------------------------
- -- Borrowed from xyz's Action Finder and very_bad_soldier's Ghost Radar
- function widget:Initialize()
- if spGetSpectatingState() then
- Echo( "<Central Build AI>: Spectator mode. Widget removed." )
- widgetHandler:RemoveWidget()
- return
- end
- 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"
- end
- -- Paint 'cb' tags on units, draw ghosts of items in central build queue.
- -- Text stuff mostly borrowed from gunblob's Group Label and trepan/JK's BuildETA.
- -- Ghost stuff borrowed from very_bad_soldier's Ghost Radar.
- function widget:DrawWorld()
- if not Spring.IsGUIHidden() then
- local alt, ctrl, meta, shift = spGetModKeyState()
- for unitID,myCmd in pairs(myUnits) do -- show user which units are in CB
- if spIsUnitInView(unitID) then
- local ux, uy, uz = spGetUnitViewPosition(unitID)
- glPushMatrix()
- glTranslate(ux, uy, uz)
- glBillboard()
- glColor(textColor)
- glText(myCmd:sub(1,4), -10.0, -15.0, textSize, "con")
- glPopMatrix()
- glColor(1, 1, 1, 1)
- end -- if InView
- end -- for unitID in group
- for key, myCmd in pairs(myQueue) do -- show items in build queue
- local cmd = myCmd.id
- cmd = abs( cmd )
- local x, y, z, h = myCmd.x, myCmd.y, myCmd.z, myCmd.h
- local degrees = h * 90
- if spIsAABBInView(x-1,y-1,z-1,x+1,y+1,z+1) then
- if ( shift ) then
- glColor(0.0, 1.0, 0.0, 1 )
- glBeginEnd(GL_LINE_STRIP, DrawOutline, cmd, x, y, z, h)
- end
- glColor(1.0, 1.0, 1.0, 0.35 ) -- ghost value 0.35
- glDepthTest(true)
- glPushMatrix()
- glTranslate( x, y, z )
- glRotate( degrees, 0, 1.0, 0 )
- glUnitShape( cmd, spGetMyTeamID() )
- glRotate( degrees, 0, -1.0, 0 )
- glBillboard() -- also show some debug stuff
- glColor(textColor)
- glText(cmd, -10.0, -15.0, textSize, "con")
- glPopMatrix()
- glDepthTest(false)
- glColor(1, 1, 1, 1)
- end -- if inview
- end
- end
- end
- function DrawOutline(cmd,x,y,z,h)
- local ud = UnitDefs[cmd]
- local baseX = ud.xsize * 4 -- ud.buildingDecalSizeX
- local baseZ = ud.zsize * 4 -- ud.buildingDecalSizeY
- if (h == 1 or h==3) then
- baseX,baseZ = baseZ,baseX
- end
- glVertex(x-baseX,y,z-baseZ)
- glVertex(x-baseX,y,z+baseZ)
- glVertex(x+baseX,y,z+baseZ)
- glVertex(x+baseX,y,z-baseZ)
- glVertex(x-baseX,y,z-baseZ)
- end
- -- Stuff which needs to run regularly but isn't covered elsewhere.
- -- Was Update(), but Niobium says GameFrame() is more better.
- function widget:GameFrame(thisFrame)
- currentFrame = thisFrame
- if ( thisFrame < nextFrame ) then
- return
- end
- if ( groupHasChanged == true ) then
- UpdateOneGroupsDetails(myGroupId)
- end
- FindEligibleWorker() -- compile list of eligible units and assign them jobs.
- nextFrame = thisFrame + 60 -- try again in 2 second if nothing else triggers
- end
- -- This function detects that a new group has been defined or changed. Use it to set a flag
- -- because it fires before all units it's going to put into group have actually been put in.
- -- Borrowed from gunblob's UnitGroups v5.1
- function widget:GroupChanged(groupId)
- if groupId == myGroupId then
- groupHasChanged = true
- nextFrame = currentFrame + ping()
- end
- end
- -- A compatibility function: receive broadcasted event from "cmd_mex_placement.lua" (ZK specific) which notify us that it has its own mex queue
- function CommandNotifyMex(id,params,options, isAreaMex)
- local groundHeight = Spring.GetGroundHeight(params[1],params[3])
- params[2] = math.max(0, groundHeight)
- local returnValue = widget:CommandNotify(id, params, options, true,isAreaMex)
- return returnValue
- end
- -- If the command is issued to something in our group, flag it.
- -- Thanks to Niobium for pointing out CommandNotify().
- function widget:CommandNotify(id, params, options, isZkMex,isAreaMex)
- 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).
- return
- end
- if options.meta then --skip special insert command (spacebar). Handled by CommandInsert() widget
- return
- end
- local selectedUnits = spGetSelectedUnits()
- for _, unitID in pairs(selectedUnits) do -- check selected units...
- if ( myUnits[unitID] ) then -- was issued to one of our units.
- if ( id < 0 ) then --if the order is for building something
- local x, y, z, h = params[1], params[2], params[3], params[4]
- local myCmd = { id=id, x=x, y=y, z=z, h=h }
- local isOverlap = CleanOrders(myCmd) -- check if current queue overlap with existing queue, and clear up any invalid queue
- if not isOverlap then
- local hash = queueType.buildQueue .. BuildHash(myCmd)
- myQueue[hash] = myCmd -- add to CB queue
- end
- nextFrame = currentFrame + 30 --wait 1 more second before distribute work, so user can queue more stuff
- if ( options.shift ) then -- if the command was given with shift
- return true -- we return true to take ownership of the command from Spring.
- 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
- -- note we still add the command to the queue so that other units can see the job and assist independently
- end
- else -- if the order is not for building something
- myUnits[unitID] = queueType.drec -- then the unit is marked as under user direction and we let spring handle it.
- end
- end
- -- do NOT return here because there may be more units.
- end
- end
- --If one of our units finished a build order, cancel units guarding/assisting it.
- --This replace UnitCmdDone() because UnitCmdDone() is called even if command is not finished, such as when new command is inserted into existing queue
- --Credit to Niobium for pointing out UnitCmdDone() originally.
- function widget:UnitFinished(unitID, unitDefID, unitTeam)
- local ux, _, uz = spGetUnitPosition(unitID)
- local myCmd = { id=-unitDefID, x=ux, z=uz, }
- local hash = queueType.buildQueue .. BuildHash(myCmd)
- StopAnyWorker(hash) --stop any other workers working on the job that finished
- myQueue[hash] = nil --remove the job from the build queue
- nextFrame = currentFrame + ping() --find new work
- end
- -- If unit detected as idle (probably finished work) and it's one of ours, time to find it some work.
- function widget:UnitIdle(unitID, unitDefID, teamID)
- if ( myUnits[unitID] ) then
- myUnits[unitID] = queueType.idle
- nextFrame = currentFrame + ping() --find new work
- end
- end
- -- Detect when player enters spectator mode (thanks to SeanHeron).
- function widget:PlayerChanged(playerID)
- if spGetSpectatingState() then
- -- Echo( "<Central Build> Spectator mode. Widget removed." )
- widgetHandler:RemoveWidget()
- return
- end
- end
- function widget:KeyPress(key, mods, isRepeat)
- --Spring.Echo("<central_build_AI.lua DEBUG>: (KeyPress): ".. key)
- if ( myGroupId > -1 ) then return end
- if ( key ~= hotkey ) then return end
- if ( mods.ctrl ) then -- ctrl means add selected units to group
- --Spring.Echo("<central_build_AI.lua DEBUG>: (KeyPress): CTRL")
- local units = spGetSelectedUnits()
- for _, unitID in ipairs(units) do -- add the new units
- if ( not myUnits[unitID] ) then
- local udid = spGetUnitDefID(unitID)
- local ud = UnitDefs[udid]
- if (ud.isBuilder and ud.canMove) then
- myUnits[unitID] = queueType.idle
- end
- end
- end
- for unitID,_ in pairs(myUnits) do -- remove any old units
- local isInThere = false
- for _,unit2 in ipairs(units) do
- if ( unitID == unit2 ) then
- isInThere = true
- break
- end
- end
- if ( not isInThere ) then
- myUnits[unitID] = nil
- end
- end
- elseif ( mods.shift ) then -- add group to selected units
- --Spring.Echo("<central_build_AI.lua DEBUG>: (KeyPress): shift")
- spSelectUnitMap(myUnits,true)
- else -- select our group or center our group if already selected.
- local myUnitsCount = 0
- for unitID,_ in pairs(myUnits) do
- myUnitsCount = myUnitsCount + 1
- end
- if ( myUnitsCount < 1 ) then return end
- local isInThere = true
- local units = spGetSelectedUnits()
- if ( # units ~= myUnitsCount ) then
- isInThere = false
- end
- for _, unitID in ipairs(units) do
- if ( not myUnits[unitID] ) then
- isInThere = false
- break
- end
- end
- if ( isInThere ) then -- center screen on our group
- local xc,yc,zc = 0,0,0 -- composite mappos
- for unitID,_ in pairs(myUnits) do
- local x, y, z = spGetUnitPosition(unitID)
- xc,yc,zc = xc+x,yc+y,zc+z
- end
- xc,yc,zc = xc/myUnitsCount,yc/myUnitsCount,zc/myUnitsCount
- Spring.SetCameraTarget(xc, yc, zc)
- end
- spSelectUnitMap(myUnits)
- end
- end
- -- Function to help clear ALL existing command instantenously
- function widget:TextCommand(command)
- if command == "cba" then
- for key,myCmd in pairs(myQueue) do
- myQueue[key]=nil
- end
- Spring.Echo("cba's command queue cleared")
- return true
- end
- return false
- end
- --------------------------------------------------------------------------------
- --------------------------------------------------------------------------------
- -- Core Logic ------------------------------------------------------------------
- --select 10 worker from a pool of idle constructor or reuse some of the non-idle constructor as new worker
- function FindEligibleWorker()
- local unitToWork = {}
- 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)
- if myCmd == queueType.idle then --if unit is marked as idle, then double check it
- local cmd1 = GetFirstCommand(unitID,true)
- if ( cmd1 and cmd1.id) then
- myUnits[unitID] = queueType.drec
- else
- if #unitToWork < 10 then
- --NOTE: only allow up to 10 idler to be processed to prevent super lag.
- --The amount of external loop will be (numberOfmyunit^2+numberOfQueue)*numberOfIdle^2.
- --ie: if all variable were 50, then the amount of loop in total is 6375000 loop (6 million).
- unitToWork[#unitToWork+1] = unitID
- end
- end
- end
- end
- --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.
- if #unitToWork < 10 then
- local gaveWork = false
- for unitID,myCmd in pairs(myUnits) do
- if (not reassignedUnits[unitID]) then
- local workType = myCmd:sub(1,4)
- if workType == queueType.buildQueue then
- unitToWork[#unitToWork+1] = unitID
- gaveWork = true
- reassignedUnits[unitID] = true
- if (#unitToWork == 10) then
- break
- end
- end
- end
- end
- if not gaveWork then --no more unit to be reassigned? then reset list
- reassignedUnits = {}
- end
- end
- CleanOrders() -- check build site(s) for blockage and handle accordingly.
- if (#unitToWork > 0) then
- GiveWorkToUnits(unitToWork)
- end
- cachedCommand = {}
- end
- -- This function finds work for all the workers compiled in our eligible worker list and issues the orders.
- function GiveWorkToUnits(unitToWork)
- if myQueue then -- if there is work on the queue
- for unitID,_ in pairs(unitToWork) do
- local myJob = FindCheapestJob(unitID) -- find the cheapest job
- if myJob then -- if myJob returns a job rather than nil
- spGiveOrderToUnit(unitID, myJob, true) -- issue the cheapest job as an order to the unit
- myUnits[unitID] = queueType.buildQueue -- and mark the unit as under CB control
- Spring.Echo("Assigned a job.")
- else
- myUnits[unitID] = queueType.idle -- otherwise if no valid job is found mark it as idle
- Spring.Echo("No valid jobs.")
- end
- end
- else
- myUnits[unitID] = queueType.idle -- or if there are no jobs on the queue, mark as idle
- Spring.Echo("Empty Queue.")
- end
- end
- -- This function returns the cheapest job for a given worker, given the cost model implemented in CostOfJob().
- function FindCheapestJob(unitID)
- local cachedJob = nil -- the cheapest job that we've seen
- local cachedCost = 0 -- the cost of the currently cached cheapest job
- local ux, uy, uz = spGetUnitPosition(unitID) -- unit location
- local udid = spGetUnitDefID(unitID)
- local moveID = UnitDefs[udid].moveDef.id -- unit pathing type
- for index,tmpJob in pairs(myQueue) do -- here we compare our unit to each job in the queue
- local cmd, jx, jy, jz = tmpJob.id, tmpJob.x, tmpJob.y, tmpJob.z --the location of the current job
- local udef = UnitDefs[udid]
- if CanBuildThis(cmd, udef) then -- if our worker can perform the job
- Spring.Echo("Job is buildable.")
- if IsTargetReachable(moveID, ux, uy, uz, jx, jy, jz) then -- and can also reach the job site
- Spring.Echo("Job is reachable.")
- local tmpCost = CostOfJob(unitID, tmpJob, index) -- calculate the job cost
- 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
- cachedJob = tmpJob
- cachedCost = tmpCost
- Spring.Echo("Cached a job.")
- end
- end
- end
- end
- return cachedJob -- after iterating over the entire queue, the resulting cached job will be the cheapest, return it.
- end
- -- This function implements the cost model by which jobs are assigned.
- function CostOfJob(unitID, job, qIndex)
- local ux, uy, uz = spGetUnitPosition(unitID) -- the location of our unit
- local jx, jy, jz = tmpJob.x, tmpJob.y, tmpJob.z -- the location of the job
- local distance = Distance(ux, uz, jx, jz) -- the distance between our worker and job
- local costMod = 1 -- our cost modifier
- for unit,cmd in pairs(myUnits) do -- find the number of other workers besides our current worker assigned to the job already
- if ( unitID ~= unit and job == cmd) then
- costMod = costMod + 1 -- and add that number to costMod
- end
- end
- costModEcho = tostring(costMod)
- Spring.Echo("costMod= " .. costModEcho)
- local cost = 0
- if costMod == 1 then
- cost = distance -- if no other workers are assigned to the job, cost equals distance
- else
- 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.
- -- this is to minimize constructors mobbing on jobs when they could build faster by splitting up; ie to reduce walking time.
- end
- return cost
- end
- -- This function removes duplicate orders, processes cancel requests, and handles blocked builds.
- function CleanOrders(newCmd)
- local xSize = nil --variables for checking queue overlaping
- local zSize = nil
- local xSize_queue = nil
- local zSize_queue = nil
- local x_newCmd = nil
- local z_newCmd = nil
- local isOverlap = nil
- if newCmd then --check the size of the new queue
- local newCmdID = abs ( newCmd.id )
- x_newCmd = newCmd.x
- z_newCmd = newCmd.z
- if x_newCmd then
- if newCmd.h == 0 or newCmd.h == 2 then --get building facing. Reference: unit_prevent_lab_hax.lua by googlefrog
- xSize = UnitDefs[newCmdID].xsize*4
- zSize = UnitDefs[newCmdID].zsize*4
- else
- xSize = UnitDefs[newCmdID].zsize*4
- zSize = UnitDefs[newCmdID].xsize*4
- end
- end
- end
- local blockageType = {
- obstructed = 0, --also applies to blocked by another structure
- mobiles = 1,
- free = 2,
- }
- for key,myCmd in pairs(myQueue) do
- local cmdID = abs( myCmd.id )
- local x, y, z, facing = myCmd.x, myCmd.y, myCmd.z, myCmd.h
- local canBuildThisThere,featureID = spTestBuildOrder(cmdID,x,y,z,facing) --check if build site is blocked by buildings & terrain
- local newFree = (newCmd and xSize and canBuildThisThere ~= blockageType.obstructed)
- local blocked = (canBuildThisThere == blockageType.mobiles or canBuildThisThere == blockageType.obstructed)
- local structureBlockage = false
- local overlappingQueue = false
- local featureBlockage = false
- local isNanoframe = false
- if blocked or newFree then
- if facing == 0 or facing == 2 then --check the size of the queued building
- xSize_queue = UnitDefs[cmdID].xsize*4
- zSize_queue = UnitDefs[cmdID].zsize*4
- else
- xSize_queue = UnitDefs[cmdID].zsize*4
- zSize_queue = UnitDefs[cmdID].xsize*4
- end
- end
- if blocked then
- local blockingUnits = spGetUnitsInRectangle(x-xSize_queue, z-zSize_queue, x+xSize_queue, z+zSize_queue)
- for i=1, #blockingUnits do
- local blockerDefID = spGetUnitDefID(blockingUnits[i])
- if blockerDefID == cmdID then
- local _,_,nanoframe = spGetUnitIsStunned(blockingUnits[i])
- if not nanoframe then
- structureBlockage= true --unit that we wanted to build is already being build, cancel this queue
- else
- isNanoframe = true
- end
- break;
- elseif math.modf(UnitDefs[blockerDefID].speed*10) == 0 then -- immobile unit is blocking. ie: modf(0.01*10) == 00 (fractional speed is ignored)
- structureBlockage= true --blocking unit can't move away, cancel this queue
- break;
- end
- end
- end
- if newFree then --check if build site overlap new queue
- local minTolerance = xSize_queue + xSize --check minimum tolerance in x direction
- local axisDist = abs (x - x_newCmd) --check actual separation in x direction
- if axisDist < minTolerance then --if too close in x direction
- minTolerance = zSize_queue + zSize --check minimum tolerance in z direction
- axisDist = abs (z - z_newCmd) -- check actual separation in z direction
- if axisDist < minTolerance then --if too close in z direction
- overlappingQueue = true --flag this queue for removal
- isOverlap = true --return true
- 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
- end
- end
- end
- if (canBuildThere==blockageType.obstructed and not structureBlockage) --terrain issue
- or (structureBlockage) --queue on existing building (not nanoframe of intended building)
- or (overlappingQueue) -- queue on another queue
- then --if queue is flagged for removal
- myQueue[key] = nil --remove queue
- elseif isNanoframe then
- myQueue[key].isStarted = true
- end
- end
- return isOverlap --return a value for "widget:CommandNotify()" to handle user's command.
- end
- --------------------------------------------------------------------------------
- --------------------------------------------------------------------------------
- -- Helper Functions ------------------------------------------------------------
- -- This function actually updates the list of builders in the CB group (myGroup).
- -- Also borrowed from gunblob's UnitGroups v5.1
- function UpdateOneGroupsDetails(myGroupId)
- local units = spGetGroupUnits(myGroupId)
- for _, unitID in ipairs(units) do -- add the new units
- if ( not myUnits[unitID] ) then
- local udid = spGetUnitDefID(unitID)
- local ud = UnitDefs[udid]
- if (ud.isBuilder and ud.canMove) then
- myUnits[unitID] = queueType.idle
- end
- end
- end
- for unitID,_ in pairs(myUnits) do -- remove any old units
- local isInThere = false
- for _,unit2 in ipairs(units) do
- if ( unitID == unit2 ) then
- isInThere = true
- break
- end
- end
- if ( not isInThere ) then
- myUnits[unitID] = nil
- end
- end
- groupHasChanged = nil
- end
- --This function tells us if a unit can perform the job in question.
- function CanBuildThis(cmdID, unitDef)
- local acmd = abs(cmdID)
- Spring.Echo("CanBuildThis was called.")
- for _, options in ipairs(unitDef.buildOptions) do
- if ( options == acmd ) then
- return true
- end
- end
- return false
- end
- --This function process result of Spring.PathRequest() to say whether target is reachable or not
- function IsTargetReachable(moveID, ox,oy,oz,tx,ty,tz)
- if moveID then --air units have no moveID, and we don't need to calculate pathing for them.
- local result,lastcoordinate, waypoints
- local path = Spring.RequestPath( moveID,ox,oy,oz,tx,ty,tz, 128)
- if path then
- 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.
- local finalCoord = waypoint[#waypoint]
- if finalCoord then --unknown why sometimes NIL
- local dx, dz = finalCoord[1]-tx, finalCoord[3]-tz
- local dist = math.sqrt(dx*dx + dz*dz)
- if dist <= radius+20 then --is within radius?
- return true --within reach
- else
- return false --not within reach
- end
- else
- return false --if finalCoord is nil for some reason, return false
- end
- else
- return false --if path is nil for some reason, return false
- end
- else
- return true --for air units; always reachable
- end
- end
- -- Borrowed distance calculation from Google Frog's Area Mex
- local function Distance(x1,z1,x2,z2)
- local dis = sqrt((x1-x2)*(x1-x2)+(z1-z2)*(z1-z2))
- return dis
- end
- -- Borrowed this from CarRepairer's Retreat. Returns only first command in queue.
- function GetFirstCommand(unitID, useCache)
- if (useCache) then
- if (not cachedCommand[unitID]) then
- local value = spGetCommandQueue(unitID, 1)
- cachedCommand[unitID] = value and value[1]
- end
- return cachedCommand[unitID]
- end
- local queue = spGetCommandQueue(unitID, 1)
- return queue and queue[1]
- end
- -- Prevent CBAI from canceling orders that just haven't made it to host yet
- -- because of high ping. Donated by SkyStar.
- function ping()
- local playerID = spGetLocalPlayerID()
- local tname, _, tspec, tteam, tallyteam, tping, tcpu = spGetPlayerInfo(playerID)
- tping = (tping*1000-((tping*1000)%1)) /100 * 4
- return max( tping, 15 ) --wait minimum 0.5 sec delay
- end
- -- Generate unique key value for each command using its parameters.
- -- Much easier than expected once I learned Lua can use *anything* for a key.
- function BuildHash(myCmd)
- return myCmd.id .. "@" .. myCmd.x .. "x" .. myCmd.z
- end
- -- Tell any worker for construction of "myQueue[key]" to stop the job immediately
- function StopAnyWorker(key,onlyOne)
- if key:sub(1,4) ~= queueType.buildQueue then --only order with SHIFT order should be stopped
- return
- end
- -- stop any constructor constructing this queue
- local builderArray = {}
- for unitID, queueKey in pairs(myUnits) do
- if queueKey == key then
- builderArray[#builderArray+1] = unitID
- myUnits[unitID]= queueType.drec --busy until really idle.
- if onlyOne then
- break
- end
- end
- end
- if #builderArray>0 then
- Spring.GiveOrderArrayToUnitArray (builderArray,{{CMD_REMOVE, {myQueue[key].id}, {"alt"}},{CMD_INSERT, {0,CMD_STOP},{"alt"}}})
- end
- end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement