function gadget:GetInfo() return { name = "NanoCommands", desc = "Improves nano layout commands.", author = "author: BigHead", date = "September 13, 2007", license = "GNU GPL, v2 or later", layer = 0, enabled = true -- loaded by default? } end local ON, OFF = 1, 0 local defaultRepair = ON local defaultReclaim = ON local defaultAssist = OFF local nanos = {} local nanoDefs = {} local addCommands = {} local CMD_AUTOREPAIR = 31250 local CMD_AUTORECLAIM = CMD_AUTOREPAIR + 1 local CMD_AUTOASSIST = CMD_AUTOREPAIR + 2 local autoRepairCmd = { action = 'repair', onTooltip = 'on', offToolip = 'off', cmdDesc = { id = CMD_AUTOREPAIR, type = CMDTYPE.ICON_MODE, name = 'Automatic repair', cursor = 'autorepair', action = 'autorepair', tooltip = '', params = { '0', 'Repair OFF', 'Repair ON'} } } local autoReclaimCmd = { action = 'reclaim', onTooltip = 'on', offToolip = 'off', cmdDesc = { id = CMD_AUTORECLAIM, type = CMDTYPE.ICON_MODE, name = 'Automatic reclaim', cursor = 'autoreclaim', action = 'autoreclaim', tooltip = '', params = { '0', 'Reclaim OFF', 'Reclaim ON'} } } local autoAssistCmd = { action = 'assist', onTooltip = 'on', offToolip = 'off', cmdDesc = { id = CMD_AUTOASSIST, type = CMDTYPE.ICON_MODE, name = 'Automatic assist', cursor = 'autoassist', action = 'autoassist', tooltip = '', params = { '0', 'Assist OFF', 'Assist ON'} } } -- speedups local EMPTYTABLE = {} local GetTeamUnits = Spring.GetTeamUnits local GetUnitDefID = Spring.GetUnitDefID local GiveOrderToUnit = Spring.GiveOrderToUnit local GetUnitPosition = Spring.GetUnitPosition local GetUnitHealth = Spring.GetUnitHealth local GetGroundHeight = Spring.GetGroundHeight local GetCommandQueue = Spring.GetCommandQueue local Echo = Spring.Echo local FindUnitCmdDesc = Spring.FindUnitCmdDesc local InsertUnitCmdDesc = Spring.InsertUnitCmdDesc local EditUnitCmdDesc = Spring.EditUnitCmdDesc local AreTeamsAllied = Spring.AreTeamsAllied local GetUnitTeam = Spring.GetUnitTeam local GetFeatureResources = Spring.GetFeatureResources local GetFeaturePosition = Spring.GetFeaturePosition local GetUnitsInCylinder = Spring.GetUnitsInCylinder local GetFeaturesInRectangle = Spring.GetFeaturesInRectangle local GetFeatureTeam = Spring.GetFeatureTeam local RemoveUnitCmdDesc = Spring.RemoveUnitCmdDesc local CMD_STOP = CMD.STOP local CMD_RECLAIM = CMD.RECLAIM local CMD_REPAIR = CMD.REPAIR function addButtons(unitID, nano) local insertID = FindUnitCmdDesc(unitID, CMD.STOP) or 1 removeButton(unitID, CMD.ATTACK) removeButton(unitID, CMD.MOVE) removeButton(unitID, CMD.FIGHT) removeButton(unitID, CMD.PATROL) removeButton(unitID, CMD.REPEAT) removeButton(unitID, CMD.MOVE_STATE) removeButton(unitID, CMD.FIRE_STATE) updateCommandDesc(unitID, autoAssistCmd, nano.assist, insertID) updateCommandDesc(unitID, autoReclaimCmd, nano.reclaim, insertID) updateCommandDesc(unitID, autoRepairCmd, nano.repair, insertID) end function updateCommandDesc(unitID, command, status, insertID) local cmdDesc = command.cmdDesc cmdDesc.params[1] = status if status == 0 then cmdDesc.tooltip = command.offTooltip else cmdDesc.tooltip = command.onTooltip end local cmdDescId = FindUnitCmdDesc(unitID, cmdDesc.id) if not cmdDescId then InsertUnitCmdDesc(unitID, insertID, cmdDesc) elseif EditUnitCmdDesc then EditUnitCmdDesc(unitID, cmdDescId, cmdDesc) end end function removeButton(unitID, ID) local cmdID = FindUnitCmdDesc(unitID, ID) if cmdID then RemoveUnitCmdDesc(unitID, cmdID) end end function gadget:Initialize() local teams = Spring.GetTeamList() for _, teamID in ipairs(teams) do for _, unitID in ipairs(GetTeamUnits(teamID)) do local unitDefID = GetUnitDefID(unitID) registerUnit(unitID, unitDefID, teamID) end end end function registerUnit(unitID, unitDefID, unitTeam) local ud = UnitDefs[unitDefID] if not (ud.builder and not ud.canMove) then return end local nanoDef = nanoDefs[unitDefID] if not nanoDef then local dims = Spring.GetUnitDefDimensions(unitDefID) nanoDef = UnitDefs[unitDefID].buildDistance + dims.radius - 5 nanoDefs[unitDefID] = nanoDef end local nano = {} nano.teamID = unitTeam nano.unitDefID = unitDefID nano.range = nanoDef nano.x, nano.y, nano.z = GetUnitPosition(unitID) nano.repair = defaultRepair nano.reclaim = defaultReclaim nano.assist = defaultAssist addButtons(unitID, nano) local health, maxHealth, paralyzeDamage, capture, build = GetUnitHealth(unitID) if build == 1 then nano.finished = true if #GetCommandQueue(unitID, 1) == 0 then nano.idle = true end else nano.finished = false end nanos[unitID] = nano end function deregisterUnit(unitID, unitDefID, unitTeam) nanos[unitID] = nil end function gadget:UnitCreated(unitID, unitDefID, unitTeam, builderID) registerUnit(unitID, unitDefID, unitTeam) end function gadget:UnitGiven(unitID, unitDefID, unitTeam, oldTeam) registerUnit(unitID, unitDefID, unitTeam) end function gadget:UnitTaken(unitID, unitDefID, unitTeam, newTeam) deregisterUnit(unitID, unitDefID, unitTeam) end function gadget:UnitDestroyed(unitID, unitDefID, unitTeam, attackerID, attackerDefID, attackerTeam) deregisterUnit(unitID, unitDefID, unitTeam) end function gadget:AllowCommand(unitID, unitDefID, teamID, cmdID, cmdParams, cmdOptions) local nano = nanos[unitID] if not nano then return true end if cmdOptions.shift and cmdID >= CMD_AUTOREPAIR and cmdID <= CMD_AUTOASSIST then updateCommand(unitID, nano, autoRepairCmd, cmdParams[1]) updateCommand(unitID, nano, autoReclaimCmd, cmdParams[1]) updateCommand(unitID, nano, autoAssistCmd, cmdParams[1]) return false elseif cmdID == CMD_AUTOREPAIR then updateCommand(unitID, nano, autoRepairCmd, cmdParams[1]) return false elseif cmdID == CMD_AUTORECLAIM then updateCommand(unitID, nano, autoReclaimCmd, cmdParams[1]) return false elseif cmdID == CMD_AUTOASSIST then updateCommand(unitID, nano, autoAssistCmd, cmdParams[1]) return false else return true end end function updateCommand(unitID, nano, cmd, newStatus) local status = nano[cmd.action] nano[cmd.action] = newStatus updateCommandDesc(unitID, cmd, newStatus) if not nano.finished or newStatus == status then return end local currentAction = getNanoAction(unitID) if newStatus == ON then if currentAction == "idle" then doSomething(unitID, nano) end else if currentAction == cmd.action then if not doSomething(unitID, nano) then addCommands[unitID] = {cmd = CMD_STOP, params = EMPTYTABLE} end end end end function getNanoAction(unitID) local commands = GetCommandQueue(unitID, 1) if #commands == 0 then return "idle" end local cmd = commands[1] if cmd.id == CMD_RECLAIM then return "reclaim" elseif cmd.id == CMD_REPAIR then local repairID = cmd.params[1] local health, maxHealth, paralyzeDamage, capture, build = GetUnitHealth(repairID) if build < 1 then return "assist" else return "repair" end end return "other" end function gadget:UnitIdle(unitID, unitDefID, unitTeam) local nano = nanos[unitID] if nano then doSomething(unitID, nano) end end function gadget:UnitFinished(unitID, unitDefID, unitTeam) local nano = nanos[unitID] if nano then nano.finished = true doSomething(unitID, nano) end end function doSomething(nanoID, nano) nano.idle = false -- speedups local repair = nano.repair == ON local assist = nano.assist == ON local nano_x = nano.x local nano_z = nano.z local nano_range = nano.range local nano_teamID = nano.teamID local bestBuildTime = nil local bestBuildUnitID = nil local bestHealth = nil local bestHealthUnitID = nil if repair or assist then local units = GetUnitsInCylinder(nano_x, nano_z, nano_range) for _, unitID in ipairs(units) do if unitID ~= nanoID and AreTeamsAllied(nano_teamID, GetUnitTeam(unitID)) then local health, maxHealth, paralyzeDamage, capture, build = GetUnitHealth(unitID) if assist and build < 1 then local buildTime = (1 - build) * UnitDefs[GetUnitDefID(unitID)].buildTime if not bestBuildTime or buildTime < bestBuildTime then bestBuildTime = buildTime bestBuildUnitID = unitID end end if repair and build == 1 and health < maxHealth then local percentualHealth = health / maxHealth if not bestHealth or percentualHealth < bestHealth then bestHealth = percentualHealth bestHealthUnitID = unitID end end end end end local bestFeatureMetal = nil local bestFeatureEnergy = nil local bestFeatureID = nil if nano.reclaim == ON then local features = GetFeaturesInRectangle(nano_x - nano_range, nano_z - nano_range, nano_x + nano_range, nano_z + nano_range) for _, featureID in ipairs(features) do local metal, _, energy = GetFeatureResources(featureID) if (metal > 0 or energy >0) and (not bestFeatureID or (metal > bestFeatureMetal or (metal == bestFeatureMetal and energy > bestFeatureEnergy))) then if not AreTeamsAllied(nano_teamID, GetFeatureTeam(featureID)) then local x, y, z = GetFeaturePosition(featureID) if getDistance(nano_x, nano_z, x, z) < nano_range then bestFeatureMetal = metal bestFeatureEnergy = energy bestFeatureID = featureID end end end end end if nano.repair and bestHealthUnitID then addCommands[nanoID] = {cmd = CMD_REPAIR, params = {bestHealthUnitID}} elseif nano.reclaim and bestFeatureID then addCommands[nanoID] = {cmd = CMD_RECLAIM, params = {5000 + bestFeatureID}} elseif nano.assist and bestBuildUnitID then addCommands[nanoID] = {cmd = CMD_REPAIR, params = {bestBuildUnitID}} else nano.idle = true return false end return true end function getDistance(x1, y1, x2, y2) return math.sqrt((x1-x2)^2 + (y1-y2)^2) end function gadget:GameFrame(n) if math.mod(n, 10) < 1 then for unitID, nano in pairs(nanos) do if nano.idle then doSomething(unitID, nano) elseif nano.finished then local commands = GetCommandQueue(unitID, 1) if #commands > 0 then local cmd = commands[1] if cmd.id == CMD_REPAIR then local targetID = cmd.params[1] local x, y, z = GetUnitPosition(targetID) if getDistance(x, z, nano.x, nano.z) > nano.range then doSomething(unitID, nano) end end elseif not addCommands[unitID] then Echo("Wrong wrong wrong") end end end end for unitID, data in pairs(addCommands) do GiveOrderToUnit(unitID, data.cmd, data.params, EMPTYTABLE) end addCommands = {} end