-------------------------------------------------------------------------------- -------------------------------------------------------------------------------- -- -- file: cmd_auto_mine.lua -- brief: Adds '/luaui automine minename' to activate ai minelayers -- author: Owen Martindell -- -- Copyright (C) 2007. -- Licensed under the terms of the GNU GPL, v2 or later. -- -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- function widget:GetInfo() return { name = "AutoMine", desc = "Usage '/luaui automine minename' to activate ai minelayers (v0.1-ALPHA)", author = "TheFatController", date = "Jan 05, 2008", license = "GNU GPL, v2 or later", layer = 0, enabled = false -- loaded by default? } end -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- local GetMyTeamID = Spring.GetMyTeamID local GetUnitDefID = Spring.GetUnitDefID local GetUnitHeading = Spring.GetUnitHeading local GetUnitPosition = Spring.GetUnitPosition local GetUnitsInCylinder = Spring.GetUnitsInCylinder local GetUnitAllyTeam = Spring.GetUnitAllyTeam local GetMyAllyTeamID = Spring.GetMyAllyTeamID local GetPositionLosState = Spring.GetPositionLosState local GetGroundHeight = Spring.GetGroundHeight local TestBuildOrder = Spring.TestBuildOrder local GiveOrderToUnit = Spring.GiveOrderToUnit local GetSelectedUnits = Spring.GetSelectedUnits local glTranslate = gl.Translate local glColor = gl.Color local glText = gl.Text local glDepthMask = gl.DepthMask local glBillboard = gl.Billboard local glDepthTest = gl.DepthTest local glAlphaTest = gl.AlphaTest local glBlending = gl.Blending local glDrawFuncAtUnit = gl.DrawFuncAtUnit local GL_GREATER = GL.GREATER local GL_SRC_ALPHA = GL.SRC_ALPHA local GL_ONE = GL.ONE local GL_ONE_MINUS_SRC_ALPHA = GL.ONE_MINUS_SRC_ALPHA local math_cos = math.cos local math_sin = math.sin local UPDATE = 1.5 local SAFE_RADIUS = 480 -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- local myTeamID = -1 local myAllyID local timeCounter = 0 local function spot(x, z, range, safe, taken, counter) return { x = x, z = z, range = range, safe = safe, taken = taken, counter = counter } end local function minelayer(unitID, mineID, safeX, safeZ, busy) return { unitID = unitID, mineID = mineID, safeX = safeX, safeZ = safeZ, busy = busy} end local layerList = {} local spotList = {} local removeList = {} local enemyList = {} -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- function widget:Initialize() widgetHandler:AddAction("automine", MineCommand, nil, opt) end local function canBuild(builderID, unitName) local unitDefID = GetUnitDefID(builderID) or 0 local unitDef = UnitDefs[unitDefID] if unitDef then for _,defID in ipairs(unitDef.buildOptions) do if (UnitDefs[defID].name == unitName) then return defID end end end return 0 end function MineCommand(cmd, opt) local units = GetSelectedUnits() if (units == 0) then return false end table.sort(units) for _,unitID in ipairs(units) do mineID = canBuild(unitID, opt) if (mineID > 0) then local notLayer = true for i,u in ipairs(layerList) do if (u == unitID) then notLayer = false end end if notLayer then local x,_,z = GetUnitPosition(unitID) table.insert(layerList, minelayer(unitID, mineID, x, z, false, false)) end end end return true end local function getDistance(x1,z1,x2,z2) local dx,dz = x1-x2,z1-z2 return (dx*dx)+(dz*dz) end local function sendLayer(mineLayer) local unitID, buildOrder = mineLayer.unitID, (mineLayer.mineID * -1) for _,spot in pairs(spotList) do if ((spot.taken == mineLayer.unitID) and (spot.safe == 1)) then local x,y,z = spot.x, GetGroundHeight(spot.x, spot.z), spot.z GiveOrderToUnit(unitID, buildOrder, {x, y, z, 1} , {""}) local rX,rY,rZ = mineLayer.safeX, GetGroundHeight(mineLayer.safeX, mineLayer.safeZ), mineLayer.safeZ GiveOrderToUnit(unitID, CMD.MOVE, {rX, rY, rZ} , {"shift"}) mineLayer.busy = true end end end local function chooseLayer(inSpot) local layerID = nil local minDist = math.huge local validList = {} for i,layer in ipairs(layerList) do if (layer.busy == false) then local x,_,z = GetUnitPosition(layer.unitID) local dist = getDistance(inSpot.x,inSpot.z,x,z) local mID,mX,mY,mZ = layer.mineID, inSpot.x, GetGroundHeight(inSpot.x, inSpot.z), inSpot.z local canBuild = TestBuildOrder(mID,mX,mY,mZ,1) if (canBuild ~= 0) then validList[i] = true if (dist < minDist) then minDist = dist layerID = i end end end end if (layerID ~= nil) then local x,_,z = GetUnitPosition(layerList[layerID].unitID) local nearby = GetUnitsInCylinder(x, z, 400) table.sort(nearby) local chooseList = {} local i = 1 for _,unitID in pairs(nearby) do for vID in pairs(validList) do if (vID == unitID) then chooseList[i] = unitID i = i + 1 end end end local cC = table.getn(chooseList) if (cC > 1) then local cH = math.random(1,cC) layerID = chooseList[cH] end end return layerID end function isAreaSafe(x,z,area) for i=0,360,40 do local xC = (x + (math_cos(math.rad(i))*area)) local zC = (z + (math_sin(math.rad(i))*area)) local yC = GetGroundHeight(xC,zC) local visible = GetPositionLosState(xC,yC,zC) if (visible == false) then return 1 end end local enemies = GetUnitsInCylinder(x, z, (area*1.2)) table.sort(enemies) for _,unitID in ipairs(enemies) do if (GetUnitAllyTeam(unitID) ~= myAllyID) then return 2 end end return 0 end local function checkSpot(inSpot, index) if (inSpot.safe == 0) then inSpot.safe = math.floor((UPDATE * 4)) + 1 end local safeArea = isAreaSafe(inSpot.x,inSpot.z, inSpot.range) if (safeArea == 0) then if (inSpot ~= nil) then local friends = GetUnitsInCylinder(inSpot.x, inSpot.z, 200) table.sort(friends) for _,unitID in ipairs(friends) do if (GetUnitAllyTeam(unitID) == myAllyID) then local unitDefID = GetUnitDefID(unitID) local unitDef = UnitDefs[unitDefID] if (unitDef ~= nil) then if ((unitDef.speed == 0) and (unitDef.kamikazeDist == 0)) then removeList[index] = true end end end end end else if (inSpot.taken > 0) then for _,lol in ipairs(layerList) do if (lol.unitID == inSpot.taken) then lol.busy = false local rX,rY,rZ = lol.safeX, GetGroundHeight(lol.safeX, lol.safeZ), lol.safeZ GiveOrderToUnit(lol.unitID, CMD.MOVE, {rX, rY, rZ} , {}) end end inSpot.taken = 0 end inSpot.safe = 0 end if (inSpot.safe > 1) then inSpot.safe = inSpot.safe - 1 elseif (inSpot.safe == 1) then if (inSpot.counter > 150) then inSpot.counter = 150 else inSpot.counter = (inSpot.counter + 1) end end if (inSpot.safe == 0) then inSpot.counter = (inSpot.counter + 1) if (inSpot.counter > (180 / UPDATE)) then removeList[index] = true end end spotList[index] = inSpot end local function newSpot(x,z,sight) for i,s in pairs(spotList) do if ((math.abs(s.x - x) < 64) and (math.abs(s.z - z) < 64)) then return false end end table.insert(spotList, 1, spot(x,z,sight,0,0,0)) myAllyID = GetMyAllyTeamID() checkSpot(spotList[1], 1) end function widget:CommandNotify(commandID, params, options) if (next(layerList) == nil) then return false end local selUnits = GetSelectedUnits() table.sort(selUnits) for i,v in ipairs(selUnits) do for q,d in ipairs(layerList) do if (d.unitID == v) then if (commandID == CMD.STOP) then table.remove(layerList, q) else layerList[q].busy = false end end end end end function widget:UnitFinished(unitID, unitDefID, unitTeam) if (unitTeam ~= myTeamID) then return end local unitDef = UnitDefs[unitDefID] if (unitDef ~= nil) then if (unitDef.kamikazeDist > 0) then local x,_,z = GetUnitPosition(unitID) for i,s in pairs(spotList) do if ((math.abs(s.x - x) < 32) and (math.abs(s.z - z) < 32)) then for li,ll in ipairs(layerList) do if (ll.unitID == s.taken) then layerList[li].busy = false end end spotList[i] = nil end end end end end function widget:UnitDestroyed(unitID, unitDefID, unitTeam) local myTmpTeamID = GetMyTeamID() if (myTeamID == -1) then myTeamID = myTmpTeamID elseif (myTeamID ~= myTmpTeamID) then Spring.Echo("Auto Mine widget disabled due to team change, restart if required") widgetHandler:RemoveWidget() end if (unitTeam ~= myTeamID) then return end if (unitDefID ~= nil) then local unitDef = UnitDefs[unitDefID] if (unitDef ~= nil) then if (unitDef.kamikazeDist > 0) and (unitDef.speed == 0) then local x,_,z = GetUnitPosition(unitID) newSpot(x,z,SAFE_RADIUS) return end end end for i, layer in pairs(layerList) do if layer.unitID == unitID then table.remove(layerList, i) end for i,spot in pairs(spotList) do if (spot.taken == unitID) then spot.taken = 0 spot.safe = 5 end end end end function widget:UnitTaken(unitID, unitDefID, unitTeam) widget:UnitDestroyed(unitID, nil, unitTeam) end function widget:UnitEnteredRadar(unitID, unitTeam) if (unitTeam ~= myTeamID) then if (timeCounter < (UPDATE*0.5)) then timeCounter = (UPDATE*0.5) end end end function widget:UnitEnteredLos(unitID, unitTeam) local myTmpTeamID = GetMyTeamID() if (myTeamID == -1) then myTeamID = myTmpTeamID elseif (myTeamID ~= myTmpTeamID) then Spring.Echo("Auto Mine widget disabled due to team change, restart if required") widgetHandler:RemoveWidget() end if (unitTeam ~= myTeamID) then if (timeCounter < (UPDATE*0.5)) then timeCounter = (UPDATE*0.5) end table.insert(enemyList, unitID) local unitDefID = GetUnitDefID(unitID) or 0 local unitDef = UnitDefs[unitDefID] if unitDef and (unitDef.speed > 0) then local x,_,z = GetUnitPosition(unitID) newSpot(x,z,SAFE_RADIUS) end end end function widget:Update(deltaTime) if (next(layerList) == nil) then return end myAllyID = GetMyAllyTeamID() if (timeCounter > UPDATE) then for i,enemy in ipairs(enemyList) do local Ex,_,Ez = GetUnitPosition(enemy) if (Ex == nil) then table.remove(enemyList,i) else newSpot(Ex,Ez,SAFE_RADIUS) if (math.random(1,3) == 2) then table.remove(enemyList,i) end end end timeCounter = 0 removeList = {} for i,spot in pairs(spotList) do checkSpot(spot, i) if (spot.safe == 1) and (spot.taken == 0) then local layerID = chooseLayer(spot) if (layerID ~= nil) then local layer = layerList[layerID].unitID spotList[i].taken = layer for _,lol in ipairs(layerList) do if (lol.unitID == layer) and (lol.busy == false) then sendLayer(lol) break end end end end end for i,layer in ipairs(layerList) do if (layer.busy == false) then sendLayer(layer) else local Mx,_,Mz = GetUnitPosition(layer.unitID) if (isAreaSafe(Mx,Mz,SAFE_RADIUS) == 2) then layer.busy = false local rX,rY,rZ = layer.safeX, GetGroundHeight(layer.safeX, layer.safeZ), layer.safeZ GiveOrderToUnit(layer.unitID, CMD.MOVE, {rX, rY, rZ} , {}) for _,Lspot in pairs(spotList) do if (Lspot.taken == layer.unitID) then Lspot.taken = 0 Lspot.safe = 5 end end end end end for i,_ in pairs(removeList) do spotList[i] = nil end else timeCounter = (timeCounter + deltaTime) end end local function DrawUnitFunc() glTranslate(0, 2, 0) glBillboard() glColor(0.93, 0, 0) glText("M", 0, 0, 12, 'cno') end function widget:DrawWorld() if next(layerList) == nil then return end glDepthMask(true) glDepthTest(true) glAlphaTest(GL_GREATER, 0) glBlending(GL_SRC_ALPHA, GL_ONE) for i, layer in pairs(layerList) do glDrawFuncAtUnit(layer.unitID, false, DrawUnitFunc) end gl.Texture(1, false) glBlending(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) glAlphaTest(false) glDepthTest(false) glDepthMask(false) end -------------------------------------------------------------------------------- --------------------------------------------------------------------------------